/* ──────────────────────────────────────────────────────────────
   THEMES — each has its own mood:
     dark    : VS-Code-style cool grays, blue+yellow syntax
     dracula : classic pink/green/cyan, deep slate
     ocean   : deep navy + coral/gold/sky (sunset on water)
     light   : warm cream paper, jewel-tone syntax
   syntax vars (--syn-*) drive CodeMirror token colors
   so that "variable" is always distinct from "function name".
   ────────────────────────────────────────────────────────────── */

:root,
[data-theme="dark"] {
  --bg:           #1e1e1e;
  --bg-panel:     #252526;
  --bg-surface:   #2d2d30;
  --bg-hover:     #3e3e42;
  --border:       #3f3f46;
  --accent:       #569cd6;
  --accent-green: #4ec9b0;
  --accent-red:   #f48771;
  --accent-yellow:#dcdcaa;
  --accent-challenge: #a78bfa;
  --text:         #d4d4d4;
  /* Bumped from #858585 to clear WCAG AA 4.5:1 on --bg-panel.
     Used for save-status, .assignment-id, profile hints, etc. */
  --text-dim:     #9d9d9d;
  --gutter-bg:    #252526;
  --panel-width:  260px;

  /* History view uses its own palette so the five event types
     (activity / run / paste / break / session-end) stay clearly distinct
     instead of clashing with the muted syntax accents. */
  --hist-activity: #38bdf8;
  --hist-run:      #22c55e;
  --hist-paste:    #facc15;
  --hist-break:    #ef4444;
  --hist-end:      #ff8c5a;
  --hist-copy:     #6366f1;

  --syn-comment:  #6a9955;
  --syn-keyword:  #c586c0;
  --syn-fn:       #dcdcaa;
  --syn-var:      #9cdcfe;
  --syn-var2:     #4ec9b0;
  --syn-builtin:  #4fc1ff;
  --syn-string:   #ce9178;
  --syn-number:   #b5cea8;
  --syn-atom:     #569cd6;
  --syn-operator: #d4d4d4;
  --syn-property: #9cdcfe;
}

[data-theme="dracula"] {
  --bg:           #282a36;
  --bg-panel:     #21222c;
  --bg-surface:   #343746;
  --bg-hover:     #44475a;
  --border:       #44475a;
  --accent:       #bd93f9;
  --accent-green: #50fa7b;
  --accent-red:   #ff5555;
  --accent-yellow:#f1fa8c;
  --accent-challenge: #bd93f9;
  --text:         #f8f8f2;
  /* Bumped from #6272a4 — original Dracula value clearly failed AA on
     panel bg. New tone keeps the muted dracula-purple feel but reaches
     ~4.6:1 against #21222c so hints + IDs are actually readable. */
  --text-dim:     #909bca;
  --gutter-bg:    #21222c;

  --syn-comment:  #6272a4;
  --syn-keyword:  #ff79c6;
  --syn-fn:       #50fa7b;
  --syn-var:      #f8f8f2;
  --syn-var2:     #ffb86c;
  --syn-builtin:  #8be9fd;
  --syn-string:   #f1fa8c;
  --syn-number:   #bd93f9;
  --syn-atom:     #bd93f9;
  --syn-operator: #ff79c6;
  --syn-property: #66d9ef;

  --hist-activity: #8be9fd;
  --hist-run:      #50fa7b;
  --hist-paste:    #f1fa8c;
  --hist-break:    #ff5555;
  --hist-end:      #ffb86c;
  --hist-copy:     #bd93f9;
}

[data-theme="ocean"] {
  --bg:           #06243a;
  --bg-panel:     #04203b;
  --bg-surface:   #0d3656;
  --bg-hover:     #15507c;
  --border:       #1a3e5d;
  --accent:       #ff9f6b;
  --accent-green: #5fd2a3;
  --accent-red:   #ff6b8a;
  --accent-yellow:#fbbf24;
  --accent-challenge: #c084fc;
  --text:         #d8ecf7;
  /* Bumped from #5e7d99 to clear AA 4.5 on Ocean's deep blue panel.
     Stays steel-blue so it still reads as "Ocean theme dim text." */
  --text-dim:     #82a3c1;
  --gutter-bg:    #04203b;

  --syn-comment:  #5a7d99;
  --syn-keyword:  #ff9f6b;
  --syn-fn:       #fcd34d;
  --syn-var:      #93c5fd;
  --syn-var2:     #c4b5fd;
  --syn-builtin:  #5eead4;
  --syn-string:   #f9a8d4;
  --syn-number:   #fb923c;
  --syn-atom:     #c084fc;
  --syn-operator: #ff9f6b;
  --syn-property: #7dd3fc;

  --hist-activity: #7dd3fc;
  --hist-run:      #5fd2a3;
  --hist-paste:    #fbbf24;
  --hist-break:    #ff6b8a;
  --hist-end:      #c084fc;
  --hist-copy:     #2563eb;
}

[data-theme="light"] {
  --bg:           #e8e1cc;
  --bg-panel:     #ddd5bd;
  --bg-surface:   #d0c6ab;
  --bg-hover:     #c3b793;
  --border:       #b1a47e;
  --accent:       #1d4ed8;
  --accent-green: #15803d;
  --accent-red:   #b91c1c;
  --accent-yellow:#a16207;
  --accent-challenge: #7c3aed;
  --text:         #2a241a;
  /* Bumped from #6f6754 — original wasn't dark enough on beige bg-panel
     (only ~3.7:1). New value is ~5.5:1 so hints + small labels are
     comfortably readable. */
  --text-dim:     #574e3d;
  --gutter-bg:    #ddd5bd;

  --syn-comment:  #7a705a;
  --syn-keyword:  #7c2d92;
  --syn-fn:       #1e40af;
  --syn-var:      #047857;
  --syn-var2:     #b91c1c;
  --syn-builtin:  #c2410c;
  --syn-string:   #166534;
  --syn-number:   #92400e;
  --syn-atom:     #7c2d92;
  --syn-operator: #be185d;
  --syn-property: #0369a1;

  --hist-activity: #1d4ed8;
  --hist-run:      #15803d;
  --hist-paste:    #a16207;
  --hist-break:    #b91c1c;
  --hist-end:      #be185d;
  --hist-copy:     #7c3aed;
}

/* ── PROFILE PANEL ── */
.profile-panel {
  position: fixed;
  top: 52px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px 14px 10px;
  z-index: 200;
  width: 280px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.45);
}

.profile-section { margin-bottom: 14px; }

.profile-label {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-dim);
  margin-bottom: 6px;
}

.profile-input,
.profile-select {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 7px 10px;
  border-radius: 5px;
  font-size: 0.88rem;
  font-family: inherit;
  outline: none;
  transition: border-color 0.15s;
}

.profile-input:focus,
.profile-select:focus { border-color: var(--accent); }

.profile-select {
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
                    linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
  background-position: calc(100% - 16px) 50%, calc(100% - 11px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 28px;
  cursor: pointer;
}

.profile-hint {
  margin-top: 5px;
  font-size: 0.72rem;
  color: var(--text-dim);
}

.profile-divider {
  height: 1px;
  background: var(--border);
  margin: 0 -14px 14px;
}

.theme-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}

.style-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--text);
  transition: background 0.15s;
  border: 1px solid transparent;
}

.style-option:hover { background: var(--bg-hover); }
.style-option.active {
  color: var(--accent);
  font-weight: 600;
  border-color: var(--accent);
  background: var(--bg-surface);
}

.swatch {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid var(--border);
  flex-shrink: 0;
  display: inline-block;
}

.profile-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  min-height: 18px;
  margin-top: 4px;
}

.profile-save-status {
  font-size: 0.72rem;
  color: var(--text-dim);
  transition: opacity 0.2s;
}

.profile-save-status.saved { color: var(--accent-green); }
.profile-save-status.error { color: var(--accent-red); }

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Segoe UI', sans-serif;
  background: var(--bg);
  color: var(--text);
  height: 100vh;
  overflow: hidden;
}

/* ── LOGIN PAGE ── */
.login-page {
  background:
    radial-gradient(1200px 600px at 85% -10%, rgba(79,195,247,0.10), transparent 60%),
    radial-gradient(900px 500px at -10% 110%, rgba(189,147,249,0.10), transparent 60%),
    var(--bg);
}

.login-split {
  display: grid;
  grid-template-columns: minmax(360px, 1fr) 1.1fr;
  width: 100vw;
  height: 100vh;
}
.login-split.hidden { display: none; }

/* Pre-auth splash for returning users. Visible only when the cache (auth.js
   localStorage) says we should expect a sign-in to land. Filled in
   immediately at script-load time so there's no flash of the login form for
   a returning student. */
.login-splash {
  position: fixed;
  inset: 0;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  z-index: 100;
}
.login-splash.hidden { display: none; }
.login-splash-spinner {
  width: 36px;
  height: 36px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: login-splash-spin 0.85s linear infinite;
}
@keyframes login-splash-spin {
  to { transform: rotate(360deg); }
}
.login-splash-text {
  color: var(--text-dim);
  font-size: 0.85rem;
  margin: 0;
}

.login-left {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
}

.login-card {
  width: 100%;
  max-width: 380px;
}

.login-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 36px;
  color: var(--accent);
}

.logo-mark {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

.logo-text {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.2px;
}

.login-headline {
  font-size: 1.75rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.2;
  margin-bottom: 10px;
  letter-spacing: -0.01em;
}

.login-sub {
  color: var(--text-dim);
  font-size: 0.9rem;
  line-height: 1.55;
  margin-bottom: 28px;
}

.google-btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  background: #fff;
  color: #1f1f1f;
  border: 1px solid #dadce0;
  padding: 10px 16px;
  border-radius: 6px;
  font-family: 'Roboto', 'Segoe UI', sans-serif;
  font-size: 0.92rem;
  font-weight: 500;
  cursor: pointer;
  transition: box-shadow 0.15s, background 0.15s, transform 0.05s;
}

.google-btn:hover {
  box-shadow: 0 1px 3px rgba(0,0,0,0.18), 0 1px 2px rgba(0,0,0,0.12);
  background: #f7f8fa;
}

.google-btn:active { transform: translateY(1px); }

.google-btn:disabled {
  opacity: 0.7;
  cursor: wait;
}

.google-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.google-btn-label { flex: 1; text-align: left; }

.login-foot {
  margin-top: 18px;
  font-size: 0.78rem;
  color: var(--text-dim);
}

.error-msg {
  color: var(--accent-red);
  margin-top: 14px;
  font-size: 0.85rem;
  min-height: 1.2em;
}

/* ── LOGIN RIGHT: code preview ── */
.login-right {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  border-left: 1px solid var(--border);
  background:
    linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.12) 100%),
    var(--bg-panel);
}

.code-card {
  width: 100%;
  max-width: 460px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 20px 50px rgba(0,0,0,0.35), 0 4px 12px rgba(0,0,0,0.2);
}

.code-card-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 14px;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border);
}

.code-card-bar .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  display: inline-block;
}

.dot-red    { background: #ff5f57; }
.dot-yellow { background: #febc2e; }
.dot-green  { background: #28c840; }

.code-card-title {
  margin-left: 10px;
  font-size: 0.75rem;
  color: var(--text-dim);
  font-family: 'Fira Code', 'Courier New', monospace;
}

.code-card-body {
  padding: 18px 20px;
  font-family: 'Fira Code', 'Courier New', monospace;
  font-size: 0.85rem;
  line-height: 1.7;
  color: var(--text);
  white-space: pre;
  overflow-x: auto;
}

/* tokens — tuned per theme via accent variables */
.code-card-body .t-c { color: var(--text-dim); font-style: italic; }   /* comment */
.code-card-body .t-k { color: var(--accent); }                          /* keyword */
.code-card-body .t-f { color: var(--accent-green); }                    /* function name */
.code-card-body .t-s { color: var(--accent-yellow); }                   /* string */
.code-card-body .t-b { color: var(--accent-red); }                      /* builtin */

@media (max-width: 820px) {
  .login-split { grid-template-columns: 1fr; }
  .login-right { display: none; }
}

/* ── TOP BAR ── */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border);
  padding: 0 12px;
  height: 48px;
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
}

.topbar-left,
.topbar-right {
  display: flex;
  align-items: center;
  gap: 4px;
}

.topbar-center {
  display: flex;
  align-items: center;
  gap: 8px;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

.nav-btn {
  background: transparent;
  color: var(--text-dim);
  border: none;
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.85rem;
  transition: background 0.15s, color 0.15s;
}

.nav-btn:hover { background: var(--bg-hover); color: var(--text); }
.nav-btn.active { background: var(--bg-surface); color: var(--text); }

/* Co-op bar — appears at the top of cell assignments with coopEnabled.
   Purple-tinted, sits inside the cells container above the first cell.
   The Invite/Accept buttons live here (not in the top nav). */
.coop-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  margin-bottom: 14px;
  background: color-mix(in srgb, var(--accent-challenge) 14%, var(--bg-panel));
  border: 1px solid color-mix(in srgb, var(--accent-challenge) 35%, transparent);
  border-left: 3px solid var(--accent-challenge);
  border-radius: 6px;
  font-size: 0.85rem;
}
.coop-bar-label {
  font-weight: 700;
  color: var(--accent-challenge);
  letter-spacing: 0.03em;
  margin-right: 4px;
}
.coop-bar .nav-btn {
  background: color-mix(in srgb, var(--accent-challenge) 18%, transparent);
  color: var(--accent-challenge);
}
.coop-bar .nav-btn:hover {
  background: color-mix(in srgb, var(--accent-challenge) 28%, transparent);
  color: var(--text);
}
.coop-button-wrap {
  position: relative;
  display: inline-block;
}
.coop-accept-badge {
  display: inline-block;
  background: var(--accent-red);
  color: white;
  font-size: 0.62rem;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: 9px;
  margin-left: 5px;
  min-width: 14px;
  text-align: center;
}
.coop-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 240px;
  max-width: 340px;
  max-height: 360px;
  overflow-y: auto;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  z-index: 200;
  padding: 4px;
}
.coop-menu-empty {
  padding: 10px 12px;
  color: var(--text-dim);
  font-size: 0.85rem;
  font-style: italic;
}
.coop-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  padding: 8px 12px;
  border-radius: 4px;
  cursor: pointer;
  color: var(--text);
  font-size: 0.88rem;
}
.coop-menu-item:hover { background: var(--bg-hover); }
.coop-menu-sub {
  color: var(--text-dim);
  font-size: 0.78rem;
  margin-left: 4px;
}
.coop-menu-invite {
  display: flex;
  align-items: center;
  gap: 4px;
}
.coop-menu-invite .coop-menu-accept { flex: 1; }
.coop-menu-decline {
  background: transparent;
  color: var(--text-dim);
  border: 0;
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
}
.coop-menu-decline:hover {
  background: color-mix(in srgb, var(--accent-red) 18%, transparent);
  color: var(--accent-red);
}

/* "Co-op with X · Exit" indicator inside the bar, shown when state.coopViewing is set */
.coop-bar-active {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  padding: 4px 10px;
  background: color-mix(in srgb, var(--accent-challenge) 24%, transparent);
  border: 1px solid var(--accent-challenge);
  color: var(--accent-challenge);
  border-radius: 14px;
  font-size: 0.78rem;
  font-weight: 700;
}
.coop-exit-btn {
  background: var(--accent-challenge);
  color: white;
  border: 0;
  padding: 2px 8px;
  border-radius: 10px;
  cursor: pointer;
  font-size: 0.72rem;
  font-weight: 700;
}
.coop-exit-btn:hover { filter: brightness(1.15); }

.run-btn {
  background: var(--accent-green);
  color: white;
  border: none;
  padding: 6px 18px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  transition: filter 0.15s;
  margin-right: 4px;
}

.run-btn:hover { filter: brightness(1.1); }

.download-btn {
  background: var(--bg-surface);
  color: var(--text);
  border: 1px solid var(--border);
  width: 30px;
  height: 30px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background 0.15s, filter 0.15s;
}
.download-btn:hover { background: var(--bg-hover); }
.download-btn:active { filter: brightness(0.9); }

.file-name {
  font-size: 0.85rem;
  color: var(--text);
}

.temp-badge {
  font-size: 0.7rem;
  color: var(--accent-yellow);
  background: rgba(255,152,0,0.1);
  border: 1px solid var(--accent-yellow);
  padding: 2px 6px;
  border-radius: 3px;
}

.save-status {
  font-size: 0.75rem;
  color: var(--text-dim);
}

/* ── PRESENCE (real-time collaboration heartbeat) ── */
.presence-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.presence-indicator.hidden { display: none; }

.presence-peer {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  color: var(--text-dim);
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-panel);
  white-space: nowrap;
}
.presence-peer.is-teacher { color: var(--accent); border-color: var(--accent); }

.presence-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent-green);
  flex: 0 0 auto;
}
.presence-peer.is-teacher .presence-dot { background: var(--accent); }

/* Pulse the dot only while the peer is actively typing. */
.presence-peer.is-typing .presence-dot { animation: presence-pulse 1s ease-in-out infinite; }
.presence-typing {
  font-style: italic;
  color: var(--accent-green);
  font-size: 0.68rem;
}
.presence-peer.is-teacher .presence-typing { color: var(--accent); }

@keyframes presence-pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(1.5); }
}

.user-email {
  font-size: 0.8rem;
  color: var(--text-dim);
  margin: 0 4px;
}

/* ── LEFT PANELS ── */
.left-panel {
  position: fixed;
  top: 48px;
  left: 0;
  width: var(--panel-width);
  height: calc(100vh - 48px);
  background: var(--bg-panel);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  z-index: 90;
  overflow: hidden;
}

.left-panel.hidden { display: none; }

.panel-header {
  padding: 10px 14px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
}

/* Filter input above each panel-list. Cheap substring filter wired in JS
   on the assignments + students panels; stays out of the way visually. */
.panel-search-wrap {
  padding: 6px 10px 4px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.panel-search {
  width: 100%;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 5px 8px;
  font-size: 0.8rem;
  font-family: inherit;
  box-sizing: border-box;
}
.panel-search:focus { outline: none; border-color: var(--accent); }
.panel-search::placeholder { color: var(--text-dim); }
.search-hide { display: none !important; }

.panel-list {
  flex: 1;
  overflow-y: auto;
  list-style: none;
  padding: 4px 0;
}

.panel-list li {
  padding: 8px 14px;
  cursor: pointer;
  font-size: 0.875rem;
  color: var(--text);
  transition: background 0.15s;
  display: flex;
  align-items: center;
  gap: 8px;
}

.panel-list li:hover { background: var(--bg-hover); }
.panel-list li.active { background: var(--bg-surface); color: var(--accent); }

/* Quiz row: amber tint + left bar + 'Quiz' badge next to the title.
   Distinct from regular homework rows so teachers + students can spot a
   graded quiz at a glance. */
#assignments-list li.is-quiz {
  background: color-mix(in srgb, #f59e0b 14%, var(--bg-panel));
  border-left: 3px solid #f59e0b;
  padding-left: 9px;   /* compensate for the 3px border */
}
#assignments-list li.is-quiz:hover {
  background: color-mix(in srgb, #f59e0b 20%, var(--bg-hover));
}
#assignments-list li.is-quiz.active {
  background: color-mix(in srgb, #f59e0b 22%, var(--bg-surface));
}
.assignment-quiz-badge {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #92400e;
  background: #fde68a;
  padding: 1px 6px;
  border-radius: 4px;
  margin-left: 6px;
  text-transform: uppercase;
  flex-shrink: 0;
}
[data-theme="light"] #assignments-list li.is-quiz {
  background: color-mix(in srgb, #f59e0b 22%, var(--bg-panel));
}

/* Bonus row: purple tint + left bar + 'Bonus' badge. Uses the same purple
   as challenge tests (--accent-challenge) so the visual language reads as
   "this is the stretch/extras section." */
#assignments-list li.is-bonus {
  background: color-mix(in srgb, var(--accent-challenge) 14%, var(--bg-panel));
  border-left: 3px solid var(--accent-challenge);
  padding-left: 9px;
}
#assignments-list li.is-bonus:hover {
  background: color-mix(in srgb, var(--accent-challenge) 20%, var(--bg-hover));
}
#assignments-list li.is-bonus.active {
  background: color-mix(in srgb, var(--accent-challenge) 22%, var(--bg-surface));
}
.assignment-bonus-badge {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: white;
  background: var(--accent-challenge);
  padding: 1px 6px;
  border-radius: 4px;
  margin-left: 6px;
  text-transform: uppercase;
  flex-shrink: 0;
}
[data-theme="light"] #assignments-list li.is-bonus {
  background: color-mix(in srgb, var(--accent-challenge) 22%, var(--bg-panel));
}

.delete-btn {
  background: transparent;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  padding: 2px 5px;
  border-radius: 3px;
  font-size: 0.75rem;
  margin-left: auto;
  opacity: 0;
  transition: opacity 0.15s, color 0.15s;
  flex-shrink: 0;
}

.panel-list li:hover .delete-btn { opacity: 1; }
.delete-btn:hover { color: var(--accent-red); }

/* Soft-deleted files drawer at the bottom of the personal panel. Hidden
   entirely when nothing's deleted; toggle row stays clickable when expanded
   and the file list itself collapses. */
.personal-deleted {
  border-top: 1px solid var(--border);
  flex-shrink: 0;
  max-height: 50%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.personal-deleted.hidden { display: none; }
.personal-deleted-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  background: transparent;
  border: none;
  padding: 8px 12px;
  color: var(--text-dim);
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  flex-shrink: 0;
}
.personal-deleted-toggle:hover { background: var(--bg-hover); color: var(--text); }
.personal-deleted-caret {
  font-size: 0.7rem;
  width: 12px;
  display: inline-flex;
  justify-content: center;
}
.personal-deleted-label { flex: 1; }
.personal-deleted-count {
  background: var(--bg-surface);
  color: var(--text-dim);
  font-size: 0.66rem;
  padding: 1px 6px;
  border-radius: 9px;
  font-weight: 600;
}
.personal-deleted-list {
  padding: 0 0 6px;
  margin: 0;
  list-style: none;
  overflow-y: auto;
}
.personal-deleted-list.hidden { display: none; }
.personal-deleted-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px 5px 26px;
  font-size: 0.78rem;
  color: var(--text-dim);
}
.personal-deleted-row:hover { background: var(--bg-hover); }
.personal-deleted-name {
  flex: 1;
  text-decoration: line-through;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.personal-deleted-action {
  background: transparent;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  padding: 2px 5px;
  border-radius: 3px;
  font-size: 0.85rem;
  line-height: 1;
  flex-shrink: 0;
}
.personal-deleted-action.restore:hover { color: var(--accent-green); }
.personal-deleted-action.purge:hover   { color: var(--accent-red); }

.panel-action-btn {
  background: var(--accent);
  color: white;
  border: none;
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.8rem;
  transition: filter 0.15s;
}

.panel-action-btn:hover { filter: brightness(1.1); }

.panel-cancel-btn {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.8rem;
  transition: background 0.15s;
}

.panel-cancel-btn:hover { background: var(--bg-hover); }

.icon-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
  width: 22px;
  height: 22px;
  border-radius: 3px;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
}

.icon-btn:hover { background: var(--bg-hover); color: var(--text); }

.new-file-form {
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
  flex-shrink: 0;
}

.new-file-form input {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 8px;
  border-radius: 4px;
  font-size: 0.85rem;
  margin-bottom: 6px;
  outline: none;
}

.new-file-form input:focus { border-color: var(--accent); }

.new-file-actions {
  display: flex;
  gap: 6px;
}

/* ── MAIN LAYOUT ── */
.main-layout {
  display: flex;
  margin-top: 48px;
  height: calc(100vh - 48px);
  margin-left: 0;
  transition: margin-left 0.2s ease;
}

.main-layout.panel-open { margin-left: var(--panel-width); }

/* ── WELCOME SCREEN ── */
.welcome-screen {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.welcome-box {
  text-align: center;
  padding: 40px 48px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  max-width: 640px;
  width: 100%;
}

.welcome-box h1 {
  font-size: 1.5rem;
  color: var(--text);
  margin-bottom: 8px;
}

.welcome-box > p {
  color: var(--text-dim);
  font-size: 0.875rem;
  line-height: 1.6;
  margin-bottom: 24px;
}

.welcome-box strong { color: var(--text); }

/* Two big side-by-side action cards. Assignments on the left (primary
   path), Quick Terminal on the right (sandbox). Stack vertically when the
   container narrows so each card stays comfortably tappable. */
.welcome-cards {
  display: flex;
  gap: 16px;
  justify-content: center;
}

.welcome-card {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 28px 18px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  cursor: pointer;
  font-family: inherit;
  text-align: center;
  transition: background 0.15s, border-color 0.15s, transform 0.05s;
}

.welcome-card:hover {
  background: var(--bg-hover);
  border-color: var(--accent-green);
}

.welcome-card:active { transform: translateY(1px); }

.welcome-card-icon {
  font-size: 2rem;
  line-height: 1;
}

.welcome-card-title {
  font-size: 1rem;
  font-weight: 600;
}

.welcome-card-note {
  font-size: 0.78rem;
  color: var(--text-dim);
}

/* The Quick Terminal card is the secondary path — subtler accent on hover. */
.welcome-card-temp:hover { border-color: var(--accent-yellow); }

@media (max-width: 540px) {
  .welcome-cards { flex-direction: column; }
}

/* ── EDITOR AREA ── */
.editor-area {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-width: 0;
  position: relative;
}

.editor-area.hidden { display: none; }

.temp-banner {
  background: rgba(255,152,0,0.1);
  border-bottom: 1px solid var(--accent-yellow);
  color: var(--accent-yellow);
  font-size: 0.8rem;
  padding: 5px 16px;
  text-align: center;
  flex-shrink: 0;
}

.temp-banner.hidden { display: none; }

.editor-wrapper {
  flex: 1;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

/* ── CODEMIRROR ── */
.CodeMirror {
  height: 100%;
  font-size: var(--editor-font-size, 14px);
  font-family: 'Fira Code', 'Courier New', monospace;
  line-height: 1.6;
}

.CodeMirror-gutters {
  background: var(--gutter-bg) !important;
  border-right: 1px solid var(--border) !important;
  min-width: 44px;
}

.CodeMirror-linenumber {
  color: var(--text-dim) !important;
  padding: 0 10px 0 6px !important;
}

/* ── OUTPUT AREA ── */
.output-area {
  height: 35vh;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  /* No border-top — the resize handle above replaces the visual divider. */
}

/* Drag bar between the code editor and the terminal. Students can drag
   vertically to shrink/grow the terminal. Reset to default on page load
   (no persistence). */
.terminal-resize-handle {
  height: 6px;
  background: var(--border);
  cursor: ns-resize;
  flex-shrink: 0;
  transition: background 0.15s;
  position: relative;
}
.terminal-resize-handle::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 36px;
  height: 2px;
  margin-left: -18px;
  margin-top: -1px;
  background: var(--text-dim);
  border-radius: 1px;
  opacity: 0.4;
}
.terminal-resize-handle:hover,
.terminal-resize-handle.dragging {
  background: var(--accent);
}
.terminal-resize-handle:hover::after,
.terminal-resize-handle.dragging::after {
  opacity: 0.8;
  background: var(--bg-panel);
}

.output-label {
  padding: 4px 12px;
  font-size: 0.7rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 1px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}

#run-status {
  font-size: 0.7rem;
  color: var(--accent-green);
  text-transform: none;
  letter-spacing: 0;
}

#terminal {
  flex: 1;
  overflow: hidden;
  padding: 4px;
}

/* ── STUDENT PANEL ── */
.student-panel {
  width: 260px;
  background: var(--bg-panel);
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
}

.student-panel.hidden { display: none; }

.student-block-header {
  padding: 8px 14px !important;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--text-dim);
  cursor: pointer !important;
  border-top: 1px solid var(--border);
  background: var(--bg-panel);
  display: flex !important;
  align-items: center;
  gap: 6px;
  user-select: none;
  transition: background 0.12s, color 0.12s;
}
.student-block-header:first-child { border-top: none; }
.student-block-header:hover { background: var(--bg-hover) !important; color: var(--text); }

.student-block-header .block-caret {
  display: inline-block;
  font-size: 0.6rem;
  transition: transform 0.12s ease;
  width: 10px;
  text-align: center;
}
.student-block-header.collapsed .block-caret { transform: rotate(-90deg); }

.student-block-header .block-title { flex: 1; }

.student-block-header .block-count {
  background: var(--bg-surface);
  color: var(--text-dim);
  padding: 1px 7px;
  border-radius: 9px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

.hidden-block { display: none !important; }

.student-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px 7px 10px !important;
  /* Left accent strip slides in when the row becomes .viewing — see below. */
  border-left: 3px solid transparent;
  transition: background 0.12s, border-color 0.12s;
}
/* The student the teacher is currently looking at. Strong enough that the
   arrow-key navigation is obvious; matches the accent color used elsewhere
   for "selected" state. The whole row, not just one assignment row under
   it, picks up the highlight so jumping between students is unmistakable. */
.student-row.viewing {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-left-color: var(--accent);
}
.student-row.viewing .student-name { color: var(--text); font-weight: 600; }


.student-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  font-size: 0.7rem;
  color: var(--text-dim);
  transition: transform 0.12s ease, background 0.15s, color 0.15s;
  flex-shrink: 0;
  border-radius: 3px;
  cursor: pointer;
}
.student-caret:hover { background: var(--bg-hover); color: var(--text); }
.student-row.expanded .student-caret { transform: rotate(90deg); }

.student-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.student-file {
  padding: 5px 14px 5px 36px !important;
  font-size: 0.82rem !important;
  color: var(--text-dim) !important;
  font-family: 'Fira Code', 'Courier New', monospace;
  display: flex !important;
  align-items: center;
  gap: 6px;
}
.student-file::before {
  content: "·";
  color: var(--text-dim);
  font-weight: 700;
  margin-right: 2px;
}
.student-file:hover { color: var(--text) !important; background: var(--bg-hover); }
.student-file.active { color: var(--accent) !important; background: var(--bg-surface); }

.student-file-empty {
  padding: 4px 14px 6px 36px !important;
  font-size: 0.78rem !important;
  color: var(--text-dim) !important;
  font-style: italic;
  cursor: default !important;
}
.student-file-empty:hover { background: transparent !important; }

/* Section divider inside an expanded student — labels the "Assignments" vs
   "Personal files" blocks so teachers know what they're scanning. */
.student-files-section {
  padding: 6px 14px 2px 32px !important;
  font-size: 0.66rem !important;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim) !important;
  cursor: default !important;
}
.student-files-section:hover { background: transparent !important; }
/* Personal file row: same shape as .student-file but the icon is the doc
   emoji rendered inline (no color override). Active matches the assignment
   active state for visual consistency. */
.student-personal-file .sub-status { color: var(--text-dim); }
/* Soft-deleted personal file in the teacher panel — strikethrough name + a
   pill tag so it's unmistakably a deleted file, but still openable + readable
   (the doc is in Firestore, just flagged deleted). */
.student-personal-deleted .student-personal-name {
  text-decoration: line-through;
  color: var(--text-dim);
}
.student-personal-deleted-tag {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--accent-red);
  background: color-mix(in srgb, var(--accent-red) 12%, transparent);
  padding: 1px 6px;
  border-radius: 8px;
  margin-left: 6px;
}

.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.status-grey   { background: #6e7681; }
.status-yellow { background: var(--accent-yellow); }
.status-green  { background: var(--accent-green); }
.status-red    { background: var(--accent-red); }

/* ── TEACHER VIEW BANNER ── */
.teacher-view-banner {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-bottom: 1px solid var(--accent);
  color: var(--accent);
  font-size: 0.82rem;
  padding: 6px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-shrink: 0;
}

.teacher-view-banner.hidden { display: none; }

.teacher-view-banner strong { color: var(--text); font-weight: 600; }

.banner-close {
  background: transparent;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font-size: 0.9rem;
  padding: 2px 6px;
  border-radius: 3px;
  line-height: 1;
}
.banner-close:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }

/* ── INSTRUCTIONS BUTTON + PANEL ── */
.instructions-btn {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 500;
  transition: background 0.15s;
}
.instructions-btn.hidden { display: none; }
.instructions-btn:hover { background: color-mix(in srgb, var(--accent) 25%, transparent); }

.instructions-panel {
  position: absolute;
  top: 18px;
  right: 18px;
  width: 360px;
  max-height: calc(100vh - 200px);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.4);
  z-index: 50;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.instructions-panel.hidden { display: none; }

.instructions-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  background: var(--bg-surface);
}
.instructions-header > :first-child { margin-right: auto; }

.history-snapshot-info {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--text-dim);
}

.history-refresh-btn {
  background: var(--bg-panel);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.75rem;
  transition: background 0.15s;
}
.history-refresh-btn:hover { background: var(--bg-hover); }
.history-refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.instructions-content {
  flex: 1;
  overflow-y: auto;
  padding: 14px 16px;
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--text);
  font-family: 'Segoe UI', -apple-system, sans-serif;
  white-space: pre-wrap;
  word-wrap: break-word;
  margin: 0;
}

/* ── HISTORY / PLAYBACK PANEL ── */
.history-panel {
  position: fixed;
  top: 20px;
  /* Reserve a strip on the LEFT for the assignments / personal panel + on
     the RIGHT for the student panel so the teacher can switch context
     without closing grade mode. Both panels are 260px wide (--panel-width);
     we leave a 30px peek + 20px gutter on each side. */
  left: 80px;
  right: 80px;
  bottom: 20px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.55);
  z-index: 200;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.history-panel.hidden { display: none; }
/* When the assignments / personal panel is open, push the grade panel right
   so the open panel stays fully interactive. */
body:has(#assignments-panel:not(.hidden)) .history-panel,
body:has(#personal-panel:not(.hidden))   .history-panel {
  left: 280px;
}
/* Same for the student panel on the right. */
body:has(#student-panel:not(.hidden)) .history-panel {
  right: 280px;
}

.history-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
}

.history-play-btn {
  background: var(--accent);
  color: white;
  border: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  cursor: pointer;
  font-size: 0.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 0;
  transition: filter 0.15s;
}
.history-play-btn:hover { filter: brightness(1.15); }

.history-slider-wrap {
  position: relative;
  flex: 1;
  height: 27px;
}

.history-markers {
  position: absolute;
  top: 0;
  /* Inset by half the slider thumb width (14px / 2 = 7px) so the markers'
     coordinate space exactly matches the thumb's travel range — otherwise
     an event at 0% sits ~7px left of where the thumb settles at value 0. */
  left: 7px;
  right: 7px;
  height: 17px;
  pointer-events: none;
  /* Sit above the gray scrubber so the run / paste / session-end ticks are
     never covered by the slider track or thumb. */
  z-index: 3;
}

.history-activity {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  display: block;
}
.history-activity path {
  fill: color-mix(in srgb, var(--hist-activity) 70%, transparent);
}

.history-marker {
  position: absolute;
  top: 0;
  height: 100%;
}
/* Run / paste / session-end ticks sit on the scrubber bar below the
   keystroke bumps. Their parent .history-markers is z-indexed above the
   slider so the ticks are never hidden by the gray track or the thumb. */
.history-marker.run,
.history-marker.paste,
.history-marker.copy,
.history-marker.session-end {
  top: 17px;
  height: 10px;
  width: 3px;
  border-radius: 1px;
  /* Center on the event time so the marker visually lines up with the
     slider thumb when you scrub to it. */
  transform: translateX(-50%);
}
/* Copy/cut blip — a thin dot on the scrubber bar. Sized in JS (a small fixed
   height, nudged taller for big copies) so a teacher can spot when a student
   lifted code out of the window. Distinct hue from paste (incoming) so the
   in/out direction reads at a glance. */
.history-marker.copy {
  background: var(--hist-copy);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hist-copy) 30%, transparent);
}
.history-marker.run {
  background: var(--hist-run);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hist-run) 30%, transparent);
}
.history-marker.paste {
  background: var(--hist-paste);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hist-paste) 30%, transparent);
}
.history-marker.session-end {
  background: var(--hist-end);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hist-end) 35%, transparent);
}
/* Off-tab span: pair of dots ("left" + "returned") connected by a thin bar.
   Reuses --hist-end as the "short break" color and escalates to --hist-break
   when the gap crosses the per-teacher break threshold (.long-break class). */
.history-marker.away,
.history-marker.back {
  top: 17px;
  height: 10px;
  width: 3px;
  border-radius: 1px;
  transform: translateX(-50%);
  background: var(--hist-end);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hist-end) 35%, transparent);
}
.history-marker.away.long-break,
.history-marker.back.long-break,
.history-marker.away.orphaned {
  background: var(--hist-break);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hist-break) 35%, transparent);
}
.history-marker.away-link {
  top: 21px;
  height: 2px;
  background: var(--hist-end);
  border-radius: 1px;
  pointer-events: auto;
}
.history-marker.away-link.long-break {
  background: var(--hist-break);
}
/* Per-student deadline line — floor-to-ceiling vertical stripe at the moment
   the assignment was due (resolved per block in mode.js). Sits behind event
   ticks so the dotted glyphs win on overlap. Uses --accent-red to read as a
   warning without competing with the run/paste blip palette. */
.history-marker.deadline {
  width: 2px;
  background: color-mix(in srgb, var(--accent-red) 85%, transparent);
  transform: translateX(-50%);
  pointer-events: auto;
  cursor: help;
  /* Extend above the marker container slightly so the line visibly meets
     the keystroke graph's baseline + dips into the scrubber bar. */
  top: -3px;
  height: calc(100% + 6px);
  z-index: 1;
  box-shadow: 0 0 4px color-mix(in srgb, var(--accent-red) 40%, transparent);
}

.history-marker.ellipsis {
  /* red dash for long breaks; sits on the scrubber bar (below the
     keystroke graph), shorter than the run/paste/session-end ticks.
     Width is set inline in JS to a fraction of the compressed gap span
     so the dash has blank padding on either side. Centered via transform
     so it anchors on the gap's midpoint, like the other markers. */
  display: block;
  height: 4px;
  top: 20px;
  background: var(--hist-break);
  border-radius: 2px;
  transform: translateX(-50%);
}

.history-sliders-stack {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex-shrink: 0;
}
.history-speed-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.history-speed-label {
  font-size: 0.72rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.history-speed {
  width: 90px;
  height: 4px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--border);
  border-radius: 2px;
  cursor: pointer;
  outline: none;
}
.history-speed::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-panel);
}
.history-speed::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-panel);
}
.history-speed-value {
  font-family: 'Fira Code', 'Courier New', monospace;
  font-size: 0.72rem;
  color: var(--text-dim);
  min-width: 28px;
  text-align: right;
}

.history-slider {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2px;
  z-index: 2;
  width: 100%;
  height: 4px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--border);
  border-radius: 2px;
  outline: none;
  cursor: pointer;
}
.history-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-panel);
}
.history-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-panel);
}

.history-time {
  font-family: 'Fira Code', 'Courier New', monospace;
  font-size: 0.78rem;
  color: var(--text-dim);
  white-space: nowrap;
  flex-shrink: 0;
}

.history-legend {
  display: flex;
  gap: 16px;
  padding: 6px 18px;
  font-size: 0.72rem;
  color: var(--text-dim);
  border-bottom: 1px solid var(--border);
  background: var(--bg-panel);
}
.legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-right: 4px;
  vertical-align: middle;
}
.legend-active { background: color-mix(in srgb, var(--hist-activity) 70%, transparent); }
.legend-run    { background: var(--hist-run); }
.legend-paste  { background: var(--hist-paste); }
.legend-copy   { background: var(--hist-copy); }
.legend-break  { background: var(--hist-break); }
.legend-end    { background: var(--hist-end); }
.legend-away   {
  /* Half end-color, half break-color so the legend chip visually communicates
     "starts orange, escalates to red" without needing two swatches. */
  background: linear-gradient(90deg, var(--hist-end) 0 50%, var(--hist-break) 50% 100%);
}

.history-author {
  padding: 7px 18px;
  font-size: 0.8rem;
  color: var(--text-dim);
  border-bottom: 1px solid var(--border);
}

.history-viewer {
  flex: 1;
  margin: 0;
  background: var(--bg);
  overflow: hidden;
  min-height: 0;
  position: relative;
}

/* CodeMirror inside the history viewer (flat mode) */
.history-viewer .CodeMirror {
  height: 100%;
  font-size: var(--editor-font-size, 14px);
  font-family: 'Fira Code', 'Courier New', monospace;
  line-height: 1.6;
}

/* Cell-mode history viewer: scrolls vertically through a stack of
   read-only cell editors. Overrides the flat-mode 100%-height rule so each
   cell editor sizes to its content. */
.history-viewer.history-viewer-cells {
  overflow-y: auto;
  padding: 12px 14px;
}
.history-viewer.history-viewer-cells .CodeMirror {
  height: auto;
}
.history-cell {
  margin-bottom: 14px;
}
/* Playback code cells have no header — collapse the empty padding that
   the live ``.cell-header`` would otherwise hold open. */
.history-viewer-cells .cell-code { border: 1px solid var(--border); }
.history-viewer-cells .cell-code .cell-editor-wrap { background: var(--bg); }

/* ── FONT SIZE SLIDER (profile panel) ── */
.font-size-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.font-size-slider {
  flex: 1;
  height: 4px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--border);
  border-radius: 2px;
  outline: none;
  cursor: pointer;
}
.font-size-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-panel);
}
.font-size-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-panel);
}
.font-size-value {
  font-family: 'Fira Code', 'Courier New', monospace;
  font-size: 0.72rem;
  color: var(--text-dim);
  min-width: 38px;
  text-align: right;
}

/* ── TEST RESULTS STRIP (under terminal) ── */
.test-results {
  background: var(--bg-panel);
  border-top: 1px solid var(--border);
  padding: 8px 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  flex-shrink: 0;
}
.test-results.hidden { display: none; }

.test-results-label {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  margin-right: 4px;
}

.test-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 12px;
  font-size: 0.76rem;
  border: 1px solid transparent;
}

.test-chip-icon {
  font-weight: 700;
  font-size: 0.78rem;
}

.test-pass {
  background: color-mix(in srgb, var(--accent-green) 14%, transparent);
  color: var(--accent-green);
  border-color: color-mix(in srgb, var(--accent-green) 35%, transparent);
}
.test-fail {
  background: color-mix(in srgb, var(--accent-red) 12%, transparent);
  color: var(--accent-red);
  border-color: color-mix(in srgb, var(--accent-red) 30%, transparent);
}
.test-challenge-pass {
  background: color-mix(in srgb, var(--accent-challenge) 14%, transparent);
  color: var(--accent-challenge);
  border-color: color-mix(in srgb, var(--accent-challenge) 35%, transparent);
}
.test-challenge-fail {
  background: color-mix(in srgb, var(--accent-challenge) 8%, transparent);
  color: var(--accent-challenge);
  border-color: color-mix(in srgb, var(--accent-challenge) 25%, transparent);
  opacity: 0.85;
}
.test-chip-tag {
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 5px;
  margin-left: 4px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent-challenge) 22%, transparent);
}
.test-detail-challenge-note {
  font-size: 0.82rem;
  color: var(--accent-challenge);
  background: color-mix(in srgb, var(--accent-challenge) 10%, transparent);
  padding: 6px 10px;
  border-radius: 6px;
  margin-bottom: 10px;
  border: 1px solid color-mix(in srgb, var(--accent-challenge) 30%, transparent);
}
.test-detail-implied-note {
  font-size: 0.82rem;
  color: var(--accent-green);
  background: color-mix(in srgb, var(--accent-green) 10%, transparent);
  padding: 6px 10px;
  border-radius: 6px;
  margin-bottom: 10px;
  border: 1px solid color-mix(in srgb, var(--accent-green) 30%, transparent);
}
.test-chip { cursor: pointer; transition: filter 0.15s; }
.test-chip:hover { filter: brightness(1.15); }

/* ── TEST DETAIL MODAL ── */
.test-detail-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
}
.test-detail-modal.hidden { display: none; }
.test-detail-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.55);
}
.test-detail-content {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.55);
  width: 540px;
  max-width: 90vw;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 1;
}
.test-detail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
}
.test-detail-body {
  padding: 16px;
  overflow-y: auto;
  font-size: 0.85rem;
  color: var(--text);
}

/* Wider variant for the hotkey overview — two columns fit comfortably. */
.hotkey-modal-content { width: 720px; }
.hotkey-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 20px;
}
.hotkey-section h3 {
  margin: 0 0 8px;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
}
.hotkey-section dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 14px;
  margin: 0;
}
.hotkey-section dt {
  white-space: nowrap;
  font-size: 0.78rem;
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.hotkey-section dd {
  margin: 0;
  color: var(--text-dim);
  font-size: 0.82rem;
  align-self: center;
}
.hotkey-section kbd {
  display: inline-block;
  padding: 1px 6px;
  font-family: var(--font-mono, ui-monospace, "SF Mono", "Cascadia Code", Menlo, monospace);
  font-size: 0.72rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 1px 0 var(--border);
  color: var(--text);
  white-space: nowrap;
}
.hotkey-note {
  font-size: 0.72rem;
  color: var(--text-dim);
  margin: 8px 0 0;
  line-height: 1.4;
}
.test-detail-status {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 0.78rem;
  font-weight: 600;
  margin-bottom: 14px;
}
.test-detail-status.passed {
  background: color-mix(in srgb, var(--accent-green) 20%, transparent);
  color: var(--accent-green);
}
.test-detail-status.failed {
  background: color-mix(in srgb, var(--accent-red) 20%, transparent);
  color: var(--accent-red);
}
.test-detail-rule {
  color: var(--text-dim);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin: 14px 0 4px;
  font-weight: 600;
}
.test-detail-rule:first-of-type { margin-top: 0; }
.test-detail-expected,
.test-detail-actual {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 8px 10px;
  font-family: 'Fira Code', monospace;
  font-size: 0.82rem;
  color: var(--text);
  margin: 0;
  max-height: 200px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
/* The substring(s) the test matched — emerald green wash so students can
   immediately see "this is the part of my output / code the test was happy
   with." Only appears on passed tests. */
.test-detail-actual mark.test-highlight {
  background: color-mix(in srgb, var(--accent-green) 32%, transparent);
  color: var(--text);
  border-radius: 2px;
  padding: 1px 1px;
  box-shadow: inset 0 -2px 0 var(--accent-green);
}
.test-detail-hint {
  margin-top: 6px;
  font-size: 0.88rem;
  color: var(--text);
  line-height: 1.45;
}
.test-detail-description {
  font-size: 0.9rem;
  color: var(--text);
  line-height: 1.45;
  margin-bottom: 4px;
}

/* ── ASSIGNMENT ROW (left panel, student progress bubbles) ── */
.assignment-row { flex-wrap: wrap; }
.assignment-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.assignment-bubbles {
  flex-basis: 100%;
  display: flex;
  gap: 4px;
  margin-top: 3px;
  margin-bottom: 1px;
}

/* ── STUDENT BUBBLES (under each student name in teacher panel) ── */
.student-row { flex-wrap: wrap; }

.student-bubbles {
  flex-basis: 100%;
  padding-left: 26px;
  display: flex;
  gap: 4px;
  margin-top: 2px;
  margin-bottom: 1px;
}

.test-bubble {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
  cursor: default;
}

.bubble-grey { background: #6e7681; opacity: 0.55; }
.bubble-pass { background: var(--accent-green); }
.bubble-fail { background: var(--accent-red); }
.bubble-partial { background: var(--accent-yellow); }

/* Challenge-test bubbles — purple star shape. Doesn't count in the
   habits formula (grade.js), and visually marks the test as a stretch /
   bonus item. The clip-path overrides .test-bubble's border-radius so
   challenge bubbles render as 5-point stars wherever they appear (own
   bubbles strip, master bubbles, teacher panel). */
.bubble-challenge-pass     { background: var(--accent-challenge); }
.bubble-challenge-fail     { background: var(--accent-challenge); opacity: 0.45; }
.bubble-challenge-partial  { background: var(--accent-challenge); opacity: 0.75; }
.bubble-challenge-unknown  { background: var(--accent-challenge); opacity: 0.30; }
.bubble-challenge-pass,
.bubble-challenge-fail,
.bubble-challenge-partial,
.bubble-challenge-unknown {
  border-radius: 0;
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
  /* The master-bubble outline doesn't follow clip-path well, so drop it
     for star variants — color alone reads as "challenge section." */
  border: 0;
}

/* Section-aggregate bubble — slightly larger + thin outline so it reads
   as "a summary of multiple tests" rather than an individual check. */
.master-bubble {
  width: 11px;
  height: 11px;
  border: 1px solid rgba(0, 0, 0, 0.18);
  box-sizing: border-box;
}

/* ── UTILITY ── */
.hidden { display: none !important; }

/* ── SYNTAX HIGHLIGHTING ──
   All themes drive their token colors via --syn-* vars defined per theme.
   The [data-theme] attribute selector wins specificity vs the CDN
   dracula stylesheet, so dark/dracula/ocean each render distinctly even
   though CodeMirror only knows about two theme classes (.cm-s-dracula,
   .cm-s-default). Variable color is always different from function name. */

[data-theme] .cm-s-dracula.CodeMirror,
[data-theme] .cm-s-default.CodeMirror {
  background: var(--bg);
  color: var(--text);
}
[data-theme] .cm-s-dracula .CodeMirror-cursor,
[data-theme] .cm-s-default .CodeMirror-cursor { border-left-color: var(--accent); }

[data-theme] .cm-s-dracula .CodeMirror-selected,
[data-theme] .cm-s-default .CodeMirror-selected { background: var(--bg-hover) !important; }

[data-theme] .cm-s-dracula .cm-comment,
[data-theme] .cm-s-default .cm-comment    { color: var(--syn-comment); font-style: italic; }

[data-theme] .cm-s-dracula .cm-keyword,
[data-theme] .cm-s-default .cm-keyword    { color: var(--syn-keyword); }

[data-theme] .cm-s-dracula .cm-def,
[data-theme] .cm-s-default .cm-def        { color: var(--syn-fn); }

[data-theme] .cm-s-dracula .cm-variable,
[data-theme] .cm-s-default .cm-variable   { color: var(--syn-var); }

[data-theme] .cm-s-dracula .cm-variable-2,
[data-theme] .cm-s-default .cm-variable-2 { color: var(--syn-var2); }

[data-theme] .cm-s-dracula .cm-variable-3,
[data-theme] .cm-s-default .cm-variable-3 { color: var(--syn-var2); }

[data-theme] .cm-s-dracula .cm-builtin,
[data-theme] .cm-s-default .cm-builtin    { color: var(--syn-builtin); }

[data-theme] .cm-s-dracula .cm-string,
[data-theme] .cm-s-default .cm-string,
[data-theme] .cm-s-dracula .cm-string-2,
[data-theme] .cm-s-default .cm-string-2   { color: var(--syn-string); }

[data-theme] .cm-s-dracula .cm-number,
[data-theme] .cm-s-default .cm-number     { color: var(--syn-number); }

[data-theme] .cm-s-dracula .cm-atom,
[data-theme] .cm-s-default .cm-atom       { color: var(--syn-atom); }

[data-theme] .cm-s-dracula .cm-operator,
[data-theme] .cm-s-default .cm-operator   { color: var(--syn-operator); }

[data-theme] .cm-s-dracula .cm-property,
[data-theme] .cm-s-default .cm-property   { color: var(--syn-property); }

[data-theme] .cm-s-dracula .cm-meta,
[data-theme] .cm-s-default .cm-meta       { color: var(--syn-comment); }

[data-theme] .cm-s-dracula .cm-bracket,
[data-theme] .cm-s-default .cm-bracket    { color: var(--text); }

/* ── CELL MODE (colab-style assignments) ───────────────────────────────── */

/* The cells-container takes the place of .editor-wrapper when an assignment
   doc has a `cells` array. Same flex slot, so the terminal below sits where
   it normally does. Scrolls internally so long cell lists don't push the
   terminal off the page. */
.cells-container {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 18px 22px 24px;
  background: var(--bg);
  scrollbar-gutter: stable;
}
.cells-container.hidden { display: none; }

.cell {
  margin-bottom: 18px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-panel);
  overflow: hidden;
}

.cell-instructions {
  background: transparent;
  border: 0;
  padding: 4px 4px 8px;
}

.cell-instr-title {
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--text);
  margin: 0 0 8px;
  letter-spacing: 0.1px;
}

.cell-instr-body {
  white-space: pre-wrap;
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--text);
}

/* Experimental rich-text instructions (cell.format === "html"). ONLY cells that
   opt in get these — currently just the Super Calculator assignment — so every
   other assignment's plain-text instructions are untouched. Lets us use real
   headings/bold + orange inline code instead of literal ** and backticks. */
.cell-instr-body.cell-instr-html { white-space: normal; }
.cell-instr-body.cell-instr-html h3 { font-size: 1.2rem;  font-weight: 700; margin: 0.7em 0 0.3em; }
.cell-instr-body.cell-instr-html h4 { font-size: 1.02rem; font-weight: 700; margin: 0.85em 0 0.25em; }
.cell-instr-body.cell-instr-html p  { margin: 0.5em 0; }
.cell-instr-body.cell-instr-html ul { margin: 0.3em 0 0.55em 1.25em; }
.cell-instr-body.cell-instr-html li { margin: 0.15em 0; }
.cell-instr-body.cell-instr-html code.code-orange {
  color: #e8913a;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: rgba(232, 145, 58, 0.12);
  padding: 0 4px;
  border-radius: 4px;
  font-size: 0.92em;
}

/* Optional embedded image inside an instructions cell (cell.image.url).
   When present, the body + image render side-by-side via the .has-image
   wrapper. Click image to open full size in a new tab. */
.cell-instr-content {
  /* Default: body alone, no flex needed. */
}
.cell-instr-content.has-image {
  display: flex;
  flex-direction: row;
  gap: 18px;
  align-items: flex-start;
}
.cell-instr-content.has-image .cell-instr-body {
  flex: 1 1 0;
  min-width: 0;
}
.cell-instr-content.has-image .cell-instr-image-link {
  /* Reserve 45% of the cell width for the image slot. Using a percent
     flex-basis (NOT auto) so an SVG with only `viewBox` and no intrinsic
     width/height still gets a non-zero box — otherwise the link collapses
     and the SVG disappears. */
  flex: 0 0 45%;
  display: block;
}
.cell-instr-image {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 6px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  cursor: zoom-in;
}
.cell-instr-content:not(.has-image) .cell-instr-image {
  /* Fallback: if a cell renders without the wrapper (older path) — keep
     it centered + size-capped instead of full-width. */
  margin: 12px auto 4px;
  max-width: 70%;
}
/* Narrow viewports — stack instead of side-by-side so the body stays
   readable. */
@media (max-width: 820px) {
  .cell-instr-content.has-image {
    flex-direction: column;
  }
  .cell-instr-content.has-image .cell-instr-image-link {
    max-width: 100%;
  }
}

.cell-code {
  background: var(--bg-panel);
}

.cell-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.cell-run-btn {
  background: var(--accent-green);
  color: #fff;
  border: 0;
  padding: 4px 12px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
}
.cell-run-btn:hover { filter: brightness(1.08); }
.cell-run-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Yellow-green Test button. Runs the cell isolated with the IO test
   stdin lines piped in automatically so the student sees the program
   execute with the test inputs, then evaluates the cell's bubbles. */
.cell-test-btn {
  background: #b4d957;
  color: #1a1a1a;
  border: 0;
  padding: 4px 12px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
}
.cell-test-btn:hover { filter: brightness(1.08); }
.cell-test-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.cell-status {
  font-size: 0.78rem;
  color: var(--text-dim);
}
.cell-status-running { color: var(--accent-yellow); }
.cell-status-error   { color: var(--accent-red); }

.cell-editor-wrap {
  /* CodeMirror grows with its content thanks to viewportMargin: Infinity. */
  background: var(--bg);
}
.cell-editor-wrap .CodeMirror {
  height: auto;
  min-height: 36px;
}

.cell-bubbles {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: 8px 12px;
  border-top: 1px solid var(--border);
  background: var(--bg);
}

.cell-bubble {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  display: inline-block;
  background: var(--text-dim);
  opacity: 0.4;
  cursor: pointer;
  transition: transform 0.08s, box-shadow 0.08s;
}
.cell-bubble[data-test-index]:hover {
  transform: scale(1.18);
  box-shadow: 0 0 0 2px var(--bg-panel), 0 0 0 3px var(--accent-green);
}
.cell-bubble-pass { background: var(--accent-green); opacity: 1; }
.cell-bubble-fail { background: var(--accent-red);   opacity: 1; }
.cell-bubble-unknown { background: var(--text-dim);  opacity: 0.4; }
.cell-bubble-challenge-pass    { background: var(--accent-challenge); opacity: 1; }
.cell-bubble-challenge-fail    { background: var(--accent-challenge); opacity: 0.45; }
.cell-bubble-challenge-unknown { background: var(--accent-challenge); opacity: 0.3; }
.cell-bubble-challenge-pass,
.cell-bubble-challenge-fail,
.cell-bubble-challenge-unknown {
  border-radius: 0;
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}
.cell-bubble[data-test-index].cell-bubble-challenge-pass:hover,
.cell-bubble[data-test-index].cell-bubble-challenge-fail:hover,
.cell-bubble[data-test-index].cell-bubble-challenge-unknown:hover {
  /* The default cell-bubble hover ring uses box-shadow + accent-green; on
     a clip-path star the shadow gets clipped and looks weird. Swap to a
     simple transform-only hover. */
  box-shadow: none;
  transform: scale(1.25);
}

.cell-bubbles-empty {
  font-size: 0.72rem;
  color: var(--text-dim);
  font-style: italic;
}

/* ── Hand-grading bar (lives inside the Grade panel) ──────────────────── */
/* Sits at the top of the Grade panel under the header. Two compact /4
   inputs (habits + style) on the left, longer text inputs in the middle,
   Canvas connection state on the right. The "/4" suffix uses .grade-of. */
.grade-bar {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-panel);
  font-size: 0.8rem;
  color: var(--text);
}
.grade-bar.hidden { display: none; }
.grade-bar-fields,
.grade-bar-canvas {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.grade-bar-fields { flex: 1 1 420px; min-width: 0; }
.grade-bar-canvas { flex: 0 0 auto; margin-left: auto; }
.grade-field {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--text-dim);
  white-space: nowrap;
}
/* Comment field — halved (was flex 1 1 120px / min 90px, uncapped growth).
   Capped at ~half the fields row so the bar reads tighter. */
.grade-field-grow  { flex: 1 1 60px; min-width: 45px; max-width: 210px; }
.grade-field-grow .grade-input { flex: 1 1 auto; min-width: 0; }
.grade-field-small { flex: 0 0 auto; gap: 4px; }
.grade-of {
  color: var(--text-dim);
  font-size: 0.72rem;
  margin-left: -2px;
}
.grade-input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  padding: 4px 7px;
  font-size: 0.8rem;
  font-family: inherit;
}
.grade-input:focus { outline: none; border-color: var(--accent); }
.grade-score-small { width: 44px; text-align: center; }

/* When the student's last Run happened after the assignment due date, the
   habits field lights up so the teacher notices. Bright warning hue, glowing
   input border, plus a ⚠ tag injected via ::after on the label text so we
   don't need to touch the input markup. */
.grade-field.after-due {
  color: color-mix(in srgb, #f59e0b 90%, var(--text));
  font-weight: 600;
}
.grade-field.after-due::after {
  content: "⚠ after due";
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #f59e0b;
  background: color-mix(in srgb, #f59e0b 14%, transparent);
  padding: 1px 6px;
  border-radius: 9px;
  margin-left: 4px;
}
.grade-field.after-due .grade-input {
  border-color: #f59e0b;
  background: color-mix(in srgb, #f59e0b 10%, var(--bg));
  box-shadow: 0 0 0 2px color-mix(in srgb, #f59e0b 25%, transparent);
}
.grade-field.after-due .grade-of {
  color: color-mix(in srgb, #f59e0b 80%, var(--text-dim));
}
.grade-save-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 4px;
  padding: 4px 12px;
  font-size: 0.8rem;
  cursor: pointer;
}
.grade-save-btn:hover { filter: brightness(1.1); }
.grade-save-status { color: var(--text-dim); font-size: 0.72rem; white-space: nowrap; }
.grade-canvas-state { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.grade-canvas-state.hidden { display: none; }
.grade-canvas-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--text-dim);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--text-dim) 25%, transparent);
}
.grade-canvas-dot.connected {
  background: var(--accent-green);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-green) 30%, transparent);
}
.grade-connect-btn {
  background: transparent;
  color: var(--hist-copy);
  border: 1px solid var(--hist-copy);
  border-radius: 4px;
  padding: 3px 10px;
  font-size: 0.76rem;
  cursor: pointer;
}
.grade-connect-btn:hover { background: color-mix(in srgb, var(--hist-copy) 12%, transparent); }
.grade-disconnect-btn {
  background: transparent;
  color: var(--text-dim);
  border: none;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 2px 4px;
}
.grade-disconnect-btn:hover { color: var(--accent-red); }
.grade-connect-form { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.grade-connect-form.hidden { display: none; }
.grade-connect-form .grade-input { width: 170px; }

/* ── Canvas standards rubric drop-down ────────────────────────────────────
   Full-width row that pulls out below the grade bar when "Open Canvas
   Rubric" is clicked. One row per standard: the standard name on the left,
   an outcome-level <select> on the right. Footer shows the running /4
   average + its own Submit. */
.grade-rubric-panel {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 4px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
}
.grade-rubric-panel.hidden { display: none; }
.grade-rubric-head {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  color: var(--text);
}
.grade-rubric-list { display: flex; flex-direction: column; gap: 6px; }
.grade-rubric-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.grade-rubric-row .grade-rubric-name {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-dim);
  white-space: normal;
}
/* Outcome buttons: one per rating level + a "None" (scored 0). The active one
   is filled; clicking another moves the selection. */
.grade-rubric-btns { display: flex; gap: 4px; flex: 0 0 auto; flex-wrap: wrap; }
.grade-rubric-btn {
  min-width: 32px;
  padding: 4px 9px;
  font-size: 0.78rem;
  font-weight: 600;
  font-family: inherit;
  line-height: 1.1;
  color: var(--text);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 5px;
  cursor: pointer;
}
.grade-rubric-btn:hover { border-color: var(--accent); }
.grade-rubric-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.grade-rubric-none { color: var(--text-dim); }
.grade-rubric-none.active {
  background: var(--text-dim);
  border-color: var(--text-dim);
  color: var(--bg);
}
.grade-rubric-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-top: 4px;
  border-top: 1px solid var(--border);
}
.grade-rubric-average {
  font-weight: 600;
  color: var(--text);
  margin-right: auto;
}
.grade-rubric-empty { color: var(--text-dim); font-size: 0.78rem; }

/* ── Clickable copy/paste blips + clipboard popup ─────────────────────── */
/* The markers' container is pointer-events:none (so the slider drags through
   it); re-enable hits on the copy/paste blips and widen the tap target with a
   transparent ::before so they're clickable on touch without changing the
   visible 3px bar. */
.history-marker.paste,
.history-marker.copy {
  pointer-events: auto;
  cursor: pointer;
}
.history-marker.paste::before,
.history-marker.copy::before {
  content: "";
  position: absolute;
  top: -4px;
  bottom: -4px;
  left: -6px;
  right: -6px;
}
.marker-popup-content { width: 600px; }
.marker-popup-pre {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 12px;
  max-height: 56vh;
  overflow: auto;
}

/* Copy/paste tally badge in the teacher's student list. */
.student-clipboard {
  flex-shrink: 0;
  margin-left: 6px;
  font-size: 0.66rem;
  font-weight: 600;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 9px;
  color: var(--hist-copy);
  background: color-mix(in srgb, var(--hist-copy) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--hist-copy) 35%, transparent);
  cursor: default;
  white-space: nowrap;
}

/* Per-student grade pill (students.js renderGradeBadge). Same pill shape as
   the clipboard badge. Yellow = graded but not pushed to Canvas; green =
   pushed (shows the score sent). */
.student-grade {
  flex-shrink: 0;
  margin-left: 6px;
  font-size: 0.66rem;
  font-weight: 700;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 9px;
  cursor: default;
  white-space: nowrap;
}
.student-grade.unpushed {
  color: #d39a00;
  background: color-mix(in srgb, #f59e0b 16%, transparent);
  border: 1px solid color-mix(in srgb, #f59e0b 45%, transparent);
}
.student-grade.pushed {
  color: var(--accent-green);
  background: color-mix(in srgb, var(--accent-green) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-green) 45%, transparent);
}

/* ── Turtle output window (real Xvfb pixels streamed from droplet) ─────── */
/* Floats over the editor. Draggable via the header, resizable from the BR
   corner. Initial position is set by turtle.js (bottom-right of viewport)
   on first open; subsequent positions + sizes persist via localStorage.
   The <img> shows PNG frames streamed via the WS — each frame is one capture
   of the actual Tk turtle window running inside the sandbox. */
.turtle-window {
  position: fixed;
  /* JS sets left/top on first show. Defaults below are just a safety net so
     the window is visible if turtle.js hasn't run yet. */
  left: auto;
  top: auto;
  right: 12px;
  bottom: 12px;
  width: 25vw;
  height: 25vh;
  min-width: 260px;
  min-height: 200px;
  max-width: 95vw;
  max-height: 95vh;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  z-index: 4000;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  resize: both;
}
.turtle-window.hidden { display: none; }
/* While the header is being grabbed, suppress text selection and drop the
   pointer-events-pass-through on the body so dragging stays smooth. */
.turtle-window.dragging { user-select: none; }
.turtle-window.dragging .turtle-window-header { cursor: grabbing; }
.turtle-window-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 4px 10px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  font-size: 0.78rem;
  color: var(--text-dim);
  flex-shrink: 0;
  user-select: none;
  cursor: grab;
  /* Touch hint: don't pan the page when the user starts dragging the header. */
  touch-action: none;
}
/* The close button gets its own cursor so it doesn't inherit the grab hand. */
.turtle-window-header button { cursor: pointer; }
.turtle-window-title { font-weight: 600; color: var(--text); }
.turtle-status {
  font-size: 0.7rem;
  color: var(--text-dim);
  margin-left: auto;
  margin-right: 4px;
}
.turtle-status.live   { color: var(--accent-green); }
.turtle-status.done   { color: var(--text-dim); }
.turtle-status.error  { color: var(--accent-red); }
.turtle-window-body {
  flex: 1 1 auto;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* The img fills the window. object-fit:contain keeps the turtle's aspect
   ratio square (server emits a 400×400 frame) inside any window dimensions
   the student drags — letterboxed if the window's aspect ratio differs.
   image-rendering:pixelated keeps the lines crisp when the window is bigger
   than the source frame. cursor:crosshair signals that the image is
   click-interactive (mouse events forward to the Tk window inside Xvfb). */
.turtle-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
  cursor: crosshair;
  /* Don't let the browser's drag-image behavior interfere with pointer
     drags we're sending to the server. */
  user-select: none;
  -webkit-user-drag: none;
}

/* ── Remote cursors (y-codemirror awareness) ──
   y-codemirror builds <span class="remote-caret"><div>NAME</div></span> with
   only inline border/background colors set. Without these rules the inner div
   is block-level and the name label drops onto its own line below the caret.
   We pin it inline-above the caret, clip its width, and use the lib's existing
   .hide-name class (auto-toggled after 2s idle) to fade the label out. */
.CodeMirror .remote-caret {
  position: relative;
  display: inline-block;
  width: 0;
  border-left-width: 2px;
  border-left-style: solid;
  margin-left: -1px;
  height: 1em;
  vertical-align: text-top;
  pointer-events: auto;
  z-index: 3;
}
.CodeMirror .remote-caret > div {
  position: absolute;
  left: -1px;
  top: -1.35em;
  display: inline-block;
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 1px 5px;
  font-family: var(--font-ui, system-ui, sans-serif);
  font-size: 10.5px;
  line-height: 1.25;
  color: #fff;
  border-radius: 3px 3px 3px 0;
  user-select: none;
  pointer-events: none;
  box-shadow: 0 1px 2px rgba(0,0,0,0.25);
  transition: opacity 0.25s ease-in-out;
}
.CodeMirror .remote-caret.hide-name > div {
  opacity: 0;
  transition-delay: 0.4s;
}
.CodeMirror .remote-caret:hover > div {
  opacity: 1;
  transition-delay: 0s;
}
/* Multi-line selection fill: y-codemirror inserts <span class="y-line-selection">
   for the body of a multi-line selection — make sure it never intercepts clicks. */
.CodeMirror .y-line-selection { pointer-events: none; }

/* ── Manage Classroom page (manage.html) ──────────────────────────────── */
/* Teacher-only page accessed from the editor topbar. Two side-by-side
   panels: Assignments (start/end dates) and Students (block dropdowns).
   Reuses the topbar + button styles from the editor page where possible. */
body.manage-page {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui, system-ui, sans-serif);
  margin: 0;
  min-height: 100vh;
}
.manage-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  margin-left: 8px;
}
.manage-body {
  display: grid;
  grid-template-columns: minmax(340px, 1fr) minmax(340px, 1fr);
  gap: 20px;
  /* 48px = .topbar height. The extra 18px is breathing room — without it the
     fixed topbar floats right over the "Assignments" / "Students" headers. */
  padding: 66px 20px 28px;
  max-width: 1400px;
  margin: 0 auto;
  height: 100vh;
  box-sizing: border-box;
}
@media (max-width: 900px) {
  .manage-body {
    grid-template-columns: 1fr;
    height: auto;     /* let the page itself scroll on narrow screens */
  }
}
.manage-section {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  /* Bound height to the grid cell so the inner list scrolls instead of the
     whole section blowing past the viewport. min-height:0 is the standard
     escape hatch for "child of a flex/grid container can shrink below its
     content size". */
  min-height: 0;
  overflow: hidden;
}
.manage-section-header {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.manage-section-header h2 {
  margin: 0;
  font-size: 1.05rem;
  color: var(--text);
}
.manage-section-hint {
  font-size: 0.78rem;
  color: var(--text-dim);
}
.manage-toolbar {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}
.manage-search {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 5px 9px;
  font-size: 0.85rem;
  font-family: inherit;
  flex: 1 1 auto;
  min-width: 0;
}
.manage-search:focus { outline: none; border-color: var(--accent); }
.manage-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  /* The scrolling region. flex:1 lets it eat the remaining vertical space in
     its .manage-section parent; overflow-y:auto adds a scrollbar when
     content overflows (lots of assignments or students). */
  flex: 1 1 auto;
  overflow-y: auto;
  /* A bit of right padding so the scrollbar doesn't sit on top of row text. */
  padding-right: 4px;
}
.manage-row {
  display: grid;
  align-items: center;
  gap: 10px 14px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.manage-row:last-child { border-bottom: 0; }
.manage-row:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }

.assignment-row { grid-template-columns: 1fr auto auto auto; }
.assignment-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.assignment-name {
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.assignment-id { font-size: 0.7rem; color: var(--text-dim); }
.assignment-date {
  display: inline-flex;
  flex-direction: column;
  font-size: 0.7rem;
  color: var(--text-dim);
  gap: 2px;
}

.student-row { grid-template-columns: 1fr auto auto; }
.student-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.student-name {
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.student-email {
  font-size: 0.7rem;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.student-block-pick {
  display: inline-flex;
  flex-direction: column;
  font-size: 0.7rem;
  color: var(--text-dim);
  gap: 2px;
}
.student-block-divider {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  padding: 14px 4px 6px;
  border-bottom: 1px solid var(--border);
}
.student-block-divider:first-child { padding-top: 0; }
.student-block-count {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  padding: 1px 7px;
  border-radius: 9px;
  font-size: 0.65rem;
  font-weight: 600;
}

.manage-input {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 4px 7px;
  font-size: 0.85rem;
  font-family: inherit;
}
.manage-input:focus { outline: none; border-color: var(--accent); }

.manage-status {
  font-size: 0.7rem;
  color: var(--text-dim);
  min-width: 60px;
  text-align: right;
}
.manage-status.saving { color: var(--accent); }
.manage-status.saved  { color: var(--accent-green); }
.manage-status.error  { color: var(--accent-red); }

.manage-loading,
.manage-empty {
  text-align: center;
  color: var(--text-dim);
  padding: 30px 0;
  font-size: 0.85rem;
}

.manage-forbidden {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.75);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 5000;
}
.manage-forbidden.hidden { display: none; }
.manage-forbidden-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 28px 36px;
  text-align: center;
  max-width: 380px;
}
.manage-forbidden-card h2 { margin-top: 0; color: var(--text); }
.manage-forbidden-card p { color: var(--text-dim); }

/* ── Classroom view (manage.html, no params) ──────────────────────────── */
/* Vertical stack: override toggle, then the blocks grid. The two columns
   from the old manage page collapsed into one wider column because the
   per-block view is its own page now. */
.classroom-body {
  display: flex;
  flex-direction: column;
  gap: 18px;
  /* Override the default grid layout from .manage-body. */
  grid-template-columns: none;
}
.classroom-body.hidden { display: none; }

.classroom-override {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-shrink: 0;
}
.classroom-override-label { flex: 1 1 auto; min-width: 0; }
.classroom-override-label strong {
  display: block;
  color: var(--text);
  margin-bottom: 2px;
  font-size: 0.95rem;
}
.classroom-override-hint {
  font-size: 0.76rem;
  color: var(--text-dim);
}

/* iOS-style toggle. The checkbox is visually hidden behind .toggle-slider. */
.toggle-switch {
  display: inline-block;
  position: relative;
  width: 46px;
  height: 24px;
  flex-shrink: 0;
}
.toggle-switch input {
  opacity: 0;
  width: 0; height: 0;
  position: absolute;
}
.toggle-slider {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 999px;
  transition: background 0.15s;
  cursor: pointer;
}
.toggle-slider::before {
  content: "";
  position: absolute;
  left: 3px; top: 3px;
  width: 18px; height: 18px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.15s;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.toggle-switch input:checked + .toggle-slider { background: var(--accent-green); }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(22px); }

.classroom-blocks {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
.blocks-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
  overflow-y: auto;
  padding-right: 4px;
  align-content: start;
}
.block-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px;
  text-decoration: none;
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: 8px;
  transition: border-color 0.12s, transform 0.08s, box-shadow 0.12s;
}
.block-card:hover {
  border-color: var(--accent);
  box-shadow: 0 4px 12px rgba(0,0,0,0.25);
  transform: translateY(-1px);
}
.block-card:active { transform: translateY(0); }
.block-card-archive {
  /* Dim the archive card slightly so the active blocks pop. */
  opacity: 0.78;
}
.block-card-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.block-card-letter {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--accent);
  min-width: 1.5em;
  text-align: center;
}
.block-card-label {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
}
.block-card-stats {
  display: flex;
  gap: 12px;
  font-size: 0.78rem;
  color: var(--text-dim);
}
.block-card-stat b { color: var(--text); font-weight: 600; }
.block-card-canvas {
  font-size: 0.72rem;
  color: var(--text-dim);
  padding-top: 6px;
  border-top: 1px solid var(--border);
}

/* ── Block view (manage.html?block=X) ─────────────────────────────────── */
.block-body {
  display: flex;
  flex-direction: column;
  gap: 18px;
  grid-template-columns: none;
}
.block-body.hidden { display: none; }

.block-meta,
.block-assignments {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
}
.block-assignments { flex: 1 1 auto; min-height: 0; overflow: hidden; }
.block-meta { flex-shrink: 0; }
.block-meta-row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 6px;
}
.block-meta-field {
  flex: 1 1 280px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 0.7rem;
  color: var(--text-dim);
}
.block-meta-field .manage-input { width: 100%; box-sizing: border-box; }
.block-back { font-size: 0.85rem; }

/* Per-assignment row inside the block view. More columns than the classroom
   page's assignment rows because we add the Canvas assignment ID input + the
   Pull button. Falls back to a stacked layout on narrow viewports. */
.block-assignment-row {
  grid-template-columns: minmax(180px, 1.3fr) auto auto minmax(140px, 1fr) auto auto auto auto;
  gap: 10px 14px;
  align-items: end;
  padding: 12px 4px;
}
.block-assignment-row .assignment-title { align-self: center; }
.manage-pull-btn, .manage-push-btn, .manage-open-btn, .manage-publish-btn {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 5px;
  padding: 4px 10px;
  font-size: 0.76rem;
  cursor: pointer;
  white-space: nowrap;
  font-family: inherit;
  text-decoration: none;
  display: inline-block;
  text-align: center;
}
.manage-pull-btn:hover, .manage-push-btn:hover,
.manage-open-btn:hover, .manage-publish-btn:hover {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.manage-pull-btn:active, .manage-push-btn:active,
.manage-open-btn:active, .manage-publish-btn:active { transform: translateY(1px); }
/* Push button takes a slightly different accent (the green "success" tone)
   so the two buttons are easy to tell apart at a glance. */
.manage-push-btn {
  color: var(--accent-green);
  border-color: var(--accent-green);
}
.manage-push-btn:hover {
  background: color-mix(in srgb, var(--accent-green) 14%, transparent);
}
/* "↗ Canvas" link — neutral indigo-ish. Disabled state when IDs aren't set. */
.manage-open-btn { color: var(--text-dim); border-color: var(--border); }
.manage-open-btn:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.manage-open-btn.disabled {
  opacity: 0.4;
  pointer-events: none;
  cursor: default;
}
/* Publish button states. */
.manage-publish-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
.manage-publish-btn.is-published {
  background: color-mix(in srgb, var(--accent-green) 18%, transparent);
  color: var(--accent-green);
  border-color: var(--accent-green);
}
.manage-publish-btn.is-unpublished {
  color: var(--accent-orange, #d97706);
  border-color: var(--accent-orange, #d97706);
}
.manage-publish-btn.is-unpublished:hover {
  background: color-mix(in srgb, var(--accent-orange, #d97706) 14%, transparent);
}

@media (max-width: 1100px) {
  .block-assignment-row {
    grid-template-columns: 1fr 1fr;
    align-items: stretch;
  }
}
