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

:root {
  /* ── Layout width ──
     --page-max governs card grids, forms and lists: layout, where more
     width is straightforwardly better on a large monitor. Change this one
     value to make every page wider or narrower; 1600px fills most of a
     large display, 1040px is roughly where this started.

     --reading-max governs running text — the chat transcript and its input.
     It is deliberately much smaller and should NOT be raised to match:
     at 860px a line of message text already runs to about 100 characters,
     which is the top of the comfortable range. Widening prose to fill a
     monitor makes it harder to read, not easier. ── */
  --page-max: 1280px;
  --reading-max: 860px;

  /* ── Light mode — Black + Ember Red-Orange (near-black anchor,
     single vermillion accent — grounded in Brisk/Diffit research:
     both competitors use near-black, not navy, as their dark anchor,
     and each commits to exactly one saturated accent color) ── */
  --navy: #0F2438;
  --navy-2: #1B3A52;
  --steel: #5C6B76;
  --steel-tint: #7C8B96;
  --light-steel: #EDECE8;
  --flame: #C1573A;
  --mid-flame: #D98F73;
  --light-flame: #F5E1D8;
  --flame-hover: #A84C32;
  --flame-rgb: 193,87,58;
  --offwhite: #F7F6F4;
  --warm-white: #FDFDFC;
  --bg: #FFFFFF;
  --bg2: #F5F4F1;
  --card: #FFFFFF;
  --ink: #0C0B0A;
  --ink-rgb: 12,11,10;
  --ink2: #56514B;
  --ink3: #8C877E;
  --line: rgba(12,11,10,0.12);
  --line2: rgba(12,11,10,0.22);
  --strong: #2E7D5B; --strong-bg: #E6F2EC;
  --bubble-teacher-bg: #F1ECE2;
  --radius: 9px;
  --radius-lg: 14px;
  --radius-sm: 6px;
  --shadow-sm: 0 1px 2px rgba(12,11,10,0.06);
  --shadow: 0 6px 24px -10px rgba(12,11,10,0.22);
  --shadow-lg: 0 18px 48px -18px rgba(12,11,10,0.34);
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --sans: 'Inter', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --spark-glow: transparent;
  /* ── Visual refresh tokens (Home / nav / Planning tools picker only — see scoped rules below) ── */
  --heading-sans: 'Plus Jakarta Sans', var(--sans);
  --pale-flame-tint: #F5E1D8;
  --cream: #FAF6EE;
  --line-bold: rgba(12,11,10,0.4);
}

/* ── Forge (dark) — the heat, the process ── */
[data-theme="forge"] {
  --navy: #0A1B2B;
  --navy-2: #163044;
  --steel: #93A3AD;
  --steel-tint: #AEBCC4;
  --light-steel: #E8E6E0;
  --flame: #D97350;
  --mid-flame: #E5A688;
  --light-flame: #3D2419;
  --flame-hover: #BA6242;
  --flame-rgb: 217,115,80;
  --offwhite: #1C1A17;
  --warm-white: #17150F;
  --bg: #151310;
  --bg2: #1E1C18;
  --card: #1E1C18;
  --ink: #EDE8DF;
  --ink-rgb: 237,232,223;
  --ink2: #B8B2A5;
  --ink3: #858074;
  --line: rgba(200,190,170,0.16);
  --line2: rgba(200,190,170,0.28);
  --strong: #4FAE84; --strong-bg: #1B2C24;
  --bubble-teacher-bg: #26231D;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
  --shadow: 0 6px 24px -10px rgba(0,0,0,0.5);
  --shadow-lg: 0 18px 48px -18px rgba(0,0,0,0.6);
  --spark-glow: rgba(217,115,80,0.28);
  --pale-flame-tint: #2E1E17;
  --cream: #1E1C18;
  --line-bold: rgba(200,190,170,0.45);
}

html { height: 100%; }
body {
  font-family: var(--serif);
  background: var(--bg);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}
body::before {
  content: "";
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image: radial-gradient(rgba(var(--ink-rgb),0.03) 1px, transparent 1px);
  background-size: 22px 22px;
  transition: background-image 0.3s ease;
}

/* ── Theme toggle switch (sun left, moon right, sliding thumb) ── */
.theme-switch {
  background: rgba(255,255,255,0.06); border: 1.5px solid rgba(255,255,255,0.25);
  border-radius: 999px; cursor: pointer; padding: 0; width: 52px; height: 26px;
  position: relative; flex-shrink: 0; transition: border-color 0.14s, background 0.14s;
}
.theme-switch:hover { border-color: rgba(255,255,255,0.5); background: rgba(255,255,255,0.1); }
.ts-icon {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 13px; height: 13px; color: rgba(255,255,255,0.55); pointer-events: none;
  transition: color 0.3s ease;
}
.ts-icon.ts-sun { left: 6px; }
.ts-icon.ts-moon { right: 6px; }
.theme-switch[aria-pressed="false"] .ts-sun { color: #FFD27A; }
.theme-switch[aria-pressed="true"] .ts-moon { color: #D6D0C5; }
.ts-thumb {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  border-radius: 50%; background: #FDFAF5;
  box-shadow: 0 1px 4px rgba(0,0,0,0.45);
  transition: transform 0.28s cubic-bezier(.4,0,.2,1);
}
.theme-switch[aria-pressed="true"] .ts-thumb { transform: translateX(26px); }
@media (prefers-reduced-motion: reduce) { .ts-thumb { transition: none; } }

/* ── Forge sparks (dark mode only, and only once unlocked via the
   //forge chat command — see the theme-toggle script near the bottom) ── */
#forgeSparks {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  overflow: hidden; opacity: 0; transition: opacity 0.5s ease;
}
[data-theme="forge"][data-sparks="on"] #forgeSparks { opacity: 1; }
.forge-spark {
  position: absolute; bottom: 0; left: 0; width: 4px; height: 4px; border-radius: 50%;
  background: radial-gradient(circle, #FFE9B8 0%, #F0A93E 45%, rgba(193,80,46,0) 75%);
  opacity: 0; will-change: transform, opacity;
}
/* Hammer-strike burst — a handful of sparks fired outward from wherever the
   teacher just submitted something, positioned by exact pixel origin rather
   than the ambient sparks' bottom-of-screen anchor. */
.forge-burst-spark {
  position: absolute; border-radius: 50%;
  background: radial-gradient(circle, #FFE9B8 0%, #F0A93E 45%, rgba(193,80,46,0) 75%);
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .forge-spark { display: none; opacity: 0; }
  .forge-burst-spark { display: none; }
}

/* ── TOP BAR ── */
.topbar {
  flex-shrink: 0; height: 76px; position: relative; z-index: 10;
  background: linear-gradient(180deg, var(--navy-2) 0%, var(--navy) 100%);
  display: flex; align-items: center; padding: 0 28px;
  box-shadow: 0 1px 0 rgba(0,0,0,0.25), 0 6px 20px -12px rgba(0,0,0,0.45);
}
.wordmark {
  font-family: var(--serif); font-weight: 700; font-size: 26px;
  letter-spacing: -0.3px; line-height: 1; user-select: none;
}
.wordmark .forge { color: var(--light-steel); }
.wordmark .pbl   { color: var(--mid-flame); }
.topbar-left { display: flex; align-items: center; gap: 34px; min-width: 0; }
.topbar-right { margin-left: auto; display: flex; align-items: center; }
.signin-btn {
  font-family: var(--sans); font-size: 13.5px; font-weight: 600;
  letter-spacing: 0.4px; text-transform: uppercase;
  background: none; border: 1.5px solid rgba(255,255,255,0.3);
  color: rgba(255,255,255,0.85); padding: 9px 19px;
  border-radius: var(--radius-sm); cursor: pointer;
  /* Never wrap. Squeezed into a narrow topbar this was breaking onto two
     lines and growing taller than the bar itself — measured at 375px,
     "Sign In" was 46px tall inside a 62px bar. A button that cannot fit
     should overflow visibly, not silently deform the row. */
  white-space: nowrap;
  transition: background 0.14s, border-color 0.14s, color 0.14s;
}
.signin-btn:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.6); color: #fff; }
/* Only the mobile overrides for this existed here; the base rule lived solely in
   the marketing page. shared-core.js now injects the button into this origin's
   topbar for signed-out visitors, so it needs the whole definition. Kept
   identical to public/index.html — it is the same control on both sites. */
.topbar-access-btn {
  font-family: var(--sans); font-size: 13.5px; font-weight: 600;
  letter-spacing: 0.4px; text-transform: uppercase;
  background: var(--flame); border: 1.5px solid var(--flame);
  color: #fff; padding: 9px 19px;
  border-radius: var(--radius-sm); cursor: pointer;
  white-space: nowrap; /* see .signin-btn */
  transition: background 0.14s, border-color 0.14s;
}
.topbar-access-btn:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.topbar-motto {
  font-family: var(--serif); font-size: 15px; font-weight: 400;
  color: rgba(255,255,255,0.65); letter-spacing: 0.2px;
}

/* ── VIEWS ── */
#homeView, #planningView, #implementationView, #assessmentView, #skillBuildingView, #chatView {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  position: relative; z-index: 1; overflow: hidden;
}
#planningView, #implementationView, #assessmentView, #skillBuildingView, #chatView { display: none; }

/* ── PAGE VIEW CONTAINER (multi-page) ──
   Each real page has exactly one .app-view as its main region. Unlike the
   per-view IDs above it is visible by default: with real page navigation
   there is no sibling view to toggle it against, so nothing has to remember
   to show it. This is the rule that replaces the old hide-every-other-view
   dance — a page can only ever display its own content. */
.app-view {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  position: relative; z-index: 1; overflow: hidden;
}

/* ── SITE NAV TABS — merged into the topbar row ── */
#siteNavTabs {
  display: flex; align-items: center; gap: 2px; min-width: 0;
  overflow-x: auto; scrollbar-width: none;
}
#siteNavTabs::-webkit-scrollbar { display: none; }
.site-nav-tab {
  background: none; border: none; cursor: pointer; white-space: nowrap;
  font-family: var(--sans); font-size: 14.5px; font-weight: 600;
  color: rgba(255,255,255,0.55); padding: 9px 16px;
  border-radius: 999px; transition: color 0.14s, background 0.14s;
  /* Now rendered as <a href> rather than <button onclick> so the tabs are
     real navigation — link defaults have to be neutralised. */
  text-decoration: none; display: inline-block; line-height: 1.2;
}
.site-nav-tab:hover { color: rgba(255,255,255,0.85); }
.site-nav-tab.active { color: #fff; }
/* The dashboard tab is a glyph, not a word. Inline SVG stroked in currentColor
   so the resting / hover / active whites above apply to it unchanged. Padding is
   narrower than a text tab's so the 18px square sits optically level with the
   x-height beside it rather than floating in a text-sized pill. */
.site-nav-tab--icon { display: inline-flex; align-items: center; padding: 8px 12px; }
.site-nav-tab--icon svg { display: block; }
/* Implementation / Assessment / Skill Building are rendered as <span>, not
   <a> — they are shown so teachers can see what's coming, but nothing about
   them should suggest they can be opened. No pointer, no hover response, and
   not selectable, so a click-and-drag doesn't highlight them like text. The
   :hover rule below deliberately restates the resting colour rather than
   being absent: .site-nav-tab:hover would otherwise brighten them. */
.site-nav-tab--soon {
  color: rgba(255,255,255,0.35);
  cursor: default;
  user-select: none; -webkit-user-select: none;
}
.site-nav-tab--soon:hover { color: rgba(255,255,255,0.35); }
.site-nav-tab--soon-badge {
  font-size: 8.5px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase;
  background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.65);
  border-radius: 3px; padding: 1px 5px; margin-left: 6px; vertical-align: 1px;
}

/* ── PLANNING TOOLS PICKER — centered, Brisk-style layout ── */
.tools-picker-wrap {
  flex:1; min-height:0; overflow-y:auto; padding: 48px 40px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.tools-picker-heading { font-family: var(--serif); font-size: 26px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.tools-picker-sub { font-family: var(--sans); font-size: 13px; color: var(--ink3); margin-bottom: 28px; }
.tools-picker-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px;
  max-width: var(--page-max); width: 100%; text-align: left;
}
.tool-card {
  background: var(--card); border: 1.5px solid var(--line2); border-radius: var(--radius-lg);
  padding: 22px 20px; cursor: pointer; transition: border-color 0.14s, box-shadow 0.14s, transform 0.14s;
}
.tool-card:hover { border-color: var(--steel-tint); box-shadow: 0 4px 18px -6px rgba(27,58,82,0.18); transform: translateY(-1px); }
/* Assessment and Skill Building on the dashboard. The same rule the nav's
   "Soon" spans follow: shown so teachers can see what is coming, but not
   clickable, and not pretending to be — no pointer, no lift on hover. */
.tool-card--soon { cursor: default; }
.tool-card--soon:hover { border-color: var(--line2); box-shadow: none; transform: none; }
.tool-card-title { font-family: var(--serif); font-size: 15.5px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.tool-card-desc { font-family: var(--sans); font-size: 12.5px; color: var(--ink3); line-height: 1.55; }
.tool-card-badge {
  display: inline-block; font-size: 9px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase;
  background: var(--bg2); color: var(--ink3); border-radius: 3px; padding: 2px 6px; margin-top: 10px;
}

/* ── FULL-PAGE VIEWS — Project Context / Your Projects. Replace the old
   cramped side-panel presentation with a page that uses the real width
   of the app, now that these are top-level destinations, not nested
   sub-panels. ── */
.full-page-wrap { flex:1; min-height:0; overflow-y:auto; padding: 32px 40px 60px; }
/* width: 100% for .impl-inner's reason, and it is the same bug in the same
   shape: .full-page-wrap sits on the SAME element as .app-view, so this is a
   flex item in a column flex container, and margin: 0 auto on the cross axis
   defeats align-items: stretch. Measured on projects.html at a 1512 viewport
   before this: 320px wide with 1432 available and a 1280 cap that could never
   be reached. Every page using it — projects, new-project, admin — sized itself
   to whatever it happened to contain. */
.full-page-inner { max-width: var(--page-max); margin: 0 auto; width: 100%; }
.full-page-heading { font-family: var(--serif); font-size: 24px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.full-page-sub { font-family: var(--sans); font-size: 13.5px; color: var(--ink3); margin-bottom: 28px; }

/* Project Context page */
.context-required-card {
  background: var(--pale-flame-tint); border: 1.5px solid var(--line-bold); border-radius: var(--radius-lg);
  padding: 22px 24px; margin-bottom: 28px;
}
.context-section-label {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--ink3); margin-bottom: 14px;
}
.context-required-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
.context-optional-label {
  font-family: var(--sans); font-size: 11px; font-weight: 600; letter-spacing: 0.3px;
  color: var(--ink3); text-transform: uppercase; margin: 4px 0 16px;
}
.context-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 18px; margin-bottom: 28px; }
.context-section-card {
  background: var(--card); border: 1.5px solid var(--line); border-radius: var(--radius-lg); padding: 20px 22px;
}
.context-section-card--full { grid-column: 1 / -1; }
.context-resources-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; margin-bottom: 16px; }
.context-submit-bar { display: flex; flex-direction: column; align-items: center; max-width: 420px; margin: 8px auto 0; }
.context-submit-bar .cta-btn { width: 100%; }

/* Your Projects page */
.projects-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; margin-bottom: 24px;
}
.projects-restore-bar { border-top: 1px solid var(--line); padding-top: 20px; max-width: 320px; margin: 0 auto; }

/* ── COMING SOON PLACEHOLDER PANELS ── */
.coming-soon-wrap { flex:1; min-height:0; overflow-y:auto; display:flex; align-items:flex-start; justify-content:center; padding: 60px 40px; }
.coming-soon-card { max-width: 720px; text-align:left; }
.coming-soon-badge {
  display:inline-block; font-family: var(--sans); font-size: 10px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase;
  background: var(--light-flame); color: var(--flame); border-radius: 4px; padding: 3px 8px; margin-bottom: 12px;
}
.coming-soon-title { font-family: var(--serif); font-size: 24px; font-weight: 700; color: var(--ink); margin-bottom: 12px; }
.coming-soon-body { font-family: var(--sans); font-size: 13.5px; color: var(--ink2); line-height: 1.7; }
.coming-soon-body p { margin-bottom: 12px; }

/* ══════════════════════════════════════════════════════════════
   VISUAL REFRESH — scoped to Home / nav / Planning tools picker
   (tools-picker, Full Plan entry forms, PBL Advising, Project
   Library placeholder, and the three "coming soon" tabs). The
   six-stage planning chat (#chatView) is deliberately untouched —
   see conversation for why: it's the live pilot tool, higher risk.

   Approach: redeclare --serif on these containers so every
   descendant that already reads var(--serif) — headings and body
   copy alike — picks up the new sans automatically via normal CSS
   custom-property inheritance. No per-element font edits needed.
   ══════════════════════════════════════════════════════════════ */
#homeView, #siteNavTabs, #planningView,
#implementationView, #assessmentView, #skillBuildingView {
  --serif: var(--heading-sans);
}

/* Pill buttons — primary CTAs across the refreshed surface */
#homeView .cta-btn, #homeView .mobile-start-cta,
#homeView .pre-chat-btn,
#planningView .cta-btn, #planningView .pre-chat-btn {
  border-radius: 999px;
}

/* Bolder, darker card borders — matches the reference sites' more
   graphic card treatment vs. the original thin steel-grey line */
#homeView .entry-card,
#planningView .tool-card, #planningView .coming-soon-card,
#implementationView .coming-soon-card, #assessmentView .coming-soon-card,
#skillBuildingView .coming-soon-card,
.app-view .tool-card, .app-view .coming-soon-card {
  border-width: 1.5px;
  border-color: var(--line-bold);
}

/* Highlighter-mark utility — soft accent-color band behind a key
   phrase, bold text on top. Applied sparingly, not globally. */
#homeView .hl-mark, #planningView .hl-mark {
  background: var(--light-flame);
  padding: 0 6px;
  border-radius: 4px;
  font-weight: 700;
}

/* Nav tabs — filled pill for the active tab instead of an
   underline, closer to both reference sites' nav convention */
.site-nav-tab.active {
  background: rgba(255,255,255,0.14);
  border-radius: 999px;
  border-bottom-color: transparent;
}
.hero-mock-stage.active { background: var(--flame); color: #fff; }
.compare-forge-card .wordmark { font-size: 26px; }

/* Bump up card headings + eyebrows a notch — small, undersized
   headers were part of why the refresh read as "the same but with
   a different font" rather than genuinely bolder */
#homeView .section-eyebrow { color: var(--flame); }

/* RIGHT PANEL — project context form */
.right-panel {
  width: 380px; flex-shrink: 0; min-height: 0; overflow-y: auto;
  background: var(--card); padding: 28px 28px 32px;
  border-left: 1px solid var(--line);
  box-shadow: -4px 0 20px -8px rgba(27,58,82,0.1);
}
.right-panel::-webkit-scrollbar { width: 5px; }
.right-panel::-webkit-scrollbar-track { background: transparent; }
.right-panel::-webkit-scrollbar-thumb { background: var(--line2); border-radius: 3px; }

/* ── LEFT PANEL CONTENT ── */
.section { margin-bottom: 36px; }
.section:last-child { margin-bottom: 0; }

.section-eyebrow {
  font-family: var(--sans); font-size: 10px; font-weight: 700;
  letter-spacing: 1.2px; text-transform: uppercase; color: var(--steel-tint);
  margin-bottom: 10px;
}
.pillar-card.authenticity { border-left-color: var(--flame); }
.pillar-card.student-centered { border-left-color: var(--steel); }
.pillar-card.scaffolding { border-left-color: #2E7D5B; }

/* ── AUTH GATE MODAL ── */
.auth-gate-overlay {
  display: none; position: fixed; inset: 0; z-index: 1000;
  background: rgba(27,42,58,0.65); backdrop-filter: blur(4px);
  align-items: center; justify-content: center;
}
.auth-gate-overlay.open { display: flex; }
.auth-gate-modal {
  background: var(--card); border-radius: var(--radius-lg);
  padding: 36px 40px; max-width: 480px; width: 90%;
  box-shadow: var(--shadow-lg);
  animation: fadeUp 0.3s ease forwards;
}
.auth-gate-eyebrow {
  font-family: var(--sans); font-size: 10px; font-weight: 700;
  letter-spacing: 1px; text-transform: uppercase; color: var(--flame);
  margin-bottom: 10px;
}
.auth-gate-title {
  font-family: var(--serif); font-size: 22px; font-weight: 700;
  color: var(--ink); line-height: 1.2; margin-bottom: 12px;
}
.auth-gate-body {
  font-family: var(--sans); font-size: 13.5px; color: var(--ink2);
  line-height: 1.7; margin-bottom: 24px;
}
.auth-gate-actions { display: flex; flex-direction: column; gap: 10px; }
.auth-gate-primary {
  width: 100%; padding: 13px 18px; background: var(--flame); color: #fff;
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  border: none; border-radius: var(--radius); cursor: pointer;
  box-shadow: 0 2px 8px -2px rgba(var(--flame-rgb),0.4);
  transition: background 0.14s;
}
.auth-gate-primary:hover { background: var(--flame-hover); }
.auth-gate-secondary {
  width: 100%; padding: 12px 18px; background: none; color: var(--ink);
  font-family: var(--sans); font-size: 13px; font-weight: 500;
  border: 1.5px solid var(--line2); border-radius: var(--radius);
  cursor: pointer; transition: border-color 0.14s;
}
.auth-gate-secondary:hover { border-color: var(--steel-tint); }
.auth-gate-dismiss {
  text-align: center; margin-top: 8px;
  font-family: var(--sans); font-size: 12px; color: var(--ink3);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.auth-gate-dismiss:hover { color: var(--ink); }
/* ── PREVIEW LIMIT NOTE — replaces the modal that used to cover the answer ── */
.pc-limit-note {
  margin-top: 4px; padding: 12px 14px;
  background: var(--pale-flame-tint); border: 1px solid rgba(var(--flame-rgb),0.28);
  border-radius: var(--radius); font-family: var(--sans); font-size: 12.5px;
  line-height: 1.65; color: var(--ink2);
}
.pc-limit-link {
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--sans); font-size: 12.5px; font-weight: 700;
  color: var(--flame); text-decoration: underline; text-underline-offset: 2px;
}
.pc-limit-link:hover { color: var(--flame-hover); }

/* ── TOKEN COUNTER ──
   The landing chat's preview meter, shown only to a visitor without planning
   access. Empty is the signed-in case and must take no space, so the element
   can stay in the markup rather than being created and destroyed. */
.token-counter {
  font-family: var(--sans); font-size: 11px; color: var(--ink3);
  text-align: center; margin-top: 8px; transition: color 0.2s;
}
.token-counter:empty { display: none; }
.token-counter.low { color: var(--flame); font-weight: 600; }

/* ── RIGHT PANEL — FORM ── */
.form-heading {
  font-family: var(--serif); font-size: 17px; font-weight: 700;
  color: var(--ink); margin-bottom: 4px; letter-spacing: -0.2px;
}

@keyframes heroFadeUp {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.pillar-timeline-item:hover .ptl-dot,
.pillar-timeline-item.open .ptl-dot { transform: scale(1.2); }
.pillar-card.open { box-shadow: 0 4px 16px -4px rgba(27,58,82,0.2); }
.pillar-card.open .pillar-expand-icon { transform: rotate(45deg); color: var(--flame); }

.pc-msg { display: flex; gap: 10px; align-items: flex-start; animation: fadeUp 0.2s ease forwards; }
.pc-msg-avatar {
  flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-size: 10px; font-weight: 700; margin-top: 2px;
}
.pc-msg.ai .pc-msg-avatar { background: var(--navy); color: var(--light-steel); }
.pc-msg.teacher .pc-msg-avatar { background: var(--light-flame); color: var(--flame); }
.pc-msg-body { flex: 1; min-width: 0; }
.pc-msg-label {
  font-family: var(--sans); font-size: 10px; font-weight: 600;
  letter-spacing: 0.5px; text-transform: uppercase; margin-bottom: 4px;
}
.pc-msg.ai .pc-msg-label { color: var(--steel-tint); }
.pc-msg.teacher .pc-msg-label { color: var(--flame); }
.pc-msg-bubble {
  padding: 10px 14px; border-radius: var(--radius-lg);
  font-size: 13.5px; line-height: 1.65; font-family: var(--serif);
}
.pc-msg.ai .pc-msg-bubble {
  background: var(--card); border: 1px solid var(--line);
  box-shadow: var(--shadow-sm); color: var(--ink); border-top-left-radius: 4px;
}
.pc-msg.teacher .pc-msg-bubble {
  background: var(--bubble-teacher-bg); border: 1px solid var(--line);
  color: var(--ink); border-top-left-radius: 4px;
}
.pc-msg-bubble p { margin-bottom: 8px; }
.pc-msg-bubble p:last-child { margin-bottom: 0; }
.pc-msg-bubble strong { font-weight: 700; color: var(--ink); }
.pc-msg-bubble em { font-style: italic; }
.pc-msg-bubble ul, .pc-msg-bubble ol { padding-left: 18px; margin: 4px 0 8px; }
.pc-msg-bubble li { margin-bottom: 3px; }
textarea#preChatInput, textarea#advisingInput {
  flex: 1; font-family: var(--serif); font-size: 13.5px; color: var(--ink);
  background: var(--bg); border: 1.5px solid var(--line2);
  border-radius: var(--radius); padding: 9px 12px;
  resize: none; min-height: 40px; max-height: 120px;
  line-height: 1.5; outline: none; transition: border-color 0.15s;
}
textarea#preChatInput:focus, textarea#advisingInput:focus { border-color: var(--steel-tint); box-shadow: 0 0 0 3px rgba(46,95,120,0.1); }
textarea#preChatInput::placeholder, textarea#advisingInput::placeholder { color: var(--ink3); font-style: italic; }
#preChatSendBtn, #advisingSendBtn {
  flex-shrink: 0; width: 38px; height: 38px;
  background: var(--flame); border: none; border-radius: var(--radius);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background 0.15s; box-shadow: 0 2px 8px -2px rgba(var(--flame-rgb),0.4);
}
#preChatSendBtn:hover, #advisingSendBtn:hover { background: var(--flame-hover); }
#preChatSendBtn:disabled, #advisingSendBtn:disabled { background: var(--ink3); cursor: not-allowed; box-shadow: none; }
#preChatSendBtn svg, #advisingSendBtn svg { width: 16px; height: 16px; }

/* ── LANDING CHAT — the hero entry point on planningToolsPicker. Inline,
   not a separate view: the thread grows downward in-page, pushing Recent
   Projects and the action cards further down rather than navigating away.
   Reuses the advisingInput/advisingSendBtn/advisingMessages IDs (and all
   of sendAdvisingMessage()'s existing logic) — just relocated inline.
   Higher-specificity selectors below (scoped under .landing-chat-box)
   override the compact bottom-bar styles those IDs get elsewhere. ── */
.landing-chat-box {
  background: var(--card); border: 1.5px solid var(--line-bold); border-radius: var(--radius-lg);
  padding: 16px 18px; box-shadow: var(--shadow); max-width: var(--reading-max); width: 100%;
  margin-bottom: 28px; text-align: left;
}
.landing-chat-messages {
  display: flex; flex-direction: column; gap: 16px;
  /* Fixed height, not max-height. Growing from nothing to 55vh pushed the
     action cards down the page as a conversation went on — and pushed them
     off-screen entirely on a laptop. A constant height means the cards sit
     in the same place from first load onward, and the transcript scrolls
     within the box instead of reflowing the page around it.
     35vh is set so all four action cards clear the fold on a laptop. */
  height: 35vh; overflow-y: auto; scroll-behavior: smooth;
}
.landing-chat-messages { padding-bottom: 16px; margin-bottom: 12px; border-bottom: 1px solid var(--line); }
.landing-chat-inner { display: flex; gap: 10px; align-items: flex-end; }
.landing-chat-box textarea#advisingInput {
  flex: 1; font-family: var(--serif); font-size: 15px; color: var(--ink);
  background: none; border: none; resize: none; min-height: 26px; max-height: 160px;
  line-height: 1.5; outline: none; padding: 4px 2px;
}
.landing-chat-box textarea#advisingInput::placeholder { color: var(--ink3); font-style: italic; }
.landing-chat-box #advisingSendBtn {
  flex-shrink: 0; width: 42px; height: 42px;
  background: var(--flame); border: none; border-radius: var(--radius);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background 0.15s; box-shadow: 0 2px 8px -2px rgba(var(--flame-rgb),0.4);
}
.landing-chat-box #advisingSendBtn:hover { background: var(--flame-hover); }
.landing-chat-box #advisingSendBtn:disabled { background: var(--ink3); cursor: not-allowed; box-shadow: none; }
.landing-chat-box #advisingSendBtn svg { width: 18px; height: 18px; }

/* ── UPLOAD ZONE ── */
.upload-zone {
  border: 2px dashed var(--line2); border-radius: var(--radius);
  padding: 28px 20px; text-align: center; cursor: pointer;
  transition: border-color 0.14s, background 0.14s; background: var(--bg);
}
.upload-zone:hover { border-color: var(--steel-tint); background: var(--offwhite); }
.upload-zone.drag-over { border-color: var(--flame); background: var(--light-flame); }
.upload-icon { font-size: 28px; color: var(--ink3); margin-bottom: 8px; }
.upload-label { font-family: var(--sans); font-size: 13px; font-weight: 600; color: var(--ink2); margin-bottom: 4px; }
.upload-hint { font-family: var(--sans); font-size: 11.5px; color: var(--ink3); }
.file-selected-card {
  display: flex; align-items: center; justify-content: space-between;
  background: var(--bg2); border: 1px solid var(--line2);
  border-radius: var(--radius-sm); padding: 8px 12px;
}

/* ── RIGHT PANEL — FORM (continued) ── */
.form-subheading {
  font-family: var(--sans); font-size: 12px; color: var(--ink3);
  margin-bottom: 22px; line-height: 1.5;
}

.field-group { margin-bottom: 16px; }
.field-label {
  display: block; font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink2); letter-spacing: 0.2px; margin-bottom: 5px;
}
.field-label .req { color: var(--flame); margin-left: 2px; }
.field-label .opt {
  font-weight: 400; color: var(--ink3); font-style: italic; margin-left: 4px;
}

/* Inline "pick a file" control inside a form field. Was a wall of inline style
   on new-project.html's curriculum upload; it has a class now because that
   upload is a first-class field rather than something hidden behind a select. */
.upload-inline-btn {
  background: none; border: 1.5px dashed var(--line2); border-radius: var(--radius-sm);
  padding: 8px 12px; font-family: var(--sans); font-size: 12.5px;
  color: var(--steel); cursor: pointer; transition: border-color 0.14s, color 0.14s;
}
.upload-inline-btn:hover { border-color: var(--flame); color: var(--ink2); }

input[type="text"], input[type="number"], select, textarea.field-textarea {
  width: 100%; font-family: var(--serif); font-size: 13.5px; color: var(--ink);
  background: var(--bg); border: 1.5px solid var(--line2);
  border-radius: var(--radius-sm); padding: 8px 10px;
  outline: none; transition: border-color 0.14s;
  line-height: 1.5;
}
input[type="text"]:focus, input[type="number"]:focus,
select:focus, textarea.field-textarea:focus {
  border-color: var(--steel-tint);
  box-shadow: 0 0 0 3px rgba(46,95,120,0.1);
}
input::placeholder, textarea::placeholder { color: var(--ink3); font-style: italic; }

textarea.field-textarea { resize: vertical; min-height: 60px; }

select { appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%238794A0' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; padding-right: 28px;
}

/* Multi-select chips */
.chip-group { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.chip {
  display: inline-flex; align-items: center;
  font-family: var(--sans); font-size: 11.5px; font-weight: 500;
  padding: 4px 10px; border-radius: 20px; cursor: pointer;
  border: 1.5px solid var(--line2); background: var(--bg);
  color: var(--ink2); transition: all 0.13s; user-select: none;
}
.chip:hover { border-color: var(--steel-tint); color: var(--steel); }
.chip.selected {
  background: var(--navy); border-color: var(--navy); color: #fff;
}
.chip.selected:hover { background: var(--navy-2); }

/* Resource checklist */
.resource-list {
  display: flex; flex-direction: column; gap: 4px; margin-top: 4px;
  max-height: 160px; overflow-y: auto; padding-right: 4px;
}
.resource-list::-webkit-scrollbar { width: 4px; }
.resource-list::-webkit-scrollbar-thumb { background: var(--line2); border-radius: 2px; }
.resource-item {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 4px 6px; border-radius: var(--radius-sm);
  font-size: 12.5px; color: var(--ink2); transition: background 0.1s;
}
.resource-item:hover { background: var(--bg2); }
.resource-item input[type="checkbox"] { width: 14px; height: 14px; flex-shrink: 0; cursor: pointer; accent-color: var(--ink); }

.form-divider { border: none; border-top: 1px solid var(--line); margin: 18px 0; }

/* CTA button */
.cta-btn {
  width: 100%; padding: 13px 20px;
  background: var(--flame); color: #fff;
  font-family: var(--sans); font-size: 14px; font-weight: 600; letter-spacing: 0.1px;
  border: none; border-radius: var(--radius); cursor: pointer;
  box-shadow: 0 3px 12px -3px rgba(var(--flame-rgb),0.45);
  transition: background 0.14s, transform 0.1s, box-shadow 0.14s;
  margin-top: 6px;
}
.cta-btn:hover { background: var(--flame-hover); box-shadow: 0 4px 16px -3px rgba(var(--flame-rgb),0.55); }
.cta-btn:active { transform: scale(0.98); }
.cta-btn:disabled { background: var(--ink3); box-shadow: none; cursor: not-allowed; }

.cta-note {
  font-family: var(--sans); font-size: 11px; color: var(--ink3);
  text-align: center; margin-top: 8px; line-height: 1.5;
}

/* ── CHAT VIEW LAYOUT ── */
#chatView { display: none; flex-direction: column; }

/* .chat-topbar, .chat-actions and .action-btn are gone with the chat topbar.
   .context-banner survives without a bar to sit in: it is display:none, but the
   element stays in the DOM because three callers read its .innerHTML. */
.context-banner {
  display: none; /* Project Context summary lives in the right-side panel instead */
  flex: 1; padding: 8px 16px; font-family: var(--sans); font-size: 11.5px;
  color: var(--offwhite); align-items: center; gap: 8px;
}
.context-banner strong { color: var(--light-steel); font-weight: 600; }
.context-banner .ctx-items { display: flex; flex-wrap: wrap; gap: 8px; }
.ctx-pill {
  background: rgba(255,255,255,0.1); border-radius: 4px;
  padding: 2px 8px; font-size: 11px; color: rgba(240,246,250,0.8);
}
.chat-body {
  flex: 1; overflow: hidden; display: flex;
}

/* MAIN CHAT */
.chat-main {
  flex: 1; overflow: hidden; display: flex; flex-direction: column; min-width: 0;
}
.chat-scroll { flex: 1; overflow-y: auto; padding: 28px 0 16px; scroll-behavior: smooth; }
.chat-scroll::-webkit-scrollbar { width: 6px; }
.chat-scroll::-webkit-scrollbar-track { background: transparent; }
.chat-scroll::-webkit-scrollbar-thumb { background: var(--line2); border-radius: 3px; }
.messages {
  max-width: var(--reading-max); margin: 0 auto; padding: 0 20px;
  display: flex; flex-direction: column; gap: 18px;
}
.msg { display: flex; gap: 12px; align-items: flex-start; animation: fadeUp 0.22s ease forwards; }
@keyframes fadeUp { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
.msg-avatar {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  letter-spacing: 0.3px; margin-top: 2px;
}
.msg.ai .msg-avatar { background: var(--navy); color: var(--light-steel); border: 1.5px solid rgba(255,255,255,0.25); }
.msg.teacher .msg-avatar { background: var(--light-flame); color: var(--flame); border: 1.5px solid rgba(var(--flame-rgb),0.2); }
.msg-body { flex: 1; min-width: 0; }
.msg-label { font-family: var(--sans); font-size: 10.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; margin-bottom: 5px; }
.msg.ai .msg-label { color: var(--steel-tint); }
.msg.teacher .msg-label { color: var(--flame); }
.msg-bubble { padding: 13px 16px; border-radius: var(--radius-lg); line-height: 1.65; font-size: 14px; }
.msg.ai .msg-bubble { background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow-sm); color: var(--ink); border-top-left-radius: 4px; }
.msg.teacher .msg-bubble { background: var(--bubble-teacher-bg); border: 1px solid var(--line); color: var(--ink); border-top-left-radius: 4px; }
.msg-bubble p { margin-bottom: 10px; }
.msg-bubble p:last-child { margin-bottom: 0; }
.msg-bubble strong { font-weight: 700; color: var(--ink); }
.msg-bubble em { font-style: italic; }
.msg-bubble code { font-family: 'Menlo','Courier New',monospace; font-size: 12.5px; background: var(--bg2); padding: 1px 5px; border-radius: 4px; color: var(--steel); }
.msg-bubble pre { background: var(--bg2); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; font-family: 'Menlo','Courier New',monospace; font-size: 12px; line-height: 1.6; overflow-x: auto; margin: 10px 0; color: var(--ink2); white-space: pre-wrap; word-break: break-word; }
.msg-bubble ul, .msg-bubble ol { padding-left: 20px; margin: 6px 0 10px; }
.msg-bubble li { margin-bottom: 4px; }
.msg-bubble h1, .msg-bubble h2, .msg-bubble h3 { font-family: var(--serif); color: var(--ink); margin: 14px 0 6px; line-height: 1.3; }
.msg-bubble h1 { font-size: 16px; } .msg-bubble h2 { font-size: 15px; } .msg-bubble h3 { font-size: 14px; }
.msg-bubble hr { border: none; border-top: 1px solid var(--line); margin: 10px 0; }
.msg-bubble blockquote { border-left: 3px solid var(--light-steel); margin: 8px 0; padding: 4px 12px; color: var(--ink2); font-style: italic; }
.msg-bubble table.md-table {
  border-collapse: collapse; width: 100%; margin: 10px 0;
  font-size: 13px; font-family: var(--sans);
}
.msg-bubble table.md-table th, .msg-bubble table.md-table td {
  border: 1px solid var(--line2); padding: 6px 10px;
  text-align: left; vertical-align: top; color: var(--ink);
}
.msg-bubble table.md-table th {
  background: var(--bg2); font-weight: 700; color: var(--ink);
  font-size: 11.5px; letter-spacing: 0.2px;
}
.msg-bubble table.md-table tr:nth-child(even) td { background: var(--bg); }

/* Stage output cards — end-of-stage block review shown to the teacher */
.stage-output {
  border: 1px solid var(--line2);
  border-left: 3px solid var(--steel);
  border-radius: var(--radius);
  background: var(--bg2);
  padding: 12px 14px 4px;
  margin: 4px 0 12px;
}
.stage-output-label {
  font-family: var(--sans);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--steel);
  margin-bottom: 10px;
}
.stage-output-block { margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.stage-output-block:last-child { border-bottom: none; margin-bottom: 8px; }
.stage-output-block-title {
  font-family: var(--serif);
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 5px;
}
.stage-output-block-body { font-size: 13.5px; line-height: 1.6; color: var(--ink); }
.stage-output-block-body p:first-child { margin-top: 0; }
.stage-output-block-body p:last-child { margin-bottom: 0; }

/* A teacher's own edit of a block. Different left border from the model's
   stage output, so which of you wrote the text in front of you is answerable
   at a glance rather than by reading it. */
.stage-output--edit { border-left-color: var(--flame); }
.stage-output-edit-row { margin-top: 8px; }
.stage-output-edit-btn, .stage-output-save-btn, .stage-output-cancel-btn {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  padding: 4px 10px; border-radius: var(--radius-sm); cursor: pointer;
  background: none; border: 1px solid var(--line); color: var(--ink3);
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.stage-output-edit-btn:hover, .stage-output-cancel-btn:hover {
  color: var(--ink); border-color: var(--steel);
}
.stage-output-save-btn { background: var(--flame); border-color: var(--flame); color: #fff; }
.stage-output-save-btn:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.stage-output-edit-btn:focus-visible, .stage-output-save-btn:focus-visible,
.stage-output-cancel-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(46,95,120,0.25); }
.stage-output-editor { margin-top: 8px; }
/* Monospace because the content is markdown — tables and lists only line up
   for the person editing them in a fixed-width face. */
.stage-output-textarea {
  width: 100%; box-sizing: border-box; resize: vertical;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; line-height: 1.55; color: var(--ink);
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 9px 11px;
}
.stage-output-textarea:focus-visible { outline: none; border-color: var(--steel); }
.stage-output-editor-actions {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 8px; margin-top: 7px;
}
.stage-output-editor-note {
  font-family: var(--sans); font-size: 11px; color: var(--ink3); margin-right: auto;
}

.typing-indicator { display: flex; gap: 12px; align-items: flex-start; animation: fadeUp 0.2s ease forwards; }
.typing-dots { display: flex; align-items: center; gap: 5px; padding: 14px 18px; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-lg); border-top-left-radius: 4px; box-shadow: var(--shadow-sm); }
.typing-dot { width: 7px; height: 7px; background: var(--steel-tint); border-radius: 50%; animation: dotBounce 1.3s ease-in-out infinite; }
.typing-dot:nth-child(2) { animation-delay: 0.18s; }
.typing-dot:nth-child(3) { animation-delay: 0.36s; }
@keyframes dotBounce { 0%,60%,100% { transform:translateY(0); opacity:0.4; } 30% { transform:translateY(-5px); opacity:1; } }

.input-bar { flex-shrink: 0; background: var(--card); border-top: 1px solid var(--line2); padding: 14px 20px 16px; z-index: 5; box-shadow: 0 -4px 20px -8px rgba(27,58,82,0.1); }
.input-inner { max-width: var(--reading-max); margin: 0 auto; display: flex; gap: 10px; align-items: flex-end; }
textarea#userInput { flex: 1; font-family: var(--serif); font-size: 14px; color: var(--ink); background: var(--bg); border: 1.5px solid var(--line2); border-radius: var(--radius); padding: 10px 13px; resize: none; min-height: 44px; max-height: 140px; line-height: 1.55; outline: none; transition: border-color 0.15s; overflow-y: auto; }
textarea#userInput:focus { border-color: var(--steel-tint); box-shadow: 0 0 0 3px rgba(46,95,120,0.1); }
textarea#userInput::placeholder { color: var(--ink3); font-style: italic; }
#sendBtn { flex-shrink: 0; width: 42px; height: 42px; background: var(--flame); border: none; border-radius: var(--radius); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.15s, transform 0.1s; box-shadow: 0 2px 8px -2px rgba(var(--flame-rgb),0.4); }
#sendBtn:hover { background: var(--flame-hover); }
#sendBtn:active { transform: scale(0.95); }
#sendBtn:disabled { background: var(--ink3); cursor: not-allowed; box-shadow: none; }
#sendBtn svg { width: 18px; height: 18px; fill: #fff; }
/* Both ends labelled, side by side, so the control reads as a choice between
   two named modes rather than a status light. The row is centred against the
   42px send button rather than sharing the composer's flex-end. */
.concise-toggle { display: flex; flex-direction: row; align-items: center; gap: 6px; flex-shrink: 0; height: 42px; }
.concise-toggle-label { font-family: var(--sans); font-size: 10px; font-weight: 600; letter-spacing: 0.02em; color: var(--ink3); white-space: nowrap; transition: color 0.15s; }
.concise-toggle-label.active { color: var(--flame); }
.concise-toggle-track { width: 36px; height: 20px; background: var(--steel); border-radius: 10px; border: none; cursor: pointer; padding: 0; position: relative; transition: background 0.2s, box-shadow 0.15s; flex-shrink: 0; }
.concise-toggle-track:hover { background: var(--steel-tint); }
.concise-toggle-track.on:hover { background: var(--flame-hover); }
.concise-toggle-track:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(46,95,120,0.25); }
.concise-toggle-track.on { background: var(--flame); }
.concise-toggle-thumb { position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; background: #fff; border-radius: 50%; transition: transform 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
.concise-toggle-track.on .concise-toggle-thumb { transform: translateX(16px); }
.input-hint { max-width: var(--reading-max); margin: 7px auto 0; font-family: var(--sans); font-size: 11px; color: var(--ink3); padding: 0 2px; }
/* Answers "what do I lose?" — the question that stops someone flipping an
   unexplained switch in the middle of a plan they care about. */
.concise-hint { color: var(--steel); }

/* The two buttons in the response-depth ask. Both are the same weight on
   purpose: one of them is the status quo and neither is the recommendation,
   so styling one as primary would be answering for the teacher. */
.concise-ask-btn {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  padding: 5px 12px; border-radius: var(--radius-sm); cursor: pointer;
  color: #fff; background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.35);
  white-space: nowrap; transition: background 0.15s;
}
.concise-ask-btn:hover { background: rgba(255,255,255,0.22); }
.concise-ask-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(255,255,255,0.3); }


.tier-continue-btn {
  width: 100%; padding: 8px 6px;
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  border-radius: var(--radius-sm); cursor: pointer;
  border: 1.5px solid var(--flame); background: var(--flame);
  color: #fff; transition: background 0.13s;
}
.tier-continue-btn:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.tier-continue-btn:disabled { background: var(--ink3); border-color: var(--ink3); cursor: not-allowed; }

.tier-export-btn {
  flex: 1; padding: 7px 6px;
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  border-radius: var(--radius-sm); cursor: pointer;
  border: 1.5px solid var(--navy); background: var(--navy);
  color: #fff; transition: background 0.13s;
}
.tier-export-btn:hover { background: var(--navy-2); border-color: var(--navy-2); }
.tier-export-btn:disabled { background: var(--ink3); border-color: var(--ink3); cursor: not-allowed; }

/* ── COLOR SCHEME PICKER (Stage 2 tier pause) ── */
.color-swatch-row {
  display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px;
}
.color-swatch-btn {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 8px; border-radius: var(--radius-sm);
  border: 1.5px solid var(--line); background: var(--card); cursor: pointer;
  font-family: var(--sans); font-size: 10.5px; color: var(--ink3);
  transition: border-color 0.13s, background 0.13s;
}
.color-swatch-btn:hover { border-color: var(--ink); background: rgba(var(--ink-rgb),0.05); }
.color-swatch-btn.selected { border-color: var(--ink); background: rgba(var(--ink-rgb),0.08); color: var(--ink); font-weight: 600; }
.color-swatch-dots { display: flex; }
.color-swatch-dot {
  width: 13px; height: 13px; border-radius: 50%;
  border: 1px solid rgba(0,0,0,0.15); margin-left: -4px;
}
.color-swatch-dot:first-child { margin-left: 0; }
.color-hint-row { display: flex; gap: 6px; margin-bottom: 6px; }
.color-hint-input {
  flex: 1; padding: 6px 8px; font-family: var(--sans); font-size: 11.5px;
  border: 1.5px solid var(--line); border-radius: var(--radius-sm); color: var(--ink);
}
.color-hint-input:focus { outline: none; border-color: var(--ink); }
.color-hint-btn {
  padding: 6px 10px; font-family: var(--sans); font-size: 11px; font-weight: 600;
  border-radius: var(--radius-sm); cursor: pointer;
  border: 1.5px solid var(--navy); background: var(--navy); color: #fff;
}
.color-hint-btn:hover { background: var(--navy-2); border-color: var(--navy-2); }
.color-skip-link {
  font-family: var(--sans); font-size: 10.5px; color: var(--ink3);
  text-decoration: underline; cursor: pointer; background: none; border: none; padding: 0;
}
.color-choice-confirmed {
  font-family: var(--sans); font-size: 11px; color: var(--strong);
  line-height: 1.5;
}

.finalize-section-label {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.4px; color: var(--ink3);
  margin-bottom: 6px;
}
.finalize-option-group { display: flex; flex-direction: column; gap: 4px; }
.finalize-option {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  cursor: pointer; padding: 3px 0;
}
.finalize-option input { accent-color: var(--ink); cursor: pointer; }
.finalize-theme-select {
  width: 100%; padding: 7px 9px; font-family: var(--sans); font-size: 12.5px;
  color: var(--ink2); background: var(--card); border: 1.5px solid var(--line);
  border-radius: var(--radius-sm); cursor: pointer;
}
.finalize-theme-select:focus { outline: none; border-color: var(--ink); }
.finalize-coming-soon { opacity: 0.5; cursor: not-allowed; }
.coming-soon-tag {
  font-family: var(--sans); font-size: 9.5px; font-weight: 600;
  background: var(--bg2); border: 1px solid var(--line2);
  border-radius: 3px; padding: 1px 5px; color: var(--ink3);
  text-transform: uppercase; letter-spacing: 0.3px;
}
.finalize-progress-item {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--sans); font-size: 12px; color: var(--ink3);
  padding: 3px 0;
}
.fp-icon { font-size: 11px; width: 14px; }
.finalize-download-btn {
  display: block; width: 100%; padding: 9px 10px;
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  border-radius: var(--radius-sm); cursor: pointer; text-align: center;
  border: 1.5px solid var(--navy); background: var(--navy); color: #fff;
  transition: background 0.13s;
}
.finalize-download-btn:hover { background: var(--navy-2); border-color: var(--navy-2); }
.finalize-download-secondary {
  background: var(--bg2); border-color: var(--line2); color: var(--ink2);
}
.finalize-download-secondary:hover { background: var(--bg); border-color: var(--steel-tint); color: var(--ink); }

.finalize-btn {
  width: 100%; padding: 10px;
  background: var(--flame); color: #fff;
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  border: none; border-radius: var(--radius); cursor: pointer;
  box-shadow: 0 2px 8px -2px rgba(var(--flame-rgb),0.4);
  transition: background 0.14s;
}
.finalize-btn:hover { background: var(--flame-hover); }
.finalize-btn:disabled { background: var(--ink3); box-shadow: none; cursor: not-allowed; }
.plan-sidebar {
  width: 272px; flex-shrink: 0; overflow: hidden;
  background: var(--card); border-left: 1px solid var(--line2);
  box-shadow: -2px 0 10px -4px rgba(27,58,82,0.08);
  display: flex; flex-direction: column;
}
.sidebar-scroll {
  flex: 1; overflow-y: auto; padding: 14px 0 0;
}
.sidebar-scroll::-webkit-scrollbar { width: 4px; }
.sidebar-scroll::-webkit-scrollbar-thumb { background: var(--line2); border-radius: 2px; }

.sidebar-actions {
  flex-shrink: 0; padding: 12px 14px;
  border-top: 1px solid var(--line2);
  background: var(--card);
  display: flex; gap: 8px;
}

.sidebar-card {
  border-bottom: 1px solid var(--line);
}
.sidebar-card-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 16px; cursor: pointer; user-select: none;
  transition: background 0.12s;
}
.sidebar-card-header:hover { background: var(--bg); }
.sidebar-card-title {
  font-family: var(--sans); font-size: 11.5px; font-weight: 700;
  color: var(--ink2); letter-spacing: 0.3px; text-transform: uppercase;
}
.sidebar-card-chevron {
  font-size: 11px; color: var(--ink3); transition: transform 0.18s;
}
.sidebar-card-chevron.open { transform: rotate(180deg); }
.sidebar-card-body { padding: 0 16px 12px; }

/* Stage card states */
.sidebar-card.stage-active .sidebar-card-title { color: var(--ink); }
.sidebar-card.stage-active .sidebar-card-header { background: var(--offwhite); }
.sidebar-card.stage-complete .sidebar-card-title { color: var(--strong); }
.sidebar-card-check {
  flex-shrink: 0; width: 16px; font-size: 11px; font-weight: 700;
  color: var(--strong); text-align: center;
}

/* Stage panel items */
.stage-panel-item {
  display: flex; align-items: flex-start; gap: 7px;
  font-family: var(--sans); font-size: 12px; color: var(--ink2);
  padding: 3px 0; line-height: 1.4;
}
.stage-panel-icon { flex-shrink: 0; width: 14px; font-size: 11px; margin-top: 1px; }
.stage-panel-icon.done { color: var(--strong); }
.stage-panel-icon.partial { color: var(--mid-flame); }
.stage-panel-icon.todo { color: var(--ink3); }

/* Stage output fields */
.sidebar-field {
  font-family: var(--sans); font-size: 11.5px; padding: 3px 0;
  border-bottom: 1px solid var(--line); line-height: 1.45;
}
.sidebar-field:last-child { border-bottom: none; }
.sidebar-field-label {
  font-weight: 700; color: var(--ink3); font-size: 10.5px;
  text-transform: uppercase; letter-spacing: 0.3px; margin-bottom: 1px;
}
.sidebar-field-value { color: var(--ink2); }
.sidebar-empty { font-family: var(--sans); font-size: 11.5px; color: var(--ink3); font-style: italic; }
.mobile-sidebar-toggle { display: none; }
.sidebar-backdrop { display: none; }

@media (max-width: 780px) {
  /* The fixed-height advising box exists so the action cards below it never
     move on a desktop. On a phone that trade is backwards: a third of a
     short viewport is a lot of empty space to scroll past before reaching
     the cards. Let it grow from nothing again here, where the page scrolls
     as one column anyway and there is no stable layout to protect. */
  .landing-chat-messages { height: auto; max-height: 35vh; }
  .landing-chat-messages:empty { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }

  /* ── Topbar ── */
  .topbar { height: 62px; padding: 0 14px; }
  .topbar-left { gap: 14px; }
  .wordmark { font-size: 20px; }
  .topbar-right { gap: 6px !important; }
  .signin-btn, .topbar-access-btn { padding: 7px 13px; font-size: 12px; }
  .site-nav-tab { padding: 7px 12px; font-size: 12.5px; }
  .site-nav-tab--soon-badge { display: none; }

  /* ── Landing layout: stack into one scrolling column ── */
  .landing-body {
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
  }
  .left-panel {
    flex: none;
    width: 100%;
    overflow-y: visible;
    border-right: none;
    border-bottom: 1px solid var(--line2);
    padding: 24px 18px;
  }
  .right-panel {
    width: 100%;
    flex-shrink: 0;
    overflow-y: visible;
    border-left: none;
    box-shadow: none;
    padding: 24px 18px 36px;
  }

  /* The pre-planning chat panel reuses .left-panel but behaves like
     a chat app, not flowing marketing copy — keep it internally
     scrollable with a pinned composer instead of letting it stack. */
  #preChatPanel.left-panel {
    flex: none;
    height: 68vh;
    max-height: 68vh;
    overflow: hidden;
    padding: 0;
  }
  #preChatPanel .pre-chat-messages { overflow-y: auto; }

  .mobile-start-cta {
    display: flex; align-items: center; justify-content: center; gap: 6px;
    position: sticky; bottom: 14px; z-index: 20;
    margin: 22px auto 0; width: fit-content;
    padding: 11px 22px; background: var(--flame); color: #fff;
    font-family: var(--sans); font-size: 12.5px; font-weight: 600;
    border: none; border-radius: 999px; cursor: pointer;
    box-shadow: 0 6px 20px -4px rgba(var(--flame-rgb),0.55);
  }

  .landing-hero {
    padding: 32px 20px 28px;
    margin: -24px -18px 28px -18px;
  }
  .hero-headline { font-size: 30px; }
  .hero-sub { max-width: none; }

  .home-hero-title { font-size: 34px; }
  .home-band-title { font-size: 24px; }
  .home-band { margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; }
  .home-hero-grid { flex-direction: column; align-items: stretch; gap: 28px; }
  .home-hero-visual { width: 100%; max-width: 340px; margin: 0 auto; }
  .hiw-section, .skills-section, .compare-section-v2, .credibility-section-v2, .close-section { margin-top: 64px; }
  .hiw-row, .hiw-row--reverse { flex-direction: column; gap: 24px; padding: 32px 0; }
  .hiw-row--reverse .hiw-row-visual { justify-content: flex-start; }
  .hiw-title, .skills-title, .compare-title { font-size: 22px; }
  .hiw-step-head { font-size: 20px; }
  .credibility-section-v2 { flex-direction: column; align-items: flex-start; gap: 24px; }
  .credibility-badge-wrap { width: 120px; }
  .cta-card { flex-direction: column; align-items: flex-start; padding: 36px 28px; }
  .proof-line { font-size: 18px; }

  /* Collapse fixed multi-column grids to a single column */
  .access-form-row {
    grid-template-columns: 1fr;
  }

  .ttl-left { width: 60px; }
  .auth-gate-modal { padding: 28px 22px; }

  /* ── Chat view: move the plan sidebar into an off-canvas drawer ── */
  #autoSaveIndicator { display: none; }
  .mobile-sidebar-toggle {
    display: inline-flex; align-items: center; gap: 5px;
    font-family: var(--sans); font-size: 11.5px; font-weight: 500;
    padding: 5px 12px; border-radius: var(--radius-sm); cursor: pointer;
    border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.08);
    color: rgba(240,246,250,0.8);
  }
  .messages, .input-inner, .input-hint { max-width: none; }
  .chat-scroll { padding: 20px 0 12px; }
  .input-bar { padding: 12px 14px 14px; }

  .plan-sidebar {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 401;
    width: 86vw; max-width: 320px;
    transform: translateX(100%);
    transition: transform 0.25s ease;
    box-shadow: -8px 0 28px -6px rgba(0,0,0,0.35);
  }
  .plan-sidebar.mobile-open { transform: translateX(0); }
  .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 400;
    background: rgba(8,12,18,0.5); opacity: 0; pointer-events: none;
    transition: opacity 0.2s ease;
  }
  .sidebar-backdrop.show { opacity: 1; pointer-events: auto; }

  /* Prevent iOS Safari's auto-zoom-on-focus, which is triggered by any
     text input under 16px, by bumping form control text size on touch. */
  input[type="text"], input[type="number"], select, textarea,
  .access-input, .access-textarea {
    font-size: 16px;
  }
  /* The landing composer needs a selector that can actually beat its base
     rule, `.landing-chat-box textarea#advisingInput`. A bare id loses to
     that, so the mitigation above was sitting here not applying and the
     composer stayed at 15px — the size that makes iOS zoom on focus. */
  #userInput, #preChatInput { font-size: 16px; }
  .landing-chat-box textarea#advisingInput { font-size: 16px; }
}

/* ══════════════════════════════════════════════════════════════
   PHONE TOPBAR — the section tabs do not fit beside the controls
   ══════════════════════════════════════════════════════════════
   Measured at 375px before this block existed: the tab strip was squeezed
   to 130px of a 400px scroller, so "Implementation" was clipped mid-word
   and sat directly under the theme toggle, while Sign In wrapped to two
   lines. Nothing overlapped in the CSS sense; it read exactly as if it did.

   Row one needs the wordmark, the toggle and the auth button; there is no
   width left for a tab strip worth showing. So the tabs take a row of their
   own and scroll along it.

   display:contents dissolves .topbar-left so its children become items of
   .topbar itself, which is what lets the tab strip claim a full-width line.
   Only #siteNavTabs is given an `order` — everything else stays at the
   default 0 and keeps source order on row one. That matters because
   onboarding.html has no .topbar-left at all and student.html has one
   holding nothing but a bare, un-anchored wordmark; ordering the brand
   explicitly would have needed a selector that misses both.

   Kept in step with the same block in public/index.html. The two topbars
   are the same component in two files, as the rest of this file's topbar
   styles already are. */
@media (max-width: 560px) {
  .topbar {
    flex-wrap: wrap; height: auto; min-height: 62px;
    padding: 8px 14px; column-gap: 10px; row-gap: 4px;
  }
  .topbar-left { display: contents; }
  #siteNavTabs { order: 1; flex: 1 0 100%; }
  .signin-btn, .topbar-access-btn { padding: 6px 11px; font-size: 11.5px; }
  /* The signed-in email is the first thing to go: it is confirmation, not
     navigation, and at this width it costs the row more than it returns. */
  .topbar-user { display: none !important; }
}

@media (max-width: 480px) {
  .hero-headline { font-size: 25px; }
  .landing-hero { padding: 26px 16px 22px; margin: -24px -16px 24px -16px; }
  .left-panel, .right-panel { padding-left: 16px; padding-right: 16px; }
  .home-hero-title { font-size: 27px; }
  .home-band-title { font-size: 20px; }
  .plan-sidebar { width: 92vw; max-width: none; }
}

/* ══════════════════════════════════════════════════════════════
   app.forgepbl.com OVERRIDES
   Everything above this marker is carried over verbatim from the
   combined site's stylesheet so the tool views render identically.
   Home-page-only rules are intentionally left in place rather than
   pruned — dead CSS is cheap, a missed selector is not.
   ══════════════════════════════════════════════════════════════ */

/* Planning is the landing view here; there is no Home tab on the app. */
#planningView { display: flex; }

/* #chatView is display:none further up, from the days when every view lived
   in one document and enterChatView() flipped this one to flex. That function
   is gone — /workspace.html is a real page whose only job is to show the
   workspace — so nothing sets it inline any more and it has to be visible by
   default, exactly like #planningView above. Without this the workspace loads,
   fetches, and renders correctly into a container nobody can see. */
#chatView { display: flex; }
.topbar-user {
  font-family: var(--sans); font-size: 12.5px; font-weight: 500;
  color: rgba(255,255,255,0.7); max-width: 220px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 860px) {
  .topbar-marketing-link, .topbar-user { display: none !important; }
}

/* ══════════════════════════════════════════════════════════════
   IMPLEMENTATION — the teacher's run view (/implementation.html)
   Step 5, first slice. Read-only.

   Real rules rather than page-projects.js-style inline styles: this
   page renders four tabs' worth of repeated structure, and deadcss.js
   can only see what lives here. Tokens only, so Forge mode works
   without a second palette.
   ══════════════════════════════════════════════════════════════ */

.impl-wrap { flex: 1; min-height: 0; overflow-y: auto; padding: 28px 40px 60px; }

/* width: 100% is load-bearing and was missing for a long time.
   implementation.html puts BOTH classes on one element — <div class="app-view
   impl-wrap"> — so .app-view's display:flex applies here, and .impl-inner is a
   flex item rather than a plain block. In a column flex container the cross
   axis is horizontal, and an auto margin on the cross axis DEFEATS
   align-items: stretch: the item sizes to fit-content instead of filling.

   The effect was that every tab was a different width and the whole frame —
   title, tabs and all — slid sideways as you switched between them. Measured at
   a 1512px viewport: Today 860, Sessions 885, Deliverables 692, Students 474,
   with the title jumping 206px between the widest and narrowest.

   It also meant max-width was capping a box that never reached the cap, so
   --page-max had no visible effect at all. */
.impl-inner { max-width: var(--page-max); margin: 0 auto; width: 100%; }

/* Implementation runs wider than the rest of the app. Its three list tabs are
   card grids and row lists — layout, where more width is straightforwardly
   better — and at 1280px they were leaving a third of a large display empty.

   Rebinding --page-max on the wrapper rather than editing .impl-inner keeps the
   single-token discipline the :root comment asks for, and rebinding it HERE
   rather than at :root is what stops it reaching .full-page-inner, the tools
   picker and .stu-inner, none of which asked to move.

   Note what this deliberately does NOT widen: every ch cap below is on a form
   or a meta line, and they stay where they are. A 1400px-wide three-row
   textarea is worse, not better.

   920 rather than something larger, chosen by looking at it. The number moved
   twice before it settled: 1440 while the box was still shrink-wrapping, where
   it did nothing at all, then 1100 once width:100% made the cap bind, which
   read as too wide. 920 is close to what the tabs were ACTUALLY rendering
   before any of this — Today at 860, Sessions at 885 — so the visible change is
   that all four now agree, rather than that the page got bigger. */
.impl-wrap { --page-max: 920px; }

/* ── header ── */
.impl-header { margin-bottom: 20px; }
.impl-title {
  font-family: var(--serif); font-size: 24px; font-weight: 700;
  color: var(--ink); margin-bottom: 6px;
}
.impl-meta {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink3);
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
}
.impl-meta a { color: var(--ink2); text-decoration: underline; }
.impl-meta a:hover { color: var(--ink); }

/* A run's state, said once, in the header. Green for a live class, muted for
   one that has been closed out — the same distinction the projects list draws
   between a run you can walk into and one that is history. */
.impl-state {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  padding: 2px 8px; border-radius: 3px;
  background: var(--strong-bg); color: var(--strong);
}
.impl-state--closed { background: var(--bg2); color: var(--ink3); }

/* ── tab strip ── */
.impl-tabs {
  display: flex; gap: 2px; border-bottom: 1.5px solid var(--line);
  margin-bottom: 22px;
}
.impl-tab {
  background: none; border: none; cursor: pointer;
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  color: var(--ink3); padding: 9px 14px; margin-bottom: -1.5px;
  border-bottom: 2px solid transparent;
  transition: color 0.13s, border-color 0.13s;
}
.impl-tab:hover { color: var(--ink2); }
.impl-tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--flame); }

/* ── phase grouping, shared by Sessions and Deliverables ── */
.impl-phase { margin-bottom: 30px; }
/* padding-left is the lane the phase chevron sat in before the Sessions tab
   grew phase tabs (September 2026). The Deliverables tab keeps it for its
   left edge; the Sessions tab's groups reset it (.impl-phase--tabbed). */
.impl-phase-head { margin-bottom: 12px; position: relative; padding-left: 22px; }

/* The collapse chevron on a session row. A real button:
   keyboard-reachable, announced by aria-expanded, and labelled with what it
   opens rather than with "toggle". */
.impl-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  font-family: var(--sans); font-size: 15px; line-height: 1;
  color: var(--ink2); background: var(--bg); border: 1px solid var(--line2);
  border-radius: var(--radius-sm); padding: 0; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.impl-toggle:hover { color: var(--flame); border-color: var(--flame); background: var(--light-flame); }
.impl-toggle:focus-visible { outline: 2px solid var(--flame); outline-offset: 1px; }
.impl-toggle[aria-expanded="true"] { color: var(--ink); background: var(--bg2); }
/* The ▸/▾ character stays in the markup (and in the tests that read it) but
   is too small in Inter to see, so it is hidden and a drawn chevron takes its
   place: right when folded, down when open. The button's aria-label says which. */
.impl-toggle { font-size: 0; }
.impl-toggle::before {
  content: ""; width: 7px; height: 7px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: translateX(-1px) rotate(-45deg);
  transition: transform 0.15s ease;
}
.impl-toggle[aria-expanded="true"]::before { transform: translateY(-2px) rotate(45deg); }
/* On a session it is the first flex item in the row, so the chevrons line up
   down the left edge. Sized as a real target (28px) since September 2026: the
   11px glyph it replaced was the smallest control on the page and the one a
   teacher reaches for most. */
.impl-session > .impl-toggle { align-self: flex-start; }

/* ── phase tabs (Sessions list) and the calendar's phase key ──
   One colour per phase, set as --phase / --phase-tint on each element by
   implPaintPhaseColor(). Every strong shade holds 4.5:1 as text on white and as
   a fill under white text, so the colour can carry the number. */
.impl-phase-tabs {
  display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px;
}
.impl-phase-tab {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-width: 132px; max-width: 230px; flex: 0 1 auto;
  font-family: var(--sans); text-align: left; cursor: pointer;
  color: var(--ink); background: var(--bg);
  border: 1px solid var(--line); border-top: 4px solid var(--phase, var(--ink3));
  border-radius: var(--radius-sm); padding: 7px 11px 8px;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.impl-phase-tab:hover { background: var(--phase-tint, var(--bg2)); }
.impl-phase-tab:focus-visible { outline: 2px solid var(--phase, var(--flame)); outline-offset: 2px; }
.impl-phase-tab[aria-selected="true"],
.impl-phase-tab[aria-pressed="true"] {
  background: var(--phase-tint, var(--bg2));
  border-color: var(--phase, var(--ink)); border-top-color: var(--phase, var(--ink));
  box-shadow: inset 0 0 0 1px var(--phase, var(--ink));
}
.impl-phase-tab-num {
  display: flex; align-items: center; gap: 6px;
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--phase, var(--ink3));
}
.impl-phase-tab-title {
  font-size: 12.5px; font-weight: 600; line-height: 1.3; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.impl-phase-tab-count { font-size: 11px; color: var(--ink3); }
.impl-phase-tab-now {
  font-size: 9.5px; letter-spacing: 0.04em; color: #fff; background: var(--flame);
  border-radius: 999px; padding: 1px 6px;
}
/* On the calendar the key has an "All phases" button that is not a phase. */
.impl-cal-legend { margin-bottom: 14px; }
.impl-cal-legend .impl-phase-tab { min-width: 0; padding: 5px 10px 6px; }
.impl-cal-legend-all { justify-content: center; font-size: 12px; font-weight: 600; }

/* The phase on screen wears its colour down its left edge, matching its tab.
   The Deliverables tab's groups are untouched. */
.impl-phase--tabbed { border-left: 4px solid var(--phase, var(--line)); padding-left: 16px; }
.impl-phase--tabbed .impl-phase-head { padding-left: 0; }
.impl-phase--tabbed .impl-phase-eyebrow { color: var(--phase, var(--flame)); }

/* Collapsed. A session keeps its number, its
   state badge, its TYPE and its DESCRIPTION — the teacher's own line, which is
   what they scan a folded list for — and folds away the activity list and the
   student version.

   The description used to fold away too, leaving "What students see" as the
   only prose on a collapsed row. That was backwards twice over: a teacher
   scanning the Sessions tab is looking for their own plan, and the derived
   second-person line is the LAST thing that should stand in for it. The student
   version is a check on what the class reads, which is a thing you open a row to
   look at, not a summary of the row.

   Literal class names, never interpolated: tools/deadcss.js reads them out of
   the JS. */
.impl-session--collapsed .impl-activities,
.impl-session--collapsed .impl-attachments,
.impl-session--collapsed .impl-stu-line { display: none; }
.impl-phase-eyebrow {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--flame); margin-bottom: 4px;
}
.impl-phase-title {
  font-family: var(--serif); font-size: 17px; font-weight: 700;
  color: var(--ink); margin-bottom: 4px;
}
.impl-phase-sub {
  font-family: var(--sans); font-size: 12px; color: var(--ink3);
  line-height: 1.6; max-width: 78ch;
}

/* The two planning fields the eighth slice surfaced: sequential_dependency and
   content_knowledge. Set BELOW the purpose and quieter than it, because they are
   reference a teacher consults rather than the sentence they scan for. Rendered
   only when non-empty, so this block never appears as a pair of empty labels. */
.impl-phase-meta { margin-top: 7px; max-width: 78ch; }
.impl-phase-metaitem {
  font-family: var(--sans); font-size: 11.5px; line-height: 1.55;
  color: var(--ink3); margin-top: 3px;
}
.impl-phase-metakey {
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  font-size: 10px; color: var(--ink3); margin-right: 6px;
}
.impl-phase-metabody { color: var(--ink2); }

/* Edit and Delete on a phase header. Always visible for the reason
   .impl-phase-add gives: a header has no row to hover, and an empty phase has no
   rows at all — which is exactly the phase a teacher most wants to delete. */
/* Edit phase, Delete phase and Add a session share this row, so the three
   things a teacher can do to a phase read as one set. Wraps rather than
   overflows — the Sessions tab is the narrowest column in the app. */
.impl-phase-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.impl-phase-btn {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-phase-btn:hover { color: var(--flame); border-color: var(--flame); }

/* Add a phase — one control for the whole run, below the last phase group. The
   margin lives here rather than on the button, which now sits inside a flex row
   on the phase header where a top margin would knock it out of line. */
.impl-phase-addwrap { margin-top: 4px; padding-top: 6px; }
.impl-phase-form, .impl-deliv-form {
  margin-top: 10px; padding: 11px 13px; max-width: 72ch;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
/* A position, a phase and a date do not want to be as wide as a paragraph
   field — the same trim .impl-add-where makes for sessions. */
.impl-phase-where, .impl-deliv-phase, .impl-deliv-due { max-width: 34ch; }

/* ── the advance block (Today) ──
   Where the run is, and the control that moves it. Given real weight at the top
   of Today because "which session are we on" is the question this tab exists to
   answer — everything below it is context. */
.impl-advance {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg2); padding: 14px 16px; margin-bottom: 22px;
  /* No max-width. This is a CARD, not running text — the 82ch cap here made it
     705px against the chat pane's 860 and the notes' full width, so the three
     stacked blocks on Today each ended at a different place. They now share the
     column, which is what makes the tab read as one thing. */
}
.impl-advance-flash {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--flame); margin-bottom: 8px;
}
.impl-advance-label {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink3); margin-bottom: 6px;
}
.impl-advance-note {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6; max-width: 74ch; margin-top: 10px;
}
/* A link inside a note switches tabs by hash, the way the tab bar does. */
.impl-advance-note a { color: var(--flame); text-decoration: underline; }

/* Two cards side by side: what the class is doing now, and what it would move
   on to. Side by side rather than stacked because the teacher is comparing
   them — that is the whole decision the advance button represents. Collapses to
   one column when there isn't room to read two. */
.impl-advance-cards {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-items: stretch;
}
@media (max-width: 720px) {
  .impl-advance-cards { grid-template-columns: 1fr; }
}
.impl-card {
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg); padding: 12px 14px;
}
/* The running session carries the same left edge its row does on the Sessions
   tab, so the two views agree about which one is live. */
.impl-card--current { box-shadow: inset 3px 0 0 var(--flame); }
.impl-card--next { border-style: dashed; }
.impl-card-label {
  font-family: var(--sans); font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink3); margin-bottom: 5px;
}
.impl-card--current .impl-card-label { color: var(--flame); }
.impl-card-title {
  font-family: var(--serif); font-size: 17px; font-weight: 700;
  color: var(--ink); margin-bottom: 2px; line-height: 1.25;
}
.impl-card-where {
  font-family: var(--sans); font-size: 11px; color: var(--ink3);
  margin-bottom: 7px;
}
.impl-card-desc {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6;
}
.impl-card-desc--empty { color: var(--ink3); font-style: italic; }
/* What the session works toward, and what has to be run in it. Both read-only
   on Today — every edit lives on Sessions. */
.impl-card-serves {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink2);
  margin-top: 9px; padding: 5px 8px;
  background: var(--light-flame); border: 1px solid transparent; border-radius: 4px;
  align-self: flex-start; max-width: 100%; text-align: left; cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.impl-card-serves:hover { color: var(--ink); border-color: var(--flame); }
.impl-card-serves:focus-visible { outline: 2px solid var(--flame); outline-offset: 1px; }
/* The card Today just sent the teacher to, lit until the eye has found it. */
.impl-deliverable--lit { box-shadow: 0 0 0 2px var(--flame); transition: box-shadow 0.6s ease; }
.impl-card-activities { margin-top: 9px; display: flex; flex-direction: column; gap: 2px; }
/* Deliberately NOT .impl-activity: these look alike and obey different rules.
   A Sessions activity row carries controls and a hover state; this one is a
   label. Sharing the class would make "a closed run offers no activity
   controls" count these too — the same trap .impl-note-btn avoided by not
   being .impl-req-btn. */
.impl-card-activity {
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
  padding: 1px 0 1px 8px; border-left: 2px solid var(--mid-flame);
  line-height: 1.5;
}
.impl-card-activity-label { color: var(--flame); font-weight: 600; flex-shrink: 0; }
/* Clipped for .impl-activity-detail's reason: a rewritten goal-setting prompt is
   a paragraph, and printed in full it would bury the activities under it. */
.impl-card-activity-detail {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* margin-top:auto pins the buttons to the bottom so two cards of unequal
   description length still line their actions up. A row, since the advance
   now has Edit beside it. */
.impl-card-actions { margin-top: auto; padding-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }

/* Asks before an advance passes over untaught sessions. Nothing has been
   written when this appears — the server refused the first call. */
.impl-confirm {
  margin-top: 12px; padding: 11px 13px; max-width: 74ch;
  background: var(--bg2); border: 1px solid var(--flame);
  border-radius: var(--radius-sm);
}
.impl-confirm-title {
  font-family: var(--sans); font-size: 12.5px; font-weight: 700;
  color: var(--ink); margin-bottom: 4px;
}
.impl-confirm-body {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.55; margin-bottom: 10px;
}

/* Shown where an action failed, on Today and on any session row. Same red as
   the editor's save error, because it is the same kind of message. */
.impl-action-error {
  font-family: var(--sans); font-size: 12px; color: var(--flame);
  line-height: 1.5; margin-top: 8px; max-width: 72ch;
}

/* ── sessions ── */
/* Rows, not cards: a session list is scanned top to bottom for the one the
   teacher is looking for, and forty cards is a wall. Carries no date because
   session_plans has no date column — the plan gives order, not schedule. */
.impl-session {
  display: flex; gap: 14px; padding: 11px 2px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
/* The row-level error takes a line of its own under everything else. */
.impl-session > .impl-action-error { flex-basis: 100%; margin-left: 88px; }
.impl-session-num {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink3); flex-shrink: 0; width: 74px; padding-top: 1px;
}
.impl-session-side {
  flex-shrink: 0; align-self: flex-start;
  display: flex; gap: 4px; align-items: center;
}

/* The active session is the one thing on this list a teacher is looking for
   while teaching, so it gets a left edge rather than a colour wash — legible at
   a glance without turning the row into a card. */
.impl-session--active {
  box-shadow: inset 2px 0 0 var(--flame);
  padding-left: 10px;
}
/* A skipped session is still readable — it is part of the record — but it is
   not part of what is being taught, so it recedes. */
.impl-session--skipped .impl-session-view { opacity: 0.55; }

.impl-state {
  display: inline-block; margin-top: 4px;
  font-family: var(--sans); font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  border-radius: var(--radius-sm); padding: 2px 6px;
}
/* An upcoming session has no badge; implPaintSessionRow hides it, and the
   display above would otherwise draw an empty pill. */
.impl-state[hidden] { display: none; }
.impl-state--active  { color: #fff; background: var(--flame); }
.impl-state--closed  { color: var(--ink3); background: var(--bg2); border: 1px solid var(--line); }
.impl-state--skipped { color: var(--ink3); background: transparent; border: 1px dashed var(--line); }

/* The reason a session wasn't taught — the whole point of recording one. */
.impl-session-why {
  font-family: var(--sans); font-size: 12px; font-style: italic;
  color: var(--ink3); line-height: 1.55; margin-top: 6px;
}

/* Why a session that is still upcoming has no Start button: it sits before the
   one running now, and the way out is the Move control on its own row. */
.impl-session-note {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
  line-height: 1.5; margin-top: 6px; max-width: 68ch;
}

/* Skip asks before it writes. Sits inside the row it concerns. */
.impl-skip-form {
  margin-top: 10px; padding: 10px 12px; max-width: 68ch;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.impl-session-body { flex: 1; min-width: 0; }
.impl-session-type {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--ink); margin-bottom: 3px;
}
.impl-session-desc {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6; max-width: 82ch;
}

/* ── editing a session ──
   The Edit button stays quiet until the row is hovered or the button is
   focused: forty rows each shouting "Edit" is a list you can't read. Focus is
   its own trigger, not just hover, so the control is reachable by keyboard. */
.impl-session-edit {
  flex-shrink: 0; align-self: flex-start;
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none; border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 3px 9px; cursor: pointer;
  opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.impl-session:hover .impl-session-edit { opacity: 1; }
.impl-session-edit:focus-visible { opacity: 1; }
.impl-session-edit:hover { color: var(--flame); border-color: var(--line); }

/* The move control. Same quiet-until-wanted treatment as Edit and for the same
   reason — a dropdown on every one of forty rows is a list you can't read — but
   with its own rule rather than the .impl-session-edit one, because a <select>
   needs a border to be recognisable as a control at all. */
.impl-session-move {
  flex-shrink: 0; align-self: flex-start;
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 3px 6px; cursor: pointer; max-width: 15ch;
  opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.impl-session:hover .impl-session-move { opacity: 1; }
.impl-session-move:focus-visible { opacity: 1; }
.impl-session-move:hover { color: var(--flame); }

/* Add a session, on a phase header. Always visible — unlike the row controls it
   has nothing to hover, and an empty phase has no rows at all, which is exactly
   the case where a teacher most needs to find it.
   .impl-run-add adds a PHASE and shares every style with it while staying a
   separate name, because the two are indistinguishable by sight and must not be
   indistinguishable to a selector. */
.impl-phase-add, .impl-run-add {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-phase-add:hover, .impl-run-add:hover { color: var(--flame); border-color: var(--flame); }

/* The add form, opened under the phase header it belongs to. */
.impl-add-form {
  margin-top: 10px; padding: 11px 13px; max-width: 68ch;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
/* Both are .impl-field-input, which is width:100%. A position and a one-word
   type do not want to be as wide as a paragraph field. */
.impl-add-where, .impl-add-type { max-width: 34ch; }

.impl-session-form { max-width: 82ch; padding: 2px 0 4px; }
.impl-field { display: block; margin-bottom: 9px; }
.impl-field-label {
  display: block; font-family: var(--sans); font-size: 11px; font-weight: 600;
  letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--ink3); margin-bottom: 4px;
}
/* Says who else reads a field, or why it is worth filling in. Sentence case and
   not uppercase, because it is prose rather than a label. */
.impl-field-hint {
  display: block; font-family: var(--sans); font-size: 11.5px;
  color: var(--ink3); line-height: 1.5; margin: -1px 0 5px; max-width: 64ch;
}
.impl-field-input {
  width: 100%; box-sizing: border-box;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 7px 9px; line-height: 1.55;
}
.impl-field-input:focus {
  outline: none; border-color: var(--flame);
  box-shadow: 0 0 0 3px rgba(var(--flame-rgb), 0.14);
}
.impl-field-textarea { resize: vertical; min-height: 72px; }

.impl-form-actions { display: flex; gap: 7px; align-items: center; }
.impl-btn {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink2); background: var(--bg2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 6px 13px; cursor: pointer;
}
.impl-btn:hover { color: var(--ink); }
.impl-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.impl-btn--primary {
  color: #fff; background: var(--flame); border-color: var(--flame);
}
.impl-btn--primary:hover { color: #fff; background: var(--flame-hover); border-color: var(--flame-hover); }

/* Sits above the buttons, so the reason a save failed is next to the control
   that failed rather than somewhere up the page. */
.impl-session-error {
  font-family: var(--sans); font-size: 12px; color: var(--flame);
  line-height: 1.5; margin-bottom: 8px; max-width: 72ch;
}

/* The foot of the Sessions tab: Close this project, after the last phase.
   Set off by a rule above it because it is the one control on the tab that is
   not an edit, and it should not read as belonging to the last phase. Used to be
   .impl-advance-close, under the cards on Today, next to the advance button. */
.impl-sessions-close {
  margin-top: 28px; padding-top: 18px; border-top: 1px solid var(--line);
}
.impl-sessions-close-note {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6; max-width: 74ch; margin-bottom: 10px;
}
/* One-shot "Project closed." where the button was. Same voice as the flash on
   Today. */
.impl-sessions-flash {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--flame); margin-top: 28px;
}

/* Why the Edit buttons aren't there on a closed run. */
.impl-notice {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.55; background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 9px 12px; margin-bottom: 22px;
  max-width: 82ch;
}

/* ── the Sessions calendar (impl-calendar.js) ──
   A second view of the same sessions. The list stays the order; the calendar
   is the teacher's weeks. Chips, not cards: a month holds forty sessions and
   each one is a single scannable line. */
.impl-view-switch {
  display: inline-flex; margin-bottom: 18px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden;
}
.impl-view-switch-btn {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink2); background: var(--bg); border: 0; padding: 6px 14px; cursor: pointer;
}
.impl-view-switch-btn + .impl-view-switch-btn { border-left: 1px solid var(--line); }
.impl-view-switch-btn[aria-pressed="true"] { color: #fff; background: var(--ink); }

.impl-cal-head {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  justify-content: space-between; margin-bottom: 10px;
}
.impl-cal-nav, .impl-cal-tools { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.impl-cal-title {
  font-family: var(--heading-sans); font-size: 16px; font-weight: 700;
  color: var(--ink); min-width: 10ch; text-align: center;
}
.impl-cal-note {
  font-family: var(--sans); font-size: 12px; color: var(--ink3);
  line-height: 1.5; margin-bottom: 12px; max-width: 82ch;
}
.impl-cal-message {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--strong); margin-bottom: 10px; max-width: 82ch;
}
.impl-cal-message--error { color: var(--flame); }
.impl-cal-panel {
  padding: 12px 14px; margin-bottom: 14px; max-width: 74ch;
  background: var(--bg2); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.impl-cal-panel .impl-form-actions { flex-wrap: wrap; margin-bottom: 10px; }
.impl-cal-panel-label {
  font-family: var(--sans); font-size: 12px; font-weight: 600; color: var(--ink2);
  margin: 12px 0 6px;
}
.impl-cal-weekdays { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.impl-cal-weekday { font-family: var(--sans); font-size: 12.5px; color: var(--ink); cursor: pointer; }
.impl-cal-offlist { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.impl-cal-off {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--sans); font-size: 11.5px; color: var(--ink2);
  background: var(--bg); border: 1px solid var(--line); border-radius: 999px; padding: 2px 4px 2px 9px;
}
.impl-cal-off-remove {
  border: 0; background: none; color: var(--ink3); cursor: pointer;
  font-size: 14px; line-height: 1; padding: 0 4px;
}
.impl-cal-off-remove:hover { color: var(--flame); }

.impl-cal-drift, .impl-cal-disorder { margin-bottom: 14px; }
.impl-cal-drift {
  padding: 11px 13px; max-width: 82ch;
  background: var(--light-flame); border: 1px solid var(--mid-flame); border-radius: var(--radius-sm);
}
.impl-cal-drift-text { font-family: var(--sans); font-size: 12.5px; color: var(--ink); line-height: 1.55; }
.impl-cal-drift-row {
  display: flex; gap: 8px; align-items: center; margin-top: 8px;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink);
}
/* Past the global input[type="number"] rule, which would make it full width
   and serif. */
.impl-cal-drift-row input.impl-cal-drift-n { width: 64px; font-family: var(--sans); font-size: 12.5px; }
.impl-cal-disorder { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

.impl-cal-tray {
  margin-bottom: 14px; padding: 9px 11px;
  border: 1px dashed var(--line2); border-radius: var(--radius-sm);
}
.impl-cal-tray-label {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600; color: var(--ink3);
  margin-bottom: 6px;
}
.impl-cal-tray-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.impl-cal-tray-chips:empty { display: none; }
.impl-cal-tray-chips .impl-cal-chip { width: auto; }

.impl-cal-grid {
  display: grid; gap: 1px; background: var(--line);
  border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden;
}
.impl-cal-grid--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.impl-cal-grid--7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.impl-cal-dow {
  font-family: var(--sans); font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink3); background: var(--bg2); padding: 6px 8px;
}
.impl-cal-day {
  background: var(--bg); min-height: 96px; padding: 5px 6px 7px;
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.impl-cal-day--off {
  background: repeating-linear-gradient(135deg, var(--bg2) 0 6px, var(--bg) 6px 12px);
}
.impl-cal-day--other .impl-cal-daynum { color: var(--ink3); opacity: 0.6; }
.impl-cal-daynum { font-family: var(--sans); font-size: 11.5px; font-weight: 600; color: var(--ink2); }
.impl-cal-day--today .impl-cal-daynum {
  color: #fff; background: var(--flame); border-radius: 999px;
  align-self: flex-start; padding: 0 6px;
}
.impl-cal-drop { outline: 2px solid var(--flame); outline-offset: -2px; background: var(--light-flame); }

.impl-cal-chip {
  display: flex; gap: 5px; align-items: baseline; width: 100%; min-width: 0;
  font-family: var(--sans); font-size: 11.5px; text-align: left; line-height: 1.35;
  color: var(--ink); background: var(--phase-tint, var(--bg2)); border: 1px solid var(--line);
  border-left: 4px solid var(--phase, var(--ink3)); border-radius: 4px; padding: 3px 6px; cursor: pointer;
  transition: opacity 0.12s ease;
}
.impl-cal-chip:hover { border-color: var(--line2); }
.impl-cal-chip:focus-visible { outline: 2px solid var(--flame); outline-offset: 1px; }
.impl-cal-chip-num { font-weight: 700; flex: none; }
.impl-cal-chip-type { color: var(--ink2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Still to come: the phase's tint, draggable. Taught: white with a tick, so
   done and planned differ by more than colour. Open: the phase's own colour,
   solid. */
.impl-cal-chip--upcoming { cursor: grab; }
.impl-cal-chip--active { color: #fff; background: var(--phase, var(--flame)); border-color: var(--phase, var(--flame)); }
.impl-cal-chip--active .impl-cal-chip-type { color: #fff; }
.impl-cal-chip--closed { color: var(--ink2); background: var(--bg); }
.impl-cal-chip--closed .impl-cal-chip-num::before { content: "✓ "; color: var(--phase, var(--ink3)); }
/* The session open in the side panel. */
.impl-cal-chip--selected { outline: 2px solid var(--ink); outline-offset: 1px; }
.impl-cal-chip--deadline {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; color: var(--phase, var(--flame)); background: var(--bg);
  border: 1px solid var(--phase, var(--mid-flame)); border-radius: 999px; padding: 2px 8px;
}
/* Another phase than the one picked out on the key. */
.impl-cal-chip--dim { opacity: 0.25; }
.impl-cal-chip--dim:hover, .impl-cal-chip--dim:focus-visible { opacity: 0.8; }
.impl-cal-chip--dragging { opacity: 0.4; }
.impl-cal-confirm { margin: 0 0 14px; }

/* The side panel: the list's own session row, moved into the grid's row so it
   starts level with the month. Beside the grid from 1100px, and sticky there so
   it stays in view while a teacher scrolls the month; above the grid on
   anything narrower. */
.impl-calendar--panel .impl-cal-row { display: flex; flex-direction: column; gap: 18px; }
.impl-calendar--panel .impl-cal-side { order: -1; }
@media (min-width: 1100px) {
  .impl-calendar--panel .impl-cal-row {
    display: grid; grid-template-columns: minmax(0, 1fr) 380px; align-items: start; gap: 20px;
  }
  .impl-calendar--panel .impl-cal-side {
    order: 0; position: sticky; top: 12px;
    max-height: calc(100vh - 24px); overflow-y: auto;
  }
}
.impl-cal-side {
  background: var(--bg); border: 1px solid var(--line);
  border-top: 4px solid var(--phase, var(--ink3));
  border-radius: var(--radius-sm); padding: 12px 14px 14px; box-shadow: var(--shadow-sm);
}
.impl-cal-side-head {
  display: flex; gap: 10px; align-items: flex-start; justify-content: space-between;
  padding-bottom: 8px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
}
.impl-cal-side-title {
  display: flex; flex-direction: column; gap: 2px;
  font-family: var(--heading-sans); font-size: 15px; font-weight: 700; color: var(--ink);
}
.impl-cal-side-phase {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--phase, var(--ink3));
}
.impl-cal-side-actions { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }
.impl-cal-side-close {
  width: 28px; height: 28px; font-size: 18px; line-height: 1; cursor: pointer;
  color: var(--ink2); background: var(--bg); border: 1px solid var(--line2); border-radius: var(--radius-sm);
}
.impl-cal-side-close:hover { color: var(--flame); border-color: var(--flame); }
/* The row stacks in the panel, as it does on a phone, and needs no chevron:
   the panel always shows it open. */
.impl-cal-side .impl-session { flex-direction: column; gap: 6px; border-bottom: 0; padding: 8px 0 0; }
.impl-cal-side .impl-session > .impl-toggle { display: none; }
.impl-cal-side .impl-session-num { width: auto; display: flex; gap: 8px; align-items: center; }
/* The panel's head already names the session; the row keeps only its state. */
.impl-cal-side .impl-session-numline { display: none; }
.impl-cal-side .impl-session > .impl-action-error { margin-left: 0; }
.impl-cal-side .impl-session--active { box-shadow: none; padding-left: 0; }

/* "Move sessions…": earlier, later or fill, from a session on, with a
   preview that says exactly what will happen before anything is written. */
.impl-cal-move-modes { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 6px 0 10px; }
.impl-cal-move-mode { font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: var(--ink); cursor: pointer; }
.impl-cal-move-fields { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.impl-cal-move-field {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
}
.impl-cal-move-field[hidden] { display: none; }
.impl-cal-move-from { width: auto; max-width: 100%; }
.impl-cal-move-field input.impl-cal-move-by { width: 64px; font-family: var(--sans); font-size: 12.5px; }
.impl-cal-move-start { width: auto; }
.impl-cal-move-preview {
  font-family: var(--sans); font-size: 12.5px; line-height: 1.55; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 8px 10px; margin-bottom: 10px;
}
.impl-cal-move-preview:empty { display: none; }
.impl-cal-move-preview--refused { color: var(--flame); border-color: var(--mid-flame); }
/* A question with more than one answer wraps its buttons rather than
   squeezing them. */
.impl-cal-choices { flex-wrap: wrap; }

/* The auto-start switch under the calendar's head. */
.impl-cal-auto { margin-bottom: 12px; max-width: 82ch; }
.impl-cal-auto-label {
  display: flex; gap: 8px; align-items: center; cursor: pointer;
  font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: var(--ink);
}
.impl-cal-auto-note { font-family: var(--sans); font-size: 12px; color: var(--ink3); margin: 3px 0 0 24px; line-height: 1.5; }

/* What auto-start will do with Today's up-next session. */
.impl-card-auto {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600; color: var(--ink2);
  margin-bottom: 7px;
}

/* "Planned for" on a list row: the keyboard and phone path to the calendar. */
.impl-planned {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 8px;
  font-family: var(--sans); font-size: 12px; color: var(--ink2);
}
.impl-planned-label { font-weight: 600; }
.impl-planned-input { width: auto; padding: 4px 8px; font-size: 12px; }
.impl-planned .impl-action-error { flex-basis: 100%; margin-top: 0; }
.impl-planned .impl-confirm { flex-basis: 100%; margin-top: 4px; }

@media (max-width: 640px) {
  .impl-cal-day { min-height: 64px; padding: 4px 3px; }
  .impl-cal-dow { padding: 5px 3px; font-size: 10px; }
  .impl-cal-chip { padding: 2px 3px; font-size: 10.5px; border-left-width: 3px; }
  .impl-cal-chip--closed .impl-cal-chip-num::before { content: none; }
  .impl-phase-tab { min-width: 0; flex: 1 1 140px; }
  .impl-cal-chip-type { display: none; }
  .impl-cal-chip--deadline { padding: 1px 4px; font-size: 10px; }
}

/* ── the shared calendar (/plan-view.html, page-plan-view.js) ──
   A colleague's read-only copy of the Sessions calendar. The grid, chips,
   legend and side panel are the calendar's own classes above; these are only
   what the teacher's calendar has no counterpart for: a header that says whose
   plan this is, the session's detail written out in full rather than as an
   editable row, and the phases and deliverables below the month. */
.pv-header { margin-bottom: 18px; }
.pv-eyebrow {
  font-family: var(--sans); font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--flame); margin-bottom: 4px;
}
.pv-title {
  font-family: var(--serif); font-size: 24px; font-weight: 700;
  color: var(--ink); margin: 0 0 4px;
}
.pv-meta, .pv-sharedby { font-family: var(--sans); font-size: 12.5px; color: var(--ink3); }
.pv-sharedby { margin-top: 6px; color: var(--ink2); font-weight: 600; }
.pv-desc { font-family: var(--sans); font-size: 14px; color: var(--ink2); line-height: 1.55; margin: 8px 0 0; max-width: 72ch; }
.pv-header .impl-notice { margin: 12px 0 0; }

.pv-routines {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  background: var(--bg2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 8px 12px; margin-bottom: 18px; max-width: 82ch;
}
.pv-routines-summary { font-weight: 600; color: var(--ink); cursor: pointer; }
.pv-routine-text { margin: 2px 0 8px; line-height: 1.55; white-space: pre-line; }

.pv-calendar { margin-bottom: 30px; }
.pv-chip { cursor: pointer; }
/* A deadline here is a link down to its deliverable, not a drag handle. */
.pv-calendar a.impl-cal-chip { text-decoration: none; }
.pv-when {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600; color: var(--ink3);
  margin: 6px 0 4px;
}
.pv-type { font-family: var(--sans); font-size: 13px; font-weight: 700; color: var(--phase, var(--ink)); }
.pv-body {
  font-family: var(--sans); font-size: 13px; color: var(--ink); line-height: 1.6;
  margin: 4px 0 8px; max-width: 72ch; white-space: pre-line;
}
.pv-serves { font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: var(--ink2); margin-bottom: 6px; }
.pv-detail-label {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink3); margin: 12px 0 4px;
}
.pv-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.pv-activity {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2); line-height: 1.55;
  padding: 2px 0 2px 10px; border-left: 2px solid var(--mid-flame);
}
.pv-activity-label { color: var(--flame); font-weight: 600; margin-right: 6px; }
.pv-activity-detail { white-space: pre-line; }
.pv-activity-who { display: inline-block; margin-left: 6px; font-size: 11px; font-weight: 600; color: var(--ink3); }
.pv-side .proj-attach { margin-top: 12px; }

.pv-phases { margin-bottom: 30px; }
.pv-section-title { font-family: var(--heading-sans); font-size: 17px; font-weight: 700; color: var(--ink); margin: 0 0 12px; }
.pv-phase {
  border-left: 4px solid var(--phase, var(--ink3)); padding: 4px 0 4px 14px; margin-bottom: 22px;
}
.pv-phase-title { font-family: var(--heading-sans); font-size: 15px; font-weight: 700; color: var(--ink); margin: 2px 0 4px; }
.pv-deliverable {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 12px; margin: 10px 0; max-width: 82ch;
}
.pv-deliverable:target { outline: 2px solid var(--phase, var(--flame)); outline-offset: 2px; }
.pv-deliverable-title { font-family: var(--sans); font-size: 13.5px; font-weight: 700; color: var(--ink); }
.pv-reqs { font-family: var(--sans); font-size: 12.5px; color: var(--ink2); line-height: 1.55; margin: 4px 0 0; padding-left: 18px; }

.pv-cta {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between;
  border-top: 1px solid var(--line); padding-top: 18px;
}
.pv-cta-text { font-family: var(--sans); font-size: 13px; color: var(--ink2); max-width: 60ch; line-height: 1.55; }
.pv-cta .impl-btn { text-decoration: none; }

/* The teacher's side: "Share with colleagues…" (impl-calendar.js). */
.impl-cal-share-list { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.impl-cal-share-item {
  display: flex; gap: 8px; align-items: center; justify-content: space-between; max-width: 460px;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 4px 4px 4px 10px;
}
.impl-cal-share-email { overflow-wrap: anywhere; }
.impl-cal-share-empty { font-family: var(--sans); font-size: 12.5px; color: var(--ink3); margin-bottom: 10px; }
.impl-cal-share-input { max-width: 300px; }

/* ── activities (Entity 20) ──
   What a session asks students to do, rendered as lines inside the session row.
   Not cards: this is two to five items per session across a list of forty, and a
   box around each one would turn something a teacher scans into something they
   have to read. The flame left border is inherited from the mini-lesson line
   this replaced — a mini-lesson is now one of the five activity types, so the
   treatment that marked it out belongs to all of them. */
.impl-activities { margin-top: 7px; }

.impl-activity {
  display: flex; align-items: baseline; gap: 7px;
  font-family: var(--sans); font-size: 12px; color: var(--ink2);
  padding: 2px 0 2px 10px; border-left: 2px solid var(--mid-flame);
  line-height: 1.55;
}
.impl-activity-label { color: var(--flame); font-weight: 600; flex-shrink: 0; }

/* One line, clipped. The seeded goal-setting prompt is a full paragraph of the
   plan's own text — printed in full it would bury the four other activities on
   the session. min-width:0 is what lets a flex child shrink below its content
   width; without it the ellipsis never appears and the row just grows. */
.impl-activity-detail {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Only ever drawn when targeting is NOT whole-class, so its presence is the
   information — a chip on every line would be a chip on none of them. */
.impl-activity-who {
  flex-shrink: 0;
  font-size: 10.5px; font-weight: 600; color: var(--ink3);
  background: var(--bg2); border-radius: var(--radius-sm); padding: 1px 6px;
}

.impl-activity-side { flex-shrink: 0; display: flex; gap: 2px; }

/* Quiet until wanted, same as .impl-session-edit and for the same reason, with
   focus as its own trigger so the arrows are reachable by keyboard. */
.impl-activity-btn {
  font-family: var(--sans); font-size: 12px; line-height: 1;
  color: var(--ink3); background: none; border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 2px 6px; cursor: pointer;
  opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.impl-session:hover .impl-activity-btn { opacity: 1; }
.impl-activity-btn:focus-visible { opacity: 1; }
.impl-activity-btn:hover { color: var(--flame); border-color: var(--line); }

/* The one control on a session row that does NOT hide until hover. Edit and
   Delete are alternative ways to do something; this is the only way, and a
   teacher who does not know to hover would never find it. */
.impl-activity-add {
  margin-top: 5px;
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 3px 9px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-activity-add:hover { color: var(--flame); border-color: var(--flame); }

/* ── a session's materials (impl-attachments.js) ──
   Under the activities, in the same quiet register: lines, not cards. The
   status line says who can see them, which is the question a teacher has
   before attaching anything. */
.impl-attachments { margin-top: 10px; }
.impl-attachments:empty { display: none; }
.impl-attach-head {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  font-family: var(--sans); margin-bottom: 4px;
}
.impl-attach-label {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink3);
}
.impl-attach-status { font-size: 11.5px; color: var(--ink3); }
.impl-attach-list { list-style: none; margin: 0; padding: 0; }
.impl-attach-row {
  display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap;
  font-family: var(--sans); font-size: 12px; color: var(--ink2);
  padding: 2px 0 2px 10px; border-left: 2px solid var(--line2); line-height: 1.55;
}
.impl-attach-kind {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.06em; color: var(--flame); flex-shrink: 0;
}
.impl-attach-title {
  flex: 1; min-width: 0; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
a.impl-attach-title:hover { color: var(--flame); }
.impl-attach-meta { font-size: 11px; color: var(--ink3); flex-shrink: 0; }
.impl-attach-row--editing .impl-attach-title,
.impl-attach-row--editing .impl-attach-meta { display: none; }
.impl-attach-rename { display: flex; gap: 6px; align-items: center; flex: 1 1 100%; margin: 3px 0; }
.impl-attach-rename-input { max-width: 48ch; }
.impl-attach-remove--armed { opacity: 1; color: var(--flame); border-color: var(--flame); }
.impl-attach-controls { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }
.impl-attach-add:disabled { opacity: 0.5; cursor: not-allowed; }
.impl-attach-busy { font-family: var(--sans); font-size: 11.5px; color: var(--ink2); }
.impl-attach-hint { margin-top: 5px; }

/* The add form, opened inside the session row it belongs to. Same treatment as
   .impl-add-form on a phase header. */
.impl-activity-form {
  margin-top: 9px; padding: 11px 13px; max-width: 68ch;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.impl-activity-type, .impl-activity-target, .impl-activity-timing,
.impl-activity-lesson, .impl-activity-deliverable { max-width: 40ch; }

/* Which activity is being edited. The editor has no type picker — an edit
   cannot change what kind of activity something is — so this says what the form
   is for, in the place the picker would otherwise sit. */
.impl-activity-editing {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--flame); margin-bottom: 9px;
}

/* The roster, revealed only when targeting is "only some students". Scrolls
   rather than growing: eighteen names is already taller than the row it opened
   in, and a class could be thirty. */
.impl-activity-roster {
  max-height: 168px; overflow-y: auto;
  margin: -4px 0 9px; padding: 7px 9px;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.impl-roster-item {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink);
  padding: 2px 0; cursor: pointer;
}

/* The phase's mini-lesson inventory. Replaced .impl-jit in the seventh slice:
   that block listed ONLY lessons with no day, which meant a lesson already on a
   session was editable nowhere and its warranted_condition was readable nowhere.
   This lists every lesson in the phase and marks where each one is. */
.impl-lessons {
  margin-top: 14px; padding: 12px 14px;
  background: var(--bg2); border-radius: var(--radius);
}
.impl-lessons-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 8px;
}
.impl-lessons-title {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink3);
}
.impl-lesson {
  font-family: var(--sans); font-size: 12px; color: var(--ink2);
  line-height: 1.55; padding: 4px 0;
}
.impl-lesson + .impl-lesson { border-top: 1px solid var(--line); }
.impl-lesson-main {
  display: flex; align-items: baseline; gap: 7px;
}

/* The topic takes the slack, so the placement chip and the controls keep their
   right-hand position on every row regardless of how long a topic is.
   min-width:0 is what lets a flex child shrink below its content width. */
.impl-lesson-topic {
  flex: 1; min-width: 0; font-weight: 600; color: var(--ink2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Its own line, wrapping freely. Sharing the topic's line was tried and the
   note ellipsised to nothing on exactly the rows that needed it — which would
   have shipped a change whose whole purpose was making this sentence readable
   for a lesson that is on a day. */
.impl-lesson-when {
  color: var(--ink3); margin-top: 1px; max-width: 78ch;
}

/* Written out rather than built as `impl-lesson-where--${state}`: tools/deadcss.js
   searches the JS for literal class names, so an interpolated one reads as a dead
   rule. Same reason IMPL_STATE_CLASS is a map. */
.impl-lesson-where {
  flex-shrink: 0;
  font-size: 10.5px; font-weight: 600;
  border-radius: var(--radius-sm); padding: 1px 6px;
}
.impl-lesson-where--placed { background: var(--light-flame); color: var(--flame); }
.impl-lesson-where--spare { background: var(--card); color: var(--ink3); }

.impl-lesson-side { flex-shrink: 0; display: flex; gap: 2px; }

/* Quiet until wanted, matching .impl-activity-btn, with focus as its own trigger
   so every control stays reachable by keyboard. */
.impl-lesson-btn {
  font-family: var(--sans); font-size: 11.5px; line-height: 1;
  color: var(--ink3); background: none; border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 2px 6px; cursor: pointer;
  opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.impl-lessons:hover .impl-lesson-btn { opacity: 1; }
.impl-lesson-btn:focus-visible { opacity: 1; }
.impl-lesson-btn:hover { color: var(--flame); border-color: var(--line); }

/* Does NOT hide until hover, for the reason .impl-activity-add gives: Edit and
   Delete are alternative ways to do something, this is the only way. */
.impl-lesson-add {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none; border: 1px dashed var(--line);
  border-radius: var(--radius-sm); padding: 3px 8px; cursor: pointer;
  flex-shrink: 0;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-lesson-add:hover { color: var(--flame); border-color: var(--mid-flame); }

.impl-lesson-form { margin: 8px 0 4px; max-width: 68ch; }
.impl-lesson-error {
  font-family: var(--sans); font-size: 12px; color: var(--flame);
  margin-bottom: 7px; line-height: 1.5;
}

/* ── deliverables ── */
.impl-deliverable {
  background: var(--card); border: 1.5px solid var(--line);
  border-radius: var(--radius); padding: 16px 18px; margin-bottom: 12px;
}
.impl-deliverable-head {
  display: flex; align-items: baseline; gap: 9px;
  flex-wrap: wrap; margin-bottom: 7px;
  /* Space held for the controls pinned below, so a long title never runs under
     them. */
  position: relative; padding-right: 74px;
}
/* Pinned top-right rather than left in the wrapping flow with margin-left:auto.
   That was the first attempt and the render sample killed it: this head wraps,
   and on a narrow column the title took the whole first line and pushed Edit and
   × onto a line of their own, floating mid-card with nothing to anchor them to.
   Every DOM assertion passed straight through it — the buttons existed, in the
   right container, with the right labels. Absolute positioning holds them
   against the title at every width. */
.impl-deliverable-side {
  position: absolute; top: 0; right: 0;
  display: inline-flex; gap: 2px;
}
.impl-deliverable-title {
  font-family: var(--sans); font-size: 14px; font-weight: 700; color: var(--ink);
}
.impl-deliverable-desc {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6; max-width: 82ch; margin-bottom: 12px;
}
.impl-req-head, .impl-open-head {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink3); margin-bottom: 7px;
}
/* Numbered by the <ol>, NOT printed from sequence_order. That is what makes
   renumbering safe on this table: the key orders the rows and nothing reads it
   as a label, unlike session_plans.session_number, which a teacher and a student
   both say out loud and which therefore freezes when a session opens. */
.impl-reqs { margin: 0 0 10px; padding-left: 20px; }
.impl-req {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6; margin-bottom: 5px; max-width: 80ch;
}
.impl-req-text { display: inline; }
.impl-req-side {
  display: inline-flex; gap: 2px; margin-left: 6px;
  vertical-align: baseline;
}

/* Quiet until wanted, matching .impl-activity-btn, with focus as its own
   trigger so the arrows stay reachable by keyboard. The hover target is the
   whole deliverable card rather than the single row: a list of one-line
   requirements is short enough that per-row reveal reads as flicker. */
.impl-req-btn, .impl-note-btn {
  font-family: var(--sans); font-size: 11.5px; line-height: 1;
  color: var(--ink3); background: none; border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 2px 6px; cursor: pointer;
  opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.impl-deliverable:hover .impl-req-btn { opacity: 1; }
.impl-req-btn:focus-visible, .impl-note-btn:focus-visible { opacity: 1; }
.impl-req-btn:hover, .impl-note-btn:hover { color: var(--flame); border-color: var(--line); }

/* Does NOT hide until hover, for the reason .impl-activity-add gives: Edit and
   Delete are alternative ways to do something, this is the only way, and a
   teacher who does not know to hover would never find it. */
.impl-req-add {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink3); background: none; border: 1px dashed var(--line);
  border-radius: var(--radius-sm); padding: 4px 9px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-req-add:hover { color: var(--flame); border-color: var(--mid-flame); }

/* Add a deliverable, at the foot of a phase group. Visible always, for
   .impl-req-add's reason: Edit and × are alternative routes to changing
   something that exists, this is the only route to something that does not. */
.impl-deliv-add {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink3); background: none; border: 1px dashed var(--line);
  border-radius: var(--radius-sm); padding: 5px 11px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-deliv-add:hover { color: var(--flame); border-color: var(--mid-flame); }

.impl-req-form { margin: 8px 0 10px; max-width: 68ch; }
.impl-req-error {
  font-family: var(--sans); font-size: 12px; color: var(--flame);
  margin-bottom: 7px; line-height: 1.5;
}
.impl-open {
  border-top: 1px solid var(--line); padding-top: 11px; margin-top: 2px;
}
.impl-open-body {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6; max-width: 82ch;
}

/* ── chips ── */
.impl-chip {
  display: inline-block; font-family: var(--sans); font-size: 10.5px;
  font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em;
  padding: 2px 7px; border-radius: 3px;
  background: var(--bg2); color: var(--ink3);
}
.impl-chip--final { background: var(--light-flame); color: var(--flame); }
/* A deadline reads as information rather than as a category, so it takes a
   border instead of a fill — distinct from the skill chips beside it without
   competing with the final-deliverable flag for emphasis. */
.impl-chip--due { background: none; border: 1px solid var(--line); color: var(--ink2); }
.impl-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 12px; }

/* ── Today ──
   .impl-summary / -line / -counts removed in the ninth slice along with the
   card they styled: a one-sentence description and three static counts are not
   things that need a decision today, and the counts are actionable on the tabs
   that draw the lists. */
.impl-section-head {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink3); margin: 0 0 12px;
}

/* A flagged decision from the Decision Confirmation screen. Keeps the flame
   accent it carried there, so a teacher who flagged it recognises it — this is
   a reminder they left themselves, not a warning the platform is issuing. */
/* Project Notes. Replaces .impl-flag* — the same cards, now editable rows
   rather than a derivation of activation_decisions. The flame left border is
   kept: it is what makes this block read as "things wanting your attention"
   rather than as more structure. */
.impl-note {
  background: var(--card); border: 1.5px solid var(--line);
  border-left: 2.5px solid var(--flame); border-radius: var(--radius);
  padding: 13px 16px; margin-bottom: 10px;
}
/* Holds the provenance chip and the controls. min-height keeps the row from
   collapsing on a teacher-written note, which has no chip — otherwise Edit and
   × would sit at different heights on adjacent cards. */
.impl-note-head {
  display: flex; align-items: center; gap: 9px;
  min-height: 22px; margin-bottom: 6px;
}
.impl-note-side { display: inline-flex; gap: 2px; margin-left: auto; }
/* pre-wrap because a seeded note carries the blank line the seeder put between
   the flagged headline and the teacher's comment on it. Collapsing that would
   run two different sentences together. */
.impl-note-body {
  font-family: var(--sans); font-size: 13px; color: var(--ink);
  line-height: 1.6; max-width: 82ch; white-space: pre-wrap;
}
.impl-note-where {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
  margin-top: 6px;
}
.impl-note-form { margin-top: 4px; max-width: 72ch; }

/* What a session builds toward, on the type line. Quiet: it is context for the
   day, not the day's identity, and it appears on at most the sessions that
   actually name a deliverable. */
.impl-serves {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); margin-left: 8px;
  background: none; border: none; padding: 0; cursor: pointer;
  transition: color 0.12s ease;
}
.impl-serves:hover { color: var(--flame); }
.impl-serves:focus-visible { outline: 2px solid var(--flame); outline-offset: 2px; border-radius: 3px; }
/* The reverse view on a deliverable card — which sessions work on it, derived
   from the sessions rather than stored here. */
.impl-serves-line {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
  margin-top: 2px; margin-bottom: 10px;
}
.impl-chip--flagged { background: var(--light-flame); color: var(--flame); }

/* Quiet until the card is hovered, matching .impl-req-btn, with focus as its
   own trigger so both stay keyboard-reachable. */
.impl-note:hover .impl-note-btn { opacity: 1; }

/* Does NOT hide until hover, for .impl-req-add's reason: Edit and × are
   alternative ways to change a note that exists, this is the only way to make
   one. */
.impl-note-add {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink3); background: none; border: 1px dashed var(--line);
  border-radius: var(--radius-sm); padding: 5px 11px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-note-add:hover { color: var(--flame); border-color: var(--mid-flame); }

/* ── roster ──
   A list, not cards: the roster's job is to be scanned for one name, and a
   grid of eighteen cards is slower to scan than eighteen lines. Names come
   from identity_map and vary far more than the rest of this page's text —
   diacritics, apostrophes, hyphens, particles, five characters to twenty-five —
   so the name column wraps rather than truncating. A truncated name is a
   wrong name. */
.impl-roster-head {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
  margin-bottom: 10px;
}
/* Capped, unlike the cards above it, and for a reason particular to this row:
   .impl-student is space-between, so the name goes hard left and the enrolment
   date hard right. Across the full column that put ~900px of nothing between a
   student and their own date, which is a long way for an eye to travel to pair
   two facts. 68ch keeps the pair readable as a pair. Matches the form measures
   used elsewhere on this page. */
.impl-roster { margin-bottom: 20px; max-width: 68ch; }
.impl-student {
  display: flex; gap: 14px; align-items: baseline; justify-content: space-between;
  padding: 9px 2px; border-bottom: 1px solid var(--line);
}
.impl-student-name {
  font-family: var(--sans); font-size: 13px; color: var(--ink);
  min-width: 0; overflow-wrap: anywhere;
}
.impl-student-name--missing { color: var(--ink3); font-style: italic; }
/* A flex row as of 5.0d: it carries the enrolled date, sometimes a sign-in
   chip, and the Remove control, and those have to sit on one line without the
   name column losing its wrap. */
.impl-student-meta {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
  flex-shrink: 0; display: flex; gap: 10px; align-items: baseline;
}


/* ── the roster's controls, 5.0d ──
   Every name below is a literal and none is shared with a control that has
   different rules. Sharing one has produced two bugs on this page already
   (impl-phase-add, impl-req-btn), and tools/deadcss.js matches literal strings,
   so a composed name reads as dead. */

/* What the last write did, painted once at the top of the tab and then cleared.
   Its own thing rather than .impl-advance-flash, which lives on Today: a
   message about the roster has to appear on the tab the teacher is looking at. */
.impl-roster-note {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.55; max-width: 72ch;
  background: var(--bg2); border-left: 2px solid var(--flame);
  padding: 8px 11px; margin-bottom: 14px; border-radius: var(--radius-sm);
}

/* Says something needs attention, and appears on nothing else. A student whose
   netlify_user_id is a seed sentinel cannot sign in, and until now the page
   drew them identically to one who can. Flame rather than grey: it is not
   metadata, it is the reason a student says the site is broken. */
/* Both colours here are MEASURED against the tint behind them, not picked by
   eye, and neither is the token you would reach for first:

     --flame on --light-flame  = 3.55:1 (tempered)   fails AA
     --flame on --light-flame  = 4.45:1 (forge)      fails AA
     --flame-hover, tempered   = 4.44:1              still fails

   10.5px is small text, so 4.5:1 is the bar. A warning label that fails it is a
   warning label a teacher's eye slides off — and this chip is the only thing on
   the roster whose entire job is to be noticed. The tempered value is --flame
   darkened to the same hue until it passes; forge uses --mid-flame.

   Found by generating the render sample and measuring it. jsdom resolves
   computed colour wrongly, so every DOM assertion passed with the failing pair
   — the same reason the sample caught a specificity bug in 5.0b. */
.impl-signin-chip {
  font-family: var(--sans); font-size: 10.5px; font-weight: 600;
  color: #8F3E28;                 /* 5.76:1 on --light-flame */
  background: var(--light-flame);
  border-radius: var(--radius-sm); padding: 2px 7px;
  white-space: nowrap;
}
[data-theme="forge"] .impl-signin-chip { color: var(--mid-flame); }   /* 6.92:1 */
.impl-student-when { white-space: nowrap; }

/* Quiet until the row is hovered, matching .impl-req-btn, with focus as its
   own way in for anyone not using a mouse. Removing a student is not a thing
   to invite by making the control loud. */
.impl-student-remove, .impl-student-restore {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 2px 8px; cursor: pointer; opacity: 0;
  transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.impl-student:hover .impl-student-remove,
.impl-student:hover .impl-student-restore { opacity: 1; }
.impl-student-remove:focus-visible, .impl-student-restore:focus-visible { opacity: 1; }
.impl-student-remove:hover, .impl-student-restore:hover {
  color: var(--flame); border-color: var(--line);
}

/* Its own section under the active list rather than dimmed rows inside it. The
   count above has to mean one thing — a teacher reading "18 students" should
   not have to subtract greyed ones — and a dimmed row is easy to miss when
   scanning for a name. */
.impl-roster-archived {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
  margin: 4px 0 3px;
}
.impl-roster-archivedwhy {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
  line-height: 1.5; max-width: 64ch; margin-bottom: 8px;
}
/* Quieter, never struck through. A line through a real child's name reads as
   deleted, and archiving is precisely NOT that. */
.impl-student--archived .impl-student-name { color: var(--ink3); }

.impl-roster-addwrap { margin-top: 6px; padding-top: 8px; }
/* Its own name, not .impl-run-add: they look alike and mean different things,
   and the render suite counts them separately. */
.impl-roster-add {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-roster-add:hover { color: var(--flame); border-color: var(--flame); }

.impl-enrol-form {
  margin-top: 10px; padding: 13px 15px; max-width: 76ch;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
/* A date is eight characters wide however wide the form is. */
.impl-enrol-consent { max-width: 22ch; }
.impl-enrol-paste { min-height: 62px; }
.impl-enrol-pastewrap { margin: -2px 0 12px; }
.impl-enrol-parse {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink2); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; cursor: pointer;
}
.impl-enrol-parse:hover { color: var(--flame); border-color: var(--mid-flame); }

/* What happened, in one sentence, above the rows it is about. */
.impl-enrol-summary {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink2); line-height: 1.5;
  padding: 7px 0 9px; border-top: 1px solid var(--line);
}

.impl-enrol-table { display: flex; flex-direction: column; gap: 6px; }
/* Name and email side by side, the drop control at the end, and the flag and
   the result on their own lines underneath — both of those are sentences, and
   a sentence squeezed into a table column is a truncated sentence. */
.impl-enrol-row {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) auto;
  gap: 6px; align-items: center;
}
.impl-enrol-name, .impl-enrol-email {
  width: 100%; box-sizing: border-box;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 6px 9px; line-height: 1.5;
}
.impl-enrol-name:focus, .impl-enrol-email:focus {
  outline: none; border-color: var(--flame);
  box-shadow: 0 0 0 3px rgba(var(--flame-rgb), 0.14);
}
.impl-enrol-drop {
  font-family: var(--sans); font-size: 15px; line-height: 1;
  color: var(--ink3); background: none;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 3px 8px; cursor: pointer;
}
.impl-enrol-drop:hover { color: var(--flame); border-color: var(--line); }

/* A line the parser could not read as a student — most often a header row
   pasted along with the class. It is shown rather than dropped, because a line
   that silently vanishes is a student the teacher believes they added. */
.impl-enrol-flag, .impl-enrol-result {
  grid-column: 1 / -1;
  font-family: var(--sans); font-size: 11.5px; line-height: 1.5;
  color: var(--flame); margin: -2px 0 2px;
}
.impl-enrol-row--failed .impl-enrol-name,
.impl-enrol-row--failed .impl-enrol-email { border-color: var(--mid-flame); }

.impl-enrol-addrowwrap { margin: 8px 0 12px; }
.impl-enrol-addrow {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; cursor: pointer;
}
.impl-enrol-addrow:hover { color: var(--flame); border-color: var(--flame); }

@media (max-width: 860px) {
  /* Two full-width fields and the drop control beside the second one. Side by
     side, each is too narrow to read an address in. */
  .impl-enrol-row { grid-template-columns: minmax(0, 1fr) auto; }
  .impl-enrol-name { grid-column: 1 / -1; }
}

/* ── empty and message states ── */
.impl-empty {
  font-family: var(--sans); font-size: 13px; color: var(--ink3);
  line-height: 1.7; max-width: 62ch; padding: 6px 0 20px;
}
.impl-message {
  font-family: var(--sans); font-size: 13.5px; color: var(--ink2);
  line-height: 1.75; max-width: 60ch; padding: 40px 0;
}
.impl-message-title {
  font-family: var(--serif); font-size: 20px; font-weight: 700;
  color: var(--ink); margin-bottom: 10px;
}
.impl-message a {
  display: inline-block; margin-top: 14px; color: var(--ink);
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  text-decoration: underline;
}

@media (max-width: 860px) {
  .impl-wrap { padding: 22px 18px 48px; }
  .impl-tabs { overflow-x: auto; }
  .impl-session { flex-direction: column; gap: 4px; }
  .impl-session-num { width: auto; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE STUDENT VIEW — /student.html, Step 5's eleventh slice (5.0b)
   
   A separate namespace from .impl-, not a variant of it. The two surfaces
   answer different questions: a teacher scans ten sessions and nineteen
   students looking for what needs attention, and density serves them. A
   student reads one session and does what it says, and density just makes it
   harder. So the type is larger, the line height looser, and the column
   narrower than --page-max.
   
   Every name here is literal and none is shared with an .impl- rule. Sharing a
   name with a control that has different rules has already produced two bugs
   on the teacher side (impl-phase-add, impl-req-btn), and tools/deadcss.js
   matches literal strings, so a composed name would read as dead.
   ══════════════════════════════════════════════════════════════════════════ */

.stu-wrap { flex: 1; min-height: 0; overflow-y: auto; padding: 32px 40px 72px; }
/* Narrower than the teacher's --page-max: this page is mostly running text —
   a session description, a deliverable's requirements — and prose widened to
   fill a monitor is harder to read, not easier. Same argument as
   --reading-max, which is what this is. */
/* Same fix, same reason — .stu-wrap is also on the .app-view element. The
   student page is one column of running text, so the cap stays --reading-max;
   what changes is that it now actually reaches it instead of tracking the
   length of whatever the current session happens to say. */
/* THE MEASURE MOVED DOWN ONE LEVEL. .stu-inner used to cap at --reading-max and
   everything inherited it; the Overview panel is now full-bleed and the other
   two are not, so the cap sits on the things that actually want it.
   width:100% stays load-bearing for the auto-margin reason in ARCHITECTURE.md —
   .stu-inner is a flex item of .app-view. */
.stu-inner { max-width: none; margin: 0 auto; width: 100%; }
#stuMessage, #stuHeader, .stu-tabs, .stu-panel {
  max-width: var(--reading-max); margin-left: auto; margin-right: auto;
}
/* (0,2,0) beats .stu-panel's (0,1,0). Keyed off proj-view, which already means
   "this panel is the project", rather than off the id. No `display` is set, so
   the hidden attribute keeps working. */
.stu-panel.proj-view { max-width: none; }

/* ── header ── */
.stu-header { margin-bottom: 24px; }
.stu-title {
  font-family: var(--serif); font-size: 27px; font-weight: 700;
  color: var(--ink); margin-bottom: 6px; line-height: 1.25;
}
.stu-sub {
  font-family: var(--sans); font-size: 14px; color: var(--ink2);
  line-height: 1.6;
}

/* ── tabs ── */
.stu-tabs {
  display: flex; gap: 2px; border-bottom: 1.5px solid var(--line);
  margin-bottom: 26px;
}
.stu-tab {
  background: none; border: none; cursor: pointer;
  font-family: var(--sans); font-size: 14px; font-weight: 600;
  color: var(--ink3); padding: 10px 16px; margin-bottom: -1.5px;
  border-bottom: 2px solid transparent;
  transition: color 0.13s, border-color 0.13s;
}
.stu-tab:hover { color: var(--ink2); }
.stu-tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--flame); }

/* ── today: the session, which heads the day ── */
.stu-session {
  border-left: 3px solid var(--flame);
  padding: 2px 0 2px 16px; margin-bottom: 30px;
}
.stu-session-line {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px;
  margin-bottom: 8px;
}
.stu-session-num {
  font-family: var(--sans); font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--flame);
}
.stu-session-type {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink3);
}
/* session_plans.description, rendered verbatim — settled 17 August 2026 as
   student-facing. Sized as the primary reading on the page, because it is.
   pre-wrap because a teacher's numbered steps are SET, not formatted: their
   line breaks are the only structure the text has, and the share link's
   .proj-now-desc already keeps them. */
.stu-session-desc {
  font-family: var(--serif); font-size: 18px; line-height: 1.65;
  color: var(--ink); white-space: pre-wrap;
}

/* ── today: the activity list ── */
.stu-acts-head {
  font-family: var(--sans); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink3);
  margin-bottom: 10px;
}
.stu-acts { display: flex; flex-direction: column; gap: 1px; }
/* No hover state and no cursor change: these rows do NOT do anything yet, and
   a row that looks clickable and is not is worse than one that looks inert.
   5.1 onward add the affordance along with the thing it opens. */
.stu-act {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 14px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.stu-act-label {
  font-family: var(--sans); font-size: 14.5px; font-weight: 500; color: var(--ink);
}
.stu-act-none, .stu-act-note {
  font-family: var(--sans); font-size: 13px; color: var(--ink3);
  line-height: 1.6; padding: 4px 2px;
}
.stu-act-note { margin-top: 8px; font-style: italic; }

/* ── deliverables ── */
.stu-deliv {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); padding: 20px 22px; margin-bottom: 14px;
}
.stu-deliv-head {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px;
  margin-bottom: 6px;
}
.stu-deliv-title {
  font-family: var(--serif); font-size: 18px; font-weight: 700; color: var(--ink);
}
.stu-deliv-final {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  padding: 2px 8px; border-radius: 3px;
  background: var(--light-flame); color: var(--flame-hover);
}
.stu-deliv-due {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--ink2); margin-bottom: 10px;
}
.stu-deliv-desc {
  font-family: var(--sans); font-size: 14px; line-height: 1.7;
  color: var(--ink2); margin-bottom: 14px;
}
.stu-reqs-head {
  font-family: var(--sans); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink3);
  margin-bottom: 8px;
}
.stu-reqs { margin: 0; padding-left: 20px; }
.stu-req {
  font-family: var(--sans); font-size: 14px; line-height: 1.7;
  color: var(--ink); margin-bottom: 6px;
}

/* ── empty states, messages, and the chooser ── */
.stu-empty { padding: 34px 0; max-width: 52ch; }
.stu-empty-title {
  font-family: var(--serif); font-size: 19px; font-weight: 700;
  color: var(--ink); margin-bottom: 8px;
}
.stu-empty-body {
  font-family: var(--sans); font-size: 14px; line-height: 1.7; color: var(--ink2);
}
.stu-message {
  font-family: var(--sans); font-size: 14px; color: var(--ink2);
  line-height: 1.75; max-width: 56ch; padding: 48px 0;
}
.stu-message-title {
  font-family: var(--serif); font-size: 22px; font-weight: 700;
  color: var(--ink); margin-bottom: 10px;
}
.stu-message-body { font-family: var(--sans); font-size: 14px; color: var(--ink2); }
/* DIRECT CHILD, and the `>` is load-bearing. As a descendant selector this
   also matched .stu-choose-item — which sits inside .stu-message — and at
   specificity 0,1,1 it beat that rule's 0,1,0, so the chooser's two-line cards
   rendered as one underlined inline-block run: "Grumbleworth's Novelty
   WidgetsPeriod 2".
   
   Caught by looking at render-student-sample.js's output, not by
   student-view-test.js, which asserted the href and the text content — both of
   which were perfectly correct while the thing was unreadable. That is the
   whole argument for generating the sample and opening it. */
.stu-message > a {
  display: inline-block; margin-top: 16px; color: var(--ink);
  font-family: var(--sans); font-size: 13.5px; font-weight: 600;
  text-decoration: underline;
}
.stu-choose { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; }
.stu-choose-item {
  display: flex; flex-direction: column; gap: 3px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); padding: 16px 18px; text-decoration: none;
  transition: border-color 0.13s;
}
.stu-choose-item:hover { border-color: var(--flame); }
.stu-choose-title {
  font-family: var(--serif); font-size: 16.5px; font-weight: 700; color: var(--ink);
}
.stu-choose-label { font-family: var(--sans); font-size: 12.5px; color: var(--ink3); }
/* Says which of two classes a student has been archived off, before they pick
   one. Flame rather than grey: it is the reason the class will look empty.
   --flame-hover rather than --flame, measured in the render sample — plain
   --flame is 4.48:1 on the page background at 12px, a hair under AA, and this
   is a line a student has to read to understand why a class looks dead.
   --flame-hover is 5.61:1 and is the same hue one step darker. */
.stu-choose-archived {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--flame-hover);
}

@media (max-width: 860px) {
  .stu-wrap { padding: 22px 18px 56px; }
  .stu-tabs { overflow-x: auto; }
  .stu-session-desc { font-size: 16.5px; }
}

/* ══════════════════════════════════════════════════════════════════════
   ADMINISTRATION (admin.html)
   Deliberately plainer than the teacher surfaces. This page performs one
   irreversible act and nothing else; it should feel like a control panel
   rather than somewhere to browse.
   ══════════════════════════════════════════════════════════════════════ */
.admin-denied {
  font-family: var(--sans); font-size: 13px; color: var(--ink3);
  border: 1.5px solid var(--line); border-radius: var(--radius);
  padding: 20px 22px; text-align: center; margin-top: 8px;
}
.admin-section { margin-top: 8px; }
.admin-section-head {
  font-family: var(--display); font-size: 15px; font-weight: 700;
  color: var(--ink); margin-bottom: 8px;
}
/* The warning carries the flame edge rather than a red panel: this is not an
   error state, it is a correct action that happens to be irreversible. */
.admin-note {
  font-family: var(--sans); font-size: 12.5px; line-height: 1.55;
  color: var(--ink2); background: var(--bg2);
  border-left: 3px solid var(--flame); border-radius: var(--radius);
  padding: 12px 14px; margin-bottom: 16px;
}
.admin-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 10px; }
.admin-search {
  flex: 1 1 220px; min-width: 0;
  font-family: var(--sans); font-size: 13px; color: var(--ink);
  background: var(--card); border: 1.5px solid var(--line);
  border-radius: var(--radius); padding: 8px 12px;
}
.admin-search:focus { outline: none; border-color: var(--navy); }
.admin-filter {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--sans); font-size: 12.5px; color: var(--steel); cursor: pointer;
}
.admin-refresh {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: var(--steel);
  background: none; border: 1.5px solid var(--line); border-radius: var(--radius);
  padding: 8px 14px; cursor: pointer;
}
.admin-refresh:hover { color: var(--navy); border-color: var(--navy); }
.admin-summary {
  font-family: var(--sans); font-size: 12px; color: var(--ink3); margin-bottom: 8px;
}
.admin-empty {
  font-family: var(--sans); font-size: 13px; color: var(--ink3);
  text-align: center; padding: 24px 0;
}
.admin-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.admin-who { min-width: 0; }
.admin-name {
  font-family: var(--sans); font-size: 13.5px; font-weight: 600; color: var(--ink);
}
.admin-meta {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3); margin-top: 2px;
}
.admin-delete {
  flex: none;
  font-family: var(--sans); font-size: 12px; font-weight: 600; color: var(--steel);
  background: none; border: 1.5px solid var(--line); border-radius: var(--radius);
  padding: 6px 12px; cursor: pointer;
}
.admin-delete:hover { color: var(--flame); border-color: var(--flame); }
.admin-confirm-field { margin: 16px 0 8px; text-align: left; }
.admin-confirm-field label {
  display: block; margin-bottom: 6px;
  font-family: var(--sans); font-size: 12px; font-weight: 600; color: var(--ink2);
}
.admin-error {
  font-family: var(--sans); font-size: 12.5px; color: var(--flame);
  margin-bottom: 8px; text-align: left;
}
.admin-receipt { margin: 12px 0 4px; text-align: left; }
.admin-receipt-line {
  font-family: var(--sans); font-size: 12px; color: var(--ink3);
  line-height: 1.5; margin-bottom: 4px;
}
/* The half a written reply to a parent has to be accurate about. */
.admin-receipt-warn { color: var(--flame); font-weight: 600; }

/* ═══════════════════════════════════════════════════════════
   REVISE WITH AI — impl-revise.js
   A changeset spans phases, sessions and deliverables, so the panel sits above
   the tabs rather than inside one of them, and stays put while the teacher
   switches tabs to check what a proposed change refers to.
   ═══════════════════════════════════════════════════════════ */

/* The opener, in the run header. Dashed like .impl-phase-add — the same visual
   claim: this adds something rather than confirming or destroying it. */
/* ══ Teacher chat (impl-chat.js) — the Today tab's advisory pane ══
   Capped at --reading-max and NOT at the widened --page-max above. A transcript
   is running text, and the :root comment states the rule: prose widened to fill
   a monitor is harder to read, not easier. The pane sits inside a container
   that just grew; this is what stops it growing with it. */
.impl-chat {
  margin: 0 0 24px; padding: 14px 16px 12px;
  /* The pane fills the column so it lines up with the advance card above and
     the notes below. The reading measure moves inward to .impl-chat-body, which
     is where it belongs: the :root comment's rule is about running TEXT, not
     about the box around it. A wide pane with a narrow text column is the
     normal answer and keeps both. */
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.impl-chat-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.impl-chat-title { margin: 0; }
.impl-chat-clear {
  font-family: var(--sans); font-size: 11px; color: var(--ink3);
  background: none; border: none; cursor: pointer; padding: 2px 4px;
}
.impl-chat-clear:hover { color: var(--ink); text-decoration: underline; }

/* The pane's OWN scroll container. The stream anchors against this rather than
   .impl-wrap: pinning a reply against the page scroller would yank the whole
   run view around while the teacher is reading the advance card above. */
.impl-chat-log {
  max-height: 55vh; overflow-y: auto;
  margin: 10px 0 12px; padding-right: 4px;
  display: flex; flex-direction: column; gap: 12px;
}
.impl-chat-log:empty { display: none; }

.impl-chat-intro {
  font-family: var(--sans); font-size: 13px; line-height: 1.55; color: var(--ink2);
}
.impl-chat-intro-sub {
  font-family: var(--sans); font-size: 12px; line-height: 1.5; color: var(--ink3);
  margin-top: -6px;
}

.impl-chat-msg { display: flex; flex-direction: column; gap: 6px; }
.impl-chat-msg--teacher { align-items: flex-end; }
.impl-chat-msg--teacher .impl-chat-body {
  background: var(--light-flame); border-radius: var(--radius);
  padding: 8px 11px; max-width: min(85%, 68ch);
  /* The teacher's own text is SET, not formatted, so their line breaks are the
     only structure it has. */
  white-space: pre-wrap;
}
/* 72ch, not 100%. This rule is more specific than the .impl-chat-body base
   below, so a 100% here silently beat the reading measure and let replies run
   the full width of the widened pane — the exact thing the :root comment warns
   about. The pane is wide; the prose inside it is not. */
.impl-chat-msg--assistant .impl-chat-body { max-width: 72ch; }
.impl-chat-body {
  font-family: var(--sans); font-size: 13px; line-height: 1.6; color: var(--ink);
  max-width: 72ch;
}
.impl-chat-body p { margin: 0 0 9px; }
.impl-chat-body p:last-child { margin-bottom: 0; }
.impl-chat-body ul, .impl-chat-body ol { margin: 0 0 9px; padding-left: 20px; }
.impl-chat-body li { margin-bottom: 4px; }
.impl-chat-body strong { font-weight: 600; }
.impl-chat-working { color: var(--ink3); font-style: italic; }

.impl-chat-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.impl-chat-handoff {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  color: var(--flame); background: none;
  border: 1px solid var(--flame); border-radius: 4px;
  padding: 4px 8px; cursor: pointer; text-align: left;
  /* Carries the model's own sentence, so it wraps rather than truncating —
     a teacher has to be able to read what would be sent before sending it. */
  max-width: 100%; line-height: 1.4;
}
.impl-chat-handoff:hover { background: var(--flame); color: var(--warm-white); }
.impl-chat-note-btn {
  font-family: var(--sans); font-size: 11px; color: var(--ink3);
  background: none; border: none; cursor: pointer; padding: 3px 4px;
}
.impl-chat-note-btn:hover { color: var(--ink); text-decoration: underline; }
.impl-chat-note-host:empty { display: none; }

.impl-chat-error { margin: 0; }
.impl-chat-cap {
  font-family: var(--sans); font-size: 12px; line-height: 1.5; color: var(--ink3);
  border-top: 1px solid var(--line); padding-top: 10px;
}

.impl-chat-composer { display: flex; gap: 8px; align-items: flex-end; }
.impl-chat-input {
  flex: 1; font-family: var(--sans); font-size: 13px; line-height: 1.5;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 9px 11px; resize: vertical;
}
.impl-chat-input:focus { outline: 2px solid var(--flame); outline-offset: -1px; }
.impl-chat-input:disabled { opacity: 0.55; }
.impl-chat-send {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--warm-white); background: var(--flame);
  border: 1px solid var(--flame); border-radius: var(--radius);
  padding: 9px 16px; cursor: pointer;
}
.impl-chat-send:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.impl-chat-send:disabled { opacity: 0.5; cursor: default; }

.impl-chat-foot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-top: 7px;
}
.impl-chat-hint { font-family: var(--sans); font-size: 11px; color: var(--ink3); }
.impl-chat-handoff-any {
  font-family: var(--sans); font-size: 11px; color: var(--ink3);
  background: none; border: none; cursor: pointer; padding: 2px 4px; white-space: nowrap;
}
.impl-chat-handoff-any:hover { color: var(--flame); text-decoration: underline; }

.impl-rev-open {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; margin-top: 10px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-rev-open:hover { color: var(--flame); border-color: var(--flame); }

.impl-rev-panel {
  margin: 0 0 20px; padding: 16px 18px;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.impl-rev-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.impl-rev-title {
  font-family: var(--serif); font-size: 17px; font-weight: 700;
  color: var(--ink); margin: 0 0 4px;
}
.impl-rev-close {
  font-family: var(--sans); font-size: 18px; line-height: 1;
  color: var(--ink3); background: none; border: none; cursor: pointer; padding: 2px 6px;
}
.impl-rev-close:hover { color: var(--ink); }

.impl-rev-lede, .impl-rev-summary, .impl-rev-asked, .impl-rev-working, .impl-rev-none {
  font-family: var(--sans); font-size: 13px; color: var(--ink2);
  margin: 0 0 12px; max-width: 76ch; line-height: 1.5;
}
.impl-rev-asked { font-style: italic; color: var(--ink3); }
.impl-rev-summary { color: var(--ink); }

.impl-rev-input {
  font-family: var(--sans); font-size: 13px; color: var(--ink);
  width: 100%; max-width: 76ch; padding: 9px 11px;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); resize: vertical;
}
.impl-rev-input:focus { outline: 2px solid var(--flame); outline-offset: -1px; }
.impl-rev-input:disabled { opacity: 0.55; }

.impl-rev-actions { display: flex; gap: 7px; align-items: center; margin-top: 12px; }
.impl-rev-go, .impl-rev-apply, .impl-rev-again, .impl-rev-discard {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  border-radius: var(--radius-sm); padding: 6px 13px; cursor: pointer;
}
.impl-rev-go, .impl-rev-apply {
  color: #fff; background: var(--flame); border: 1px solid var(--flame);
}
.impl-rev-go:hover, .impl-rev-apply:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.impl-rev-again, .impl-rev-discard {
  color: var(--ink2); background: var(--bg); border: 1px solid var(--line);
}
.impl-rev-again:hover, .impl-rev-discard:hover { color: var(--ink); }
.impl-rev-go:disabled, .impl-rev-apply:disabled,
.impl-rev-again:disabled, .impl-rev-discard:disabled { opacity: 0.55; cursor: not-allowed; }

.impl-rev-error {
  font-family: var(--sans); font-size: 12.5px; color: var(--flame);
  margin-top: 10px; max-width: 76ch; line-height: 1.5;
}

/* What the model was asked for and could not do. Stated before the cards, not
   after them — a teacher who reads the proposal first and the refusals last has
   already formed a view of a changeset missing a piece. */
.impl-rev-cannot {
  margin: 0 0 14px; padding: 10px 12px;
  background: var(--bg); border: 1px solid var(--line);
  border-left: 3px solid var(--mid-flame); border-radius: var(--radius-sm);
}
.impl-rev-cannot-head {
  font-family: var(--sans); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink3); margin: 0 0 6px;
}
.impl-rev-cannot-item {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  margin: 0 0 4px; line-height: 1.5; max-width: 76ch;
}

.impl-rev-group { margin-bottom: 14px; }
.impl-rev-group-head {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink3); margin: 0 0 6px;
}

/* One decision per card. A label wrapping its own checkbox, so the whole row is
   the hit target and the keyboard reaches it without a separate tab stop. */
.impl-rev-card {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 8px 11px; margin-bottom: 5px; max-width: 82ch;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); cursor: pointer;
}
.impl-rev-card:hover { border-color: var(--line2); }
/* A dropped change stays visible and legible rather than disappearing — the
   list is the teacher's record of what was proposed, including what they said
   no to, and a shrinking list is one they cannot re-read. */
.impl-rev-card--dropped { opacity: 0.5; }
.impl-rev-card--dropped .impl-rev-card-body { text-decoration: line-through; }
.impl-rev-card--dropped .impl-rev-card-note { text-decoration: none; }

.impl-rev-keep { margin-top: 2px; flex: none; accent-color: var(--flame); }
.impl-rev-card-body {
  font-family: var(--sans); font-size: 13px; color: var(--ink2); line-height: 1.5;
}
.impl-rev-verb { color: var(--ink); font-weight: 700; }
.impl-rev-card-detail, .impl-rev-card-note {
  display: block; font-size: 12px; color: var(--ink3); margin-top: 3px;
}
.impl-rev-card-note { font-style: italic; }

/* The resulting session list. The one view where "18 sessions became 13" reads
   at a glance, which is the question a restructure is actually about. */
.impl-rev-preview {
  margin: 14px 0 0; padding: 11px 13px; max-width: 82ch;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.impl-rev-preview-head {
  font-family: var(--sans); font-size: 12px; font-weight: 700;
  color: var(--ink); margin: 0 0 8px;
}
.impl-rev-preview-list { list-style: none; margin: 0; padding: 0; }
/* What the kept changes do to the planned days, and the two choices about it. */
.impl-rev-calendar {
  margin: 14px 0; padding: 10px 12px; max-width: 82ch;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg);
}
.impl-rev-calendar-option {
  display: flex; gap: 8px; align-items: flex-start; margin: 6px 0;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink); cursor: pointer;
}
.impl-rev-calendar-note { font-family: var(--sans); font-size: 12px; color: var(--ink3); margin: 6px 0 0; }
.impl-rev-calendar-summary {
  font-family: var(--sans); font-size: 12.5px; line-height: 1.55; color: var(--ink2);
  margin: 8px 0 0;
}
.impl-rev-preview-item {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  padding: 2px 0; line-height: 1.5;
}
.impl-rev-preview-item--new { color: var(--strong); font-weight: 600; }
.impl-rev-preview-item--moved { color: var(--flame); }

.impl-rev-followup { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.impl-rev-followup-label {
  display: block; font-family: var(--sans); font-size: 12px;
  color: var(--ink3); margin-bottom: 6px;
}
.impl-rev-followup .impl-rev-again { margin-top: 8px; }

/* ── the student's register ───────────────────────────────────────────────
   impl-page-*   the Student & family page panel itself (impl-student-page.js)
   impl-voice-*  its Write the student version section (impl-student-voice.js)
   impl-narr-*   its Write the project's premise section (impl-project-narrative.js)
   impl-headline-* the title and one-sentence editor at the top of that section
   impl-share-*  its Share this project section (impl-project-narrative.js)
   impl-stu-*    the per-row "What students see" block (page-implementation.js)

   impl-narr-* shares every rule below by sitting in the same selector lists
   rather than by copying them. The two sections are the same act — request,
   compare source against proposal, keep card by card — and a second copy of 120
   lines of chrome would be two sections one edit away from looking different
   for no reason anybody chose. Each name is still a literal string, which is all
   tools/deadcss.js needs; what is NOT done is giving one element both prefixes,
   which is the shared-name bug the note below is about.

   Literal names, prefixed impl-, reusing no stu- name — page-student.js:27-30's
   rule, and because tools/deadcss.js matches on literal strings. The student
   page's own stu- classes govern a 7th grader's screen and these govern a
   teacher's preview of it; a shared name would be two sets of rules one edit
   away from each other.                                                     */

/* The one button, beside Revise with AI. Solid rather than dashed once the
   public link is live, which is the state a teacher most wants to see from
   the header without opening anything. */
.impl-page-open {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink3); background: none;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; margin-top: 10px; margin-left: 7px; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.impl-page-open:hover { color: var(--flame); border-color: var(--flame); }
.impl-page-open--live { color: var(--strong); border-style: solid; border-color: var(--strong); }

/* The panel, and the sections inside it. Sections are separated by a rule,
   not boxed: three boxes inside a box was the old three panels stacked. */
.impl-page-panel {
  margin: 0 0 20px; padding: 16px 18px;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.impl-page-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.impl-page-title {
  font-family: var(--serif); font-size: 19px; font-weight: 700;
  color: var(--ink); margin: 0 0 4px;
}
.impl-page-close {
  font-family: var(--sans); font-size: 18px; line-height: 1;
  color: var(--ink3); background: none; border: none; cursor: pointer; padding: 2px 6px;
}
.impl-page-close:hover { color: var(--ink); }
.impl-page-lede {
  font-family: var(--sans); font-size: 13px; color: var(--ink2);
  margin: 0 0 6px; max-width: 76ch; line-height: 1.5;
}
.impl-page-section { padding-top: 16px; margin-top: 16px; border-top: 1px solid var(--line); }

.impl-voice-head, .impl-narr-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.impl-voice-title, .impl-narr-title {
  font-family: var(--serif); font-size: 17px; font-weight: 700;
  color: var(--ink); margin: 0 0 4px;
}

.impl-voice-lede, .impl-narr-lede, .impl-voice-working, .impl-narr-working {
  font-family: var(--sans); font-size: 13px; color: var(--ink2);
  margin: 0 0 12px; max-width: 76ch; line-height: 1.5;
}

.impl-voice-group-head, .impl-narr-group-head {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink3);
  margin: 16px 0 8px;
}

.impl-voice-card, .impl-narr-card {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 12px; margin-bottom: 8px; background: var(--bg);
  transition: opacity 0.12s ease;
}
.impl-voice-card--dropped, .impl-narr-card--dropped { opacity: 0.5; }
.impl-voice-card--dropped .impl-voice-after .impl-voice-side-text, .impl-narr-card--dropped .impl-narr-after .impl-narr-side-text { text-decoration: line-through; }

.impl-voice-card-head, .impl-narr-card-head {
  display: flex; align-items: baseline; gap: 8px; cursor: pointer;
  margin-bottom: 8px;
}
.impl-voice-card-name, .impl-narr-card-name {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: var(--ink);
}
.impl-voice-card-sub, .impl-narr-card-sub {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
}

/* Two columns where there is room, stacked where there is not. Stacked keeps
   the teacher's line ABOVE the rewrite, which is the reading order the review
   depends on — the question is "does this still say what I meant?", and that
   is only answerable in that order. */
.impl-voice-pair, .impl-narr-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 760px) {
  .impl-voice-pair, .impl-narr-pair { grid-template-columns: 1fr; }
}
.impl-voice-side-head, .impl-narr-side-head {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink3);
  margin-bottom: 4px;
}
.impl-voice-side-text, .impl-narr-side-text {
  font-family: var(--sans); font-size: 13px; line-height: 1.5; color: var(--ink2);
}
.impl-voice-before .impl-voice-side-text, .impl-narr-before .impl-narr-side-text { color: var(--ink3); }
.impl-voice-after .impl-voice-side-text, .impl-narr-after .impl-narr-side-text { color: var(--ink); }
.impl-voice-after, .impl-narr-after { border-left: 2px solid var(--flame); padding-left: 10px; }
@media (max-width: 760px) {
  .impl-voice-after, .impl-narr-after { border-left: none; border-top: 1px solid var(--line); padding-left: 0; padding-top: 8px; }
}

.impl-voice-left-alone, .impl-narr-left-alone {
  display: flex; gap: 10px; align-items: baseline;
  font-family: var(--sans); font-size: 12px; padding: 4px 0;
  border-bottom: 1px solid var(--line);
}
.impl-voice-left-alone-name, .impl-narr-left-alone-name { color: var(--ink2); font-weight: 600; }

.impl-voice-error, .impl-narr-error {
  font-family: var(--sans); font-size: 12.5px; color: var(--flame);
  margin-top: 10px; max-width: 76ch; line-height: 1.5;
}

/* The scope toggle: owed rows only by default, everything on request. Disabled
   and ticked when nothing is owed, because starting over is then the only
   thing the pass can do. */
.impl-voice-scope {
  display: flex; align-items: center; gap: 8px; margin-top: 12px;
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2); cursor: pointer;
}
.impl-voice-scope-all { margin: 0; accent-color: var(--flame); }
.impl-voice-scope-all:disabled + .impl-voice-scope-text { color: var(--ink3); }
.impl-voice-scope-text { line-height: 1.4; }
.impl-voice-actions, .impl-narr-actions { display: flex; gap: 7px; align-items: center; margin-top: 14px; }
.impl-voice-go, .impl-narr-go, .impl-voice-apply, .impl-narr-apply {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  border-radius: var(--radius-sm); padding: 6px 13px; cursor: pointer;
  color: #fff; background: var(--flame); border: 1px solid var(--flame);
}
.impl-voice-go:hover, .impl-narr-go:hover, .impl-voice-apply:hover, .impl-narr-apply:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.impl-voice-discard, .impl-narr-discard {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  border-radius: var(--radius-sm); padding: 6px 13px; cursor: pointer;
  color: var(--ink3); background: none; border: 1px solid var(--line);
}
.impl-voice-discard:hover, .impl-narr-discard:hover { color: var(--ink); }
.impl-voice-go:disabled, .impl-narr-go:disabled, .impl-voice-apply:disabled, .impl-narr-apply:disabled, .impl-voice-discard:disabled, .impl-narr-discard:disabled {
  opacity: 0.55; cursor: default;
}

/* ── editing what students read (impl-project-narrative.js) ───────────────
   The same editor in two places: a proposal card before anything is saved, and
   a line that is already written. One set of rules, because they are one act. */

.impl-narr-edit {
  width: 100%; box-sizing: border-box; resize: vertical;
  font-family: var(--sans); font-size: 13.5px; line-height: 1.6;
  color: var(--ink); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 9px 11px;
}
.impl-narr-edit:focus { outline: none; border-color: var(--flame); }
.impl-narr-count {
  font-family: var(--sans); font-size: 11px; color: var(--ink3);
  text-align: right; margin-top: 3px;
}
/* The server refuses over its ceiling regardless; this is the warning, not the
   gate — same relationship the voice pass's counter has to student-voice.js. */
.impl-narr-count--over { color: var(--flame); font-weight: 700; }

.impl-narr-roles-edit { display: flex; flex-direction: column; gap: 10px; }
.impl-narr-role-edit {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 11px 12px; background: var(--bg);
}
.impl-narr-role-label {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--ink3); margin: 8px 0 4px;
}
.impl-narr-role-edit .impl-narr-role-label:first-child { margin-top: 0; }
.impl-narr-role-field {
  width: 100%; box-sizing: border-box; resize: vertical;
  font-family: var(--sans); font-size: 13px; line-height: 1.55;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 7px 9px;
}
.impl-narr-role-field:focus { outline: none; border-color: var(--flame); }
.impl-narr-role-remove, .impl-narr-role-add {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink3); background: none; border: 1px dashed var(--line);
  border-radius: var(--radius-sm); padding: 5px 10px; margin-top: 9px; cursor: pointer;
}
.impl-narr-role-remove:hover { color: var(--flame); border-color: var(--flame); }
.impl-narr-role-add:hover { color: var(--ink); border-color: var(--line2); }

/* ── what students read now ── */
.impl-narr-written { display: flex; flex-direction: column; gap: 10px; }
.impl-narr-written-row {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px; background: var(--card);
}
.impl-narr-written-name {
  font-family: var(--sans); font-size: 13px; font-weight: 700;
  color: var(--ink); margin-bottom: 6px;
}
.impl-narr-written-stale {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  color: var(--flame); background: var(--light-flame);
  border-radius: 3px; padding: 2px 6px; margin-left: 8px;
}
.impl-narr-written-text {
  font-family: var(--sans); font-size: 13px; line-height: 1.6;
  color: var(--ink2); white-space: pre-wrap; max-width: 76ch;
}
.impl-narr-written-actions {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-top: 9px;
}
.impl-narr-mini {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink3); background: none; border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 4px 10px; cursor: pointer;
}
.impl-narr-mini:hover { color: var(--ink); border-color: var(--line2); }
.impl-narr-mini--go {
  color: #fff; background: var(--flame); border-color: var(--flame);
}
.impl-narr-mini--go:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.impl-narr-mini:disabled { opacity: 0.5; cursor: default; }

/* ── the share link ───────────────────────────────────────────────────────
   impl-share-*  the Share this project controls (impl-project-narrative.js)

   Its own names rather than the narrative panel's, because it is a different
   act with a different consequence: the pass writes text a teacher reviewed,
   and this publishes a URL. A control that looked like the one beside it would
   be the wrong kind of quiet.                                              */

.impl-share-lede {
  font-family: var(--sans); font-size: 13px; line-height: 1.6;
  color: var(--ink2); margin: 6px 0 12px; max-width: 76ch;
}
.impl-share-url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
  color: var(--ink); background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 9px 11px; margin-bottom: 12px;
  word-break: break-all; user-select: all;
}
.impl-share-missing {
  font-family: var(--sans); font-size: 12.5px; line-height: 1.5;
  color: var(--ink2); background: var(--bg);
  border: 1px solid var(--flame); border-radius: var(--radius-sm);
  padding: 8px 11px; margin: 0 0 12px; max-width: 76ch;
}
/* The headline: the run's title and one-sentence line, at rest and open for
   editing. Sits at the top of the premise section: these two lines are the
   top of the page a student or a parent reads, above the premise. */
.impl-headline {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 12px; margin: 0 0 14px; max-width: 76ch;
}
.impl-headline-label {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink3); margin-bottom: 6px;
}
.impl-headline-title {
  font-family: var(--serif); font-size: 17px; font-weight: 700; color: var(--ink); line-height: 1.3;
}
.impl-headline-desc {
  font-family: var(--sans); font-size: 13px; line-height: 1.55; color: var(--ink2); margin-top: 4px;
}
.impl-headline-desc--empty { color: var(--ink3); font-style: italic; }
.impl-headline-edit { margin-top: 6px; }
.impl-headline-error {
  font-family: var(--sans); font-size: 12.5px; color: var(--flame); margin-top: 8px;
}
.impl-headline-field { display: block; margin-bottom: 10px; }
.impl-headline-name {
  display: block; font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  color: var(--ink2); margin-bottom: 4px;
}
.impl-headline-input, .impl-headline-text {
  display: block; width: 100%; box-sizing: border-box;
  font-family: var(--sans); font-size: 13px; line-height: 1.5; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 7px 9px;
}
.impl-headline-text { resize: vertical; }
.impl-headline-input:focus, .impl-headline-text:focus { outline: none; border-color: var(--flame); }
.impl-headline-count {
  display: block; text-align: right; font-family: var(--sans); font-size: 11px; color: var(--ink3); margin-top: 2px;
}
.impl-headline-hint {
  font-family: var(--sans); font-size: 12px; line-height: 1.5; color: var(--ink3); margin: 2px 0 10px;
}
.impl-headline-actions { display: flex; gap: 7px; align-items: center; }
.impl-share-actions { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.impl-share-go {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: #fff; background: var(--flame); border: 1px solid var(--flame);
  border-radius: var(--radius-sm); padding: 6px 13px; cursor: pointer;
}
.impl-share-go:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.impl-share-quiet {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--ink3); background: none; border: none; cursor: pointer; padding: 6px 8px;
}
.impl-share-quiet:hover { color: var(--ink); }
.impl-share-go:disabled, .impl-share-quiet:disabled { opacity: 0.5; cursor: default; }
.impl-share-error {
  font-family: var(--sans); font-size: 12.5px; color: var(--flame);
  margin-top: 10px; max-width: 76ch;
}

/* ── the per-row block ──────────────────────────────────────────────────── */

/* Indented and rule-marked so it reads as DERIVED FROM the line above it
   rather than as a second field of equal standing. The teacher's line is the
   plan; this is what their class was handed. */
.impl-stu-line {
  margin: 8px 0 0; padding: 7px 0 7px 11px;
  border-left: 2px solid var(--line2);
}
.impl-stu-head { display: flex; align-items: baseline; gap: 8px; }
.impl-stu-label {
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink3);
}
.impl-stu-stale {
  font-family: var(--sans); font-size: 10.5px; font-weight: 600;
  color: var(--flame); border: 1px solid var(--flame);
  border-radius: var(--radius-sm); padding: 1px 6px;
}
/* pre-wrap for the same reason as .stu-session-desc: this is the teacher's
   preview of what students see, and it should have the same shape. */
.impl-stu-text {
  font-family: var(--sans); font-size: 13px; line-height: 1.5;
  color: var(--ink2); margin-top: 4px; max-width: 76ch; white-space: pre-wrap;
}
/* "Your line, as written." — the row has no student version. A status, not a
   preview, and set apart from real prose so nobody reads it as the line. */
.impl-stu-text--asis { color: var(--ink3); font-style: italic; }
/* Struck through, because an out-of-date version is not what students are
   reading — get-student-run.js is serving the teacher's line instead. The mark
   says "not in use", which is the true thing, rather than "broken". */
.impl-stu-line:has(.impl-stu-stale) .impl-stu-text {
  color: var(--ink3); text-decoration: line-through;
}


/* A pass that came back incomplete. Stated before the teacher commits, because
   saving 12 of 40 rows is a correct outcome only if they knew that is what they
   were doing. */
.impl-voice-partial, .impl-narr-partial {
  font-family: var(--sans); font-size: 12.5px; line-height: 1.5;
  color: var(--ink2); background: var(--bg);
  border: 1px solid var(--flame); border-radius: var(--radius-sm);
  padding: 8px 11px; margin: 0 0 12px; max-width: 76ch;
}

.impl-voice-left-alone-note, .impl-narr-left-alone-note {
  font-family: var(--sans); font-size: 12px; color: var(--ink3);
  line-height: 1.5; margin: 0 0 8px; max-width: 76ch;
}

/* ══════════════════════════════════════════════════════════════════════
   DASHBOARD — the site root, page-dashboard.js

   Its own dash- namespace, never a class shared with impl- or the projects
   page: two controls with different rules under one name is how the
   impl-phase-add and impl-req-btn bugs happened. Values are copied where
   they should match (.dash-section-head is .impl-section-head's, .dash-row
   --current carries .impl-card--current's flame edge) so the surfaces agree
   without coupling. Token-only, so Forge mode is free.

   --page-max is rebound to 920 the way .impl-wrap does it: a list of rows
   reads badly at 1280. .full-page-inner already carries width:100%, which is
   what makes this bind at all — see the Gotchas in ARCHITECTURE.md.
   ══════════════════════════════════════════════════════════════════════ */
.dash-wrap { --page-max: 920px; }
.dash-section { margin-top: 26px; }
.dash-section-head {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink3); margin: 0 0 10px;
}

/* Rows, not cards — a teacher with five classes scans this top to bottom for
   the one they are about to walk into. The whole row is the <a>. Adjacent rows
   share a border (the -1px overlap), and a hovered row lifts above its
   neighbours so its highlighted edge is not hidden under the next one. */
.dash-rows { display: flex; flex-direction: column; }
/* The row is a wrapper, not the <a>: it carries one button, and a button
   cannot live inside an anchor. .dash-row-link is the anchor and covers
   everything that is not the control, so the click target for "open this run"
   is still almost the whole row. */
.dash-row {
  display: flex; align-items: center; gap: 18px;
  background: var(--card); border: 1px solid var(--line);
  padding: 12px 16px; margin-top: -1px;
  color: inherit;
  transition: border-color 0.13s, background 0.13s;
}
.dash-row-link {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  flex: 1 1 auto; min-width: 0;
  text-decoration: none; color: inherit;
}
.dash-row:first-child { margin-top: 0; border-radius: var(--radius) var(--radius) 0 0; }
.dash-row:last-child { border-radius: 0 0 var(--radius) var(--radius); }
.dash-row:only-child { border-radius: var(--radius); }
.dash-row:hover { border-color: var(--ink); background: var(--bg2); position: relative; z-index: 1; }
/* The run with a session open right now — the same flame edge Today's "Now
   running" card and the Sessions row carry, so three views agree. */
.dash-row--current { box-shadow: inset 3px 0 0 var(--flame); }
.dash-row-main { flex: 1 1 auto; min-width: 0; }
.dash-row-label {
  font-family: var(--sans); font-size: 13.5px; font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dash-row-title {
  font-family: var(--sans); font-size: 12px; color: var(--ink3); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dash-row-where { flex: 0 1 auto; min-width: 0; text-align: right; }
.dash-row-line { font-family: var(--sans); font-size: 12.5px; color: var(--ink2); }
.dash-row-now { color: var(--flame); font-weight: 700; }
.dash-row-phase { font-family: var(--sans); font-size: 11.5px; color: var(--ink3); margin-top: 2px; }
.dash-row-close { color: var(--flame); font-weight: 600; }

/* The action cell. Always present, even when it holds nothing, so five rows
   stay in column whether or not each one has something to press — a list whose
   right edge moves per row is a list that is harder to scan than it was
   before the buttons arrived. */
.dash-row-action {
  flex: 0 0 auto; min-width: 124px;
  display: flex; justify-content: flex-end;
}

/* The one control that moves a run. Primary on the row, because on a page
   whose other elements are all links it is the only thing that writes
   anything — and because a teacher hunting for it between periods should not
   have to. Values are .impl-btn--primary's, so the button here and the button
   on Today read as the same act. */
.dash-advance-btn {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: #fff; background: var(--flame);
  border: 1px solid var(--flame); border-radius: var(--radius-sm);
  padding: 6px 13px; cursor: pointer; white-space: nowrap;
}
.dash-advance-btn:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.dash-advance-btn:disabled { opacity: 0.55; cursor: not-allowed; }

/* Undo, in its place, after this page has advanced that run. Deliberately
   quiet: it is the exit from a mistake, not a thing to reach for. */
.dash-undo-btn,
.dash-bulk-btn {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink2); background: var(--bg2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 6px 13px; cursor: pointer; white-space: nowrap;
}
.dash-undo-btn:hover,
.dash-bulk-btn:hover { color: var(--ink); border-color: var(--ink); }
.dash-undo-btn:disabled,
.dash-bulk-btn:disabled { opacity: 0.55; cursor: not-allowed; }

/* "Advance all" sits in the section head rather than above the rows: it acts
   on the whole section, and that is where the section says what it is. Quiet
   like the undo — the confirmation it opens is where the weight belongs. */
.dash-section-bar {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px;
}
.dash-section-bar .dash-section-head { margin-bottom: 10px; }

/* Said once after a write, then gone. */
.dash-flash {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--flame); margin-bottom: 10px;
}

/* The classes that did NOT move. Persists until the next action, unlike the
   flash above it, because it is the only record that one period was left
   behind while the others went on. */
.dash-action-error {
  margin-bottom: 10px; padding: 9px 12px; max-width: 74ch;
  background: var(--bg2); border: 1px solid var(--flame);
  border-radius: var(--radius-sm);
}
.dash-action-error-head {
  font-family: var(--sans); font-size: 12.5px; font-weight: 700;
  color: var(--flame); margin-bottom: 4px;
}
.dash-action-error-line {
  font-family: var(--sans); font-size: 12px; color: var(--ink2);
  line-height: 1.55;
}
.dash-action-error-who { font-weight: 600; color: var(--ink); }

/* Asks before moving several classes at once. Values are .impl-confirm's —
   the gap confirmation on the implementation page is the same kind of
   question, and the two should not look like different products. */
.dash-confirm {
  margin-bottom: 12px; padding: 11px 13px; max-width: 74ch;
  background: var(--bg2); border: 1px solid var(--flame);
  border-radius: var(--radius-sm);
}
.dash-confirm-title {
  font-family: var(--sans); font-size: 12.5px; font-weight: 700;
  color: var(--ink); margin-bottom: 6px;
}
.dash-confirm-body {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink2);
  line-height: 1.6; margin-bottom: 8px;
}
.dash-confirm-line { padding: 1px 0; }
.dash-confirm-who { font-weight: 600; color: var(--ink); }
.dash-confirm-untouched {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink3);
  line-height: 1.6; margin-bottom: 10px; max-width: 72ch;
}
.dash-confirm-actions { display: flex; gap: 7px; align-items: center; }
.dash-confirm-go {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: #fff; background: var(--flame);
  border: 1px solid var(--flame); border-radius: var(--radius-sm);
  padding: 6px 13px; cursor: pointer;
}
.dash-confirm-go:hover { background: var(--flame-hover); border-color: var(--flame-hover); }
.dash-confirm-go:disabled { opacity: 0.55; cursor: not-allowed; }
.dash-confirm-cancel {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--ink2); background: var(--bg2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 6px 13px; cursor: pointer;
}
.dash-confirm-cancel:hover { color: var(--ink); }
.dash-confirm-cancel:disabled { opacity: 0.55; cursor: not-allowed; }

.dash-empty {
  font-family: var(--sans); font-size: 13px; color: var(--ink3);
  line-height: 1.7; max-width: 62ch; padding: 6px 0 12px;
}
.dash-message {
  font-family: var(--sans); font-size: 13.5px; color: var(--ink2);
  line-height: 1.75; max-width: 60ch; padding: 40px 0;
}
.dash-message-title {
  font-family: var(--serif); font-size: 20px; font-weight: 700;
  color: var(--ink); margin-bottom: 10px;
}
.dash-message a,
.dash-message button {
  display: inline-block; margin-top: 14px; color: var(--ink);
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  text-decoration: underline;
}
/* The widget-opening actions on a failed sign-in are buttons, not links —
   there is no address to send anybody to. Reset to read as the link above. */
.dash-message button {
  background: none; border: 0; padding: 0; cursor: pointer;
}

@media (max-width: 560px) {
  .dash-row { flex-direction: column; align-items: stretch; gap: 6px; }
  .dash-row-link { flex-direction: column; align-items: stretch; gap: 6px; }
  .dash-row-where { text-align: left; }
  /* The control goes full width under the row rather than beside it. An empty
     action cell collapses to nothing so a row with no button gains no gap. */
  .dash-row-action { min-width: 0; justify-content: stretch; }
  .dash-row-action:empty { display: none; }
  .dash-row-action > button { width: 100%; }
  .dash-section-bar { flex-direction: column; align-items: stretch; gap: 0; }
}

/* ══════════════════════════════════════════════════════════
   The project view — shared-project-view.js
   ══════════════════════════════════════════════════════════
   Rendered in two places from one renderer: the Overview tab inside
   /student.html, and the whole of /project.html, the share link. Both load this
   stylesheet, which is why these rules live here rather than beside either page.

   The accent. --proj-accent-hue is set on the root element by the renderer,
   seeded off the project title, and everything below derives from it in hsl().
   That is the entire extent of the per-project theming: no second palette, no
   template variants, no motifs. The generated student site had all three and it
   was right for a self-contained file; here the structure, the type and the
   spacing are the app's, and the hue is what makes one class's project not look
   like another's with the words swapped.

   Deliberately NOT --flame. The accent has to move per project, and a rule that
   sometimes resolves to the brand colour and sometimes does not would make the
   brand read as arbitrary. The two live side by side: --flame stays on the tab
   underline and the topbar, the project hue on the project's own content. */

.proj-wrap { flex: 1; min-height: 0; overflow-y: auto; padding: 32px 40px 72px; }
/* Like .stu-inner, this no longer caps: the bands reach the padding edge and the
   measure sits on the things inside them. width:100% stays for the auto-margin
   reason .stu-inner, .impl-inner and .full-page-inner each shipped without once. */
.proj-inner { max-width: none; margin: 0 auto; width: 100%; }
.proj-masthead, #projMessage {
  max-width: var(--reading-max); margin-left: auto; margin-right: auto;
}

.proj-view {
  --proj-accent-hue: 18;
  /* ── the four solved lightnesses ──────────────────────────────────────
     Every one is computed per hue by shared-project-view.js and set as an
     inline property; the literals here are only the pre-render fallback. They
     are solved rather than chosen because a flat lightness does not hold
     contrast across the hue circle — a flat 42% put 170 of the 360 possible
     hues below AA, bottoming out at 2.68:1 on 10px text — and which hue a
     project gets depends on its TITLE, so the failure is invisible until it
     lands on somebody's class.

     tools/proj-contrast-sweep.js measures all four across all 360 hues in both
     themes on every `npm run check`, against the shipped functions rather than
     copies of them. If you change a saturation here, change it there. */
  --proj-accent-l: 42%;        /* accent text on paper, >= 4.6:1 */
  --proj-band-l: 30%;          /* white on the hero band,  >= 7:1 */
  --proj-mid-l: 60%;           /* ink on the open phase,   >= 7:1 */
  --proj-dark-band-l: 30%;     /* forge theme's band; unused in :root */

  --proj-accent: hsl(var(--proj-accent-hue) 52% var(--proj-accent-l));
  --proj-accent-soft: hsl(var(--proj-accent-hue) 46% 96%);
  --proj-accent-line: hsl(var(--proj-accent-hue) 40% 82%);
  --proj-paper: hsl(var(--proj-accent-hue) 34% 96.5%);
  --proj-band: hsl(var(--proj-accent-hue) 52% var(--proj-band-l));
  --proj-mid: hsl(var(--proj-accent-hue) 45% var(--proj-mid-l));

  /* ── BANDS OWN THEIR OWN FOREGROUND ───────────────────────────────────
     Inside a band the text is --proj-on-band, or an alpha of it at 0.80 or
     above. NEVER --proj-accent: it measures 2.34:1 on the dark band and 1.75:1
     on the light mid tone. That one rule replaces four more bisections, and
     the last line of each sweep in proj-contrast-sweep.js exists to catch it
     being broken. */
  --proj-on-band: #ffffff;
  --proj-motif: none;

  /* The bleed distance is the scroll container's OWN horizontal padding, and
     nothing else. Not 100vw: .stu-wrap computes overflow-x:hidden (from
     .app-view's `overflow:hidden` plus this file's overflow-y override), so a
     100vw child is not scrolled but silently CLIPPED — and centred against the
     viewport rather than the scrollport, which lands it half a scrollbar left
     of every other measure on the page. That is 0px on macOS overlay
     scrollbars and ~7.5px wrong on a Chromebook, which is what students use. */
  --proj-bleed: 0px;
  --proj-measure: 760px;
}
.stu-wrap .proj-view, .proj-wrap .proj-view { --proj-bleed: 40px; }

/* margin-inline:-P with width:auto resolves to (containing block + 2P), which
   is the padding box exactly. Do NOT add width:100% — that re-adds 2P of real
   overflow and gets clipped on the right. Keep .proj-view in normal block flow:
   under display:flex this becomes a cross-axis stretch and stops being
   obviously true. */
.proj-band {
  margin-inline: calc(-1 * var(--proj-bleed));
  padding-inline: var(--proj-bleed);
}
.proj-band-inner, .proj-section { max-width: var(--proj-measure); margin-inline: auto; }
/* The dark theme INVERTS the band. A band deeper than --bg (#151310) is not
   available, so it goes LIGHTER than the paper and carries --ink rather than
   white — pure white would be the only white text in the theme.
   --proj-accent-l and --proj-band-l are deliberately not read here, so a solved
   light value cannot leak into the dark palette.

   The accent stays FIXED at 62%/68% because its ground is fixed (--card, worst
   4.65:1) and only one side varies. The band is SOLVED because its ground
   varies per hue while the ink is fixed — the same problem mirrored. */
[data-theme="forge"] .proj-view {
  --proj-accent: hsl(var(--proj-accent-hue) 62% 68%);
  --proj-accent-soft: hsl(var(--proj-accent-hue) 24% 16%);
  --proj-accent-line: hsl(var(--proj-accent-hue) 30% 34%);
  --proj-paper: hsl(var(--proj-accent-hue) 10% 10.5%);
  --proj-band: hsl(var(--proj-accent-hue) 30% var(--proj-dark-band-l));
  --proj-mid: hsl(var(--proj-accent-hue) 22% 30%);
  --proj-on-band: var(--ink);
}

/* ── the hero band ──
   A field, not a heading. The single largest difference between a page that
   reads as a document inside a tool and one that reads as a place is whether
   the top of it is a coloured surface you arrive on. The motif is painted in
   white at low alpha over that surface, so one tile serves both themes — the
   band underneath carries the hue. */
.proj-hero {
  background-color: var(--proj-band);
  background-image: var(--proj-motif);
  background-repeat: repeat;
  color: var(--proj-on-band);
  padding-top: 44px; padding-bottom: 38px;
  margin-bottom: 38px;
}
.proj-hero-eyebrow {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--proj-on-band); opacity: 0.80;   /* 0.72 fails AA on some hues */
  margin-bottom: 10px;
}
.proj-title {
  font-family: var(--serif); font-size: clamp(1.9rem, 4vw, 2.6rem); font-weight: 700;
  color: var(--proj-on-band); margin: 0 0 12px; line-height: 1.15;
}
.proj-onesentence {
  font-family: var(--sans); font-size: 15.5px;
  color: var(--proj-on-band); opacity: 0.86;
  line-height: 1.7; margin: 0; max-width: 62ch;
}
/* The meta row — mono, because a label voice distinct from both the display and
   the body is most of what makes a briefing read as a briefing. Every value in
   it was already on the run and simply thrown away. */
.proj-meta {
  display: flex; flex-wrap: wrap; gap: 26px; margin-top: 22px;
}
.proj-meta-item { display: flex; flex-direction: column; gap: 3px; }
.proj-meta-label {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--proj-on-band); opacity: 0.80;
}
.proj-meta-value {
  font-family: var(--sans); font-size: 14px; font-weight: 600;
  color: var(--proj-on-band);
}
/* Where the class is. Inside the band, so it takes the band's own colour — not
   the accent, which is unreadable there. */
.proj-hero-state {
  margin-top: 20px; display: inline-block;
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--proj-on-band);
  border-left: 2px solid var(--proj-on-band); padding-left: 10px;
}

/* ── sections ── */
.proj-section { margin-bottom: 44px; }
.proj-eyebrow {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--ink3); margin-bottom: 14px;
}
.proj-scenario {
  font-family: var(--serif); font-size: 19px; line-height: 1.66;
  color: var(--ink); margin: 0; max-width: 60ch;
}

/* ── the entry artifact ──
   pre-wrap rather than splitting on newlines in JS: a memo with a sign-off and
   one written as a single paragraph both come out the way the teacher approved
   them, and neither needs the renderer to have an opinion about its shape. */
.proj-memo {
  white-space: pre-wrap;
  font-family: var(--sans); font-size: 14px; line-height: 1.7;
  color: var(--ink); background: var(--proj-accent-soft);
  border: 1px solid var(--proj-accent-line);
  border-radius: var(--radius); padding: 22px 24px;
}

/* ── roles ── */
.proj-roles {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.proj-role {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px 18px;
}
.proj-role-name {
  font-family: var(--sans); font-size: 14.5px; font-weight: 700;
  color: var(--ink); margin: 0 0 8px;
}
.proj-role-desc {
  font-family: var(--sans); font-size: 13.5px; line-height: 1.6;
  color: var(--ink2); margin: 0;
}
.proj-role-more { margin-top: 10px; }
.proj-role-summary {
  cursor: pointer; list-style: none;
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--proj-accent);
}
.proj-role-summary::-webkit-details-marker { display: none; }
.proj-role-summary::after { content: " →"; }
.proj-role-more[open] .proj-role-summary::after { content: " ↑"; }
.proj-role-day {
  font-family: var(--sans); font-size: 13px; line-height: 1.65;
  color: var(--ink2); margin: 9px 0 0;
  padding-top: 9px; border-top: 1px solid var(--line);
}

/* ── the arc ── */
.proj-phases { display: flex; flex-direction: column; gap: 12px; }
.proj-phase {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* The phase the class is in now. A left rule rather than a fill, so a page of
   phases still reads as one list with one of them marked. */
.proj-phase-now { border-left: 3px solid var(--proj-accent); }

/* The closed row. Padding lives here rather than on .proj-phase so the whole
   bar is the hit target, which is what a collapsed row has to be. */
.proj-phase-summary {
  cursor: pointer; list-style: none; padding: 16px 20px 16px 42px;
  position: relative; border-radius: var(--radius);
}
.proj-phase-summary::-webkit-details-marker { display: none; }
/* The chevron, drawn rather than a glyph so it rotates. Positioned against the
   first line of the row, not centred on it — a phase whose deliverable list
   wraps to two lines would otherwise float its marker into the middle. */
.proj-phase-summary::before {
  content: ""; position: absolute; left: 20px; top: 21px;
  width: 6px; height: 6px; border-right: 1.5px solid var(--ink3);
  border-bottom: 1.5px solid var(--ink3); transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.proj-phase[open] > .proj-phase-summary::before { transform: rotate(45deg); top: 19px; }
.proj-phase-summary:hover { background: var(--bg2); }
.proj-phase-summary:focus-visible { outline: 2px solid var(--proj-accent); outline-offset: -2px; }
.proj-phase-body { padding: 0 20px 18px 42px; }

.proj-phase-num {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--proj-accent); margin-bottom: 6px;
}
.proj-phase-title {
  font-family: var(--serif); font-size: 23px; font-weight: 700;
  color: var(--ink); margin: 0; line-height: 1.25; display: inline;
}
/* An OPEN phase gets a ground of its own. This is what a collapsible row can
   borrow from the export's full-page phase without becoming one: the phase you
   are in stops being a row in a list and becomes a place on the page, while the
   whole arc stays scannable above and below it. */
.proj-phase[open] {
  background-color: var(--proj-paper);
  border-color: var(--proj-accent-line);
}
.proj-phase[open] > .proj-phase-summary { background-image: var(--proj-motif); }
.proj-phase[open] > .proj-phase-summary:hover { background-color: transparent; }
/* What the phase produces, on the closed row. Without it a collapsed arc is a
   list of numbers and a reader has to open every one to find anything. */
.proj-phase-produces {
  font-family: var(--sans); font-size: 12.5px; color: var(--ink3);
  margin-top: 5px; line-height: 1.5;
}
/* Serif, italic, behind a rule. In the export this one treatment is most of why
   a phase reads as an invitation rather than a row in a table of contents. */
.proj-phase-hook {
  font-family: var(--serif); font-style: italic; font-size: 16.5px;
  line-height: 1.6; color: var(--ink2); margin: 0;
  border-left: 3px solid var(--proj-accent); padding-left: 16px;
  max-width: 56ch;
}
.proj-deliv-label {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink3); margin: 20px 0 10px;
}
.proj-deliv {
  border-top: 1px solid var(--line); padding-top: 11px; margin-top: 11px;
}
.proj-deliv:first-of-type { border-top: none; padding-top: 0; margin-top: 0; }
.proj-deliv-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.proj-deliv-title {
  font-family: var(--sans); font-size: 14px; font-weight: 650; color: var(--ink);
}
.proj-deliv-final {
  font-family: var(--sans); font-size: 10px; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--proj-accent); background: var(--proj-accent-soft);
  border: 1px solid var(--proj-accent-line);
  border-radius: 3px; padding: 2px 6px;
}
.proj-deliv-due {
  font-family: var(--sans); font-size: 12px; color: var(--ink3); margin-top: 4px;
}
.proj-deliv-desc {
  font-family: var(--sans); font-size: 13.5px; line-height: 1.62;
  color: var(--ink2); margin: 7px 0 0;
}
.proj-reqs-head {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--proj-accent); margin: 14px 0 8px;
}
/* The marker is a ::before, NOT a character in the markup, and that is not a
   style preference. student-view-test.js asserts that the arc's requirement
   text is string-equal to the Deliverables tab's, and textContent concatenates
   descendants — a glyph in a span would break that equality while the page
   looked perfect. Decoration belongs in CSS for exactly this reason. */
.proj-reqs { margin: 0; padding-left: 0; list-style: none; }
.proj-req {
  font-family: var(--sans); font-size: 14px; line-height: 1.6;
  color: var(--ink2); margin-bottom: 7px; padding-left: 18px; position: relative;
}
.proj-req::before {
  content: "\203A"; position: absolute; left: 0; top: -1px;
  color: var(--proj-accent); font-weight: 700;
}

/* ── the role strip ──
   A picker that marks one card and changes no text, because there are no
   per-role hooks to change it to. Buttons, not links: nothing navigates. */
.proj-rolebar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 14px;
}
.proj-rolebar-label {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink3);
  margin-right: 3px;
}
.proj-rolebar-btn {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--ink2); background: var(--card);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 5px 13px; cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.proj-rolebar-btn:hover { color: var(--ink); border-color: var(--line2); }
.proj-rolebar-btn[aria-pressed="true"] {
  color: var(--proj-accent); border-color: var(--proj-accent);
  background: var(--proj-accent-soft);
}
.proj-role-mine {
  border-color: var(--proj-accent);
  box-shadow: inset 3px 0 0 var(--proj-accent);
}

/* ── the look ──
   impl-look-*: the colour and texture picker, one section of the Student &
   family page panel (impl-page-look.js). The preview carries .proj-view, so
   the real proj- rules paint it from the same six custom properties the page
   sets — nothing here restyles the page; it frames it. */
.impl-look-lede {
  font-family: var(--sans); font-size: 13px; line-height: 1.6;
  color: var(--ink2); margin: 6px 0 12px; max-width: 76ch;
}
.impl-look-preview {
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  margin-bottom: 8px; --proj-bleed: 0px;
}
.impl-look-preview .proj-hero { margin-bottom: 0; padding-top: 24px; padding-bottom: 22px; }
.impl-look-preview .proj-title { font-size: 1.5rem; }
.impl-look-paper {
  background: var(--proj-paper); padding: 14px 18px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.impl-look-paper .proj-eyebrow, .impl-look-paper .proj-phase-num { margin: 0; }
.impl-look-status {
  font-family: var(--sans); font-size: 12px; color: var(--ink3); margin-bottom: 14px;
}
.impl-look-field { margin-bottom: 14px; }
.impl-look-label {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink3); margin-bottom: 8px;
}
.impl-look-swatches { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.impl-look-swatch {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--sans); font-size: 12px; font-weight: 600; color: var(--ink2);
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  padding: 4px 11px 4px 6px; cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.impl-look-swatch:hover { color: var(--ink); border-color: var(--line2); }
.impl-look-swatch[aria-pressed="true"] { color: var(--ink); border-color: var(--ink); }
.impl-look-dot { width: 14px; height: 14px; border-radius: 50%; display: inline-block; }
.impl-look-slider {
  display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
  font-family: var(--sans); font-size: 12px; color: var(--ink2);
}
.impl-look-slider-name { flex: 0 0 auto; }
.impl-look-hue { flex: 1; max-width: 360px; accent-color: var(--ink); }
.impl-look-hue--seeded { opacity: 0.55; }
.impl-look-hue-value {
  font-family: var(--mono); font-size: 11.5px; color: var(--ink3); min-width: 3.5ch;
}
.impl-look-motifs {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px; margin-bottom: 10px;
}
.impl-look-motif {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 6px; cursor: pointer; text-align: left;
  transition: border-color 0.12s ease;
}
.impl-look-motif:hover { border-color: var(--line2); }
.impl-look-motif[aria-pressed="true"] { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
/* The motifs are drawn as tiles of different sizes (40 to 96 wide, 28 to 64
   tall). Scaled so ONE repeat fills the box's height, they repeat seamlessly
   sideways the way they do on the band; at their native size a 48px box
   showed a cropped slice with the next row's tops peeking in underneath. */
.impl-look-tile {
  display: block; height: 48px; border-radius: 3px;
  background-repeat: repeat; background-size: auto 100%; background-position: left top;
}
.impl-look-motif-name { font-family: var(--sans); font-size: 12px; font-weight: 600; color: var(--ink); }
.impl-look-motif-family { font-family: var(--sans); font-size: 10.5px; color: var(--ink3); }
.impl-look-seeded, .impl-look-guessed { margin-top: 2px; }
.impl-look-seeded[aria-pressed="true"], .impl-look-guessed[aria-pressed="true"] { color: var(--ink); font-weight: 700; }
.impl-look-actions { display: flex; gap: 7px; align-items: center; margin-top: 4px; }
.impl-look-error {
  font-family: var(--sans); font-size: 12.5px; color: var(--flame); margin-top: 8px;
}

/* ── the three tabs ──
   The share link's frame: Current, Overview, The Plan. Same bones as .stu-tabs,
   with the underline in the project's own accent because every colour on this
   surface is the project's. .proj-pane has NO display of its own — the panes
   are switched with the `hidden` attribute, and a display rule here would
   override it (the .stu-panel note above). */
.proj-tabs {
  display: flex; gap: 2px; border-bottom: 1.5px solid var(--line);
  max-width: var(--proj-measure); margin: 0 auto 30px;
}
.proj-tab {
  background: none; border: none; cursor: pointer;
  font-family: var(--sans); font-size: 14px; font-weight: 600;
  color: var(--ink3); padding: 10px 16px; margin-bottom: -1.5px;
  border-bottom: 2px solid transparent; white-space: nowrap;
  transition: color 0.13s, border-color 0.13s;
}
.proj-tab:hover { color: var(--ink2); }
.proj-tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--proj-accent); }
.proj-tab:focus-visible { outline: 2px solid var(--proj-accent); outline-offset: -2px; border-radius: 3px; }
.proj-pane { max-width: var(--proj-measure); margin-inline: auto; }
.proj-pane-empty {
  font-family: var(--sans); font-size: 14px; line-height: 1.6;
  color: var(--ink3); padding: 28px 0;
}

/* ── where the class is ──
   The one card on the page that changes week to week. Ink on card and accent
   on card only — both pairs are in the contrast sweep; the band colours are not
   used here on purpose. */
.proj-now {
  background: var(--card); border: 1px solid var(--proj-accent-line);
  border-left: 3px solid var(--proj-accent);
  border-radius: var(--radius); padding: 20px 24px;
  display: flex; flex-direction: column; gap: 22px;
}
.proj-now-state {
  font-family: var(--sans); font-size: 14px; font-weight: 600;
  color: var(--ink2); line-height: 1.5;
}
.proj-now-open, .proj-now-next, .proj-now-last { display: block; }
.proj-now-last { opacity: 0.78; }
.proj-now-label {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--proj-accent); margin-bottom: 6px;
}
.proj-now-title {
  font-family: var(--serif); font-size: 24px; font-weight: 700;
  color: var(--ink); line-height: 1.2;
}
.proj-now-phase {
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  color: var(--ink3); margin-top: 4px;
}
.proj-now-desc {
  font-family: var(--serif); font-size: 16.5px; line-height: 1.65;
  color: var(--ink); margin: 12px 0 0; max-width: 58ch;
  white-space: pre-wrap;
}
/* ── a session's materials ──
   Drawn by projectViewAttachments on the share link's card and on the
   student's Today. Ink on card for the title and accent on card for the kind
   label — both pairs already in the contrast sweep. On Today there is no
   .proj-view around it, so the accent falls back to the brand's --flame. */
.proj-attach { margin-top: 16px; max-width: 58ch; }
.proj-attach-head {
  font-family: var(--sans); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink3);
  margin-bottom: 8px;
}
.proj-attach-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.proj-attach-item {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  text-decoration: none; transition: border-color 0.12s ease;
}
.proj-attach-item:hover { border-color: var(--proj-accent, var(--flame)); }
.proj-attach-item:focus-visible { outline: 2px solid var(--proj-accent, var(--flame)); outline-offset: 2px; }
.proj-attach-kind {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.08em; color: var(--proj-accent, var(--flame)); flex: 0 0 auto;
}
.proj-attach-title {
  font-family: var(--sans); font-size: 14.5px; font-weight: 600; line-height: 1.35;
  flex: 1 1 12ch; min-width: 0; overflow-wrap: anywhere;
}
.proj-attach-meta { font-family: var(--sans); font-size: 12.5px; color: var(--ink3); }

/* ── today: materials from sessions already taught ── */
.stu-earlier { margin-top: 30px; }
.stu-earlier-head {
  font-family: var(--sans); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink3);
  margin-bottom: 4px;
}
.stu-earlier-session { margin-top: 14px; }
.stu-earlier-line { font-family: var(--sans); font-size: 13px; font-weight: 600; color: var(--ink2); }
.stu-earlier-session .proj-attach { margin-top: 6px; }
.stu-earlier-session .proj-attach-head { display: none; }

/* The card pages: one slide in the body, the arrows under it. The body keeps
   the card's own column gap so the between-sessions pair sits as it did before
   the card could page. A slide away from the position is tinted like a taught
   session, so the eye knows it is not looking at this week. */
.proj-now-body { display: flex; flex-direction: column; gap: 22px; }
.proj-now-nav {
  display: flex; align-items: center; gap: 10px;
  padding-top: 14px; border-top: 1px solid var(--proj-accent-line);
}
.proj-now-arrow, .proj-now-home {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600; cursor: pointer;
  background: var(--card); color: var(--ink2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 7px 12px; line-height: 1.2;
  transition: border-color 0.12s ease;
}
.proj-now-arrow:hover, .proj-now-home:hover { border-color: var(--line2); color: var(--ink); }
.proj-now-arrow:focus-visible, .proj-now-home:focus-visible {
  outline: 2px solid var(--proj-accent); outline-offset: 2px;
}
.proj-now-arrow:disabled { opacity: 0.38; cursor: default; }
.proj-now-arrow:disabled:hover { border-color: var(--line); color: var(--ink2); }
.proj-now-fwd { margin-left: auto; }
.proj-now-home { color: var(--proj-accent); }
.proj-now-home[hidden] { display: none; }
.proj-now-away { border-left-color: var(--proj-accent-line); }

/* ── progress ──
   Pills in a row that scrolls sideways rather than wraps, so eight phases on a
   phone read as one line the thumb can move along. The phase the class is in
   takes the mid tone under ink, which is one of the four solved pairs. */
.proj-steps {
  list-style: none; margin: 0; padding: 2px 0 8px;
  display: flex; gap: 8px; overflow-x: auto;
  scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
}
.proj-step { flex: 0 0 auto; scroll-snap-align: start; }
.proj-step-btn {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  min-width: 132px; max-width: 200px; text-align: left; cursor: pointer;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 10px 14px;
  transition: border-color 0.12s ease;
}
.proj-step-btn:hover { border-color: var(--line2); }
.proj-step-btn:focus-visible { outline: 2px solid var(--proj-accent); outline-offset: 2px; }
.proj-step-num {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--proj-accent);
}
.proj-step-title {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: var(--ink);
  line-height: 1.35; overflow: hidden; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.proj-step-done .proj-step-btn { background: var(--bg2); }
.proj-step-done .proj-step-num, .proj-step-done .proj-step-title { color: var(--ink3); }
.proj-step-now .proj-step-btn {
  background-color: var(--proj-mid); border-color: var(--proj-mid);
  background-image: var(--proj-motif);
}
.proj-step-now .proj-step-num, .proj-step-now .proj-step-title { color: var(--ink); }

/* ── deadlines ──
   A date column and a title column, like a calendar's day list. The date is the
   accent on card, the same pair the Final chip already uses. */
.proj-upcoming { display: flex; flex-direction: column; gap: 8px; }
.proj-upcoming-item {
  display: flex; gap: 16px; align-items: flex-start; width: 100%; text-align: left;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 16px; cursor: pointer;
  transition: border-color 0.12s ease;
}
.proj-upcoming-item:hover { border-color: var(--proj-accent); }
.proj-upcoming-item:focus-visible { outline: 2px solid var(--proj-accent); outline-offset: 2px; }
/* The deliverable a deadline just sent the reader to, lit until the eye finds it. */
.proj-deliv-lit { box-shadow: 0 0 0 2px var(--proj-accent); border-radius: var(--radius-sm); transition: box-shadow 0.6s ease; }
.proj-upcoming-date {
  flex: 0 0 74px; font-family: var(--sans); font-size: 12.5px; font-weight: 700;
  color: var(--proj-accent); padding-top: 2px; line-height: 1.4;
}
.proj-upcoming-body { flex: 1; min-width: 0; }
.proj-upcoming-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.proj-upcoming-title {
  font-family: var(--sans); font-size: 14.5px; font-weight: 650; color: var(--ink);
}
.proj-upcoming-phase {
  font-family: var(--sans); font-size: 12px; color: var(--ink3); margin-top: 3px;
}
.proj-upcoming-empty {
  font-family: var(--sans); font-size: 13.5px; line-height: 1.6; color: var(--ink3);
}

/* ── the share link's own frame ──
   /project.html has no topbar, no nav and no theme toggle, so it carries its own
   masthead. Deliberately says ForgePBL and nothing about signing in: there is no
   account this reader is expected to have, and a Sign In button on a share link
   is an invitation to a door that would refuse them. */
.proj-masthead {
  border-bottom: 1px solid var(--line); padding: 16px 0 14px; margin-bottom: 30px;
}
.proj-mark {
  font-family: var(--serif); font-size: 13px; font-weight: 700;
  letter-spacing: 0.04em; color: var(--ink3);
}
.proj-gone { padding: 60px 0; max-width: 52ch; }
.proj-gone-title {
  font-family: var(--serif); font-size: 22px; font-weight: 700;
  color: var(--ink); margin-bottom: 8px;
}
.proj-gone-body {
  font-family: var(--sans); font-size: 14px; line-height: 1.6; color: var(--ink2);
}

/* The two containers change padding at DIFFERENT widths — .stu-wrap at 860px,
   .proj-wrap at 560px — so the bleed has to follow each one separately. If you
   change a padding above, change its bleed here or the bands stop reaching the
   edge on that surface only. deadcss.js does not look inside @media, so nothing
   will tell you. */
@media (max-width: 860px) {
  .stu-wrap .proj-view { --proj-bleed: 18px; }
}
@media (max-width: 560px) {
  .proj-wrap { padding: 24px 20px 60px; }
  .proj-wrap .proj-view { --proj-bleed: 20px; }
  .proj-hero { padding-top: 32px; padding-bottom: 28px; }
  .proj-phase-summary { padding: 14px 16px 14px 36px; }
  .proj-phase-summary::before { left: 16px; }
  .proj-phase-body { padding: 0 16px 16px 36px; }
  .proj-phase-title { font-size: 20px; }
  .proj-meta { gap: 16px; }
  /* The role cards become a strip the thumb moves along rather than a column
     to scroll past: four jobs is two screens stacked and one swipe sideways. */
  .proj-roles {
    display: flex; overflow-x: auto; gap: 12px; padding-bottom: 6px;
    scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
  }
  .proj-role { flex: 0 0 82%; scroll-snap-align: start; }
  .proj-tabs { overflow-x: auto; }
  .proj-now { padding: 16px 18px; }
  .proj-now-title { font-size: 21px; }
  .proj-upcoming-date { flex-basis: 62px; }
}
