/**
 * Shared platform visual tokens (সহজশিক্ষা).
 * Copied per app under static/css/ — keep in sync when tokens change.
 */
:root {
  --ease-brand-gradient: linear-gradient(90deg, #4338ca 0%, #7c3aed 50%, #2563eb 100%);
  --ease-header-fg: #ffffff;
  --ease-footer-bg: #111827;
  --ease-footer-fg: #d1d5db;
  --ease-footer-accent: #818cf8;
  --ease-footer-muted: #9ca3af;
  --ease-radius-md: 0.375rem;
  --ease-space-page: 1rem;

  /* ── Typography tokens ──────────────────────────────────────────────────── */
  --font-sans: "Hind Siliguri", "Noto Sans Bengali", "Segoe UI", system-ui, "Nirmala UI",
    "Kohinoor Bangla", "Bangla Sangam MN", -apple-system, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    "Liberation Mono", "Courier New", monospace;
  /* --font-ui is an alias for --font-sans (same value, clearer name for new
     code) — see docs/font-typography-system.md. Don't rename --font-sans
     itself, it's already referenced in many places (incl. a page-local
     alias in digitlibrary/notes_list.html). */
  --font-ui: var(--font-sans);
  /* Numbers/dates/currency/badges/step-indicators — set apart from body
     text so Bengali digits read clearly. Falls back to --font-sans, never
     to a generic sans-serif, so a slow/blocked Google Fonts load still
     looks like one coherent typeface. */
  --font-numeric: "Anek Bangla", var(--font-sans);

  /* Type scale — fluid clamp() for responsive sizing */
  --font-size-xs:   clamp(0.65rem,  0.6rem + 0.25vw, 0.75rem);
  --font-size-sm:   clamp(0.75rem,  0.7rem + 0.3vw,  0.875rem);
  --font-size-base: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
  --font-size-lg:   clamp(1rem,     0.9rem + 0.4vw,  1.125rem);
  --font-size-xl:   clamp(1.1rem,   1rem + 0.45vw,   1.25rem);
  --font-size-2xl:  clamp(1.25rem,  1.1rem + 0.55vw, 1.5rem);
  --font-size-3xl:  clamp(1.5rem,   1.3rem + 0.7vw,  1.875rem);

  /* Weight scale */
  --font-weight-normal:    400;
  --font-weight-medium:    500;
  --font-weight-semibold:  600;
  --font-weight-bold:      700;
  --font-weight-extrabold: 800;

  /* Line heights */
  --leading-tight:   1.1;
  --leading-snug:    1.375;
  --leading-normal:  1.5;
  --leading-relaxed: 1.55;

  /* ── Design-system color tokens ────────────────────────────────────────── */
  --sp-brand:         #3D52A0;
  --sp-brand-soft:    #7886C7;
  --sp-brand-bg:      #EEF0FA;
  --sp-brand-border:  #93A8D4;

  --sp-text-1:        #111827;
  --sp-text-2:        #374151;
  --sp-text-3:        #6B7280;
  --sp-text-4:        #9CA3AF;

  --sp-border:        #E5E7EB;
  --sp-border-soft:   #F3F4F6;

  --sp-logout-text:   #B91C1C;
  --sp-logout-border: #FECACA;
  --sp-logout-bg:     #FEF2F2;
  --sp-logout-bg-hov: #FEE2E2;

  --sp-nav:           #3B82F6;
  --sp-nav-strong:    #2563EB;
  --sp-nav-bg:        #DBEAFE;
  --sp-nav-bg-hov:    #EFF6FF;

  --sp-item-text:     #3D7A56;
  --sp-item-strong:   #166534;
  --sp-item-bg:       #DCFCE7;
  --sp-item-bg-hov:   #F0FDF4;
}

/* ===== Typography utilities — see docs/font-typography-system.md =====
   Hand-written (not Tailwind-generated) so these work immediately with no
   CLI rebuild. A matching `font-ui`/`font-numeric` Tailwind fontFamily
   extend exists in build/tailwind.config.js for parity, but stays inert
   until a human runs the Tailwind build — these two classes are the ones
   that actually take effect today. */
.font-ui { font-family: var(--font-ui); }
.font-numeric {
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
}

/* ===== Existing teacher-desk header/footer ===== */
.ease-site-header {
  background: var(--ease-brand-gradient);
  color: var(--ease-header-fg);
}

.ease-site-footer {
  background: var(--ease-footer-bg);
  color: var(--ease-footer-fg);
}

.ease-header-shell {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ease-space-page);
}

@media (max-width: 640px) {
  .ease-header-shell {
    flex-direction: column;
    align-items: flex-start;
  }
}

.ease-header-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  color: var(--ease-header-fg);
}

.ease-header-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
}

.ease-nav-link {
  color: var(--ease-header-fg);
  text-decoration: none;
  font-weight: var(--font-weight-medium);
  padding: 0.25rem 0.5rem;
  border-radius: var(--ease-radius-md);
}

.ease-nav-link:hover {
  background: rgba(255, 255, 255, 0.15);
}

/* ============================================================================
   Shared design system — reusable semantic component classes.
   ============================================================================ */

/* ===== Headings ===== */
.sp-section-label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--sp-text-3);
}
.sp-card-title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--sp-text-1);
  line-height: var(--leading-snug);
}

/* ===== Reusable text roles ===== */
.sp-body { font-size: var(--font-size-sm); color: var(--sp-text-2); }
.sp-meta { font-size: var(--font-size-xs); color: var(--sp-text-4); }
.sp-link { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--sp-brand); text-decoration: none; }
.sp-link:hover { text-decoration: underline; }

/* ===== Buttons ===== */
.sp-btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
  border-radius: 0.5rem;
  transition: background-color 0.15s, color 0.15s, transform 0.1s ease;
  cursor: pointer; border: none;
}
.sp-btn-primary {
  padding: 0.625rem 1.5rem;
  background: #2563eb; color: #fff;
}
.sp-btn-primary:hover { background: #1d4ed8; }
.sp-btn-secondary {
  padding: 0.625rem 1.5rem;
  background: #f3f4f6; color: #374151;
}
.sp-btn-secondary:hover { background: #e5e7eb; }

/* Touch target: components.css's pointer:coarse pass can't reach these two
   since they're defined here, not in the vendored block — see
   standard-components/README.md's adoption step 4. Previously missing
   entirely in teacher-desk (study-prep already had this). */
@media (pointer: coarse) {
  .sp-btn-primary,
  .sp-btn-secondary {
    min-height: 2.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* ===== Form inputs ===== */
.sp-input {
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: 0.5rem;
  background: #fff;
  font-size: var(--font-size-sm);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.sp-input:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}

/* ===== Layout shell (viewport-locked) ===== */
html { overflow-x: hidden; }

.sp-viewport {
  height: 100vh;
  overflow: hidden;
  font-family: var(--font-sans);
}

.sp-content-shell {
  flex: 1 1 0%;
  display: flex;
  overflow: hidden;
}

.sp-margin-x {
  padding-left: 5%;
  padding-right: 5%;
}

/* ===== Login split layout / brand marks / showcase panel: relocated to
   elearn-web/standard-components/css/auth-shell.css (appended below via
   sync.sh) — do not re-add here. ===== */
@media (max-width: 768px) {
  .sp-viewport { height: auto; min-height: 100vh; overflow: auto; }
  .sp-content-shell { overflow: auto; flex-direction: column; }
  .sp-light-nav-blue { font-size: var(--font-size-sm); }
}
@media (max-width: 480px) {
  .sp-margin-x { padding-left: 3%; padding-right: 3%; }
  .sp-light-nav-blue { font-size: var(--font-size-xs); }
  .sp-pill { font-size: var(--font-size-xs); padding: 0.3rem 0.75rem; }
}

.sp-overlay-z { z-index: 9999; }

/* ===== Light header (login page) ===== */
.sp-light-nav-blue {
  font-size: var(--font-size-base);
  color: #2563eb;
  text-decoration: none;
  white-space: nowrap;
  font-weight: var(--font-weight-medium);
}
.sp-light-nav-blue:hover { color: #1d4ed8; }
.sp-light-header-bar {
  min-height: 4.2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}
.sp-light-header-nav {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
@media (max-width: 640px) {
  .sp-light-header-bar { justify-content: center; }
  .sp-light-header-nav { justify-content: center; gap: 0.5rem; }
}
.sp-pill {
  border-radius: 999px;
  font-size: var(--font-size-sm);
  padding: 0.4rem 1rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}
.sp-pill-outline { color: var(--sp-nav-strong); border: 1px solid #C7D2FE; }
.sp-pill-outline:hover { background: var(--sp-nav-bg-hov); }
.sp-pill-solid { color: #fff; background: var(--sp-nav-strong); }
.sp-pill-solid:hover { background: #1d4ed8; }

/* ===== Account / Logout (matches top-nav link color + size, .sp-light-nav-blue) ===== */
.sp-logout {
  font-size: var(--font-size-base);
  color: #2563eb;
  font-weight: var(--font-weight-medium);
  border: 1px solid transparent;
  background: transparent;
  text-decoration: none;
}
.sp-logout:hover { color: #1d4ed8; background: var(--sp-nav-bg-hov); }
.sp-logout-danger { color: var(--sp-logout-text); }
.sp-logout-danger:hover { color: var(--sp-logout-text); background: var(--sp-logout-bg-hov); }

/* .sp-item*/.sp-dash-sidebar/.sp-dash-main now come from the
   standard-components: dashboard-shell.css block below (single source). */

/* === standard-components: assessment-card.css === */
/**
 * standard-components — assessment summary card
 * ============================================================================
 * SOURCE_VERSION: 2026-07-17
 *
 * Additive to each app's own static/css/brand-tokens.css. Deliberately
 * self-contained (own --sp-acard-* tokens, not borrowed from
 * question-card.css) so this file can be adopted independently — same
 * semantic palette (emerald/blue/red for full/pass/fail) but no hidden
 * cross-file dependency.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that — change the source here and re-sync instead.
 * ============================================================================
 */

:root {
  --sp-acard-full-text: #3B6D11;
  --sp-acard-pass-text: #185FA5;
  --sp-acard-fail-text: #854F0B;
  --sp-acard-badge-pending-bg:   #FFFBEB;
  --sp-acard-badge-pending-text: #B45309;
  --sp-acard-badge-pending-border: #FDE68A;
  --sp-acard-badge-reviewed-bg:   #ECFDF5;
  --sp-acard-badge-reviewed-text: #047857;
  --sp-acard-badge-reviewed-border: #6EE7B7;
}

.sp-acard {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  background: #fff;
  border: 1px solid var(--sp-border);
  border-radius: 0.5rem;
  padding: 0.75rem;
}

.sp-acard-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}

.sp-acard-title {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--sp-text-1);
}

.sp-acard-grade {
  font-size: var(--font-size-xs);
  color: var(--sp-text-3);
}

.sp-acard-meta {
  font-size: var(--font-size-xs);
  color: var(--sp-text-2);
}

.sp-acard-bottom {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: 0.25rem;
}

.sp-acard-actions { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }

.sp-acard-score { font-size: var(--font-size-sm); font-weight: 700; }
.sp-acard-score.is-full { color: var(--sp-acard-full-text); }
.sp-acard-score.is-pass { color: var(--sp-acard-pass-text); }
.sp-acard-score.is-fail { color: var(--sp-acard-fail-text); }

.sp-acard-badge {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  font-weight: 700;
  padding: 0.25rem 0.75rem;
  border-radius: 9999px;
  border: 1px solid transparent;
}
.sp-acard-badge.is-pending {
  background: var(--sp-acard-badge-pending-bg);
  color: var(--sp-acard-badge-pending-text);
  border-color: var(--sp-acard-badge-pending-border);
}
.sp-acard-badge.is-reviewed {
  background: var(--sp-acard-badge-reviewed-bg);
  color: var(--sp-acard-badge-reviewed-text);
  border-color: var(--sp-acard-badge-reviewed-border);
}
.sp-acard-badge.is-warning {
  background: var(--sp-acard-badge-pending-bg);
  color: var(--sp-acard-badge-pending-text);
  border-color: var(--sp-acard-badge-pending-border);
}
.sp-acard-badge.is-success {
  background: var(--sp-acard-badge-reviewed-bg);
  color: var(--sp-acard-badge-reviewed-text);
  border-color: var(--sp-acard-badge-reviewed-border);
}

/* === standard-components: auth-shell.css === */
/**
 * standard-components — auth-shell (login-page split layout + brand marks)
 * ============================================================================
 * SOURCE_VERSION: 2026-07-18
 *
 * Canonical home for .sp-login-*, .sp-brand-*, .sp-showcase*, .sp-feature-*
 * — previously hand-baked nearly-identically into teacher-desk's and
 * study-prep's own brand-tokens.css independently (confirmed byte-identical
 * at the time, but each app's login.html had already drifted to 6 different
 * independently-hand-picked feature-card accent colors). This is not a
 * byte-verbatim copy of either app's old rules — it bakes in a UX/
 * accessibility review's fixes (see standard-components/README.md):
 *   1. Caption/tagline/signature text uses --sp-text-3 (~4.6:1 contrast on
 *      white), not --sp-text-4 (~2.8:1 — fails WCAG AA).
 *   2. Feature-card accents reduced to 2 token-driven groups (.sp-feature-
 *      accent-a / -b) instead of 6 independently-hand-picked hex colors.
 *   3. .sp-promo-line replaces the old two-colorful-boxes "news/offers"
 *      pattern with one low-emphasis line.
 *   4. .sp-feature-grid adds a 1-column fallback under 400px width (the old
 *      grid was hardcoded 2-column Tailwind utility classes with no mobile
 *      override).
 *   5. .sp-feature-card/.sp-feature-num/.sp-feature-title get min-width: 0 +
 *      overflow-wrap so long Bengali labels wrap inside the card instead of
 *      overflowing past its border on narrow phones — grid/flex items
 *      default to min-width: auto, which lets unbreakable text push past
 *      the container's own width unless explicitly overridden.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that append — change the source here and re-sync instead.
 * ============================================================================
 */

/* ===== Brand marks ===== */
.sp-brand-name {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: -0.01em;
  line-height: 1.1;
  background: var(--ease-brand-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.sp-brand-tagline {
  font-size: var(--font-size-sm);
  color: var(--sp-text-3);
  line-height: 1.25;
  margin-top: 2px;
}
.sp-brand-tile {
  background: var(--ease-brand-gradient);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ===== Login split layout ===== */
.sp-login-split {
  display: flex;
  width: 100%;
  height: 100%;
}
.sp-login-left {
  width: 50%;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  overflow-y: auto;
  padding-top: 2.5rem;
}
.sp-login-right {
  width: 50%;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  padding-top: 1.5rem;
  padding-bottom: 1.5rem;
}
.sp-login-headline {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  color: var(--sp-text-1);
  line-height: 1.35;
}

/* ===== Showcase panel (right side) ===== */
.sp-showcase {
  background: linear-gradient(135deg, #EEF0FA 0%, #E5E9FB 100%);
  position: relative;
  overflow: hidden;
}
.sp-showcase-blob {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}
.sp-showcase-title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--sp-brand);
}

/* ===== Feature cards — 2 accent groups instead of 6 independent hex colors ===== */
.sp-feature-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}
@media (max-width: 400px) {
  .sp-feature-grid { grid-template-columns: 1fr; }
}
.sp-feature-card {
  background: #fff;
  border-radius: 0.75rem;
  padding: 1rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
  min-width: 0;
}
.sp-feature-num {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  line-height: 1.1;
  overflow-wrap: break-word;
}
.sp-feature-title {
  display: flex;
  align-items: center;
  gap: 5px;
  font-weight: 700;
  font-size: var(--font-size-sm);
  min-width: 0;
  overflow-wrap: break-word;
}
.sp-feature-label {
  font-size: var(--font-size-xs);
  color: var(--sp-text-3);
  margin-top: 2px;
}
.sp-feature-accent-a { border-left: 3px solid var(--sp-brand); }
.sp-feature-accent-a .sp-feature-num,
.sp-feature-accent-a .sp-feature-title { color: var(--sp-brand); }

.sp-feature-accent-b { border-left: 3px solid #0d9488; }
.sp-feature-accent-b .sp-feature-num,
.sp-feature-accent-b .sp-feature-title { color: #0d9488; }

/* ===== Promo line — replaces the old two-colorful-box "news/offers" pattern ===== */
.sp-promo-line {
  background: var(--sp-brand-bg);
  border-radius: 0.5rem;
  padding: 0.75rem 0.875rem;
  font-size: var(--font-size-sm);
  color: var(--sp-text-2);
  line-height: 1.5;
}
.sp-promo-line strong { color: var(--sp-brand); }

/* ===== Responsive — kept alongside the base rules above on purpose: these
   MUST stay co-located with their base .sp-login-*/.sp-feature-* rules in
   the same appended block, or CSS source order breaks the override (a base
   rule appended later in the file would otherwise beat an earlier media-
   query rule at equal specificity, regardless of which file either came
   from). General shell classes (.sp-viewport, .sp-content-shell,
   .sp-light-nav-blue, .sp-pill, .sp-margin-x) are NOT part of this
   component — their responsive rules stay in each app's own
   brand-tokens.css, untouched. ===== */
@media (max-width: 1024px) {
  .sp-login-left { width: 55%; }
  .sp-login-right { width: 45%; }
  .sp-feature-card { padding: 0.625rem; }
  .sp-feature-num { font-size: var(--font-size-lg); }
  .sp-showcase-title { font-size: var(--font-size-base); }
}
@media (max-width: 768px) {
  .sp-login-split { flex-direction: column; overflow-y: auto; }
  .sp-login-left {
    width: 100%;
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;
    overflow-y: visible;
  }
  .sp-login-right {
    width: 100%;
    padding: 1.5rem 0;
    overflow-y: visible;
  }
}
@media (max-width: 480px) {
  .sp-login-left { padding-top: 1rem; }
  .sp-login-headline { font-size: var(--font-size-xl); }
  .sp-feature-card { padding: 0.5rem; }
  .sp-feature-num { font-size: var(--font-size-base); }
}
@media (max-width: 360px) {
  .sp-feature-num { font-size: var(--font-size-sm); }
  .sp-feature-title { font-size: var(--font-size-xs); }
}

/* === standard-components: components.css === */
/**
 * standard-components — button additions
 * ============================================================================
 * SOURCE_VERSION: 2026-07-27
 *
 * Additive to each app's own static/css/brand-tokens.css, which already
 * defines .sp-btn / .sp-btn-primary / .sp-btn-secondary and the --sp-* color
 * tokens these rules build on. This file only adds what brand-tokens.css is
 * missing (danger/success/warning/ghost/outline/pill variants, icon-only +
 * loading-spinner modifiers, a small size modifier) — it does not redefine
 * .sp-btn/-primary/-secondary.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that append — change the source here and re-sync instead.
 *
 * 2026-07-26: added a `pointer: coarse` touch-target pass. None of these
 * variants had a min-height/min-width before — height came purely from
 * padding + text line-height, landing well under the ~44px guideline (sm/
 * pill ≈24-26px, icon-only ≈20-24px square). Scoped to `pointer: coarse` so
 * mouse/desktop users keep the original compact sizing; only touch devices
 * get the larger targets. `.sp-btn`/`.sp-btn-primary`/`.sp-btn-secondary`
 * live in each app's own brand-tokens.css (not here — see note above), so
 * each app must add the matching rule to its own base button block; see
 * README.md's adoption steps.
 *
 * 2026-07-27: fixed contrast on danger/success/warning — the original
 * white-on-#ef4444/#16a34a/#f59e0b fills measured ~3.8:1 / ~3.4:1 / ~2.1:1,
 * all below WCAG AA's 4.5:1 for normal text (warning was the worst
 * offender). Darkened each fill to its own former hover shade (danger,
 * success) or one step further (warning, since even its hover shade
 * #d97706 only reaches ~2.9:1) so white text now measures ~4.8:1 / ~5.0:1 /
 * ~5.0:1 respectively — all pass AA. Hover shades moved one step darker
 * again so hover still reads as a distinct, darker state. Added
 * `.sp-btn:active` press feedback while here (was entirely absent before —
 * the only `active:scale` in either app was a one-off hand-rolled instance
 * outside this shared system).
 * ============================================================================
 */

/* Press feedback — relies on each app's base .sp-btn already declaring
   `transform` in its own `transition` property (see README.md's adoption
   note) so this animates instead of snapping instantly. */
.sp-btn:active {
  transform: scale(0.97);
}

.sp-btn-danger {
  padding: 0.625rem 1.5rem;
  background: #dc2626;
  color: #fff;
}
.sp-btn-danger:hover { background: #b91c1c; }

.sp-btn-success {
  padding: 0.625rem 1.5rem;
  background: #15803d;
  color: #fff;
}
.sp-btn-success:hover { background: #166534; }

.sp-btn-warning {
  padding: 0.625rem 1.5rem;
  background: #b45309;
  color: #fff;
}
.sp-btn-warning:hover { background: #92400e; }

.sp-btn-ghost {
  padding: 0.625rem 1.5rem;
  background: transparent;
  color: var(--sp-text-2);
}
.sp-btn-ghost:hover { background: var(--sp-border-soft); }

.sp-btn-outline-danger {
  padding: 0.625rem 1.5rem;
  background: transparent;
  color: #dc2626;
  border: 1px solid #fecaca;
}
.sp-btn-outline-danger:hover { background: #fef2f2; color: #b91c1c; }

.sp-btn-outline-primary {
  padding: 0.625rem 1.5rem;
  background: #fff;
  color: var(--sp-brand);
  border: 1px solid var(--sp-brand-border);
}
.sp-btn-outline-primary:hover { background: var(--sp-brand-bg); }

.sp-btn-sm {
  padding: 0.375rem 1rem;
  font-size: var(--font-size-xs);
}

.sp-btn:disabled,
.sp-btn[disabled] {
  opacity: 0.6;
  cursor: not-allowed;
}

/* ===== Pill / toggle filter chip — e.g. সব / শেয়ার্ড two-state filters ===== */
.sp-btn-pill {
  padding: 0.375rem 1rem;
  font-size: var(--font-size-xs);
  border-radius: 999px;
  background: transparent;
  color: var(--sp-text-3);
  border: 1px solid var(--sp-border);
}
.sp-btn-pill:hover { background: var(--sp-border-soft); }
.sp-btn-pill.is-active {
  background: var(--sp-brand);
  border-color: var(--sp-brand);
  color: #fff;
}
.sp-btn-pill.is-active:hover { background: var(--sp-brand); }

/* ===== Icon-only — square footprint, no visible label (aria-label carries
   the accessible name, see _button.html) ===== */
.sp-btn-icon {
  padding: 0.375rem;
  min-width: 0;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ===== Loading spinner — swapped in for the icon slot when loading=True ===== */
.sp-btn-spinner {
  width: 1em;
  height: 1em;
  animation: sp-btn-spin 0.75s linear infinite;
  vertical-align: -0.15em;
}
.sp-btn-spinner-track { opacity: 0.25; }
.sp-btn-spinner-arc { opacity: 0.9; }
@keyframes sp-btn-spin {
  to { transform: rotate(360deg); }
}

/* ===== Touch targets: bump to ~44px on touch devices only (mouse/desktop
   sizing above is untouched) — see 2026-07-17 header note ===== */
@media (pointer: coarse) {
  .sp-btn-danger,
  .sp-btn-success,
  .sp-btn-warning,
  .sp-btn-ghost,
  .sp-btn-outline-danger,
  .sp-btn-outline-primary {
    min-height: 2.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .sp-btn-sm {
    min-height: 2.75rem;
    min-width: 2.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .sp-btn-pill {
    min-height: 2.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .sp-btn-icon {
    min-height: 2.75rem;
    min-width: 2.75rem;
  }
}

/* === standard-components: dashboard-shell.css === */
/**
 * standard-components — dashboard shell (sidebar + bottom nav + content blocks)
 * ============================================================================
 * SOURCE_VERSION: 2026-07-21
 *
 * Relocates .sp-dash-sidebar/.sp-dash-main/.sp-item* (already token-driven,
 * confirmed byte-identical between teacher-desk and study-prep — pure
 * relocation, not a redesign) and adds what was missing per a UX review:
 *
 *   1. The sidebar fully disappears below 768px with NO replacement in both
 *      apps today (confirmed: teacher-desk has dead hamburger-toggle code
 *      with mismatched element IDs; study-prep has no mobile-nav attempt at
 *      all). .sp-bottom-nav* fixes this with a fixed bottom tab bar.
 *   2. .sp-stat-card* — a token-driven stat-card recipe, replacing
 *      teacher-desk's 4 independently-hand-picked hex colors per card.
 *   3. .sp-activity-card* — a token-driven activity-list card recipe,
 *      replacing study-prep's hardcoded Tailwind text-gray-* usage.
 *   4. Contrast fix: meta/timestamp text uses --sp-text-3 (~4.6:1), not
 *      --sp-text-4 (~2.8:1, fails WCAG AA) — same fix already applied in
 *      auth-shell.css, extended here to dashboard-scoped text.
 *
 * 2026-07-19: added .sp-dash-sidebar.is-open + .sp-sidebar-close/
 * .sp-sidebar-backdrop — the ☰ hamburger in app-header.css's mobile action
 * row dispatches a `toggle-sidebar` window event; each app's own
 * _sidebar.html toggles `sidebarOpen` and applies `.is-open` to
 * #left-sidebar. Previously neither app had any CSS for this at all, so
 * the hamburger dispatched the event but nothing visible happened.
 *
 * 2026-07-21: the mobile `.sp-dash-sidebar { display: none }` rule was
 * silently losing to Tailwind's plain `.flex{display:flex}` utility (also
 * present on the sidebar's own class list, unconditionally) — both are
 * single-class selectors of equal specificity, and each app's compiled
 * app.css loads AFTER this file, so app.css's `.flex` won the cascade tie
 * and the sidebar stayed visible on mobile regardless of this media query.
 * Fixed by adding the `#left-sidebar` id (already present in both apps'
 * _sidebar.html markup) alongside the class selector — an id beats a class
 * regardless of source order, so this can no longer lose to any
 * unconditional utility class.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that append — change the source here and re-sync instead.
 * ============================================================================
 */

/* ===== Sidebar shell (relocated, unchanged behavior) ===== */
.sp-dash-sidebar {
  width: 20%;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}
.sp-dash-main {
  width: 80%;
  overflow-y: auto;
  overflow-x: hidden;
}

/* ===== Sidebar nav items (relocated, unchanged behavior) ===== */
.sp-item {
  font-size: var(--font-size-sm);
  color: var(--sp-item-text);
}
.sp-item:hover {
  background: var(--sp-item-bg-hov);
  color: var(--sp-item-strong);
}
.sp-item.is-active {
  background: var(--sp-item-bg);
  color: var(--sp-item-strong);
  font-weight: var(--font-weight-medium);
}
.sp-item-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* ===== Bottom tab bar (NEW — mobile navigation fallback) ===== */
.sp-bottom-nav {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: #fff;
  border-top: 1px solid var(--sp-border);
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.sp-bottom-nav-list {
  display: flex;
  align-items: stretch;
  justify-content: space-around;
}
.sp-bottom-nav-item {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0.5rem 0.25rem;
  color: var(--sp-item-text);
  font-size: var(--font-size-xs);
  text-decoration: none;
  cursor: pointer;
  border: none;
  background: transparent;
}
.sp-bottom-nav-item .sp-item-icon { width: 20px; height: 20px; }
.sp-bottom-nav-item.is-active {
  color: var(--sp-item-strong);
  font-weight: var(--font-weight-medium);
}

/* ===== Responsive: sidebar hidden below 768px, reachable instead as a
   ☰-triggered overlay drawer (see app-header.css's .sp-app-header-hamburger
   and each app's own base.html header). .sp-bottom-nav stays available for
   any app that hasn't adopted the app-header.css mobile pattern yet. ===== */
@media (max-width: 768px) {
  .sp-dash-sidebar, #left-sidebar { display: none; }
  .sp-dash-main { width: 100%; padding-bottom: 4rem; }
  .sp-bottom-nav { display: block; }
  .sp-dash-sidebar.is-open,
  #left-sidebar.is-open {
    display: flex;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: 85%;
    max-width: 20rem;
    z-index: 50;
    box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
  }
}

/* ===== Sidebar mobile-drawer overlay controls — close button + backdrop.
   Hidden entirely on desktop; only meaningful once .sp-dash-sidebar.is-open
   (above) makes the drawer visible. ===== */
.sp-sidebar-close,
.sp-sidebar-backdrop {
  display: none;
}
@media (max-width: 767px) {
  .sp-sidebar-searchrow {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .sp-sidebar-searchrow .sp-input {
    flex: 1;
    min-width: 0;
  }
  .sp-sidebar-close {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    border: none;
    background: transparent;
    color: var(--sp-item-strong);
    font-size: 1.1rem;
    cursor: pointer;
  }
  .sp-sidebar-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 40;
  }
}

/* ===== Stat cards (NEW — token-driven, one recipe instead of N hex colors) ===== */
.sp-stat-card {
  background: #fff;
  border: 1px solid var(--sp-border);
  border-radius: 0.75rem;
  padding: 1rem;
}
.sp-stat-card .sp-stat-bar {
  height: 3px;
  border-radius: 999px;
  margin-bottom: 0.75rem;
  background: var(--sp-brand);
}
.sp-stat-num {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  color: var(--sp-text-1);
  line-height: 1.1;
}
.sp-stat-label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--sp-text-3);
}
.sp-stat-card.sp-stat-accent-b .sp-stat-bar { background: #0d9488; }

/* ===== Activity cards (NEW — token-driven, replaces hardcoded text-gray-*) ===== */
.sp-activity-card {
  background: #fff;
  border: 1px solid var(--sp-border);
  border-radius: 0.75rem;
  overflow: hidden;
}
.sp-activity-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--sp-border-soft);
}
.sp-activity-row:last-child { border-bottom: none; }
.sp-activity-title {
  font-size: var(--font-size-sm);
  color: var(--sp-text-2);
  font-weight: var(--font-weight-medium);
}
.sp-activity-meta {
  font-size: var(--font-size-xs);
  color: var(--sp-text-3);
}
/* === standard-components: creative-question-card.css === */
/**
 * standard-components — creative question (সৃজনশীল) card
 * ============================================================================
 * SOURCE_VERSION: 2026-07-22
 *
 * Additive to each app's own static/css/brand-tokens.css. Self-contained
 * --sp-crcard-* tokens (amber passage box, one consistent orange shade for
 * every part row regardless of source data shape — fixes the orange-300-vs-
 * orange-200 inconsistency found across teacher-desk's independent CR
 * implementations).
 *
 * .sp-crcard-part-hint (added 2026-07-22, mode='hint'): an optional model-
 * answer excerpt shown inline below a part row, separated by a dashed
 * divider — replaces apps' previous ad hoc blue "answer hint" side panels.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that — change the source here and re-sync instead.
 * ============================================================================
 */

:root {
  --sp-crcard-passage-bg:     #FFFBEB;  /* amber-50 */
  --sp-crcard-passage-border: #FBBF24;  /* amber-400 */
  --sp-crcard-passage-label:  #B45309;  /* amber-700 */

  --sp-crcard-part-bg:     #FFF7ED;  /* orange-50 */
  --sp-crcard-part-border: #FFEDD5;  /* orange-100 */
  --sp-crcard-part-badge-bg:   #FDBA74;  /* orange-300 — one shade, always */
  --sp-crcard-part-badge-text: #7C2D12;  /* orange-900 */

  --sp-crcard-answer-bg:     #FFF7ED;  /* orange-50, matches part rows */
  --sp-crcard-answer-border: #FFEDD5;  /* orange-100 */

  --sp-crcard-hint-bg:     #ECFEFF;  /* cyan-50 — blue-green tint, distinct from left orange */
  --sp-crcard-hint-border: #99F6E4;  /* teal-200 */
  --sp-crcard-hint-text:   #115E59;  /* teal-800 */

  /* mode='hint' unified cards (note-creator): one card for passage+parts,
     one card for all answer hints — replaces the old passage-stripe +
     per-part boxes + per-answer boxes look (2026-08-08). */
  --sp-crcard-qcard-bg:     #FFFBEB;  /* amber-50 */
  --sp-crcard-qcard-border: #FDE68A;  /* amber-200 */
  --sp-crcard-qcard-divider: #FDE68A;  /* amber-200 */
}

.sp-crcard {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  background: #fff;
  border: 1px solid var(--sp-border);
  border-radius: 0.75rem;
  padding: 1rem;
}

.sp-crcard-header { display: flex; align-items: center; }

.sp-crcard-number {
  flex-shrink: 0;
  width: auto;
  min-width: 1.75rem;
  height: 1.75rem;
  padding: 0 0.65rem;
  border-radius: 9999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: 700;
  background: var(--sp-brand);
  color: #fff;
  white-space: nowrap;
}

.sp-crcard-passage {
  background: var(--sp-crcard-passage-bg);
  border-left: 4px solid var(--sp-crcard-passage-border);
  border-radius: 0 0.5rem 0.5rem 0;
  padding: 0.75rem 1rem;
}

.sp-crcard-passage-top {
  display: block;
}

.sp-crcard-passage-text {
  color: var(--sp-text-1);
  font-size: var(--font-size-sm);
  line-height: 1.6;
  text-align: left;
}

.sp-crcard-number-inline {
  display: inline-flex;
  vertical-align: middle;
  margin-right: 0.5rem;
}

.sp-crcard-source-badge {
  display: inline-block;
  vertical-align: middle;
  margin-left: 0.5rem;
  font-size: 11px;
  padding: 0.125rem 0.5rem;
  border-radius: 0.25rem;
  border: 1px solid var(--sp-nav-bg);
  background: var(--sp-nav-bg-hov);
  color: var(--sp-nav-strong);
  font-weight: var(--font-weight-medium);
}

.sp-crcard-parts { display: flex; flex-direction: column; gap: 0.5rem; }

.sp-crcard-parts-plain {
  margin-left: 0.5rem;
  padding-left: 0.75rem;
}

.sp-crcard-parts-plain .sp-crcard-part-row {
  padding: 0.375rem 0;
  border-bottom: 1px solid var(--sp-border);
}

.sp-crcard-parts-plain .sp-crcard-part-row:last-child { border-bottom: none; }

.sp-crcard-parts-hint {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem;
}

@media (min-width: 768px) {
  .sp-crcard-parts-hint { grid-template-columns: 1fr 1fr; gap: 1rem; }
}

.sp-crcard-parts-col { display: flex; flex-direction: column; gap: 0.5rem; }

.sp-crcard-parts-hint .sp-crcard-part-hint {
  border-top: none;
  padding-top: 0;
}

/* mode='hint' unified cards: passage + all parts sit in one .sp-crcard-qcard,
   all answer hints sit in one .sp-crcard-acard — single flat color each, no
   per-row boxing and no passage side-stripe. */
.sp-crcard-qcard {
  display: flex;
  flex-direction: column;
  background: var(--sp-crcard-qcard-bg);
  border: 1px solid var(--sp-crcard-qcard-border);
  border-radius: 0.75rem;
  padding: 0.75rem 1rem;
}

.sp-crcard-acard {
  display: flex;
  flex-direction: column;
  background: var(--sp-crcard-hint-bg);
  border: 1px solid var(--sp-crcard-hint-border);
  border-radius: 0.75rem;
  padding: 0.75rem 1rem;
}

.sp-crcard-qcard .sp-crcard-part-row,
.sp-crcard-acard .sp-crcard-part-row {
  padding: 0.5rem 0;
}

.sp-crcard-qcard .sp-crcard-passage-top + .sp-crcard-part-row,
.sp-crcard-qcard .sp-crcard-part-row + .sp-crcard-part-row,
.sp-crcard-acard .sp-crcard-part-row + .sp-crcard-part-row {
  border-top: 1px dashed var(--sp-crcard-qcard-divider);
}

.sp-crcard-acard .sp-crcard-part-row + .sp-crcard-part-row {
  border-top-color: var(--sp-crcard-hint-border);
}

.sp-crcard-qcard .sp-crcard-part-row:first-child {
  padding-top: 0;
}

.sp-crcard-acard .sp-crcard-part-row:first-child {
  padding-top: 0;
}

.sp-crcard-acard .sp-crcard-part-hint { color: var(--sp-crcard-hint-text); flex: 1; }

/* Still used directly (not via the macro) by paper_step3_review.html's
   "নমুনা উত্তর দেখুন" accordion rows — keep independent of the mode='hint'
   unified-card rules above. */
.sp-crcard-part.sp-crcard-part-answer {
  background: var(--sp-crcard-hint-bg);
  border-color: var(--sp-crcard-hint-border);
}

.sp-crcard-part-letter-answer {
  color: var(--sp-crcard-hint-text);
  background: var(--sp-crcard-hint-border);
}

.sp-crcard-part {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--sp-crcard-part-bg);
  border: 1px solid var(--sp-crcard-part-border);
  border-radius: 0.75rem;
  padding: 0.5rem 0.75rem;
  font-size: var(--font-size-sm);
  color: var(--sp-text-2);
}

.sp-crcard-part-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.sp-crcard-part-hint {
  margin: 0;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--sp-crcard-part-border);
  font-size: var(--font-size-sm);
  color: var(--sp-crcard-passage-label);
  line-height: 1.5;
  text-align: justify;
}

.sp-crcard-part-letter {
  flex-shrink: 0;
  font-weight: 700;
  color: var(--sp-crcard-part-badge-text);
  background: var(--sp-crcard-part-badge-bg);
  border-radius: 9999px;
  width: 1.75rem;
  height: 1.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
}

.sp-crcard-part-text { flex: 1; line-height: 1.5; text-align: justify; }

.sp-crcard-part-marks {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  color: var(--sp-text-4);
}

.sp-crcard-answer {
  background: var(--sp-crcard-answer-bg);
  border: 1px solid var(--sp-crcard-answer-border);
  border-radius: 0.75rem;
  padding: 1rem;
}

.sp-crcard-answer-label {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--sp-crcard-passage-label);
  margin-bottom: 0.5rem;
}

.sp-crcard-answer-image {
  max-width: 100%;
  max-height: 16rem;
  border-radius: 0.5rem;
  border: 1px solid var(--sp-border);
  object-fit: contain;
}

.sp-crcard-answer-empty {
  font-size: var(--font-size-sm);
  color: var(--sp-text-4);
  font-style: italic;
}

/* AI answer regeneration (mode='hint', opt-in) — see
   docs/features/library/cr-answer-regeneration.md */
.sp-crcard-answer-regen {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--sp-border);
}

.sp-crcard-regen-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.sp-crcard-regen-btn {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--sp-brand);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}
.sp-crcard-regen-btn:hover { text-decoration: underline; }

.sp-crcard-regen-spinner {
  font-size: var(--font-size-xs);
  color: var(--sp-text-4);
  /* .htmx-indicator's default opacity-based show/hide (htmx core CSS,
     already relied on elsewhere in this app — see step4_save.py's
     spinner) handles visibility; no extra display override needed here. */
}

.sp-crcard-candidates { margin-top: 0.375rem; }

.sp-crcard-score {
  font-size: var(--font-size-sm);
  font-weight: 700;
  color: var(--sp-brand);
  margin-top: 0.5rem;
}

.sp-crcard-comment {
  font-size: var(--font-size-sm);
  color: var(--sp-crcard-passage-label);
  margin-top: 0.25rem;
}

.sp-crcard-file-input {
  font-size: var(--font-size-sm);
  color: var(--sp-text-2);
}

/* ===== App-specific (NOT vendored — do not move into sync.sh's copy):
   .sp-crcard-neutral neutralizes the CRQ card's amber/orange passage+part
   highlighting for assessment-creation review screens (shared/_question_card.html),
   so a CRQ card matches the plain white/blue MCQ card sitting next to it in
   the same list. Other callers of creative_question_card (note-creator hint
   mode, digitlibrary preview, etc.) are unaffected — they don't wrap in this
   class and keep the component's default colors. ===== */
.sp-crcard-neutral .sp-crcard-passage {
  background: #fff;
  border-left: none;
  padding-left: 0;
}
.sp-crcard-neutral .sp-crcard-qcard {
  background: #fff;
  border-color: var(--sp-border);
}
.sp-crcard-neutral .sp-crcard-part-letter {
  background: var(--sp-brand);
  color: #fff;
}

/* .sp-crcard-flat strips the CR card's own outer border/padding/background
   for callers that already wrap it in their own bordered card (e.g.
   digitlibrary's paper_step2_select.html / paper_step3_review.html "প্রশ্ন
   N" list rows) — avoids a card-inside-a-card look. Other callers that
   don't wrap in this class keep the component's own border. */
.sp-crcard-flat .sp-crcard {
  border: none;
  border-radius: 0;
  padding: 0;
  background: transparent;
}

/* === standard-components: question-card.css === */
/**
 * standard-components — MCQ question card
 * ============================================================================
 * SOURCE_VERSION: 2026-07-21
 *
 * Additive to each app's own static/css/brand-tokens.css. Defines new
 * semantic tokens for "correct answer" / "wrong answer" / "not yet revealed"
 * — brand-tokens.css has no such tokens today (--sp-item-* is scoped to nav
 * active-state, --sp-logout-* to the logout button; neither fits a question
 * card semantically), so this file adds dedicated ones rather than repurpose
 * either.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that — change the source here and re-sync instead.
 * ============================================================================
 */

:root {
  --sp-correct-bg:     #ECFDF5;   /* emerald-50 */
  --sp-correct-border: #6EE7B7;   /* emerald-300 */
  --sp-correct-text:   #047857;   /* emerald-700 */

  --sp-wrong-bg:        #FEF2F2;  /* red-50 */
  --sp-wrong-border:    #FCA5A5;  /* red-300 */
  --sp-wrong-text:      #B91C1C;  /* red-700 — matches --sp-logout-text */

  --sp-neutral-bg:      #F9FAFB;  /* gray-50 */
  --sp-neutral-border:  #F3F4F6;  /* matches --sp-border-soft */
  --sp-neutral-text:    #6B7280;  /* matches --sp-text-3 */
}

.sp-qcard {
  background: #fff;
  border: 2px solid var(--sp-neutral-border);
  border-radius: 0.75rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  padding: 1rem;
}

.sp-qcard-header { display: flex; align-items: flex-start; gap: 0.75rem; margin-bottom: 0.75rem; }

.sp-qcard-number {
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 9999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: 700;
  background: var(--sp-brand-bg, #EEF0FA);
  color: var(--sp-brand, #3D52A0);
}
.sp-qcard-number.is-correct { background: var(--sp-correct-bg); color: var(--sp-correct-text); }
.sp-qcard-number.is-wrong   { background: var(--sp-wrong-bg);   color: var(--sp-wrong-text); }

.sp-qcard-question-wrap { flex: 1; min-width: 0; }

.sp-qcard-question {
  color: var(--sp-text-1);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}

.sp-qcard-options { margin-left: 2.5rem; display: flex; flex-direction: column; gap: 0.5rem; }

.sp-qcard-option {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: var(--font-size-sm);
  border-radius: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--sp-neutral-bg);
  border: 1px solid transparent;
  color: var(--sp-text-2);
}
.sp-qcard-option.is-correct {
  background: var(--sp-correct-bg);
  border-color: var(--sp-correct-border);
  color: var(--sp-correct-text);
}
.sp-qcard-option.is-wrong {
  background: var(--sp-wrong-bg);
  border-color: var(--sp-wrong-border);
  color: var(--sp-wrong-text);
}

.sp-qcard-option-letter { font-weight: 700; flex-shrink: 0; }

.sp-qcard-badge {
  margin-left: auto;
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  font-weight: 700;
}
.sp-qcard-badge.is-correct { color: var(--sp-correct-text); }
.sp-qcard-badge.is-wrong   { color: var(--sp-wrong-text); }

/* Neutral "picked this, not yet graded" state — used while an assessment is
   still pending teacher review, so the student sees what they answered
   without any correct/wrong coloring. */
.sp-qcard-option.is-selected {
  background: var(--sp-brand-bg);
  border-color: var(--sp-brand-border);
  color: var(--sp-brand);
}
.sp-qcard-badge.is-selected { color: var(--sp-brand); }

/* Student picked this option, but there's no confirmed correct answer yet
   (teacher hasn't set/approved an answer key) — used by the grading
   screen's 4-state option model alongside is-correct/is-wrong/is-selected. */
.sp-qcard-option.is-pending {
  background: var(--sp-pending-bg);
  border-color: var(--sp-pending-border);
  color: var(--sp-pending-text);
}
.sp-qcard-badge.is-pending { color: var(--sp-pending-text); }

.sp-qcard-source-badge {
  display: inline-block;
  font-size: 11px;
  padding: 0.125rem 0.5rem;
  margin-left: 0.25rem;
  border-radius: 0.25rem;
  border: 1px solid var(--sp-nav-bg);
  background: var(--sp-nav-bg-hov);
  color: var(--sp-nav-strong);
  font-weight: var(--font-weight-medium);
}

/* Live-input (taking) variant — same visual language, selectable option row */
.sp-qcard-option-input {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  border: 1px solid var(--sp-border);
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--sp-text-2);
}
.sp-qcard-option-input:hover { border-color: var(--sp-brand-border); background: var(--sp-brand-bg); }
.sp-qcard-option-input:has(input:checked) {
  border-color: var(--sp-brand);
  background: var(--sp-brand-bg);
}

/* === standard-components: tab-bar.css === */
/**
 * standard-components — tab bar
 * ============================================================================
 * SOURCE_VERSION: 2026-07-19
 *
 * Additive to each app's own static/css/brand-tokens.css. Moved verbatim
 * from study-prep's own hand-written .sp-tab rules (values unchanged) so
 * every app now has one source of truth instead of a per-app duplicate.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that — change the source here and re-sync instead.
 *
 * 2026-07-19: active tabs previously only changed color+underline while
 * every tab (active and inactive) was already font-weight:semibold — the
 * "bolder when active" signal was fully diluted. Inactive tabs now sit at
 * `medium` weight and active tabs move up to `bold`, so weight is a real,
 * distinguishing signal again (per the accessibility guideline that active
 * state shouldn't rely on color alone). Also added a visible
 * `:focus-visible` ring, since the new keyboard arrow-key navigation
 * (tab_bar.js) needs a clear focus indicator to be usable. Active color
 * switched from a hardcoded #2563eb to `var(--sp-brand)` so it follows each
 * app's actual brand token instead of a one-off blue.
 *
 * 2026-07-19: added the `is-stacked` variant (icon above label, equal-width
 * flex-1 tabs) — this is teacher-desk's own hand-rolled "detail panel tabs"
 * pattern (👤 ব্যক্তিগত / 📅 উপস্থিতি / 💳 পেমেন্ট / 📊 একাডেমিক), previously
 * copy-pasted verbatim into 3 separate templates with no shared CSS at all.
 * Folded in here so all 3 call sites can move onto the shared macro instead
 * of maintaining 3 independent copies of the same markup.
 * ============================================================================
 */

.sp-tab-bar {
  display: flex;
  gap: 0.25rem;
}

.sp-tab-bar.is-segmented {
  background: #f3f4f6;
  border-radius: 0.75rem;
  padding: 0.25rem;
}

.sp-tab {
  padding: 0.625rem 1.25rem;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  border-bottom: 2px solid transparent;
  border-radius: 0.5rem;
  color: var(--sp-item-text);
  transition: color 0.15s, border-color 0.15s;
  cursor: pointer;
  background: none;
  border-top: none;
  border-left: none;
  border-right: none;
}

.sp-tab:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

.sp-tab.is-active {
  color: var(--sp-brand);
  font-weight: var(--font-weight-bold);
  border-bottom-color: var(--sp-brand);
}

/* ===== Stacked variant: icon above label, equal-width tabs — used for
   e.g. a detail-panel's own internal tab strip (teacher-desk's student
   detail: ব্যক্তিগত/উপস্থিতি/পেমেন্ট/একাডেমিক) ===== */
.sp-tab-bar.is-stacked {
  border-bottom: 1px solid var(--sp-border);
  background: #fff;
}
.sp-tab-bar.is-stacked .sp-tab {
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  gap: 0.125rem;
  padding: 0.75rem 0.5rem;
  font-size: var(--font-size-xs);
  border-radius: 0;
}
.sp-tab-bar.is-stacked .sp-tab-icon {
  font-size: 1rem;
  line-height: 1;
}

/* === standard-components: toast.css === */
/**
 * standard-components — error toast
 * ============================================================================
 * SOURCE_VERSION: 2026-07-17
 *
 * Additive to each app's own static/css/brand-tokens.css, which already
 * defines --sp-logout-text/-border/-bg (reused here as the error/danger
 * palette instead of inventing new colors) and, where the dashboard-shell
 * component has been adopted, --sp-bottom-nav-h (used so the toast clears
 * the mobile bottom tab bar).
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that append — change the source here and re-sync instead.
 * ============================================================================
 */

.sp-toast-container {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(1rem + var(--sp-bottom-nav-h, 0px));
  z-index: 9999;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  max-width: calc(100vw - 2rem);
  pointer-events: none;
}

.sp-toast {
  pointer-events: auto;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.25rem;
  border-radius: 0.75rem;
  background: var(--sp-logout-bg, #FEF2F2);
  color: var(--sp-logout-text, #B91C1C);
  border: 1px solid var(--sp-logout-border, #FECACA);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  font-size: var(--font-size-sm);
  line-height: var(--leading-snug, 1.375);
  max-width: 28rem;
  text-align: center;
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.sp-toast.is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .sp-toast-container {
    bottom: calc(1rem + var(--sp-bottom-nav-h, 4rem) + env(safe-area-inset-bottom, 0px));
  }
}
/* === standard-components: modal.css === */
/**
 * standard-components — modal chrome (plain CSS, Tailwind-purge-safe)
 * ============================================================================
 * SOURCE_VERSION: 2026-07-17
 *
 * A handful of classes covering the modal backdrop/card/icon/button
 * treatments used by _confirm_modal.html (and study-prep's pre-existing
 * #custom-alert-modal). Added after discovering — by checking each class
 * against both apps' actually-compiled static/css/app.css, not just
 * assuming it was there — that several Tailwind utility classes these
 * modals relied on were never purged in:
 *   study-prep app.css missing: bg-black/60, backdrop-blur-[2px],
 *     shadow-2xl, scale-95, scale-100, text-amber-500, shadow-blue-200,
 *     active:scale-[0.98], w-14, h-14
 *   teacher-desk app.css missing: backdrop-blur-[2px], scale-95,
 *     scale-100, active:scale-[0.98]
 * (the two apps compile independently from their own templates, so the
 * missing set differs per app — see README.md's "why copy instead of
 * Tailwind" rationale for why vendored components use plain CSS instead
 * of relying on any one app's Tailwind purge output.) This means the
 * pre-existing #custom-alert-modal was already rendering without its
 * backdrop dim/blur and press/scale animation in production before this
 * fix, not just the newer confirm modal.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that append — change the source here and re-sync instead.
 * ============================================================================
 */

.sp-modal-backdrop {
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(2px);
}

.sp-modal-panel {
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  transform: scale(0.95);
}
.sp-modal-panel.is-open {
  transform: scale(1);
}

.sp-modal-icon {
  width: 3.5rem;
  height: 3.5rem;
}

.sp-modal-icon-warning {
  color: #F59E0B; /* amber-500 */
}

.sp-modal-btn-shadow {
  box-shadow: 0 10px 15px -3px rgba(37, 99, 235, 0.2), 0 4px 6px -4px rgba(37, 99, 235, 0.2);
}

.sp-modal-press {
  transition: transform 0.1s ease;
}
.sp-modal-press:active {
  transform: scale(0.98);
}

/* === standard-components: step-indicator.css === */
/**
 * standard-components — step indicator
 * ============================================================================
 * SOURCE_VERSION: 2026-07-19
 *
 * Additive to each app's own static/css/brand-tokens.css. Added after a UI
 * review found teacher-desk's 5-stage assessment-creation wizard
 * (stage_1_filter.html -> ... -> stage_5_done.html) had no progress
 * indicator at all — a user had no way to know how many steps remained.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that append — change the source here and re-sync instead.
 * ============================================================================
 */

.sp-steps {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  margin-bottom: 1rem;
}

.sp-steps-track {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.sp-steps-dot {
  flex: 1 1 0;
  height: 0.375rem;
  border-radius: 999px;
  background: var(--sp-border);
}

.sp-steps-dot.is-done {
  background: var(--sp-brand);
}

.sp-steps-dot.is-current {
  background: var(--sp-brand);
  box-shadow: 0 0 0 3px var(--sp-brand-bg);
}

.sp-steps-text {
  font-size: var(--font-size-xs);
  color: var(--sp-text-3);
  font-weight: var(--font-weight-medium);
}
/* === standard-components: inbox.css === */
/**
 * standard-components — unified Inbox list
 * ============================================================================
 * SOURCE_VERSION: 2026-07-19
 *
 * Additive to each app's own static/css/brand-tokens.css, which already
 * defines the --sp-* color tokens these rules build on. Backs
 * templates/_inbox_list.html — the shared "one line per item, বিস্তারিত to
 * expand" feed used by both teacher-desk's and study-prep's /inbox page.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that append — change the source here and re-sync instead.
 * ============================================================================
 */

.sp-inbox-list {
  display: flex;
  flex-direction: column;
}

.sp-inbox-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.875rem 0.25rem;
  border-bottom: 1px solid var(--sp-border);
}
.sp-inbox-row:last-child {
  border-bottom: none;
}

.sp-inbox-row-main {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  min-width: 0;
}

.sp-inbox-dot {
  flex-shrink: 0;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.4rem;
  border-radius: 50%;
  background: var(--sp-brand);
}
.sp-inbox-row:not(.is-unread) .sp-inbox-dot {
  visibility: hidden;
}

.sp-inbox-text {
  font-size: var(--font-size-sm);
  color: var(--sp-text-2);
  line-height: 1.5;
}
.sp-inbox-row.is-unread .sp-inbox-text {
  font-weight: var(--font-weight-semibold);
  color: var(--sp-text-1);
}

.sp-inbox-detail-btn {
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: var(--sp-brand);
  font-size: var(--font-size-sm);
  font-weight: 600;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
}
.sp-inbox-detail-btn:hover {
  text-decoration: underline;
}

.sp-inbox-detail {
  padding: 0 0.25rem;
}
.sp-inbox-detail:not(:empty) {
  padding-bottom: 0.875rem;
  margin-top: -0.5rem;
  font-size: var(--font-size-sm);
  color: var(--sp-text-3);
  line-height: 1.55;
  white-space: pre-wrap;
}

.sp-inbox-empty {
  padding: 2.5rem 1rem;
  text-align: center;
  font-size: var(--font-size-sm);
  color: var(--sp-text-4);
  border: 1px dashed var(--sp-border);
  border-radius: 0.75rem;
}
/* === standard-components: app-header.css === */
/**
 * standard-components — app header (responsive shell)
 * ============================================================================
 * SOURCE_VERSION: 2026-07-19
 *
 * Additive to each app's own static/css/brand-tokens.css. Moved verbatim
 * from study-prep's own hand-written .sp-app-header-* rules (values
 * unchanged) so every app now has one source of truth instead of a
 * per-app duplicate — teacher-desk adopted this for the first time
 * alongside study-prep's pre-existing usage.
 *
 * Adoption: appended into each app's own brand-tokens.css by
 * elearn-web/standard-components/sync.sh. Do not hand-edit the copy in an
 * app beyond that — change the source here and re-sync instead.
 *
 * Pairs with templates/_nav_icons.html (mobile primary-tab icons) and
 * each app's own base.html header markup (not vendored — brand name,
 * nav links, and primary-tab items differ per app; only the responsive
 * shell/behavior is shared). See the file's own header comment in each
 * app's base.html for the exact markup contract.
 * ============================================================================
 */

/* ===== Logged-in app header: desktop single row, mobile two-row
   (brand row + ☰/tabs/⋮ action row) — see base.html's header markup ===== */
.sp-app-header-row {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 0.75rem;
  padding-bottom: 0.75rem;
}
.sp-app-header-topline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  position: relative;
}
.sp-app-header-brand-row {
  display: flex;
  align-items: center;
  width: 20%;
  flex-shrink: 0;
}
@media (max-width: 768px) {
  .sp-app-header-brand-row { width: auto; }
}
.sp-app-header-nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex-wrap: wrap;
}
@media (min-width: 769px) {
  .sp-app-header-nav {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
}
.sp-app-header-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
  flex-shrink: 0;
}
.sp-app-header-actionrow { display: none; }

@media (max-width: 768px) {
  .sp-app-header-nav,
  .sp-app-header-right {
    display: none;
  }
  .sp-app-header-actionrow {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
}

.sp-app-header-hamburger {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  flex-shrink: 0;
}
.sp-app-header-hamburger span {
  display: block;
  width: 1.25rem;
  height: 2px;
  background: var(--sp-text-2);
  border-radius: 1px;
}

.sp-app-header-tabs {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  overflow-x: auto;
  flex: 1 1 auto;
  min-width: 0;
}
.sp-app-header-tab {
  font-size: var(--font-size-sm);
  color: #2563eb;
  text-decoration: none;
  white-space: nowrap;
  padding: 0.35rem 0.6rem;
  border-radius: 0.375rem;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.sp-app-header-tab.is-active {
  background: var(--sp-nav-bg);
  font-weight: var(--font-weight-semibold);
}
.sp-app-header-tab .sp-item-icon {
  width: 1.1rem;
  height: 1.1rem;
}

.sp-app-header-more { position: relative; flex-shrink: 0; }
.sp-app-header-more-btn {
  width: 2rem;
  height: 2rem;
  border: none;
  background: transparent;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  color: var(--sp-text-2);
}
.sp-app-header-more-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.25rem);
  background: #fff;
  border: 1px solid var(--sp-border);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  min-width: 11rem;
  padding: 0.25rem;
  z-index: 50;
}
/* .sp-item is a plain <a> (inline by default) — without this, sibling
   items in this panel sit side-by-side instead of one per row. */
.sp-app-header-more-panel .sp-item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.6rem;
  border-radius: 0.375rem;
  text-decoration: none;
}
@media (min-width: 769px) {
  .sp-app-header-more { display: none; }
}

/* === teacher-desk local (NOT vendored — do not move into standard-components
   without a second consumer; see web/assessments/templates/grading/marking_interface.html) === */
/**
 * Grading screen 4th state: a student's pending (undisputed, not-yet-
 * confirmed-correct-or-wrong) pick — sits alongside the shared
 * --sp-correct-*/--sp-wrong-* tokens above (question-card.css) so every
 * green/red in the app matches exactly, while this amber state has no
 * other consumer today.
 */
:root {
  --sp-pending-bg:     #FFFBEB;  /* amber-50 */
  --sp-pending-border: #FCD34D;  /* amber-300 */
  --sp-pending-text:   #B45309;  /* amber-700 */
}

/* ===== Delayed-loading toast (base.html #global-slow-loading) — Tailwind's
   top-20/left-4 utility classes only pin it to the top-left corner; these
   ID-scoped overrides re-center it horizontally and enlarge it. ID
   specificity beats the plain utility classes still on the element. ===== */
#global-slow-loading {
  left: 50%;
  transform: translateX(-50%);
  padding: 1rem 1.5rem;
  gap: 0.75rem;
}
#global-slow-loading svg {
  width: 1.75rem;
  height: 1.75rem;
}
#global-slow-loading span {
  font-size: 1rem;
}
