/* Declare which schemes this page renders itself. Dark mode here is driven by
   a .dark class on <html>, not by prefers-color-scheme alone, so without an
   explicit color-scheme the browser assumes the page is light-only and applies
   its own forced darkening (Samsung One UI dark mode, Chrome's "auto dark theme
   for web contents"). That re-colours the amber #b45309 header to maroon and
   washes out accent colours. Declaring it also makes native form controls and
   scrollbars follow the active theme. */
:root { color-scheme: light; }
html.dark { color-scheme: dark; }

/* Pull-to-refresh and the overscroll glow are suppressed HERE, on the element
   that actually scrolls the page, and nowhere else. Do not put
   overscroll-behavior (or Tailwind's overscroll-* classes) on <body>: calm.css
   sets overflow-x:hidden on <html>, so the viewport takes its overflow from
   <html> and <body> becomes a scroll container in its own right — one with
   nothing to scroll. Containment on that dead scroller swallowed every
   one-finger drag app-wide and refused to chain to the page, leaving pinch-zoom
   as the only gesture that moved anything. */
html { overscroll-behavior: none; }
body { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
h1, .font-display { font-family: 'Lora', Georgia, 'Times New Roman', serif; letter-spacing: -0.015em; line-height: 1.15; }
h2 { font-family: 'Lora', Georgia, 'Times New Roman', serif; letter-spacing: -0.01em; line-height: 1.2; }

/* ────────────────────────────────────────────────────────────────
   "Warm study paper" polish — a calm, warm canvas with white cards
   that lift gently, soft amber-tinted shadows, and hairline borders.
   Light-mode treatments use !important so they win over the Tailwind
   CDN utilities; the dark-mode block further down has higher
   specificity, so dark mode keeps overriding these.
   ──────────────────────────────────────────────────────────────── */
body { background-color: #faf7f2; }              /* warm ivory canvas (light) */

/* ── Ambient study background ── a subtle corner-glow layered on top of
   the canvas color via background-image (background-color stays the
   bottom layer, so this never fights the rule above or the dark-mode
   override below). Driven by companion.html's updateAmbientMode(),
   which only sets data-ambient-mode from real, already-tracked state
   (an in-flight AI/hint fetch, a socratic/sanity coaching moment, the
   same fatigue read the watchdog nudge uses) — gated on both
   data-companion-page and data-study-page so it's a "currently
   studying, with Pagey on" affordance, not a whole-app reskin. */
body[data-companion-page="1"][data-study-page="1"] {
  transition: background-image 1.4s ease;
  background-image:
    radial-gradient(ellipse 70% 50% at 15% -10%, rgba(30,58,138,.16), transparent 60%),
    radial-gradient(ellipse 70% 50% at 100% 110%, rgba(51,65,85,.14), transparent 55%);
}
body[data-companion-page="1"][data-study-page="1"][data-ambient-mode="socratic"] {
  background-image:
    radial-gradient(ellipse 70% 50% at 15% -10%, rgba(217,119,6,.18), transparent 60%),
    radial-gradient(ellipse 70% 50% at 100% 110%, rgba(180,83,9,.14), transparent 55%);
}
body[data-companion-page="1"][data-study-page="1"][data-ambient-mode="generating"] {
  background-image:
    radial-gradient(ellipse 70% 50% at 15% -10%, rgba(56,189,248,.2), transparent 60%),
    radial-gradient(ellipse 70% 50% at 100% 110%, rgba(139,92,246,.16), transparent 55%);
}
body[data-companion-page="1"][data-study-page="1"][data-ambient-mode="fatigue"] {
  background-image:
    radial-gradient(ellipse 70% 50% at 15% -10%, rgba(168,85,247,.16), transparent 60%),
    radial-gradient(ellipse 70% 50% at 100% 110%, rgba(244,114,182,.14), transparent 55%);
}
.bg-gray-50  { background-color: #f4efe7 !important; }  /* warm inset, deeper than canvas */
.shadow-sm   { box-shadow: 0 1px 2px rgba(120,85,30,.06), 0 2px 6px rgba(120,85,30,.05) !important; }
.shadow-md   { box-shadow: 0 2px 6px rgba(120,85,30,.08), 0 6px 18px rgba(120,85,30,.08) !important; }
.shadow-lg   { box-shadow: 0 6px 16px rgba(120,85,30,.10), 0 16px 36px rgba(120,85,30,.10) !important; }
.border-gray-100 { border-color: #f1ebe1 !important; }  /* warm hairlines */
.border-gray-200 { border-color: #e9e1d4 !important; }

/* Mastery-ring unfilled track — themed via a CSS variable so the inline
   conic-gradient rings (dashboard, subjects, topic) pick up a warm light
   track in light mode and a dark one in dark mode instead of a fixed
   cool grey that glared on dark cards. */
:root     { --ring-track: #e9e1d4; }
html.dark { --ring-track: #44403c; }

/* ── U0: shared motion tokens for the Phase U signature-UI work ──
   Every U feature that animates anything reads these instead of picking
   its own duration/easing, so the whole set of "earned motion" moments
   (an answer landing, a stack settling, a session completing) feels like
   one consistent language rather than N features with N different timings.
   fast = a small state change (a toggle, a hover response); normal = a
   deliberate transition (a card entering/leaving); completion = the one
   longer beat reserved for a session/streak finishing. */
:root {
  --u-motion-fast: 120ms;
  --u-motion-normal: 220ms;
  --u-motion-completion: 420ms;
  --u-motion-ease: cubic-bezier(0.4, 0, 0.2, 1);
}
@media (prefers-reduced-motion: reduce) {
  :root {
    --u-motion-fast: 0ms;
    --u-motion-normal: 0ms;
    --u-motion-completion: 0ms;
  }
}

/* ── Hide chrome nav during a study/exam session ──────────────────
   The brief calls for a minimal top bar (progress + Exit only) while
   answering — the normal top nav and mobile bottom tab bar would duplicate
   that Exit affordance and add noise. Both already carry their own in-page
   "Exit"/"Finish early" control, so this is safe: nothing needed to leave
   the page becomes unreachable. Keyed off the existing data-study-page
   body attribute (already set by session_exercise.html/exam_question.html)
   rather than a new one. #main keeps its top padding so content doesn't
   jump when the fixed nav disappears; the nav-spacer div collapses instead. */
body[data-study-page="1"] > nav.bg-amber-700,
body[data-study-page="1"] > nav.calm-topbar,
body[data-study-page="1"] > nav.mobile-bottom-nav {
  display: none;
}
body[data-study-page="1"] > .h-\[52px\] { display: none; }
/* The site footer belongs to the same category as the tabs: "Delete
   account" three lines under a flashcard is noise on a screen whose whole
   job is one question. */
body[data-study-page="1"] footer { display: none; }
/* The calm shell's bar is nav.calm-topbar, not nav.bg-amber-700, so before it
   was listed above the spacer was hidden while the bar stayed — and the
   session's "Question 1 of 4" line and its Exit link rendered underneath the
   header, invisible. The calm bottom bar (nav.calm-navbar) deliberately stays:
   it is how you leave a session, and the companion stands on it. */

/* ── U1: focus-light study mode ──────────────────────────────────
   A quiet pool of light behind the study surface. The body attribute is
   emitted only while FEATURE_U1_FOCUS_LIGHT is enabled, and data-study-page
   is already owned by the established study templates. This is deliberately
   paint-only: the pseudo-element cannot receive input, while nav, progress,
   Exit, hints, Pagey and the mobile tab bar retain their layout and contrast. */
body[data-u-focus-light="1"][data-study-page="1"] #main {
  position: relative;
  isolation: isolate;
}
body[data-u-focus-light="1"][data-study-page="1"] #main::before {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  inset: -1.25rem -2rem 8%;
  border-radius: 2.5rem;
  opacity: .82;
  background: radial-gradient(ellipse 70% 48% at 50% 24%, rgba(255,255,255,.94) 0%, rgba(253,230,138,.16) 52%, transparent 78%);
  transition: opacity var(--u-motion-normal) var(--u-motion-ease);
}
body[data-u-focus-light="1"][data-study-page="1"] > nav.bg-amber-700,
body[data-u-focus-light="1"][data-study-page="1"] > nav.mobile-bottom-nav {
  filter: saturate(.94) brightness(.98);
  transition: filter var(--u-motion-normal) var(--u-motion-ease);
}
html.dark body[data-u-focus-light="1"][data-study-page="1"] #main::before {
  opacity: .72;
  background: radial-gradient(ellipse 72% 50% at 50% 24%, rgba(120,53,15,.22) 0%, rgba(245,158,11,.075) 52%, transparent 80%);
}
@media (max-width: 639px) {
  body[data-u-focus-light="1"][data-study-page="1"] #main::before {
    inset: -.75rem -.5rem 12%;
    border-radius: 1.75rem;
  }
}
@media print, (forced-colors: active) {
  body[data-u-focus-light="1"][data-study-page="1"] #main::before { content: none; }
  body[data-u-focus-light="1"][data-study-page="1"] > nav.bg-amber-700,
  body[data-u-focus-light="1"][data-study-page="1"] > nav.mobile-bottom-nav { filter: none; }
}

/* ── U2: tactile session stack ───────────────────────────────────
   Three layers are enough to suggest accumulated work without turning the
   progress row into another dashboard. It is aria-hidden and never carries
   the actual position; the established text/progressbar remain authoritative. */
.u-session-stack {
  position: relative;
  display: inline-block;
  flex: 0 0 26px;
  width: 26px;
  height: 22px;
  perspective: 80px;
}
.u-session-stack-layer {
  --u-stack-layer: 0;
  position: absolute;
  left: calc(var(--u-stack-layer) * 2px);
  bottom: calc(var(--u-stack-layer) * 2px);
  width: 21px;
  height: 14px;
  border: 1px solid #d6c7b1;
  border-radius: 4px;
  background: #fffdf8;
  box-shadow: 0 1px 2px rgba(120,85,30,.08);
  transform: translateY(0) rotateX(0deg);
  transition: transform var(--u-motion-normal) var(--u-motion-ease), border-color var(--u-motion-fast) var(--u-motion-ease), background-color var(--u-motion-fast) var(--u-motion-ease);
}
.u-session-stack.is-settling .u-session-stack-layer:last-child {
  animation: u-session-stack-settle var(--u-motion-completion) var(--u-motion-ease) both;
}
.u-session-stack-layer[data-outcome="again"] { border-color: #fca5a5; background: #fff7f7; }
.u-session-stack-layer[data-outcome="hard"] { border-color: #fdba74; background: #fffaf3; }
.u-session-stack-layer[data-outcome="good"] { border-color: #86efac; background: #f7fff9; }
.u-session-stack-layer[data-outcome="easy"] { border-color: #7dd3fc; background: #f5fcff; }
html.dark .u-session-stack-layer { border-color: #57534e; background: #292524; box-shadow: 0 1px 2px rgba(0,0,0,.28); }
html.dark .u-session-stack-layer[data-outcome="again"] { border-color: #991b1b; background: #2a1717; }
html.dark .u-session-stack-layer[data-outcome="hard"] { border-color: #9a3412; background: #2b1d13; }
html.dark .u-session-stack-layer[data-outcome="good"] { border-color: #166534; background: #14251a; }
html.dark .u-session-stack-layer[data-outcome="easy"] { border-color: #075985; background: #12232b; }
.session-progress-fill.u-session-progress-pulse { animation: u-session-progress-pulse var(--u-motion-completion) var(--u-motion-ease) both; }
@keyframes u-session-stack-settle {
  0% { transform: translateY(-8px) rotateX(8deg); opacity: .35; }
  70% { transform: translateY(1px) rotateX(-1deg); opacity: 1; }
  100% { transform: translateY(0) rotateX(0deg); opacity: 1; }
}
@keyframes u-session-progress-pulse {
  0%, 100% { filter: brightness(1); }
  45% { filter: brightness(1.2) saturate(1.15); }
}
@media (forced-colors: active) {
  .u-session-stack-layer { border-color: CanvasText; background: Canvas; box-shadow: none; }
  .u-session-stack-layer[data-outcome] { border-color: Highlight; background: Canvas; }
}

/* ── U3: deterministic subject visual fingerprints ───────────────
   Each motif (partials/subject_fingerprint.html) is a filled-and-outlined
   glyph: soft currentColor fills give it body, round strokes draw the edge.
   The SVG is decorative and text-free; the existing icon, name and colour
   remain the identity fallback. Panels use a quiet watermark, while beside
   a subject's name the glyph sits on a tinted disc, like an app icon, so a
   row of subjects reads as a set rather than as stray sketches. */
.subject-fingerprint {
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 3.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}
.subject-fingerprint .fp-badge { display: none; fill: currentColor; stroke: none; opacity: .14; }
.subject-fingerprint .fp-fill { fill: currentColor; stroke: none; opacity: .22; }
.subject-fingerprint .fp-solid { fill: currentColor; stroke: none; }
.subject-fingerprint .fp-thin { stroke-width: 2.4; opacity: .7; }
.subject-fingerprint-detail,
.subject-fingerprint-panel,
.subject-fingerprint-row,
.subject-fingerprint-inline { position: relative; isolation: isolate; }
.subject-fingerprint-detail > :not(.subject-fingerprint),
.subject-fingerprint-panel > :not(.subject-fingerprint),
.subject-fingerprint-row > :not(.subject-fingerprint) { position: relative; z-index: 1; }
/* Watermarks: the glyph alone, lighter strokes, no disc. */
.subject-fingerprint-detail > .subject-fingerprint,
.subject-fingerprint-panel > .subject-fingerprint,
.subject-fingerprint-row > .subject-fingerprint { stroke-width: 3; }
.subject-fingerprint-detail > .subject-fingerprint {
  position: absolute;
  z-index: 0;
  top: -12px;
  right: 10px;
  width: 148px;
  height: 148px;
  opacity: .075;
}
.subject-fingerprint-panel > .subject-fingerprint {
  position: absolute;
  z-index: 0;
  top: -24px;
  right: 46px;
  width: 136px;
  height: 136px;
  opacity: .058;
}
.subject-fingerprint-row { overflow: hidden; }
.subject-fingerprint-row > .subject-fingerprint {
  position: absolute;
  z-index: 0;
  right: 72px;
  top: 50%;
  width: 70px;
  height: 70px;
  opacity: .06;
  transform: translateY(-50%);
}
/* Repeated calm rows need an actual icon-sized identity mark, not a large
   watermark behind every line. Keeping it in normal flex flow means motifs
   can never overlap a neighbouring subject or sit beneath its label. The
   tinted disc is what makes it an icon rather than a line drawing. */
.subject-fingerprint-inline > .subject-fingerprint {
  position: static;
  z-index: 1;
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  opacity: 1;
}
.subject-fingerprint-inline > .subject-fingerprint .fp-badge { display: block; }
html.dark .subject-fingerprint-detail > .subject-fingerprint { opacity: .11; }
html.dark .subject-fingerprint-panel > .subject-fingerprint,
html.dark .subject-fingerprint-row > .subject-fingerprint { opacity: .09; }
/* The accents are 600-shade hexes chosen for light paper; on the dark
   surface they are lifted a little so the disc and glyph stay vivid. */
html.dark .subject-fingerprint-inline > .subject-fingerprint { opacity: 1; filter: brightness(1.28) saturate(1.05); }
html.dark .subject-fingerprint-inline > .subject-fingerprint .fp-badge { opacity: .2; }
@media (max-width: 639px) {
  .subject-fingerprint-detail > .subject-fingerprint { right: -8px; width: 112px; height: 112px; opacity: .055; }
  .subject-fingerprint-panel > .subject-fingerprint { right: 34px; width: 104px; height: 104px; opacity: .045; }
  .subject-fingerprint-row > .subject-fingerprint { right: 58px; width: 58px; height: 58px; opacity: .045; }
  .subject-fingerprint-inline > .subject-fingerprint { width: 34px; height: 34px; flex-basis: 34px; }
}
@media (forced-colors: active) {
  .subject-fingerprint { display: none; }
}

/* Warm text selection + a quiet brand touch */
::selection { background: #fde68a; color: #4a2c00; }

/* Top nav: a touch of depth so content scrolls cleanly under it. Solid,
   not glass — as an installed PWA the OS title bar sits solid right
   above this nav, and a translucent bar created a visible seam against
   it. */
nav.bg-amber-700 {
  background-image: linear-gradient(180deg, #b45309 0%, #a8470a 100%);
  box-shadow: 0 1px 0 rgba(0,0,0,.06), 0 4px 16px rgba(124,60,0,.14);
}
/* Morphic desktop nav pill's active item — a dedicated class rather than
   bg-white/text-amber-800 because those get globally repurposed by the
   dark-mode card overrides below, which would mute this pill to grey.
   The amber nav bar itself never changes with theme, so this shouldn't
   either. */
.nav-pill-active { background-color: #fff; color: #92400e; }
/* Mobile bottom-tab active pill — translucent rather than bg-amber-100 so
   it stays visible over both the light tab-bar background and its
   dark-mode override (which remaps bg-amber-100 to a near-black shade
   for unrelated card theming, making a solid fill invisible there). */
.tab-pill-active { background-color: rgba(217,119,6,.14); }
html.dark .tab-pill-active { background-color: rgba(251,191,36,.16); }
/* Mobile bottom tab bar + action bars share one measured edge so sticky
   CTAs sit flush above the nav, including iPhone safe-area space. */
:root { --mobile-nav-height: calc(4rem + env(safe-area-inset-bottom, 0px)); }
nav.mobile-bottom-nav {
  background-color: rgba(255,255,255,.72) !important;
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  min-height: var(--mobile-nav-height);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  box-shadow: 0 -2px 14px rgba(120,85,30,.07);
}
/* Higher specificity than the generic html.dark .bg-white card override,
   so the bottom tab bar stays glass instead of a solid dark card. */
html.dark nav.mobile-bottom-nav { background-color: rgba(28,25,23,.72) !important; }
.mobile-action-bar { bottom: var(--mobile-nav-height); }
@media (min-width: 768px) {
  .mobile-action-bar { bottom: 0; }
}

/* The one subject card marked as today's most useful next step (Library
   page) — a warmer border/shadow than the default .app-panel, using the
   same amber the rest of the app already treats as "this matters" (kicker,
   primary button) rather than introducing a new accent colour. */
.subject-card-primary {
  /* #f3ca85 measured under 1.6:1 against the card's #fffdf8 background --
     well short of WCAG 1.4.11's 3:1 minimum for a UI-component boundary.
     amber-600 clears it (~3.1:1) while staying the same hue family as the
     rest of the app's "this matters" accent. The border is also no longer
     the only signal -- see the "Most useful next step" pill next to the
     subject name (subject_list.html) for the WCAG 1.4.1 (use of color) side
     of the same finding. */
  border-color: #d97706 !important;
  box-shadow: 0 2px 8px rgba(180,83,9,.12), 0 8px 20px rgba(180,83,9,.10);
}
html.dark .subject-card-primary {
  /* #92400e measured ~2.1:1 against the dark card's #292524 background.
     amber-500 clears 3:1 by a wide margin and is already used elsewhere in
     dark mode (.app-kicker) for the same accent role. */
  border-color: #f59e0b !important;
  box-shadow: 0 2px 8px rgba(0,0,0,.4), 0 8px 20px rgba(180,83,9,.14);
}

/* Gentle hover lift for clickable cards (opt-in via .lift) */
.lift:hover { border-color: #e3c9a0; box-shadow: 0 4px 10px rgba(120,85,30,.10), 0 12px 28px rgba(120,85,30,.10); }
@media (prefers-reduced-motion: no-preference) {
  .lift { transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; }
  .lift:hover { transform: translateY(-2px); }
  a[href], button, summary {
    transition-property: color, background-color, border-color, box-shadow, transform, opacity;
    transition-duration: .16s;
    transition-timing-function: ease;
  }
  /* A small tactile press on taps/clicks across the app */
  button:active, a[href]:active { transform: translateY(0.5px); }
}

/* One consistent, keyboard-only focus ring (inputs keep their own amber ring) */
a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid #d97706; outline-offset: 2px; border-radius: 6px;
}
html.dark a:focus-visible, html.dark button:focus-visible,
html.dark summary:focus-visible, html.dark [tabindex]:focus-visible { outline-color: #f59e0b; }

.word-span { cursor: pointer; border-bottom: 1px dashed #d97706; }
.word-span:hover { background-color: #fffbeb; border-radius: 2px; }
#tooltip-box { position: fixed; z-index: 50; }
.subject-accent-line {
  height: 3px;
  width: min(8.5rem, 38vw);
  border-radius: 999px;
  background: var(--subject-accent, #d97706);
}
.subject-identity-icon {
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.45), 0 8px 18px rgba(120,85,30,.08);
}

/* On-brand checkboxes/radios in both themes (no Tailwind forms plugin loaded,
   so the native tick wouldn't otherwise pick up our amber colour). */
input[type=checkbox], input[type=radio]            { accent-color: #d97706; }
html.dark input[type=checkbox], html.dark input[type=radio] { accent-color: #f59e0b; }

/* Review-density slider (subject settings) — a warm gradient track that
   reads "sparser → denser" left to right instead of a stock grey range
   input, so the density change feels tangible as you drag it. */
input.retention-slider {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 8px; border-radius: 9999px; outline: none;
  background: linear-gradient(90deg, #fde68a 0%, #f59e0b 55%, #b45309 100%);
}
input.retention-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px; border-radius: 50%; margin-top: -6px;
  background: #fff; border: 3px solid #b45309; cursor: pointer;
  box-shadow: 0 1px 3px rgba(120,85,30,.35);
}
input.retention-slider::-moz-range-track {
  height: 8px; border-radius: 9999px;
  background: linear-gradient(90deg, #fde68a 0%, #f59e0b 55%, #b45309 100%);
}
input.retention-slider::-moz-range-thumb {
  width: 20px; height: 20px; border-radius: 50%; border: 3px solid #b45309;
  background: #fff; cursor: pointer; box-shadow: 0 1px 3px rgba(120,85,30,.35);
}
html.dark input.retention-slider::-webkit-slider-thumb,
html.dark input.retention-slider::-moz-range-thumb { background: #292524; border-color: #f59e0b; }

/* ── Dark mode overrides ── */
html.dark body                          { background-color: #1c1917 !important; color: #fafaf9 !important; }
html.dark .bg-white                     { background-color: #292524 !important; }
html.dark .bg-gray-50                   { background-color: #1c1917 !important; }
html.dark .bg-gray-100                  { background-color: #44403c !important; }
html.dark .bg-gray-200                  { background-color: #57534e !important; }
html.dark .bg-white\/95                 { background-color: rgba(41,37,36,0.95) !important; }
/* Opacity-modified utilities compile to their own selector (e.g.
   .bg-amber-50\/70), so they need their own override — the plain
   .bg-amber-50 rule below doesn't match them. Without this, "tip" boxes
   built from bg-amber-50/NN or bg-white/NN stayed light in dark mode
   while the text-gray-* rules above still forced their text near-white,
   making them nearly unreadable (amber-tinted "Why it matters"/"Study
   science" cards and similar callouts across the app). */
html.dark .bg-amber-50\/70,
html.dark .bg-amber-50\/60             { background-color: #1c0f00 !important; }
html.dark .bg-white\/75                 { background-color: #292524 !important; }
html.dark .bg-amber-50                  { background-color: #1c0f00 !important; }
html.dark .bg-amber-100                 { background-color: #2d1900 !important; }
html.dark .bg-red-50                    { background-color: #1c0606 !important; }
html.dark .bg-green-50                  { background-color: #052e16 !important; }
html.dark .text-gray-900,
html.dark .text-gray-800               { color: #fafaf9 !important; }
html.dark .text-gray-700               { color: #e7e5e4 !important; }
html.dark .text-gray-600               { color: #d6d3d1 !important; }
html.dark .text-gray-500               { color: #a8a29e !important; }
html.dark .text-gray-400               { color: #78716c !important; }
/* Catch-all: keep all headings legible in dark mode (colored headings below override this) */
html.dark h1, html.dark h2, html.dark h3, html.dark h4 { color: #fafaf9; }
html.dark .border-gray-100,
html.dark .border-gray-200             { border-color: #44403c !important; }
html.dark .border-gray-300             { border-color: #57534e !important; }
html.dark .border-dashed               { border-color: #57534e !important; }
html.dark .border-amber-200            { border-color: #92400e !important; }
html.dark .border-red-100,
html.dark .border-red-200              { border-color: #7f1d1d !important; }
html.dark .border-green-200            { border-color: #166534 !important; }
html.dark .bg-blue-50                  { background-color: #0c1a2e !important; }
html.dark .border-blue-200             { border-color: #1e3a5f !important; }
html.dark .text-blue-800              { color: #93c5fd !important; }

/* Subject accent-colour badges (bg-{color}-100 + text-{color}-700, see
   Subject.COLOR_OPTIONS in models.py — icons on dashboard/subject_list/etc.
   use these dynamically, so every palette option needs a dark pairing here). */
html.dark .bg-orange-100               { background-color: #431407 !important; }
html.dark .text-orange-700             { color: #fdba74 !important; }
html.dark .bg-rose-100                 { background-color: #4c0519 !important; }
html.dark .text-rose-700               { color: #fda4af !important; }
html.dark .bg-pink-100                 { background-color: #500724 !important; }
html.dark .text-pink-700               { color: #f9a8d4 !important; }
html.dark .bg-violet-100               { background-color: #2e1065 !important; }
html.dark .text-violet-700             { color: #c4b5fd !important; }
html.dark .bg-indigo-100               { background-color: #1e1b4b !important; }
html.dark .text-indigo-700             { color: #a5b4fc !important; }
html.dark .bg-blue-100                 { background-color: #172554 !important; }
html.dark .text-blue-700               { color: #93c5fd !important; }
html.dark .bg-sky-100                  { background-color: #082f49 !important; }
html.dark .text-sky-700                { color: #7dd3fc !important; }
html.dark .bg-cyan-100                 { background-color: #083344 !important; }
html.dark .text-cyan-700               { color: #67e8f9 !important; }
html.dark .bg-teal-100                 { background-color: #042f2e !important; }
html.dark .text-teal-700               { color: #5eead4 !important; }
html.dark .bg-emerald-100              { background-color: #022c22 !important; }
html.dark .text-emerald-700            { color: #6ee7b7 !important; }
html.dark .bg-slate-100                { background-color: #0f172a !important; }
html.dark .text-slate-700              { color: #cbd5e1 !important; }
html.dark .divide-gray-50 > :not(:last-child),
html.dark .divide-gray-100 > :not(:last-child) { border-color: #44403c !important; }
html.dark .text-amber-800,
html.dark .text-amber-700,
html.dark .text-amber-900,
html.dark .text-amber-950              { color: #fbbf24 !important; }
/* Same opacity-selector gap as the bg-amber-50/NN boxes above, but for the
   dark, high-contrast-on-paper caption text (text-amber-900/70 etc.) used
   inside them — those captions were staying near-black-on-near-black. */
html.dark .text-amber-950\/80,
html.dark .text-amber-900\/75,
html.dark .text-amber-900\/70,
html.dark .text-amber-800\/80,
html.dark .text-amber-800\/70,
html.dark .text-amber-700\/70          { color: #fbbf24 !important; }
html.dark .bg-amber-50\/40              { background-color: #1c0f00 !important; }
html.dark .bg-white\/70                 { background-color: #292524 !important; }
html.dark .text-amber-600              { color: #d97706 !important; }
html.dark .text-amber-500              { color: #f59e0b !important; }
html.dark .text-amber-400              { color: #fbbf24 !important; }
html.dark .text-red-700                { color: #f87171 !important; }
html.dark .text-red-600                { color: #f87171 !important; }
html.dark .text-red-500                { color: #f87171 !important; }
html.dark .text-red-400                { color: #f87171 !important; }
html.dark .text-green-800              { color: #4ade80 !important; }
html.dark .text-green-700              { color: #4ade80 !important; }
html.dark .text-green-600              { color: #4ade80 !important; }
html.dark .shadow-sm,
html.dark .shadow-md                   { box-shadow: 0 1px 3px 0 rgba(0,0,0,0.5) !important; }
/* Calm fields (.calm-field, .calm-box, .calm-input) have their own dark
   surface in calm.css; this grey box only stands in for unstyled fields. */
html.dark textarea:not(.calm-field):not(.calm-box):not(.calm-input),
html.dark input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=hidden]):not(:where(.calm-auth .calm-field)):not(.calm-field):not(.calm-box):not(.calm-input),
html.dark select:not(.calm-field):not(.calm-box):not(.calm-input) { background-color: #44403c !important; border-color: #57534e !important; color: #fafaf9 !important; }
html.dark textarea:not(.calm-field):not(.calm-box):not(.calm-input)::placeholder,
html.dark input:not(.calm-field):not(.calm-box):not(.calm-input)::placeholder { color: #78716c !important; }
html.dark pre                           { background-color: #1c1917 !important; color: #d6d3d1 !important; }
html.dark .word-span:hover             { background-color: #1c0f00 !important; }
html.dark #tooltip-box                 { background-color: #292524 !important; border-color: #44403c !important; color: #fafaf9 !important; }
html.dark .hover\:bg-gray-50:hover     { background-color: #44403c !important; }
html.dark .hover\:bg-gray-100:hover    { background-color: #44403c !important; }
html.dark .hover\:bg-gray-200:hover    { background-color: #57534e !important; }
html.dark .hover\:bg-amber-50:hover    { background-color: #1c0f00 !important; }
/* "Look it up" button (partials/lookup_button.html) */
html.dark .hover\:bg-blue-50:hover     { background-color: #0c1a2e !important; }

/* ── Selectable option cards (a checkbox/radio styled as a card) ──
   Defined for both themes so the checked highlight always keeps text
   legible. (Tailwind's has-[:checked] arbitrary variant isn't covered by
   the dark overrides above, which would leave white text on a light card.) */
.opt-card:has(:checked)          { border-color: #f59e0b; background-color: #fffbeb; }
html.dark .opt-card:has(:checked) { border-color: #b45309; background-color: #2d1900; }

/* ── Heatmap squares ── */
.hm0 { background-color: #e7e5e4; }
.hm1 { background-color: #fcd34d; }
.hm2 { background-color: #d97706; }
.hm3 { background-color: #92400e; }
html.dark .hm0 { background-color: #44403c; }
html.dark .hm1 { background-color: #78350f; }
html.dark .hm2 { background-color: #d97706; }
html.dark .hm3 { background-color: #fbbf24; }
html.dark .toast { background-color: #292524 !important; border-color: #44403c !important; color: #fafaf9 !important; }
/* Achievement toasts get a celebratory amber tint instead of the neutral
   toast background — declared after .toast so it wins the (equal-specificity,
   both !important) cascade in both themes. */
.toast-achievement { background-color: #fffbeb !important; border-color: #fcd34d !important; }
html.dark .toast-achievement { background-color: #2d1900 !important; border-color: #92400e !important; }

/* ── Unified app theme layer ──
   Most templates still use the established Tailwind "white rounded card"
   pattern. This maps that common structure onto the newer warm panel
   language used by the dashboard and copy-paste flows, so older pages feel
   part of the same product without rewriting every workflow template. */
.bg-white.rounded-xl.border.border-gray-200,
.bg-white.rounded-2xl.border.border-gray-200,
.card-enter.bg-white.border.border-gray-200,
.menu-panel.bg-white.border.border-gray-200 {
  background-color: #fffdf8;
  border-color: #e9e1d4;
  border-radius: 14px;
}
.bg-white.rounded-xl.border.border-gray-200.shadow-sm,
.bg-white.rounded-2xl.border.border-gray-200.shadow-sm,
.card-enter.bg-white.border.border-gray-200.shadow-sm,
.menu-panel.bg-white.border.border-gray-200 {
  box-shadow: 0 1px 2px rgba(120,85,30,.06), 0 2px 8px rgba(120,85,30,.05);
}
.bg-white.rounded-xl.border.border-gray-200.shadow-sm.overflow-hidden,
.bg-white.rounded-2xl.border.border-gray-200.shadow-sm.overflow-hidden {
  overflow: hidden;
}
.bg-white.rounded-xl.border.border-dashed,
.bg-gray-50.border.border-dashed {
  background-color: #fffaf2;
  border-color: #dfc9a8;
}
.text-xs.font-medium.text-gray-500.uppercase.tracking-wide,
.text-xs.font-semibold.text-gray-500.uppercase.tracking-wide,
.text-\[11px\].font-medium.text-gray-400.uppercase.tracking-wide,
.text-\[11px\].font-semibold.text-gray-400.uppercase.tracking-wide {
  color: #d97706;
  font-weight: 800;
  letter-spacing: .12em;
}
html.dark .bg-white.rounded-xl.border.border-gray-200,
html.dark .bg-white.rounded-2xl.border.border-gray-200,
html.dark .card-enter.bg-white.border.border-gray-200,
html.dark .menu-panel.bg-white.border.border-gray-200 {
  background-color: #292524 !important;
  border-color: #44403c !important;
}
html.dark .bg-white.rounded-xl.border.border-gray-200.shadow-sm,
html.dark .bg-white.rounded-2xl.border.border-gray-200.shadow-sm,
html.dark .card-enter.bg-white.border.border-gray-200.shadow-sm,
html.dark .menu-panel.bg-white.border.border-gray-200 {
  box-shadow: 0 1px 3px rgba(0,0,0,.5) !important;
}
html.dark .bg-white.rounded-xl.border.border-dashed,
html.dark .bg-gray-50.border.border-dashed {
  background-color: #292524 !important;
  border-color: #57534e !important;
}
html.dark .text-xs.font-medium.text-gray-500.uppercase.tracking-wide,
html.dark .text-xs.font-semibold.text-gray-500.uppercase.tracking-wide,
html.dark .text-\[11px\].font-medium.text-gray-400.uppercase.tracking-wide,
html.dark .text-\[11px\].font-semibold.text-gray-400.uppercase.tracking-wide {
  color: #f59e0b !important;
}

/* ── Named app primitives ──
   Same visual result as the compound-selector mapping above, but as
   classes a template can opt into directly instead of re-deriving the
   "warm panel" look from a specific Tailwind utility combo. New/edited
   templates should reach for these; the mapping above stays so older,
   unconverted templates keep matching without a rewrite. */
.app-panel {
  background-color: #fffdf8;
  border: 1px solid #e9e1d4;
  border-radius: 1rem;
  box-shadow: 0 1px 2px rgba(120,85,30,.06), 0 2px 8px rgba(120,85,30,.05);
}
html.dark .app-panel { background-color: #292524; border-color: #44403c; box-shadow: 0 1px 3px rgba(0,0,0,.5); }

.app-panel-soft {
  background-color: #f4efe7;
  border: 1px solid #eee1cc;
  border-radius: .75rem;
}
html.dark .app-panel-soft { background-color: #1c1917; border-color: #44403c; }

.app-kicker { font-size: 11px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: #92400e; }
html.dark .app-kicker { color: #f59e0b; }

/* Small rounded metadata chip — daily-goal %, streak count, confidence
   read, coach-note action label. */
.app-pill {
  display: inline-flex; align-items: center; white-space: nowrap;
  border-radius: 9999px; border: 1px solid #fde3b8; background-color: #fffaf0;
  color: #b45309; font-size: 12px; font-weight: 700; padding: .375rem .75rem;
}
html.dark .app-pill { background-color: #2d1900; border-color: #92400e; color: #fbbf24; }
/* Quieter variant for a non-priority chip (e.g. a link-styled pill). */
.app-pill-quiet { border-color: #e9e1d4; background-color: #f4efe7; color: #57534e; }
html.dark .app-pill-quiet { border-color: #44403c; background-color: #1c1917; color: #d6d3d1; }
/* Wrapping variant for a pill whose text isn't a short fixed label (a
   topic name, a sentence-y coach note) — without this, .app-pill's
   nowrap forces the pill (and everything upstream that lacks its own
   min-w-0) wider than the viewport instead of just wrapping to a second
   line, clipped by the nearest overflow:hidden ancestor. */
.app-pill-wrap { white-space: normal; text-align: left; }

/* Colour + weight only, so a button keeps its own layout utilities
   (padding, rounding, width, min-height) and just opts into the shared
   primary/secondary skin. */
.app-primary-btn { background-color: #b45309; color: #fff; font-weight: 700; }
.app-primary-btn:hover { background-color: #92400e; }
html.dark .app-primary-btn:hover { background-color: #92400e; }
.app-secondary-btn { background-color: #fff; color: #b45309; font-weight: 700; border: 1px solid #fde3b8; }
.app-secondary-btn:hover { background-color: #fffaf0; }
html.dark .app-secondary-btn { background-color: #292524; border-color: #57534e; color: #fbbf24; }
html.dark .app-secondary-btn:hover { background-color: #33302c; }

.app-quiet-btn {
  background-color: transparent;
  color: #78716c;
  font-weight: 700;
  border: 1px solid #e9e1d4;
}
.app-quiet-btn:hover { background-color: #f4efe7; color: #92400e; border-color: #dfc9a8; }
html.dark .app-quiet-btn { color: #d6d3d1; border-color: #44403c; }
html.dark .app-quiet-btn:hover { background-color: #33302c; color: #fbbf24; border-color: #57534e; }

.app-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.75rem; min-height: 2.75rem; border-radius: .75rem;
  color: #78716c;
}
.app-icon-btn:hover { background-color: #f4efe7; color: #92400e; }
html.dark .app-icon-btn { color: #a8a29e; }
html.dark .app-icon-btn:hover { background-color: #33302c; color: #fbbf24; }

.app-danger-btn {
  background-color: #fef2f2;
  color: #dc2626;
  border: 1px solid #fecaca;
  font-weight: 700;
}
.app-danger-btn:hover { background-color: #fee2e2; color: #b91c1c; }
html.dark .app-danger-btn { background-color: #1c0606; border-color: #7f1d1d; color: #f87171; }
html.dark .app-danger-btn:hover { background-color: #2b0b0b; color: #fca5a5; }

.app-stat-card {
  background-color: #fffdf8;
  border: 1px solid #e9e1d4;
  border-radius: .875rem;
  box-shadow: 0 1px 2px rgba(120,85,30,.04);
}
html.dark .app-stat-card { background-color: #292524; border-color: #44403c; box-shadow: 0 1px 3px rgba(0,0,0,.4); }

.app-menu-panel {
  background-color: #fffdf8;
  border: 1px solid #e9e1d4;
  border-radius: .875rem;
  box-shadow: 0 8px 24px rgba(80,55,20,.12);
}
html.dark .app-menu-panel { background-color: #292524; border-color: #44403c; box-shadow: 0 12px 28px rgba(0,0,0,.45); }

.app-action-bar {
  border-top: 1px solid #f1ebe1;
  background: linear-gradient(180deg, rgba(255,253,248,.88), #fffdf8);
}
html.dark .app-action-bar {
  border-top-color: #44403c;
  background: linear-gradient(180deg, rgba(41,37,36,.88), #292524);
}

.app-rhythm-divider { display: flex; align-items: flex-end; gap: 6px; height: 12px; opacity: .42; }
.app-rhythm-divider i { display: block; width: 2px; border-radius: 2px; background: #d97706; }
.app-rhythm-divider i:nth-child(1){ height: 12px; opacity: .9; }
.app-rhythm-divider i:nth-child(2){ height: 10px; opacity: .7; margin-left: 2px; }
.app-rhythm-divider i:nth-child(3){ height: 8px; opacity: .52; margin-left: 6px; }
.app-rhythm-divider i:nth-child(4){ height: 6px; opacity: .34; margin-left: 10px; }
.app-rhythm-divider i:nth-child(5){ height: 5px; opacity: .2; margin-left: 15px; }
html.dark .app-rhythm-divider i { background: #f59e0b; }

/* Slightly stronger press/lift than the app-wide default (see the
   generic a[href]/button rule above) — reserved for a page's single
   hero CTA, matching landing's primary-button feel. */
.btn-press { transition: transform .15s ease, box-shadow .15s ease, background-color .15s ease; }
.btn-press:hover { transform: translateY(-1px); }
.btn-press:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) {
  .btn-press { transition: none; }
  .btn-press:hover { transform: none; }
}

/* ── Rendered notes (prose) in dark mode ── */
/* Override the Typography plugin's own colour variables so EVERY element
   (body, headings, bold, links, list markers, lead text, captions, table
   header text, borders…) flips to light — not just the few we list. */
html.dark #notes-content,
html.dark #read-mode-content {
  --tw-prose-body:          #e7e5e4;
  --tw-prose-headings:      #fafaf9;
  --tw-prose-lead:          #d6d3d1;
  --tw-prose-links:         #fbbf24;
  --tw-prose-bold:          #fafaf9;
  --tw-prose-counters:      #a8a29e;
  --tw-prose-bullets:       #78716c;
  --tw-prose-hr:            #44403c;
  --tw-prose-quotes:        #e7e5e4;
  --tw-prose-quote-borders: #57534e;
  --tw-prose-captions:      #a8a29e;
  --tw-prose-code:          #fcd34d;
  --tw-prose-pre-code:      #d6d3d1;
  --tw-prose-pre-bg:        #1c1917;
  --tw-prose-th-borders:    #57534e;
  --tw-prose-td-borders:    #44403c;
  color: #e7e5e4;
}
/* Headings inside notes keep the warm serif */
#notes-content h1, #notes-content h2, #notes-content h3, #notes-content h4,
#read-mode-content h1, #read-mode-content h2, #read-mode-content h3, #read-mode-content h4 { font-family: 'Lora', Georgia, serif; }

/* ── Shared study-card motion ──
   A gentle rise-in when a card/question appears, and a soft pop when an
   answer is revealed. Defined only inside the no-preference query so users
   who ask for reduced motion get the cards instantly (never hidden). */
@media (prefers-reduced-motion: no-preference) {
  @keyframes card-enter { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
  .card-enter { animation: card-enter .28s cubic-bezier(.4,.2,.2,1) both; }
  @keyframes answer-pop { 0% { opacity: 0; transform: scale(.97); } 60% { transform: scale(1.01); } 100% { opacity: 1; transform: scale(1); } }
  .answer-pop { animation: answer-pop .3s ease-out both; }
}

/* ── Page + list entrance motion ──
   Every page fades gently up as it opens (feels calm on app launch and
   when moving between pages); opt-in .stagger-children makes a list or
   grid cascade its items in. All gated behind no-preference so reduced
   motion users see everything instantly. */
@media (prefers-reduced-motion: no-preference) {
  /* opacity-only: a transform here would make #main a containing block for
     any position:fixed descendant (e.g. session_done.html's share modal),
     which breaks its inset-0 sizing to #main's box instead of the viewport
     for as long as this animation is running. */
  @keyframes page-enter { from { opacity: 0; } to { opacity: 1; } }
  #main { animation: page-enter .42s cubic-bezier(.4,.2,.2,1) both; }

  .stagger-children > *            { animation: card-enter .4s cubic-bezier(.4,.2,.2,1) both; }
  .stagger-children > *:nth-child(1) { animation-delay: .04s; }
  .stagger-children > *:nth-child(2) { animation-delay: .10s; }
  .stagger-children > *:nth-child(3) { animation-delay: .16s; }
  .stagger-children > *:nth-child(4) { animation-delay: .22s; }
  .stagger-children > *:nth-child(5) { animation-delay: .28s; }
  .stagger-children > *:nth-child(6) { animation-delay: .34s; }
  .stagger-children > *:nth-child(n+7) { animation-delay: .40s; }

  /* Dashboard daily-goal ring sweeps up to its value on load */
  @keyframes ring-fill { from { stroke-dashoffset: 175.93; } }
  .goal-ring { animation: ring-fill 1.05s cubic-bezier(.4,.2,.2,1) both; }
}

/* ── Scroll-reveal ──
   Opt-in .reveal fades a section up into place the first time it's
   scrolled into view (driven by the shared IntersectionObserver near the
   end of <body>). Reserve it for below-the-fold content — anything
   visible on initial load should render immediately, not wait on JS.
   Reduced-motion users see everything at full opacity from the start. */
.reveal { opacity: 0; transform: translateY(14px); }
.reveal.reveal-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: no-preference) {
  .reveal { transition: opacity .5s cubic-bezier(.4,.2,.2,1), transform .5s cubic-bezier(.4,.2,.2,1); }
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
}

/* ── Progress-bar fill ──
   Opt-in .fill-bar (paired with data-fill-to="<0-100>") for a bar whose
   width is already correct in the server-rendered HTML — the JS below
   briefly resets it to 0 and back only once its card becomes visible, so
   it reads as "filling in" instead of a hard cut. If JS never runs, the
   bar simply keeps its correct SSR width; correctness never depends on
   the animation firing. */
@media (prefers-reduced-motion: no-preference) {
  .fill-bar { transition: width .6s cubic-bezier(.4,.2,.2,1); }
}

/* ── Top loading bar ──
   A slim amber bar that climbs during page navigations and HTMX requests
   (hints, translations, grading), so slow actions never feel frozen.
   Driven by the small script near the end of <body>. */
#progressbar {
  position: fixed; top: 0; left: 0; height: 2px; width: 0; z-index: 60;
  background: linear-gradient(90deg, #f59e0b, #d97706);
  box-shadow: 0 0 8px rgba(217,119,6,.55);
  opacity: 0; pointer-events: none;
  transition: width .2s ease, opacity .3s ease;
}
html.dark #progressbar { background: linear-gradient(90deg, #fbbf24, #f59e0b); }

#submit-loading-cover {
  position: fixed;
  inset: 0;
  z-index: 55;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(248, 243, 234, .72);
  backdrop-filter: blur(8px);
}
#submit-loading-cover.visible { display: flex; }
html.dark #submit-loading-cover { background: rgba(12, 10, 9, .68); }
.submit-loading-card {
  width: min(100%, 24rem);
  background-color: #fffdf8;
  border: 1px solid #e9e1d4;
  border-radius: 1rem;
  box-shadow: 0 14px 38px rgba(120,85,30,.16);
  padding: 1.25rem;
}
html.dark .submit-loading-card {
  background-color: #292524;
  border-color: #44403c;
  box-shadow: 0 18px 42px rgba(0,0,0,.45);
}
.submit-loading-spinner {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 9999px;
  border: 3px solid #fde3b8;
  border-top-color: #d97706;
  animation: spin 1s linear infinite;
}
html.dark .submit-loading-spinner { border-color: #44403c; border-top-color: #f59e0b; }
@media (prefers-reduced-motion: reduce) {
  .submit-loading-spinner { animation: none; }
}

/* ── Smooth cross-document page transitions ──
   Chromium cross-fades between same-origin pages on navigation; browsers
   without support simply ignore this and keep the existing page-enter fade.
   Kept gentle and quick so it feels like the app settling, not sliding. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation-duration: .18s; animation-timing-function: ease; }
::view-transition-new(root) { animation-duration: .28s; animation-timing-function: cubic-bezier(.4,.2,.2,1); }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* ── Toast entrance ── a soft rise-in so status messages feel alive ── */
@media (prefers-reduced-motion: no-preference) {
  @keyframes toast-in { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }
  .toast { animation: toast-in .32s cubic-bezier(.4,.2,.2,1) both; }
}

/* ── Celebration / delete splash (Lottie) ──
   A full-screen cover painted in the app's canvas colour, rendered
   server-side so it hides the page from the very FIRST paint — the clip is
   the only thing on screen. When the clip finishes, the cover fades away and
   the page underneath (the exercise, the dashboard, the updated list) is
   revealed. The CSS keyframe is a guaranteed reveal even if the player never
   loads; the script normally reveals sooner (the moment the clip ends) by
   cancelling this animation. */
.anim-cover {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center;
  background: #faf7f2;
  animation: anim-cover-hide 4s ease forwards;
}
html.dark .anim-cover { background: #1c1917; }
.anim-cover ~ .anim-cover { display: none; }   /* only the first one ever shows */
.anim-box { width: 220px; height: 220px; }
.anim-box svg { width: 100%; height: 100%; }
/* The static icon is only a fallback for when the player can't load — hidden
   by default so the normal path shows JUST the moving clip (no tick flash). */
.anim-fallback { opacity: 0; }
@keyframes anim-cover-hide {
  0%, 75% { opacity: 1; }
  100% { opacity: 0; visibility: hidden; }
}
/* Reduced motion: no splash at all — the page is shown immediately. */
@media (prefers-reduced-motion: reduce) {
  .anim-cover { display: none !important; }
}

/* ── Flashcard flip ── */
.flip-card { perspective: 1200px; }
.flip-inner { display: grid; transition: transform .55s cubic-bezier(.4,.2,.2,1); transform-style: preserve-3d; }
.flip-front, .flip-back { grid-area: 1 / 1; -webkit-backface-visibility: hidden; backface-visibility: hidden; }
.flip-back { transform: rotateY(180deg); }
.flip-card.flipped .flip-inner { transform: rotateY(180deg); }
.flip-card .flip-back { pointer-events: none; }
.flip-card.flipped .flip-back { pointer-events: auto; }
.flip-card.flipped .flip-front { pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .flip-inner { transition: none; } }

/* ── Windows High Contrast / forced colours ──
   Decorative fills and shadows may disappear in forced-colour mode, so the
   selected state, card edges, controls and keyboard focus must still be
   distinguishable using system colours.  Icons use currentColor throughout
   the app and therefore follow ButtonText/LinkText automatically. */
@media (forced-colors: active) {
  .app-panel,
  .app-card,
  .app-stat-card,
  .app-menu-panel,
  .app-primary-btn,
  .app-secondary-btn,
  .app-quiet-btn,
  .app-danger-btn,
  .app-icon-btn,
  input,
  select,
  textarea {
    border: 1px solid CanvasText;
  }

  .app-primary-btn,
  .app-secondary-btn,
  .app-quiet-btn,
  .app-danger-btn,
  .app-icon-btn {
    background: ButtonFace;
    color: ButtonText;
  }

  .nav-pill-active,
  .tab-pill-active,
  [aria-current="page"],
  [aria-selected="true"] {
    outline: 2px solid Highlight;
    outline-offset: -2px;
  }

  a:focus-visible,
  button:focus-visible,
  summary:focus-visible,
  input:focus-visible,
  select:focus-visible,
  textarea:focus-visible,
  [tabindex]:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }

  .subject-accent-line,
  .app-rhythm-divider,
  .app-rhythm-divider i {
    forced-color-adjust: none;
    background: CanvasText;
  }
}

/* ── Kebab dropdown menus (<details>) ── */
summary::-webkit-details-marker { display: none; }

/* ════════════════════════════════════════════════════════════════
   CALM DESIGN TOKENS — additive only, do not edit anything above.
   A small, quiet palette for the "calm minimalist companion" pass:
   warm parchment/stone surfaces, charcoal text, soft hairline
   borders, and one accent per subject family (mapped from
   Subject.SUBJECT_TYPES in core/models.py). Consumers should read
   these custom properties rather than hard-coding hex values, so a
   later palette tweak stays a one-file change. Motion for anything
   built on these tokens should reuse --u-motion-fast/--u-motion-ease
   (defined above) rather than inventing new durations.
   ════════════════════════════════════════════════════════════════ */
:root {
  /* Surfaces — warm parchment/stone, quieter than the existing ivory
     canvas above (#faf7f2); for calm-pass panels that want to sit
     visually "behind" ordinary white cards. */
  --calm-bg-parchment: #f6f1e7;
  --calm-bg-parchment-deep: #ece4d3;
  --calm-bg-stone: #f2ede4;
  --calm-surface: #ffffff;

  /* Text */
  --calm-text: #292524;       /* charcoal, matches --companion-ink */
  --calm-text-soft: #57534e;
  --calm-text-muted: #78716c;

  /* Hairline borders */
  --calm-border: #e7ddc9;
  --calm-border-strong: #d9cbaa;

  /* Recommended-action / focus accent — gold, reusing the app's
     existing amber (.app-primary-btn background #b45309). */
  --calm-accent-focus: #b45309;
  --calm-accent-focus-soft: #fdf0da;
  --calm-accent-focus-strong: #92400e;

  /* Subject-family accents. TYPE_MATH/PHYSICS/CHEMISTRY/PROGRAMMING/
     INFORMATICS/OTHER intentionally share the neutral charcoal/grey
     token below rather than getting their own hue. */
  --calm-accent-biology: #5b7a5a;        /* moss green */
  --calm-accent-biology-soft: #e6ecdf;
  --calm-accent-language: #5b7f99;       /* dusty blue */
  --calm-accent-language-soft: #e4ecf1;
  --calm-accent-history: #a16250;        /* muted terracotta/plum */
  --calm-accent-history-soft: #f1e4de;
  --calm-accent-neutral: #57534e;        /* structure / supporting info */
  --calm-accent-neutral-soft: #eeeae4;
}

html.dark {
  --calm-bg-parchment: #26221c;
  --calm-bg-parchment-deep: #201c17;
  --calm-bg-stone: #2a251e;
  --calm-surface: #292524;

  --calm-text: #f5f5f4;
  --calm-text-soft: #d6d3d1;
  --calm-text-muted: #a8a29e;

  --calm-border: #40382a;
  --calm-border-strong: #4d4130;

  --calm-accent-focus: #fbbf24;
  --calm-accent-focus-soft: #3a2e13;
  --calm-accent-focus-strong: #fde68a;

  --calm-accent-biology: #8bab86;
  --calm-accent-biology-soft: #303b2c;
  --calm-accent-language: #8cb0c7;
  --calm-accent-language-soft: #263640;
  --calm-accent-history: #cd9a86;
  --calm-accent-history-soft: #3a2c26;
  --calm-accent-neutral: #a8a29e;
  --calm-accent-neutral-soft: #33302b;
}

/* Per-subject-type accent binding — a template/component opts in by
   setting data-subject-type="{{ subject.subject_type }}" on a wrapping
   element and reading var(--calm-subject-accent) /
   var(--calm-subject-accent-soft) rather than a hard-coded color, so
   any future page can pick up the right hue without new CSS. Falls
   back to the neutral accent when unset or on a type without its own
   hue (math/physics/chemistry/programming/informatics/other). */
:root, [data-subject-type] {
  --calm-subject-accent: var(--calm-accent-neutral);
  --calm-subject-accent-soft: var(--calm-accent-neutral-soft);
}
[data-subject-type="biology"] {
  --calm-subject-accent: var(--calm-accent-biology);
  --calm-subject-accent-soft: var(--calm-accent-biology-soft);
}
[data-subject-type="language"] {
  --calm-subject-accent: var(--calm-accent-language);
  --calm-subject-accent-soft: var(--calm-accent-language-soft);
}
[data-subject-type="history"] {
  --calm-subject-accent: var(--calm-accent-history);
  --calm-subject-accent-soft: var(--calm-accent-history-soft);
}

/* ════════════════════════════════════════════════════════════════
   U7 CALM CORE — visual foundation

   These rules deliberately consume the calm tokens only beneath the U7
   body attribute.  Existing templates can retain their utility classes,
   leaving the flag-off response and presentation untouched while the
   redesign gets one quieter visual language across the app.
   ════════════════════════════════════════════════════════════════ */
body[data-u7-calm-core="1"] {
  background: var(--calm-bg-parchment);
  color: var(--calm-text);
}
body[data-u7-calm-core="1"] #main {
  color: var(--calm-text);
}
body[data-u7-calm-core="1"] > nav.bg-amber-700 {
  background: var(--calm-surface);
  color: var(--calm-text);
  border-bottom: 1px solid var(--calm-border);
  box-shadow: none;
}
body[data-u7-calm-core="1"] > nav.bg-amber-700 a,
body[data-u7-calm-core="1"] > nav.bg-amber-700 button,
body[data-u7-calm-core="1"] > nav.bg-amber-700 .text-amber-100,
body[data-u7-calm-core="1"] > nav.bg-amber-700 .text-amber-200 {
  color: var(--calm-text-soft);
}
body[data-u7-calm-core="1"] > nav.bg-amber-700 a:hover,
body[data-u7-calm-core="1"] > nav.bg-amber-700 button:hover {
  color: var(--calm-text);
}
body[data-u7-calm-core="1"] > nav.bg-amber-700 .nav-pill-active {
  background: var(--calm-accent-focus-soft);
  color: var(--calm-accent-focus-strong);
  box-shadow: none;
}
body[data-u7-calm-core="1"] nav.mobile-bottom-nav {
  background: color-mix(in srgb, var(--calm-surface) 94%, transparent) !important;
  border-color: var(--calm-border);
  box-shadow: none;
}
body[data-u7-calm-core="1"] nav.mobile-bottom-nav a {
  color: var(--calm-text-muted) !important;
}
body[data-u7-calm-core="1"] nav.mobile-bottom-nav a .tab-pill-active {
  background: var(--calm-accent-focus-soft);
  color: var(--calm-accent-focus-strong);
}
body[data-u7-calm-core="1"] .tab-pill-active {
  background: var(--calm-accent-focus-soft);
}
body[data-u7-calm-core="1"] .app-panel {
  background: var(--calm-surface);
  border-color: var(--calm-border);
  box-shadow: 0 1px 3px rgba(41, 37, 36, .05);
}
body[data-u7-calm-core="1"] .app-panel-soft {
  background: var(--calm-bg-stone);
  border-color: var(--calm-border);
}
body[data-u7-calm-core="1"] .app-kicker {
  color: var(--calm-accent-focus-strong);
  font-weight: 700;
}
body[data-u7-calm-core="1"] .app-panel .app-panel,
body[data-u7-calm-core="1"] .app-panel .app-panel-soft {
  background: transparent;
  border-color: var(--calm-border);
  border-radius: 0;
  box-shadow: none;
}
body[data-u7-calm-core="1"] .subject-card-primary {
  border-left: 3px solid var(--calm-accent-focus);
  border-color: var(--calm-border);
  box-shadow: 0 1px 3px rgba(41, 37, 36, .05);
}
body[data-u7-calm-core="1"] .subject-fingerprint-panel .subject-fingerprint-clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}
body[data-u7-calm-core="1"] .subject-fingerprint-panel .subject-fingerprint-clip .subject-fingerprint {
  position: absolute;
}
body[data-u7-calm-core="1"] details.app-disclosure {
  display: block;
  width: 100%;
  border-top: 1px solid var(--calm-border);
}
body[data-u7-calm-core="1"] details.app-disclosure > summary {
  display: flex;
  width: 100%;
  justify-content: space-between;
  padding: .9rem 0;
  color: var(--calm-text-soft);
  font-weight: 600;
}
body[data-u7-calm-core="1"] details.app-disclosure > :not(summary) {
  padding-bottom: 1rem;
}
body[data-u7-calm-core="1"] .text-gray-500,
body[data-u7-calm-core="1"] .text-gray-600 {
  color: var(--calm-text-soft);
}
body[data-u7-calm-core="1"] .text-gray-400 {
  color: var(--calm-text-muted);
}
@media (prefers-reduced-motion: reduce) {
  body[data-u7-calm-core="1"] *,
  body[data-u7-calm-core="1"] *::before,
  body[data-u7-calm-core="1"] *::after {
    transition-duration: 0ms !important;
    animation-duration: 0ms !important;
  }
}
details[data-menu] > summary { list-style: none; }

/* ── Progressive-disclosure chevron ──
   Shared "this expands" affordance for a <details> used to tuck secondary
   content/actions out of the way (More options, Why this, advanced tools,
   etc.), so every new disclosure added for the calm-redesign pass gets one
   consistent visual cue instead of each template hand-rolling its own. Put
   .app-disclosure on the <details>; its <summary> gets a trailing chevron
   that rotates open. Purely decorative — no effect on <details> semantics
   or existing data-menu kebab styling above. */
details.app-disclosure > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  border-radius: .5rem;
  transition: color var(--u-motion-fast) var(--u-motion-ease);
}
details.app-disclosure > summary::-webkit-details-marker { display: none; }
/* A proper rounded-stroke chevron (same polyline/stroke style as the app's
   other chevron icons, e.g. subject_list's topic-toggle) rendered via a
   masked currentColor square instead of the old rotated-border-corner hack
   — crisper at small sizes and automatically follows the summary's text
   colour (including hover and dark mode) with no extra rules needed. */
details.app-disclosure > summary::after {
  content: "";
  flex-shrink: 0;
  width: .8rem;
  height: .8rem;
  background-color: currentColor;
  opacity: .5;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  transition: transform var(--u-motion-fast) var(--u-motion-ease), opacity var(--u-motion-fast) var(--u-motion-ease);
}
details.app-disclosure > summary:hover::after,
details.app-disclosure > summary:focus-visible::after { opacity: .9; }
details.app-disclosure[open] > summary::after { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  details.app-disclosure > summary,
  details.app-disclosure > summary::after { transition: none; }
}
@media (forced-colors: active) {
  details.app-disclosure > summary::after { background-color: CanvasText; opacity: 1; forced-color-adjust: none; }
}

/* ── Library "Show all topics" toggle (subject_list.html) ──
   A hand-rolled disclosure (button + JS-toggled `hidden` list, not a
   native <details>) added for the calm redesign. Its Tailwind
   transition-colors/transition-transform utilities animate by default;
   this instantly disables both the hover-color transition and the
   chevron's click-triggered rotation under reduced motion, matching the
   .app-disclosure chevron above. */
@media (prefers-reduced-motion: reduce) {
  [data-topics-toggle],
  [data-topics-toggle-chevron] {
    transition: none;
  }
}

/* The wordmark, soft orange. It is that colour on the signed-out pages and in
   the calm shell; on the screens still using the legacy one it inherited the
   header text colour, so the same app showed two different brands depending on
   which screen you were on. Hex values rather than --calm-accent-focus: that
   token resolves to a different amber on this shell, which is part of how the
   two drifted apart in the first place. The long selector is there to outrank
   the calm-flag navbar rule above, which colours every link in that bar. */
.app-wordmark,
body[data-u7-calm-core="1"] > nav.bg-amber-700 a.app-wordmark { color: #b95d0a; }
html.dark .app-wordmark,
html.dark body[data-u7-calm-core="1"] > nav.bg-amber-700 a.app-wordmark { color: #f0a35a; }
