/* ── The hidden attribute must always win ──────────────────────────────────
   Any rule that sets `display` outranks the UA stylesheet's [hidden] rule, so
   an element written as `<div class="panel" hidden>` renders anyway, and
   `el.hidden = true` in JS appears to do nothing. Every toggle on this site
   uses the hidden property, so the failure is always the same: a panel that
   is permanently open and cannot be dismissed.

   This was patched once, for .dbg-loading. It then recurred for .debug-panel,
   .java-advisories, .dbg-scrubber and the report dialog — which a student hit
   as "Report a problem shows up without me asking, and no way to dismiss".
   One rule, at the top, instead of remembering every time.

   !important because the whole point is to beat later, more specific rules. */
[hidden] { display: none !important; }

:root {
  --bg: #f6f7fb;
  --panel: #ffffff;
  --panel-2: #ffffff;
  --fg: #1f2330;
  --muted: #5b6472;
  --border: #d9dde6;
  --accent: #2563eb;
  --keyword: #1d4ed8;
  --string: #b45309;
  --number: #7c3aed;
  --comment: #6b7280;
  --builtin: #0d9488;
  --def: #b45309;
  --error: #b91c1c;
  --ok: #16a34a;
  /* Semantic status pairs (bg + fg) — formerly repeated as raw hex across
     badges, test summaries, messages, and similarity tints. Same values, named,
     so a future theme/dark-mode is a token swap. */
  --success-bg: #dcfce7; --success-fg: #166534;
  --warning-bg: #fef3c7; --warning-fg: #92400e;
  --danger-bg:  #fee2e2; --danger-fg:  #991b1b;
  --info-bg:    #dbeafe; --info-fg:    #1e40af;
  /* Radius scale — current values, named so a future retune/dark-mode is one
     place. 5px (writeup toolbar button) stays literal as a one-off. */
  --radius-sm: 6px; --radius-md: 8px; --radius-lg: 10px;
  --radius-pill: 999px; --radius-round: 50%;
  /* Spacing scale (2px base; --space-N == N*2 px). Mirrors the existing
     literals EXACTLY — zero visual change. Applied to padding/margin/gap only,
     not to widths/heights/insets/line-heights/border-widths. Odd-pixel one-offs
     (1/3/5/7/9px, used for borders + fine alignment) stay literal; snapping the
     whole UI to a strict 4px grid is a later, intentionally visual slice. */
  --space-1: 2px;  --space-2: 4px;  --space-3: 6px;  --space-4: 8px;
  --space-5: 10px; --space-6: 12px; --space-7: 14px; --space-8: 16px;
  --space-9: 18px; --space-12: 24px; --space-16: 32px; --space-20: 40px;
  /* App shell. --chrome-h is the vertical space the shell takes OUTSIDE the
     page body (just .content's own padding now that the topbar is gone); panes
     that want to fill the viewport subtract it. .has-banner raises it. */
  --sidebar-w: 232px; --rail-narrow-w: 56px; --chrome-h: 32px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  height: 100%;
}

/* `.topbar`, `.brand` and a bare `nav a` rule lived here, from the horizontal
   top navigation the rail replaced. They are gone. The `nav a` rule in
   particular was load-bearing in the wrong direction: written for a row of
   links, it put a 16px LEFT margin on every anchor inside any <nav> in the
   app, which is why the sidebar was first built as an <aside> to dodge it —
   trading a landmark for a workaround. It was also still indenting
   .chapter-nav's links for no reason. Removing it lets the rail be a real
   <nav>, and every surviving nav sets its own spacing. */
.inline-form { display: inline; margin: 0; }

/* Off-screen until focused, then a real button in the top-left. */
.skip-link {
  position: absolute;
  left: var(--space-4);
  top: -100px;
  z-index: 100;
  padding: var(--space-4) var(--space-8);
  background: var(--panel);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: top 0.1s;
}
.skip-link:focus { top: var(--space-4); }
/* The target takes focus programmatically; never draw a ring around the page. */
#main:focus { outline: none; }
.linkbutton {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  margin-left: var(--space-8);
  font-size: 14px;
  padding: 0;
}
.linkbutton:hover { color: var(--fg); }

.content { padding: var(--space-8); }

/* ---- App shell -----------------------------------------------------------
   A persistent rail plus the page. `.linkbutton` still carries a 16px left
   margin from the old topbar, hence the reset below. `minmax(0, 1fr)` and
   `min-width: 0` stop a wide child (a code block, a table) from forcing the
   whole grid wider than the viewport. */
.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
}
.app-shell.rail-narrow { grid-template-columns: var(--rail-narrow-w) minmax(0, 1fr); }
.app-shell.has-banner { --chrome-h: 72px; }
.app-main { min-width: 0; }

.app-sidebar {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-8) var(--space-6);
  background: var(--panel);
  border-right: 1px solid var(--border);
}
.app-sidebar a { margin-left: 0; }
.app-sidebar .linkbutton,
.app-sidebar .rail-linkbutton { margin-left: 0; }

.rail-brand {
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--fg);
  text-decoration: none;
  font-size: 15px;
}
.rail-heading {
  margin: var(--space-5) 0 var(--space-1);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}
.rail-list { list-style: none; margin: 0; padding: 0; }
.rail-link {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
  line-height: 1.3;
}
.rail-link:hover { background: var(--surface-hover, rgba(0, 0, 0, 0.04)); color: var(--fg); }
.rail-link.is-current { background: var(--info-bg); color: var(--info-fg); font-weight: 600; }
.rail-icon {
  flex: 0 0 auto;
  width: 18px;
  text-align: center;
  font-size: 12px;
  opacity: 0.85;
}
.rail-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.rail-sublist { list-style: none; margin: var(--space-1) 0 var(--space-3); padding: 0 0 0 30px; }
.rail-sublink {
  display: block;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  color: var(--muted);
  text-decoration: none;
  font-size: 13px;
}
.rail-sublink:hover { color: var(--fg); }
.rail-sublink.is-current { color: var(--fg); font-weight: 600; }
.rail-empty { color: var(--muted); font-size: 13px; padding: var(--space-3) var(--space-4); }

.rail-footer { margin-top: auto; border-top: 1px solid var(--border); padding-top: var(--space-5); }
.rail-account {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4) 0;
}
.rail-linkbutton { font-size: 13px; text-align: left; }

/* The stylesheet had no focus styling at all; a keyboard-only rail would be
   unusable without it. */
.rail-link:focus-visible,
.rail-sublink:focus-visible,
.rail-brand:focus-visible,
.rail-linkbutton:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Collapsed rail (the workbench): icons only, labels gone. */
.rail-narrow .app-sidebar { padding-left: var(--space-3); padding-right: var(--space-3); }
.rail-narrow .rail-label,
.rail-narrow .rail-heading,
.rail-narrow .rail-sublist,
.rail-narrow .rail-empty,
.rail-narrow .rail-account .inline-form { display: none; }
/* The theme toggle survives the collapse — the workbench is exactly where a
   student sits longest, so it is the worst page to lose dark mode on. */
.rail-narrow .rail-account { align-items: center; padding: var(--space-4) 0 0; }
.rail-narrow .rail-brand { font-size: 0; }
.rail-narrow .rail-brand::before { content: "KE"; font-size: 15px; }
.rail-narrow .rail-link { justify-content: center; padding-left: 0; padding-right: 0; }

/* Narrow viewports: the rail becomes a horizontal strip above the page rather
   than eating half the width. The stylesheet's first media query. */
@media (max-width: 900px) {
  .app-shell,
  .app-shell.rail-narrow { grid-template-columns: 1fr; }
  .app-sidebar {
    position: static;
    height: auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .app-sidebar .rail-list { display: flex; flex-wrap: wrap; gap: var(--space-2); }
  .rail-heading, .rail-sublist { display: none; }
  .rail-footer { margin-top: 0; border-top: 0; padding-top: 0; }
  .rail-account { flex-direction: row; padding: 0; }
}
/* NOTE: rules that override something defined LATER in this file cannot live
   in the block above — same specificity, so the later rule wins regardless of
   the media query. The workbench overrides are at the end of the file for
   exactly that reason. */

/* ---- Next-up landing ----------------------------------------------------- */
.home-page { max-width: 760px; margin: 0 auto; }
.home-hero {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-12);
}
.home-hero-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-6);
  margin-bottom: var(--space-4);
}
.home-hero-course {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
}
.home-hero-course:hover { color: var(--fg); }
/* The basis label is load-bearing honesty: it says WHY this is next. With no
   due dates in the data it reads "next in the book", never "due soon". */
.home-basis { font-size: 12px; color: var(--muted); white-space: nowrap; }
.home-hero-title { margin: 0 0 var(--space-3); font-size: 20px; line-height: 1.3; }
.home-progress { margin: 0 0 var(--space-8); font-size: 14px; color: var(--muted); }
.home-actions { display: flex; align-items: center; gap: var(--space-12); flex-wrap: wrap; margin: 0; }
.home-alt { font-size: 13px; color: var(--muted); text-decoration: none; }
.home-alt:hover { color: var(--fg); text-decoration: underline; }

.home-then {
  margin: var(--space-16) 0 var(--space-4);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}
.home-others { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border); }
.home-other {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto;
  gap: var(--space-6);
  align-items: baseline;
  padding: var(--space-6) var(--space-2);
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.home-other-course { color: var(--muted); text-decoration: none; }
.home-other-course:hover { color: var(--fg); }
.home-other-step { color: var(--fg); text-decoration: none; min-width: 0; }
.home-other-step:hover { text-decoration: underline; }
.home-other-state { color: var(--muted); font-size: 13px; white-space: nowrap; }
.home-footnote { margin-top: var(--space-8); font-size: 13px; }

.home-empty {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-16);
}
.home-course-links { list-style: none; margin: var(--space-8) 0 0; padding: 0; }
.home-course-links li { margin-bottom: var(--space-3); }

@media (max-width: 640px) {
  .home-other { grid-template-columns: 1fr; gap: var(--space-1); }
}

/* ---- In-book cross references -------------------------------------------- */
/* Links the book wrote as notebook paths, repointed at our URLs at render time
   (courses/booklinks.py). `.is-missing` is one we could not resolve: shown as
   plain text, because a link that does nothing when clicked reads as the
   reader's mistake. */
.ke-book-link { color: var(--accent); }
.ke-book-link.is-missing {
  color: var(--muted);
  text-decoration: underline dotted;
  cursor: help;
}
.problem-intro {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel);
  padding: var(--space-5) var(--space-8);
  margin-bottom: var(--space-6);
}
.problem-intro > summary { color: var(--muted); font-size: 13px; cursor: pointer; }

/* ---- The book as a spine ------------------------------------------------- */
.course-page { max-width: 900px; }
.course-blurb { color: var(--muted); margin-top: 0; }
.course-next {
  background: var(--info-bg);
  color: var(--info-fg);
  padding: var(--space-5) var(--space-8);
  border-radius: var(--radius-md);
  margin: var(--space-8) 0;
}
.course-next a { color: inherit; font-weight: 600; }
.course-footnote { margin-top: var(--space-12); font-size: 13px; }
.spine-heading, .hw-heading {
  margin: var(--space-16) 0 var(--space-4);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.spine { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border); }
.spine-row { border-bottom: 1px solid var(--border); }
.spine-row.is-next { background: var(--info-bg); }
.spine-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-6) var(--space-4);
}
/* The glyph, not colour alone, carries done/skipped — colour-blind readers and
   greyscale printouts both need it. */
.spine-mark { flex: 0 0 auto; width: 14px; color: var(--muted); font-size: 13px; }
.is-done > .spine-head > .spine-mark { color: var(--ok); }
.spine-title { color: var(--fg); text-decoration: none; font-weight: 500; min-width: 0; }
.spine-title:hover { text-decoration: underline; }
.spine-num { color: var(--muted); font-variant-numeric: tabular-nums; }
.is-done .spine-title, .is-skipped .spine-title { color: var(--muted); font-weight: 400; }
.spine-meta { margin-left: auto; color: var(--muted); font-size: 13px; white-space: nowrap; }
.spine-mark-form { display: flex; gap: var(--space-6); margin: 0; flex: 0 0 auto; }
.spine-action { font-size: 12px; margin-left: 0; opacity: 0; transition: opacity 0.1s; }
.spine-row:hover .spine-action,
.spine-action:focus-visible { opacity: 1; }
.badge-next {
  background: var(--accent);
  color: #fff;
  border-radius: var(--radius-pill);
  padding: 1px var(--space-4);
  font-size: 11px;
  font-weight: 600;
}

.spine-details { padding: 0 var(--space-4) var(--space-5) 30px; }
.spine-summary { color: var(--muted); font-size: 12px; cursor: pointer; list-style: revert; }
.spine-summary::marker { color: var(--border); }
.spine-hw, .spine-sub {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  font-size: 14px;
}
.spine-hw.is-next .spine-hw-title { font-weight: 600; }
.spine-hw-title { min-width: 0; }
.spine-parts { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.spine-sub { color: var(--muted); font-size: 13px; }

.grade-list { list-style: none; margin: 0; padding: 0; }
.grade-row {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel);
  padding: var(--space-8);
  margin-bottom: var(--space-6);
}
.grade-row-head { display: flex; justify-content: space-between; gap: var(--space-6); align-items: baseline; }
.grade-where { color: var(--fg); text-decoration: none; font-weight: 600; }
.grade-where:hover { text-decoration: underline; }

@media (max-width: 640px) {
  .spine-head { flex-wrap: wrap; }
  .spine-meta { margin-left: 0; }
  .spine-action { opacity: 1; }
}

/* Editor layout */
.editor-page {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: var(--space-6);
  height: calc(100vh - var(--chrome-h));
}
/* A live Java debug session swaps the split: the stack/heap diagram is much
   wider than a variables table, and at the default 1.4fr/1fr an ordinary 1500px
   desktop left the heap column — every arrow endpoint — off-screen behind a
   horizontal scroll. The editor stays visible and readable; it just stops being
   the larger half while the picture is the thing being looked at. */
.editor-page.java-debugging { grid-template-columns: 1fr 1.6fr; }

.editor-pane, .output-pane {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.pane-header {
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  color: var(--muted);
}
.pane-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }

/* Code editor */
.code-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  background: var(--panel-2);
  display: flex;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  line-height: 1.5;
}
.gutter {
  width: 46px;
  flex-shrink: 0;
  background: #f3f5fa;
  border-right: 1px solid var(--border);
  padding: var(--space-6) 0;
  overflow: hidden;
  user-select: none;
  color: #8b95a7;
}
.gutter .ln {
  height: 21px;
  padding: 0 var(--space-4) 0 var(--space-2);
  text-align: right;
  cursor: pointer;
  white-space: nowrap;
}
.gutter .ln:hover { background: #e3e8f1; }
.gutter .ln.bp {
  background: #fee2e2;
  color: #b91c1c;
  font-weight: 700;
  position: relative;
}
.gutter .ln.bp::before {
  content: '●';
  position: absolute;
  left: 4px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 10px;
  color: #dc2626;
}
.code-area {
  position: relative;
  flex: 1;
  min-width: 0;
}
.current-line {
  position: absolute;
  left: 0; right: 0;
  height: 21px;
  background: rgba(250, 204, 21, 0.28);
  border-top: 1px solid rgba(245, 158, 11, 0.6);
  border-bottom: 1px solid rgba(245, 158, 11, 0.6);
  pointer-events: none;
  z-index: 0;
  transition: top 0.08s ease-out;
}
.code-highlight, .code-input {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: var(--space-6);
  border: 0;
  white-space: pre;
  overflow: auto;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  tab-size: 4;
}
.code-highlight {
  pointer-events: none;
  color: var(--fg);
  background: transparent;
  z-index: 1;
}
.code-input {
  color: transparent;
  caret-color: var(--fg);
  background: transparent;
  resize: none;
  outline: none;
  z-index: 2;
}

.hint { font-size: 12px; color: var(--muted); margin-left: auto; align-self: center; }

.debug-panel {
  border-top: 1px solid var(--border);
  padding: var(--space-5) var(--space-6);
  background: #fbfcfe;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.debug-bar { display: flex; gap: var(--space-3); align-items: center; }
.debug-status {
  margin-left: auto;
  font-size: 12px;
  color: var(--muted);
  padding: var(--space-1) var(--space-4);
  border-radius: var(--radius-pill);
  background: #eef1f6;
  border: 1px solid var(--border);
}
.debug-status.running  { background: #dcfce7; border-color: #86efac; color: #14532d; }
.debug-status.paused   { background: #fef3c7; border-color: #fcd34d; color: #78350f; }
.debug-status.done     { background: #e0e7ff; border-color: #a5b4fc; color: #3730a3; }
.debug-status.error    { background: #fee2e2; border-color: #fca5a5; color: #7f1d1d; }
.debug-info { font-size: 13px; color: var(--fg); }
.debug-info b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.vars-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.vars-table th, .vars-table td {
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}
.vars-table th { color: var(--muted); font-weight: 500; background: #f3f5fa; }
.vars-table td.name  { color: #1d4ed8; font-weight: 600; }
.vars-table td.type  { color: #6b7280; }
.vars-table td.val   { color: var(--fg); white-space: pre-wrap; word-break: break-all; }
.vars-table td.val.changed { background: #fef3c7; }
.vars-table tr.empty td { text-align: center; color: var(--muted); padding: var(--space-5); }
.code-input::selection { background: rgba(37, 99, 235, 0.25); color: transparent; }

/* Python syntax tokens */
.tok-keyword { color: var(--keyword); font-weight: 600; }
.tok-string  { color: var(--string); }
.tok-number  { color: var(--number); }
.tok-comment { color: var(--comment); font-style: italic; }
.tok-builtin { color: var(--builtin); }
.tok-def     { color: var(--def); }

.controls { padding: var(--space-4) var(--space-6); display: flex; gap: var(--space-4); border-top: 1px solid var(--border); }
/* .btn works on <button>, <a>, and <label> (file-upload control), so link- and
   label-buttons render identically — element-qualifying it silently left the
   app's many <a class="btn"> links unstyled. */
.btn {
  display: inline-block;
  background: var(--accent);
  color: #ffffff;
  border: 0;
  padding: var(--space-4) var(--space-7);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.btn.secondary { background: #eef1f6; color: var(--fg); border: 1px solid var(--border); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

.output {
  flex: 1;
  padding: var(--space-6);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  white-space: pre-wrap;
  overflow: auto;
  color: var(--fg);
}
.output .err { color: var(--error); }

.status-bar {
  font-size: 12px;
  color: var(--muted);
  padding: var(--space-3) var(--space-6);
  border-top: 1px solid var(--border);
  display: flex;
  gap: var(--space-6);
}
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); margin-right: var(--space-3); }
.dot.ok { background: var(--ok); }
.dot.warn { background: #f59e0b; }
.dot.err { background: var(--error); }

/* Auth + listing */
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-9);
  max-width: 420px;
  margin: var(--space-12) auto;
}
.card h1 { margin-top: 0; font-size: 18px; }
.card label { display: block; margin-top: var(--space-5); font-size: 13px; color: var(--muted); }
.card input[type="text"],
.card input[type="password"],
.card input[type="email"] {
  width: 100%;
  padding: var(--space-4) var(--space-5);
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: var(--radius-sm);
  font-size: 14px;
}
.card .help, .card .errorlist {
  font-size: 12px;
  color: var(--muted);
  margin: var(--space-2) 0 0;
  padding-left: var(--space-8);
}
.card .errorlist { color: var(--error); list-style: disc; }
.card .actions { margin-top: var(--space-7); display: flex; gap: var(--space-4); align-items: center; }

table.sessions {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
table.sessions th, table.sessions td {
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: 13px;
}
table.sessions th { color: var(--muted); font-weight: 500; }
table.sessions a { color: var(--accent); text-decoration: none; }

/* Replay */
.replay-page { display: grid; grid-template-rows: auto 1fr; gap: var(--space-6); height: calc(100vh - var(--chrome-h)); }
.replay-controls { display: flex; gap: var(--space-4); align-items: center; }
.replay-controls input[type=range] { flex: 1; }

/* Phase 1 trace-debugger controls */
.dbg-scrubber { width: 100%; margin: var(--space-4) 0 var(--space-2); display: block; }
.dbg-pos { font-size: 12px; color: #6b7280; margin: 0 var(--space-3); align-self: center; }

/* Phase 2 stack/heap visualizer */
.dbg-viz { margin-top: var(--space-5); overflow: auto; max-height: 360px; border-top: 1px solid #e5e7eb; padding-top: var(--space-4); }
.ke-viz-wrap { position: relative; min-height: 20px; }
.ke-arrows { position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible; z-index: 0; }
.ke-frame, .ke-obj { position: absolute; z-index: 1; background: #fff; border: 1px solid #cbd5e1;
  border-radius: var(--radius-sm); padding: var(--space-2) 7px; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  box-shadow: 0 1px 2px rgba(0,0,0,.06); }
.ke-frame { background: #f8fafc; }
.ke-obj-title { font-weight: 600; color: #64748b; font-size: 10px; text-transform: uppercase;
  letter-spacing: .03em; margin-bottom: 3px; }
.ke-kv { white-space: nowrap; }
.ke-vname { color: var(--accent); }
.ke-attr { color: #7c3aed; }
.ke-arrow-sep { color: #94a3b8; margin: 0 var(--space-2); }
.ke-prim { color: #0f766e; }
.ke-ref { color: #dc2626; font-size: 10px; vertical-align: middle; }
.ke-cells { display: flex; }
.ke-cell { border: 1px solid #e2e8f0; padding: 1px var(--space-3); text-align: center; min-width: 16px; }
.ke-idx { font-size: 9px; color: #94a3b8; }
.ke-more, .ke-empty { color: #94a3b8; font-style: italic; }
.ke-arrow { fill: none; stroke: #dc2626; stroke-width: 1.5; opacity: .8; }
.ke-arrowhead { fill: #dc2626; }

/* Pyodide debugger: highlight the current (innermost) call-stack frame */
.ke-frame-current { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 1px 2px rgba(0,0,0,.06); }

/* Reference-semantics view: scalar (int/str) heap boxes, garbage objects, toggle */
.ke-scalar { background: #ecfeff; padding: 3px 9px; }
.ke-scalar-val { color: #0f766e; font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
.ke-garbage { opacity: .5; border-style: dashed; }
.ke-garbage-tag { font-size: 9px; color: #b91c1c; font-style: italic; margin-top: var(--space-1); }
.dbg-toggle { font-size: 12px; color: #6b7280; margin-left: var(--space-7); cursor: pointer; user-select: none; }
.dbg-toggle input { vertical-align: middle; margin-right: var(--space-2); }

/* Pyodide first-load spinner overlay */
.pane-body { position: relative; }
.dbg-loading { position: absolute; inset: 0; z-index: 20; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--space-8); background: rgba(255,255,255,.94); }
.dbg-spinner { width: 44px; height: 44px; border: 4px solid #dbeafe; border-top-color: var(--accent);
  border-radius: var(--radius-round); animation: ke-spin .8s linear infinite; }
@keyframes ke-spin { to { transform: rotate(360deg); } }
.dbg-loading-text { font-size: 14px; font-weight: 600; color: #374151; text-align: center; }
.dbg-loading-text span { display: block; margin-top: 5px; font-weight: 400; font-size: 12px; color: #6b7280; }

/* Heap object collapse/expand */
.ke-caret { display: inline-block; width: 11px; color: #94a3b8; font-size: 10px; }
.ke-clickable { cursor: pointer; user-select: none; }
.ke-clickable:hover { color: var(--accent); }
.ke-collapsed { color: #94a3b8; font-style: italic; font-size: 11px; padding: 3px var(--space-3); white-space: nowrap; }

/* ── Java advisories (java-mode.js renderAdvisories) ───────────────────────
   Divergences from a real JVM that the transpiler can name statically — today
   32-bit int overflow. Amber, never red: nothing has failed, the number is
   simply not the one Java would print. Shown on Run as well as Debug, because
   Run is where a student meets it. Uses the same width pinning as .dbg-java so
   a long message scrolls inside itself rather than widening the page. */
.java-advisories {
  width: 0; min-width: 100%;
  padding: var(--space-2) var(--space-4) 0;
  display: flex; flex-direction: column; gap: var(--space-1);
}
.java-advisory {
  display: flex; align-items: baseline; gap: var(--space-2);
  font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: #fffbeb; border: 1px solid #fcd34d; border-radius: var(--radius-sm);
  color: #92400e; padding: var(--space-2) var(--space-3);
}
.java-advisory-tag {
  flex: 0 0 auto; font-weight: 600; text-transform: uppercase;
  font-size: 10px; letter-spacing: .06em; opacity: .8;
}
.java-advisory-msg { min-width: 0; overflow-wrap: anywhere; }
:root[data-theme="dark"] .java-advisory {
  background: #2a2011; border-color: #7c5b12; color: #fcd34d;
}

/* ── Java stack/heap variables view (java-vars.js) ─────────────────────────
   Java problems only; the Python table above is untouched. Every colour goes
   through a --jv-* custom property so the dark theme is a token swap at the
   bottom of this file rather than a parallel set of selectors. */
.dbg-java {
  --jv-surface: var(--panel);
  --jv-surface-2: #f8fafc;
  --jv-line: #cbd5e1;
  --jv-head: var(--muted);
  --jv-type: #7c3aed;      /* declared type — the compiler's half */
  --jv-name: var(--accent);
  --jv-lit: #0f766e;       /* primitive values living in the frame slot */
  --jv-str: #b45309;
  --jv-ref: #dc2626;       /* references and the arrows they draw */
  --jv-cls: #0f766e;
  --jv-null: #94a3b8;
  --jv-faint: #94a3b8;
  margin-top: var(--space-5);
  border-top: 1px solid var(--jv-line);
  padding-top: var(--space-4);
  overflow: auto;
  max-height: 420px;
  /* The workbench sizes .output-pane to its content (a grid item with the
     default min-width:auto, holding column flexboxes all the way down), so a
     wide diagram would push the whole PAGE wider instead of scrolling. Pinning
     the width to zero and re-inflating with a percentage min-width makes this
     box track its parent's width exactly and keeps the overflow inside itself.
     Measured: without this, a 375px viewport grew to 728px of page scroll. */
  width: 0;
  min-width: 100%;
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  /* so .jv-cols can size its gap against THIS box, not the viewport */
  container: dbgjava / inline-size;
}
.jv-wrap { position: relative; width: max-content; }
.jv-arrows { position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible; z-index: 0; }
/* Two columns, side by side at any width. On a narrow viewport the container
   scrolls horizontally rather than reflowing — an arrow that has to wrap to the
   next line stops being a picture of a pointer. */
/* The gap is the horizontal run the arrows are drawn across, so it wants to be
   generous — but not at the price of pushing the heap column, and with it every
   arrowhead, off the right edge of the pane. .dbg-java is an inline-size
   container (above), so the gap can answer to the PANE's width rather than the
   viewport's: full run when the diagram fits, tightened when it is the last
   20px standing between the student and the picture. */
.jv-cols {
  display: flex; align-items: flex-start; position: relative; z-index: 1;
  gap: var(--space-20);
}
@container dbgjava (max-width: 860px) { .jv-cols { gap: var(--space-9); } }
.jv-col { display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }
.jv-stack { flex: 0 0 auto; }
.jv-heap { flex: 0 0 auto; }
.jv-colhead {
  margin: 0 0 var(--space-1); font: 600 10px/1.4 -apple-system, system-ui, sans-serif;
  letter-spacing: .08em; text-transform: uppercase; color: var(--jv-head);
  border-bottom: 1px solid var(--jv-line); padding-bottom: var(--space-2);
}

.jv-frame, .jv-obj {
  background: var(--jv-surface); border: 1px solid var(--jv-line);
  border-radius: var(--radius-sm); padding: var(--space-3) var(--space-4);
}
.jv-frame { background: var(--jv-surface-2); }
.jv-frame-current { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/* Class and method names keep their own casing — HashMap is not HASHMAP. Only
   the two column headings are set in small caps. */
.jv-frame-title, .jv-obj-head {
  font: 600 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--jv-head); margin-bottom: var(--space-2); white-space: nowrap;
}
.jv-slots { border-collapse: collapse; }
.jv-slots td { padding: 1px var(--space-2) 1px 0; white-space: nowrap; vertical-align: baseline; }
.jv-type { color: var(--jv-type); }
.jv-name { color: var(--jv-name); font-weight: 600; }
.jv-eq { color: var(--jv-faint); }
.jv-lit { color: var(--jv-lit); }
.jv-lit-str { color: var(--jv-str); }
.jv-null { color: var(--jv-null); font-style: italic; }
.jv-value.jv-changed { background: var(--warning-bg); border-radius: var(--radius-sm); padding: 0 var(--space-2); }
.jv-empty, .jv-elided, .jv-legend { color: var(--jv-faint); font-style: italic; }
.jv-legend {
  max-width: 340px; margin-top: var(--space-2); white-space: normal;
  font: italic 10px/1.5 -apple-system, system-ui, sans-serif;
}

/* A reference: the anchor an arrow leaves from, labelled with the runtime class
   and the identity tag so it stays readable even when the target has scrolled. */
.jv-ref { color: var(--jv-ref); cursor: default; }
.jv-ref-dash { margin-right: var(--space-1); }
.jv-ref-label { text-decoration: underline dotted; text-underline-offset: 2px; }
.jv-arrow { fill: none; stroke: var(--jv-ref); stroke-width: 1.5; opacity: .75; }
.jv-arrowhead { fill: var(--jv-ref); opacity: .75; }
.jv-arrow.jv-hot, .jv-arrowhead.jv-hot { opacity: 1; stroke-width: 2.5; }
.jv-obj.jv-hot { border-color: var(--jv-ref); box-shadow: 0 0 0 1px var(--jv-ref); }

.jv-obj-cls { color: var(--jv-cls); }
.jv-obj-id { color: var(--jv-ref); margin-left: var(--space-1); }
.jv-obj-size { color: var(--jv-faint); margin-left: var(--space-4); font-weight: 400; }
.jv-obj-body { white-space: nowrap; }
.jv-strval { color: var(--jv-str); }
.jv-boxval { color: var(--jv-lit); }
.jv-entry, .jv-field { white-space: nowrap; }
.jv-to { color: var(--jv-faint); margin: 0 var(--space-2); }
.jv-fname { color: var(--jv-name); margin: 0 var(--space-2) 0 var(--space-2); }
.jv-field .jv-eq { margin-right: var(--space-2); }
.jv-cells { display: flex; flex-wrap: wrap; }
.jv-cell {
  border: 1px solid var(--jv-line); padding: 1px var(--space-3);
  text-align: center; min-width: 20px;
}
.jv-idx { font-size: 9px; color: var(--jv-faint); }
.jv-garbage { opacity: .55; border-style: dashed; }
.jv-garbage-tag { font-size: 9px; color: var(--danger-fg); font-style: italic; }

/* An int that has left Java's 32-bit range. Amber, not red: nothing failed —
   real Java would have wrapped silently, and that silence is the lesson. */
.jv-flag {
  margin-left: var(--space-3); padding: 0 var(--space-2); border-radius: var(--radius-sm);
  background: var(--warning-bg); color: var(--warning-fg); font-size: 11px; cursor: help;
}
.jv-badge {
  margin-left: var(--space-3); padding: 0 var(--space-2); border-radius: var(--radius-pill);
  background: var(--info-bg); color: var(--info-fg); font-size: 10px; cursor: help;
}

/* ---- Courses / chapters ---- */
.course-page, .chapter-page { max-width: 820px; margin: var(--space-12) auto; padding: 0 var(--space-9); }
.course-card { border: 1px solid #e5e7eb; border-radius: var(--radius-lg); padding: var(--space-7) var(--space-9); margin: var(--space-6) 0; }
.course-card h2 { margin: 0 0 var(--space-3); font-size: 18px; }
.badge { display: inline-block; font-size: 11px; padding: var(--space-1) var(--space-4); border-radius: var(--radius-pill); }
.badge-pending { background: var(--warning-bg); color: var(--warning-fg); }
.badge-approved { background: var(--success-bg); color: var(--success-fg); }
.badge-rejected { background: var(--danger-bg); color: var(--danger-fg); }
.crumb { font-size: 13px; margin-bottom: var(--space-2); }
.crumb a { color: var(--accent); text-decoration: none; }
.chapter-list { line-height: 1.9; list-style: none; padding-left: 0; }
.enroll-status { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: var(--radius-md); padding: var(--space-5) var(--space-7); }
.muted { color: #6b7280; }
.messages { list-style: none; padding: 0; }
.messages li { padding: var(--space-4) var(--space-6); border-radius: var(--radius-sm); margin: var(--space-3) 0; background: #f1f5f9; }
.messages li.success { background: var(--success-bg); } .messages li.error { background: var(--danger-bg); }
.chapter-body { line-height: 1.65; }
.chapter-body h1, .chapter-body h2, .chapter-body h3 { line-height: 1.25; margin-top: 1.4em; }
.chapter-body .nb-code,
.chapter-body .nb-md pre { background: #0f172a; color: #e2e8f0; padding: var(--space-6) var(--space-7); border-radius: var(--radius-md);
  overflow-x: auto; font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
.chapter-body .nb-md pre code { background: none; color: inherit; padding: 0; }
.chapter-body blockquote { border-left: 3px solid #cbd5e1; margin: 1em 0; padding: var(--space-1) var(--space-7); color: #475569; }
.chapter-nav { display: flex; justify-content: space-between; gap: var(--space-6); margin: var(--space-16) 0; font-size: 14px; }
.chapter-nav a { color: var(--accent); text-decoration: none; }
/* Non-required "going further" subchapters: visually distinct, clearly optional. */
.subchapter-list { list-style: none; margin: var(--space-1) 0 var(--space-2); padding-left: var(--space-8); }
.subchapter-item { font-size: 14px; color: #475569; }
.subchapter-item a { color: var(--accent); text-decoration: none; }
.subchapters-block { margin-top: var(--space-12); padding-top: var(--space-6); border-top: 1px dashed #cbd5e1; }
.subchapters-block h2 { font-size: 18px; }
.subchapter-page .chapter-body { opacity: 0.98; }
.subchapter-badge { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin: var(--space-3) 0 var(--space-8); }
.badge-optional { display: inline-block; background: #eef2ff; color: #3730a3; border: 1px solid #c7d2fe;
  border-radius: var(--radius-sm); padding: 2px var(--space-4); font-size: 12px; font-weight: 600; }
.badge-difficulty { display: inline-block; border-radius: var(--radius-sm); padding: 2px var(--space-4);
  font-size: 12px; font-weight: 600; text-transform: capitalize; }
.badge-difficulty.small, .badge-optional.small { font-size: 11px; padding: 1px var(--space-3); }
.diff-gentle { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0; }
.diff-moderate { background: #fef9c3; color: #854d0e; border: 1px solid #fde68a; }
.diff-spicy { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
.ke-subchapter-link { font-weight: 600; }
.subchapter-also { margin: calc(-1 * var(--space-5)) 0 var(--space-8); }
.subchapter-also a { color: var(--accent); text-decoration: none; }
/* Inline answer box embedded at a pause_and_try callout (writeup_inline.js). */
.ke-pat-slot:empty { display: none; }
.writeup-inline { margin: var(--space-4) 0 var(--space-10); padding-left: var(--space-6);
  border-left: 3px solid var(--accent); }
.writeup-inline .writeup-label { margin: 0 0 var(--space-2); font-weight: 600; }

/* ---- Problem page ---- */
.problem-page { max-width: 1100px; margin: var(--space-9) auto; padding: 0 var(--space-9); }
.problem-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-6); }
.problem-head h1 { font-size: 20px; }
.part-nav a { color: var(--accent); text-decoration: none; margin-left: var(--space-6); font-size: 14px; }
.problem-prompt { border: 1px solid #e5e7eb; border-radius: var(--radius-lg); padding: var(--space-3) var(--space-8); margin: var(--space-6) 0; background: #fafafa; }
.problem-prompt > summary { cursor: pointer; font-weight: 600; padding: var(--space-3) 0; }
.prompt-body { line-height: 1.6; }
.prompt-body pre, .prompt-body code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.prompt-body pre { background: #0f172a; color: #e2e8f0; padding: var(--space-5) var(--space-6); border-radius: var(--radius-md); overflow-x: auto; }
.sample-inputs { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); margin: var(--space-5) 0; }
.problem-actions { display: flex; align-items: center; gap: var(--space-6); margin: var(--space-7) 0; }
.assignment-list { list-style: none; padding: 0; }
.assignment-list li { padding: var(--space-4) 0; border-bottom: 1px solid #f1f5f9; }
.assignment-list .parts { display: inline-flex; flex-wrap: wrap; gap: var(--space-3); margin-left: var(--space-4); }
.part-pill { font-size: 12px; padding: var(--space-1) var(--space-5); border: 1px solid #c7d2fe; border-radius: var(--radius-pill);
  color: #3730a3; text-decoration: none; background: #eef2ff; }
.test-results { margin: var(--space-6) 0 var(--space-20); }
.test-summary { font-weight: 700; padding: var(--space-4) var(--space-6); border-radius: var(--radius-md); margin-bottom: var(--space-4); }
.test-summary.all-pass { background: var(--success-bg); color: var(--success-fg); }
.test-summary.some-fail { background: var(--warning-bg); color: var(--warning-fg); }
.test-row { padding: var(--space-2) var(--space-6); border-radius: var(--radius-sm); }
.test-row.test-pass .test-name { color: var(--success-fg); }
.test-row.test-fail .test-name, .test-row.test-error .test-name { color: var(--danger-fg); }
.test-name { font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
.test-msg { background: #fef2f2; color: #7f1d1d; padding: var(--space-4) var(--space-5); border-radius: var(--radius-sm); margin: var(--space-2) 0 var(--space-4);
  font: 12px/1.45 ui-monospace, monospace; white-space: pre-wrap; overflow-x: auto; }
.test-error { background: var(--danger-bg); color: var(--danger-fg); padding: var(--space-4) var(--space-6); border-radius: var(--radius-md); }

/* ---- Submissions (instructor + submit UI) ---- */
.btn.primary { background: var(--ok); color: #fff; }
.submit-status { font-size: 13px; color: var(--success-fg); }
.instructor-bar { margin: var(--space-5) 0; }
.subs-table { width: 100%; border-collapse: collapse; margin: var(--space-7) 0; font-size: 14px; }
.subs-table th, .subs-table td { text-align: left; padding: var(--space-4) var(--space-5); border-bottom: 1px solid #eef2f7; }
.subs-table th { color: #64748b; font-weight: 600; }
.sub-meta { color: #475569; font-size: 14px; }
.result-list { list-style: none; padding: 0; font: 13px/1.7 ui-monospace, monospace; }
.result-list .test-pass { color: var(--success-fg); }
.result-list .test-fail, .result-list .test-error { color: var(--danger-fg); }
.badge-submitted { background: var(--success-bg); color: var(--success-fg); }
.badge-draft { background: var(--warning-bg); color: var(--warning-fg); }
.badge-graded { background: var(--info-bg); color: var(--info-fg); }

/* ---- Guardrailed tutor ---- */
.tutor-panel { border: 1px solid #e5e7eb; border-radius: var(--radius-lg); padding: var(--space-4) var(--space-8); margin: var(--space-7) 0 var(--space-20); background: #fffbeb; }
.tutor-panel > summary { cursor: pointer; font-weight: 600; padding: var(--space-3) 0; }
.tutor-q { width: 100%; box-sizing: border-box; font: 14px/1.5 inherit; padding: var(--space-4); border: 1px solid #d1d5db; border-radius: var(--radius-md); margin: var(--space-3) 0; }
.tutor-reply { white-space: pre-wrap; margin-top: var(--space-5); line-height: 1.55; color: #1f2937; }
.tutor-turn { border-left: 3px solid #fcd34d; padding: var(--space-2) var(--space-6); margin: var(--space-5) 0; }
.tutor-a { white-space: pre-wrap; color: #374151; margin: var(--space-2) 0; }
.small { font-size: 11px; }

/* ---- Similarity (winnowing) ---- */
.subs-table tr.sim-high td { background: var(--danger-bg); }
.subs-table tr.sim-mid td { background: var(--warning-bg); }

/* ---- View-as-student preview ---- */
.student-view-banner { background: #1e293b; color: #fef9c3; text-align: center; padding: var(--space-3) var(--space-6);
  font-size: 13px; display: flex; gap: var(--space-5); align-items: center; justify-content: center; }
.student-view-banner .linkbutton { color: #fde68a; text-decoration: underline; }

/* ---- Teacher self-service ---- */
.manage-section { border-top: 1px solid #e5e7eb; padding: var(--space-7) 0; }
.manage-actions { display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap; }
.roster-row { display: flex; justify-content: space-between; align-items: center; gap: var(--space-6);
  padding: var(--space-3) 0; border-bottom: 1px solid #f1f5f9; }
.course-edit-form p { margin: var(--space-4) 0; }
.course-edit-form label { display: inline-block; min-width: 150px; font-weight: 600; font-size: 13px; vertical-align: top; }
.course-edit-form input[type=text], .course-edit-form textarea {
  width: 360px; max-width: 100%; padding: var(--space-3) var(--space-4); border: 1px solid #d1d5db; border-radius: var(--radius-sm); }

/* ---- Assignment scheduling ---- */
.assign-row { display: flex; align-items: center; gap: var(--space-5); padding: var(--space-3) 0;
  border-bottom: 1px solid #f1f5f9; flex-wrap: wrap; }
.assign-title { flex: 1; min-width: 200px; }
.assign-row select, .assign-row input[type=date] { padding: var(--space-2) var(--space-3); border: 1px solid #d1d5db; border-radius: var(--radius-sm); }
.due { color: #b45309; font-size: 12px; }

/* ---- Writeup (no-code) problems: rich-text editor ---- */
.writeup-label { display: block; font-weight: 600; margin: var(--space-8) 0 var(--space-2); }
.writeup-prompt { line-height: 1.6; }
.writeup-editor { border: 1px solid #d1d5db; border-radius: var(--radius-md); overflow: hidden; }
.writeup-toolbar { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center;
  padding: var(--space-3) var(--space-4); background: #f9fafb; border-bottom: 1px solid #e5e7eb; }
.writeup-toolbar button { min-width: 30px; height: 28px; padding: 0 var(--space-4); cursor: pointer;
  border: 1px solid transparent; background: transparent; border-radius: 5px;
  font-size: 13px; color: #374151; }
.writeup-toolbar button:hover { background: #e5e7eb; }
.writeup-toolbar .sep { width: 1px; height: 18px; background: #d1d5db; margin: 0 var(--space-2); }
.writeup-area { min-height: 220px; padding: var(--space-6) var(--space-7); font: 15px/1.6 inherit; outline: none; }
.writeup-area:empty:before { content: attr(data-placeholder); color: #9ca3af; }
.writeup-area p { margin: 0 0 var(--space-4); }
.writeup-area blockquote { margin: var(--space-4) 0; padding-left: var(--space-6);
  border-left: 3px solid #d1d5db; color: #4b5563; }
.capture-status { font-size: 12px; }
.nb-writeup { line-height: 1.6; }
.rich-replay { height: 100%; box-sizing: border-box; padding: var(--space-7) var(--space-8); overflow: auto;
  background: var(--surface, #fff); line-height: 1.6; }

/* ---- Assignment kind tag ---- */
.badge-kind { background: var(--info-bg); color: var(--info-fg); }

/* ---- Grading: student grade panel + instructor controls ---- */
.grade-panel { border: 1px solid var(--border); border-left: 3px solid var(--ok); border-radius: var(--radius-md);
  padding: 10px 14px; margin: 12px 0; background: var(--surface-subtle, #f9fafb); }
.grade-head { display: flex; align-items: center; gap: 10px; }
.grade-score { font-size: 16px; }
.grade-feedback { margin-top: 8px; line-height: 1.6; }
.grade-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 8px 0 12px; }
.grade-upload-form { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; }
.file-label { position: relative; overflow: hidden; }
.file-label input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.grade-form-wrap { border: 1px solid var(--border); border-radius: var(--radius-md); padding: 8px 14px; margin: 14px 0; }
.grade-form-wrap > summary { cursor: pointer; font-weight: 600; padding: 4px 0; }
.grade-form { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; margin-top: 8px; }
.grade-form label.block { display: block; width: 100%; font-size: 13px; color: var(--muted); }
.grade-form input[type=number] { padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--panel-2); color: var(--fg); width: 120px; }

/* ---- Progress + lateness ---- */
.resumed-note { margin: 0 0 var(--space-4); font-size: 13px; }
.badge-done { background: var(--success-bg); color: var(--success-fg); }
.badge-late { background: var(--danger-bg); color: var(--danger-fg); }
.prog { font-size: 13px; color: #374151; margin-left: var(--space-2); }
.due-over { color: #b91c1c; font-weight: 600; }

/* ============================================================================
   Dark theme — append-only. Light-mode rules above are untouched (so light mode
   is byte-for-byte unchanged). Theme is applied by setting data-theme on <html>
   pre-paint (base.html head script): "dark"/"light" from localStorage, else the
   OS prefers-color-scheme. Most of the UI is token-driven, so the bulk is a
   token swap; the override rules below cover raw-hex holdouts that a future
   structural-color tokenization slice can fold back into tokens.
   ========================================================================== */
:root { color-scheme: light; }

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0f1117;
  --panel: #161b22;
  --panel-2: #161b22;
  --fg: #e6edf3;
  --muted: #9198a1;
  --border: #2a3038;
  --accent: #4493f8;
  --keyword: #79c0ff;
  --string: #a5d6ff;
  --number: #d2a8ff;
  --comment: #8b949e;
  --builtin: #56d4bb;
  --def: #d2a8ff;
  --error: #f85149;
  --ok: #3fb950;
  --success-bg: #11281b; --success-fg: #56d364;
  --warning-bg: #2a2410; --warning-fg: #e3b341;
  --danger-bg:  #2d1517; --danger-fg:  #f85149;
  --info-bg:    #122740; --info-fg:    #79c0ff;
  /* dark-only structural helpers (cover the raw-hex holdouts below) */
  --surface-subtle: #1b222c;
  --surface-muted:  #21262d;
  --surface-hover:  #262d38;
  --text-faint:     #6e7681;
}

/* Raw-hex holdouts: borders → token */
:root[data-theme="dark"] .dbg-viz,
:root[data-theme="dark"] .course-card,
:root[data-theme="dark"] .problem-prompt,
:root[data-theme="dark"] .enroll-status,
:root[data-theme="dark"] .tutor-panel,
:root[data-theme="dark"] .tutor-q,
:root[data-theme="dark"] .manage-section,
:root[data-theme="dark"] .roster-row,
:root[data-theme="dark"] .assignment-list li,
:root[data-theme="dark"] .assign-row,
:root[data-theme="dark"] .assign-row select,
:root[data-theme="dark"] .assign-row input[type=date],
:root[data-theme="dark"] .subs-table th,
:root[data-theme="dark"] .subs-table td,
:root[data-theme="dark"] .course-edit-form input[type=text],
:root[data-theme="dark"] .course-edit-form textarea,
:root[data-theme="dark"] .writeup-editor,
:root[data-theme="dark"] .writeup-toolbar,
:root[data-theme="dark"] .writeup-area blockquote,
:root[data-theme="dark"] .chapter-body blockquote,
:root[data-theme="dark"] .ke-cell,
:root[data-theme="dark"] .ke-frame,
:root[data-theme="dark"] .ke-obj { border-color: var(--border); }

:root[data-theme="dark"] .writeup-toolbar .sep { background: var(--border); }

/* Raw-hex holdouts: subtle surfaces → token */
:root[data-theme="dark"] .gutter,
:root[data-theme="dark"] .debug-panel,
:root[data-theme="dark"] .vars-table th,
:root[data-theme="dark"] .enroll-status,
:root[data-theme="dark"] .problem-prompt,
:root[data-theme="dark"] .tutor-panel,
:root[data-theme="dark"] .ke-frame,
:root[data-theme="dark"] .ke-scalar,
:root[data-theme="dark"] .writeup-toolbar { background: var(--surface-subtle); }
:root[data-theme="dark"] .ke-obj { background: var(--panel); }
:root[data-theme="dark"] .debug-status,
:root[data-theme="dark"] .btn.secondary { background: var(--surface-muted); }
:root[data-theme="dark"] .gutter .ln:hover,
:root[data-theme="dark"] .writeup-toolbar button:hover { background: var(--surface-hover); }

/* Messages keep their status tint; only the neutral default becomes dark */
:root[data-theme="dark"] .messages li { background: var(--surface-subtle); }
:root[data-theme="dark"] .messages li.success { background: var(--success-bg); }
:root[data-theme="dark"] .messages li.error { background: var(--danger-bg); }

/* Code blocks were navy on white; on a dark page lift them to a readable block */
:root[data-theme="dark"] .chapter-body .nb-code,
:root[data-theme="dark"] .chapter-body .nb-md pre,
:root[data-theme="dark"] .prompt-body pre { background: var(--surface-subtle); color: var(--fg); }
:root[data-theme="dark"] .rich-replay { background: var(--panel); }

/* Raw-hex holdouts: text greys → token */
:root[data-theme="dark"] .muted,
:root[data-theme="dark"] .dbg-pos,
:root[data-theme="dark"] .dbg-toggle,
:root[data-theme="dark"] .dbg-loading-text span,
:root[data-theme="dark"] .vars-table td.type,
:root[data-theme="dark"] .ke-obj-title,
:root[data-theme="dark"] .sub-meta,
:root[data-theme="dark"] .subs-table th,
:root[data-theme="dark"] .chapter-body blockquote,
:root[data-theme="dark"] .tutor-a,
:root[data-theme="dark"] .prog,
:root[data-theme="dark"] .writeup-area blockquote { color: var(--muted); }

:root[data-theme="dark"] .dbg-loading-text,
:root[data-theme="dark"] .tutor-reply,
:root[data-theme="dark"] .writeup-toolbar button { color: var(--fg); }

:root[data-theme="dark"] .gutter,
:root[data-theme="dark"] .ke-arrow-sep,
:root[data-theme="dark"] .ke-idx,
:root[data-theme="dark"] .ke-more,
:root[data-theme="dark"] .ke-empty,
:root[data-theme="dark"] .ke-caret,
:root[data-theme="dark"] .ke-collapsed { color: var(--text-faint); }
:root[data-theme="dark"] .writeup-area:empty:before { color: var(--text-faint); }

:root[data-theme="dark"] .vars-table td.name { color: var(--keyword); }
:root[data-theme="dark"] .ke-attr { color: var(--number); }
:root[data-theme="dark"] .ke-prim,
:root[data-theme="dark"] .ke-scalar-val { color: var(--builtin); }
:root[data-theme="dark"] .ke-ref,
:root[data-theme="dark"] .ke-garbage-tag { color: var(--danger-fg); }
:root[data-theme="dark"] .ke-arrow { stroke: var(--danger-fg); }
:root[data-theme="dark"] .ke-arrowhead { fill: var(--danger-fg); }

/* Status-bearing holdouts that used their own darker palette */
:root[data-theme="dark"] .vars-table td.val.changed { background: var(--warning-bg); }
:root[data-theme="dark"] .gutter .ln.bp { background: var(--danger-bg); color: var(--danger-fg); }
:root[data-theme="dark"] .gutter .ln.bp::before { color: var(--danger-fg); }
:root[data-theme="dark"] .test-msg { background: var(--danger-bg); color: var(--danger-fg); }
:root[data-theme="dark"] .tutor-turn { border-left-color: var(--warning-fg); }
:root[data-theme="dark"] .due { color: var(--warning-fg); }
:root[data-theme="dark"] .due-over { color: var(--danger-fg); }
:root[data-theme="dark"] .part-pill {
  background: var(--info-bg); color: var(--info-fg); border-color: transparent;
}

/* Debug-status pills: re-point the four states at status tokens */
:root[data-theme="dark"] .debug-status.running { background: var(--success-bg); border-color: var(--success-fg); color: var(--success-fg); }
:root[data-theme="dark"] .debug-status.paused  { background: var(--warning-bg); border-color: var(--warning-fg); color: var(--warning-fg); }
:root[data-theme="dark"] .debug-status.done    { background: var(--info-bg);    border-color: var(--info-fg);    color: var(--info-fg); }
:root[data-theme="dark"] .debug-status.error   { background: var(--danger-bg);  border-color: var(--danger-fg);  color: var(--danger-fg); }

/* Java stack/heap view: one token swap, since java-vars' colours all route
   through --jv-* on .dbg-java. */
:root[data-theme="dark"] .dbg-java {
  --jv-surface: var(--panel);
  --jv-surface-2: var(--surface-subtle);
  --jv-line: var(--border);
  --jv-head: var(--muted);
  --jv-type: var(--number);
  --jv-name: var(--keyword);
  --jv-lit: var(--builtin);
  --jv-str: var(--string);
  --jv-ref: var(--danger-fg);
  --jv-cls: var(--builtin);
  --jv-null: var(--text-faint);
  --jv-faint: var(--text-faint);
}

/* Pyodide first-load overlay + spinner */
:root[data-theme="dark"] .dbg-loading { background: rgba(13, 17, 23, 0.94); }
:root[data-theme="dark"] .dbg-spinner { border-color: var(--border); border-top-color: var(--accent); }

/* Topbar theme toggle */
.theme-toggle { font-size: 15px; line-height: 1; }

/* Bulk scheduling on the manage page — the deployed box has no shell most
   days, so the only way to make a freshly synced 48-assignment course real
   has to be in the browser. */
.bulk-assign {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6);
  margin-bottom: var(--space-8);
  background: var(--surface-subtle, var(--bg));
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: 13px;
}
.bulk-label { color: var(--muted); }

/* ---- Needs grading ------------------------------------------------------- */
.grading-lede { font-size: 15px; margin-bottom: var(--space-4); }
.grading-actions {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex-wrap: wrap;
  margin-bottom: var(--space-16);
}
.grading-clear { font-size: 15px; }
.grading-groups { list-style: none; margin: 0; padding: 0; }
.grading-group { border-top: 1px solid var(--border); padding: var(--space-6) 0; }
.grading-group-head {
  display: flex;
  justify-content: space-between;
  gap: var(--space-6);
  align-items: baseline;
}
.grading-where { font-weight: 600; }
.grading-subs { list-style: none; margin: var(--space-3) 0 0; padding: 0 0 0 var(--space-8); }
.grading-subs li { padding: var(--space-2) 0; display: flex; gap: var(--space-4); align-items: baseline; }
.grading-foot { margin-top: var(--space-16); }
.grade-submit { display: flex; gap: var(--space-4); flex-wrap: wrap; margin-top: var(--space-5); }
.rail-subdivider {
  margin: var(--space-5) 0 var(--space-1);
  padding-left: var(--space-4);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-faint, var(--muted));
}

/* ---- Narrow viewports, part two -----------------------------------------
   These override rules defined further UP the file (.editor-page at ~408,
   .problem-page at ~784), so they have to come after them: a media query does
   not raise specificity, and at equal specificity the later rule wins. */
@media (max-width: 900px) {
  /* The workbench stacks. Side by side at this width the editor pane gets
     about 310px, which is not a code editor. Height goes auto rather than
     splitting one viewport between two panes and giving each half a screen. */
  .editor-page { grid-template-columns: 1fr; height: auto; }
  /* .editor-page.java-debugging (two classes) outranks .editor-page (one), and
     a media query adds no specificity — so the Java debug split survived the
     stack and pushed the debug panel off-screen (measured at 375px: page
     scrollWidth 431 -> 766). Reset it at matching specificity. */
  .editor-page.java-debugging { grid-template-columns: 1fr; }
  .editor-pane { min-height: 60vh; }
  .output-pane { min-height: 40vh; }
  .problem-page { padding: 0 var(--space-5); margin-top: var(--space-5); }
  .problem-head { flex-wrap: wrap; }
  .part-nav a:first-child { margin-left: 0; }
  .course-page, .home-page { max-width: 100%; }
}

/* ---- Sync status --------------------------------------------------------- */
.sync-status {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-8);
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  font-size: 14px;
}
.sync-status.is-running { background: var(--info-bg); color: var(--info-fg); }
.sync-status.is-ok { background: var(--success-bg); color: var(--success-fg); }
.sync-status.is-bad { background: var(--danger-bg); color: var(--danger-fg); }
.sync-output {
  display: block;
  width: 100%;
  margin-top: var(--space-3);
  font-size: 12px;
  white-space: pre-wrap;
  opacity: 0.85;
}
.sync-spinner {
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--radius-round);
  animation: sync-spin 0.8s linear infinite;
  flex: 0 0 auto;
}
@keyframes sync-spin { to { transform: rotate(360deg); } }
/* Respect a reduced-motion preference — the text already says it is running. */
@media (prefers-reduced-motion: reduce) {
  .sync-spinner { animation: none; }
}

/* ---- Scratch editor language switch -------------------------------------- */
.scratch-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.scratch-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}
.scratch-langs { display: flex; gap: var(--space-2); }
.lang-btn.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.scratch-note { flex: 1 1 auto; min-width: 0; }

/* ---- Book code cells, by language ----------------------------------------
   The OOPS book alternates Java and Kotlin to make the same point twice. A
   Kotlin cell is currently illustrative — nothing in the browser runs Kotlin
   yet — so it is marked as such rather than looking like something that failed
   to run. A left border is the whole treatment: enough to tell the two apart
   while reading, not so much that the page becomes a colour chart. */
.chapter-body .nb-code.lang-kotlin {
  border-left: 3px solid var(--number);
}
.chapter-body .nb-code.lang-java {
  border-left: 3px solid var(--builtin);
}
.nb-lang-note {
  margin: calc(-1 * var(--space-3)) 0 var(--space-8);
  font-size: 12px;
  color: var(--muted);
}

/* ---- Draft recovery bar (drafts.js) --------------------------------------
   Sits directly above the box it belongs to. Deliberately not a modal: it
   must be ignorable, because most of the time the student did not lose
   anything and the box on screen is the one they want. */
.ke-draft-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin: 0 0 .5rem; padding: .55rem .7rem;
  border: 1px solid var(--warn-border, #d8bd6a);
  border-left: 3px solid var(--warn-accent, #c9a227);
  border-radius: 4px;
  background: var(--warn-bg, #fdf8e8);
  font-size: .9rem;
}
.ke-draft-msg { flex: 1 1 18rem; }
.ke-draft-bar .btn { padding: .25rem .6rem; font-size: .85rem; }
.ke-draft-list {
  flex: 1 1 100%; margin-top: .4rem;
  /* The bar sits above a fixed-height editor pane and steals its height, so a
     student with a long history must not push the code off screen. */
  max-height: 9rem; overflow-y: auto;
}
.ke-draft-row {
  display: flex; align-items: baseline; gap: .5rem;
  padding: .35rem 0; border-top: 1px solid rgba(0,0,0,.08);
}
.ke-draft-when { white-space: nowrap; opacity: .75; font-size: .8rem; }
.ke-draft-prev {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: ui-monospace, Menlo, monospace; font-size: .78rem; opacity: .8;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ke-draft-bar {
    background: #2e2a1c; border-color: #6b5c2a; color: #e8e2cf;
  }
  :root:not([data-theme="light"]) .ke-draft-row { border-top-color: rgba(255,255,255,.12); }
}
[data-theme="dark"] .ke-draft-bar {
  background: #2e2a1c; border-color: #6b5c2a; color: #e8e2cf;
}
[data-theme="dark"] .ke-draft-row { border-top-color: rgba(255,255,255,.12); }

/* ---- To-do bell (sidebar account area) ----------------------------------
   A student had to email to say she was waiting on a course approval: the
   queue existed, but only somebody who went looking would find it. This is
   the thing that goes looking for you. */
.todo-bell { position: relative; }
.todo-toggle { display: flex; align-items: center; gap: .4rem; width: 100%; }
.todo-icon { font-size: .95em; filter: grayscale(1); opacity: .65; }
.todo-bell.has-todo .todo-icon { filter: none; opacity: 1; }
.todo-count {
  margin-left: auto; min-width: 1.25rem; padding: 0 .35rem;
  border-radius: 999px; background: #c0392b; color: #fff;
  font-size: .72rem; font-weight: 700; line-height: 1.35rem; text-align: center;
}
.todo-panel {
  position: absolute; bottom: calc(100% + .35rem); left: 0;
  min-width: 15rem; max-width: 20rem; z-index: 40;
  padding: .5rem .6rem; border: 1px solid var(--border);
  border-radius: var(--radius-md, 6px); background: var(--panel, #fff);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .18);
}
.todo-panel ul { list-style: none; margin: 0; padding: 0; }
.todo-panel li { padding: .3rem 0; }
.todo-panel li + li { border-top: 1px solid var(--border); }
.todo-panel li.urgent b { color: #c0392b; }
.todo-panel a { text-decoration: none; display: block; font-size: .85rem; }
.todo-panel a:hover { text-decoration: underline; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .todo-panel { background: #1e2430; }
}
[data-theme="dark"] .todo-panel { background: #1e2430; }

/* ---- Report a problem ---------------------------------------------------
   Reachable from every page, because the moment someone hits a bug is the
   only moment they will reliably report it. */
.report-dialog {
  position: fixed; inset: 0; z-index: 200; display: flex;
  align-items: center; justify-content: center; padding: 1rem;
  background: rgba(0, 0, 0, .45);
}
.report-box {
  width: min(34rem, 100%); max-height: 90vh; overflow-y: auto;
  padding: 1.1rem 1.25rem; border-radius: var(--radius-md, 8px);
  background: var(--panel, #fff); box-shadow: 0 18px 50px rgba(0, 0, 0, .3);
}
.report-box h2 { margin: 0 0 .3rem; font-size: 1.15rem; }
.report-field { display: block; margin: .8rem 0 0; font-size: .9rem; font-weight: 600; }
.report-field select, .report-field textarea {
  display: block; width: 100%; margin-top: .3rem; padding: .45rem .55rem;
  border: 1px solid var(--border); border-radius: 4px;
  background: var(--panel-2, #fff); color: inherit;
  font: inherit; font-weight: 400;
}
.report-field textarea { resize: vertical; }
.report-actions { display: flex; align-items: center; gap: .6rem; margin-top: .9rem; }
.report-card .report-message {
  white-space: pre-wrap; padding: .5rem .7rem; border-left: 3px solid var(--border);
  background: var(--panel-2, #f7f7f5);
}
.report-filters { display: flex; flex-wrap: wrap; gap: .4rem; margin: 1rem 0; }
.badge-open { background: #fdecea; color: #b3261e; }
.badge-acknowledged { background: #fff4e5; color: #8a5a00; }
.badge-closed { background: #eef6ee; color: #2e6b34; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .report-box { background: #1e2430; }
}
[data-theme="dark"] .report-box { background: #1e2430; }

/* ---- "Waiting on me" queue ---------------------------------------------- */
.queue-list { list-style: none; margin: .4rem 0 0; padding: 0; }
.queue-list li { padding: .35rem 0; }
.queue-list li + li { border-top: 1px solid var(--border); }
.queue-list li.urgent b { color: #c0392b; }

/* ---- Outcomes ("Where I stand") ----------------------------------------- */
.target-picker { margin: 1rem 0 1.5rem; gap: .5rem; align-items: baseline; }
.outcome-focus { border-left: 3px solid var(--border); }
.outcome-focus.ok { border-left-color: #2e6b34; }
.outcome-focus.off { border-left-color: #c0392b; }
.outcome-bundle.is-target { box-shadow: 0 0 0 2px rgba(37, 99, 235, .18); }
.outcome-list { list-style: none; margin: .5rem 0 0; padding: 0; }
.outcome-list .req { display: flex; gap: .6rem; padding: .5rem 0; }
.outcome-list .req + .req { border-top: 1px solid var(--border); }
.req-mark { flex: 0 0 1rem; font-weight: 700; line-height: 1.4; }
.req-met .req-mark { color: #2e6b34; }
.req-unmet .req-mark { color: #c0392b; }
.req-unknown .req-mark { color: #999; }
.req-body { flex: 1 1 auto; }
.req-action { margin: .25rem 0 0; font-size: .88rem; }
.req-near { margin: .3rem 0 0; padding-left: 1.1rem; font-size: .85rem; }

/* ---- Expand the editor ---------------------------------------------------
   Reported in class: "the horizontal scroll bar was at the very bottom of a
   giant text box and they could not view their full code". The pane is sized
   to the viewport, so a long line's scrollbar sits below the fold on a page
   with a prompt above the editor. Filling the window puts the whole file — and
   its scrollbar — back on screen.

   Line-wrapping would be the other fix and is deliberately NOT offered: the
   gutter draws one fixed-height row per LOGICAL line, so wrapped lines would
   slide the numbers out of step with the code they label. */
.pane-expand {
  margin-left: auto; margin-right: var(--space-4);
  font-size: 11px; opacity: .75;
}
.pane-expand:hover { opacity: 1; }
.editor-pane.is-expanded {
  position: fixed; inset: 0; z-index: 300;
  border-radius: 0; border: 0;
  height: 100vh; width: 100vw;
}
body.editor-expanded { overflow: hidden; }
.editor-pane.is-expanded .pane-header { padding-right: var(--space-6); }

/* ---- "Submitted" said unmissably --------------------------------------
   Reported in class: students could not tell whether a homework had been
   submitted, even after clicking Submit. The only signal was small grey text
   beside the button, weighted identically to the text already there. */
.submitted-banner {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem;
  margin: 0 0 .6rem; padding: .5rem .75rem;
  border: 1px solid #bcdcc2; border-left: 3px solid #2e6b34;
  border-radius: 4px; background: #eef6ee; font-size: .9rem;
}
.submitted-banner .tick { color: #2e6b34; font-weight: 700; }
.submitted-banner.just-now { animation: submitted-flash 2.4s ease-out; }
@keyframes submitted-flash {
  0%, 30% { background: #cdeacf; }
  100% { background: #eef6ee; }
}
.test-source { margin: .8rem 0; }
.test-source > summary { cursor: pointer; font-weight: 600; }
.test-source pre { max-height: 28rem; overflow: auto; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .submitted-banner {
    background: #1e2b20; border-color: #3c5c42; color: #d7e8da;
  }
}
[data-theme="dark"] .submitted-banner {
  background: #1e2b20; border-color: #3c5c42; color: #d7e8da;
}

/* ---- Mixed-indentation help (runner.js) --------------------------------- */
.indent-help {
  display: block; margin: .5rem 0; padding: .55rem .7rem;
  border: 1px solid #d8bd6a; border-left: 3px solid #c9a227;
  border-radius: 4px; background: #fdf8e8; color: #3a3320;
  font-size: .85rem; line-height: 1.45; white-space: normal;
}
.indent-help .btn { margin-left: .4rem; padding: .15rem .5rem; font-size: .8rem; }
[data-theme="dark"] .indent-help { background: #2e2a1c; border-color: #6b5c2a; color: #e8e2cf; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .indent-help {
    background: #2e2a1c; border-color: #6b5c2a; color: #e8e2cf;
  }
}

/* ---- Output / Tests tabs ------------------------------------------------ */
.pane-tabs { display: flex; align-items: stretch; gap: 0; padding: 0; }
.pane-tab {
  appearance: none; border: 0; background: none; cursor: pointer;
  padding: var(--space-3) var(--space-5);
  font: inherit; font-size: 12px; color: var(--muted);
  border-bottom: 2px solid transparent;
}
.pane-tab:hover { color: var(--fg); }
.pane-tab.is-active { color: var(--fg); font-weight: 600; border-bottom-color: var(--accent); }
.tests-panel { flex: 1; min-height: 0; overflow: auto; padding: var(--space-4) var(--space-5); }
.tests-note { margin: 0 0 .5rem; }
.tests-source {
  margin: 0; padding: var(--space-4);
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; line-height: 1.5; white-space: pre; overflow-x: auto;
}

/* ---- Where I stand ------------------------------------------------------
   The headline names the TARGET, never the slack: "17 more to go, from the 24
   still ahead" rather than "room for 7 below 90". The second reads as
   permission to skip seven — which the A bundle forbids outright, since it
   requires every homework on time.

   Three states on the bar, packed rather than chronological: at-level from the
   left, out-of-reach from the right, still-ahead between, with a line at the
   target. The gap from the green edge to the line IS "how many more to go",
   and the red eats inward as the ceiling drops — the margin, visible as space,
   without a word.

   Red here is one clear step down from the red that means "act now", so the
   two never compete. A revisable 84 is BLANK, not red: painting work that can
   still be saved as lost is the week-3 mistake in a different colour. */
.outcomes .verdict {
  margin: 1rem 0 1.5rem; padding: .9rem 1.1rem;
  border: 1px solid var(--border); border-left: 4px solid #8a8f98;
  border-radius: 6px; background: var(--panel);
}
.outcomes .verdict h2 { margin: 0 0 .35rem; font-size: 1.15rem; }
.outcomes .verdict-line { margin: .2rem 0 .1rem; }
.outcomes .verdict-sub { margin: .35rem 0 0; font-size: .9rem; }
.tier-open { border-left-color: #2563eb; }
.tier-met { border-left-color: #2e6b34; background: #f2f8f3; }
.tier-tight, .tier-all_in { border-left-color: #c9a227; background: #fdf8e8; }
.tier-closed { border-left-color: #8a8f98; background: #f4f5f7; }

.bar {
  position: relative; display: flex; gap: 2px;
  margin: .8rem 0 .35rem; padding-bottom: 2px;
}
.bar .cell { flex: 1 1 0; height: 20px; border-radius: 2px; }
.cell-level { background: #2e6b34; }
.cell-ahead { background: transparent; border: 1px solid #c7ccd4; }
.cell-locked { background: #d98b8b; }
.bar-marker {
  position: absolute; top: -3px; bottom: -3px; width: 2px;
  background: #1f2330; border-radius: 1px;
}
.bar-key { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.bar-key .key {
  display: inline-block; width: 10px; height: 10px; border-radius: 2px;
  margin-left: .6rem;
}
.bar-key .cell-ahead { border: 1px solid #c7ccd4; }
.near-list { margin: .6rem 0 0; padding-left: 1.2rem; }
.near-list li { margin: .25rem 0; }
.outcomes .req-met .req-mark { color: #2e6b34; }
.outcomes .req-unmet .req-mark { color: #8a5a00; }
.outcomes .req-unknown .req-mark { color: #9aa0a8; }
[data-theme="dark"] .tier-met { background: #1e2b20; }
[data-theme="dark"] .tier-tight, [data-theme="dark"] .tier-all_in { background: #2e2a1c; }
[data-theme="dark"] .tier-closed { background: #23262c; }
[data-theme="dark"] .cell-ahead { border-color: #4a515c; }
