/* ── Reset & base ──────────────────────────────────────────── */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

/*
 * ── Design tokens ─────────────────────────────────────────────
 *
 * Canonical tokens (colors, font, type scale, spacing, radii) come from the CDN:
 *   https://cdn.neorgon.org/v1.0.0/styles/base.css   (linked in index.html)
 * Do NOT redeclare them here: a local copy is how the fleet drifted. The block
 * below holds only what base.css does not define, plus the per-site accent.
 *
 * Motion: base.css still ships --ease-snap (bounce) for legacy sites, but it is
 * deprecated for new work (impeccable law: ease-out exponential, no bounce).
 * Use --ease-out below.
 */

:root {
  /* Per-site identity: this site's hub card accent (PROJECTS.md section 4).
     Teal: a study tone, and the one identity knob this site owns. */
  --accent: #14b8a6;
  --accent-bright: #2dd4bf;

  /* Site extras base.css does not define */
  --surface-toast: #1a1730;
  --danger: #e11d48;
  --danger-hover: #f43f5e;
  /* Hit and miss, declared once. Four hex literals used to sit in the review
     block below, which meant the "you recalled it" green had no relationship
     to the site's own teal and the Hard amber had no relationship to anything.
     Both are derived: --ok IS the accent, and --warn is the accent and danger
     mixed, which in oklch lands on amber. Caution, not failure. */
  --ok: var(--accent-bright);
  --warn: color-mix(in oklch, var(--danger) 60%, var(--accent-bright));
  /* A popover needs an opaque ground: --surface-1 is translucent white and the
     row underneath would read straight through the menu sitting on it. */
  --surface-menu: color-mix(in srgb, var(--text-primary) 7%, var(--bg));
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --control-height: 44px;
  --control-height-sm: 36px;
  --shadow-card: 0 12px 28px rgba(15, 23, 42, 0.5);
  --shadow-modal: 0 24px 48px rgba(2, 6, 23, 0.58);
  --z-sticky-header: 200;
  --z-modal: 400;
  --z-toast: 500;
}

/* Author display values (e.g. .section/.stack flex) would otherwise defeat the
   hidden attribute, so restore its authority once, globally. */
[hidden] { display: none !important; }

html { height: 100%; }
body {
  font-family: var(--font);
  background: var(--bg);
  min-height: 100%;
  color: var(--text-primary);
  display: flex; flex-direction: column;
}

body.modal-open {
  overflow: hidden;
}

main#main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  width: 100%;
}

/* ── Layout ────────────────────────────────────────────────── */
/*
 * GOTCHA: body { display:flex; flex-direction:column } + margin: 0 auto
 * on a child collapses its width, so always pair with width: 100%.
 *
 * GOTCHA: External images (YouTube ggpht, Gravatar, etc.) may block loading
 * when the Referer header points to localhost or an unknown origin. Fix:
 * 1. Add <meta name="referrer" content="no-referrer"> in <head> (global)
 * 2. Or add referrerpolicy="no-referrer" on each <img> tag
 */
.container {
  max-width: 900px;
  width: 100%;
  margin: 0 auto;
  padding: var(--space-6);
  flex: 1;
}

/* ── Side-rail shell (opt-in) ──────────────────────────────────
 * A desktop window is far wider than a comfortable line of text, so a single
 * centred column leaves two large empty gutters and still runs the prose to
 * 100+ characters. This puts the section nav in the left gutter and narrows
 * the reading column, without changing .container for sites that do not use it.
 *
 *   <div class="shell">
 *     <nav class="shell__rail" aria-label="Section"> … </nav>
 *     <div class="shell__main"> … </div>
 *   </div>
 *
 * Below 940px it collapses to one column and the rail becomes a scrollable
 * row, so the same markup works on a phone with no JavaScript involved. From
 * 1240px up, the spare width goes to the right margin only, so the reading
 * column walks onto the page's centre line instead of sitting right of it.
 */
.shell {
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-6) 64px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  align-content: start;
  flex: 1;
}
/* A 1fr track is minmax(auto, 1fr): a wide child (a board, a table, a long
   code line) sets min-content and stretches the column past the viewport. */
.shell > * { min-width: 0; }

.shell__rail {
  display: flex;
  gap: var(--space-1);
  padding: 4px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-x: auto;
  scrollbar-width: none;
}
.shell__rail::-webkit-scrollbar { display: none; }

@media (min-width: 940px) {
  .shell {
    grid-template-columns: 208px minmax(0, 1fr);
    gap: var(--space-8);
    padding: var(--space-8) var(--space-8) 80px;
  }
  .shell__rail {
    position: sticky;
    top: 76px;          /* clears the app-mode header */
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: var(--space-2);
    overflow: visible;
    align-self: start;
  }
}

/* The rail sits in the left gutter, but with only two tracks it does not stay
   there: the rail plus its gap add to main's left side and nothing balances
   them on the right, so main sits half that distance right of the page's centre
   line (120px at a 208px rail). A column that far off centre reads as a
   misalignment rather than as a nav beside it.
   The correction is to stop splitting the spare width between both margins and
   spend all of it on the right. Main then walks back onto the centre line as the
   window grows, reaching it at 1240 + 208 + 32 = 1480px, and it costs main no
   width at any point on the way: the shell widens, main does not move relative
   to the rail. Below 1240px the shell already fills the viewport, so there is no
   spare width to spend and any correction would come straight out of the
   reading column. That trade is not worth making. */
@media (min-width: 1240px) {
  .shell {
    --shell-nudge: calc(208px + var(--space-8));
    max-width: calc(1240px + var(--shell-nudge));
    padding-right: clamp(
      var(--space-8),
      calc(var(--space-8) + 100vw - 1240px),
      calc(var(--space-8) + var(--shell-nudge))
    );
  }
}

/* Vertical rhythm: use inside .container or nested regions */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.stack--tight { gap: var(--space-4); }
/* Major sections need a real break. At 32px a heading sits almost on top of
   the block above it and the page reads as one undifferentiated column. */
.stack--loose { gap: 56px; }

/* Page section: title row + body (replaces “card everything” for grouping) */
.section {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.section__header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
}
.section__titles {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.section__title {
  /* One step of at least 1.25 over the deck-name --text-lg below it. At
     --text-xl the two were 1.11 apart and the page had no top of hierarchy. */
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.25;
  color: var(--text-primary);
  /* render.js focuses this heading after a view change, so it takes a visible
     ring. Shrunk to its text: a block-level h2 would draw the ring around the
     whole column and read as a text field rather than as a heading. */
  width: fit-content;
  max-width: 100%;
}
/* Both skip links land on one of these. A target scrolled flush to the top of
   the viewport sits under the sticky 56px app header, so it clears the bar's
   own height (the kit's token, not a literal) and keeps 16px of air. */
.section__title,
#rpCard { scroll-margin-top: calc(var(--header-h-app, 56px) + var(--space-4)); }
.section__title:focus-visible { outline-offset: 6px; border-radius: 2px; }
.section__lead {
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.6;
  max-width: 64ch;   /* 65-75 characters is the readable measure */
}
.section__lead code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: 0.92em;
  color: var(--text-secondary);
}
.stack > .card { margin-bottom: 0; }
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* ── Header ────────────────────────────────────────────────────
 * Owned by the Neorgon Header Kit (css/neorgon-header.css, canonical
 * source: packages/neorgon-ui/header/). Do NOT style .header-bar,
 * .header-logo*, .header-title-link, .header-subtitle, .header-right,
 * .header-actions, or .header-home here. Auth styles (.auth-*) stay
 * site-owned below.
 *
 * The one sanctioned exception is a custom skin: TOKENS ONLY, and the selector
 * is copied exactly from packages/neorgon-ui/header/README.md, section
 * `custom`. The kit's magenta over a teal site put two identities on one page,
 * and this site's palette is not one any other site would reuse, so there is no
 * named skin to reach for. html:not([data-theme]) is what makes the whole block
 * stop matching the moment a visitor picks a theme: the person outranks the
 * site, and the site outranks nothing.
 */
/* The mix is in oklab, not oklch: oklch interpolates the hue angle, and the
   ground sits at 265 while the accent sits at 180, so the header landed on
   oklch(0.43 0.079 224) and read as a different blue from the teal buttons
   under it. oklab keeps the accent's own hue and only darkens it. */
html:not([data-theme]) .header-bar[data-header-skin="custom"] {
  --header-bg-start: color-mix(in oklab, var(--accent) 52%, var(--bg));
  --header-bg-mid: color-mix(in oklab, var(--accent) 24%, var(--bg));
  --header-bg-end: var(--bg);
  --header-accent: var(--accent-bright);
  --header-menu-bg: var(--surface-menu);
}
nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* ── Buttons (canonical) ─────────────────────────────────────
 * Use .btn + a variant. Aliases: .nav-link = toolbar ghost; .auth-* map below.
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  padding: 0 var(--space-4);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-primary);
  transition:
    background-color var(--dur),
    color var(--dur),
    border-color var(--dur),
    box-shadow var(--dur),
    transform var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
.btn--sm {
  min-height: var(--control-height-sm);
  padding: 0 var(--space-3);
  font-size: var(--text-xs);
}
.btn--block { width: 100%; }
.btn--icon {
  min-width: var(--control-height);
  padding: 0;
}
.btn--primary {
  background: var(--accent);
  /* White on this teal is 2.49:1. The page ground as ink reads at over 8:1. */
  color: var(--bg, #040714);
  border-color: transparent;
}
.btn--primary:hover { background: var(--accent-bright); }
.btn--secondary {
  background: var(--surface-2);
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.btn--secondary:hover {
  background: color-mix(in oklab, var(--text-primary) 10%, var(--surface-2));
}
.btn--ghost {
  background: transparent;
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.btn--ghost:hover {
  background: var(--surface-2);
  color: var(--text-primary);
}
.btn--danger {
  background: var(--danger);
  color: var(--text-primary);
  border-color: transparent;
}
.btn--danger:hover { background: var(--danger-hover); }

/* Toolbar ghost (header nav). Inside .shell__rail the border comes off: a
   bordered button inside a bordered box is a box in a box, and the rail drew
   four of them. The rail keeps its own frame and the items are plain until
   they are hovered or current. */
.nav-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-height);
  padding: 0 var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.nav-link:hover {
  background: var(--surface-2);
  color: var(--text-primary);
}
.shell__rail .nav-link {
  justify-content: flex-start;
  border-color: transparent;
  color: var(--text-secondary);
}
.shell__rail .nav-link:hover { color: var(--text-primary); }

.auth-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: var(--control-height);
  min-height: var(--control-height);
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
  font-family: inherit;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.auth-toggle:hover {
  background: var(--surface-2);
  color: var(--text-primary);
}
.auth-toggle svg {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  display: block;
}
.auth-toggle.logged-in { color: var(--accent); }
.auth-toggle.logged-in svg circle:nth-of-type(2) {
  fill: currentColor;
  stroke: none;
}

/* ── Sheet (inline panel under header), not a modal ──────────
 * Use for auth, filters, compact forms. No backdrop; page scrolls.
 * For blocking overlays use .modal below.
 */
.auth-panel {
  background: var(--surface-1);
  border-bottom: 1px solid var(--border-subtle);
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-out;
}
.auth-panel.open { max-height: 300px; }
.auth-panel-inner {
  padding: var(--space-6);
  max-width: 400px;
  margin: 0 auto;
}
.auth-tabs {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.auth-tab {
  flex: 1;
  min-height: var(--control-height-sm);
  padding: 0 var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.auth-tab.active {
  background: var(--surface-2);
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.auth-form input {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-family: inherit;
}
.auth-submit {
  width: 100%;
  min-height: var(--control-height);
  padding: 0 var(--space-4);
  border: none;
  border-radius: var(--radius-sm);
  background: var(--accent);
  /* Same reason as .btn--primary: white on this teal is 2.49:1. */
  color: var(--bg);
  font-family: inherit;
  font-weight: 600;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background-color var(--dur);
}
.auth-submit:hover { background: var(--accent-bright); }
#authUser {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}
.auth-username {
  color: var(--text-primary);
  font-weight: 600;
}
.auth-logout {
  min-height: var(--control-height-sm);
  padding: 0 var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.auth-logout:hover {
  background: var(--surface-2);
  color: var(--text-primary);
}
.auth-divider {
  height: 1px;
  background: var(--border-subtle);
}

/* ── Modal (blocking overlay) ──────────────────────────────── */
.modal[hidden] {
  display: none !important;
}
.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}
.modal__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  -webkit-backdrop-filter: blur(10px); /* Safari still needs the prefix */
  backdrop-filter: blur(10px);
}
.modal__dialog {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  width: min(100%, 520px);
  max-height: min(90vh, 680px);
  overflow: hidden;
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-modal);
}
/* For modals holding option grids or editors: narrow dialogs cramp them */
.modal__dialog--wide {
  width: min(100%, 760px);
}
.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--border-subtle);
}
.modal__header h2 {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.modal__body {
  padding: var(--space-6);
  overflow: auto;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: 1.55;
}
.modal__body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: 0.9em;
  color: var(--text-primary);
}
.modal__footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-6);
  border-top: 1px solid var(--border-subtle);
}

/* ── Card ──────────────────────────────────────────────────── */
/*
 * GOTCHA: backdrop-filter creates a stacking context, so dropdowns inside
 * a card can be clipped by a later card's stacking context. Fix by adding
 * position: relative; z-index: 10 to the card that owns the open dropdown.
 *
 * Default: static surface (no hover lift, avoids generic “dashboard demo”).
 * Use .card--interactive for raised hover + entrance motion.
 *
 * GOTCHA: a <button> or <a> used AS a card does not inherit body text color,
 * buttons default to canvas text (near-black), rendering invisible on the dark
 * bg. When a card is an interactive element, set color: var(--text-primary)
 * and font-family: var(--font) explicitly on it.
 */
.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  margin-bottom: var(--space-6);
  backdrop-filter: blur(10px);
  transition: border-color var(--dur), box-shadow var(--dur);
}
.card:hover {
  border-color: var(--border-strong);
  box-shadow: 0 8px 20px color-mix(in srgb, var(--bg) 60%, transparent);
}
.card--interactive {
  transition:
    transform var(--dur) ease-out,
    box-shadow var(--dur) ease-out,
    border-color var(--dur);
  animation: fadeIn 0.35s ease-out both;
}
.card--interactive:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-card);
  border-color: var(--border-strong);
}
.card--enter {
  animation: fadeIn 0.35s ease-out both;
}
.card--flat:hover {
  box-shadow: none;
}
/* Card with an action row, in an equal-height grid: pin the toolbar to the
   bottom so buttons align across cards whose text wraps differently. */
.card--actions {
  display: flex;
  flex-direction: column;
}
.card--actions > .toolbar:last-child {
  margin-top: auto;
}

/* ── Button press feedback ─────────────────────────────────── */
button:active,
.btn:active,
.nav-link:active,
.auth-toggle:active,
.auth-tab:active,
.auth-submit:active,
.auth-logout:active {
  transform: scale(0.98);
  transition-duration: var(--dur-fast);
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Skeleton loading ──────────────────────────────────────── */
@keyframes shimmer { to { background-position: -200% 0; } }
.skeleton {
  background: linear-gradient(90deg, var(--surface-1) 25%, var(--surface-2) 50%, var(--surface-1) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
  border-radius: var(--radius);
}

/* ── Toast ─────────────────────────────────────────────────── */
.toast {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  z-index: var(--z-toast);
  max-width: min(360px, calc(100vw - var(--space-8)));
  background: var(--surface-toast);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-primary);
  box-shadow: var(--shadow-card);
  pointer-events: none;
  transform: translateY(8px);
  opacity: 0;
  transition: opacity 0.2s ease-out, transform 0.2s ease-out;
}
.toast.visible {
  opacity: 1;
  transform: translateY(0);
}

/* ── Footer ────────────────────────────────────────────────────
   Owned by the Neorgon Footer Kit (css/neorgon-footer.css).
   Don't add .neo-footer rules here. Edit packages/neorgon-ui/footer/
   and re-run sync-footer.sh. */

/* ── Focus styles ──────────────────────────────────────────── */
:focus-visible {
  outline: 2px solid var(--accent-bright);
  outline-offset: 2px;
}

/* ── Skip link ─────────────────────────────────────────────── */
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-4);
  z-index: 1000;
  padding: var(--space-2) var(--space-4);
  background: var(--accent-bright);
  color: var(--bg);
  font-weight: 600;
  font-size: var(--text-sm);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  text-decoration: none;
  transition: top var(--dur);
}
.skip-link:focus { top: 0; }

/* ── Reduced motion ────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .card--interactive:hover { transform: none; }
}

/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 600px) {
  .container { padding: var(--space-4); }
  .toast {
    left: var(--space-4);
    right: var(--space-4);
    max-width: none;
  }
}

/* A deck row folds rather than crushes: the name and its sentence take the
   full width, and Study takes what is left of the line beside the menu. Study
   stays 44px at every width, which is the point of moving the other five
   actions off the row. */
@media (max-width: 620px) {
  .rp-deck {
    grid-template-columns: minmax(0, 1fr) auto;
    padding-inline: 0;
  }
  .rp-deck__text { grid-column: 1 / -1; }
  .rp-deck__go { justify-self: stretch; }
  .rp-panel { padding: var(--space-4); }
  .rp-menu__list { min-width: 180px; }
  /* A settings label is 15ch wide and the slider asks for 150, which at 390px
     leaves the percentage readout nothing and drops it to a line of its own,
     under a control it belongs beside. The label takes the line instead, and
     the slider and its readout share the next one. */
  .rp-field > span { flex-basis: 100%; }
}

/* ══════════════════════════════════════════════════════════════
 * Rappel
 *
 * Everything below is prefixed .rp- and belongs to this site. No header or
 * footer kit selector appears here: those kits own their own CSS and a
 * site-local override of them is how the fleet drifts.
 * ══════════════════════════════════════════════════════════════ */

body {
  /* The viz heatmap ramp defaults to blue. Retint it to the site accent, on
     body rather than :root so it wins over viz.css, which loads later. */
  --viz-heat-1: color-mix(in srgb, var(--accent) 22%, transparent);
  --viz-heat-2: color-mix(in srgb, var(--accent) 44%, transparent);
  --viz-heat-3: color-mix(in srgb, var(--accent) 68%, transparent);
  --viz-heat-4: color-mix(in srgb, var(--accent) 95%, transparent);
}

.rp-visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.nav-link.is-active {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--text-primary);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.rp-note {
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: 1.5;
  margin: 0;
}

/* A grouping surface with no backdrop blur and no hover reaction. .card has
   both, which on a settings form is decoration reacting to a mouse that is on
   its way somewhere else. */
.rp-panel {
  /* A form and a notice are prose plus controls, and prose has a measure. A
     desktop window is far wider than one, and the settings notes ran past 110
     characters a line before this. */
  max-width: 780px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}

/* Every chart on the stats screen shares one measure with the panels. The
   heatmap's own shape is about two and a half to one, so at the full width of
   a 1280px window it was drawing 18 weeks of mostly-empty grid 400px tall. */
.rp-charts { max-width: 780px; }

/* The label above a secondary block: shelf, storage, a settings group. Small
   and quiet on purpose, so it separates without competing with the h2. */
.rp-panel__title,
.rp-shelf__title,
.rp-storage__title {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ── Deck library ─────────────────────────────────────────────
 * Rows, not cards. Four decks used to be four bordered boxes, each holding six
 * equally weighted buttons and three pills, one of which drew "0 due" in the
 * accent colour. The page had no first thing to look at.
 *
 * A row is now one line of hierarchy: the name, one sentence that means
 * something, one primary button, and everything else behind a menu. The accent
 * appears only on the sentence of a deck that has work waiting, so a library
 * with nothing due is entirely monochrome and the deck to study is the only
 * coloured thing on the screen.
 */
.rp-decks {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border-subtle);
}
.rp-deck {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  padding: var(--space-4) var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
}
/* The roving tabindex means one row at a time holds the keyboard. Saying which
   one is not decoration: without it, Up and Down move an invisible cursor. */
.rp-deck:focus-within { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.rp-deck__text { min-width: 0; }
.rp-deck__name {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.rp-deck__readout {
  margin: 3px 0 0;
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.rp-deck__readout--ready { color: var(--accent-bright); }
/* "4 to study now" and "3 due, 1 new" are two sentences sharing a line, and a
   space between them read as one run-on: "204 to study now 4 due, 200 new". */
.rp-deck__split { color: var(--text-muted); }
.rp-deck__split::before { content: ": "; }
.rp-deck__go { min-width: 108px; }

/* ── The row's overflow menu ──────────────────────────────────
 * Cram, Browse, the two exports and Forget. Forget was a saturated red button
 * on every row of the library; it is now one item inside a menu a person has
 * to open, so the page has at most one red thing on it and never by default.
 */
.rp-menu { position: relative; }
.rp-menu__btn {
  display: inline-grid;
  place-items: center;
  width: var(--control-height);
  height: var(--control-height);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: var(--text-lg);
  line-height: 1;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.rp-menu__btn:hover,
.rp-menu__btn[aria-expanded="true"] {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-primary);
}
.rp-menu__list {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 200px;
  padding: var(--space-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-menu);
  box-shadow: var(--shadow-card);
  animation: rp-menu-in 140ms var(--ease-out) both;
}
@keyframes rp-menu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}
.rp-menu__item {
  display: flex;
  align-items: center;
  min-height: var(--control-height-sm);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-fast), color var(--dur-fast);
}
.rp-menu__item:hover,
.rp-menu__item:focus-visible {
  background: var(--surface-2);
  color: var(--text-primary);
}
/* Forget is separated from the four reversible actions above it by a hairline
   rather than by being loud. */
.rp-menu__item--danger {
  position: relative;
  margin-top: var(--space-2);
  color: color-mix(in srgb, var(--danger-hover) 75%, var(--text-primary));
}
.rp-menu__item--danger::before {
  content: "";
  position: absolute;
  inset: calc(var(--space-1) * -1) var(--space-2) auto;
  height: 1px;
  background: var(--border-subtle);
}
.rp-menu__item--danger:hover,
.rp-menu__item--danger:focus-visible {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
  color: var(--text-primary);
}

/* ── Shelf and storage ────────────────────────────────────── */
.rp-shelf,
.rp-storage { display: flex; flex-direction: column; gap: var(--space-3); }
.rp-storage {
  gap: var(--space-4);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border-subtle);
}
.rp-storage .rp-note { max-width: 72ch; }

.rp-empty h3 { margin: 0 0 var(--space-2); }
.rp-empty p { color: var(--text-secondary); margin: 0; max-width: 60ch; }
.rp-empty .toolbar { margin-top: var(--space-4); }

/* Scheduling state arrived without the reviews behind it: sync carries the
   card map and never the log (C12 A4), and an export leaves the log out by
   default. Rendered as a statement with a route out of it, never as a zero. */
.rp-elsewhere { border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); }
.rp-elsewhere p { color: var(--text-secondary); margin: 0; max-width: 70ch; }
.rp-elsewhere p + p { margin-top: var(--space-3); }
.rp-elsewhere .toolbar { margin-top: var(--space-4); }

/* The deck version, under the deck name on the browse screen. It was a stamp
   on every library row at the same weight as the counts, meaning nothing to
   anyone standing in front of a list of decks to study, and it is a sibling of
   the h2 rather than inside it: a heading's accessible name is the deck, and
   render.js reads that heading out on arrival. */
.rp-version {
  margin: 2px 0 0;
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ── Review surface ───────────────────────────────────────────
 * The whole review block used to live here and every rule of it was shadowed
 * by css/session.css, which loads after this file and declares the same
 * selectors with more specificity. Two descriptions of one screen is one
 * description too many: a change to the card was made twice or made once and
 * silently lost. Only the two media rules were ever this file's alone.
 */
.rp-media-img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }
.rp-media-audio {
  font: inherit; font-size: var(--text-sm); padding: 4px 12px;
  border-radius: 999px; border: 1px solid var(--border-strong);
  background: var(--surface-1); color: var(--text-primary); cursor: pointer;
}

/* ── Browse table ─────────────────────────────────────────── */
/* A horizontally scrolling region is unreachable by keyboard unless it is a
   tab stop, so the markup gives it tabindex="0" and a name. */
.rp-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
}
.rp-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.rp-table th, .rp-table td { padding: var(--space-2) var(--space-3); text-align: left; border-bottom: 1px solid var(--border-subtle); }
.rp-table th { color: var(--text-muted); font-weight: 600; position: sticky; top: 0; background: var(--bg); }
.rp-table tbody tr:hover { background: var(--surface-1); }
.rp-cell-front { max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── Forms ────────────────────────────────────────────────── */
.rp-field { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.rp-field > span { min-width: 15ch; color: var(--text-secondary); font-size: var(--text-sm); }
.rp-field--block { align-items: flex-start; }
.rp-field input[type="text"], .rp-field input[type="number"], .rp-field textarea {
  flex: 1 1 220px; font: inherit; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); border: 1px solid var(--border-strong);
  background: var(--surface-1); color: var(--text-primary);
}
/* A field is as wide as what goes in it. "60, 600" in a 1100px box and a day
   of the hour clock in another one read as a form nobody laid out. */
.rp-field input[type="text"] { max-width: 22ch; }
.rp-field input[type="number"] { max-width: 8ch; }
.rp-field textarea { max-width: 62ch; }
.rp-field textarea { min-height: 5em; resize: vertical; font-family: var(--mono, ui-monospace, Menlo, monospace); font-size: var(--text-sm); }
/* The track is 16px tall; the control it belongs to is not. min-height gives
   the thumb a 44px band to be grabbed in without changing how it draws. */
/* Shrinkable, so the percentage readout stays on the slider's own line at
   390px instead of dropping to a third line under it. */
.rp-field input[type="range"] {
  flex: 1 1 150px;
  max-width: 260px;
  min-height: var(--control-height);
  accent-color: var(--accent);
}
.rp-field output { font-variant-numeric: tabular-nums; min-width: 4ch; }
.rp-check { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-secondary); }
.rp-check input { accent-color: var(--accent); }
.rp-radios { border: 0; padding: 0; margin: 0; display: grid; gap: var(--space-2); }
.rp-radios legend { color: var(--text-muted); font-size: var(--text-sm); padding: 0 0 var(--space-2); }

/* ── The licence acknowledgement ──────────────────────────── */
.neo-attrib {
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: 1.6;
  max-width: 80ch;
}
.neo-attrib__line { margin: 0 0 var(--space-2); }
.neo-attrib a { color: var(--text-secondary); }

/* ── Embed mode ───────────────────────────────────────────── */
/* Chrome down to one bar with an escape link, the shape proven in
   projects/proctor-site/js/app.js:25-37. */
.rp-embed-bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: 6px var(--space-4);
  border-bottom: 1px solid var(--border);
  background: var(--surface-1);
  font-size: var(--text-xs); color: var(--text-muted);
}
.rp-embed-bar a { color: var(--accent-bright); text-decoration: none; white-space: nowrap; }
.rp-embed-bar a:hover { text-decoration: underline; }
body.is-embed .header-bar,
body.is-embed .neo-footer,
body.is-embed .shell__rail,
body.is-embed .neo-beacon,
body.is-embed .skip-link { display: none; }
body.is-embed .shell { display: block; padding: 0; }
body.is-embed main#main { padding: var(--space-4); }
body.is-embed .stack--loose { gap: var(--space-4); }



/* The one auth class the template does not style: the sheet's one-line note. */
.auth-note { margin: 0; color: var(--text-muted, #9aa3b2); font-size: 0.9rem; }
/* The template caps the open sheet at 300px with overflow hidden, which clips Clerk's form. */
.auth-panel.open { max-height: min(80vh, 640px); overflow: auto; }
