/* Python Game Dev — the one stylesheet for the whole product.
   ===========================================================================
   Dark by default (it is a code editor); [data-theme="light"] on <html> flips
   it. System fonts only: nothing is ever fetched from the public internet,
   because school networks block it.

   Read docs/DESIGN-SYSTEM.md for what every class is for and how to use it.

   Contents
     1. Tokens (dark, then light overrides)
     2. Base / reset / typography
     3. Layout shell: topbar, page, centred page, headers
     4. Controls: buttons, icon buttons, inputs, selects, fields, segmented, tabs
     5. Surfaces: cards, badges, status pills, notices, empty states, tables,
        menus, kbd, dividers, spinner
     6. IDE: grid shell, toolbar, file tree, split panes, resizers, panes,
        terminal, game host, status bar
     7. Overlays: modal, toast
     8. Utilities
     9. Motion + responsive
    10. Print (the roster password list)
   =========================================================================== */

/* ---- 1. Tokens ------------------------------------------------------------ */

:root {
  /* surfaces, from the page ground upward */
  --bg:          #0f1116;   /* page ground                              */
  --bg-raised:   #171a21;   /* cards, topbar, toolbars                  */
  --bg-inset:    #0b0d11;   /* sunken: editor, terminal, inputs, tree   */
  --bg-overlay:  #1d2129;   /* dialogs, menus, toasts (sit above all)   */
  --bg-hover:    rgba(255, 255, 255, 0.045);
  --bg-active:   rgba(255, 255, 255, 0.08);

  --border:         #262b35;
  --border-strong:  #353c49;

  /* text */
  --fg:        #e8ebf0;
  --fg-dim:    #a3acbc;
  --fg-faint:  #7b8598;   /* >= 4.5:1 on every surface above           */

  /* semantic colours + a translucent "soft" tint of each for backgrounds */
  --accent:       #6ea8fe;
  --accent-fg:    #0b1220;
  --accent-soft:  rgba(110, 168, 254, 0.14);
  --good:         #6fcf8a;
  --good-soft:    rgba(111, 207, 138, 0.14);
  --warn:         #e6b45c;
  --warn-soft:    rgba(230, 180, 92, 0.15);
  --bad:          #f0776f;
  --bad-soft:     rgba(240, 119, 111, 0.14);
  --focus:        var(--accent);

  /* shape */
  --radius-sm:   6px;
  --radius:      10px;
  --radius-lg:   14px;
  --radius-full: 999px;

  /* spacing scale (use these for any margin/padding/gap) */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --gap: var(--sp-3);

  /* type scale */
  --fs-xs:   12px;
  --fs-sm:   13px;
  --fs-md:   14px;
  --fs-base: 15px;
  --fs-lg:   17px;
  --fs-xl:   22px;
  --fs-2xl:  28px;
  --mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas,
          "Liberation Mono", "DejaVu Sans Mono", monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
          Arial, sans-serif;

  /* elevation */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 18px 48px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.35);

  /* fixed dimensions */
  --control-h:    36px;
  --control-h-sm: 28px;
  --control-h-lg: 44px;
  --topbar-h:     52px;
  --toolbar-h:    46px;
  --statusbar-h:  26px;
  --sidebar-w:    220px;

  /* motion */
  --dur:  120ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);

  color-scheme: dark;
}

:root[data-theme="light"] {
  --bg:          #f3f4f7;
  --bg-raised:   #ffffff;
  --bg-inset:    #eceef3;
  --bg-overlay:  #ffffff;
  --bg-hover:    rgba(15, 20, 35, 0.045);
  --bg-active:   rgba(15, 20, 35, 0.08);

  --border:         #dde1e8;
  --border-strong:  #c3cad6;

  --fg:        #171a21;
  --fg-dim:    #4b5364;
  --fg-faint:  #6b7385;

  --accent:       #2f6fed;
  --accent-fg:    #ffffff;
  --accent-soft:  rgba(47, 111, 237, 0.12);
  --good:         #1f7a3a;
  --good-soft:    rgba(31, 122, 58, 0.12);
  --warn:         #8a5b00;
  --warn-soft:    rgba(138, 91, 0, 0.13);
  --bad:          #c2352b;
  --bad-soft:     rgba(194, 53, 43, 0.11);

  --shadow-sm: 0 1px 2px rgba(20, 24, 40, 0.08);
  --shadow-md: 0 4px 14px rgba(20, 24, 40, 0.10), 0 1px 3px rgba(20, 24, 40, 0.08);
  --shadow-lg: 0 18px 48px rgba(20, 24, 40, 0.18), 0 2px 6px rgba(20, 24, 40, 0.10);

  color-scheme: light;
}

/* ---- 2. Base --------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: var(--fs-base)/1.5 var(--sans);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }

h1, h2, h3, h4 { color: var(--fg); font-weight: 650; line-height: 1.25; }
h1 { font-size: var(--fs-2xl); margin: 0 0 var(--sp-1); letter-spacing: -0.02em; }
h2 { font-size: var(--fs-lg);  margin: var(--sp-6) 0 var(--sp-3); letter-spacing: -0.01em; }
h3 { font-size: var(--fs-base); margin: var(--sp-5) 0 var(--sp-2); }
h4 { font-size: var(--fs-md);  margin: var(--sp-4) 0 var(--sp-2); }
p  { margin: 0 0 var(--sp-3); }
.sub { color: var(--fg-dim); margin: 0 0 var(--sp-5); }

hr, .divider {
  border: 0;
  height: 1px;
  background: var(--border);
  margin: var(--sp-5) 0;
}

code, .mono { font-family: var(--mono); font-size: 0.92em; }
:not(pre) > code {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.05em 0.35em;
}

kbd {
  display: inline-block;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.4;
  color: var(--fg-dim);
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 0 5px;
  vertical-align: 1px;
}

::selection { background: var(--accent-soft); }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* ---- 3. Layout shell ------------------------------------------------------- */

.topbar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--topbar-h);
  padding: 0 var(--sp-4);
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar .brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-weight: 650;
  font-size: var(--fs-base);
  letter-spacing: -0.01em;
  color: var(--fg);
  text-decoration: none;
  border-radius: var(--radius-sm);
}
.topbar .spacer { flex: 1; }
.topbar .who { color: var(--fg-dim); font-size: var(--fs-md); }
.topbar-nav { display: flex; align-items: center; gap: var(--sp-1); }
.topbar-nav .btn[aria-current="page"] { background: var(--bg-active); color: var(--fg); }
.topbar form { margin: 0; }

/* Small rounded logo square used in the topbar and on the login card. */
.brand-mark {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--accent);
  color: var(--accent-fg);
  font: 700 12px/1 var(--mono);
  letter-spacing: -0.02em;
  flex: none;
}
.brand-mark-lg { width: 48px; height: 48px; border-radius: 13px; font-size: 21px; }

.page { max-width: 1100px; margin: 0 auto; padding: var(--sp-6) var(--sp-4) var(--sp-7); }
.page-wide { max-width: 1320px; }
.page-narrow { max-width: 720px; }

.center-page {
  min-height: 100%;
  display: grid;
  place-items: center;
  padding: var(--sp-5) var(--sp-4);
}
.center-page.with-topbar { min-height: calc(100% - var(--topbar-h)); }
.auth-page {
  background:
    radial-gradient(60% 40% at 50% 0%, var(--accent-soft), transparent 70%),
    var(--bg);
}
.auth-card {
  width: min(400px, 100%);
  padding: var(--sp-6) var(--sp-6) var(--sp-5);
  box-shadow: var(--shadow-md);
}
.auth-card .brand-mark { margin-bottom: var(--sp-4); }
.auth-foot { color: var(--fg-faint); font-size: var(--fs-sm); margin: var(--sp-5) 0 0; }

/* Page header: title + subtitle on the left, actions on the right. */
.page-head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--sp-5);
}
.page-head .sub { margin-bottom: 0; }
.page-head .actions { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* Section header: an h2 with optional actions to its right. */
.section-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-6) 0 var(--sp-3);
}
.section-head h2 { margin: 0; }
.section-head .actions { margin-left: auto; display: flex; gap: var(--sp-2); }
.section-head .count { color: var(--fg-faint); font-weight: 500; font-size: var(--fs-md); }

/* A narrow list of things beside the thing being looked at. The instructor's
   read-only view of a student uses it: projects on the left, code on the right.
   Collapses to one column before the sidebar starts squeezing the code. */
.workspace {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
}

.navlist { display: flex; flex-direction: column; gap: 2px; }
.navlist-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h-sm);
  padding: 5px var(--sp-2);
  border-radius: var(--radius-sm);
  color: var(--fg-dim);
  font-size: var(--fs-md);
  text-decoration: none;
}
.navlist-item:hover { background: var(--bg-hover); color: var(--fg); text-decoration: none; }
.navlist-item .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.navlist-item .badge { margin-left: auto; flex: none; }
.navlist-item[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--fg);
  font-weight: 600;
}

/* ---- 4. Controls ----------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--control-h);
  padding: 0 var(--sp-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-raised);
  color: var(--fg);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;       /* an <a class="btn"> looks like a button, not a link */
  user-select: none;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.btn:hover { background: var(--bg-hover); border-color: var(--fg-faint); }
.btn:active { background: var(--bg-active); }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
}
.btn-primary:hover { background: var(--accent); border-color: var(--accent); filter: brightness(1.08); }
.btn-primary:active { filter: brightness(0.95); }
.btn-danger { color: var(--bad); border-color: var(--border-strong); background: transparent; }
.btn-danger:hover { background: var(--bad-soft); border-color: var(--bad); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--fg-dim); }
.btn-ghost:hover { background: var(--bg-hover); border-color: transparent; color: var(--fg); }
.btn-sm { min-height: var(--control-h-sm); padding: 0 10px; font-size: var(--fs-sm); }
.btn-lg { min-height: var(--control-h-lg); padding: 0 var(--sp-5); font-size: var(--fs-base); }
.btn-block { width: 100%; }
.btn .spinner { margin: 0 -2px 0 0; }

/* Square icon-only button. ALWAYS give it aria-label. */
.icon-btn {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg-dim);
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  flex: none;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.icon-btn:hover { background: var(--bg-hover); color: var(--fg); }
.icon-btn:active { background: var(--bg-active); }
.icon-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.icon-btn-sm { width: 26px; height: 26px; font-size: 14px; }
.icon-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; }
.icon-btn-danger:hover { color: var(--bad); background: var(--bad-soft); }

label { display: block; font-size: var(--fs-sm); font-weight: 500; color: var(--fg-dim); margin-bottom: 5px; }

input:not([type]),
input[type="text"], input[type="password"], input[type="email"], input[type="search"],
input[type="number"], input[type="url"], select, textarea {
  width: 100%;
  min-height: var(--control-h);
  padding: 7px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-inset);
  color: var(--fg);
  font: inherit;
  font-size: var(--fs-md);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
input::placeholder, textarea::placeholder { color: var(--fg-faint); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--bad);
}
input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px var(--bad-soft); }
input:disabled, select:disabled, textarea:disabled { opacity: 0.55; cursor: not-allowed; }
textarea { min-height: 96px; line-height: 1.5; resize: vertical; }
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 32px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--fg-dim) 50%),
    linear-gradient(135deg, var(--fg-dim) 50%, transparent 50%);
  background-position: calc(100% - 16px) 55%, calc(100% - 11px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.input-auto, select.input-auto { width: auto; min-width: 160px; }
input[type="file"] {
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--fg-dim);
  max-width: 100%;
}
input[type="file"]::file-selector-button {
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 500;
  min-height: var(--control-h-sm);
  padding: 0 10px;
  margin-right: 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-raised);
  color: var(--fg);
  cursor: pointer;
}
input[type="checkbox"], input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.field { margin-bottom: var(--sp-4); }
.field:last-child { margin-bottom: 0; }
.field-hint { color: var(--fg-faint); font-size: var(--fs-xs); margin: 5px 0 0; }
.field-error { color: var(--bad); font-size: var(--fs-sm); margin: 5px 0 0; }

/* A checkbox or radio with its label on one line. */
.check {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-md);
  font-weight: 400;
  color: var(--fg);
  margin: 0;
  cursor: pointer;
}

/* Segmented control: a group of mutually exclusive choices. Mark the active
   one with aria-pressed="true". */
.segmented {
  display: inline-flex;
  padding: 2px;
  gap: 2px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.seg-btn {
  min-height: 26px;
  padding: 0 10px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--fg-dim);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.seg-btn:hover { color: var(--fg); }
.seg-btn[aria-pressed="true"] {
  background: var(--bg-raised);
  color: var(--fg);
  box-shadow: var(--shadow-sm);
}

/* Tabs: underline style. Active tab has aria-selected="true". */
.tabs {
  display: flex;
  gap: var(--sp-1);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--sp-4);
  overflow-x: auto;
}
.tab {
  position: relative;
  min-height: 38px;
  padding: 0 var(--sp-3);
  border: 0;
  background: none;
  color: var(--fg-dim);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  margin-bottom: -1px;
}
.tab:hover { color: var(--fg); background: var(--bg-hover); }
.tab[aria-selected="true"] { color: var(--fg); box-shadow: inset 0 -2px 0 var(--accent); }
.tab .count { color: var(--fg-faint); font-size: var(--fs-xs); margin-left: 5px; }
.tabpanel[hidden] { display: none; }

/* ---- 5. Surfaces ----------------------------------------------------------- */

.card {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-4);
  box-shadow: var(--shadow-sm);
}
.card-flush { padding: 0; overflow: hidden; }
.card > h2:first-child, .card > h3:first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }
.card-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.card-head h2, .card-head h3 { margin: 0; font-size: var(--fs-base); }
.card-head .actions { margin-left: auto; display: flex; gap: var(--sp-2); }
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--gap);
}
a.card, .card-hover, .project-card {
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
a.card:hover, .card-hover:hover, .project-card:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.project-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  text-decoration: none;
  color: inherit;
  min-height: 118px;
}
/* A link that covers its whole card, so the card is one big target. Buttons in
   the card sit above it and keep their own clicks -- which is why the card is a
   <div> with one stretched <a> inside, and not an <a> with buttons nested in it
   (invalid HTML, and the browser fires the link on every button press). */
.stretch-link::after { content: ""; position: absolute; inset: 0; }
.project-card .card-actions,
.project-card .menu-anchor { position: relative; }
.project-card .name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The card title is a link, but it must read as a title: the card itself is the
   affordance, so no blue and no underline. */
a.name, .project-card .name { color: var(--fg); text-decoration: none; }
.project-card .meta { color: var(--fg-faint); font-size: var(--fs-sm); }
.project-card .card-actions { margin-top: auto; display: flex; gap: var(--sp-2); align-items: center; }
.project-card .card-actions .right { margin-left: auto; }
/* The "..." menu button on a card is quiet until wanted -- but never invisible:
   these are touch Chromebooks, and an affordance that needs a hover to exist is
   an affordance half the class cannot find. */
.project-card .card-menu { opacity: 0.5; }
.project-card:hover .card-menu,
.project-card:focus-within .card-menu,
.project-card .card-menu[aria-expanded="true"] { opacity: 1; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 1px 8px;
  border-radius: var(--radius-full);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.6;
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--fg-dim);
  white-space: nowrap;
  vertical-align: middle;
}
.badge-game     { color: var(--accent); border-color: transparent; background: var(--accent-soft); }
.badge-console  { color: var(--good);   border-color: transparent; background: var(--good-soft); }
.badge-pico     { color: var(--warn);   border-color: transparent; background: var(--warn-soft); }
.badge-live     { color: var(--good);   border-color: transparent; background: var(--good-soft); }
.badge-accent   { color: var(--accent); border-color: transparent; background: var(--accent-soft); }
.badge-good     { color: var(--good);   border-color: transparent; background: var(--good-soft); }
.badge-warn     { color: var(--warn);   border-color: transparent; background: var(--warn-soft); }
.badge-bad      { color: var(--bad);    border-color: transparent; background: var(--bad-soft); }
.badge-neutral  { color: var(--fg-dim); border-color: transparent; background: var(--bg-active); }

/* Status pill with a coloured dot: for save state, run state, job state.
   Set data-state to one of: saved | unsaved | saving | running | stopped |
   error | idle | live | offline. */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 22px;
  padding: 0 9px 0 8px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: var(--bg-inset);
  color: var(--fg-dim);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}
.status-pill::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg-faint);
  flex: none;
}
.status-pill[data-state="saved"],
.status-pill[data-state="live"]     { color: var(--good); }
.status-pill[data-state="saved"]::before,
.status-pill[data-state="live"]::before { background: var(--good); }
.status-pill[data-state="unsaved"]  { color: var(--warn); }
.status-pill[data-state="unsaved"]::before { background: var(--warn); }
.status-pill[data-state="saving"],
.status-pill[data-state="running"]  { color: var(--accent); }
.status-pill[data-state="saving"]::before,
.status-pill[data-state="running"]::before { background: var(--accent); animation: pulse 1.2s ease-in-out infinite; }
.status-pill[data-state="error"],
.status-pill[data-state="offline"]  { color: var(--bad); }
.status-pill[data-state="error"]::before,
.status-pill[data-state="offline"]::before { background: var(--bad); }
@keyframes pulse { 50% { opacity: 0.35; } }

.empty {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  padding: var(--sp-7) var(--sp-5);
  text-align: center;
  color: var(--fg-dim);
  background: transparent;
}
.empty p { margin: 0; }
.empty-title { margin: 0 0 6px; font-weight: 600; color: var(--fg); font-size: var(--fs-lg); }
.empty .actions { margin-top: var(--sp-4); display: flex; gap: var(--sp-2); justify-content: center; flex-wrap: wrap; }
.mb-tight { margin: 0 0 var(--sp-1); }
.subhead { font-size: var(--fs-base); margin: var(--sp-5) 0 var(--sp-1); }

.notice {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px 10px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  border-left: 3px solid var(--fg-faint);
  background: var(--bg-inset);
  color: var(--fg);
  font-size: var(--fs-md);
  margin-bottom: var(--sp-4);
}
.notice p { margin: 0; }
.notice-bad  { border-left-color: var(--bad);    background: var(--bad-soft);    color: var(--fg); }
.notice-good { border-left-color: var(--good);   background: var(--good-soft); }
.notice-warn { border-left-color: var(--warn);   background: var(--warn-soft); }
.notice-info { border-left-color: var(--accent); background: var(--accent-soft); }
.notice-bad strong  { color: var(--bad); }
.notice-good strong { color: var(--good); }
.notice-warn strong { color: var(--warn); }

table { width: 100%; border-collapse: collapse; font-size: var(--fs-md); }
th, td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
th {
  color: var(--fg-faint);
  font-weight: 600;
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
thead th { border-bottom-color: var(--border-strong); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--bg-hover); }
.table-zebra tbody tr:nth-child(even) td { background: var(--bg-hover); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td .btn-sm, td .icon-btn-sm { vertical-align: middle; }
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); }
.table-wrap table { min-width: 560px; }
.card-flush .table-wrap { border: 0; border-radius: 0; }

/* Dropdown menu, anchored to a trigger. Toggle with `hidden` on .menu and
   aria-expanded on the trigger. */
.menu-anchor { position: relative; display: inline-block; }
.menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 30;
  min-width: 160px;
  padding: 4px;
  background: var(--bg-overlay);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
}
.menu-left { right: auto; left: 0; }
/* For a trigger near the bottom of its container (a card's action row), so the
   menu opens over the card instead of off the end of it. */
.menu-up { top: auto; bottom: calc(100% + 4px); }
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 32px;
  padding: 0 10px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--fg);
  font: inherit;
  font-size: var(--fs-md);
  text-align: left;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.menu-item:hover, .menu-item:focus-visible { background: var(--bg-hover); outline: none; }
.menu-item-danger { color: var(--bad); }
.menu-item-danger:hover { background: var(--bad-soft); }
.menu-sep { height: 1px; background: var(--border); margin: 4px 0; }

/* A row of one decision: label left, control right, hairline between. Feature
   flags and starter examples are lists of single choices -- a table would be
   four columns of nothing. */
.list-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.list-row:first-child { padding-top: 0; }
.list-row:last-child { border-bottom: 0; padding-bottom: 0; }
.list-row .list-main { flex: 1; min-width: 0; }
.list-row .list-title { display: block; color: var(--fg); }
.list-row .list-desc {
  display: block;
  margin-top: 2px;
  color: var(--fg-faint);
  font-size: var(--fs-sm);
}

/* On/off switch. A <button aria-pressed> rather than a checkbox: it carries its
   own state for assistive tech, and one click is the whole interaction. */
.switch {
  --sw-w: 40px;
  position: relative;
  flex: none;
  width: var(--sw-w);
  height: 22px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--bg-inset);
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.switch::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--fg-faint);
  transform: translateY(-50%);
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.switch[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); }
.switch[aria-pressed="true"]::after {
  background: var(--accent-fg);
  transform: translate(calc(var(--sw-w) - 22px), -50%);
}
.switch:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.switch:disabled { cursor: progress; opacity: 0.55; }
.switch-label {
  min-width: 28px;
  color: var(--fg-faint);
  font-size: var(--fs-sm);
  text-align: right;
}

/* Read-only source, for the instructor's grading view. Not .term: that one is a
   live console with a caret and stream colouring. */
.codeview {
  margin: 0;
  max-height: 64vh;
  overflow: auto;
  padding: var(--sp-4);
  background: var(--bg-inset);
  color: var(--fg);
  font-family: var(--mono);
  font-size: var(--fs-md);
  line-height: 1.55;
  white-space: pre;
  tab-size: 4;
}
.codeview.err { color: var(--bad); white-space: pre-wrap; }

/* Tiny loading spinner; pair with text or an aria-label. */
.spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  vertical-align: -2px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- 6. IDE ---------------------------------------------------------------- */

.ide {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
  background: var(--bg);
}
.ide[data-sidebar="hidden"] { grid-template-columns: minmax(0, 1fr); }
.ide[data-sidebar="hidden"] .filetree { display: none; }

.ide-toolbar {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--toolbar-h);
  padding: 0 var(--sp-3);
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
}
.ide-toolbar .title {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.ide-toolbar .spacer { flex: 1; }
.ide-toolbar .toolbar-group { display: flex; align-items: center; gap: 6px; }
.toolbar-sep { width: 1px; height: 22px; background: var(--border); margin: 0 4px; flex: none; }
.savestate { font-size: var(--fs-sm); color: var(--fg-faint); min-width: 84px; white-space: nowrap; }
.savestate.err { color: var(--bad); }

.filetree {
  grid-column: 1;
  grid-row: 2;
  background: var(--bg-inset);
  border-right: 1px solid var(--border);
  overflow: auto;
  padding: var(--sp-2);
  min-height: 0;
  font-size: var(--fs-sm);
}
.tree-head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 0 4px 4px 8px;
  color: var(--fg-faint);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.tree-head .icon-btn { margin-left: auto; width: 24px; height: 24px; font-size: 15px; }
.tree-head .icon-btn + .icon-btn { margin-left: 0; }

.file-row {
  display: flex;
  align-items: center;
  min-height: 30px;
  border-radius: var(--radius-sm);
  color: var(--fg-dim);
  position: relative;
}
.file-row:hover { background: var(--bg-hover); }
.file-row .file-open {
  flex: 1;
  min-width: 0;
  display: block;
  line-height: 30px;
  text-align: left;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: inherit;
  font: inherit;
  font-size: var(--fs-sm);
  padding: 0 8px;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-row[aria-current="true"] { background: var(--accent-soft); color: var(--fg); }
.file-row[aria-current="true"] .file-open { font-weight: 600; }
.file-row[aria-current="true"]::before {
  content: "";
  position: absolute;
  left: 0; top: 6px; bottom: 6px;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
}
.file-row.is-binary .file-open { opacity: 0.55; cursor: default; }
.file-row.is-dirty .file-open::after {
  content: "";
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--warn);
  margin-left: 7px;
  vertical-align: 1px;
}
/* row actions (rename, delete, "...") appear on hover or keyboard focus */
.file-row .file-del,
.file-row .file-act {
  opacity: 0;
  flex: none;
  width: 24px;
  height: 24px;
  margin-right: 3px;
  display: inline-grid;
  place-items: center;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--fg-faint);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  padding: 0;
}
.file-row:hover .file-del, .file-row:hover .file-act,
.file-row .file-del:focus-visible, .file-row .file-act:focus-visible,
.file-row .file-act[aria-expanded="true"] { opacity: 1; }
.file-row .file-del:hover, .file-row .file-act:hover { background: var(--bg-active); color: var(--fg); }
.file-row .file-del:hover { color: var(--bad); }

.workarea {
  grid-column: 2;
  grid-row: 2;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  min-height: 0;
  min-width: 0;
  background: var(--bg-inset);
}
.ide[data-sidebar="hidden"] .workarea { grid-column: 1; }
.editor-host { min-height: 0; min-width: 0; overflow: hidden; background: var(--bg-inset); }
.editor-host .cm-editor { height: 100%; }
.editor-host .cm-editor.cm-focused { outline: none; }
.editor-host .cm-scroller { font-family: var(--mono); font-size: 14px; line-height: 1.55; }

/* Split panes. --split sizes the FIRST pane (px, %, or fr); the second pane
   takes the rest. JS drags set el.style.setProperty("--split", px + "px").
     .split-h  editor LEFT, game RIGHT      (uses .resizer-v between them)
     .split-v  editor ABOVE, terminal BELOW (uses .resizer-h between them) */
.split { display: grid; min-height: 0; min-width: 0; height: 100%; }
.split-h { grid-template-columns: minmax(240px, var(--split, 1fr)) auto minmax(240px, 1fr); }
.split-v { grid-template-rows:    minmax(120px, var(--split, 1.6fr)) auto minmax(120px, 1fr); }
.pane {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}
.pane > .editor-host, .pane > .term, .pane > .gamehost, .pane > .pane-body { flex: 1; min-height: 0; }
.pane-body { min-height: 0; min-width: 0; overflow: auto; }

/* Header strip over a pane (the .outpanel header and any .pane-head). */
.outpanel > header, .pane-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 34px;
  padding: 0 var(--sp-2) 0 var(--sp-3);
  border-bottom: 1px solid var(--border);
  background: var(--bg-raised);
  font-size: var(--fs-sm);
  color: var(--fg-dim);
  flex: none;
}
.pane-head .title, .outpanel > header .title { font-weight: 600; color: var(--fg); }
.outpanel .spacer, .pane-head .spacer { flex: 1; }

/* Resizer handles. A .resizer-v is a vertical bar between side-by-side
   panes (drag left/right); a .resizer-h is a horizontal bar between stacked
   panes (drag up/down). Keyboard: give it tabindex="0" and move it with the
   arrow keys in JS. */
.resizer {
  position: relative;
  flex: none;
  background: var(--border);
  z-index: 2;
  touch-action: none;
  user-select: none;
  transition: background var(--dur) var(--ease);
}
.resizer::after {
  /* a wider invisible hit area than the 1px visual line */
  content: "";
  position: absolute;
  inset: 0;
}
.resizer-v { width: 1px; cursor: col-resize; }
.resizer-v::after { left: -4px; right: -4px; }
.resizer-h { height: 1px; cursor: row-resize; }
.resizer-h::after { top: -4px; bottom: -4px; }
.resizer:hover, .resizer.is-dragging, .resizer:focus-visible {
  background: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
  outline: none;
}
/* While dragging, iframes must not swallow pointer events and text must not select. */
body.is-resizing { user-select: none; }
body.is-resizing iframe { pointer-events: none; }
body.is-resizing-v { cursor: col-resize; }
body.is-resizing-h { cursor: row-resize; }

/* Legacy stacked output panel (still works; the split layout is preferred). */
.outpanel {
  border-top: 1px solid var(--border);
  background: var(--bg-inset);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  height: 38vh;
  min-height: 120px;
}

/* Terminal. Beginners type on the SAME line as the input() prompt, so the
   caret sits inline with the output rather than on a separate row. */
.term {
  margin: 0;
  padding: 10px 14px;
  overflow: auto;
  font-family: var(--mono);
  font-size: 13.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--fg);
  background: var(--bg-inset);
  cursor: text;
  min-height: 0;
}
.term:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.term .err { color: var(--bad); }
.term .sys { color: var(--fg-faint); font-style: italic; }
.term .typed { color: var(--accent); }
.term-caret {
  display: inline-block;
  width: 7px;
  height: 1em;
  vertical-align: text-bottom;
  background: var(--accent);
  animation: blink 1.1s steps(2, start) infinite;
}
@keyframes blink { to { visibility: hidden; } }
/* The console placeholder talks about input(); a Pico needs a board first. */
.ide[data-kind="pico"] .term:empty::before {
  content: "Press Connect, choose your Pico from the list, then press Run.";
}
.term:empty::before {
  content: "Press Run to start your program. When it asks a question, type your answer here and press Enter.";
  color: var(--fg-faint);
  font-style: italic;
}

.gamehost {
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  background: #000;
  display: grid;
  place-items: stretch;
  position: relative;
}
.gameframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: #000;
  display: block;
}
/* Something to look at before the game is running. */
.game-idle {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  gap: var(--sp-2);
  color: var(--fg-faint);
  font-size: var(--fs-md);
  background: var(--bg-inset);
  padding: var(--sp-5);
}
.game-idle .btn { justify-self: center; }

/* Bottom status bar for the IDE. */
.statusbar {
  grid-column: 1 / -1;
  grid-row: 3;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--statusbar-h);
  padding: 0 var(--sp-3);
  background: var(--bg-raised);
  border-top: 1px solid var(--border);
  font-size: var(--fs-xs);
  color: var(--fg-faint);
  white-space: nowrap;
  overflow: hidden;
}
.statusbar .spacer { flex: 1; }
.statusbar-item { display: inline-flex; align-items: center; gap: 5px; }
.statusbar kbd { font-size: 10px; line-height: 1.3; }
.statusbar .status-pill { min-height: 18px; border: 0; background: transparent; padding-left: 0; }

/* Older keyboard-hint footer (same look as the status bar). */
.ide-hint {
  grid-column: 1 / -1;
  padding: 4px 12px;
  font-size: var(--fs-xs);
  color: var(--fg-faint);
  border-top: 1px solid var(--border);
  background: var(--bg-raised);
}
.ide-hint kbd { font-size: 10px; }

/* ---- 7. Overlays ----------------------------------------------------------- */

dialog.modal {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--bg-overlay);
  color: var(--fg);
  padding: 0;
  max-width: 520px;
  width: min(92vw, 520px);
  box-shadow: var(--shadow-lg);
  margin: auto;
}
dialog.modal[open] { animation: modal-in 160ms var(--ease); }
dialog.modal::backdrop { background: rgba(4, 6, 10, 0.6); backdrop-filter: blur(2px); }
@keyframes modal-in { from { opacity: 0; transform: translateY(6px) scale(0.98); } }
.modal form, .modal-body { padding: var(--sp-5); }
.modal h2 { font-size: var(--fs-lg); margin-top: 0; }
.modal .sub { margin-bottom: var(--sp-4); }
.modal-lg { max-width: 760px; width: min(94vw, 760px); }
.modal-actions {
  display: flex;
  gap: var(--sp-2);
  justify-content: flex-end;
  margin-top: var(--sp-4);
}
.modal-actions .left { margin-right: auto; }
.ui-error { color: var(--bad); font-size: var(--fs-sm); margin: -8px 0 12px; }

/* One value shown once and meant to be written down or copied -- a reset
   password. Big, monospaced and selectable, with the Copy button beside it. */
.copyfield {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
}
.copyfield .value {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  font-family: var(--mono);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--fg);
  user-select: all;
}

/* Brief non-blocking message (replaces alert() for confirmations). */
.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translate(-50%, 12px);
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(92vw, 480px);
  background: var(--bg-overlay);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 10px 16px 10px 14px;
  font-size: var(--fs-md);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease);
  z-index: 100;
}
.toast::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.toast[data-kind="error"]   { border-color: var(--bad); }
.toast[data-kind="error"]::before   { background: var(--bad); }
.toast[data-kind="success"]::before { background: var(--good); }

/* ---- 8. Utilities ---------------------------------------------------------- */

.row { display: flex; align-items: center; gap: var(--sp-2); }
.row-wrap { flex-wrap: wrap; }
.row-end { align-items: flex-end; }
.col { display: flex; flex-direction: column; gap: var(--sp-2); }
.gap-sm { gap: var(--sp-1); }
.gap-lg { gap: var(--sp-4); }
.grow { flex: 1; min-width: 0; }
.right { margin-left: auto; }
.muted { color: var(--fg-dim); }
.muted-sm { color: var(--fg-faint); font-size: var(--fs-sm); }
.text-sm { font-size: var(--fs-sm); }
.text-xs { font-size: var(--fs-xs); }
.strong { font-weight: 600; }
.tnum { font-variant-numeric: tabular-nums; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.nowrap { white-space: nowrap; }
.mt { margin-top: var(--sp-4); }
.mb { margin-bottom: var(--sp-4); }
.mt-sm { margin-top: var(--sp-2); }
.mb-sm { margin-bottom: var(--sp-2); }
.mt-lg { margin-top: var(--sp-5); }
.mb-lg { margin-bottom: var(--sp-5); }
.m-0 { margin: 0; }
/* Padding for content dropped straight into a .card-flush (a message where a
   table would otherwise be). */
.pad { padding: var(--sp-4); }
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}
[hidden] { display: none !important; }
/* show an element in one theme only (used for the sun/moon toggle icons) */
:root[data-theme="light"] .only-dark { display: none !important; }
:root:not([data-theme="light"]) .only-light { display: none !important; }

/* ---- 9. Motion + responsive ------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .term-caret, .status-pill::before, .spinner { animation: none; }
  a.card:hover, .card-hover:hover, .project-card:hover { transform: none; }
}

@media (max-width: 860px) {
  /* side-by-side becomes stacked: editor on top, game below */
  .split-h {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(120px, 1fr) auto minmax(200px, 1fr);
  }
  .split-h > .resizer-v { width: auto; height: 1px; cursor: row-resize; }
  .split-h > .resizer-v::after { left: 0; right: 0; top: -4px; bottom: -4px; }
}

@media (max-width: 720px) {
  .ide { grid-template-columns: minmax(0, 1fr); }
  .ide .filetree { display: none; }
  .ide .workarea { grid-column: 1; }
  .ide[data-sidebar="open"] .filetree {
    display: block;
    position: fixed;
    top: var(--toolbar-h);
    bottom: 0;
    left: 0;
    width: min(80vw, var(--sidebar-w));
    z-index: 15;
    border-right: 1px solid var(--border-strong);
    box-shadow: var(--shadow-lg);
  }
  .hide-sm { display: none !important; }
  .page { padding: var(--sp-4) var(--sp-3) var(--sp-6); }
  h1 { font-size: var(--fs-xl); }
  .auth-card { padding: var(--sp-5) var(--sp-4); }
  .modal form, .modal-body { padding: var(--sp-4); }
}

/* ---- 10. Print ------------------------------------------------------------- */

/* The only thing anyone prints from this app is the freshly generated password
   list, which is shown exactly once and then never again. ui.js printElement()
   clones what should be printed into #printarea and sets .printing on <body>;
   everything else is hidden for the duration, and the screen is untouched. */
#printarea { display: none; }

@media print {
  body { background: #fff; color: #000; }
  body.printing > *:not(#printarea) { display: none !important; }
  body.printing #printarea { display: block; }
  #printarea h1 { font-size: 18pt; margin: 0 0 12pt; }
  #printarea table { width: 100%; border-collapse: collapse; color: #000; font-size: 11pt; }
  #printarea th, #printarea td { border-bottom: 1px solid #999; padding: 5pt 6pt; text-align: left; }
  #printarea .mono { font-family: var(--mono); }
  .toast, .topbar { display: none !important; }
}

@media (max-width: 860px) {
  .workspace { grid-template-columns: minmax(0, 1fr); }
}

/* ---- 11. Roster (projected in class) --------------------------------------- */

/* This page is read from the back of a room, so the password is the largest
   thing on it and set in a font where 0/O and 1/l cannot be confused -- the
   temp passwords are word-number-word for the same reason. */
.temp-pass {
  font-family: var(--mono);
  font-size: 1.15em;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--fg);
  user-select: all;
}
.roster-table td { padding-top: 11px; padding-bottom: 11px; }
/* A claimed row recedes so the remaining work is what stands out. */
.roster-table tr.is-done td { color: var(--fg-faint); }
.roster-table tr.is-done td.mono { opacity: 0.7; }

body.roster-big .roster-table { font-size: 1.6rem; }
body.roster-big .roster-table .temp-pass { font-size: 1.25em; }
body.roster-big .roster-table td { padding-top: 16px; padding-bottom: 16px; }
body.roster-big .roster-table th { font-size: var(--fs-md); }

/* ---- 12. Learn (clickable-line tutorial) ----------------------------------- */

.learn-split { grid-template-columns: minmax(0, 1.15fr) minmax(280px, 1fr); }
.learn-code { max-height: none; padding: var(--sp-4) 0; }

/* Each line is its own target. Only annotated lines light up, so a student can
   see at a glance how much is left rather than clicking blanks hopefully. */
.code-line {
  display: block;
  padding: 1px var(--sp-4);
  border-left: 3px solid transparent;
  white-space: pre;
}
.code-line.is-clickable { cursor: pointer; }
.code-line.is-clickable:hover { background: var(--bg-hover); border-left-color: var(--border-strong); }
.code-line.is-clickable:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.code-line.is-read { border-left-color: var(--good); }
.code-line.is-read.is-clickable:hover { border-left-color: var(--good); }

.learn-panel { position: sticky; top: calc(var(--topbar-h) + var(--sp-4)); max-height: 78vh; overflow: auto; }
.learn-panel h2 { font-size: var(--fs-lg); }

/* ---- the figures ---- */

.viz { margin-top: var(--sp-4); }
.viz-mounted {
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-inset);
}
.viz-caption { color: var(--fg-dim); font-size: var(--fs-sm); margin: 0 0 var(--sp-3); }
.viz-controls { margin-top: var(--sp-3); }
.viz-code { font-family: var(--mono); font-size: var(--fs-sm); margin-bottom: var(--sp-3); }
.viz-num-wrap { display: inline-flex; align-items: center; gap: 5px; }
.viz-num { width: 84px; vertical-align: middle; accent-color: var(--accent); }
.viz-num-out { color: var(--accent); font-weight: 600; min-width: 3ch; display: inline-block; }
.viz-size { color: var(--fg-faint); font-size: var(--fs-sm); font-family: var(--mono); }

.viz-screen { display: flex; justify-content: center; }
.viz-screen-bar { height: 10px; background: var(--border-strong); border-radius: 4px 4px 0 0; }
.viz-screen-face {
  background: #12122a;
  border: 1px solid var(--border-strong);
  border-radius: 0 0 4px 4px;
  transition: width var(--dur) var(--ease), height var(--dur) var(--ease);
}

.viz-flip { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.viz-surface { text-align: center; }
.viz-surface-label { font-size: var(--fs-xs); color: var(--fg-faint); margin-bottom: 4px; }
.viz-face {
  height: 90px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  position: relative;
  transition: background var(--dur) var(--ease);
}
.viz-face.has-fill { background: #12122a; border-style: solid; }
.viz-face.has-circle::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 44px; height: 44px;
  margin: -22px 0 0 -22px;
  border-radius: 50%;
  background: #e87a5a;
}

.viz-grid {
  position: relative;
  width: 260px; height: 160px;
  margin: 0 auto var(--sp-2);
  background:
    linear-gradient(var(--border) 1px, transparent 1px) 0 0 / 100% 40px,
    linear-gradient(90deg, var(--border) 1px, transparent 1px) 0 0 / 40px 100%,
    var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  cursor: crosshair;
}
.viz-dot {
  position: absolute;
  width: 12px; height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.viz-readout { display: block; text-align: center; color: var(--accent); }

/* A student who asked for less motion gets the same figures without transitions. */
.viz-still .viz-screen-face,
.viz-still .viz-face { transition: none; }
/* Inline code inside a lesson must wrap rather than force the panel wide. */
.learn-panel code, .notice code { overflow-wrap: anywhere; }

/* ---- 13. Roster column mapper --------------------------------------------- */

/* One box per column of the pasted list: a sample of the real data above the
   choice, so the teacher maps columns by recognising their own students'
   names rather than by counting commas. */
.col-pick { display: inline-flex; flex-direction: column; gap: 4px; }
.col-sample {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--fg-faint);
  padding: 2px 6px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 4px;
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.col-pick select { width: auto; min-width: 130px; }
.due-field input[type="date"] { width: auto; min-width: 150px; }

/* more figures */
.viz-face-tall { height: 120px; overflow: hidden; }
.viz-swatch {
  height: 72px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); margin-top: var(--sp-2);
}
.viz-blob, .viz-trail {
  position: absolute; top: 44px; left: 18px;
  width: 28px; height: 28px; border-radius: 50%;
  background: #e87a5a; transition: left 140ms linear;
}
.viz-trail { opacity: 0.35; transition: none; }
.viz-sq { position: absolute; left: 82px; top: 34px; width: 52px; height: 52px; background: #6ea8fe; }
.viz-ci { position: absolute; left: 68px; top: 20px; width: 80px; height: 80px; border-radius: 50%; background: #e87a5a; }
.viz-order { margin-bottom: var(--sp-2); }
.viz-order-line {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 4px 0; font-size: var(--fs-xs);
}
.viz-order-line code { flex: 1; }
.viz-still .viz-blob { transition: none; }

/* the last four figures */
.viz-queue {
  display: flex; flex-wrap: wrap; gap: 6px; min-height: 34px;
  padding: 6px; border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm); background: var(--bg);
}
.viz-queue-empty { color: var(--fg-faint); font-size: var(--fs-sm); }
.viz-event {
  font-family: var(--mono); font-size: var(--fs-xs);
  padding: 2px 8px; border-radius: var(--radius-full);
  background: var(--accent-soft); color: var(--accent);
}
.viz-queue-log { margin-top: var(--sp-2); }
.viz-queue-line {
  font-family: var(--mono); font-size: var(--fs-xs);
  color: var(--fg-dim); padding: 2px 0;
}
.viz-rect-demo { position: absolute; background: #6ea8fe; opacity: 0.85; }
.viz-circle-demo { position: absolute; border-radius: 50%; background: #e87a5a; opacity: 0.85; }
.viz-corner-dot {
  position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px;
  border-radius: 50%; background: var(--fg); box-shadow: 0 0 0 2px var(--bg);
}
.viz-parts { display: flex; flex-direction: column; gap: 4px; }
.viz-part {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 5px 8px; border-radius: var(--radius-sm);
  background: var(--bg); font-size: var(--fs-sm); color: var(--fg-faint);
}
.viz-part-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--border-strong); flex: none;
}
.viz-part.is-on { color: var(--fg); }
.viz-part.is-on .viz-part-dot { background: var(--good); }
.viz-part-state { margin-left: auto; font-size: var(--fs-xs); }

/* ---- 14. Lesson tasks in the editor ---------------------------------------- */

/* Sits under the file tree, which for a practice project holds one file and a
   lot of empty space. The tasks belong next to the code they describe. */
.tasks-panel { margin-top: var(--sp-4); border-top: 1px solid var(--border); }
.tasks-panel .tree-head .count { margin-left: auto; color: var(--fg-faint); }
.task-list { list-style: none; margin: 0 0 var(--sp-3); padding: 0; }
.task {
  border-bottom: 1px solid var(--border);
}
.task:last-child { border-bottom: 0; }
.task-head {
  display: flex; align-items: flex-start; gap: 7px;
  width: 100%; padding: 7px 4px;
  background: none; border: 0; cursor: pointer;
  color: var(--fg-dim); font: inherit; font-size: var(--fs-sm); text-align: left;
}
.task-head:hover { color: var(--fg); background: var(--bg-hover); }
.task-head:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.task-num {
  flex: none; width: 18px; height: 18px; border-radius: 50%;
  background: var(--bg-active); color: var(--fg-faint);
  font-size: 10px; font-weight: 600;
  display: inline-grid; place-items: center;
}
.task-title { flex: 1; min-width: 0; line-height: 1.35; }
/* A dot, not a word: the sidebar is narrow and the state is glanceable. */
.task-state {
  flex: none; width: 9px; height: 9px; margin-top: 5px;
  border-radius: 50%; border: 1px solid var(--border-strong);
}
.task-state[data-state="done"] { background: var(--good); border-color: var(--good); }
.task-state[data-state="todo"] { background: var(--warn); border-color: var(--warn); }
.task-body {
  padding: 0 4px 9px 29px;
  font-size: var(--fs-xs); color: var(--fg-dim); line-height: 1.5;
}
.task-body p { margin: 0 0 6px; }
.task-why { color: var(--warn); }
.task:has(.task-state[data-state="done"]) .task-title { color: var(--fg-faint); }
.task-note {
  margin: 6px 0 0; font-size: 10px; color: var(--fg-faint); text-align: center;
}

/* --- 15. a mistake in the student's own code ------------------------- */
/* Lives in the game pane, where the student is already looking when Run
   fails.  Deliberately not a toast: a syntax error is the one message a
   beginner needs to sit and read, and a toast is gone in three seconds.
   Overlays the pane the same way .game-idle does -- the host is black and
   stretches its children, so this has to paint its own background. */

.game-error {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: start;
  gap: var(--sp-3);
  padding: var(--sp-5);
  overflow: auto;
  text-align: left;
  background: var(--bg-inset);
}
.game-error-title {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--fg);
}
.game-error-msg {
  margin: 0;
  max-width: 44ch;
  font-family: var(--mono);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--bad);
  overflow-wrap: anywhere;
}

/* --- 16. lesson progress (instructor) -------------------------------- */
/* A class of thirty read at a glance while walking the room, so the cells
   carry a shape and a word as well as a colour -- a red/green grid alone is
   unreadable to a colour-blind teacher, and one of ours is. */

.progress-table th[scope="col"] { vertical-align: bottom; }
.progress-table .cell {
  display: inline-block;
  min-width: 3.4em;
  padding: 2px var(--sp-2);
  border-radius: var(--radius-sm);
  font-variant-numeric: tabular-nums;
}
.cell-done {
  color: var(--good);
  background: color-mix(in srgb, var(--good) 14%, transparent);
  font-weight: 600;
}
.cell-part {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
}
.cell-none { color: var(--fg-faint); }

/* A checkbox that reads as a switch rather than a form field. */
.chip-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  user-select: none;
}
.chip-toggle:hover { background: var(--bg-hover); }
.chip-toggle input { margin: 0; }
.chip-toggle:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.chip-toggle:has(input:disabled) { opacity: .55; cursor: default; }

.d-block { display: block; }

/* A lesson a student cannot open yet. Dimmed but fully readable -- the point
   of listing it is that they can see what is coming. */
.project-card.is-locked { opacity: .72; }
.project-card.is-locked .name { color: var(--fg-dim); cursor: default; }

/* --- 17. an open card menu has to paint above the next card ----------- */
/* .project-card:hover applies `transform`, and a transform creates a stacking
   context.  That traps the card's own dropdown inside it, so the NEXT card in
   the grid painted over the open menu -- Rename and Delete sat behind the tile
   below and could not be clicked.  z-index on .menu could never fix it: it was
   already winning inside a context that was itself losing.
   Raising the card that owns the menu is the fix.  :focus-within does the work
   (clicking "..." focuses it); :has() catches a menu left open after focus has
   moved on. */
.project-card:hover,
.project-card:focus-within { z-index: 40; }
.project-card:has(.menu:not([hidden])) { z-index: 41; }

/* --- 18. assignment preview ------------------------------------------- */
/* The instructions as the teacher wrote them, markdown and all. Shown as
   source rather than rendered: this page exists to check the wording before
   releasing it, and a rendered view hides the markdown that is actually
   stored. */
.prose-src {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  line-height: 1.6;
  color: var(--fg-dim);
}

/* --- 19. assignment panel in the editor ------------------------------- */
/* Built on the lesson tasks panel (section 14) so the two read alike. What is
   added: the title and terms, collapsible instructions, and a label on checks
   students cannot see the details of. */
.asg-title { margin: var(--sp-2) 4px 2px; font-size: var(--fs-sm); font-weight: 600; color: var(--fg); }
.asg-meta  { margin: 0 4px var(--sp-2); font-size: var(--fs-xs); color: var(--fg-faint); }
.asg-instructions { margin: 0 4px var(--sp-2); font-size: var(--fs-xs); }
.asg-instructions > summary { cursor: pointer; color: var(--fg-dim); padding: 4px 0; }
.asg-instructions > summary:hover { color: var(--fg); }
.asg-instructions-body { color: var(--fg-dim); line-height: 1.5; max-height: 40vh; overflow: auto; }
.asg-instructions-body h3, .asg-instructions-body h4, .asg-instructions-body h5 {
  margin: 6px 0 4px; font-size: var(--fs-xs); color: var(--fg);
}
.asg-instructions-body p { margin: 0 0 6px; }
.asg-instructions-body code { font-family: var(--mono); font-size: 0.95em; }
/* A task-why on an assignment result can quote printed output verbatim. */
.asg-panel .task-why { white-space: pre-wrap; overflow-wrap: anywhere; }
/* A check on a function not started yet: a status, not a failure. The dot
   keeps .task-state's plain hollow ring (data-state="new" has no fill). */
.asg-panel .task-why.task-fresh { color: var(--fg-faint); }
.asg-hidden-tag {
  display: inline-block; margin-left: 2px; padding: 0 5px;
  border-radius: var(--radius-full); background: var(--bg-active);
  color: var(--fg-faint); font-size: 9px; vertical-align: 1px;
}
.asg-panel .btn-block + .btn-block { margin-top: 6px; }

/* --- 20. a tab left open by someone who has signed out ---------------- */
/* Pinned across the top so it cannot be missed: every save from this tab is
   now refused, and a student who keeps typing would otherwise lose it all. */
.stale-banner {
  position: sticky; top: 0; z-index: 100;
  margin: 0; border-radius: 0;
}
.stale-banner .btn { margin-left: var(--sp-2); }

/* The file stopped part-way as it loaded; said once, above the checks. */
.asg-warning {
  margin: 0 0 6px; padding: 6px 8px;
  border-left: 3px solid var(--warn); border-radius: var(--radius-sm);
  background: var(--warn-soft); color: var(--fg-dim);
  font-size: var(--fs-xs); line-height: 1.45; list-style: none;
}

/* --- 21. tabs, and two editors side by side --------------------------- */
/* Every project kind. The right-hand group is hidden until Split; each
   group is a strip of tabs over one editor. */
.pane > .editors { flex: 1; min-height: 0; }
.editors {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-height: 0; min-width: 0; height: 100%;
}
.editors.is-split {
  /* Left editor | the bar between them | right editor. --gsplit is the left
     editor's share, set by dragging the bar (ide.js); each keeps 160px. */
  grid-template-columns:
    clamp(160px, var(--gsplit, 50%), calc(100% - 161px)) auto minmax(0, 1fr);
}
.editors:not(.is-split) > .group-resizer { display: none; }
.editor-group {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-width: 0; min-height: 0;
  background: var(--bg-inset);
}
.editor-group > .editor-host,
.editor-group > .editor-empty { grid-row: 2; }
.editor-empty {
  display: grid; place-items: center; padding: var(--sp-4);
  color: var(--fg-faint); font-size: var(--fs-sm); text-align: center;
}
.tabbar {
  display: flex; align-items: stretch; min-width: 0; min-height: 32px;
  background: var(--bg-raised); border-bottom: 1px solid var(--border);
}
.tabbar-tabs { display: flex; flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: thin; }
.tab {
  display: inline-flex; align-items: center; flex: none;
  border-right: 1px solid var(--border);
  color: var(--fg-dim);
}
.tab[aria-selected="true"] { background: var(--bg-inset); color: var(--fg); box-shadow: inset 0 -2px 0 var(--border-strong); }
/* With two editors, the one being typed in carries the accent. */
.editor-group.is-focused .tab[aria-selected="true"] { box-shadow: inset 0 -2px 0 var(--accent); }
.tab-name {
  padding: 6px 4px 6px 12px; border: 0; background: none; color: inherit;
  font: inherit; font-size: var(--fs-sm); white-space: nowrap; cursor: pointer;
}
.tab-name:hover { color: var(--fg); }
.tab-note {
  margin-left: 4px; padding: 0 6px; border-radius: var(--radius-full);
  background: var(--bg-active); color: var(--fg-faint); font-size: 10px; vertical-align: 1px;
}
.tab.is-dirty .tab-name::after { content: " \2022"; color: var(--warn); }
.tab-close {
  width: 22px; height: 22px; margin-right: 4px; border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--fg-faint); font-size: 15px; line-height: 1; cursor: pointer;
}
.tab-close:hover { background: var(--bg-hover); color: var(--fg); }
.tabbar-actions { display: flex; align-items: center; gap: 2px; padding: 0 6px; flex: none; }
.tabbar-btn {
  padding: 3px 8px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: none; color: var(--fg-dim); font: inherit; font-size: var(--fs-xs); cursor: pointer;
  white-space: nowrap;
}
.tabbar-btn:hover { background: var(--bg-hover); border-color: var(--border); color: var(--fg); }
/* A file open in a tab, in the file list. */
.file-row.is-open .file-open { color: var(--fg); }
/* Narrow screens: stack the two editors instead of squeezing them. */
@media (max-width: 900px) {
  .editors.is-split {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
    row-gap: 1px;
    background: var(--border);        /* the 1px gap is the divider */
  }
  .editors > .group-resizer { display: none; }
}

/* --- 22. Submit, always in reach ------------------------------------- */
/* Pinned to the bottom of the sidebar while the list of checks scrolls, so
   Submit is never a long scroll away from the code. */
.asg-actions {
  position: sticky; bottom: calc(-1 * var(--sp-2)); z-index: 2;
  margin: 0 calc(-1 * var(--sp-2)) calc(-1 * var(--sp-2));
  padding: var(--sp-2);
  background: var(--bg-inset);
  border-top: 1px solid var(--border);
  box-shadow: 0 -8px 12px -8px rgba(0, 0, 0, 0.45);
}
.asg-actions .task-note { margin: 4px 0 0; }
.asg-actions .btn-block + .task-note + .btn-block { margin-top: 6px; }
/* A long file name must not push its own close button out of sight. */
.tab-label {
  display: inline-block; max-width: 18ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; vertical-align: bottom;
}
/* Two editors share the width: the move button keeps only its arrow. */
.editors.is-split .tabbar-btn .lbl { display: none; }
/* Tabs shrink to fit a narrow strip (only the NAME is trimmed, with "..."),
   and scroll only once each is down to a readable minimum. */
.tab { flex: 0 1 auto; min-width: 64px; }
.tab-name { display: inline-flex; align-items: center; gap: 4px; min-width: 0; overflow: hidden; }
.tab-label { flex: 0 1 auto; min-width: 0; }
.tab-note { flex: none; margin-left: 0; }
.tab-close { flex: none; }

/* --- 23. assignment progress (instructor) ----------------------------- */
/* Like lesson progress: every state has a SHAPE as well as a colour. */
.stat-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin: 0 0 var(--sp-4); }
.stat {
  display: flex; flex-direction: column; min-width: 110px;
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--bg-raised);
}
.stat-num { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat-label { color: var(--fg-dim); font-size: var(--fs-sm); }
.asg-progress .check-col { min-width: 1.9em; padding-left: 2px; padding-right: 2px; font-size: var(--fs-xs); }
.asg-progress .check-hidden { display: block; color: var(--fg-faint); font-size: 9px; }
.asg-progress .check-cell { padding-left: 2px; padding-right: 2px; font-weight: 700; }
.check-pass { color: var(--good); }
.check-fail { color: var(--bad); }
.check-new  { color: var(--fg-faint); font-weight: 400; }
.check-none { color: var(--fg-faint); }
.asg-progress .check-stuck { color: var(--bad); font-weight: 700; background: var(--bad-soft); }

/* --- 24. uploads, and the image / sound viewer ------------------------- */
.tree-head .icon-btn ~ .icon-btn { margin-left: 2px; }
.filetree.is-drop-target { outline: 2px dashed var(--accent); outline-offset: -4px; background: var(--accent-soft); }
/* An image or sound open in a tab: fills the editor area of its side. */
.editor-group > .media-host { grid-row: 2; min-height: 0; min-width: 0; overflow: auto; background: var(--bg-inset); }
.media-panel { padding: var(--sp-3); outline: none; }
.media-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-2); }
.media-code { font-family: var(--mono); font-size: var(--fs-sm); padding: 3px 8px; border-radius: var(--radius-sm); background: var(--bg-raised); }
.media-controls { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); align-items: center; font-size: var(--fs-sm); color: var(--fg-dim); }
.media-field { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
.media-field input[type="number"] { width: 4.8em; padding: 2px 6px; }
.media-field select { padding: 2px 6px; min-width: 7em; }
.media-info { margin: var(--sp-2) 0; font-size: var(--fs-sm); color: var(--fg-dim); }
.media-stage {
  overflow: auto; padding: var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  /* A checkerboard, so transparent parts of a sprite show as transparent. */
  background-color: #2a2f3a;
  background-image:
    linear-gradient(45deg, #353b48 25%, transparent 25%), linear-gradient(-45deg, #353b48 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #353b48 75%), linear-gradient(-45deg, transparent 75%, #353b48 75%);
  background-size: 16px 16px;
  background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}
.media-stage canvas { display: block; image-rendering: pixelated; cursor: crosshair; }
.media-readout { font-family: var(--mono); font-size: var(--fs-sm); color: var(--fg-dim); margin: var(--sp-2) 0 0; min-height: 1.5em; }
.media-panel audio { width: 100%; max-width: 520px; margin: var(--sp-2) 0; }
.media-field input[type="color"] { width: 2.4em; height: 1.7em; padding: 0 2px; cursor: pointer; }
/* A font: the same words at each size pygame.font.Font() might be given. */
.media-sample { width: 100%; max-width: 640px; margin-top: var(--sp-2); }
.media-font { overflow-x: auto; }
.media-font-row { display: flex; align-items: baseline; gap: var(--sp-3); padding: 6px 0; border-bottom: 1px solid var(--border); }
.media-font-size { flex: none; width: 2.5em; text-align: right; font-family: var(--mono); font-size: var(--fs-sm); color: var(--fg-faint); }
.media-font-text { white-space: nowrap; color: var(--fg); line-height: 1.25; }

/* --- 25. a teacher's project, shared with a class ---------------------- */
/* The student's lists (assignments, From your teacher) sit above their own
   projects: keep them from touching what follows. */
#asg-section, #shared-section { margin-bottom: var(--sp-5); }
#shared-section > .sub { margin-bottom: var(--sp-3); }
/* Up to three buttons beside a name: on a phone they go under it. */
@media (max-width: 560px) {
  #shared-list .list-row { flex-wrap: wrap; row-gap: var(--sp-2); }
  #shared-list .list-main { flex-basis: 100%; }
}

/* --- 26. what a game prints ------------------------------------------- */
/* A strip under the game: its print() output. The game above gives up the
   room (a canvas is nearly always limited by the pane's width, not height). */
.gameout {
  flex: none; display: flex; flex-direction: column; min-height: 0;
  height: clamp(84px, 26%, 230px);
  border-top: 1px solid var(--border);
}
.gameout > .term { flex: 1; min-height: 0; }
.gameout > .term:empty::before { content: "Anything your game prints with print() shows up here."; }
