/* LearnCore official visual system — "Executive Blue + Accent Expansion".
   Locked core palette (brand-mandated, do not improvise substitutes):
     Midnight Navy #0A1428 · Executive Blue #27507A (signature) ·
     Slate Blue #5C6F8D · White #FFFFFF · Cloud Slate #64748B
   Accent expansion (used intentionally, not decoratively):
     Signal Coral #FF6B57 (CTAs/alerts) · Electric Teal #14C8C4 (highlights/active) ·
     Golden Amber #FFB547 (warnings/callouts) · Violet Pulse #8B5CF6 (admin/special) ·
     Fresh Lime #84CC16 (success/progress) · Raspberry Magenta #E94E9A (badges/emphasis) */
:root {
  --primary-blue: #27507A;   /* Executive Blue — signature */
  --midnight-navy: #0A1428;
  --slate-blue: #5C6F8D;
  --cloud-slate: #64748B;
  --light-blue: #DCEBFF;
  --slate-navy: #0A1428;     /* Midnight Navy — sidebar / secondary buttons */
  --soft-teal: #14C8C4;      /* Electric Teal — highlights / active states */
  --warm-coral: #FF6B57;     /* Signal Coral — CTAs / alerts / danger */
  --accent-amber: #FFB547;   /* warnings / callouts */
  --accent-violet: #8B5CF6;  /* admin / special / reserved */
  --accent-lime: #84CC16;    /* success / progress */
  --accent-magenta: #E94E9A; /* badges / emphasis */
  --ink: #0A1428;
  --muted: #64748B;
  --line: #DDE3EC;
  --panel: #FFFFFF;
  --surface: #F5F8FC;
  --disabled: #EEF1F5;
  --shadow: 0 18px 45px rgba(10, 20, 40, 0.12);
  --radius-lg: 24px;
  --radius-md: 18px;
  --radius-sm: 12px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 400;
  color: var(--ink);
  background: linear-gradient(135deg, #F7FAFF 0%, #EEF5FF 46%, #F8FBFA 100%);
}

/* Locked typography — see BRAND.md. Headings: Space Grotesk Semibold.
   Body: Inter Regular. UI/buttons/nav/labels: Inter Medium. */
h1, h2, h3, h4 {
  font-family: "Space Grotesk", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 600;
}

button, input, textarea { font: inherit; }
.btn, label, .admin-nav-item, .nav-row a, .eyebrow { font-family: Inter, ui-sans-serif, system-ui, sans-serif; font-weight: 500; }

a { color: var(--primary-blue); }

.wrap {
  max-width: 920px;
  margin: 0 auto;
  padding: 32px 24px 64px;
}

/* ---- Student left sidebar (2026-07-18) ----
   Replaces the old top nav bar with a fixed left column, per Anthony's
   request: "put the menu bar... on the side... just a white box with the
   menu choices." .page-shell is the outermost container partials/head.ejs
   always opens and partials/foot.ejs always closes (exactly one <div> +
   one <main>, regardless of which branch head.ejs took) -- by default it
   behaves exactly like the old .wrap (centered, max-width, padded); when
   .has-student-sidebar is added, it becomes a sidebar-+ main-content row
   instead, and .page-main takes over .wrap's old centering/padding job. */
.page-shell {
  max-width: 920px;
  margin: 0 auto;
  padding: 32px 24px 64px;
}
.page-shell.has-student-sidebar {
  max-width: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: stretch;
  min-height: 100vh;
}
.has-student-sidebar .page-main {
  flex: 1;
  min-width: 0;
  max-width: 920px;
  margin: 0 auto;
  padding: 32px 24px 64px;
}

.student-sidebar-toggle-btn { display: none; }

.student-sidebar {
  width: 250px;
  flex-shrink: 0;
  background: var(--panel);
  border-right: 1px solid var(--line);
  padding: 22px 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  overflow-y: auto;
}

.student-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 8px;
  text-decoration: none;
  color: inherit;
}
.student-brand .brand-mark { width: 38px; height: 38px; font-size: 14px; }
.student-brand h1 { margin: 0; font-size: 15px; color: var(--ink); }
.student-brand p { margin: 0; font-size: 10px; color: var(--muted); letter-spacing: 0.08em; text-transform: uppercase; }

/* Greeting + ABēi mascot -- see the #sidebarAbeiGreet override further down
   for the one-time (not looping) "hi" animation. */
.student-greet {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 8px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.student-greet-hi { margin: 0; font-weight: 700; font-size: 15px; color: var(--ink); overflow-wrap: anywhere; }

.student-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; overflow-y: auto; }
.student-nav-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  color: var(--ink);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
}
.student-nav-item:hover { background: var(--light-blue); }
span.student-nav-item.disabled { color: var(--muted); cursor: default; }
.student-sidebar-foot { border-top: 1px solid var(--line); padding-top: 10px; }
a.student-nav-item.logout { color: var(--muted); }
a.student-nav-item.logout:hover { background: var(--disabled); color: var(--ink); }

/* One-time "hi" wave for the sidebar greeting mascot (2026-07-18, Anthony's
   request: "Anmation need only run once then stay static as to not
   distract student"). Overrides the abei_mascot partial's own default
   continuous idle "breathing" glow loop for this one specific instance --
   #sidebarAbeiGreet is more specific than the partial's own .abei-mascot
   class selector, so this wins. Runs once (no infinite), holds its final
   frame (both fill mode), then just sits there like a face on a badge. */
#sidebarAbeiGreet.abei-mascot .abei-face-idle {
  animation: student-greet-wave 0.9s ease-out 1 both;
}
@keyframes student-greet-wave {
  0% { transform: rotate(0deg) scale(1); filter: drop-shadow(0 0 2px rgba(168,85,247,.35)); }
  35% { transform: rotate(-10deg) scale(1.1); filter: drop-shadow(0 0 8px rgba(168,85,247,.8)); }
  65% { transform: rotate(7deg) scale(1.04); filter: drop-shadow(0 0 5px rgba(168,85,247,.55)); }
  100% { transform: rotate(0deg) scale(1); filter: drop-shadow(0 0 2px rgba(168,85,247,.35)); }
}
@media (prefers-reduced-motion: reduce) {
  #sidebarAbeiGreet.abei-mascot .abei-face-idle { animation: none !important; }
}

/* The floating Feedback button (bottom-left, fixed to the viewport) would
   otherwise sit on top of the sidebar's own bottom-of-list Log out link on
   desktop, same class of overlap already fixed for the staff sidebar
   (2026-07-13/16). #lcFeedbackWidget is a sibling of .page-shell (see
   partials/foot.ejs), so this only applies when the student sidebar is
   actually showing. */
.page-shell.has-student-sidebar ~ #lcFeedbackWidget { left: 270px; }

@media (max-width: 900px) {
  .student-sidebar { width: 210px; }
  .page-shell.has-student-sidebar ~ #lcFeedbackWidget { left: 230px; }
}
@media (max-width: 680px) {
  .page-shell.has-student-sidebar { display: block; }
  /* Sidebar collapses to an off-canvas drawer below this breakpoint, so
     Feedback goes back to its normal bottom-left spot. */
  .page-shell.has-student-sidebar ~ #lcFeedbackWidget { left: 20px; }
  .student-sidebar-toggle-btn {
    display: inline-block;
    position: fixed;
    top: 14px;
    left: 14px;
    z-index: 45;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 20px;
    line-height: 1;
    padding: 6px 10px;
    cursor: pointer;
    color: var(--ink);
    box-shadow: var(--shadow);
  }
  .has-student-sidebar .student-sidebar {
    position: fixed;
    left: 0;
    top: 0;
    width: 78vw;
    max-width: 280px;
    z-index: 44;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: 2px 0 16px rgba(10,20,40,0.18);
  }
  .has-student-sidebar .student-sidebar.is-open { transform: translateX(0); }
  .has-student-sidebar .page-main { padding-top: 68px; }
}

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin-bottom: 24px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Logo/wordmark as a "home" link (2026-07-14): .brand (student topbar) and
   .admin-brand (staff sidebar) are both <a href="/"> now, so undo the
   browser's default link underline/color-inherit-breaking behavior — the
   text should look exactly like it did as a plain div. */
a.brand, a.admin-brand {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}

.brand-mark {
  width: 44px;
  height: 44px;
  border-radius: 14px;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, var(--primary-blue), var(--soft-teal));
  color: #fff;
  font-weight: 800;
  letter-spacing: 0.04em;
}

.brand h1 { margin: 0; font-size: 18px; }
.eyebrow { margin: 0; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }

/* Topbar nav links + mobile hamburger toggle (2026-07-13, mobile-usability
   audit). Desktop/tablet: identical to the old inline style (flex row). Below
   a phone-width breakpoint, the links row collapses into a hidden vertical
   panel and a hamburger button appears to reveal/hide it, so up to 7 nav
   items never overflow the topbar on a phone screen. */
.topbar-links {
  display: flex;
  align-items: center;
  gap: 14px;
}
.topbar-toggle-btn {
  display: none;
  background: none;
  border: 1px solid var(--border, #dde3ea);
  border-radius: 8px;
  font-size: 20px;
  line-height: 1;
  padding: 6px 10px;
  cursor: pointer;
  color: var(--ink, #1b2430);
}
@media (max-width: 680px) {
  .topbar { flex-wrap: wrap; }
  .topbar-toggle-btn { display: inline-block; }
  .topbar-links {
    display: none;
    width: 100%;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--border, #dde3ea);
  }
  .topbar-links.is-open { display: flex; }
}

.pill {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: var(--disabled);
  color: var(--muted);
}
.pill.safe { background: rgba(132, 204, 22, 0.15); color: #3F6212; }
.pill.warning { background: rgba(255, 181, 71, 0.20); color: #92400E; }
.pill.neutral { background: var(--light-blue); color: var(--primary-blue); }
/* "Missed" flag on My Assignments (2026-07-14) — Signal Coral is BRAND.md's
   reserved alert/danger accent, a real semantic fit here (not decorative). */
.pill.danger { background: rgba(255, 107, 87, 0.16); color: #9A2E1F; }

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 24px;
  margin-bottom: 20px;
}

.card h2, .card h3 { margin-top: 0; }

.grid {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

.tile {
  display: block;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  padding: 18px;
  text-decoration: none;
  color: inherit;
}
.tile h3 { margin: 8px 0 4px; color: var(--ink); }
.tile p { margin: 0; color: var(--muted); font-size: 14px; }

/* Date-gated student course tiles (2026-07-19, Anthony's request): a course
   shell with no start date yet, one that hasn't opened yet, or one that's
   already ended is greyed out and not clickable on the student side --
   staff access/management is completely unaffected by this, see
   studentCourseAvailability() in server.js. */
.tile-unavailable { cursor: default; opacity: 0.55; }
.tile-unavailable img { filter: grayscale(85%); }
.tile-unavailable .pill { background: var(--disabled, #E5E7EB); color: var(--muted); }

/* Student My Courses / Past Courses drag-and-drop (2026-07-21, Anthony's
   request — extend the same drag-handle reorder pattern to the student
   side). .tile itself stays untouched (still a plain block <a>/<div>) --
   each one is wrapped in this relatively-positioned container so the grip
   handle can sit on top of it without disturbing the tile's own layout. This
   is a purely personal ordering (see sortByPersonalOrder in server.js), so
   it lives on the student's own user record, never the shared course
   record. */
.tile-drag-wrap { position: relative; }
.tile-drag-wrap[draggable="true"] { cursor: grab; }
.tile-drag-wrap.dragging { opacity: 0.4; outline: 2px dashed var(--primary-blue); outline-offset: 2px; border-radius: var(--radius-md); }
.tile-drag-handle {
  position: absolute; left: 6px; top: 6px; z-index: 4;
  width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  color: var(--muted); font-size: 13px; line-height: 1; cursor: grab; user-select: none;
}
.tile-drag-handle:hover { background: var(--panel); color: var(--ink); }

.nav-row {
  display: flex;
  gap: 8px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.nav-row a {
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  background: var(--panel);
  border: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
}
.nav-row a.active { background: var(--slate-navy); color: #fff; border-color: var(--slate-navy); }

label { display: block; font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
input[type=text], input[type=password], input[type=date], input[type=number], input[type=datetime-local], textarea {
  width: 100%;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  margin-bottom: 14px;
  background: var(--surface);
  font-family: inherit;
  font-size: 14px;
  box-sizing: border-box;
}
textarea { min-height: 100px; resize: vertical; }
/* input[type=number] fix (2026-07-20, Anthony's report — Attendance Policy
   fields on Settings→Grading rendered garbled/overlapping). Root cause:
   number inputs were the one text-like input type missing from the rule
   above, so they fell back to the browser's tiny native default width with
   no font-family override — at that size, the native up/down spinner
   buttons visually crowd right on top of a 1-2 digit value. Now sized and
   styled the same as every other field. */

.btn {
  display: inline-block;
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  border: none;
  background: var(--primary-blue);
  color: #fff;
  font-weight: 700;
  cursor: pointer;
  text-decoration: none;
}
.btn.secondary { background: var(--slate-navy); }
.btn.danger { background: var(--warm-coral); }
.btn.small { padding: 6px 12px; font-size: 13px; }

/* ---- ABēi brand button image (real asset — see BRAND.md) ---- */
.btn-abei-img { border: none; background: none; padding: 0; cursor: pointer; line-height: 0; }
.btn-abei-img img { height: 42px; width: auto; display: block; border-radius: var(--radius-sm); }
.btn-abei-img:hover img { filter: brightness(1.12); }
.btn-abei-img:disabled { cursor: default; }
.btn-abei-img:disabled img { opacity: 0.4; filter: grayscale(0.4); }

/* ---- ABēi live mascot button (2026-07-15) — replaces the old static
   "ask-abei-button.png" artwork wherever a click actually triggers a real
   AI call. Icon + label pill, using BRAND.md's locked Violet Pulse accent
   (#8B5CF6) since the mascot itself is purple — the mascot's own CSS
   (partials/abei_mascot.ejs) handles the idle/thinking animation; this
   class is just the button chrome around it. ---- */
.btn-abei {
  display: inline-flex;
  align-items: center;
  background: var(--accent-violet, #8B5CF6);
  color: #fff;
  border: none;
  border-radius: 999px;
  padding: 7px 18px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  font-family: inherit;
  line-height: 1;
  transition: filter .15s ease;
}
.btn-abei:hover { filter: brightness(1.1); }
.btn-abei:disabled { cursor: default; opacity: .55; filter: grayscale(.35); }

/* 2026-07-16 fix (Anthony's report: the mascot's own purple glow sat
   directly on this button's matching purple fill, making the icon hard to
   see) — the mascot now sits just outside the button, in the surrounding
   card's white/light background, instead of inside the colored pill. */
.abei-btn-wrap { display: inline-flex; align-items: center; gap: 8px; }

.powered-by-abei { margin: 18px 0 0; text-align: center; }
.powered-by-abei img { width: 130px; max-width: 100%; height: auto; border-radius: var(--radius-md); }

.error-box {
  background: rgba(255, 107, 87, 0.12);
  color: #B3311E;
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  margin-bottom: 14px;
  font-size: 14px;
}
.success-box {
  background: rgba(132, 204, 22, 0.12);
  color: #3F6212;
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  margin-bottom: 14px;
  font-size: 14px;
}

/* "Grade saved" visual confirmation (2026-07-15, Anthony's request: "once
   grade accepted there should be some visual that a grade has been
   registered, big green check mark"). Shown once, right after a grade POST
   redirects back to the same submission/student card — pops in, holds a
   couple seconds, then fades so it doesn't linger indefinitely on a long
   roster/submission list. */
.grade-saved-flash {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: rgba(132, 204, 22, 0.15);
  color: #3F6212;
  border: 1px solid rgba(132, 204, 22, 0.45);
  border-radius: 999px;
  padding: 6px 16px 6px 12px;
  font-weight: 600;
  font-size: 14px;
  margin: 10px 0;
  animation: gradeSavedPop 0.35s ease-out, gradeSavedFadeOut 0.6s ease-in 2.6s forwards;
}
.grade-saved-flash .grade-saved-check {
  font-size: 22px;
  line-height: 1;
  color: var(--accent-lime, #84CC16);
}
@keyframes gradeSavedPop {
  0% { transform: scale(0.6); opacity: 0; }
  60% { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes gradeSavedFadeOut {
  to { opacity: 0; transform: translateY(-4px); }
}

.post {
  border-top: 1px solid var(--line);
  padding: 14px 0;
}
.post:first-child { border-top: none; }
.post .meta { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.post .body { white-space: pre-wrap; }
.reply { margin-left: 20px; border-left: 2px solid var(--line); padding-left: 14px; margin-top: 10px; }

.assignment-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 0;
  border-top: 1px solid var(--line);
}
.assignment-row:first-child { border-top: none; }

.muted { color: var(--muted); font-size: 13px; }
hr { border: none; border-top: 1px solid var(--line); margin: 20px 0; }

/* ---- Admin dashboard shell (sidebar + main) ---- */

.admin-shell {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  min-height: 100vh;
  transition: grid-template-columns 0.2s ease;
}

.admin-shell:has(.admin-sidebar.collapsed) {
  grid-template-columns: 58px minmax(0, 1fr);
}

/* On staff/admin pages, the floating Feedback button (bottom-left, fixed
   to the viewport) otherwise sits directly on top of the sidebar's own
   "Log out" / "My dashboard home" links (Anthony's report, 2026-07-13 —
   "log out used to be... where feedback now is": Log out was never
   removed, the button was just covering it). The first fix (shifting it
   right by a fixed 280px, staying bottom-left) still wasn't enough in real
   use (Anthony's follow-up report, 2026-07-14) — rather than keep tuning a
   pixel offset against the sidebar's own width, this moves the button to
   the opposite corner (bottom-right) entirely on staff pages, guaranteeing
   no overlap regardless of sidebar width. Safe to do only on staff pages:
   the Calculator widget (bottom-right) only ever renders on student
   test-taking pages, which use partials/foot.ejs (no .admin-shell), so
   there's no collision to worry about here — student pages are untouched
   and keep Feedback at its default bottom-left. #lcFeedbackWidget is a
   sibling of .admin-shell (see partials/admin-foot.ejs), so this sibling
   selector only ever applies on staff pages. */
.admin-shell ~ #lcFeedbackWidget { left: auto; right: 20px; }

.admin-sidebar {
  background: var(--slate-navy);
  color: #fff;
  padding: 22px 16px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  /* Static/sticky sidebar (2026-07-20, Anthony's request) — previously the
     sidebar just scrolled away with the page's own content on any long
     page, since .admin-shell is a plain 2-column grid with no positioning
     on either side. Sticking it to the viewport (its own independent
     scroll for a nav list taller than the screen) means it's always
     visible/reachable no matter how far down a long page (e.g. Templates
     with many sections) the DON/instructor has scrolled. */
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.admin-sidebar-toggle {
  align-self: flex-end;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 8px;
  background: rgba(255,255,255,0.08);
  color: #fff;
  cursor: pointer;
  flex-shrink: 0;
}
.admin-sidebar-toggle:hover {
  background: rgba(255,255,255,0.16);
}
.admin-sidebar-toggle-icon {
  font-size: 14px;
  line-height: 1;
}
.admin-sidebar.collapsed .admin-sidebar-toggle {
  align-self: center;
}
.admin-sidebar.collapsed .admin-brand,
.admin-sidebar.collapsed .ai-status-indicator,
.admin-sidebar.collapsed .admin-nav-item,
.admin-sidebar.collapsed .admin-sidebar-foot {
  display: none;
}

.admin-brand { display: flex; align-items: center; gap: 10px; padding: 0 8px; }
.admin-brand .brand-mark { width: 38px; height: 38px; font-size: 14px; }
.admin-brand h1 { margin: 0; font-size: 15px; color: #fff; }
.admin-brand p { margin: 0; font-size: 10px; color: rgba(255,255,255,0.6); letter-spacing: 0.08em; text-transform: uppercase; }

/* AI sidebar status light (2026-07-14) — sits on the dark sidebar
   background, so uses light/translucent tints rather than the light-page
   .pill colors. Fresh Lime dot when active (BRAND.md's "success/progress"
   accent), muted gray dot when off. */
.ai-status-indicator {
  display: flex; align-items: center; gap: 7px;
  margin: -6px 8px 0; padding: 7px 10px;
  border-radius: var(--radius-sm);
  font-size: 12px; font-weight: 600;
  text-decoration: none;
  background: rgba(255,255,255,0.06);
  color: rgba(255,255,255,0.75);
}
a.ai-status-indicator:hover { background: rgba(255,255,255,0.11); }
.ai-status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.ai-status-indicator.on { color: #D9F99D; }
.ai-status-indicator.on .ai-status-dot { background: var(--accent-lime); box-shadow: 0 0 0 3px rgba(132,204,22,0.28); }
.ai-status-indicator.off .ai-status-dot { background: rgba(255,255,255,0.35); }

.admin-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; overflow-y: auto; }
.admin-nav-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  color: rgba(255,255,255,0.82);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
}
.admin-nav-item:hover { background: rgba(255,255,255,0.08); }
.admin-nav-item.active { background: var(--primary-blue); color: #fff; }
.admin-nav-item.disabled { color: rgba(255,255,255,0.35); cursor: default; }
.admin-nav-item.disabled small { display: block; font-size: 10px; font-weight: 500; color: rgba(255,255,255,0.3); }
.admin-nav-item .tag { font-size: 9px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; padding: 2px 6px; border-radius: 999px; background: rgba(255,255,255,0.12); }
.admin-nav-item .tag.reserved { background: rgba(139,92,246,0.28); color: #E9D8FD; }

/* Per-instructor module toggle, not yet granted (2026-07-21) — a greyed nav
   item with a real "Ask Admin for access" button underneath, instead of the
   plain <small> note the regular .disabled state uses. */
.admin-nav-item-requestable { flex-direction: column; align-items: flex-start; gap: 6px; padding-bottom: 10px; }
.admin-nav-item-requestable span { font-size: 14px; font-weight: 600; }
.admin-nav-item-requestable form { margin: 0; width: 100%; }
.admin-nav-request-btn {
  font-size: 11px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.25);
  background: rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.75);
  cursor: pointer;
}
.admin-nav-request-btn:hover { background: rgba(255,255,255,0.18); color: #fff; }

.admin-sidebar-foot { display: flex; flex-direction: column; gap: 4px; border-top: 1px solid rgba(255,255,255,0.12); padding-top: 12px; }
.admin-sidebar-foot a { color: rgba(255,255,255,0.75); text-decoration: none; font-size: 13px; padding: 6px 12px; }
.admin-sidebar-foot a:hover { color: #fff; }

.admin-main { padding: 28px 32px 64px; max-width: 1280px; }

.dash-columns { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 20px; align-items: start; }
@media (max-width: 900px) { .dash-columns { grid-template-columns: 1fr; } }

.course-card-v2 {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  display: block;
}
.course-card-v2 .banner {
  height: 84px;
  background: linear-gradient(135deg, var(--primary-blue), var(--soft-teal));
  display: flex; align-items: center; justify-content: center;
}
.course-card-v2 .banner img { width: 100%; height: 100%; object-fit: cover; }
.course-card-v2 .body { padding: 14px; }
.course-card-v2 h4 { margin: 0 0 2px; font-size: 15px; }
.course-card-v2 .body p { margin: 0; font-size: 12px; color: var(--muted); }
.course-card-v2 .icon-row { display: flex; gap: 10px; margin-top: 10px; font-size: 12px; color: var(--muted); }

.add-course-card {
  border: 2px dashed var(--line);
  border-radius: var(--radius-md);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 24px; text-decoration: none; color: var(--muted); min-height: 148px;
  font-weight: 600; text-align: center;
}
.add-course-card:hover { border-color: var(--primary-blue); color: var(--primary-blue); }

/* ---- Square course tiles (My Courses) ---- */
.course-tile-grid {
  display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
/* Tile height increased 2026-07-18 (Anthony's report: a cohort-name badge
   plus a 2-line title plus Term/Code overflowed a strict 1:1 square, with
   the badge spilling out past the card's rounded bottom edge into the tile
   below). Anthony's explicit call: make every tile uniformly taller rather
   than letting a tile grow only when ITS OWN content happens to be long
   (which would make the grid look uneven tile-to-tile) -- so this changes
   the shared aspect-ratio for every tile using this class app-wide
   (Dashboard, Courses archive, Templates, Test Shells, Cohorts, Practice
   Buddy folders, Discussion Board), all of which keep identical sizing to
   each other, just taller than before. `overflow: hidden` on the tile itself
   is a second, belt-and-suspenders fix -- even if some future combination of
   long title + long badge still doesn't fit, it now clips at the card's own
   rounded edge instead of visually bleeding into a neighboring tile. */
/* Follow-up pass, same day: the 4/5 ratio above fixed the literal spillover
   (Templates tiles, which never carry a 3rd "cohort name" line, looked
   correct) but Dashboard/Courses-archive tiles carry a THIRD line (Term/Code
   + title + cohort-name badge) that a real long cohort name ("Vocational
   Nursing - VN02") still crowded uncomfortably close to the image, even once
   wrapped. Anthony's own standing instruction here is "uniform, not
   content-dependent" sizing, so the fix is more headroom for every tile
   again (not a special-cased taller variant just for pages with a cohort
   badge) -- taller ratio still, a smaller image proportion so the body gets
   more of the larger total, and a tighter line-height on the (now
   sometimes-2-line) cohort pill so its wrapped text takes less vertical
   room per line. */
.add-course-card-sq { aspect-ratio: 2 / 3; min-height: 0; width: 100%; cursor: pointer; background: none; }
.course-tile-sq {
  position: relative; display: flex; flex-direction: column; aspect-ratio: 2 / 3;
  border-radius: var(--radius-md); border: 1px solid var(--line);
  background: var(--panel); box-shadow: var(--shadow);
}
/* overflow:hidden lives on the LINK (image+body), not the outer .course-tile-sq
   itself -- the 3-dot menu's dropdown (a sibling of this link, absolutely
   positioned, opens upward past the tile's own top edge on purpose, see the
   .course-tile-menu-wrap comment below) would get clipped/invisible if the
   outer tile clipped overflow instead. */
.course-tile-sq-link { display: flex; flex-direction: column; flex: 1; text-decoration: none; color: inherit; min-height: 0; overflow: hidden; }
/* 2026-07-25: adding the Open/Not open yet/Closed status pill (2026-07-23)
   gave the body a real 4th stacked line (Term/Code, Title, cohort-name pill,
   status pill) on any Dashboard tile that also has a cohort attached -- on
   the previous 3/4 ratio + 55% image split, that 4th line was landing right
   at (or past) the .course-tile-sq-link's own overflow:hidden clip, so the
   status pill existed in the DOM (confirmed via a real page-source check)
   but was invisible on screen. Deepened the ratio again and shrunk the
   image's share of it further, same "give every tile more room, uniformly"
   fix already used twice before for this exact class of overflow bug. */
.course-tile-sq-image {
  flex: 0 0 48%; background: linear-gradient(135deg, var(--primary-blue), var(--soft-teal));
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.course-tile-sq-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.course-tile-sq-body { flex: 1; padding: 7px 30px 7px 10px; display: flex; align-items: flex-start; min-height: 0; }
.course-tile-sq-title {
  font-size: 13px; font-weight: 700; color: var(--ink); line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  /* Reserves space for a full 2 lines always (2 * 1.25 line-height), even
     when a given tile's title only wraps to 1 line -- otherwise whatever
     sits below the title (the cohort-name chip, a date-status chip, etc.)
     lands at a different vertical position tile-to-tile depending on how
     long that one tile's title happens to be, which is what reads as
     "inconsistent" across a row of tiles (2026-07-19, Anthony's report). */
  min-height: 2.5em;
}
/* Cohort-name badge (admin_dashboard.ejs) -- was a forced single nowrap
   line, which is what let a long cohort name render WIDER than the card
   itself. Now wraps within the tile's own width instead of forcing overflow,
   with a tightened line-height so a 2-line-wrapped cohort name doesn't take
   any more vertical room than it has to. */
.course-tile-sq-body .pill { font-size: 10px; padding: 2px 8px; line-height: 1.15; white-space: normal; word-break: break-word; max-width: 100%; }

/* Templates drag-and-drop (2026-07-18): a tile being dragged fades and
   shows a dashed outline so there's clear visual feedback mid-drag; the
   grid itself gets a subtle dashed border while something is actively
   being dragged over it, so an empty section reads as a valid drop target. */
.course-tile-sq[draggable="true"] { cursor: grab; }
.course-tile-sq.dragging { opacity: 0.4; outline: 2px dashed var(--primary-blue); outline-offset: 2px; }
.course-tile-grid[data-drop-grid] { min-height: 40px; }

/* Visible drag-grip handle (2026-07-21, Anthony's request: "can course tiles
   have a 9-dot handle to move them" — the whole tile has been draggable
   since Templates shipped this, but nothing on the tile signaled that, so
   this makes the affordance discoverable everywhere a course-tile-sq grid
   supports drag-and-drop, staff and student views alike). Sits top-left so
   it never collides with the 3-dot options menu (bottom-right) or, on
   student views with no options menu at all, any other tile control. */
.course-tile-drag-handle {
  position: absolute; left: 6px; top: 6px; z-index: 4;
  width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  color: var(--muted); font-size: 13px; line-height: 1; cursor: grab; user-select: none;
}
.course-tile-drag-handle:hover { background: var(--panel); color: var(--ink); }

.course-tile-menu-wrap { position: absolute; right: 6px; bottom: 6px; z-index: 4; }
.course-tile-menu-btn {
  background: var(--surface); border: 1px solid var(--line); color: var(--muted); border-radius: 8px;
  width: 24px; height: 24px; cursor: pointer; font-size: 15px; line-height: 1;
}
.course-tile-menu-btn:hover { background: var(--panel); color: var(--ink); }
/* .tb-tile-menu-dropdown defaults to top:30px (opens downward), which — anchored
   to a wrap pinned to the *bottom* of this small square tile — rendered below
   the tile's own bounds and used to get clipped away by the tile's rounded-corner
   overflow, making the menu look completely non-functional. Open upward instead,
   and keep it above neighboring tiles in the grid. */
.course-tile-menu-wrap .tb-tile-menu-dropdown { top: auto; bottom: 30px; right: 0; z-index: 6; }

/* ---- Cohort tiles (2026-07-13 redesign: square tiles matching Templates/
   Courses/Test Shells, not the old inline-expanding <details> panel). Each
   tile's "image" area defaults to the cohort's own name in bold on a
   rotating brand-palette background — no AI image generation needed here,
   the name itself IS the visual. Colors cycle through the approved accent
   palette (server.js's COHORT_TILE_COLORS), text color per-swatch for
   contrast — set inline per tile since the rotation is server-computed. */
.cohort-tile-initial {
  font-family: 'Space Grotesk', sans-serif; font-weight: 700; font-size: 22px;
  text-align: center; padding: 0 10px; line-height: 1.25; word-break: break-word;
}

.list-item { display: flex; justify-content: space-between; gap: 10px; padding: 10px 0; border-top: 1px solid var(--line); font-size: 13px; }
.list-item:first-child { border-top: none; }
.list-item .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 8px; flex-shrink: 0; }

/* "What's new" activity indicators — added 2026-07-07. Orange (Golden
   Amber, the brand's "callout" accent) for count badges on a specific tab;
   Signal Coral (the brand's "alert" accent) for the plain presence dot on a
   course tile, since a tile doesn't have room for per-section counts. */
.nav-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  margin-left: 6px;
  border-radius: 999px;
  background: var(--accent-amber);
  color: var(--midnight-navy);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  vertical-align: middle;
}
/* Internal Messaging file attachments (2026-07-18, Anthony's request) —
   drop zone + preview chips shared by the compose and reply forms via
   views/partials/message_attach_field.ejs, plus read-side chips in
   message_thread.ejs. */
.msg-attach-zone {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  border: 1.5px dashed var(--line);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin: 8px 0;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.msg-attach-zone-active {
  border-color: var(--primary-blue);
  background: var(--light-blue);
}
.msg-attach-preview {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}
.msg-attach-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--bg-light, #F4F6F8);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  max-width: 220px;
}
.msg-attach-thumb {
  width: 28px;
  height: 28px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}
.msg-attach-file-icon { font-size: 16px; flex-shrink: 0; }
.msg-attach-chip-name {
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: inherit;
}
.msg-attach-remove {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
}
.msg-attach-remove:hover { color: var(--warm-coral); }

.tile-activity-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--warm-coral);
  margin-left: 8px;
  vertical-align: middle;
  flex-shrink: 0;
}

.preview-banner {
  background: var(--slate-navy);
  color: #fff;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 16px; font-size: 13px;
}
.preview-banner a { color: #fff; text-decoration: underline; }

/* ---- Testing Buddy dashboard nav tiles (name only, no subtitle) ---- */
.tb-nav-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 120px;
  font-family: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  font-size: 22px;
  font-weight: 600;
  color: var(--primary-blue);
}
.tb-nav-tile:hover { border-color: var(--primary-blue); }

/* ---- Welcome / login hero (index.ejs) ---- */
.welcome-hero {
  background: linear-gradient(135deg, var(--midnight-navy), var(--primary-blue) 55%, var(--soft-teal));
  border-radius: var(--radius-lg);
  padding: 30px 28px;
  color: #fff;
  box-shadow: var(--shadow);
  margin-bottom: 22px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}
.welcome-hero .brand { gap: 14px; }
.welcome-hero .brand-mark { background: rgba(255,255,255,0.18); font-size: 16px; }
.welcome-hero .eyebrow { color: rgba(255,255,255,0.7); }
.welcome-hero h1 { margin: 4px 0 4px; font-size: 26px; color: #fff; }
.welcome-hero p { margin: 0; color: rgba(255,255,255,0.82); font-size: 14px; }
.welcome-hero h1 { margin: 4px 0 0; }

/* ---- ABēi brand-education card (welcome/login page) ----
   Redesigned 2026-07-25 (Anthony's request, second pass after his annotated
   screenshot correction): a horizontal row -- the large "Awake" mascot on
   the LEFT (continuously animated via state:"active", not the calmer idle
   glow used elsewhere as ambient presence), the wordmark chip + pronunciation
   moved to the right, vertically centered "in line with" the mascot rather
   than stacked above it, and enlarged. Description text spans the full
   width underneath the row, still centered. */
.abei-intro {
  text-align: center;
  border-top: 3px solid var(--accent-violet);
}
.abei-intro .eyebrow { margin-bottom: 10px; }
.abei-intro-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 28px;
  margin-bottom: 14px;
}
.abei-intro .abei-mascot-wrap {
  flex: 0 0 auto;
}
.abei-intro-chip-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
}
.abei-intro .abei-chip {
  height: 64px;
  width: auto;
  display: block;
  border-radius: var(--radius-sm);
  margin-bottom: 8px;
}
.abei-intro .abei-pronounce {
  font-style: italic;
  color: var(--muted);
  font-size: 14px;
  margin: 0;
}
.abei-intro .abei-intro-copy {
  margin: 0 auto;
  max-width: 560px;
  color: var(--ink);
  text-align: center;
}
@media (max-width: 560px) {
  .abei-intro-row { flex-direction: column; gap: 12px; }
  .abei-intro-chip-col { align-items: center; text-align: center; }
}

/* ---- Landing portal cards ----
   2026-07-25: laid out as a flex column so the "Log in" button + "Need help
   signing in?" toggle always sit at the same visual position at the bottom
   of both cards (margin-top:auto on the toggle), regardless of small
   differences in how long each card's own description line runs -- keeps
   the Student/Staff pair looking like a true symmetric match rather than
   two independently-sized boxes. */
.portal-card { border-top: 3px solid var(--primary-blue); display: flex; flex-direction: column; }
.portal-card h3 { margin: 8px 0 6px; }
.portal-card p { margin: 0 0 10px; }
.portal-card .btn { margin-bottom: 6px; }
.portal-card.role-student { border-top-color: var(--soft-teal); }
.portal-card.role-instructor { border-top-color: var(--accent-amber); }
.portal-card.role-admin { border-top-color: var(--accent-violet); }
.portal-card .forgot-note-toggle { margin-top: auto; }
.portal-card .forgot-note-toggle summary {
  font-size: 12px; font-weight: 600; color: var(--muted); cursor: pointer; list-style: none;
}
.portal-card .forgot-note-toggle summary::-webkit-details-marker { display: none; }
.portal-card .forgot-note-toggle[open] summary { color: var(--primary-blue); }
.portal-card .forgot-note-toggle .forgot-note {
  margin-top: 8px; font-size: 12px; color: var(--muted);
  background: var(--surface); border-radius: var(--radius-sm); padding: 8px 10px;
}
.portal-card .forgot-note-toggle .forgot-note-links {
  margin-top: 6px; display: flex; gap: 8px; flex-wrap: wrap;
}
.portal-card .forgot-note-toggle .forgot-note-links a {
  font-size: 12px; font-weight: 600; color: var(--primary-blue);
  background: var(--light-blue); border-radius: var(--radius-sm); padding: 6px 10px;
  text-decoration: none;
}
.portal-card .forgot-note-toggle .forgot-note-links a:hover { text-decoration: underline; }

/* 2026-07-25: the base .grid class uses repeat(auto-fill, minmax(240px,1fr)),
   which computes to 3 columns on this card's real container width even
   though only 2 tiles (Student/Staff) exist -- leaving an empty phantom
   third column of dead space to the right (Anthony circled this in red on
   his screenshot: "you have the portals under but you need to make the
   tile fill the space"). Force exactly 2 equal columns here so both cards
   expand to fill the whole width, with no gap. */
.portal-grid {
  grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 640px) {
  .portal-grid { grid-template-columns: 1fr; }
}

/* ---- First-time login tile (unified, spans full width, matches portal cards) ---- */
.first-time-tile {
  grid-column: 1 / -1;
  border-top: 3px solid var(--primary-blue);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.first-time-tile h3 { margin: 0 0 2px; font-size: 15px; }
.first-time-tile p { margin: 0; font-size: 13px; color: var(--muted); }
.first-time-links { display: flex; gap: 10px; flex-wrap: wrap; }
.first-time-links a {
  font-size: 13px; font-weight: 700; padding: 8px 14px; border-radius: var(--radius-sm);
  background: var(--light-blue); color: var(--primary-blue); text-decoration: none;
  white-space: nowrap;
}
.first-time-links a:hover { background: var(--primary-blue); color: #fff; }

/* ---- Invite / cohort code display ---- */
.code-box {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--surface);
  border: 1px dashed var(--slate-blue);
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--primary-blue);
}

/* ---- Roster / people tables ---- */
.roster-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
}
.roster-row:first-child { border-top: none; }
/* ---- Testing Buddy: subject tiles (Setup Subjects) — locked palette only, see BRAND.md ---- */
.tb-tile-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin-top: 14px; }
.tb-course-tile {
  position: relative;
  border-radius: var(--radius-md);
  padding: 0;
  min-height: 78px;
  box-shadow: var(--shadow);
  display: flex;
  align-items: stretch;
}
.tb-tile-link {
  flex: 1;
  display: flex;
  align-items: center;
  padding: 22px 44px 22px 18px;
  color: #fff;
  font-weight: 700;
  font-size: 15px;
  text-decoration: none;
}
.tb-course-tile.tb-color-blue { background: var(--primary-blue); }
.tb-course-tile.tb-color-teal { background: var(--soft-teal); }
.tb-course-tile.tb-color-navy { background: var(--midnight-navy); }
.tb-course-tile.tb-color-coral { background: var(--warm-coral); }
.tb-course-tile.tb-color-amber { background: var(--accent-amber); }
.tb-course-tile.tb-color-violet { background: var(--accent-violet); }
.tb-course-tile.tb-color-lime { background: var(--accent-lime); }
.tb-course-tile.tb-color-magenta { background: var(--accent-magenta); }

.tb-color-swatch-row { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 12px; }
.tb-color-swatch-row[hidden] { display: none; }
.tb-color-swatch-form { display: contents; }
.tb-color-swatch {
  width: 24px; height: 24px; border-radius: 50%; border: 2px solid transparent;
  cursor: pointer; padding: 0;
}
.tb-color-swatch.tb-swatch-active { border-color: var(--ink); }
.tb-course-tile .tb-ready-badge {
  position: absolute; top: 10px; right: 40px;
  background: rgba(255,255,255,0.25); border-radius: 999px;
  width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 12px;
}
.tb-tile-menu-wrap { position: absolute; top: 8px; right: 8px; }
.tb-tile-menu-btn {
  background: rgba(255,255,255,0.18); border: none; color: #fff; border-radius: 8px;
  width: 26px; height: 26px; cursor: pointer; font-size: 16px; line-height: 1;
}
.tb-tile-menu-btn:hover { background: rgba(255,255,255,0.32); }
.tb-tile-menu-dropdown {
  position: absolute; right: 0; top: 30px; z-index: 5;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--shadow); min-width: 120px; overflow: hidden;
}
/* :not(.btn) added 2026-07-21 (Anthony's report: real .btn-styled buttons
   nested inside a dropdown -- e.g. the roster row's "Set photo"/"Lock
   photo" buttons -- rendered as illegible dark-text-on-dark-background
   until hovered). Root cause: this plain-menu-item rule's "color" property
   has slightly higher CSS specificity (1 class + 1 element) than .btn's own
   "color: #fff" (1 class alone), so it silently overrode just the text
   color (not the background, which .btn.secondary's 2-class selector still
   won), leaving dark ink text on a dark navy background -- readable only on
   hover, when this rule's own hover background briefly lightened things.
   Excluding real .btn elements here lets them keep their normal, correct
   appearance everywhere they're nested in a dropdown, while plain
   text-style menu rows (Rename/Delete/etc., which never carry .btn) are
   completely unaffected by this change. */
.tb-tile-menu-dropdown button:not(.btn), .tb-tile-menu-dropdown a:not(.btn) {
  display: block; width: 100%; text-align: left; padding: 9px 12px; border: none; background: none;
  color: var(--ink); font-size: 13px; font-weight: 600; cursor: pointer; text-decoration: none; box-sizing: border-box;
}
.tb-tile-menu-dropdown button:not(.btn):hover, .tb-tile-menu-dropdown a:not(.btn):hover { background: var(--surface); }
.tb-tile-menu-dropdown button.tb-delete-item { color: var(--warm-coral); }
.tb-add-course-tile {
  border: 2px dashed var(--line); border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  min-height: 78px; color: var(--primary-blue); font-weight: 700; font-size: 15px;
  text-decoration: none; background: none; cursor: pointer;
}
.tb-add-course-tile:hover { border-color: var(--primary-blue); background: var(--light-blue); }

/* ---- Testing Buddy: Upload .csv modal ---- */
.tb-upload-modal-overlay {
  position: fixed; inset: 0; background: rgba(10, 20, 40, 0.55);
  display: flex; align-items: center; justify-content: center; z-index: 50;
}
.tb-upload-modal-overlay[hidden] { display: none; }
.tb-upload-modal {
  background: var(--panel); border-radius: var(--radius-lg); box-shadow: var(--shadow);
  padding: 22px; width: 92%; max-width: 440px;
}
.tb-upload-modal-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.tb-upload-modal-head button { background: none; border: none; font-size: 20px; cursor: pointer; color: var(--muted); line-height: 1; }
.tb-upload-dropzone {
  border: 2px dashed var(--slate-blue); border-radius: var(--radius-md);
  padding: 36px 20px; text-align: center; color: var(--muted);
}
.tb-upload-dropzone p { margin: 4px 0; }
.tb-upload-dropzone--over { border-color: var(--primary-blue); background: var(--light-blue); }

/* ---- Testing Buddy: Build a Test allocation table ---- */
.tb-alloc-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 12px; }
.tb-alloc-table th { text-align: left; padding: 8px 6px; border-bottom: 2px solid var(--line); color: var(--muted); font-weight: 600; }
.tb-alloc-table td { padding: 6px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.tb-chapter-row td { background: var(--surface); padding-top: 10px; }
.tb-subtopic-label { color: var(--ink); }
.tb-count-input { width: 56px; padding: 6px 8px; margin-bottom: 0; text-align: center; }

/* ---- Testing Buddy: Build a Test — compact chapter accordion ---- */
.tb-chapter-search {
  width: 100%; margin-top: 10px; padding: 8px 12px;
  border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--surface);
  font-size: 13px;
}
.tb-chapter-accordion { margin-top: 12px; border: 1px solid var(--line); border-radius: var(--radius-md); overflow: hidden; }
.tb-chapter-item { border-top: 1px solid var(--line); }
.tb-chapter-item:first-child { border-top: none; }
.tb-chapter-item[open] { background: var(--surface); }
.tb-chapter-summary {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  cursor: pointer; font-size: 13px; font-weight: 600; color: var(--ink); list-style: none;
}
.tb-chapter-summary::-webkit-details-marker { display: none; }
.tb-chapter-summary-title { flex: 1; }
.tb-chapter-count-badge {
  background: var(--light-blue); color: var(--primary-blue); border-radius: 999px;
  padding: 3px 11px; font-size: 12px; font-weight: 700; white-space: nowrap;
}
.tb-chapter-count-badge.tb-badge-zero { background: var(--surface); color: var(--muted); }
.tb-alloc-table.compact { margin: 0 14px 12px; width: calc(100% - 28px); }
.tb-alloc-table.compact th, .tb-alloc-table.compact td { padding: 4px 6px; font-size: 12px; }
.tb-count-input.compact { width: 44px; padding: 4px 6px; }
.tb-total-over { color: var(--warm-coral); border-color: var(--warm-coral); }

/* ---- Testing Buddy: NGN "+ Add Scenario" widget ---- */
.tb-ngn-cell { min-width: 150px; }
.tb-ngn-add-btn {
  background: none; border: 1px dashed var(--primary-blue); color: var(--primary-blue);
  border-radius: var(--radius-sm); padding: 3px 9px; font-size: 11px; font-weight: 700;
  cursor: pointer; white-space: nowrap;
}
.tb-ngn-scenario-list { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.tb-ngn-scenario {
  display: flex; align-items: center; gap: 5px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 3px 6px; font-size: 11px;
}
.tb-ngn-scenario-label { flex: 1; color: var(--ink); font-weight: 600; white-space: nowrap; }
.tb-stepper-btn {
  width: 18px; height: 18px; border-radius: 50%; border: none; background: var(--primary-blue);
  color: #fff; font-size: 12px; font-weight: 700; line-height: 1; cursor: pointer; padding: 0;
}
.tb-ngn-scenario-count { min-width: 12px; text-align: center; font-weight: 700; }
.tb-ngn-q-label { font-size: 10px; }
.tb-ngn-remove {
  background: none; border: none; color: var(--muted); cursor: pointer; font-size: 13px;
  line-height: 1; padding: 0 2px;
}
.tb-ngn-remove:hover { color: var(--warm-coral); }

/* ---- Testing Buddy: question cards ---- */
.tb-question-card { border: 1px solid var(--line); border-radius: var(--radius-md); padding: 16px; margin-bottom: 14px; }
.tb-question-card .tb-q-meta { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.tb-question-card .tb-q-options { margin: 10px 0 0; padding-left: 0; list-style: none; }
.tb-question-card .tb-q-options li { padding: 6px 10px; border-radius: var(--radius-sm); margin-bottom: 4px; background: var(--surface); }
.tb-question-card .tb-q-options li.correct { background: rgba(132, 204, 22, 0.15); font-weight: 600; }
/* "Your answer" highlight on a result page (2026-07-14 fix, live-test
   feedback: bold+underline text was too easy to miss — Marissa suggested a
   real highlight instead). Amber tint so it reads distinctly from the green
   "correct" tint even when the same option is both (correct.your-answer
   below layers a left accent bar on top of the green background rather than
   fighting it with a second background color). */
.tb-question-card .tb-q-options li.your-answer { background: rgba(255, 181, 71, 0.25); font-weight: 700; }
.tb-question-card .tb-q-options li.correct.your-answer { border-left: 4px solid var(--accent-amber); }

/* Answer radio/checkbox inputs on a test-taking page (2026-07-18, Anthony's
   report: "on the mobile version of a test, the check boxes/circles were
   differently sized for some reason"). These previously relied only on an
   inline style="width:auto; margin-top:3px;" -- with no explicit width or
   height set, the actual rendered size fell back entirely to each mobile
   browser's own native control sizing, which some mobile WebKit/Chromium
   builds size relative to inherited font-size/zoom rather than a fixed
   physical size, so the same radio/checkbox could render at visibly
   different sizes across questions. A fixed width+height makes every answer
   control the same size everywhere, on every device, regardless of that
   browser quirk. flex-shrink:0 keeps it from being squeezed by a long
   option's wrapping text in the same flex row; accent-color keeps the
   checked state on-brand instead of each OS's own default blue/green. */
.tb-answer-input {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 2px;
  accent-color: var(--primary-blue);
}

/* NGN scenario cards (2026-07-11, Anthony's request) — a shared clinical
   scenario/passage with several linked sub-questions needs to visually read
   as one connected unit, distinct from an ordinary single question, at a
   glance rather than blending into the same plain white card. Uses the
   brand's Electric Teal accent (--soft-teal, "highlights / active states"
   per BRAND.md) — a tinted background + left accent bar on the outer
   scenario card, with each nested sub-question given its own subtler inset
   card so the shared-scenario relationship stays visually obvious without
   losing per-question separation. */
.tb-ngn-scenario-card {
  background: rgba(20, 200, 196, 0.07);
  border: 1px solid rgba(20, 200, 196, 0.35);
  border-left: 5px solid var(--soft-teal);
  border-radius: var(--radius-md);
  padding: 16px;
  margin-bottom: 14px;
}
.tb-ngn-scenario-card > .tb-q-meta {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #0e8f8c;
  margin-bottom: 8px;
}
.tb-ngn-subquestion {
  background: var(--panel, #fff);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-top: 10px;
}
.tb-ngn-subquestion:first-of-type { margin-top: 12px; }
.tb-ngn-subquestion .tb-q-meta { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.tb-ngn-subquestion .tb-q-options { margin: 10px 0 0; padding-left: 0; list-style: none; }
.tb-ngn-subquestion .tb-q-options li { padding: 6px 10px; border-radius: var(--radius-sm); margin-bottom: 4px; background: var(--surface); }
.tb-ngn-subquestion .tb-q-options li.correct { background: rgba(132, 204, 22, 0.15); font-weight: 600; }
.tb-ngn-subquestion .tb-q-options li.your-answer { background: rgba(255, 181, 71, 0.25); font-weight: 700; }
.tb-ngn-subquestion .tb-q-options li.correct.your-answer { border-left: 4px solid var(--accent-amber); }

.roster-row .tag-role {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(139, 92, 246, 0.15);
  color: var(--accent-violet);
}

/* ---- Course Shell (internal course sidebar + tabs, Canvas-style layout) ---- */
.shell-breadcrumb {
  font-size: 13px; color: var(--muted); margin-bottom: 14px;
}
.shell-breadcrumb a { color: var(--muted); }
.shell-breadcrumb .current { color: var(--ink); font-weight: 600; }
.shell-layout {
  display: grid; grid-template-columns: 180px 1fr; gap: 20px; align-items: start;
}
.shell-layout.has-right-panel { grid-template-columns: 180px 1fr 260px; }
.shell-nav {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-md);
  box-shadow: var(--shadow); overflow: hidden; padding: 6px 0;
}
.shell-nav a, .shell-nav .shell-nav-disabled {
  display: block; padding: 10px 16px; font-size: 13px; font-weight: 600;
  color: var(--ink); text-decoration: none; border-left: 3px solid transparent;
}
.shell-nav a:hover { background: var(--surface); }
.shell-nav a.active { background: var(--light-blue); border-left-color: var(--primary-blue); color: var(--primary-blue); }
.shell-nav .shell-nav-disabled { color: var(--muted); cursor: default; display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.shell-nav .shell-nav-disabled .tag {
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  background: var(--disabled); color: var(--muted); padding: 2px 6px; border-radius: 999px; white-space: nowrap;
}
.shell-main { min-width: 0; }
.shell-tabs {
  display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 18px; flex-wrap: wrap;
}
.shell-tabs a, .shell-tabs .shell-tab-disabled {
  padding: 10px 14px; font-size: 13px; font-weight: 600; text-decoration: none;
  color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.shell-tabs a.active { color: var(--primary-blue); border-bottom-color: var(--primary-blue); }
.shell-tabs a:hover { color: var(--ink); }
.shell-tabs .shell-tab-disabled { color: var(--disabled); cursor: default; }
.shell-right-panel { display: flex; flex-direction: column; gap: 14px; }
.shell-right-panel .card { padding: 16px; }
.shell-right-panel h4 { margin: 0 0 10px; font-size: 13px; }
.shell-right-link {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  padding: 7px 0; font-size: 13px; color: var(--ink); text-decoration: none;
}
.shell-right-link.disabled { color: var(--muted); cursor: default; }
.shell-right-link .tag {
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  background: var(--disabled); color: var(--muted); padding: 2px 6px; border-radius: 999px; white-space: nowrap;
}
.shell-user-counts { font-size: 13px; }
.shell-user-counts .row { display: flex; justify-content: space-between; padding: 4px 0; }
.shell-user-counts .row .muted { color: var(--muted); }
@media (max-width: 900px) {
  .shell-layout, .shell-layout.has-right-panel { grid-template-columns: 1fr; }
}

/* Dashboard cohort-filter tabs (2026-07-26, Anthony's request: "TABS HERE FOR
   CURRENT ACTIVE COHORTS... THE TAB CAN POPULATE AND APPEAR ONCE A NEW COHORT
   IS ADDED"). Built with <button>s, not <a>s, since this is a pure
   client-side filter over tiles already on the page -- no navigation/reload.
   Redesigned as a real segmented pill-tab group (2026-07-28, Anthony's
   screenshot: the original plain underline style — same as .shell-tabs used
   for actual page navigation elsewhere — read as flat text rather than a
   distinct, clickable control) -- a light housing strip with individually
   rounded tabs and a solid-filled active state, so this reads as its own
   toggle/filter control rather than a page-navigation tab bar. */
.dash-cohort-tabs {
  display: flex; gap: 4px; flex-wrap: wrap; width: fit-content;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  padding: 4px; margin: 4px 0 18px;
}
.dash-cohort-tabs button {
  padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: 9px;
  color: var(--muted); transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.dash-cohort-tabs button.active {
  background: var(--primary-blue); color: #fff; border-color: var(--primary-blue);
  box-shadow: 0 2px 6px rgba(39, 80, 122, 0.28);
}
.dash-cohort-tabs button:not(.active):hover { background: var(--panel); color: var(--ink); border-color: var(--line); }

/* Course Settings page cleanup (2026-07-18) -- a small collapsed disclosure
   for the honest "not built yet" placeholder list (previously a permanently-
   disabled sub-tabs bar + an 8-link right-panel card, both prominent), and a
   visually distinct, clearly red-flagged Danger Zone card so a destructive
   action never carries the same visual weight as a routine settings card. */
.not-built-details { margin: 4px 0 20px; }
.not-built-details summary {
  cursor: pointer; color: var(--muted); font-size: 13px; font-weight: 600;
  padding: 6px 2px; list-style: none;
}
.not-built-details summary::-webkit-details-marker { display: none; }
.not-built-details .card { margin-top: 8px; margin-bottom: 0; }
.not-built-details ul { margin: 0; padding-left: 18px; }
.not-built-details li { margin-bottom: 5px; color: var(--muted); font-size: 13px; }

.danger-zone-card {
  border: 1px solid rgba(255, 107, 87, 0.45);
  background: rgba(255, 107, 87, 0.05);
}
.danger-zone-card h3 { color: #9A2E1F; }

/* Templates page: collapsible named sections (2026-07-18, Anthony's request)
   -- each section (including the synthetic "Unassigned Templates" bucket) is
   a plain <details>, open by default, collapsible by clicking its own
   heading. Deliberately not styled as a <details>/<summary> that looks like
   a form control -- reads as a section header with a disclosure triangle. */
.template-section-details { margin-bottom: 18px; }
.template-section-details summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 4px;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 16px;
  color: var(--ink);
  border-bottom: 1px solid var(--line);
}
.template-section-details summary::-webkit-details-marker { display: none; }
.template-section-details summary::before {
  content: "\25BE";
  display: inline-block;
  margin-right: 8px;
  color: var(--muted);
  transition: transform 0.15s ease;
}
.template-section-details:not([open]) summary::before { transform: rotate(-90deg); }
.template-section-details .card { margin-top: 12px; }
.template-section-summary-text { flex: 1; }
.template-section-summary-actions { display: flex; gap: 4px; }
.template-section-icon-btn {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
}
.template-section-icon-btn:hover { background: var(--light-blue); color: var(--ink); }

/* Section-level drag-and-drop (2026-07-18): the whole section (summary +
   its tile grid) sits inside a draggable "grip" wrapper so a DON can drag
   one named section above/below another (e.g. Vocational Nursing above
   Nurse Assistant), same reorder pattern as the templates themselves. */
.template-section-drag-wrap { cursor: grab; }
.template-section-drag-wrap.dragging-section {
  opacity: 0.4;
  outline: 2px dashed var(--primary-blue);
  outline-offset: 2px;
  border-radius: var(--radius-md);
}
.template-section-drag-handle {
  color: var(--muted);
  font-size: 14px;
  margin-right: 4px;
  cursor: grab;
}

/* ==========================================================================
   THE BUDDIES PILL (2026-09-11, Anthony)
   Four Buddies came out of the sidebar list and went behind one pill at the
   very top of it. The pill is NOT a nav item and must not read as one: it is
   a raised object -- the LearnCore gradient, a soft shadow, and a one-pixel
   inner highlight along the top edge. It keeps the sidebar's own typeface;
   a second typeface for one group reads as a mistake.
   Shared by the staff sidebar and the student sidebar, which is why nothing
   here is scoped to .admin-sidebar or .student-nav.
   ========================================================================== */
.buddies-wrap { position: relative; flex: 0 0 auto; }

.buddies-pill {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 12px 14px;
  border: 0;
  border-radius: 14px;
  cursor: pointer;
  font-family: inherit;              /* the sidebar's own font, deliberately */
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .01em;
  color: #fff;
  background: linear-gradient(135deg, var(--midnight-navy), var(--primary-blue) 55%, var(--soft-teal));
  /* the raised look: a real shadow under it, and a 1px highlight along the
     top edge so it catches light like a physical object */
  box-shadow: 0 6px 16px rgba(10, 20, 40, .34), inset 0 1px 0 rgba(255, 255, 255, .28);
  transition: transform .12s ease, box-shadow .12s ease;
}
.buddies-pill:hover { transform: translateY(-1px); box-shadow: 0 9px 22px rgba(10,20,40,.40), inset 0 1px 0 rgba(255,255,255,.34); }
.buddies-pill:active { transform: translateY(0); box-shadow: 0 4px 12px rgba(10,20,40,.30), inset 0 1px 0 rgba(255,255,255,.22); }
.buddies-pill:focus-visible { outline: 3px solid var(--soft-teal); outline-offset: 2px; }
.buddies-pill-label { line-height: 1.1; }

/* the count of what is inside, before you open it */
.buddies-pill-count {
  min-width: 22px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .20);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .28);
  font-size: 12.5px;
  font-weight: 800;
  text-align: center;
}
/* something is actually waiting inside -- coral, the signal colour */
.buddies-pill-count[data-waiting] { background: var(--warm-coral); box-shadow: none; }

/* ---- the bubble: juts out to the RIGHT, pointing back at the pill ---- */
/* position:FIXED, and the panel is moved onto <body> while it is open. Both
   sidebars scroll their own content (overflow-y:auto), and an absolutely
   positioned bubble inside one is simply clipped at the sidebar's edge -- it
   cannot jut out. The script sets left/top from the pill's own rect. */
.buddies-bubble {
  position: fixed;
  z-index: 60;
  width: 310px;
  padding: 8px;
  border-radius: 16px;
  background: #101B2E;
  box-shadow: 0 22px 50px rgba(0, 0, 0, .46), inset 0 1px 0 rgba(255, 255, 255, .07);
}
.buddies-arrow {
  position: absolute;
  top: 20px;
  left: -7px;
  width: 14px;
  height: 14px;
  background: #101B2E;
  transform: rotate(45deg);
  border-radius: 2px;
}
.buddies-sheet-grip { display: none; }   /* phone only, see the media query */

.buddies-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 10px 11px;
  border-radius: 11px;
  text-decoration: none;
  color: #fff;
}
.buddies-row:hover, .buddies-row:focus-visible { background: rgba(255, 255, 255, .10); outline: none; }
.buddies-row:focus-visible { box-shadow: inset 0 0 0 2px var(--soft-teal); }
.buddies-row.on { background: rgba(20, 200, 196, .16); }
.buddies-row-icon { flex: 0 0 auto; font-size: 17px; line-height: 1.35; width: 22px; text-align: center; }
.buddies-row-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.buddies-row-name { display: flex; align-items: center; gap: 7px; font-size: 14.5px; font-weight: 700; }
.buddies-row-blurb { font-size: 12.5px; line-height: 1.35; color: #9FB0C9; }
.buddies-row-tag {
  padding: 1px 6px; border-radius: 999px;
  background: var(--soft-teal); color: #04212B;
  font-size: 10.5px; font-weight: 800; letter-spacing: .03em; text-transform: uppercase;
}
.buddies-row-badge {
  min-width: 18px; padding: 0 6px; border-radius: 999px;
  background: var(--warm-coral); color: #fff;
  font-size: 11px; font-weight: 800; text-align: center;
}

/* ---- on a phone there is no sidebar to point at ----------------------------
   So the bubble stops being a bubble and becomes a panel that slides up from
   the bottom of the screen. Same rows, different shape. The office manager
   opens LearnCore on her phone and this has to work there. */
@media (max-width: 680px) {
  .buddies-bubble {
    position: fixed;
    top: auto; left: 0; right: 0; bottom: 0;
    width: auto;
    padding: 6px 12px calc(18px + env(safe-area-inset-bottom, 0px));
    border-radius: 20px 20px 0 0;
    box-shadow: 0 -14px 40px rgba(0, 0, 0, .5);
    animation: buddiesSlideUp .2s ease-out;
    /* Above the student drawer (z-index 44) and its toggle (45), because the
       panel is moved onto <body> while it is open -- see the note in the
       script about transformed ancestors trapping position:fixed. */
    z-index: 60;
  }
  .buddies-arrow { display: none; }
  .buddies-sheet-grip {
    display: block;
    width: 42px; height: 4px;
    margin: 8px auto 10px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .28);
  }
  .buddies-row { padding: 13px 12px; }      /* a real tap target */
  .buddies-row-name { font-size: 15.5px; }
  /* The floating Feedback button lives at the bottom-left and would sit on
     top of the last row of this panel -- it has collided with the bottom of
     the nav before. It is hidden for as long as the panel is open, and comes
     straight back when it closes. */
  body.buddies-open #lcFeedbackWidget { display: none; }
  /* the panel gets a scrim so the page behind it reads as inactive */
  body.buddies-open::after {
    content: "";
    position: fixed; inset: 0;
    background: rgba(6, 12, 24, .5);
    z-index: 55;
  }
}
@keyframes buddiesSlideUp { from { transform: translateY(14px); opacity: .6; } to { transform: translateY(0); opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .buddies-pill, .buddies-bubble { transition: none; animation: none; }
}

/* A Buddy an instructor has not been granted yet: visible but not a link, with
   the same real "Ask Admin for access" button the sidebar used to carry. A
   feature that exists and is closed to you is honest; one that silently is not
   there is not. */
.buddies-row.locked { cursor: default; opacity: .72; }
.buddies-row.locked:hover { background: none; }
.buddies-row.locked .buddies-row-name { color: #C7D3E4; }
.buddies-row-ask {
  margin-top: 6px;
  padding: 4px 9px;
  border: 1px solid rgba(255,255,255,.28);
  border-radius: 7px;
  background: rgba(255,255,255,.08);
  color: #fff;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
}
.buddies-row-ask:hover { background: rgba(255,255,255,.16); }

/* The instruction preview under a day item's title (2026-09-11, Anthony).
   The day list is a glance, not a read -- an eleven-line reflection prompt
   swamped the whole day. One line, with an ellipsis only when there is
   actually more; the full instruction lives inside the assignment, so nothing
   is truncated server-side and no text is lost. The title above it keeps its
   full length, because the title is the thing being scanned. */
.lc-asg-sub {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  font-size: 12px;
  margin-top: 2px;
}
