/* css/theme.css */
/* theme.css — Classic 2 locked portal theme. */

body{transition:background .2s ease,color .2s ease}

/* ---- 1. Classic: clean portal ---- */
html[data-design="classic"]{
  --ui-font:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --bg:#f4f6fb; --panel:#fff; --panel2:#f7f9fc; --ink:#1b2230; --dim:#697186; --line:#e4e8f0;
  --accent:#5b53e6; --accent2:#7c74ff; --accent-soft:#eceaff; --ok:#16a34a; --warn:#d97706; --err:#dc2626;
  --sidebar:#171a2b; --sidebar-ink:#c7ccdd; --sidebar-soft:#262b44; --shadow:0 8px 30px #1b22301a; --radius:14px;
}
html[data-design="classic"] body{font-family:var(--ui-font);background:var(--bg);color:var(--ink)}

/* ---- 2. Journal: Fable-style field notes ---- */
html[data-design="journal"]{
  --ui-font:"Iowan Old Style",Palatino,"Palatino Linotype",Georgia,serif;
  --bg:#f7f3ea; --panel:#fffdf8; --panel2:#f1eadc; --ink:#211d17; --dim:#645b50; --line:#d8cfc0;
  --accent:#b3402a; --accent2:#27566e; --accent-soft:#f6e6dc; --ok:#3c6e47; --warn:#a8751f; --err:#b3402a;
  --sidebar:#2b241c; --sidebar-ink:#eadfce; --sidebar-soft:#44382b; --shadow:0 10px 28px rgba(80,60,30,.14); --radius:6px;
}
html[data-design="journal"] body{
  font-family:var(--ui-font);
  background:
    radial-gradient(900px 420px at 105% -10%,#efe4cf 0%,transparent 60%),
    radial-gradient(760px 500px at -10% 105%,#e8e0cf 0%,transparent 55%),
    var(--bg);
}
html[data-design="journal"] .sidebar{border-right:3px double var(--ink);padding-top:20px}
html[data-design="journal"] .brand{font-variant:small-caps;letter-spacing:.4px}
html[data-design="journal"] .logo{background:transparent;color:var(--accent);font-size:28px}
html[data-design="journal"] .navlink{border-radius:4px}
html[data-design="journal"] .navlink.active{background:var(--panel);color:var(--accent);box-shadow:inset 4px 0 0 var(--accent)}
html[data-design="journal"] .main{max-width:1020px;margin:0 auto;width:100%;line-height:1.65}
html[data-design="journal"] .topbar{border-bottom:3px double var(--ink);padding-bottom:14px}
html[data-design="journal"] .topbar h2{font-size:27px;font-variant:small-caps}
html[data-design="journal"] .panel,
html[data-design="journal"] .stat,
html[data-design="journal"] .card{
  border-color:#d8cfc0;border-radius:6px;box-shadow:0 4px 16px rgba(80,60,30,.09);
}
html[data-design="journal"] .panel-head{border-bottom:1px solid var(--line)}
html[data-design="journal"] .btn,
html[data-design="journal"] input,
html[data-design="journal"] select,
html[data-design="journal"] textarea{border-radius:5px}
html[data-design="journal"] .btn.primary{background:var(--accent);border-color:var(--accent)}
html[data-design="journal"] th{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:1.3px}
html[data-design="journal"] .center-wrap{
  background:
    radial-gradient(900px 420px at 105% -10%,#efe4cf 0%,transparent 60%),
    radial-gradient(760px 500px at -10% 105%,#e8e0cf 0%,transparent 55%),
    var(--bg);
}
html[data-design="journal"] .center-wrap .card{border-top:3px double var(--ink)}

/* ---- 3. Lab: dark console ---- */
html[data-design="lab"]{
  --ui-font:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
  --bg:#081014; --panel:#101b20; --panel2:#0b1519; --ink:#e4fbf5; --dim:#83a8a0; --line:#214047;
  --accent:#38f2c2; --accent2:#70a7ff; --accent-soft:#0e352f; --ok:#65f28e; --warn:#ffd166; --err:#ff6b6b;
  --sidebar:#05090c; --sidebar-ink:#a9cbc6; --sidebar-soft:#0f1d22; --shadow:0 12px 34px rgba(0,0,0,.48); --radius:4px;
}
html[data-design="lab"] body{
  font-family:var(--ui-font);letter-spacing:.01em;
  background:
    linear-gradient(rgba(56,242,194,.055) 1px,transparent 1px),
    linear-gradient(90deg,rgba(56,242,194,.045) 1px,transparent 1px),
    radial-gradient(900px 420px at 70% -20%,rgba(56,242,194,.17),transparent 60%),
    var(--bg);
  background-size:22px 22px,22px 22px,auto,auto;
}
html[data-design="lab"] .shell,
html[data-design="lab"] #app{grid-template-columns:276px 1fr}
html[data-design="lab"] .sidebar{border-right:1px solid var(--accent);box-shadow:inset -1px 0 0 rgba(56,242,194,.25)}
html[data-design="lab"] .brand{color:var(--accent);text-transform:uppercase;font-size:13px}
html[data-design="lab"] .logo{background:transparent;border:1px solid var(--accent);border-radius:3px;color:var(--accent)}
html[data-design="lab"] .navlink,
html[data-design="lab"] .btn,
html[data-design="lab"] .chip,
html[data-design="lab"] input,
html[data-design="lab"] select,
html[data-design="lab"] textarea{border-radius:3px}
html[data-design="lab"] .navlink.active{background:#102c2b;color:var(--accent);outline:1px solid var(--accent)}
html[data-design="lab"] .panel,
html[data-design="lab"] .stat,
html[data-design="lab"] .card,
html[data-design="lab"] .modal{box-shadow:var(--shadow);border-color:var(--line)}
html[data-design="lab"] .stat .n{text-shadow:0 0 18px rgba(56,242,194,.35)}
html[data-design="lab"] input,
html[data-design="lab"] select,
html[data-design="lab"] textarea{background:var(--panel2);color:var(--ink);border-color:var(--line)}
html[data-design="lab"] .btn{background:var(--panel2)}
html[data-design="lab"] .btn.primary{background:var(--accent);border-color:var(--accent);color:#06100f}
html[data-design="lab"] .badge.code{background:#0f2d1e;color:#65f28e}
html[data-design="lab"] .badge.none,
html[data-design="lab"] .badge.warn{background:#382c10;color:#ffd166}
html[data-design="lab"] .hw-overdue td{background:#2a1715}
html[data-design="lab"] #toast{background:#d7fff6;color:#06100f}
html[data-design="lab"] .center-wrap{
  background:
    linear-gradient(rgba(56,242,194,.06) 1px,transparent 1px),
    linear-gradient(90deg,rgba(56,242,194,.05) 1px,transparent 1px),
    radial-gradient(760px 420px at 50% 0%,rgba(56,242,194,.18),transparent 60%),
    var(--bg);
  background-size:22px 22px,22px 22px,auto,auto;
}

/* ---- 4. Blueprint: horizontal technical desk ---- */
html[data-design="blueprint"]{
  --ui-font:"Avenir Next","Segoe UI",Arial,sans-serif;
  --bg:#eaf4ff; --panel:#fafdff; --panel2:#e0efff; --ink:#10243f; --dim:#4c6d91; --line:#9ec4e8;
  --accent:#0f65d8; --accent2:#00a8a8; --accent-soft:#d8ecff; --ok:#047857; --warn:#b7791f; --err:#c2413a;
  --sidebar:#0b3d75; --sidebar-ink:#d7ecff; --sidebar-soft:#154f91; --shadow:0 8px 0 rgba(15,101,216,.12); --radius:2px;
}
html[data-design="blueprint"] body{
  font-family:var(--ui-font);
  background:
    linear-gradient(rgba(15,101,216,.08) 1px,transparent 1px),
    linear-gradient(90deg,rgba(15,101,216,.08) 1px,transparent 1px),
    var(--bg);
  background-size:28px 28px;
}
html[data-design="blueprint"] #app:not(:has(> .center-wrap)),
html[data-design="blueprint"] .shell{grid-template-columns:1fr;grid-template-rows:auto 1fr}
html[data-design="blueprint"] .sidebar{
  flex-direction:row;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 18px;border-bottom:4px solid var(--accent2);
}
html[data-design="blueprint"] .brand{margin:0 12px 0 0}
html[data-design="blueprint"] .logo{border-radius:0;background:#fff;color:var(--accent)}
html[data-design="blueprint"] #nav{display:flex;gap:4px;flex-wrap:wrap;align-items:center}
html[data-design="blueprint"] .navlink{border-radius:0;border:1px solid rgba(255,255,255,.18);padding:8px 10px}
html[data-design="blueprint"] .navlink.active{background:#fff;color:var(--accent)}
html[data-design="blueprint"] .sidebar .spacer{display:block;flex:1 1 20px}
html[data-design="blueprint"] .shell-widgets{
  order:5;
  flex:1 1 100%;
  grid-template-columns:repeat(4,minmax(140px,1fr));
  margin:6px 0 0;
}
html[data-design="blueprint"] .shell-art{display:none}
html[data-design="blueprint"] .mini-card{
  padding:8px 10px;
  border-radius:0;
}
html[data-design="blueprint"] .main{padding:22px clamp(18px,3vw,34px)}
html[data-design="blueprint"] .panel,
html[data-design="blueprint"] .stat,
html[data-design="blueprint"] .card,
html[data-design="blueprint"] .modal{border-width:2px;border-radius:2px;box-shadow:var(--shadow)}
html[data-design="blueprint"] .topbar h2{text-transform:uppercase;letter-spacing:.08em}
html[data-design="blueprint"] .btn,
html[data-design="blueprint"] .chip,
html[data-design="blueprint"] input,
html[data-design="blueprint"] select,
html[data-design="blueprint"] textarea{border-radius:2px}
html[data-design="blueprint"] .btn.primary{background:var(--accent);border-color:var(--accent)}
html[data-design="blueprint"] th{letter-spacing:1.1px;color:var(--accent)}
html[data-design="blueprint"] .center-wrap{
  background:
    linear-gradient(rgba(15,101,216,.12) 1px,transparent 1px),
    linear-gradient(90deg,rgba(15,101,216,.12) 1px,transparent 1px),
    var(--bg);
  background-size:28px 28px;
}

@media(max-width:760px){
  html[data-design="lab"] .shell,
  html[data-design="lab"] #app,
  html[data-design="blueprint"] #app:not(:has(> .center-wrap)),
  html[data-design="blueprint"] .shell{grid-template-columns:1fr}
  html[data-design="blueprint"] .sidebar{align-items:flex-start}
  html[data-design="blueprint"] #nav{width:100%}
}

/* ---- shared experimental shell details ---- */
.line-ico{width:1.1em;height:1.1em;display:inline-block;vertical-align:-.18em}
.navlink .ic{display:inline-flex;align-items:center;justify-content:center}
.navlink .line-ico{width:18px;height:18px}
.navlink--sub{padding-left:28px!important;font-size:.9em;opacity:.85}
.navlink--sub .ic{width:20px!important;height:20px!important}
.navlink--sub .line-ico{width:15px!important;height:15px!important}
.btn .line-ico,.chip .line-ico{width:16px;height:16px}
.tile-ic .line-ico,.empty-art .line-ico{width:100%;height:100%}
.tb-spine .line-ico,.type-ico .line-ico{width:24px;height:24px}
.apps-panel,.shell-widgets,.side-scrim{display:none}

@media(min-width:761px){
  #app.sidebar-collapsed{
    grid-template-columns:0 1fr!important;
  }
  #app.sidebar-collapsed .sidebar{
    transform:translateX(-100%);
    pointer-events:none;
    overflow:hidden;
  }
  #app.sidebar-collapsed .side-scrim{
    display:none!important;
  }
}

@media(max-width:760px){
  #app:not(.menu-open) .side-scrim{display:none!important}
}

html:not([data-design="classic"]) .brand .logo,
html:not([data-design="classic"]) .mobile-brand .logo{
  background:transparent;
  color:var(--accent);
  border:1.5px solid currentColor;
}
html:not([data-design="classic"]) .navlink{
  position:relative;
  min-height:40px;
}
.shell-widgets{
  display:grid;
  gap:10px;
  margin:14px 0;
}
html:not([data-design="classic"]) .mini-card{
  border:1px solid color-mix(in srgb,var(--sidebar-ink) 18%,transparent);
  background:color-mix(in srgb,var(--sidebar-soft) 72%,transparent);
  color:var(--sidebar-ink);
  padding:10px 11px;
  border-radius:calc(var(--radius) - 4px);
}
html:not([data-design="classic"]) .mini-card b{
  display:block;
  color:#fff;
  font-size:12px;
  margin-bottom:2px;
}
html:not([data-design="classic"]) .mini-card span{
  display:block;
  font-size:11.5px;
  line-height:1.35;
  color:color-mix(in srgb,var(--sidebar-ink) 84%,transparent);
}
.shell-art{
  min-height:178px;
  border:1px solid color-mix(in srgb,var(--sidebar-ink) 18%,transparent);
  border-radius:var(--radius);
  background:color-mix(in srgb,var(--panel) 92%,#eef6ff);
  overflow:hidden;
}
.shell-heat-card{
  height:100%;
  min-height:178px;
  display:flex;
  flex-direction:column;
  gap:10px;
  padding:15px;
  color:var(--ink);
}
.shell-heat-top{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  gap:10px;
}
.shell-heat-top b{
  font-size:13px;
  color:var(--ink);
}
.shell-heat-top span{
  font-size:11px;
  font-weight:800;
  color:var(--accent);
  white-space:nowrap;
}
.shell-heat-body{display:contents}
.shell-heat-grid{
  display:grid;
  grid-template-columns:14px repeat(8,1fr);
  grid-template-rows:repeat(7,auto);
  grid-auto-flow:column;
  gap:3px;
  width:100%;
}
.shell-heat-day-label{
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:9px;
  font-weight:900;
  color:var(--dim);
  line-height:1;
  aspect-ratio:1/1;
}
.shell-heat-cell{
  --heat-pct:0%;
  display:block;
  aspect-ratio:1/1;
  border-radius:35%;
  background:color-mix(in srgb,#16834c var(--heat-pct),#e5edf7);
  border:1px solid rgba(8,38,75,.06);
}
.shell-heat-cell.disabled{
  background:#f1f4f8;
  border-color:#e7edf4;
  opacity:.72;
}
.shell-heat-cell.l1{background:#cfead9}
.shell-heat-cell.l2{background:#91d5a8}
.shell-heat-cell.l3{background:#45b970}
.shell-heat-cell.l4{background:#16834c}
.shell-heat-caption{
  color:var(--dim);
  font-size:11.5px;
  line-height:1.25;
}
.assistant-panel-tabs{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:3px;
  border:1px solid color-mix(in srgb,var(--sidebar-ink) 18%,transparent);
  background:color-mix(in srgb,var(--sidebar-soft) 70%,transparent);
  border-radius:12px;
  padding:3px;
}
.assistant-panel-tab{
  min-height:34px;
  border:1px solid transparent;
  border-radius:9px;
  background:transparent;
  color:var(--sidebar-ink);
  font:inherit;
  font-size:12px;
  font-weight:900;
  cursor:pointer;
}
.assistant-panel-tab:hover{
  color:#fff;
  border-color:color-mix(in srgb,var(--accent) 45%,transparent);
}
.assistant-panel-tab.on{
  background:var(--accent);
  border-color:var(--accent);
  color:#fff;
}
.assistant-panel-pane[hidden]{display:none}
.assistant-task-pane{
  display:grid;
  gap:10px;
}
.assistant-task-summary{
  display:flex;
  align-items:baseline;
  gap:6px;
  border:1px solid color-mix(in srgb,var(--sidebar-ink) 18%,transparent);
  background:color-mix(in srgb,var(--panel) 94%,#eef6ff);
  color:var(--ink);
  border-radius:var(--radius);
  padding:11px 12px;
}
.assistant-task-summary b{
  color:var(--accent);
  font-size:28px;
  line-height:1;
}
.assistant-task-summary span{
  font-size:12px;
  font-weight:850;
}
.assistant-task-summary em{
  margin-left:auto;
  color:var(--dim);
  font-style:normal;
  font-size:11px;
  font-weight:850;
}
.assistant-task-list{
  display:grid;
  gap:8px;
}
.assistant-task{
  display:grid;
  grid-template-columns:28px minmax(0,1fr);
  gap:9px;
  align-items:start;
  width:100%;
  border:1px solid color-mix(in srgb,var(--sidebar-ink) 18%,transparent);
  background:color-mix(in srgb,var(--panel) 94%,#eef6ff);
  color:var(--ink);
  border-radius:12px;
  padding:10px;
  text-align:left;
  font:inherit;
  cursor:pointer;
}
.assistant-task:hover{
  border-color:var(--accent);
}
.assistant-task.done{
  opacity:.68;
}
.assistant-task-check{
  display:grid;
  place-items:center;
  width:24px;
  height:24px;
  border:1px solid color-mix(in srgb,var(--accent) 28%,var(--line));
  border-radius:8px;
  color:var(--ok);
  font-weight:950;
}
.assistant-task-copy{
  min-width:0;
}
.assistant-task-copy b{
  display:block;
  color:var(--ink);
  font-size:12.5px;
  line-height:1.25;
}
.assistant-task.done .assistant-task-copy b{
  text-decoration:line-through;
}
.assistant-task-copy small{
  display:block;
  margin-top:3px;
  color:var(--dim);
  font-size:10.5px;
  font-weight:750;
}
.assistant-task-empty{
  border:1px dashed color-mix(in srgb,var(--sidebar-ink) 20%,transparent);
  border-radius:12px;
  padding:16px 12px;
  color:var(--sidebar-ink);
  font-size:12px;
  text-align:center;
}
.assistant-task-page{
  max-width:720px;
}
.assistant-task-page .assistant-task-summary,
.assistant-task-page .assistant-task,
.assistant-task-page .assistant-task-empty{
  border-color:var(--line);
  background:var(--panel);
  color:var(--ink);
}
.assistant-task-page .assistant-task-copy b{
  font-size:14px;
}
.assistant-task-page .assistant-task-copy small{
  font-size:12px;
}
.assistant-task-page .assistant-task-empty{
  color:var(--dim);
}
/* Staff-only add row. Sits above the board so the first thing a teacher sees on the page is the
   box they came here to type into. */
.assistant-task-add{
  display:flex;
  gap:8px;
  align-items:center;
}
.assistant-task-add input{
  flex:1 1 auto;
  min-width:0;
}
.assistant-task-add .btn{
  flex:0 0 auto;
}
.assistant-task-hint{
  margin:-4px 0 2px;
  color:var(--dim);
  font-size:12px;
}
.assistant-task-heading{
  margin:8px 0 0;
  color:var(--dim);
  font-size:12px;
  font-weight:850;
  text-transform:uppercase;
  letter-spacing:.06em;
}
@media(max-width:520px){
  .assistant-task-add{flex-wrap:wrap}
  .assistant-task-add .btn{width:100%;justify-content:center}
}

@media(max-width:760px){
  html:not([data-design="classic"]) body{padding-bottom:88px}
  html:not([data-design="classic"]) #app:not(:has(> .center-wrap)),
  html:not([data-design="classic"]) .shell{
    display:block;
    min-height:100%;
  }
  html:not([data-design="classic"]) .mobile-top{
    position:sticky;
    top:0;
    z-index:1200;
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:12px;
    height:60px;
    min-height:60px;
    padding:0 14px;
    border-bottom:1px solid var(--line);
    background:color-mix(in srgb,var(--panel) 94%,transparent);
    color:var(--ink);
    backdrop-filter:blur(14px);
    overflow:hidden;
  }
  html:not([data-design="classic"]) .mobile-brand{
    display:flex;
    align-items:center;
    gap:9px;
    font-weight:800;
    line-height:1.05;
  }
  html:not([data-design="classic"]) .mobile-brand small{
    display:block;
    font-size:9px;
    letter-spacing:1.4px;
    color:var(--dim);
    margin-top:3px;
  }
  html:not([data-design="classic"]) .menu-btn{
    width:42px;
    height:42px;
    border:1px solid var(--line);
    border-radius:12px;
    background:var(--panel2);
    color:var(--ink);
    display:grid;
    place-items:center;
  }
  html:not([data-design="classic"]) .menu-btn .line-ico{width:23px;height:23px}
  html:not([data-design="classic"]) .sidebar{
    position:fixed;
    z-index:1300;
    inset:0 auto 0 0;
    width:min(326px,86vw);
    display:flex;
    flex-direction:column;
    flex-wrap:nowrap;
    align-items:stretch;
    padding:18px 14px;
    transform:translateX(-105%);
    transition:transform .16s cubic-bezier(.2,.8,.2,1);
    overflow:auto;
    box-shadow:24px 0 50px rgba(0,0,0,.22);
  }
  html:not([data-design="classic"]) #app.menu-open .sidebar{transform:translateX(0)}
  html:not([data-design="classic"]) .side-scrim{
    position:fixed;
    inset:0;
    z-index:1250;
    border:0;
    background:rgba(12,18,28,.42);
  }
  html:not([data-design="classic"]) #app.menu-open .side-scrim{display:block}
  html:not([data-design="classic"]) .sidebar .spacer{display:block;flex:1 1 auto}
  html:not([data-design="classic"]) .devswitch{display:block}
  html:not([data-design="classic"]) #nav{
    display:grid;
    grid-template-columns:1fr;
    width:100%;
  }
  html:not([data-design="classic"]) .main{
    padding:18px 14px 24px;
    overflow:visible;
  }
  html:not([data-design="classic"]) .topbar{
    align-items:flex-start;
    gap:10px;
  }
  html:not([data-design="classic"]) .toolbar{
    width:100%;
  }
  html:not([data-design="classic"]) .topbar:has(.toolbar .btn[data-back]) .toolbar{
    width:auto;
  }
  html:not([data-design="classic"]) .toolbar .btn,
  html:not([data-design="classic"]) .toolbar .chip{
    min-height:40px;
  }
}

@media(max-width:760px){
  html[data-design="classic"] body{padding-bottom:88px}
  html[data-design="classic"] #app:not(:has(> .center-wrap)),
  html[data-design="classic"] .shell{
    display:block;
    min-height:100%;
  }
  html[data-design="classic"] .mobile-top{
    position:sticky;
    top:0;
    z-index:1200;
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:12px;
    height:60px;
    min-height:60px;
    padding:0 14px;
    border-bottom:1px solid var(--line);
    background:color-mix(in srgb,var(--panel) 94%,transparent);
    color:var(--ink);
    backdrop-filter:blur(14px);
    overflow:hidden;
  }
  html[data-design="classic"] .mobile-brand{
    display:flex;
    align-items:center;
    gap:9px;
    font-weight:800;
    line-height:1.05;
  }
  html[data-design="classic"] .mobile-brand small{
    display:block;
    font-size:9px;
    letter-spacing:1.4px;
    color:var(--dim);
    margin-top:3px;
  }
  html[data-design="classic"] .menu-btn{
    width:42px;
    height:42px;
    border:1px solid var(--line);
    border-radius:12px;
    background:var(--panel2);
    color:var(--ink);
    display:grid;
    place-items:center;
  }
  html[data-design="classic"] .menu-btn .line-ico{width:23px;height:23px}
  html[data-design="classic"] .sidebar{
    position:fixed;
    z-index:1300;
    inset:0 auto 0 0;
    width:min(326px,86vw);
    display:flex;
    flex-direction:column;
    flex-wrap:nowrap;
    align-items:stretch;
    padding:18px 14px;
    transform:translateX(-105%);
    transition:transform .16s cubic-bezier(.2,.8,.2,1);
    overflow:auto;
    box-shadow:24px 0 50px rgba(0,0,0,.22);
  }
  html[data-design="classic"] #app.menu-open .sidebar{transform:translateX(0)}
  html[data-design="classic"] .side-scrim{
    position:fixed;
    inset:0;
    z-index:1250;
    border:0;
    background:rgba(12,18,28,.42);
  }
  html[data-design="classic"] #app.menu-open .side-scrim{display:block}
  html[data-design="classic"] .sidebar .spacer{display:block;flex:1 1 auto}
  html[data-design="classic"] .devswitch{display:block}
  html[data-design="classic"] #nav{
    display:grid;
    grid-template-columns:1fr;
    width:100%;
  }
  html[data-design="classic"] .main{
    padding:18px 14px 24px;
    overflow:visible;
  }
}

/* ---- 2. Classic 2: blue/yellow dashboard refresh ---- */
html[data-design="classic2"]{
  --ui-font:"Inter","Segoe UI",Roboto,system-ui,sans-serif;
  --bg:#eef5ff; --panel:#ffffff; --panel2:#f4f8ff; --ink:#11233f; --dim:#557092; --line:#d5e4f4;
  --accent:#0a61c9; --accent2:#ffc928; --accent-soft:#e2f0ff; --ok:#16834c; --warn:#b7791f; --err:#c23b45;
  --sidebar:#0c315f; --sidebar-ink:#d9eaff; --sidebar-soft:#143f75; --shadow:0 14px 34px rgba(15,61,115,.13); --radius:12px;
}
html[data-design="classic2"] body{
  font-family:var(--ui-font);
  background:linear-gradient(180deg,#f6fbff 0,#eaf3ff 42%,#f7fbff 100%);
}
html[data-design="classic2"] #app:not(:has(> .center-wrap)),
html[data-design="classic2"] .shell{grid-template-columns:284px 1fr}
html[data-design="classic2"] .sidebar{
  padding:20px 16px;
  border-right:6px solid var(--accent2);
}
html[data-design="classic2"] .brand{
  padding:12px;
  background:rgba(255,255,255,.08);
  border:1px solid rgba(255,255,255,.12);
  border-radius:var(--radius);
}
html[data-design="classic2"] .navlink{
  border:1px solid transparent;
  border-radius:10px;
  font-weight:700;
}
html[data-design="classic2"] .navlink.active{
  background:#fff;
  color:var(--accent);
  border-color:#fff;
  box-shadow:inset 4px 0 0 var(--accent2);
}
html[data-design="classic2"] .main{
  padding:26px clamp(20px,3vw,42px);
}
html[data-design="classic2"] .topbar{
  border-bottom:1px solid var(--line);
  padding-bottom:16px;
}
html[data-design="classic2"] .topbar h2{
  color:#09264b;
  font-size:clamp(23px,3vw,34px);
}
html[data-design="classic2"] .panel,
html[data-design="classic2"] .stat,
html[data-design="classic2"] .card{
  border-radius:12px;
  box-shadow:var(--shadow);
}
html[data-design="classic2"] .btn.primary{
  background:var(--accent);
  border-color:var(--accent);
}
html[data-design="classic2"] .chip.on,
html[data-design="classic2"] .badge.year,
html[data-design="classic2"] .badge.info{
  background:#fff2b8;
  color:#6b4d00;
}

/* ---- Classic 2 iterations ---- */
html[data-design="classic2-river"]{
  --ui-font:"Inter","Segoe UI",Roboto,system-ui,sans-serif;
  --bg:#eef9fb; --panel:#ffffff; --panel2:#f0fbff; --ink:#102c3a; --dim:#557782; --line:#cfe8ee;
  --accent:#087b95; --accent2:#ffcf5a; --accent-soft:#dff6fb; --ok:#16834c; --warn:#b7791f; --err:#c23b45;
  --sidebar:#0b3b4a; --sidebar-ink:#d9f5fa; --sidebar-soft:#154d5f; --shadow:0 14px 34px rgba(8,89,112,.13); --radius:12px;
}
html[data-design="classic2-scholar"]{
  --ui-font:"Source Sans 3","Segoe UI",Roboto,system-ui,sans-serif;
  --bg:#f5f7fb; --panel:#ffffff; --panel2:#f7f8fd; --ink:#17223b; --dim:#667085; --line:#dce3ef;
  --accent:#244d9d; --accent2:#f1b534; --accent-soft:#e8efff; --ok:#16834c; --warn:#b7791f; --err:#c23b45;
  --sidebar:#18233d; --sidebar-ink:#dce6ff; --sidebar-soft:#263553; --shadow:0 14px 34px rgba(31,51,94,.13); --radius:10px;
}
html[data-design="classic2-studio"]{
  --ui-font:"Aptos","Segoe UI",Roboto,system-ui,sans-serif;
  --bg:#fff7f2; --panel:#ffffff; --panel2:#fff4e9; --ink:#2e2433; --dim:#7b6672; --line:#efd8cf;
  --accent:#a23d73; --accent2:#ffc857; --accent-soft:#fae7f1; --ok:#16834c; --warn:#b7791f; --err:#c23b45;
  --sidebar:#452337; --sidebar-ink:#ffe6f0; --sidebar-soft:#62324e; --shadow:0 14px 34px rgba(111,54,86,.13); --radius:14px;
}
html[data-design="classic2-orbit"]{
  --ui-font:"Inter","Segoe UI",Roboto,system-ui,sans-serif;
  --bg:#f5fbf7; --panel:#ffffff; --panel2:#effaf2; --ink:#142d25; --dim:#5f756d; --line:#d6e8dc;
  --accent:#246b4b; --accent2:#6bb7ff; --accent-soft:#e2f3e9; --ok:#16834c; --warn:#b7791f; --err:#c23b45;
  --sidebar:#123328; --sidebar-ink:#dcf3e9; --sidebar-soft:#1d493a; --shadow:0 14px 34px rgba(28,91,67,.13); --radius:18px;
}
html[data-design^="classic2"] body{
  font-family:var(--ui-font);
  background:linear-gradient(180deg,color-mix(in srgb,var(--panel) 72%,var(--accent-soft)) 0,var(--bg) 44%,var(--panel2) 100%);
}
html[data-design^="classic2"] #app:not(:has(> .center-wrap)),
html[data-design^="classic2"] .shell{grid-template-columns:284px 1fr}
html[data-design^="classic2"] .sidebar{
  padding:20px 16px;
  border-right:6px solid var(--accent2);
}
html[data-design^="classic2"] .brand{
  padding:12px;
  background:rgba(255,255,255,.08);
  border:1px solid rgba(255,255,255,.12);
  border-radius:var(--radius);
}
html[data-design^="classic2"] .navlink{
  border:1px solid transparent;
  border-radius:10px;
  font-weight:700;
}
html[data-design^="classic2"] .navlink.active{
  background:#fff;
  color:var(--accent);
  border-color:#fff;
  box-shadow:inset 4px 0 0 var(--accent2);
}
html[data-design^="classic2"] .main{padding:26px clamp(20px,3vw,42px)}
html[data-design^="classic2"] .topbar{border-bottom:1px solid var(--line);padding-bottom:16px}
html[data-design^="classic2"] .topbar h2{color:var(--ink);font-size:clamp(23px,3vw,34px)}
html[data-design^="classic2"] .panel,
html[data-design^="classic2"] .stat,
html[data-design^="classic2"] .card{
  border-radius:var(--radius);
  box-shadow:var(--shadow);
}
html[data-design^="classic2"] .btn.primary{background:var(--accent);border-color:var(--accent)}
html[data-design^="classic2"] .chip.on,
html[data-design^="classic2"] .badge.year,
html[data-design^="classic2"] .badge.info{
  background:color-mix(in srgb,var(--accent2) 32%,#fff);
  color:color-mix(in srgb,var(--accent) 70%,#171717);
}

/* ---- loading splash ---- */
.kc-splash{
  position:fixed;inset:0;z-index:5000;display:grid;place-items:center;
  background:linear-gradient(135deg,#edf6ff 0,#fff8da 52%,#f2fbf5 100%);
  transition:opacity .18s ease;
}
.kc-splash.done{opacity:0;pointer-events:none}
.kc-splash-card{
  width:min(360px,calc(100vw - 36px));
  background:#fff;
  border:1px solid #d7e5f3;
  border-radius:18px;
  box-shadow:0 24px 70px rgba(18,48,84,.18);
  padding:24px;
  display:grid;
  gap:16px;
}
.kc-splash-mark{
  width:52px;height:52px;border-radius:15px;display:grid;place-items:center;
  background:#0a61c9;color:#fff;font-weight:900;letter-spacing:.04em;
}
.kc-splash-copy b{display:block;font-size:20px;color:#10243f}
.kc-splash-copy span{display:block;color:#5f7088;font-size:13px;margin-top:2px}
.kc-splash-track{height:8px;border-radius:999px;background:#e6eef8;overflow:hidden}
.kc-splash-fill{
  width:0;height:100%;border-radius:inherit;
  background:linear-gradient(90deg,#0a61c9,#ffc928,#35a36b);
  transition:width var(--splash-ms,3s) cubic-bezier(.22,.8,.2,1);
}
/* Creeps to ~90% while real loading runs in the background; never claims "done" on its own. */
.kc-splash.run .kc-splash-fill{width:90%}
.kc-splash.run.at100 .kc-splash-fill{width:100%}

/* ---- 6. Apps: tile launcher, no side menu on desktop ---- */
html[data-design="apps"]{
  --ui-font:"Trebuchet MS","Segoe UI",system-ui,sans-serif;
  --bg:#f7fbf7; --panel:#fffdf5; --panel2:#edf8f1; --ink:#15312a; --dim:#5e756f; --line:#cfe4d6;
  --accent:#357b5b; --accent2:#eaa7b0; --accent-soft:#e3f2e9; --ok:#26744d; --warn:#b87519; --err:#b4444c;
  --sidebar:#fff7dd; --sidebar-ink:#23463c; --sidebar-soft:#f1eccd; --shadow:0 12px 28px rgba(47,91,72,.12); --radius:16px;
}
html[data-design="apps"] body{
  font-family:var(--ui-font);
  background:
    linear-gradient(120deg,rgba(234,167,176,.18),transparent 35%),
    linear-gradient(240deg,rgba(164,216,255,.18),transparent 40%),
    var(--bg);
}
html[data-design="apps"] #app:not(:has(> .center-wrap)),
html[data-design="apps"] .shell{
  grid-template-columns:1fr;
  grid-template-rows:auto 1fr;
}
html[data-design="apps"] .sidebar{
  min-height:auto;
  display:grid;
  grid-template-columns:minmax(190px,260px) 1fr auto;
  align-items:start;
  gap:16px;
  padding:18px clamp(18px,3vw,36px);
  background:var(--sidebar);
  border-bottom:1px solid #eadfa9;
  color:var(--sidebar-ink);
}
html[data-design="apps"] .brand{margin:0;color:var(--ink)}
html[data-design="apps"] #nav,
html[data-design="apps"] .spacer{display:none}
html[data-design="apps"] .apps-panel{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(126px,1fr));
  gap:10px;
  width:100%;
}
html[data-design="apps"] .app-tile{
  min-height:92px;
  border:1px solid #d9e8cd;
  border-radius:14px;
  background:#fffef8;
  color:var(--ink);
  display:grid;
  align-content:center;
  justify-items:start;
  gap:8px;
  padding:14px;
  font:800 13px/1.2 var(--ui-font);
  text-align:left;
  box-shadow:0 6px 0 rgba(53,123,91,.08);
  cursor:pointer;
}
html[data-design="apps"] .app-tile:hover,
html[data-design="apps"] .app-tile:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
}
html[data-design="apps"] .app-tile-icon{
  width:28px;
  height:28px;
  color:var(--accent);
}
html[data-design="apps"] .app-tile-icon .line-ico{width:28px;height:28px}
html[data-design="apps"] .shell-widgets{
  margin:0;
  grid-template-columns:170px;
}
html[data-design="apps"] .shell-art{
  min-height:92px;
  background-image:url("../assets/designs/pastel-lab.svg");
}
html[data-design="apps"] .mini-ann,
html[data-design="apps"] .mini-calendar{display:none}
html[data-design="apps"] .mini-card{
  background:#fffef8;
  color:var(--ink);
  border-color:#d9e8cd;
}
html[data-design="apps"] .mini-card b{color:var(--accent)}
html[data-design="apps"] .mini-card span{color:var(--dim)}
html[data-design="apps"] .main{
  max-width:1180px;
  width:100%;
  margin:0 auto;
  padding:28px clamp(16px,3vw,38px);
}
html[data-design="apps"] .panel,
html[data-design="apps"] .stat,
html[data-design="apps"] .card{
  border-radius:16px;
  box-shadow:var(--shadow);
}

/* ---- 7. Campus: pastel line-drawing dashboard ---- */
html[data-design="campus"]{
  --ui-font:"Nunito","Segoe UI",system-ui,sans-serif;
  --bg:#fff9e8; --panel:#ffffff; --panel2:#f0f8ff; --ink:#18334f; --dim:#62798d; --line:#d6e7ee;
  --accent:#1f6fb2; --accent2:#f2bf3d; --accent-soft:#e4f3ff; --ok:#2e8757; --warn:#ab7418; --err:#bb4652;
  --sidebar:#eaf6ff; --sidebar-ink:#24445f; --sidebar-soft:#d8eefc; --shadow:0 16px 34px rgba(52,105,145,.13); --radius:18px;
}
html[data-design="campus"] body{
  font-family:var(--ui-font);
  background:linear-gradient(180deg,#fffaf0 0,#eef8ff 100%);
}
html[data-design="campus"] #app:not(:has(> .center-wrap)),
html[data-design="campus"] .shell{grid-template-columns:316px 1fr}
html[data-design="campus"] .sidebar{
  background:var(--sidebar);
  color:var(--sidebar-ink);
  border-right:1px solid var(--line);
}
html[data-design="campus"] .brand{color:var(--ink)}
html[data-design="campus"] .brand small{color:var(--dim)}
html[data-design="campus"] .navlink{
  color:#315c7a;
  border:1px solid transparent;
  border-radius:999px;
  font-weight:800;
}
html[data-design="campus"] .navlink:hover{background:#fff;color:var(--accent)}
html[data-design="campus"] .navlink.active{
  background:#fff;
  color:var(--accent);
  border-color:#cfe5f4;
  box-shadow:0 7px 18px rgba(31,111,178,.1);
}
html[data-design="campus"] .shell-art{
  min-height:150px;
  background-image:url("../assets/designs/pastel-campus.svg");
}
html[data-design="campus"] .mini-card{
  background:#fff;
  color:var(--ink);
  border-color:#cfe5f4;
}
html[data-design="campus"] .mini-card b{color:var(--accent)}
html[data-design="campus"] .mini-card span{color:var(--dim)}
html[data-design="campus"] .main{padding:28px clamp(18px,4vw,48px)}
html[data-design="campus"] .topbar{
  background:#fff;
  border:1px solid var(--line);
  border-radius:18px;
  padding:18px 20px;
  box-shadow:var(--shadow);
}
html[data-design="campus"] .topbar h2{font-size:clamp(24px,3vw,36px)}
html[data-design="campus"] .panel,
html[data-design="campus"] .stat,
html[data-design="campus"] .card{
  border-radius:18px;
  box-shadow:var(--shadow);
}
html[data-design="campus"] .stat .n{color:#d39b00}

/* ---- 8. Focus: streak and task console ---- */
html[data-design="focus"]{
  --ui-font:"IBM Plex Sans","Segoe UI",system-ui,sans-serif;
  --bg:#101414; --panel:#f7faf7; --panel2:#e8f0ea; --ink:#111b19; --dim:#66736e; --line:#d6ded8;
  --accent:#ffcf5a; --accent2:#70c6a4; --accent-soft:#fff2c8; --ok:#2d8a5f; --warn:#bb7b10; --err:#bd4c58;
  --sidebar:#16201d; --sidebar-ink:#d7e6dd; --sidebar-soft:#22322d; --shadow:0 18px 36px rgba(6,14,11,.15); --radius:8px;
}
html[data-design="focus"] body{
  font-family:var(--ui-font);
  background:
    linear-gradient(90deg,#101414 0 310px,#edf4ee 310px 100%);
}
html[data-design="focus"] #app:not(:has(> .center-wrap)),
html[data-design="focus"] .shell{grid-template-columns:310px 1fr}
html[data-design="focus"] .sidebar{
  background:var(--sidebar);
  border-right:1px solid #31463f;
}
html[data-design="focus"] .brand{
  padding-bottom:14px;
  border-bottom:1px solid #31463f;
}
html[data-design="focus"] .navlink{
  color:#c2d6cb;
  border-radius:8px;
  border:1px solid transparent;
  font-weight:700;
}
html[data-design="focus"] .navlink.active{
  background:#f9f4dd;
  color:#1c2925;
  border-color:#ffcf5a;
}
html[data-design="focus"] .shell-widgets{order:-1;margin:6px 0 18px}
html[data-design="focus"] .shell-art{
  display:none;
}
html[data-design="focus"] .mini-card{
  border-radius:8px;
  background:#f7faf7;
  color:#1c2925;
  border-color:#f7faf7;
}
html[data-design="focus"] .mini-card b{color:#1c2925}
html[data-design="focus"] .mini-card span{color:#4f5f59}
html[data-design="focus"] .mini-streak{
  min-height:96px;
  display:grid;
  place-content:center start;
  padding-left:70px;
  background:
    radial-gradient(circle at 35px 46px,transparent 0 20px,#ffcf5a 21px 24px,transparent 25px),
    #f7faf7;
}
html[data-design="focus"] .main{
  padding:30px clamp(18px,4vw,52px);
  background:#edf4ee;
}
html[data-design="focus"] .topbar h2{
  font-size:clamp(28px,4vw,44px);
  letter-spacing:0;
}
html[data-design="focus"] .panel,
html[data-design="focus"] .stat,
html[data-design="focus"] .card{
  border-radius:8px;
  box-shadow:none;
}
html[data-design="focus"] .stat{
  border-left:6px solid var(--accent);
}
html[data-design="focus"] .btn.primary{
  background:#1e2e29;
  border-color:#1e2e29;
  color:#fff;
}

/* ---- 9. Care: pastoral care board ---- */
html[data-design="care"]{
  --ui-font:"Atkinson Hyperlegible","Segoe UI",system-ui,sans-serif;
  --bg:#fbf7ff; --panel:#ffffff; --panel2:#f6edf9; --ink:#26354f; --dim:#667084; --line:#eaddec;
  --accent:#7c4d8f; --accent2:#64a6bd; --accent-soft:#f0e4f4; --ok:#3b7f60; --warn:#a3711b; --err:#b24b5d;
  --sidebar:#fff5e2; --sidebar-ink:#3b4255; --sidebar-soft:#ffe9bb; --shadow:0 16px 38px rgba(87,69,110,.13); --radius:14px;
}
html[data-design="care"] body{
  font-family:var(--ui-font);
  background:linear-gradient(135deg,#fff8ed 0,#fbf7ff 50%,#edf7fb 100%);
}
html[data-design="care"] #app:not(:has(> .center-wrap)),
html[data-design="care"] .shell{grid-template-columns:286px 1fr}
html[data-design="care"] .sidebar{
  background:var(--sidebar);
  color:var(--sidebar-ink);
  border-right:1px solid #edd7ad;
}
html[data-design="care"] .brand{
  color:var(--ink);
  align-items:flex-start;
}
html[data-design="care"] .brand small{color:#786b57}
html[data-design="care"] .navlink{
  color:#4c4d58;
  border-radius:14px;
  border:1px solid transparent;
  font-weight:700;
}
html[data-design="care"] .navlink:hover{background:#fff;color:var(--accent)}
html[data-design="care"] .navlink.active{
  background:#fff;
  color:var(--accent);
  border-color:#f0d7a7;
}
html[data-design="care"] .shell-art{
  min-height:136px;
  background-image:url("../assets/designs/pastel-care.svg");
}
html[data-design="care"] .mini-card{
  background:#fffaf0;
  color:var(--ink);
  border-color:#eed8ad;
}
html[data-design="care"] .mini-card b{color:var(--accent)}
html[data-design="care"] .mini-card span{color:#6c6470}
html[data-design="care"] .main{
  padding:28px clamp(18px,4vw,50px);
}
html[data-design="care"] .topbar{
  padding:18px 0;
  border-bottom:1px solid var(--line);
}
html[data-design="care"] .topbar h2{
  font-size:clamp(24px,3vw,34px);
}
html[data-design="care"] .panel,
html[data-design="care"] .stat,
html[data-design="care"] .card{
  border-radius:14px;
  box-shadow:var(--shadow);
}
html[data-design="care"] .panel-head{
  background:#fffaf4;
}

@media(max-width:900px){
  html[data-design="apps"] .sidebar{
    grid-template-columns:1fr;
  }
  html[data-design="apps"] .shell-widgets{
    grid-template-columns:1fr 1fr;
  }
}

@media(max-width:760px){
  html[data-design="classic2"] #app:not(:has(> .center-wrap)),
  html[data-design="classic2"] .shell,
  html[data-design="apps"] #app:not(:has(> .center-wrap)),
  html[data-design="apps"] .shell,
  html[data-design="campus"] #app:not(:has(> .center-wrap)),
  html[data-design="campus"] .shell,
  html[data-design="focus"] #app:not(:has(> .center-wrap)),
  html[data-design="focus"] .shell,
  html[data-design="care"] #app:not(:has(> .center-wrap)),
  html[data-design="care"] .shell{display:block}

  html[data-design="apps"] .sidebar{
    display:flex;
    grid-template-columns:none;
    min-height:100%;
    background:var(--sidebar);
  }
  html[data-design="apps"] #nav{display:none}
  html[data-design="apps"] .apps-panel{
    display:grid;
    grid-template-columns:1fr 1fr;
  }
  html[data-design="apps"] .app-tile{
    min-height:82px;
    padding:12px;
  }
  html[data-design="apps"] .shell-widgets{
    grid-template-columns:1fr;
  }
  html[data-design="campus"] .topbar,
  html[data-design="care"] .topbar{
    border-radius:14px;
    padding:14px;
  }
  html[data-design="focus"] body{
    background:#edf4ee;
  }
  html[data-design="blueprint"] .sidebar{
    flex-direction:column;
    flex-wrap:nowrap;
    align-items:stretch;
  }
  html[data-design="blueprint"] #nav{
    display:grid;
  }
}

@media(max-width:700px){
  html[data-design] .mobile-brand{display:none}
}
@media(max-width:760px){
  .assistant-panel-tabs{border-radius:10px}
  .assistant-task-summary b{font-size:22px}
}

/* css/app.css */
/* KC Portal — design system (frozen spine). Tokens + base components.
   Task-specific styles go in css/<task>.css, never here. */
:root{
  --bg:#f4f6fb; --panel:#fff; --panel2:#f7f9fc; --ink:#1b2230; --dim:#697186; --line:#e4e8f0;
  --accent:#5b53e6; --accent2:#7c74ff; --accent-soft:#eceaff; --ok:#16a34a; --warn:#d97706; --err:#dc2626;
  --sidebar:#171a2b; --sidebar-ink:#c7ccdd; --sidebar-soft:#262b44; --shadow:0 8px 30px #1b22301a; --radius:14px;
  /* "Preview as another user" chrome. Deliberately amber — nothing else in the portal is, so an
     amber header can only mean one thing. Fixed rather than themed: it must not blend in. */
  --preview-ink:#b45309; --preview-on-ink:#fffaf3;
}
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:var(--ink);background:var(--bg)}
/* Scrollbars live in css/scroll.css — overlay pills, with a quiet native bar as the fallback. */
a{color:var(--accent);text-decoration:none}
h1,h2,h3{margin:0}

/* shell */
.shell,#app{display:grid;grid-template-columns:236px 1fr;grid-template-rows:64px 1fr;height:100%;transition:grid-template-columns .16s cubic-bezier(.2,.8,.2,1)}
/* logged-out / centered screens (auth) mount straight into #app — drop the sidebar grid so the card
   centers on the whole viewport, not the 236px column. 100vh because #app has only min-height (not a
   definite height), so the wrap's percentage min-height can't resolve and the gradient would stop short. */
#app:has(.center-wrap){display:block}
#app .center-wrap{min-height:100vh}
/* Give #app a definite height (not min-height) so the grid's 1fr row is bounded by the viewport and
   .main/.sidebar's own overflow:auto become the single scroll owners — otherwise the grid row grows
   with content, body scrolls too, and tall pages (e.g. What's New) show two scrollbars at once.
   Desktop only: mobile deliberately makes body the sole scroller (see theme.css @media 760px) and
   the auth screen (:has(.center-wrap)) must stay free to grow past the viewport, not get clipped. */
@media(min-width:761px){
  #app:not(:has(.center-wrap)){overflow:hidden}
}
.sidebar{grid-column:1;grid-row:2;background:var(--sidebar);color:var(--sidebar-ink);display:flex;flex-direction:column;padding:18px 14px;gap:4px;transition:transform .16s cubic-bezier(.2,.8,.2,1);overflow-y:auto}
.brand{display:flex;align-items:center;gap:11px;color:#fff;font-weight:700;margin-bottom:16px}
.brand small{display:block;font-size:10px;letter-spacing:1.5px;color:#7e85a6;font-weight:600}
.logo{display:inline-grid;place-items:center;width:34px;height:34px;border-radius:9px;background:var(--accent);color:#fff;font-size:20px;font-weight:700}
.navlink{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:9px;color:var(--sidebar-ink);font-size:14px;cursor:pointer}
.navlink:hover{background:var(--sidebar-soft);color:#fff}
.navlink.active{background:var(--accent);color:#fff}
.navlink .ic{width:18px;text-align:center;opacity:.9}
/* unread count on a nav row (Messages). Red pill sits hard right, with the row's icon brought to
   full opacity. Deliberately sets no text colour or weight — the designs already run the nav label
   at 700-800 and the light-sidebar ones (campus/care/focus/classic2) keep their own ink. */
.navlink.has-unread .ic{opacity:1}
.nav-badge{margin-left:auto;flex:0 0 auto;min-width:20px;height:20px;padding:0 6px;border-radius:999px;
  background:#e5484d;color:#fff;font-size:11px;font-weight:700;line-height:1;display:grid;place-items:center}
.navlink.active .nav-badge{background:#e5484d;color:#fff}   /* keep badge red on the accent-filled active row */
/* "NEW" flag on a nav row. Orange pill, hard right like the unread badge, but decorative rather
   than a count — aria-hidden so the row still reads as its plain label. */
.nav-new{margin-left:auto;margin-right:0;flex:0 0 auto;height:14px;padding:0 6px;border-radius:999px;
  background:#ea580c;color:#fff;font-size:8px;font-weight:800;letter-spacing:.4px;line-height:1;
  display:grid;place-items:center}
.navlink.active .nav-new{background:#ea580c;color:#fff}   /* stays orange on the accent-filled active row */
/* Purple variant carrying a short deadline instead of "NEW" — wider text, so let it size itself. */
.nav-new--until,.navlink.active .nav-new--until{background:#7c3aed;color:#fff;letter-spacing:.2px;text-transform:none}
/* "Opens in a new tab" arrow, for the handful of nav rows that leave the single-page app (Spectral
   ID and anything else registered with `external`). It takes the hard-right slot that the NEW chip
   normally holds; when a row carries both, the arrow goes right and the chip sits just left of it,
   so the arrow always reads as belonging to the row's edge. */
.nav-ext{margin-left:auto;flex:0 0 auto;font-size:11px;line-height:1;opacity:.75}
.nav-new+.nav-ext{margin-left:6px}   /* chip already took the auto margin — just sit beside it */
.navlink:hover .nav-ext{opacity:1}
.app-tile .nav-ext{position:absolute;bottom:6px;right:7px;margin-left:0}
.app-tile{position:relative}
.app-tile .nav-badge{position:absolute;top:6px;right:6px;margin-left:0}
.app-tile .nav-new{position:absolute;top:6px;right:6px;margin-left:0}
.spacer{flex:1}
.devswitch{padding:8px 11px;font-size:11px;color:#8a90ad}
.devswitch label{display:block;text-transform:uppercase;letter-spacing:.8px;margin-bottom:4px}
.devroles{display:flex;flex-wrap:wrap;gap:4px}
.devrole{flex:1 1 auto;background:var(--sidebar-soft);color:#c7ccdd;border:1px solid #343a5c;border-radius:7px;padding:5px 7px;font:inherit;font-size:11px;font-weight:700;cursor:pointer}
.devrole:hover{color:#fff;border-color:var(--accent)}
.devrole.on{background:var(--accent);border-color:var(--accent);color:#fff}
.devstep{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:5px;font-size:11px;color:#c7ccdd}
.devstep span{flex:1;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.devstep-b{background:var(--sidebar-soft);color:#c7ccdd;border:1px solid #343a5c;border-radius:6px;width:22px;height:22px;cursor:pointer;font-size:13px;line-height:1}
.devstep-b:hover{color:#fff;border-color:var(--accent)}
.theme-btn{flex:0 0 auto;width:38px;height:38px;display:grid;place-items:center;background:var(--sidebar-soft);border:1px solid #343a5c;border-radius:9px;font-size:17px;cursor:pointer}
.theme-btn:hover{border-color:var(--accent)}
.main{grid-column:2;grid-row:2;padding:26px 30px;overflow:auto}
.mobile-top{
  grid-column:1/-1;
  grid-row:1;
  position:sticky;
  top:0;
  z-index:1200;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:14px;
  height:64px;
  min-height:64px;
  padding:0 16px;
  border-bottom:1px solid var(--line);
  background:color-mix(in srgb,var(--panel) 94%,transparent);
  color:var(--ink);
  backdrop-filter:blur(14px);
  overflow:hidden;
}
.header-left,.header-actions{display:flex;align-items:center;gap:10px;min-width:0}
.header-left{flex:1}
.header-actions{flex:none}
.preview-roles{
  flex:0 1 auto;
  display:flex;
  align-items:center;
  gap:4px;
  min-width:0;
  max-width:100%;
  overflow:hidden;
  border:1px solid var(--line);
  background:color-mix(in srgb,var(--panel2) 78%,var(--panel));
  border-radius:12px;
  padding:3px;
}
.preview-role{
  flex:0 0 auto;
  min-height:28px;
  border:1px solid transparent;
  background:transparent;
  color:var(--dim);
  border-radius:9px;
  padding:4px 8px;
  font:inherit;
  font-size:11.5px;
  font-weight:850;
  white-space:nowrap;
  cursor:pointer;
}
.preview-role:hover{border-color:var(--accent);color:var(--accent)}
.preview-role.on{background:var(--accent);border-color:var(--accent);color:#fff}
.preview-student-step{display:flex;gap:2px;margin-left:2px}
.preview-step-b{
  width:26px;
  height:28px;
  border:1px solid var(--line);
  background:var(--panel);
  color:var(--ink);
  border-radius:8px;
  font:inherit;
  font-weight:900;
  cursor:pointer;
}
.preview-step-b:hover{border-color:var(--accent);color:var(--accent)}
/* ---- "Preview as…" (admin only) -----------------------------------------------------------
   The box sits immediately right of the admin's own name. While a preview is on, the whole header
   goes amber-striped so no admin ever mistakes a student's screen for their own. */
.preview-as{
  flex:0 1 auto;
  display:flex;
  align-items:center;
  gap:5px;
  min-width:0;
}
.preview-as-input{
  width:150px;
  min-width:0;
  height:38px;
  padding:0 11px;
  border:1px solid var(--line);
  border-radius:11px;
  background:var(--panel2);
  color:var(--ink);
  font:inherit;
  font-size:12.5px;
  font-weight:700;
  transition:width .16s cubic-bezier(.2,.8,.2,1);
}
.preview-as-input::placeholder{color:var(--dim);font-weight:750}
.preview-as-input:focus{outline:none;width:225px;border-color:var(--accent);background:var(--panel)}
.preview-as-input::-webkit-search-cancel-button{-webkit-appearance:none}
.preview-as-btn{
  flex:none;
  width:38px;
  height:38px;
  display:grid;
  place-items:center;
  border:1px solid var(--line);
  border-radius:11px;
  background:var(--panel2);
  color:var(--ink);
  font:inherit;
  font-size:15px;
  line-height:1;
  cursor:pointer;
}
.preview-as-btn:hover{border-color:var(--accent);color:var(--accent)}
.preview-as-exit{font-size:14px;font-weight:900}
/* Who you are looking at, stated in the header itself rather than only in the name slot. */
.preview-as-badge{
  display:inline-flex;
  align-items:center;
  gap:7px;
  max-width:270px;
  height:38px;
  padding:0 11px;
  border-radius:11px;
  background:var(--preview-ink);
  color:var(--preview-on-ink);
  font-size:10.5px;
  font-weight:900;
  letter-spacing:.9px;
  text-transform:uppercase;
  white-space:nowrap;
}
.preview-as-badge>span{flex:none}
/* The NAME is the point of the badge, so it is the last thing allowed to shrink. */
.preview-as-badge b{
  min-width:4.5rem;
  overflow:hidden;
  text-overflow:ellipsis;
  font-size:12.5px;
  font-weight:850;
  letter-spacing:0;
  text-transform:none;
}
.preview-as-badge i{
  flex:none;
  padding:2px 6px;
  border-radius:6px;
  background:color-mix(in srgb,var(--preview-on-ink) 26%,transparent);
  font-style:normal;
  font-size:10px;
  letter-spacing:.6px;
}
/* The unmissable part: amber stripes + a heavy amber underline across the whole bar.
   `html[data-design]` is not decoration: theme.css re-declares the header's background and
   border-bottom under @media(max-width:760px) with a `html:not([data-design=…]) .mobile-top`
   selector, which outspecifies a bare `.mobile-top.preview-on` — the stripes silently vanished on
   phones until this matched it. Keep the prefix on any rule here that sets those two properties. */
html[data-design] .mobile-top.preview-on{
  background:
    repeating-linear-gradient(135deg,
      color-mix(in srgb,var(--preview-ink) 13%,transparent) 0 10px,
      transparent 10px 20px),
    color-mix(in srgb,var(--preview-ink) 9%,var(--panel));
  border-bottom:3px solid var(--preview-ink);
}
/* The name slot still names the admin (who is really signed in), so it stays sober — the amber
   badge next to it is what says whose view you are looking at. */
.mobile-top.preview-on .header-user{color:color-mix(in srgb,var(--preview-ink) 70%,var(--ink))}
.mobile-top.preview-on .mobile-brand small::after{content:" · PREVIEW";color:var(--preview-ink);font-weight:900}
.mobile-top.preview-on .preview-as-input,
.mobile-top.preview-on .preview-as-btn{border-color:color-mix(in srgb,var(--preview-ink) 45%,var(--line))}
.mobile-top.preview-on .preview-as-exit{background:var(--preview-ink);border-color:var(--preview-ink);color:var(--preview-on-ink)}
/* Portaled to #modalMount (the header is overflow:hidden), positioned to the input in JS. */
.preview-as-menu{
  position:fixed;
  z-index:1400;
  max-height:min(52vh,340px);
  overflow:auto;
  padding:6px;
  border:1px solid var(--line);
  border-radius:13px;
  background:var(--panel);
  box-shadow:0 18px 40px -18px rgba(11,14,26,.45);
}
.preview-as-opt{
  display:flex;
  flex-direction:column;
  gap:2px;
  width:100%;
  padding:8px 10px;
  border:1px solid transparent;
  border-radius:9px;
  background:none;
  color:var(--ink);
  font:inherit;
  text-align:left;
  cursor:pointer;
}
.preview-as-opt:hover,.preview-as-opt.on{background:var(--panel2);border-color:var(--accent)}
.preview-as-opt-name{font-size:13px;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.preview-as-opt-meta{color:var(--dim);font-size:11px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.preview-as-note{padding:9px 10px;color:var(--dim);font-size:12px;font-weight:750}
.header-user{
  min-width:0;
  max-width:190px;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  color:var(--dim);
  font-size:12.5px;
  font-weight:750;
}
/* "2026 T3W5" — current term + week, sits just left of Settings. Hidden when empty (holidays). */
.header-termweek{
  flex:none;
  display:inline-flex;
  align-items:center;
  height:38px;
  padding:0 10px;
  border:1px solid var(--line);
  border-radius:11px;
  background:var(--panel2);
  color:var(--dim);
  font-size:12.5px;
  font-weight:800;
  letter-spacing:.3px;
  white-space:nowrap;
}
.header-termweek:empty{display:none}
.mobile-brand{
  display:flex;
  align-items:center;
  gap:9px;
  border:0;
  background:none;
  color:var(--ink);
  padding:0;
  font:inherit;
  font-weight:800;
  line-height:1.05;
  text-align:left;
  cursor:pointer;
}
.mobile-brand:hover{color:var(--accent)}
.mobile-brand:focus-visible{outline:2px solid var(--accent);outline-offset:4px;border-radius:10px}
.mobile-brand small{display:block;font-size:9px;letter-spacing:1.4px;color:var(--dim);margin-top:3px}
.menu-btn,.header-control{
  min-height:38px;
  height:38px;
  border:1px solid var(--line);
  background:var(--panel2);
  color:var(--ink);
  border-radius:11px;
  font:inherit;
  font-size:13.5px;
  font-weight:750;
  cursor:pointer;
}
.menu-btn{width:40px;display:grid;place-items:center;flex:none}
.header-control{display:inline-flex;align-items:center;gap:7px;padding:8px 10px}
.header-control:hover,.menu-btn:hover{border-color:var(--accent);color:var(--accent)}
.header-control .line-ico,.menu-btn .line-ico{width:18px;height:18px}
.menu-btn .line-ico{transition:transform .12s cubic-bezier(.2,.8,.2,1)}
#app.sidebar-collapsed .menu-btn .line-ico{transform:rotate(-180deg)}
#app.menu-open .menu-btn .line-ico{transform:rotate(-180deg)}
@media(max-width:1080px){
  .header-left{flex:1 1 auto}
  .header-actions{gap:7px}
  .mobile-brand{display:none}
  .header-user{max-width:132px}
  .preview-roles{gap:2px;padding:2px}
  .preview-role{
    width:31px;
    padding:4px 0;
    overflow:hidden;
    font-size:0;
    text-align:center;
  }
  .preview-role::after{content:attr(data-short);font-size:11px}
  .preview-step-b{width:24px}
  .header-control{width:40px;padding:8px;justify-content:center}
  .header-control .navtxt{display:none}
  .preview-as-input{width:118px}
  .preview-as-input:focus{width:180px}
  /* Narrow: the word "Previewing" goes, the amber bar already says it. Name + role stay. */
  .preview-as-badge{max-width:200px;padding:0 9px;gap:6px}
  .preview-as-badge>span{display:none}
}
@media(max-width:700px){
  .header-actions{gap:6px}
  .header-user{max-width:112px;font-size:12px}
  .header-termweek{padding:0 8px;font-size:11.5px}
  /* Phone: keep the box and the X (the way out); the dice gives up the room. While previewing, the
     badge says who — so the box shrinks to an icon-width prompt that widens on focus. */
  .preview-as{gap:4px}
  .preview-as-input{width:104px}
  .preview-as-input:focus{width:min(62vw,225px)}
  .preview-as.on .preview-as-input{width:76px}
  .preview-as.on .preview-as-badge{max-width:150px}
  .preview-as-badge i{display:none}
  .preview-as #previewAsDice{display:none}
  .preview-role{width:28px}
  .preview-role::after{font-size:10.5px}
}
@media(max-width:520px){
  .header-user{display:none}
  /* Small phone, mid-preview: the badge and the X are all that fit alongside the fixed controls.
     Pick someone else by exiting first — the box comes back the moment the preview is off. */
  .preview-as.on .preview-as-input{display:none}
  .preview-as.on .preview-as-badge{max-width:112px}
  /* Something has to go for the badge to fit next to Settings and Sign out: the term chip is the
     one piece of the bar that is pure decoration. It comes straight back on exit. */
  .mobile-top.preview-on .header-termweek{display:none}
}
@media(max-width:460px){
  .preview-role{width:25px}
  .preview-step-b{width:22px}
}

/* topbar */
.topbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:22px;gap:14px;flex-wrap:wrap}
.topbar h2{font-size:22px}
.topbar .sub{color:var(--dim);font-size:13.5px;margin-top:2px}
.topbar .sub:empty{display:none}
.toolbar{display:flex;gap:9px;align-items:center;flex-wrap:wrap}
/* Back-button pages: toolbar moves to the left of the title on the same row.
   Mark a back button with data-back and it lands on the left — this used to be
   an allowlist of ids, which silently missed every new back button. */
.topbar:has(.toolbar .btn[data-back]){
  justify-content:flex-start;
  align-items:center;
}
.topbar:has(.toolbar .btn[data-back]) .toolbar{
  order:-1;
  width:auto;
  flex-shrink:0;
}

/* buttons */
.btn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--panel);color:var(--ink);padding:9px 14px;border-radius:10px;font:inherit;font-size:14px;font-weight:600;cursor:pointer;transition:.12s}
.btn[hidden]{display:none}   /* the display above beats the UA's [hidden] rule, so say it here */
.btn:hover{border-color:var(--accent);color:var(--accent)}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}
.btn.primary:hover{background:var(--accent2);border-color:var(--accent2);color:#fff}
.btn.sm{padding:6px 11px;font-size:13px}
.btn.danger{color:var(--err)}
.btn.danger:hover{border-color:var(--err);background:#fdecec}
.btn:disabled{opacity:.55;cursor:default}
/* ---- filter chips: ONE look, everywhere.  app.css owns the appearance; the page-specific
   names (.st-fchip on Students, .qan-range on the analytics/homework range bars,
   .set-year-chip in Settings) are aliases so no view markup had to change.
   Layout primitives: .filters > .filterrow > (.filterlabel + .chiprow). */
.chip,.st-fchip,.qan-range,.set-year-chip{appearance:none;border:1px solid var(--line);background:var(--panel);color:var(--dim);padding:6px 14px;border-radius:999px;font:inherit;font-size:13px;font-weight:600;line-height:1.25;cursor:pointer;white-space:nowrap}
.chip:hover,.st-fchip:hover,.qan-range:hover,.set-year-chip:hover{border-color:var(--accent);color:var(--accent)}
.chip.on,.st-fchip.on,.qan-range.on,.set-year-chip.on{background:var(--accent);border-color:var(--accent);color:#fff}
.chip:disabled,.st-fchip:disabled,.qan-range:disabled{opacity:.5;cursor:default}
.chip:focus-visible,.st-fchip:focus-visible,.qan-range:focus-visible,.set-year-chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

.filters,.st-filters{display:flex;flex-direction:column;gap:8px;margin:2px 0 14px}
.filterrow,.st-filterrow{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.filterlabel,.st-filterlabel{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--dim);min-width:48px}
.chiprow,.st-chiprow{display:flex;gap:7px;flex-wrap:wrap;min-width:0}
/* chip rows scroll rather than stack into a wall of chips on a phone */
@media(max-width:560px){
  .filters,.st-filters{gap:6px;margin-bottom:12px}
  .filterrow,.st-filterrow{align-items:flex-start;gap:4px;flex-direction:column}
  .filterlabel,.st-filterlabel{min-width:0;font-size:11px}
  .chiprow,.st-chiprow{gap:6px;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;width:100%;padding-bottom:2px}
  .chiprow::-webkit-scrollbar,.st-chiprow::-webkit-scrollbar{display:none}
  .chip,.st-fchip,.qan-range,.set-year-chip{padding:7px 12px;font-size:12.5px;flex:0 0 auto}
}

/* fields */
.field{margin-bottom:14px}
.field label{display:block;font-size:13px;font-weight:600;margin-bottom:5px}
.field .hint,.hint{font-size:12px;color:var(--dim);margin-top:4px}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
input,select,textarea{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:9px;font:inherit;background:#fff;color:var(--ink)}
textarea{resize:vertical}
input:focus,select:focus,textarea:focus{outline:2px solid var(--accent-soft);border-color:var(--accent)}

/* cards / panels */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:16px;margin-bottom:20px}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px}
.stat .n{font-size:30px;font-weight:800;color:var(--accent)}
.stat .l{color:var(--dim);font-size:13px}
.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:18px;overflow:hidden}
.panel-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 18px;border-bottom:1px solid var(--line)}
.panel-head h3{font-size:15px}
.panel-body{padding:0}
/* Panel bodies are deliberately unpadded so tables and lists can run to the card edge. Anything
   else dropped straight into a body therefore sits out of line with the heading above it — wrap
   it in .panel-pad, which matches .panel-head's own 14px/18px. (Several views predate this and
   roll their own equivalent: .sh-pad, .qan-scope, .cls-att-hint, .la-panel-pad.) */
.panel-pad{padding:14px 18px}
/* A padded block sitting directly on top of a table hands its bottom spacing to the table. */
.panel-pad:has(+ .qan-tablewrap),.panel-pad:has(+ table){padding-bottom:0}
.panel-pad>:last-child{margin-bottom:0}
.panel-body table{width:100%}
/* Table-only panel bodies scroll sideways instead of stretching the page (see KC.panel). */
.panel-body-table{overflow-x:auto;-webkit-overflow-scrolling:touch}
@media(max-width:760px){
  .panel-body-table>table{min-width:560px}
  /* Give the row its air back once the table is allowed to be wider than the screen. */
  .panel-body-table th,.panel-body-table td{padding-left:14px;padding-right:14px}
}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-bottom:18px}
@media(max-width:900px){.two-col{grid-template-columns:1fr}}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}

/* badges + tables */
.badge{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:700}
.badge.year,.badge.info{background:var(--accent-soft);color:var(--accent)}
.badge.code{background:#eef7ee;color:var(--ok);font-family:ui-monospace,monospace;letter-spacing:1px}
.badge.muted{background:var(--panel2);color:var(--dim)}
.badge.none,.badge.warn{background:#fef3e2;color:var(--warn)}
table{border-collapse:collapse}
th{text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--dim);padding:11px 18px;border-bottom:1px solid var(--line)}
/* Click-to-sort headings (KC.autoSortTables in js/kc.js). The arrow span is always present so
   the heading does not shift sideways when a column becomes the sorted one. */
th.th-sortable{cursor:pointer;user-select:none;white-space:nowrap}
th.th-sortable:hover{color:var(--ink)}
th.th-sortable.on{color:var(--accent)}
th.th-sortable:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
th.th-sortable .th-arrow{display:inline-block;min-width:9px;margin-left:4px;font-size:9px;vertical-align:1px;opacity:.75}
td{padding:12px 18px;border-bottom:1px solid var(--line);font-size:14px}
tr:last-child td{border-bottom:0}
.nm{font-weight:600}
/* clickable table rows (used instead of per-row Open/View buttons) */
tr.row-link{cursor:pointer;transition:background .12s}
tr.row-link:hover,tr.row-link:focus-visible{background:var(--accent-soft)}
tr.row-link:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.empty{padding:38px 34px;text-align:center;color:var(--dim);font-size:14px;display:flex;flex-direction:column;align-items:center;gap:12px}
.empty-art{width:46px;height:54px;color:var(--accent);opacity:.85}

/* modal + toast */
.modal-bg{position:fixed;inset:0;background:#1b223066;display:grid;place-items:center;z-index:1000;padding:20px}
.modal{background:var(--panel);border-radius:var(--radius);box-shadow:0 24px 60px #0006;width:100%;max-width:520px;max-height:88vh;overflow:auto}
.modal-head{display:flex;align-items:center;justify-content:space-between;padding:16px 18px;border-bottom:1px solid var(--line)}
.modal-head .x{border:0;background:none;font-size:22px;cursor:pointer;color:var(--dim)}
.modal-body{padding:18px}
.modal-foot{display:flex;justify-content:flex-end;gap:9px;padding:14px 18px;border-top:1px solid var(--line)}
#toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(12px);background:#171a2b;color:#fff;padding:11px 18px;border-radius:10px;font-size:14px;opacity:0;pointer-events:none;transition:.2s;z-index:2000}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.access-denied-notice{display:flex;align-items:center;gap:10px;background:color-mix(in srgb,var(--warn) 12%,var(--panel));color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 30%,var(--line));border-radius:var(--radius);padding:11px 16px;font-size:14px;margin:16px 20px 0;transition:opacity .6s}
.access-denied-notice.fade-out{opacity:0;pointer-events:none}
.access-denied-close{margin-left:auto;background:none;border:none;cursor:pointer;font-size:20px;line-height:1;color:inherit;padding:0 2px;opacity:.6}
.access-denied-close:hover{opacity:1}

/* tab host: in-page tabs that hold several pages under one menu item (KC.tabHost) */
.tabbar{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:18px;flex-wrap:wrap}
.tabbar .tab{border:0;background:none;color:var(--dim);cursor:pointer;font:inherit;font-weight:600;
  font-size:14px;padding:10px 16px;border-bottom:2px solid transparent;margin-bottom:-1px}
.tabbar .tab:hover{color:var(--ink)}
.tabbar .tab.on{color:var(--accent);border-bottom-color:var(--accent)}
.tabcontent{min-width:0}
.home-ann{margin-top:8px}

/* ---- loading skeletons ----
   Every page paints its chrome (head, panel shells, nav) synchronously and drops these where the
   data will land, so navigation is instant and the content fills in behind it. Shapes deliberately
   match the real thing — a tile skeleton is tile-sized, a table skeleton has the same row height —
   so the swap-in doesn't make the page jump. See KC.skel* in js/kc.js for the markup helpers. */
@keyframes kc-shimmer{to{transform:translateX(100%)}}
.kc-skel{position:relative;overflow:hidden;border:1px solid var(--line);border-radius:10px;
  /* --panel2 is a hair off --bg, so a skeleton sitting on the page background all but vanished;
     mixing toward --line keeps the shape legible both on the page and inside a panel. */
  background:color-mix(in srgb,var(--line) 55%,var(--panel));}
.kc-skel::after{
  content:"";position:absolute;inset:0;transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--ink) 10%,transparent),transparent);
  animation:kc-shimmer 1.25s infinite;
}
@media(prefers-reduced-motion:reduce){.kc-skel::after{animation:none}}
/* A bare bar (no panel chrome) — for text lines inside a shell that is already drawn. */
.kc-skel-line{border:0;background:var(--line);border-radius:6px;height:12px}
.kc-skel-stack{display:grid;gap:10px}
.kc-skel-card{height:88px}
.kc-skel-tile{height:132px;border-radius:12px}
.kc-skel-row{height:44px;border-radius:8px}
/* Matches the tile grids on the home screens so the placeholder occupies the real layout. */
.kc-skel-tiles{display:grid;gap:14px;grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}
.view-loading{display:grid;gap:12px;padding:4px 0 20px}

/* auth / centered card (used by auth + student message-style screens) */
.center-wrap{min-height:100%;display:grid;place-items:center;padding:24px;background:radial-gradient(120% 90% at 50% -10%,#2a2f55 0%,#171a2b 55%,#0e1020 100%)}
.card{background:var(--panel);border-radius:18px;box-shadow:var(--shadow);padding:30px;width:100%;max-width:440px}

/* On mobile the sidebar is a fixed off-canvas drawer (see theme.css) — it must NOT be re-laid-out
   as an in-flow wrapping row here, or its text bleeds through the content when "closed". */
@media(max-width:760px){#app,.shell{grid-template-columns:1fr}.devswitch{display:none}.main{padding:18px}}

/* Mobile: no pinch-zoom, no text selection; re-allow in fields */
@media(max-width:760px){
  body{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
  input,textarea,select,[contenteditable="true"]{-webkit-user-select:text;user-select:text}
  .main{min-height:calc(100dvh - 60px)}
}

/* dot-point / curriculum code badges: hovering reveals the full text via the native title tooltip */
.badge[title],.cur-dot-code[title],.cur-textbook-dots span[title]{cursor:help}

/* ---- bar charts: the house rule ------------------------------------------------------------
   STANDING RULE: a bar chart never squeezes its bars to fit. It shows a fixed number of bars in
   view (--vis), scrolls sideways for the rest, and fades whichever edge still has chart behind it.
   Three weeks in view is the default for a weekly chart; pick --vis to suit the grain.

   Markup:
     <div class="kc-bars" data-barscroll style="--vis:3">
       <div class="kc-bars-scroll"><div class="my-bar-row">...one .kc-bar-item per bar...</div></div>
     </div>
   then call KC.barScroll(root) after painting (it opens on the most recent bars; pass
   data-barscroll="start" to open on the oldest instead).

   A bar is a --vis-th of the VISIBLE width (100cqw = the scroller, not the row inside it), so the
   chart resizes with the panel with no measuring in JS and no redraw. --bar-min is the floor: past
   the point where --vis legible bars no longer fit, fewer show and the rest is scrollback.
   teacher-home's activity chart is the older, bespoke implementation of this same rule (it carries
   a y-axis and gridlines that must NOT scroll); anything new uses the classes here. */
.kc-bars{position:relative;min-width:0;--vis:3;--gap:10px;--bar-min:34px}
.kc-bars-scroll{overflow-x:auto;overflow-y:hidden;container-type:inline-size;
  scrollbar-width:thin;overscroll-behavior-x:contain}
.kc-bar-item{flex:1 0 auto;min-width:var(--bar-min)}
@supports (container-type:inline-size){
  /* Behind @supports because a var() a browser cannot parse computes to min-width:auto rather than
     falling back to the declaration above. */
  .kc-bar-item{min-width:max(var(--bar-min),calc((100cqw - (var(--vis) - 1) * var(--gap)) / var(--vis)))}
}
/* Fades, lit by KC.barScroll only on a side that has more chart behind it — a chart that fits
   shows neither. Over the bars, under any tooltip. */
.kc-bars::before,.kc-bars::after{content:"";position:absolute;top:0;bottom:0;width:34px;
  pointer-events:none;opacity:0;transition:opacity .15s ease;z-index:2}
.kc-bars::before{left:0;background:linear-gradient(90deg,var(--panel),transparent)}
.kc-bars::after{right:0;background:linear-gradient(270deg,var(--panel),transparent)}
.kc-bars.can-left::before{opacity:1}
.kc-bars.can-right::after{opacity:1}

/* ---- connection banner (app.js netBanner): offline warning / back-online flash ---- */
#netBanner{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:3000;
  max-width:min(92vw,480px);padding:9px 16px;border-radius:999px;font-size:13.5px;font-weight:600;
  box-shadow:var(--shadow);border:1px solid var(--line);text-align:center}
#netBanner[hidden]{display:none}
#netBanner.net-off{background:#7a2e2e;color:#f6efe9;border-color:#8f3a3a}
#netBanner.net-on{background:#1e7a4d;color:#f2f7f0;border-color:#2b8a5c}

/* ---- print: the page's content, nothing of the app around it ----
   Sidebar, header, buttons and banners go; the main pane loses its scroll cage so the whole page
   prints instead of one viewport's worth. Panels keep a hairline border so sections stay legible
   in black and white. */
@media print{
  .sidebar,.mobile-top,.side-scrim,.devswitch,#netBanner,#toast,.btn,button{display:none !important}
  #app,.shell{display:block !important}
  .main{padding:0 !important;overflow:visible !important;height:auto !important}
  body{background:#fff !important}
  .panel{box-shadow:none !important;border:1px solid #999 !important;break-inside:avoid}
}

/* css/scroll.css */
/* Scrollbars, portal-wide.  OWNER: shared chrome — pairs with js/kc-scroll.js.

   Two layers, and which one you get depends on whether the script is running:

     FALLBACK (no script, or a browser that will not play along) — the native bar, made quiet: the
     gutter is reserved so a list that grows past its box never reflows the page, the track is
     invisible, and the thumb only colours in while the pointer is inside the scroller.

     OVERLAY (js/kc-scroll.js is live; it puts .kc-ovsb on <html>) — native bars are switched off
     entirely and the script draws a pill floating OVER the content. No gutter, no reflow, and the
     same pill on every platform instead of three different native bars.

   Sideways tile rows (.hw-row and the chip rails) get neither: a bar is the wrong signal on a row
   of tiles, so the row simply fades out where it continues. See .kc-rail below. */

/* ---------------------------------------------------------------- fallback: quiet native bars */
*{scrollbar-width:thin;scrollbar-color:transparent transparent}
*::-webkit-scrollbar{width:11px;height:11px}
*::-webkit-scrollbar-track{background:transparent}
/* The 3px transparent border + content-box clip is what makes the thumb read as a floating pill
   rather than as a bar filling a groove. */
*::-webkit-scrollbar-thumb{background:transparent;border:3px solid transparent;background-clip:content-box;border-radius:999px}
html:not(.kc-ovsb) .main,
html:not(.kc-ovsb) .panel-body-table,
html:not(.kc-ovsb) .sidebar,
html:not(.kc-ovsb) .modal-body{scrollbar-gutter:stable}
:hover > *::-webkit-scrollbar-thumb,
*:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--accent) 42%,var(--line));background-clip:content-box}
*:hover{scrollbar-color:color-mix(in srgb,var(--accent) 42%,var(--line)) transparent}

/* ------------------------------------------------------------------- overlay: no native bars */
html.kc-ovsb *{scrollbar-width:none}
html.kc-ovsb *::-webkit-scrollbar{width:0;height:0}

/* The pill itself. One vertical and one horizontal element for the whole page, moved to whichever
   scroller is being used — position:fixed, so no view's layout is touched and nothing has to be a
   positioning context. Its tone is set by the script from the surface it sits on, so it stays
   visible on the dark sidebar in a light theme and vice versa. */
.kc-sb{
  position:fixed;z-index:2147483000;border-radius:999px;
  opacity:0;pointer-events:none;
  background:rgba(20,26,40,.38);
  box-shadow:0 0 0 1px rgba(255,255,255,.5);
  transition:opacity .26s ease,background-color .15s ease;
}
.kc-sb--light{background:rgba(255,255,255,.42);box-shadow:0 0 0 1px rgba(0,0,0,.28)}
.kc-sb--y{width:7px}
.kc-sb--x{height:7px}
.kc-sb.on{opacity:1;pointer-events:auto}
.kc-sb:hover,.kc-sb.dragging{background:rgba(20,26,40,.62)}
.kc-sb--light:hover,.kc-sb--light.dragging{background:rgba(255,255,255,.68)}
/* Grabbing the pill widens it, so the thing under the cursor is the thing being dragged. */
.kc-sb--y:hover,.kc-sb--y.dragging{width:10px}
.kc-sb--x:hover,.kc-sb--x.dragging{height:10px}
.kc-sb.dragging{transition:none}
html.kc-sb-dragging{user-select:none}

/* --------------------------------------------------------------- sideways rows: no bar at all */
/* The mask fades the ROW's own pixels out at the edge, so it works over any panel colour and in
   any theme — a gradient overlay would have to know the background. Only the side that actually
   has more content fades: the script sets .at-start / .at-end. */
.kc-rail{scrollbar-width:none}
/* Grab-and-pull (js/kc-scroll.js). Snapping has to be off while the hand is moving or the row
   yanks itself to the nearest tile under the pointer; smooth scrolling likewise, since the pull
   already sets scrollLeft every frame. The closed hand is the only cursor the row shows — the
   tiles keep their pointer until the pull actually engages. */
.kc-rail-pulling{scroll-snap-type:none;scroll-behavior:auto;cursor:grabbing}
.kc-rail-pulling *{cursor:grabbing !important;pointer-events:none}
.kc-rail::-webkit-scrollbar{width:0;height:0}
.kc-rail.at-end{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 52px),transparent);mask-image:linear-gradient(90deg,#000 calc(100% - 52px),transparent)}
.kc-rail.at-start{-webkit-mask-image:linear-gradient(90deg,transparent,#000 52px);mask-image:linear-gradient(90deg,transparent,#000 52px)}
.kc-rail.at-start.at-end{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 52px,#000 calc(100% - 52px),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 52px,#000 calc(100% - 52px),transparent);
}

/* The nudge buttons are shared, fixed-position elements too — a row never has to make room for
   them, and a row that fits has none. Mouse only: a touch user just swipes. */
.kc-nudge{
  position:fixed;z-index:2147483000;width:30px;height:30px;border-radius:999px;
  display:grid;place-items:center;padding:0;
  border:1px solid var(--line);background:var(--panel);color:var(--ink);
  font:inherit;font-size:15px;font-weight:800;line-height:1;
  box-shadow:0 6px 18px rgba(20,26,40,.18);
  opacity:0;pointer-events:none;transition:opacity .2s ease,transform .12s ease;
}
.kc-nudge.on{opacity:1;pointer-events:auto;cursor:pointer}
.kc-nudge:hover{transform:scale(1.06);border-color:var(--accent);color:var(--accent)}

/* Chrome renders position:fixed elements on the first printed page; a pill frozen across a
   printout is nobody's idea of a scrollbar. Same for the fade, which would print as a real fade. */
@media print{
  .kc-sb,.kc-nudge{display:none !important}
  .kc-rail{-webkit-mask-image:none !important;mask-image:none !important;overflow:visible !important}
}
@media (prefers-reduced-motion:reduce){
  .kc-sb,.kc-nudge{transition:none}
}
/* Coarse pointers already get auto-hiding overlay bars from the OS, and there is no hover to
   reveal a pill with — so the script stands down and these are never shown. */
@media (pointer:coarse){
  .kc-sb,.kc-nudge{display:none}
}

/* css/auth.css */
/* Task 2 (auth) styles. Additive only — do not edit css/app.css.
   Builds on .center-wrap + .card from app.css; everything here is auth-prefixed. */

.auth-card{max-width:420px}

/* brand header (dark-on-light variant of the sidebar .brand) */
.auth-brand{display:flex;align-items:center;gap:11px;color:var(--ink);font-weight:700;font-size:17px;margin-bottom:18px}
.auth-brand small{display:block;font-size:10px;letter-spacing:1.5px;color:var(--dim);font-weight:600}

.auth-h{font-size:19px;margin:0 0 4px}
.auth-sub{color:var(--dim);font-size:13.5px;margin:0 0 16px;line-height:1.55}
.auth-sub b{color:var(--ink)}

/* sign-in / create-account segmented tabs */
.auth-tabs{display:grid;grid-template-columns:1fr 1fr;gap:4px;background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:4px;margin-bottom:18px}
.auth-tab{border:0;background:none;padding:8px 6px;border-radius:8px;font:inherit;font-size:13.5px;font-weight:600;color:var(--dim);cursor:pointer;transition:.12s}
.auth-tab:hover{color:var(--accent)}
.auth-tab.on{background:var(--panel);color:var(--accent);box-shadow:0 1px 4px #1b223018}

/* account-type picker on the sign-up form */
.auth-types{display:grid;grid-template-columns:repeat(4,1fr);gap:4px;background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:4px}
.auth-type{border:0;background:none;padding:8px 4px;border-radius:8px;font:inherit;font-size:12.5px;font-weight:600;color:var(--dim);cursor:pointer;transition:.12s}
.auth-type:hover{color:var(--accent)}
.auth-type.on{background:var(--panel);color:var(--accent);box-shadow:0 1px 4px #1b223018}

/* inline alerts */
.auth-alert{border-radius:10px;padding:10px 13px;font-size:13.5px;line-height:1.5;margin-bottom:14px}
.auth-alert.err{background:#fdecec;color:var(--err);border:1px solid #f5c8c8}
.auth-alert.ok{background:#eef7ee;color:var(--ok);border:1px solid #cfe8cf}

/* full-width submit */
.auth-submit{width:100%;justify-content:center;padding:11px 14px;margin-top:2px}

/* password show/hide */
.auth-passwrap{position:relative}
.auth-passwrap input{padding-right:62px}
.auth-eye{position:absolute;right:5px;top:50%;transform:translateY(-50%);border:0;background:none;color:var(--accent);font-size:12.5px;font-weight:700;cursor:pointer;padding:6px 8px;border-radius:7px}
.auth-eye:hover{background:var(--accent-soft)}

/* join-code box */
input.auth-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:19px;font-weight:700;letter-spacing:5px;text-align:center;text-transform:uppercase;padding:12px}
input.auth-code::placeholder{letter-spacing:2px;font-weight:500;font-size:14px;text-transform:none}

/* footer links */
.auth-links{margin-top:16px;text-align:center;font-size:13.5px;color:var(--dim)}
.auth-links a{cursor:pointer;font-weight:600}
.auth-links a:hover{text-decoration:underline}

/* awaiting-approval */
.auth-pending{text-align:center;padding:4px 0}
.auth-pending-ic{width:56px;height:56px;border-radius:50%;background:var(--accent-soft);display:grid;place-items:center;font-size:26px;margin:0 auto 12px}

/* demo-mode hint */
.auth-demo{margin-top:18px;border-top:1px dashed var(--line);padding-top:12px;font-size:12px;color:var(--dim);line-height:1.7}
.auth-demo code{background:var(--panel2);border:1px solid var(--line);border-radius:5px;padding:1px 6px;font-size:11px;font-family:ui-monospace,monospace}

/* preview access code (live login screen) */
.auth-demo-access{margin-top:16px;border-top:1px dashed var(--line);padding-top:12px;text-align:center}
.auth-demo-access [data-demo-toggle]{cursor:pointer;font-size:12.5px;font-weight:600;color:var(--dim)}
.auth-demo-access [data-demo-toggle]:hover{text-decoration:underline;color:var(--accent)}
.auth-demo-row{display:flex;gap:8px;margin-top:10px}
.auth-demo-row input.auth-code{flex:1;font-size:16px;letter-spacing:3px;padding:10px}
.auth-demo-row .btn{white-space:nowrap}

/* mobile */
@media(max-width:480px){
  .auth-card{padding:22px}
  .auth-types{grid-template-columns:repeat(2,1fr)}
  .auth-wrap{padding:14px}
}

/* css/teacher.css */
/* Task 3 (teacher dashboard/classes) styles. Additive only — do not edit css/app.css. */

/* dashboard: quick links */
.quick-links{display:flex;gap:10px;flex-wrap:wrap;padding:16px 18px}

/* dashboard: stat cards on a phone.
   The shared .cards grid (auto-fit, minmax(150px,1fr)) gives two columns on a 375 px screen, and
   every tile in a row is then stretched to the tallest one in it — so the five-line "Questions
   completed…" label dragged "Classes" out to a near-empty 350 px box, and an odd tile count left a
   hole beside "Pending approvals". A dashboard of one-digit numbers took most of a phone screen
   before the chart even started. One column of compact rows instead: label on the left, number on
   the right. Each tile is its own grid, so a left-hand number column could only be lined up by
   fixing its width — and a fixed width either clips the five-figure questions count or wastes the
   room. Right-aligned against the card edge, every figure lines up down the page whatever its
   length, and a long one eats into its own row's slack instead of shifting anything. */
@media (max-width:700px){
  #homePrivate .cards{grid-template-columns:1fr;gap:10px;margin-bottom:14px}
  #homePrivate .stat{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;
    gap:0 14px;padding:13px 16px}
  #homePrivate .stat .n{grid-column:2;grid-row:1;font-size:24px;line-height:1.1;
    display:flex;align-items:center;gap:7px;white-space:nowrap}
  #homePrivate .stat .n .online-dot{flex:0 0 auto}
  /* The masked '•••' stands in for the number. Everything is right-aligned here, so the hidden
     number is taken out of flow entirely rather than left holding its width (which would strand the
     dots mid-row); the dots land exactly where the number will, so nothing moves on reveal. */
  #homePrivate.masked .stat .n::after{font-size:20px}
  #homePrivate.masked .stat .n .val{display:none}
  #homePrivate .stat .l{grid-column:1;grid-row:1;font-size:13px;line-height:1.35}
}

/* dashboard: full-width pending-approvals banner (only rendered when approvals exist) */
.home-pending{margin-bottom:18px}
.home-pending .panel{margin-bottom:0;border-color:color-mix(in srgb,var(--warn) 45%,var(--line));box-shadow:0 0 0 1px color-mix(in srgb,var(--warn) 20%,transparent),var(--shadow)}
.home-pending .panel-head{background:color-mix(in srgb,var(--warn) 9%,transparent)}
.home-pend-row{cursor:pointer;transition:background .12s}
.home-pend-row:hover,.home-pend-row:focus-visible{background:color-mix(in srgb,var(--warn) 8%,transparent)}
.home-pend-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* role badge + the class chips a code-joined signup brings with it */
.home-pend-meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center}

/* assistant task queue */
.staff-task-form{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  gap:10px;
  padding:16px 18px 10px;
}
.staff-task-count{
  padding:0 18px 12px;
  color:var(--dim);
  font-size:12.5px;
  font-weight:750;
}
.staff-task-list{
  display:grid;
  gap:8px;
  padding:0 18px 18px;
}
.staff-task-row{
  display:grid;
  grid-template-columns:54px minmax(0,1fr);
  gap:8px 12px;
  align-items:center;
  border:1px solid var(--line);
  border-radius:10px;
  background:var(--panel2);
  padding:10px 12px;
}
.staff-task-row b{
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}
.staff-task-row small{
  grid-column:2;
  color:var(--dim);
  font-size:11.5px;
}
.staff-task-row.done{
  opacity:.72;
}
.staff-task-state{
  grid-row:1 / span 2;
  align-self:stretch;
  display:grid;
  place-items:center;
  border-radius:8px;
  background:color-mix(in srgb,var(--accent-soft) 70%,var(--panel));
  color:var(--accent);
  font-size:10.5px;
  font-weight:950;
  text-transform:uppercase;
}
.staff-task-row.done .staff-task-state{
  background:color-mix(in srgb,var(--ok) 12%,var(--panel));
  color:var(--ok);
}
@media (max-width:560px){.staff-task-form{grid-template-columns:1fr}.staff-task-form .btn{justify-content:center}}

/* dashboard: 7-day activity bars (y axis + gridlines; --plot-h is the bar area only,
   day labels sit in their own row below it so the ticks line up with the bar tops) */
.bars-chart{--plot-h:130px;--lbl-h:32px;display:grid;grid-template-columns:auto auto 1fr;gap:0 10px;padding:28px 18px 6px;align-items:start}
.bars-ylab{grid-column:1;height:var(--plot-h);writing-mode:vertical-rl;transform:rotate(180deg);display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--dim)}
.bars-axis{grid-column:2;height:var(--plot-h);display:flex;flex-direction:column;justify-content:space-between;align-items:flex-end;font-size:10.5px;color:var(--dim);line-height:1;font-variant-numeric:tabular-nums}
.bars-tick{height:0;display:flex;align-items:center}
/* min-width:0 is what makes the scrolling work at all. A grid item's automatic minimum size is its
   MIN-CONTENT width, so without this the 1fr track refuses to go narrower than 21 bars and the
   plot spills out past the edge of the card instead of the scroller taking over. */
.bars-plot{grid-column:3;position:relative;min-width:0}
.bars-grid{position:absolute;top:0;left:0;right:0;height:var(--plot-h);display:flex;flex-direction:column;justify-content:space-between;pointer-events:none}
.bars-gridline{border-top:1px solid var(--line);opacity:.55}
/* Three weeks does not always fit, and the answer is not thinner bars: the plot scrolls sideways
   while the y axis and the gridlines stay put (which is why the scroller is INSIDE .bars-plot and
   the grid is not). The label row's reserved height moves in here too, so the labels sit inside
   the scroll box and cannot raise a vertical scrollbar of their own. The top inset is the
   tooltip's headroom, cancelled by an equal negative margin: it buys clipping room above the bars
   without moving anything down. The scroller is also the CONTAINER the bars size themselves
   against — see .bar-wrap. */
.bars-scroll{overflow-x:auto;overflow-y:hidden;padding-top:28px;margin-top:-28px;container-type:inline-size;
  padding-bottom:calc(var(--lbl-h) + 4px);scrollbar-width:thin;overscroll-behavior-x:contain}
/* --vis is how many days are IN VIEW; the chart itself holds three months to scroll back through. */
.bars{display:flex;align-items:flex-end;gap:var(--gap);height:var(--plot-h);position:relative;--gap:10px;--vis:21}
/* Grow but never shrink: one rule covers both cases. On a wide panel the bars share out the room
   exactly as they used to; on a narrow one they stop at --bar-w and the plot overflows. */
.bar-wrap{flex:1 0 auto;min-width:var(--bar-w,34px);display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;position:relative}
/* A bar is a --vis-th of the VISIBLE width (100cqw = the scroller, not the three months inside it),
   so three weeks fill the panel on a desktop and a week fills a phone, at any panel width and
   through a resize — no redraw, no measuring in JS. --bar-w stays as the floor: past the point
   where 21 legible bars no longer fit, fewer are shown and the rest is scrollback, which is far
   better than 21 slivers. Kept behind @supports because a var() value a browser can't parse
   computes to min-width:auto rather than falling back to the declaration above. */
@supports (container-type:inline-size){
  .bar-wrap{min-width:max(var(--bar-w,34px),calc((100cqw - (var(--vis) - 1) * var(--gap)) / var(--vis)))}
}
/* Edge fades, lit by .can-left / .can-right from teacher-home.js so they only ever appear on a
   side that has more chart behind it. z-index 1 puts them over the bars (also z-index 1, earlier
   in the DOM) but under .bar-tip at 2, so a tooltip near the edge is never washed out. */
.bars-plot::before,.bars-plot::after{content:"";position:absolute;top:0;bottom:0;width:34px;
  pointer-events:none;opacity:0;transition:opacity .15s ease;z-index:1}
.bars-plot::before{left:0;background:linear-gradient(90deg,var(--panel),transparent)}
.bars-plot::after{right:0;background:linear-gradient(270deg,var(--panel),transparent)}
.bars-plot.can-left::before{opacity:1}
.bars-plot.can-right::after{opacity:1}
.bar{position:relative;z-index:1;width:100%;max-width:34px;background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 86%,#fff),var(--accent));border-radius:6px 6px 2px 2px;min-height:4px}
/* Sat/Sun bars run a touch more purple than the weekday accent — enough to pick the weekends out
   without reading as a different series. Mixed off --accent so it follows whatever theme is on. */
.bar-wrap.is-weekend .bar{background:linear-gradient(180deg,
  color-mix(in srgb,color-mix(in srgb,var(--accent) 18%,#a855f7) 86%,#fff),
  color-mix(in srgb,var(--accent) 18%,#a855f7))}
.bar-wrap:hover .bar,.bar-wrap:focus-visible .bar{filter:brightness(1.08)}
.bar-wrap:focus{outline:none}
/* exact-value tooltip, always above the bar (.bars-chart keeps 28px of top padding as headroom) */
.bar-tip{position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);
  background:var(--ink);color:var(--panel);padding:3px 7px;border-radius:6px;
  font-size:11px;line-height:1.3;white-space:nowrap;font-variant-numeric:tabular-nums;
  opacity:0;visibility:hidden;transition:opacity .12s ease;pointer-events:none;z-index:2}
.bar-wrap:hover .bar-tip,.bar-wrap:focus-visible .bar-tip{opacity:1;visibility:visible}
/* The end tooltips anchor to their bar's inside edge rather than centring on it. A centred tip is
   about 124px wide on a bar a third of that, so the last one hung ~36px past the end of the row:
   hidden, but still laid out, so it gave the scroller 36px of phantom scroll and lit the fades on a
   chart that actually fitted. Anchoring inwards also stops the first one being clipped at the
   scroll origin. */
.bar-wrap:first-child .bar-tip{left:0;transform:none}
.bar-wrap:last-child .bar-tip{left:auto;right:0;transform:none}
/* Two lines under each bar: the date, and the weekday beneath it. --lbl-h reserves the room for
   both, so the labels still sit clear of the panel edge. */
.bar-l{position:absolute;z-index:1;top:100%;left:50%;transform:translateX(-50%);margin-top:5px;font-size:10.5px;color:var(--dim);white-space:nowrap;
  display:flex;flex-direction:column;align-items:center;gap:1px;line-height:1.15}
.bar-dow{font-size:9.5px;letter-spacing:.05em;text-transform:uppercase;color:color-mix(in srgb,var(--dim) 82%,transparent)}
/* Three-week window: ~30 px per bar is too narrow for a "16 Aug" label under every bar, so the
   date is kept only on Mondays and on today (.is-week-start) and the weekday initials carry the
   rest. Every bar still names its full date in the tooltip and its aria-label. */
.bars.is-dense{--gap:5px;--bar-w:26px}
/* visibility, not display: .bar-l is a two-row column, and removing the date row would lift the
   weekday initials of every non-Monday bar to a different height from the Mondays'. */
.bars.is-dense .bar-wrap:not(.is-week-start) .bar-d{visibility:hidden}
.bars.is-dense .bar-d{font-weight:600;color:var(--ink)}
.bars.is-dense .bar-dow{font-size:9px;letter-spacing:0}
/* A short bar on a Saturday is the timetable, not a fall in engagement — so weekends read quieter. */
.bar-dow.is-weekend{color:color-mix(in srgb,var(--dim) 52%,transparent)}
/* Phone: a week in view, not three. 21 bars on a 390 px screen are a few pixels each — the days are
   still there, they are just scrolled to rather than squinted at. The breakpoint matches the one
   teacher-home.js uses for the per-week summary, so the two agree about what "in view" means.
   With only seven wide bars on screen there is room for every date, so the dense window's
   Mondays-only date row is handed back. */
@media (max-width:700px){
  .bars{--vis:7}
  /* `inherit`, not `visible`: the privacy mask hides the chart by setting visibility:hidden on the
     panel body's children and letting it inherit down. A literal `visible` here outranked that and
     handed every date back — on a phone the masked panel said "Hidden for privacy" with a full row
     of dates still printed under it. `inherit` means "whatever the bar does", which is visible
     normally and hidden under the mask. */
  .bars.is-dense .bar-wrap:not(.is-week-start) .bar-d{visibility:inherit}
}
@media (max-width:560px){
  .bars-chart{--plot-h:110px;--lbl-h:29px;padding:26px 12px 6px;gap:0 6px}
  .bars{--gap:5px;--bar-w:22px}
  .bar-l{font-size:9px}
  .bar-dow{font-size:8.5px}
  .bars-ylab{display:none}
}
/* desktop gets a 50% taller plot area */
@media (min-width:900px){
  .bars-chart{--plot-h:195px}
}
.bars-sum{padding:10px 18px 14px;color:var(--dim);font-size:13px;border-top:1px solid var(--line)}
/* placeholder for the bars chart while the overview query is in flight. A .panel-body is
   padding:0, so the placeholder has to carry the chart's own insets — otherwise it sits flush
   to the card edge, out of line with the heading above it, and jumps sideways when the real
   chart lands. The inset is PADDING on a wrapper, not a margin on the shape: a margin against
   a padding-less .panel-body collapses straight out of the card and the placeholder ends up
   welded to the panel head. Sizes track .bars-chart + .bars-sum so the card doesn't resize
   either. */
.bars-skel{padding:28px 18px 6px}
.bars-skel-plot{height:calc(130px + var(--lbl-h,18px) + 4px)}
.bars-skel-sum{margin-top:24px;height:19px;max-width:230px}
@media (max-width:560px){
  .bars-skel{padding:26px 12px 6px}
  .bars-skel-plot{height:calc(110px + var(--lbl-h,18px) + 4px)}
}
@media (min-width:900px){.bars-skel-plot{height:calc(195px + var(--lbl-h,18px) + 4px)}}

/* roster / pending tables: secondary email line */
.sub-mail{color:var(--dim);font-size:12px}

/* Phones: the 8-column class roster is too wide to fit, which squeezes the name
   to a sliver. Let the table scroll sideways and keep the name on one line so it
   stays readable; the other columns are reachable by swiping across. */
@media(max-width:560px){
  .cls-roster-table,
  .cls-staff-table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .cls-roster-table th,
  .cls-roster-table td,
  .cls-staff-table th,
  .cls-staff-table td{white-space:nowrap}
  .cls-roster-table .sub-mail,
  .cls-staff-table .sub-mail{white-space:nowrap}
}

/* class colour coding */
.cls-dot{display:inline-block;width:11px;height:11px;border-radius:999px;margin-right:8px;vertical-align:baseline}
.cls-dot.big{width:14px;height:14px;margin-right:10px}

/* class detail: join code */
.joincode{padding:18px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.joincode .badge.big{font-size:20px;padding:8px 16px;letter-spacing:3px}
.joincode .hint{flex-basis:100%;margin:0}

/* class list: filter bar (by weekday + subject + room) */
.cls-filters{display:flex;flex-direction:column;gap:8px;margin:0 0 16px}
.cls-filter-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.cls-filter-label{font-size:11.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:var(--dim);min-width:62px}
/* Four chip rows is 350px of scrolling before the first class on a phone, so there they collapse
   behind a toggle that still reports how many filters are on. Desktop never shows the toggle. */
.cls-filters-toggle{display:none;margin:0 0 14px}
.cls-filters-toggle .badge{margin-left:7px}
@media (max-width:560px){
  .cls-filters-toggle{display:inline-flex}
  .cls-filters:not(.open){display:none}
  .cls-filters{gap:10px}
  .cls-filter-row{gap:6px}
  .cls-filter-label{min-width:0;flex-basis:100%;margin-bottom:-2px}
}
/* room badge on a class card. One hue per room, so a glance down the list separates them. */
.badge.room{background:var(--panel2);color:var(--dim);padding:2px 8px;font-size:11px}
.badge.room-north{background:#e8f0fb;color:#1c5aa8}
.badge.room-south{background:#f6ecfb;color:#7a3ea1}
.badge.room-g2{background:#e6f4f1;color:#0b6a5c}
html[data-design="lab"] .badge.room-north{background:#12263f;color:#8dbdf5}
html[data-design="lab"] .badge.room-south{background:#2a1838;color:#d3a5f0}
html[data-design="lab"] .badge.room-g2{background:#0d2a26;color:#7fd9c7}

/* class list: join-code badge in its column */
.joincode-badge{letter-spacing:1.5px;font-weight:700}
/* In the class list the badge is the control: clicking it opens the full-screen code. Styled as a
   badge, not a button, so the row stays quiet until you look for it. */
button.joincode-badge{border:0;font-family:ui-monospace,monospace;font-size:12px;cursor:pointer}
button.joincode-badge:hover{filter:brightness(.94)}
button.joincode-badge:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Class-detail pending panel: same amber treatment as the class-list tile above,
   all-around border/background tint — not a left-edge accent bar. */
.panel.panel-pending{
  border-color:color-mix(in srgb,var(--warn) 42%,var(--line));
  background:color-mix(in srgb,var(--warn) 12%,var(--panel));
}
.panel.panel-pending .panel-head{
  border-bottom-color:color-mix(in srgb,var(--warn) 30%,var(--line));
}

/* ---- class list: one table row per class ----------------------------------------------------
   Replaced the tile grid. ~17 classes fit one screen as rows, and When / Students / Join code
   line up so you read down a column instead of hunting the same corner of 17 cards. */
.cls-table{width:100%}
.cls-table th{white-space:nowrap}
.cls-trow td{vertical-align:middle}
/* The class colour is a left edge on the first cell — now the Time cell. border-left would be
   eaten by border-collapse; an inset shadow survives it and keeps the row height honest. */
/* Start time, with the room under it. Only the start: every lesson but one runs 80 minutes, so
   the end time was buying width to say almost nothing. The narrow layout still prints the full
   range inline, where there is space for it and no column to keep tight. */
.cls-td-time{white-space:nowrap;box-shadow:inset 4px 0 0 var(--cls-colour,var(--line))}
.cls-time{display:block;font-variant-numeric:tabular-nums;color:var(--dim);font-weight:700;font-size:13px}
.cls-place{display:block;margin-top:3px}
.cls-td-name{font-size:15px;width:100%}          /* takes all the slack — least likely to wrap */
.cls-th-name{width:100%}
/* Time repeated under the title in the narrow layout, where the Time column is hidden. */
.cls-mobtime{color:var(--dim);font-size:12.5px;font-weight:700;font-variant-numeric:tabular-nums}
/* Subject chip. Four subjects, four hues, matched to the row's colour spine. */
.badge.subj{padding:2px 9px;font-size:11px}
.badge.subj-chemistry{background:#eceafd;color:#4b45c6}
.badge.subj-physics{background:#fdeee4;color:#b5450a}
.badge.subj-biology{background:#e6f5ea;color:#1f7a33}
.badge.subj-latin{background:#faf1dc;color:#8a6108}
.badge.subj-literacy{background:#fbe9ef;color:#a61e4d}
html[data-design="lab"] .badge.subj-chemistry{background:#1b1a3a;color:#a9a4ff}
html[data-design="lab"] .badge.subj-physics{background:#33190c;color:#ffab7a}
html[data-design="lab"] .badge.subj-biology{background:#102a16;color:#79dd93}
html[data-design="lab"] .badge.subj-latin{background:#2b2109;color:#e6c46a}
html[data-design="lab"] .badge.subj-literacy{background:#33101e;color:#f3a0bd}
/* Subject + room repeated under the title, shown only in the narrow layout (see the media query). */
.cls-mobmeta{display:none}
.cls-td-name b{font-weight:700}
.cls-td-n,.cls-td-code,.cls-td-acts{white-space:nowrap}
/* The compact columns give their side padding back to the class name, which is the only column
   whose content can wrap. app.css sets 18px on every cell; these need far less. */
.cls-table th:not(.cls-th-name),.cls-td-time,.cls-td-n,.cls-td-code{padding-left:10px;padding-right:10px}
.cls-td-time{padding-left:16px}
.cls-td-n{font-variant-numeric:tabular-nums}
.cls-td-n strong{color:var(--accent);font-size:17px}
.cls-td-n span{color:var(--dim);font-size:12.5px}
.cls-codewrap{display:inline-flex;align-items:center;gap:7px}
.cls-td-acts{text-align:right}
/* Kept in flow rather than floated over the row's right end: the buttons are opacity:0 until
   hover, so a floating group lands exactly on top of the join-code badge the moment you hover to
   click it. Reserving the width is the cheaper trade. */
.cls-rowacts{display:inline-flex;gap:6px;justify-content:flex-end}
/* Action buttons stay quiet until the row is hovered or tabbed into — 17 rows x 3 buttons is a
   wall of chrome otherwise. Never display:none: they must remain reachable by keyboard. */
.cls-rowacts .btn{opacity:0;transition:opacity .12s}
.cls-trow:hover .btn,.cls-trow:focus-within .btn{opacity:1}
@media (hover:none){.cls-rowacts .btn{opacity:1}}

/* day band: a labelled hairline every time the weekday changes */
.cls-dayband th{background:var(--panel2);padding-top:7px;padding-bottom:7px;
  font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:var(--dim)}
.cls-dayband th span{font-weight:600;text-transform:none;letter-spacing:0;margin-left:8px}

/* classes with students waiting on an approval read as "needs attention" from the list */
.cls-trow.has-pending{background:color-mix(in srgb,var(--warn) 10%,var(--panel))}
.cls-trow.has-pending:hover{background:color-mix(in srgb,var(--warn) 16%,var(--panel))}

/* Narrow: drop the columns that repeat what the class name already says, and let the two that
   carry the real information (when, students) wrap under it. */
/* ---- narrow screens -------------------------------------------------------------------------
   Deliberately fewer features rather than a squeezed copy of the desktop table. What survives is
   what you actually need standing in a corridor: which class, which subject, which room, how many
   students. Join code and the per-row action buttons are dropped — tapping the row opens the class
   page, where both live in full. */
@media (max-width:820px){
  /* Stacked rows fit the screen, so opt out of the shared 560px min-width that turns a
     table-only panel body into a sideways scroller. */
  .panel-body-table>.cls-table{min-width:0}
  .cls-table thead{display:none}
  .cls-trow{display:grid;grid-template-columns:1fr auto;gap:2px 10px;padding:11px 0}
  .cls-trow td{border:0;padding:0 16px}
  /* the spine survives as a real border on mobile — box-shadow insets don't render on a grid
     item the way they do on a table cell, so the class colour rides a CSS var instead */
  /* the spine moves to the row itself so it runs the full height of the stacked block */
  .cls-trow{border-left:4px solid var(--cls-colour,var(--line))}
  .cls-trow td.cls-td-name{grid-column:1/-1;width:auto;padding-left:12px}
  .cls-trow td{padding-left:12px}
  .cls-td-name b{display:block}
  /* the desktop Subject and Room cells collapse into one line under the title */
  .cls-mobmeta{display:flex;gap:6px;align-items:center;margin-top:5px}
  .cls-td-time,.cls-td-code,.cls-td-acts{display:none}   /* .cls-mobtime keeps the full range */
  .cls-td-n{grid-column:1/-1;padding-left:12px;margin-top:4px}
  .cls-dayband th{display:block}
}

.cls-archived-actions{
  display:flex;
  justify-content:center;
  padding:4px 0 18px;
}
.cls-archived{
  margin-top:2px;
}
.cls-archived h3{
  margin:0 0 10px;
  color:var(--dim);
  font-size:13px;
  font-weight:900;
  letter-spacing:.5px;
  text-transform:uppercase;
}
.teacher-class-grid.archived-grid{
  grid-template-columns:repeat(auto-fit,minmax(160px,1fr));
  padding:0;
}
.teacher-class-card.archived{
  min-height:72px;
  padding:12px 14px;
  gap:6px;
  background:color-mix(in srgb,var(--line) 26%,var(--panel));
  border-color:color-mix(in srgb,var(--line) 80%,var(--panel));
  color:var(--dim);
  cursor:default;
  box-shadow:none;
  opacity:.78;
}
.teacher-class-card.archived:hover,
.teacher-class-card.archived:focus-within{
  border-color:color-mix(in srgb,var(--line) 80%,var(--panel));
  background:color-mix(in srgb,var(--line) 26%,var(--panel));
  transform:none;
}
.teacher-class-card.archived .teacher-class-card-top b{
  color:var(--dim);
  font-size:14px;
}
.teacher-class-card.archived .cls-dot{
  background:color-mix(in srgb,var(--dim) 45%,transparent);
}

.teacher-picker{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:10px;
}
.staff-chip{
  border:1px solid var(--line);
  border-radius:10px;
  background:var(--panel);
  color:var(--ink);
  padding:12px 14px;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:4px;
  text-align:left;
  font:inherit;
  cursor:pointer;
}
.staff-chip span{font-weight:850}
.staff-chip small{color:var(--dim);font-weight:700}
.staff-chip.on{
  border-color:var(--accent);
  background:var(--accent-soft);
  box-shadow:inset 0 0 0 1px var(--accent);
}
.staff-chip span .st-dot{margin-right:7px}

/* multi-add pickers (add students to a class / a student to classes) */
.picker-tools{margin-bottom:10px}
.picker-tools .st-search{width:100%;max-width:none}
.teacher-picker.scroll{
  max-height:44vh;
  overflow:auto;
  padding:2px;
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
}
.staff-chip.is-in{opacity:.72;cursor:default}
.staff-chip.is-in .badge{margin-top:4px}
.picker-empty{grid-column:1/-1;color:var(--dim);text-align:center;padding:18px 4px}
.picker-already{margin-top:12px}
.picker-already>summary{cursor:pointer;color:var(--dim);font-weight:800;padding:4px 0}
.picker-already[open]>summary{margin-bottom:8px}

/* day/subject filter bar inside the class-picker modals */
.cls-pick-filters{margin-bottom:8px}
.cls-pick-filters .st-filterrow{margin:0 0 6px}
.cls-pick-filters .st-filterrow:last-child{margin-bottom:0}
/* the target-class grid in "Add students to a class" stays compact (the student list is the main area) */
.cls-pick-grid{max-height:26vh}

/* two-column layout — stacks on narrow screens */
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}
@media (max-width:820px){.two-col{grid-template-columns:1fr}}
/* roster is the priority column; give it more room than the pending queue */
.roster-grid{grid-template-columns:1.4fr 1fr}
@media (max-width:820px){.roster-grid{grid-template-columns:1fr}}

/* class summary mini-stats */
.cls-summary{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;padding:18px}
@media (max-width:480px){.cls-summary{grid-template-columns:repeat(2,1fr)}}
.cls-stat{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:12px;text-align:center;
  display:flex;flex-direction:column;justify-content:center}
/* Fixed line box, not a fixed font size: the Room tile holds a word and is stepped down a couple of
   points, which used to make its value sit high and drag its label out of line with the numbers.
   Centring the value inside a box the height of the big numerals keeps all four labels on one line. */
.cls-stat-n{font-size:22px;font-weight:800;color:var(--accent);line-height:1.15;
  min-height:calc(22px * 1.15);display:flex;align-items:center;justify-content:center}
.cls-stat-n.cls-stat-word{font-size:17px;letter-spacing:.2px}
.cls-stat-l{font-size:11.5px;color:var(--dim);margin-top:3px}

/* Class page: the archive control, last thing on the page and visibly separate from the panels
   above it so it is never mistaken for part of the roster. */
.cls-danger{border:1px solid color-mix(in srgb,var(--err) 34%,var(--line));
  border-radius:12px;background:color-mix(in srgb,var(--err) 5%,var(--panel));
  padding:18px;margin:22px 0 8px}
.cls-danger h3{margin:0 0 6px;font-size:15px;color:var(--err)}
.cls-danger p{margin:0 0 14px;color:var(--dim);font-size:13.5px;max-width:60ch}

/* class files (attachments) */
/* Padding is symmetric by DEFAULT — the last card must never sit flush against the panel border.
   The only case that drops the bottom padding is a trailing .cls-att-hint, which supplies its own.
   Do NOT re-express this as `.panel-body>.cls-att-list:last-child`: several pages
   (js/views/practice-materials.js) mount the list inside a wrapper div so it can repaint on its
   own, which silently breaks a direct-child/last-child selector and un-pads the list again. */
.cls-att-list{list-style:none;margin:0;padding:18px;display:flex;flex-direction:column;gap:8px}
.cls-att-list:has(+ .cls-att-hint){padding-bottom:0}
.cls-att{display:flex;align-items:center;gap:10px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);padding:9px 12px}
.cls-att-dl{flex:1;min-width:0;display:flex;align-items:center;gap:9px;background:none;border:0;padding:0;cursor:pointer;font:inherit;color:var(--accent);text-align:left}
.cls-att-dl:hover .cls-att-name{text-decoration:underline}
.cls-att-ic{flex:0 0 auto;display:flex;align-items:center;color:var(--accent)}
.cls-att-ic .line-ico{width:16px;height:16px}
.cls-att-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:650}
/* second line under a file name (Resources → Files from Messages: who it was with + when) */
.cls-att-sub{display:block;font-weight:400;font-size:12px;color:var(--dim);overflow:hidden;text-overflow:ellipsis}
.cls-att-size{flex:0 0 auto;color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
/* Scheduled-release note (staff-only — students can't select the row until then). */
.cls-att-queued{color:var(--accent);font-variant-numeric:normal}
/* Visibility state, staff-only: a row students cannot see must never look shared. */
.cls-att-vis{
  margin-left:6px;padding:2px 8px;border-radius:999px;
  font-size:11px;font-weight:800;font-variant-numeric:normal;letter-spacing:.02em;
}
.cls-att-vis.hidden{background:color-mix(in srgb,var(--err) 13%,transparent);color:var(--err)}
.cls-att-vis.scheduled{background:color-mix(in srgb,var(--accent) 13%,transparent);color:var(--accent)}
/* Visibility modal: three states, then the two switch dates (either may be blank, any order). */
.cls-vis-file{margin:0 0 14px;font-weight:700}
.cls-vis-opt{display:flex;gap:10px;align-items:flex-start;padding:10px 12px;margin-bottom:8px;
  border:1px solid var(--line);border-radius:10px;background:var(--panel2);cursor:pointer}
.cls-vis-opt:has(input:checked){border-color:var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--panel2))}
/* The portal's base rule stretches inputs to the full field width — a radio must opt out of it
   or it eats the row and pushes its own label off the modal. */
.cls-vis-opt input[type=radio]{width:16px;height:16px;min-width:0;margin:3px 0 0;flex:0 0 auto}
.cls-vis-opt span{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}
.cls-vis-opt small{color:var(--dim);font-size:12px;line-height:1.4}
.cls-vis-dates{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:4px}
.cls-vis-dates label{display:flex;flex-direction:column;gap:5px;font-size:12px;font-weight:750;color:var(--dim)}
.cls-vis-dates input{font:inherit}
.cls-vis-dates .hint{grid-column:1/-1;margin:0}
.cls-vis-dates.off{opacity:.45}
@media (max-width:480px){.cls-vis-dates{grid-template-columns:1fr}}
.cls-att-addbtn{cursor:pointer;margin:0}
/* "Solutions" opens the paper's paired answers file from inside its row (see
   js/views/practice-materials.js) — green so it reads as the companion, not another action. */
.cls-att-sol{
  border-color:color-mix(in srgb,var(--ok) 32%,var(--line));
  background:color-mix(in srgb,var(--ok) 12%,var(--panel));
  color:var(--ok);font-weight:650
}
.cls-att-sol:hover{background:color-mix(in srgb,var(--ok) 20%,var(--panel));border-color:color-mix(in srgb,var(--ok) 46%,var(--line))}
/* SAC task-type chip (Practice SACs tab), sits right of the file size. One hue per VCAA task type
   so the list can be scanned by type at a glance. Every colour is mixed against --panel/--ink/--line
   rather than hard-coded, so the chips re-tint themselves on the dark designs (theme.css) instead
   of staying light-on-light. Hues deliberately avoid green — that reads as the "Solutions" button. */
.sac-chip{
  --c:var(--dim);
  flex:0 0 auto;display:inline-flex;align-items:center;
  padding:3px 9px;border-radius:999px;font-size:12px;font-weight:700;white-space:nowrap;
  background:color-mix(in srgb,var(--c) 15%,var(--panel));
  color:color-mix(in srgb,var(--c) 72%,var(--ink));
  border:1px solid color-mix(in srgb,var(--c) 32%,var(--line))
}
.sac-chip[data-sac-type="analysis"]{--c:#0e7490}         /* teal   */
.sac-chip[data-sac-type="case-study"]{--c:#b45309}       /* amber  */
.sac-chip[data-sac-type="comparison"]{--c:#7c3aed}       /* violet */
.sac-chip[data-sac-type="problem-solving"]{--c:#be185d}  /* rose   */

/* "Add SAC file" dialog — pick the task type before the upload starts. The options ARE .sac-chips,
   so what you choose is exactly what the list will show. */
.pm-up-file{display:flex;align-items:center;gap:8px;margin:0 0 16px;color:var(--ink);font-weight:650}
.pm-up-file span:first-of-type{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pm-up-file .cls-att-size{margin-left:auto;font-weight:400}
.pm-up-file .line-ico{width:16px;height:16px;flex:0 0 auto;color:var(--accent)}
.pm-up-lbl{margin:0;font-weight:650;font-size:13px}
.pm-up-hint{font-weight:400;color:var(--dim)}
.pm-up-types{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.pm-up-opt{cursor:pointer;padding:6px 12px;font-size:13px;-webkit-user-select:none;user-select:none;transition:background .12s,box-shadow .12s}
/* The radio stays in the DOM (keyboard + arrow-key roving still work) but is drawn by the chip. */
.pm-up-opt input{position:absolute;width:1px;height:1px;opacity:0;margin:0}
.pm-up-opt:hover{background:color-mix(in srgb,var(--c) 24%,var(--panel))}
.pm-up-opt:has(:checked){background:color-mix(in srgb,var(--c) 30%,var(--panel));box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--c) 62%,var(--line))}
.pm-up-opt:has(:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
.pm-up-match{margin:14px 0 0;font-size:12.5px;color:var(--dim)}
.pm-up-foot{display:flex;justify-content:flex-end;gap:8px}
/* ---- practice-materials live search (mig 0118 keywords) ---- */
.pm-searchbar{display:flex;align-items:center;gap:12px;margin:0 0 14px;flex-wrap:wrap}
.pm-search{flex:1 1 320px;max-width:420px}
.pm-count{font-size:12.5px;color:var(--dim)}
/* The keywords a row matched on, so a hit against metadata the student can't see explains itself.
   Full-width line under the row: they must never compete with the filename for horizontal space.
   .cls-att is nowrap, so the row must be told to wrap or the 100%-basis chip line would squeeze
   the (min-width:0) filename button down to nothing. */
.cls-att:has(.pm-kwhits){flex-wrap:wrap;row-gap:8px}
.pm-kwhits{flex:1 0 100%;display:flex;flex-wrap:wrap;gap:6px;padding-left:30px}
.pm-kw{
  font-size:11.5px;padding:2px 8px;border-radius:999px;white-space:nowrap;
  background:color-mix(in srgb,var(--accent) 13%,var(--panel));
  color:color-mix(in srgb,var(--accent) 78%,var(--ink));
  border:1px solid color-mix(in srgb,var(--accent) 26%,var(--line))
}
.pm-kw-ta{width:100%;font-size:13px;line-height:1.6;resize:vertical}
@media(max-width:620px){
  .pm-search{max-width:none}
  .pm-kwhits{padding-left:0;order:5}
}
/* Phone: a SAC/exam/attachment row is a one-line filename with the meta + actions beneath it.
   Squeezing the title, size and "Remove" onto one 375px line ellipsised the
   filename down to nothing, which is the whole point of the row. */
@media(max-width:620px){
  .cls-att{flex-wrap:wrap;row-gap:9px;padding:11px 12px}
  .cls-att-dl{flex:1 0 100%;order:1;align-items:flex-start}
  .cls-att-name{white-space:normal;overflow-wrap:anywhere;line-height:1.35}
  .cls-att-ic{padding-top:2px}
  .cls-att .badge{order:2}
  .cls-att-size{order:3;margin-right:auto}
  /* Chip stays glued to the size on its meta line; whichever of the two comes last carries the
     auto margin that pushes Solutions/Remove to the right edge. */
  .sac-chip{order:3;margin-right:auto}
  .cls-att:has(.sac-chip) .cls-att-size{margin-right:0}
  .cls-att .btn{order:4;min-height:34px;display:inline-flex;align-items:center}
  .cls-att-list{padding:14px 12px;gap:9px}
  .cls-att-list:has(+ .cls-att-hint){padding-bottom:0}
}
.cls-att-hint{margin:10px 18px 18px;padding:0}

/* ---- drag-to-reorder (KC.sortable) ----
   Rendered only for the admins and teachers who may set the order; everyone else gets the same
   row without a handle. Handle-only dragging, because these rows are full of buttons.
   touch-action:none is load-bearing, not decoration: without it a touch drag on the handle is
   claimed by the page scroller and the pointermove stream stops dead mid-drag. */
.drag-grip{
  flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  width:26px;height:30px;margin-left:-4px;padding:0;
  border:0;border-radius:7px;background:none;color:var(--dim);
  cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none
}
.drag-grip:hover{background:var(--accent-soft);color:var(--accent)}
.drag-grip:focus-visible{outline:2px solid var(--accent);outline-offset:1px;color:var(--accent)}
.drag-grip .line-ico{width:18px;height:18px;pointer-events:none}
/* The whole list dims slightly during a drag so the row being carried is the one thing in focus,
   and every row stops responding to hover (the pointer is captured — hover states would lie). */
.cls-att-list.sorting{cursor:grabbing}
.cls-att-list.sorting .cls-att{transition:transform .12s ease}
.cls-att-list.sorting .drag-grip{cursor:grabbing}
.cls-att-list.sorting .cls-att:not(.dragging){opacity:.72}
.cls-att.dragging{
  border-color:color-mix(in srgb,var(--accent) 55%,var(--line));
  background:color-mix(in srgb,var(--accent) 10%,var(--panel2));
  box-shadow:0 6px 18px color-mix(in srgb,var(--ink) 18%,transparent);
  transform:scale(1.012)
}
.pm-sort-hint{margin:-6px 0 14px}
/* Phone: the handle shares the filename's line rather than taking one of its own — .cls-att-dl's
   100% basis would otherwise wrap the name below a lone grip. Bigger target, too: 34px tall. */
@media(max-width:620px){
  .drag-grip{order:0;width:32px;height:34px;margin-left:-2px}
  .cls-att:has(.drag-grip) .cls-att-dl{flex:1 0 calc(100% - 42px)}
}
@media(prefers-reduced-motion:reduce){
  .cls-att-list.sorting .cls-att{transition:none}
  .cls-att.dragging{transform:none}
}

/* clickable person rows (roster + pending) → open the student's profile */
.person-row{cursor:pointer;transition:background .12s}
.person-row:hover,.person-row:focus-visible{background:var(--accent-soft)}
.person-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* A person's name is a link everywhere in the Portal (KC.personLink / KC.bindPersons).
   Same affordance wherever it appears, so a name always looks clickable and always is. */
.person-link{
  appearance:none;border:0;background:none;color:var(--accent);padding:0;
  font:inherit;font-weight:850;cursor:pointer;text-align:left;
}
.person-link:hover{text-decoration:underline}
.person-link:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}

/* staff person card — the click target for a teacher/admin, who has no profile page */
.person-card-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.person-card-name{font-size:18px;font-weight:900}
.person-card-mail{margin-top:2px}
.person-card-mail a{color:var(--accent)}
.person-card-h{margin-top:16px;font-size:12px;font-weight:900;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
.person-card-classes{margin:6px 0 0;padding-left:0;list-style:none;display:flex;flex-direction:column;gap:4px}
.person-card-none{margin-top:6px;color:var(--muted)}

/* class detail: one-stop lesson admin */
.cls-today-table th:nth-child(1),.cls-today-table td:nth-child(1){min-width:190px}
.cls-today-table th:nth-child(2),.cls-today-table td:nth-child(2){min-width:190px}
.cls-today-table th:nth-child(3),.cls-today-table td:nth-child(3){min-width:135px;text-align:center}
.cls-today-table th:nth-child(4),.cls-today-table td:nth-child(4){min-width:260px}
.cls-person-link{
  appearance:none;border:0;background:none;color:var(--accent);padding:0;font:inherit;font-weight:850;cursor:pointer;text-align:left;
}
.cls-person-link:hover{text-decoration:underline}
.cls-score-input{
  width:82px;
  min-height:34px;
  padding:5px 8px;
  border-radius:10px;
  text-align:center;
  font-size:13px;
  font-weight:900;
  background:var(--panel);
}
.cls-score-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 14%,transparent)}
.cls-score-input.bad{border-color:var(--err);background:color-mix(in srgb,var(--err) 8%,var(--panel))}
/* Term tests: the mid-term (week 5) and end-of-term (week 10) papers. Tinted in the marking table
   so the one unusual column stands out, banded in the Term tests panel so a weak result is spotted
   without reading the numbers. */
.cls-termtest-input{
  border-color:color-mix(in srgb,var(--accent) 34%,var(--line));
  background:color-mix(in srgb,var(--accent-soft) 45%,var(--panel));
}
.cls-tt-score{
  display:inline-block;
  padding:2px 8px;
  border-radius:999px;
  font-weight:900;
  font-variant-numeric:tabular-nums;
}
/* Four bands, not three: above 75% green, above 60% yellow, above 50% orange, at or below 50% red.
   Yellow stays the theme's own --warn; orange is mixed from --warn towards --err, so the new band
   sits visibly between yellow and red in every theme without adding a token. */
.cls-tt-score.high{background:color-mix(in srgb,var(--ok) 16%,transparent);color:var(--ok)}
.cls-tt-score.mid{background:color-mix(in srgb,var(--warn) 18%,transparent);color:var(--warn)}
.cls-tt-score.warn{
  background:color-mix(in srgb,color-mix(in srgb,var(--warn) 60%,var(--err)) 17%,transparent);
  color:color-mix(in srgb,var(--warn) 60%,var(--err));
}
.cls-tt-score.low{background:color-mix(in srgb,var(--err) 15%,transparent);color:var(--err)}
.cls-tt-score.muted{background:transparent;color:var(--dim)}
.cls-tt-pct{color:var(--dim);font-size:12px;font-weight:750}
/* The editable Term tests panel: box, "/ 20", then the live percentage. One row, baseline-aligned,
   and the percentage keeps a fixed width so a column of them does not jitter as marks are typed. */
.cls-tt-cell{display:inline-flex;align-items:center;gap:8px}
.cls-tt-input{
  width:74px;
  border-color:color-mix(in srgb,var(--accent) 34%,var(--line));
  background:color-mix(in srgb,var(--accent-soft) 45%,var(--panel));
}
.cls-tt-out{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
.cls-tt-pctlive{min-width:34px;text-align:right;font-variant-numeric:tabular-nums}
.cls-tt-pctlive.high{color:var(--ok)}
.cls-tt-pctlive.mid{color:var(--warn)}
.cls-tt-pctlive.warn{color:color-mix(in srgb,var(--warn) 60%,var(--err))}
.cls-tt-pctlive.low{color:var(--err)}
/* .panel-pad carries the gutter; .hint's own margin-top would add a second, uneven one on top. */
.cls-tt-hint{margin:0}
.cls-hw-buttons{
  display:inline-flex;
  align-items:center;
  gap:4px;
  border:1px solid var(--line);
  border-radius:999px;
  background:var(--panel2);
  padding:3px;
  white-space:nowrap;
}
.cls-hw-btn{
  border:0;
  border-radius:999px;
  min-height:30px;
  padding:5px 9px;
  background:transparent;
  color:var(--dim);
  font:inherit;
  font-size:11.5px;
  font-weight:900;
  cursor:pointer;
}
.cls-hw-btn.on.complete{background:color-mix(in srgb,var(--ok) 16%,var(--panel));color:var(--ok)}
.cls-hw-btn.on.incomplete{background:color-mix(in srgb,var(--warn) 18%,var(--panel));color:color-mix(in srgb,var(--warn) 78%,var(--ink))}
.cls-hw-btn.on.not-seen{background:color-mix(in srgb,var(--dim) 14%,var(--panel));color:var(--dim)}
/* Absence is a homework state now — the attendance buttons that used to sit beside these are gone. */
.cls-hw-btn.on.absent{background:color-mix(in srgb,var(--err) 13%,var(--panel));color:var(--err)}
.cls-note-cell{display:flex;gap:8px;align-items:center}
.cls-note-input{min-width:180px;min-height:34px;padding:6px 9px;font-size:13px}

/* Homework panel on the class page — task list + completion bar (admins can remove past tasks). */
.hw-table td,.hw-table th{vertical-align:middle}
.hw-comp{display:flex;align-items:center;gap:10px;min-width:120px}
.hw-comp-n{font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--ink)}
.hw-comp-n strong{color:var(--accent)}
.hw-bar{position:relative;flex:1;height:6px;min-width:52px;max-width:120px;background:color-mix(in srgb,var(--line) 70%,transparent);border-radius:999px;overflow:hidden}
.hw-bar-fill{position:absolute;inset:0 auto 0 0;height:100%;background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 86%,#fff),var(--accent));border-radius:999px}
/* Expandable homework rows — reveal who has / hasn't done the quiz. NOT .hw-row: that name is
   already taken by the student homework tile rail (progress.css), which is display:flex with
   padding and overflow-x, and js/kc-scroll.js tags every .hw-row as a sideways rail. A <tr>
   picking those up stops being a table row, so the body cells sized themselves independently of
   the header row above them. */
.hw-task-row{cursor:pointer}
.hw-task-row:hover{background:color-mix(in srgb,var(--accent) 6%,transparent)}
.hw-task-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.hw-caret{width:1.4em;color:var(--muted);transition:transform .12s ease;transform-origin:center}
.hw-task-row.open .hw-caret{transform:rotate(90deg)}
.hw-detail-row>td{padding:0 12px 14px}
.hw-detail-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:4px 4px 2px}
@media(max-width:560px){.hw-detail-cols{grid-template-columns:1fr}}
.hw-detail-h{font-weight:600;font-size:13px;margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid var(--line)}
.hw-detail-h.done{color:var(--ok,#1E7A4D)}
.hw-detail-h.todo{color:var(--danger,#c0392b)}
.hw-detail-names{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.hw-detail-names li{font-size:13px}
.hw-detail-names .cls-person-link{background:none;border:0;padding:2px 0;color:var(--ink);cursor:pointer;font:inherit;text-align:left}
.hw-detail-names .cls-person-link:hover{color:var(--accent);text-decoration:underline}
.hw-detail-empty,.hw-detail-note{color:var(--muted);font-size:13px;padding:2px 0}
/* Assistant teacher tools — collapsible panel (collapsed by default). */
.cls-tools-details>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:20px 18px;min-height:64px}
.cls-tools-details>summary::-webkit-details-marker{display:none}
.cls-tools-details[open]>summary{border-bottom:1px solid var(--line)}
.cls-tools-summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}
.cls-tools-caret{color:var(--muted);transition:transform .12s ease}
.cls-tools-details[open] .cls-tools-caret{transform:rotate(90deg)}
.cls-tools-body{margin-top:0}

/* "Online now" tile (admin home) — the digit is the count; hover or focus reveals who they are. */
.online-stat{position:relative;cursor:default}
.online-stat:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.online-stat .n{display:flex;align-items:center;gap:9px}
.online-dot{width:9px;height:9px;border-radius:50%;background:var(--ok);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--ok) 22%,transparent);animation:online-pulse 2.4s ease-in-out infinite}
.online-dot.idle{background:var(--dim);box-shadow:none;animation:none}
@keyframes online-pulse{0%,100%{opacity:1}50%{opacity:.45}}
@media (prefers-reduced-motion:reduce){.online-dot{animation:none}}

.online-pop{position:absolute;z-index:40;top:calc(100% + 8px);left:12px;min-width:230px;max-width:320px;
  max-height:320px;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 10px 30px rgba(16,20,35,.16);padding:10px 12px;
  opacity:0;visibility:hidden;transform:translateY(-4px);transition:opacity .12s ease,transform .12s ease,visibility .12s}
.online-stat:hover .online-pop,.online-stat:focus-within .online-pop,.online-stat:focus .online-pop{
  opacity:1;visibility:visible;transform:translateY(0)}
.online-pop-h{font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:var(--dim);margin-bottom:6px}
.online-list{list-style:none;margin:0;padding:0;display:grid;gap:6px}
.online-list li{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:13px}
.online-nm{font-weight:600;color:var(--ink)}
.online-meta{color:var(--dim);font-size:11px;white-space:nowrap}
.online-none{font-size:13px;color:var(--dim)}

/* ---------- teacher home: privacy mask ----------
   The dashboard is routinely projected in front of a class, so its numbers, the pending-approval
   names and the activity chart are hidden outright until asked for — not blurred. Blur was not
   enough: solid blocks of colour survive it and the shape of a week's activity still read across a
   room. The content keeps its box (visibility, not display) so nothing on the page jumps when it
   comes back. Clicking a tile or a panel toggles it; js/views/teacher-home.js owns the timing. */
.kc-private.masked .stat .n .val,
.kc-private.masked .panel-head .badge,
.kc-private.masked .panel .panel-body > *{visibility:hidden}
.kc-private.masked .stat,
.kc-private.masked .panel{background:var(--panel2)}
.kc-private.masked .stat,
.kc-private.masked .panel-head,
.kc-private.masked .panel .panel-body{cursor:pointer}
/* The hidden state has to look deliberate rather than broken, so each emptied panel says so. Set
   on the body itself (the children are what's hidden), so the note stays visible. */
.kc-private.masked .panel .panel-body{position:relative;min-height:64px}
.kc-private.masked .panel .panel-body::after{
  content:'Hidden for privacy. Click to view.';position:absolute;inset:0;display:grid;place-items:center;
  color:var(--dim);font-size:13px;letter-spacing:.01em;pointer-events:none;
}
.kc-private.masked .stat .n::after{content:'•••';color:var(--line);letter-spacing:2px;font-size:26px}
/* The two things on this page that appear ON DEMAND have to be shut off with it, or the mask is
   only as good as nobody touching the screen: the per-bar tooltip carries that day's exact minutes
   and date, and the presence tile's popover carries the NAMES of everyone signed in. Both are shown
   by their own visibility:visible, which the mask's inherited hidden cannot outrank — so they are
   named here. Hover, focus and focus-within alike: the first tap on a masked tile is swallowed by
   the reveal handler, but a keyboard tab through the page would otherwise walk straight into both. */
.kc-private.masked .bar-wrap:hover .bar-tip,
.kc-private.masked .bar-wrap:focus-visible .bar-tip,
.kc-private.masked .online-stat:hover .online-pop,
.kc-private.masked .online-stat:focus .online-pop,
.kc-private.masked .online-stat:focus-within .online-pop{opacity:0;visibility:hidden}

.privacy-hint{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--dim);
  margin:-6px 0 12px;min-height:18px}
.privacy-key{border:1px solid var(--line);background:var(--panel);border-radius:6px;padding:1px 6px;
  font-size:11.5px;font-weight:600;color:var(--ink)}
.privacy-countdown{display:none;align-items:center;gap:7px;color:var(--accent);font-weight:600}
.kc-private.shown .privacy-countdown{display:inline-flex}
.privacy-ring{width:16px;height:16px;transform:rotate(-90deg);flex:0 0 auto}
.privacy-ring circle{fill:none;stroke-width:3}
.privacy-ring .track{stroke:var(--line)}
.privacy-ring .lead{stroke:var(--accent);stroke-dasharray:37.7;stroke-dashoffset:0}
.kc-private.shown .privacy-ring .lead{animation:privacy-drain 10s linear forwards}
@keyframes privacy-drain{to{stroke-dashoffset:37.7}}
@media (prefers-reduced-motion:reduce){.kc-private.shown .privacy-ring .lead{animation:none;stroke-dashoffset:18}}

/* ---------- teacher home: current class code screen ----------
   Built to be read from the back of a classroom: the address and the join code are display-sized,
   and the QR (a fixed matrix for the Portal address) lets phones skip the typing. */
.modal:has(.codescreen){max-width:min(1180px,96vw)}
/* Projector view: blur the page out completely behind it, so the only thing readable in the room
   is the code itself. */
.modal-bg:has(.codescreen){backdrop-filter:blur(14px) saturate(.9);-webkit-backdrop-filter:blur(14px) saturate(.9);
  background:color-mix(in srgb,var(--ink) 55%,transparent)}
.codescreen{--kc-url-blue:#2f7fe8;display:flex;flex-direction:column;gap:20px;padding:8px 4px}
.code-class{display:flex;flex-direction:column;gap:10px}
/* The class is stepped through with ‹ ›; day/time/room filters hide behind the Filters button and
   only narrow which classes those arrows walk. */
.code-class-nav{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.code-class-nav .code-class-fixed{flex:1;min-width:0}
.code-step{font-size:20px;line-height:1;padding:2px 12px;font-weight:700}
.code-class-count{font-size:12px;color:var(--dim);font-variant-numeric:tabular-nums}
.code-filters-btn{margin-left:auto}
.code-class-filters{min-width:0;margin:0}
.code-class-filters .chiprow{gap:6px}
.code-chip-when{font-weight:400;opacity:.75;margin-left:4px}
.code-class-fixed{font-size:17px;font-weight:700}
.code-class-fixed span{display:block;font-size:12.5px;font-weight:400;color:var(--dim);margin-top:2px}
.codegrid{display:grid;grid-template-columns:1fr auto;gap:34px;align-items:center}
.code-lbl{font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}
.code-url{font-size:clamp(22px,3.9vw,52px);font-weight:700;letter-spacing:-.025em;white-space:nowrap;
  color:var(--kc-url-blue);margin:6px 0 28px}
.badge.code.code-big{font-size:clamp(42px,8vw,92px);font-weight:800;letter-spacing:5px;line-height:1.1;
  border-radius:18px;padding:10px 28px;margin-top:10px}
.badge.code.code-big.code-missing{font-size:20px;letter-spacing:1px;color:var(--dim)}
.code-qr{background:#fff;border:1px solid var(--line);border-radius:14px;padding:12px;
  display:grid;place-items:center;gap:8px}
.code-qr svg{width:clamp(180px,20vw,250px);height:clamp(180px,20vw,250px);display:block}
.code-qr-cap{font-size:12.5px;color:#697186;text-align:center}
@media (max-width:720px){
  .codegrid{grid-template-columns:1fr;justify-items:start}
  /* Scale the address to the width rather than wrapping it — a URL broken mid-word ("…com.a / u")
     is harder to read off a screen than a smaller one on a single line. */
  .code-url{font-size:clamp(14px,5vw,26px)}
}

/* Square icon button — the "show full size" control that sits beside a join code's Copy. Same
   height as .btn.sm next to it, but square, so the icon reads as a glyph rather than a label. */
.btn.btn-sq{padding:6px;width:30px;height:30px;justify-content:center;border-radius:9px;flex:0 0 auto}
.btn.btn-sq .ic-max{width:16px;height:16px;display:block}
.joincode-btns{display:inline-flex;align-items:center;gap:6px}
.joincode .btn-sq{margin-right:-4px}

/* css/quizauthor.css */
/* Task 4 (quiz authoring) styles. Additive only — do not edit css/app.css. */

/* ---- question cards (editor screen) ---- */
.qa-q .panel-head h3{font-size:14.5px;line-height:1.45;font-weight:600;flex:1;min-width:0}
.qa-num{display:inline-grid;place-items:center;min-width:34px;padding:2px 7px;margin-right:8px;
  border-radius:8px;background:var(--accent-soft);color:var(--accent);font-size:12px;font-weight:800}
.qa-q-body{padding:14px 18px;display:flex;flex-direction:column;gap:8px}
.qa-actions{display:flex;gap:7px;flex-wrap:wrap;justify-content:flex-end}

.qa-opt{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--line);
  border-radius:10px;background:var(--panel2);font-size:14px}
.qa-opt.correct{border-color:var(--ok);background:#eef7ee}
.qa-opt-body{flex:1;min-width:0;overflow-wrap:anywhere}
.qa-letter{display:inline-grid;place-items:center;width:22px;height:22px;flex:0 0 22px;border-radius:6px;
  background:#fff;border:1px solid var(--line);color:var(--dim);font-size:11.5px;font-weight:800}
.qa-opt.correct .qa-letter{background:var(--ok);border-color:var(--ok);color:#fff}
.qa-tick{color:var(--ok);font-size:12px;font-weight:800;white-space:nowrap}

.qa-expl{background:var(--panel2);border-left:3px solid var(--accent);padding:9px 13px;
  border-radius:0 10px 10px 0;font-size:13.5px;color:var(--dim)}
.qa-expl b{color:var(--ink);margin-right:5px}
.qa-tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:2px}

/* ---- segmented control (unit picker in new-quiz modal) — NO dropdown ---- */
.qa-seg{display:flex;flex-wrap:wrap;gap:6px}
.qa-seg-btn{cursor:pointer;border:1px solid var(--line);background:var(--panel2);color:var(--dim);
  font-size:13px;font-weight:600;padding:7px 13px;border-radius:10px;text-align:left;
  transition:background .12s,border-color .12s,color .12s}
.qa-seg-btn:hover{border-color:var(--accent);color:var(--ink)}
.qa-seg-btn.on{background:var(--accent);border-color:var(--accent);color:#fff}

/* ---- class chip strip (assign-to-class modal) — NO dropdown ---- */
.qa-chipstrip{display:flex;flex-wrap:wrap;gap:8px}
.qa-chip{cursor:pointer;border:1px solid var(--line);background:var(--panel2);color:var(--dim);
  font-size:13px;font-weight:700;padding:8px 13px;border-radius:11px;text-align:left;line-height:1.25;
  display:flex;flex-direction:column;gap:1px;transition:background .12s,border-color .12s,color .12s}
.qa-chip:hover{border-color:var(--accent);color:var(--ink)}
.qa-chip.on{background:var(--accent);border-color:var(--accent);color:#fff}
.qa-chip-sub{font-size:11px;font-weight:600;opacity:.75}

/* ---- question modal (option editor) ---- */
.qa-modal{max-width:680px}
.qa-optrow{display:flex;align-items:center;gap:9px;margin-bottom:8px}
/* correct-answer marker: a button, not a radio/dropdown */
.qa-mark{flex:0 0 auto;cursor:pointer;border:1px solid var(--line);background:var(--panel2);
  padding:3px;border-radius:8px;line-height:0;transition:border-color .12s,background .12s}
.qa-mark:hover{border-color:var(--ok)}
.qa-mark.on{background:var(--ok);border-color:var(--ok)}
.qa-mark.on .qa-letter{background:var(--ok);border-color:#fff;color:#fff}
.qa-optrow input[type=text]{flex:1}
.qa-x{flex:0 0 auto;border:0;background:none;color:var(--dim);font-size:19px;line-height:1;
  cursor:pointer;padding:2px 6px;border-radius:7px}
.qa-x:hover{color:var(--err);background:#fdecec}
.qa-x:disabled{opacity:.3;cursor:default;background:none;color:var(--dim)}
.qa-chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:6px}
.qa-chips .chip{text-align:left}

/* ---- delete confirm ---- */
.qa-del-stem{margin:10px 0 0;padding:10px 13px;background:var(--panel2);border:1px solid var(--line);
  border-radius:10px;font-weight:600}

/* css/student.css */
/* Task 5 (student home + quiz player) styles. Additive only — do not edit css/app.css. */

/* home: tile grid */
.student-home-hero{
  display:flex;
  align-items:center;
  /* The greeting takes the row's left, the "What's new" button its right (KC.whatsNew.btnHtml).
     They wrap rather than squeeze on a narrow phone: a long greeting in a language with long words
     would otherwise crush the button into two lines of its own. */
  justify-content:space-between;
  flex-wrap:wrap;
  gap:12px;
  margin:0 0 12px;
}
.student-home-hero h2{font-size:26px;line-height:1.05}
.student-home-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,360px);gap:20px;align-items:start}
.student-home-main{min-width:0}
.student-home-side{position:sticky;top:18px;min-width:0}
.student-side-title{
  color:var(--ink);
  font-size:13px;
  font-weight:900;
  letter-spacing:.08em;
  margin:0 0 8px;
  text-transform:uppercase;
}
.student-home-side .topbar{display:none}
.student-home-side .panel{margin-bottom:0}
.student-focus-home{
  display:grid;
  grid-template-columns:minmax(260px,1.2fr) minmax(220px,.9fr) minmax(220px,.8fr);
  gap:16px;
  align-items:stretch;
}
.student-focus-card{
  min-height:220px;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:9px;
  padding:22px;
  border:1px solid var(--line);
  border-radius:var(--radius);
  background:var(--panel);
  box-shadow:var(--shadow);
  color:var(--ink);
  font:inherit;
  text-align:left;
}
button.student-focus-card{
  cursor:pointer;
  transition:transform .15s,border-color .15s,box-shadow .15s;
}
button.student-focus-card:hover{
  transform:translateY(-2px);
  border-color:var(--accent);
  box-shadow:0 14px 32px color-mix(in srgb,var(--accent) 18%,transparent);
}
.student-focus-card.homework{
  color:#fff;
  border-color:transparent;
}
/* Beats button.student-focus-card:hover's border-color:var(--accent) — the homework tile stays
   borderless even on hover, since its coloured background already signals interactivity. */
button.student-focus-card.homework:hover{
  border-color:transparent;
}
.student-focus-card.homework .home-card-ic{
  color:inherit;
  background:rgba(255,255,255,.18);
}
.student-focus-card.homework .home-card-kicker,
.student-focus-card.homework .home-card-copy,
.student-focus-card.homework strong{color:#fff}
/* Flat colours, not gradients — a 135deg gradient across a wide short tile read as a stray band
   down the left edge rather than as shading. */
.student-focus-card.homework.start{
  background:#189b57;
}
.student-focus-card.homework.today{
  background:#f4bb32;
}
/* Amber is far lighter than the red and green, so white text on it sits near 2:1
   contrast. Amber alone takes near-black ink — same value the Homework page's amber band uses
   (.hw-band-amber in progress.css). */
.student-focus-card.homework.today .home-card-kicker,
.student-focus-card.homework.today .home-card-copy,
.student-focus-card.homework.today .home-card-class,
.student-focus-card.homework.today strong{color:#1f1500}
.student-focus-card.homework.today .home-card-ic{
  background:rgba(31,21,0,.14);
  color:#1f1500;
}
.student-focus-card.homework.urgent{
  background:#d63e52;
}
/* All homework done — calm light-blue sky with soft clouds. */
.student-focus-card.homework.done{
  background:
    radial-gradient(42px 26px at 22% 32%,rgba(255,255,255,.92),rgba(255,255,255,0) 70%),
    radial-gradient(58px 32px at 34% 30%,rgba(255,255,255,.85),rgba(255,255,255,0) 72%),
    radial-gradient(50px 30px at 74% 58%,rgba(255,255,255,.88),rgba(255,255,255,0) 72%),
    radial-gradient(38px 24px at 86% 64%,rgba(255,255,255,.8),rgba(255,255,255,0) 72%),
    radial-gradient(46px 28px at 56% 80%,rgba(255,255,255,.8),rgba(255,255,255,0) 72%),
    linear-gradient(160deg,#bfe0ff,#9fcbf5 60%,#88bdf0);
}
.student-focus-card.homework.done .home-card-ic{
  background:rgba(255,255,255,.55);
  color:#2a5f96;
}
.student-focus-card.homework.done .home-card-kicker,
.student-focus-card.homework.done .home-card-copy,
.student-focus-card.homework.done strong{color:#1f4d7a}
.student-focus-card.inspiration{
  background:linear-gradient(145deg,color-mix(in srgb,var(--accent-soft) 48%,var(--panel)),var(--panel));
  touch-action:pan-y;
  user-select:none;
}
.student-focus-card.inspiration strong{
  overflow-wrap:anywhere;
}
.student-focus-card.statistic{
  background:linear-gradient(145deg,color-mix(in srgb,#e8f6ef 68%,var(--panel)),var(--panel));
}
/* One headline size for every tile on the home page, whatever the tile and whatever state the
   student is in — the titles range from two words ("Homework due today") to a full sentence, and
   a per-variant size made the row look ragged. Sized for the longest sentence. */
.student-focus-card strong{
  margin-top:auto;
  font-size:clamp(19px,2.3vw,26px);
  line-height:1.08;
  color:var(--ink);
}
.student-focus-card .home-card-copy{
  font-size:15px;
  max-width:46ch;
}
/* Which class this card is for. Only rendered when the student has more than one, so a
   single-class student's card is unchanged. */
.student-focus-card .home-card-class{
  font-size:13px;
  font-weight:800;
  opacity:.85;
}
.student-focus-card.homework .home-card-class{color:#fff}
.student-focus-card.homework.done .home-card-class{color:#1f4d7a}
/* One homework card per class: even columns that wrap, rather than the fixed 3-up template the
   single-card layout uses. The 300px floor (up from 240px) means a third column only appears once
   the row is ~930px wide — on a narrowed desktop window the cards wrap to 2-up instead of being
   squeezed three across. */
.student-focus-home.multi{
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
}
/* The home page now carries ONE homework tile summarising every class, so it takes the full row
   rather than sitting in the first of three columns. It holds a single line of text, so it is
   shorter than the old multi-line card too. */
/* Half the row on a desktop — a single short banner stretched across 1200px reads as a page
   header rather than a thing to click. It goes full width on narrow screens (the 900px rule
   below), where half a row is too cramped for the longest headline. */
.student-focus-home.solo{grid-template-columns:repeat(2,1fr)}
.student-focus-card.solo{min-height:124px;gap:12px;padding:20px 22px;justify-content:center}
/* Icon and headline sit together: the `margin-top:auto` that pushed the title below two lines of
   supporting text would otherwise strand it at the bottom of an otherwise empty tile. */
.student-focus-card.solo strong{margin-top:0}
/* ---- RACI Chemistry & Art Competition tile -------------------------------------------------
   Second column of the solo row, beside the homework tile. Its background is one of the 2025
   artworks (picked in js/views/student-home.js), so the tile is the only thing on the page whose
   look changes between visits — which is the point: it advertises the competition with the
   competition's own work.

   The scrim is doing the accessibility work. The eight artworks run from near-black to a pale
   watercolour, so white text laid straight on them would pass on some and fail on others. A fixed
   dark wash under the text takes the worst case to ~5.4:1 and the best to ~13:1, both clear of AA,
   whichever image loads. It is angled rather than flat so the artwork still reads on the right,
   where there are no words. */
.student-focus-card.artcomp{
  position:relative;
  overflow:hidden;
  isolation:isolate;
  border-color:transparent;
  color:#fff;
}
.student-focus-card.artcomp .art-bg{
  position:absolute;
  inset:0;
  z-index:-2;
  background-size:cover;
  background-position:center;
  transform:scale(1.04);            /* pre-scaled so the hover zoom never reveals an edge */
  transition:transform .5s ease;
}
button.student-focus-card.artcomp:hover .art-bg{transform:scale(1.09)}
.student-focus-card.artcomp .art-scrim{
  position:absolute;
  inset:0;
  z-index:-1;
  background:linear-gradient(100deg,rgba(8,10,20,.86) 0%,rgba(8,10,20,.72) 45%,rgba(8,10,20,.45) 100%);
}
/* Like the homework tile, this one stays borderless on hover — the artwork already reads as a
   surface, and an accent border round it looks like a rendering fault. */
button.student-focus-card.artcomp:hover{
  border-color:transparent;
  box-shadow:0 14px 32px rgba(8,10,20,.28);
}
.student-focus-card.artcomp strong{color:#fff;text-shadow:0 1px 12px rgba(0,0,0,.5)}
.student-focus-card.artcomp .art-kicker{
  display:flex;
  align-items:center;
  gap:8px;
  font-size:12px;
  font-weight:800;
  letter-spacing:.07em;
  text-transform:uppercase;
  color:rgba(255,255,255,.86);
}
/* Same orange as the sidebar's NEW chip (.nav-new), so "new" means one thing across the portal. */
.student-focus-card.artcomp .art-new{
  background:#f97316;
  color:#fff;
  border-radius:999px;
  padding:2px 8px;
  font-size:10.5px;
  letter-spacing:.06em;
}
.student-focus-card.artcomp .art-foot{
  font-size:13px;
  font-weight:700;
  color:rgba(255,255,255,.9);
  text-shadow:0 1px 10px rgba(0,0,0,.5);
}
/* The artwork is decoration, not information — hold it still for anyone who has asked for less
   motion, and leave the scrim and text exactly as they are. */
@media (prefers-reduced-motion:reduce){
  .student-focus-card.artcomp .art-bg,
  button.student-focus-card.artcomp:hover .art-bg{transition:none;transform:scale(1.04)}
}

/* ---- White-book homework tile (migration 0114) --------------------------------------------
   The weekly written task out of the white exercise book, sitting immediately to the right of its
   class's homework card. Deliberately the plainest tile on the page — the homework card carries the
   urgency colour, and two shouting tiles side by side would cancel each other out. It reads as the
   book itself: off-white paper, faint ruling, a red margin rule down the left.
   Two controls (open the Homework page, tick it off), so like the Homework page's written tile it is
   a div wrapping two real buttons rather than one big button. */
.student-focus-card.whitebook{
  padding:0;
  overflow:hidden;
  background:
    linear-gradient(90deg,transparent 25px,color-mix(in srgb,var(--err) 30%,transparent) 25px,
      color-mix(in srgb,var(--err) 30%,transparent) 26px,transparent 26px),
    repeating-linear-gradient(180deg,var(--panel),var(--panel) 25px,
      color-mix(in srgb,var(--line) 55%,var(--panel)) 25px,var(--panel) 26px);
}
.student-focus-card.whitebook:focus-within{border-color:var(--accent)}
.whitebook-body{
  flex:1 1 auto;
  width:100%;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:9px;
  padding:22px 22px 14px 30px;
  background:none;
  border:0;
  color:inherit;
  font:inherit;
  text-align:left;
  cursor:pointer;
}
.whitebook-body:hover strong{color:var(--accent)}
.student-focus-card.whitebook .home-card-ic{
  color:var(--ink);
  background:color-mix(in srgb,var(--line) 45%,transparent);
}
.whitebook-done{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:7px;
  width:100%;
  padding:13px 18px;
  border:0;
  border-top:1px solid var(--line);
  background:var(--panel);
  color:var(--ink);
  font:inherit;
  font-size:13.5px;
  font-weight:750;
  cursor:pointer;
}
.whitebook-done:hover{background:var(--panel2);color:var(--accent)}
.whitebook-done:focus-visible{outline:2px solid var(--accent);outline-offset:-3px}
/* Only the done state shows a tick: an empty circle beside "Mark as completed" read as a separate
   control you had to tick as well as press. */
.whitebook-tick{display:inline-grid;place-items:center;font-size:12px;font-weight:800}
.whitebook-done.on{background:var(--ok);color:#fff}
.whitebook-done.on:hover{background:var(--ok);color:#fff;opacity:.9}
.student-focus-card.whitebook.is-done .home-card-kicker{color:var(--ok)}

.student-focus-card .home-card-link{
  color:var(--accent);
  font-size:13px;
  font-weight:850;
  text-decoration:none;
}
.student-focus-card .home-card-link:hover{text-decoration:underline}
.headline-dots{
  display:flex;
  gap:7px;
  margin-top:2px;
}
.headline-dot{
  width:8px;
  height:8px;
  border:0;
  border-radius:999px;
  background:color-mix(in srgb,var(--dim) 38%,transparent);
  cursor:pointer;
  padding:0;
}
.headline-dot.active{
  width:22px;
  background:var(--accent);
}
.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px}
.tile{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:22px 18px;text-align:left;cursor:pointer;font:inherit;color:var(--ink);transition:.15s}
.tile:hover{transform:translateY(-2px);border-color:var(--accent);box-shadow:0 12px 34px #5b53e62a}
.tile-ic{display:inline-grid;place-items:center;width:42px;height:42px;border-radius:11px;background:var(--accent-soft);color:var(--accent);font-size:20px;margin-bottom:12px}
.tile-t{font-size:16px;font-weight:700;margin-bottom:4px}
.tile-d{color:var(--dim);font-size:13px}

.home-card-board,.home-completed{margin:0 0 18px}
.home-section-head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  margin:0 0 10px;
}
.home-section-head h3{font-size:15px;color:var(--ink)}
.home-section-head span{color:var(--dim);font-size:12px;font-weight:800}
.home-card-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:14px;
}
.home-task-card{
  position:relative;
  min-height:176px;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:7px;
  padding:18px;
  border:1px solid var(--line);
  border-radius:var(--radius);
  background:var(--panel);
  box-shadow:var(--shadow);
  color:var(--ink);
  font:inherit;
  text-align:left;
  overflow:hidden;
}
button.home-task-card{cursor:pointer;transition:transform .15s,border-color .15s,box-shadow .15s}
button.home-task-card:hover{transform:translateY(-2px);border-color:var(--accent);box-shadow:0 14px 32px color-mix(in srgb,var(--accent) 18%,transparent)}
.home-task-card.quote{
  background:linear-gradient(145deg,color-mix(in srgb,var(--accent-soft) 54%,var(--panel)),var(--panel));
}
.home-task-card.assignment.done{
  background:color-mix(in srgb,var(--ok) 7%,var(--panel));
}
.home-card-ic{
  display:grid;
  place-items:center;
  width:42px;
  height:42px;
  border-radius:12px;
  background:var(--accent-soft);
  color:var(--accent);
  margin-bottom:8px;
}
.home-card-ic .line-ico{width:24px;height:24px}
.home-card-kicker{
  color:var(--dim);
  font-size:11px;
  font-weight:850;
  letter-spacing:.06em;
  text-transform:uppercase;
}
.home-task-card strong{font-size:17px;line-height:1.18;color:var(--ink)}
.home-card-copy{color:var(--dim);font-size:13px;line-height:1.42}
.home-card-status{
  margin-top:auto;
  border-radius:999px;
  padding:5px 10px;
  font-size:12px;
  font-weight:900;
}
.home-card-status.done{background:#e5f8e9;color:#087a3b}
.home-card-status.soon{background:#fff0ba;color:#0752a7}
.home-card-status.urgent{background:#ffe1e1;color:#9f1d2d}
.home-card-status.open{background:var(--panel2);color:var(--dim)}
.completed-grid .home-task-card{min-height:138px}
.completed-grid .home-card-ic{width:34px;height:34px;margin-bottom:4px}
.completed-grid .home-card-copy{display:none}
.home-show-more{margin-top:12px}

/* home: due-soon list */
.due-list{display:flex;flex-direction:column;gap:8px;padding:12px}
.due-list-row{
  width:100%;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:14px;
  background:var(--panel2);
  border:1px solid var(--line);
  border-radius:12px;
  padding:12px 14px;
  text-align:left;
  cursor:pointer;
  font:inherit;
  color:var(--ink);
}
.due-list-row:hover{border-color:var(--accent);background:color-mix(in srgb,var(--accent-soft) 55%,var(--panel2))}
.due-list-main{min-width:0;display:grid;gap:2px}
.due-list-main b{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.due-list-main small{color:var(--dim);font-size:12px}

/* home: chemistry streak heatmap */
.streak-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px;margin-bottom:18px}
.streak-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;margin-bottom:14px}
.streak-head h3{font-size:16px;margin:0 0 3px}
.streak-head p{margin:0;color:var(--dim);font-size:13px}
.heatmap{display:grid;grid-template-columns:repeat(14,1fr);grid-auto-rows:14px;gap:5px;max-width:560px}
.heat-cell{--heat-pct:0%;display:block;border-radius:4px;background:color-mix(in srgb,#16834c var(--heat-pct),#edf1f5);border:1px solid rgba(0,0,0,.04)}
.heat-cell.l1{background:#cdeed8}
.heat-cell.l2{background:#8ed7a6}
.heat-cell.l3{background:#43b86e}
.heat-cell.l4{background:#16834c;box-shadow:0 0 10px rgba(22,131,76,.22)}
.heat-legend{display:flex;align-items:center;gap:5px;color:var(--dim);font-size:11px;margin-top:12px}
.heat-legend .heat-cell{width:14px;height:14px}

/* quiz list */
.quiz-group{margin-bottom:26px}
.quiz-group-h{font-size:15px;margin:0 0 12px;display:flex;align-items:center;gap:10px}
.quiz-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}
.quiz-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px;text-align:left;cursor:pointer;font:inherit;color:var(--ink);display:flex;flex-direction:column;gap:8px;transition:.15s}
.quiz-card:hover{transform:translateY(-2px);border-color:var(--accent)}
.quiz-topic{font-size:11.5px;text-transform:uppercase;letter-spacing:.8px;color:var(--accent);font-weight:700}
.quiz-title{font-size:16px;font-weight:700}
.quiz-meta{display:flex;gap:7px;flex-wrap:wrap}
.quiz-go{margin-top:auto;color:var(--accent);font-weight:700;font-size:14px}
.quiz-one-shell{
  display:grid;
  gap:14px;
  max-width:880px;
  margin:0 auto;
}
.quiz-one-top{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
}
.quiz-one-card{
  min-height:320px;
}
.quiz-one-card .q-body{
  padding:clamp(18px,3vw,30px);
}
.q-meta-chips{
  display:flex;
  flex-wrap:wrap;
  gap:7px;
  margin:0 0 12px;
}
.q-meta-chip{
  display:inline-flex;
  align-items:center;
  min-height:24px;
  border-radius:999px;
  padding:4px 10px;
  font-size:11.5px;
  font-weight:900;
  letter-spacing:.03em;
  line-height:1.2;
}
.q-meta-chip.dot{
  background:color-mix(in srgb,var(--accent-soft) 72%,#fff);
  color:var(--accent);
}
.q-meta-chip.diff{
  background:#eef7ee;
  color:var(--ok);
}
.q-meta-chip.diff.d-L{background:#e7f6ec;color:#1f7a44}
.q-meta-chip.diff.d-M{background:#fbf0d2;color:#8a5d00}
.q-meta-chip.diff.d-H{background:#fce4df;color:#b23b2e}
.q-meta-chip.diff.d-E{background:#5b2a86;color:#fdf3ff}
.quiz-one-card .q-options{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:12px;
}
.quiz-one-card .opt{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;
  gap:12px;
  min-height:68px;
  padding:14px 16px;
  font-weight:650;
}
.quiz-one-card .opt-letter{
  display:grid;
  place-items:center;
  width:32px;
  height:32px;
  border-radius:999px;
  background:var(--accent-soft);
  color:var(--accent);
  font-weight:950;
}
.quiz-one-card .opt.on .opt-letter{
  background:var(--accent);
  color:#fff;
}
.quiz-one-card .opt.locked{
  cursor:default;
}
.quiz-one-card .opt.locked:not(.correct):not(.wrong){
  opacity:.62;
}
/* graded homework: chosen option while the server commits + checks it (brief) */
.quiz-one-card .opt.checking{
  opacity:1;
  border-color:var(--accent);
  animation:opt-checking 1s ease-in-out infinite;
}
@keyframes opt-checking{0%,100%{opacity:1}50%{opacity:.55}}
.quiz-one-card .opt.correct{
  border-color:#88d4a6;
  background:#e6f8ec;
  color:#0d6f3d;
  font-weight:650;
}
.quiz-one-card .opt.correct .opt-letter{
  background:#118348;
  color:#fff;
}
.quiz-one-card .opt.wrong{
  border-color:#eea8b4;
  background:#ffe6ea;
  color:#9f1d2d;
  font-weight:650;
}
.quiz-one-card .opt.on{
  font-weight:650;
}
.quiz-one-card .opt.wrong .opt-letter{
  background:#c82f46;
  color:#fff;
}
.quiz-one-card .opt-mark{
  min-width:18px;
  text-align:center;
  font-weight:950;
}
.q-explain{
  display:none;
  align-items:center;
  justify-content:space-between;
  gap:14px;
  margin-top:16px;
  border:1px solid var(--line);
  border-radius:12px;
  background:var(--panel2);
  color:var(--ink);
  padding:12px 14px;
  font-size:14px;
  line-height:1.55;
}
.q-explain.show{
  display:flex;
}
.q-explain-text{
  min-width:0;
  flex:1;
}
.quiz-nav-bar{
  max-width:880px;
  margin:18px auto 0;
  justify-content:flex-end;
}

/* personalised practice */
.personal-practice{
  width:100%;
  text-align:left;
  display:grid;
  gap:8px;
  margin:0 0 22px;
  padding:3px;
  border:0;
  border-radius:18px;
  color:var(--ink);
  background:linear-gradient(115deg,var(--accent),var(--accent2),#35a36b);
  box-shadow:0 16px 38px rgba(10,97,201,.18);
  cursor:pointer;
  font:inherit;
}
.personal-practice > *{margin-left:18px;margin-right:18px}
.personal-practice:before{content:"";display:block;grid-row:1/-1;grid-column:1;min-height:0}
.personal-practice{grid-template-columns:1fr;background-origin:border-box}
.personal-practice span,.personal-practice strong{position:relative;z-index:1}
.personal-practice:after{
  content:"";
  grid-column:1;
  grid-row:1 / span 4;
  margin:3px;
  border-radius:15px;
  background:linear-gradient(180deg,#fff,#f7fbff);
  z-index:0;
}
.personal-kicker{grid-row:1;grid-column:1;align-self:end;margin-top:18px;color:var(--accent);font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:1.4px}
.personal-practice strong{grid-row:2;grid-column:1;font-size:clamp(20px,3vw,30px);line-height:1.05}
.personal-copy{grid-row:3;grid-column:1;color:var(--dim);font-size:14px;max-width:760px}
.personal-action{grid-row:4;grid-column:1;justify-self:start;margin-bottom:18px;background:var(--accent);color:#fff;border-radius:999px;padding:9px 14px;font-weight:800;font-size:13px}
.personal-hero{overflow:hidden}
.personal-hero-body{padding:26px;max-width:760px}
.personal-hero h3{font-size:clamp(24px,4vw,42px);line-height:1.04;margin:6px 0 12px}
.personal-hero p{color:var(--dim);max-width:680px}
.personal-steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-bottom:18px}
.personal-step{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:16px;display:flex;gap:13px}
.personal-step-num{width:40px;height:40px;border-radius:12px;background:var(--accent-soft);color:var(--accent);display:grid;place-items:center;font-weight:900}
.personal-step p{margin:3px 0 0;color:var(--dim);font-size:13px}
.personal-template .panel-body{padding:18px}
.personal-load{
  display:grid;
  grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr);
  gap:18px;
  align-items:stretch;
}
.personal-load-simple{
  grid-template-columns:minmax(0,920px);
}
.personal-load-card{
  min-height:360px;
  border:1px solid var(--line);
  border-radius:22px;
  background:
    radial-gradient(circle at 86% 18%,color-mix(in srgb,var(--accent2) 28%,transparent),transparent 28%),
    linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--accent-soft) 70%,var(--panel)));
  box-shadow:var(--shadow);
  padding:28px;
  display:flex;
  flex-direction:column;
  justify-content:center;
  overflow:hidden;
}
.personal-load-top{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:10px}
.personal-load-card h3{font-size:clamp(30px,5vw,58px);line-height:1.02;margin:8px 0 12px;max-width:780px}
.personal-load-card p{margin:0;color:var(--dim);font-size:17px;line-height:1.45;max-width:680px}
.personal-start{
  align-self:flex-start;
  opacity:0;
  pointer-events:none;
  transform:translateY(8px);
  border:0;
  border-radius:8px;
  padding:15px 26px;
  margin:0 0 18px;
  /* subtle monochrome fade — a single accent, gently lit at the top */
  background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 88%,#fff),var(--accent));
  color:#fff;
  font:inherit;
  font-size:18px;
  font-weight:800;
  letter-spacing:.01em;
  cursor:pointer;
  /* steady accent glow (no pulse) */
  box-shadow:0 14px 34px color-mix(in srgb,var(--accent) 30%,transparent);
  transition:opacity .4s ease,transform .4s ease,box-shadow .25s ease,filter .25s ease;
}
.personal-start.ready{
  opacity:1;
  pointer-events:auto;
  transform:none;
}
.personal-start.ready:hover{
  filter:brightness(1.04);
  box-shadow:0 16px 40px color-mix(in srgb,var(--accent) 40%,transparent);
}
.personal-start[data-started="1"]{
  opacity:1;
  pointer-events:none;
  transform:none;
  filter:none;
  background:var(--ok);
  box-shadow:0 14px 34px color-mix(in srgb,var(--ok) 28%,transparent);
}
.personal-load-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.personal-load-simple .personal-load-stats{grid-template-columns:repeat(2,minmax(0,180px))}
.personal-load-stats span{
  border:1px solid var(--line);
  background:color-mix(in srgb,var(--panel) 82%,transparent);
  border-radius:14px;
  padding:12px;
}
.personal-load-stats b{display:block;color:var(--accent);font-size:25px;line-height:1}
.personal-load-stats small{display:block;color:var(--dim);font-size:11.5px;font-weight:750;margin-top:4px}
.personal-priority{
  display:grid;
  gap:10px;
  margin-top:2px;
}
.personal-priority-head{
  color:var(--dim);
  font-size:11px;
  font-weight:950;
  letter-spacing:.08em;
  text-transform:uppercase;
}
.personal-priority-grid{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:10px;
}
.personal-priority-dot{
  min-width:0;
  border:1px solid var(--line);
  background:color-mix(in srgb,var(--panel) 84%,transparent);
  border-radius:14px;
  padding:13px 14px;
}
.personal-priority-dot b{
  display:block;
  color:var(--accent);
  font-size:clamp(26px,4vw,42px);
  font-weight:950;
  line-height:.95;
  letter-spacing:0;
  margin-bottom:8px;
  font-variant-numeric:tabular-nums;
}
.personal-priority-dot span{
  display:block;
  color:var(--ink);
  font-size:12.5px;
  font-weight:750;
  line-height:1.3;
}
.personal-game-board{
  display:grid;
  gap:12px;
  counter-reset:node;
}
.personal-flow{
  display:grid;
  grid-template-columns:repeat(7,minmax(0,1fr));
  gap:8px;
  align-self:start;
}
.personal-flow-step{
  min-height:72px;
  border:1px solid var(--line);
  background:var(--panel);
  border-radius:16px;
  padding:10px 8px;
  display:grid;
  gap:6px;
  align-content:center;
  justify-items:center;
  text-align:center;
  box-shadow:var(--shadow);
  color:var(--dim);
  transition:border-color .2s,background .2s,transform .2s,color .2s;
}
.personal-flow-step span{
  width:26px;
  height:26px;
  display:grid;
  place-items:center;
  border-radius:999px;
  background:var(--panel2);
  color:inherit;
  font-weight:950;
  font-size:12px;
}
.personal-flow-step b{
  color:inherit;
  font-size:12px;
  line-height:1.15;
  text-transform:uppercase;
  letter-spacing:.03em;
}
.personal-flow-step.on{
  border-color:color-mix(in srgb,var(--accent) 52%,var(--line));
  background:color-mix(in srgb,var(--accent-soft) 58%,var(--panel));
  color:var(--accent);
  transform:translateY(-1px);
}
.personal-flow-step.on span{background:var(--accent);color:#fff}
.personal-node,.personal-reward{
  border:1px solid var(--line);
  background:var(--panel);
  border-radius:18px;
  box-shadow:var(--shadow);
  padding:16px;
  color:var(--ink);
  transition:border-color .2s,transform .2s,background .2s;
}
.personal-node{
  display:grid;
  grid-template-columns:42px minmax(0,1fr);
  column-gap:12px;
  align-items:center;
}
.personal-node span{
  grid-row:1 / span 2;
  width:42px;
  height:42px;
  border-radius:14px;
  display:grid;
  place-items:center;
  background:var(--panel2);
  color:var(--dim);
  font-weight:900;
}
.personal-node b{font-size:15px}
.personal-node small,.personal-reward small{color:var(--dim);font-size:12.5px;line-height:1.35}
.personal-node.on{
  border-color:color-mix(in srgb,var(--accent) 55%,var(--line));
  background:color-mix(in srgb,var(--accent-soft) 48%,var(--panel));
  transform:translateX(2px);
}
.personal-node.on span{background:var(--accent);color:#fff}
.personal-rewards{
  grid-column:1 / -1;
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:12px;
}
.personal-reward span{
  width:42px;
  height:42px;
  border-radius:14px;
  display:grid;
  place-items:center;
  background:var(--accent-soft);
  color:var(--accent);
  margin-bottom:10px;
}
.personal-reward .line-ico{width:22px;height:22px}
.personal-reward b{display:block;margin-bottom:3px}
.personal-reward.on{
  border-color:color-mix(in srgb,var(--accent) 55%,var(--line));
  background:linear-gradient(180deg,var(--panel),color-mix(in srgb,var(--accent-soft) 54%,var(--panel)));
}
.practice-focus{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;margin-bottom:18px}
.practice-focus-card{
  display:grid;
  gap:6px;
  border:1px solid var(--line);
  background:var(--panel);
  border-radius:var(--radius);
  box-shadow:var(--shadow);
  padding:16px;
}
.practice-focus-card .line-ico{width:25px;height:25px;color:var(--accent)}
.practice-focus-card b{font-size:14px}
.practice-focus-card span{font-size:12.5px;color:var(--dim);line-height:1.4}
.practice-tile-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px;margin-bottom:20px}
/* Calibrated set sits on a row of its own, full width, above the three difficulty sets. */
.practice-tile-grid.one-up{grid-template-columns:1fr;margin-bottom:14px}
.practice-tile-grid.one-up .practice-tile{min-height:0}
/* Exactly three sets — give them equal thirds rather than auto-fill, so the row reads as a set of
   three rather than three tiles floating in a wider grid. Stacks on narrow screens. */
.practice-tile-grid.three-up{grid-template-columns:repeat(3,1fr)}
@media(max-width:720px){.practice-tile-grid.three-up{grid-template-columns:1fr}}
/* VCE units add the standing Key science skills tile on the end, so the row is four equal columns.
   Drops to two before it drops to one — four narrow tiles read worse than two comfortable ones. */
.practice-tile-grid.four-up{grid-template-columns:repeat(4,1fr)}
@media(max-width:1000px){.practice-tile-grid.four-up{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.practice-tile-grid.four-up{grid-template-columns:1fr}}
/* One row of Easy/Medium/Hard tiles per enrolled course. */
.pr-course-row{margin-bottom:4px}
.pr-course-row-head{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--dim);margin-bottom:8px}
.practice-tile{
  min-height:170px;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:8px;
  border:1px solid var(--line);
  border-radius:14px;
  background:var(--panel);
  color:var(--ink);
  box-shadow:var(--shadow);
  padding:16px;
  cursor:pointer;
  text-align:left;
  font:inherit;
}
.practice-tile:hover{border-color:var(--accent);transform:translateY(-1px)}
.practice-tile.calibrated{border:4px solid transparent;background:linear-gradient(var(--panel),var(--panel)) padding-box,linear-gradient(130deg,var(--accent),var(--accent2),#35a36b) border-box}
.practice-tile-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:10px;background:var(--accent-soft);color:var(--accent)}
.practice-tile-icon .line-ico{width:21px;height:21px}
.practice-kicker{font-size:11px;font-weight:900;letter-spacing:.9px;text-transform:uppercase;color:var(--accent)}
.practice-tile strong{font-size:16px;line-height:1.22;overflow-wrap:anywhere}
.practice-tile strong.tile-title-sm{font-size:14px;line-height:1.24}
.practice-tile strong.tile-title-md{font-size:12.5px;line-height:1.26}
.practice-tile strong.tile-title-xs{font-size:11.5px;line-height:1.3}
.practice-tile span:last-child{margin-top:auto;color:var(--dim);font-size:12.5px;line-height:1.35}
/* Difficulty-tiered practice tiles: same green/yellow/red/purple convention as .diff-chip. */
.practice-diff-label{margin-top:auto;font-weight:900;font-size:12.5px;letter-spacing:.02em}
/* The whole tile carries a pale wash of its band colour, inside a thicker border, so the
   Easy/Medium/Hard split reads at a glance rather than from the label alone. */
.practice-tile.tile-diff-easy,
.practice-tile.tile-diff-medium,
.practice-tile.tile-diff-hard{border-width:3px;padding:14px}
.practice-tile.tile-diff-easy{
  border-color:color-mix(in srgb,#1f7a44 40%,var(--line));
  background:color-mix(in srgb,#1f7a44 8%,var(--panel));
}
.practice-tile.tile-diff-easy .practice-tile-icon{background:color-mix(in srgb,#1f7a44 16%,var(--panel));color:#1f7a44}
.practice-tile.tile-diff-easy .practice-diff-label{color:#1f7a44}
.practice-tile.tile-diff-easy:hover{border-color:#1f7a44}
.practice-tile.tile-diff-medium{
  border-color:color-mix(in srgb,#8a5d00 38%,var(--line));
  background:color-mix(in srgb,#c9922a 10%,var(--panel));
}
.practice-tile.tile-diff-medium .practice-tile-icon{background:color-mix(in srgb,#c9922a 20%,var(--panel));color:#8a5d00}
.practice-tile.tile-diff-medium .practice-diff-label{color:#8a5d00}
.practice-tile.tile-diff-medium:hover{border-color:#8a5d00}
.practice-tile.tile-diff-hard{
  border-color:color-mix(in srgb,#b23b2e 38%,var(--line));
  background:color-mix(in srgb,#b23b2e 8%,var(--panel));
}
.practice-tile.tile-diff-hard .practice-tile-icon{background:color-mix(in srgb,#b23b2e 16%,var(--panel));color:#b23b2e}
.practice-tile.tile-diff-hard .practice-diff-label{color:#b23b2e}
.practice-tile.tile-diff-hard:hover{border-color:#b23b2e}
/* Key science skills — not a difficulty, a standing topic, so it takes the course blue rather than
   sitting on the green→amber→red scale the three sets read as. */
.practice-tile.tile-kss{
  border-width:3px;padding:14px;
  border-color:color-mix(in srgb,#1553A2 38%,var(--line));
  background:color-mix(in srgb,#1553A2 8%,var(--panel));
}
.practice-tile.tile-kss .practice-tile-icon{background:color-mix(in srgb,#1553A2 16%,var(--panel));color:#1553A2}
.practice-tile.tile-kss .practice-diff-label{color:#1553A2}
.practice-tile.tile-kss:hover{border-color:#1553A2}
/* The dot-point numbers each difficulty set covers — codes only, no titles. Tabular figures keep the
   columns of numbers aligned; the tile colour tints the chips so they read as part of the tile. */
.practice-dots{display:flex;flex-wrap:wrap;gap:4px 5px;margin:2px 0 4px}
.practice-dots i{
  font-style:normal;
  font-size:11px;
  font-weight:800;
  font-variant-numeric:tabular-nums;
  line-height:1;
  padding:4px 6px;
  border-radius:6px;
  background:color-mix(in srgb,var(--ink) 7%,transparent);
  color:var(--dim);
}
.practice-dots i.more{background:transparent;font-weight:700}
.tile-diff-easy .practice-dots i{background:color-mix(in srgb,#1f7a44 13%,transparent);color:#1f7a44}
.tile-diff-medium .practice-dots i{background:color-mix(in srgb,#8a5d00 13%,transparent);color:#8a5d00}
.tile-diff-hard .practice-dots i{background:color-mix(in srgb,#b23b2e 13%,transparent);color:#b23b2e}
/* Calibrated card lists the personalised focus dot points in place of a generic subtitle. */
.calibrated-focus{display:grid;gap:5px;width:100%}
.calibrated-focus em{font-style:normal;color:var(--dim);font-size:10.5px;font-weight:950;letter-spacing:.07em;text-transform:uppercase}
.calibrated-focus i{font-style:normal;display:block;color:var(--ink);font-size:12px;line-height:1.3;overflow-wrap:anywhere}
.calibrated-focus i b{color:var(--accent);font-weight:900;font-variant-numeric:tabular-nums;margin-right:6px}

/* play */
.play{max-width:760px}
.q-body{padding:18px}
.q-stem{font-size:15.5px;font-weight:600;margin-bottom:14px;line-height:1.55}
.q-num{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:8px;background:var(--accent-soft);color:var(--accent);font-size:13px;font-weight:800;margin-right:6px}
.q-options{display:flex;flex-direction:column;gap:9px}
.opt{text-align:left;font:inherit;font-size:14.5px;color:var(--ink);background:var(--panel2);border:1.5px solid var(--line);border-radius:11px;padding:12px 15px;cursor:pointer;transition:.12s}
.opt:hover{border-color:var(--accent2)}
.opt.on{border-color:var(--accent);background:var(--accent-soft);font-weight:600}
.submit-bar{position:sticky;bottom:0;display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow);padding:12px 16px;margin:6px 0 20px}

/* result + review */
.score-hero{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:34px;text-align:center;margin-bottom:18px;max-width:760px}
.score-n{font-size:54px;font-weight:800;color:var(--accent)}
.score-max{font-size:26px;color:var(--dim);font-weight:700}
.score-msg{color:var(--dim);font-size:15px;margin:8px 0 18px}
.score-actions{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
/* No coloured left edge — correct/incorrect is carried by the ✓/✗ badge in .rv-row. */
.review-item{max-width:760px}
.rv-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.rv-ans{font-size:13.5px;color:var(--dim)}
.rv-ans b{color:var(--ink)}
.rv-expl{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:11px 14px;font-size:13.5px;color:var(--ink);line-height:1.55}

/* question figures (migrated kc-figure SVGs are theme-aware via currentColor) */
.q-visual{margin:0 0 14px;--muted:var(--dim);--surface-2:var(--panel2)}  /* alias old-app tokens used inside kc-figure SVGs */
.q-visual .kc-figure{margin:0 auto;padding:10px 12px;background:var(--panel2);border:1px solid var(--line);border-radius:10px}
/* Cap landscape spectra/diagrams to a modest width so a figure can't fill the
   whole question column; a figure wrapping a molecule keeps its small baked cap. */
.q-visual .kc-figure:not(:has(svg.struct)){max-width:460px !important}
.q-visual svg{max-width:100%;height:auto;display:block}
.q-visual figcaption{font-size:12px;color:var(--dim);margin-top:6px}
.q-stem sub,.q-stem sup,.opt sub,.opt sup{font-size:.72em}
.q-table{border-collapse:collapse;margin:10px 0 4px;font-size:14px;font-weight:400}
.q-table th,.q-table td{border:1px solid var(--line);padding:6px 12px;text-align:center}
.q-table th{background:var(--panel2);font-weight:600}

@media(max-width:900px){
  .student-home-grid{grid-template-columns:1fr}
  .student-home-side{position:static}
  .student-focus-home{grid-template-columns:1fr}
  /* Needs the same specificity as the half-width rule above to beat it, not just to follow it. */
  .student-focus-home.solo{grid-template-columns:1fr}
  .student-focus-card{min-height:180px}
  .personal-load{grid-template-columns:1fr}
  .personal-flow{grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media(max-width:600px){
  .q-body{padding:14px}.score-hero{padding:24px}
  .quiz-one-card .q-options{grid-template-columns:1fr}
  .heatmap{grid-template-columns:repeat(10,1fr)}
  .student-home-hero h2{font-size:24px}
  .student-focus-card{min-height:160px;padding:18px}
  .student-focus-card strong{font-size:21px}
  .personal-load-card{min-height:0;padding:22px}
  .personal-load-card h3{font-size:32px}
  .personal-load-stats,.personal-rewards{grid-template-columns:1fr}
  .personal-priority-grid{grid-template-columns:1fr}
  .personal-flow{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ---------- Tips from Examiner Reports (quizzes.kind = 'examtips') ---------- */
/* The block shares the top row of the Quizzes page with the calibrated set. Teal is the only strong
   hue on that page not already carrying meaning — indigo is the calibrated set, and green/amber/red
   are the difficulty bands — so it reads as a distinct family rather than another difficulty. */
:root{ --et:#0e7490; --et-dark:#0a6076; --et-soft:#e0f2f7; }
.practice-tile-grid.two-up{grid-template-columns:1fr 1fr;margin-bottom:14px;align-items:stretch}
@media(max-width:900px){.practice-tile-grid.two-up{grid-template-columns:1fr}}

.examtips{
  border-radius:14px;
  box-shadow:var(--shadow);
  padding:15px;
  display:flex;
  flex-direction:column;
  gap:12px;
  color:#fff;
  /* Diagonal teal gradient — no pattern. A hatch competed with the set names sitting on top of it. */
  background:linear-gradient(135deg,#12839f 0%,#0b5f78 100%);
}
.examtips-head{display:flex;align-items:center;gap:11px}
.examtips-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:10px;background:#ffffff2b;color:#fff;flex:none}
.examtips-icon .line-ico{width:21px;height:21px}
.examtips-head-t{display:grid;gap:1px;min-width:0}
.examtips-kicker{font-size:11px;font-weight:900;letter-spacing:.9px;text-transform:uppercase;color:#bfe8f2}
.examtips-head strong{font-size:16px;line-height:1.2;color:#fff;overflow-wrap:anywhere}
.examtips-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}
@media(max-width:520px){.examtips-grid{grid-template-columns:1fr}}
.et-tile{
  background:var(--panel);border:2px solid var(--line);border-radius:10px;padding:11px 12px;
  display:flex;flex-direction:column;gap:5px;text-align:left;min-height:92px;
  color:var(--ink);font:inherit;cursor:pointer;
  transition:transform .12s ease,border-color .12s ease;
}
/* Hover matches .practice-tile: the border takes the tile's own colour and it lifts 1px. */
.et-tile:hover{transform:translateY(-1px)}
.et-code{font-size:11px;font-weight:900;letter-spacing:.06em;color:var(--et);font-variant-numeric:tabular-nums}
/* Bumped from 12.8px: at that size the two-line set names read as a caption rather than a title. */
.et-name{font-size:14.5px;font-weight:750;line-height:1.25}
.et-meta{margin-top:auto;display:flex;align-items:center;gap:7px;font-size:11.5px;color:var(--dim)}
.et-best{font-weight:900;color:var(--et);font-variant-numeric:tabular-nums}
.et-dot{width:3px;height:3px;border-radius:50%;background:var(--line);flex:none}

/* Score bands. Thresholds are the same 80/50 split kc_examtips_retag() uses for its difficulty
   banding, so the feature only ever has one set of cut-offs. Unattempted is a near-white grey:
   deliberately the quietest state, since at launch every tile is in it. Same green/amber/red hues
   as the Easy/Medium/Hard practice tiles. */
.et-tile.et-s-none{background:#f5f6f8;border-color:#e6e9ef}
.et-tile.et-s-none .et-code{color:#7b8394}
.et-tile.et-s-none:hover{border-color:#c3c9d4}
.et-tile.et-s-low{background:#fdecea;border-color:color-mix(in srgb,#b23b2e 34%,transparent)}
.et-tile.et-s-low .et-code,.et-tile.et-s-low .et-best{color:#b23b2e}
.et-tile.et-s-low:hover{border-color:#b23b2e}
.et-tile.et-s-mid{background:#fdf4e2;border-color:color-mix(in srgb,#8a5d00 32%,transparent)}
.et-tile.et-s-mid .et-code,.et-tile.et-s-mid .et-best{color:#8a5d00}
.et-tile.et-s-mid:hover{border-color:#8a5d00}
.et-tile.et-s-high{background:#e8f7ee;border-color:color-mix(in srgb,#1f7a44 34%,transparent)}
.et-tile.et-s-high .et-code,.et-tile.et-s-high .et-best{color:#1f7a44}
.et-tile.et-s-high:hover{border-color:#1f7a44}

/* ---- the player ---- */
.et-play{max-width:880px;margin:0 auto}
.et-play-head{display:flex;align-items:center;gap:14px;margin-bottom:14px}
.et-play-title{display:grid;gap:1px;min-width:0}
.et-play-kicker{font-size:11px;font-weight:900;letter-spacing:.9px;text-transform:uppercase;color:var(--et)}
.et-play-title strong{font-size:19px;line-height:1.2}
.et-play-head .btn{margin-left:auto;flex:none}
.et-progress{height:5px;border-radius:999px;background:var(--line);overflow:hidden;margin-bottom:16px}
.et-progress i{display:block;height:100%;width:0;border-radius:999px;background:var(--et);transition:width .2s ease}
/* Staff walkthrough banner: preview-as is read-only, so no attempt is opened and nothing is saved. */
.et-preview-note{
  border:1px solid color-mix(in srgb,var(--warn) 40%,var(--line));
  background:color-mix(in srgb,var(--warn) 10%,var(--panel));
  color:#7a4c00;
  border-radius:10px;padding:9px 12px;font-size:12.5px;line-height:1.4;margin-bottom:14px;
}
.et-qmeta{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.et-qnum{font-size:11px;font-weight:900;letter-spacing:.07em;text-transform:uppercase;color:var(--dim)}
/* Selected-but-unmarked picks up teal rather than the usual indigo, so the run feels like its own
   thing; the green/red marked states are left exactly as every other quiz renders them. */
.et-card .opt.on:not(.correct):not(.wrong){border-color:var(--et);background:var(--et-soft)}
.et-card .opt.on:not(.correct):not(.wrong) .opt-letter{background:var(--et);color:#fff}
.et-verdict{font-weight:900;font-size:13px;margin-bottom:6px}
.et-verdict.ok{color:#0d6f3d}
.et-verdict.no{color:#9f1d2d}
/* The examiner's own words, and — in grey — the 19 notes that are Kennedy College guidance. */
.et-note{display:grid;gap:4px}
.et-note-label{font-size:10.5px;font-weight:950;letter-spacing:.07em;text-transform:uppercase;color:#0d6f3d}
.et-note-kc .et-note-label{color:var(--dim)}
.et-note-body{font-size:13.5px;line-height:1.5;color:var(--ink)}
.et-nav{display:flex;align-items:center;gap:12px;max-width:880px;margin:16px auto 0}
.et-nav .btn.primary{margin-left:auto}
.et-nav-hint{font-size:12.5px;color:var(--dim);text-align:center;flex:1}
.et-score{text-align:center}
.et-score .panel-body{padding:26px 18px}
.et-score-n{font-size:40px;font-weight:900;color:var(--et);font-variant-numeric:tabular-nums}
.et-score-l{font-size:14px;color:var(--dim);margin-top:2px}
.et-score-best{font-size:13px;color:var(--dim);margin:10px 0 16px}

/* css/progress.css */
/* Student homework + progress styles. */

/* homework */
.hw-tick{display:inline-grid;place-items:center;width:24px;height:24px;border-radius:999px;border:1.5px solid var(--line);color:#fff;font-size:13px;font-weight:800}
.hw-tick.done{background:var(--ok);border-color:var(--ok)}
.hw-class{color:var(--dim);font-size:12px}
/* One class = one row of tiles, exactly one tile high, scrolling sideways when the class has more
   homework than fits. Tiles are a fixed width and refuse to shrink, so a row of two and a row of
   nine line up down the page. Horizontal inset is 18px, not 14px, to sit under the panel head's
   own 18px padding — otherwise the tiles start 4px left of the class name above them. Vertical
   stays 14px, and scroll-padding keeps a snapped tile clear of the panel's left edge. */
.hw-row{
  display:flex;gap:14px;padding:14px 18px;
  overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x proximity;scroll-padding-left:18px;
  -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;
}
.hw-row>.hw-tile{flex:0 0 240px;scroll-snap-align:start}
/* No scrollbar under the row: js/kc-scroll.js tags it .kc-rail, which fades the row's own pixels
   out at whichever edge has more tiles and puts a nudge button under the mouse. See css/scroll.css. */
.hw-done-block[hidden]{display:none}
.hw-done-block{border-top:1px solid var(--line)}
.hw-done-block .hw-done-hint{display:block;padding:12px 18px 0}
.hw-done-block .hw-row{padding-top:10px}

.hw-tile{
  min-height:152px;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:8px;
  border:1px solid var(--line);
  border-radius:14px;
  background:var(--panel2);
  color:var(--ink);
  box-shadow:var(--shadow);
  padding:15px;
  cursor:pointer;
  text-align:left;
  font:inherit;
}
.hw-tile:hover{border-color:var(--accent);transform:translateY(-1px)}
.hw-tile-top{width:100%;display:flex;align-items:center;justify-content:space-between;gap:10px}
.hw-tile-title{font-weight:800;font-size:15px;line-height:1.25}
.hw-instructions{color:var(--dim);font-size:12.5px;line-height:1.35;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.hw-due{margin-top:auto;color:var(--dim);font-size:12.5px}
.hw-overdue .hw-tile-title{color:var(--err)}

/* Urgency bands, styled exactly like the home page's homework focus card (.student-focus-card
   .homework in student.css): the same three colours, white text.
   Red = due today/tomorrow (or overdue), amber = 2-3 days, green = 4+ days or no due date.
   The badge repeats the band in words, so the signal never rests on hue alone. */
.hw-band-green,.hw-band-amber,.hw-band-red{color:#fff}
/* Amber is a light background, so white text sits near 2:1 contrast — too weak for the 12.5px
   class/date lines. Amber alone takes near-black ink instead; green and red stay white. Kept in
   step with .student-focus-card.homework.today in student.css, which paints the same amber. */
.hw-band-amber{color:#1f1500}
/* Every tile is now the .hw-tile-written wrapper (body button + footer bar), so the gradient
   belongs on .hw-open only — painting it on the outer tile let it show through the tile's own
   (transparent-colored) border as a thin ring past the footer bar's rounded corner. */
.hw-band-green .hw-open{background:linear-gradient(135deg,#0f8a47,#21a663)}
.hw-band-amber .hw-open{background:linear-gradient(135deg,#f6c945,#f2a91f)}
.hw-band-red .hw-open{background:linear-gradient(135deg,#c9283d,#e25767)}
.hw-band-green .hw-tile-title,.hw-band-red .hw-tile-title,
.hw-band-green .hw-class,.hw-band-red .hw-class,
.hw-band-green .hw-due,.hw-band-red .hw-due,
.hw-band-green .hw-instructions,.hw-band-red .hw-instructions{color:#fff}
.hw-band-amber .hw-tile-title,.hw-band-amber .hw-class,
.hw-band-amber .hw-due,.hw-band-amber .hw-instructions{color:#1f1500}
.hw-band-green .hw-tick,.hw-band-red .hw-tick{border-color:rgba(255,255,255,.55)}
.hw-band-amber .hw-tick{border-color:rgba(31,21,0,.5)}
.hw-band-amber .hw-badge{background:rgba(255,255,255,.42)}
.hw-band-green:hover,.hw-band-amber:hover,.hw-band-red:hover{border-color:transparent}
/* Beat the two rules that assume an unpainted tile: the overdue title (red on red) and the
   written tile's hover title (accent blue on the gradient). */
.hw-band-red.hw-overdue .hw-tile-title,
.hw-band-green .hw-open:hover .hw-tile-title,
.hw-band-red .hw-open:hover .hw-tile-title{color:#fff}
.hw-band-amber .hw-open:hover .hw-tile-title{color:#1f1500}

.hw-badge{display:inline-block;padding:3px 9px;border-radius:999px;font-size:11.5px;font-weight:800;
  letter-spacing:.01em;white-space:nowrap;background:rgba(255,255,255,.18);color:inherit}

/* Written homework tile: the body opens the message, the footer button is the student's own
   "I've done this" tick. Two controls, so the tile is a div wrapping two real buttons. */
.hw-tile-written{padding:0;cursor:default;overflow:hidden;gap:0}
.hw-tile-written:hover{transform:none;border-color:var(--line)}
.hw-tile-written:focus-within{border-color:var(--accent)}
.hw-open{flex:1 1 auto;width:100%;display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:15px 15px 10px;background:none;border:0;border-radius:13px 13px 0 0;color:inherit;font:inherit;text-align:left;cursor:pointer}
.hw-open:hover .hw-tile-title{color:var(--accent)}
.hw-donebtn{
  display:flex;align-items:center;justify-content:center;gap:7px;
  width:100%;padding:11px 15px;
  border:0;border-top:1px solid var(--line);border-radius:0 0 13px 13px;
  background:var(--panel);color:var(--ink);
  font:inherit;font-size:13px;font-weight:700;cursor:pointer;
}
.hw-donebtn:hover{background:var(--panel2);color:var(--accent)}
/* Only the DONE state shows a tick, and it's a bare glyph — an empty circle next to
   "Mark as done" read as a control you had to tick separately from the button. */
.hw-donebtn-tick{display:inline-grid;place-items:center;font-size:12px;font-weight:800}
.hw-donebtn.on{background:var(--ok);color:#fff}
.hw-donebtn.on:hover{background:var(--ok);color:#fff;opacity:.9}

/* mastery bars */
.mast-list{padding:14px 18px;display:flex;flex-direction:column;gap:13px}
.mast-row{display:grid;grid-template-columns:minmax(180px,1.2fr) 2fr 86px;gap:14px;align-items:center}
.mast-label{font-size:13.5px;font-weight:600;display:flex;align-items:center;gap:8px;min-width:0}
.mast-track{height:12px;border-radius:999px;background:var(--panel2);border:1px solid var(--line);overflow:hidden}
.mast-fill{height:100%;border-radius:999px;transition:width .4s ease}
.mast-fill.low{background:var(--err)}
.mast-fill.mid{background:var(--warn)}
.mast-fill.high{background:var(--ok)}
.mast-pct{font-size:14px;font-weight:800;text-align:right}
.mast-tries{display:block;font-size:11px;font-weight:600;color:var(--dim)}
.grade-summary-cards{grid-template-columns:minmax(220px,340px)}

/* grades table */
.grade-table{width:100%;border-collapse:separate;border-spacing:0 4px;padding:4px 8px 10px}
.grade-table th{border-bottom:0;padding:5px 8px}
.grade-table td{border-bottom:0;padding:5px 8px;background:transparent}
.grade-row{overflow:hidden}
.grade-row td:first-child{border-radius:10px 0 0 10px}
.grade-row td:last-child{border-radius:0 10px 10px 0}
.grade-row.green td{background:#e5f8ec;color:#123d25}
.grade-row.yellow td{background:#fff6c8;color:#4f3d00}
.grade-row.orange td{background:#ffe9d1;color:#55330d}
.grade-row.red td{background:#ffe1e1;color:#5c1b23}
.grade-row.na td{background:#eceef1;color:#5b626b}
.grade-row.na .na{font-weight:700;opacity:.7;letter-spacing:.02em}
.grade-code{font-weight:900;font-variant-numeric:tabular-nums}
.grade-table td:last-child b{display:block;font-size:14px}
.grade-actions{
  min-width:184px;
  text-align:right;
  white-space:nowrap;
}
.grade-actions .btn{
  opacity:0;
  transform:translateX(8px);
  pointer-events:none;
  transition:opacity .14s ease,transform .14s ease,border-color .12s,color .12s,background .12s;
  margin-left:6px;
}
.grade-row:hover .grade-actions .btn,
.grade-row:focus-within .grade-actions .btn{
  opacity:1;
  transform:translateX(0);
  pointer-events:auto;
}
.motivation-grid{display:flex;flex-wrap:wrap;gap:8px;padding:14px 16px}
.motivation-chip{border:1px solid var(--line);background:var(--panel2);border-radius:999px;padding:7px 11px;font-size:12.5px;font-weight:750;color:var(--ink)}

@media(max-width:760px){
  .grade-actions{white-space:normal;min-width:132px}
  .grade-actions .btn{opacity:1;pointer-events:auto;transform:none;margin:2px 0 2px 4px}
}
@media(max-width:640px){.mast-row{grid-template-columns:1fr 70px;grid-template-areas:"label pct" "track pct"}.mast-label{grid-area:label}.mast-track{grid-area:track}.mast-pct{grid-area:pct}}
.hw-done-toggle{margin:0}
.hw-done-hint{font-size:12.5px;font-weight:400;color:var(--dim)}
.hw-all-done{padding:18px;margin:0;color:var(--dim);font-size:14px}

/* css/analytics.css */
/* Task 7 (analytics) styles. Additive only — do not edit css/app.css.
   All classes are namespaced "an-" so they stay independent of other tasks' files. */

/* ---- inline stats row inside a panel ---- */
.an-stats{display:flex;gap:34px;flex-wrap:wrap;padding:16px 18px;border-bottom:1px solid var(--line)}
.an-stat .n{font-size:24px;font-weight:800;color:var(--accent);line-height:1.2}
.an-stat .l{color:var(--dim);font-size:12.5px}
.an-stat.dt .n{font-size:15px;padding-top:7px}

/* ---- section sub-heading inside a panel ---- */
.an-sub{padding:14px 18px 0;font-size:11.5px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.7px}

/* ---- 7-day minutes bar chart (own variant; independent of teacher.css .bars) ---- */
/* A whole term's weeks live in here; --gap and the bar width come from .kc-bars, so the row is as
   wide as it needs to be and the scroller shows --vis of it. */
/* Scrolling lives on .kc-bars-scroll (css/app.css) now, not here: three bars in view, the rest
   scrolled to, with the edge fades KC.barScroll lights. */
.an-bars{display:flex;align-items:flex-end;gap:var(--gap);height:150px;padding:18px 18px 6px}
.an-bar-wrap{display:flex;flex-direction:column;align-items:center;height:100%;gap:6px}
/* With only three bars in view a 38px bar is a needle in the middle of its slot — let it grow with
   the slot but stop short of a solid block of colour. */
.an-bar-zone{flex:1;width:100%;max-width:68px;display:flex;align-items:flex-end}
.an-bar{width:100%;background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 86%,#fff),var(--accent));border-radius:6px 6px 2px 2px;min-height:4px}
.an-bar-l{font-size:10.5px;color:var(--dim);white-space:nowrap}
.an-bars-sum{padding:10px 18px 14px;color:var(--dim);font-size:13px;border-top:1px solid var(--line)}

/* ---- two-pane chart layout ---- */
/* min-width:0 — grid items default to min-width:auto, so the wide bar strip inside a pane
   stretched the pane past the panel instead of letting .an-bars scroll. */
.an-two-pane{display:grid;grid-template-columns:1fr 1fr;min-width:0}
.an-pane{min-width:0}
.an-pane-divider{border-left:1px solid var(--line)}
.an-pane-head{padding:12px 18px 2px;font-size:11.5px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.7px}
@media(max-width:640px){.an-two-pane{grid-template-columns:1fr}.an-pane-divider{border-left:none;border-top:1px solid var(--line)}}

/* ---- homework completion bars (horizontal, doneFrac 0–1) ---- */
.an-comp{padding:12px 18px 16px;display:flex;flex-direction:column;gap:12px}
.an-comp-row{display:grid;grid-template-columns:minmax(120px,230px) 1fr 44px;align-items:center;gap:12px}
.an-comp-name{font-size:13.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.an-comp-track{height:10px;background:var(--panel2);border:1px solid var(--line);border-radius:999px;overflow:hidden}
.an-comp-fill{height:100%;background:linear-gradient(90deg,var(--accent),color-mix(in srgb,var(--accent) 86%,#fff));border-radius:999px;min-width:2px}
.an-comp-pct{font-size:12.5px;color:var(--dim);font-weight:700;text-align:right}
@media(max-width:640px){.an-comp-row{grid-template-columns:minmax(90px,150px) 1fr 40px}}

/* Panel heads wrap so a head-mounted picker has room to sit under its title. */
.an-cols .panel > .panel-head{flex-wrap:wrap;row-gap:8px;align-items:center}

/* ---- search box above the class chips (replaces the old student-picker panel) ---- */
.an-search-wrap{position:relative;margin-bottom:12px}
.an-search-wrap input{width:100%;height:42px;padding:9px 14px;font-size:14px;background:var(--panel);
  border:1px solid var(--line);border-radius:10px}
.an-search-results:not(:empty){position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:5;
  background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden;
  box-shadow:0 8px 24px rgba(0,0,0,.12)}
.an-stu-list{display:flex;flex-direction:column}
.an-stu-item{display:flex;align-items:baseline;gap:10px;padding:9px 14px;border:none;border-top:1px solid var(--line);background:transparent;text-align:left;cursor:pointer;font:inherit;font-size:13px;color:var(--dim);width:100%}
/* class label helps disambiguate same-name students across the whole cohort */
.an-stu-cls{margin-left:auto;font-size:11px;color:var(--dim);opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:52%}
.an-stu-item:first-child{border-top:none}
.an-stu-item:hover{background:var(--accent-soft);color:var(--ink)}
.an-stu-item.on{background:var(--accent-soft);color:var(--accent)}
.an-stu-empty{padding:13px 14px;color:var(--dim);font-size:13px}

/* ---- class filter chips (wrapped rows, shared with the Classes page) ---- */
.an-filterchips{margin:0 0 14px}

/* ---- sortable headings (same affordance as the Usage / Practice materials tabs) ---- */
.an-table th.th-sort{cursor:pointer;user-select:none;white-space:nowrap}
.an-table th.th-sort:hover,.an-table th.th-sort.on{color:var(--ink)}
.an-table th.th-sort:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.an-table .th-arrow{opacity:.6;font-size:.85em;margin-left:3px}

/* ---- expanded class row: which pages that class used, and its homework completion ---- */
.an-caret{color:var(--dim);font-size:12.5px;line-height:1;width:12px;flex:none}
.an-table-row.open{background:var(--accent-soft)}
.an-drawer-row td{background:var(--panel2);padding:0 !important}
.an-drawer{display:grid;grid-template-columns:1.35fr 1fr;gap:18px;padding:14px 18px 16px}
.an-drawer-h{font-size:11.5px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.7px;margin-bottom:8px}
.an-drawer-none{color:var(--dim);font-size:13px;padding:4px 0 6px}
.an-drawer .an-comp{padding:0;gap:10px}
.an-subtable{width:100%;border-collapse:collapse;font-size:13px}
.an-subtable th{text-align:left;font-size:11px;font-weight:700;color:var(--dim);text-transform:uppercase;
  letter-spacing:.06em;padding:5px 10px 5px 0;border-bottom:1px solid var(--line)}
.an-subtable td{padding:6px 10px 6px 0;border-bottom:1px solid var(--line)}
.an-subtable tr:last-child td{border-bottom:none}
.an-subtable th.num,.an-subtable td.num{text-align:right;padding-right:0;white-space:nowrap}
@media(max-width:760px){.an-drawer{grid-template-columns:1fr;gap:14px}}

/* ---- full-width "All classes" table ---- */
.an-table{width:100%;border-collapse:collapse;font-size:13.5px}
.an-table th{text-align:left;font-size:11.5px;font-weight:700;color:var(--dim);text-transform:uppercase;
  letter-spacing:.07em;padding:10px 18px;border-bottom:1px solid var(--line)}
.an-table td{padding:11px 18px;border-bottom:1px solid var(--line);vertical-align:middle}
.an-table tbody tr:last-child td{border-bottom:none}
.an-table-row{cursor:pointer}
.an-table-row:hover{background:var(--accent-soft)}
.an-table-name{display:flex;align-items:center;gap:9px;font-weight:600}
.an-cls-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
.an-table-bar{display:flex;align-items:center;gap:10px}
.an-table-bar .an-comp-track{flex:1 1 auto;min-width:80px}
.an-table-bar .an-comp-pct{flex:0 0 auto;white-space:nowrap}
@media(max-width:640px){
  .an-table th:nth-child(2),.an-table td:nth-child(2){display:none}
  .an-table th,.an-table td{padding:9px 12px}
}

/* ---- content area: class table/detail, or a picked student's panel ---- */
.an-content > .panel{margin:0}

/* class/student switch refreshes the content column IN PLACE — the old data stays visible (never
   blanks) and we only show this cue if the fetch is slow enough to notice (added after 180ms). */
.an-content.an-busy{position:relative}
.an-content.an-busy > *{opacity:.45;transition:opacity .15s ease}
.an-content.an-busy::after{content:"";position:absolute;left:50%;top:34px;width:22px;height:22px;margin-left:-11px;
  border-radius:50%;border:2.5px solid var(--line);border-top-color:var(--accent);animation:an-spin .7s linear infinite}
@keyframes an-spin{to{transform:rotate(360deg)}}

/* ---- recent-activity list ---- */
.an-recent{list-style:none;margin:0;padding:6px 0 4px}
.an-recent li{display:flex;align-items:center;gap:10px;padding:9px 18px;border-top:1px solid var(--line);font-size:13.5px}
.an-recent li:first-child{border-top:0}
.an-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);flex:none}
.an-recent .when{margin-left:auto;color:var(--dim);font-size:12.5px;white-space:nowrap}

/* ---- email engagement (analytics by-student) ---- */
.an-email{padding:10px 18px 14px}
.an-email-subj{font-weight:600;font-size:13.5px}
.an-email-meta{color:var(--dim);font-size:12px;margin:2px 0 9px}
.an-email-pills{display:flex;flex-wrap:wrap;gap:6px}

/* ---- phone ---- */
@media(max-width:560px){
  /* --gap, not gap: the bar width is calculated from it in .kc-bar-item, so the two must agree. */
  .an-barscroll{--gap:5px}
  .an-bars{height:130px;padding:14px 12px 6px}
  .an-bar-l{font-size:9px}
  .an-bars-sum,.an-pane-head,.an-sub{padding-left:12px;padding-right:12px}
  .an-comp{padding:12px 12px 14px}
  /* name / track / % stops fitting on one line — stack the label above its bar */
  .an-comp-row{grid-template-columns:1fr auto;grid-template-areas:"name pct" "track track";gap:5px 10px}
  .an-comp-name{grid-area:name;white-space:normal;overflow:visible}
  .an-comp-pct{grid-area:pct}
  .an-comp-track{grid-area:track}
}

/* css/analytics-v2.css */
/* analytics-v2.css — the admin-only "v2" Analytics tab.
   Exception-first dashboard: what needs attention, then every number against the window before it.

   SPACING CONTRACT (the whole point of this sheet — v1 drifted):
     · one horizontal gutter, 18px, from panel head to table cell to attention row to drawer;
     · one vertical step, 14px inside a block, 18px between blocks;
     · numeric columns are right-aligned and tabular so digits line up column-to-column;
     · nothing carries a decorative coloured edge — severity is shown by a dot in its own
       fixed-width column, so every row's text starts on the same x whatever its severity. */

/* ---- header: the scope sentence, then the range chips ---- */
.v2-head{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin:0 0 18px}
.v2-scope{margin:0;font-size:13px;color:var(--dim);line-height:1.45;max-width:52ch}
.v2-head .qan-rangebar{margin:0}

/* ---- shared: deltas against the preceding window ---- */
.v2-delta{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:800;
  padding:2px 8px;border-radius:999px;white-space:nowrap;font-variant-numeric:tabular-nums}
.v2-delta.up{background:#eaf7ee;color:var(--ok)}
.v2-delta.down{background:#fdecec;color:var(--err)}
.v2-delta.flat{background:var(--panel2);color:var(--dim)}
.v2-delta.na{background:var(--panel2);color:var(--dim);font-weight:600}

/* ---- stat cards: number, label, then one delta line that always sits at the same height ---- */
.v2-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px;margin-bottom:18px}
.v2-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:16px 18px;display:flex;flex-direction:column}
.v2-card .n{font-size:30px;font-weight:800;color:var(--accent);line-height:1.12;font-variant-numeric:tabular-nums}
.v2-card .l{color:var(--dim);font-size:13px;margin-top:2px}
.v2-card .foot{display:flex;align-items:center;gap:8px;margin-top:auto;padding-top:12px;flex-wrap:wrap}
.v2-card .prev{font-size:11.5px;color:var(--dim);font-variant-numeric:tabular-nums}

/* metric definition tooltip — inline, never shifts the baseline of the label it sits in */
.v2-info{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
  border-radius:50%;border:1px solid var(--line);color:var(--dim);font-size:10px;font-weight:800;
  cursor:help;margin-left:5px;vertical-align:1px;line-height:1}

/* ---- needs attention ---- */
.v2-attn-count{background:var(--warn);color:#fff;border-radius:999px;font-size:11.5px;
  font-weight:800;padding:2px 9px;margin-left:8px;font-variant-numeric:tabular-nums}
.v2-attn-row{display:grid;grid-template-columns:14px 1fr auto;align-items:start;gap:12px;
  padding:13px 18px;border-bottom:1px solid var(--line);width:100%;text-align:left;
  background:none;border-left:0;border-right:0;border-top:0;font:inherit;color:inherit;cursor:pointer}
.v2-attn-row:last-child{border-bottom:0}
.v2-attn-row:hover{background:var(--accent-soft)}
.v2-attn-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* Dot lives in its own column and is nudged onto the first line's optical centre, so the text
   column starts at the same x on every row regardless of severity. */
.v2-sev{width:9px;height:9px;border-radius:50%;margin-top:5px;flex-shrink:0}
.v2-sev.high{background:var(--err)}
.v2-sev.mid{background:var(--warn)}
.v2-sev.low{background:var(--dim)}
.v2-attn-txt{font-size:13.5px;line-height:1.45;min-width:0}
.v2-attn-txt b{font-weight:700}
.v2-attn-why{display:block;color:var(--dim);font-size:12px;margin-top:3px;line-height:1.45}
/* The action is a span, not a nested <button> (a button inside a button is invalid and swallows the
   row's own click), so it has to borrow the row's hover state to look alive. */
.v2-attn-go{flex-shrink:0;align-self:center;pointer-events:none}
.v2-attn-row:hover .v2-attn-go{border-color:var(--accent);color:var(--accent)}
.v2-allclear{display:flex;align-items:center;gap:10px;padding:16px 18px;font-size:13.5px;color:var(--dim)}
.v2-allclear .tick{color:var(--ok);font-weight:800}

/* ---- daily chart: real axis, weekend shading, median reference ---- */
.v2-chart{padding:16px 18px 14px}
.v2-chart-grid{display:grid;grid-template-columns:42px 1fr;gap:12px;align-items:start}
.v2-yaxis{position:relative;height:150px;font-size:11px;color:var(--dim);text-align:right;
  font-variant-numeric:tabular-nums}
.v2-yaxis span{position:absolute;right:0;transform:translateY(-50%);line-height:1}
.v2-plot{position:relative;height:150px;border-bottom:1px solid var(--line)}
.v2-gridline{position:absolute;left:0;right:0;border-top:1px solid var(--line);opacity:.55}
.v2-median{position:absolute;left:0;right:0;border-top:1px dashed var(--dim);opacity:.75}
.v2-median b{position:absolute;right:0;top:-8px;transform:translateY(-100%);font-size:10.5px;
  font-weight:700;color:var(--dim);background:var(--panel);padding:0 5px;white-space:nowrap}
.v2-bars{position:absolute;inset:0;display:flex;align-items:flex-end;gap:4px}
.v2-bwrap{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:flex-end;height:100%}
.v2-bwrap.wknd{background:var(--panel2)}
.v2-bar{background:linear-gradient(180deg,var(--accent2),var(--accent));border-radius:4px 4px 0 0;min-height:2px}
.v2-bwrap.wknd .v2-bar{background:color-mix(in srgb,var(--dim) 45%,var(--panel2))}
.v2-xaxis{display:flex;gap:4px;margin-top:7px}
.v2-xaxis div{flex:1;min-width:0;text-align:center;font-size:10.5px;color:var(--dim);font-weight:600;
  overflow:hidden;white-space:nowrap}
.v2-chart-foot{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;
  margin-top:14px;padding-top:12px;border-top:1px solid var(--line);font-size:12px;color:var(--dim)}
.v2-legend{display:flex;gap:14px;align-items:center;flex-wrap:wrap}
.v2-legend span{display:inline-flex;align-items:center;gap:6px}
.v2-legend i{width:10px;height:10px;border-radius:2px;flex-shrink:0}
.v2-legend i.dash{height:0;width:14px;border-radius:0;border-top:2px dashed var(--dim)}

/* ---- tables: one gutter, numerics right-aligned and tabular ---- */
.v2-table{width:100%;border-collapse:collapse;font-size:13.5px}
.v2-table th{text-align:left;font-size:11.5px;font-weight:700;color:var(--dim);text-transform:uppercase;
  letter-spacing:.07em;padding:10px 18px;border-bottom:1px solid var(--line);white-space:nowrap}
.v2-table td{padding:11px 18px;border-bottom:1px solid var(--line);vertical-align:middle}
.v2-table tbody tr:last-child > td{border-bottom:0}
.v2-table .num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.v2-table th.num{text-align:right}
/* Fixed minimums stop the layout shifting as values change width row to row (a 3-digit delta next
   to a 1-digit one used to drag the whole column sideways on every range switch). */
.v2-table td.num{min-width:74px}
.v2-table .v2-spark{width:52px;justify-content:flex-start}
.v2-hint{color:var(--dim);font-weight:400}
.v2-note{font-size:12px;color:var(--dim);padding:12px 18px;border-top:1px solid var(--line);line-height:1.5}

/* class rows expand in place, so the league table never scrolls away underneath you */
.v2-clsrow{cursor:pointer}
.v2-clsrow:hover{background:var(--accent-soft)}
.v2-clsname{display:flex;align-items:center;gap:9px;font-weight:600;min-width:0}
.v2-caret{width:11px;flex-shrink:0;color:var(--dim);transition:transform .15s ease;font-size:10px;line-height:1}
.v2-clsrow.open .v2-caret{transform:rotate(90deg)}
.v2-clsdot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
.v2-per{display:flex;align-items:center;gap:10px}
.v2-track{height:9px;background:var(--panel2);border:1px solid var(--line);border-radius:999px;overflow:hidden;flex:1;min-width:70px}
.v2-fill{height:100%;background:linear-gradient(90deg,var(--accent),color-mix(in srgb,var(--accent) 86%,#fff));
  border-radius:999px;min-width:2px}
.v2-per .v{font-weight:700;font-size:13px;font-variant-numeric:tabular-nums;white-space:nowrap;
  min-width:38px;text-align:right}
.v2-sub{display:block;color:var(--dim);font-size:12px;margin-top:3px;font-variant-numeric:tabular-nums}

.v2-drawer{display:none;background:var(--panel2)}
.v2-drawer.show{display:table-row}
.v2-drawer > td{padding:16px 18px 18px;border-bottom:1px solid var(--line)}
/* An open drawer on the last class would otherwise stack its border on the panel's own edge. */
.v2-table tbody tr:last-child > td{border-bottom:0}
/* The row above an open drawer keeps its rule; the drawer supplies the closing one. */
.v2-clsrow.open > td{border-bottom-color:transparent}
.v2-drawer-h{font-size:11.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--dim);margin:0 0 12px}
.v2-hw{display:flex;flex-direction:column;gap:12px;margin:0 0 16px}
.v2-hwrow{display:grid;grid-template-columns:minmax(120px,240px) 1fr 92px;align-items:center;gap:12px}
.v2-hwname{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.v2-hwpct{font-size:12.5px;color:var(--dim);font-weight:700;text-align:right;font-variant-numeric:tabular-nums}
.v2-missing{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px 16px}
.v2-missing:not(:last-child){margin-bottom:12px}
.v2-names{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px}
.v2-name{background:var(--panel2);border:1px solid var(--line);border-radius:999px;padding:4px 11px;font-size:12.5px;font-weight:600}
.v2-drawer-actions{display:flex;gap:8px;flex-wrap:wrap}

/* ---- student roster ---- */
.v2-stale{color:var(--err);font-weight:700}
.v2-warmish{color:var(--warn);font-weight:700}
.v2-spark{display:inline-flex;align-items:flex-end;gap:2px;height:18px;vertical-align:middle}
.v2-spark i{width:3px;background:var(--accent);opacity:.5;border-radius:1px;min-height:2px}
.v2-toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.v2-search{width:min(320px,52vw);padding:8px 12px;border:1px solid var(--line);border-radius:10px;
  font:inherit;font-size:13.5px;background:var(--panel);color:var(--ink)}
.v2-search:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}

/* Panel heads on this page carry a chip group + buttons; let them wrap rather than crush the title. */
.v2-page .panel-head{flex-wrap:wrap;row-gap:10px}

/* Range switches refetch; dim the body rather than blanking it, so the page never jumps.
   (v1's .an-busy is scoped to .an-content, which this page does not use.) */
.v2-busy{position:relative}
.v2-busy > *{opacity:.45;transition:opacity .15s ease;pointer-events:none}
.v2-busy::after{content:"";position:absolute;left:50%;top:60px;width:22px;height:22px;margin-left:-11px;
  border:2px solid var(--line);border-top-color:var(--accent);border-radius:50%;animation:v2spin .7s linear infinite}
@keyframes v2spin{to{transform:rotate(360deg)}}

@media(max-width:760px){
  .v2-table th,.v2-table td,.v2-attn-row,.v2-note,.v2-chart,.v2-drawer > td{padding-left:14px;padding-right:14px}
  .v2-cards{gap:12px;margin-bottom:14px}
  .v2-hwrow{grid-template-columns:minmax(90px,150px) 1fr 78px}
  .v2-table .hide-sm{display:none}
  .v2-table td.num{min-width:0}
  .v2-chart-grid{grid-template-columns:32px 1fr;gap:8px}
  /* Two columns, action underneath: at this width a third column squeezed the sentence to
     two words a line. */
  .v2-attn-row{grid-template-columns:14px 1fr;row-gap:10px}
  .v2-attn-go{grid-column:2;justify-self:start;align-self:start}
  .v2-head{align-items:flex-start}
  .v2-scope{max-width:none}
}

/* css/quiz-analytics.css */
/* Quiz analytics tab (teacher-quiz-analytics) — the in-app sibling of the public quiz dashboard.
   Additive only; themed via the shared CSS variables (theme.css). Prefixed .qan- to avoid clashes. */

/* date-range bar */
/* .qan-range appearance now comes from the shared chip rules in app.css — keep layout only. */
.qan-rangebar{display:flex;align-items:center;gap:8px;margin:0 0 18px;flex-wrap:wrap}
.qan-range{transition:.12s}
.qan-resetbtn{margin-left:auto}

/* stat cards */
.qan-stats{display:flex;gap:14px;flex-wrap:wrap;margin:0 0 18px}
.qan-stat{flex:1 1 140px;min-width:130px;background:var(--panel);border:1px solid var(--line);
  border-radius:14px;padding:14px 16px}
.qan-stat-n{font-size:26px;font-weight:800;color:var(--accent);line-height:1.15}
.qan-stat-l{color:var(--dim);font-size:12.5px;margin-top:2px}

/* tables */
.qan-tablewrap{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}
.qan-table{width:100%;border-collapse:collapse;font-size:13.5px;line-height:1.35}
.qan-table th{text-align:left;font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;
  color:var(--dim);font-weight:700;padding:11px 18px;border-bottom:1px solid var(--line);white-space:nowrap}
.qan-table td{padding:13px 18px;border-bottom:1px solid var(--line);vertical-align:middle}
.qan-table tbody tr:last-child td{border-bottom:none}
.qan-table td:first-child,.qan-table th:first-child{padding-left:20px}
.qan-table td:last-child,.qan-table th:last-child{padding-right:20px}
.qan-table .num{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
.qan-table th.num{text-align:right}
.qan-empty{color:var(--dim);text-align:center;padding:28px 20px !important}
.qan-dim{color:var(--dim);font-size:12px}

/* clickable quiz rows (filter the student table) */
.qan-quizrow{cursor:pointer;transition:background .1s}
.qan-quizrow:hover{background:color-mix(in srgb,var(--accent) 7%,transparent)}
.qan-quizrow.selected{background:color-mix(in srgb,var(--accent) 14%,transparent)}

/* expandable student rows */
.qan-scope{color:var(--dim);font-size:12.5px;margin:0;padding:11px 18px 10px}
.qan-caret{width:34px;text-align:center}
.qan-table .qan-caret{padding-left:18px;padding-right:8px}
.qan-caret-ic{display:inline-block;color:var(--dim);transition:transform .12s}
.qan-stu{cursor:pointer;transition:background .1s}
.qan-stu:hover{background:color-mix(in srgb,var(--accent) 6%,transparent)}
.qan-stu.expanded .qan-caret-ic{transform:rotate(90deg);color:var(--accent)}
.qan-detail.hidden{display:none}
.qan-detail > td{padding:0 18px 0 52px;background:color-mix(in srgb,var(--accent) 4%,transparent)}
.qan-subtable{width:100%;border-collapse:collapse;font-size:12px}
.qan-subtable td{padding:5px 10px;line-height:1.2;border-bottom:1px solid var(--line);color:var(--dim)}
.qan-subtable tr:last-child td{border-bottom:none}
/* The expanded rows carry the same class="num" score cells as the table above them, so they need
   the same treatment — without this they sat left-aligned in proportional digits directly under a
   right-aligned, tabular column, and the numbers stopped lining up. */
.qan-subtable .num{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
.qan-tries{display:inline-flex;align-items:center;justify-content:center;min-width:28px;padding:2px 8px;border-radius:999px;
  background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);font-weight:700;font-size:12px}

/* hardest questions */
.qan-stem{max-width:520px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* pagination footer — bottom-left size buttons under a table */
.qan-pager{display:flex;align-items:center;gap:6px;padding:10px 18px;flex-wrap:wrap}
.qan-pagebtn{border:1px solid var(--line);background:var(--panel);color:var(--dim);font-size:12px;font-weight:600;
  padding:5px 10px;border-radius:8px;cursor:pointer;transition:.12s}
.qan-pagebtn:hover{background:color-mix(in srgb,var(--accent) 7%,transparent)}
.qan-pagebtn.on{background:var(--accent);border-color:var(--accent);color:var(--panel)}

@media(max-width:640px){
  .qan-rangebar{gap:7px}
  .qan-range{padding:7px 12px}
  .qan-resetbtn{margin-left:0}
  .qan-stat{flex-basis:calc(50% - 7px)}
  .qan-table{min-width:680px}
  .qan-table th,.qan-table td{padding-left:14px;padding-right:14px}
  .qan-table td:first-child,.qan-table th:first-child{padding-left:16px}
  .qan-table td:last-child,.qan-table th:last-child{padding-right:16px}
  .qan-scope{padding-left:16px;padding-right:16px}
  .qan-stem{max-width:200px}
}

/* Examiner-tips attempts, kept visually distinct from ordinary quizzes in the Quizzes tab.
   Their averages are deliberately low — students are meant to get these wrong first time — so the
   badge must make clear they are not being compared with ordinary quiz results (migration 0190). */
.badge.examtips{background:#e0f2f7;color:#0a6076}

/* "Tips from Examiner Reports" panel: one row per student, one column per set, cell = questions
   answered out of that set's total. Progress, not score — these sets save after every question, so
   a part-finished set is the normal state and has to read as progress rather than as a bad result.
   A completed set is tinted; everything else is plain text so the eye lands on the gaps. */
.et-cell{display:inline-block;min-width:48px;font-variant-numeric:tabular-nums;font-weight:600}
.et-cell.done{background:color-mix(in srgb,var(--ok,#1a7f5a) 12%,transparent);color:var(--ok,#1a7f5a);
  border-radius:7px;padding:2px 8px}
.qan-scope [data-et-all]{margin-left:6px;vertical-align:baseline}

/* css/students.css */
/* students styles. Additive only — do not edit css/app.css. */

/* ---- directory ---- */
.st-search{min-width:260px;max-width:340px}
.toolbar:has(#stSearch){
  flex-wrap:nowrap;
  max-width:100%;
}
.toolbar:has(#stSearch) .btn{
  flex:0 0 auto;
}
.toolbar:has(#stSearch) .st-search{
  flex:1 1 320px;
  width:auto;
}
@media(max-width:520px){
  .toolbar:has(#stSearch){
    flex-wrap:wrap;
  }
  .toolbar:has(#stSearch) .st-search{
    flex-basis:100%;
    max-width:none;
  }
}
.st-zero{color:var(--dim)}
.st-name-line{display:flex;align-items:center;gap:8px;min-width:0}
.st-name-line .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.st-profile-title{display:flex;align-items:center;gap:10px;min-width:0}
.st-profile-title h2{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* email as an inline button: envelope + address; opens the message composer, not the profile */
.st-email{display:inline-flex;align-items:center;gap:2px;margin-top:3px;padding:0;border:0;background:none;
  font:inherit;font-size:12.5px;color:var(--accent,#5b53e6);cursor:pointer;text-align:left;border-radius:6px}
.st-email:hover{text-decoration:underline}
.st-email:focus-visible{outline:2px solid var(--accent,#5b53e6);outline-offset:2px}
.st-email-head{font-size:13px}
/* assistants can't message: plain, non-interactive email */
.st-email-static{color:var(--dim,#6b7280);cursor:default}
.st-email-static:hover{text-decoration:none}
.st-email-icon{
  width:28px;
  height:28px;
  display:inline-grid;
  place-items:center;
  flex:none;
  border:1px solid var(--line);
  background:var(--panel2);
  color:var(--accent);
  border-radius:9px;
  cursor:pointer;
}
.st-email-icon .line-ico{width:15px;height:15px}
.st-email-icon:hover{border-color:var(--accent);background:var(--accent-soft)}
.st-email-head{width:32px;height:32px}
.st-copy-pop{
  position:fixed;
  left:50%;
  top:50%;
  z-index:2500;
  transform:translate(-50%,-50%) scale(.98);
  opacity:0;
  pointer-events:none;
  border:1px solid var(--line);
  background:color-mix(in srgb,var(--panel) 96%,transparent);
  color:var(--ink);
  box-shadow:0 18px 46px rgba(18,35,70,.18);
  border-radius:999px;
  padding:10px 16px;
  font-size:13px;
  font-weight:800;
  transition:opacity .18s ease,transform .18s ease;
}
.st-copy-pop.show{opacity:1;transform:translate(-50%,-50%) scale(1)}

/* ---- filter chips (buttons, never <select>) ---- */
/* .st-filters/.st-filterrow/.st-filterlabel/.st-chiprow/.st-fchip are aliased to the
   canonical filter-chip primitives in app.css — nothing page-specific left to declare here. */
.st-week-tools{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap;
  padding-left:58px;
}
.st-week-tools label{
  display:inline-flex;
  align-items:center;
  gap:8px;
  font-size:12px;
  font-weight:900;
  color:var(--dim);
  text-transform:uppercase;
  letter-spacing:.04em;
}
.st-week-tools input{
  width:76px;
  min-height:36px;
  padding:6px 9px;
  font-size:15px;
  font-weight:900;
  text-align:center;
}
.st-week-tools .hint{margin:0;font-size:12px}
.st-class-context{
  display:flex;
  align-items:stretch;
  gap:10px;
  flex-wrap:wrap;
  margin:-8px 0 14px;
}
.st-class-context > div{
  min-width:118px;
  border:1px solid var(--line);
  border-radius:10px;
  background:var(--panel);
  padding:10px 12px;
}
.st-class-context span{
  display:block;
  font-size:10.5px;
  font-weight:900;
  letter-spacing:.04em;
  text-transform:uppercase;
  color:var(--dim);
}
.st-class-context b{
  display:block;
  margin-top:2px;
  font-size:18px;
  line-height:1.1;
  color:var(--ink);
}
.st-class-context .btn{align-self:center}

/* whole-row click target for opening a profile */
#stTableWrap{
  width:100%;
  overflow-x:auto;
}
.st-directory-table{
  width:100%;
  table-layout:fixed;
}
.st-directory-table th,
.st-directory-table td{
  box-sizing:border-box;
}
.st-directory-table th{
  white-space:nowrap;
}
.st-directory-table .st-col-info{width:auto}
.st-directory-table .st-col-homework{width:128px}
.st-directory-table .st-col-performance{width:150px}
.st-directory-table .st-col-notes{width:92px}
.st-directory-table th:nth-child(2),
.st-directory-table td:nth-child(2),
.st-directory-table th:nth-child(3),
.st-directory-table td:nth-child(3),
.st-directory-table th:nth-child(4),
.st-directory-table td:nth-child(4){
  text-align:center;
}
/* "Show more" pagination footer row (10 students per page) */
tr.st-more-row td{
  text-align:center;
  padding:12px;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:12px;
}
tr.st-more-row .st-more-count{color:var(--dim);font-size:12.5px}
tr.st-row{cursor:pointer;transition:background .12s}
tr.st-row:hover{background:var(--panel2)}
tr.st-row:focus-visible{outline:2px solid var(--accent,#5b53e6);outline-offset:-2px}
tr.st-row td{vertical-align:middle;padding:7px 12px}
tr.st-row .st-chip{padding:2px 8px;font-size:11.5px}
.st-info-cell{min-width:0}
.st-info-cell .st-chips{margin-top:6px}
.st-name-line,
.st-info-cell .st-chips{
  min-width:0;
}
.st-name-line .nm{
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}
.st-info-cell .st-chip{
  max-width:100%;
  overflow:hidden;
  text-overflow:ellipsis;
}

/* Phones: the fixed 3-column table (128+150+92px) leaves the name column zero
   width, so the name clips to nothing. Stack each row into a card instead, with
   the name full-width on top and the status cells labelled below. */
@media(max-width:560px){
  .st-directory-table{table-layout:auto}
  .st-directory-table thead{
    position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;
  }
  .st-directory-table,
  .st-directory-table tbody{display:block;width:100%}
  tr.st-row{
    display:flex;
    flex-wrap:wrap;
    align-items:center;
    gap:6px 14px;
    padding:10px 4px;
    border-bottom:1px solid var(--line);
  }
  tr.st-row td{
    display:block;
    padding:0;
    text-align:left;
  }
  .st-info-cell{flex:1 1 100%}
  .st-name-line{flex-wrap:wrap}
  .st-name-line .nm{
    white-space:normal;
    overflow:visible;
    font-size:15px;
  }
  /* status cells sit in a labelled row beneath the name */
  tr.st-row td[data-label]{
    flex:0 0 auto;
    display:flex;
    align-items:center;
    gap:6px;
  }
  tr.st-row td[data-label]::before{
    content:attr(data-label);
    font-size:11px;
    font-weight:800;
    letter-spacing:.03em;
    text-transform:uppercase;
    color:var(--dim);
  }
  .st-mini-signal{width:auto}
  tr.st-more-row td{width:100%}
}
.st-hw-signals{
  display:flex;
  justify-content:center;
  align-items:center;
  gap:7px;
  flex-wrap:nowrap;
}
.st-mini-signal{
  display:inline-grid;
  justify-items:center;
  gap:1px;
  width:120px;
  border:1px solid var(--line);
  border-radius:999px;
  padding:5px 9px;
  line-height:1.05;
  white-space:nowrap;
}
.st-mini-signal small{
  color:var(--dim);
  font-size:9px;
  font-weight:900;
  text-transform:uppercase;
  letter-spacing:.04em;
}
.st-mini-signal b{font-size:13px;font-weight:950}
.st-mini-signal em{font-style:normal;color:var(--dim);font-size:10px;font-weight:850}
.st-mini-signal.green{background:color-mix(in srgb,var(--ok) 13%,var(--panel));border-color:color-mix(in srgb,var(--ok) 36%,var(--line));color:var(--ok)}
.st-mini-signal.yellow{background:color-mix(in srgb,var(--warn) 17%,var(--panel));border-color:color-mix(in srgb,var(--warn) 42%,var(--line));color:color-mix(in srgb,var(--warn) 76%,var(--ink))}
.st-mini-signal.red{background:color-mix(in srgb,var(--err) 12%,var(--panel));border-color:color-mix(in srgb,var(--err) 38%,var(--line));color:var(--err)}
.st-simple-status,
.st-performance{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:7px;
  min-width:84px;
  min-height:34px;
  padding:5px 9px;
  border:1px solid var(--line);
  border-radius:999px;
  background:var(--panel2);
  line-height:1;
  white-space:nowrap;
}
.st-simple-status b{
  display:grid;
  place-items:center;
  width:20px;
  height:20px;
  border-radius:999px;
  font-size:13px;
  font-weight:950;
  background:var(--panel);
}
.st-simple-status small,
.st-performance small{
  color:var(--dim);
  font-size:10px;
  font-weight:850;
}
.st-performance{
  flex-direction:column;
  gap:2px;
}
.st-performance b{
  font-size:17px;
  font-weight:950;
}
.st-simple-status.green,
.st-performance.green{background:color-mix(in srgb,var(--ok) 12%,var(--panel));border-color:color-mix(in srgb,var(--ok) 35%,var(--line));color:var(--ok)}
.st-simple-status.yellow,
.st-performance.yellow{background:color-mix(in srgb,var(--warn) 17%,var(--panel));border-color:color-mix(in srgb,var(--warn) 42%,var(--line));color:color-mix(in srgb,var(--warn) 78%,var(--ink))}
.st-simple-status.red,
.st-performance.red{background:color-mix(in srgb,var(--err) 12%,var(--panel));border-color:color-mix(in srgb,var(--err) 38%,var(--line));color:var(--err)}
.st-simple-status.grey,
.st-performance.grey{background:var(--panel2);border-color:var(--line);color:var(--dim)}
.st-notes-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:5px;
  min-width:52px;
  min-height:36px;
  border:1px solid var(--line);
  border-radius:12px;
  background:var(--panel2);
  color:var(--dim);
  cursor:pointer;
  font:inherit;
  font-weight:900;
}
.st-notes-btn.has-notes{
  color:var(--accent);
  border-color:color-mix(in srgb,var(--accent) 35%,var(--line));
  background:color-mix(in srgb,var(--accent) 9%,var(--panel));
}
.st-notes-btn .line-ico{width:17px;height:17px}
.st-notes-modal textarea{width:100%;resize:vertical}
.st-note-history{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.st-note-item{padding:9px 10px;border:1px solid var(--line);border-radius:10px;background:var(--panel2)}
.st-note-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:4px}
.st-note-item b{display:block;font-size:12px;color:var(--dim)}
.st-note-item p{margin:0}
.st-note-delete{
  border:1px solid color-mix(in srgb,var(--err) 35%,var(--line));
  border-radius:9px;
  background:color-mix(in srgb,var(--err) 8%,var(--panel));
  color:var(--err);
  cursor:pointer;
  font:inherit;
  font-size:12px;
  font-weight:900;
  padding:4px 8px;
}
.st-note-delete:hover{background:color-mix(in srgb,var(--err) 14%,var(--panel))}
.st-marks{
  display:inline-flex;
  min-width:34px;
  justify-content:center;
  border:1px solid color-mix(in srgb,var(--accent) 24%,var(--line));
  background:color-mix(in srgb,var(--accent-soft) 72%,var(--panel));
  color:var(--accent);
  border-radius:999px;
  padding:2px 8px;
  font-size:12px;
  font-weight:900;
}
/* One cell per day, last 7 days. Empty = no activity, light = logged in, full = practice question correct. */
.sr-heat{
  display:flex;
  gap:4px;
  width:max-content;
  justify-self:center;
}
.sr-heat-cell{
  width:15px;
  aspect-ratio:1;
  border-radius:4px;
  border:1px solid color-mix(in srgb,var(--line) 72%,transparent);
  background:var(--panel2);
}
.sr-heat-cell.is-login{
  background:color-mix(in srgb,var(--ok) 30%,var(--panel2));
  border-color:color-mix(in srgb,var(--ok) 42%,var(--line));
}
.sr-heat-cell.is-practice{
  background:var(--ok);
  border-color:var(--ok);
}
.sr-signal{
  display:inline-grid;
  min-width:74px;
  justify-items:center;
  gap:1px;
  border:1px solid var(--line);
  border-radius:999px;
  padding:4px 8px;
  font-weight:900;
  line-height:1.05;
  white-space:nowrap;
}
.sr-signal b{font-size:12px}
.sr-signal small{font-size:9.5px;color:var(--dim);font-weight:800}
.sr-signal.green{background:color-mix(in srgb,var(--ok) 13%,var(--panel));border-color:color-mix(in srgb,var(--ok) 34%,var(--line));color:var(--ok)}
.sr-signal.yellow{background:color-mix(in srgb,var(--warn) 15%,var(--panel));border-color:color-mix(in srgb,var(--warn) 36%,var(--line));color:color-mix(in srgb,var(--warn) 78%,var(--ink))}
.sr-signal.red{background:color-mix(in srgb,var(--err) 12%,var(--panel));border-color:color-mix(in srgb,var(--err) 34%,var(--line));color:var(--err)}
.sr-tight{min-width:58px}

/* class chips: small pill with a colour dot; tint applied inline from the class colour */
.st-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.st-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;
  border:1px solid var(--line);background:var(--panel2);color:var(--dim);font-size:12px;font-weight:700;white-space:nowrap}
.st-chip-link{cursor:pointer;font-family:inherit}
.st-chip-link:hover{text-decoration:underline}
.st-dot{width:8px;height:8px;border-radius:50%;flex:none}

/* status badge (active = green, non-mono — app.css .badge.code is monospace) */
.badge.st-ok{background:#eaf7ef;color:var(--ok)}

/* ---- profile ---- */
.st-headrow{display:flex;align-items:center;gap:14px}
.stat .n.nn{font-size:17px;line-height:30px;font-weight:700}
.st-center-pad{padding:0 0 26px;text-align:left}

/* attempts table */
.st-score{font-weight:700}
.st-score.low{color:var(--err)}
.st-score.mid{color:var(--warn)}
/* Term-test band between 50% and 60% — orange, sitting between .mid yellow and .low red. */
.st-score.warn{color:color-mix(in srgb,var(--warn) 60%,var(--err))}
.st-score.high{color:var(--ok)}
/* Term-test tile: the "/20" reads as a unit, not a second number. */
.st-tt-outof{font-size:.55em;font-weight:800;color:var(--dim);margin-left:2px}
/* The editable tile (admin/teacher): box, "/20", then the live percentage, on the .stat .n line.
   The box borrows the class page's .cls-tt-input so a mark is typed into the same control here as
   it is there; the tile's own 30px number line just has to stop stretching it. */
.st-tt-n{display:flex;align-items:center;gap:6px;line-height:1}
.st-tt-input.cls-tt-input{width:78px;font-size:19px;min-height:38px}
.st-tt-n .st-tt-outof{font-size:15px}
.st-tt-live{font-size:13px;font-weight:800;min-width:0}
.st-tt-meta{margin-top:5px;font-size:11.5px;color:var(--dim);font-weight:600}
/* A student in two term-test classes sits two papers, so the tile holds one box per class. The
   label then heads the stack instead of sitting under a single number, and a hairline keeps the
   second class's box from reading as more of the first one's. */
.st-tt-multi .st-tt-head{margin-bottom:9px}
.st-tt-multi .st-tt-row + .st-tt-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}
.st-pct{font-size:12px;color:var(--dim);margin-left:4px}

/* mastery bars: <50 red / 50–80 amber / >80 green */
.st-mastery{padding:14px 18px;display:flex;flex-direction:column;gap:13px}
.st-mastery-row{display:grid;grid-template-columns:1fr;gap:5px}
.st-mastery-label{font-size:13.5px;font-weight:600;display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.st-mastery-meta{font-size:11.5px;color:var(--dim);font-weight:500}
.st-bar{height:9px;border-radius:999px;background:var(--panel2);border:1px solid var(--line);overflow:hidden;position:relative}
.st-bar-fill{height:100%;border-radius:999px;transition:width .25s}
.st-bar-fill.low{background:var(--err)}
.st-bar-fill.mid{background:var(--warn)}
.st-bar-fill.high{background:var(--ok)}
.st-bar-n{font-size:12px;font-weight:800;margin-left:auto}
.st-bar-n.low{color:var(--err)}
.st-bar-n.mid{color:var(--warn)}
.st-bar-n.high{color:var(--ok)}

.st-topic-browser{display:flex;flex-direction:column}
.st-topic-head{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;
  gap:10px;
  padding:12px 18px;
  border-bottom:1px solid var(--line);
  background:var(--panel2);
}
.st-topic-head b{display:block;font-size:14px}
.st-topic-head span{display:block;margin-top:2px;color:var(--dim);font-size:12px;font-weight:700}
.st-topic-panel[hidden]{display:none}

/* activity list */
.st-activity{list-style:none;margin:0;padding:6px 0}
.st-activity li{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:10px 18px;border-bottom:1px solid var(--line);font-size:14px}
.st-activity li:last-child{border-bottom:0}
.st-act-type{font-weight:600}
.st-act-at{color:var(--dim);font-size:12.5px;white-space:nowrap}

/* ---- message composer ---- */
.st-msg-to{font-size:13px;color:var(--dim);margin-bottom:10px}
.st-msg-ta{width:100%;min-height:120px;resize:vertical}
.st-msg-foot{display:flex;justify-content:flex-end;gap:10px}

/* ---- homework completion panel ---- */
.st-wb{display:flex;flex-direction:column}
.st-wb-list{list-style:none;margin:0;padding:0}
.st-wb-item{padding:12px 18px;border-bottom:1px solid var(--line)}
.st-wb-item:last-child{border-bottom:0}
.st-wb-main{display:flex;align-items:center;gap:9px}
.st-wb-tick{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;
  font-size:13px;font-weight:800;flex:none}
.st-wb-tick.done{background:#eaf7ef;color:var(--ok)}
.st-wb-tick.todo{background:var(--panel2);color:var(--dim);border:1px solid var(--line)}
.st-wb-label{font-weight:650;font-size:14px}
.st-wb-comment{margin:7px 0 0 29px;padding:8px 11px;border-radius:10px;background:var(--panel2);
  border:1px solid var(--line);font-size:13px;line-height:1.4}
.st-wb-meta{margin:6px 0 0 29px;font-size:11.5px;color:var(--dim)}

/* log form */
.st-wb-form{display:flex;flex-direction:column;gap:11px;padding:16px 18px;border-top:1px solid var(--line);
  background:var(--panel2)}
.st-wb-input,.st-wb-ta{width:100%}
.st-wb-ta{resize:vertical;min-height:48px}
.st-wb-donerow{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.st-wb-donelabel{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--dim)}
.st-wb-formfoot{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
.st-wb-note{font-size:12px;color:var(--dim)}

/* ---- name change requests (admin, migration 0238) ----
   One card per waiting request, at the very top of the Students page. Two names, then the names
   already in Portal that the new one runs into, then the decision. The one row that must not be
   skimmed past is a match in the SAME CLASS, so that one gets the danger badge and a warm tint. */
.st-ncr-list{display:flex;flex-direction:column}
.st-ncr{display:grid;gap:12px;padding:16px 18px;border-bottom:1px solid var(--line)}
.st-ncr:last-child{border-bottom:0}
.st-ncr-names{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
.st-ncr-from{font-size:15px;color:var(--dim);text-decoration:line-through}
.st-ncr-arrow{color:var(--dim)}
.st-ncr-to{font-size:18px}
.st-ncr-meta{display:flex;gap:14px;flex-wrap:wrap;margin-top:5px;font-size:12.5px;color:var(--dim)}
.st-ncr-none{margin:0;font-size:13px;color:var(--dim)}

.st-ncr-simwrap{display:grid;gap:7px;padding:11px 13px;border:1px solid var(--line);border-radius:12px;
  background:var(--panel2)}
.st-ncr-simhead{font-size:11px;font-weight:850;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
.st-ncr-simlist{list-style:none;margin:0;padding:0;display:grid;gap:5px}
.st-ncr-sim{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:13.5px;
  padding:5px 8px;border-radius:9px}
.st-ncr-sim.same-class{background:color-mix(in srgb,var(--warn) 13%,transparent)}
.st-ncr-match{font-size:12px;color:var(--dim)}
.st-ncr-classes{font-size:12px;color:var(--dim)}
.st-ncr-more{margin:0;font-size:12px;color:var(--dim)}

.st-ncr-actions{display:flex;justify-content:flex-end;gap:9px}
@media (max-width:560px){
  .st-ncr-actions{justify-content:stretch}
  .st-ncr-actions .btn{flex:1}
}

/* ---- "Show all activity" popup (student profile → Recent activity) ----
   A window of the events trail, loaded 200 at a time. The table scrolls inside the modal so the
   Load more control and the count stay put at the foot. */
.st-actall-wrap{max-height:min(58vh,520px);overflow:auto;border:1px solid var(--line);border-radius:10px}
.st-actall-t{width:100%;border-collapse:collapse;font-size:13.5px}
.st-actall-t th{position:sticky;top:0;background:var(--panel);text-align:left;font-size:11.5px;font-weight:700;
  color:var(--dim);text-transform:uppercase;letter-spacing:.06em;padding:8px 12px;border-bottom:1px solid var(--line)}
.st-actall-t td{padding:8px 12px;border-bottom:1px solid var(--line);vertical-align:top}
.st-actall-t tr:last-child td{border-bottom:none}
.st-act-num{text-align:right;white-space:nowrap}
.st-actall-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 2px 0}

/* css/accounts.css */
/* accounts styles (admin Accounts page). Additive only — do not edit css/app.css. */

.acc-wrap{width:100%}
.acc-head{margin:2px 0 18px}
.acc-head h2{margin:0 0 4px;font-size:24px;letter-spacing:-.01em}
.acc-lead{margin:0;color:var(--dim);font-size:14px;max-width:60ch}

/* Full content width: drop the shared .card 440px cap so the users table can use
   the whole page (admins/teachers can be in dozens of classes). */
.acc-card{margin:0 0 18px;padding:16px 18px;max-width:none}
.acc-card-head{display:flex;align-items:center;gap:10px;margin:0 0 12px}
.acc-card-head h3{margin:0;font-size:16px}
.acc-count{display:inline-grid;place-items:center;min-width:24px;height:22px;padding:0 7px;border-radius:999px;
  background:var(--accent-soft);color:var(--accent);font-size:12.5px;font-weight:800}

/* ---- pending approval rows ---- */
.acc-pend{display:flex;align-items:center;gap:14px;padding:11px 4px;border-top:1px solid var(--line)}
.acc-pend:first-child{border-top:0}
.acc-pend-main{min-width:0;flex:1 1 200px}
.acc-pend-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acc-pend-email{display:block;margin-top:2px;font-size:12.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acc-meta{display:flex;gap:6px;flex-wrap:wrap;flex:0 1 auto;justify-content:flex-end}
.acc-chip{display:inline-block;padding:3px 9px;border-radius:999px;background:var(--panel2);border:1px solid var(--line);
  color:var(--dim);font-size:12px;font-weight:600;white-space:nowrap}
.acc-pend-actions{flex:none;display:flex;align-items:center;gap:8px}
/* approved/rejected row retires on its own: collapse it instead of repainting the card */
.acc-pend-leaving{height:0!important;min-height:0;padding-top:0;padding-bottom:0;opacity:0;overflow:hidden;
  pointer-events:none;border-top-color:transparent;
  transition:height .18s ease,padding .18s ease,opacity .14s ease}
@media (prefers-reduced-motion:reduce){.acc-pend-leaving{transition:none}}
.acc-approve{flex:none}
.acc-approve-sm{padding:5px 12px;font-size:13px}
/* reject confirm modal */
.acc-modal-note{margin:8px 0 0;color:var(--dim);font-size:13px;line-height:1.45}

/* ---- search + table ---- */
.acc-search{width:100%;max-width:380px;margin:0 0 14px;padding:9px 13px;border:1px solid var(--line);border-radius:10px;
  background:var(--panel2);color:var(--ink);font:inherit;font-size:14px}
.acc-search:focus{outline:none;border-color:var(--accent);background:var(--panel)}
.acc-table-wrap{overflow-x:auto}
/* Classes list one per line so long enrolment lists do not spill across the row. */
.acc-table th:nth-child(1),.acc-table td:nth-child(1){width:24%;min-width:210px}
.acc-table th:nth-child(2),.acc-table td:nth-child(2){width:110px}
.acc-table th:nth-child(3),.acc-table td:nth-child(3){min-width:320px;width:auto}
.acc-table th:nth-child(4),.acc-table td:nth-child(4){width:170px;text-align:right;white-space:nowrap}
.acc-table td:nth-child(3){white-space:normal;line-height:1.35}
.acc-class-list{display:flex;flex-direction:column;gap:4px;min-width:0}
.acc-class-list span,.acc-class-link{display:block;min-width:0;overflow-wrap:anywhere}
.acc-class-link{border:0;background:none;color:var(--accent);font:inherit;text-align:left;padding:0;cursor:pointer}
.acc-class-link:hover{text-decoration:underline}
/* join-code enrolment that activates on approval — shown, but visibly not yet live */
.acc-class-joining{color:var(--dim)}
.acc-class-joining:hover{color:var(--accent)}
.acc-chip-class{background:var(--accent-soft);border-color:transparent;color:var(--accent)}
.acc-class-more{display:inline-flex;align-self:flex-start;border:0;background:none;color:var(--dim);font:inherit;font-size:11.5px;text-align:left;padding:0;cursor:pointer;margin-top:2px}
.acc-class-more:hover{color:var(--accent);text-decoration:underline}
.acc-class-extra{display:flex;flex-direction:column;gap:4px;margin-top:4px}
.acc-class-extra[hidden]{display:none}
.acc-table{width:100%;border-collapse:collapse;font-size:13.5px}
.acc-table th{text-align:left;padding:8px 12px;font-size:11.5px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--dim);font-weight:800;border-bottom:1px solid var(--line);white-space:nowrap}
.acc-table td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.acc-table tr:last-child td{border-bottom:0}
.acc-table tbody tr:hover{background:var(--panel2)}
.acc-click-row{cursor:pointer}
.acc-click-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.acc-pend.acc-click-row:hover{background:var(--panel2)}
.acc-sub{margin-top:2px;font-size:12px;color:var(--dim)}
.acc-mail-btn{margin-left:6px;vertical-align:middle}
.acc-dim{color:var(--dim)}
.acc-role-btn{background:none;border:none;padding:0;font:inherit;color:inherit;cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px;opacity:.85}
.acc-role-btn:hover{opacity:1;color:var(--accent)}
.acc-ok{display:inline-block;padding:3px 10px;border-radius:999px;background:var(--accent-soft);color:var(--ok);
  font-size:12px;font-weight:800}
.acc-empty{padding:16px 4px;color:var(--dim);font-size:13.5px;text-align:center}
td.acc-empty{text-align:left}

/* ---- inline table actions (Approve / Remove / Restore) ---- */
.acc-sm{padding:4px 11px;font-size:12.5px}
.acc-table td:nth-child(4) .acc-ok,
.acc-table td:nth-child(4) .acc-archived{margin-right:6px;vertical-align:middle}
.acc-archived{display:inline-block;padding:3px 10px;border-radius:999px;background:var(--panel2);
  border:1px solid var(--line);color:var(--dim);font-size:12px;font-weight:700}
/* archived rows read as muted, but the Restore action stays full-strength */
.acc-row-arch td:nth-child(1) b,.acc-row-arch td:nth-child(2),.acc-row-arch td:nth-child(3){opacity:.5}
.acc-class-list em{font-style:normal;color:var(--dim);font-size:11px}

/* ---- archived accounts section ---- */
.acc-archive-card{padding-top:14px}
.acc-archive-toggle{display:flex;align-items:center;gap:10px;justify-content:flex-start}
.acc-archive-panel{margin-top:14px}
.acc-archive-panel[hidden]{display:none}
.acc-arch-table th:nth-child(1),.acc-arch-table td:nth-child(1){width:34%;min-width:220px}
.acc-arch-table th:nth-child(2),.acc-arch-table td:nth-child(2){width:140px}
.acc-arch-table th:nth-child(3),.acc-arch-table td:nth-child(3){width:140px;min-width:120px}
.acc-arch-table th:nth-child(4),.acc-arch-table td:nth-child(4){width:260px;text-align:right;white-space:nowrap}
.acc-arch-actions{display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap}

/* ---- filter bar ---- */
.acc-filters{display:flex;flex-direction:column;gap:8px;margin:0 0 14px}
.acc-filterrow{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.acc-filterlabel{flex:none;width:48px;font-size:11px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--dim);font-weight:800}
.acc-chiprow{display:flex;gap:6px;flex-wrap:wrap;flex:1 1 auto;min-width:0}
.acc-fchip{padding:5px 12px;font-size:12.5px}

/* ---- class picker in the approve modal ---- */
.acc-pickrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;max-height:230px;overflow:auto}
.panel-body>.acc-pickrow[data-role-chips]{margin-top:0;padding:18px 18px 0;max-height:none}
.acc-pick{font-size:13px}
.acc-pick em{font-style:normal;opacity:.65;font-weight:600;margin-left:4px}

@media (max-width:640px){
  .acc-pend{flex-wrap:wrap}
  .acc-meta{justify-content:flex-start;width:100%;order:3}
  .acc-pend-actions{order:2}
  .acc-filterlabel{width:100%}
  .acc-arch-actions{justify-content:flex-start}
  .acc-arch-table th:nth-child(4),.acc-arch-table td:nth-child(4){white-space:normal}
}

/* css/messages.css */
/* messages styles. Tokens only so the four portal designs can reskin them. */

.msg-layout{--msg-listw:300px;display:grid;grid-template-columns:var(--msg-listw) 10px minmax(0,1fr);gap:18px;height:calc(100vh - 158px);min-height:380px}
/* the Files button swaps the panes out — an explicit display:grid beats the UA's [hidden] rule */
.msg-layout[hidden]{display:none}
.msg-list{overflow-y:auto;margin-bottom:0}
.msg-thread{display:flex;flex-direction:column;margin-bottom:0;overflow:hidden}

/* draggable divider between list and thread */
.msg-resizer{position:relative;align-self:stretch;cursor:col-resize;touch-action:none;display:grid;place-items:center;margin:0 -8px;z-index:1}
.msg-resizer::before{content:"";width:4px;height:46px;border-radius:4px;background:var(--line);transition:background .12s}
.msg-resizer:hover::before,.msg-resizer.dragging::before{background:var(--accent)}
/* suppress text selection across the whole doc while dragging */
body.msg-resizing{cursor:col-resize;user-select:none;-webkit-user-select:none}

/* search above the conversation rows — sticky, outside the rows' repaint */
.msg-list-search{position:sticky;top:0;z-index:2;padding:10px 12px;border-bottom:1px solid var(--line);background:var(--panel)}
.msg-list-search input{width:100%;padding:7px 11px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);color:var(--ink);font:inherit;font-size:13px}
.msg-list-search input:focus{outline:none;border-color:var(--accent)}
/* "Matching messages" — individual message hits under the filtered conversation rows */
.msg-hits-head{padding:12px 14px 4px;font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--dim);font-weight:800;border-top:1px solid var(--line)}

/* conversation list.
   Unread is blue, not accent-coloured: it must read as "unread" identically in all four designs,
   so the wash and the dot are their own tokens rather than --accent-soft. */
.msg-list{--msg-unread-bg:#d8e9fb;--msg-unread-dot:#1565c9}
html[data-design="lab"] .msg-list{--msg-unread-bg:#102639;--msg-unread-dot:#6aa9f5}
.msg-item{position:relative;display:flex;align-items:center;gap:11px;width:100%;padding:12px 14px 12px 20px;border:0;border-bottom:1px solid var(--line);background:transparent;text-align:left;cursor:pointer;font:inherit;color:var(--ink)}
.msg-item:hover{background:var(--panel2)}
/* unread before active: opening a conversation reads it, so the selected row wins the tie */
.msg-item.unread{background:var(--msg-unread-bg)}
.msg-item.active{background:var(--accent-soft)}
.msg-item:last-child{border-bottom:0}
/* the unread dot rides in the row's left padding, so it costs the snippet almost no width and
   every avatar — dotted or not — still lines up in one column */
.msg-lead{position:absolute;left:6px;top:50%;transform:translateY(-50%);display:grid;place-items:center}
.msg-avatar{flex:0 0 36px;width:36px;height:36px;border-radius:50%;background:var(--accent-soft);color:var(--accent);display:inline-grid;place-items:center;font-weight:700;font-size:13px}
.msg-item.active .msg-avatar{background:var(--accent);color:#fff}
.msg-meta{flex:1;min-width:0}
.msg-name{display:block;font-weight:600;font-size:14px}
.msg-snippet{display:block;color:var(--dim);font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.msg-item.unread .msg-name,.msg-item.unread .msg-snippet{font-weight:700;color:var(--ink)}
.msg-side{display:flex;flex-direction:column;align-items:flex-end;gap:5px}
.msg-time{font-size:11px;color:var(--dim);white-space:nowrap}
.msg-dot{width:9px;height:9px;border-radius:50%;background:var(--msg-unread-dot)}

/* right-click menu on a conversation row. Fixed to the pointer, so it never scrolls with the list. */
.msg-ctx{position:fixed;z-index:60;min-width:150px;padding:5px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.msg-ctx button{display:block;width:100%;padding:8px 11px;border:0;border-radius:calc(var(--radius) - 5px);background:transparent;text-align:left;font:inherit;font-size:13.5px;color:var(--ink);cursor:pointer}
.msg-ctx button:hover,.msg-ctx button:focus-visible{background:var(--accent-soft);outline:0}

/* staff room — the one avatar that is an icon, on purple, so it is findable at a glance */
.msg-avatar-room{background:#6b3fa0;color:#fff}
.msg-item.active .msg-avatar-room{background:#57318a}
/* class rooms (0227): same icon treatment, on the accent, so the two kinds read apart */
.msg-avatar-class{background:var(--accent)}
.msg-avatar-room .line-ico{width:19px;height:19px}

/* thread pane */
.msg-thread-head{display:flex;align-items:center;gap:11px;padding:11px 16px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.msg-thread-head h3{font-size:15px}
/* A name that opens that student's profile — the thread header of a DM, the sender line on a bubble
   in a class room. It reads as plain text and only underlines under the pointer, so neither place
   turns into furniture. Declared BEFORE .bubble-from so that rule still owns its size/weight/colour;
   everything here is the button reset, not a look of its own. */
.msg-name-link{appearance:none;-webkit-appearance:none;background:none;border:0;padding:0;margin:0;
  font:inherit;color:inherit;text-align:left;cursor:pointer;
  text-decoration:underline;text-decoration-color:transparent;text-underline-offset:2px;
  transition:text-decoration-color .12s}
.msg-name-link:hover,.msg-name-link:focus-visible{text-decoration-color:currentColor}
.msg-name-link:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
/* the room's roster, named: admins then teachers, A–Z by surname. Wraps rather than truncating —
   the whole point is being able to see who is in there. */
.msg-room-sub{flex:1 0 100%;font-size:12px;line-height:1.5;color:var(--dim);padding-left:47px;margin-top:-2px}
.msg-back{display:none}
/* Vertical rhythm comes from margins, not flex gap: a clustered row (same sender inside five
   minutes) huddles with its predecessor, which a uniform gap could not do. */
.msg-bubbles{flex:1;overflow-y:auto;overflow-x:hidden;padding:26px 18px 18px;display:flex;flex-direction:column}
.msg-bubbles>*+*{margin-top:14px}
.msg-bubbles>.bubble-row.grouped{margin-top:3px}
/* day separators — "Today", "Yesterday", "Mon 11 Aug" */
.msg-day{display:flex;align-items:center;gap:12px;color:var(--dim);font-size:11.5px;font-weight:700}
.msg-day::before,.msg-day::after{content:"";flex:1;height:1px;background:var(--line)}
/* read receipt for the sender — sits under my newest read message, DMs only */
.msg-seen{align-self:flex-end;margin-top:3px !important;font-size:11px;color:var(--dim)}
/* pages one thread back past the loaded window */
.msg-earlier{align-self:center}
/* files dragged over the open thread */
.msg-thread.dropping{outline:2px dashed var(--accent);outline-offset:-6px}
.bubble{max-width:100%;padding:9px 13px;border-radius:14px;font-size:14px;line-height:1.45;overflow-wrap:break-word}
.bubble.mine{align-self:flex-end;background:var(--accent);color:#fff;border-bottom-right-radius:4px}
.bubble.theirs{align-self:flex-start;background:var(--panel2);border:1px solid var(--line);color:var(--ink);border-bottom-left-radius:4px}
.bubble .bt{display:block;font-size:11px;opacity:.72;margin-top:4px;text-align:right}
/* who said it — group chat only, so a room thread reads like a conversation and not a monologue */
.bubble-from{display:block;font-size:12px;font-weight:700;color:var(--accent);margin-bottom:2px}
/* a link inside a message body — underlined so it reads as a link on the accent-filled bubble too,
   where colour alone can't carry it. anywhere breaks a long URL rather than widening the bubble. */
.bubble-link{color:inherit;text-decoration:underline;text-underline-offset:2px;word-break:break-word;overflow-wrap:anywhere}
.bubble.theirs .bubble-link{color:var(--accent);font-weight:600}
.bubble-link:hover{text-decoration-thickness:2px}

/* one message = bubble + its reaction controls, so the tapbacks sit with the bubble they belong to */
.bubble-row{position:relative;display:flex;flex-direction:column;max-width:74%}
.bubble-row.mine{align-self:flex-end;align-items:flex-end}
.bubble-row.theirs{align-self:flex-start;align-items:flex-start}
/* jump-to-message flash, for the quote on a reply */
@keyframes msgFlash{from{box-shadow:0 0 0 3px var(--accent)}to{box-shadow:0 0 0 3px transparent}}
.bubble-row.flash .bubble{animation:msgFlash 1.1s ease-out}

/* Hover bar: six tapbacks + reply, floating over the bubble's top edge. Hovering the message IS
   the opener — one click reacts. Absolute so appearing costs no layout shift; .msg-bubbles keeps a
   top padding so the first message's bar is never clipped by the scroll box. */
.tapback-bar{position:absolute;top:-15px;z-index:2;display:flex;align-items:center;gap:1px;padding:3px 6px;
  border:1px solid var(--line);border-radius:999px;background:var(--panel);box-shadow:0 2px 10px rgba(0,0,0,.12);
  opacity:0;pointer-events:none;transition:opacity .16s ease}
.bubble-row.mine .tapback-bar{right:6px}
.bubble-row.theirs .tapback-bar{left:6px}
/* Hover in, fade out the moment the pointer leaves. Deliberately NOT :focus-within — clicking an
   emoji leaves focus on that button, which pinned the bar open after the mouse had gone. Keyboard
   users get it back through :focus-visible, which a click never sets. */
.bubble-row:hover .tapback-bar,
.bubble-row:has(.tapback-bar :focus-visible) .tapback-bar{opacity:1;pointer-events:auto}
/* press-and-hold on touch sets .picking; harmless on desktop, which never sets it */
.bubble-row.picking .tapback-bar{opacity:1;pointer-events:auto}
.tapback-opt{border:0;background:none;cursor:pointer;font-size:16px;line-height:1;padding:3px 4px;border-radius:50%;transition:transform .1s}
.tapback-opt:hover{background:var(--panel2);transform:scale(1.25)}
.tapback-opt.mine{background:var(--accent-soft)}
.tapback-sep{width:1px;height:16px;margin:0 3px;background:var(--line)}
.tapback-reply{border:0;background:none;cursor:pointer;color:var(--dim);font-size:14px;line-height:1;padding:3px 5px;border-radius:50%}
.tapback-reply:hover{background:var(--panel2);color:var(--accent)}

/* Swipe sideways to reply. The bubble follows the finger (or a two-finger trackpad swipe) and the
   ↩ behind it fills in; past the trigger point it goes solid, so you can see the reply will fire
   before you let go. Transform on the bubble, not the row, so the tapback bar stays put, and the ↩
   sits INSIDE the row underneath it — nothing pokes outside the scroll box asking for a horizontal
   scrollbar. touch-action:pan-y leaves vertical scrolling to the browser and the sideways axis to us. */
.swipe-hint{position:absolute;top:14px;z-index:0;opacity:0;
  width:26px;height:26px;display:grid;place-items:center;border-radius:50%;
  background:var(--panel2);color:var(--dim);font-size:14px;line-height:1;pointer-events:none;transition:background .12s,color .12s}
.bubble-row.mine .swipe-hint{right:8px}
.bubble-row.theirs .swipe-hint{left:8px}
.swipe-hint.ready{background:var(--accent);color:#fff}
.bubble-row{touch-action:pan-y}
.bubble-row .bubble{position:relative;z-index:1}

/* Touch has no hover: press and hold a message instead. The bar drops into the flow underneath the
   bubble there, where the scroll box can never clip it, and the targets grow to finger size.
   After the base rules above, so the larger sizes win. */
@media(hover:none){
  /* display:none, not just opacity:0 — in the flow it would reserve its full height under EVERY
     bubble, which is what pushed the messages a finger apart on a phone. */
  .tapback-bar{position:static;display:none;margin-top:6px;box-shadow:none;transform-origin:top center;transform:scale(.96);transition:opacity .16s ease,transform .16s ease}
  .bubble-row:hover .tapback-bar{display:none;opacity:0;pointer-events:none}
  .bubble-row.picking .tapback-bar{display:flex;opacity:1;pointer-events:auto;transform:scale(1)}
  .tapback-opt{font-size:20px;padding:6px 7px}
  .tapback-opt:hover{background:none;transform:none}
  .tapback-reply{font-size:17px;padding:6px 8px}
}

/* the quoted message at the top of a reply */
.bubble-quote{display:flex;flex-direction:column;gap:1px;width:100%;margin-bottom:5px;padding:4px 9px;border:0;border-left:3px solid currentColor;
  border-radius:0 8px 8px 0;background:rgba(127,127,127,.13);font:inherit;font-size:12.5px;text-align:left;color:inherit;cursor:pointer;opacity:.9}
.bubble-quote:hover{opacity:1}
.bubble-quote.muted{cursor:default;font-style:italic}
.bubble-quote-who{font-weight:700}
.bubble-quote-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
/* the pills under a bubble: one per emoji used, count when more than one person picked it */
.tapback-row{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
.tapback{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border:1px solid var(--line);border-radius:999px;
  background:var(--panel);cursor:pointer;font:inherit;font-size:12px;line-height:1.6;color:var(--ink)}
.tapback.mine{border-color:var(--accent);background:var(--accent-soft)}
.tapback-emoji{font-size:13px}
.tapback-n{font-variant-numeric:tabular-nums;color:var(--dim);font-weight:650}
.tapback.mine .tapback-n{color:var(--accent)}

/* attached files inside a bubble — one chip per file, opens the #file viewer */
.bubble-file{display:flex;align-items:center;gap:8px;width:100%;margin-top:6px;padding:7px 10px;border-radius:10px;cursor:pointer;font:inherit;font-size:13px;text-align:left;
  border:1px solid var(--line);background:var(--panel);color:var(--ink)}
.bubble.mine .bubble-file{border-color:rgba(255,255,255,.45);background:rgba(255,255,255,.16);color:#fff}
.bubble-file:hover .bubble-file-name{text-decoration:underline}
.bubble-file-ic{flex:0 0 auto;display:flex;align-items:center}
.bubble-file-ic .line-ico{width:15px;height:15px}
.bubble-file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:650}
.bubble-file-size{flex:0 0 auto;font-size:11px;opacity:.75;font-variant-numeric:tabular-nums}

/* An image attachment gets a thumbnail instead of a file chip; clicking opens the lightbox below.
   The WHOLE picture is always shown — it is scaled down to fit the box and never cropped to it, so
   a wide banner or a tall portrait screenshot reads as itself rather than as a square crop of its
   middle. Hence a max-width/max-height pair with the intrinsic ratio left alone (no aspect-ratio,
   no object-fit:cover), and a button that shrinks to whatever the scaled image actually measures.

   A very wide, very short picture (a cropped banner, a spectrum strip) scales down to only a few
   pixels tall, which is a miserable thing to hit with a thumb — so the BUTTON keeps a 44px floor
   while the image inside it stays whatever size it scaled to. The extra height is invisible
   padding around the picture, not letterboxing of it. */
.bubble-image{display:block;width:fit-content;max-width:min(240px,100%);min-height:44px;margin-top:6px;padding:0;border:0;background:none;cursor:pointer;border-radius:10px;overflow:hidden;text-align:left}
.bubble-image-img{display:block;width:auto;height:auto;max-width:100%;max-height:260px;object-fit:contain;border-radius:8px;background:var(--panel2)}
.bubble-image.thumb-fail .bubble-image-img{display:none}
.bubble-image-name{display:block;margin-top:2px;padding:0 1px;font-size:11px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bubble.mine .bubble-image-name{color:rgba(255,255,255,.8)}

/* the picture full-size, over the page. Wider than a standard modal, and the empty footer is
   hidden so the image sits on the bottom edge rather than above a stray rule.
   Zoomed: the image goes natural-size and the body scrolls it; click again (or Fit) to come back. */
.modal.img-lightbox{max-width:min(1100px,94vw)}
.modal.img-lightbox .modal-body{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;padding:12px;background:var(--panel2)}
.modal.img-lightbox .modal-body.zoomed{overflow:auto;justify-content:flex-start;align-items:flex-start}
.modal.img-lightbox .modal-foot{display:none}
.img-lightbox-img{display:block;max-width:100%;max-height:74vh;border-radius:8px;cursor:zoom-in}
.img-lightbox-img.zoomed{max-width:none;max-height:none;cursor:zoom-out}
.img-lightbox-bar{display:flex;gap:8px;justify-content:flex-end;width:100%}
.modal.img-lightbox .modal-body.zoomed .img-lightbox-bar{position:sticky;left:0;bottom:0}

/* compose */
.msg-compose{display:flex;flex-direction:column;gap:8px;padding:12px 14px;border-top:1px solid var(--line)}
.msg-compose-row{display:flex;gap:9px;align-items:flex-end}
.msg-compose textarea{flex:1;min-height:42px;max-height:130px}
/* paperclip attach button: icon only, and exactly as tall as the textarea beside it — align-self
   stretch rather than a fixed height, so it keeps pace as the textarea grows with the message. */
.msg-attach{flex:0 0 auto;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;margin:0;padding:0 12px}
.msg-attach .line-ico{width:19px;height:19px}
/* saved-replies button (staff): same icon-only treatment as the paperclip */
.msg-snips{flex:0 0 auto;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;margin:0;padding:0 12px}
.msg-snips .line-ico{width:19px;height:19px}
/* the saved-replies modal: one row per snippet, click the text to insert it */
.msg-snip-list{display:flex;flex-direction:column;gap:8px;max-height:46vh;overflow-y:auto}
.msg-snip{display:flex;align-items:center;gap:9px;padding:4px 0}
.msg-snip-body{flex:1;min-width:0;padding:9px 12px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);color:var(--ink);font:inherit;font-size:13.5px;text-align:left;cursor:pointer;white-space:pre-wrap;overflow-wrap:break-word}
.msg-snip-body:hover{border-color:var(--accent)}
.msg-snip-tools{flex:0 0 auto;display:flex;align-items:center;gap:6px}
.msg-snip-edit{flex:1;min-width:0;font:inherit;font-size:13.5px}
.msg-snip-new{display:flex;align-items:flex-end;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.msg-snip-new textarea{flex:1;font:inherit;font-size:13.5px}
/* the armed reply, above the box — Escape or × backs out.
   The [hidden] rules matter: an explicit display beats the UA's [hidden]{display:none}, so without
   them these two sit above the composer permanently — the reply strip as an empty tinted bar. */
.msg-replying{display:flex;align-items:center;gap:9px;padding:7px 10px;border:1px solid var(--line);border-radius:10px;background:var(--panel2)}
.msg-replying[hidden]{display:none}
.msg-reply-bar{flex:0 0 3px;align-self:stretch;border-radius:3px;background:var(--accent)}
.msg-reply-body{flex:1;min-width:0}
.msg-reply-who{display:block;font-size:11.5px;font-weight:700;color:var(--accent)}
.msg-reply-text{display:block;font-size:12.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* files picked but not yet sent */
.msg-pending{display:flex;flex-wrap:wrap;gap:7px}
.msg-pending[hidden]{display:none}
.msg-chip{display:inline-flex;align-items:center;gap:7px;max-width:100%;padding:5px 8px 5px 11px;border:1px solid var(--line);border-radius:999px;background:var(--panel2);font-size:12px}
.msg-chip-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px;font-weight:650}
.msg-chip-size{color:var(--dim);font-variant-numeric:tabular-nums}
.msg-chip-x{border:0;background:none;cursor:pointer;color:var(--dim);font-size:15px;line-height:1;padding:0 2px}
.msg-chip-x:hover{color:var(--ink)}

/* Files list — the same attachments, indexed by file instead of by conversation */
.msg-files-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:14px 16px;border-bottom:1px solid var(--line)}
.msg-files-head h3{font-size:15px;margin:0}
.msg-files-search{flex:1;min-width:200px;max-width:380px;padding:8px 12px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);color:var(--ink);font:inherit}
.msg-files-search:focus{outline:none;border-color:var(--accent)}
.msg-files-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.msg-files-table{width:100%;min-width:640px;border-collapse:collapse;font-size:13.5px}
.msg-files-table th{text-align:left;padding:8px 12px;font-size:11.5px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--dim);font-weight:800;border-bottom:1px solid var(--line);white-space:nowrap}
.msg-files-table td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.msg-files-table tr:last-child td{border-bottom:0}
.msg-files-table tbody tr:hover{background:var(--panel2)}
.msg-files-table td.num{white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--dim)}
.msg-files-table td:nth-child(5){white-space:nowrap;color:var(--dim)}
.msg-files-table td:last-child{text-align:right}
.msg-file-name{display:inline-flex;align-items:center;gap:8px;max-width:100%;border:0;background:none;padding:0;cursor:pointer;font:inherit;font-weight:650;color:var(--ink);text-align:left}
.msg-file-name:hover span{text-decoration:underline}
.msg-file-name .line-ico{flex:0 0 auto;width:15px;height:15px;color:var(--dim)}
.msg-file-type{display:inline-block;padding:1px 7px;border:1px solid var(--line);border-radius:6px;font-size:11px;font-weight:700;color:var(--dim);letter-spacing:.02em}
.msg-file-dir{font-weight:700}
.msg-file-dir.sent{color:var(--accent)}
.msg-file-dir.received{color:var(--ok,#2f9e44)}
.msg-file-dir.room{color:#6b3fa0}

/* recipient picker (inside KC.modal) */
/* the staff room, pinned above every person */
.msg-pick-room{border-color:#6b3fa0}
.msg-pick-room:hover{border-color:#57318a}
/* staff can message every student, so the picker is long enough to need a filter */
.msg-pick-search{width:100%;margin-bottom:10px;padding:9px 12px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);color:var(--ink);font:inherit}
.msg-pick-search:focus{outline:none;border-color:var(--accent)}
.msg-picker{display:flex;flex-direction:column;gap:7px;max-height:55vh;overflow-y:auto}
.msg-pick{display:flex;align-items:center;gap:11px;width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--panel);cursor:pointer;font:inherit;text-align:left;color:var(--ink)}
.msg-pick:hover{border-color:var(--accent)}
.msg-pick-sub{display:block;font-size:12px;color:var(--dim)}

/* mobile: single column; list ↔ thread toggled via .show-thread */
@media(max-width:760px){
  .msg-layout{grid-template-columns:1fr;height:auto;min-height:0}
  .msg-resizer{display:none}      /* handle is hidden + inert when panes stack */
  .msg-thread{display:none}
  .msg-layout.show-thread .msg-list{display:none}
  .msg-layout.show-thread .msg-thread{display:flex;height:calc(100vh - 230px);min-height:340px}
  .msg-back{display:inline-flex}
  /* tighter on a phone: bubbles are narrower, so they need less air between them to read as
     separate messages, and the screen has far less to spare */
  .msg-bubbles{padding:18px 12px 12px}
  .msg-bubbles>*+*{margin-top:7px}
  .msg-bubbles>.bubble-row.grouped{margin-top:2px}
  .bubble-row{max-width:84%}
}

/* css/settings.css */
/* settings styles. Additive only — do not edit css/app.css.
   Owns: settings view (account info, class colours) + term-dates view extras. */

/* ---- constrain to a comfortable reading width so nothing floats off right ---- */
.set-wrap{max-width:760px}

/* ---- account (clean left-aligned key/value) ---- */
.set-account{margin:0;padding:16px 18px;display:flex;flex-direction:column;gap:14px}
.set-account .set-kv{display:flex;flex-direction:column;gap:3px}
.set-account dt{font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--dim)}
.set-account dd{margin:0;font-weight:600;word-break:break-word}
.account-edit{padding:16px 18px}
.account-edit .btn{margin-top:2px}
.student-account-edit{padding:16px 18px;display:grid;gap:16px}
.set-readonly-card{
  display:grid;
  gap:3px;
  padding:13px 14px;
  border:1px solid var(--line);
  border-radius:12px;
  background:color-mix(in srgb,var(--accent-soft) 42%,var(--panel));
}
.set-readonly-label{
  color:var(--dim);
  font-size:11px;
  font-weight:850;
  letter-spacing:.06em;
  text-transform:uppercase;
}
.set-readonly-card strong{font-size:18px}
/* Name card: the name reads on the left, its one action sits on the right (migration 0238).
   The name and its label keep their own 3px stack, so only the row gains a second column. */
.set-name-card{grid-template-columns:1fr auto;align-items:center;gap:12px}
.set-name-main{display:grid;gap:3px;min-width:0}
.set-name-main strong{word-break:break-word}
.set-name-pending{display:grid;gap:4px;justify-items:end;text-align:right}
.set-name-pending .hint{margin:0}
@media (max-width:560px){
  .set-name-card{grid-template-columns:1fr}
  .set-name-pending{justify-items:start;text-align:left}
}
.set-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 16px}
/* "optional" tag next to a field label */
.set-optional{margin-left:6px;font-weight:600;font-size:11.5px;letter-spacing:.02em;text-transform:uppercase;color:var(--dim)}
.set-year-field{display:grid;gap:7px}
.set-year-field label{font-weight:700}
.set-year-buttons{display:flex;gap:7px;flex-wrap:wrap}
/* Colours come from the shared chip rules in app.css; only the square-ish sizing is local. */
.set-year-chip{
  min-width:44px;
  text-align:center;
  justify-content:center;
}
/* "Change password" (and similar) subheading inside the account card */
.set-subhead{margin:4px 0 -4px;font-size:14px;font-weight:800;letter-spacing:.01em}
/* Save button: shrink to its content instead of stretching across the grid column */
.student-account-edit>.set-save-btn{justify-self:start}
/* Join a new class panel (students) */
.set-join-class{padding:16px 18px;display:grid;gap:12px}
.set-join-class .set-join-btn{justify-self:start}
.student-profile-edit{padding:16px 18px;display:grid;grid-template-columns:1fr 180px;gap:0 12px}
.student-profile-edit .btn{grid-column:1/-1;justify-self:start;margin-top:2px}
.school-combobox{position:relative}
.school-suggest{
  position:absolute;
  z-index:20;
  top:calc(100% - 1px);
  left:0;
  right:0;
  display:grid;
  gap:0;
  max-height:274px;
  overflow:auto;
  border:1px solid color-mix(in srgb,var(--accent) 34%,var(--line));
  border-radius:0 0 14px 14px;
  background:var(--panel);
  box-shadow:0 18px 38px rgba(10,47,92,.16);
}
.school-suggest[hidden]{display:none}
.school-suggest button{
  width:100%;
  border:0;
  border-bottom:1px solid color-mix(in srgb,var(--line) 72%,transparent);
  background:transparent;
  color:var(--ink);
  padding:10px 13px;
  text-align:left;
  font:inherit;
  font-weight:650;
  cursor:pointer;
}
.school-suggest button:last-child{border-bottom:0}
.school-suggest button:hover,
.school-suggest button.on{
  background:var(--accent);
  color:#fff;
}
.school-suggest-empty{padding:11px 13px;color:var(--dim);font-size:13px}
/* ---- class colours: two aligned columns (label sized to widest, swatches right after) ---- */
.colour-list{display:grid;grid-template-columns:max-content 1fr;align-items:center;column-gap:24px;padding:4px 18px}
.colour-row{display:contents}                       /* row's children join the parent grid → aligned columns */
.colour-class{display:flex;align-items:center;gap:9px;padding:13px 0;white-space:nowrap}
.colour-class strong{font-weight:700}
.class-dot{display:inline-block;width:13px;height:13px;border-radius:50%;flex:none}
.class-dot.off{border:1px dashed var(--dim)}
.swatches{display:flex;gap:8px;flex-wrap:wrap;align-items:center;padding:13px 0}
.colour-row:not(:first-child) .colour-class,
.colour-row:not(:first-child) .swatches{border-top:1px solid var(--line)}
.swatch{width:26px;height:26px;border-radius:50%;border:0;cursor:pointer;padding:0;transition:transform .1s}
.swatch:hover{transform:scale(1.12)}
.swatch.sel{outline:2px solid var(--accent);outline-offset:2px}
.swatch.none{display:inline-grid;place-items:center;background:var(--panel2);color:var(--dim);font-size:12px;line-height:1;border:1px dashed var(--dim)}
/* narrow: stack label above its swatches */
@media(max-width:560px){
  .set-form-grid{grid-template-columns:1fr}
  .student-profile-edit{grid-template-columns:1fr}
  .colour-list{grid-template-columns:1fr;column-gap:0}
  .colour-class{padding:13px 0 4px}
  .swatches{padding:0 0 13px}
  .colour-row:not(:first-child) .swatches{border-top:0}
}

/* ---- term dates ---- */
tr.term-current td{background:var(--accent-soft)}
.term-now{margin-left:8px}
.term-actions{white-space:nowrap;text-align:right}
.term-dates-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.term-modal-foot{display:flex;gap:9px}
/* current year + next year shown side by side as two columns */
.term-year-cols{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;align-items:start}
.term-year-cols .panel{margin-bottom:0}
/* tighter cells + buttons so the half-width admin table (5 cols) fits without clipping */
.term-year-cols th,.term-year-cols td{padding-left:11px;padding-right:11px}
.term-year-cols td:first-child,.term-year-cols th:first-child{padding-left:16px}
.term-year-cols .term-actions{padding-left:4px}
.term-year-cols .term-actions .btn.sm{padding:5px 9px;font-size:12.5px}
.term-dow{color:var(--dim);font-weight:600;margin-right:3px}
.term-default-note{padding:11px 18px;color:var(--dim);font-size:13px;border-bottom:1px solid var(--line)}
@media (max-width:760px){.term-year-cols{grid-template-columns:1fr}}
/* Victorian Government reference dates — distinct teal so they're obvious next to KC's own dates */
.term-gov{color:#0e7490;font-weight:700}
/* full-width sub-row under a KC term showing the official Vic Govt dates (only where they differ) */
.term-has-gov td{border-bottom:0;padding-bottom:3px}
.term-gov-row td{padding-top:0;padding-bottom:11px;color:#0e7490;font-size:12.5px;font-weight:600}
.term-gov-row .term-gov{font-weight:800;margin-right:2px}
/* edit modal: roomier, with each date input's Vic Govt reference + "Use" button beneath it */
.term-edit-modal{max-width:640px}
.term-edit-modal #tStart,.term-edit-modal #tEnd{font-size:15px;padding:9px 11px}
.term-gov-hint{margin:0 0 14px;color:var(--dim);font-size:13px}
.term-gov-ref{min-height:22px;margin-top:7px;display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:12.5px}
.term-ten-week{margin-top:8px}
.btn.xs{padding:3px 9px;font-size:12px}
.term-zoom-label-notice{
  position:fixed;
  right:18px;
  bottom:18px;
  z-index:2100;
  display:flex;
  align-items:center;
  gap:12px;
  max-width:min(390px,calc(100vw - 36px));
  padding:12px 13px 12px 15px;
  border:1px solid var(--line);
  border-radius:12px;
  background:var(--panel);
  color:var(--ink);
  box-shadow:0 18px 46px rgba(18,35,70,.20);
  font-size:13.5px;
  font-weight:750;
  opacity:0;
  pointer-events:none;
  transform:translateY(10px);
  transition:opacity .18s ease,transform .18s ease;
}
.term-zoom-label-notice.show{
  opacity:1;
  pointer-events:auto;
  transform:translateY(0);
}
.term-zoom-label-notice button{
  flex:0 0 auto;
  border:0;
  background:transparent;
  color:var(--dim);
  cursor:pointer;
  font:inherit;
  font-size:20px;
  line-height:1;
  padding:0 2px;
}
.term-zoom-label-notice button:hover{color:var(--ink)}

/* ---- Email notifications (migration 0231): two switches, save-on-flip ---- */
.set-notif{display:flex;flex-direction:column;gap:14px;padding:16px}
.set-toggle{display:flex;align-items:flex-start;gap:12px;cursor:pointer}
.set-toggle input{margin-top:3px;width:17px;height:17px;accent-color:var(--accent);cursor:pointer;flex:0 0 auto}
.set-toggle-text{display:block}
.set-toggle-text .hint{display:block;margin-top:2px}

/* css/lab-bookings.css */
/* lab-bookings — additive styles only. */
.lb-actions{text-align:right;white-space:nowrap}
.lb-note{margin-top:3px;font-size:12.5px;color:var(--dim)}
.lb-dim{color:var(--dim)}
.lb-opt{color:var(--dim);font-weight:400;font-size:12px}
.lb-chips{display:flex;flex-wrap:wrap;gap:8px}
.lb-modal-foot{display:flex;justify-content:flex-end;gap:9px;width:100%}

/* clickable rows */
.lb-row{cursor:pointer}
.lb-row:hover{background:var(--sidebar-soft,#262b44)0d}
.lb-row:hover td{background:color-mix(in srgb,var(--accent,#5b53e6)8%,transparent)}
.lb-row:focus-visible{outline:2px solid var(--accent,#5b53e6);outline-offset:-2px}
.lb-meta{margin-top:5px;display:flex;flex-wrap:wrap;gap:6px}
.lb-tag{font-size:11.5px;color:var(--dim);background:color-mix(in srgb,var(--accent,#5b53e6)12%,transparent);border-radius:6px;padding:1px 7px}

/* attachment add-row (book modal) */
.lb-att-add{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:8px}
.lb-att-input{flex:1 1 150px;min-width:120px}
#lbAttList{display:flex;flex-wrap:wrap;gap:7px}
.lb-att-chip{display:inline-flex;align-items:center;gap:6px;background:var(--soft,#f1f2f7);border:1px solid var(--line);border-radius:999px;padding:4px 6px 4px 11px;font-size:12.5px}
.lb-att-link{color:var(--dim)}
.lb-att-x{border:0;background:none;cursor:pointer;font-size:16px;line-height:1;color:var(--dim);padding:0 2px}
.lb-att-x:hover{color:var(--danger,#c0392b)}

/* ===== detail sheet ===== */
#modalMount.lb-printing .modal{max-width:680px}
.lb-sheet-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap;padding-bottom:12px;margin-bottom:14px;border-bottom:2px solid var(--line)}
.lb-sheet-title{font-size:17px;font-weight:700}
.lb-sheet-when{color:var(--dim);font-size:13.5px}
.lb-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px 18px;margin:0 0 18px}
.lb-f dt{font-size:11.5px;text-transform:uppercase;letter-spacing:.03em;color:var(--dim);margin-bottom:2px}
.lb-f dd{margin:0;font-size:14.5px}
.lb-block{margin-bottom:16px}
.lb-block h4{font-size:13px;text-transform:uppercase;letter-spacing:.03em;color:var(--dim);margin:0 0 5px}
.lb-block-body{font-size:14px;line-height:1.5;white-space:pre-wrap}
.lb-att-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.lb-att-item{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 11px;border:1px solid var(--line);border-radius:9px}
.lb-att-name{font-size:13.5px}
.lb-detail-foot{align-items:center}
.lb-foot-grow{flex:1 1 auto}

/* ===== print: show only the booking sheet, hide all app chrome ===== */
@media print{
  body *{visibility:hidden!important}
  #modalMount.lb-printing,
  #modalMount.lb-printing *{visibility:visible!important}
  #modalMount.lb-printing .modal-bg{position:static!important;background:none!important;display:block!important;padding:0!important}
  #modalMount.lb-printing .modal{position:static!important;box-shadow:none!important;max-width:100%!important;max-height:none!important;width:100%!important;border-radius:0!important;overflow:visible!important}
  #modalMount.lb-printing .modal-head .x,
  #modalMount.lb-printing .modal-foot{display:none!important}
  .sidebar,.topbar,.toolbar,#toast{display:none!important}
}

/* css/zoom-recordings.css */
/* zoom-recordings — additive styles only. */
.zr-link-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px;padding:18px}
.zr-link-card{display:flex;flex-direction:column;gap:16px;border:1px solid var(--line);border-radius:12px;background:linear-gradient(180deg,color-mix(in srgb,var(--panel) 94%,var(--accent-soft)),var(--panel));padding:18px;min-width:0}
.zr-link-main{display:flex;align-items:center;gap:12px;min-width:0}
.zr-link-ic{flex:0 0 44px;width:44px;height:44px;border-radius:12px;display:grid;place-items:center;background:var(--accent-soft);color:var(--accent)}
.zr-link-ic .line-ico{width:23px;height:23px}
.zr-link-main h3{font-size:18px;line-height:1.2;overflow-wrap:anywhere}
.zr-link-kicker{margin-bottom:3px;color:var(--dim);font-size:12px;font-weight:800;letter-spacing:.5px;text-transform:uppercase}
.zr-join-details{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.zr-join-details div{border:1px solid var(--line);border-radius:10px;background:var(--panel);padding:10px 12px;min-width:0}
.zr-join-details span{display:block;margin-bottom:2px;color:var(--dim);font-size:11px;font-weight:800;letter-spacing:.5px;text-transform:uppercase}
.zr-join-details b{display:block;color:var(--ink);font-size:17px;line-height:1.25;overflow-wrap:anywhere}
.zr-link-actions{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.zr-link-actions .btn.primary{min-width:128px;justify-content:center}
.zr-list{display:flex;flex-direction:column}
.zr-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:13px 18px;border-bottom:1px solid var(--line)}
.zr-row:last-child{border-bottom:0}
.zr-meta{min-width:0}
.zr-titleline{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}
.zr-title{font-weight:600;color:var(--ink)}
/* Unit chip — solid class colour, white text; the colour is darkened in JS until white clears
   4.5:1. inline-block rather than inline-flex because the longest unit name ("Year 11 Chemistry
   Intensive [Accelerated]") is 277px, which all but fills a phone row: the chip has to be able to
   ellipsise rather than push the row sideways. */
.zr-cls{display:inline-block;max-width:100%;padding:2px 10px;border-radius:999px;
  font-size:11.5px;font-weight:800;letter-spacing:.2px;line-height:1.55;color:#fff;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.zr-date{font-size:12.5px;color:var(--dim);margin-top:2px}
.zr-action{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}
.zr-unsorted-note{margin:0;padding:12px 18px 0;color:var(--dim);font-size:13px}
.zr-edit-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 12px}
.zr-edit-full{grid-column:1/-1}

@media(max-width:620px){
  .zr-link-grid{grid-template-columns:1fr;padding:14px}
  .zr-join-details{grid-template-columns:1fr}
  .zr-link-actions{align-items:stretch;flex-direction:column}
  .zr-link-actions .btn{justify-content:center}
  .zr-edit-grid{grid-template-columns:1fr}
  .zr-row{align-items:flex-start;flex-direction:column}
  .zr-action{justify-content:flex-start}
}

/* css/textbooks.css */
/* textbooks — card shelf. Additive styles only. */
.tb-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px}
.tb-card{display:flex;gap:14px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:16px}
.tb-spine{flex:none;width:52px;border-radius:8px;display:grid;place-items:center;font-size:24px;color:#fff}
.tb-body{display:flex;flex-direction:column;gap:4px;min-width:0}
.tb-title{font-weight:700;font-size:15px;line-height:1.3}
.tb-meta{font-size:12.5px;color:var(--dim)}
.tb-action{margin-top:auto;padding-top:8px}
.tb-card--btn{cursor:pointer;text-align:left;font:inherit}
.tb-card--btn:hover{border-color:var(--tb-accent,var(--accent));transform:translateY(-1px);transition:.12s}

/* ---- concise reader ---- */
.tb-reader{display:grid;grid-template-columns:290px 1fr;gap:16px;align-items:start}
.tb-side{position:sticky;top:12px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:10px;max-height:calc(100vh - 90px);display:flex;flex-direction:column;min-height:0}
.tb-search{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:10px;background:var(--bg,transparent);color:inherit;font:inherit;margin-bottom:8px}
.tb-search:focus{outline:none;border-color:var(--tb-accent,var(--accent))}
.tb-nav{overflow-y:auto;min-height:0;padding-right:2px}
.tb-navunit{font-size:11px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--tb-accent,var(--accent));margin:12px 6px 4px}
.tb-navunit:first-child{margin-top:2px}
.tb-navitem{display:block;width:100%;text-align:left;border:0;background:none;color:inherit;font:inherit;font-size:13px;padding:6px 8px;border-radius:8px;cursor:pointer;line-height:1.3}
.tb-navitem:hover{background:color-mix(in srgb,var(--tb-accent,var(--accent)) 12%,transparent)}
.tb-navitem--active{background:color-mix(in srgb,var(--tb-accent,var(--accent)) 20%,transparent);font-weight:600}
.tb-code{display:inline-block;min-width:2.6em;color:var(--dim);font-variant-numeric:tabular-nums}
.tb-noresults{color:var(--dim);font-size:13px;padding:10px 6px}
.tb-main{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;min-width:0}
.tb-content{padding:22px 26px;overflow-y:auto;max-height:calc(100vh - 150px)}
.tb-unitlabel{font-size:11.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--tb-accent,var(--accent))}
.tb-lessontitle{margin:2px 0 12px;font-size:22px;line-height:1.2}
.tb-dpbox{background:color-mix(in srgb,var(--tb-accent,var(--accent)) 8%,transparent);border:1px solid color-mix(in srgb,var(--tb-accent,var(--accent)) 30%,transparent);border-radius:10px;padding:10px 14px;margin-bottom:16px}
.tb-dpbox-h{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--tb-accent,var(--accent));margin-bottom:4px}
/* the dot-point code replaces the bullet; hanging indent keeps wrapped lines
   aligned under the text rather than under the code */
.tb-dpbox ul{margin:0;padding-left:0;list-style:none}
.tb-dpbox li{font-size:13px;margin:2px 0;padding-left:62px;text-indent:-62px}
/* wide enough for the longest code (x.xx.10) so every row's text starts level */
.tb-dpbox-code{display:inline-block;min-width:62px;padding-right:6px;text-indent:0;
  color:var(--tb-dot-bar);font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.tb-body-blocks{font-size:14.5px;line-height:1.62}
.tb-sec{font-size:16px;margin:20px 0 6px;padding-bottom:3px;border-bottom:2px solid color-mix(in srgb,var(--tb-accent,var(--accent)) 40%,transparent)}
.tb-p{margin:8px 0}
.tb-bullet{margin:3px 0 3px 6px;padding-left:14px;position:relative}
.tb-bullet:before{content:"•";position:absolute;left:0;color:var(--tb-accent,var(--accent))}

/* ---- print-book palette ----------------------------------------------
   These are the SAME hexes the colour-book print engine uses
   (build1/scripts/engine.py in each book), so a block on screen matches the block on
   paper. The bar/label hue identifies the block; body text stays near-black
   on a pale fill, exactly as it prints. Only the dot-point slate is
   deliberately book-independent — it is fixed in every printed book too. */
.tb-reader{
  --tb-ink:#23272e;                              /* callout body text */
  --tb-def-bar:#404040;  --tb-def-fill:#ECEFF3;  /* DEFGREY / DEF_FILL   */
  --tb-tip-bar:#FFC000;  --tb-tip-lab:#BF8F00;  --tb-tip-fill:#FBF1D2;
  --tb-misc-bar:#9C2B2B; --tb-misc-fill:#F8E9EC; /* MISCRED / MISC_FILL  */
  --tb-corr-bar:#2E8B57; --tb-corr-fill:#EAF6EE; /* CORRECT "Actually:"  */
  --tb-dot-bar:#3E5C76;  --tb-dot-fill:#E7ECF2;  /* DOTCOL / DOT_TINT    */
  --tb-work-bar:#F08C00; --tb-work-fill:#FBEAD2; /* WORK_BAR / WORK_FILL */
  --tb-work-ans:#7030A0;                         /* PURPLE answer runs   */
}
.tb-callout{border-radius:0 8px 8px 0;padding:8px 12px;margin:9px 0;font-size:13.5px;
  border-left:3px solid var(--tb-cbar);background:var(--tb-cfill);color:var(--tb-ink)}
.tb-lab{font-weight:800;color:var(--tb-clab,var(--tb-cbar));margin-right:5px}

.tb-def{--tb-cbar:var(--tb-def-bar);--tb-cfill:var(--tb-def-fill);
  border-radius:0 8px 8px 0;border-left:3px solid var(--tb-cbar);background:var(--tb-cfill);
  color:var(--tb-ink);padding:8px 12px;margin:9px 0}
.tb-def-tag{display:block;font-size:13px;font-weight:800;letter-spacing:.01em;color:var(--tb-def-bar);margin:0 0 2px}
.tb-tip{--tb-cbar:var(--tb-tip-bar);--tb-clab:var(--tb-tip-lab);--tb-cfill:var(--tb-tip-fill)}
.tb-misc{--tb-cbar:var(--tb-misc-bar);--tb-cfill:var(--tb-misc-fill)}
.tb-corr{--tb-cbar:var(--tb-corr-bar);--tb-cfill:var(--tb-corr-fill)}
/* red misconception beside its green correction, as the book prints them */
.tb-miscpair{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:9px 0}
.tb-miscpair .tb-callout{margin:0}
@media (max-width:1040px){.tb-miscpair{grid-template-columns:1fr;gap:6px}}

/* dot points: fixed slate in every book, never the cover accent */
.tb-dp{--tb-cbar:var(--tb-dot-bar);--tb-cfill:var(--tb-dot-fill);font-size:12.5px}
.tb-dpbox{background:var(--tb-dot-fill);border:1px solid color-mix(in srgb,var(--tb-dot-bar) 30%,transparent);border-radius:10px;padding:10px 14px;margin-bottom:16px;color:var(--tb-ink)}
.tb-dpbox-h{color:var(--tb-dot-bar)}

/* worked example: one orange-barred box holding the stem + purple answer */
.tb-we{background:var(--tb-work-fill);border:0;border-left:3px solid var(--tb-work-bar);
  border-radius:0 8px 8px 0;padding:9px 13px;margin:10px 0;font-size:13.5px;color:var(--tb-ink)}
.tb-we-h{font-weight:800;color:var(--tb-work-bar);margin-bottom:3px}
/* the stem sits on the "Worked example" lead-in: same bar colour, but lighter than
   the label so the label still reads as a label and the question as a sentence */
.tb-we-q{font-weight:600}
.tb-we .tb-p{margin:4px 0}
.tb-we-marks{float:right;margin-left:12px;font-weight:700;color:var(--tb-ink);opacity:.75;font-variant-numeric:tabular-nums}
.tb-we-ans{font-weight:800;color:var(--tb-work-ans);margin:6px 0 2px}
.tb-we-pt{color:var(--tb-work-ans);margin:2px 0 2px 4px;padding-left:18px;text-indent:-18px}
/* figures + tables (the VCE books carry real artwork; some cells are pictures) */
.tb-fig{margin:12px 0;text-align:center}
.tb-fig img{max-width:100%;height:auto;border-radius:6px}
.tb-fig-missing{margin:12px 0;padding:14px;border:1px dashed var(--line);border-radius:8px;color:var(--dim);font-size:12.5px;text-align:center}
.tb-tablewrap{overflow-x:auto;margin:12px 0}
.tb-table{border-collapse:collapse;font-size:13px;min-width:min(100%,420px)}
.tb-table th,.tb-table td{border:1px solid var(--line);padding:6px 10px;text-align:left;vertical-align:middle}
/* app.css uppercases every th. Case is meaningful in a chemistry header: n, m and M
   are three different quantities, and "pH" is not "PH". Keep header text as authored. */
.tb-table th{background:var(--tb-dot-fill);color:var(--tb-ink);font-weight:700;
  text-transform:none;letter-spacing:normal}
.tb-table img{max-width:100%;height:auto;display:block;margin:2px 0}
.tb-body-blocks mark{background:#ffe58a;color:inherit;border-radius:3px;padding:0 1px}
.tb-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 18px;border-top:1px solid var(--line)}
.tb-pager-mid{font-size:12.5px;color:var(--dim);font-variant-numeric:tabular-nums}
@media (max-width:760px){.tb-reader{grid-template-columns:1fr}.tb-side{position:static;max-height:none}.tb-nav{max-height:230px}.tb-content{max-height:none}}

/* css/page-visibility.css */
/* page-visibility — per-role Show/Hide buttons for each page. Additive styles only. */
.pv-note{padding:14px 18px;color:var(--dim);font-size:13px;border-bottom:1px solid var(--line);line-height:1.5}
.pv-list{padding:8px 18px}
.pv-row{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:14px 0;flex-wrap:wrap}
.pv-row + .pv-row{border-top:1px solid var(--line)}
.pv-page{font-weight:700;font-size:15px;display:flex;align-items:center;gap:9px;min-width:150px}
.pv-ic{width:22px;text-align:center}

/* big role toggle "boxes" — one per user type */
.pv-roles{display:flex;gap:10px;flex-wrap:wrap}
.pv-rbtn{display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  min-width:92px;padding:10px 14px;border-radius:14px;border:2px solid var(--line);background:var(--panel2);
  color:var(--dim);font:inherit;font-size:13px;font-weight:800;cursor:pointer;transition:.12s}
.pv-rstate{font-size:11.5px;font-weight:700}
.pv-rbtn.on{border-color:var(--ok);background:rgba(22,163,74,.12);color:var(--ok)}
.pv-rbtn.off{border-color:var(--line);background:var(--panel2);color:var(--dim)}
.pv-rbtn.off .pv-rstate{color:var(--warn)}
.pv-rbtn{position:relative}
/* a small accent dot marks a cell the admin has explicitly overridden (vs the page default) */
.pv-rbtn:not(.default)::after{content:'';position:absolute;top:6px;right:7px;width:6px;height:6px;border-radius:50%;background:var(--accent)}
.pv-rbtn:hover{border-color:var(--accent)}
.pv-rbtn:active{transform:scale(.97)}
.pv-rbtn:disabled{opacity:.6;cursor:default}

/* css/announcements.css */
/* announcements.css — whole-school announcements feed (wave-4).
   Additive only; uses var(--…) tokens so light + dark both work via theme.css. */

/* Constrain to a comfortable reading width, centred. */
.ann-wrap{max-width:760px;margin:0 auto}

/* Feed: a stack of cards. */
.ann-feed{display:flex;flex-direction:column;gap:14px}

.ann-card{
  background:var(--panel);
  border:1px solid var(--line);
  border-left:3px solid var(--accent);
  border-radius:var(--radius);
  box-shadow:var(--shadow);
  padding:16px 18px;
}

.ann-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.ann-title{margin:0;font-size:17px;font-weight:700;color:var(--ink);line-height:1.3}

.ann-actions{display:flex;gap:8px;flex:0 0 auto}
.ann-del{flex:0 0 auto}

.ann-body{margin:8px 0 12px;color:var(--ink);font-size:14.5px;line-height:1.6;word-break:break-word}

.ann-meta{display:flex;align-items:center;gap:7px;font-size:13px;color:var(--dim)}
.ann-author{font-weight:600;color:var(--ink)}
.ann-dot{color:var(--dim)}
.ann-audience{color:var(--dim);font-weight:600}

/* Warm empty state. */
.ann-empty{
  max-width:760px;margin:0 auto;
  text-align:center;
  padding:48px 28px;
  background:var(--accent-soft);
  border:1px solid var(--line);
  border-radius:var(--radius);
}
.ann-empty-glyph{font-size:40px;line-height:1;margin-bottom:10px}
.ann-empty-title{font-size:18px;font-weight:700;color:var(--ink);margin-bottom:4px}
.ann-empty-sub{font-size:14px;color:var(--dim);max-width:420px;margin:0 auto}

/* Modal footer (matches house pattern). */
.ann-modal-foot{display:flex;justify-content:flex-end;gap:9px;width:100%}

.ann-all{display:flex;align-items:center;gap:8px;margin:2px 0 10px;font-weight:600;color:var(--ink)}
.ann-targets{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:12px}
.ann-target-col{border:1px solid var(--line);border-radius:14px;background:var(--panel);min-height:220px;overflow:hidden}
.ann-target-head{padding:10px 12px;border-bottom:1px solid var(--line);font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--dim)}
.ann-target-list{display:flex;flex-direction:column;gap:4px;padding:8px;max-height:280px;overflow:auto}
.ann-course{display:flex;align-items:center;gap:9px;width:100%;border:0;background:transparent;color:var(--ink);text-align:left;border-radius:10px;padding:8px;cursor:pointer}
.ann-course:hover,.ann-course.active{background:var(--accent-soft)}
.ann-course input{flex:0 0 auto}
.ann-course span,.ann-class span{display:flex;flex-direction:column;gap:2px;min-width:0}
.ann-course small,.ann-class small{font-size:12px;color:var(--dim);font-weight:500}
.ann-class{display:flex;align-items:center;gap:9px;border-radius:10px;padding:8px;color:var(--ink);cursor:pointer}
.ann-class:hover{background:var(--accent-soft)}
.ann-class input{flex:0 0 auto}
.ann-help{margin:8px 0 0;color:var(--dim);font-size:12.5px;line-height:1.45}

@media (max-width:720px){
  .ann-targets{grid-template-columns:1fr}
  .ann-target-col{min-height:unset}
}

/* css/lesson.css */
/* KC Portal — lesson views (lesson-join + lesson-attendance). OWNER: wave-4 agent.
   Additive only; uses design tokens from app.css. Light + dark. */

/* ============================ lesson-join ============================ */
.lj-layout{display:grid;grid-template-columns:minmax(360px,1fr) minmax(280px,420px);gap:22px;align-items:start}
.lj-wrap{display:flex;justify-content:center;align-items:flex-start;padding:24px 8px 40px}
.lj-card{
  width:100%;max-width:420px;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow);padding:30px 26px 26px;text-align:center;
}
.lj-logo{
  width:54px;height:54px;margin:0 auto 14px;display:flex;align-items:center;justify-content:center;
  font-size:30px;font-weight:700;color:#ffd43b;background:var(--accent);border-radius:14px;line-height:1;
}
.lj-hello{font-size:22px;margin:0 0 6px}
.lj-lead{color:var(--dim);margin:0 0 22px;font-size:14px}
.lj-form{display:block;text-align:left}
.lj-label{display:block;font-size:13px;font-weight:600;margin-bottom:6px}
.lj-code{
  width:100%;text-align:center;font-size:30px;font-weight:700;letter-spacing:.32em;
  text-transform:uppercase;padding:14px 12px;border-radius:12px;
}
.lj-code::placeholder{letter-spacing:.2em;color:var(--dim);opacity:.5;font-weight:600}
.lj-code.bad{border-color:var(--err);outline:2px solid color-mix(in srgb,var(--err) 22%,transparent)}
.lj-err{
  margin-top:10px;color:var(--err);font-size:13px;font-weight:600;
  background:color-mix(in srgb,var(--err) 10%,transparent);
  border:1px solid color-mix(in srgb,var(--err) 35%,transparent);
  border-radius:9px;padding:8px 11px;
}
.lj-go{width:100%;margin-top:16px;padding:11px;font-size:15px;font-weight:600}
.lj-help{text-align:center;color:var(--dim);font-size:13px;margin:14px 0 0}
.lj-ann-col{position:sticky;top:18px;min-width:0;padding-top:24px}
.lj-ann-col .topbar{display:none}
.lj-ann-col .panel{margin-bottom:0}
.lj-ann-col .ann-wrap{max-width:none}
.lj-ann-col .ann-feed{gap:10px}
.lj-ann-col .ann-card{padding:13px 14px;border-radius:12px}
.lj-ann-col .ann-title{font-size:15px}
.lj-ann-col .ann-body{font-size:13.5px;line-height:1.45}
.lj-ann-col .ann-empty{max-width:none;padding:28px 18px}

/* ============================ lesson-attendance ============================ */
/* Title row on top, nav steppers on their own row below — both left-aligned. */
.la-headrow{display:flex;flex-direction:column;align-items:flex-start;gap:10px;width:100%}
.la-lesson-nav-btn{font-size:13px;padding:7px 12px;border-radius:9px;flex-shrink:0}
.la-lesson-nav-btn:disabled{opacity:.35;cursor:default;pointer-events:none}
.la-nav-group{display:flex;align-items:center;gap:6px;flex-shrink:0}
.la-headtitle{min-width:0}
.la-headtitle-row{display:flex;align-items:center;gap:10px}
/* Phone: the four steppers share one row, two per side, instead of squeezing onto one line. */
@media(max-width:760px){
  .la-headrow>.la-nav-group{flex-wrap:wrap;row-gap:6px;width:100%}
  .la-headrow>.la-nav-group .btn{flex:1 1 calc(50% - 3px);min-width:0;justify-content:center;text-align:center}
  .la-headrow h2{font-size:19px}
}
.la-dot{width:14px;height:14px;border-radius:50%;flex:0 0 auto;box-shadow:0 0 0 3px color-mix(in srgb,var(--ink) 6%,transparent)}

.la-roster{
  --la-roster-gap:10px;
  --la-roster-pad-x:12px;
  --la-roster-pad-y:10px;
  /* four columns: name, score, homework, notes. The homework control carries absence too, so it
     gets the width the attendance and effort toggles used to take between them. */
  --la-roster-cols:
    minmax(140px,1fr)
    minmax(74px,92px)
    minmax(300px,420px)
    minmax(40px,52px);
  display:flex;
  flex-direction:column;
  gap:var(--la-roster-gap);
}
/* Week 5 and week 10 only: the term-test column sits between the quiz score and the homework
   buttons, so the mark goes in beside the rest of that student's night. */
.la-roster.has-termtest{
  --la-roster-cols:
    minmax(140px,1fr)
    minmax(74px,92px)
    minmax(84px,104px)
    minmax(300px,420px)
    minmax(40px,52px);
  flex-direction:column;
  gap:var(--la-roster-gap);
}
.la-panel-pad{
  padding:14px 16px;
}
.la-roster-pad{
  padding:14px 16px 16px;
  overflow-x:auto;
}
.la-roster-head{
  display:grid;
  grid-template-columns:var(--la-roster-cols);
  gap:var(--la-roster-gap);
  align-items:end;
  min-height:24px;
  padding:0 var(--la-roster-pad-x) 5px;
  color:var(--dim);
  font-size:10.5px;
  font-weight:850;
  line-height:1.15;
  letter-spacing:.04em;
  text-transform:uppercase;
}
/* Headings may run to two words ("Written Homework", "Quiz Score") — let them
   wrap rather than clip, and keep the row bottom-aligned so one- and two-line
   headings sit on the same baseline as the roster below. */
.la-roster-head span{
  min-width:0;
  overflow-wrap:break-word;
}
.la-roster-head span:first-child{text-align:left}
.la-roster-head span:nth-child(n+2){text-align:center}
.la-row{
  display:grid;
  grid-template-columns:var(--la-roster-cols);
  align-items:center;
  gap:var(--la-roster-gap);
  min-height:54px;
  padding:var(--la-roster-pad-y) var(--la-roster-pad-x);
  border:1px solid var(--line);
  border-radius:12px;
  background:var(--panel2);
}
.la-row>*{min-width:0}
/* An absent student's row is tinted, so a glance down the roster shows who wasn't there.
   Every row starts absent, so the tint is deliberately faint — it marks what is left to do. */
.la-row.la-absent{background:color-mix(in srgb,var(--err) 6%,var(--panel2))}
.la-name{
  align-self:center;
  font-weight:750;
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}
.la-perf-icon{
  width:32px;
  height:32px;
  display:grid;
  place-items:center;
  border:1px solid color-mix(in srgb,var(--accent) 20%,var(--line));
  border-radius:10px;
  background:color-mix(in srgb,var(--accent-soft) 50%,var(--panel));
  color:var(--accent);
  cursor:pointer;
}
.la-perf-icon:hover{
  border-color:var(--accent);
  background:var(--accent);
  color:#fff;
}
.la-perf-icon .line-ico{width:18px;height:18px}
.la-note-btn{justify-self:center;position:relative}
.la-note-btn.has-notes{
  border-color:color-mix(in srgb,var(--accent) 45%,var(--line));
  background:var(--accent-soft);
}
.la-note-btn .la-cmt-badge{position:absolute;top:-6px;right:-6px;margin-left:0}
.la-hw-rate{display:flex;align-items:baseline;gap:7px;font-variant-numeric:tabular-nums}
.la-hw-rate b{font-size:15px}
.la-hw-rate span{font-size:11.5px;color:var(--dim);white-space:nowrap}
.la-signal{
  min-width:0;
  display:grid;
  justify-self:center;
  width:100%;
  gap:2px;
  line-height:1.1;
  text-align:center;
  min-height:34px;
  align-content:center;
  padding:4px 8px;
  border:1px solid var(--line);
  border-radius:999px;
  background:var(--panel);
}
.la-signal b{
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  font-size:12px;
  font-weight:950;
  font-variant-numeric:tabular-nums;
}
.la-signal span{
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  color:var(--dim);
  font-size:9.5px;
  font-weight:700;
}
.la-signal.green b{color:var(--ok)}
.la-signal.yellow b{color:var(--warn)}
.la-signal.red b{color:var(--err)}
.la-signal.green{border-color:color-mix(in srgb,var(--ok) 34%,var(--line));background:color-mix(in srgb,var(--ok) 10%,var(--panel))}
.la-signal.yellow{border-color:color-mix(in srgb,var(--warn) 34%,var(--line));background:color-mix(in srgb,var(--warn) 11%,var(--panel))}
.la-signal.red{border-color:color-mix(in srgb,var(--err) 32%,var(--line));background:color-mix(in srgb,var(--err) 9%,var(--panel))}
.la-score-inline{
  display:flex;
  align-items:center;
  justify-content:center;
  justify-self:stretch;
}
.la-score-cell{
  width:100%;
  max-width:78px;
  height:32px;
  padding:4px 7px;
  border-radius:10px;
  text-align:center;
  font-size:13px;
  font-weight:900;
  background:var(--panel);
}
.la-score-cell:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 14%,transparent)}
.la-row.score-bad .la-score-cell{border-color:var(--err);background:color-mix(in srgb,var(--err) 8%,var(--panel))}
/* Term test: tinted so it reads as the one unusual thing on the row this week. */
.la-termtest .la-score-cell{
  max-width:96px;
  border-color:color-mix(in srgb,var(--accent) 34%,var(--line));
  background:color-mix(in srgb,var(--accent-soft) 45%,var(--panel));
}
.la-score-cell.bad{border-color:var(--err);background:color-mix(in srgb,var(--err) 8%,var(--panel))}
/* Per-control labels and hints. A wide screen is already headed by .la-roster-head, so they stay
   hidden there and only appear once the row stacks on a phone (see the max-width:680px block). */
.la-field-label,.la-field-hint,.la-note-txt{display:none}
/* The homework control now sits in a labelled wrapper; on a wide screen the wrapper is the grid
   cell the pills used to be, so it must not add width of its own. */
.la-field-hw{display:flex;justify-content:center;min-width:0}
/* Phone-only: says out loud that there is no Save button. Hidden on a wide screen, where the staff
   using this page already know. */
.la-autosave{
  display:none;align-items:flex-start;gap:9px;margin:0 0 12px;padding:10px 12px;
  border:1px solid color-mix(in srgb,var(--accent) 26%,transparent);border-radius:12px;
  background:var(--accent-soft);font-size:13px;line-height:1.4;
}
.la-autosave b{display:block}
.la-hw-choice{display:inline-flex;justify-self:center;width:100%;max-width:420px;border:1px solid var(--line);border-radius:999px;overflow:hidden;background:var(--panel)}
.la-hw-choice-btn{
  flex:1;
  min-width:0;
  min-height:34px;
  appearance:none;
  border:0;
  background:transparent;
  color:var(--dim);
  font:inherit;
  font-weight:850;
  padding:7px 6px;
  cursor:pointer;
  font-size:11px;
  line-height:1;
  transition:background .12s,color .12s;
}
.la-hw-choice-btn+.la-hw-choice-btn{border-left:1px solid var(--line)}
.la-hw-choice-btn:hover{color:var(--ink)}
.la-hw-choice-btn.complete.on{background:color-mix(in srgb,var(--ok) 16%,transparent);color:var(--ok)}
.la-hw-choice-btn.incomplete.on{background:color-mix(in srgb,var(--warn) 16%,transparent);color:var(--warn)}
.la-hw-choice-btn.not-seen.on{background:color-mix(in srgb,var(--dim) 14%,transparent);color:var(--dim)}
.la-hw-choice-btn.absent.on{background:color-mix(in srgb,var(--err) 14%,transparent);color:var(--err)}

.la-actions{display:inline-flex;gap:6px;flex-wrap:wrap;justify-content:center;justify-self:center}
.la-actions .btn{
  width:40px;
  min-width:40px;
  height:34px;
  padding-inline:0;
  overflow:hidden;
  white-space:nowrap;
}
.la-actions .btn .line-ico{margin:0}
.la-actions .subtle.on{border-color:color-mix(in srgb,var(--err) 35%,var(--line));color:var(--err);background:color-mix(in srgb,var(--err) 10%,var(--panel))}
/* homework panel */
.la-hw{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.la-hw-item{
  display:flex;align-items:center;justify-content:space-between;gap:8px 12px;flex-wrap:wrap;
  padding:10px 13px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);
}
.la-hw-title{font-weight:600}
.la-hw-note{flex:1 1 220px;color:var(--dim);font-size:12px;line-height:1.35}

/* guard card */
.la-guard{display:flex;justify-content:center;padding:24px 8px}
.la-guard-card{
  width:100%;max-width:440px;text-align:center;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow);padding:30px 26px;
}
.la-guard-ic{font-size:34px;margin-bottom:8px}
.la-guard-card h3{font-size:19px;margin-bottom:8px}
.la-guard-card p{color:var(--dim);margin:0 0 18px;font-size:14px}

/* comment modal */
.la-cmt-to{margin-bottom:10px;color:var(--dim);font-size:14px}
.la-cmt-ta{width:100%;resize:vertical;min-height:96px}
.la-cmt-foot{display:flex;gap:10px;justify-content:flex-end}

/* attendance landing — calendar */
.la-page-wrap{width:100%}
.la-cal-nav{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:11px 18px;border-bottom:1px solid var(--line);
}
.la-cal-nav-arrows{display:flex;gap:4px;flex-wrap:wrap;min-width:0}
.la-cal-nav-btn{
  display:inline-flex;align-items:center;justify-content:center;
  height:34px;padding:0 11px;gap:5px;
  border:1px solid var(--line);background:var(--panel);color:var(--dim);
  border-radius:9px;font:inherit;font-size:13px;font-weight:600;cursor:pointer;
  white-space:nowrap;transition:.1s;
}
.la-cal-nav-btn:hover{border-color:var(--accent);color:var(--accent)}
.la-cal-nav-btn.today-btn{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 35%,var(--line))}
.la-cal-nav-btn.today-btn:hover{background:var(--accent-soft)}
.la-cal-date-input{
  height:34px;padding:0 10px;
  border:1px solid var(--line);background:var(--panel);color:var(--ink);
  border-radius:9px;font:inherit;font-size:13px;font-weight:600;cursor:pointer;
}
.la-cal-date-input:focus{border-color:var(--accent);outline:none}
.la-cal-range-label{font-size:13px;font-weight:600;color:var(--dim);white-space:nowrap;margin:0 4px}
.la-cal-scroll{
  overflow-x:auto;-webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain;              /* don't hand a swipe to the browser's back gesture */
  scroll-snap-type:x proximity;
}
/* Its scrollbar comes from css/scroll.css like every other pane's. */
/* 7 equal columns that share the width when the week fits, but hold a legible floor and
   scroll horizontally instead of squashing once it doesn't. */
.la-cal-grid{
  display:grid;
  grid-template-columns:repeat(7,minmax(120px,1fr));
  border-top:1px solid var(--line);
  width:100%;   /* 1fr shares the width when the week fits; the floor overflows into a scroll when it doesn't */
}
.la-cal-col{border-right:1px solid var(--line);scroll-snap-align:center}
.la-cal-col:last-child{border-right:0}
.la-cal-col.is-today .la-cal-col-hdr{background:color-mix(in srgb,var(--accent) 7%,var(--panel))}
.la-cal-col.is-today .la-cal-dow{color:var(--accent)}
.la-cal-col.is-today .la-cal-dom{color:var(--accent)}
.la-cal-col.is-past{opacity:.6}
/* Selected day — the one the user has navigated to. Strong accent header so it's unmistakable. */
.la-cal-col.is-selected{opacity:1}
.la-cal-col.is-selected .la-cal-col-hdr{background:var(--accent);border-bottom-color:var(--accent);box-shadow:inset 0 -3px 0 color-mix(in srgb,var(--accent) 60%,#000)}
.la-cal-col.is-selected .la-cal-dow{color:#fff}
.la-cal-col.is-selected .la-cal-dom{color:#fff}
.la-cal-col.is-selected .la-cal-month{color:rgba(255,255,255,.85)}
/* A button, but it must read as a column header — hence the full reset. */
.la-cal-col-hdr{
  display:block;width:100%;font:inherit;color:inherit;cursor:pointer;
  border:0;border-bottom:1px solid var(--line);border-radius:0;
  padding:10px 12px 9px;text-align:center;background:var(--panel2);
  transition:background .12s;
}
.la-cal-col-hdr:hover{background:color-mix(in srgb,var(--accent) 12%,var(--panel2))}
.la-cal-col-hdr:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.la-cal-dow{font-size:10.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
.la-cal-dom{font-size:22px;font-weight:700;line-height:1.1;color:var(--ink);margin:2px 0}
.la-cal-month{font-size:11px;color:var(--dim)}
.la-cal-tiles{padding:8px;display:flex;flex-direction:column;gap:7px;min-height:70px}
.la-cal-tile{
  display:block;width:100%;text-align:left;
  border:none;border-radius:10px;padding:9px 11px;
  cursor:pointer;font:inherit;line-height:1.25;
  transition:opacity .1s,filter .1s;
}
.la-cal-tile:hover{filter:brightness(1.08)}
.la-cal-tile:active{opacity:.75}
.la-cal-tile-name{font-size:12.5px;font-weight:700;display:block;margin-bottom:3px}
.la-cal-tile-time{font-size:11px;display:block;opacity:.82}
.la-cal-empty{font-size:11.5px;color:var(--dim);text-align:center;padding:14px 8px;font-style:italic}

/* Tiny screens — one day at a time instead of the whole week. Show only the selected
   day full-width and drop the week-jump arrows so ‹ Day / Day › steps through days. */
@media(max-width:680px){
  .la-cal-grid{grid-template-columns:1fr}
  .la-cal-col{display:none}
  .la-cal-col.is-selected{display:block;border-right:0}
  #laPrevWeek,#laNextWeek{display:none}
}

/* performance modal */
.la-perf-h{font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--dim);margin:18px 0 8px}
.la-perf-h:first-child{margin-top:2px}
.la-mastery{display:flex;flex-direction:column;gap:10px}
.la-mb-label{display:flex;align-items:center;gap:8px;font-size:13px;margin-bottom:5px;flex-wrap:wrap}
.la-mb-n{margin-left:auto;font-weight:700;font-variant-numeric:tabular-nums}
.la-mb{height:9px;border-radius:6px;background:color-mix(in srgb,var(--ink) 9%,transparent);overflow:hidden}
.la-mb-fill{height:100%;border-radius:6px}
.la-mb-fill.low,.la-mb-n.low{color:var(--err)} .la-mb-fill.low{background:var(--err)}
.la-mb-fill.mid,.la-mb-n.mid{color:var(--warn)} .la-mb-fill.mid{background:var(--warn)}
.la-mb-fill.high,.la-mb-n.high{color:var(--ok)} .la-mb-fill.high{background:var(--ok)}

.la-attempts{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.la-attempts li{display:flex;align-items:center;gap:10px;font-size:14px;padding:7px 11px;border:1px solid var(--line);border-radius:9px}
.la-at-score{font-weight:700;font-variant-numeric:tabular-nums}
.la-at-score.low{color:var(--err)} .la-at-score.mid{color:var(--warn)} .la-at-score.high{color:var(--ok)}
.la-at-pct{color:var(--dim)}
.la-at-at{margin-left:auto;color:var(--dim);font-size:13px}

@media(max-width:1500px){
  .la-roster{overflow-x:visible;padding-bottom:0}
  .la-row{
    grid-template-columns:var(--la-roster-cols);
    align-items:center;
    gap:9px 10px;
    min-width:0;
    padding:10px 12px;
  }
}

@media(max-width:1200px){
  .la-roster{
    --la-roster-cols:minmax(120px,1fr) minmax(74px,92px) minmax(280px,380px) minmax(40px,52px);
  }
}

/* narrow screens: stack the roster row.
   The column header is gone here, so each control carries its own label (.la-field-label) and, where
   the answer isn't obvious, a line of hint under it — otherwise an assistant on a phone is left with
   a box saying "Score", a box saying "/20" and a bare icon, with nothing to say which is which. */
@media(max-width:680px){
  .lj-layout{grid-template-columns:1fr}
  .lj-wrap{padding:14px 0 4px}
  .lj-ann-col{position:static;padding-top:0}
  .la-roster{overflow-x:visible;gap:12px}
  .la-roster-head{display:none;min-width:0}
  .la-roster-pad{padding:12px}
  .la-row{display:flex;flex-direction:column;align-items:stretch;gap:14px;min-width:0;padding:13px;background:var(--panel);border-radius:14px}
  .la-row.la-absent{background:color-mix(in srgb,var(--err) 6%,var(--panel))}
  .la-name{align-self:stretch;white-space:normal;font-size:17px;font-weight:800}

  /* one labelled field per control */
  .la-field{display:block}
  .la-field-label{display:block;font-size:13px;font-weight:800;line-height:1.3;margin-bottom:6px;color:var(--ink)}
  .la-field-tag{
    display:inline-block;margin-left:4px;font-style:normal;font-size:10.5px;font-weight:900;
    letter-spacing:.04em;text-transform:uppercase;padding:2px 7px;border-radius:999px;
    background:color-mix(in srgb,var(--warn) 18%,transparent);color:var(--warn);
  }
  .la-field-hint{display:block;margin-top:5px;font-size:12px;line-height:1.35;color:var(--dim)}

  /* the two mark boxes: full width, thumb-sized, and the test box tinted so it is never the quiz box */
  .la-score-inline{display:block}
  .la-score-cell{max-width:none;width:100%;height:48px;font-size:18px;text-align:left;padding:0 14px}
  .la-termtest .la-score-cell{
    max-width:none;
    border-color:color-mix(in srgb,var(--warn) 55%,transparent);
    background:color-mix(in srgb,var(--warn) 7%,var(--panel));
  }

  /* four pills won't fit one 390 px row at a usable size — wrap them 2x2 */
  .la-field-hw{display:block}
  .la-hw-choice{display:grid;grid-template-columns:1fr 1fr;width:100%;max-width:none;border-radius:12px}
  .la-hw-choice-btn{min-height:46px;font-size:13px;border-top:1px solid var(--line);border-right:1px solid var(--line)}
  .la-hw-choice-btn+.la-hw-choice-btn{border-left:0}
  .la-hw-choice-btn:nth-child(-n+2){border-top:0}
  .la-hw-choice-btn:nth-child(2n){border-right:0}

  /* the notes button says what it does instead of being a bare speech bubble */
  .la-note-btn{
    justify-self:stretch;width:100%;height:auto;min-height:46px;
    display:flex;align-items:center;justify-content:center;gap:8px;
    border-style:dashed;border-radius:12px;font-weight:800;font-size:13.5px;
  }
  .la-note-btn:hover{border-style:solid}
  .la-note-txt{display:inline}
  .la-note-btn .la-cmt-badge{display:none}   /* the button's own text already carries the count */
  .la-autosave{display:flex}

  .la-class-pad{padding:14px}
  .la-class-grid{grid-template-columns:1fr}
}

/* ---- lesson comment UI ---- */
.la-cmt-to{margin-bottom:10px;color:var(--dim);font-size:14px}
.la-cmt-list-host{margin-bottom:12px}
.la-cmt-empty{color:var(--dim);font-size:13.5px;padding:6px 0 4px}
.la-cmt-list{display:flex;flex-direction:column;gap:7px}
.la-cmt-item{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 12px}
.la-cmt-item-head{display:flex;align-items:center;gap:6px;margin-bottom:5px}
.la-cmt-who{font-size:11px;font-weight:800;color:var(--dim);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.la-cmt-edit-btn,.la-cmt-del-btn{border:0;background:none;font:inherit;font-size:11.5px;font-weight:700;cursor:pointer;padding:2px 7px;border-radius:6px;flex:none}
.la-cmt-edit-btn{color:var(--accent)}
.la-cmt-edit-btn:hover{background:var(--accent-soft)}
.la-cmt-del-btn{color:var(--err)}
.la-cmt-del-btn:hover{background:color-mix(in srgb,var(--err) 10%,transparent)}
.la-cmt-text{font-size:14px;line-height:1.4;word-break:break-word}
.la-cmt-edit-area{
  width:100%;margin-top:5px;resize:none;min-height:58px;
  padding:7px 9px;border:1px solid var(--accent);border-radius:8px;
  font:inherit;font-size:14px;background:var(--panel);color:var(--ink);
}
.la-cmt-form{margin-top:2px;padding-top:10px;border-top:1px solid var(--line)}
.la-cmt-form textarea{font:inherit;font-size:14px;padding:8px 10px;border-radius:9px;resize:none;min-height:68px}
.la-cmt-form-foot{display:flex;align-items:center;justify-content:space-between;margin-top:7px;gap:8px}
.la-cmt-counter{font-size:12px;font-weight:700;color:var(--dim)}
.la-cmt-counter.near{color:var(--warn)}
.la-cmt-counter.full{color:var(--err)}
.la-cmt-badge{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:17px;height:17px;padding:0 4px;
  background:var(--accent);color:#fff;
  border-radius:999px;font-size:10px;font-weight:900;margin-left:3px;
}
.la-date-input{font:inherit;font-size:13px;font-weight:700;color:var(--ink);background:var(--panel);border:1.5px solid var(--line);border-radius:8px;padding:5px 10px;cursor:pointer;outline-offset:2px}
.la-date-input:focus{border-color:var(--accent);outline:2px solid color-mix(in srgb,var(--accent) 30%,transparent)}

/* ---- phone: the calendar nav and the lesson/week steppers ---- */
@media(max-width:560px){
  .la-cal-nav{padding:10px 12px;gap:6px}
  .la-cal-nav-btn{height:36px;padding:0 9px;font-size:12.5px}
  .la-cal-range-label{margin:0;flex:1 0 100%;order:-1}
  .la-cal-date-input{height:36px;font-size:12.5px}
  /* prev/next week + prev/next lesson: two even rows rather than one crushed line */
  .la-nav-group{flex:1 1 100%;justify-content:space-between}
  .la-nav-group .btn{flex:1 1 0;min-height:38px;justify-content:center}
}

/* css/practice.css */
/* practice — list + play + Section B review. Reuses quiz-play classes from student.css; adds pr-*. */

/* ---- soft-load: shimmer skeletons + visible error state ----
   The practice screens paint their chrome first and drop these placeholders where the data will land.
   Shapes match the real content (tile / bank card / question row) so nothing jumps when it swaps in. */
.pr-skel{
  position:relative;
  overflow:hidden;
  border:1px solid var(--line);
  border-radius:8px;
  background:var(--panel2);
}
.pr-skel::after{
  content:"";
  position:absolute;
  inset:0;
  transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--ink) 7%,transparent),transparent);
  animation:pr-shimmer 1.25s infinite;
}
@keyframes pr-shimmer{to{transform:translateX(100%)}}
.pr-skel-tile{min-height:132px;border-radius:12px}
.pr-skel-card{min-height:142px}
.pr-skel-row{min-height:104px}
.pr-stat-pending{opacity:.35}
@media(prefers-reduced-motion:reduce){.pr-skel::after{animation:none}}

.pr-error{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:8px 14px;
  max-width:980px;
  border:1px solid color-mix(in srgb,var(--warn,#b23b2e) 45%,var(--line));
  background:color-mix(in srgb,var(--warn,#b23b2e) 7%,var(--panel));
  border-radius:10px;
  padding:14px 16px;
  font-size:13.5px;
  line-height:1.45;
}
.pr-error b{font-size:14.5px}
.pr-error span{color:var(--dim);flex:1 1 220px}
.pr-error .btn{margin-left:auto}

/* course sidebar layout (shown when student is in >1 course) */
.pr-split{display:grid;grid-template-columns:180px 1fr;gap:16px;align-items:start}
@media(max-width:700px){.pr-split{grid-template-columns:1fr}}
.pr-sidebar{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--panel);position:sticky;top:72px}
.pr-sidebar-head{padding:10px 14px;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--dim);border-bottom:1px solid var(--line);background:var(--panel2)}
.pr-unit-list{display:flex;flex-direction:column}
.pr-unit-item{padding:11px 14px;border:none;border-bottom:1px solid var(--line);background:transparent;text-align:left;cursor:pointer;font:inherit;font-size:14px;font-weight:600;color:var(--ink);width:100%}
.pr-unit-item:last-child{border-bottom:none}
.pr-unit-item:hover{background:var(--accent-soft)}
.pr-unit-item.on{background:var(--accent-soft);color:var(--accent)}
.pr-content{min-width:0}
.pr-sub{font-size:12.5px;color:var(--dim)}
.pr-blurb{font-size:13px;color:var(--dim);margin:-4px 0 12px}
.pr-section{font-size:14px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;color:var(--dim);margin:22px 0 12px;max-width:760px}
.pr-section:first-child{margin-top:4px}
.pr-marks{font-size:12.5px;font-weight:600;color:var(--accent)}

.pr-answer{width:100%;box-sizing:border-box;font:inherit;font-size:14.5px;line-height:1.55;color:var(--ink);background:var(--panel2);border:1.5px solid var(--line);border-radius:11px;padding:12px 14px;resize:vertical;min-height:120px}
.pr-answer:focus{outline:none;border-color:var(--accent)}

/* Section B review blocks */
.pr-yours,.pr-model,.pr-guide{border-radius:10px;padding:11px 14px;margin-top:10px;font-size:13.5px;line-height:1.55}
.pr-lbl{font-size:11px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--dim);margin-bottom:5px}
.pr-yours{background:var(--panel2);border:1px solid var(--line)}
.pr-yourtext,.pr-modeltext{white-space:pre-wrap}
.pr-empty{color:var(--dim);font-style:italic}
.pr-model{background:var(--accent-soft);border:1px solid var(--accent2)}
.pr-guide{background:var(--panel2);border:1px dashed var(--line);color:var(--dim)}
.pr-guide .pr-lbl{color:var(--dim)}

/* staff question-bank browser */
.practice-bank-summary{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(160px,220px));
  gap:10px;
  margin:0 0 14px;
}
.practice-bank-summary div{
  border:1px solid var(--line);
  background:var(--panel);
  border-radius:8px;
  padding:13px 15px;
  box-shadow:var(--shadow);
}
.practice-bank-summary b{display:block;color:var(--accent);font-size:24px;line-height:1}
.practice-bank-summary span{display:block;margin-top:5px;color:var(--dim);font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.04em}
.practice-bank-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}
.practice-bank-card{
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:8px;
  min-height:142px;
  border:1px solid var(--line);
  background:var(--panel);
  color:var(--ink);
  border-radius:8px;
  padding:15px;
  box-shadow:var(--shadow);
  text-align:left;
  cursor:pointer;
  font:inherit;
  transition:transform .15s,border-color .15s,box-shadow .15s;
}
.practice-bank-card:hover{
  transform:translateY(-1px);
  border-color:var(--accent);
  box-shadow:0 12px 28px color-mix(in srgb,var(--accent) 16%,transparent);
}
.practice-bank-code{
  color:var(--accent);
  font-size:11.5px;
  font-weight:900;
  letter-spacing:.08em;
  text-transform:uppercase;
}
.practice-bank-card strong{font-size:16px;line-height:1.25;overflow-wrap:anywhere}
.practice-bank-card strong.tile-title-sm{font-size:14px;line-height:1.28}
.practice-bank-card strong.tile-title-md{font-size:12.5px;line-height:1.3}
.practice-bank-card strong.tile-title-xs{font-size:11.5px;line-height:1.32}
.practice-bank-meta{display:grid;gap:3px;margin-top:auto;color:var(--dim);font-size:12.5px;line-height:1.35}
.practice-bank-meta b{color:var(--ink);font-size:20px;line-height:1}
.practice-bank-meta em{font-style:normal;color:var(--dim)}
.pr-bank-list{display:grid;gap:12px;max-width:980px}
.pr-bank-controls{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;max-width:980px;margin:0 0 14px}
.pr-bank-note{color:var(--dim);font-size:13.5px;line-height:1.4}
.pr-bank-note b{color:var(--ink)}
#bankMore{white-space:nowrap}
@keyframes pr-bank-swap{from{opacity:.3;transform:translateY(4px)}to{opacity:1;transform:none}}
.pr-bank-list.pr-bank-swap{animation:pr-bank-swap .2s ease}
.pr-bank-question .panel-head{align-items:flex-start}
.pr-bank-question .panel-head h3{font-size:15px;line-height:1.45;font-weight:650;min-width:0}

/* difficulty band chip on each question (Low green / Medium yellow / High red / Extreme purple) */
.diff-chip{display:inline-flex;align-items:center;white-space:nowrap;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:700}
.diff-chip.d-L{background:#e7f6ec;color:#1f7a44}
.diff-chip.d-M{background:#fbf0d2;color:#8a5d00}
.diff-chip.d-H{background:#fce4df;color:#b23b2e}
.diff-chip.d-E{background:#5b2a86;color:#fdf3ff}

/* dot-point code chip + its hover tooltip (floated on <body> — .panel is overflow:hidden) */
.dp-chip{cursor:help}
.dp-tip-float{position:absolute;display:none;z-index:9999;width:min(340px,80vw);background:var(--ink);color:var(--bg);
  padding:8px 11px;border-radius:8px;font-size:12.5px;line-height:1.45;box-shadow:var(--shadow);pointer-events:none}
.pr-bank-options{display:grid;gap:7px}
.pr-bank-opt{
  display:grid;
  grid-template-columns:30px minmax(0,1fr) auto;
  gap:10px;
  align-items:start;
  border:1px solid var(--line);
  background:var(--panel2);
  border-radius:8px;
  padding:9px 11px;
}
.pr-bank-opt span{
  display:grid;
  place-items:center;
  width:24px;
  height:24px;
  border-radius:7px;
  background:var(--panel);
  color:var(--dim);
  font-size:12px;
  font-weight:900;
}
.pr-bank-opt p{margin:0;font-size:13.5px;line-height:1.42}
.pr-bank-opt b{
  align-self:center;
  border-radius:999px;
  background:#e5f8e9;
  color:#087a3b;
  padding:3px 8px;
  font-size:11px;
  font-weight:900;
}
.pr-bank-opt.correct{
  border-color:color-mix(in srgb,var(--ok,#16834c) 45%,var(--line));
  background:color-mix(in srgb,var(--ok,#16834c) 8%,var(--panel2));
}
.pr-bank-opt.correct span{background:#e5f8e9;color:#087a3b}

@media(max-width:560px){
  .practice-bank-summary{grid-template-columns:1fr 1fr}
  .practice-bank-grid{grid-template-columns:1fr}
  .pr-bank-opt{grid-template-columns:28px minmax(0,1fr)}
  .pr-bank-opt b{grid-column:2;justify-self:start}
}

/* css/online-sac.css */
/* online-sac.css — sitting a SAC inside Portal, and marking the scripts that come back
   (js/views/online-sac.js). Additive only — do not edit css/app.css.

   ADMIN ONLY for now: the tile, both routes and every RPC behind them are gated on is_admin().

   Spacing contract, same as the rest of the Portal: one 18px horizontal gutter from panel head to
   question body to marking row; 14px inside a block, 18px between blocks; mark values right-aligned
   and tabular so they line up down the page whatever the question is worth. */

/* ---------------- the tile, at the top of the SACs page ----------------
   Above the search bar, so it never scrolls away with the file list and a search never filters it
   out. Two decks: a coloured band that carries the paper's identity (flag, title, blurb, and a
   piece of line art drawn from the paper's own subject), then a light footer holding the facts and
   the one thing you can do. The band's palette is deliberately NOT the theme accent — this tile is
   an event, not another panel, and the art has to sit on a colour that belongs to its content.
   Art and palette are keyed off data-art on the tile, so a second paper brings its own without
   touching the layout; osac-art-default is the fallback when a paper has no art of its own. */
.osac-tile{
  --osac-band1:#0b3b32; --osac-band2:#0f5347; --osac-band3:#10695a;
  --osac-band-ink:#fff; --osac-band-dim:#a9d8c9; --osac-go:#8fd14f; --osac-go-ink:#06251f;
  margin:0 0 18px;overflow:hidden;
  border:1px solid var(--line);
  border-radius:var(--radius);background:var(--panel);box-shadow:var(--shadow);
}
.osac-band{
  position:relative;padding:18px 18px 20px;color:var(--osac-band-ink);
  background:linear-gradient(115deg,var(--osac-band1) 0%,var(--osac-band2) 52%,var(--osac-band3) 100%);
}
.osac-band>svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.osac-band-in{position:relative}
.osac-flag{
  display:inline-flex;align-items:center;gap:7px;margin-bottom:11px;
  border-radius:999px;padding:4px 12px;
  font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  background:#ffffff1f;color:var(--osac-band-ink);border:1px solid #ffffff33;
}
/* The dot is the only state colour left in the band: the words say the rest. */
.osac-flag .dot{width:7px;height:7px;border-radius:50%;background:var(--osac-go)}
.osac-tile.state-submitted .osac-flag .dot{background:#f6c453}
.osac-tile.state-marked .osac-flag .dot{background:#7ee0a6}
.osac-band h3{margin:0 0 5px;font-size:21px;letter-spacing:-.015em;line-height:1.25;color:#fff}
.osac-sub{margin:0;color:var(--osac-band-dim);font-size:13.5px;line-height:1.5;max-width:66ch}
.osac-foot{
  display:flex;align-items:center;gap:18px;flex-wrap:wrap;
  padding:14px 18px;background:var(--panel2);border-top:1px solid var(--line);
}
.osac-facts{display:flex;flex-wrap:wrap;gap:8px;align-items:center;flex:1 1 auto}
.osac-fact{
  display:inline-flex;align-items:center;gap:6px;padding:5px 12px;
  border:1px solid var(--line);border-radius:999px;background:var(--panel);
  font-size:12.5px;font-weight:600;color:var(--dim);
}
.osac-fact b{color:var(--ink);font-variant-numeric:tabular-nums}
/* The action end of the footer: the state's own number, then the button. */
.osac-side{display:flex;align-items:center;gap:14px;flex:0 0 auto;margin-left:auto}
.osac-when{font-size:12px;color:var(--dim);line-height:1.45;text-align:right;max-width:19ch}
.osac-when b{color:var(--ink)}
.osac-ring{
  display:flex;align-items:baseline;gap:7px;padding:6px 13px;
  border:1px solid var(--ok);border-radius:999px;background:var(--panel);line-height:1;
}
.osac-ring b{font-size:17px;font-variant-numeric:tabular-nums;color:var(--ink)}
.osac-ring span{font-size:11px;font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ok)}
.osac-prog{font-size:11.5px;color:var(--dim);line-height:1.5;text-align:right}
.osac-prog b{color:var(--ink)}
.osac-bar{width:132px;height:7px;margin-top:5px;border-radius:999px;background:var(--line);overflow:hidden}
.osac-bar i{display:block;height:100%;background:linear-gradient(90deg,var(--osac-band2),var(--osac-go))}
/* One green button, whatever the theme: it belongs to the band above it, not to the accent. */
.osac-foot .btn.primary{
  background:var(--osac-go);border-color:var(--osac-go);color:var(--osac-go-ink);font-weight:800;
}
.osac-foot .btn.primary:hover{background:#7fc341;border-color:#7fc341;color:var(--osac-go-ink)}
@media(max-width:820px){
  .osac-band h3{font-size:19px}
  .osac-side{width:100%;margin-left:0;justify-content:space-between}
  .osac-side .btn{flex:0 0 auto}
}

/* ---------------- the runner ---------------- */
.osac-top{
  position:sticky;top:0;z-index:20;
  display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:13px 18px;margin-bottom:16px;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--panel);box-shadow:var(--shadow);
}
.osac-top-title{font-weight:800;font-size:15px}
.osac-top-title small{display:block;font-weight:600;color:var(--dim);font-size:12.5px}
.osac-top-right{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.osac-saved{font-size:12px;color:var(--dim)}
.osac-saved.on{color:var(--ok);font-weight:700}
.osac-clock{
  padding:6px 13px;border-radius:10px;background:var(--panel2);border:1px solid var(--line);
  font-size:20px;font-weight:800;font-variant-numeric:tabular-nums;
}
.osac-clock.warn{background:#fff6e0;border-color:#f0d48a;color:#7a5600}
.osac-clock.danger{background:#fdeced;border-color:#f2b6bc;color:#98202e}
.osac-intro{margin:0 0 18px;padding:14px 18px;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--panel);box-shadow:var(--shadow);font-size:13.5px;line-height:1.6;color:var(--dim)}
.osac-intro b{color:var(--ink)}

.osac-steps{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:18px}
.osac-step{
  width:34px;height:34px;border-radius:9px;display:grid;place-items:center;
  border:1px solid var(--line);background:var(--panel);color:var(--dim);
  font:inherit;font-size:13px;font-weight:800;cursor:pointer;
}
.osac-step.on{border-color:var(--accent);color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
.osac-step.done{background:color-mix(in srgb,var(--ok) 13%,var(--panel));color:var(--ok);
  border-color:color-mix(in srgb,var(--ok) 40%,var(--line))}

.osac-q{border:1px solid var(--line);border-radius:var(--radius);background:var(--panel);
  box-shadow:var(--shadow);margin-bottom:18px;overflow:hidden}
.osac-qhead{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 18px;border-bottom:1px solid var(--line);background:var(--panel2);
}
.osac-qhead h3{font-size:13px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
.osac-qmarks{font-size:12.5px;font-weight:700;color:var(--dim);font-variant-numeric:tabular-nums}
.osac-qbody{padding:18px}
.osac-stem{margin:0 0 14px;font-size:15px;line-height:1.6}
.osac-fig{margin:0 0 16px}
.osac-fig svg,.osac-fig img{display:block;width:100%;max-width:560px;height:auto;
  border:1px solid var(--line);border-radius:10px;background:var(--panel)}
.osac-cap{margin:7px 0 0;max-width:560px;font-size:12px;color:var(--dim);line-height:1.45}

.osac-part{margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
.osac-part:first-of-type{margin-top:0;padding-top:0;border-top:0}
.osac-pstem{display:flex;gap:10px;align-items:baseline;margin:0 0 10px}
.osac-plbl{flex:0 0 22px;font-weight:800}
.osac-ptxt{flex:1;font-size:15px;line-height:1.55}
.osac-pmarks{flex:0 0 auto;white-space:nowrap;font-size:12.5px;font-weight:700;color:var(--dim)}
.osac-ans{
  width:100%;font:inherit;font-size:14.5px;line-height:1.6;color:var(--ink);
  background:var(--panel2);border:1.5px solid var(--line);border-radius:11px;padding:12px 14px;resize:vertical;
}
.osac-ans:focus{outline:none;border-color:var(--accent);background:var(--panel)}
.osac-ans[readonly]{background:#fbfcfe;border-style:dashed;color:var(--dim);cursor:default}
.osac-words{margin:5px 0 0;text-align:right;font-size:11.5px;color:var(--dim)}

.osac-foot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:14px 18px;
  border-top:1px solid var(--line);background:var(--panel2)}
.osac-foot .hint{flex:1 1 240px;margin:0}

/* ---------------- submitted / marked ---------------- */
.osac-state{
  display:flex;gap:22px;align-items:center;flex-wrap:wrap;
  padding:22px;margin-bottom:18px;border-radius:var(--radius);box-shadow:var(--shadow);
  border:1px solid #f0d48a;background:#fff6e0;
}
.osac-state.marked{border-color:#a6dcbf;background:#eaf8f0}
.osac-state-body{flex:1;min-width:260px}
.osac-state-body h3{margin:0 0 6px;font-size:20px}
.osac-state-body p{margin:0 0 12px;max-width:62ch;font-size:14px;line-height:1.55}
.osac-bigring{
  width:132px;height:132px;flex:none;border-radius:50%;display:grid;place-items:center;
  text-align:center;line-height:1;background:var(--panel);border:7px solid #189b57;
}
.osac-bigring b{display:block;font-size:33px;font-variant-numeric:tabular-nums}
.osac-bigring span{display:block;margin-top:4px;font-size:11.5px;font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;color:var(--dim)}

/* ---------------- the marking screen ----------------
   Laid out as the PAPER is: the question, then what the student wrote in that box, then the
   marking scheme for that box. Marking a script you cannot read the questions of is guesswork. */
.osac-mk{margin-top:12px;border-radius:11px;overflow:hidden;
  border:1px solid color-mix(in srgb,var(--accent) 28%,var(--line));
  background:color-mix(in srgb,var(--accent) 4%,var(--panel))}
.osac-mk-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 14px;
  background:color-mix(in srgb,var(--accent) 10%,var(--panel));
  border-bottom:1px solid color-mix(in srgb,var(--accent) 22%,var(--line));
}
.osac-mk-lbl{font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--accent)}
.osac-mk-award{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:700;color:var(--dim)}
.osac-mk-award input{
  width:56px;padding:5px 8px;border:1px solid var(--line);border-radius:8px;background:var(--panel);
  color:var(--ink);font:inherit;font-size:14px;font-weight:800;text-align:center;font-variant-numeric:tabular-nums;
}
.osac-mk-award input:focus{outline:none;border-color:var(--accent)}
.osac-given{
  margin:0;padding:10px 14px;background:var(--panel2);font-size:13.5px;line-height:1.55;
  white-space:pre-wrap;border-bottom:1px solid color-mix(in srgb,var(--accent) 18%,var(--line));
}
.osac-given b{display:block;margin-bottom:4px;font-size:11px;font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;color:var(--dim)}
.osac-given.blank{color:var(--dim);font-style:italic}
.osac-sol{margin:0;padding:11px 14px;font-size:13.5px;line-height:1.6}
.osac-sol b{display:block;margin-bottom:4px;font-size:11px;font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;color:var(--dim)}
.osac-pts{list-style:none;margin:0;padding:2px 14px 12px}
.osac-pt{display:flex;gap:12px;align-items:flex-start;padding:8px 0;
  border-bottom:1px solid color-mix(in srgb,var(--accent) 12%,var(--line))}
.osac-pt:last-child{border-bottom:0}
.osac-pt-txt{flex:1;font-size:13.5px;line-height:1.55}
.osac-pt input{flex:0 0 auto;width:18px;height:18px;margin-top:2px;accent-color:var(--accent);cursor:pointer}
.osac-note{margin:0;padding:0 14px 12px;font-size:12.5px;line-height:1.5;color:var(--dim)}
.osac-mkbar{
  position:sticky;bottom:0;z-index:15;display:flex;align-items:center;justify-content:space-between;
  gap:14px;flex-wrap:wrap;margin-top:18px;padding:13px 18px;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--panel);box-shadow:var(--shadow);
}
.osac-total{font-size:20px;font-weight:800;font-variant-numeric:tabular-nums}
.osac-total span{display:block;font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--dim)}
.osac-fb{flex:1 1 260px;min-width:200px;padding:9px 12px;border:1px solid var(--line);border-radius:10px;
  background:var(--panel2);color:var(--ink);font:inherit;font-size:13.5px;resize:vertical}
.osac-fb:focus{outline:none;border-color:var(--accent)}

/* css/usage.css */
/* usage — portal analytics dashboard. */
.ux-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:14px;margin-bottom:20px}
.ux-cards.sm{grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:10px;margin-bottom:8px}
.ux-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px}
.ux-card.sm{padding:13px}
.ux-n{font-size:26px;font-weight:800;line-height:1.1;color:var(--ink)}
.ux-card.sm .ux-n{font-size:20px}
.ux-k{font-size:12.5px;color:var(--dim);margin-top:4px}

/* Sortable table headings — click a heading to sort by that column (asc/desc). */
th.th-sort{cursor:pointer;user-select:none;white-space:nowrap}
th.th-sort:hover{color:var(--ink)}
th.th-sort:focus-visible{outline:2px solid var(--accent,#1c75bc);outline-offset:-2px}
th.th-sort.on{color:var(--ink)}
th.th-sort .th-arrow{opacity:.55;font-size:.85em;margin-left:3px}
th.th-sort.on .th-arrow{opacity:1}

.ux-h{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--dim);margin:18px 0 8px}
.ux-tl{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:10px;overflow:hidden}
.ux-tl-row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:9px 13px;border-bottom:1px solid var(--line)}
.ux-tl-row:last-child{border-bottom:0}
.ux-tl-pg{font-weight:600;font-size:13.5px}
.ux-tl-meta{font-size:12px;color:var(--dim);white-space:nowrap}

/* A sub-heading inside a .panel-body has to bring the panel's own 18px gutter, or it sits left of
   the panel heading above it (the same mismatch KC.auditPanels warns about for body content).
   Table cells already carry that 18px, so the tables underneath line up with it for free. */
.ux-h-inset{padding:0 18px}
/* The cards sit in a .panel-pad; the sub-heading below brings its own 18px of space, so the pad
   must not add a second helping underneath. */
.ux-after-top{padding-bottom:0}
.ux-after-top .ux-cards{margin-bottom:0}

/* Data table with short numeric columns. Without this, `table{width:100%}` shares the width equally
   between five columns and the digits drift into the middle of their cells, metres apart. `width:1%`
   shrinks each numeric column to its content and hands the slack to the label column, so the numbers
   group at the right and read as one block. Tabular figures keep the digits in vertical columns. */
.ux-t-num th.n,.ux-t-num td.n{width:1%;white-space:nowrap;text-align:right;font-variant-numeric:tabular-nums}
.ux-t-num td.n .badge{margin-left:6px}

/* Wide tables scroll inside the panel instead of pushing the page sideways on a phone. */
.ux-tablewrap{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}
@media(max-width:760px){
  .ux-tablewrap>table{min-width:520px}
  .ux-cards{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px}
  .ux-card{padding:14px}
  .ux-n{font-size:22px}
  .ux-tl-row{flex-direction:column;align-items:flex-start;gap:2px}
  .ux-tl-meta{white-space:normal}
}

/* ---- Portal's dream panel (js/views/usage.js dreamHTML) ----------------- */
/* The third line in a stat card: what the big number is counting, when the label alone
   would have to say "students (4 presses)" and stop being a label. */
.ux-card .ux-sub{font-size:11.5px;color:var(--dim);opacity:.8;margin-top:2px}
/* Sits between the cards and the first table, so it needs the panel gutter itself. */
.ux-note{margin:0 0 14px;font-size:12.5px;color:var(--dim);line-height:1.5}

/* css/practice-usage.css */
/* practice-usage.css — Analytics → Practice materials.
   All colour comes from theme tokens so the panel works in Classic / Journal / Lab alike. */

/* ---- headline cards: number, label, and a supporting line so the number needs no interpretation ---- */
.pmu-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;margin-bottom:20px}
.pmu-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:16px 18px}
.pmu-card-n{font-size:30px;font-weight:800;line-height:1.05;color:var(--accent)}
.pmu-card-of{font-size:18px;font-weight:700;color:var(--dim)}
.pmu-card-k{font-size:13px;font-weight:700;color:var(--ink);margin-top:6px}
.pmu-card-sub{font-size:12px;color:var(--dim);margin-top:2px}

/* ---- shared table chrome ---- */
.pmu-table{width:100%}
.pmu-table th.num,.pmu-table td.num{text-align:right;font-variant-numeric:tabular-nums}
.pmu-table td{vertical-align:top}
.th-sort{cursor:pointer;user-select:none;white-space:nowrap}
.th-sort:hover{color:var(--accent)}
.th-sort.on{color:var(--accent)}
.th-arrow{margin-left:4px;font-size:9px;vertical-align:1px}
.th-sort:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* app.css sets `input{width:100%}`, which would stretch the box across the whole panel head and
   squash the panel title onto two lines — pin it to a fixed track instead. */
.pmu-search{flex:0 1 230px;width:230px;border:1px solid var(--line);background:var(--panel2);color:var(--ink);
  border-radius:999px;padding:6px 13px;font:inherit;font-size:13px}
.pmu-search:focus{outline:2px solid var(--accent);outline-offset:1px;background:var(--panel)}

.pmu-name{font-weight:600}
.pmu-none{padding:16px 18px;color:var(--dim);font-size:13.5px}
.pmu-all-done{color:var(--ok);font-weight:600}
.pmu-note{padding:10px 18px;color:var(--dim);font-size:12.5px;border-bottom:1px solid var(--line)}

/* ---- file rows ---- */
.pmu-frow{cursor:pointer}
.pmu-frow:hover{background:var(--panel2)}
.pmu-frow:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.pmu-frow.open{background:var(--panel2)}
.pmu-frow.open td{border-bottom-color:transparent}
.pmu-frow.cold .pmu-file-t{color:var(--dim)}
.pmu-file{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.pmu-file-t{font-weight:600}
.pmu-caret{color:var(--dim);font-size:11px;width:10px;flex:none}
.pmu-file-open{font-size:11.5px;font-weight:500;color:var(--accent);text-decoration:none;padding:1px 7px;border:1px solid var(--line);border-radius:99px}
.pmu-file-open:hover{background:var(--panel2);text-decoration:underline}
/* Kind/status badges sit on the meta line, not beside the title — a long SAC name would otherwise
   push them onto a second row and give the table a ragged left edge. */
.pmu-file-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--dim);margin-top:5px;padding-left:18px}

/* ---- reach bar: the "have they all seen it?" glance ---- */
.pmu-reach{display:flex;align-items:center;gap:9px;min-width:110px}
.pmu-reach-track{flex:1;height:7px;border-radius:999px;background:var(--panel2);border:1px solid var(--line);overflow:hidden}
.pmu-reach-fill{height:100%;border-radius:999px;background:var(--accent)}
.pmu-reach-fill.ok{background:var(--ok)}
.pmu-reach-fill.mid{background:var(--warn)}
.pmu-reach-fill.low{background:var(--err)}
.pmu-reach-n{font-size:12.5px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.pmu-reach-of{color:var(--dim);font-weight:600}

/* ---- drill-down: who opened it, and who has not ---- */
.pmu-drill>td{background:var(--panel2);padding:0 18px 16px}
.pmu-drill-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:22px;align-items:start}
.pmu-sub-h{font-size:11.5px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--dim);margin:4px 0 8px}
.pmu-sub-note{font-weight:600;text-transform:none;letter-spacing:0}
.pmu-sub{width:100%;background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden}
.pmu-sub th{padding:8px 12px;font-size:11px}
.pmu-sub td{padding:8px 12px;font-size:13px}
.pmu-sub tr:last-child td{border-bottom:0}
.pmu-sub th.num,.pmu-sub td.num{text-align:right;font-variant-numeric:tabular-nums}
.pmu-drill .pmu-none{padding:10px 0}

.pmu-chips{display:flex;flex-wrap:wrap;gap:6px}
.pmu-miss{display:inline-flex;padding:4px 11px;border-radius:999px;font-size:12.5px;font-weight:600;
  background:var(--panel);border:1px solid var(--line);color:var(--dim)}

@media (max-width:860px){
  .pmu-drill-grid{grid-template-columns:1fr;gap:16px}
}

/* css/curriculum.css */
/* Curriculum & Textbooks — section-level reader for CHEM12. */
.cur-toprow{
  display:grid;
  grid-template-columns:minmax(260px, 520px) 1fr;
  gap:12px;
  align-items:center;
  margin:0 0 14px;
}
.cur-tools input{
  min-height:46px;
  border-radius:13px;
  background:var(--panel);
  box-shadow:var(--shadow);
}
.cur-secbar{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap}
.cur-sec{
  border:1px solid var(--line);
  background:var(--panel);
  color:var(--ink);
  border-radius:10px;
  padding:8px 14px;
  font-weight:700;
  font-size:14px;
  cursor:pointer;
}
.cur-sec:hover{border-color:var(--accent)}
.cur-sec.on{background:var(--accent);border-color:var(--accent);color:#fff}

.cur-layout{
  display:grid;
  grid-template-columns:minmax(330px, 380px) minmax(0, 1fr);
  gap:18px;
  align-items:start;
}
.cur-layout.definitions-mode{
  grid-template-columns:minmax(0, 1fr);
}
.cur-layout.definitions-mode .cur-content{
  width:100%;
}

/* left tree */
.cur-side{
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:16px;
  padding:12px;
  position:sticky;
  top:12px;
  max-height:calc(100vh - 92px);
  overflow:auto;
}
/* Scrollbars: css/scroll.css draws an overlay pill on every pane, this one included. */
.cur-side-pages{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:7px;
  margin:0 0 12px;
}
.cur-side-page{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:6px;
  border:1px solid var(--line);
  background:var(--panel2);
  color:var(--ink);
  border-radius:11px;
  padding:7px 8px;
  font:inherit;
  font-size:12px;
  font-weight:850;
  cursor:pointer;
}
.cur-side-page .line-ico{width:15px;height:15px}
.cur-side-page:hover{border-color:var(--accent);color:var(--accent)}
.cur-side-page.on{background:var(--accent);border-color:var(--accent);color:#fff}
.cur-course-switcher{
  display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));
  justify-content:start;
  gap:6px;
  margin:0 0 10px;
}
.cur-course-card{
  position:relative;
  display:grid;
  align-content:center;
  gap:2px;
  width:100%;
  min-height:54px;
  max-width:none;
  min-width:0;
  border:1px solid var(--line);
  border-radius:13px;
  background:
    linear-gradient(145deg,color-mix(in srgb,var(--panel) 88%,white),color-mix(in srgb,var(--accent-soft) 42%,var(--panel)));
  color:var(--ink);
  padding:7px 7px 6px;
  cursor:pointer;
  text-align:center;
  box-shadow:0 6px 16px rgba(24,64,120,.06);
  overflow:hidden;
}
.cur-course-card::after{
  content:none;
}
.cur-course-card:hover{border-color:var(--accent);transform:translateY(-1px)}
.cur-course-card.on{
  border-color:var(--accent);
  background:
    linear-gradient(145deg,color-mix(in srgb,var(--accent-soft) 72%,var(--panel)),var(--panel));
  box-shadow:0 8px 18px rgba(24,100,210,.12), inset 0 0 0 1px color-mix(in srgb,var(--accent) 18%,transparent);
}
.cur-course-code{
  color:var(--accent);
  font-weight:900;
  font-size:15px;
  line-height:1;
  letter-spacing:0;
}
.cur-course-sub{
  color:var(--ink);
  font-weight:750;
  font-size:8.75px;
  line-height:1.15;
  max-width:none;
  white-space:normal;
}
.cur-course-count{
  justify-self:center;
  border:1px solid var(--line);
  background:var(--panel);
  color:var(--dim);
  border-radius:999px;
  padding:1px 5px;
  font-size:8.75px;
  font-weight:800;
  line-height:1.25;
}
.cur-def-nav{
  border:1px solid var(--line);
  background:var(--panel2);
  border-radius:13px;
  padding:13px 14px;
  display:grid;
  gap:2px;
}
.cur-def-nav-kicker{
  color:var(--accent);
  font-size:10px;
  font-weight:900;
  letter-spacing:.08em;
  text-transform:uppercase;
}
.cur-def-nav strong{font-size:18px;color:var(--ink);line-height:1.1}
.cur-def-nav small{color:var(--dim);line-height:1.35}
.cur-tree,.cur-areas,.cur-sections{list-style:none;margin:0;padding:0}
.cur-area-tree{display:grid;gap:2px}
.cur-areas{margin:8px 0 2px 12px;border-left:1px solid var(--line);padding-left:10px}
.cur-sections{display:grid;gap:5px;margin:6px 0 12px 16px}
.cur-area,.cur-section,.cur-result{
  width:100%;
  text-align:left;
  border:0;
  background:none;
  color:var(--ink);
  cursor:pointer;
  border-radius:10px;
  font:inherit;
}
.cur-area{
  display:grid;
  grid-template-columns:14px minmax(0, 1fr) auto;
  gap:8px;
  align-items:center;
  padding:9px 8px;
  font-weight:750;
}
.cur-area-main,.cur-section-main{
  display:grid;
  grid-template-columns:auto minmax(0, 1fr);
  gap:8px;
  align-items:start;
  min-width:0;
}
.cur-section{
  display:grid;
  grid-template-columns:minmax(0, 1fr) auto;
  gap:8px;
  align-items:center;
  padding:8px 9px;
  font-size:13.5px;
  line-height:1.28;
}
.cur-area:hover,.cur-section:hover,.cur-result:hover{background:var(--panel2)}
.cur-section.on{
  background:var(--accent-soft);
  color:var(--accent);
}
.cur-caret{width:12px;display:inline-block;color:var(--dim);flex:none}
.cur-badge{
  margin-left:auto;
  font-size:11px;
  color:var(--dim);
  background:var(--panel2);
  border-radius:20px;
  padding:2px 8px;
  line-height:1.4;
}
.cur-code{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--accent);
  font-weight:800;
  white-space:nowrap;
}
.cur-code.lg{font-size:17px}

/* right reader */
.cur-content{min-width:0}
.cur-view-panel{
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:18px;
  box-shadow:var(--shadow);
  overflow:hidden;
}
.cur-view-head{
  display:flex;
  justify-content:space-between;
  gap:14px;
  align-items:flex-start;
  padding:22px 24px 18px;
  border-bottom:1px solid var(--line);
  background:linear-gradient(180deg,color-mix(in srgb,var(--accent-soft) 54%,var(--panel)),var(--panel));
}
.cur-kicker{
  margin:0 0 7px;
  color:var(--accent);
  text-transform:uppercase;
  letter-spacing:1px;
  font-weight:850;
  font-size:12px;
}
.cur-title-stack{min-width:0}
.cur-view-head h2{
  margin:0;
  color:var(--ink);
  font-size:26px;
  line-height:1.18;
  letter-spacing:0;
}
.cur-view-head h2 span{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--accent);
  margin-right:7px;
}
.cur-count{
  flex:none;
  border:1px solid var(--line);
  background:var(--panel);
  color:var(--dim);
  border-radius:999px;
  padding:7px 12px;
  font-size:12.5px;
  font-weight:800;
}
.cur-area-context{display:none!important}
.cur-reader{padding:20px 24px 24px}
.cur-dot-list{display:grid;gap:10px}
.cur-dot-item{
  display:grid;
  grid-template-columns:92px minmax(0, 1fr);
  gap:14px;
  align-items:start;
  padding:13px 0;
  border-bottom:1px solid color-mix(in srgb,var(--line) 70%,transparent);
}
.cur-dot-item:last-child{border-bottom:0}
.cur-dot-code{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--accent);
  background:var(--accent-soft);
  border:1px solid color-mix(in srgb,var(--accent) 24%,var(--line));
  border-radius:999px;
  text-align:center;
  padding:4px 8px;
  font-weight:850;
  font-size:13px;
}
.cur-dot-item p{margin:0;line-height:1.58;color:var(--ink)}
.cur-muted{color:var(--dim);line-height:1.5;margin:8px 0 0}
.cur-textbook-list{display:grid;gap:12px}
.cur-textbook-target{
  display:grid;
  grid-template-columns:58px minmax(0,1fr);
  gap:16px;
  border:1px solid var(--line);
  background:var(--panel2);
  border-radius:16px;
  padding:16px;
}
.cur-textbook-target .tb-spine{
  width:58px;
  min-height:118px;
  border-radius:12px;
  display:grid;
  place-items:center;
  color:white;
}
.cur-textbook-copy h3{
  margin:0;
  font-size:22px;
  line-height:1.22;
  color:var(--ink);
}
.cur-textbook-copy h3 span{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--accent);
  margin-right:6px;
}
.cur-textbook-copy p:not(.cur-kicker){margin:8px 0 12px;color:var(--dim);line-height:1.45}
.cur-textbook-dots{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 14px}
.cur-textbook-dots span{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  background:var(--panel);
  border:1px solid var(--line);
  color:var(--accent);
  border-radius:999px;
  padding:4px 8px;
  font-size:12px;
  font-weight:800;
}

/* search/definitions */
.cur-result-list{display:grid;gap:8px;padding:18px 22px 24px}
.cur-result{
  display:grid;
  grid-template-columns:auto minmax(0, 1fr);
  gap:13px;
  align-items:start;
  border:1px solid var(--line);
  background:var(--panel2);
  padding:12px 14px;
}
.cur-result.on{border-color:var(--accent);background:var(--accent-soft)}
.cur-result b{display:block;line-height:1.3}
.cur-result small{display:block;margin-top:3px;color:var(--dim);line-height:1.35}
.cur-term-grid{display:flex;gap:8px;flex-wrap:wrap}
.cur-term{
  border:1px solid var(--line);
  background:var(--panel2);
  border-radius:999px;
  padding:7px 11px;
  font-weight:750;
  color:var(--ink);
}
.cur-def-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
  gap:12px;
}
.cur-definitions-panel .cur-reader{
  padding:22px 24px 26px;
}
.cur-def-card{
  border:1px solid var(--line);
  background:var(--panel2);
  border-radius:14px;
  padding:14px;
}
.cur-def-card-head{
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  gap:10px;
  margin:0 0 8px;
}
.cur-def-card h3{
  margin:0;
  color:var(--ink);
  font-size:16px;
  line-height:1.2;
}
.cur-def-card p{margin:0;color:var(--dim);line-height:1.48}
/* Study Design (verbatim VCAA) entries: authority chip + a slightly darker card. */
.cur-def-card.is-sd{
  background:color-mix(in srgb, var(--panel2) 88%, var(--ink) 12%);
  border-color:color-mix(in srgb, var(--line) 55%, var(--accent) 45%);
}
/* VCAA command terms (verbatim VCAA glossary): purple tint + outline chip, a hue of
   their own so they never read as the accent-blue Study Design cards. --cmd is mixed
   with --ink wherever it carries text, so it lightens on the dark designs. */
.cur-definitions-panel{--cmd:#7b4bd0}
.cur-def-card.is-ct{
  background:color-mix(in srgb, var(--panel2) 90%, var(--cmd) 10%);
  border-color:color-mix(in srgb, var(--line) 55%, var(--cmd) 45%);
}
.cur-def-chip{
  flex:none;
  padding:3px 9px;
  border-radius:999px;
  background:var(--accent);
  color:#fff;
  font-size:10.5px;
  font-weight:800;
  letter-spacing:.04em;
  text-transform:uppercase;
  white-space:nowrap;
}
.cur-def-chip.is-ct{
  background:color-mix(in srgb, var(--cmd) 12%, var(--panel));
  color:color-mix(in srgb, var(--cmd) 74%, var(--ink));
  border:1px solid color-mix(in srgb, var(--cmd) 42%, transparent);
  padding:2px 8px;
}

@media(max-width:900px){
  .cur-toprow{grid-template-columns:1fr}
  .cur-secbar{justify-content:flex-start}
  .cur-layout{grid-template-columns:1fr}
  .cur-side{position:static;max-height:46vh}
}
@media(max-width:560px){
  .cur-view-head{display:grid;padding:18px}
  .cur-view-head h2{font-size:21px}
  .cur-reader{padding:14px 18px 18px}
  .cur-dot-item{grid-template-columns:1fr;gap:8px}
  .cur-dot-code{justify-self:start}
  .cur-textbook-target{grid-template-columns:1fr}
  .cur-textbook-target .tb-spine{width:100%;min-height:54px}
}

/* css/set-homework.css */
/* Set homework page — additive only. Themed via the shared CSS variables (theme.css). */

/* two columns: written homework (left) + optional Portal Quiz (right); stack on narrow screens */
.sh-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start;margin:0}
@media (max-width:900px){.sh-cols{grid-template-columns:1fr}}
.sh-cols > .panel{margin:0}
.sh-pad{padding:16px 18px}
.sh-quiz-lead{margin:-2px 0 14px;line-height:1.45}
.sh-quiz-name{color:var(--accent);white-space:nowrap}

/* attach-a-quiz toggle — replaces the old free-text quiz title (portal quizzes auto-name by week) */
.sh-quiz-switch{display:flex;align-items:center;gap:10px;cursor:pointer;font-weight:700;margin:0 0 8px;user-select:none}
.sh-quiz-switch input{width:18px;height:18px;accent-color:var(--accent);cursor:pointer;flex:0 0 auto}
.sh-quiz-switch-txt{font-size:14.5px}
/* the quiz config dims + goes non-interactive until the toggle is on */
.sh-quiz-fields{transition:opacity .15s ease}
.sh-quiz-fields.is-off{opacity:.45;pointer-events:none;filter:grayscale(.3)}

/* topics box — scrollable checklist grouped by unit (ported from the standalone designer) */
.sh-topics{border:1px solid var(--line);border-radius:12px;background:var(--panel2);
  max-height:320px;overflow-y:auto;padding:8px 10px}
.sh-unit-head{font-weight:800;font-size:11px;text-transform:uppercase;letter-spacing:.5px;
  color:var(--dim);margin:10px 0 2px;display:flex;align-items:center;gap:8px}
.sh-unit-head:first-child{margin-top:2px}
.sh-unit-head button{border:0;background:none;color:var(--accent);font-weight:700;font-size:11px;cursor:pointer;padding:0}
.sh-unit-head button:hover{text-decoration:underline}
.sh-topic{display:flex;align-items:center;gap:9px;padding:5px 3px;font-size:14px;cursor:pointer;border-radius:8px}
.sh-topic:hover{background:var(--accent-soft)}
.sh-topic input{accent-color:var(--accent);width:16px;height:16px;flex:0 0 16px;cursor:pointer}
.sh-tcode{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums;min-width:2.4em}
.sh-tname{flex:1;min-width:0;overflow-wrap:anywhere}
.sh-tcount{margin-left:auto;color:var(--dim);font-size:12px;white-space:nowrap}

/* section (1.1.1) rows — expandable, with a checkbox that ticks the whole section at once */
.sh-sec{border-radius:8px}
.sh-sec-head{display:flex;align-items:center;gap:2px}
.sh-sec-toggle{border:0;background:none;cursor:pointer;color:var(--dim);font-size:13px;line-height:1;
  padding:5px 4px;flex:0 0 auto;transition:transform .12s;transform:rotate(0deg)}
.sh-sec-toggle[aria-expanded="true"]{transform:rotate(90deg)}
.sh-sec-toggle:hover{color:var(--accent)}
.sh-sec-check{display:flex;align-items:center;gap:9px;flex:1;min-width:0;padding:5px 3px;
  font-size:14px;font-weight:600;cursor:pointer;border-radius:8px}
.sh-sec-check:hover{background:var(--accent-soft)}
.sh-sec-check input{accent-color:var(--accent);width:16px;height:16px;flex:0 0 16px;cursor:pointer}
.sh-sec-sub{font-weight:400;color:var(--dim);font-size:12.5px}
.sh-sec-leaves{margin-left:24px;border-left:2px solid var(--line);padding-left:6px}
.sh-sec-leaves[hidden]{display:none}

.sh-pool{font-size:13.5px;color:var(--dim);margin:10px 0 0;min-height:1.2em}
.sh-pool b{color:var(--accent)}

/* two-up rows that collapse on narrow screens */
.sh-grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media (max-width:560px){.sh-grid2{grid-template-columns:1fr}}

.sh-advanced{margin-top:6px}
.sh-advanced > summary{cursor:pointer;font-size:13px;font-weight:700;color:var(--accent);
  list-style:none;padding:6px 0;user-select:none}
.sh-advanced > summary::-webkit-details-marker{display:none}
.sh-advanced > summary::before{content:"▸ ";font-size:11px}
.sh-advanced[open] > summary::before{content:"▾ "}

/* Written homework is collapsed by default — it can't be auto-marked, so it's the fallback,
   not the first thing staff should reach for. Matches .cls-tools-details in teacher.css. */
.sh-written-details>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;
  flex-wrap:wrap;padding:16px 18px;min-height:56px;user-select:none}
.sh-written-details>summary::-webkit-details-marker{display:none}
.sh-written-details[open]>summary{border-bottom:1px solid var(--line)}
.sh-written-caret{display:inline-block;transition:transform .15s ease;color:var(--dim);font-size:12px}
.sh-written-details[open] .sh-written-caret{transform:rotate(90deg)}

.sh-warn{margin:0 0 14px;padding:10px 12px;border-radius:8px;font-size:13.5px;line-height:1.45;
  background:color-mix(in srgb,var(--warn) 11%,var(--panel));
  border:1px solid color-mix(in srgb,var(--warn) 34%,var(--line));color:var(--ink)}
.sh-warn-ico{margin-right:7px}

.sh-classsum{font-size:13.5px;color:var(--dim);margin:12px 0 0}
.sh-classsum b{color:var(--ink)}

/* term/week selector for the homework task being set */
.sh-weekpick{display:grid;grid-template-columns:minmax(180px,1fr) minmax(120px,160px) auto;gap:12px;align-items:end}
.sh-weekpick .field{margin:0}
.sh-weekpick select{width:100%;min-height:40px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink);font:inherit;padding:8px 10px}
.sh-weekpick select:focus{outline:2px solid var(--accent-soft);border-color:var(--accent)}
.sh-week-note{margin:0 0 4px;align-self:end}
.sh-week-note b{color:var(--accent);white-space:nowrap}
@media (max-width:720px){
  .sh-weekpick{grid-template-columns:1fr 1fr}
  .sh-week-note{grid-column:1 / -1}
}
@media (max-width:460px){.sh-weekpick{grid-template-columns:1fr}}

/* grouped class picker (sections by level) */
.sh-cgroups{display:flex;flex-direction:column;gap:14px}
.sh-cgroup{border:1px solid var(--line);border-radius:11px;background:var(--panel2);padding:10px 12px}
.sh-cgroup-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 9px}
.sh-cgroup-label{font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--dim)}
.sh-cgroup-label em{font-style:normal;font-weight:700;color:var(--accent);background:var(--accent-soft);border-radius:999px;padding:1px 8px;margin-left:4px;font-size:11px}
.sh-cgroup-all{border:1px solid var(--line);background:var(--panel);color:var(--dim);border-radius:7px;font:inherit;font-size:12px;font-weight:700;padding:4px 9px;cursor:pointer}
.sh-cgroup-all:hover{border-color:var(--accent);color:var(--accent)}
.sh-cgroup .qa-chipstrip{gap:7px}

/* "Homework you've set" list: compact, aligned summary rows. */
.sh-hwtable{width:100%;overflow-x:auto}
.sh-hwtable table{width:100%;table-layout:fixed;border-collapse:collapse}
.sh-hwtable th,.sh-hwtable td{
  padding:9px 12px;
  line-height:1.35;
  vertical-align:middle;
}
.sh-hwtable th{
  white-space:nowrap;
}
.sh-hwtable tbody td{
  font-size:13.5px;
  overflow-wrap:anywhere;
}
.sh-hwtable tbody tr.sh-mine-row{cursor:pointer}
.sh-hwtable tbody tr.sh-mine-row:hover,.sh-hwtable tbody tr.sh-mine-row:focus-visible{background:var(--accent-soft)}
.sh-notdone{opacity:.75}
.sh-hwtable th:nth-child(1),.sh-hwtable td:nth-child(1){width:46%}
.sh-hwtable th:nth-child(2),.sh-hwtable td:nth-child(2){width:24%}
.sh-hwtable th:nth-child(3),.sh-hwtable td:nth-child(3){width:14%;text-align:center}
.sh-hwtable th:nth-child(4),.sh-hwtable td:nth-child(4){width:16%;text-align:right;white-space:nowrap}
.sh-hwtable td:nth-child(3) .badge{justify-content:center}
@media (max-width:640px){
  .sh-hwtable th,.sh-hwtable td{padding:8px 10px}
  .sh-hwtable th:nth-child(1),.sh-hwtable td:nth-child(1){width:40%}
  .sh-hwtable th:nth-child(2),.sh-hwtable td:nth-child(2){width:24%}
  .sh-hwtable th:nth-child(3),.sh-hwtable td:nth-child(3){width:18%}
  .sh-hwtable th:nth-child(4),.sh-hwtable td:nth-child(4){width:18%}
}

.sh-submitbar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:12px 0 20px}
.btn.lg{font-size:15px;padding:12px 22px}
.sh-msg{font-size:13.5px;color:var(--dim)}
.sh-msg.warn{color:var(--err)}
.sh-msg.ok{color:var(--ok);font-weight:700}

/* css/homework.css */
/* Staff Homework overview — additive; reuses .qan-* (quiz-analytics.css) and the shared
   filter-chip primitives (.filters/.filterrow/.filterlabel/.chiprow/.chip) in app.css. */
.hw-spark{width:90px;height:24px;display:inline-block;vertical-align:middle}
.hw-openlink{white-space:nowrap;color:var(--accent);font-weight:600;cursor:pointer}
.hw-openlink:hover{text-decoration:underline}
.hw-stu-link{cursor:pointer}
.hw-stu-link:hover{text-decoration:underline}
/* The assistant's copy of the same cell: a name, not a link — the student record behind it is a
   staff page they cannot open, and a link that goes nowhere is worse than plain text. */
.hw-stu-name{font-weight:650}
/* Class column (only rendered in "All classes") — long names wrap instead of stretching the table. */
.hw-classcol{font-size:12.5px;color:var(--dim);max-width:220px;white-space:normal}

@media(max-width:640px){
  /* The table stays intact and scrolls inside .qan-tablewrap; just tighten what we can. */
  .hw-classcol{max-width:140px;font-size:11.5px}
  .hw-spark{width:64px}
}

/* css/whats-new.css */
/* whats-new.css — the "New in Portal this week" release-notes popup (js/whats-new.js).
   Its own accent rather than the theme's: the notes are a once-a-release announcement, and reading
   as a distinct card is the point. Everything else (panel, ink, line) rides the theme.

   The accent CHANGES EVERY RELEASE and belongs to the release, not to this stylesheet — a new set of
   notes reads as new at a glance instead of as the same card again. js/whats-new.js stamps
   --wn-accent/--wn-soft onto the card from the entry's `accent`; the values below are the green that
   every release up to 27 July shipped in, so an archived set previewed from Analytics still opens in
   its own colour rather than being retro-painted in this week's. */

.wn-bg{position:fixed;inset:0;background:#1b223066;display:grid;place-items:center;z-index:1200;padding:20px}
/* Losing the ball fades the whole popup out over half a second instead of cutting it (js/whats-new.js
   adds this, then removes the popup once the fade has run). The card and the dimmed backdrop go
   together — fading the card alone would leave the room dark around nothing. */
.wn-bg.wn-fade{opacity:0;pointer-events:none;transition:opacity .5s ease}
/* --wn-ink is what is written ON the accent, and --wn-mark is the accent used AS TEXT (the label
   and the chip numerals). They default to white and to the accent itself, so a release that names
   only a colour and a tint behaves exactly as before. They are separate because of yellow: a yellow
   pale enough to read as yellow cannot carry white text, and cannot be text on the panel either —
   it wants dark ink on the band and a darker gold wherever it becomes type. */
/* position:relative is for the pong bits below, which are absolutely placed against the card. */
.wn-card{background:var(--panel);color:var(--ink);border-radius:18px;box-shadow:0 30px 70px #00000059;
  width:100%;max-width:640px;max-height:88vh;overflow:auto;display:flex;flex-direction:column;position:relative;
  --wn-accent:#1e7a4d;--wn-soft:#e9f4ee;--wn-ink:#fff;--wn-mark:var(--wn-accent)}

/* A release with `game: true` turns Awesome into a pong bat on click (js/whats-new.js). The ball
   wears the week's accent and the two arrows the week's --wn-mark, so the game arrives in the same
   colour as the notes it interrupts rather than in a palette of its own. Both are pointer-events:
   none: the bat is the only thing anyone needs to hit, and a ball that could swallow a tap would
   take away the backdrop press that closes the notes. Nothing here renders unless the game starts,
   and prefers-reduced-motion never starts it. */
.wn-ball{position:absolute;width:15px;height:15px;border-radius:50%;background:var(--wn-accent);z-index:3;pointer-events:none}
/* Fixed-width, centred boxes (not shrink-to-fit): '‹' and '›' do not share an advance width in
   every font, so a box sized by the glyph makes one arrow sit closer to the bat than the other.
   A box of a known width lets the JS place both from the same gap and have them LOOK equidistant. */
.wn-bat-arrow{position:absolute;z-index:3;pointer-events:none;color:var(--wn-mark);font-size:38px;line-height:1;
  width:26px;text-align:center;animation:wn-bat-pulse 1.1s ease-in-out infinite}
@keyframes wn-bat-pulse{0%,100%{transform:scale(1);opacity:.45}50%{transform:scale(1.25);opacity:1}}

.wn-head{background:var(--wn-accent);color:var(--wn-ink);padding:22px 28px 26px;flex:none}
.wn-kicker{font-size:12px;letter-spacing:.14em;text-transform:uppercase;font-weight:600;opacity:.85}
.wn-head h2{margin:6px 0 0;font-size:24px;font-weight:700;line-height:1.2}

.wn-body{padding:24px 28px 10px}
.wn-label{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--wn-mark);font-weight:700;margin:0 0 12px}
.wn-label-dim{color:var(--dim)}
.wn-item{display:flex;gap:14px;margin-bottom:14px}
.wn-num{flex:none;width:28px;height:28px;border-radius:9px;background:var(--wn-soft);color:var(--wn-mark);
  font-weight:700;font-size:14px;display:grid;place-items:center}
.wn-item b{display:block;font-size:15px;margin-bottom:3px}
.wn-item p{margin:0;font-size:13.5px;line-height:1.5;color:var(--dim)}
.wn-body hr{border:0;border-top:1px solid var(--line);margin:18px 0}
.wn-body ul{margin:0;padding-left:18px}
.wn-body li{font-size:13.5px;line-height:1.7;color:var(--dim)}
.wn-body li b{color:var(--ink)}

.wn-foot{padding:16px 28px 22px;display:flex;justify-content:flex-end;border-top:1px solid var(--line);flex:none;position:relative}
/* The weekly-limit reply (js/whats-new.js blockedGame): a small bubble anchored above Awesome,
   in the release's own soft/mark tokens so it reads as part of THIS card, not a generic tooltip.
   pointer-events:none — the button underneath is still what's clickable, the bubble is a label. */
.wn-bubble{position:absolute;right:28px;bottom:64px;background:var(--wn-soft);color:var(--wn-mark);
  border:1px solid color-mix(in srgb, var(--wn-mark) 35%, transparent);border-radius:12px;
  padding:8px 14px;font-size:13px;font-weight:600;white-space:nowrap;pointer-events:none;
  box-shadow:0 8px 18px #0000001f;animation:wn-bubble-in .18s ease-out}
.wn-bubble::after{content:"";position:absolute;left:50%;bottom:-6px;width:12px;height:12px;
  transform:translateX(-50%) rotate(45deg);background:var(--wn-soft);
  border-right:1px solid color-mix(in srgb, var(--wn-mark) 35%, transparent);
  border-bottom:1px solid color-mix(in srgb, var(--wn-mark) 35%, transparent)}
@keyframes wn-bubble-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
@media (prefers-reduced-motion:reduce){.wn-bubble{animation:none}}
/* Awesome sits in the release's own accent. Hovering it goes to near-black with off-white type;
   where there is no pointer at all it carries the same travelling rainbow as the days-to-go
   countdown on the home page (.eng-big in js/views/student-home.js).

   Rainbow tile mechanics: the image is 200% of the button and carries the spectrum exactly twice
   (last stop at 50%), so one full red→violet sweep spans the pill whatever its width, and a
   percentage background-position resolves against (image width - box width) = one tile, so
   animating 0% → 100% lands on a frame identical to the first — no jump at the loop point. Two
   differences from the countdown, both forced by this being a solid button rather than text: the
   gradient paints the FILL (there are no glyphs to background-clip into), and the stops are darker
   than the countdown's — the yellow especially — because white text has to stay legible on every
   one of them.

   Where there is NO pointer — every student meeting these notes on a phone or iPad, which is most
   of them — the rainbow simply runs, because there is no hover to discover anything with and a
   treatment nobody can reach may as well not exist. Same reasoning covers staff previewing from
   Analytics on a tablet. It is deliberately not keyed to :focus-visible as on the countdown:
   render() calls .focus() on this button as the popup opens, so that would have it spinning before
   the notes have been read. Keyboard users get the ring below. */
.wn-btn{background:var(--wn-accent);color:var(--wn-ink);border:0;border-radius:10px;padding:11px 26px;
  font:inherit;font-size:14px;font-weight:600;cursor:pointer;transition:transform .12s ease,background-color .12s ease,color .12s ease;
  /* The shadow firms up WHITE on the spectrum, so it belongs to the state that switches the
     spectrum on, not to the button at rest: a release with dark ink (yellow) would wear it as a
     smudge under its own text. Same reason that state also forces the ink white — the spectrum
     stops below are chosen to carry white, whatever colour the week is. */
  background-size:200% 100%;background-repeat:repeat;
  --wn-spectrum:repeating-linear-gradient(90deg,
    hsl(0 85% 45%) 0%,hsl(26 90% 40%) 4.1667%,hsl(46 95% 32%) 8.3333%,hsl(90 70% 30%) 12.5%,
    hsl(132 70% 30%) 16.6667%,hsl(160 75% 29%) 20.8333%,hsl(184 85% 30%) 25%,hsl(205 90% 38%) 29.1667%,
    hsl(232 75% 50%) 33.3333%,hsl(263 70% 50%) 37.5%,hsl(292 70% 42%) 41.6667%,hsl(325 80% 44%) 45.8333%,
    hsl(360 85% 45%) 50%);
  --wn-glow:drop-shadow(0 0 7px #ff5aa08c) drop-shadow(0 0 14px #5a96ff59);
}
/* Hover goes to very dark grey with white type (James, 6 Aug 2026), not the rainbow. It reads as a
   deliberate press rather than a party trick, and it is the one treatment that lands the same way
   on every week's colour — the spectrum under a pale accent like this week's yellow looked like the
   button had broken. A NEUTRAL grey, not the sidebar's near-black navy: next to a warm accent the
   navy read as a colour of its own rather than as "dark". Off-white rather than #fff, house rule.
   The rainbow stays where there is NO pointer (below): a phone has no hover to discover, so that is
   the only flourish those students can reach. */
.wn-btn:hover{
  background-image:none;
  background-color:#2a2a2c;
  color:#fbfaf7;
  transform:translateY(-1px);
}
@media (hover:none){
  .wn-btn{
    background-image:var(--wn-spectrum);
    color:#fff;text-shadow:0 1px 2px #00000059;
    filter:var(--wn-glow);
    animation:wn-rainbow 1.8s linear infinite;
    will-change:background-position;
  }
}
.wn-btn:active{transform:translateY(0) scale(.98)}
/* The UA default ring is blue and clashes with whatever the week's accent is. --wn-mark rather than
   --wn-accent: the ring is drawn on the panel, where a pale accent would barely be a ring at all. */
.wn-btn:focus-visible{outline:2px solid var(--wn-mark);outline-offset:3px}
@keyframes wn-rainbow{
  from{background-position:0% 50%}
  to{background-position:100% 50%}
}
/* The bat's flinch when the ball lands on it is NOT here: it is an element.animate() call in
   js/whats-new.js. A CSS class could not do it without restarting the rainbow below — `animation`
   is one property, and the phone state runs wn-rainbow on this same element forever. */
/* Reduced motion: keep the rainbow, park the movement — same call as the countdown. Covers the
   pointerless always-on state too, which is the one that would otherwise animate unprompted. The
   flinch needs no rule here: the game never starts under reduced motion. */
@media (prefers-reduced-motion:reduce){
  .wn-btn,.wn-btn:hover{animation:none;transform:none;transition:none}
}

/* Analytics → What's new: green "Seen" against the amber .badge.warn "Not yet", so the two states
   are told apart by colour and not just by the word. */
.badge.wn-seen{background:#e9f4ee;color:#1e7a4d}

/* "View the popup" sits inline at the end of the intro hint, so it reads as part of the sentence
   that describes what the popup is. Nudged down a touch to sit on the 12px text's baseline. */
#wnPreview{margin-left:8px;vertical-align:middle;position:relative;top:-1px}

@media (max-width:600px){
  .wn-bg{padding:12px}
  .wn-head{padding:18px 20px 20px}
  .wn-head h2{font-size:21px}
  .wn-body{padding:20px 20px 8px}
  .wn-foot{padding:14px 20px 18px}
  /* NOT full width. A phone-wide bar was fine while Awesome only dismissed the notes, but it is
     also the pong bat on a release with `game: true`, and a bat as wide as the card cannot be
     missed — the ball would have nowhere to get past and the game could never end. Narrow enough
     to leave most of the card as playing field, still a comfortable thumb target at 96px. */
  .wn-btn{width:auto;min-width:96px;padding:11px 18px;text-align:center}
}

/* Analytics → Awesome button (js/views/pong-analytics.js): hour-of-day strip. 24 equal columns,
   bar heights relative to the busiest hour, so the shape of "when do people play" reads at a
   glance and the counts sit in the tooltip and under each bar. */
/* Twelve hours in view, twenty-four in the row: the shared bar-chart scroller (.kc-bars in
   css/app.css) sizes each hour off the visible width, so the bars stay legible on a phone instead
   of thinning to slivers. The axis rides INSIDE the scroller with the bars so its labels stay over
   the hours they name. */
/* All 24 hours fit a desktop panel; a phone gets twelve in view and scrolls for the rest. */
.pong-hours-scroll{--vis:24;--gap:3px;--bar-min:22px}
@media(max-width:700px){.pong-hours-scroll{--vis:12}}
.pong-hours-inner{display:flex;flex-direction:column;min-width:min-content}
.pong-hours{display:flex;align-items:end;gap:var(--gap);height:120px;margin-top:4px}
.pong-hour{display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch;height:100%;gap:4px}
.pong-hour-bar{height:var(--h);min-height:3px;border-radius:4px 4px 2px 2px;background:linear-gradient(180deg,var(--accent),color-mix(in srgb,var(--accent) 55%,transparent))}
.pong-hour.is-empty .pong-hour-bar{background:var(--line);height:3px;min-height:3px}
.pong-hour-n{font-size:10.5px;font-weight:700;color:var(--dim);text-align:center;min-height:13px}
.pong-hours-axis{display:flex;justify-content:space-between;margin-top:8px;font-size:11px;color:var(--dim)}
@media (max-width:600px){
  .pong-hours-scroll{--gap:2px}
  .pong-hours{height:92px}
  .pong-hour-n{display:none}
}

/* The "What's new" button on every home page (KC.whatsNew.btnHtml). Deliberately quiet — a plain
   pill in the panel colour, sitting on the greeting's own line rather than in a toolbar of its own.
   No icon and no dot: this is a door that is always there, not an alert, and the notes behind it
   announce themselves once on their own anyway.

   It does NOT take the release accent. The accent is how a NEW set of notes reads as new when it
   interrupts you; a button that repainted itself every Monday would just look unstable in the
   corner of a page nobody opened for the release notes. */
.wn-open{
  display:inline-flex;align-items:center;
  border:1px solid var(--line);background:var(--panel);color:var(--ink);
  font:inherit;font-size:13.5px;font-weight:700;
  padding:9px 15px;border-radius:999px;cursor:pointer;white-space:nowrap;
  box-shadow:var(--shadow);
  transition:border-color .15s,transform .15s;
}
.wn-open:hover{border-color:var(--accent);transform:translateY(-1px)}
.wn-open:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* css/sleep.css */
/* sleep.css — the after-hours "Portal is sleeping." screen (js/sleep.js).
   Additive only — do not edit css/app.css. Everything is namespaced "slp-".

   Deliberately theme-independent: this screen is always the night sky, whatever data-design the
   visitor has chosen, because it reads as the Portal itself being off rather than a page inside it. */

.slp{
  position:fixed;inset:0;z-index:9999;
  display:grid;place-items:center;padding:26px;
  font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  color:#e8ecf8;
  background:
    radial-gradient(1100px 620px at 78% -12%, #24306080 0%, transparent 62%),
    radial-gradient(820px 520px at -8% 108%, #1c2a5a80 0%, transparent 58%),
    linear-gradient(#0d1429, #0f1730 46%, #0b1124);
  -webkit-font-smoothing:antialiased;
  overflow:hidden;
}
/* the shell keeps running underneath (it may still be booting); freeze it out of reach */
body.slp-on{overflow:hidden}

/* ---- aurora: only during a dream window (see DREAMS in js/sleep.js) ----
   Three heavily blurred colour bodies swimming behind the card on long, mutually prime loops so the
   pattern never visibly repeats. mix-blend-mode:screen lightens the night sky rather than painting
   over it, so the screen stays dark whatever the bodies are doing. The whole layer's opacity is set
   inline per dream — later dreams are more vivid — and CSS fades it in and out over 24 s so a dream
   never snaps on. */
.slp-aurora{
  position:absolute;inset:-25%;pointer-events:none;
  filter:blur(46px);opacity:0;transition:opacity 24s linear;
}
.slp-aurora b{position:absolute;display:block;border-radius:50%;mix-blend-mode:screen}
.slp-aurora b:nth-child(1){
  width:62%;height:46%;left:4%;top:14%;
  background:radial-gradient(closest-side,#2fd0bd,transparent 72%);
  animation:slp-swim1 21s ease-in-out infinite;
}
.slp-aurora b:nth-child(2){
  width:56%;height:52%;right:2%;top:2%;
  background:radial-gradient(closest-side,#7d6bff,transparent 72%);
  animation:slp-swim2 27s ease-in-out infinite;
}
.slp-aurora b:nth-child(3){
  width:48%;height:38%;left:26%;bottom:2%;
  background:radial-gradient(closest-side,#c95bb4,transparent 74%);
  animation:slp-swim3 33s ease-in-out infinite;
}
@keyframes slp-swim1{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(14%,-9%) scale(1.22)}}
@keyframes slp-swim2{0%,100%{transform:translate(0,0) scale(1.1)}50%{transform:translate(-16%,12%) scale(.88)}}
@keyframes slp-swim3{0%,100%{transform:translate(0,0) scale(.95)}50%{transform:translate(10%,-14%) scale(1.2)}}

/* ---- star field: decorative only ---- */
.slp-sky{position:absolute;inset:0;pointer-events:none;overflow:hidden}
.slp-sky i{
  position:absolute;width:2px;height:2px;border-radius:50%;
  background:#cfd7f5;opacity:.5;
  animation:slp-twinkle 5.5s ease-in-out infinite;
}
.slp-sky i.b{width:3px;height:3px;opacity:.75}
@keyframes slp-twinkle{0%,100%{opacity:.18}50%{opacity:.85}}

/* ---- the card ---- */
.slp-card{
  position:relative;z-index:1;width:min(560px,100%);
  background:linear-gradient(#1b2547, #18224259);
  border:1px solid #2b3763;border-radius:22px;
  box-shadow:0 30px 80px #05091a80, inset 0 1px 0 #ffffff0f;
  padding:46px 42px 34px;text-align:center;
  backdrop-filter:blur(8px);
}

/* ---- ✱ badge, dimmed and slowly breathing ---- */
.slp-badge{
  width:74px;height:74px;margin:0 auto 22px;border-radius:20px;
  background:linear-gradient(150deg,#2f3a72,#222c5c);
  border:1px solid #3a4780;
  display:grid;place-items:center;
  box-shadow:0 10px 30px #060b1c66, inset 0 1px 0 #ffffff14;
  animation:slp-breathe 6s ease-in-out infinite;
}
.slp-badge span{font-size:40px;line-height:1;color:#b9b2ff;text-shadow:0 0 22px #8f86ff59}
@keyframes slp-breathe{0%,100%{transform:scale(1);opacity:.92}50%{transform:scale(1.045);opacity:1}}

/* ---- drifting z z z above the badge ---- */
.slp-z{position:absolute;top:32px;left:calc(50% + 26px);width:60px;height:60px;pointer-events:none}
.slp-z b{
  position:absolute;left:0;top:26px;font-size:13px;font-weight:700;color:#a9b2dd;opacity:0;
  animation:slp-drift 4.2s ease-in-out infinite;
}
.slp-z b:nth-child(2){animation-delay:1.4s;font-size:15px}
.slp-z b:nth-child(3){animation-delay:2.8s;font-size:17px}
@keyframes slp-drift{
  0%{opacity:0;transform:translate(0,10px) rotate(-8deg)}
  25%{opacity:.85}
  100%{opacity:0;transform:translate(24px,-30px) rotate(12deg)}
}

/* the z z z belong to plain sleep; a dreaming Portal drops them */
.slp-dreaming .slp-z{display:none}

@media (prefers-reduced-motion:reduce){
  .slp-badge,.slp-sky i,.slp-z b,.slp-aurora b,.slp-dream::before{animation:none}
  .slp-z b{opacity:.5}
  /* the aurora still shows — it is the state, not the decoration — it just holds still */
  .slp-aurora{transition:none}
}

.slp-card h1{margin:0 0 10px;font-size:27px;letter-spacing:-.015em;font-weight:700;color:#e8ecf8}
.slp-lead{margin:0 auto;max-width:40ch;color:#95a0c4;font-size:15px;line-height:1.6}

.slp-hours{
  margin:28px 0 0;padding:15px 18px;border-radius:14px;
  background:#131c3b;border:1px solid #2b3763;
  display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap;
}
.slp-hours .k{font-size:11.5px;letter-spacing:.09em;text-transform:uppercase;color:#95a0c4;font-weight:700}
.slp-hours .v{font-size:19px;font-weight:700;color:#cdc8ff}

/* The two ways out, stacked, and NOT of equal weight — at 2 am these are not equal choices.
   THE BYPASS IS THE BIG ONE. It was the other way round: the dream was a wide filled pill and the
   way into the Portal a thin outline under it, which offered a student at 2 am the entertainment
   first and the thing they actually came for second. The Portal is the primary action, full width
   and filled; the dream is the smaller one underneath, and earns its attention from its colour
   rather than its size. */
.slp-go{
  display:block;width:100%;margin:24px 0 0;padding:12px 20px;
  border-radius:999px;border:1px solid #3d4a80;
  background:linear-gradient(180deg,#243157,#1a2444);
  color:#e8ecf8;font:inherit;font-size:15px;font-weight:600;cursor:pointer;
  box-shadow:0 10px 28px rgba(6,10,28,.5), inset 0 1px 0 rgba(255,255,255,.06);
  transition:border-color .15s,background .15s,transform .15s;
}
.slp-go:hover{border-color:#5566ab;background:linear-gradient(180deg,#2a3a66,#1e2a4e);transform:translateY(-1px)}
.slp-go:focus-visible{outline:2px solid #8f86ff;outline-offset:2px}

/* Arrives a moment after the card, once the server has said whether there is a seat, so it fades
   in rather than appearing. */
@keyframes slp-dream-in{ from{ opacity:0; transform:translateY(4px) } to{ opacity:1; transform:none } }
.slp-dream.is-busy{ opacity:.6; pointer-events:none }
.slp-dream-gone{
  margin:14px auto 0;max-width:32ch;color:#95a0c4;font-size:13px;line-height:1.5;
  animation:slp-dream-in .4s ease-out both;
}

/* THE AURORA IS INSIDE THE BUTTON. Not a halo around it: a glow thrown outwards would smear into
   the card behind it and read as a focus ring or an error, and at this size it would be the
   loudest thing on a screen whose whole job is to be quiet. So the curtains live behind the label,
   clipped by the button's own radius (overflow:hidden), and the only thing outside the border is
   the border.

   `isolation:isolate` is what lets ::before sit at z-index -1: it makes the button its own
   stacking context, so the layer paints above the button's background and below its own text
   instead of disappearing behind the card. */
.slp-dream{
  position:relative;isolation:isolate;overflow:hidden;
  display:block;width:fit-content;margin:14px auto 0;padding:9px 18px;
  border-radius:999px;border:1px solid #3a3a7e;background:#151a3a;
  color:#e9e6ff;font:inherit;font-size:13.5px;font-weight:600;text-decoration:none;
  letter-spacing:.01em;
  animation:slp-dream-in .5s ease-out both;
  transition:border-color .15s,color .15s;
}
.slp-dream::before{
  content:'';position:absolute;z-index:-1;inset:-60% -20%;
  background:
    radial-gradient(46% 130% at 22% 60%, rgba(63,224,160,.46), transparent 70%),
    radial-gradient(40% 120% at 52% 40%, rgba(47,208,189,.38), transparent 72%),
    radial-gradient(44% 130% at 80% 62%, rgba(150,110,255,.44), transparent 70%);
  filter:blur(10px);
  animation:slp-dream-aurora 9s ease-in-out infinite alternate;
}
/* The curtains drift and lean, the way the ones on the card behind it do. Small numbers on
   purpose: this is a 13px button, and anything more reads as a loading state. */
@keyframes slp-dream-aurora{
  0%{transform:translateX(-6%) skewX(-6deg) scaleY(1)}
  100%{transform:translateX(6%) skewX(6deg) scaleY(1.12)}
}
.slp-dream:hover{border-color:#6f66d8;color:#f2f4ff}
.slp-dream:focus-visible{outline:2px solid #8f86ff;outline-offset:2px}

.slp-foot{
  margin:22px 0 0;padding-top:20px;border-top:1px solid #2b3763;
  color:#6f7aa3;font-size:11.5px;letter-spacing:.02em;
}

@media (max-width:520px){
  .slp-card{padding:36px 22px 26px}
  .slp-card h1{font-size:23px}
}

/* css/icq-papers.css */
/* ICQ past papers grid. Rows are years (newest first), columns are year levels.
   Every row keeps the same four columns even in 2024, where RACI split the paired
   divisions: those two files stack inside their shared cell rather than adding a column. */

.icq-grid {
  border: 1px solid var(--line, #d9d4c8);
  border-radius: 10px;
  overflow: hidden;
  background: var(--panel, #faf8f2);
}

.icq-row {
  display: grid;
  grid-template-columns: 92px repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--line, #d9d4c8);
}

.icq-row > * { background: var(--panel, #faf8f2); padding: 8px; }

.icq-head > * {
  background: var(--panel-2, #f2eee2);
  font-weight: 600;
  font-size: .86rem;
  text-align: center;
  padding: 10px 8px;
}

.icq-head .icq-year { text-align: left; font-weight: 500; opacity: .7; }

.icq-year { display: flex; flex-direction: column; justify-content: center; gap: 2px; }
.icq-yr { font-weight: 600; font-size: 1rem; }
.icq-brand { font-size: .72rem; opacity: .6; letter-spacing: .04em; }

.icq-cell { display: flex; flex-direction: column; gap: 4px; }
.icq-cell .btn { width: 100%; justify-content: center; text-align: center; }
.icq-none { opacity: .35; text-align: center; font-size: .85rem; padding: 4px 0; }

/* Solutions is the same green as the Practice materials solutions button, so the two
   pages read as one system. */
.icq-sol,
.icq-sol:link,
.icq-sol:visited {
  background: var(--ok-bg, #e6f4ec);
  border-color: var(--ok-line, #b7ddc7);
  color: var(--ok-ink, #1d6b45);
}
.icq-sol:hover { background: var(--ok-bg-hover, #d8ecdf); }

/* Hover colours per division, sampled straight off the 2025 RACI cover bands so the grid
   matches the paper a student is about to open: Years 7 & 8 cyan, Years 9 & 10 amber,
   Year 11 green, Year 12 lilac. Column order here must track DIVS in js/views/icq-papers.js,
   offset by one for the year cell. */
.icq-row > *:nth-child(2) .icq-q:hover { background: #96ccde; border-color: #6fb4cc; }
.icq-row > *:nth-child(3) .icq-q:hover { background: #fcde96; border-color: #e8c368; }
.icq-row > *:nth-child(4) .icq-q:hover { background: #a2d2a2; border-color: #79b879; }
.icq-row > *:nth-child(5) .icq-q:hover { background: #d2bad8; border-color: #b494be; }

.icq-q { transition: background-color .12s ease, border-color .12s ease; }
.icq-q:hover { color: #1f3864; }

/* Filter chips exist only for the phone layout, where the grid shows one division at a
   time. On anything wider the whole grid fits, so the strip would be dead weight. */
.icq-filters { display: none; }
.icq-filters .btn.on {
  background: var(--accent-bg, #e8eefb);
  border-color: var(--accent-line, #b9cbf0);
  color: var(--accent-ink, #1f3864);
}

@media (max-width: 640px) {
  .icq-filters { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
  .icq-row { grid-template-columns: 64px repeat(4, minmax(0, 1fr)); }
  .icq-head > * { font-size: .72rem; padding: 8px 4px; }
  .icq-row > * { padding: 5px 4px; }
  .icq-cell .btn { font-size: .72rem; padding: 4px 2px; }
  .icq-yr { font-size: .88rem; }
  .icq-brand { display: none; }

  /* A chosen division hides the other three columns, header cells included. */
  .icq-grid[data-only="7-8"] .icq-row > *:nth-child(n+3),
  .icq-grid[data-only="9-10"] .icq-row > *:nth-child(2),
  .icq-grid[data-only="9-10"] .icq-row > *:nth-child(n+4),
  .icq-grid[data-only="11"] .icq-row > *:nth-child(2),
  .icq-grid[data-only="11"] .icq-row > *:nth-child(3),
  .icq-grid[data-only="11"] .icq-row > *:nth-child(5),
  .icq-grid[data-only="12"] .icq-row > *:nth-child(n+2):nth-child(-n+4) { display: none; }

  .icq-grid[data-only="7-8"] .icq-row,
  .icq-grid[data-only="9-10"] .icq-row,
  .icq-grid[data-only="11"] .icq-row,
  .icq-grid[data-only="12"] .icq-row { grid-template-columns: 64px minmax(0, 1fr); }
}

/* css/competitions.css */
/* ==========================================================================
   competitions.css — the RACI Chemistry & Art page (view: comp-chem-art).
   Everything here is new; every other class the page uses is existing portal
   vocabulary (.topbar .panel .panel-pad .cards .stat .badge .btn), so the page
   inherits the theme for free.
   ========================================================================== */

/* ---- A Back button, when a page has one, ALWAYS sits on the left of the
   title. KC.head() renders `tools` right-aligned, so a Back button passed that
   way lands on the wrong side; .topbar-left holds it beside the h2 instead.
   This page is a top-level nav item and has no Back button, but the rule lives
   here for the next page in this family that does. */
.topbar-left{display:flex;align-items:center;gap:14px;min-width:0}
.topbar-left h2{margin:0}
.cmp-back{flex:0 0 auto}

/* ---- Status strip: where the competition is up to, right now. ---------- */
.cmp-status{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  border:1px solid var(--line);border-radius:12px;
  padding:12px 16px;margin:0 0 18px;font-size:14px;font-weight:600;
  background:var(--panel);color:var(--ink);
}
.cmp-status .cmp-dot{width:10px;height:10px;border-radius:999px;flex:0 0 auto}
.cmp-status small{font-weight:500;color:var(--dim)}
.cmp-status.is-open{background:color-mix(in srgb,var(--ok) 9%,var(--panel));border-color:color-mix(in srgb,var(--ok) 28%,var(--line));color:var(--ok)}
.cmp-status.is-open .cmp-dot{background:var(--ok)}
.cmp-status.is-urgent{background:color-mix(in srgb,var(--warn) 12%,var(--panel));border-color:color-mix(in srgb,var(--warn) 32%,var(--line));color:var(--warn)}
.cmp-status.is-urgent .cmp-dot{background:var(--warn)}

/* ---- Fact tiles. Same box as .stat, but the headline is words. --------- */
.cmp-facts .stat{display:flex;flex-direction:column;gap:4px}
.cmp-facts .cmp-k{font-size:12px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--dim)}
.cmp-facts .cmp-v{font-size:19px;font-weight:800;color:var(--accent);line-height:1.25}
.cmp-facts .cmp-sub{font-size:12.5px;color:var(--dim)}

/* ---- Days-to-go counter inside the "entries close" tile. ---------------
   Same treatment as the VCE exam countdown on the Home page (.eng-big in
   js/views/student-home.js): hover or focus the number and the digits fill
   with a rainbow that travels leftward, and a click makes it pulse. The
   gradient is painted into the glyphs with background-clip:text, so the glow
   must come from drop-shadow — text-shadow draws nothing once the text
   itself is transparent.

   The tile is sized to the NUMBER, not to an em length: the image is 200% of
   the element and carries the spectrum exactly twice (last stop at 50%), so
   one full red-to-violet sweep spans the digits whether it is 7, 54 or 107.
   With background-size:200%, animating 0% to 100% lands on a frame identical
   to the first, so the loop has no jump. */
.cmp-count{display:flex;align-items:baseline;gap:7px;margin-top:2px}
.cmp-days{
  font-size:34px;font-weight:800;line-height:1.05;color:var(--accent);
  font-variant-numeric:tabular-nums;cursor:default;display:inline-block;transform-origin:center bottom;
}
.cmp-days:hover,.cmp-days:focus-visible{
  background-image:repeating-linear-gradient(90deg,
    hsl(0 100% 50%) 0%,hsl(30 100% 48%) 4.1667%,hsl(55 100% 44%) 8.3333%,hsl(90 85% 40%) 12.5%,
    hsl(120 85% 38%) 16.6667%,hsl(150 85% 38%) 20.8333%,hsl(180 90% 40%) 25%,hsl(205 95% 47%) 29.1667%,
    hsl(235 90% 55%) 33.3333%,hsl(265 85% 55%) 37.5%,hsl(290 85% 50%) 41.6667%,hsl(325 95% 50%) 45.8333%,
    hsl(360 100% 50%) 50%);
  background-size:200% 100%;background-repeat:repeat;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  filter:drop-shadow(0 0 7px rgba(255,90,160,.55)) drop-shadow(0 0 14px rgba(90,150,255,.4));
  animation:cmp-rainbow 1.8s linear infinite;
  will-change:background-position;outline:none;
}
@keyframes cmp-rainbow{from{background-position:0% 50%}to{background-position:100% 50%}}
.cmp-days.is-pulsing{animation:cmp-pulse .42s cubic-bezier(.34,1.56,.64,1) 1}
.cmp-days.is-pulsing:hover,.cmp-days.is-pulsing:focus-visible{
  animation:cmp-pulse .42s cubic-bezier(.34,1.56,.64,1) 1,cmp-rainbow 1.8s linear infinite;
}
@keyframes cmp-pulse{0%{transform:scale(1)}40%{transform:scale(1.34)}100%{transform:scale(1)}}
.cmp-daysunit{font-size:13px;font-weight:700;color:var(--dim)}
/* Reduced motion: keep the rainbow, park the movement, drop the pulse. */
@media(prefers-reduced-motion:reduce){
  .cmp-days:hover,.cmp-days:focus-visible,
  .cmp-days.is-pulsing,.cmp-days.is-pulsing:hover{animation:none}
}

/* ---- The 3D gallery call to action. A full-width button rather than an
   embed: the Kunstmatrix walkthrough is heavy, wants the whole screen, and
   loading it inside a panel makes the page scroll badly on a phone. -------- */
.cmp-galbtn{
  display:flex;align-items:center;justify-content:center;gap:12px;
  width:100%;min-height:132px;padding:26px 24px;margin:0 0 18px;
  border:0;border-radius:14px;box-shadow:var(--shadow);
  background:linear-gradient(115deg,#0a3f8f 0%,#0a61c9 26%,#7c5cd6 55%,#c9457f 78%,#ffc928 100%);
  color:#fff;font:inherit;font-size:20px;font-weight:800;letter-spacing:.01em;
  text-align:center;text-decoration:none;cursor:pointer;
  text-shadow:0 1px 10px rgba(10,25,60,.4);
  background-size:180% 100%;background-position:0% 50%;
  transition:background-position .5s ease,transform .12s ease,box-shadow .2s ease;
}
.cmp-galbtn:hover,.cmp-galbtn:focus-visible{
  background-position:100% 50%;transform:translateY(-2px);
  box-shadow:0 18px 40px rgba(15,61,115,.28);color:#fff;outline:none;
}
.cmp-galbtn:active{transform:translateY(0)}
.cmp-galbtn .cmp-galsub{display:block;font-size:13.5px;font-weight:600;opacity:.9;margin-top:5px;letter-spacing:0}
.cmp-galbtn .cmp-galarrow{font-size:22px;opacity:.85}
@media(prefers-reduced-motion:reduce){.cmp-galbtn{transition:none}}
@media(max-width:560px){.cmp-galbtn{font-size:17px;min-height:112px}}

/* The closing call to action. Deliberately the SAME button as the 3D gallery
   above it, gradient and hover slide included, so the page closes on the
   treatment students already recognise as "the big thing to press". Only the
   size changes: taller and larger type, because it is the last thing on the
   page and the one thing we want pressed. Everything visual comes from
   .cmp-galbtn; do not give this one a palette of its own. */
.cmp-enterbtn{
  min-height:150px;font-size:24px;margin:22px 0 8px;
}
.cmp-enterbtn .cmp-galsub{font-size:14.5px;margin-top:7px}
@media(max-width:560px){.cmp-enterbtn{font-size:19px;min-height:126px}}

/* ---- Scoring: one stacked horizontal bar, a colour per criterion, with the
   criterion named inside its own segment. The four segment colours are set
   inline from the JS so the bar and its legend can never drift apart. ----- */
.cmp-stack{display:flex;width:100%;height:96px;border-radius:14px;overflow:hidden;border:1px solid var(--line)}
.cmp-stack > span{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  height:100%;padding:8px 10px;min-width:0;
  color:#fff;text-align:center;text-shadow:0 1px 3px rgba(0,0,0,.35);
}
.cmp-stack .cmp-seglabel{font-size:12px;font-weight:700;line-height:1.25;overflow-wrap:anywhere}
.cmp-stack .cmp-segpts{font-size:20px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}
/* Narrow screens cannot hold four labels side by side. Rather than dropping the
   wording, the bar turns on its side: flex still divides by points, so the
   segments stay proportional and every criterion keeps its name. */
@media(max-width:760px){
  .cmp-stack{flex-direction:column;height:300px}
  .cmp-stack > span{flex-direction:row;justify-content:space-between;gap:12px;padding:6px 14px;text-align:left}
  .cmp-stack .cmp-seglabel{font-size:13px}
  .cmp-stack .cmp-segpts{font-size:17px}
}
/* ---- Numbered steps. --------------------------------------------------- */
.cmp-steps{list-style:none;counter-reset:cmp;margin:0;padding:0;display:grid;gap:14px}
.cmp-steps li{counter-increment:cmp;display:grid;grid-template-columns:30px 1fr;gap:12px;align-items:start;font-size:14.5px;line-height:1.55}
.cmp-steps li::before{
  content:counter(cmp);
  display:grid;place-items:center;width:30px;height:30px;border-radius:999px;
  background:var(--accent-soft);color:var(--accent);font-weight:800;font-size:14px;
}
/* The ::before counter is grid child 1; label and detail both belong in column 2. */
.cmp-steps li > b,.cmp-steps li > span{grid-column:2}
.cmp-steps li > b{color:var(--ink)}
.cmp-steps li > span{color:var(--dim)}

/* ---- Tick / cross rules list. ------------------------------------------ */
.cmp-rules{list-style:none;margin:0;padding:0;display:grid;gap:10px;font-size:14.5px;line-height:1.5}
.cmp-rules li{display:grid;grid-template-columns:22px 1fr;gap:10px;align-items:start}
.cmp-rules .cmp-m{font-weight:800;line-height:1.5}
.cmp-rules .yes .cmp-m{color:var(--ok)}
.cmp-rules .no .cmp-m{color:var(--err)}

/* ---- Key-dates timeline. ----------------------------------------------- */
.cmp-timeline{list-style:none;margin:0;padding:0 0 0 6px;display:grid;gap:0}
.cmp-timeline li{display:grid;grid-template-columns:126px 22px 1fr;gap:10px;align-items:start;padding:11px 0;font-size:14.5px}
.cmp-timeline li + li{border-top:1px dashed var(--line)}
.cmp-timeline .cmp-d{font-weight:700;color:var(--ink)}
.cmp-timeline .cmp-rail{position:relative;justify-self:center;width:12px}
.cmp-timeline .cmp-rail::before{content:"";position:absolute;left:4px;top:-16px;bottom:-16px;width:2px;background:var(--line)}
.cmp-timeline li:first-child .cmp-rail::before{top:8px}
.cmp-timeline li:last-child .cmp-rail::before{bottom:calc(100% - 8px)}
.cmp-timeline .cmp-rail i{position:absolute;left:0;top:4px;width:10px;height:10px;border-radius:999px;background:var(--panel);border:2px solid var(--line)}
.cmp-timeline .on .cmp-rail i{background:var(--accent);border-color:var(--accent)}
.cmp-timeline .done .cmp-d,.cmp-timeline .done .cmp-t{color:var(--dim)}
.cmp-timeline .cmp-t{color:var(--dim)}
.cmp-timeline .on .cmp-t{color:var(--ink);font-weight:600}

/* ---- Artwork gallery. contain, not cover: these are artworks, and cropping
   one to fill a tile throws away the composition students are meant to be
   learning from. --------------------------------------------------------- */
.cmp-gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:14px}
.cmp-gallery figure{margin:0;display:flex;flex-direction:column;gap:8px}
.cmp-gallery img{
  width:100%;height:230px;object-fit:contain;display:block;
  border-radius:10px;border:1px solid var(--line);background:var(--panel2);padding:6px;
}
.cmp-gallery figcaption{font-size:12.5px;line-height:1.45;color:var(--dim)}
.cmp-gallery b{color:var(--ink)}

/* ---- Tips. ------------------------------------------------------------- */
.cmp-tips{display:grid;gap:14px}
.cmp-tips .cmp-tip{display:grid;grid-template-columns:26px 1fr;gap:12px;align-items:start;font-size:14.5px;line-height:1.55}
.cmp-tips .cmp-tipn{
  display:grid;place-items:center;width:26px;height:26px;border-radius:8px;
  background:var(--accent-soft);color:var(--accent);font-weight:800;font-size:13px;
}
.cmp-tips b{display:block;color:var(--ink)}
.cmp-tips span > span{color:var(--dim)}

.cmp-prose{font-size:14.5px;line-height:1.6;color:var(--ink)}
.cmp-prose p{margin:0 0 10px}
.cmp-prose p:last-child{margin-bottom:0}
.cmp-cap{font-size:12.5px;color:var(--dim);margin:10px 0 0}

/* css/science-week.css */
/* science-week.css — Science Week 2026 page (js/views/science-week.js). Green "Seeds of Science"
   accent, deliberately distinct from the art competition's blue so the two event pages read as
   siblings, not the same page twice. */

.sw-hero{
  display:flex;align-items:center;gap:14px;position:relative;overflow:hidden;
  background:linear-gradient(100deg,#155c39 0%,#1e7a4d 55%,#2e9862 100%);
  color:#f2faf5;border-radius:var(--radius);padding:12px 20px;box-shadow:var(--shadow);margin:14px 0 20px;
}
.sw-hero::after{content:"";position:absolute;right:-40px;top:-60px;width:220px;height:220px;border-radius:50%;background:rgba(255,255,255,.07)}
.sw-hero .sw-seed{font-size:24px}
.sw-hero b{font-size:16px;font-weight:800}
.sw-daybadge{
  margin-left:auto;white-space:nowrap;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.25);
  border-radius:999px;padding:6px 14px;font-size:12.5px;font-weight:800;letter-spacing:.3px;
}

/* Stacked, not side by side: the jar is the event, so it gets the full content width and most of
   the viewport height; the question card sits below it. */
.sw-grid{display:grid;grid-template-columns:1fr;gap:20px;align-items:start}

/* ---- bean jar ---- */
.sw-jarwrap{display:flex;gap:24px}
@media (max-width:760px){.sw-jarwrap{flex-direction:column}}
.sw-jarstage{
  flex:1;min-width:0;height:520px;position:relative;overflow:hidden;
  background:radial-gradient(ellipse at 50% 26%,#f5fcf7 0%,#d8ecdf 78%);
  border:1px solid #cfe7d9;border-radius:12px;cursor:grab;touch-action:none;
}
.sw-jarstage:active{cursor:grabbing}
.sw-jarstage canvas{display:block;width:100%;height:100%}
.sw-3d{position:absolute;top:10px;left:10px;background:var(--sidebar);color:#fff;font-size:10.5px;font-weight:800;letter-spacing:.6px;padding:4px 9px;border-radius:999px;pointer-events:none}
.sw-jarerr{position:absolute;inset:0;display:grid;place-items:center;font-size:14px;font-weight:700;color:var(--dim);padding:20px;text-align:center}
.sw-spinhint{position:absolute;bottom:9px;left:0;right:0;text-align:center;font-size:11.5px;color:#3f7d5c;font-weight:700;pointer-events:none}
.sw-jarfacts{flex:0 0 330px;min-width:0}
@media (max-width:760px){.sw-jarfacts{flex:1}}
.sw-jarfacts p{font-size:14.5px;line-height:1.5;margin:0}
.sw-dim{color:var(--dim);font-size:13px}
.sw-guessrow{display:flex;gap:10px;margin:14px 0 8px}
.sw-guessin{flex:1;min-width:0;border:2px solid var(--line);border-radius:10px;padding:10px 14px;font-size:16px;font-weight:700;background:var(--panel)}
.sw-guessin:focus{outline:none;border-color:#1e7a4d}
.sw-fine{font-size:12px;color:var(--dim);line-height:1.55}
.sw-statrow{display:flex;gap:10px;margin-top:14px}
.sw-ministat{flex:1;background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 12px;text-align:center}
.sw-ministat b{display:block;font-size:19px;color:#155c39}
.sw-ministat span{font-size:11px;color:var(--dim);font-weight:700}

.sw-loading{padding:40px 0;color:var(--dim);font-weight:700}

/* Staff-only guess roll. Name is scoped to this sheet only (see tools/check-table-classes.mjs). */
.sw-roll{width:100%;border-collapse:collapse;margin-top:12px;font-size:14px}
.sw-roll th,.sw-roll td{padding:8px 10px;border-bottom:1px solid var(--line);text-align:left}
.sw-roll th{font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:var(--dim)}
.sw-roll .num{text-align:right;font-variant-numeric:tabular-nums}
.sw-roll tr.lead td{background:#e6f4ec;font-weight:800;color:#155c39}

/* ---- home tile (student-home.js scienceWeekCard) ----
   Rides on .student-focus-card.artcomp for skeleton + hover; these rules give it the green
   identity and a pure-CSS seed scatter instead of a background photograph. */
.student-focus-card.sciweek .art-scrim{
  background:linear-gradient(100deg,rgba(10,36,22,.92) 0%,rgba(16,74,44,.82) 45%,rgba(30,122,77,.55) 100%);
}
.student-focus-card.sciweek .art-bg{
  background:
    radial-gradient(14px 10px at 82% 30%,#7cba5e 98%,transparent),
    radial-gradient(11px 8px at 90% 55%,#5da046 98%,transparent),
    radial-gradient(13px 9px at 74% 68%,#8fce6f 98%,transparent),
    radial-gradient(10px 7px at 86% 80%,#4f8f3a 98%,transparent),
    radial-gradient(12px 9px at 66% 42%,#6bab52 98%,transparent),
    radial-gradient(15px 11px at 94% 22%,#8fce6f 98%,transparent),
    radial-gradient(11px 8px at 60% 84%,#7cba5e 98%,transparent),
    linear-gradient(115deg,#0c2e1c 0%,#155c39 60%,#2e9862 100%);
}
.student-focus-card.sciweek .sw-tile-day{
  background:#ffc928;color:#0c315f;border-radius:999px;padding:2px 8px;
  font-size:10.5px;letter-spacing:.06em;font-weight:800;
}
.sw-locked{display:flex;flex-direction:column;gap:3px;background:var(--panel2);border:1.5px solid #1e7a4d;border-radius:10px;padding:12px 14px;margin:14px 0 8px;font-size:15px}
.sw-locked b{color:#155c39}
.sw-locked span{font-size:12.5px;color:var(--dim)}

/* ---- question of the day ---- */
.sw-daydots{display:flex;gap:7px;margin-bottom:14px;flex-wrap:wrap}
.sw-dot{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;font-size:11.5px;font-weight:800;background:var(--panel2);border:1.5px solid var(--line);color:var(--dim);cursor:pointer;font-family:inherit;padding:0}
.sw-dot.open{border-color:#1e7a4d;color:#155c39}
.sw-dot.right{background:#1e7a4d;border-color:#1e7a4d;color:#fff}
.sw-dot.wrong{background:#c23b45;border-color:#c23b45;color:#fff}
.sw-dot.sel{box-shadow:0 0 0 3px #cdeadb}
.sw-verdict{font-weight:800;font-size:14px;margin:12px 0 0}
.sw-verdict.ok{color:#155c39}
.sw-verdict.no{color:#c23b45}
.sw-opt.done{cursor:default}
.sw-opt.was-right{border-color:#1e7a4d;background:#e6f4ec}
.sw-opt.was-right .sw-letter{background:#1e7a4d;color:#fff}
.sw-opt.was-wrong{border-color:#c23b45;background:#fbeaec}
.sw-opt.was-wrong .sw-letter{background:#c23b45;color:#fff}
.sw-q{font-size:15px;line-height:1.5;margin:0 0 14px}
/* Sits in the Question of the Day panel header, where it names the sunrise the SELECTED day
   opened at (repaintQotd keeps it in step with the day dots). */
.sw-sunrise{display:inline-block;white-space:nowrap;background:#fff3cf;color:#8a6410;font-size:12px;font-weight:800;border-radius:999px;padding:4px 11px}
.sw-opt{
  display:flex;align-items:center;gap:12px;width:100%;text-align:left;cursor:pointer;
  border:2px solid var(--line);border-radius:10px;padding:11px 14px;margin-bottom:9px;
  font:inherit;font-size:14px;background:var(--panel);color:var(--ink);
}
.sw-opt:hover{border-color:#9cc9af}
.sw-letter{width:26px;height:26px;flex:0 0 26px;border-radius:50%;display:grid;place-items:center;font-weight:800;font-size:13px;background:var(--panel2);color:var(--dim)}
.sw-opt.sel{border-color:#1e7a4d;background:#e6f4ec}
.sw-opt.sel .sw-letter{background:#1e7a4d;color:#fff}
.sw-qfoot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:14px}
.sw-streak{font-size:12.5px;color:var(--dim);font-weight:700}

/* css/spectral.css */
/* Spectral ID — the Portal-side chrome only (the game has its own stylesheets under
   apps/spectral-id/). That is the Analytics tab's collapsed "hardest molecules" section; the
   headline cards and the leaderboard table reuse .pmu-card* and .qan-table* as-is. */
.sp-details{margin-top:18px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}
.sp-details>summary{padding:14px 20px;font-size:13.5px;font-weight:700;color:var(--ink);cursor:pointer;list-style:none}
.sp-details>summary::-webkit-details-marker{display:none}
.sp-details>summary::before{content:'▸';display:inline-block;margin-right:9px;color:var(--dim);transition:transform .15s}
.sp-details[open]>summary::before{transform:rotate(90deg)}
.sp-details>summary:hover{color:var(--accent)}
.sp-details[open]>summary{border-bottom:1px solid var(--line)}

/* css/midterm.css */
/* midterm.css — the Midterm tests section and the online sitting (js/views/student-midterm.js).
   The three tones are the SAME values the homework tile uses in student.css, because the two tiles
   sit side by side on Home and must read as one colour language, not two. */

/* ------------------------------------------------------------------ the hero */
/* One card per test: the tinted top, then that test's own sittings and breakdown inside the same
   border. The tint and the rounding live on the card, so the inner sections carry neither. */
.mt-hero{
  margin-bottom:18px;overflow:hidden;
  border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);
  background:var(--panel);
}
.mt-hero-top{display:flex;gap:20px;align-items:center;flex-wrap:wrap;padding:20px 22px}
.mt-hero.tone-green{background:#eaf8f0;border-color:#a6dcbf}
.mt-hero.tone-amber{background:#fff6e0;border-color:#f0d48a}
.mt-hero.tone-red{background:#fdeced;border-color:#f2b6bc}
/* No ring until there is a score to put in it, so the body takes the whole card rather than
   leaving a gap where a circle used to be. */
.mt-hero.no-score .mt-hero-top{gap:0}
/* The lower half sits on the panel colour, not the tint: a table of scores needs a plain ground to
   be read off, and the coloured band above it is what carries the state. */
.mt-more{background:var(--panel);border-top:1px solid var(--line)}
.mt-hero.tone-green .mt-more{border-top-color:#a6dcbf}
.mt-hero.tone-amber .mt-more{border-top-color:#f0d48a}
.mt-hero.tone-red .mt-more{border-top-color:#f2b6bc}
.mt-sec + .mt-sec{border-top:1px solid var(--line)}
.mt-sechead{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  padding:14px 18px 10px;
}
.mt-sechead h4{margin:0;font-size:13px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--dim)}
.mt-score{
  width:118px;height:118px;border-radius:50%;flex:none;
  display:grid;place-items:center;text-align:center;line-height:1;
  background:var(--panel);border:6px solid var(--line);
}
.mt-score.tone-green{border-color:#189b57}
.mt-score.tone-amber{border-color:#f4bb32}
.mt-score.tone-red{border-color:#d63e52}
.mt-score b{display:block;font-size:32px;font-variant-numeric:tabular-nums}
.mt-score span{display:block;margin-top:3px;font-size:12px;font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;color:var(--dim)}
.mt-hero-body{flex:1;min-width:250px}
.mt-hero-body h3{margin:0 0 6px;font-size:20px}
.mt-hero-body p{margin:0 0 12px;color:var(--dim);font-size:14px;max-width:62ch}
.mt-status{
  display:inline-flex;align-items:center;gap:7px;margin-bottom:10px;
  border-radius:999px;padding:4px 12px;
  font-size:11.5px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
}
.mt-status.tone-green{background:#189b57;color:#fff}
.mt-status.tone-amber{background:#f4bb32;color:#1f1500}
.mt-status.tone-red{background:#d63e52;color:#fff}
.mt-status.tone-grey{background:var(--panel2);color:var(--dim);border:1px solid var(--line)}
.mt-actions{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
/* The "your last attempt ran out of time" card — plain prose, then the way out. */
.mt-expired{padding:16px 18px;max-width:62ch}
.mt-expired p{margin:0 0 12px;line-height:1.55}
.mt-expired .mt-actions{margin-top:16px}
/* Amber is far lighter than the red and green, so it takes near-black ink — same reasoning (and
   the same value) as the amber homework tile in student.css. */
.btn.mt-amber{background:#f4bb32;border-color:#f4bb32;color:#1f1500}
.btn.mt-amber:hover{color:#1f1500;filter:brightness(1.05)}

/* ------------------------------------------------------------------- tables */
.mt-table{width:100%;border-collapse:collapse;font-size:14px}
.mt-table th,.mt-table td{text-align:left;padding:11px 18px;border-bottom:1px solid var(--line);vertical-align:top}
.mt-table th{font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim);font-weight:800}
.mt-table tbody tr:last-child td{border-bottom:0}
.mt-table td.num,.mt-table th.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.mt-pill{display:inline-block;border-radius:999px;padding:2px 10px;font-size:11.5px;font-weight:800;white-space:nowrap}
.mt-pill.ok{background:#dff3e7;color:#0f6b3d}
.mt-pill.part{background:#fff0cf;color:#7a5600}
/* Fourth band on the marking queue's score pill: above 50% but not yet 60%, between yellow and red. */
.mt-pill.near{background:#ffe2cc;color:#8a4108}
.mt-pill.no{background:#fbe1e4;color:#98202e}
.mt-empty{padding:22px 18px;color:var(--dim);font-size:14px}
.mt-dim{color:var(--dim);font-size:12.5px}
.mt-missed{margin:0;padding-left:18px}
.mt-missed li{margin:2px 0}

/* ---- "Where you lost marks": the student's own answer beside the mark scheme ----
   The same layout the marker sees on their side of the script, so a question can be talked about
   without either of them having to translate. The tint on the answer is the whole verdict: green
   when that box earned every mark, amber when it earned some, red when it earned none. */
.mt-fb-intro{padding:0 18px 12px;margin:0;line-height:1.55}
.mt-fbq{border-top:1px solid var(--line);padding:14px 18px}
.mt-fbq-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.mt-fbq-head h5{margin:0;font-size:14px;font-weight:800}
.mt-fbq-score{margin-left:auto;font-weight:800;font-size:13px;font-variant-numeric:tabular-nums;color:var(--dim)}

.mt-fb + .mt-fb{margin-top:12px;padding-top:12px;border-top:1px dashed var(--line)}
.mt-fb-head{display:flex;margin-bottom:6px}
.mt-fb-score{margin-left:auto;font-weight:800;font-size:12.5px;font-variant-numeric:tabular-nums}
.mt-fb-score.ok{color:#0f6b3d} .mt-fb-score.part{color:#7a5600} .mt-fb-score.no{color:#98202e}

.mt-fb-cols{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.mt-fb-cols h5{margin:0 0 5px;font-size:11px;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;color:var(--dim)}
.mt-fb-ans,.mt-fb-model{border-radius:10px;padding:10px 12px;font-size:14.5px;line-height:1.6;
  border:1px solid var(--line)}
.mt-fb-ans{white-space:pre-wrap}
.mt-fb-ans.ok{background:#eef7f1;border-color:#cfe6d8}
.mt-fb-ans.part{background:#fdf6e7;border-color:#eddcb8}
.mt-fb-ans.no{background:#fbecea;border-color:#f0cdc8}
.mt-fb-ans.is-blank{color:var(--dim);font-style:italic}
.mt-fb-model{background:var(--panel2)}
.mt-fb-model p{margin:0 0 6px} .mt-fb-model p:last-child{margin:0}

.mt-fb-note{margin-top:10px;padding:10px 12px;border-radius:10px;
  background:#eef3fb;border:1px solid #d3e0f2;font-size:14px;line-height:1.6}
.mt-fb-note b{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#3a5a8c;margin-right:7px}

@media (max-width:640px){ .mt-fb-cols{grid-template-columns:1fr} }

/* ------------------------------------------------------------------ the runner */
.mt-run-top{
  position:sticky;top:0;z-index:20;
  display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:13px 18px;margin-bottom:14px;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--panel);box-shadow:var(--shadow);
}
.mt-run-title{font-weight:800}
.mt-run-title small{display:block;font-weight:600;color:var(--dim);font-size:12.5px}
.mt-run-timer{display:flex;align-items:center;gap:10px}
.mt-clock{
  font-size:26px;font-weight:800;font-variant-numeric:tabular-nums;
  padding:6px 14px;border-radius:10px;background:var(--panel2);border:1px solid var(--line);
}
.mt-clock.warn{background:#fff6e0;border-color:#f0d48a;color:#7a5600}
.mt-clock.danger{background:#fdeced;border-color:#f2b6bc;color:#98202e;animation:mt-pulse 1s ease-in-out infinite}
@keyframes mt-pulse{50%{opacity:.55}}
@media (prefers-reduced-motion:reduce){.mt-clock.danger{animation:none}}
/* The countdown beside "Resume the test" on the hero. The same instrument as the runner's clock —
   it reuses .mt-clock so the amber and red tones and the pulse are shared, and the two read as one
   thing — but sized to sit on a button's line rather than to be read across a room. MUST stay after
   .mt-clock: same specificity, so the later rule is the one that wins. */
.mt-hero-clock{
  font-size:19px;padding:7px 12px;background:var(--panel);
  min-width:92px;text-align:center;
}
.mt-instruction{margin:0 0 18px;color:var(--dim);font-size:13.5px}
.mt-section{margin:22px 0 12px;font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}

.mt-q{
  padding:20px 22px;margin-bottom:14px;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--panel);box-shadow:var(--shadow);
}
.mt-qhead{
  display:flex;justify-content:space-between;gap:12px;margin-bottom:10px;
  font-size:11.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--dim);
}
.mt-stem{font-size:16px;margin-bottom:14px}
/* A printed figure, inlined into the stem. It sits ABOVE the words it belongs to, exactly as it does
   on the paper, and is capped so a wide diagram cannot push the answer boxes off a phone. The white
   plate is deliberate in both themes: these are black-on-white line drawings, and inverting the page
   around them would leave the lines invisible. */
.mt-fig{margin:0 0 12px;text-align:center}
.mt-fig img{max-width:min(100%,460px);height:auto;background:#fff;border:1px solid var(--line);
  border-radius:10px;padding:8px}
.mt-part{margin-top:16px;padding-top:14px;border-top:1px dashed var(--line)}
.mt-parthead{display:flex;gap:8px;align-items:baseline;margin-bottom:10px;font-size:15px}
.mt-partmarks{margin-left:auto;white-space:nowrap;font-size:11.5px;font-weight:800;
  letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}

.mt-field{display:block;margin-bottom:12px}
.mt-flabel{display:block;margin-bottom:5px;font-size:13.5px;font-weight:700}
/* A short numeric answer gets a short box: a full-width input invites an essay where one number
   is wanted, and the unit needs to sit right beside the figure to be read as its unit. */
.mt-field.is-inline{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.mt-field.is-inline .mt-flabel{margin:0;min-width:140px}
.mt-inputwrap{display:inline-flex;align-items:center;gap:8px;flex:1;min-width:0}
.mt-field.is-number .mt-inputwrap{flex:none}
.mt-field.is-number .mt-input{width:150px}
/* A one-word classification ("displacement", "endothermic"): wider than a number, nowhere near a
   sentence. The width is the hint about how much is wanted. */
.mt-field.is-short .mt-inputwrap{flex:none}
.mt-field.is-short .mt-input{width:240px}

/* ------------------------------------------------- balancing: a box per coefficient
   The equation is printed and the student fills the coefficients, the way it is done on paper.
   Boxes are square and digit-sized so the row reads as an equation, not as a form. */
.mt-field-block{margin-bottom:14px}
.mt-field.is-coeffs{
  display:flex;align-items:center;flex-wrap:wrap;gap:4px 8px;
  padding:10px 0;font-size:17px;
}
.mt-term{display:inline-flex;align-items:center;gap:6px}
.mt-coef{
  width:44px;height:40px;padding:0;text-align:center;
  font:inherit;font-weight:700;font-variant-numeric:tabular-nums;
  color:var(--ink);background:var(--panel2);
  border:1px solid var(--line);border-radius:8px;
}
.mt-coef:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
.mt-formula{white-space:nowrap}
.mt-op,.mt-arrow{color:var(--dim);padding:0 2px}
.mt-arrow{font-size:19px;padding:0 8px}
/* Screen-reader wording for the arrow, which is decorative to a sighted reader and meaningless
   to a screen reader as a bare glyph. */
.mt-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ------------------------------------------------------------- options, as chips
   Used ONLY where the printed paper itself prints options. Every choice is visible at once and one
   tap answers it: no menu to open, and nothing hidden behind a click. */
.mt-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:2px}
.mt-chip{
  display:inline-flex;align-items:center;gap:9px;
  padding:9px 15px 9px 11px;border-radius:999px;
  border:1px solid var(--line);background:var(--panel2);color:var(--ink);
  font:inherit;font-size:14.5px;cursor:pointer;
}
.mt-chip b{
  display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;
  background:var(--panel);border:1px solid var(--line);font-size:12px;color:var(--dim);
}
.mt-chip:hover{border-color:var(--accent);color:var(--accent)}
.mt-chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.mt-chip.on{background:var(--accent);border-color:var(--accent);color:#fff}
.mt-chip.on b{background:rgba(255,255,255,.22);border-color:transparent;color:#fff}
.mt-funit{color:var(--dim);font-size:13.5px;white-space:nowrap}
.mt-input{
  font:inherit;color:var(--ink);background:var(--panel2);
  border:1px solid var(--line);border-radius:10px;padding:10px 13px;
}
.mt-input:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
.mt-text{width:100%;resize:vertical;min-height:74px}
.mt-field.is-choice select.mt-input{min-width:230px}
.mt-field.is-choice{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.mt-field.is-choice .mt-flabel{margin:0;min-width:220px;font-weight:600}

.mt-run-foot{
  display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;
  margin:18px 0 40px;
}

@media (max-width:640px){
  .mt-score{width:92px;height:92px;border-width:5px}
  .mt-score b{font-size:26px}
  .mt-field.is-inline .mt-flabel,.mt-field.is-choice .mt-flabel{min-width:0}
  .mt-field.is-number .mt-input{width:120px}
  .mt-run-top{position:static}
}

/* ------------------------------------------------- staff view (admins and teachers)
   Same route as the student page, different screen: every test, which class sits it when,
   and how those classes went.

   Deliberately almost no CSS. app.css already gives th/td the 18px gutter that lines a table
   up with .panel-head, so the ONLY things worth saying here are the ones the base cannot know:
   which columns are numeric. Overriding the padding is what threw the whole card out of
   alignment the first time. */
.mt-staff-sum{font-size:13px;color:var(--dim)}
/* Columns 2 to 5 shrink to their content so the numbers group at the right instead of drifting
   apart across a wide table; the class name takes the slack. */
table.mt-staff th:nth-child(n+2),table.mt-staff td:nth-child(n+2){width:1%;white-space:nowrap}
table.mt-staff th:nth-child(n+3),table.mt-staff td:nth-child(n+3){
  text-align:right;font-variant-numeric:tabular-nums;
}
.mt-sub{color:var(--dim);font-size:12.5px}
/* Every class row opens its own students underneath it. */
.mt-staff-row{cursor:pointer}
.mt-caret{display:inline-block;width:14px;color:var(--dim);font-weight:800;
  transition:transform .12s ease}
.mt-staff-row[aria-expanded="true"] .mt-caret{transform:rotate(90deg)}
@media (prefers-reduced-motion:reduce){.mt-caret{transition:none}}
/* The per-student list. Indented under its class and on the panel's second tone, so a long test
   with several classes still reads as a list of classes rather than one run-on table. */
.mt-staff-detail>td{padding:0 0 0 22px;background:var(--panel2)}
table.mt-stu{width:100%;border-collapse:collapse;font-size:13.5px}
table.mt-stu th{font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim);
  text-align:left;padding:8px 10px}
table.mt-stu td{padding:7px 10px;border-top:1px solid var(--line)}
table.mt-stu th:nth-child(n+2),table.mt-stu td:nth-child(n+2){width:1%;white-space:nowrap}
table.mt-stu th:nth-child(2),table.mt-stu td:nth-child(2),
table.mt-stu th:nth-child(3),table.mt-stu td:nth-child(3){
  text-align:right;font-variant-numeric:tabular-nums;
}
.mt-stu tr.is-notsat .nm{color:var(--dim)}
/* An online sitting opens its script; a paper mark has nothing behind it, so only the rows that
   lead somewhere take the pointer. */
.mt-stu tr.is-open{cursor:pointer}
.mt-stu tr.is-open:hover{background:var(--panel)}
.mt-stu tr.is-open:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.mt-stu tr.is-open .nm::after{content:' \203A';color:var(--dim);font-weight:800}
.mt-stu-pct{font-weight:750}
.mt-stu-pct.high{color:var(--ok)}
.mt-stu-pct.mid{color:var(--warn)}
.mt-stu-pct.warn{color:color-mix(in srgb,var(--warn) 60%,var(--err))}
.mt-stu-pct.low{color:var(--err)}
.mt-stu-pct.muted{color:var(--dim);font-weight:600}
/* What the integrity counters recorded. Stated flatly, never as a verdict. */
.mt-stu-flag{color:var(--dim);font-size:12.5px}
/* The paper marks are typed in the Score column, so the foot is a caption plus the way through to
   the class page, not a button on its own line. It wraps to two lines on a narrow screen. */
.mt-stu-foot{padding:10px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.mt-stu-foot .hint{margin:0;flex:1 1 260px}
/* The mark box sits in a right-aligned, tabular column; the cell keeps the box and its "/ 20"
   together on the right edge so the column of boxes lines up under the heading. */
.mt-stu .cls-tt-cell{justify-content:flex-end}
.mt-stu .cls-tt-input{width:64px}
.mt-stu-empty{padding:12px 10px;color:var(--dim);font-size:13.5px}
.mt-staff-row:hover{background:var(--panel2)}
.mt-staff-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* The header chips are one flex item, not four: .panel-head is space-between. */
.mt-staff-meta{display:inline-flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
/* The 3-minute and 30-second reminders: one plain sentence, nothing to read twice. */
.mt-remind{margin:0;font-size:15px;line-height:1.55}

/* --------------------------------------------------- the integrity warning dialog
   Shown on EVERY tab switch and every copy or paste during a test (student-midterm.js). It is the
   one dialog in Portal that is meant to be uncomfortable, so it takes the red from the "resit" tone
   rather than the neutral panel: a red head, a red rule down the side of the message, and a red
   button. Scoped through :has() so only this dialog is dressed and every other modal is untouched. */
.modal:has(.mt-warn) .modal-head{background:#fdeced;border-bottom-color:#f2b6bc}
.modal:has(.mt-warn) .modal-head h3{color:#98202e}
.mt-warn{display:flex;gap:14px;align-items:flex-start}
.mt-warn-icon{
  flex:none;width:34px;height:34px;border-radius:50%;
  display:grid;place-items:center;
  background:#d63e52;color:#fff;font-weight:900;font-size:20px;line-height:1;
}
.mt-warn-copy{flex:1;border-left:3px solid #f2b6bc;padding-left:14px}
.mt-warn-copy p{margin:0 0 10px;font-size:14px;line-height:1.55}
.mt-warn-copy p:last-child{margin-bottom:0}
.mt-warn-lead{font-weight:800;font-size:15.5px;color:#98202e}
/* The tally. Stated plainly, because "the 3rd time" is the part that lands. */
.mt-warn-count{font-weight:700}
.mt-warn-note{color:var(--dim);font-size:13px}
/* "I understand" fills with the icon's red on hover, so the button the student has to press is the
   same red as the mark that told them off. Scoped to this dialog; .btn.danger elsewhere is untouched. */
.modal:has(.mt-warn) .modal-foot .btn.danger:hover,
.modal:has(.mt-warn) .modal-foot .btn.danger:focus-visible{
  background:#d63e52;border-color:#d63e52;color:#fff;
}

/* css/marking.css */
/* css/marking.css — the marking queue and one student's script (js/views/marking.js).
   This page had no stylesheet at all until now: it was running on .panel and .table alone, which is
   why its rows did not react to a pointer and why a marking point sat flat against its neighbour.

   The colour language is the one the rest of Portal already uses for a mark: green when every mark
   was awarded, amber when some were, red when none were. It is applied at three sizes — the mark,
   the answer box, the question — so a script can be read at a glance and then in detail. */

/* ------------------------------------------------------------------ the queue */
.mk-table{width:100%}
.mk-sub{color:var(--dim);font-size:12.5px}
/* Every row opens that script. It has been clickable since the page shipped; nothing said so. */
.mk-row{cursor:pointer;transition:background .12s ease}
.mk-row:hover{background:var(--panel2)}
.mk-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.mk-row.is-marked .nm{color:var(--dim)}
@media (prefers-reduced-motion:reduce){.mk-row{transition:none}}
.mk-state{font-weight:750;font-size:12.5px}
.mk-state.todo{color:var(--warn)}
.mk-state.done{color:var(--ok)}

/* ---- marked history: ten at a time, filtered from the panel head ----
   The body holds a table AND a footer, so it does not get .panel-body-table's scroller for free —
   the table gets its own, and the footer keeps the panel's own gutter. */
.mk-search{min-width:200px;max-width:280px}
.mk-hist-table{overflow-x:auto}
.mk-more{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:12px 16px;border-top:1px solid var(--line);
}
@media (max-width:640px){
  .panel-head:has(.mk-search){flex-wrap:wrap}
  .mk-search{max-width:none;width:100%}
}

/* --------------------------------------------------------------- script header */
.mk-headrow{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.mk-headrow h2{margin:0;font-size:19px}
.mk-headrow .sub{color:var(--dim);font-size:13px;margin-top:2px}
.mk-headscore{margin-left:auto;text-align:right}
.mk-total{font-size:19px;font-weight:800}
.mk-total.ok{color:var(--ok)}
.mk-total.part{color:var(--warn)}
.mk-total.near{color:color-mix(in srgb,var(--warn) 60%,var(--err))}
.mk-total.no{color:var(--err)}

/* What the integrity counters recorded. One sentence, never a verdict. */
.mk-integrity{
  margin:14px 0 0;padding:11px 14px;border-radius:10px;
  background:#fef3e2;border:1px solid #f6d8a8;color:#7a4b06;
  font-size:13.5px;line-height:1.55;
}
.mk-integrity.is-clean{background:var(--panel2);border-color:var(--line);color:var(--dim)}
.mk-judged-note{margin:12px 0 0;color:var(--dim);font-size:13.5px;line-height:1.55}

/* ------------------------------------------------------------------- one question
   The stem is the paper's own words, so it is set like question text rather than like UI copy. */
.mk-stem{
  padding:14px 16px;font-size:15px;line-height:1.6;
  border-bottom:1px solid var(--line);
}
.mk-q-marks{font-weight:800;font-variant-numeric:tabular-nums;font-size:13.5px}
.mk-q-marks.full{color:var(--ok)}
.mk-q-marks.part{color:var(--warn)}
.mk-q-marks.none{color:var(--err)}

/* ---- one answer box: its label, what the student wrote, and the marks hanging off it ---- */
.mk-field{padding:14px 16px;border-bottom:1px solid var(--line)}
.mk-field:last-child{border-bottom:0}
.mk-field-head{display:flex;align-items:baseline;gap:10px;margin-bottom:7px}
.mk-field-label{font-weight:750;font-size:13px;color:var(--dim)}
.mk-field-score{margin-left:auto;font-weight:800;font-size:13px;font-variant-numeric:tabular-nums}
.mk-field-score.full{color:var(--ok)}
.mk-field-score.part{color:var(--warn)}
.mk-field-score.none{color:var(--err)}
/* The student's own words. Monospaced-ish spacing and a quoted left rule so it never reads as if
   Portal wrote it, and pre-wrap because a working-out box is written across several lines. */
.mk-answer{
  padding:10px 14px;border-left:3px solid var(--accent);border-radius:0 8px 8px 0;
  background:var(--panel2);white-space:pre-wrap;font-size:14.5px;line-height:1.6;
}
.mk-answer.is-blank{color:var(--dim);font-style:italic;border-left-color:var(--line)}
.mk-unit{color:var(--dim)}

/* ---- one marking point ---- */
.mk-list{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:7px}
.mk-mark{
  display:flex;gap:10px;align-items:flex-start;
  padding:9px 12px;border-radius:9px;border:1px solid var(--line);background:var(--panel);
}
/* Awarded or withheld, at a glance. The tint is deliberately light: the tick is the statement, the
   background only groups it. */
.mk-mark.on{background:color-mix(in srgb,var(--ok) 8%,var(--panel));border-color:color-mix(in srgb,var(--ok) 30%,var(--line))}
.mk-mark.off{background:color-mix(in srgb,var(--err) 6%,var(--panel));border-color:color-mix(in srgb,var(--err) 25%,var(--line))}
.mk-mark.judged{box-shadow:inset 3px 0 0 var(--warn)}
.mk-tick{
  flex:none;width:26px;height:26px;border-radius:7px;cursor:pointer;
  border:1px solid var(--line);background:var(--panel2);color:var(--dim);
  font-size:14px;font-weight:800;line-height:1;display:grid;place-items:center;
}
.mk-mark.on .mk-tick{background:var(--ok);border-color:var(--ok);color:#fff}
.mk-mark.off .mk-tick{background:var(--panel2);border-color:color-mix(in srgb,var(--err) 40%,var(--line));color:var(--err)}
.mk-tick:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.mk-mark-body{flex:1;min-width:0}
.mk-mark-head{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
.mk-point{flex:1;min-width:0;font-size:14px;line-height:1.55}
.mk-worth{color:var(--dim);font-size:12.5px;white-space:nowrap}

/* ---- a comment on one marking point (migration 0216) ----
   Quiet until it is wanted: an icon on the mark's own line, and a box that opens under it. What is
   typed here reaches the student on their feedback sheet, so it is written where the marker can see
   the marking point and what the student wrote at the same time.

   A squircle, the same 26px shape and radius as the tick to its left, so the two read as one pair
   of controls rather than a control and a form button. It fills in once there is a comment. */
.mk-cbtn{
  flex:none;width:26px;height:26px;border-radius:8px;cursor:pointer;padding:0;
  border:1px solid var(--line);background:var(--panel2);color:var(--dim);
  display:grid;place-items:center;transition:background .12s ease,color .12s ease,border-color .12s ease;
}
.mk-cbtn svg{width:15px;height:15px;display:block}
.mk-cbtn:hover{border-color:var(--accent);color:var(--accent)}
.mk-cbtn.has{background:var(--accent);border-color:var(--accent);color:#fff}
.mk-cbtn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.mk-cbtn{transition:none}}
.mk-cbox{margin-top:8px}
.mk-ctext{
  width:100%;resize:vertical;font:inherit;font-size:13.5px;line-height:1.55;
  padding:9px 11px;border:1px solid var(--line);border-radius:9px;background:var(--panel2);color:inherit;
}
.mk-ctext:focus{outline:2px solid var(--accent);outline-offset:-1px;border-color:var(--accent)}

/* ---------------------------------------------------------------------- the foot */
.mk-foot{margin-top:18px;display:flex;flex-direction:column;gap:10px;align-items:flex-start}
.mk-foot .hint{margin:0;color:var(--dim);font-size:13px;line-height:1.55;max-width:60ch}

@media (max-width:700px){
  .mk-headscore{margin-left:0;text-align:left;width:100%}
  .mk-field,.mk-stem{padding-left:12px;padding-right:12px}
}

/* css/titration.css */
/* ==========================================================================
   titration.css — the Titration Challenge student page (view: comp-titration)
   and the staff awarding page (view: comp-titration-teacher).

   Every other class these pages use is existing portal vocabulary
   (.topbar .panel .panel-pad .cards .stat .badge .btn .chip .field .hint), so
   both inherit every theme for free.
   ========================================================================== */

/* ---- 1. Hero: image band on the left, lesson clock on the right ----------
   One card, two cells. The clock lives here rather than in a panel further
   down because it is the thing a bench looks up at from a stool — it has to
   be above the fold, next to the title, not scrolled to.

   Below 860px the clock drops under the image and goes horizontal, so the
   ring and the time sit side by side instead of eating a phone screen. */
.tc-hero{
  display:grid;grid-template-columns:minmax(0,1fr) 268px;
  border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;
  box-shadow:var(--shadow);margin:0 0 18px;background:var(--panel);
}
.tc-heroimg{
  position:relative;isolation:isolate;min-height:236px;
  display:flex;align-items:flex-end;background:#0d1b2e;
}
.tc-hero-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
.tc-heroimg::after{
  content:'';position:absolute;inset:0;z-index:1;
  background:linear-gradient(100deg,rgba(9,18,32,.93) 0%,rgba(9,18,32,.78) 42%,rgba(9,18,32,.18) 78%,rgba(9,18,32,.05) 100%);
}
.tc-hero-body{position:relative;z-index:2;padding:26px 28px;max-width:620px;color:#f4f7fb}
.tc-kicker{font-size:12px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#ffd9ea;margin:0 0 8px}
.tc-heroimg h1{font-size:31px;line-height:1.12;margin:0;color:#fdfdfd;letter-spacing:-.01em}

/* ---- 2. The clock tile ---------------------------------------------------
   A depleting ring. The circle is drawn from 12 o'clock (rotated -90°) and
   the arc is shortened by raising stroke-dashoffset, so the ring empties
   clockwise as the lesson runs down. The dash geometry is set once in the
   markup from the real circumference — no magic numbers here. */
.tc-clocktile{
  border-left:1px solid var(--line);background:var(--panel2);
  padding:18px 16px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;
}
.tc-ringwrap{position:relative;width:150px;height:150px;flex:0 0 auto}
.tc-ring{width:100%;height:100%;transform:rotate(-90deg)}
.tc-ring .tc-track{fill:none;stroke:var(--line);stroke-width:11}
.tc-ring .tc-arc{
  fill:none;stroke:var(--accent);stroke-width:11;stroke-linecap:round;
  transition:stroke-dashoffset 1s linear,stroke .3s ease;
}
.tc-ring.is-warn .tc-arc{stroke:var(--warn)}
.tc-ring.is-over .tc-arc{stroke:var(--err)}
.tc-ringmid{
  position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
}
.tc-time{font-variant-numeric:tabular-nums;font-size:31px;font-weight:800;line-height:1;color:var(--accent);letter-spacing:-.02em}
.tc-time.is-warn{color:var(--warn)}
.tc-time.is-over{color:var(--err)}
.tc-timelabel{font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}
.tc-phasenow{font-size:12.5px;font-weight:700;color:var(--ink);text-align:center;line-height:1.35;min-height:34px}
.tc-phasenow small{display:block;font-weight:500;color:var(--dim);font-size:11.5px}
/* No controls here by design — this clock is read-only for a bench. The line
   below it says why, so nobody hunts for a Start button. */
.tc-clocksync{
  font-size:11.5px;font-weight:700;color:var(--dim);text-align:center;
  border-top:1px solid var(--line);padding-top:9px;width:100%;
}

@media(max-width:860px){
  .tc-hero{grid-template-columns:1fr}
  .tc-clocktile{
    border-left:0;border-top:1px solid var(--line);
    flex-direction:row;justify-content:space-between;gap:16px;flex-wrap:wrap;
  }
  .tc-ringwrap{width:112px;height:112px}
  .tc-time{font-size:24px}
  .tc-phasenow{text-align:left;flex:1 1 130px}
  .tc-clocksync{border-top:0;text-align:left;width:auto;padding-top:0}
}
@media(max-width:700px){
  .tc-heroimg{min-height:186px}
  .tc-heroimg h1{font-size:24px}
  .tc-heroimg::after{background:linear-gradient(180deg,rgba(9,18,32,.55) 0%,rgba(9,18,32,.92) 65%)}
}

/* ---- 3. Fact tiles + status strip ---------------------------------------- */
.tc-facts .stat{display:flex;flex-direction:column;gap:4px}
.tc-facts .tc-k{font-size:12px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--dim)}
.tc-facts .tc-v{font-size:19px;font-weight:800;color:var(--accent);line-height:1.25}
.tc-facts .tc-sub{font-size:12.5px;color:var(--dim)}
.tc-status{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  border:1px solid var(--line);border-radius:12px;
  padding:12px 16px;margin:0 0 18px;font-size:14px;font-weight:600;
  background:var(--panel);color:var(--ink);
}
.tc-status .tc-dot{width:10px;height:10px;border-radius:999px;flex:0 0 auto}
.tc-status small{font-weight:500;color:var(--dim)}
.tc-status.is-open{background:color-mix(in srgb,var(--ok) 9%,var(--panel));border-color:color-mix(in srgb,var(--ok) 28%,var(--line));color:var(--ok)}
.tc-status.is-open .tc-dot{background:var(--ok)}

/* ---- 4. Prose ------------------------------------------------------------ */
.tc-prose p{margin:0 0 11px;line-height:1.6;font-size:14.5px}
.tc-prose p:last-child{margin-bottom:0}

/* ---- 5. Method: a 3 × 3 grid of picture cards ----------------------------
   Nine steps, drawn rather than described, because the class reads this with
   wet gloves on from half a metre away. Three across on a desk, two on a
   tablet, one at a time on a phone — a phone shows the step you are on, not
   a wall of nine.

   Each card carries its own hue (--sn), set inline from one spectrum in the
   script, so the numbers double as a position: "we're on the green one". */
.tc-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
@media(max-width:1060px){.tc-grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:680px){.tc-grid{grid-template-columns:1fr}}
.tc-card{
  border:1px solid var(--line);border-radius:12px;background:var(--panel2);
  padding:14px;display:flex;flex-direction:column;gap:10px;position:relative;
  border-top:3px solid var(--sn);
}
.tc-cardhead{display:flex;align-items:center;gap:10px}
.tc-num{
  width:28px;height:28px;border-radius:999px;flex:0 0 auto;
  display:grid;place-items:center;background:var(--sn);color:#fff;
  font-weight:800;font-size:14px;line-height:1;
}
.tc-cardhead b{font-size:14px;line-height:1.25}
/* The drawing. One flat band so the nine illustrations line up across a row
   whatever their own proportions. */
.tc-figure{
  background:var(--panel);border:1px solid var(--line);border-radius:9px;
  height:104px;display:grid;place-items:center;padding:8px;
}
.tc-figure svg{width:100%;height:100%;display:block}
.tc-why{font-size:12.5px;color:var(--dim);line-height:1.5;margin:0}
/* An equation only where a step actually needs one. */
.tc-formula{
  font-size:13px;font-weight:700;color:var(--ink);background:var(--panel);
  border:1px dashed var(--sn);border-radius:8px;padding:7px 10px;
}
.tc-safety{
  margin-top:16px;border-left:3px solid var(--err);background:color-mix(in srgb,var(--err) 6%,var(--panel));
  padding:10px 14px;border-radius:0 8px 8px 0;font-size:13px;
}
/* The four equations that survive, as one strip under the grid. */
.tc-eqstrip{
  margin-top:16px;border:1px solid var(--line);border-radius:11px;background:var(--panel2);padding:13px 15px;
}
.tc-eqstrip h4{margin:0 0 10px;font-size:12px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--dim)}
.tc-eqline{display:flex;flex-wrap:wrap;gap:10px 22px}
.tc-eqline div{font-size:14px;font-weight:600;color:var(--ink)}
.tc-eqline small{display:block;font-size:11.5px;font-weight:500;color:var(--dim);margin-top:2px}

/* ---- 6. Bench roster: a type-ahead, not a wall of chips ------------------
   A class list is long enough that scanning it is slower than typing three
   letters, and the same control has to accept a name that is not in the list
   at all — so the suggestion list ends with an "add anyway" row. That row is
   the whole of what used to be a separate Other field. */
.tc-rosterhint{font-size:13px;color:var(--dim);margin:0 0 12px;line-height:1.55}
/* A panel clips its own content (.panel{overflow:hidden} in app.css), and this
   list hangs below the input — measured on a real bench page, five matching
   classmates lost the last three rows off the bottom of the panel. The panel that
   holds the type-ahead is the one panel that must not clip. */
.panel:has(.tc-ac){overflow:visible}
.tc-ac{position:relative;max-width:520px}
.tc-ac input{width:100%}
.tc-aclist{
  position:absolute;z-index:30;left:0;right:0;top:calc(100% + 5px);
  margin:0;padding:5px;list-style:none;max-height:264px;overflow-y:auto;
  background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);
}
.tc-aclist[hidden]{display:none}
.tc-acitem{
  padding:8px 11px;border-radius:7px;font-size:14px;cursor:pointer;
  display:flex;align-items:center;justify-content:space-between;gap:10px;
}
/* Hover and keyboard highlight are the SAME class, set by the script, so the
   mouse and the arrow keys can never disagree about which row is current. */
.tc-acitem.is-active{background:var(--accent-soft);color:var(--accent)}
.tc-acitem small{color:var(--dim);font-size:12px;font-weight:600}
.tc-acitem.is-add{border-top:1px solid var(--line);border-radius:0 0 7px 7px;margin-top:4px;padding-top:10px}
.tc-acitem.is-add b{color:var(--accent)}
.tc-acempty{padding:9px 11px;font-size:13px;color:var(--dim)}
.tc-benchlist{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:7px;max-width:520px}
.tc-benchrow{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  border:1px solid var(--line);border-radius:10px;background:var(--panel2);padding:9px 12px;font-size:14px;
}
.tc-benchrow .tc-guest{font-size:11.5px;font-weight:700;color:var(--warn);text-transform:uppercase;letter-spacing:.05em;margin-left:8px}
.tc-benchrow .tc-me{font-size:12px;color:var(--dim);margin-left:8px;font-weight:500}
.tc-rm{
  appearance:none;border:0;background:transparent;color:var(--dim);cursor:pointer;
  font-size:16px;line-height:1;padding:4px 6px;border-radius:6px;flex:0 0 auto;
}
.tc-rm:hover{color:var(--err);background:color-mix(in srgb,var(--err) 8%,transparent)}
.tc-rm:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.tc-benchempty{
  margin-top:14px;border:1px dashed var(--line);border-radius:10px;background:var(--panel2);
  padding:11px 14px;font-size:13.5px;color:var(--dim);font-style:italic;max-width:520px;
}

/* ---- 7. Result entry ----------------------------------------------------
   One card per vinegar. The titre boxes are the working: type them and the
   concentration and % acidity fall out live, so a bench sees a bad titre
   before it is saved. The % box stays editable — a bench that did the
   arithmetic on paper is not blocked by it. */
.tc-samples{display:grid;grid-template-columns:minmax(0,560px);gap:18px;margin-bottom:18px}
.tc-sample .panel-head h3{display:flex;align-items:center;gap:9px}
.tc-swatch{width:12px;height:12px;border-radius:999px;flex:0 0 auto}
.tc-brand{font-size:13px;color:var(--dim);margin:0 0 12px}
.tc-titres{display:grid;grid-template-columns:repeat(3,1fr);gap:9px;margin-bottom:4px}
.tc-titres label{display:block;font-size:11.5px;font-weight:700;color:var(--dim);margin-bottom:4px;text-transform:uppercase;letter-spacing:.04em}
.tc-titres input{width:100%}
.tc-derived{
  margin:12px 0 14px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);
  padding:10px 13px;display:flex;flex-direction:column;gap:6px;font-size:13px;
}
.tc-drow{display:flex;justify-content:space-between;gap:12px;align-items:baseline}
.tc-drow span:last-child{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink)}
.tc-drow.is-muted span{color:var(--dim)}
.tc-conc{font-size:12.5px;font-weight:700;margin-top:2px}
.tc-conc.ok{color:var(--ok)}
.tc-conc.bad{color:var(--warn)}
/* The equation the bench needs, directly above the box they type the answer
   into. The page used to fill that box in from the titres, which did the one
   piece of chemistry the practical exists to teach. */
.tc-eqbox{
  border:1px dashed color-mix(in srgb,var(--accent) 40%,var(--line));
  background:var(--panel2);border-radius:9px;padding:9px 12px;margin:0 0 9px;
  font-size:13px;line-height:1.6;
}
.tc-eqbox b{color:var(--accent)}
.tc-eqbox small{display:block;color:var(--dim);font-size:11.5px;margin-top:3px;line-height:1.45}
.tc-pct input{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;padding:9px 12px}
/* A result away from the label is a prompt to check the arithmetic, not an
   accusation — so the far case is warn, never err. */
.tc-verdict{font-size:13px;font-weight:700;margin-top:10px}
.tc-verdict.near{color:var(--ok)}
.tc-verdict.off{color:var(--warn)}

/* ---- 8. Submit button + autosave strip ----------------------------------
   Every edit is already saved, so the button is not what gets the result to
   the teacher — it is the bench SAYING they are finished, which is a
   different claim and the one they want to be able to make. The two share a
   row: align-items:stretch gives the button the strip's height without a
   fixed pixel value, so the two stay level however the strip's text wraps. */
.tc-submitrow{display:flex;align-items:stretch;gap:12px;margin:0 0 18px;flex-wrap:wrap}
.tc-submitrow .btn{flex:0 0 auto;padding-left:22px;padding-right:22px}
.tc-submitrow .tc-save{flex:1 1 320px;margin:0}
.tc-save{
  display:flex;align-items:center;gap:11px;flex-wrap:wrap;
  border:1px solid var(--line);border-radius:12px;background:var(--panel);
  padding:12px 16px;margin:0 0 18px;font-size:13.5px;font-weight:600;color:var(--ink);
}
.tc-save .tc-dot{width:9px;height:9px;border-radius:999px;flex:0 0 auto;background:var(--dim)}
.tc-save small{font-weight:500;color:var(--dim)}
.tc-save.is-saving{background:var(--accent-soft);border-color:color-mix(in srgb,var(--accent) 30%,var(--line));color:var(--accent)}
.tc-save.is-saving .tc-dot{background:var(--accent);animation:tc-pulse 1s ease-in-out infinite}
.tc-save.is-saved{background:color-mix(in srgb,var(--ok) 9%,var(--panel));border-color:color-mix(in srgb,var(--ok) 28%,var(--line));color:var(--ok)}
.tc-save.is-saved .tc-dot{background:var(--ok)}
.tc-save.is-error{background:color-mix(in srgb,var(--err) 8%,var(--panel));border-color:color-mix(in srgb,var(--err) 30%,var(--line));color:var(--err)}
.tc-save.is-error .tc-dot{background:var(--err)}
@keyframes tc-pulse{0%,100%{opacity:1}50%{opacity:.35}}
@media(prefers-reduced-motion:reduce){.tc-save.is-saving .tc-dot{animation:none}}

/* ---- 9. The award tile and its popup ------------------------------------
   There are no badges any more, so this tile IS the record of winning. It
   lives at the top of this page and nowhere else in Portal: an award for a
   titration belongs on the titration page, not scattered through the app.

   The popup fires once, on the first visit after the teacher publishes.
   Once. A congratulation that reappears every visit stops being one. */
.tc-won{
  display:flex;align-items:flex-start;gap:15px;
  border:1px solid color-mix(in srgb,var(--ok) 34%,var(--line));
  background:linear-gradient(100deg,color-mix(in srgb,var(--ok) 13%,var(--panel)),var(--panel) 70%);
  border-radius:var(--radius);padding:16px 18px;margin:0 0 18px;
}
.tc-wonicon{
  width:44px;height:44px;flex:0 0 auto;border-radius:999px;display:grid;place-items:center;font-size:21px;
  background:color-mix(in srgb,var(--ok) 16%,var(--panel));color:var(--ok);
}
.tc-wonbody{flex:1;min-width:0}
.tc-wonbody > b{display:block;font-size:16px;margin-bottom:3px;color:var(--ok)}
.tc-wonbody p{margin:0 0 4px;font-size:13.5px;line-height:1.55}
.tc-wonbody small{color:var(--dim);font-size:12.5px}
.tc-wonlist{display:flex;gap:8px;flex-wrap:wrap;margin:9px 0 0}
.tc-wonpill{
  font-size:12.5px;font-weight:700;border-radius:999px;padding:4px 12px;
  background:var(--panel);border:1px solid color-mix(in srgb,var(--ok) 34%,var(--line));color:var(--ok);
}

/* The popup. Plain centred dialog — this is one sentence of good news, not a
   flow, so it gets one button and no decoration that has to be dismissed. */
.tc-pop{
  position:fixed;inset:0;z-index:200;display:grid;place-items:center;
  background:color-mix(in srgb,var(--ink) 55%,transparent);padding:20px;
}
.tc-pop[hidden]{display:none}
.tc-popcard{
  background:var(--panel);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow);
  max-width:430px;width:100%;padding:26px 26px 22px;text-align:center;
}
.tc-popicon{
  width:64px;height:64px;margin:0 auto 14px;border-radius:999px;display:grid;place-items:center;font-size:30px;
  background:color-mix(in srgb,var(--ok) 15%,var(--panel));color:var(--ok);
}
.tc-popcard h3{margin:0 0 8px;font-size:20px}
.tc-popcard p{margin:0 0 12px;font-size:14px;line-height:1.6;color:var(--ink)}
.tc-popcard .tc-wonlist{justify-content:center;margin-bottom:16px}
.tc-popcard small{display:block;color:var(--dim);font-size:12.5px;margin-bottom:16px}

/* ---- 10. Awards ----------------------------------------------------------
   A judged award and a computed one are not the same kind of thing, and the
   row says which is which — otherwise a bench spends the lesson wondering
   whether someone is watching them. */
.tc-awards{display:grid;grid-template-columns:repeat(auto-fit,minmax(272px,1fr));gap:13px}
.tc-award{
  display:flex;gap:12px;align-items:flex-start;
  border:1px solid var(--line);border-radius:11px;background:var(--panel2);padding:13px 14px;
}
/* Bigger than a glyph badge needs to be: the marks are drawings — three flasks, a burette
   over a flask — and at 21px their strokes closed up into a blot. */
.tc-awicon{
  width:44px;height:44px;flex:0 0 auto;border-radius:999px;display:grid;place-items:center;
  background:var(--accent-soft);color:var(--accent);
}
.tc-awicon .line-ico{width:25px;height:25px;vertical-align:0}
.tc-award.is-judged .tc-awicon{background:color-mix(in srgb,var(--warn) 16%,var(--panel));color:var(--warn)}
.tc-award b{display:block;font-size:14px;margin-bottom:3px}
.tc-award p{font-size:12.5px;color:var(--dim);line-height:1.5;margin:0}
.tc-awtags{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
.tc-awtag{
  font-size:10.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  border:1px solid var(--line);border-radius:999px;padding:2px 8px;background:var(--panel);color:var(--dim);
}
.tc-awtag.is-auto{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 32%,var(--line))}
.tc-awtag.is-judged{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 32%,var(--line))}



/* ---- the class clock ----------------------------------------------------
   The teacher owns the clock; the benches only read it. What is stored is
   `startedAt`, `pausedAt` and `durationSec` — never a countdown — so every
   device derives the same remaining time from the same three numbers and
   nothing can drift apart. */
.tt-clock{display:flex;align-items:center;gap:20px;flex-wrap:wrap}
.tt-dial{
  font-variant-numeric:tabular-nums;font-size:46px;font-weight:800;line-height:1;
  color:var(--accent);letter-spacing:-.02em;min-width:150px;
}
.tt-dial.is-warn{color:var(--warn)}
.tt-dial.is-over{color:var(--err)}
.tt-clockstate{font-size:13px;font-weight:700;color:var(--dim)}
.tt-clockstate b{display:block;font-size:15px;color:var(--ink);margin-bottom:2px}
.tt-clockbtns{display:flex;gap:9px;flex-wrap:wrap;margin-left:auto}
.tt-live{
  display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:600;color:var(--ok);
  background:color-mix(in srgb,var(--ok) 9%,var(--panel));
  border:1px solid color-mix(in srgb,var(--ok) 28%,var(--line));
  border-radius:999px;padding:5px 12px;
}
.tt-live .dot{width:8px;height:8px;border-radius:999px;background:var(--ok)}
.tt-live.is-idle{color:var(--dim);background:var(--panel2);border-color:var(--line)}
.tt-live.is-idle .dot{background:var(--dim)}

/* ---- who may be here ---------------------------------------------------- */
.tt-gate{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  border:1px solid color-mix(in srgb,var(--warn) 32%,var(--line));
  background:color-mix(in srgb,var(--warn) 10%,var(--panel));
  border-radius:12px;padding:11px 15px;margin:0 0 18px;font-size:13.5px;font-weight:600;color:var(--warn);
}
.tt-gate small{font-weight:500;color:var(--dim)}

/* ---- step 1: the true values -------------------------------------------
   Held back until the event closes. They are the reference every bench is
   scored against, so the moment they are typed the whole table below
   recalculates — which is why they sit at the top and nothing else can be
   done until they are in. */
.tt-truth{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px}
.tt-truth .field{margin:0}
.tt-truth input{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;padding:9px 12px;width:100%}
.tt-swatch{width:11px;height:11px;border-radius:999px;display:inline-block;margin-right:7px}

/* ---- the bench table ---------------------------------------------------- */
.tt-wrap{overflow-x:auto}
.tt-table{width:100%;border-collapse:collapse;font-size:13.5px;min-width:720px}
.tt-table th{
  text-align:left;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim);
  padding:9px 12px;border-bottom:1px solid var(--line);white-space:nowrap;
}
.tt-table td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.tt-table tr:last-child td{border-bottom:0}
.tt-table .num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.tt-table .best{font-weight:800;color:var(--ok)}
.tt-names{font-size:12px;color:var(--dim)}
.tt-miss{color:var(--warn);font-weight:600;font-size:12.5px}
/* A bench that never saved a result is not a competitor — grey, and it cannot
   be given an award by accident. */
.tt-table tr.is-empty td{opacity:.55}

/* ---- award rows: multi-select, by design -------------------------------- */
.tt-award{
  border:1px solid var(--line);border-radius:12px;background:var(--panel2);
  padding:14px 16px;margin-bottom:13px;
}
.tt-award:last-child{margin-bottom:0}
.tt-awhead{display:flex;align-items:center;gap:11px;flex-wrap:wrap;margin-bottom:4px}
.tt-awicon{
  width:40px;height:40px;flex:0 0 auto;border-radius:999px;display:grid;place-items:center;
  background:var(--accent-soft);color:var(--accent);
}
.tt-awicon .line-ico{width:23px;height:23px;vertical-align:0}
.tt-award.is-judged .tt-awicon{background:color-mix(in srgb,var(--warn) 16%,var(--panel));color:var(--warn)}
.tt-awhead b{font-size:15px}
.tt-tag{
  font-size:10.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  border:1px solid var(--line);border-radius:999px;padding:2px 8px;background:var(--panel);color:var(--dim);
}
.tt-tag.is-auto{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 32%,var(--line))}
.tt-tag.is-judged{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 32%,var(--line))}
.tt-awsub{font-size:12.5px;color:var(--dim);margin:0 0 10px;line-height:1.5}
.tt-picks{display:flex;gap:8px;flex-wrap:wrap}
/* Reuses the portal .chip. The tick is added by the script so a selected
   bench survives a theme where accent and panel sit close together. */
.tt-pick.on::before{content:'✓ ';font-weight:900}
.tt-pick:disabled{opacity:.4}
.tt-hint{font-size:12px;color:var(--dim);margin-top:9px}
.tt-hint b{color:var(--accent)}
.tt-hint.is-warn{color:var(--warn)}

/* ---- publish ------------------------------------------------------------ */
.tt-publish{display:flex;align-items:stretch;gap:12px;flex-wrap:wrap}
.tt-publish .btn{flex:0 0 auto;padding-left:24px;padding-right:24px}
.tt-summary{
  flex:1 1 340px;border:1px solid var(--line);border-radius:12px;background:var(--panel);
  padding:12px 16px;font-size:13.5px;display:flex;align-items:center;
}
.tt-summary.is-ready{background:color-mix(in srgb,var(--ok) 9%,var(--panel));border-color:color-mix(in srgb,var(--ok) 28%,var(--line));color:var(--ok);font-weight:600}
.tt-summary.is-blocked{background:color-mix(in srgb,var(--warn) 10%,var(--panel));border-color:color-mix(in srgb,var(--warn) 30%,var(--line));color:var(--warn);font-weight:600}
.tt-summary.is-done{background:var(--accent-soft);border-color:color-mix(in srgb,var(--accent) 30%,var(--line));color:var(--accent);font-weight:600}

/* ---- what the student will get ------------------------------------------
   Shown before publishing, not after. A teacher about to message 14 students
   should be able to read what they will read. */
.tt-preview{display:grid;grid-template-columns:minmax(0,1fr) 250px;gap:18px}
@media(max-width:820px){.tt-preview{grid-template-columns:1fr}}
.tt-msg{
  border:1px solid var(--line);border-radius:12px;background:var(--panel2);padding:14px 16px;font-size:13.5px;line-height:1.6;
}
.tt-msg .tt-msghead{font-weight:800;margin-bottom:6px}
.tt-msg .tt-sig{color:var(--dim);font-size:12.5px;margin-top:9px}


/* ---- the staff-only table at the foot of the bench page ------------------
   Same .tt-table as the awarding screen, so the two never drift apart; only
   the two columns that screen does not have need anything of their own. */
.tc-allwho{font-size:12.5px;line-height:1.5;min-width:200px}
.tc-allguest{color:var(--dim);font-size:11.5px}
.tc-allnobody{color:var(--warn);font-size:12.5px}
.tc-allstatus{font-size:12.5px;white-space:nowrap}
.tc-allstatus .is-sub{color:var(--ok)}
.tc-allstatus small{display:block;color:var(--dim);font-size:11.5px}
.tc-allfoot{color:var(--dim);font-size:12.5px;padding-top:0}

/* css/milestone.css */
/* milestone.css — the Portal-wide question counter bar, and the 10,000th-question celebration.
   Component lives in js/milestone.js; the number and the winner come from migration 0201. */

/* ---------------------------------------------------------------- the counter bar
   Deep blue with the KC yellow doing the work: the same two colours as the sidebar and the logo,
   so the bar reads as part of Portal rather than as an advert dropped into it. Wide and short on
   purpose — it must be the first thing on the page without competing with the homework tile, which
   is the tall coloured block underneath it and still the thing a student is meant to act on. */
.kc-counter{
  position:relative;
  overflow:hidden;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(300px,392px);
  gap:26px;
  align-items:center;
  padding:20px 24px;
  margin:0 0 18px;
  border-radius:var(--radius);
  color:#fff;
  box-shadow:var(--shadow);
  background:linear-gradient(115deg,#0c315f 0%,#0a4f9e 52%,#0a61c9 100%);
}
/* A faint scatter of confetti already in the background, so the bar looks like it is heading
   somewhere. Decoration only: aria-hidden, and it never carries meaning. Kept to the middle band
   so a piece can never land on the figure or on "N to go" and turn a number into a smudge. */
.kc-counter-deco{position:absolute;inset:0;pointer-events:none}
.kc-counter-deco i{position:absolute;display:block;width:9px;height:14px;border-radius:2px;opacity:.75}

/* On Home the whole bar IS the button — the copy says "click here", so the target has to be the
   whole thing rather than a link buried in a sentence. A <button> may only contain phrasing
   content, which is why every box inside the countdown state is a <span> given its display here.
   The .is-hit state is a plain <section> and keeps its divs. */
.kc-counter-main{display:block;min-width:0}
button.kc-counter{
  width:100%;font:inherit;text-align:left;border:0;cursor:pointer;
  transition:transform .15s,box-shadow .15s;
}
button.kc-counter:hover{transform:translateY(-2px);box-shadow:0 18px 40px rgba(10,97,201,.3)}
button.kc-counter:focus-visible{outline:3px solid var(--accent2);outline-offset:3px}
@media(prefers-reduced-motion:reduce){
  button.kc-counter,button.kc-counter:hover{transition:none;transform:none}
}

.kc-counter-kicker{
  display:flex;align-items:center;gap:9px;
  font-size:11px;font-weight:900;letter-spacing:.1em;text-transform:uppercase;color:#a9d3ff;
}
.kc-counter-kicker .ast{color:var(--accent2);font-size:14px;letter-spacing:0}
.kc-counter-figure{display:flex;align-items:baseline;gap:12px;margin:6px 0 2px}
/* Tabular figures: the number changes under the student while they read it, and proportional
   digits make the whole line jump sideways when 9,512 becomes 9,522. */
.kc-counter-n{
  font-size:52px;font-weight:900;line-height:1;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;color:#fff;
}
.kc-counter-of{font-size:20px;font-weight:800;color:#9fcbff;font-variant-numeric:tabular-nums}
.kc-counter-say{display:block;font-size:14px;color:#cfe5ff;line-height:1.4;max-width:52ch}
.kc-counter-say strong{color:#fff;font-weight:800}

/* right-hand rail: how far there is to go, and what happens when it gets there */
.kc-counter-rail{display:grid;gap:9px}
.kc-counter-togo{display:flex;align-items:baseline;gap:8px;justify-content:flex-end;font-size:13.5px;color:#cfe5ff}
.kc-counter-togo b{font-size:23px;font-weight:900;color:var(--accent2);font-variant-numeric:tabular-nums}
.kc-counter-track{
  display:block;height:14px;border-radius:999px;
  background:#00193866;border:1px solid #ffffff2e;overflow:hidden;
}
.kc-counter-fill{
  display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,#ffd75e,var(--accent2));
  box-shadow:0 0 18px #ffc92899;
}

/* Under 100 to go the rail breathes. Slow and small on purpose — this sits above a page students
   use for an hour at a time, and anything faster becomes something to look away from. */
.kc-counter.is-close .kc-counter-fill{animation:kc-counter-pulse 2.4s ease-in-out infinite}
@keyframes kc-counter-pulse{
  0%,100%{box-shadow:0 0 14px #ffc92877}
  50%    {box-shadow:0 0 26px #ffc928dd}
}
/* The confetti is suppressed in JS (never created), but this pulse is pure CSS and has to be
   turned off here. The rail keeps its glow — it just stops breathing. */
@media(prefers-reduced-motion:reduce){
  .kc-counter.is-close .kc-counter-fill{animation:none;box-shadow:0 0 20px #ffc928aa}
}

/* --- won --- */
.kc-counter.is-hit{background:linear-gradient(115deg,#0a4f9e 0%,#0a61c9 45%,#1a7fe0 100%)}
.kc-counter.is-hit .kc-counter-n{color:var(--accent2)}
.kc-counter-winner{display:flex;align-items:center;gap:12px;justify-content:flex-end}
.kc-counter-winner-av{
  width:44px;height:44px;border-radius:50%;display:grid;place-items:center;flex:none;
  background:var(--accent2);color:#0c315f;font-weight:900;font-size:16px;
}
.kc-counter-winner-t{display:grid;gap:1px;text-align:right}
.kc-counter-winner-t em{
  font-style:normal;font-size:11px;font-weight:900;letter-spacing:.09em;
  text-transform:uppercase;color:#a9d3ff;
}
.kc-counter-winner-t b{font-size:18px;font-weight:900}

/* The rail drops under the figure rather than squeezing beside it. 820px, not the usual 760, because
   the rail needs ~300px of its own before "488 to go" and the pill start colliding. */
@media(max-width:820px){
  .kc-counter{grid-template-columns:1fr;gap:16px;padding:18px}
  .kc-counter-togo,.kc-counter-winner{justify-content:flex-start}
  .kc-counter-winner-t{text-align:left}
  .kc-counter-n{font-size:42px}
  .kc-counter-say{font-size:13.5px}
}

/* ---------------------------------------------------------------- confetti
   One fixed layer over everything, pointer-events off so it cannot swallow a click on the page
   underneath. Pieces are torn out of the DOM by the script once they have landed.
   prefers-reduced-motion is handled in js/milestone.js by not creating them at all — a display:none
   rule here would still pay for 150 nodes and their animations. */
.kc-confetti{position:fixed;inset:0;overflow:hidden;pointer-events:none;z-index:2200}
.kc-confetti i{
  position:absolute;top:-8vh;display:block;width:10px;height:16px;border-radius:2px;
  animation-name:kc-confetti-fall;animation-timing-function:linear;animation-fill-mode:both;
}
/* Falls the full viewport and drifts sideways by its own --x, spinning by --spin, so no two pieces
   travel the same line. */
@keyframes kc-confetti-fall{
  from{transform:translate3d(0,0,0) rotate(0deg);opacity:1}
  to  {transform:translate3d(var(--x,0),112vh,0) rotate(var(--spin,360deg));opacity:.9}
}

/* ---------------------------------------------------------------- the winner's card
   Only ever seen by one person, once. z-index sits above .modal-bg (1000) and above the confetti's
   own layer, so the card stays readable while the confetti falls behind it. */
.kc-win-bg{
  position:fixed;inset:0;z-index:2300;display:grid;place-items:center;padding:20px;
  background:#0c315f80;
}
.kc-win{
  position:relative;width:560px;max-width:100%;border-radius:18px;overflow:hidden;
  background:var(--panel);text-align:center;box-shadow:0 30px 80px #0c315f66;
}
.kc-win-top{
  padding:34px 34px 28px;color:#fff;
  background:linear-gradient(150deg,#0c315f 0%,#0a61c9 60%,#1a7fe0 100%);
}
.kc-win-ast{font-size:34px;color:var(--accent2);line-height:1}
.kc-win-kicker{
  margin-top:10px;font-size:11.5px;font-weight:900;letter-spacing:.13em;
  text-transform:uppercase;color:#a9d3ff;
}
.kc-win-n{
  font-size:66px;font-weight:900;line-height:1;letter-spacing:-.025em;color:var(--accent2);
  font-variant-numeric:tabular-nums;margin:8px 0 4px;text-shadow:0 4px 22px #ffc9284d;
}
.kc-win-th{font-size:26px;vertical-align:super}
.kc-win-t{font-size:20px;font-weight:800}
.kc-win-body{padding:22px 30px 26px}
.kc-win-body p{margin:0 0 16px;color:var(--dim);font-size:14.5px;line-height:1.55}
.kc-win-body p b{color:var(--ink)}
.kc-win-actions{display:flex;gap:10px;justify-content:center}

@media(max-width:520px){
  .kc-win-top{padding:26px 22px 22px}
  .kc-win-n{font-size:52px}
  .kc-win-body{padding:18px 20px 22px}
}

/* ---------------------------------------------------------------- the in-quiz pill
   The same number, a fifth of the size, riding the quiz player's own chrome row opposite
   "Question 3 of 10" (css/student.css .quiz-one-top, which is already a space-between flex row).
   It therefore costs the question card no vertical space at all — the alternative, a band above
   the card, pushes the stem down the screen on every question of every quiz for the sake of a
   number nobody opened the quiz to read.

   Same two colours and the same tabular figures as the full bar, so it reads as the same thing
   seen from further away rather than as a second unrelated widget. */
.kc-counter-mini{
  display:inline-flex;align-items:center;gap:10px;
  padding:7px 14px 7px 11px;border-radius:999px;white-space:nowrap;
  background:linear-gradient(115deg,#0c315f,#0a61c9);
  color:#fff;box-shadow:0 6px 16px rgba(10,49,95,.24);
}
.kc-counter-mini .ast{color:var(--accent2);font-size:14px;line-height:1}
.kc-mini-n{font-size:16px;font-weight:900;letter-spacing:-.01em;font-variant-numeric:tabular-nums}
.kc-mini-of{font-size:12px;font-weight:700;color:#a9d3ff;font-variant-numeric:tabular-nums}
.kc-mini-track{
  display:block;width:86px;height:7px;border-radius:999px;
  background:#00193877;border:1px solid #ffffff2e;overflow:hidden;
}
.kc-mini-fill{
  display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,#ffd75e,var(--accent2));box-shadow:0 0 10px #ffc92899;
}
.kc-mini-togo{font-size:12px;font-weight:800;color:var(--accent2);font-variant-numeric:tabular-nums}

/* Phones: the chrome row wraps and the pill sheds the rail, not the number — the rail is the
   decoration and the figure is the point. */
@media(max-width:620px){
  .kc-counter-mini{gap:8px;padding:6px 12px 6px 10px}
  .kc-mini-track{display:none}
  .kc-mini-of{font-size:11.5px}
}

/* The pill is a button wherever it is visible, same as the full bar — it goes to Quizzes. */
button.kc-counter-mini{
  font:inherit;border:0;cursor:pointer;
  transition:transform .15s,box-shadow .15s;
}
button.kc-counter-mini:hover{transform:translateY(-1px);box-shadow:0 10px 22px rgba(10,49,95,.34)}
button.kc-counter-mini:focus-visible{outline:3px solid var(--accent2);outline-offset:3px}
@media(prefers-reduced-motion:reduce){
  button.kc-counter-mini,button.kc-counter-mini:hover{transition:none;transform:none}
}

/* In the Examiner-Report player the pill sits in .et-play-head, which pushes its "Back to Quizzes"
   button right with margin-left:auto. The pill has to take that auto margin instead, or it lands
   flush against the set title and the button loses its right-hand anchor. */
.et-play-head .kc-counter-mini{margin-left:auto}
.et-play-head .kc-counter-mini + .btn{margin-left:0}
/* That head row is a nowrap flex line holding a long set title, the pill and a button. On a phone
   the three do not fit, so let it wrap and put the pill on its own line under the title rather than
   letting the three squeeze into an unreadable row. */
@media(max-width:760px){
  .et-play-head{flex-wrap:wrap}
  .et-play-head .kc-counter-mini{margin-left:0;order:3}
  .et-play-head .btn{margin-left:auto}
}

/* --- won: the rail carries the date, not a person ---
   Replaces the old winner block. Nobody is named on the public bar (migration 0207 does not even
   send a name), so the right-hand side records WHEN rather than WHO. */
.kc-counter-reached{
  /* .kc-counter-rail is a grid, so without this the badge stretches the full 392px of the column
     and reads as an empty panel with text pushed into one corner. Shrink to content, sit right. */
  justify-self:end;
  display:flex;flex-direction:column;align-items:flex-end;gap:2px;
  padding:10px 16px;border-radius:12px;
  background:#ffffff1f;border:1px solid #ffffff33;
}
.kc-counter-reached b{font-size:15px;font-weight:900;color:var(--accent2);letter-spacing:.01em}
.kc-counter-reached em{font-style:normal;font-size:12.5px;color:#cfe5ff;font-variant-numeric:tabular-nums}
@media(max-width:820px){
  .kc-counter-reached{justify-self:start;align-items:flex-start}
}

/* css/assistant.css */
/* Assistant teacher pages: the Home cards (js/views/assistant-home.js) and the live-class roster
   (js/views/assistant-students.js). Both are small on purpose — an assistant opens Portal on a
   phone or a shared laptop between marking two scripts, so these pages are read at arm's length. */

/* ---- Home ---- */
.ah-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.ah-card{display:flex;flex-direction:column;gap:4px;align-items:flex-start;text-align:left;
  padding:18px 20px;border:1px solid var(--line);border-radius:16px;background:var(--panel);
  color:var(--ink);font:inherit;cursor:pointer;transition:border-color .14s,transform .14s,box-shadow .14s}
.ah-card:hover{border-color:var(--accent);transform:translateY(-1px);box-shadow:0 6px 18px rgba(0,0,0,.06)}
.ah-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* The idle card is not a button — there is nothing to open — so it must not invite a click. */
.ah-card--idle{cursor:default;background:var(--panel2)}
.ah-card--idle:hover{border-color:var(--line);transform:none;box-shadow:none}
.ah-card--live{border-color:var(--accent);background:var(--panel)}
.ah-kicker{font-size:11.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
.ah-card--live .ah-kicker{color:var(--accent)}
.ah-title{font-size:20px;font-weight:800;line-height:1.25}
.ah-sub{font-size:13px;color:var(--dim);line-height:1.45}

/* ---- Students ---- */
.as-note{margin:0 0 12px;font-size:13px;color:var(--dim)}
.as-table td,.as-table th{white-space:nowrap}
.as-table .as-name{white-space:normal;font-weight:700}
.as-none{color:var(--dim)}
.as-mark{display:inline-flex;align-items:baseline;gap:6px;font-weight:700}
.as-mark small{font-size:11.5px;font-weight:700;opacity:.85}
.as-mark--green{color:var(--ok)}
.as-mark--yellow{color:var(--warn)}
.as-mark--red{color:var(--err)}
.as-hw{display:inline-block;padding:2px 9px;border-radius:999px;font-size:12px;font-weight:700;
  background:var(--panel2);color:var(--dim)}
.as-hw--complete{background:color-mix(in srgb,var(--ok) 16%,transparent);color:var(--ok)}
.as-hw--incomplete{background:color-mix(in srgb,var(--err) 14%,transparent);color:var(--err)}
.as-hw--absent{background:color-mix(in srgb,var(--warn) 16%,transparent);color:var(--warn)}
