/* Student profile — modal + standalone page.
   House pattern (matches .db_* in Student.module.css): crisp 1.5px ink borders
   on cards, square corners everywhere, uppercase letterspaced eyebrows,
   #FF4D00 accent only where it MEANS something, modality colours from the
   student dashboard (--c-writing etc.). Five type sizes: 11/12.5/14/18/26px.
   One family (inherited). Nothing moves after it appears.

   THEME: built on the sk tokens so the curated night palette applies without
   a repaint — every colour is either a var(--sk-*, light-fallback) or a
   color-mix against var(--card), so dark mode is CLEARED, not repainted.
   Text contrast: nothing below 4.5:1 on its own surface, either theme. */

.sp-root, .sp-page, .sp-pagenav, dialog.sp-dialog {
  --ink: var(--sk-ink, #111111);
  --card: var(--sk-surface, #ffffff);
  --well: var(--sk-surface-2, #fafafa);   /* inset boxes: spark frames, result box */
  --gutter: var(--sk-paper, #f4f4f5);     /* the ground the cards sit on */
  --field: var(--sk-paper, #ffffff);      /* form inputs sit one step off the card */
  --sub: var(--sk-ink-2, #3f3f46);        /* secondary text */
  --label: var(--sk-muted, #52525b);      /* eyebrow/label text */
  --hair: var(--sk-hair, #c9c9cf);        /* structural hairlines only, never text */
  --accent: #FF4D00;
  --brand: #0ea5e9;
  --c-writing: #3882BA;
  --c-speaking: #CD3843;
  --c-reading: #2E9E5B;
  --c-listening: #E0A800;
  --c-listening-text: #8a6d00;  /* listening yellow fails as TEXT on white */
  --c-other: #52525b;
  --ok: #15803d;
  --bad: #b91c1c;
}
/* Night: semantic + modality colours need a brighter cut to hold 4.5:1 on the
   dark card. Same hues, lifted — the brand and accent are untouched. */
html[data-theme="dark"] .sp-root, html[data-theme="dark"] .sp-page,
html[data-theme="dark"] .sp-pagenav, html[data-theme="dark"] dialog.sp-dialog {
  --c-writing: #6FADE0;
  --c-speaking: #E4737E;
  --c-reading: #4CC57F;
  --c-listening: #E8B93B;
  --c-listening-text: #E0A800;
  --c-other: #A6A59D;
  --ok: #5DD68A;
  --bad: #F28B82;
}

.sp-root {
  color: var(--ink);
  font-size: 14px;
  /* no background — the host paints the gutter the cards sit on */
}

/* ---------- dialog shell (created by student-profile.js) ---------- */
dialog.sp-dialog {
  border: 1.5px solid var(--ink);
  border-radius: 0;
  padding: 0;
  width: min(1180px, 96vw);
  height: min(90vh, 920px);
  background: var(--card);
  color: var(--ink);
}
dialog.sp-dialog::backdrop {
  background: rgba(8, 17, 31, 0.55);
}
.sp-dialog-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  height: 52px;
  padding: 0 14px;
  /* Matches the report toolbar (#lr-bar): ink ground, ONE orange rule. */
  background: #111111;
  color: #ffffff;
  border-bottom: 2px solid var(--accent);
}
.sp-dialog-bar .sp-nav-hint {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.65);
  margin-right: auto;
}
.sp-btn {
  appearance: none;
  border: 1.5px solid var(--hair);
  border-radius: 0;
  background: var(--card);
  color: var(--sub);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  padding: 9px 14px;
  cursor: pointer;
}
/* On the ink bar the buttons take the document-viewer treatment. */
.sp-dialog-bar .sp-btn {
  background: transparent;
  color: #ffffff;
  border-color: rgba(255, 255, 255, 0.32);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 8px 12px;
}
.sp-dialog-bar .sp-btn:hover {
  background: var(--sk-orange-fill, #cc3d00);
  border-color: var(--sk-orange-fill, #cc3d00);
  color: #ffffff;
}
.sp-dialog-bar .sp-btn:disabled { opacity: 0.35; }
.sp-dialog-bar .sp-btn:disabled:hover {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.32);
  color: #ffffff;
}
.sp-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}
/* Close is not a sibling of Prev/Next — it leaves, they navigate. */
.sp-dialog-bar [data-sp-close] {
  margin-left: 6px;
  padding: 8px 14px;
  font-size: 13px;
  line-height: 1;
  /* An orange fill carrying a glyph: the AA cut, not the #FF4D00 accent (2026-10-13). */
  background: var(--sk-orange-fill, #cc3d00);
  border-color: var(--sk-orange-fill, #cc3d00);
  color: #ffffff;
}
.sp-dialog-bar [data-sp-close]:hover {
  background: #ffffff;
  border-color: #ffffff;
  color: #111111;
}
.sp-btn:disabled {
  color: var(--label);
  border-color: var(--hair);
  cursor: default;
}
/* Primary cut: the button that COMMITS something is filled ink; hover goes
   accent. Light: black button. Night: warm-white button. Same statement. */
.sp-btn-primary {
  background: var(--ink);
  color: var(--card);
  border-color: var(--ink);
  font-weight: 700;
}
.sp-btn-primary:hover {
  background: var(--sk-orange-fill, #cc3d00);
  border-color: var(--sk-orange-fill, #cc3d00);
  color: #fff;
}
.sp-dialog-scroll {
  overflow-y: auto;
  height: calc(100% - 56px);
  background: var(--gutter);
}

/* ---------- standalone page ---------- */
.sp-page {
  max-width: 1180px;
  /* The site header is fixed, so a card flush to the top of the document has
     its own top edge hidden underneath it — which ate the border. */
  margin: 96px auto 32px;
  background: var(--gutter);
  /* Framed like the dialog: the standalone view is the same object, so it
     carries the same 1.5px ink edge rather than floating on the page. */
  border: 1.5px solid var(--ink);
  padding: 4px;
}

/* ---------- shared layout ---------- */
.sp-view-main { padding: 14px; }
.sp-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 14px;
  align-items: start;
}
@media (max-width: 900px) {
  .sp-grid { grid-template-columns: 1fr; }
}
.sp-grid-left, .sp-grid-right {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}
.sp-card {
  --eye: var(--brand);
  background: var(--card);
  border: 1.5px solid var(--ink);
  padding: 16px 18px 14px;
}
.sp-card--work   { --eye: var(--accent); }
.sp-card--strong { --eye: var(--c-reading); }
.sp-card--acct   { --eye: var(--c-other); }
.sp-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--label);
  margin: 0 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--hair);
}
/* Each zone carries a small colour flag — the one square of colour per card. */
.sp-eyebrow::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  background: var(--eye, var(--brand));
  margin-right: 8px;
}
.sp-eyebrow-count { color: var(--label); font-weight: 400; margin-left: 6px; }
.sp-empty { font-size: 12.5px; color: var(--sub); margin: 0 0 6px; }
.sp-footnote {
  font-size: 12.5px;
  color: var(--sub);
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--hair);
}
.sp-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* ---------- 1 · identity band ---------- */
/* Dark, because this is a document header and the report's is dark: #111 with
   the orange rule under it (report_living.js #lr-bar). Chris, 2026-08-14: "my
   real document has got a black header, white buttons, orange for close — make
   it the same design as that." A student's record IS a document, so it opens
   the way the report opens. */
.sp-band {
  display: flex;
  align-items: center;
  gap: 20px;
  margin: 14px 14px 0;
  padding: 18px 20px;
  background: #111111;
  color: #ffffff;
  border: 1.5px solid #111111;
  border-bottom: 2px solid var(--accent);
}
/* Everything inside the band is now sitting on black, so the light-surface
   tokens (--ink, --sub, --label) would vanish. Restated locally rather than
   redefined globally — the rest of the page is still a light surface. */
.sp-band .sp-name { color: #ffffff; }
.sp-band .sp-native { color: rgba(255, 255, 255, 0.62); }
.sp-band .sp-metaitem { color: rgba(255, 255, 255, 0.72); }
.sp-band .sp-chip {
  color: rgba(255, 255, 255, 0.88);
  border-color: rgba(255, 255, 255, 0.32);
  background: transparent;
}
.sp-band a.sp-chip:hover {
  color: #ffffff;
  border-color: var(--sk-orange-fill, #cc3d00);
  background: var(--sk-orange-fill, #cc3d00);
}
.sp-band .sp-status-ok { color: #6ee7a8; }
.sp-band .sp-status-bad { color: #ffb4a2; }
.sp-band .sp-photo-btn { color: rgba(255, 255, 255, 0.72); }
.sp-band .sp-photo-btn:hover { color: var(--accent); }
/* The avatar keeps a visible edge against the dark rather than dissolving. */
.sp-band .sp-avatar,
.sp-band .sp-avatar-img { border-color: rgba(255, 255, 255, 0.28); }
.sp-avatar-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  flex: none;
}
.sp-avatar, .sp-avatar-img {
  width: 126px;
  height: 126px;
  border: 1.5px solid var(--ink);
  background: color-mix(in srgb, var(--brand) 12%, var(--card));
}
.sp-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 66px;
}
.sp-avatar-img { object-fit: cover; display: block; }
.sp-photo-btn {
  appearance: none;
  border: none;
  border-radius: 0;
  background: none;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label);
  cursor: pointer;
  padding: 2px 0 0;
}
.sp-photo-btn:hover { color: var(--accent); }

.sp-idblock { flex: 1; min-width: 0; }
.sp-name {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  margin: 0;
  letter-spacing: -0.02em;
}
.sp-native { font-size: 18px; font-weight: 400; color: var(--sub); margin-left: 8px; }
.sp-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--sub);
}
.sp-chip {
  display: inline-block;
  padding: 3px 9px;
  border: 1.5px solid var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, var(--card));
  color: var(--ink);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
}
a.sp-chip:hover { color: var(--accent); border-color: var(--accent); background: var(--card); }
.sp-status-ok { color: var(--ok); font-weight: 600; }
.sp-status-bad { color: var(--bad); font-weight: 700; text-transform: uppercase; }

/* ---------- 2 · quick actions ---------- */
.sp-actions {
  display: flex;
  align-items: stretch;
  gap: 8px;
  flex: none;
  align-self: center;
}
/* When the kebab cannot appear, the reason does. A control that is silently
   absent teaches nothing; a sentence naming the obstacle and the fix does. */
.sp-manage-note {
  margin: 10px 0 0;
  max-width: 46ch;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted, #6b6b6b);
}
.sp-manage-note a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.sp-manage-note a:hover { color: var(--accent); }

/* The rare admin pair lives behind a kebab so the row stays two decisions wide. */
.sp-more { position: relative; display: flex; }
.sp-more-btn {
  min-width: 42px;
  padding: 12px 10px;
  font-size: 15px;
  letter-spacing: 0;
  line-height: 1;
}
.sp-more-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  min-width: 180px;
  background: #111111;
  border: 1.5px solid rgba(255, 255, 255, 0.85);
  z-index: 40;
}
.sp-more-item {
  appearance: none;
  display: block;
  width: 100%;
  border: 0;
  background: transparent;
  color: #ffffff;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: left;
  padding: 12px 14px;
  cursor: pointer;
}
.sp-more-item:hover { color: var(--accent); background: rgba(255, 255, 255, 0.06); }
/* Navigation home for the standalone page — the band's corner, not the action row. */
.sp-band { position: relative; }
.sp-band-back {
  position: absolute;
  top: 10px;
  right: 14px;
  color: rgba(255, 255, 255, 0.55);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
}
.sp-band-back:hover { color: var(--accent); }
/* Hierarchy: ONE filled primary (Build report), the rest outlined — four solid
   ink blocks made every action look equally urgent. House cut either way:
   square, ink, accent on hover; never slate-blue, never rounded. */
.sp-action {
  appearance: none;
  display: block;
  border: 1.5px solid var(--ink);
  border-radius: 0;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  line-height: 1;
  padding: 12px 14px;
  cursor: pointer;
  white-space: nowrap;
}
.sp-action--quiet {
  color: var(--sub);
  border-color: var(--hair);
}
.sp-action--quiet:hover { color: var(--accent); border-color: var(--accent); }
.sp-action--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}
.sp-action--primary:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: #ffffff;
}
.sp-action:hover {
  background: var(--sk-orange-fill, #cc3d00);
  border-color: var(--sk-orange-fill, #cc3d00);
  color: #fff;
}

/* ---------- 3 · scores ---------- */
.sp-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
}
.sp-tile {
  border: 1.5px solid var(--ink);
  border-top-width: 5px;
  padding: 12px 14px 12px;
  min-width: 0;
  background: color-mix(in srgb, currentColor 5%, var(--card));
}
.sp-tile-writing   { border-top-color: var(--c-writing);   color: var(--c-writing); }
.sp-tile-speaking  { border-top-color: var(--c-speaking);  color: var(--c-speaking); }
.sp-tile-reading   { border-top-color: var(--c-reading);   color: var(--c-reading); }
.sp-tile-listening { border-top-color: var(--c-listening); color: var(--c-listening); }
.sp-tile-other     { border-top-color: var(--c-other);     color: var(--c-other); }
.sp-tile-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: inherit;
}
.sp-tile-score {
  font-size: 26px;
  font-weight: 700;
  margin-top: 4px;
  line-height: 1.1;
  color: var(--ink);
}
.sp-tile-max { font-size: 14px; font-weight: 400; color: var(--sub); margin-left: 3px; }
.sp-delta { font-size: 12.5px; font-weight: 600; margin-top: 4px; color: var(--sub); min-height: 18px; }
.sp-delta.sp-up { color: var(--ok); }
.sp-delta.sp-down { color: var(--bad); }
.sp-delta-vs { color: var(--label); font-weight: 400; margin-left: 4px; }
.sp-delta-first { color: var(--label); font-weight: 400; }
.sp-spark-frame {
  margin-top: 10px;
  border: 1px solid color-mix(in srgb, currentColor 30%, var(--card));
  background: var(--card);
  padding: 6px 8px;
}
.sp-spark { display: block; width: 100%; }

/* ---------- results table ---------- */
.sp-work-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.sp-work-table th {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label);
  text-align: left;
  padding: 6px 10px 8px 0;
  border-bottom: 1.5px solid var(--ink);
}
.sp-work-table td {
  padding: 8px 10px 8px 0;
  border-bottom: 1px solid var(--hair);
  vertical-align: baseline;
}
.sp-work-table tbody tr:last-child td { border-bottom: none; }
.sp-work-table .sp-num { text-align: right; }
.sp-cell-date { font-size: 12.5px; color: var(--sub); white-space: nowrap; }
.sp-work-link {
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid var(--hair);
}
.sp-work-link:hover { color: var(--accent); border-bottom-color: var(--accent); }
.sp-type-chip {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 7px;
  border: 1.5px solid currentColor;
  background: color-mix(in srgb, currentColor 9%, var(--card));
  white-space: nowrap;
}
.sp-type-writing   { color: var(--c-writing); }
.sp-type-speaking  { color: var(--c-speaking); }
.sp-type-reading   { color: var(--c-reading); }
.sp-type-listening { color: var(--c-listening-text); }  /* chip border stays the family; text darkened for contrast */
.sp-type-other     { color: var(--c-other); }
.sp-score { font-weight: 700; }
.sp-score-max { font-size: 12.5px; color: var(--sub); }
.sp-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 7px;
  border: 1.5px solid var(--label);
  color: var(--label);
}
.sp-badge-pub {
  border-color: var(--ok);
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 9%, var(--card));
}

/* ---------- 4 · skills ---------- */
.sp-skill-list { list-style: none; margin: 0; padding: 0; }
.sp-skill-row {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 1px solid var(--hair);
  border-radius: 0;
  width: 100%;
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 2px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.sp-skill-list li:last-child .sp-skill-row { border-bottom: none; }
.sp-skill-row:hover .sp-skill-name,
.sp-skill-row:hover .sp-skill-arrow { color: var(--accent); }
.sp-skill-name { flex: 1; min-width: 0; }
.sp-skill-stat { font-size: 12.5px; font-weight: 400; color: var(--sub); white-space: nowrap; }
.sp-skill-arrow { color: var(--label); font-size: 18px; line-height: 1; font-weight: 400; }

/* ---------- 5 · account & personal ---------- */
.sp-facts { margin: 0; }
.sp-fact {
  display: flex;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--hair);
  font-size: 12.5px;
}
.sp-fact:last-child { border-bottom: none; }
.sp-fact dt {
  flex: none;
  width: 88px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--label);
  padding-top: 1px;
}
.sp-fact dd { margin: 0; color: var(--ink); min-width: 0; }
.sp-copy-inline {
  appearance: none;
  border: none;
  border-radius: 0;
  background: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink);
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}
.sp-copy-inline:hover { color: var(--accent); }
.sp-quiet-flag { color: var(--accent); font-weight: 700; }

/* ---------- swapped panels (skill detail / reset / edit) ---------- */
.sp-view-detail { padding: 14px; }
.sp-panel {
  --eye: var(--brand);
  background: var(--card);
  border: 1.5px solid var(--ink);
  padding: 18px 20px;
  max-width: 640px;
}
.sp-back {
  appearance: none;
  background: none;
  border: none;
  border-radius: 0;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label);
  cursor: pointer;
  padding: 0 0 14px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.sp-back:hover { color: var(--accent); }
.sp-detail-title { font-size: 18px; font-weight: 700; margin: 0 0 2px; }
.sp-detail-sub { font-size: 12.5px; color: var(--sub); margin: 0 0 16px; }
.sp-detail-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px;
  margin-bottom: 18px;
}
.sp-detail-stat {
  border: 1.5px solid var(--ink);
  padding: 10px 12px;
  background: color-mix(in srgb, var(--brand) 5%, var(--card));
}
.sp-detail-stat .v { font-size: 18px; font-weight: 700; display: block; }
.sp-detail-stat .k {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--label);
}
.sp-example {
  border-left: 3px solid var(--hair);
  padding: 4px 0 4px 12px;
  margin: 0 0 10px;
  font-size: 14px;
}
.sp-example .was { color: var(--bad); text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--bad) 50%, transparent); }
.sp-example .now { color: var(--ok); font-weight: 600; }
.sp-example .arr { color: var(--label); padding: 0 6px; }

/* forms inside panels */
.sp-field { margin-bottom: 14px; }
.sp-field label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label);
  margin-bottom: 5px;
}
.sp-input {
  width: 100%;
  box-sizing: border-box;
  border: 1.5px solid var(--ink);
  border-radius: 0;
  background: var(--field);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  padding: 9px 11px;
}
.sp-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.sp-panel-row { display: flex; gap: 8px; align-items: stretch; }
.sp-panel-row .sp-input { flex: 1; }
.sp-panel-err { color: var(--bad); font-size: 12.5px; font-weight: 600; margin: 8px 0 0; min-height: 16px; }
.sp-result {
  border: 1.5px solid var(--ok);
  background: color-mix(in srgb, var(--ok) 7%, var(--card));
  padding: 14px 16px;
  margin-top: 16px;
}
.sp-result-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label);
  margin-bottom: 6px;
}
.sp-result-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 18px;
  font-weight: 700;
  word-break: break-all;
}


/* ---------- results tabs, show-all, and the graph view ---------- */
.sp-tabrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.sp-tabs { display: flex; gap: 0; }
.sp-tab {
  appearance: none;
  border: 1px solid var(--hair);
  border-radius: 0;
  background: var(--card);
  color: var(--sub);
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 6px 12px;
  cursor: pointer;
}
.sp-tab + .sp-tab { border-left: none; }
.sp-tab:hover { background: var(--accent); border-color: var(--accent); color: #ffffff; }
/* The selected tab is the ink one — same language as the primary action. */
.sp-tab.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--card);
}

.sp-showall {
  appearance: none;
  margin-top: 10px;
  border: 1px solid var(--hair);
  border-radius: 0;
  background: var(--card);
  color: var(--sub);
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 7px 12px;
  cursor: pointer;
}
.sp-showall:hover { color: var(--accent); border-color: var(--accent); }

.sp-chart { margin: 0 0 18px; }
.sp-chart + .sp-chart { border-top: 1px solid var(--hair); padding-top: 14px; }
.sp-chart-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.sp-chart-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.sp-chart-meta { font-size: 11px; color: var(--label); }
.sp-chart-svg { width: 100%; height: auto; display: block; overflow: visible; }
.sp-chart-rule { stroke: var(--hair); stroke-width: 1; }
.sp-chart-rule--base { stroke: var(--sub); }
.sp-chart-axis { font-size: 10px; fill: var(--label); font-family: inherit; }
.sp-chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.sp-chart-dot { stroke: none; }
/* Stroke only — the polyline shares this class, so a fill here paints the area
   under the line and turns a trend into a blob. Dots get their fill below. */
.sp-line-writing { stroke: var(--c-writing); }
.sp-line-speaking { stroke: var(--c-speaking); }
.sp-line-reading { stroke: var(--c-reading); }
.sp-line-listening { stroke: var(--c-listening); }
.sp-line-other { stroke: var(--c-other); }
circle.sp-line-writing { fill: var(--c-writing); }
circle.sp-line-speaking { fill: var(--c-speaking); }
circle.sp-line-reading { fill: var(--c-reading); }
circle.sp-line-listening { fill: var(--c-listening); }
circle.sp-line-other { fill: var(--c-other); }
.sp-chart-foot {
  display: flex;
  justify-content: space-between;
  font-size: 10px;
  color: var(--label);
  padding: 2px 0 0 34px;
}

/* ---------------- the clickable name ----------------
   "A student name — if it's shown, you can click on it." (Chris, 2026-08-25)

   Deliberately quiet: it INHERITS its surroundings' colour and weight, so a
   name inside a light table, a dark card or a night-mode roster stays exactly
   as designed until you point at it. Only the hover is ours — the house
   accent, #FF4D00 — which is also why this needs no dark-mode block: it has no
   colour of its own to re-theme. Square, like every other affordance here.

   The resting rules are pinned to ELEMENT specificity via :where(), so they
   only ever beat the UA's blue-and-underlined link default; any Tailwind
   colour or weight already on the tag (text-slate-800, font-bold) still wins.
   The hover deliberately outranks those — pointing at a name must change it. */
a:where(.sp-namelink), button:where(.sp-namelink), :where(.sp-namelink) {
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  background: none;
  border: 0;
  padding: 0;
  border-radius: 0;          /* square: no rounded anything in this app */
  cursor: pointer;
  text-decoration: none;
  text-underline-offset: 2px;
}
.sp-namelink:hover, .sp-namelink:focus-visible {
  color: #FF4D00;
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
}
.sp-namelink:focus-visible {
  outline: 2px solid #FF4D00;
  outline-offset: 2px;
}

/* ============================================================================
   THE HOUSE MODAL — the same pattern the profile modal above uses, made
   reusable so every other modal stops inventing its own.

   The newer modals were built from flat utility greys, which in the night
   palette lands as dark text on a dark panel on a dark page: nothing has an
   edge, headings disappear into the bar above them, and the whole thing reads
   as amateur. The fix is not "more contrast" — it is the house pattern:
   crisp 1.5px ink borders, square corners, one ink bar with a single orange
   rule, uppercase letterspaced eyebrows each carrying one small colour square.

   Built on the same --sk-* tokens, so dark mode is CLEARED rather than
   repainted, and light mode still works from the fallbacks.
   ========================================================================== */
.sk-modal {
  --ink: var(--sk-ink, #111111);
  --card: var(--sk-surface, #ffffff);
  --well: var(--sk-surface-2, #fafafa);
  --gutter: var(--sk-paper, #f4f4f5);
  --sub: var(--sk-ink-2, #3f3f46);
  --label: var(--sk-muted, #52525b);
  --hair: var(--sk-hair, #c9c9cf);
  --accent: #FF4D00;
  --brand: #0ea5e9;
  --ok: #15803d;
  --warn: #b45309;
  /* .sp-input grounds itself on --field, and .sk-btn-danger fills with --bad.
     Both were undefined inside a modal, so a field had no ground and the
     destructive hover did nothing. --bad stays the deep red in BOTH themes:
     it is only ever used as a fill under white text, and the lighter night red
     the profile page uses for TEXT drops well under 4.5:1 as a background. */
  --field: var(--sk-paper, #ffffff);
  --bad: #b91c1c;
  /* Red does two different jobs and needs two values. --bad is a FILL under
     white text, so it stays the deep red in both themes. --bad-ink is red as
     TEXT or a border on the card, which has to lift in the night palette or it
     drops under 4.5:1 on a dark ground. */
  --bad-ink: #b91c1c;
}
html[data-theme="dark"] .sk-modal { --bad-ink: #F28B82; }

/* The panel. One ink border, square, sitting on the dimmed page. */
.sk-modal-shell {
  background: var(--card);
  border: 1.5px solid var(--ink);
  border-radius: 0;
  box-shadow: 12px 12px 0 rgba(17, 17, 17, 0.18);
}

/* The bar: ink ground, white text, ONE orange rule. Same as the report
   toolbar and the profile dialog, so a teacher recognises it instantly. */
.sk-modal-bar {
  background: #111111;
  color: #ffffff;
  border-bottom: 2px solid var(--accent);
  padding: 12px 18px;
}
.sk-modal-bar .sk-modal-eyebrow { color: rgba(255, 255, 255, 0.62); }
.sk-modal-bar h2 { color: #ffffff; margin: 2px 0 0; font-size: 18px; font-weight: 700; }
.sk-modal-bar .sk-modal-sub { color: rgba(255, 255, 255, 0.72); font-size: 12.5px; margin-top: 2px; }
.sk-modal-close {
  appearance: none; background: transparent; border: 1.5px solid rgba(255,255,255,.32);
  color: #fff; border-radius: 0; line-height: 1; padding: 6px 10px; cursor: pointer;
  font-size: 16px;
}
.sk-modal-close:hover { background: var(--accent); border-color: var(--accent); }

/* Eyebrow: 11px, letterspaced, one colour square. The square is the only
   colour in the row, which is what stops it looking decorated. */
.sk-modal-eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--label); margin: 0;
}
.sk-modal-eyebrow::before {
  content: ""; display: inline-block; width: 8px; height: 8px;
  background: var(--eye, var(--brand)); margin-right: 8px;
}

/* A group inside the modal: its own hairline, its own header band that is
   clearly a header — not a grey bar you cannot read. */
.sk-modal-group { border-top: 1.5px solid var(--ink); }
.sk-modal-group:first-of-type { border-top: 0; }
.sk-modal-grouphead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 18px;
  background: var(--well);
  border-bottom: 1px solid var(--hair);
}

/* Rows separate from each other with a hairline, never by shade alone. */
.sk-modal-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--hair);
  background: var(--card);
}
.sk-modal-row:last-child { border-bottom: 0; }
.sk-modal-row:hover { background: var(--well); }

.sk-modal-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 18px;
  background: var(--well);
  border-top: 1.5px solid var(--ink);
}

/* Status words. Weight and colour, never a pill — pills add noise at this size. */
.sk-tag { font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
.sk-tag--in   { color: var(--ok); }
.sk-tag--out  { color: var(--label); }
.sk-tag--late { color: var(--accent); }
.sk-tag--note { color: var(--brand); }
.sk-modal-meta { font-size: 11px; color: var(--label); font-variant-numeric: tabular-nums; }

/* ---------- working parts, for the modals that are a FORM, not a list ----------
   The further-actions panel, the logistics drawer and the class dialogs all need
   fields, a sentence of explanation, and a row of buttons. Same five type sizes,
   same tokens — so a group in a form modal reads identically to a group in a
   list modal and nothing has to be re-invented per screen. */

/* The padded body under a grouphead. Sits on the card, never on a shade. */
.sk-modal-section { padding: 14px 18px 18px; background: var(--card); }

/* The one sentence that says what this group does. 12.5px, secondary ink —
   readable on its own surface in both themes, which the old slate-400 was not. */
.sk-modal-note { font-size: 12.5px; line-height: 1.5; color: var(--sub); margin: 0 0 12px; }
.sk-modal-note--quiet { color: var(--label); margin: 10px 0 0; }

/* Field label: the eyebrow's typography WITHOUT the colour square — squares are
   one per group head, not one per input. */
.sk-fieldlabel {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--label); margin: 0 0 6px;
}
.sk-fieldhint { display: block; font-size: 11px; color: var(--label); margin-top: 6px; }
.sk-fieldgrid { display: grid; gap: 12px; grid-template-columns: 1fr; }
@media (min-width: 640px) { .sk-fieldgrid--2 { grid-template-columns: 1fr 1fr; } }
.sk-btnrow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 12px; }

/* What just happened, said back plainly. Bordered so it has an edge in the
   night palette instead of being a green wash that vanishes. */
.sk-modal-notice {
  font-size: 12.5px; font-weight: 600; color: var(--ok);
  border: 1.5px solid var(--ok); background: color-mix(in srgb, var(--ok) 7%, var(--card));
  padding: 9px 12px; margin: 14px 18px 0;
}
/* The same strip when the news is bad -- a failed save, a warning that work
   will be destroyed. Bordered for the same reason: a red WASH disappears on the
   night card, a red EDGE does not. */
.sk-modal-notice--bad {
  color: var(--bad-ink);
  border-color: var(--bad-ink);
  background: color-mix(in srgb, var(--bad-ink) 8%, var(--card));
}
/* Inside a section the strip is already inside the padding, so it drops the
   horizontal margin. It only needs a gap above when something precedes it --
   which is the rule, rather than an inline style whose only job is to undo one. */
.sk-modal-section > .sk-modal-notice { margin-left: 0; margin-right: 0; margin-top: 0; }
.sk-modal-section > * + .sk-modal-notice { margin-top: 14px; }

/* A toggle chip: square, ink border, filled ink when on. The fill IS the state,
   so no tick, no second colour. */
.sk-chip-toggle {
  display: inline-flex; align-items: center; gap: 8px;
  border: 1.5px solid var(--hair); border-radius: 0;
  background: var(--card); color: var(--sub);
  font-size: 12.5px; font-weight: 600; padding: 8px 12px; cursor: pointer;
}
.sk-chip-toggle:hover { border-color: var(--ink); color: var(--ink); }
/* The box inside a chip is drawn, not native: a native checkbox with no explicit
   border inherits the ink text colour and reads as ALREADY TICKED, and on the
   ink ground of an .is-on chip it disappears entirely. */
.sk-chip-toggle input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 13px; height: 13px; flex: none;
  border: 1.5px solid var(--hair); border-radius: 0;
  background: var(--card); cursor: pointer; margin: 0;
}
.sk-chip-toggle input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
}
.sk-chip-toggle.is-on input[type="checkbox"] { border-color: rgba(255, 255, 255, 0.55); }
.sk-chip-toggle.is-on {
  background: var(--ink); border-color: var(--ink); color: var(--card);
}

/* Destructive is a word, not a red button. It only turns red under the pointer,
   at the moment it could actually happen. */
.sk-btn-danger:hover { background: var(--bad); border-color: var(--bad); color: #fff; }

/* ---------- the kebab menu ----------
   Same statement as .sp-more-menu (ink ground, white rules) but position-agnostic,
   because the assignment list places its menu with a computed fixed offset. A
   white menu with slate hairlines had no edge at all against the night page. */
.sk-menu {
  --ink: var(--sk-ink, #111111);
  --accent: #FF4D00;
  --bad: #b91c1c;
  /* 176px == the w-44 the assignment list's placement maths assumes when it
     computes a fixed left offset. Changing it here would push the menu off. */
  min-width: 176px;
  background: #111111;
  border: 1.5px solid rgba(255, 255, 255, 0.85);
  border-radius: 0;
  padding: 0;
}
.sk-menu-item {
  appearance: none; display: block; width: 100%; text-align: left;
  border: 0; background: transparent; color: #ffffff; font: inherit;
  font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  padding: 10px 14px; cursor: pointer;
}
.sk-menu-item:hover { color: var(--accent); background: rgba(255, 255, 255, 0.06); }
/* Delete is the only item that means harm, so it is the only one that goes red. */
.sk-menu-item--danger:hover { color: #ffffff; background: #b91c1c; }
.sk-menu-sep { height: 1px; background: rgba(255, 255, 255, 0.22); margin: 0; }
/* The dots themselves: square hit area, accent on hover. Never a grey pill. */
.sk-kebab {
  appearance: none; border: 1.5px solid transparent; background: transparent;
  border-radius: 0; padding: 4px; cursor: pointer; color: var(--sk-muted, #52525b);
  line-height: 0;
}
.sk-kebab:hover { color: #FF4D00; border-color: #FF4D00; }

/* ---- Directory / Class Finder modal ---------------------------------------
   Same house shell; it just needs to be big, because a directory is a list you
   scan rather than a card you read. The page inside keeps its own layout. */
dialog.dir-dialog {
  border: 0; padding: 0; background: transparent; max-width: none; max-height: none;
  width: 100%; height: 100%;
}
dialog.dir-dialog::backdrop { background: rgba(8, 17, 31, 0.55); }
.dir-shell {
  width: min(1200px, calc(100vw - 48px));
  max-height: calc(100vh - 64px);
  margin: 32px auto;
  display: flex; flex-direction: column;
  overflow: hidden;
}
.dir-bar { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.dir-bar-actions { display: flex; align-items: center; gap: 10px; flex: none; }
/* On the ink bar the secondary button takes the bar's treatment, not the card's. */
.dir-bar .sp-btn {
  background: transparent; border-color: rgba(255, 255, 255, 0.32); color: #fff;
  text-decoration: none;
}
.dir-bar .sp-btn:hover { background: var(--accent); border-color: var(--accent); }
.dir-body { overflow: auto; background: var(--gutter); flex: 1; }
/* The directory page paints its own full-height ground; inside the modal the
   shell already provides it, so drop the min-height or it scrolls twice. */
.dir-body .min-h-screen { min-height: 0; }
.dir-loading { padding: 28px; font-size: 12.5px; color: var(--label); }

/* TEACHER PASS 09-30.
   Standalone page: base.html's <main> already clears the fixed header (pt-24), so
   the page's own 96px margin doubled the gap; and a page is not a dialog — it
   sits on the ground like every other page instead of in a second ink frame.
   Actions: buttons are sentence case at the app's 13px (caps stay on the section
   kickers only), and orange fills carry dark ink like every other primary. */
.sp-page {
  margin: 8px auto 32px;
  border: 0;
  padding: 0;
  background: transparent;
}
.sp-action {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  padding: 0 14px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-width: 1px;
}
/* Build report — the view's one orange primary. Orange fills that carry text use the AA
   cut with white (Chris, 2026-10-13), not #FF4D00 with ink or white. */
.sp-action--primary {
  background: var(--sk-orange-fill, #cc3d00);
  border-color: var(--sk-orange-fill, #cc3d00);
  color: #fff;
  font-weight: 600;
}
.sp-action--primary:hover {
  background: var(--sk-orange-fill-hover, #b83700);
  border-color: var(--sk-orange-fill-hover, #b83700);
  color: #fff;
}
.sp-photo-btn { font-size: 12px; font-weight: 500; letter-spacing: 0; text-transform: none; }

/* ---------- family strip (Option A, 2026-10-13) ----------
   Full width, directly under the name band, in view on open at 1440 and 390. The
   server paints the first line; FamilyLinkControls (variant="strip", parents bundle)
   takes the box over with the live controls. Its look lives in that module's CSS. */
.sp-family {
  margin: 14px 14px 0;
  background: var(--card);
  border: 1.5px solid var(--ink);
  padding: 12px 16px;
  min-height: 50px;
  box-sizing: border-box;
  color: var(--ink);
}
.sp-family-first { display: flex; align-items: center; gap: 10px; min-width: 0; min-height: 28px; }
.sp-family-k {
  font-size: 11px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--label); width: 64px; flex: none;
}
.sp-family-state { font-size: 12.5px; font-weight: 700; color: var(--ink); }
.sp-family-state.is-off { color: var(--accent); }
.sp-family-url {
  font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 12px; color: var(--sub);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* Advanced options: the Parents page for this pupil, swapped into the modal body —
   full width, not the 640px column the small panels use. */
.sp-panel:has(.sp-family-hub) { max-width: none; }
.sp-family-hub { margin-top: 2px; }

/* ---------- identity band at phone width (2026-10-13) ----------
   The actions sat on the same row as the name with nowrap, so at 390 they squeezed the
   name block to a letter's width (the native name stacked one letter per line) and
   "Open in class" ran off the edge. Under 640 the band wraps: photo + name on one row,
   the actions on their own full-width row beneath. */
@media (max-width: 640px) {
  .sp-band { flex-wrap: wrap; align-items: flex-start; gap: 14px; padding: 16px; }
  .sp-avatar, .sp-avatar-img { width: 72px; height: 72px; }
  .sp-avatar { font-size: 38px; }
  .sp-idblock { flex: 1 1 0; min-width: 0; }
  .sp-name { font-size: 22px; overflow-wrap: anywhere; }
  .sp-native { display: block; margin: 2px 0 0; font-size: 16px; }
  .sp-actions { width: 100%; flex-wrap: wrap; align-self: stretch; }
  .sp-actions .sp-action { flex: 1 1 auto; }
  .sp-actions .sp-more { flex: none; }
  .sp-family { margin: 12px 12px 0; padding: 12px; }
  .sp-view-main { padding: 12px; }
  .sp-band { margin: 12px 12px 0; }
  /* The results table keeps its columns; it scrolls inside its card, not the modal. */
  [data-sp-tabpanel="list"] { overflow-x: auto; }
}
