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

:root {
  --navy:      #1A2B4A;
  --gold-lt:   #D4AF70;
  --section-default-bg: #1D5E3F;
  --bg:        #F7F6F3;
  --surface:   #FFFFFF;
  --border:    rgba(0,0,0,0.09);
  --border-md: rgba(0,0,0,0.15);
  --text:      #1A2B4A;
  --text-2:    #5A6478;
  --text-3:    #8A96A8;
  --danger:    #C0392B;
  --danger-lt: #FCEBEB;
  --warn:      #7C4A00;
  --warn-lt:   #FEF9E7;
  --warn-bd:   #F6D860;
  --success:   #0F6E56;
  --success-lt:#E1F5EE;
  /* Selected-chip fill. Deliberately NOT --navy: navy means "this is an
     action button", this neutral dark means "this filter/target is ON".
     Used by .pill.on and .mod-pill.is-selected so every chip in the admin
     agrees (VR-5). */
  --chip-on:   #2A2E35;
  --r-md: 8px;
  --r-lg: 12px;

  /* ── Phase VR spacing + type scale ──────────────────────────────────────
     Declared globally (a custom-property declaration renders nothing on its
     own) but only APPLIED under `body.admin-page` — see the Phase VR block
     below. Learner screens keep their current values until a later phase
     deliberately adopts the scale (VR decision V1).

     The type scale NAMES the sizes already in use rather than inventing new
     ones, so adopting it is a consolidation, not a redesign. h1 stays 26px
     DM Serif Display — the admin's one serif anchor. */
  --sp-1: 4px;    /* tight — icon gaps, badge padding                  */
  --sp-2: 8px;    /* control gaps within a cluster                     */
  --sp-3: 16px;   /* between a control group and its label             */
  --sp-4: 24px;   /* between sections                                  */

  --fs-xs: 11px;  /* meta lines, dates, repo-item secondary text       */
  --fs-sm: 12px;  /* dense row text, status pill, back-link            */
  --fs-md: 13px;  /* body/button default — matches .btn's current 13px */
  --fs-lg: 15px;  /* panel headings                                    */
}

body { font-family: 'DM Sans', system-ui, -apple-system, sans-serif; font-size: 14px; background: var(--bg); color: var(--text); min-height: 100vh; line-height: 1.5; }
a { color: var(--navy); }
h1 { font-family: 'DM Serif Display', Georgia, serif; font-weight: 400; font-size: 26px; letter-spacing: -0.3px; }
h2 { font-size: 17px; font-weight: 500; }

/* Topbar */
.topbar { background: #000; padding: 0 2rem; display: flex; align-items: center; justify-content: space-between; height: 52px; position: sticky; top: 0; z-index: 100; }
.topbar-logo { display: flex; align-items: center; text-decoration: none; }
.topbar-logo img { height: 20px; width: auto; display: block; }
.topbar-right { display: flex; align-items: center; gap: 1.25rem; }
.topbar-link { font-size: 13px; color: rgba(255,255,255,0.65); text-decoration: none; transition: color 0.15s; }
.topbar-link:hover { color: #fff; }
.topbar-link.active { color: #CFD6D8; }

/* Left side of the topbar — holds the greeting button that toggles the
   sidebar. Trailing divider separates it visually from the logo. */
.topbar-left {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding-right: 1rem;
  margin-right: 1rem;
  border-right: 1px solid rgba(255, 255, 255, 0.25);
}
.topbar-greeting {
  font-size: 13px;
  font-weight: bold;
  color: #fff;
  white-space: nowrap;
}
.topbar-user {
  font-size: 13px;
  font-weight: bold;
  color: #A5C2D6;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.topbar-username { color: inherit; }

/* Keep greeting, nav links, and the Morris button on one line each —
   the two-word items would otherwise wrap at tight widths. */
.topbar-greeting,
.topbar-user,
.topbar-right .topbar-link,
#morris-topbar-btn {
  white-space: nowrap;
}

/* ── Topbar search box ─────────────────────────────────────────────────
   Lives inline inside .topbar-right between Ask Morris and Comment.
   Restored from 2026-04-18 styling. SVG magnifying-glass is embedded in
   the background so we don't need an extra icon file. */
.topbar-search {
  display: flex;
  align-items: center;
  flex: 0 1 220px;
  min-width: 0;
}
.topbar-search input[type="search"] {
  width: 100%;
  padding: 6px 12px 6px 32px;
  font-size: 14px;
  font-family: "DM Sans", sans-serif;
  background: rgba(255, 255, 255, 0.08) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' opacity='0.7'><circle cx='11' cy='11' r='8'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>") no-repeat 9px center;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 6px;
  color: #fff;
  outline: none;
  transition: background 0.15s, border-color 0.15s;
}
.topbar-search input[type="search"]::placeholder {
  color: rgba(255, 255, 255, 0.55);
}
.topbar-search input[type="search"]:focus {
  background-color: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.45);
}
/* Invert the native "x" clear button so it blends with the dark topbar */
.topbar-search input[type="search"]::-webkit-search-cancel-button {
  filter: invert(1);
  opacity: 0.6;
}
/* Hide the search box on narrow screens — the topbar gets too crowded */
@media (max-width: 720px) {
  .topbar-search { display: none; }
}

.card-repo-search {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}
.card-repo-search-field {
  flex: 1;
  min-width: 240px;
}
.card-repo-search input[type="search"] {
  width: 100%;
  min-height: 42px;
  padding: 9px 12px 9px 36px;
  background: var(--surface) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238B98AA' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='8'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>") no-repeat 10px center;
  border-width: 0.5px;
  border-radius: 9px;
}
.card-repo-search input[type="search"]::placeholder {
  color: var(--text-3);
}
.topbar-comment-btn { background: none; border: 0.5px solid rgba(255,255,255,0.35); border-radius: 20px; padding: 3px 12px; cursor: pointer; font-family: inherit; font-size: 13px; color: rgba(255,255,255,0.65); transition: all 0.15s; }
.topbar-comment-btn:hover { border-color: rgba(255,255,255,0.7); color: #fff; }

/* Modal */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 200; display: flex; align-items: center; justify-content: center; padding: 1rem; }
.modal-card { background: var(--surface); border-radius: var(--r-lg); width: 100%; max-width: 480px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); }
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 1.25rem 1.5rem 0; }
.modal-header h2 { font-size: 18px; font-weight: 500; }
.modal-close { background: none; border: none; font-size: 22px; cursor: pointer; color: var(--text-3); line-height: 1; padding: 0 2px; }
.modal-close:hover { color: var(--text); }
.modal-sub { font-size: 13px; color: var(--text-2); margin-bottom: 1rem; }

/* Confirm modal (Phase 2 / PR B) — shared destructive-action confirmation.
   z-index sits above the action dropdowns (z 500) and content modals so a
   Delete inside a dropdown is confirmable. Visibility via .on, not [hidden]. */
.confirm-backdrop { position: fixed; inset: 0; background: rgba(20,24,36,0.45); z-index: 11000; display: none; align-items: center; justify-content: center; padding: 1rem; }
.confirm-backdrop.on { display: flex; }
.confirm-card { background: var(--surface); border-radius: var(--r-lg); width: 100%; max-width: 400px; box-shadow: 0 12px 40px rgba(0,0,0,0.22); padding: 1.5rem; }
.confirm-msg { font-size: 14px; color: var(--text); line-height: 1.5; margin-bottom: 1.25rem; }
.confirm-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* Temporary-password modal — shown after an admin creates a user or resets a
   password (the `temp_password` flash category). Center-screen so the one-time
   password isn't missed in a top-of-page banner. Same visual language as the
   confirm modal; visibility via .on (a display:flex rule overrides [hidden]). */
.temppw-backdrop { position: fixed; inset: 0; background: rgba(20,24,36,0.45); z-index: 11001; display: none; align-items: center; justify-content: center; padding: 1rem; }
.temppw-backdrop.on { display: flex; }
.temppw-card { background: var(--surface); border-radius: var(--r-lg); width: 100%; max-width: 440px; box-shadow: 0 12px 40px rgba(0,0,0,0.22); padding: 1.5rem; }
.temppw-title { font-size: 18px; font-weight: 500; margin-bottom: 0.75rem; }
.temppw-msg { font-size: 14px; color: var(--text); line-height: 1.55; margin-bottom: 0.75rem; }
.temppw-msg strong { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 15px; }
.temppw-hint { font-size: 13px; color: var(--text-2); line-height: 1.5; margin-bottom: 1.25rem; }
.temppw-actions { display: flex; gap: 8px; justify-content: flex-end; }

#comment-form-wrap { padding: 1rem 1.5rem 1.5rem; }
.modal-actions { display: flex; gap: 8px; margin-top: 1rem; }
#comment-success { padding: 2rem 1.5rem; }

/* Page */
.page { max-width: 980px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }

/* Flash */
.flash-wrap { margin-bottom: 1.25rem; }
.flash { padding: 10px 14px; border-radius: var(--r-md); font-size: 13px; margin-bottom: 8px; border: 0.5px solid transparent; }
.flash--success { background: var(--success-lt); color: var(--success); border-color: #9FE1CB; }
.flash--error   { background: var(--danger-lt);  color: var(--danger);  border-color: #F7C1C1; }
.flash--info    { background: #E6F1FB; color: #185FA5; border-color: #B5D4F4; }
/* Yellow: the action was understood and changed nothing — not an error, but
   not the success the admin expected either (e.g. adding a Material to a
   Subtopic it is already in). Reuses the existing --warn token trio that
   .menu-item--warning already uses, so the yellow matches. */
.flash--warning { background: var(--warn-lt); color: var(--warn); border-color: var(--warn-bd); }

/* Auth */
.auth-page { background: var(--navy); }
.auth-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 2rem; }
.auth-card { background: var(--surface); border-radius: var(--r-lg); padding: 2.5rem 2rem; width: 100%; max-width: 420px; }
.auth-logo { background: #000; margin: -2.5rem -2rem 1.5rem; padding: 1rem 2rem; border-radius: var(--r-lg) var(--r-lg) 0 0; }
.auth-logo img { height: 32px; width: auto; display: block; }
.auth-heading { margin-bottom: 4px; }
.auth-sub { font-size: 13px; color: var(--text-2); margin-bottom: 1.5rem; }
.auth-form { margin-top: 1.25rem; }
.auth-footer { font-size: 12px; color: var(--text-3); margin-top: 1.25rem; text-align: center; line-height: 1.5; }
.auth-card--login { max-width: 420px; padding: 0 2rem 2.5rem; overflow: hidden; }
.auth-card--login .auth-logo { margin: 0 -2rem 0; padding: 1rem 2rem; }
.auth-card--login .auth-logo img { height: 32px; }
.auth-robot-logo { display: flex; justify-content: center; padding: 1rem 0 1.05rem; }
.auth-robot-logo img { display: block; width: 76px; height: auto; }
.auth-heading--login { font-family: 'DM Sans', system-ui, -apple-system, sans-serif; font-size: 26px; font-weight: 700; line-height: 1.15; text-align: center; margin-bottom: 1.65rem; }
.auth-card--login .auth-form { margin-top: 0; }
.auth-card--login .field { margin-bottom: 1rem; }
.auth-card--login .field label { font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.auth-card--login .field input[type=email],
.auth-card--login .field input[type=password] {
  min-height: 44px;
  padding: 9px 11px;
  border-radius: 10px;
}
.auth-card--login .btn--full { margin-top: 0.1rem; padding: 10px 14px; border-radius: 10px; font-size: 16px; font-weight: 700; }
.auth-forgot { text-align: center; margin-top: 1.25rem; }
.auth-forgot a { color: var(--text-2); font-size: 14px; font-weight: 500; text-decoration: underline; text-underline-offset: 3px; }
.auth-card--login .auth-footer { max-width: 300px; margin: 1.4rem auto 0; font-size: 13px; font-weight: 500; line-height: 1.45; }
@media (max-width: 640px) {
  .auth-wrap { padding: 1rem; }
  .auth-card--login { padding: 0 1.5rem 2.5rem; }
  .auth-card--login .auth-logo { margin: 0 -1.5rem; padding: 1rem 1.5rem; }
  .auth-card--login .auth-logo img { height: 32px; }
  .auth-robot-logo img { width: 72px; }
  .auth-heading--login { font-size: 25px; margin-bottom: 1.5rem; }
}

/* Fields */
.field { margin-bottom: 1rem; }
.field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 5px; }
.field-hint { font-weight: 400; color: var(--text-3); font-size: 12px; }
.field input[type=text], .field input[type=email], .field input[type=password],
.field input[type=url], .field input[type=number], .field select, .field textarea {
  width: 100%; padding: 8px 10px; border: 0.5px solid var(--border-md);
  border-radius: var(--r-md); font-family: inherit; font-size: 14px;
  color: var(--text); background: var(--surface); transition: border-color 0.15s;
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--navy); box-shadow: 0 0 0 2px rgba(26,43,74,0.1);
}
.field textarea { resize: vertical; }
.field--inline { display: flex; align-items: center; gap: 8px; }
.field--inline label { margin: 0; display: flex; align-items: center; gap: 6px; cursor: pointer; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
/* Deduped 2026-07-18 (Phase VR-1). `.form-actions` was declared TWICE — here
   and again ~1070 lines later inside the Morris-settings block — where the
   later, unscoped copy silently won on gap / margin-top / border-top and
   hardcoded #e1e5e8 instead of a token. Every admin save bar was being styled
   by what read like a Morris-local override.

   The values below are the ones that were WINNING, so this dedupe is a
   deliberate no-op: same pixels, one rule. Tokenizing the #e1e5e8 (and the
   .drag-handle #aaa) is left to VR-4, where a staging screenshot can confirm
   the change rather than it riding along inside a "no visual change" PR. */
.form-actions { display: flex; gap: 0.75rem; margin-top: 1rem; padding-top: 1rem; border-top: 0.5px solid var(--border-md); }
.form-card { background: var(--surface); border: 0.5px solid var(--border-md); border-radius: var(--r-lg); padding: 1.5rem 2rem; max-width: 640px; }

/* Edit User's sections (B8 PR 5b; separated in 5c, Stacy on staging).
   SIX SEPARATE CARDS with a gap between them, not one card of bands.

   ⚠️ 5b did the opposite and this comment used to argue for it ("six floating
   cards read as six unrelated screens"). Stacy looked at it on staging and
   called the other way: with a shared outer card the bands read as stripes
   within one form, which undersells that Content access / Family tree /
   Password each save on their own. Kept as a correction rather than deleted,
   because the old argument is the one a re-reader reconstructs.

   The shading still alternates via an explicit --alt modifier rather than
   :nth-child. Three of the six sections live INSIDE #user-form and three
   outside it (the reset-password POST cannot be nested in another form), so
   they are not siblings and no nth-child rule can see the whole run. */
.user-edit-sections { max-width: 640px; }
.user-edit-section {
  background: var(--surface);
  border: 0.5px solid var(--border-md);
  border-radius: var(--r-lg);
  padding: 1.25rem 1.5rem;
  margin-bottom: 0.75rem;
}
/* Darkened from rgba(0,0,0,0.018) — that was near-invisible once each section
   had its own border to compete with. Still well short of the repository row
   tints (--success-lt / --warn-lt / the admin wash), which ENCODE published /
   draft / admin; a band at that strength would read as a state this section is
   in rather than as alternation. ~7% navy over white. */
.user-edit-section--alt { background: #EFF1F4; }
.user-edit-section h2 { font-size: 15px; font-weight: 600; margin: 0 0 0.85rem; }
.user-edit-section .field:last-child { margin-bottom: 0; }
.user-edit-section > .text-muted { margin: 0 0 0.5rem; font-size: 13px; }
/* The Save row is NOT a card: it acts on the three sections above it, so giving
   it its own card would make it look like a seventh section. Bare, with the
   cards' horizontal padding so the buttons line up with their content. */
.user-edit-sections > form > .form-actions {
  margin: 0 0 1.5rem;
  padding: 0.25rem 1.5rem 0;
  border-top: none;
}
/* Required-field marker. The input's own `required` attribute is what assistive
   tech announces, so the glyph is decorative and aria-hidden — it exists for
   sighted admins scanning which fields they must fill. */
.required-mark { color: var(--danger); font-weight: 700; margin-left: 2px; }

/* Buttons */
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border-radius: var(--r-md); font-family: inherit; font-size: 13px; font-weight: 500; cursor: pointer; text-decoration: none; border: 0.5px solid transparent; transition: all 0.13s; white-space: nowrap; }
.btn--primary   { background: var(--navy);    color: #fff; border-color: var(--navy); }
.btn--primary:hover { background: #243d6a; }
.btn--secondary { background: var(--surface); color: var(--text); border-color: var(--border-md); }
.btn--secondary:hover { background: var(--bg); }
.btn--danger    { background: var(--danger-lt); color: var(--danger); border-color: #F7C1C1; }
.btn--danger:hover { background: #F7C1C1; }
.btn--full  { width: 100%; justify-content: center; padding: 9px 14px; font-size: 14px; }
.btn--sm    { padding: 5px 10px; font-size: 12px; }
.btn--xs    { padding: 3px 8px;  font-size: 11px; }

/* ── Phase VR: the semantic button palette (admin-scoped) ───────────────────
   VR decision V1: `.btn` is NOT admin-only — it is also on the login screen
   (auth/login.html's "Let's talk!" CTA), base.html's comment modal, and ~16
   learner card controls in curriculum/. Restyling `.btn--*` at the root would
   silently redesign learner-facing UI, so every VR rule is scoped to
   `body.admin-page`, which base.html already sets from the URL prefix.

   Consequence, accepted deliberately: admin and learner buttons diverge for
   the first time. Unifying them is a separate decision needing learner
   staging verification; VR does not make it by accident.

   Two NEW members complete the palette. Existing members are NOT restyled
   here — that is VR-2, against staging screenshots.

   btn--toggle (V2) — flips a binary state; BOTH states carry equal weight.
   The named §4.5 complaint: Publish renders btn--primary (navy/white) and
   Unpublish btn--secondary (white/dark), so two halves of one control read as
   two weights. The status-pill beside it already carries the state; the button
   carries only the verb. Adopted by _status_toggle.html in VR-2. */
body.admin-page .btn--toggle {
  background: var(--surface);
  color: var(--text-2);
  border-color: var(--border-md);
}
body.admin-page .btn--toggle:hover { background: var(--bg); color: var(--text); }

/* btn--ghost (V5) — icon/close affordance, no chrome. This class was already
   in use at admin/_table_card_panel.html:92 with NO rule anywhere in the
   stylesheet, so that modal's close button silently fell back to a bare `.btn`
   and had no intended appearance. Defining it is a latent-bug fix, not a
   restyle. */
body.admin-page .btn--ghost {
  background: transparent;
  color: var(--text-3);
  border-color: transparent;
}
body.admin-page .btn--ghost:hover { background: var(--bg); color: var(--text); }

/* Badges */
.badge { font-size: 10px; font-weight: 500; padding: 2px 7px; border-radius: 20px; white-space: nowrap; display: inline-block; }
.b-core     { background: #E1F5EE; color: #085041; }
.b-supp     { background: #F1EFE8; color: #5F5E5A; }
.b-fmt      { background: #E6F1FB; color: #185FA5; }
.b-fmt-vid  { background: #FAEEDA; color: #633806; }
.b-fmt-audio{ background: #FAECE7; color: #712B13; }
.b-fmt-deck { background: #EEEDFE; color: #3C3489; }
.b-beg      { background: #EAF3DE; color: #3B6D11; }
.b-mid      { background: #FAEEDA; color: #854F0B; }
.b-adv      { background: #FCEBEB; color: #A32D2D; }

/* Curriculum */
.curr-header { padding: 1.5rem 0 1.25rem; border-bottom: 0.5px solid var(--border-md); margin-bottom: 1.5rem; }
.curr-header h1 { margin-bottom: 4px; }
.curr-header p { font-size: 13px; color: var(--text-2); }
.curr-footer { text-align: center; padding: 2rem 0 0; font-size: 12px; color: var(--text-3); border-top: 0.5px solid var(--border-md); margin-top: 2rem; }
.curr-footer a { color: #B8914A; text-decoration: none; }

.filters { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 1.5rem; align-items: center; }
.filter-label { font-size: 12px; color: var(--text-2); font-weight: 500; }
.filter-break { width: 100%; height: 0; }

/* Per-section page: sticky filter bar (2026-04-24)
   Pins the Format/Level/Access chip rows to the top on scroll so they
   remain reachable while the module list scrolls underneath. Top offset
   matches the .topbar height (52px) so it docks directly beneath the
   nav. Background must be opaque to hide content scrolling behind it. */
.section-filters-sticky {
  position: sticky;
  top: 52px;
  background: var(--bg);
  z-index: 10;
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}

@media (max-width: 640px) {
  .section-filters-sticky {
    padding: 8px 0;
  }
}

.pill { font-size: 12px; padding: 4px 11px; border-radius: 20px; border: 0.5px solid var(--border-md); background: transparent; color: var(--text-2); cursor: pointer; font-family: inherit; transition: all 0.13s; }
.pill:hover { background: rgba(26,43,74,0.06); color: var(--text); }
.pill.on { background: var(--chip-on); color: #fff; border-color: var(--chip-on); }

.module-card { border: 0.5px solid var(--border-md); border-radius: var(--r-lg); margin-bottom: 10px; overflow: hidden; background: var(--surface); }
.mod-header { display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: start; padding: 1rem 1.25rem; cursor: pointer; user-select: none; }
.mod-header:hover { background: #F2F1EE; }
.mod-title { font-size: 20px; font-weight: 500; margin-bottom: 3px; }
.mod-sub { font-size: 14px; color: var(--text-2); }
.mod-right { display: flex; align-items: center; gap: 10px; }
.age-badge { font-size: 11px; color: var(--text-3); white-space: nowrap; }
.chevron { font-size: 16px; color: var(--text-3); transition: transform 0.2s; line-height: 1; }
.chevron.open { transform: rotate(180deg); }

.mod-body { border-top: 0.5px solid var(--border); padding: 1.25rem; display: none; }
.mod-body.open { display: block; }
.overview { font-size: 13px; color: var(--text-2); line-height: 1.7; margin-bottom: 1.25rem; padding-bottom: 1rem; border-bottom: 0.5px solid var(--border); }

.deck-row { display: flex; align-items: center; gap: 10px; margin-bottom: 1.25rem; padding: 12px 14px; border-radius: var(--r-md); border: 0.5px solid transparent; }
.deck-row--pending { opacity: 0.6; }
.deck-label { font-size: 10px; font-weight: 500; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; }
.deck-title { font-size: 13px; font-weight: 500; color: var(--text); }
.deck-link { text-decoration: none; color: var(--navy); }
.deck-link:hover { text-decoration: underline; }
.deck-coming { font-size: 13px; color: var(--text-3); font-style: italic; }

.section-label { font-size: 13px; font-weight: 500; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.7px; margin-bottom: 12px; padding-top: 1rem; border-top: 0.5px solid var(--border-md); margin-top: 0.25rem; }
.subtopic { margin-bottom: 1.25rem; margin-top: 2.5rem; }
.subtopic-name { font-size: 15px; font-weight: 500; margin-bottom: 8px; }
.materials-grid { display: flex; flex-direction: column; gap: 18px; padding-left: 10%; }
.mat-row { display: grid; grid-template-columns: 1fr; gap: 8px; align-items: start; padding: 8px 10px; border-radius: var(--r-md); border: 0.5px solid var(--border); background: #FAFAF8; }
.mat-row:hover { background: #F2F1EE; }
.mat-title { font-size: 12px; font-weight: 500; line-height: 1.4; }
.mat-title a { color: var(--text); text-decoration: none; }
.mat-title a:hover { text-decoration: underline; }
.mat-detail { font-size: 11px; color: var(--text-3); margin-top: 1px; }
.mat-desc   { font-size: 11px; color: var(--text-2); margin-top: 2px; line-height: 1.5; }
.mat-fmts   { display: flex; flex-wrap: wrap; gap: 3px; align-self: center; }
.mat-chips-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

.mat-divider { height: 1px; background: var(--border-md); margin: 8px 0; margin-left: -7%; }
.mat-section-header { padding: 6px 0 2px; font-size: 13px; font-weight: 500; color: var(--text-2); margin-left: -7%; line-height: 1.5; }
.fc-text { font-size: 15px; line-height: 1.1; color: var(--text); margin-bottom: 8px; }
.fc-text * { line-height: 1.1; }
.fc-embedded-quiz, .fc-embedded-match { margin-top: 1rem; padding-top: 1rem; border-top: 0.5px solid var(--border); }
.fmt-checkboxes { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.fmt-check { display: flex; align-items: center; gap: 5px; font-size: 13px; cursor: pointer;
  padding: 4px 10px; border-radius: 20px; border: 0.5px solid var(--border-md);
  background: var(--surface); transition: all 0.12s; }
.fmt-check:hover { background: var(--bg); }
.fmt-check input[type=checkbox] { width: 13px; height: 13px; cursor: pointer; margin: 0; }
.fmt-check:has(input:checked) { background: #E6F1FB; border-color: #B5D4F4; color: #185FA5; }
.fmt-check--divider { border-style: dashed; }
.fmt-check--divider:has(input:checked) { background: #F1EFE8; border-color: #B4B2A9; color: #444441; }
.mat-age { font-size: 11px; color: var(--text-3); white-space: nowrap; }

/* Admin */
.admin-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
.admin-header h1 { margin-bottom: 2px; }
/* Right-side actions slot of _admin_header.html's {% call %} form. Was an
   inline style= on the div; moved here in VR-2 so the header is restyleable
   from CSS. Same values. */
.admin-header-actions { display: flex; gap: 8px; align-items: center; }
.admin-header p { font-size: 13px; color: var(--text-2); }
.back-link { font-size: 12px; color: var(--text-3); text-decoration: none; display: block; margin-bottom: 6px; }
.back-link:hover { color: var(--navy); }
.admin-tabs { display: flex; gap: 4px; border-bottom: 0.5px solid var(--border-md); margin-bottom: 1.5rem; }
.tab-btn { background: none; border: none; padding: 8px 16px 9px; font-family: inherit; font-size: 14px; color: var(--text-2); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -0.5px; transition: all 0.13s; }
.tab-btn:hover { color: var(--text); }
.tab-btn.active { color: var(--navy); border-bottom-color: var(--navy); font-weight: 500; }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
.panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.admin-module-list { display: flex; flex-direction: column; gap: 6px; }
.admin-module-row { display: grid; grid-template-columns: auto 1fr auto auto; gap: 12px; align-items: center; padding: 10px 12px; background: var(--surface); border: 0.5px solid var(--border-md); border-radius: var(--r-md); }
/* ⚠️ Both cells are placed EXPLICITLY, and that is load-bearing. A draft row
   renders no drag handle (§1.3), so with auto-placement its children shifted a
   column: meta landed in the content-sized `auto` column instead of `1fr`,
   which let it grow until .admin-module-actions was squeezed and wrapped its
   three buttons into a vertical stack. Draft rows then looked nothing like
   published ones. Explicit columns make the two identical regardless of the
   handle. Column 1 collapses to zero width when no handle occupies it. */
.admin-module-meta { min-width: 0; grid-column: 2; }
.admin-module-meta strong { display: block; font-size: 13px; }
/* Own line, beneath the subtitle — see the markup note in _modules_body.html. */
.admin-module-status { display: block; margin-top: 4px; }
.admin-module-actions { grid-column: 3; display: flex; gap: 6px; flex-wrap: wrap; }
.admin-table { width: 100%; border-collapse: collapse; background: var(--surface); border: 0.5px solid var(--border-md); border-radius: var(--r-lg); overflow: hidden; }
.admin-table th { padding: 10px 12px; text-align: left; font-size: 12px; font-weight: 500; color: var(--text-2); background: var(--bg); border-bottom: 0.5px solid var(--border-md); }
.admin-table td { padding: 10px 12px; font-size: 13px; border-bottom: 0.5px solid var(--border); vertical-align: middle; }
.admin-table tr:last-child td { border-bottom: none; }
.td-actions { display: flex; gap: 6px; align-items: center; flex-wrap: nowrap; }
.actions-menu { position: relative; }
/* overflow-y is the BACKSTOP for placeDropdown() in main.js, which prefers to
   flip the menu above the toggle rather than scroll it. It only engages when a
   menu fits neither above nor below (a long menu in a short window), and it is
   what makes the max-height it sets reachable rather than clipping. */
.actions-menu-dropdown { position: fixed; z-index: 500; background: var(--surface); border: 0.5px solid var(--border-md); border-radius: var(--r-md); box-shadow: 0 4px 16px rgba(0,0,0,0.12); min-width: 210px; padding: 4px; overflow-y: auto; }
.menu-item { display: block; width: 100%; text-align: left; padding: 7px 12px; font-size: 13px; background: none; border: none; cursor: pointer; border-radius: 6px; color: var(--text); white-space: nowrap; text-decoration: none; }
.menu-item:hover { background: var(--bg); }
.menu-item--warning { color: var(--warn); background: var(--warn-lt); }
.menu-item--warning:hover { background: var(--warn-bd); }
.menu-item--danger { color: var(--danger); background: var(--danger-lt); }
.menu-item--danger:hover { background: #F7C1C1; }
/* Non-actionable explanation in place of Delete (VR-2b) — used when the delete
   guard is blocked by something the admin can't navigate to, e.g. learner
   responses referencing a block. Reads as information, not a dead button. */
.menu-item--disabled { color: var(--text-3); cursor: default; }
.menu-item--disabled:hover { background: none; }
.menu-divider { border: none; border-top: 0.5px solid var(--border-md); margin: 4px 0; }
.menu-icon { margin-right: 6px; font-style: normal; }
.user-name-cell { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.user-name-edit-form { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.user-name-input { padding: 5px 8px; font-size: 13px; border: 0.5px solid var(--border-md); border-radius: var(--r-md); min-width: 180px; }
.admin-section { margin-bottom: 2rem; }
.admin-section h2 { margin-bottom: 1rem; }
.deck-admin-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 12px; background: var(--surface); border: 0.5px solid var(--border-md); border-radius: var(--r-md); flex-wrap: wrap; }
.deck-admin-actions { display: flex; gap: 6px; flex-wrap: wrap; }
/* Badge colour pickers + live preview (S4). Pairs with
   admin/_badge_color_field.html and admin_badge_color.js. */
.badge-color-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.badge-color-swatch { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-2); margin: 0; }
.badge-color-swatch input[type="color"] {
  width: 44px; height: 32px; padding: 2px; cursor: pointer;
  border: 0.5px solid var(--border-md); border-radius: var(--r-md); background: var(--surface);
}
/* The preview is the feature: two pickers show two colours, this shows what
   they look like TOGETHER — which is the thing actually being chosen. Shaped
   like the badge it previews rather than a plain colour chip. */
.badge-color-preview {
  display: inline-flex; align-items: center; padding: 4px 12px;
  border-radius: 999px; font-size: 12px; font-weight: 600; letter-spacing: 0.2px;
  border: 0.5px solid var(--border);
}

.admin-subtopic { background: var(--surface); border: 0.5px solid var(--border-md); border-radius: var(--r-md); margin-bottom: 10px; overflow: hidden; }
/* Subtopic header — stronger tint and bolder text so admins can
   distinguish a subtopic divider from a resource row at a glance.
   (Pre-fix: matched the page bg, blended into the row strip.)

   Deepened from #E7EEF5 to #D3E2F2 on 2026-07-22, after §1.3 put a green
   Published band behind these rows. The two tints were reported as
   indistinguishable, and the measurement says why: it was NOT a hue problem.
   #E7EEF5 and the band's #E8F3EC sat at L* 93.8 vs 94.9 — a ΔL* of 1.1, the
   same apparent lightness — with chroma of only 4.3 and 5.3. Two large flat
   areas matching in lightness and carrying almost no colour read as one tint
   whatever their hue, so making it "bluer" alone would not have worked.

   #D3E2F2 moves BOTH axes: ΔL* 5.6 against the band and chroma 9.7 (2.3x).
   It also separates better from the two surfaces this header already had to
   stand apart from — the white card below it and the #FAFAF8 resource rows —
   so it does not trade the old problem back for the one the comment above
   records. Keep any future adjustment on the HEADER: the band tints are shared
   by three screens and signed off. There is a test pinning the separation. */
.subtopic-header { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: #D3E2F2; border-bottom: 0.5px solid var(--border-md); }
.subtopic-name-text { flex: 1; font-size: 14px; font-weight: 600; color: var(--text-1); }
.subtopic-actions { display: flex; gap: 6px; }
/* Published / Draft bands (sketch-reconciliation §1.3; sketches p.3, p.5, p.7).
   Each band is a tinted container with an icon heading; an EMPTY band still
   renders, carrying its message, because a missing band reads as "no drafts
   concept here" rather than "none right now". See admin/_status_bands.html —
   the drag rule and the sort_order hazard are documented there. */
.status-band { border-radius: var(--r-lg); padding: 14px 16px 16px; margin-bottom: 16px; }
.status-band--published { background: #E8F3EC; }
.status-band--draft     { background: #F4E6EB; }
.status-band-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.status-band-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%; font-size: 12px; line-height: 1;
  color: #fff;
}
.status-band--published .status-band-icon { background: #1E7A46; }
/* The draft icon is a document glyph in the sketch, not a filled disc. */
.status-band--draft .status-band-icon {
  background: none; color: #B3243B; font-size: 16px; width: auto; height: auto;
}
.status-band-title { font-size: 16px; font-weight: 600; }
.status-band--published .status-band-title { color: #1E7A46; }
.status-band--draft .status-band-title     { color: #B3243B; }
.status-band-items { display: flex; flex-direction: column; gap: 8px; }
.status-band-empty { margin: 0; padding: 4px 2px; font-size: 13px; color: var(--text-2); }
/* A draft row keeps the row's grid alignment without a handle: the sketch shows
   the drag dots simply absent, not replaced by a gap of a different width. */
.status-band--draft .admin-module-row { padding-left: 12px; }

.admin-materials { padding: 8px; display: flex; flex-direction: column; gap: 4px; }
.admin-mat-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--r-md); border: 0.5px solid var(--border); background: #FAFAF8; }

/* Empty-state drop target (conventions §8). Every drag container on the module
   build screen — module level and each subtopic — carries .drop-list and one
   .drop-empty-hint child.

   Before this, an empty module level rendered no container at all, so Sortable
   never bound one and a resource could not be dragged UP out of a subtopic; an
   empty subtopic rendered its container but collapsed to a 16px padding-only
   sliver, which is technically a drop zone and practically un-hittable. This
   gives both a real target with an affordance that says so.

   :has() does the show/hide with no JS and re-evaluates on drop, drag-out and
   reorder for free, because Sortable moves real DOM nodes. It also means the
   hint disappears the instant a drag enters the container rather than on drop —
   Sortable's ghost IS an .admin-mat-row — which is the behaviour we want: once
   the ghost is showing where the row will land, it has replaced the hint as the
   affordance. (So there is deliberately no separate drag-over style: it could
   never render.) */
.drop-empty-hint {
  display: flex; align-items: center; justify-content: center;
  min-height: 46px; padding: 10px 12px;
  border: 1px dashed var(--border-md); border-radius: var(--r-md);
  font-size: 12px; color: var(--text-3); text-align: center;
}
.drop-list:has(.admin-mat-row) > .drop-empty-hint { display: none; }

/* Repository listings: list ↔ grid view (Phase 2 / PR F). The same .repo-item
   markup renders as a full-width row (default) or a grid tile (.view-grid),
   toggled by admin_repo_view.js and persisted in localStorage. */
.repo-items { display: flex; flex-direction: column; gap: 6px; margin-bottom: 1.5rem; }
.repo-items.view-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
.repo-items.view-grid .repo-item { flex-direction: column; align-items: stretch; gap: 10px; }
/* Base appearance, moved out of inline style= attributes 2026-07-18 (VR-2).
   These class names already existed as hooks, but ONLY the .view-grid
   overrides below had rules — every base value lived in _repo_item.html's
   style= attributes, so the repository row was the one shared component that
   could not be restyled from CSS at all. Values are byte-identical to the
   inline ones they replace (proven by a computed-style diff); this PR moves
   them, it does not change them. The .view-grid rules below still win by
   specificity, so the grid toggle is unaffected. */
.repo-item { padding: 10px 12px; }
.repo-item-body { flex: 1; min-width: 0; }
.repo-item-title { font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.repo-item-title a { color: inherit; text-decoration: none; }
.repo-item-meta { font-size: 11px; color: var(--text-3); margin-top: 2px; }
.repo-item-dates { font-size: 11px; color: var(--text-3); margin-top: 2px; }
/* An extra line INSIDE a tile's meta slot (B8 PR 3 — the Users tile puts its
   badges on the first line and email/last-login underneath). The slot is one
   captured HTML blob, so a caller wanting more than one line has to bring its
   own block element; this is that element, matching the meta line's own size
   and colour so the tile reads as one paragraph rather than two components. */
.repo-item-submeta { margin-top: 2px; word-break: break-word; }
.repo-item-actions { display: flex; gap: 6px; flex-shrink: 0; }

.repo-items.view-grid .repo-item-title { white-space: normal; overflow: visible; }
.repo-items.view-grid .repo-item-actions { flex-wrap: wrap; }

/* Card mini-mock thumbnail (Q4, decided 2026-07-20 —
   docs/admin-redesign/20-card-minimock-scoping.md). Markup in
   admin/_card_minimock.html; the optional `thumb` slot is in _repo_item.html.

   The slot is a direct child of .repo-item, so the existing list↔grid toggle
   places it for free and no JS was added: .repo-item inherits flex-row from
   .admin-mat-row (small square left of the body — the shape p.16/p.18 draw on
   deck and page member rows), and .view-grid flips it to flex-column (full-width
   4:3 band above the body — the shape p.12/p.16/p.18 draw on tiles).

   Only Cards pass a thumb today; the other five repository screens omit it and
   render byte-identically. */
.repo-item-thumb {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: var(--r-md);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.05);
  border: 0.5px solid var(--border);
}
.repo-items.view-grid .repo-item-thumb { width: auto; height: auto; aspect-ratio: 4 / 3; }
.repo-item-thumb-img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Faces, not artwork (B8 PR 4, Users tiles). `object-fit: cover` crops to the
   CENTRE, which on a portrait photo in a 4:3 box takes the top off the head.
   Anchoring the crop to the top keeps the face.
   Deliberately a MODIFIER rather than a change to the shared rule: a Card's
   mini-mock is artwork and wants centre-crop. */
.repo-item-thumb--portrait .repo-item-thumb-img { object-position: center top; }
/* Tier 3 — the serif capital, matching the learner tile's .resource-tile-initial
   treatment. Dark rather than white because a Card's bg_color is a light tint,
   unlike the saturated Module colours that rule was written against. */
.repo-item-thumb-initial {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: 20px;
  line-height: 1;
  font-weight: 500;
  color: rgba(0, 0, 0, 0.32);
}
.repo-items.view-grid .repo-item-thumb-initial { font-size: clamp(32px, 6vw, 52px); }
/* Tier 2 — bars standing in for body text. Decreasing widths read as a paragraph
   at a glance without implying any particular content. */
.repo-item-thumb-bars { width: 62%; display: flex; flex-direction: column; gap: 3px; }
.repo-item-thumb-bars span { display: block; height: 3px; border-radius: 2px; background: rgba(0, 0, 0, 0.2); }
.repo-item-thumb-bars span:nth-child(2) { width: 78%; }
.repo-item-thumb-bars span:nth-child(3) { width: 52%; }
.repo-items.view-grid .repo-item-thumb-bars { gap: 6px; }
.repo-items.view-grid .repo-item-thumb-bars span { height: 6px; border-radius: 3px; }
/* Sort + direction + view toggle, moved out of .repo-toolbar and below the
   facet chips 2026-07-20 so they sit directly above the listing they act on.
   Right-aligned, matching where they sat in the toolbar row. */
.repo-arrange { display: flex; align-items: center; justify-content: flex-end; gap: 12px; flex-wrap: wrap; margin-bottom: 0.75rem; }
.repo-view-toggle { display: inline-flex; border: 0.5px solid var(--border-md); border-radius: var(--r-md); overflow: hidden; }
.repo-view-btn { background: var(--surface); border: none; padding: 5px 9px; font-size: 14px; line-height: 1; color: var(--text-3); cursor: pointer; }
/* The two view icons are inline SVG (was ☰ / ▦ glyphs). currentColor keeps them
   in step with the button's existing hover/active colour rules, so the states
   below did not need touching. */
.repo-view-btn svg { display: block; width: 15px; height: 15px; fill: currentColor; }
.repo-view-btn + .repo-view-btn { border-left: 0.5px solid var(--border-md); }
.repo-view-btn:hover { background: var(--bg); color: var(--text); }
.repo-view-btn.is-active { background: #E6F1FB; color: var(--navy); }

/* Repository TABLE (Phase B6, PR 2) — what the "list" button now yields on a
   repository that has adopted it. Sits on .admin-table for the shared padding /
   border / header treatment and only overrides what a data table needs.

   Both panes carry an explicit [hidden] rule. .repo-grid-pane is a plain div so
   the UA default would already cover it, but .repo-items inside it is
   display:flex — and a CSS display rule beating [hidden] is the exact silent
   failure codified in CLAUDE.md, so the intent is written down rather than
   inferred from which elements happen not to be styled. */
.repo-table-wrap[hidden], .repo-grid-pane[hidden] { display: none; }
/* A repository that renders a table needs more than the app's 980px reading
   column: eleven columns want ~1570px, and inside `main.page`'s 980px cap (minus
   the 244px sidebar offset) only three of them are visible without scrolling —
   which is the feature not working rather than a polish item.

   Scoped with :has() to the page that actually contains a table, so it is not a
   global widening of every admin screen and needs no body class, no template
   change and no JS. It applies whichever pane is showing, so the page width does
   not jump when toggling grid ⇄ table; the grid simply fits more tiles per row.
   Repositories that have not adopted the table are untouched — one more thing
   that resolves itself as 3a/3b/3c land. */
main.page:has(.repo-table-wrap) { max-width: 1500px; }
/* Scrolls inside itself; the page body must never scroll sideways (§5). Now up
   to ~21 columns (Page Decks), where the Name column scrolls out of view and a
   row becomes unidentifiable — confirmed on a staging screenshot 2026-07-20. So
   the header row and the Name column are FROZEN (the sticky rules below), the
   standard frozen-header-and-first-column data grid.

   The wrap is already a horizontal scroll container (overflow-x). A capped
   height makes it a vertical one too, so the header can stick to its top; a
   table shorter than the cap never scrolls, so nothing changes for it. The
   360px offset ≈ the page title + subtitle + search + filter/facet + sort rows
   above the table; erring large so the busiest screen (Materials) still fits in
   the viewport. Tunable against a staging screenshot of a long table. */
.repo-table-wrap { overflow: auto; max-height: calc(100vh - 360px); margin-bottom: 1.5rem; }
.repo-table { min-width: 100%; }
/* .admin-table sets overflow:hidden (to clip its rounded corners); that would
   clip the sticky cells relative to the scroll container, so turn it off on the
   table — the wrap does the clipping now. */
.repo-table { overflow: visible; }
/* Header row freezes to the top of the scroll box. .admin-table th already has
   an opaque var(--bg) background, so scrolling rows don't show through. */
.repo-table thead th { position: sticky; top: 0; z-index: 2; }
/* Per-column sort (B6 PR 4): the whole header label is a link. Kept `inline` so
   the two-word headers' <br> still breaks; the ↑/↓ flows after the label and is
   empty (no space) on the unsorted columns. */
.repo-th-sort { color: inherit; text-decoration: none; cursor: pointer; }
.repo-th-sort:hover { text-decoration: underline; }
.repo-th-sort.is-sorted { font-weight: 600; }
.repo-th-arrow { margin-left: 3px; font-size: 11px; color: var(--success); }
.repo-table th, .repo-table td { padding: 8px 10px; white-space: nowrap; }
/* Data cells align to the TOP, not the default `middle` (Stacy 2026-07-20).
   Only matters once a cell wraps — which the Used In column now does — but then
   it matters a lot: a centred one-line cell floats to the vertical middle of a
   three-line row, so the eye has to hunt up and down to read a row across. Top
   alignment puts every cell's first line on the same baseline.

   `td` only, deliberately: the sketch's header labels sit centred in their band,
   and the ask was about the data rows. Headers are a separate call if they ever
   need one. */
.repo-table td { vertical-align: top; }
/* Two-word headers wrap instead of forcing their column as wide as the phrase:
   "Module Uses" over a column of single digits, "Age Range" over "Adult".
   Done in CSS rather than by putting a <br> in the label, because the label is
   ALSO the sort dropdown's option text (§2.2 — one label per column) and markup
   there would render as literal characters inside <option>. `max-width` is what
   makes the wrap actually happen; `white-space: normal` alone would not, since
   the cells below are still nowrap and set the column width. */
/* 4.5em, not 5.5: at 5.5 "Module Uses" wrapped but "Age Range" still fitted on
   one line and kept its column as wide as the phrase. 4.5em breaks both, and no
   single-word header ("Format", "Created", …) comes close to it. */
/* Two-word headers break via an explicit <br> in _repo_table.html, not by CSS
   wrapping — the max-width approach that used to live here only wrapped when the
   CELLS did not already force the column wider, so "Age Range" stopped breaking
   the moment a row held "Young Adult". `white-space: normal` stays so the <br>
   is honoured (the shared nowrap rule would otherwise flatten it); the max-width
   is gone, since the break no longer depends on it. */
.repo-table th { position: relative; white-space: normal; line-height: 1.25; }
.repo-table td[data-col="name"] a { color: inherit; text-decoration: none; font-weight: 500; }
.repo-table td[data-col="name"] a:hover { text-decoration: underline; }
/* THE FROZEN FIRST COLUMN. Keyed on `data-sticky`, which _repo_table.html
   stamps from the registry's `sticky` flag — NOT on a column key.
   It was `[data-col="name"]` until B8 PR 7, which meant Users (first column
   keyed `first`) silently had no frozen column: with ten columns, scrolling
   right left rows unidentifiable, exactly the problem this rule was added to
   fix for Page Decks. CSS cannot select on a value supplied by data, so the
   flag has to reach the markup as its own attribute.

   The body cell carries the row's tint via `inherit` (it re-inherits on :hover
   too), so cells sliding under it are hidden rather than showing through. The
   leading Edit / Duplicate cells are NOT frozen — they just scroll off, which
   is why `left: 0` needs no per-type offset maths, and why the sticky column
   must be the FIRST data column.

   z-index: sticky body cell (1) over scrolling body cells (0); the frozen
   header (2) over the sticky body cell; the header's own sticky cell is the
   CORNER and must beat both (3).

   Truncation rides along: a frozen column that can grow arbitrarily wide would
   eat the viewport it is pinned to. */
.repo-table td[data-sticky] {
  position: sticky;
  left: 0;
  z-index: 1;
  background: inherit;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.repo-table thead th[data-sticky] { left: 0; z-index: 3; }
.repo-table td[data-col="uses"] { text-align: right; font-variant-numeric: tabular-nums; }
/* …but the SAME column key holds a module NAME on Interactive Blocks, because a
   block is single-placement and a 0/1 count would say nothing (B6 3a). A long
   module title under the default `white-space: nowrap` widened the whole table,
   so this column wraps — and drops the numeric right-align/tabular treatment,
   which is wrong for a name.

   Keyed on the CELL'S OWN CONTENT (`:has(.repo-uses-name)`) rather than on the
   screen: the two renderings are a property of what the cell holds, and a 3b
   type that emits a name opts in by using the same class. `:has()` is already
   load-bearing in this file (`main.page:has(.repo-table-wrap)`).

   Wraps rather than truncating like `name` above — Stacy 2026-07-20, and the
   right call: a truncated module title ("Retirement Plan…") is unusable for
   telling two similarly-named modules apart, whereas the Name column has the
   row's other cells for context. */
.repo-table td[data-col="uses"]:has(.repo-uses-name) {
  white-space: normal;
  text-align: left;
  max-width: 200px;
  line-height: 1.3;
}
.repo-table td[data-col="created"], .repo-table td[data-col="updated"] { color: var(--text-3); }
/* Cards table (B6 3b). The two placement counts read like `uses` — right-aligned
   tabular numbers. */
.repo-table td[data-col="placements"],
.repo-table td[data-col="page_placements"] { text-align: right; font-variant-numeric: tabular-nums; }
/* Pages / Page Decks: the six per-structural-kind count columns (data-col="k_*")
   read like the placement counts — right-aligned tabular numbers. */
.repo-table td[data-col^="k_"] { text-align: right; font-variant-numeric: tabular-nums; }
/* The eight content-type checkbox columns (data-col="c_text"…"c_match"): narrow,
   centred, a green ✓ when present and a muted · when absent. */
.repo-table th[data-col^="c_"], .repo-table td[data-col^="c_"] { text-align: center; }
.repo-check { color: var(--success); font-weight: 700; }
.repo-check-off { color: var(--text-3); }
/* Heavier row dividers than .admin-table's 0.5px --border (Stacy 2026-07-20).
   The status tints fill each row edge-to-edge, so a hairline divider was
   competing with two background washes and losing — rows read as one block of
   colour. Overrides the inherited .admin-table td rule. */
.repo-table td { border-bottom: 1.5px solid var(--border-md); }
.repo-table tbody tr:last-child td { border-bottom: none; }
/* Status tint — now the PRIMARY status indicator, not a redundant one: the
   Status column was removed 2026-07-20 in favour of the tint carrying it. So
   these went from barely-there (the published tint was 3.5% alpha) to legible
   at a glance, using the palette's own status colours rather than new ones.

   ⚠️ Colour is now the ONLY visual encoding of status in this table, which is
   not accessible on its own — roughly 1 in 12 men has some colour-vision
   deficiency. Mitigated cheaply rather than by re-adding the column: every row
   carries title="Draft"/"Published" (see _repo_table.html), so the state is
   available on hover and to assistive tech. If a stronger signal is ever wanted,
   the move is a left border-stripe on draft rows — a second, non-colour channel
   — not a second column. */
.repo-table-row--pub { background: var(--success-lt); }
.repo-table-row--draft { background: var(--warn-lt); }
/* Manage Users tints a different fact — there is no is_published on a User.
   Sketch p.23: "Admin listings have different background shading than normal
   users and include a verified check icon badge."

   Deliberately NOT green or yellow: those two now mean published/draft on six
   screens, and reusing either would say "this admin is published". The navy
   wash is the panel's own accent, and it reads as "staff" rather than as a
   status. Client rows are left untinted rather than given a third colour —
   they are the majority case, and tinting everything tints nothing.

   Same non-colour channel as the other six: every row carries a title=
   ("Admin — full access to this panel" / "Client account"), so the state
   survives a colour-vision deficiency and reaches assistive tech. */
/* ⚠️ BOTH MUST BE OPAQUE, and this is not cosmetic. The frozen first column
   uses `background: inherit` to take the row's tint, so whatever these resolve
   to is what hides the cells sliding underneath it. A translucent tint lets
   them show THROUGH the pinned column: with `--user` at `transparent` and
   `--admin` at 6% alpha, scrolling right rendered the email text straight over
   the first names ("Princprince.mononym.30@…"). Found on a staging-shaped
   screenshot in B8 PR 7 — no test saw it, because every assertion was about
   markup and this is a paint-order effect.

   The six repository tints (--success-lt / --warn-lt) are opaque hex, which is
   why the frozen column worked there from the day it shipped. These two are
   the pre-composited equivalents of what they used to be, so nothing changes
   visually on a white table — they just stop being see-through.
   tests/test_repo_table.py pins the opacity rule. */
.repo-table-row--admin { background: #F1F2F4; }
.repo-table-row--user { background: var(--surface); }
/* The verified badge the sketch asks for, beside the Admin role badge. A glyph
   rather than an icon font: it inherits colour and size, and there is no icon
   system on this screen to join. aria-hidden + the row title= carry the
   meaning, so this is decoration on top of a fact already exposed. */
.user-verified-check {
  color: var(--success);
  font-weight: 700;
  margin-left: 3px;
}
/* Hover has to beat all the tints, so it is a full-strength wash rather than a
   subtle one; without this a hovered draft row reads as unhighlighted. */
.repo-table-row:hover { background: #DCEBF9; }
.repo-table-action { white-space: nowrap; }
.repo-table-inuse { color: var(--text-3); font-size: 12px; text-decoration: none; border-bottom: 1px dotted var(--border-md); }
/* "Used in N" links to the resource editor's usage section (plan D2 — the
   link-to-editor resolution). Subtle so the count still reads as a number, with
   the dotted underline the In-Use cell uses; works in the table cell and the
   grid meta line alike. */
.repo-uses-link { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--border-md); cursor: pointer; }
.repo-uses-link:hover { border-bottom-color: currentColor; }
.repo-table-empty { padding: 1.5rem; text-align: center; color: var(--text-2); background: var(--bg); border-radius: var(--r-md); }
/* Link-styled submit button. The sketch's Edit / Duplicate / Delete are all
   links, but two of the three must be POSTs, so the control is a <button> that
   reads as a link rather than a link that lies about its method. */
/* ── In-form status switch (2026-07-20) ────────────────────────────────────
   The Publish/Unpublish control on every edit screen. Sits on a beige panel so
   it reads as a distinct thing inside forms otherwise built from white cards —
   the same family as --sidebar-bg (#EAE0CB), lightened so it recedes behind the
   switch rather than competing with it. */
.status-box { background: #F2EBDB; border: 0.5px solid #E2D5B8; border-radius: var(--r-md); padding: 12px 14px; }
.status-box > label { display: block; margin-bottom: 8px; }
.status-box .field-hint { margin: 8px 0 0; display: block; }
/* The switch is a submit BUTTON, not a checkbox — the state change is a POST,
   and a checkbox would either need JS or sit unsaved until Save, which is the
   "status is not a form field" line this must not cross. */
.status-switch { display: inline-flex; align-items: center; gap: 9px; background: none; border: none; padding: 0; font: inherit; cursor: pointer; }
.status-switch-track { position: relative; flex-shrink: 0; width: 38px; height: 22px; border-radius: 999px; background: var(--text-3); transition: background 0.16s ease; }
.status-switch-thumb { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #FFF; box-shadow: 0 1px 2px rgba(0,0,0,0.25); transition: transform 0.16s ease; }
.status-switch[aria-checked="true"] .status-switch-track { background: var(--success); }
.status-switch[aria-checked="true"] .status-switch-thumb { transform: translateX(16px); }
.status-switch-text { font-size: 13px; font-weight: 500; color: var(--text); }
.status-switch:hover .status-switch-track { filter: brightness(1.08); }
/* Keyboard focus must be visible on a control whose state is the whole point. */
.status-switch:focus-visible { outline: 2px solid var(--navy); outline-offset: 3px; border-radius: var(--r-md); }

.btn-link { background: none; border: none; padding: 0; font: inherit; font-size: 12px; color: var(--navy); cursor: pointer; text-decoration: underline; }
.btn-link:hover { opacity: 0.75; }
.btn-link--danger { color: var(--danger); }

/* Repository/picker toolbar (VR-2c, 2026-07-18) — the filter+sort row above a
   listing, and the content-facet chip rows below it. Moved out of six inline
   style= blocks in _repo_collection_header.html for the same reason as
   _repo_item.html: this is the single filter/sort/search surface for all six
   repositories AND the 5c picker, so it must be restyleable from CSS.
   Values are the ones the inline styles carried. */
.repo-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 1rem; }
.repo-toolbar-filters { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.repo-toolbar-controls { display: flex; align-items: center; gap: 12px; }
.repo-sort-form { display: flex; align-items: center; gap: 8px; }
.repo-sort-form select { width: auto; min-width: 180px; }
.repo-facet-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 0.5rem; }
/* Facet block (2026-07-19). The chip rows used to be loose siblings under the
   toolbar, which read as one undifferentiated field once Materials had five of
   them. They now sit in a panel with hairline dividers.
   The panel is --surface (white) on the page's --bg, which is the SAME pairing
   .form-card uses for a grouped region — the established vocabulary, and the
   only one that is actually visible here. The first cut used --bg for the
   panel, which is precisely the body background, so it rendered no tint at
   all: a token named "bg" is not a tint unless you check what it sits on. */
.repo-facets { background: var(--surface); border: 0.5px solid var(--border-md); border-radius: var(--r-lg); padding: 0 0.75rem; margin-bottom: 1rem; }
/* Inside the block the row's own bottom margin would fight the dividers. */
.repo-facets .repo-facet-row { margin-bottom: 0; padding: 8px 0; }
/* 1px, not the 0.5px used elsewhere: these separate sibling rows INSIDE a panel
   that already has its own border, so at 0.5px against --border (9% black) they
   barely registered. Scoped to .repo-facets — the 0.5px hairline stays the
   default everywhere else. */
.repo-facets .repo-facet-row + .repo-facet-row,
.repo-facets .repo-more-traits { border-top: 1px solid var(--border); }
/* Bigger and darker than .repo-control-label, and NOT sharing it: the row
   labels name a dimension, this is a control you click. It should not read as
   one more label in the stack. */
.repo-more-traits > summary { display: flex; align-items: center; gap: 6px; padding: 10px 0; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text); user-select: none; list-style: none; }
/* Both are needed: list-style hides the marker in Firefox, the pseudo-element
   rule in Safari/Chrome. Without them the native triangle sits beside ours. */
.repo-more-traits > summary::-webkit-details-marker { display: none; }
/* A caret AFTER the label, the way a dropdown reads — the earlier version put a
   small right-pointing bullet before it, which looked like a list marker rather
   than a control. Down = more below; flips up when open. */
.repo-more-traits > summary::after { content: '\25BE'; font-size: 11px; color: var(--text-2); transition: transform 0.15s; }
.repo-more-traits[open] > summary::after { transform: rotate(180deg); }
.repo-more-traits[open] > summary { border-bottom: 1px solid var(--border); }
.repo-more-traits > summary:hover { color: var(--navy); }
.repo-more-traits > summary:hover::after { color: var(--navy); }
/* Count of active filters hiding inside the collapsed section. --chip-on is the
   neutral dark "this is ON" token (VR: navy means "this is an action"). */
.repo-traits-badge { background: var(--chip-on); color: #FFF; border-radius: 999px; padding: 1px 7px; font-size: 11px; font-weight: 600; line-height: 1.5; }
/* One label treatment for both the "Sort" label and each facet's name. */
.repo-control-label { font-size: 12px; color: var(--text-2); font-weight: 500; }
.repo-facet-row .repo-control-label { margin-right: 2px; }
/* Count inside a filter/facet chip. Was an inline conditional colour on every
   chip; the selected state is expressible as a rule, so it is one now. */
.pill-count { color: var(--text-3); }
.pill.on .pill-count { color: inherit; }
/* "(N total)" beside a panel heading — .text-muted supplies the colour, this
   keeps it from inheriting the h2's weight/size.

   COMPOUND selector on purpose. The element is `class="text-muted
   repo-total-count"`, and `.text-muted` (font-size 13px) is declared later in
   this file, so a single-class rule here would LOSE the font-size on source
   order and silently shrink the text to 13px — the inline style this replaces
   won because inline beats any selector. Two classes outrank one regardless of
   order, the same trick .form-actions--top/--bottom already use. */
.text-muted.repo-total-count { font-weight: 400; font-size: 14px; }
.admin-mat-info { flex: 1; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.admin-mat-title { font-size: 12px; font-weight: 500; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.admin-mat-actions { display: flex; align-items: center; gap: 6px; padding-left: 10px; border-left: 1px solid var(--border-md); }
/* Draft/Published state pill on admin resource rows (Phase 4 polish). A quiet
   status indicator; the actual publish/unpublish action lives in the ⋯ menu. */
.status-pill { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 999px; white-space: nowrap; letter-spacing: 0.02em; text-transform: uppercase; }
.status-pill--pub { background: var(--success-lt); color: var(--success); }
.status-pill--draft { background: var(--warn-lt); color: var(--warn); border: 0.5px solid var(--warn-bd); }

/* Shared save-bar (UX-R §4.2, _save_bar.html). Top bar: divider BELOW; bottom
   bar: divider ABOVE. Compound selectors so these win over both base
   .form-actions rules regardless of source order. */
.form-actions.form-actions--top { margin: 0 0 1rem; padding: 0 0 0.75rem; border-top: none; border-bottom: 0.5px solid var(--border-md); }
.form-actions.form-actions--bottom { margin: 1rem 0 0; padding: 0.75rem 0 0; border-top: 0.5px solid var(--border-md); border-bottom: none; }
/* A bar with no divider — an action row inside a panel that already has its own
   border. Replaces `style="border:none;padding:0;margin-top:0.75rem"`, which
   was hand-repeated on 14 bars across 7 templates (VR-4). Compound selector so
   it beats the base .form-actions rule regardless of source order, same as the
   two above. */
.form-actions.form-actions--bare { border: none; padding: 0; margin-top: 0.75rem; }

/* One spacing step above — nudges an element clear of the control above it.
   Replaces 11 inline margin-top declarations that were split 7×4px / 4×6px
   for no reason anyone recorded; both collapse to --sp-1 (VR-4). The four
   that were 6px move up by 2px, the only visual delta here.

   Named for the SPACING, not for buttons: 10 of the 11 sites are buttons but
   the eleventh is a <p class="field-hint"> sitting under a <select>
   (_selection_card_panel.html), and a `.btn-stack` would have been the wrong
   name for it — the first draft of this sweep stripped that paragraph's
   spacing because a blanket sed assumed every site was a button. */
.stack-1 { margin-top: var(--sp-1); }
/* Deduped 2026-07-18 (VR-1) — see the note in the Morris-settings block. The
   later copy won on color/padding/font-size while this one still supplied
   flex-shrink, so the live handle was a blend of both. These are the blended
   (i.e. currently-rendering) values: another deliberate no-op. */
.drag-handle { cursor: grab; color: var(--text-3); padding: 0.3rem 0.2rem; font-size: 1rem; user-select: none; flex-shrink: 0; }
.drag-handle:active { cursor: grabbing; }
.sortable-ghost { opacity: 0.4; background: #E6F1FB; }
.access-row { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: var(--r-md); border: 0.5px solid var(--border-md); background: var(--surface); cursor: pointer; transition: background 0.12s; }
.access-row:hover { background: #F2F1EE; }
.access-row input[type=checkbox] { flex-shrink: 0; width: 15px; height: 15px; cursor: pointer; }
.access-row--hidden { opacity: 0.5; background: var(--bg); }
.access-module-title { font-size: 13px; font-weight: 500; flex-shrink: 0; }
.access-module-sub { font-size: 12px; color: var(--text-3); flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Inline Content-access editor on the Edit User page (2026-07-25). The summary
   is styled as a button; hide the native disclosure triangle on every engine
   (list-style covers Firefox, the ::marker/::-webkit rules cover the rest). */
.access-inline > summary { list-style: none; }
.access-inline > summary::marker { content: ""; }
.access-inline > summary::-webkit-details-marker { display: none; }
.access-inline > summary::after { content: " ▾"; }
.access-inline[open] > summary::after { content: " ▴"; }
.text-muted { color: var(--text-2); font-size: 13px; }

/* Phase 4d — admin dashboard section grouping */
.admin-section-header { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0; margin-bottom: 0.75rem; cursor: pointer; user-select: none; border-bottom: 0.5px solid var(--border-md); }
.admin-section-header .admin-section-title { font-size: 15px; font-weight: 600; color: var(--text); }
.admin-section-header .admin-section-add { margin-left: auto; }
.collapse-caret { display: inline-block; font-size: 10px; color: var(--text-2); transition: transform 0.15s ease; }
.admin-section--collapsed .collapse-caret { transform: rotate(-90deg); }
.admin-section--collapsed .admin-module-list { display: none; }
/* .admin-module-list-empty and its `~` hide rule lived here until 2026-07-22.
   The Modules list now groups into Published/Draft bands (§1.3), and each band
   renders its own empty-state message, so nothing emits that class any more.
   Removed rather than left behind: a rule matching markup that no longer exists
   reads to the next person as a live style with a missing element. */

@media (max-width: 640px) {
  .topbar { padding: 0 1rem; }
  .page { padding: 1rem 1rem 3rem; }
  .mat-row .mat-age { display: none; }
  .admin-module-row { grid-template-columns: auto 1fr; }
  /* Two columns here, so the explicit desktop placement must be undone or the
     actions would land in an implicit 3rd column and overflow. Full-width row
     of their own instead. */
  .admin-module-actions { grid-column: 1 / -1; }
  .admin-module-deck { display: none; }
  .field-row { grid-template-columns: 1fr; }
}

/* ── Interactive Blocks ──────────────────────────────────────────────────── */
.iblock { border: 0.5px solid var(--border-md); border-radius: var(--r-lg); margin-bottom: 12px; overflow: hidden; background: var(--surface); }
.iblock-header { display: flex; align-items: center; gap: 10px; padding: 12px 16px; background: var(--bg); border-bottom: 0.5px solid var(--border); }
.iblock-type-badge { font-size: 10px; font-weight: 500; padding: 2px 8px; border-radius: 20px; background: #EEEDFE; color: #3C3489; white-space: nowrap; }
.iblock-title { font-size: 15px; font-weight: 500; }
.iblock-body { padding: 1.25rem 1.5rem; }

/* Quiz / Poll */
.quiz-progress { font-size: 12px; color: var(--text-3); margin-bottom: 1rem; }
.quiz-question { font-size: 15px; font-weight: 500; margin-bottom: 1rem; line-height: 1.5; }
.quiz-choices { display: flex; flex-direction: column; gap: 8px; margin-bottom: 1rem; }
.quiz-choice { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border: 0.5px solid var(--border-md); border-radius: var(--r-md); cursor: pointer; font-size: 14px; background: var(--surface); transition: all 0.13s; text-align: left; font-family: inherit; }
.quiz-choice.correct::before, .quiz-choice.wrong::before { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; flex: 0 0 18px; border-radius: 50%; color: #FFFFFF; font-size: 14px; font-weight: 700; line-height: 1; }
.quiz-choice.correct::before { content: "✓"; background: #0F6E56; }
.quiz-choice.wrong::before { content: "×"; background: #C0392B; }
.quiz-choice-text { flex: 1; min-width: 0; }
.quiz-choice:hover:not(:disabled) { background: var(--bg); border-color: var(--navy); }
.quiz-choice.selected { border-color: var(--navy); background: #E6F1FB; }
.quiz-choice.correct  { border-color: #0F6E56; background: #E1F5EE; color: #085041; }
.quiz-choice.wrong    { border-color: #C0392B; background: #FCEBEB; color: #A32D2D; }
.quiz-choice:disabled { cursor: default; }
.quiz-explainer { font-size: 13px; color: var(--text-2); background: var(--bg); border-radius: var(--r-md); padding: 10px 14px; margin-bottom: 1rem; line-height: 1.6; border-left: 3px solid var(--border-md); }
.quiz-next-btn { margin-top: 0.5rem; }
.quiz-final { text-align: center; padding: 1.5rem 0 0.5rem; }
.quiz-score { font-size: 32px; font-weight: 500; color: var(--navy); margin-bottom: 4px; }
.quiz-score-label { font-size: 13px; color: var(--text-2); margin-bottom: 1rem; }
.quiz-final-msg { font-size: 14px; color: var(--text-2); line-height: 1.6; }
.quiz-restart { margin-top: 1rem; }

/* Match game */
.match-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 1rem; }
.match-col-label { font-size: 11px; font-weight: 500; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
.match-items { display: flex; flex-direction: column; gap: 6px; }
.match-item { padding: 10px 14px; border: 0.5px solid var(--border-md); border-radius: var(--r-md); cursor: pointer; font-size: 13px; background: var(--surface); transition: all 0.13s; user-select: none; }
.match-item:hover:not(.matched):not(.disabled) { background: var(--bg); border-color: var(--navy); }
.match-item.selected { border-color: var(--navy); background: #E6F1FB; font-weight: 500; }
.match-item.matched { background: #E1F5EE; border-color: #9FE1CB; color: #085041; cursor: default; }
.match-item.disabled { cursor: default; opacity: 0.5; }
.match-action { display: flex; align-items: center; gap: 10px; margin-bottom: 1rem; }
.match-feedback { font-size: 13px; color: #C0392B; font-weight: 500; }
.match-feedback.ok { color: #0F6E56; }
.match-explainer { font-size: 13px; color: var(--text-2); background: var(--bg); border-radius: var(--r-md); padding: 10px 14px; margin-bottom: 0.75rem; line-height: 1.6; border-left: 3px solid #9FE1CB; }
.match-final { text-align: center; padding: 1rem 0 0.5rem; font-size: 14px; color: var(--text-2); line-height: 1.6; }

/* Flash cards */
.fc-player { position: relative; }
.fc-card { }
.fc-card-inner { min-height: 160px; display: flex; flex-direction: column; gap: 12px; }
.fc-front { font-size: 16px; font-weight: 500; line-height: 1.5; color: var(--navy); }
.fc-back  { font-size: 14px; color: var(--text-2); line-height: 1.6; padding-top: 12px; border-top: 0.5px solid var(--border); }
.fc-img   { max-width: 100%; max-height: 300px; border-radius: var(--r-md); object-fit: contain; }
.fc-video { margin-top: 8px; }
.fc-video iframe { width: 100%; aspect-ratio: 16/9; border-radius: var(--r-md); border: none; }
.fc-audio { margin-top: 8px; }
.fc-audio audio { width: 100%; }
.fc-nav   { display: flex; align-items: center; justify-content: space-between; margin-top: 1.25rem; padding-top: 1rem; border-top: 0.5px solid var(--border); }
.fc-counter { font-size: 13px; color: var(--text-3); }
.fc-prev:disabled, .fc-next:disabled { opacity: 0.4; cursor: default; }

/* Page Deck — one whole Page per slide, prev/next navigation */
.pd-player { position: relative; }
.pd-slide-header { margin-bottom: 1rem; padding-bottom: 0.75rem; border-bottom: 0.5px solid var(--border); }
.pd-slide-title { font-size: 18px; font-weight: 600; color: var(--navy); margin: 0; }
.pd-slide-overview { font-size: 14px; color: var(--text-2); line-height: 1.6; margin-top: 0.5rem; }
.pd-prev:disabled, .pd-next:disabled { opacity: 0.4; cursor: default; }

/* Selection (Pick-List) card */
.selection-card { margin-top: 0.5rem; }
.selection-empty { font-size: 14px; color: var(--text-2); font-style: italic; padding: 1rem; background: var(--bg); border-radius: var(--r-md); }
.selection-note { font-size: 13px; color: var(--text-2); background: #FBF3E0; border-radius: var(--r-md); padding: 8px 12px; margin-bottom: 0.75rem; }
.selection-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.selection-chip { font-size: 13px; padding: 7px 14px; border: 0.5px solid var(--border-md); border-radius: 999px; background: var(--bg-card, #fff); color: var(--navy); cursor: pointer; transition: background 0.12s, border-color 0.12s; }
.selection-chip:hover { border-color: var(--navy); }
.selection-chip.selected { background: #16A37A; color: #fff; border-color: #16A37A; }
.selection-chip--disabled { opacity: 0.4; cursor: default; }
.selection-chip--disabled:hover { border-color: var(--border-md); }
.selection-add { display: flex; gap: 8px; margin-top: 0.75rem; }
.selection-add-input { flex: 1; min-width: 0; padding: 7px 10px; border: 0.5px solid var(--border-md); border-radius: var(--r-md); font-size: 13px; }
.selection-footer { display: flex; align-items: center; gap: 12px; margin-top: 1rem; padding-top: 0.75rem; border-top: 0.5px solid var(--border); }
.selection-counter { font-size: 13px; color: var(--text-3); }
.selection-status { font-size: 12px; }
/* Save stays navy (the btn--primary default); selected chips are green, so
   the button and the chips no longer share a color. */

/* ── Interactive block row & modal ───────────────────────────────────────── */
.b-interactive { background: #EEEDFE; color: #3C3489; }
.mat-row--interactive:hover { background: #F0EFFE; border-color: #C4C0F7; }

/* Pages (Phase 5b) — slightly different chip tone so admins and
   learners can distinguish a Page from an InteractiveBlock at a glance. */
.b-page { background: #D9E8F5; color: #1A2B4A; }

.resource-tile--page {
  background: linear-gradient(180deg, var(--surface) 0%, #F4F8FC 100%);
}

.page-view { max-width: 920px; margin: 0 auto; padding: 1.5rem 1rem 3rem; }
.page-view-header { margin-bottom: 1.5rem; }
.page-view-title { font-size: 28px; margin: 0.5rem 0 0.75rem; }
.page-view-overview { font-size: 14px; color: var(--text-2); line-height: 1.6; }
/* gap is driven by the per-page --card-gap custom property (set inline from
   Page.card_gap_px); falls back to the historical 16px when unset. */
.page-view-cards { display: flex; flex-direction: column; gap: var(--card-gap, 16px); }
.page-card { scroll-margin-top: 1rem; }
/* Page filter chips (Page.show_filter_chips) — the shared .pill, sized up a
   step because on a Page they are the primary control, not a secondary filter.
   .page-card sets display (flex/grid via its containers), so [hidden] needs its
   own rule or pageFilterChipClick's card.hidden = true does nothing. */
.page-filter-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 1.25rem; }
.page-filter-chips .pill { font-size: 14px; padding: 6px 15px; }
.page-view-cards > .page-card[hidden] { display: none; }

/* Deeplink targets must land BELOW the module page's sticky chrome (.topbar,
   52px, plus .section-filters-sticky docked under it). scroll-margin-top is the
   only mechanism the BROWSER'S OWN fragment scroll respects — a JS scrollTo
   just races it and loses: on 2026-07-16 the tile ended at viewport y=1, the
   native jump's position, because the browser re-scrolled to #material-N after
   images finished, overriding our smooth scroll mid-animation.

   The filter row WRAPS, so its height varies with viewport width (52+164 on a
   laptop, more when the format pills wrap further). curriculum.js measures the
   real chrome and sets --deeplink-chrome; this default clears the topbar alone,
   so a JS failure degrades to "slightly tight" rather than "title hidden". */
.resource-tile,
.subtopic { scroll-margin-top: var(--deeplink-chrome, 64px); }

/* Grid layout (Page.layout == 'grid') — tiles that reflow like a Section
   page: 1 column on mobile, 4 across on desktop. Scoped to the modifier so
   stacked pages keep the flex-column rule above untouched. No descendant
   element-type selectors, so nested content (e.g. a Morris chat <main>) is
   unaffected. */
.page-view-cards--grid {
  display: grid;
  /* Tiles are at least ~260px wide, so the column count adapts to the page
     width — ~3 across at the 920px page max, fewer as the window narrows,
     1 on mobile. min(260px, 100%) prevents overflow on very narrow screens. */
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
  gap: var(--card-gap, 18px);
}

/* ── Profile card ──────────────────────────────────────────────────────── */
/* The wrapper carries .profile-card-tile (set in page.html) so the photo can
   bleed to the rounded edges; the inner faces hold the padding. */
.profile-card-tile {
  padding: 0;
  overflow: hidden;
  border-radius: var(--r-md);
  border: 0.5px solid var(--border-md);
  background: var(--surface);
  display: flex;
  flex-direction: column;
}
.profile-front, .profile-back { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 100%; }
.profile-photo img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
}
.profile-body { padding: 14px 16px 4px; display: flex; flex-direction: column; gap: 6px; }
.profile-name { font-size: 18px; font-weight: 600; color: var(--navy); line-height: 1.25; }
.profile-text { font-size: 14px; color: var(--text-2); line-height: 1.5; }
.profile-buttons { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 16px 16px; margin-top: auto; }
.profile-btn { white-space: nowrap; }

/* Back side mirrors the front: photo on top, text stacked below — reuses
   .profile-photo / .profile-body / .profile-buttons from above. */

/* In stacked layout a profile tile would otherwise span the full page width;
   cap it so it keeps the tile proportions and centers. */
.page-view-cards:not(.page-view-cards--grid) .profile-card-tile { max-width: 320px; }

.iblock-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 300;
  display: flex; align-items: center; justify-content: center; padding: 1rem; }
.iblock-modal-card { background: var(--surface); border-radius: var(--r-lg);
  width: 100%; max-width: 640px; max-height: 90vh; display: flex; flex-direction: column;
  box-shadow: 0 12px 40px rgba(0,0,0,0.22); }
.iblock-modal-card--wide { max-width: min(80vw, 1100px); }
.iblock-modal-header { display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 1.5rem; border-bottom: 0.5px solid var(--border); flex-shrink: 0; }
.iblock-modal-title { font-size: 16px; font-weight: 500; margin-left: 8px; }
.iblock-modal-body { padding: 1.5rem; overflow-y: auto; flex: 1; }

/* Image upload */
.image-upload-wrap { padding: 12px; background: var(--bg); border-radius: var(--r-md); border: 0.5px solid var(--border-md); }

/* Essay & My Responses */
.essay-textarea { width: 100%; padding: 10px 12px; border: 0.5px solid var(--border-md);
  border-radius: var(--r-md); font-family: inherit; font-size: 14px; line-height: 1.6;
  resize: vertical; margin-bottom: 0.75rem; color: var(--text); background: var(--surface); }
.essay-textarea:focus { outline: none; border-color: var(--navy); box-shadow: 0 0 0 2px rgba(26,43,74,0.1); }
.essay-textarea:disabled { background: var(--bg); color: var(--text-2); }

.my-responses-section { margin-top: 0.5rem; }
.my-response-card { background: #F7F6F3; border: 0.5px solid var(--border-md);
  border-radius: var(--r-md); padding: 12px 14px; margin-bottom: 8px; }
.my-response-question { font-size: 12px; font-weight: 500; color: var(--text-3);
  text-transform: uppercase; letter-spacing: 0.4px; margin-bottom: 6px; }
.my-response-text { font-size: 13px; color: var(--text); line-height: 1.6; margin-bottom: 8px; white-space: pre-wrap; }
.my-response-meta { font-size: 11px; color: var(--text-3); display: flex;
  align-items: center; justify-content: space-between; }
.my-response-delete { background: none; border: none; font-size: 11px; color: var(--danger);
  cursor: pointer; padding: 0; font-family: inherit; }
.my-response-delete:hover { text-decoration: underline; }

/* Formatting toolbar */
.fmt-toolbar { display: flex; align-items: center; gap: 3px; padding: 4px 6px;
  background: var(--bg); border: 0.5px solid var(--border-md);
  border-bottom: none; border-radius: var(--r-md) var(--r-md) 0 0; flex-wrap: wrap; }
.fmt-toolbar button { background: var(--surface); border: 0.5px solid var(--border-md);
  border-radius: 4px; padding: 3px 8px; font-size: 13px; cursor: pointer;
  font-family: inherit; color: var(--text); transition: background 0.12s; line-height: 1.4; }
.fmt-toolbar button:hover { background: #E6F1FB; border-color: #B5D4F4; }
.fmt-toolbar button.is-active { background: #E6F1FB; border-color: #4A90E2; color: var(--navy); }
.fmt-sep { width: 1px; height: 18px; background: var(--border-md); margin: 0 3px; }
/* Icon-only toolbar buttons (text-alignment): stacked-lines SVGs via CSS so
   the icon look lives in one place across every card text editor. */
.fmt-toolbar button.fmt-icon { width: 28px; height: 24px; padding: 0;
  background-repeat: no-repeat; background-position: center; background-size: 15px 15px; }
.fmt-toolbar button.fmt-al-l { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%222%22%20y%3D%222.4%22%20width%3D%2212%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%222%22%20y%3D%226.0%22%20width%3D%227%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%222%22%20y%3D%229.6%22%20width%3D%2212%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%222%22%20y%3D%2213.2%22%20width%3D%227%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3C%2Fsvg%3E"); }
.fmt-toolbar button.fmt-al-c { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%222%22%20y%3D%222.4%22%20width%3D%2212%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%224.5%22%20y%3D%226.0%22%20width%3D%227%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%222%22%20y%3D%229.6%22%20width%3D%2212%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%224.5%22%20y%3D%2213.2%22%20width%3D%227%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3C%2Fsvg%3E"); }
.fmt-toolbar button.fmt-al-r { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%222%22%20y%3D%222.4%22%20width%3D%2212%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%227%22%20y%3D%226.0%22%20width%3D%227%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%222%22%20y%3D%229.6%22%20width%3D%2212%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3Crect%20x%3D%227%22%20y%3D%2213.2%22%20width%3D%227%22%20height%3D%221.6%22%20rx%3D%220.8%22%20fill%3D%22%231A2B4A%22%2F%3E%3C%2Fsvg%3E"); }
.fmt-toolbar + textarea { border-top-left-radius: 0; border-top-right-radius: 0; }

/* WYSIWYG editor */
.wysiwyg-wrap { border: 0.5px solid var(--border-md); border-radius: var(--r-md); overflow: hidden; }
.wysiwyg-wrap .fmt-toolbar { border: none; border-bottom: 0.5px solid var(--border-md); border-radius: 0; }
.wysiwyg-editor { min-height: 80px; padding: 8px 10px; font-size: 14px; font-family: inherit;
  color: var(--text); background: var(--surface); line-height: 1.6; outline: none; }
.wysiwyg-editor--scroll { height: 220px; max-height: 220px; overflow-y: auto; }
.wysiwyg-editor:focus { background: #FAFAFA; }
.wysiwyg-editor:empty:before { content: attr(data-placeholder); color: var(--text-3); }

/* Table Card editor (admin) */
.tc-frame { display: flex; flex-direction: column; gap: 4px; }
.tc-frame-row { display: flex; align-items: stretch; gap: 4px; }
.tc-resize { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; }
.tc-resize--top, .tc-resize--bottom { padding: 4px 0; }
.tc-resize--left, .tc-resize--right { flex-direction: column; padding: 0 4px; }
.tc-grid { flex: 1; display: grid; gap: 4px; min-width: 0;
  background: var(--surface); padding: 4px; border-radius: var(--r-md);
  border: 0.5px dashed var(--border-md); }
.tc-cell { min-height: 64px; padding: 6px 8px; cursor: pointer;
  background: #fff; border-radius: 4px; outline: 0.5px solid var(--border-md);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; color: var(--text-1); overflow: hidden; word-break: break-word;
  transition: outline-color 0.12s, transform 0.08s; }
.tc-cell:hover { outline-color: #4A90E2; }
.tc-cell:focus-visible { outline: 2px solid #4A90E2; }
.tc-cell--empty { color: var(--text-3); background: #FAFAFA; }
.tc-cell-empty { font-size: 18px; font-weight: 300; color: var(--text-3); }
.tc-cell-text { font-size: 13px; line-height: 1.4; max-height: 100%; overflow: hidden; }
.tc-cell-img { max-width: 100%; max-height: 60px; object-fit: contain; }

/* Table Card learner-facing rendering (inside .fc-card) */
.fc-table-wrap { width: 100%; overflow-x: auto; margin: 0.5rem 0; }
.fc-table { border-collapse: collapse; width: 100%; min-width: 0;
  font-family: inherit; font-size: 14px; line-height: 1.5; color: var(--text-1); }
.fc-table-cell { padding: 8px 10px; vertical-align: middle;
  background: transparent; outline: 0.5px solid var(--border-md);
  min-width: 80px; word-break: break-word; }
.fc-table-cell--filled { /* hook for future styling of filled cells */ }
.fc-table-cell-text { font-size: 14px; }
.fc-table-cell-text > * { margin: 0; }
.fc-table-cell-text p { margin-bottom: 0.25rem; }
.fc-table-cell-text p:last-child { margin-bottom: 0; }
.fc-table-cell-img { max-width: 100%; max-height: 120px; object-fit: contain;
  display: block; cursor: zoom-in; }

/* Cell editor modal */
.tc-modal-backdrop { position: fixed; inset: 0; background: rgba(20, 24, 36, 0.45);
  display: flex; align-items: center; justify-content: center; z-index: 1100;
  padding: 16px; }
.tc-modal-card { background: var(--surface); border-radius: var(--r-md);
  width: min(540px, 100%); max-height: calc(100vh - 32px); overflow: auto;
  box-shadow: 0 18px 36px rgba(0, 0, 0, 0.18); display: flex; flex-direction: column; }
.tc-modal-header { display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; border-bottom: 0.5px solid var(--border-md); }
.tc-modal-body { padding: 14px 16px; }
.tc-modal-footer { display: flex; align-items: center; gap: 8px; padding: 12px 16px;
  border-top: 0.5px solid var(--border-md); }

/* Flash card split layout */
.fc-split { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; align-items: start; }
.fc-split-text { min-width: 0; }
.fc-split-img img { width: 100%; height: auto; border-radius: var(--r-md); }
@media (max-width: 640px) {
  .fc-split { grid-template-columns: 1fr; }
  .fc-split-img { order: 1; }
  .fc-split-text { order: 0; }
}

/* Lightbox */
#lightbox { position: fixed; inset: 0; background: rgba(0,0,0,0.88); z-index: 500;
  display: flex; align-items: center; justify-content: center; cursor: zoom-out; padding: 1rem; }
#lightbox-img { max-width: 95vw; max-height: 92vh; object-fit: contain;
  border-radius: var(--r-md); box-shadow: 0 8px 40px rgba(0,0,0,0.5); }
.fc-img--zoom { cursor: zoom-in; transition: opacity 0.15s; }
.fc-img--zoom:hover { opacity: 0.88; }

/* Highlighted resource row (module feature color) */
.mat-row--highlight { border-color: transparent; }
.mat-row--highlight:hover { filter: brightness(0.97); }

/* ============================================================
 * APPEND THIS TO app/static/css/main.css
 *
 * All Morris-related styles are prefixed `.morris-` or scoped
 * to #morris-... so they shouldn't collide with existing styles.
 * ============================================================ */

/* ---------- Floating FAB ---------- */
#morris-fab {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 10000;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.85rem 1.15rem;
  border: none;
  border-radius: 999px;
  background: #0b0b0b;
  color: #fff;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
#morris-fab:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.3);
  background: #1a1a1a;
}
#morris-fab .morris-fab-icon {
  font-size: 1.1rem;
  line-height: 1;
}
#morris-fab:active { transform: translateY(0); }

@media (max-width: 600px) {
  #morris-fab { right: 1rem; bottom: 1rem; padding: 0.75rem 1rem; }
  #morris-fab .morris-fab-label { display: none; }
  #morris-fab { width: 52px; height: 52px; justify-content: center; padding: 0; }
  #morris-fab .morris-fab-icon { font-size: 1.35rem; }
}

/* ---------- Panel ---------- */
#morris-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 640px;
  max-width: 100vw;
  background: #fff;
  z-index: 10001;
  transform: translateX(110%);
  transition: transform 0.25s ease;
  box-shadow: -12px 0 32px rgba(0, 0, 0, 0.18);
  display: flex;
  flex-direction: column;
}
#morris-panel.open {
  transform: translateX(0);
}
@media (max-width: 600px) {
  #morris-panel { width: 100vw; }
}

.morris-panel-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

/* ---------- Header ---------- */
.morris-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  background: #0b0b0b;
  color: #fff;
  border-bottom: 1px solid #1f1f1f;
  flex-shrink: 0;
}
.morris-header-title {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.morris-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: linear-gradient(135deg, #4c9aff 0%, #00b8d4 100%);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1rem;
}
.morris-name { font-weight: 600; font-size: 1rem; line-height: 1.1; }
.morris-subtitle { font-size: 0.75rem; color: #cfd6d8; line-height: 1.1; margin-top: 2px; }

.morris-header-actions { display: flex; gap: 0.25rem; }
.morris-icon-btn {
  background: transparent;
  border: none;
  color: #fff;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0.8;
  transition: background 0.12s ease, opacity 0.12s ease;
}
.morris-icon-btn:hover { background: rgba(255, 255, 255, 0.1); opacity: 1; }

/* ---------- History drawer ---------- */
#morris-history {
  position: absolute;
  top: 60px;
  left: 0;
  right: 0;
  bottom: 0;
  background: #fafafa;
  z-index: 2;
  transform: translateY(-12px);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.2s ease, opacity 0.2s ease;
  overflow-y: auto;
}
#morris-history.open {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}
.morris-history-header {
  padding: 1rem 1.25rem 0.5rem;
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #666;
}
#morris-history-list {
  list-style: none;
  margin: 0;
  padding: 0 0.5rem 1rem;
}
.morris-history-item {
  display: flex;
  align-items: center;
  padding: 0.5rem 0.5rem;
  border-radius: 8px;
  gap: 0.4rem;
}
.morris-history-item:hover { background: #f0f0f0; }
.morris-history-item.active { background: #e8f1fc; }
.morris-history-title {
  flex: 1;
  background: transparent;
  border: none;
  text-align: left;
  padding: 0.3rem 0.5rem;
  cursor: pointer;
  font-size: 0.92rem;
  color: #1a1a1a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.morris-history-delete {
  background: transparent;
  border: none;
  color: #999;
  cursor: pointer;
  padding: 0.2rem 0.5rem;
  font-size: 0.9rem;
  border-radius: 4px;
}
.morris-history-delete:hover { color: #d33; background: #fff; }
.morris-history-empty {
  padding: 1rem 1.25rem;
  color: #888;
  font-size: 0.9rem;
  font-style: italic;
}

/* ---------- Messages area ---------- */
.morris-messages {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-height: 0;
}

.morris-greeting-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}
.morris-greeting-logo {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  background: transparent;
}
.morris-greeting {
  background: #f4f7f9;
  border-radius: 12px;
  padding: 0.9rem 1rem;
  font-size: 0.95rem;
  color: #2b2b2b;
  line-height: 1.45;
}
.morris-greeting p:first-child { margin-top: 0; }
.morris-greeting p:last-child { margin-bottom: 0; }

.morris-starters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.25rem;
}
.morris-starter-chip {
  background: #fff;
  border: 1px solid #d8dde1;
  border-radius: 999px;
  padding: 0.4rem 0.8rem;
  font-size: 0.85rem;
  color: #2b2b2b;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
  text-align: left;
}
.morris-starter-chip:hover {
  background: #f4f7f9;
  border-color: #99a5ac;
}

/* ---------- Message bubbles ---------- */
.morris-msg {
  max-width: 100%;
}
.morris-msg-user {
  align-self: flex-end;
  max-width: 80%;
}
.morris-msg-assistant {
  align-self: stretch;
  width: 100%;
}

.morris-msg-inner {
  padding: 0.7rem 0.95rem;
  border-radius: 14px;
  font-size: 0.95rem;
  line-height: 1.5;
  word-wrap: break-word;
  overflow-wrap: break-word;
}
.morris-msg-user .morris-msg-inner {
  background: #0b0b0b;
  color: #fff;
  border-bottom-right-radius: 4px;
}
.morris-msg-assistant .morris-msg-inner {
  background: #f4f7f9;
  color: #1a1a1a;
  border-bottom-left-radius: 4px;
}

.morris-msg-inner p { margin: 0 0 0.5em; }
.morris-msg-inner p:last-child { margin-bottom: 0; }
/* Inline images Morris embeds from find_image (roadmap PR 5). */
.morris-msg-inner .chat-img,
.chat-img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0.5em 0;
  border-radius: 10px;
}
.morris-msg-inner ul,
.morris-msg-inner ol {
  margin: 0.35em 0;
  padding-left: 1.3em;
}
.morris-msg-inner li { margin-bottom: 0.2em; }
.morris-msg-inner code {
  background: rgba(0, 0, 0, 0.08);
  padding: 0.1em 0.35em;
  border-radius: 4px;
  font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
  font-size: 0.88em;
}
.morris-msg-user .morris-msg-inner code {
  background: rgba(255, 255, 255, 0.15);
}
.morris-msg-inner pre {
  background: rgba(0, 0, 0, 0.08);
  padding: 0.6em 0.8em;
  border-radius: 8px;
  overflow-x: auto;
  font-size: 0.85em;
  margin: 0.4em 0;
}
.morris-msg-inner pre code { background: transparent; padding: 0; }
.morris-msg-inner a {
  color: #0066cc;
  text-decoration: underline;
}
.morris-msg-user .morris-msg-inner a { color: #8fb8ff; }
.morris-msg-inner strong { font-weight: 600; }

.morris-error {
  color: #c02a2a;
  font-style: italic;
}

/* ---------- Typing indicator ---------- */
.morris-typing {
  display: inline-flex;
  gap: 4px;
  padding: 4px 0;
}
.morris-typing span {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #999;
  animation: morris-bounce 1.2s infinite ease-in-out;
}
.morris-typing span:nth-child(2) { animation-delay: 0.15s; }
.morris-typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes morris-bounce {
  0%, 80%, 100% { transform: translateY(0); opacity: 0.5; }
  40%           { transform: translateY(-4px); opacity: 1; }
}

/* Tool-call status indicator — shown while Morris is calling a
   family-tree tool, replaced by streaming text when the answer arrives. */
.morris-tool-status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-style: italic;
  color: var(--text-2, #6b7280);
  font-size: 0.95em;
}
.morris-tool-status::before {
  content: "";
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: morris-tool-spin 0.9s linear infinite;
}
@keyframes morris-tool-spin {
  to { transform: rotate(360deg); }
}

/* ---------- Citations (Phase C12) ---------- */
/* Sources Morris cited from the reference-document corpus, rendered under
   the answer. Deliberately quiet: this is provenance the reader can check,
   not part of the answer, so it reads as a footnote rather than competing
   with the prose above it. */
.morris-citations {
  margin-top: 0.7rem;
  padding-top: 0.5rem;
  border-top: 1px solid #e1e5e8;
  font-size: 0.85em;
  color: var(--text-2, #6b7280);
}
.morris-citations-label {
  display: block;
  font-size: 0.9em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.25rem;
}
.morris-citations ul {
  margin: 0;
  padding-left: 1.1rem;
}
.morris-citations li { margin: 0.15rem 0; }
.morris-cite-loc { color: #98a0a8; }
/* Only rendered for documents an admin marked shareable, so the underline is
   a real affordance rather than decoration on every source line. */
.morris-cite-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.morris-cite-link:hover { color: var(--text-1, #1a2b4a); }

/* Admin: the Access Level segmented control (table row + upload form) */
.access-seg {
  display: inline-flex;
  border: 1px solid #d8dde1;
  border-radius: 6px;
  overflow: hidden;
  background: #fff;
  white-space: nowrap;
}
.access-seg label { cursor: pointer; margin: 0; }
/* The radio stays in the DOM and keeps doing the work — focus, keyboard
   arrows, form semantics — it just isn't the thing being painted. Clipped
   rather than display:none, which would drop it out of the tab order. */
.access-seg input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}
.access-seg span {
  display: block;
  padding: 0.3rem 0.6rem;
  font-size: 0.82em;
  color: #6b7280;
  transition: background 0.12s ease, color 0.12s ease;
}
.access-seg label + label span { border-left: 1px solid #e3e8ec; }
.access-seg input:checked + span {
  background: #4a90d9;
  color: #fff;
  font-weight: 500;
}
/* Keyboard users need to see where they are, since the radio itself is
   clipped — without this the control is unusable without a mouse. */
.access-seg input:focus-visible + span {
  outline: 2px solid #1a2b4a;
  outline-offset: -2px;
}

.doc-upload-access {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex-basis: 100%;
}
.doc-access-question { font-weight: 500; font-size: 0.9rem; }
.doc-access-question em {
  font-weight: 400;
  color: #6b7280;
}

/* ---------- Input ---------- */
.morris-input-wrap {
  border-top: 1px solid #e1e5e8;
  padding: 0.75rem 0.85rem 0.85rem;
  background: #fff;
  flex-shrink: 0;
}
#morris-form {
  display: flex;
  gap: 0.5rem;
  align-items: flex-end;
}
#morris-input {
  flex: 1;
  resize: none;
  border: 1px solid #d8dde1;
  border-radius: 10px;
  padding: 0.6rem 0.8rem;
  font-family: inherit;
  font-size: 0.95rem;
  line-height: 1.4;
  max-height: 180px;
  min-height: 40px;
  overflow-y: auto;
  outline: none;
  transition: border-color 0.12s ease;
}
#morris-input:focus { border-color: #0b0b0b; }
#morris-send-btn {
  background: #0b0b0b;
  color: #fff;
  border: none;
  border-radius: 10px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.12s ease, opacity 0.12s ease;
}
#morris-send-btn:hover:not(:disabled) { background: #333; }
#morris-send-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.morris-footnote {
  margin-top: 0.5rem;
  font-size: 0.72rem;
  color: #888;
  text-align: center;
  line-height: 1.3;
}

/* ---------- Admin Morris page helpers ---------- */
.morris-form .card {
  background: #fff;
  border: 1px solid #e1e5e8;
  border-radius: 10px;
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.25rem;
}
.morris-form h2 {
  font-size: 1.1rem;
  margin: 0 0 0.35rem;
  color: #1a1a1a;
}
.morris-form .hint {
  font-size: 0.85rem;
  color: #666;
  margin: 0 0 0.75rem;
  line-height: 1.4;
}
.morris-form .optional { color: #999; font-weight: 400; font-size: 0.85rem; }
.morris-form .w-full {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid #d8dde1;
  border-radius: 6px;
  padding: 0.55rem 0.7rem;
  font-family: inherit;
  font-size: 0.95rem;
}
.morris-form .w-full.mono {
  font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
  font-size: 0.88rem;
  line-height: 1.5;
}
.morris-form .form-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
.morris-form .form-grid-2 label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-weight: 500;
  font-size: 0.9rem;
}
/* Pairs with the rule above: a `display:` declaration outranks the browser's
   default [hidden] { display: none }, so without this the Temperature field's
   .hidden toggle would silently do nothing for models that reject the
   parameter. */
.morris-form .form-grid-2 label[hidden] { display: none; }
.morris-form select,
.morris-form input[type="range"] {
  font-family: inherit;
  padding: 0.5rem;
  border: 1px solid #d8dde1;
  border-radius: 6px;
}

/* ── Morris Reference Documents (Phase C12) ─────────────────────────────── */

/* The corpus meter. Tokens are the number that binds, so the bar tracks
   tokens and the byte/page figures ride alongside as plain text. */
.doc-meter { margin: 0.75rem 0 1rem; }
.doc-meter-bar {
  height: 8px;
  border-radius: 4px;
  background: #eceff1;
  overflow: hidden;
}
.doc-meter-fill {
  height: 100%;
  background: #4a90d9;
  border-radius: 4px;
  transition: width 0.25s ease;
}
.morris-form .doc-meter-text { margin: 0.4rem 0 0; }
/* The asset library reuses the same meter component (Phase A1). The `doc-`
   prefix is historical — the bar is a plain usage gauge, and a second copy of
   it under a different name would be worse than the naming mismatch. */
.asset-meter .doc-meter-text { margin: 0.4rem 0 0; }
/* Upload progress for the direct-to-bucket transfer. The [hidden] pairing is
   defensive: `.doc-meter` currently sets only `margin`, so `hidden` works on its
   own — but a `display:` added there later would override the browser default
   and the JS toggle would stop hiding this, silently and with no console error. */
.asset-progress .doc-meter-text { margin: 0.4rem 0 0; }
.asset-progress[hidden] { display: none; }

.doc-upload-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  padding: 0.85rem;
  background: #f7f9fa;
  border: 1px solid #e3e8ec;
  border-radius: 8px;
}
.doc-upload-row label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-weight: 500;
  font-size: 0.9rem;
}
.doc-upload-title { flex: 1; min-width: 12rem; }
.doc-upload-row input[type="text"],
.doc-upload-row select {
  font-family: inherit;
  padding: 0.5rem;
  border: 1px solid #d8dde1;
  border-radius: 6px;
}
.doc-upload-row em { color: #999; font-weight: 400; }

/* Errors are surfaced verbatim from the server (a .json rejection, a cap
   breach), so this needs room for a sentence rather than a word. */
.morris-form .doc-error {
  color: #b3261e;
  background: #fdecea;
  border: 1px solid #f5c6c2;
  border-radius: 6px;
  padding: 0.6rem 0.75rem;
  margin: 0.6rem 0 0;
}
/* Pairs with the rule above — a `display:`/`padding:` block would otherwise
   outrank the browser default [hidden] { display: none } and the error would
   never hide. Same trap the .form-grid-2 label rule above guards against. */
.morris-form .doc-error[hidden],
.morris-form .doc-empty[hidden] { display: none; }

/* The scroll box the sticky header and Name column resolve against. Without
   a scrolling ancestor, `position: sticky` has nothing to stick to. */
.doc-table-scroll {
  max-height: 60vh;
  overflow: auto;
  border: 1px solid #eceff1;
  border-radius: 8px;
}

.doc-table {
  width: 100%;
  /* separate, not collapse: collapsed borders belong to the TABLE, not the
     cells, so they scroll away from under a sticky header and leave it
     underlined by nothing. */
  border-collapse: separate;
  border-spacing: 0;
  font-size: 0.9rem;
}
.doc-table th,
.doc-table td {
  text-align: left;
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid #eceff1;
  vertical-align: middle;
  background: #fff;               /* see the opacity note below */
}
.doc-table th {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #667;
  border-bottom: 2px solid #e3e8ec;
}

/* ⚠️ EVERY STICKY CELL FILL MUST BE FULLY OPAQUE.
   Sticky positioning does not clip what passes beneath it — a transparent or
   low-alpha background lets the scrolling cells paint straight through the
   pinned header and column. That is why the blanket `background: #fff` above
   is on every cell rather than only the sticky ones, and why the inactive-row
   rule below re-states an opaque colour instead of tinting with alpha. */
.doc-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}
.doc-table .doc-name-col {
  position: sticky;
  left: 0;
  z-index: 1;
  /* The pinned column needs a visible edge; the header's own bottom border
     stops at the header row. */
  box-shadow: 1px 0 0 #eceff1;
}
/* The corner cell is sticky in BOTH axes, so it has to outrank both. */
.doc-table thead th.doc-name-col { z-index: 3; }

/* Headers all read left, including the numeric ones — the RIGHT alignment is
   a property of the figures, not of the label above them. Scoped to td so it
   cannot leak back onto th. */
.doc-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.doc-table td.doc-actions { text-align: right; }
.doc-table select {
  font-family: inherit;
  font-size: 0.85rem;
  padding: 0.3rem;
  border: 1px solid #d8dde1;
  border-radius: 5px;
}

.doc-name { font-weight: 500; }
/* Shown only when the citation title differs from the filename, so the admin
   can tell which file a renamed source actually is. */
.doc-filename {
  display: block;
  font-weight: 400;
  font-size: 0.8rem;
  color: #8a9199;
}
.doc-badge {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  background: #eceff1;
  color: #667;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* Deactivated documents stay legible — they are still managed rows, not
   disabled controls — but read as clearly out of the corpus. */
/* Colour only, never an alpha tint. A translucent row fill would show the
   scrolling cells through the sticky Name column — see the opacity note
   above. If this ever needs a background, give it a solid hex. */
.doc-row.is-inactive .doc-name,
.doc-row.is-inactive td { color: #98a0a8; }

.doc-actions {
  display: flex;
  gap: 0.35rem;
  justify-content: flex-end;
  white-space: nowrap;
}

.doc-folder-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.9rem;
}
.doc-folder-row input {
  font-family: inherit;
  padding: 0.45rem 0.6rem;
  border: 1px solid #d8dde1;
  border-radius: 6px;
}

/* The table is the widest thing on this screen. `.doc-table-scroll` now owns
   the overflow at every width — the old rule put it on the section, which
   would have been a SECOND scrolling ancestor and would have broken the
   sticky column (sticky resolves against the nearest one). The min-width
   below is what makes the horizontal scroll happen at all, so the sticky
   Name column has something to stay put against. */
@media (max-width: 1100px) {
  .doc-table { min-width: 52rem; }
}

/* ── User Preferences (Phase C11) ───────────────────────────────────────── */

/* Universal panel: read-only prose, laid out as labelled groups. Deliberately
   plain — nothing here is interactive, so it must not look like a form. */
.pref-universal {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2.5rem;
  margin-top: 0.5rem;
}
.pref-universal-group h3 {
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #667;
  margin: 0 0 0.35rem;
}
.pref-universal-group ul {
  margin: 0;
  padding-left: 1.1rem;
  font-size: 0.92rem;
}
.pref-universal-group li { margin: 0.15rem 0; color: #4b5563; }

/* Additional panel: one gated row (a real <form>) per code-declared pref. */
.pref-no-labels {
  color: #92400e;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 6px;
  padding: 0.6rem 0.75rem;
}
.pref-additional {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin-top: 0.5rem;
}
.pref-row {
  border: 1px solid #e3e8ec;
  border-radius: 8px;
  padding: 0.85rem 1rem;
  background: #fbfcfd;
}
.pref-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.pref-row-meta { display: flex; flex-direction: column; gap: 0.15rem; }
.pref-row-label { font-weight: 600; }
.pref-row-desc { font-size: 0.85rem; color: #6b7280; }
.pref-row-kind {
  flex-shrink: 0;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #667;
  background: #eceff1;
  border-radius: 4px;
  padding: 0.1rem 0.45rem;
}
.pref-audiences {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin: 0.7rem 0;
}
.pref-audiences-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: #667;
}
.pref-audience {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.9rem;
  cursor: pointer;
}
.pref-row-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.pref-row-status {
  font-size: 0.85rem;
  color: #15803d;                 /* success */
}
.pref-row-status.is-error { color: #b3261e; }
/* Pairs with the visible-state rule so [hidden] wins over the flex/inline
   display above — the codified trap where a display: rule outranks the
   browser's default [hidden] { display: none }. */
.pref-row-status[hidden] { display: none; }

.starter-row {
  display: flex;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}
.starter-row input {
  flex: 1;
  border: 1px solid #d8dde1;
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
  font-size: 0.92rem;
}
/* VR-3 (2026-07-18): retinted onto the palette. Was `1px solid #d8dde1` /
   `#888` with a `#d33` hover — three hardcoded hexes, and its hover red was
   not var(--danger). Geometry kept (36px square icon button); only the colours
   move, so nothing reflows. The class name stays because it is a JS hook
   (_morris_body.html rebuilds starter-chip rows and re-emits it). */
.btn-icon {
  background: transparent;
  border: 0.5px solid var(--border-md);
  border-radius: var(--r-md);
  width: 36px;
  cursor: pointer;
  color: var(--text-3);
}
.btn-icon:hover { color: var(--danger); border-color: var(--danger); }

.contact-row {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 0.75rem;
  background: var(--bg);
  border-radius: var(--r-md);
  border: 0.5px solid var(--border-md);
  margin-bottom: 0.6rem;
}
.contact-row.dragging { opacity: 0.5; }
/* `.drag-handle` was declared here too (unscoped). Folded into the canonical
   rule in the Admin section — VR-1 dedupe, 2026-07-18. */
.contact-fields { flex: 1; }
.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}
.contact-fields label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.82rem;
  color: var(--text-2);
  font-weight: 500;
}
.contact-fields input,
.contact-fields textarea {
  border: 0.5px solid var(--border-md);
  border-radius: 6px;
  padding: 0.45rem 0.6rem;
  font-size: 0.9rem;
  font-family: inherit;
}
.contact-fields textarea {
  resize: vertical;
  width: 100%;
  box-sizing: border-box;
}
.contact-actions {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
/* `.form-actions` was declared here too (unscoped, so it applied app-wide and
   overrode the canonical rule). Folded into that rule in the Fields section —
   VR-1 dedupe, 2026-07-18. */
@media (max-width: 600px) {
  .morris-form .form-grid-2,
  .contact-grid { grid-template-columns: 1fr; }
}

/* ============================================================
 * APPEND THIS TO app/static/css/main.css
 * (v2 — per-field save buttons + module-targeting pills)
 * ============================================================ */

/* Card header: title on left, save button on right ----------- */
.morris-form .card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.35rem;
  gap: 1rem;
}
.morris-form .card-header h2 {
  margin: 0;
}
.morris-form .card-header-hint {
  font-size: 0.78rem;
  color: #888;
  font-style: italic;
}

/* Per-section Save button ------------------------------------ */
/* VR-3 (2026-07-18): the Morris settings Save buttons now carry
   `btn btn--primary btn--sm` in the markup, so geometry and the resting colour
   come from the palette. This rule is reduced to what the palette can't
   express: the flex behaviour and the two JS-driven feedback states.

   It was a parallel button system — white on #0b0b0b with a hover that
   inverted to solid black, none of it using a CSS variable. Black is not a
   colour in this palette; --navy is.

   The class name stays as the JS hook: _morris_body.html queries
   `.btn-section-save` and toggles .is-saved / .is-error on it. The state rules
   need to beat .btn--primary, so they are compound (two classes) rather than
   relying on source order — the .form-actions--top lesson from VR-2c. */
.btn-section-save { flex-shrink: 0; }
.btn-section-save:disabled { cursor: default; }
.btn--primary.btn-section-save.is-saved {
  background: var(--success-lt);
  color: var(--success);
  border-color: var(--success);
}
.btn--primary.btn-section-save.is-error {
  background: var(--danger-lt);
  color: var(--danger);
  border-color: var(--danger);
}
/* The palette's primary hover would otherwise repaint a feedback state navy
   the moment the pointer crossed it, throwing away the message. */
.btn--primary.btn-section-save.is-saved:hover { background: var(--success-lt); }
.btn--primary.btn-section-save.is-error:hover { background: var(--danger-lt); }

/* Starter rows ----------------------------------------------- */
.starter-row {
  background: #fafafa;
  border: 1px solid #e1e5e8;
  border-radius: 8px;
  padding: 0.75rem;
  margin-bottom: 0.6rem;
}
.starter-row-top {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.6rem;
}
.starter-text {
  flex: 1;
  border: 1px solid #d8dde1;
  border-radius: 6px;
  padding: 0.55rem 0.75rem;
  font-family: inherit;
  font-size: 0.95rem;
}
.starter-remove {
  flex-shrink: 0;
}

/* Module targeting pills ------------------------------------- */
.starter-mods {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  padding-top: 0.25rem;
}
.mods-label {
  font-size: 0.78rem;
  color: #666;
  margin-right: 0.3rem;
  font-weight: 500;
}
.mod-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.65rem;
  background: transparent;
  border: 0.5px solid var(--border-md);
  border-radius: 999px;
  font-size: 0.78rem;
  cursor: pointer;
  user-select: none;
  transition: all 0.1s ease;
  color: var(--text-2);
}
.mod-pill:hover {
  background: rgba(26, 43, 74, 0.06);
  color: var(--text);
}
.mod-pill input[type=checkbox] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
/* VR-3: was a solid-black selected state — a SECOND pill system sitting beside
   .pill / .pill.on (main.css:288-290), which the repository filter chips use.
   Now visually identical to that one, so "a selected chip" means one thing in
   the admin. `.mod-pill` keeps its name because the starter-question JS calls
   closest('.mod-pill') and toggles .is-selected on it. */
.mod-pill.is-selected {
  background: var(--chip-on);
  color: #fff;
  border-color: var(--chip-on);
}
/* The chip's label is a nested <span>, so the white above does not reach it by
   inheritance alone once anything sets a colour on the child. */
.mod-pill.is-selected .mod-pill-title { color: #fff; }
.mod-pill-title {
  font-weight: 500;
}
.mod-hint-none {
  font-size: 0.72rem;
  color: #999;
  font-style: italic;
  margin-left: auto;
  padding-left: 0.5rem;
}

/* Responsive: stack pills on narrow screens ------------------ */
@media (max-width: 600px) {
  .starter-mods .mod-hint-none {
    margin-left: 0;
    padding-left: 0;
    flex-basis: 100%;
  }
}
/* ============================================================
 * APPEND THIS TO app/static/css/main.css  (v3)
 *
 * These styles replace the old #morris-fab styles. You can either:
 *   (a) Just append this, and the new rules override the old ones by
 *       virtue of coming later — works but leaves dead FAB CSS in place
 *   (b) Delete the old #morris-fab block (see instructions in INSTALL_V3.md)
 *
 * Either way is fine; the FAB is no longer rendered so its CSS has
 * no visible effect, just extra bytes.
 * ============================================================ */

/* Hide the old FAB if it's somehow still in the DOM */
#morris-fab {
  display: none !important;
}

/* Topbar button ------------------------------------------------------ */
#morris-topbar-btn {
  /* Reset button defaults */
  border: none;
  font-family: inherit;
  cursor: pointer;
  /* Visual: bright green pill */
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.95rem;
  border-radius: 999px;
  background: #4ade80; /* Tailwind green-400 — friendly, confident */
  color: #052e16;      /* Deep green-ish black, strong contrast on green */
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1;
  transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease;
  box-shadow: 0 0 0 0 rgba(74, 222, 128, 0);
  /* Stand out against the black topbar */
  position: relative;
  z-index: 1;
  /* Attention pulse on first view */
  animation: morris-pulse 2.4s ease-in-out 0.5s 3;
}
#morris-topbar-btn:hover {
  background: #22c55e; /* green-500 */
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(34, 197, 94, 0.4);
}
#morris-topbar-btn:active {
  transform: translateY(0);
}
#morris-topbar-btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}
/* Once the user has opened Morris, stop the pulse for the rest of session */
#morris-topbar-btn.morris-seen {
  animation: none;
}

.morris-topbar-icon {
  font-size: 1rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
}
/* Tiny one-color Morris logo in place of the old speech-bubble emoji.
   The source SVG is black; brightness(0) normalizes any internal fills to
   pure black so it reads cleanly on the green pill. */
.morris-topbar-icon img {
  height: 1.15rem;
  width: auto;
  display: block;
  filter: brightness(0);
}
.morris-topbar-label {
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* Mobile: show just the icon to save space */
@media (max-width: 640px) {
  #morris-topbar-btn {
    padding: 0.45rem 0.7rem;
  }
  .morris-topbar-label {
    display: none;
  }
  .morris-topbar-icon {
    font-size: 1.15rem;
  }
  .morris-topbar-icon img {
    height: 1.3rem;
  }
}

/* Pulse animation — subtle green halo that expands and fades ---------- */
@keyframes morris-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.7);
  }
  50% {
    box-shadow: 0 0 0 8px rgba(74, 222, 128, 0);
  }
}

/* Respect users who've requested reduced motion */
@media (prefers-reduced-motion: reduce) {
  #morris-topbar-btn {
    animation: none;
  }
}
/* ============================================================
 * APPEND THIS BLOCK TO THE BOTTOM OF app/static/css/main.css
 *
 * Adds the 2-column desktop module layout with a slide-deck tile
 * on the left. On mobile (< 900px) everything stacks into the
 * original single-column look.
 *
 * All new class names — nothing collides with existing styles.
 * The old .deck-row rules are left untouched but are no longer
 * emitted by index.html, so they have no visible effect.
 *
 * Added 2026-04-18 for module desktop-layout feature.
 * ============================================================ */

/* ── Mobile default: single-column stack ─────────────────────── */
.mod-body-grid {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.mod-body-left  { order: 0; min-width: 0; }   /* deck above subtopics on mobile */
.mod-body-right { order: 1; min-width: 0; }

/* ── Deck card — mobile layout (horizontal row, looks like old .deck-row) ── */
.deck-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  margin-bottom: 1.25rem;
  border-radius: var(--r-md);
  border: 0.5px solid transparent;
}
.deck-card--pending { opacity: 0.6; }

.deck-card-icon-mobile  { flex-shrink: 0; opacity: 0.7; display: block; }
.deck-card-icon-desktop { display: none; }
.deck-card-image        { display: none; }

.deck-card-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.deck-card-head {
  display: flex;
  align-items: center;
  gap: 6px;
}
.deck-card .deck-label {
  font-size: 10px;
  font-weight: 500;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.deck-card .deck-title,
.deck-card .deck-coming {
  font-size: 15px;
  font-weight: 500;
  line-height: 1.35;
  color: var(--text);
}
.deck-card .deck-coming {
  color: var(--text-3);
  font-style: italic;
  font-weight: 400;
}
.deck-card .deck-link {
  text-decoration: none;
  color: var(--navy);
}
.deck-card .deck-link:hover { text-decoration: underline; }

.deck-card-badges {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  flex-shrink: 0;
}

/* ── Desktop (≥ 900px): 2-column grid with vertical deck tile ── */
@media (min-width: 900px) {
  .mod-body-grid {
    display: grid;
    grid-template-columns: 240px 1fr;
    gap: 1.75rem;
    align-items: start;
  }
  .mod-body-left  { order: unset; }
  .mod-body-right { order: unset; }

  /* Card becomes a vertical tile */
  .deck-card {
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
    padding: 1rem;
    margin-bottom: 0;
    border-color: var(--border);
  }

  /* Swap which icon is visible */
  .deck-card-icon-mobile  { display: none; }
  .deck-card-icon-desktop {
    display: block;
    flex-shrink: 0;
    opacity: 0.7;
  }

  /* Image slot becomes visible at the top of the tile */
  .deck-card-image {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border-radius: var(--r-md);
    background: rgba(0,0,0,0.04);
  }
  .deck-card-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  .deck-card-image--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-3);
    border: 1px dashed var(--border-md);
    background: transparent;
  }

  /* Body flows naturally in the column; badges sit below it */
  .deck-card-body { gap: 0.5rem; }
  .deck-card-badges { margin-top: 0.25rem; }

  /* Right column: don't show the thick top border on the first section-label
     (the grid gap + tile already provide visual separation from the overview) */
  .mod-body-right > .section-label:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
  }
  .mod-body-right > .subtopic:first-of-type {
    margin-top: 0.75rem;
  }

  /* Wider page max-width on the curriculum view so the 4-across resource-tile
     grid (PR 3 + 2026-05-09 polish) has room to breathe. Scoped via the body
     class so admin pages are untouched. */
  body.curriculum-page .page { max-width: 1280px; }
}
/* ============================================================
 * APPEND THIS BLOCK TO THE BOTTOM OF app/static/css/main.css
 *
 * Adds a small thumbnail image to the mobile slide-deck row when
 * the module has an uploaded tile image. Modules without one keep
 * showing just the computer icon (no change).
 *
 * Added 2026-04-18 (mobile-thumbnail follow-up to layout update).
 * ============================================================ */

@media (max-width: 899.98px) {
  /* Show the tile image as a 48px square thumbnail on the left of the
     mobile deck row. :not(...--placeholder) ensures we only show actual
     uploaded images, never the dashed placeholder. */
  .deck-card-image:not(.deck-card-image--placeholder) {
    display: block;
    width: 48px;
    height: 48px;
    flex-shrink: 0;
    overflow: hidden;
    border-radius: var(--r-md);
    order: -1;   /* position before the computer icon */
  }
  .deck-card-image:not(.deck-card-image--placeholder) img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  /* Chips below title on mobile: let the flex row wrap and force the
     badges wrapper onto its own full-width line below image+icon+body. */
  .deck-card { flex-wrap: wrap; }
  .deck-card-badges { flex-basis: 100%; }
}
/* =========================================================================
   RMS Learning System — Feature Build 2026-04-20
   Append this block to the end of app/static/css/main.css

   Sections:
     1. FREE FILTER PILL            (Feature 1)
     2. LEARNER STYLE: sidebar tile (Feature 2)
     3. LEARNER STYLE: quiz modal   (Feature 2)
     4. WELCOME MODAL               (Feature 3)
   ========================================================================= */


/* =========================================================================
   1. FREE FILTER PILL  (Feature 1)
   -------------------------------------------------------------------------
   Adds a "Free only" toggle pill to the curriculum filter bar. Composes
   with the existing .pill styles — we just add a subtle green dot when
   off, and flip to green-filled when on.

   Wire-up in curriculum.js: data-filter="free" on the pill toggles
   activeFreeOnly boolean, which then filters material rows that have
   data-free="true".
   ========================================================================= */

.pill.pill-free {
  --free-accent: #0F6E56; /* matches --success in the main palette */
}
.pill.pill-free .free-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--free-accent);
  margin-right: 6px;
  vertical-align: middle;
  transition: background .12s ease;
}
.pill.pill-free.on {
  background: var(--free-accent);
  color: #fff;
  border-color: var(--free-accent);
}
.pill.pill-free.on .free-dot {
  background: #fff;
}

/* Badge variant for the material card itself */
.badge.b-free {
  background: var(--success-lt, #E1F5EE);
  color: var(--success, #0F6E56);
  font-weight: 600;
}


/* =========================================================================
   2. LEARNER STYLE: sidebar tile  (Feature 2)
   -------------------------------------------------------------------------
   Two visual states:
     .ls-tile--cta      — user has no type yet (big friendly CTA)
     .ls-tile--has-type — user has a type (compact display + retake link)
   Both collapse to full-width on narrow screens.
   ========================================================================= */

.ls-tile {
  --ls-tile-accent: #4A90E2;
  position: relative;
  border-radius: 12px;
  background: #fff;
  border: 1px solid var(--surface-border, #e5e5e5);
  overflow: hidden;
  margin-bottom: 1rem;
}
.ls-tile::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: var(--ls-tile-accent);
}

/* ── CTA state ──────────────────────────────────────────────────────── */
.ls-tile--cta .ls-tile-cta-btn {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 0.75rem;
  align-items: center;
  width: 100%;
  padding: 1rem 1rem 1rem 1.25rem;
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  text-align: left;
  color: inherit;
  border-radius: 12px;
  transition: background .12s ease, transform .12s ease;
}
.ls-tile--cta .ls-tile-cta-btn:hover {
  background: linear-gradient(90deg, rgba(74,144,226,0.04), transparent 60%);
}
.ls-tile--cta .ls-tile-cta-btn:active {
  transform: scale(0.995);
}
.ls-tile--cta .ls-tile-icon--cta {
  font-size: 28px;
  line-height: 1;
  padding: 4px 0;
}
.ls-tile--cta .ls-tile-arrow {
  font-size: 20px;
  color: var(--ls-tile-accent);
  transition: transform .15s ease;
}
.ls-tile--cta .ls-tile-cta-btn:hover .ls-tile-arrow {
  transform: translateX(2px);
}

/* ── Has-type state ─────────────────────────────────────────────────── */
.ls-tile--has-type {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 1rem 1rem 1rem 1.25rem;
}
.ls-tile--has-type .ls-tile-icon {
  font-size: 28px;
  line-height: 1;
  padding-top: 2px;
}

/* ── Shared bits ────────────────────────────────────────────────────── */
.ls-tile-eyebrow {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-2, #777);
  font-weight: 600;
  margin-bottom: 2px;
}
.ls-tile-name {
  font-size: 16px;
  font-weight: 600;
  color: var(--text, #222);
  margin-bottom: 2px;
  line-height: 1.2;
}
.ls-tile-tagline {
  font-size: 13px;
  color: var(--text-2, #666);
  line-height: 1.35;
}
.ls-tile-retake-btn {
  display: inline-block;
  margin-top: 0.5rem;
  padding: 4px 10px;
  font-size: 12px;
  background: none;
  border: 1px solid var(--ls-tile-accent);
  color: var(--ls-tile-accent);
  border-radius: 999px;
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.ls-tile-retake-btn:hover {
  background: var(--ls-tile-accent);
  color: #fff;
}


/* =========================================================================
   3. LEARNER STYLE: quiz modal  (Feature 2)
   -------------------------------------------------------------------------
   Self-contained modal with 5 stages: intro, questions, result, saved,
   empty. All selectors prefixed with .ls- to avoid collisions with the
   existing comment/lightbox modals.
   ========================================================================= */

.ls-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(10, 15, 20, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 10000;
  animation: ls-fade-in 0.18s ease;
}
@keyframes ls-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
body.ls-modal-open { overflow: hidden; }

.ls-modal-card {
  --ls-accent: #4A90E2;
  background: #fff;
  border-radius: 16px;
  width: 100%;
  max-width: 540px;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  position: relative;
  animation: ls-slide-up 0.22s ease;
}
@keyframes ls-slide-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.ls-modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.25rem 1.5rem 0.75rem;
}
.ls-modal-header h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  color: var(--text, #222);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.ls-modal-sparkle { font-size: 22px; }

.ls-modal-close {
  background: none;
  border: none;
  color: var(--text-2, #888);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  transition: background .12s ease, color .12s ease;
}
.ls-modal-close:hover {
  background: rgba(0, 0, 0, 0.05);
  color: var(--text, #222);
}

.ls-modal-body { padding: 0.5rem 1.5rem 1.5rem; }

/* ── Progress bar ──────────────────────────────────────────────────── */
.ls-progress { padding: 0 1.5rem 0.75rem; }
.ls-progress-track {
  width: 100%;
  height: 4px;
  background: #e8ebee;
  border-radius: 4px;
  overflow: hidden;
}
.ls-progress-bar {
  height: 100%;
  width: 0%;
  background: var(--ls-accent);
  border-radius: 4px;
  transition: width .28s ease;
}
.ls-progress-label {
  font-size: 12px;
  color: var(--text-2, #888);
  text-align: right;
  margin-top: 4px;
}

/* ── Stage: intro ──────────────────────────────────────────────────── */
.ls-intro-lead {
  font-size: 15px;
  color: var(--text, #333);
  line-height: 1.5;
  margin: 0.25rem 0 0.5rem;
}
.ls-intro-sub {
  font-size: 13px;
  color: var(--text-2, #777);
  margin: 0 0 1rem;
}
.ls-intro-current {
  font-size: 13px;
  color: var(--text-2, #666);
  background: #F2F6FA;
  padding: 8px 12px;
  border-radius: 8px;
  margin-bottom: 1rem;
}

/* ── Stage: questions ──────────────────────────────────────────────── */
.ls-question-text {
  font-size: 18px;
  font-weight: 500;
  color: var(--text, #222);
  margin: 0 0 1rem;
  line-height: 1.4;
}
.ls-choices {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.ls-choice {
  text-align: left;
  background: #F8F9FB;
  border: 1px solid #E3E8ED;
  color: var(--text, #222);
  padding: 0.9rem 1rem;
  border-radius: 10px;
  font-size: 15px;
  font-family: inherit;
  cursor: pointer;
  transition: all .12s ease;
}
.ls-choice:hover {
  background: #fff;
  border-color: var(--ls-accent);
  transform: translateX(2px);
}
.ls-choice--selected {
  background: var(--ls-accent);
  color: #fff;
  border-color: var(--ls-accent);
}
.ls-between-actions { justify-content: flex-start; }

/* ── Stage: result ─────────────────────────────────────────────────── */
.ls-result-hero {
  text-align: center;
  padding: 1rem 0 1.25rem;
  background: linear-gradient(180deg,
                              color-mix(in srgb, var(--ls-accent) 12%, transparent),
                              transparent);
  border-radius: 12px;
  margin-bottom: 1rem;
}
.ls-result-emoji {
  font-size: 56px;
  line-height: 1;
  margin-bottom: 0.5rem;
}
.ls-result-title {
  font-size: 24px;
  font-weight: 600;
  color: var(--text, #222);
  margin-bottom: 4px;
}
.ls-result-title #ls-result-name { color: var(--ls-accent); }
.ls-result-tagline {
  font-size: 15px;
  color: var(--text-2, #666);
  font-style: italic;
}
.ls-result-short {
  font-size: 14px;
  color: var(--text, #333);
  line-height: 1.55;
  margin: 0 0 1rem;
}
.ls-result-more {
  margin-bottom: 1.25rem;
  border-top: 1px solid #eee;
  padding-top: 0.75rem;
}
.ls-result-more summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--ls-accent);
  font-weight: 500;
  padding: 4px 0;
  user-select: none;
}
.ls-result-more p {
  font-size: 13px;
  color: var(--text-2, #555);
  line-height: 1.55;
  margin-top: 0.5rem;
}

.ls-result-saved {
  background: linear-gradient(180deg, rgba(39, 174, 96, 0.1), transparent);
}
.ls-result-saved .ls-result-emoji {
  font-size: 48px;
  color: #27AE60;
}

/* ── Empty stage ───────────────────────────────────────────────────── */
.ls-empty-msg {
  font-size: 14px;
  color: var(--text-2, #666);
  line-height: 1.5;
  margin: 0.5rem 0 1rem;
}

/* ── Action rows ───────────────────────────────────────────────────── */
.ls-stage-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}
.ls-spacer { flex: 1; }

.ls-btn {
  padding: 0.65rem 1.1rem;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all .12s ease;
}
.ls-btn-primary {
  background: var(--ls-accent);
  color: #fff;
  border-color: var(--ls-accent);
}
.ls-btn-primary:hover:not(:disabled) {
  filter: brightness(0.92);
}
.ls-btn-primary:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.ls-btn-secondary {
  background: #fff;
  color: var(--text, #222);
  border-color: #D0D5DA;
}
.ls-btn-secondary:hover {
  background: #F5F7F9;
}
.ls-btn-ghost {
  background: none;
  color: var(--text-2, #666);
  border: none;
}
.ls-btn-ghost:hover { color: var(--text, #222); }


/* =========================================================================
   4. WELCOME MODAL  (Feature 3)
   ========================================================================= */

.welcome-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(10, 15, 20, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 10000;
  animation: welcome-fade-in 0.18s ease;
}
@keyframes welcome-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
body.welcome-modal-open { overflow: hidden; }

.welcome-modal-card {
  background: #fff;
  border-radius: 16px;
  width: 100%;
  max-width: 440px;
  padding: 2rem 1.75rem 1.5rem;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  position: relative;
  text-align: center;
  animation: welcome-slide-up 0.22s ease;
}
@keyframes welcome-slide-up {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.welcome-modal-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  background: none;
  border: none;
  color: var(--text-2, #888);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  transition: background .12s ease, color .12s ease;
}
.welcome-modal-close:hover {
  background: rgba(0, 0, 0, 0.05);
  color: var(--text, #222);
}

.welcome-modal-icon {
  display: flex;
  justify-content: center;
  margin: 0 0 0.75rem;
}
.welcome-modal-icon img {
  display: block;
  width: 76px;
  height: auto;
}

.welcome-modal-title {
  font-size: 22px;
  font-weight: 600;
  color: var(--text, #222);
  margin: 0 0 0.75rem;
}
.welcome-modal-body {
  font-size: 15px;
  color: var(--text-2, #555);
  line-height: 1.55;
  margin: 0 0 1.5rem;
}

.welcome-modal-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1rem;
}
.welcome-modal-actions .welcome-btn {
  min-width: 220px;
}
.welcome-btn {
  padding: 0.7rem 1.25rem;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all .12s ease;
}
.welcome-btn-primary {
  background: linear-gradient(135deg, #4A90E2, #3E7CC2);
  color: #fff;
  border-color: transparent;
}
.welcome-btn-primary:hover {
  filter: brightness(0.94);
  transform: translateY(-1px);
}
.welcome-btn-ghost {
  background: #fff;
  color: var(--text, #333);
  border-color: #d1d5db;
}
.welcome-btn-ghost:hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--text, #222);
  border-color: #9ca3af;
}

.welcome-modal-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 13px;
  color: var(--text-2, #888);
  cursor: pointer;
  user-select: none;
  padding: 4px 8px;
}
.welcome-modal-checkbox input {
  margin: 0;
  cursor: pointer;
}
.welcome-modal-checkbox:hover { color: var(--text, #444); }

.welcome-modal-helper {
  font-size: 12px;
  color: var(--text-2, #888);
  margin: 0.4rem 0 0;
  line-height: 1.4;
}


/* =========================================================================
   Narrow-screen adjustments for all three features
   ========================================================================= */
@media (max-width: 560px) {
  .ls-modal-card { max-height: 96vh; }
  .ls-modal-header { padding: 1rem 1.25rem 0.5rem; }
  .ls-modal-header h2 { font-size: 18px; }
  .ls-modal-body { padding: 0.5rem 1.25rem 1.25rem; }
  .ls-progress { padding: 0 1.25rem 0.75rem; }
  .ls-result-emoji { font-size: 48px; }
  .ls-result-title { font-size: 20px; }

  .welcome-modal-card { padding: 1.75rem 1.25rem 1.25rem; }
  .welcome-modal-title { font-size: 19px; }
  .welcome-modal-body { font-size: 14px; }
  .welcome-modal-actions { flex-direction: column; }
  .welcome-btn { width: 100%; }

  .ls-tile--has-type { padding: 0.875rem 1rem 0.875rem 1.125rem; }
  .ls-tile--cta .ls-tile-cta-btn { padding: 0.875rem 1rem 0.875rem 1.125rem; }
  .ls-tile-name { font-size: 15px; }
}


/* =========================================================================
   Topbar greeting toggle button ("Hello, [Name] ▸")
   The dropdown menu it used to open was removed when the left sidebar
   landed; this button now toggles the sidebar's collapsed/expanded state.
   Persistence handled in preferences.js via /preferences/me.
   ========================================================================= */

button.user-menu-btn {
  position: relative;       /* anchors the [data-tooltip]::after */
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  transition: color 0.15s;
}
button.user-menu-btn:hover { color: #C5DBE8; }
button.user-menu-btn:hover .topbar-greeting { color: #C5DBE8; }
button.user-menu-btn:focus-visible {
  outline: 2px solid #A5C2D6;
  outline-offset: 3px;
  border-radius: 3px;
}

.user-menu-caret {
  font-size: 12px;
  line-height: 1;
  opacity: 0.95;
  margin-left: 2px;
  transition: transform 0.18s ease;
}
/* aria-expanded="true" means the sidebar is expanded — rotate the caret
   90° clockwise so it points down ("collapse me"). aria-expanded="false"
   keeps the default right-pointing chevron ("expand me"). */
button.user-menu-btn[aria-expanded="true"] .user-menu-caret {
  transform: rotate(90deg);
}


/* =========================================================================
   Left sidebar (siderail) — Phase A
   Fixed-position rail below the topbar. Two states driven by
   body[data-sidebar-collapsed]: expanded (220px, icons+labels) and
   collapsed (64px, icons only). Default expanded for new users.
   ========================================================================= */

:root {
  --sidebar-bg: #EAE0CB;
  --sidebar-divider: rgba(0, 0, 0, 0.12);
  --sidebar-width-expanded: 220px;
  --sidebar-width-collapsed: 64px;
}

.sidebar {
  position: fixed;
  top: 52px;               /* matches .topbar height */
  left: 0;
  bottom: 0;
  width: var(--sidebar-width-expanded);
  background: var(--sidebar-bg);
  z-index: 90;             /* below topbar (z=100) and modals */
  transition: width 0.18s ease;
  /* No overflow rule — custom [data-tooltip] tooltips on collapsed-state
     items render via ::after and need to escape the sidebar's 64px
     bounds. Only 10 items today so vertical overflow isn't a concern;
     reintroduce overflow on .sidebar-nav if the list grows. */
}
body[data-sidebar-collapsed="true"] .sidebar {
  width: var(--sidebar-width-collapsed);
}

.sidebar-nav {
  padding: 1rem 0;
  display: flex;
  flex-direction: column;
}

.sidebar-group {
  list-style: none;
  margin: 0;
  padding: 0;
}
.sidebar-group li { list-style: none; }

.sidebar-divider {
  height: 1px;
  background: var(--sidebar-divider);
  margin: 0.75rem 1.25rem;
}
body[data-sidebar-collapsed="true"] .sidebar-divider {
  margin: 0.75rem 1rem;
}

/* Item — shared by <button>, <a>, and disabled <span>. Flex layout so
   the icon stays put and the label is the flexible bit. position:
   relative anchors the custom tooltip ::after pseudo-element. */
.sidebar-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 8px 1.25rem;
  background: none;
  border: none;
  font-family: inherit;
  font-size: 14px;
  color: var(--text);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.sidebar-item:hover {
  background: rgba(0, 0, 0, 0.05);
}
.sidebar-item:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: -2px;
}

.sidebar-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 22px;
  width: 22px;
  height: 22px;
  color: var(--text);
}
.sidebar-icon svg {
  width: 20px;
  height: 20px;
  display: block;
}

.sidebar-label {
  flex: 1 1 auto;
  font-weight: 500;
  line-height: 1.2;
}

/* Collapsed state: hide labels, center the icons. The icon column is
   still 22px wide, so centering relies on shrinking item padding too. */
body[data-sidebar-collapsed="true"] .sidebar-label {
  display: none;
}
body[data-sidebar-collapsed="true"] .sidebar-item {
  justify-content: center;
  padding: 8px 0;
}

/* Disabled placeholders — visible but unclickable. Tooltip text comes
   from the title attribute (rendered as a browser tooltip on hover).
   We deliberately leave pointer-events on so the title tooltip can fire;
   the span has no href and no click handler so a click is a no-op. */
.sidebar-item--disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.sidebar-item--disabled:hover {
  background: transparent;
}

/* =========================================================================
   Custom tooltips ([data-tooltip])

   Rendered via ::after on hover/focus-visible. Uses data-tooltip rather
   than the native title attribute so the browser doesn't layer its
   slow native tooltip on top of ours; aria-label stays present for
   screen readers.

   Two scoped positionings:
     - Sidebar items: tooltip floats to the RIGHT of the icon, and
       ONLY appears when the sidebar is collapsed (when expanded, the
       visible label is enough).
     - Topbar toggle (greeting): tooltip drops BELOW the button,
       always shown on hover because the button's visible text says
       what it IS, not what clicking will DO.
   ========================================================================= */
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  background: #1A2B4A;
  color: #fff;
  font-size: 12px;
  font-weight: 500;
  padding: 5px 9px;
  border-radius: 5px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 110;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
}

/* Sidebar items: tooltip to the right, vertically centered, only when
   the sidebar is collapsed. */
body[data-sidebar-collapsed="true"] .sidebar-item[data-tooltip]:hover::after,
body[data-sidebar-collapsed="true"] .sidebar-item[data-tooltip]:focus-visible::after {
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
  opacity: 1;
  visibility: visible;
}

/* Topbar toggle button: tooltip below the button. */
.user-menu-btn[data-tooltip]:hover::after,
.user-menu-btn[data-tooltip]:focus-visible::after {
  top: calc(100% + 6px);
  left: 0;
  opacity: 1;
  visibility: visible;
}


/* =========================================================================
   Sidebar-aware main content offset

   Pinned to the expanded sidebar width plus a gutter, regardless of
   collapsed state. Collapsing the sidebar reveals additional white
   space on the left instead of shifting page content — content
   position stays anchored, only the sidebar itself moves.
   ========================================================================= */
body.has-sidebar > main {
  padding-left: calc(var(--sidebar-width-expanded) + 1.5rem);
}


/* =========================================================================
   Mobile drawer (Phase B)

   Under 720px the sidebar becomes a slide-in overlay anchored to the
   left edge, with a darkened backdrop behind it. Body content drops
   the sidebar padding-left so the page reads full-width. The desktop
   sys_sidebar_collapsed pref is ignored at this breakpoint — drawer
   state is binary open/closed via body[data-mobile-drawer-open],
   transient and not persisted.

   The backdrop element lives in base.html as a sibling of .sidebar
   and is display:none on desktop. JS in preferences.js dispatches
   open/close on toggle-button click, backdrop click, Esc key, and
   sidebar-item activation.
   ========================================================================= */
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 85;             /* between page content and the sidebar (90) */
  opacity: 0;
  transition: opacity 0.18s ease;
}

@media (max-width: 720px) {
  /* Page content no longer reserves room for a fixed sidebar — the
     drawer slides over the top instead. */
  body.has-sidebar > main {
    padding-left: 0;
  }

  /* Drawer always uses the expanded width on mobile (labels visible),
     transformed off-screen by default. Slides in when the body flag
     is set. */
  .sidebar {
    width: var(--sidebar-width-expanded);
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: 0 0 24px rgba(0, 0, 0, 0.18);
  }
  body[data-mobile-drawer-open="true"] .sidebar {
    transform: translateX(0);
  }

  /* Override the collapsed-state rules at this breakpoint so the
     desktop pref doesn't shrink labels or hide them on mobile. */
  body[data-sidebar-collapsed="true"] .sidebar {
    width: var(--sidebar-width-expanded);
  }
  body[data-sidebar-collapsed="true"] .sidebar-label {
    display: inline;
  }
  body[data-sidebar-collapsed="true"] .sidebar-item {
    justify-content: flex-start;
    padding: 8px 1.25rem;
  }

  /* Tooltips are unnecessary on mobile — labels are always visible
     inside the drawer. Suppress the ::after pseudo-element. */
  .sidebar-item[data-tooltip]:hover::after,
  .sidebar-item[data-tooltip]:focus-visible::after {
    display: none;
  }

  /* Backdrop: hidden until drawer opens, then fades in. Click-to-
     close handled in JS. */
  .sidebar-backdrop {
    display: block;
  }
  body[data-mobile-drawer-open="true"] .sidebar-backdrop {
    opacity: 1;
  }
}


/* =========================================================================
   Preferences modal
   Opened from the user menu dropdown

   CONVENTION (design decision 2026-04-22): this modal is an always-light
   utility surface, visually demarcated from the main app. All text colors
   are hardcoded to light-mode values (navy #1A2B4A for titles/body, slate
   #5A6478 for secondary/desc). Do not use var(--text) / var(--text-2) /
   var(--surface) / var(--bg) inside this modal — they inherit the body
   theme and will flip to pale-on-white the moment the user toggles dark
   mode, since the modal card itself stays #fff.
   ========================================================================= */

.prefs-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(10, 15, 20, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 10000;
  animation: prefs-fade-in 0.18s ease;
}
@keyframes prefs-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
body.prefs-modal-open { overflow: hidden; }

.prefs-modal-card {
  background: #fff;
  border-radius: 16px;
  width: 100%;
  max-width: 520px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  animation: prefs-slide-up 0.22s ease;
}
@keyframes prefs-slide-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.prefs-modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.25rem 1.5rem 0.5rem;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.prefs-modal-header h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  color: #1A2B4A;
}
.prefs-modal-close {
  background: none;
  border: none;
  color: #5A6478;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  transition: background 0.12s, color 0.12s;
}
.prefs-modal-close:hover {
  background: rgba(0, 0, 0, 0.05);
  color: #1A2B4A;
}

.prefs-modal-body {
  padding: 1rem 1.5rem;
  overflow-y: auto;
  flex: 1;
}

.prefs-section {
  padding: 0.75rem 0;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.prefs-section:last-child { border-bottom: none; }

.prefs-section-label {
  margin-bottom: 0.75rem;
}
.prefs-section-title {
  font-size: 14px;
  font-weight: 600;
  color: #1A2B4A;
  margin-bottom: 2px;
}
.prefs-section-desc {
  font-size: 13px;
  color: #5A6478;
  line-height: 1.4;
}

.prefs-section-controls {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.prefs-radio {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.4rem 0.6rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  color: #1A2B4A;
  line-height: 1.4;
  transition: background 0.1s;
}
.prefs-radio:hover { background: #F8F9FB; }
.prefs-radio input[type="radio"] {
  margin: 2px 0 0 0;
  accent-color: #4A90E2;
  cursor: pointer;
  flex-shrink: 0;
}

.prefs-modal-footer {
  padding: 0.875rem 1.5rem 1.25rem;
  border-top: 1px solid rgba(0, 0, 0, 0.06);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
}
.prefs-save-status {
  font-size: 13px;
  color: #5A6478;
}
.prefs-modal-actions {
  display: flex;
  gap: 0.5rem;
}
.prefs-btn {
  padding: 0.6rem 1.1rem;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all 0.12s;
}
.prefs-btn-primary {
  background: #4A90E2;
  color: #fff;
  border-color: #4A90E2;
}
.prefs-btn-primary:hover:not(:disabled) { filter: brightness(0.92); }
.prefs-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.prefs-btn-ghost {
  background: none;
  color: #5A6478;
  border-color: transparent;
}
.prefs-btn-ghost:hover { background: rgba(0, 0, 0, 0.04); color: #1A2B4A; }

@media (max-width: 560px) {
  .prefs-modal-card { max-height: 96vh; }
  .prefs-modal-header { padding: 1rem 1.25rem 0.5rem; }
  .prefs-modal-body { padding: 1rem 1.25rem; }
  .prefs-modal-footer {
    flex-direction: column;
    align-items: stretch;
    padding: 0.875rem 1.25rem 1rem;
  }
  .prefs-save-status { text-align: center; }
  .prefs-modal-actions { justify-content: flex-end; }
}


/* =========================================================================
   User Directory modal — Phase 2
   Reuses the .prefs-modal-* skin; only the bits that diverge live here.
   ========================================================================= */

.prefs-modal-card.directory-modal-card {
  max-width: 640px;
  height: 80vh;
  max-height: 720px;
  display: flex;
  flex-direction: column;
}

.directory-search-row {
  padding: 0.25rem 1.5rem 0.75rem;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.directory-search-input {
  width: 100%;
  font-family: inherit;
  font-size: 14px;
  color: #1A2B4A;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.14);
  border-radius: 6px;
  padding: 0.55rem 0.85rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.directory-search-input:focus {
  outline: none;
  border-color: #4A90E2;
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.18);
}

.directory-modal-body {
  flex: 1;
  overflow-y: auto;
}

.directory-results {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.directory-tile {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  padding: 0.65rem 0.85rem;
  border-radius: 8px;
  background: #fff;
  border: 1px solid transparent;
  transition: background 0.1s, border-color 0.1s;
}
/* No hover effect in Phase 2 since tiles are inert — Phase 3 adds an
   action button on each tile and we'll re-introduce hover then. */

.directory-tile-photo-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  flex-shrink: 0;
  width: 48px;
}
.directory-tile-photo {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #F1F4F9;
  color: #8893A8;
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(0, 0, 0, 0.05);
}
.directory-tile-status-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 3px;
  line-height: 1;
}
.directory-tile-status-chips .badge {
  padding: 1px 6px;
  font-size: 9px;
}
.directory-tile-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.directory-tile-photo-initial {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: 22px;
  color: #5A6478;
}

.directory-tile-info {
  flex: 1;
  min-width: 0;
}
.directory-tile-name {
  font-size: 15px;
  font-weight: 600;
  color: #1A2B4A;
  line-height: 1.3;
}
.directory-tile-location {
  font-size: 13px;
  color: #5A6478;
  margin-top: 1px;
}
.directory-tile-about {
  font-size: 13px;
  color: #1A2B4A;
  margin-top: 0.35rem;
  line-height: 1.45;
}

.directory-state-msg {
  padding: 1.5rem 0.5rem;
  text-align: center;
  font-size: 14px;
  color: #5A6478;
}
.directory-state-msg--error { color: #C0392B; }

@media (max-width: 560px) {
  .prefs-modal-card.directory-modal-card { height: 96vh; max-height: 96vh; }
  .directory-search-row { padding: 0.25rem 1.25rem 0.6rem; }
  .directory-tile { padding: 0.6rem 0.5rem; }
}


/* =========================================================================
   Messages modal — Phase 3 / PR C
   Inbox view; thread view and composer come in PR D.
   Reuses the .prefs-modal-* skin; sized like the User Directory modal.
   ========================================================================= */

.prefs-modal-card.messages-modal-card {
  max-width: 640px;
  height: 80vh;
  max-height: 720px;
  display: flex;
  flex-direction: column;
}

.messages-modal-body {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem 1rem 1rem;
}

.messages-thread-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.messages-thread-row {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  padding: 0.65rem 0.75rem;
  border-radius: 8px;
  background: #fff;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background 0.1s, border-color 0.1s;
}
.messages-thread-row:hover {
  background: #F8F9FB;
  border-color: rgba(0, 0, 0, 0.06);
}

.messages-thread-photo {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #F1F4F9;
  color: #8893A8;
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(0, 0, 0, 0.05);
}
.messages-thread-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.messages-thread-photo-initial {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: 20px;
  color: #5A6478;
}

.messages-thread-info {
  flex: 1;
  min-width: 0;
}
.messages-thread-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}
.messages-thread-name {
  font-size: 14px;
  font-weight: 600;
  color: #1A2B4A;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.messages-thread-time {
  font-size: 12px;
  color: #8893A8;
  flex-shrink: 0;
}
.messages-thread-preview {
  font-size: 13px;
  color: #5A6478;
  margin-top: 0.2rem;
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.messages-thread-preview-author {
  color: #8893A8;
}

.messages-thread-row--unread .messages-thread-name {
  color: #1A2B4A;
}
.messages-thread-row--unread .messages-thread-preview {
  color: #1A2B4A;
  font-weight: 500;
}
.messages-thread-unread-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #4A90E2;
  flex-shrink: 0;
  align-self: center;
}

.messages-state-msg {
  padding: 2rem 0.5rem;
  text-align: center;
  font-size: 14px;
  color: #5A6478;
  line-height: 1.5;
}
.messages-state-msg strong { color: #1A2B4A; }
.messages-state-msg--error { color: #C0392B; }

@media (max-width: 560px) {
  .prefs-modal-card.messages-modal-card { height: 96vh; max-height: 96vh; }
  .messages-modal-body { padding: 0.5rem 0.75rem 0.75rem; }
}

/* ── View switching ────────────────────────────────────────────────────
   The modal hosts two views; JS toggles data-view on the backdrop. The
   .messages-modal-header--* and .messages-modal-body--* elements show
   only when their matching view is active.                            */
#messages-modal[data-view="inbox"]  .messages-modal-header--thread,
#messages-modal[data-view="inbox"]  .messages-modal-body--thread,
#messages-modal[data-view="thread"] .messages-modal-header--inbox,
#messages-modal[data-view="thread"] .messages-modal-body--inbox {
  display: none;
}

.messages-modal-header--thread {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-right: 0.85rem;
}
.messages-back-btn {
  background: none;
  border: 1px solid transparent;
  color: #5A6478;
  font-size: 20px;
  line-height: 1;
  padding: 0.35rem 0.55rem;
  border-radius: 6px;
  cursor: pointer;
  flex-shrink: 0;
}
.messages-back-btn:hover {
  background: rgba(0, 0, 0, 0.04);
  color: #1A2B4A;
}
.messages-thread-header-info {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex: 1;
  min-width: 0;
}
.messages-thread-header-photo {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: #F1F4F9;
  color: #8893A8;
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(0, 0, 0, 0.05);
}
.messages-thread-header-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.messages-thread-header-photo .messages-thread-photo-initial {
  font-size: 16px;
}
.messages-thread-header-name {
  font-size: 16px;
  font-weight: 600;
  color: #1A2B4A;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Thread-view body ───────────────────────────────────────────────── */
.messages-modal-body--thread {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.messages-conversation {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.messages-bubble {
  max-width: 78%;
  padding: 0.55rem 0.85rem;
  border-radius: 14px;
  font-size: 14px;
  line-height: 1.4;
  color: #1A2B4A;
  word-wrap: break-word;
  overflow-wrap: break-word;
}
.messages-bubble--mine {
  align-self: flex-end;
  background: #4A90E2;
  color: #fff;
  border-bottom-right-radius: 4px;
}
.messages-bubble--theirs {
  align-self: flex-start;
  background: #F1F4F9;
  border-bottom-left-radius: 4px;
}
.messages-bubble-meta {
  font-size: 11px;
  color: #8893A8;
  margin: 0 0.25rem 0.15rem;
  align-self: flex-end;   /* timestamp aligns with bubble it follows */
}
.messages-bubble--theirs + .messages-bubble-meta,
.messages-bubble-meta.messages-bubble-meta--theirs {
  align-self: flex-start;
}
.messages-bubble--mine + .messages-bubble-meta,
.messages-bubble-meta.messages-bubble-meta--mine {
  align-self: flex-end;
}

/* ── Composer ───────────────────────────────────────────────────────── */
.messages-composer {
  display: flex;
  gap: 0.5rem;
  padding: 0.65rem 0.85rem 0.85rem;
  border-top: 1px solid rgba(0, 0, 0, 0.06);
  background: #fff;
  align-items: flex-end;
  flex-wrap: wrap;
}
/* The composer's wysiwyg wrap fills the composer row alongside the
   Send button. Override the default .wysiwyg-wrap layout where needed. */
.messages-composer .wysiwyg-wrap.messages-composer-wrap {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.messages-composer .messages-composer-editor {
  min-height: 2.4em;
  max-height: 9rem;
  overflow-y: auto;
  font-size: 14px;
  line-height: 1.4;
  padding: 0.55rem 0.7rem;
}
.messages-composer-send {
  padding: 0.55rem 1.1rem;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  background: #4A90E2;
  color: #fff;
  border: 1px solid #4A90E2;
  cursor: pointer;
  flex-shrink: 0;
  transition: filter 0.1s;
}
.messages-composer-send:hover:not(:disabled) { filter: brightness(0.92); }
.messages-composer-send:disabled { opacity: 0.55; cursor: not-allowed; }
.messages-composer-error {
  width: 100%;
  font-size: 12px;
  color: #C0392B;
  padding: 0.2rem 0.1rem 0;
}

/* ── Bubble delete affordance ───────────────────────────────────────── */
/* Wrap that contains bubble + delete button. Holds layout so the button
   can sit alongside the bubble without disrupting flex flow. */
.messages-bubble-wrap {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  max-width: 100%;
}
.messages-bubble-wrap--mine {
  align-self: flex-end;
  flex-direction: row-reverse;     /* button left of mine bubble */
}
.messages-bubble-wrap--theirs {
  align-self: flex-start;
}
.messages-bubble-delete {
  background: none;
  border: 1px solid rgba(0, 0, 0, 0.08);
  color: #8893A8;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  font-size: 14px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.1s, color 0.1s, border-color 0.1s;
}
.messages-bubble-delete:hover,
.messages-bubble-delete:focus-visible {
  background: rgba(192, 57, 43, 0.1);
  color: #C0392B;
  border-color: rgba(192, 57, 43, 0.25);
}

/* ── Sidebar unread badge ───────────────────────────────────────────── */
.sidebar-item--has-badge .sidebar-icon {
  position: relative;
}
.sidebar-badge {
  position: absolute;
  top: -4px;
  right: -6px;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: #C0392B;
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.05rem;
  text-align: center;
  border: 1.5px solid #fff;
  pointer-events: none;
}

/* ── Directory tile "Send message" action ───────────────────────────── */
.directory-tile-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
}
.directory-tile-send-btn {
  background: none;
  border: 1px solid rgba(0, 0, 0, 0.12);
  color: #5A6478;
  border-radius: 999px;
  padding: 0.35rem 0.85rem;
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: all 0.12s;
}
.directory-tile-send-btn:hover {
  background: #4A90E2;
  color: #fff;
  border-color: #4A90E2;
}
.directory-tile-send-btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}


/* =========================================================================
   Profile section — Profile + Preferences modal (PR B1)
   Photo upload, city/state autocomplete, about-me, directory opt-in.
   Sits inside the existing .prefs-section skin.
   ========================================================================= */

/* Stronger divider after the Profile section than between sibling prefs
   sections — signals the transition from personal info to the more
   mechanical app-wide preferences below. Overrides the default
   .prefs-section border-bottom (1px / 0.06 alpha). */
.prefs-section[data-pref-group="profile"] {
  border-bottom: 2px solid rgba(0, 0, 0, 0.18);
  padding-bottom: 1rem;
}

.profile-section-controls { gap: 0.9rem; }

.profile-photo-row {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.profile-photo-preview {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: #F1F4F9;
  color: #8893A8;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
  border: 1px solid rgba(0, 0, 0, 0.06);
}
.profile-photo-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.profile-photo-preview svg {
  width: 40px;
  height: 40px;
}

.profile-photo-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}
.profile-photo-actions .prefs-btn {
  padding: 0.4rem 0.85rem;
  font-size: 13px;
}
/* The "Choose photo" button is a <label> wrapping a hidden file input,
   so it needs to look like the ghost button. */
.profile-photo-upload-label {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  background: none;
  color: #5A6478;
  border: 1px solid rgba(0, 0, 0, 0.12);
}
.profile-photo-upload-label:hover {
  background: rgba(0, 0, 0, 0.04);
  color: #1A2B4A;
}

.profile-photo-status {
  font-size: 12px;
  color: #5A6478;
  min-height: 1em;
}
.profile-photo-status.profile-photo-status--error { color: #C0392B; }

.profile-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.profile-field-label {
  font-size: 13px;
  font-weight: 500;
  color: #1A2B4A;
}

.profile-text-input,
.profile-textarea {
  font-family: inherit;
  font-size: 14px;
  color: #1A2B4A;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.14);
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.profile-text-input:focus,
.profile-textarea:focus {
  outline: none;
  border-color: #4A90E2;
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.18);
}
.profile-textarea {
  resize: vertical;
  min-height: 5rem;
  line-height: 1.45;
}

.profile-city-state {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}
.profile-city-input-wrap {
  position: relative;
  flex: 1;
  min-width: 0;
}
.profile-city-input-wrap > .profile-text-input { width: 100%; }
.profile-state-input {
  width: 4.5rem;
  text-align: center;
  text-transform: uppercase;
  background: #F8F9FB;
  color: #5A6478;
  cursor: default;
}

.profile-city-suggestions {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 5;
  margin: 0;
  padding: 0.25rem 0;
  list-style: none;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
  max-height: 14rem;
  overflow-y: auto;
}
.profile-city-suggestions li {
  padding: 0.4rem 0.75rem;
  font-size: 14px;
  color: #1A2B4A;
  cursor: pointer;
}
.profile-city-suggestions li:hover,
.profile-city-suggestions li[aria-selected="true"] {
  background: #EEF4FB;
}
.profile-city-suggestions li .profile-city-suggestion-state {
  color: #5A6478;
  font-size: 13px;
  margin-left: 0.25rem;
}

.profile-directory-toggle {
  margin-top: 0.25rem;
}


/* =========================================================================
   Notepad modal — Phase 6b (sidebar Notepad)
   Reuses .prefs-modal-* skin; only the bits that diverge live here.
   ========================================================================= */

/* A bit wider than the prefs modal so the rich-text editor breathes. */
.prefs-modal-card.notepad-modal-card { max-width: 640px; }

.notepad-blurb {
  font-size: 13px;
  color: #5A6478;
  margin: 0 0 0.75rem 0;
  line-height: 1.4;
}

.notepad-wrap .wysiwyg-editor.notepad-editor {
  /* Tall enough to be a real scratch-pad, but capped so the modal
     doesn't overflow on short viewports. wysiwyg-editor--scroll already
     gives us internal scrolling. */
  height: 320px;
  max-height: 320px;
}

/* Status indicator color states. Default uses the .prefs-save-status
   color; on error we use a softer warning red so "retrying" doesn't
   read alarming. */
.notepad-status--error { color: #C0392B; }

@media (max-width: 560px) {
  .notepad-wrap .wysiwyg-editor.notepad-editor {
    height: 240px;
    max-height: 240px;
  }
}


/* =========================================================================
   Preferences modal — admin-defined toggle & radio items
   Round 2 of 2026-04-20
   ========================================================================= */

/* Wrap each admin preference in its own spacing unit */
.prefs-admin-item {
  padding: 0.4rem 0;
  border-bottom: 1px dashed rgba(0, 0, 0, 0.06);
}
.prefs-admin-item:last-child { border-bottom: none; }

/* iOS-style toggle switch for boolean prefs */
.prefs-toggle {
  display: grid;
  grid-template-columns: auto 44px 1fr;
  grid-template-areas: "_ sw labels";
  gap: 0.6rem;
  align-items: center;
  padding: 0.5rem 0;
  cursor: pointer;
}
.prefs-toggle input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.prefs-toggle-slider {
  grid-area: sw;
  position: relative;
  width: 44px;
  height: 24px;
  background: #CED5DD;
  border-radius: 999px;
  transition: background 0.18s;
  flex-shrink: 0;
}
.prefs-toggle-slider::before {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
  transition: transform 0.18s;
}
.prefs-toggle input[type="checkbox"]:checked + .prefs-toggle-slider {
  background: #4A90E2;
}
.prefs-toggle input[type="checkbox"]:checked + .prefs-toggle-slider::before {
  transform: translateX(20px);
}
.prefs-toggle input[type="checkbox"]:focus-visible + .prefs-toggle-slider {
  box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.3);
}

.prefs-toggle-labels { grid-area: labels; }
.prefs-toggle-title {
  font-size: 14px;
  font-weight: 500;
  color: #1A2B4A;
  line-height: 1.3;
}
.prefs-toggle-desc {
  font-size: 13px;
  color: #5A6478;
  line-height: 1.4;
  margin-top: 2px;
}

/* Radio groups in the admin-defined section get the title+desc up top
   and the individual radio rows use the existing .prefs-radio styling */
.prefs-radio-group {
  padding: 0.25rem 0;
}
.prefs-radio-group > .prefs-section-title {
  margin-top: 0;
  margin-bottom: 2px;
}

/* ─────────────────────────────────────────────────────────────
   Section tiles (Library → /curriculum, Sections Phase 3c)
   Tile index for the Library. Tiles flip with dark mode via the
   existing palette variables — no spot-fixes needed. Hover combines
   a warm background tint (var(--bg)) with a border darken — matches
   .mod-header hover idiom; no transform/shadow.
   ───────────────────────────────────────────────────────────── */

.section-tiles {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin: 1.5rem 0;
}

@media (min-width: 900px) {
  .section-tiles {
    grid-template-columns: 1fr 1fr;
    gap: 24px;
  }
}

.section-tile {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border-md);
  border-radius: var(--r-lg);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.section-tile:hover,
.section-tile:focus-visible {
  background: var(--bg);
  border-color: var(--text-3);
  text-decoration: none;
}

.section-tile:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
}

.section-tile-image {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--section-default-bg);
}

.section-tile-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.section-tile-initial {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: clamp(72px, 12vw, 120px);
  line-height: 1;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.95);
}

/* Section tile bodies are an always-light utility surface — hardcoded
   hexes below are deliberate (not theme vars), matching the Preferences
   modal pattern (see Quirk #19). Tile bodies do NOT flip with dark
   mode — they remain cream with dark text in both themes so the tiles
   read as distinctive visual objects.

   flex: 1 lets the body grow to fill the row's stretched height, so
   shorter-content tiles in a two-up grid don't leave a white strip
   below the cream body. Pairs with .section-tile being a flex column. */
.section-tile-body {
  padding: 16px 20px 20px;
  background: #EFE6D1;
  flex: 1 1 auto;
}

.section-tile-name {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: 22px;
  font-weight: 500;
  color: #1A2B4A;
  margin: 0 0 6px;
  line-height: 1.25;
}

.section-tile-subtitle {
  font-size: 14px;
  color: #3D4A5E;
  line-height: 1.5;
  margin: 0 0 8px;
}

.section-tile-count {
  font-size: 12px;
  color: #5A6478;
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ─────────────────────────────────────────────────────────────
   Module tile grid (PR 2 of module-page redesign, 2026-05-09).
   Section pages render a grid of module tiles that click through
   to per-module pages. Modeled on .section-tile, with one key
   difference: no border. Module tiles are already a defined
   visual object via image + caption; borders are reserved for
   resource tiles in PR 3.
   ───────────────────────────────────────────────────────────── */

.module-tiles {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin: 1.5rem 0;
}

@media (min-width: 900px) {
  .module-tiles {
    grid-template-columns: repeat(3, 1fr);
    gap: 24px;
  }
}

.module-tile {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: var(--r-lg);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: background 0.15s ease, transform 0.15s ease;
}

.module-tile:hover,
.module-tile:focus-visible {
  background: var(--bg);
  text-decoration: none;
}

.module-tile:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
}

.module-tile-image {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--section-default-bg);
}

.module-tile-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.module-tile-initial {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: clamp(72px, 12vw, 120px);
  line-height: 1;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.95);
}

/* Same always-light cream surface as .section-tile-body — not theme-flipped.
   flex: 1 so the cream extends to the bottom of the tile even when the row
   is taller than this tile's content (driven by a sibling's longer body). */
.module-tile-body {
  padding: 16px 20px 20px;
  background: #EFE6D1;
  flex: 1;
}

.module-tile-name {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: 22px;
  font-weight: 500;
  color: #1A2B4A;
  margin: 0 0 6px;
  line-height: 1.25;
}

.module-tile-subtitle {
  font-size: 14px;
  color: #3D4A5E;
  line-height: 1.5;
  margin: 0 0 8px;
}

.module-tile-count {
  font-size: 12px;
  color: #5A6478;
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ─────────────────────────────────────────────────────────────
   Resource tile grid (PR 3 of module-page redesign, 2026-05-09).
   On per-module pages, each material / interactive block / deck
   renders as a tile: image (or serif-capital fallback) at top,
   title + source + description in the middle, format/level/core/
   free chips at the bottom. 3-across desktop, 1-across mobile.
   Per-module pastel from module.color_style. Equal-height tiles
   per row via CSS grid stretch; description clamped at 5 lines
   so a single very-long tile cannot blow out a row.

   Note: this rule overrides the older .materials-grid (line ~300)
   which used flex column for the legacy .mat-row layout.
   ───────────────────────────────────────────────────────────── */

.module-page .materials-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  padding-left: 0;
}

@media (min-width: 900px) {
  .module-page .materials-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 18px;
  }
}

.resource-tile {
  display: flex;
  flex-direction: column;
  border: 0.5px solid #4A4A4A;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.resource-tile:hover,
.resource-tile:focus-visible {
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  text-decoration: none;
}

.resource-tile:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
}

.resource-tile--highlight {
  border-width: 1px;
  border-color: var(--navy);
}

.resource-tile--pending {
  opacity: 0.7;
}

.resource-tile-image {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.04);
}

.resource-tile-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.resource-tile-initial {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: clamp(56px, 9vw, 96px);
  line-height: 1;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.95);
}

/* Inline video player (Phase A1 PR 3b). Occupies the tile's image slot at the
   same 4:3 box the thumbnail uses, so a video tile is the same height as its
   neighbours in the grid. The tile itself is not a link — the <video> owns its
   clicks — so the lift-on-hover transform is dropped here to avoid nudging the
   player while someone reaches for the scrubber. */
.resource-tile-video {
  aspect-ratio: 4 / 3;
  background: #000;
  overflow: hidden;
}
.resource-tile-video video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background: #000;
}
.resource-tile--video:hover,
.resource-tile--video:focus-within {
  transform: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.resource-tile-body {
  padding: 14px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}

.resource-tile-title {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.35;
  color: var(--text);
}

.resource-tile-source {
  font-size: 12px;
  color: var(--text-3);
  line-height: 1.4;
}

.resource-tile-desc {
  font-size: 13px;
  color: var(--text-2);
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 5;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.resource-tile-chips-row {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: auto;
  padding-top: 8px;
}

.resource-tile--interactive { cursor: pointer; }

/* Section Header materials sit inline within a subtopic's resource grid;
   they need to span all columns and shed the legacy negative margin from
   the old flex-column .materials-grid layout. Scoped to .module-page so
   the original rule is preserved for any other consumers.

   (2026-05-09 polish: the .subtopic-divider and .intra-subtopic-divider
   rules were removed — all dividers were dropped from per-module pages
   in favor of the shaded subtopic-name bands.) */
.module-page .mat-section-header {
  grid-column: 1 / -1;
  margin-left: 0;
}

/* Module header row — hero image side-by-side with the title block on
   desktop, stacked on mobile. (2026-05-09 polish, replaces the original
   full-width 21:9 banner from PR 3.) */
.module-header-row {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

@media (min-width: 900px) {
  .module-header-row {
    flex-direction: row;
    align-items: center;
    gap: 2rem;
  }
}

.module-hero-image {
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--r-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--section-default-bg);
  flex-shrink: 0;
}

@media (min-width: 900px) {
  .module-hero-image {
    width: 280px;
  }
}

.module-hero-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.module-hero-initial {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: clamp(96px, 14vw, 160px);
  line-height: 1;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.95);
}

/* In the side-by-side header row, .curr-header takes the remaining space
   and drops its own bottom border / padding so the row reads as a single
   unified header block. */
.module-header-row .curr-header {
  flex: 1;
  min-width: 0;
  padding: 0;
  border-bottom: none;
  margin-bottom: 0;
}

/* Larger title sizes across all Library pages — Library home, Section,
   and Module detail (2026-05-09 polish, extended 2026-05-13). H1 jumps
   from the global 26px to 40px; subtopic names jump from 15px sans to
   22px serif so they read as proper section headings under the H1. */
.curriculum-page .curr-header h1 {
  font-size: 40px;
  line-height: 1.1;
}

.module-page .subtopic-name {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: 22px;
  font-weight: 400;
  letter-spacing: -0.2px;
  margin-bottom: 16px;
  /* Module.color_style is stored as an opaque inline-style string in the
     DB and on some modules contains both background-color and color
     declarations. The inline style="..." otherwise wins over external
     CSS for color, so we !important the black to make sure subtopic
     titles stay readable regardless of what's stored in color_style. */
  color: #000 !important;
  /* Shaded band behind the title — module pastel comes through the inline
     style="{{ module.color_style }}" on each .subtopic-name element. */
  padding: 12px 18px;
  border-radius: var(--r-md);
}

/* ─────────────────────────────────────────────────────────────
   Back-to-Library link on per-section pages (Phase 3c)
   Sits inline above the H1 on /curriculum/<section_id>.
   ───────────────────────────────────────────────────────────── */

.back-to-library {
  display: inline-block;
  font-size: 13px;
  color: var(--text-2);
  text-decoration: none;
  margin-bottom: 6px;
  transition: color 0.15s ease;
}

.back-to-library:hover,
.back-to-library:focus-visible {
  color: var(--text);
  text-decoration: underline;
}

/* Optional section-overview paragraph (below H1 + subtitle on section page) */
.section-overview {
  font-size: 15px;
  color: var(--text-2);
  line-height: 1.6;
  margin: 0.5rem 0 1rem;
  max-width: 720px;
}

/* ── Morris Card panel — embedded chatbot inside a card ─────────────────── */
.morris-card-panel {
  display: flex;
  flex-direction: column;
  border: 0.5px solid var(--border-md);
  border-radius: var(--r-md);
  background: var(--surface, #fff);
  margin-top: 0.75rem;
  max-height: 420px;
  min-height: 280px;
  overflow: hidden;
}
.morris-card-panel-header {
  display: flex;
  justify-content: flex-end;
  padding: 0.4rem 0.6rem 0;
  min-height: 1.5rem;
}
.morris-card-clear-btn {
  background: transparent;
  border: 0;
  padding: 2px 6px;
  font-size: 12px;
  color: var(--text-muted, #666);
  cursor: pointer;
  border-radius: var(--r-sm, 4px);
}
.morris-card-clear-btn:hover {
  color: var(--accent, #1A2B4A);
  background: rgba(0, 0, 0, 0.04);
}
.morris-card-clear-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.morris-card-clear-btn[hidden] { display: none; }
.morris-card-msgs {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem;
  overflow-y: auto;
  min-height: 0;
}
.morris-card-panel .morris-starters {
  padding: 0 0.75rem 0.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
}
.morris-card-form {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 0.5rem 0.75rem 0.75rem;
  border-top: 0.5px solid var(--border-md);
  background: var(--bg, #fafafa);
}
.morris-card-input {
  flex: 1;
  resize: none;
  font-family: inherit;
  font-size: 13px;
  line-height: 1.4;
  padding: 7px 10px;
  border: 0.5px solid var(--border-md);
  border-radius: var(--r-md);
  min-height: 36px;
  max-height: 140px;
  background: #fff;
}
.morris-card-input:focus {
  outline: none;
  border-color: var(--accent, #1A2B4A);
}
.morris-card-send { flex-shrink: 0; }


/* =========================================================================
   Uploads modal — Phase 6c (sidebar Uploads)
   Reuses the .prefs-modal-* skin; only the bits that diverge live here.
   ========================================================================= */

.prefs-modal-card.uploads-modal-card {
  max-width: 760px;
  height: 80vh;
  max-height: 720px;
  display: flex;
  flex-direction: column;
}

.uploads-toolbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.4rem 1.5rem 0.75rem;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  flex-wrap: wrap;
}
.uploads-breadcrumb {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem;
  font-size: 13px;
  color: #5A6478;
}
.uploads-crumb {
  background: none;
  border: none;
  padding: 2px 4px;
  font: inherit;
  color: inherit;
  cursor: default;
}
.uploads-crumb--link {
  color: #4A90E2;
  cursor: pointer;
  border-radius: 4px;
}
.uploads-crumb--link:hover { background: rgba(74, 144, 226, 0.08); }
.uploads-crumb--current {
  color: #1A2B4A;
  font-weight: 600;
}
.uploads-crumb-sep {
  color: #A0A8BA;
  margin: 0 1px;
}

.uploads-toolbar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.uploads-quota {
  font-size: 12px;
  color: #5A6478;
  margin-right: 0.25rem;
}

.uploads-btn {
  display: inline-flex;
  align-items: center;
  font: inherit;
  font-size: 13px;
  padding: 0.4rem 0.75rem;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 0.1s, border-color 0.1s;
}
.uploads-btn--ghost {
  background: transparent;
  color: #1A2B4A;
  border-color: rgba(0, 0, 0, 0.14);
}
.uploads-btn--ghost:hover { background: #F1F4F9; }
.uploads-btn--primary {
  background: #4A90E2;
  color: #fff;
  border-color: #4A90E2;
}
.uploads-btn--primary:hover { background: #3D7BC4; border-color: #3D7BC4; }

.uploads-modal-body {
  flex: 1;
  overflow-y: auto;
}

.uploads-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.uploads-row {
  display: flex;
  align-items: center;
  border-bottom: 1px solid rgba(0, 0, 0, 0.04);
  position: relative;
}
.uploads-row:last-child { border-bottom: none; }

.uploads-row-main {
  flex: 1;
  display: grid;
  grid-template-columns: 28px 1fr auto auto;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.85rem;
  border: none;
  background: none;
  text-align: left;
  font: inherit;
  color: #1A2B4A;
  cursor: pointer;
  text-decoration: none;
  min-width: 0;
}
.uploads-row-main:hover { background: #F8FAFD; }
.uploads-row--file .uploads-row-main { color: #1A2B4A; }

.uploads-row-icon {
  width: 22px;
  height: 22px;
  color: #5A6478;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.uploads-row--folder .uploads-row-icon { color: #4A90E2; }

.uploads-row-name {
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.uploads-row-meta,
.uploads-row-date {
  font-size: 12px;
  color: #5A6478;
  white-space: nowrap;
}

.uploads-row-actions {
  position: relative;
  padding-right: 0.5rem;
}
.uploads-actions-btn {
  background: none;
  border: none;
  font-size: 18px;
  line-height: 1;
  color: #5A6478;
  cursor: pointer;
  padding: 0.25rem 0.6rem;
  border-radius: 4px;
}
.uploads-actions-btn:hover { background: #F1F4F9; color: #1A2B4A; }

.uploads-menu {
  position: absolute;
  top: 100%;
  right: 0;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 6px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  min-width: 140px;
  z-index: 20;
  padding: 0.25rem 0;
  display: flex;
  flex-direction: column;
}
/* The browser's default display:none on [hidden] is overridden by the
   display:flex above; restore it explicitly with a higher-specificity
   selector. Without this, every row's menu would be visible at once. */
.uploads-menu[hidden] { display: none; }
/* When a row is near the bottom of the scrollable body, JS flips the
   menu upward so it isn't clipped by overflow-y: auto on the body. */
.uploads-menu.uploads-menu--up {
  top: auto;
  bottom: 100%;
}
.uploads-menu-item {
  background: none;
  border: none;
  text-align: left;
  font: inherit;
  font-size: 13px;
  color: #1A2B4A;
  padding: 0.4rem 0.9rem;
  cursor: pointer;
  text-decoration: none;
}
.uploads-menu-item:hover { background: #F1F4F9; }
.uploads-menu-item--danger { color: #C0392B; }
.uploads-menu-item--danger:hover { background: #FDEDEC; }

.uploads-state-msg {
  padding: 1.5rem 0.5rem;
  text-align: center;
  font-size: 14px;
  color: #5A6478;
}
.uploads-state-msg--error { color: #C0392B; }

.uploads-status {
  font-size: 12px;
  color: #5A6478;
}
.uploads-status--error { color: #C0392B; }

/* Move-to dialog (a secondary modal on top of the Uploads modal).
   .prefs-modal-backdrop runs at z-index 10000, so this one has to sit
   above that — otherwise the Move dialog renders behind the Uploads
   modal and isn't visible until the user closes it. */
.uploads-move-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  z-index: 10010;
  display: flex;
  align-items: center;
  justify-content: center;
}
.uploads-move-card {
  background: #fff;
  border-radius: 8px;
  padding: 1.25rem 1.5rem;
  width: min(420px, 92vw);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
.uploads-move-card h3 {
  margin: 0 0 0.75rem 0;
  font-size: 16px;
  color: #1A2B4A;
}
.uploads-move-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

@media (max-width: 560px) {
  .prefs-modal-card.uploads-modal-card { height: 96vh; max-height: 96vh; }
  .uploads-toolbar { padding: 0.25rem 1rem 0.6rem; }
  .uploads-row-main {
    grid-template-columns: 24px 1fr auto;
    padding: 0.55rem 0.6rem;
  }
  .uploads-row-date { display: none; }
}
