/* ============================================================
   EdQuest - site styles
   Colors: #f84 (flame), #666 (slate), #000 (ink), #fff (paper)
   Font: Noticia Text (self-hosted woff2 in /static/fonts/)
   Mobile-first, no framework. HTMX-friendly.
   ============================================================ */

:root {
  --flame: #ff8844;
  --flame-deep: #e66a1a;
  --slate: #666666;
  --slate-soft: #8a8a8a;
  --slate-faint: #e6e6e6;
  --ink: #000000;
  --paper: #ffffff;
  --paper-warm: #fafafa;
  --canvas: #faf7f1;   /* warm page-surface - core brand color */

  /* Brand rounding: a squared bottom-right corner mirrors the Q-mark logo.
     Baking it into the radius tokens means every control that already uses them
     (buttons, inputs, cards, the search box, the subscribe field, ...) gets the
     motif from one place - no per-control edits. Order: TL TR BR BL. */
  --radius-sm: 4px 4px 0 4px;
  --radius: 8px 8px 0 8px;
  --radius-lg: 14px 14px 0 14px;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
  --shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.12);

  --font-body: "Noticia Text", Georgia, "Times New Roman", serif;
  --gap: 1rem;
  --container: 72rem;

  --focus: 0 0 0 3px rgba(255, 136, 68, 0.4);
}

/* ---------- Self-hosted Noticia Text ---------- */
@font-face {
  font-family: "Noticia Text";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local("Noticia Text"),
       url("/static/fonts/noticia-text-400.94e4b31c31cb.woff2") format("woff2");
}
@font-face {
  font-family: "Noticia Text";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: local("Noticia Text Italic"),
       url("/static/fonts/noticia-text-400-italic.da14259e743a.woff2") format("woff2");
}
@font-face {
  font-family: "Noticia Text";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: local("Noticia Text Bold"),
       url("/static/fonts/noticia-text-700.a64945645224.woff2") format("woff2");
}

/* ---------- Reset-ish ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ink);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  /* Sticky footer: fill the viewport so a short page's footer still reaches the
     bottom instead of leaving a canvas-coloured strip below it. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* The content grows to absorb spare height, pushing the footer to the bottom. */
main { flex: 1 0 auto; }
img, svg { max-width: 100%; height: auto; display: block; }
a { color: var(--flame-deep); text-decoration: underline; text-underline-offset: 3px; }
a:hover { color: var(--ink); }
a:focus-visible, button:focus-visible, input:focus-visible,
textarea:focus-visible, select:focus-visible {
  outline: none; box-shadow: var(--focus); border-radius: 4px;
}

h1, h2, h3, h4 {
  font-family: var(--font-body);
  line-height: 1.2;
  margin: 0 0 0.5em;
  font-weight: 700;
}
h1 { font-size: clamp(1.85rem, 4vw + 0.5rem, 2.75rem); }
h2 { font-size: clamp(1.45rem, 2.5vw + 0.5rem, 2rem); }
h3 { font-size: 1.25rem; }

p { margin: 0 0 1rem; }

/* ---------- Layout ---------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 1.25rem;
}

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: var(--paper);
  padding: 0.5rem 0.75rem; z-index: 100;
}
.skip-link:focus { left: 0.5rem; top: 0.5rem; }

/* ---------- Header / nav ---------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: var(--paper);
  border-bottom: 1px solid var(--slate-faint);
  box-shadow: var(--shadow-sm);
}
.site-header__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: 0.75rem 0;
}
.brand {
  display: inline-flex; align-items: center; gap: 0.55rem;
  text-decoration: none; color: inherit;
}
.brand__mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.4rem; height: 2.4rem;
}
.brand__mark > svg { width: 100%; height: 100%; display: block; }
.brand__wordmark {
  font-family: var(--font-body);
  font-weight: 500; font-size: 1.55rem; line-height: 1;
  letter-spacing: -0.01em; color: var(--ink);
}
.brand__wordmark em {
  font-style: italic; color: var(--flame-deep);
}
/* Legacy <img class="brand__logo"> still works if any template uses it. */
.brand__logo { height: 44px; width: auto; }

.nav-toggle {
  appearance: none; background: transparent; border: 0;
  padding: 0.5rem; cursor: pointer; color: var(--ink);
}
.nav-toggle__bar {
  display: block; width: 24px; height: 2px; background: var(--ink);
  margin: 5px 0; transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) {
  opacity: 0;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

.site-nav {
  display: none;
  flex-direction: column;
  position: absolute; left: 0; right: 0; top: 100%;
  background: var(--paper);
  border-bottom: 1px solid var(--slate-faint);
  box-shadow: var(--shadow);
  padding: 0.5rem 1.25rem 1.25rem;
}
.site-nav[data-open="true"] { display: flex; }
.site-nav a {
  display: block; padding: 0.75rem 0;
  color: var(--ink); text-decoration: none;
  font-weight: 700;
  border-bottom: 1px solid var(--slate-faint);
}
.site-nav a:last-child { border-bottom: 0; }
.site-nav a:hover, .site-nav a[aria-current="page"] { color: var(--flame-deep); }
.site-nav__signin { font-weight: 500; }
.site-nav__signup {
  border: 1px solid var(--flame);
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius);
  font-weight: 500;
}
.site-nav__signup:hover {
  background: var(--flame); color: var(--paper);
}
/* ---------- Language picker ----------
 *
 * A toggle: the active language as plain text, the others as submit buttons.
 * The header's switch outgrew this shape at five languages and became .lang
 * below; what is left here is the slide editor's content-language control,
 * which answers a different question ("which copy am I editing") on one wide
 * page, and reads better as a row while it has two or three options. */
.lang-pick {
  display: inline-flex; align-items: center; gap: 0.15rem;
  margin: 0; padding: 0.15rem;
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
}
.lang-pick__on,
.lang-pick__opt {
  font: inherit; font-size: 0.82rem; line-height: 1;
  padding: 0.3rem 0.55rem;
  border-radius: calc(var(--radius) - 2px);
}
.lang-pick__on { background: var(--flame); color: var(--ink); font-weight: 600; }
.lang-pick__opt {
  background: transparent; border: 0; color: var(--slate); cursor: pointer;
}
.lang-pick__opt:hover { background: var(--slate-faint); color: var(--ink); }
.lang-pick__opt:focus-visible { outline: 2px solid var(--flame-deep); outline-offset: 1px; }
/* The editor's copy of the control, which needs a label: on that page it sits
   near the site picker and the two would otherwise be indistinguishable while
   meaning different things. */
.lang-pick--content { background: var(--paper); }
.lang-pick__what {
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--slate); padding-left: 0.4rem;
}

.site-nav__logout { margin: 0; display: inline; }
.site-nav__logout-btn {
  background: transparent; border: 0; padding: 0;
  font: inherit; color: var(--slate); cursor: pointer;
}
.site-nav__logout-btn:hover { color: var(--flame-deep); }
.site-nav__inbox { position: relative; display: inline-flex; align-items: center; gap: 0.35rem; }
.site-nav__inbox-count {
  background: var(--flame); color: var(--paper);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.7rem; font-weight: 500;
  border-radius: 999px;
  padding: 1px 7px;
  min-width: 1.4em;
  text-align: center;
  line-height: 1.4;
}

/* ---------- Account control (header) ----------
   A top-bar button that opens a dropdown of account actions, à la GardenCAD's
   account/sync button. Mobile-first: the trigger is hidden and the menu items
   render inline within the collapsed nav; the desktop block below turns it
   into a pill button + floating popover. */
/* Language menu. Same two-mode shape as .acct: inline inside the collapsed
   nav on mobile, a pill trigger with a floating popover on desktop. It is a
   menu rather than a row of toggles because the list is heading for five, and
   a header cannot absorb a control that widens every time a catalogue lands. */
.lang { display: block; }
.lang__btn { display: none; }                 /* no trigger on mobile */
.lang__menu, .lang__menu[hidden] { display: block; }  /* always inline on mobile */
.lang__label {
  padding: 0.75rem 0 0.25rem; margin: 0;
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--slate);
}
.lang__form { margin: 0; display: block; }
.lang__item {
  display: flex; align-items: center; gap: 0.5rem;
  width: 100%; padding: 0.6rem 0;
  font: inherit; font-weight: 600; text-align: left;
  color: var(--ink); background: transparent;
  border: 0; border-bottom: 1px solid var(--slate-faint);
  cursor: pointer;
}
.lang__form:last-child .lang__item { border-bottom: 0; }
.lang__item:hover { color: var(--flame-deep); }
.lang__item[aria-current="true"] { color: var(--flame-deep); }
/* Reserved whether or not it is filled, so the names line up and the menu does
   not shift by a character's width as the choice moves. */
.lang__tick { width: 1em; flex: none; color: var(--flame-deep); }

.acct { display: block; }
.acct__btn { display: none; }                 /* no trigger on mobile */
.acct__menu, .acct__menu[hidden] { display: block; }  /* always inline on mobile */
.acct__who {
  padding: 0.75rem 0 0.5rem; margin: 0;
  color: var(--slate); font-size: 0.85rem; line-height: 1.4;
}
.acct__who strong { color: var(--ink); }
.acct__menu .acct__item {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  width: 100%; padding: 0.75rem 0;
  font: inherit; font-weight: 700; text-align: left;
  color: var(--ink); text-decoration: none;
  background: transparent; border: 0; border-bottom: 1px solid var(--slate-faint);
  cursor: pointer;
}
.acct__menu .acct__item:last-child { border-bottom: 0; }
.acct__menu .acct__item:hover { color: var(--flame-deep); }
.acct__signout { color: var(--flame-deep); }
.acct__signout-form { margin: 0; display: block; }
.acct__sep { display: none; }                 /* mobile: item borders separate */
.acct__badge {
  background: var(--flame); color: var(--paper);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.7rem; font-weight: 500;
  border-radius: 999px; padding: 1px 7px;
  min-width: 1.4em; text-align: center; line-height: 1.4;
}

/* ---------- Inbox ---------- */
.inbox-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--slate-faint);
  margin: 1rem 0 1.5rem;
}
.inbox-tab {
  padding: 0.7rem 1.1rem;
  color: var(--slate);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.inbox-tab:hover { color: var(--ink); }
.inbox-tab.is-active {
  color: var(--ink);
  border-bottom-color: var(--flame);
}
.inbox-tab__count {
  background: var(--flame); color: var(--paper);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.7rem; padding: 1px 6px; border-radius: 999px;
}

/* Triage filters over the shared inbox. */
.inbox-filters { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1.25rem; }
.chip {
  display: inline-block;
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--slate-faint);
  /* Same rounding as .btn - a filter chip is a control, and at ~34px tall the
     standard radius reads as a rectangle rather than the capsule it was. */
  border-radius: var(--radius);
  color: var(--slate);
  text-decoration: none;
  font-size: 0.88rem;
}
.chip:hover { color: var(--ink); border-color: var(--slate-soft); }
.chip.is-active { color: var(--paper); background: var(--ink); border-color: var(--ink); }

/* Standing note above a list - what the Spam tab is, and what it is not. */
.inbox-note {
  margin: 0 0 1.25rem;
  padding: 0.75rem 1rem;
  background: var(--canvas);
  border-left: 3px solid var(--slate-faint);
  border-radius: var(--radius-sm);
  color: var(--slate);
  font-size: 0.92rem;
}

/* Bulk selection bar. Always present rather than revealed on first tick, so
   the list does not jump down a row the moment you select something - and so
   "select all" is reachable before you have selected anything. */
.inbox-bulkbar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 0.75rem;
  padding: 0.6rem 0.5rem;
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
  background: var(--paper-warm);
  margin-bottom: 0.75rem;
}
.inbox-bulkbar__all {
  display: inline-flex; align-items: center; gap: 0.6rem;
  color: var(--slate); font-size: 0.92rem; cursor: pointer;
}
.inbox-bulkbar[data-has-selection="true"] {
  border-color: var(--flame);
  background: rgba(255, 136, 68, 0.06);
}
.inbox-bulkbar[data-has-selection="true"] .inbox-bulkbar__all { color: var(--ink); font-weight: 500; }
.inbox-bulkbar__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* The checkbox sits outside the row's link, not inside it: nesting a control
   in an anchor means every tick is also a navigation. */
.inbox-list--selectable .inbox-row { display: flex; align-items: stretch; }
.inbox-list--selectable .inbox-row__link { flex: 1 1 auto; min-width: 0; }
.inbox-row__check {
  flex: 0 0 auto;
  align-self: center;
  margin: 0 0.25rem 0 0.5rem;
}

/* Brand checkbox.
 *
 * `accent-color` alone was not enough: it tints the checked fill and leaves
 * the unchecked box, the border and the focus ring in the browser's own blue,
 * so a half-ticked list read as two different controls. `appearance: none`
 * hands all four to us.
 *
 * Still a real <input type=checkbox>, so the label association, the space key,
 * the indeterminate state and the accessibility tree are the browser's. The
 * usual mistake here is a <span> painted to look like one, which loses all of
 * that and then has to reimplement it badly.
 *
 * The tick is a background SVG rather than a ::before, because a pseudo-element
 * on a replaced element is not something the spec guarantees will render.
 */
.check {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 1.05rem; height: 1.05rem;
  margin: 0;
  border: 1.5px solid var(--slate-soft);
  /* Not var(--radius): that corner is asymmetric by design, and at 17px it
     reads as a rendering fault rather than as the brand shape. */
  border-radius: 3px;
  background: var(--paper) no-repeat center / 0.7rem 0.7rem;
  cursor: pointer;
  display: inline-block;
  vertical-align: middle;
  transition: background-color 90ms ease, border-color 90ms ease;
}
.check:hover { border-color: var(--ink); }
.check:focus-visible { outline: none; box-shadow: var(--focus); border-color: var(--flame-deep); }
.check:checked,
.check:indeterminate {
  background-color: var(--flame);
  border-color: var(--flame-deep);
}
/* Ink on flame, not white: white on #ff8844 is 2.1:1 and the tick disappears.
   Same reasoning as .badge--published, which is the same two colours. */
.check:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.5 3.5L13 4.5'/%3E%3C/svg%3E");
}
/* Some-but-not-all is a dash. It is a third state, not a dimmer tick, and
   showing it as either checked or unchecked lies about the selection. */
.check:indeterminate {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M4 8h8'/%3E%3C/svg%3E");
}
.check:disabled { cursor: not-allowed; opacity: 0.5; }

/* Pager and page-size control share a line; the pager keeps its own top rule,
   so this only has to align the two. */
.inbox-footer {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 1rem;
}
.inbox-footer .pager { flex: 1 1 20rem; }
.inbox-perpage {
  display: flex; align-items: center; gap: 0.4rem;
  margin-top: 1.5rem; padding-top: 1rem;
  font-size: 0.9rem;
}
.chip--sm { padding: 0.15rem 0.5rem; font-size: 0.82rem; }

.inbox-emptyspam {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem;
  margin-top: 1.5rem; padding-top: 1rem;
  border-top: 1px solid var(--slate-faint);
  font-size: 0.9rem;
}

/* ---------- Management: one account's detail page ---------- */
/* A key/value stack for a single record. `.staff-table` is the listing table -
   many records, headers across the top - and using it sideways for one
   record's fields is what made this page look unlike the rest of the site.
   Same shape as .message-detail__meta, which is the established idiom. */
.detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem 2rem;
  margin: 1rem 0 2rem;
  font-size: 0.95rem;
}
.detail-grid > div { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.detail-grid dt {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--slate);
}
.detail-grid dd { margin: 0; overflow-wrap: anywhere; }
.detail-grid dd .badge { margin-left: 0.3rem; }
/* SSO can list several links; let it run the full width rather than squeezing
   subject strings into a 14rem column. */
.detail-grid__wide { grid-column: 1 / -1; }
.detail-grid__list { list-style: none; padding: 0; margin: 0; }
.detail-grid__list li { padding: 0.1rem 0; }

/* The role list on the user detail page. Rows a viewer may change carry a
   checkbox where the others carry a tick, in the same column, so a list where
   only some roles are editable still reads as one list. */
.role-list { list-style: none; padding: 0; margin: 0 0 0.75rem; }
.role-list__item {
  display: flex; gap: 0.6rem; align-items: baseline;
  padding: 0.5rem 0;
  border-top: 1px solid var(--slate-faint);
}
.role-list__item:last-child { border-bottom: 1px solid var(--slate-faint); }
.role-list__mark {
  flex: 0 0 1rem; text-align: center; font-weight: 700; color: var(--flame-deep);
}
.role-list__item--off { color: var(--slate); }
.role-list__item--off .role-list__mark { color: var(--slate-faint); }
.role-list__item .check { flex: 0 0 1rem; margin: 0.2rem 0 0; }

/* Fieldsets carry a UA border and padding that fight the form grid. */
.fieldset-plain { border: 0; padding: 0; margin: 0; }
/* A checkbox with its label beside it, rather than the stacked label/control
   a .field normally is. */
.field.field--check { flex-direction: row; gap: 0.6rem; align-items: flex-start; }
.field.field--check .check { margin-top: 0.2rem; }

/* ---------- Personal mailbox (manage/user_detail.html) ---------- */
/* `.field.mailbox-toggle`, not `.mailbox-toggle`: this block sits above the
   `.field` rules in the file, and at equal specificity the later one wins - so
   the bare class lost `flex-direction: row` to `.field`'s column and the label
   stacked under the checkbox. */
.field.mailbox-toggle { flex-direction: row; gap: 0.6rem; align-items: flex-start; }
.field.mailbox-toggle .check { margin-top: 0.2rem; }
/* The domain rides inside the field's border so the control reads as one
   address rather than as a text box next to some grey text. */
.mailbox-address {
  display: flex; align-items: stretch;
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
  background: var(--paper);
  overflow: hidden;
  max-width: 28rem;
}
/* The wrapper carries the border, so the input inside it must not - hence the
   extra specificity over `.field input`. */
.field .mailbox-address input,
.mailbox-address input {
  flex: 1 1 auto; min-width: 0; width: auto;
  border: 0; outline: none; background: transparent;
  padding: 0.5rem 0.7rem; min-height: 40px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
}
.field .mailbox-address input:focus { border: 0; }
.mailbox-address:focus-within { box-shadow: var(--focus); border-color: var(--flame-deep); }
.mailbox-address__domain {
  display: flex; align-items: center;
  padding: 0.5rem 0.7rem;
  background: var(--canvas);
  border-left: 1px solid var(--slate-faint);
  color: var(--slate);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  white-space: nowrap;
}
.mailbox-others { list-style: none; padding: 0; margin: 0.3rem 0 0; }
.mailbox-others li { padding: 0.15rem 0; }
/* Nothing below the toggle means anything until the toggle is on, so it is not
   shown until then. Pure CSS via :has() rather than a script - the form still
   works with JavaScript off, and with it the fields simply stay visible, which
   is a degradation nobody is harmed by.

   Save stays visible either way: unticking the box is a change, and it needs
   a button to persist it. */
.mailbox-form:has(input[name="mailbox_enabled"]:not(:checked)) .mailbox-when-on {
  display: none;
}

/* ---------- Confirmation dialog (partials/confirm_dialog.html) ----------
 *
 * A native <dialog>, so the focus trap, Escape handling and inert background
 * come from the browser. Only the look is ours.
 *
 * `border: 0` and `padding: 0` are load-bearing: the UA stylesheet gives
 * <dialog> a solid border and its own padding, and without clearing them the
 * panel sits inside a second, squarer box in the wrong colour.
 */
.confirm {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: min(30rem, calc(100vw - 2rem));
  color: var(--ink);
}
.confirm::backdrop {
  /* Warmer than a neutral scrim, so the page behind reads as dimmed rather
     than as a screenshot behind glass. */
  background: rgba(26, 23, 21, 0.55);
}
.confirm__inner {
  background: var(--paper);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.confirm__title { margin: 0; font-size: 1.15rem; }
.confirm__body { margin: 0; color: var(--slate); }
.confirm__body:empty { display: none; }
.confirm__actions {
  display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 0.5rem;
  margin-top: 0.5rem;
}
/* Opens without animation for anyone who asked for that; the entrance is a
   nicety and the dialog is fully usable without it. */
@media (prefers-reduced-motion: no-preference) {
  .confirm[open] .confirm__inner {
    animation: confirm-in 120ms ease-out;
  }
  @keyframes confirm-in {
    from { opacity: 0; transform: translateY(-6px) scale(0.98); }
    to   { opacity: 1; transform: none; }
  }
}

/* ---------- Pagination (partials/pagination.html) ---------- */
.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin: 1.5rem 0 0; padding-top: 1rem;
  border-top: 1px solid var(--slate-faint);
}
.pager__link { text-decoration: none; font-weight: 500; }
.pager__link.is-disabled { color: var(--slate-faint); cursor: default; }
.pager__status { color: var(--slate); font-size: 0.9rem; }

.inbox-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.inbox-row {
  border-top: 1px solid var(--slate-faint);
}
.inbox-row:last-child { border-bottom: 1px solid var(--slate-faint); }
.inbox-row--unread { background: rgba(255, 136, 68, 0.05); }
.inbox-row__link {
  display: grid;
  grid-template-columns: minmax(140px, 1fr) minmax(200px, 2fr) minmax(0, 3fr) 60px;
  gap: 1rem;
  padding: 0.75rem 0.5rem;
  text-decoration: none;
  color: var(--ink);
  align-items: baseline;
}
/* Shared inbox (triage): one extra column for claim state. A fixed width keeps
   the badges aligned down the list so the unclaimed ones are scannable in a
   single vertical sweep - this used to be a paragraph under each row, which
   read fine but doubled the height of the list. */
/* The Spam tab uses the same fifth column for the reason badge, so a
   quarantined row lines up with a claimed one instead of wrapping the badge
   onto a line of its own. */
.inbox-list--triage .inbox-row__link,
.inbox-list--reasons .inbox-row__link {
  grid-template-columns: minmax(140px, 1fr) minmax(200px, 2fr) minmax(0, 3fr) 9rem 60px;
}
.inbox-row__owner { min-width: 0; }
.inbox-row__owner .badge {
  margin-left: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.inbox-row__link:hover { background: var(--canvas); }
.inbox-row__from {
  font-weight: 600;
  display: inline-flex; align-items: center; gap: 0.4rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inbox-row__dot {
  display: inline-block;
  width: 8px; height: 8px;
  background: var(--flame);
  border-radius: 50%;
  flex: 0 0 auto;
}
.inbox-row__subject {
  font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.inbox-row__preview {
  color: var(--slate);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 0.92rem;
}
.inbox-row--unread .inbox-row__preview,
.inbox-row--unread .inbox-row__subject { color: var(--ink); }
.inbox-row__when {
  color: var(--slate);
  font-size: 0.85rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 720px) {
  /* Named areas here, so the claim badge can share the preview's line instead
     of claiming one of its own - the stacked row stays three lines tall
     whether or not this is a shared inbox. */
  .inbox-row__link,
  .inbox-list--triage .inbox-row__link,
  .inbox-list--reasons .inbox-row__link {
    grid-template-columns: 1fr auto;
    grid-template-areas: "from when" "subject subject" "preview owner";
  }
  .inbox-row__from { grid-area: from; }
  .inbox-row__when { grid-area: when; }
  .inbox-row__subject { grid-area: subject; }
  .inbox-row__preview { grid-area: preview; }
  .inbox-row__owner { grid-area: owner; justify-self: end; }
  /* Cap it at the desktop column width so a long name ellipsises instead of
     squeezing the preview down to a couple of words. */
  .inbox-row__owner .badge { max-width: 9rem; }
}
.empty-state {
  padding: 2rem;
  text-align: center;
  border: 1px dashed var(--slate-faint);
  border-radius: var(--radius);
}

.message-detail {
  background: var(--canvas);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
  padding: 1.4rem 1.6rem;
}
.message-detail__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 1.2rem 2rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--slate-faint);
  margin-bottom: 1rem;
  font-size: 0.95rem;
}
.message-detail__meta > div { display: flex; flex-direction: column; gap: 0.2rem; }
/* From's value is compound (name + <email>); keep it on one value line under
   the label, wrapping a long address rather than splitting name off the bracket. */
.message-detail__fromval { overflow-wrap: anywhere; }
.message-detail__body {
  font-family: var(--font-body);
  font-size: 1.05rem;
  line-height: 1.6;
}
.message-detail__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.4rem;
  padding-top: 1rem;
  border-top: 1px solid var(--slate-faint);
}
/* Spam and Delete sit at the far end, separated from Reply / Claim / Back.
   They are the two actions on this page that cannot be undone, and they should
   not be adjacent to the button somebody clicks to mark a message read. */
.message-detail__danger {
  display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.6rem;
  margin-left: auto;
}

/* Replies already sent, shown under the message so the thread reads in order.
   Indented and flipped to the paper tone so an answer is never mistaken for
   part of what the sender wrote. */
.reply-sent {
  margin-top: 1.2rem;
  padding: 0.9rem 1.1rem;
  background: var(--paper);
  border-left: 3px solid var(--flame);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.reply-sent--failed { border-left-color: #d23b3b; }
.reply-sent__meta {
  margin: 0 0 0.5rem;
  font-size: 0.9rem;
  color: var(--slate);
}
.reply-sent__body { font-size: 1rem; line-height: 1.55; }
.reply-sent__body p:last-child { margin-bottom: 0; }

/* The composer is a <details> so it opens with no JavaScript; the summary is
   styled as the button that replaced "Reply via email". */
.reply-composer { margin-top: 1.4rem; }
.reply-composer__toggle {
  display: inline-block;
  cursor: pointer;
  list-style: none;
}
/* Safari still needs the vendor pseudo-element to drop the disclosure marker,
   which would otherwise sit inside the button next to the label. */
.reply-composer__toggle::-webkit-details-marker { display: none; }
/* Flex column rather than margins on each row: the hidden CSRF input and the
   <datalist> are display:none, so they never become gapped flex items. */
.reply-composer__form {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-top: 1rem;
  padding: 1.1rem 1.2rem;
  background: var(--paper);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
}
.reply-composer__to {
  margin: 0;
  font-size: 0.95rem;
  overflow-wrap: anywhere;
}
.reply-composer__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
}
.reply-composer__actions .muted { font-size: 0.85rem; }

@media (min-width: 800px) {
  .nav-toggle { display: none; }
  .site-nav {
    display: flex; flex-direction: row; position: static;
    align-items: center;
    padding: 0; border: 0; box-shadow: none; gap: 0.9rem;
  }
  /* A nav label is a phrase, not prose: "Acerca de" broken across two lines
     reads as two items and shoves the row taller. Spanish is around a fifth
     longer than the English these widths were set against, so this is the
     rule that keeps the row one line deep in both. */
  .site-nav a { white-space: nowrap; }
  .site-nav a {
    padding: 0.25rem 0; border-bottom: 2px solid transparent;
  }
  .site-nav a[aria-current="page"] {
    border-bottom-color: var(--flame);
  }
  /* Auth actions are buttons, not plain links. The `.site-nav a.<class>`
     specificity (0,2,1) beats `.site-nav a` (0,1,1), so the link padding /
     hover-underline above don't leak in and break the pills. */
  .site-nav a.site-nav__signin,
  .site-nav a.site-nav__signup {
    padding: 0.45rem 0.85rem;
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-weight: 600;
    line-height: 1;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  }
  .site-nav a.site-nav__signin {
    border-color: var(--flame);
    color: var(--flame-deep);
  }
  .site-nav a.site-nav__signin:hover {
    background: var(--flame); border-color: var(--flame); color: var(--paper);
  }
  .site-nav a.site-nav__signup {
    background: var(--flame); border-color: var(--flame); color: var(--paper);
  }
  .site-nav a.site-nav__signup:hover {
    background: var(--flame-deep); border-color: var(--flame-deep); color: var(--paper);
  }

  /* Language menu on desktop: the same pill + popover as the account control,
     deliberately - two header dropdowns that behaved differently would be two
     things to learn. Quieter than the account pill: no border until hover,
     because most visitors set this once and it should not compete with
     Sign in. */
  .lang { position: relative; }
  .lang__btn {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.3rem 0.5rem;
    border: 1px solid transparent; border-radius: var(--radius);
    background: transparent; color: var(--slate); cursor: pointer;
    font: inherit; font-size: 0.85rem; font-weight: 600; line-height: 1;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  }
  .lang__btn:hover,
  .lang__btn[aria-expanded="true"] {
    border-color: var(--slate-faint); background: var(--paper); color: var(--ink);
  }
  .lang__globe { flex: none; }
  .lang__caret { color: var(--slate); transition: transform 0.15s ease; }
  .lang__btn[aria-expanded="true"] .lang__caret { transform: rotate(180deg); }

  .lang__menu {
    position: absolute; right: 0; top: calc(100% + 10px);
    min-width: 200px; padding: 0.4rem; z-index: 60;
    background: var(--paper);
    border: 1px solid var(--slate-faint); border-radius: 10px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.14);
  }
  .lang__menu[hidden] { display: none; }
  .lang__menu:not([hidden]) { display: block; }
  .lang__label {
    padding: 0.3rem 0.6rem 0.4rem; margin-bottom: 0.25rem;
    border-bottom: 1px solid var(--slate-faint);
  }
  .lang__item {
    padding: 0.45rem 0.6rem; border-bottom: 0; border-radius: 7px;
    font-weight: 500; font-size: 0.92rem;
  }
  .lang__item:hover { background: var(--paper-warm); color: var(--flame-deep); }

  /* Account control on desktop: pill trigger + floating popover. */
  .acct { position: relative; }
  .acct__btn {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0.3rem 0.55rem 0.3rem 0.35rem;
    border: 1px solid var(--slate-faint); border-radius: var(--radius);
    background: var(--paper); color: var(--ink); cursor: pointer;
    font: inherit; font-weight: 600; line-height: 1;
    transition: background 0.15s ease, border-color 0.15s ease;
  }
  .acct__btn:hover,
  .acct__btn[aria-expanded="true"] {
    border-color: var(--flame); background: var(--paper-warm);
  }
  .acct__avatar {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.7rem; height: 1.7rem; flex: none;
    /* A circle with a squared lower-right corner - mirrors the Q-mark logo. */
    border-radius: 50% 50% 0 50%; background: var(--flame); color: var(--paper);
    font-size: 0.8rem; font-weight: 700;
  }
  .acct__name { max-width: 13ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .acct__caret { color: var(--slate); transition: transform 0.15s ease; }
  .acct__btn[aria-expanded="true"] .acct__caret { transform: rotate(180deg); }

  .acct__menu {
    position: absolute; right: 0; top: calc(100% + 10px);
    min-width: 240px; padding: 0.4rem; z-index: 60;
    background: var(--paper);
    border: 1px solid var(--slate-faint); border-radius: 10px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.14);
  }
  .acct__menu[hidden] { display: none; }
  .acct__menu:not([hidden]) { display: block; }
  .acct__who {
    padding: 0.4rem 0.6rem 0.5rem; margin-bottom: 0.3rem;
    border-bottom: 1px solid var(--slate-faint);
  }
  .acct__menu .acct__item {
    padding: 0.5rem 0.6rem; border-bottom: 0; border-radius: 7px;
    font-weight: 500; font-size: 0.92rem;
  }
  .acct__menu .acct__item:hover { background: var(--paper-warm); color: var(--flame-deep); }
  .acct__sep { display: block; height: 1px; background: var(--slate-faint); margin: 0.3rem 0.25rem; }
}

/* ---------- Hero ---------- */
.hero {
  padding: 3rem 0 2.5rem;
  border-bottom: 1px solid var(--slate-faint);
}
.hero__eyebrow {
  text-transform: uppercase; letter-spacing: 0.1em;
  font-size: 0.78rem; color: var(--flame-deep); font-weight: 700;
  margin-bottom: 0.5rem;
}
.hero__lede { font-size: 1.15rem; color: var(--slate); max-width: 40rem; }
.hero__actions { margin-top: 1.5rem; display: flex; flex-wrap: wrap; gap: 0.75rem; }

/* ---------- Buttons ---------- */
.btn {
  --bg: var(--flame); --fg: var(--ink); --bd: var(--flame);
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.4rem;
  padding: 0.7rem 1.15rem;
  background: var(--bg); color: var(--fg);
  border: 2px solid var(--bd); border-radius: var(--radius);
  font: inherit; font-weight: 700; text-decoration: none;
  cursor: pointer; min-height: 44px; /* touch target */
  transition: transform 0.05s ease, background 0.2s ease, color 0.2s ease;
}
.btn:hover { background: var(--flame-deep); border-color: var(--flame-deep); color: var(--paper); }
.btn:active { transform: translateY(1px); }
/* A disabled button has to LOOK disabled. There was no rule for it, so the
   course page's "Rebuild all videos" sat there in full contrast with its hover
   intact while refusing every press - the worst of the three possible states,
   because the only feedback was nothing happening. Greyed, flat, and the hover
   and press animations off, so the reason in the title attribute is the second
   thing you look for rather than the first. */
.btn:disabled,
.btn[aria-disabled="true"] {
  --bg: var(--slate-faint); --fg: var(--slate); --bd: var(--slate-faint);
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled:hover,
.btn[aria-disabled="true"]:hover {
  background: var(--slate-faint); border-color: var(--slate-faint); color: var(--slate);
}
.btn:disabled:active { transform: none; }
.btn--ghost {
  --bg: transparent; --fg: var(--ink); --bd: var(--ink);
}
.btn--ghost:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
/* Destructive. Outlined rather than filled: a solid red button in a toolbar
   above a list of checkboxes is the one you hit by accident, and the fill is
   held back for the hover, when the pointer is already committed. */
.btn--danger {
  --bg: transparent; --fg: #b3261e; --bd: #b3261e;
}
.btn--danger:hover,
.btn--danger:focus-visible {
  background: #b3261e; border-color: #b3261e; color: var(--paper);
}
.btn--block { width: 100%; }
.btn--sm { padding: 0.45rem 0.8rem; min-height: 36px; font-size: 0.95rem; }
/* The size and running time ride inside a download button rather than beside
   it: they are what you decide on before clicking - 412 MB over a hotel wifi
   is a different proposition from 41 MB - and beside it they were a caption
   nobody read. Lighter than the label so the button still reads as a verb. */
.btn__meta { font-weight: 400; font-size: 0.85em; opacity: 0.75; white-space: nowrap; }
/* Never let the glyph shrink when the label wraps. */
.icon-dl { flex: none; }

/* ---------- Sections ---------- */
section { padding: 1.5rem 0; }
.section-title { font-size: 1.4rem; margin-bottom: 1rem; }
.section-title small {
  display: block; text-transform: uppercase; letter-spacing: 0.1em;
  font-size: 0.78rem; color: var(--flame-deep); font-weight: 700;
  margin-bottom: 0.25rem;
}

/* ---------- Card grid ---------- */
.cards {
  display: grid; gap: 1.25rem;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .cards { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 960px) {
  .cards--3 { grid-template-columns: repeat(3, 1fr); }
  .cards--2 { grid-template-columns: repeat(2, 1fr); }
}
.card {
  background: var(--paper);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius-lg);
  padding: 1.5rem;
  box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; gap: 0.75rem;
}
.card__title { margin: 0; font-size: 1.15rem; }
.card__meta { color: var(--slate); font-size: 0.92rem; }
/* Direct children only. This used to be `.card a.btn`, which is what made the
   CTA row wander: inside `.card__actions` - a row flex container - `margin-top:
   auto` aligns the button to the bottom of its own line rather than pushing
   the row down the card, so every card put its button wherever its summary
   happened to end. The row itself is what has to be pushed; see
   `.card__actions` below. */
.card > a.btn { margin-top: auto; }

/* ---------- Project theming (class pages) ----------
 *
 * A class belongs to an EdQuest project, and `data-project` on the page's own
 * content puts that project's colours on it. The slug is CourseTemplate.project
 * (apps/core/projects.py); the same string keys the deck themes in deck.css.
 *
 * Scoped to the content, never to <body>: the site header and footer stay
 * EdQuest's on every page. A green nav bar on a GardenCAD course would say the
 * whole site had changed rather than this one class.
 *
 * The palettes are each project's real product colours. They differ from the
 * .project-card--* set below, which is the marketing grid: ResQ-R shows grey
 * there on purpose, because it sits beside WHFD's near-identical red. A course
 * page is never beside another project's course page, so it wears the red.
 */
[data-project] {
  --proj-accent: var(--flame);
  --proj-accent-deep: var(--flame-deep);
  --proj-tile-bg: #fff1e6;
  --proj-tile-border: #ffd9bd;
  --proj-wash: var(--canvas);
}
[data-project="gardencad"] {
  --proj-accent: #4a8a3e;
  --proj-accent-deep: #2f6429;
  --proj-tile-bg: #e2efdd;
  --proj-tile-border: #c6dfbd;
  --proj-wash: #f4f8f2;
}
[data-project="resqr"] {
  --proj-accent: #d11f27;
  --proj-accent-deep: #9a0b22;
  --proj-tile-bg: #fcebed;
  --proj-tile-border: #f3c1bb;
  --proj-wash: #fdf6f4;
}
[data-project="fireprevented"] {
  --proj-accent: var(--flame);
  /* Darker than the deck's #c2521a. This one also colours the badge text, and
     #c2521a on the #fff1e6 tile is 4.18:1 - under AA for 10px uppercase.
     #a8460f takes it to 5.3:1 and is still recognisably the flame. */
  --proj-accent-deep: #a8460f;
  --proj-tile-bg: #fff1e6;
  --proj-tile-border: #ffd9bd;
  --proj-wash: #fdf7f2;
}
[data-project="whfd"] {
  --proj-accent: #c8102e;
  --proj-accent-deep: #8f0a20;
  --proj-tile-bg: #fcebed;
  --proj-tile-border: #f3c1bb;
  --proj-wash: #f7f8f9;
}

/* What the theme actually recolours. Deliberately a short list - the hero
   wash, the accent rules and the primary button. Recolouring everything makes
   a page that looks like another site; recolouring these three makes an
   EdQuest page that is visibly a GardenCAD one. */
.course-hero[data-project] { background: var(--proj-wash); }
.course-hero[data-project] .progress-bar__fill,
.course-hero[data-project] .lesson-progress-row .bar > div { background: var(--proj-accent); }
.course-hero[data-project] .course-price.free {
  background: var(--proj-accent);
  border-color: var(--proj-accent);
  color: #fff;
}
/* Two scopes, one rule: the course page's hero, and the lesson player's nav
   under the deck. The Next button is the control a student uses most, and an
   orange one under a green deck reads as two products on one page. */
.course-hero[data-project] .btn:not(.btn--ghost):not(.btn--danger),
.lesson-nav[data-project] .btn:not(.btn--ghost):not(.btn--danger) {
  --bg: var(--proj-accent);
  --bd: var(--proj-accent);
  --fg: #fff;
}
.course-hero[data-project] .btn:not(.btn--ghost):not(.btn--danger):hover,
.course-hero[data-project] .btn:not(.btn--ghost):not(.btn--danger):focus-visible,
.lesson-nav[data-project] .btn:not(.btn--ghost):not(.btn--danger):hover,
.lesson-nav[data-project] .btn:not(.btn--ghost):not(.btn--danger):focus-visible {
  background: var(--proj-accent-deep);
  border-color: var(--proj-accent-deep);
  color: #fff;
}
/* The ghost (Previous) button keeps the neutral border but takes the accent
   for its label, so the pair reads as one set rather than one themed control
   beside one that was forgotten. */
.lesson-nav[data-project] .btn--ghost {
  color: var(--proj-accent-deep);
  border-color: var(--proj-accent);
}
.lesson-nav[data-project] .btn--ghost:hover,
.lesson-nav[data-project] .btn--ghost:focus-visible {
  background: var(--proj-wash);
  color: var(--proj-accent-deep);
}
/* EdQuest keeps the flame button exactly as it was: dark label on orange, not
   white, because white on #ff8844 is 2.1:1 and unreadable. */
.course-hero[data-project="edquest"] .btn:not(.btn--ghost):not(.btn--danger),
.course-hero[data-project="fireprevented"] .btn:not(.btn--ghost):not(.btn--danger),
.lesson-nav[data-project="edquest"] .btn:not(.btn--ghost):not(.btn--danger),
.lesson-nav[data-project="fireprevented"] .btn:not(.btn--ghost):not(.btn--danger) {
  --fg: var(--ink);
}

/* The title and its badge share a line, and the badge drops below on a narrow
   screen rather than squeezing a long course title. */
.course-title-row {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: 0.5rem 1rem;
}
.course-title-row h1 { margin: 0; }

/* The badge itself. Outlined rather than filled: it appears next to a title on
   a wash of the same hue, and a solid chip there reads as a button. */
.project-badge {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--proj-tile-border);
  background: var(--proj-tile-bg);
  border-radius: var(--radius-sm);
  color: var(--proj-accent-deep);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.project-badge__mark { height: 1.05em; width: auto; display: block; }
.project-badge--sm { padding: 0.15rem 0.45rem; font-size: 0.66rem; }

/* The badge leads the card's subtitle, directly under the title it qualifies,
   so the card reads "which class, which project, how long". A flex row rather
   than an inline run: the session card's subtitle is a date and a location on
   two lines, and inline the badge would sit inside that wrap. */
.card__meta--badged { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
/* Hovering the title of a project's class picks up that project's accent. */
.card[data-project] .card__title a:hover { color: var(--proj-accent-deep); }

/* ---------- Project showcase cards ---------- */
.project-card {
  /* Per-project accent; the modifiers below recolor the tile, bullets + CTA. */
  --proj-accent: #4a8a3e;             /* GardenCAD green (default) */
  --proj-accent-deep: #3c7233;        /* hover/active */
  --proj-tile-bg: #eef4ea;
  --proj-tile-border: #d8e6d0;
  --proj-btn-fg: #fff;                /* CTA label color */
}
.project-card--fire {
  --proj-accent: var(--flame);        /* FirePrevented orange */
  --proj-accent-deep: var(--flame-deep);
  --proj-tile-bg: #fff1e6;
  --proj-tile-border: #ffd9bd;
  --proj-btn-fg: var(--ink);          /* dark label reads better on light orange */
}
.project-card--whfd {
  --proj-accent: #c8102e;             /* Woodbury Heights red */
  --proj-accent-deep: #a50d26;
  --proj-tile-bg: #fcebed;
  --proj-tile-border: #f3c1bb;
}
.project-card--resqr {
  --proj-accent: #6b7280;             /* ResQ-R grey (green is GardenCAD's) */
  --proj-accent-deep: #565c66;
  --proj-tile-bg: #f1f2f4;
  --proj-tile-border: #dcdfe3;
}
.project-card .hero__actions { margin-top: auto; }  /* line CTAs up across cards */

/* Each project's CTA button wears that project's accent color. */
.project-card .btn {
  --bg: var(--proj-accent);
  --bd: var(--proj-accent);
  --fg: var(--proj-btn-fg);
}
.project-card .btn:hover,
.project-card .btn:focus-visible {
  background: var(--proj-accent-deep);
  border-color: var(--proj-accent-deep);
  color: #fff;
}
.project-card__head {
  display: flex; align-items: center; gap: 1rem;
}
.project-card__logo {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 72px; height: 72px;
  background: var(--proj-tile-bg);     /* soft tile carrying the mark */
  border: 1px solid var(--proj-tile-border);
  border-radius: var(--radius);        /* squared bottom-right, brand corner */
}
.project-card__logo img { width: 40px; height: auto; display: block; }
.project-card__heading {
  display: flex; flex-direction: column; gap: 0.1rem; min-width: 0;
}
.project-card__tagline { margin: 0; color: var(--slate); font-size: 0.98rem; }
.project-card__by {
  margin: 0.15rem 0 0; font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em; color: var(--slate-soft);
}
.project-card__features {
  list-style: none; margin: 0.1rem 0 0; padding: 0;
  display: grid; gap: 0.45rem;
}
.project-card__features li {
  position: relative; padding-left: 1.6rem; color: var(--slate);
}
.project-card__features li::before {
  content: ""; position: absolute; left: 0.15rem; top: 0.4em;
  width: 0.7rem; height: 0.7rem;
  background: var(--proj-accent);      /* per-project accent */
  border-radius: 50% 50% 0 50%;        /* mirrors the avatar / logo corner */
}

/* ---------- Forms ---------- */
.form {
  display: grid; gap: 1rem;
  max-width: 32rem;
}
.field { display: flex; flex-direction: column; gap: 0.35rem; }
.field label { font-weight: 700; font-size: 0.95rem; }
/* font-weight, because a checkbox's help sits *inside* its <label> - which is
   700 - so without this the advice under a tickbox reads heavier than the
   advice under every other field. */
.field .help { color: var(--slate); font-size: 0.85rem; font-weight: 400; }
/* Text-shaped inputs only.
 *
 * This was `.field input`, which also caught checkboxes and radios - and
 * width:100% plus min-height:44px turned every one of them into a 327x44
 * rectangle. The roles list on /manage/users/<pk>/ had been rendering as three
 * giant squares; it looked deliberate enough that nobody questioned it.
 *
 * Excluded rather than fixed per-control, because the next checkbox somebody
 * puts in a .field would have hit exactly the same thing.
 */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select,
.field textarea {
  font: inherit; line-height: 1.4;
  color: var(--ink); background: var(--paper);
  border: 2px solid var(--slate-faint); border-radius: var(--radius);
  padding: 0.65rem 0.8rem; min-height: 44px; width: 100%;
}
/* A bare checkbox in a .field, for the forms that do not use `.check`. */
.field input[type="checkbox"],
.field input[type="radio"] {
  flex: 0 0 auto;
  width: 1.05rem; height: 1.05rem;
  accent-color: var(--flame);
}
/* Native <select> ships with its own chrome and line-height, which
   makes it taller than a paired <input> at the same padding. Strip
   that and supply a custom arrow so it matches input height pixel-
   for-pixel. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23666' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.8rem center;
  background-size: 12px 8px;
  padding-right: 2.4rem;
}
.field textarea { min-height: 8rem; }
.field input:not([type="checkbox"]):not([type="radio"]):focus,
.field select:focus, .field textarea:focus { border-color: var(--flame); }
.field--error input:not([type="checkbox"]):not([type="radio"]),
.field--error select, .field--error textarea { border-color: #d23b3b; }
/* The reason a save was refused, distinct from the advice above it. Both were
   .help, so an error read as a suggestion and only the border said otherwise. */
.field .field-error { display: block; color: #b02a2a; font-size: 0.85rem; font-weight: 600; }

/* ---------- Auth pages (allauth: login, signup, password reset, …) ----------
   These pages render through templates/allauth/layouts/base.html into the
   .auth card. allauth's form fields are <p><label><input></p> and its buttons
   are class-less <button>, so we style those element shapes here (plus the
   custom login.html, which uses the same shapes). */
.auth { padding: 3rem 0; background: var(--canvas); }
.auth__card {
  max-width: 26rem; margin: 0 auto;
  background: var(--paper);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 2rem;
}
.auth__card h1 { font-size: 1.6rem; line-height: 1.15; margin: 0 0 1.25rem; }
.auth__card > p,
.auth__card form p { margin: 0 0 1rem; }
.auth__card label { display: block; font-weight: 700; font-size: 0.95rem; margin-bottom: 0.3rem; }
.auth__card input[type="text"],
.auth__card input[type="email"],
.auth__card input[type="password"],
.auth__card input[type="url"],
.auth__card input[type="number"],
.auth__card input:not([type]) {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
  background: var(--paper);
  font: inherit; color: var(--ink);
}
.auth__card input:focus { border-color: var(--flame); outline: none; }
.auth__card input[type="checkbox"],
.auth__card input[type="radio"] { margin-right: 0.45rem; }
.auth__card input[type="checkbox"] + label,
.auth__card input[type="radio"] + label { display: inline; font-weight: 500; margin-bottom: 0; }
/* allauth renders class-less <button>s; make them the primary action. The
   custom login.html uses explicit .btn classes, so exclude classed buttons. */
.auth__card button:not([class]) {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; margin-top: 0.25rem;
  padding: 0.65rem 1rem;
  background: var(--flame); color: var(--paper);
  border: 1px solid var(--flame); border-radius: var(--radius);
  font: inherit; font-weight: 700; cursor: pointer;
}
.auth__card button:not([class]):hover { background: var(--flame-deep); border-color: var(--flame-deep); }
.auth__card a { color: var(--flame-deep); }
.auth__card hr { border: 0; border-top: 1px solid var(--slate-faint); margin: 1.25rem 0; }
/* Consent checkboxes on the signup + consent pages. Tighter than a normal form
   row so the age box and the terms box read as one group, and hanging-indented
   so a wrapped label lines up under itself rather than under the box. */
/* Specificity, not !important: .auth__card form p sets the 1rem row gap above. */
.auth__card form p.auth__check {
  display: grid; grid-template-columns: auto 1fr;
  gap: 0 0.15rem; align-items: start;
  margin-bottom: 0.6rem;
}
.auth__check input[type="checkbox"] { margin-top: 0.35rem; }
.auth__check .auth__field-error { grid-column: 2; }

.auth__divider {
  display: flex; align-items: center; gap: 0.75rem;
  margin: 1rem 0; color: var(--slate); font-size: 0.85rem;
}
.auth__divider::before, .auth__divider::after {
  content: ""; flex: 1; height: 1px; background: var(--slate-faint);
}
.auth__errors {
  list-style: none; margin: 0 0 1rem; padding: 0.6rem 0.8rem;
  background: #fdecec; border: 1px solid #f5c2c2; border-radius: var(--radius);
  color: #a12b2b; font-size: 0.9rem;
}
.auth__field-error { display: block; margin-top: 0.3rem; color: #d23b3b; font-size: 0.85rem; }
.auth__help { display: block; margin-top: 0.3rem; color: var(--slate); font-size: 0.85rem; }
.auth__alt { margin: 1.25rem 0 0; font-size: 0.9rem; color: var(--slate); text-align: center; }
.auth__alt span { margin: 0 0.4rem; color: var(--slate-faint); }
.field__error { color: #d23b3b; font-size: 0.85rem; }

/* ---------- Messages ---------- */
.messages { list-style: none; margin: 1.25rem 0; padding: 0; display: grid; gap: 0.5rem; }
.message {
  padding: 0.75rem 1rem; border-radius: var(--radius);
  background: var(--paper-warm); border-left: 4px solid var(--slate);
}
.message--success { border-left-color: #2c8a2c; }
.message--warning { border-left-color: var(--flame); }
.message--error { border-left-color: #d23b3b; }

/* ---------- Footer ---------- */
.site-footer {
  background: var(--ink); color: var(--paper);
  padding: 2.5rem 0 1.5rem;
  margin-top: 3rem;
}
.site-footer a {
  color: var(--flame);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 120ms, border-color 120ms;
}
.site-footer a:hover,
.site-footer a:focus-visible {
  color: var(--paper);
  border-bottom-color: var(--flame);
}
.site-footer__grid {
  display: grid; gap: 2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 720px) {
  .site-footer__grid { grid-template-columns: 1.5fr 1fr 1fr; }
}
.site-footer__col h3 { color: var(--paper); font-size: 1rem; margin-bottom: 0.75rem; }
.site-footer__col ul { list-style: none; padding: 0; margin: 0; }
.site-footer__col li { margin-bottom: 0.35rem; }
.site-footer__fine {
  margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #333;
  color: #aaa; font-size: 0.85rem; display: flex; flex-wrap: wrap;
  gap: 1rem; justify-content: space-between;
}
.site-footer__version {
  color: #888;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.78rem;
}
.site-footer__version a { color: #aaa; }
.site-footer__legal { display: flex; gap: 1rem; }
.site-footer__legal a { color: #aaa; }

/* ---------- Newsletter (footer) ---------- */
.newsletter-form { display: grid; gap: 0.5rem; }
.newsletter-form input[type="email"] {
  background: var(--paper); color: var(--ink); border: 0;
  border-radius: var(--radius); padding: 0.65rem 0.8rem; min-height: 44px;
}

/* ---------- Utility ---------- */
.muted { color: var(--slate); }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; }
.mt-4 { margin-top: 2rem; }
.text-center { text-align: center; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---------- Classes catalog + detail ---------- */
.catalog-filters {
  display: grid; gap: 1rem;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .catalog-filters { grid-template-columns: 2fr 1fr 1fr; align-items: end; }
}

.session-grid {
  display: grid; gap: 2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 880px) {
  .session-grid { grid-template-columns: 2fr 1fr; align-items: start; }
  .session-grid__side { position: sticky; top: 90px; }
}

/* ---------- HTMX hooks ---------- */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline-block; }
.htmx-request.htmx-indicator { display: inline-block; }

/* ---------- Materials + progress + prereqs ---------- */
.material-list { list-style: none; padding: 0; margin: 0.5rem 0 0 0; }
.material-list li {
  padding: 0.5rem 0;
  border-top: 1px solid var(--slate-faint);
  display: flex; flex-wrap: wrap; gap: 0.4rem 0.6rem; align-items: baseline;
}
.material-list li:first-child { border-top: none; }
.material-list li > p { flex-basis: 100%; margin: 0; font-size: 0.9rem; }

.card__materials { margin: 0.5rem 0; }
.card__materials summary {
  cursor: pointer; color: var(--flame-deep);
  padding: 0.25rem 0; min-height: 36px; display: inline-flex; align-items: center;
}
.card__materials summary:focus-visible {
  outline: none; box-shadow: var(--focus); border-radius: 4px;
}

.progress {
  background: var(--slate-faint);
  border-radius: 999px;
  height: 8px;
  overflow: hidden;
  margin: 0.5rem 0;
}
.progress__bar {
  background: var(--flame);
  height: 100%;
  transition: width 220ms ease-out;
  min-width: 0;
}
.card--progress .card__meta { margin-top: 0.25rem; }

.badge {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-sm);
  background: var(--slate-faint);
  color: var(--slate);
  vertical-align: middle;
  margin-left: 0.4rem;
  line-height: 1.4;
}
.badge--complete { background: var(--flame); color: var(--paper); }

.prereqs { padding: 0; list-style: none; margin: 0.5rem 0; }
.prereqs li { padding: 0.3rem 0; line-height: 1.45; }
.prereqs .check {
  color: #2a8a3e; font-weight: 700; margin-right: 0.35rem;
  display: inline-block; width: 1.1em; text-align: center;
}
.prereqs .cross {
  color: #c0392b; font-weight: 700; margin-right: 0.35rem;
  display: inline-block; width: 1.1em; text-align: center;
}

/* Certificate verification */
.card--verify { max-width: 540px; margin: 0 auto; }
.cert-detail {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.15rem 1rem;
  margin: 1rem 0;
}
.cert-detail dt {
  font-weight: 700; color: var(--slate);
  font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;
  margin-top: 0.5rem;
}
.cert-detail dt:first-child { margin-top: 0; }
.cert-detail dd { margin: 0; word-break: break-word; }
@media (min-width: 520px) {
  .cert-detail { grid-template-columns: max-content 1fr; align-items: baseline; }
  .cert-detail dt { margin-top: 0; }
}

/* ---------- Instructor roster ---------- */
/* Mobile-first: horizontal scroll wrapper for the table so 4 columns
   never break the page width. On wide screens the scroll never kicks in. */
.roster-wrap {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 -1.25rem; /* extend to the container edge for thumb-scrolling */
  padding: 0 1.25rem;
}
.roster {
  width: 100%; border-collapse: collapse;
  min-width: 36rem; /* keeps columns from collapsing into illegibility */
}
.roster th, .roster td {
  padding: 0.75rem 0.6rem; text-align: left;
  border-bottom: 1px solid var(--slate-faint);
  vertical-align: top;
}
.roster th {
  background: var(--paper-warm); font-weight: 700;
  font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--slate);
}
.roster td small { display: block; color: var(--slate); }
.roster select {
  font: inherit; padding: 0.4rem 0.5rem; min-height: 36px;
  border: 2px solid var(--slate-faint); border-radius: var(--radius);
  background: var(--paper);
}
.roster__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* ---------- Inline video/audio player ---------- */
.player {
  background: var(--ink); border-radius: var(--radius-lg);
  overflow: hidden; max-width: 920px; margin: 0 auto;
}
.player video {
  width: 100%; height: auto; display: block;
  max-height: 70vh; background: var(--ink);
}
.player audio { width: 100%; display: block; padding: 1rem; background: var(--paper-warm); }

/* ---------- Cancel-session disclosure ---------- */
.hero__danger { margin-top: 1rem; }
.hero__danger > summary {
  display: inline-flex; align-items: center; gap: 0.4rem;
  cursor: pointer; color: #c0392b; font-weight: 700;
  padding: 0.4rem 0.6rem; min-height: 36px;
  border: 1px solid currentColor; border-radius: var(--radius);
  list-style: none;
}
.hero__danger > summary::-webkit-details-marker { display: none; }
.hero__danger[open] > summary { background: #fdf3f1; }

/* ---------- Generic narrow form ---------- */
.form-grid { display: grid; gap: 1rem; max-width: 36rem; }
.form-grid label {
  display: flex; flex-direction: column; gap: 0.35rem;
  font-weight: 700; font-size: 0.95rem;
}
.form-grid input[type="text"],
.form-grid input[type="email"],
.form-grid input[type="url"],
.form-grid input[type="number"],
.form-grid input[type="file"],
.form-grid textarea,
.form-grid select {
  font: inherit; color: var(--ink); background: var(--paper);
  padding: 0.65rem 0.8rem; min-height: 44px;
  border: 2px solid var(--slate-faint); border-radius: var(--radius);
}
.form-grid textarea { min-height: 6rem; resize: vertical; }
.form-grid input:focus, .form-grid select:focus, .form-grid textarea:focus {
  border-color: var(--flame); outline: none;
}
.form-grid input[type="file"] { padding: 0.45rem 0.5rem; }
.form-grid .check-row {
  flex-direction: row; align-items: center; gap: 0.6rem;
  min-height: 44px; font-weight: 400;
}
.form-grid .check-row input { margin: 0; width: 20px; height: 20px; flex: 0 0 auto; }
.form-grid > div { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* ---------- Staff tables ---------- */
.staff-table {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0 2rem;
  font-size: 0.95rem;
}
.staff-table th, .staff-table td {
  text-align: left;
  padding: 0.55rem 0.7rem;
  border-bottom: 1px solid var(--slate-faint);
  vertical-align: middle;
}
.staff-table th {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--slate);
  border-bottom: 2px solid var(--ink);
}
.staff-table tr:hover { background: var(--canvas); }
.staff-table code {
  background: var(--canvas);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 0.85rem;
}

/* ---------- StreamField blocks ---------- */

.block-heading { margin: 2rem 0 1rem; }
.block-heading__eyebrow {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--flame-deep);
  margin: 0 0 0.4rem;
}
.block-heading__text { margin: 0; line-height: 1.15; }
.block-heading--h2 .block-heading__text { font-size: 1.8rem; }
.block-heading--h3 .block-heading__text { font-size: 1.3rem; }

.block-callout {
  padding: 1rem 1.2rem;
  border-radius: var(--radius);
  border-left: 4px solid var(--slate);
  background: var(--canvas);
  margin: 1.4rem 0;
}
.block-callout__title { margin: 0 0 0.4rem; font-size: 1.05rem; }
.block-callout__body p:last-child { margin-bottom: 0; }
.block-callout--info    { border-left-color: var(--slate); }
.block-callout--flame   { border-left-color: var(--flame); background: rgba(255, 136, 68, 0.08); }
.block-callout--warning { border-left-color: #d23b3b; background: #fdecea; }
.block-callout--success { border-left-color: #2e7d32; background: #ebf6ec; }

.block-cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 1rem 0 1.6rem;
}

.block-async-course { max-width: 28rem; }

.block-event-list {
  list-style: none;
  padding: 0;
  margin: 1rem 0 1.6rem;
}
.block-event-list li {
  display: grid;
  grid-template-columns: 60px 1fr;
  gap: 1rem;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--slate-faint);
  align-items: start;
}
.block-event-list__date {
  text-align: center;
  border: 1px solid var(--slate-faint);
  padding: 0.3rem;
  border-radius: var(--radius);
  font-family: var(--font-body);
}
.block-event-list__date .month {
  display: block;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.65rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--flame-deep);
}
.block-event-list__date .day {
  font-size: 1.3rem;
  font-weight: 700;
}
.block-event-list__body p { margin: 0.3rem 0; }
.block-event-list__body p:last-child { margin: 0.3rem 0 0; }

.block-instructor {
  background: var(--canvas);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
  padding: 1.4rem 1.6rem;
  margin: 1.4rem 0;
}
.block-instructor__eyebrow {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--slate);
  margin: 0 0 0.4rem;
}
.block-instructor__name { margin: 0 0 0.3rem; }

.block-impact {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(220px, 1fr);
  gap: 1.6rem;
  align-items: center;
  background: var(--canvas);
  border: 1px solid var(--slate-faint);
  border-left: 4px solid var(--flame);
  border-radius: var(--radius);
  padding: 1.4rem 1.6rem;
  margin: 1.4rem 0;
}
@media (max-width: 720px) {
  .block-impact { grid-template-columns: 1fr; }
}
.block-impact__quote { margin: 0; }
.block-impact__quote p {
  font-family: var(--font-body);
  font-size: 1.15rem;
  font-style: italic;
  line-height: 1.4;
  margin: 0 0 0.5rem;
}
.block-impact__quote cite { color: var(--slate); font-style: normal; }
.block-impact__ask { text-align: center; }
.block-impact__cost { margin: 0 0 0.6rem; }
.block-impact__cost strong { font-size: 1.6rem; color: var(--flame-deep); display: block; }
.block-impact__campaign { font-size: 0.8rem; margin-top: 0.5rem; }

/* ---------- Donation campaigns ---------- */
.campaign-progress {
  padding: 16px 18px;
  background: var(--canvas);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius);
  margin: 12px 0;
}
.campaign-progress--compact { padding: 10px 12px; margin: 8px 0; }
.campaign-progress__name { font-weight: 700; margin: 0 0 4px; font-size: 1.05rem; }
.campaign-progress__summary { font-size: 0.9rem; margin: 0 0 8px; }
.campaign-progress__bar {
  width: 100%;
  height: 14px;
  background: #fff;
  border: 1px solid var(--slate-faint);
  border-radius: 999px;
  overflow: hidden;
  margin: 8px 0;
}
.campaign-progress__fill {
  height: 100%;
  background: linear-gradient(90deg, var(--flame), var(--flame-deep));
  transition: width 200ms;
}
.campaign-progress__stats {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 1.05rem;
}
.campaign-progress__pct {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  color: var(--flame-deep);
  font-weight: 600;
}
.campaign-progress__meta { font-size: 0.85rem; margin-top: 4px; }
.campaign-progress__cta { margin-top: 12px; }

.campaign-detail__cols {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
  gap: 2rem;
  align-items: start;
}
@media (max-width: 800px) {
  .campaign-detail__cols { grid-template-columns: 1fr; }
}
.campaign-detail__cover {
  width: 100%; max-height: 320px; object-fit: cover;
  border-radius: var(--radius); margin-bottom: 1rem;
}
.supporter-list { list-style: none; padding: 0; margin: 0; }
.supporter-list li {
  border-bottom: 1px solid var(--slate-faint);
  padding: 0.6rem 0;
}
.supporter-list li p { margin: 0.3rem 0 0; font-style: italic; }

/* ---------- Status badges ---------- */
/* Rounded rectangle, not a pill: at ~17px tall the standard --radius (8px)
   would round a badge almost to a full capsule, so badges get their own
   smaller token. Same squared bottom-right brand corner as everything else. */
.badge {
  display: inline-block;
  vertical-align: middle;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.65rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-left: 0.4rem;
}
.badge--draft     { background: #f0ebe0; color: #87807a; }
.badge--published { background: var(--flame); color: var(--ink); }
/* Something is wrong and a person has to act. The vocabulary above is
   flame-for-live and grey-for-everything-else, which cannot say that - the
   events list resorts to an inline red style for a cancelled event. */
.badge--alert     { background: #fdecea; color: #b8341e; }

/* Shared-inbox claim state (contact/inbox.html). Unclaimed is the actionable
   one, so it's outlined in flame to catch the eye without turning a busy list
   into a wall of solid orange; a message someone else already owns recedes
   into the default grey. All three carry a border so the outlined one doesn't
   sit a pixel taller than its neighbours. */
.badge--mine      { background: var(--flame); color: var(--ink); border: 1px solid transparent; }
.badge--claimed   { background: var(--slate-faint); color: var(--slate); border: 1px solid transparent; }
.badge--unclaimed { background: transparent; color: var(--ink); border: 1px solid var(--flame); }
/* Why something is in the quarantine. Muted on purpose - the Spam tab is a
   list you skim before emptying, and a column of loud red badges makes the one
   genuine message in it harder to spot, not easier. */
.badge--spam { background: var(--slate-faint); color: var(--slate); border: 1px solid transparent; }

/* ---------- Message read / unread status ---------- */
/* The badge and the "read by ... ago" note share one row under the Status
   label, so the note never wraps onto its own line. */
.message-detail__status { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
.message-detail__status .badge { margin-left: 0; }
/* Both use the standard site font (not the mono badge face). Read is a plain
   black outline; unread stays a solid flame fill, so the two states are still
   easy to tell apart at a glance. Shape comes from the shared badge radius, so
   the status on a message detail page matches the badges in the inbox list. */
.badge--read,
.badge--unread {
  font-family: inherit;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  border: 1px solid transparent;
}
.badge--read   { background: transparent; color: var(--ink); border-color: var(--ink); }
.badge--unread { background: var(--flame); color: var(--ink); font-weight: 700; }

/* Pinned to the bottom of the card, so the CTA sits at the same height across
   a row of cards whatever length their summaries are. `margin-bottom: 0` -
   the trailing 0.6rem was invisible while the row floated mid-card and reads
   as a stray gap once it is against the padding. */
.card__actions {
  display: flex; flex-wrap: wrap; gap: 0.4rem;
  margin: auto 0 0;   /* auto top = pushed to the bottom of the card */
}
.lesson-mini-list {
  list-style: none; padding: 0; margin: 0.6rem 0 0;
  border-top: 1px solid var(--slate-faint);
}
.lesson-mini-list li {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--slate-faint);
  display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: baseline;
}
.lesson-mini-list li a { text-decoration: none; color: var(--ink); font-weight: 500; }
.lesson-mini-list li a:hover { color: var(--flame-deep); }

/* A course-level content action: what it does on the left, the button that
   does it on the right. Two of them stack, and each explains itself, because
   these replace commands somebody used to run on the server and the person
   pressing one should not have to guess what it touches. */
.content-action {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: flex-start;
  justify-content: space-between;
  padding: 0.9rem 0;
  border-top: 1px solid var(--slate-faint);
}
.content-action > div { flex: 1 1 22rem; min-width: 0; }
.content-action p { margin: 0.25rem 0 0; }
.content-action form { flex: 0 0 auto; }
.content-action code { font-size: 0.85em; word-break: break-all; }
.content-action__log { margin-top: 0.5rem; font-size: 0.85rem; }
.content-action__log summary { cursor: pointer; color: var(--flame-deep); }
.content-action__log pre {
  margin: 0.4rem 0 0;
  padding: 0.6rem 0.8rem;
  max-height: 14rem;
  overflow: auto;
  background: var(--paper-2, #f4f1ea);
  border: 1px solid var(--slate-faint);
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.8rem;
}

/* ---------- Contact form ---------- */
.contact-form { max-width: 36rem; }
.contact-form__honeypot {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden;
}

/* ---------- Brand mark (Q) ----------
   Two complementary use patterns:

   1. `<img src="{% static 'img/q-mark.svg' %}" class="brand-mark">` - wraps the
       baked-color file at a controlled size. Use brand-mark--dark or
       --light to pull in the single-color variants instead.

   2. `{% include "brand/q_mark.svg" %}` - inlines the SVG so its
       currentColor picks up the surrounding text color. The parent
       sets `color:` (or `class="brand-mark"`), the mark inherits it.
       Same `.brand-mark--<size>` classes work for sizing here too.
*/
.brand-mark {
  display: inline-block;
  vertical-align: middle;
  width: 1.5em; height: 1.5em;
  flex: 0 0 auto;
  color: var(--flame);
}
.brand-mark--sm { width: 1em; height: 1em; }
.brand-mark--md { width: 2em; height: 2em; }
.brand-mark--lg { width: 3em; height: 3em; }
.brand-mark--xl { width: 5em; height: 5em; }
.brand-mark--block { display: block; }
.brand-mark > svg, .brand-mark > img {
  width: 100%; height: 100%; display: block;
}
.brand-mark--ink { color: var(--ink); }
.brand-mark--paper { color: var(--paper); }
.brand-mark--slate { color: var(--slate); }
.brand-mark--flame { color: var(--flame); }
.brand-mark--canvas { color: var(--canvas); }

/* Duotone Q-mark - independent CSS variables driven by data-q-fill /
   data-q-stroke attributes. Each named value maps to a brand token.
   For raw colors, set --q-fill / --q-stroke on a wrapping element. */
.q-mark-duo[data-q-fill="flame"]       { --q-fill: var(--flame); }
.q-mark-duo[data-q-fill="flame-deep"]  { --q-fill: var(--flame-deep); }
.q-mark-duo[data-q-fill="slate"]       { --q-fill: var(--slate); }
.q-mark-duo[data-q-fill="ink"]         { --q-fill: var(--ink); }
.q-mark-duo[data-q-fill="paper"]       { --q-fill: var(--paper); }
.q-mark-duo[data-q-fill="canvas"]      { --q-fill: var(--canvas); }
.q-mark-duo[data-q-fill="transparent"] { --q-fill: transparent; }

.q-mark-duo[data-q-stroke="flame"]       { --q-stroke: var(--flame); }
.q-mark-duo[data-q-stroke="flame-deep"]  { --q-stroke: var(--flame-deep); }
.q-mark-duo[data-q-stroke="slate"]       { --q-stroke: var(--slate); }
.q-mark-duo[data-q-stroke="ink"]         { --q-stroke: var(--ink); }
.q-mark-duo[data-q-stroke="paper"]       { --q-stroke: var(--paper); }
.q-mark-duo[data-q-stroke="canvas"]      { --q-stroke: var(--canvas); }
.q-mark-duo[data-q-stroke="transparent"] { --q-stroke: transparent; }

/* Footer lockup - Q-mark + wordmark, on the dark footer background. */
.site-footer__lockup {
  display: inline-flex; align-items: center; gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.site-footer__lockup .brand-mark { width: 3rem; height: 3rem; }
.site-footer__wordmark {
  font-family: var(--font-body);
  font-size: 1.75rem; font-weight: 500; line-height: 1;
  margin: 0; color: var(--paper);
  letter-spacing: -0.01em;
}
.site-footer__motto {
  color: var(--flame); font-style: italic;
  font-size: 1.05rem; margin: 0 0 0.75rem;
}
.site-footer__credit {
  display: inline-flex; align-items: center; gap: 0.5rem;
}

/* ---------- Async-course viewer ---------- */
.hero--compact { padding: 2rem 0 1.5rem; }


.lesson-nav {
  display: flex; gap: 0.5rem; flex-wrap: wrap;
  justify-content: space-between; align-items: center;
}

/* Slide player */
.player {
  background: var(--paper);
  border: 1px solid var(--slate-faint);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.player__progress { margin: 0; border-radius: 0; }
.player__viewport {
  position: relative;
  min-height: 22rem;
  padding: 2rem 1.25rem;
  background: var(--paper-warm);
}
.player__slide { display: none; }
.player__slide--active { display: block; }
.player__main { max-width: 48rem; margin: 0 auto; }
.player__image {
  display: block; max-width: 100%; height: auto;
  margin: 0 auto 1.5rem; border-radius: var(--radius);
}
.player__title {
  font-size: clamp(1.5rem, 2.5vw + 0.5rem, 2.1rem);
  margin: 0 0 1rem;
}
.player__body { font-size: 1.05rem; }
.player__body ul, .player__body ol { padding-left: 1.25rem; }
.player__audio {
  display: block; width: 100%; max-width: 48rem;
  margin: 1.5rem auto 0;
}
.player__script {
  max-width: 48rem; margin: 1.5rem auto 0;
  font-size: 0.95rem; color: var(--slate);
}
.player__script summary { cursor: pointer; color: var(--flame-deep); }
.player__controls {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--slate-faint);
  background: var(--paper);
}
.player__counter { font-variant-numeric: tabular-nums; }

@media (max-width: 600px) {
  .player__viewport { padding: 1.5rem 1rem; min-height: 18rem; }
  .player__controls { padding: 0.75rem 1rem; }
}

/* ---------- Volunteer log ---------- */
.vol-grid {
  display: grid; gap: 2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 880px) {
  .vol-grid { grid-template-columns: 1.5fr 1fr; align-items: start; }
}
.vol-grid__form > .form-grid { max-width: none; }

/* ---------- Policy pages (privacy, terms) ---------- */
/* Long-form legal copy: one narrow measure so it stays readable, and rules
   between top-level sections so somebody scanning for "how long do you keep X"
   can find the boundary. */
/* Cap each block rather than the container itself: .policy sits on the
   .container div, and narrowing that would centre the copy while the hero
   heading above it stays left-aligned. Constraining the children instead gives
   a left-aligned measure, the same way .hero__lede does. */
.policy > * { max-width: 44rem; }
.policy h2 {
  font-size: 1.3rem; margin: 2.5rem 0 0.75rem;
  padding-top: 1.5rem; border-top: 1px solid var(--slate-faint);
}
.policy > :first-child { margin-top: 0; }
.policy > h2:first-child { padding-top: 0; border-top: none; }
.policy h3 { font-size: 1.02rem; margin: 1.5rem 0 0.4rem; }
.policy p { margin: 0 0 1rem; }
.policy ul { margin: 0 0 1rem; padding-left: 1.25rem; }
.policy li { margin-bottom: 0.4rem; }
.policy .roster-wrap { margin-bottom: 1.5rem; }
.policy .roster td small { margin-top: 0.25rem; }

.policy__version {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.78rem;
}
.policy__summary {
  background: var(--paper-warm);
  border: 1px solid var(--slate-faint);
  border-left: 4px solid var(--flame);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  margin-bottom: 2rem;
}
.policy__summary > :last-child { margin-bottom: 0; }
.policy__notice {
  background: var(--canvas);
  border-left: 4px solid var(--flame-deep);
  border-radius: var(--radius-sm);
  padding: 1rem 1.25rem;
}

/* ---------- Provenance notice ----------
 *
 * "A machine made this." Informational, not a warning: a synthesized reading
 * is a legitimate way to narrate a class and a machine translation is a
 * legitimate way to start one. Styled as a quiet aside rather than in the
 * warning red, which would tell the reader something is wrong when nothing is.
 */
/* This block used to be var(--canvas) text on a var(--canvas) page, with grey
   type and a grey rule - which is to say it was the page, and nobody read it.
   A notice that says "a machine made this" has to be legible or it is not a
   notice, so: paper against the canvas, a real border, the flame rule, and
   ink for the words. Still quiet - it is not a warning and the content is not
   suspect - but findable. */
.notice--provenance {
  display: flex;
  gap: 0.7rem;
  align-items: flex-start;
  background: var(--paper);
  border: 1px solid var(--slate-faint);
  border-left: 4px solid var(--flame);
  border-radius: var(--radius-sm);
  padding: 0.8rem 1.1rem;
  margin: 0 0 1.4rem;
  color: var(--ink);
  font-size: 0.94rem;
}
.notice__icon { flex: none; margin-top: 0.12rem; color: var(--flame-deep); }
.notice--provenance .notice__lines { flex: 1 1 auto; min-width: 0; }
.notice--provenance .notice__line { margin: 0; }
.notice--provenance .notice__line + .notice__line { margin-top: 0.45rem; }

/* Provenance badges on the transcript. The block above is the page's
   statement; these sit on the words themselves, which is what they are about
   - the transcript IS the narration script, and in another language it is the
   translation. Same shape as every other badge on the site: a reader should
   not have to work out that these are the same kind of object. */
.badge--ai,
.badge--machine {
  background: #fff4ec;
  color: var(--flame-deep);
  border: 1px solid var(--flame);
}
.lesson-transcript summary .badge { margin-left: 0.5rem; }

/* ---------- Rename confirmation ----------
 *
 * Sits at the top of an edit form, once, when saving would move a live page's
 * address. Louder than the provenance notice next door because it is asking a
 * question rather than stating a fact: the person has already pressed Save and
 * the save did not happen. It is not styled in the error red either - nothing
 * is wrong, and a red block would read as "you can't do that" when what we
 * mean is "here is what this does".
 */
.rename-confirm {
  background: #fff4ec;
  border: 1px solid var(--flame);
  border-left: 4px solid var(--flame-deep);
  border-radius: var(--radius-sm);
  padding: 1rem 1.2rem;
  margin-bottom: 0.4rem;
}
.rename-confirm__title {
  margin: 0 0 0.4rem;
  font-size: 1.05rem;
  color: var(--ink);
}
.rename-confirm__lede { margin: 0 0 0.7rem; font-size: 0.94rem; }

/* The two addresses. Which characters changed is the whole point of showing
   them, so a long path wraps rather than scrolling out of view sideways. */
.rename-confirm__moves {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0 0 0.9rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}
/* On a phone the two paths do not fit on one line, and flex-wrapping them
   leaves the arrow stranded at the end of the first. Stacking puts each
   address on its own line with the arrow between, which is the shape that
   makes the difference between them readable. */
@media (max-width: 600px) {
  .rename-confirm__moves { flex-direction: column; gap: 0.15rem; }
}
.rename-confirm__was { color: var(--slate); text-decoration: line-through; }
.rename-confirm__now { color: var(--ink); font-weight: 600; }
.rename-confirm__arrow { color: var(--flame-deep); }

/* Descendant-scoped, not `.rename-confirm__keep` alone: this is a <label>
   inside .form-grid, and `.form-grid label` is the more specific selector -
   it would otherwise stack the box above the words and bold the lot. */
.rename-confirm .rename-confirm__keep {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 0.55rem;
  align-items: start;
  font-size: 0.9rem;
  font-weight: 400;
  line-height: 1.45;
  cursor: pointer;
}
.rename-confirm .rename-confirm__keep strong { font-weight: 700; }
.rename-confirm .rename-confirm__keep input[type="checkbox"] {
  margin-top: 0.25rem;
}

/* ---------- Prefers reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
  html { scroll-behavior: auto; }
}
