/* The page stays invisible until the first real screen renders, so no loading text ever flashes; it reveals itself after 4s regardless. */
@keyframes app-reveal { to { visibility: visible; } }
html.booting body { visibility: hidden; animation: app-reveal 0s 4s forwards; }
/* Learner compositions reuse the public system. No new colors or size tokens. */
.app-header { border-bottom: 1px solid var(--border-subtle); padding-block: var(--space-4); }
.app-wordmark { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-primary); text-decoration: none; }
.app-wordmark:hover, .auth-brand:hover { color: var(--text-primary); }
.app-wordmark__logo { display: block; height: 2rem; width: auto; }
.app-mark { display: none; color: var(--text-primary); }
body.in-activity .app-wordmark { visibility: hidden; }
body.in-activity .app-header .container { position: relative; }
body.in-activity .app-mark { display: block; position: absolute; left: 50%; transform: translateX(-50%); }
body.in-activity #nav { visibility: hidden; }
.activity-title { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.activity-title .activity-close { width: var(--target-min); min-width: var(--target-min); padding: 0; flex-shrink: 0; justify-content: flex-end; }
.activity-close svg { width: 0.95rem; height: 0.95rem; }
.app-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, var(--container-narrow)); gap: var(--space-7); padding-block: var(--space-7); }
.app-layout:has(aside[hidden]) { grid-template-columns: minmax(0, 1fr); max-width: var(--measure); }
/* Sign-in and sign-up sit in the middle of the screen, both ways, at a form-sized width. */
.app-layout:has(.auth-form) { min-height: 100vh; align-content: center; }
body:has(.auth-form) .app-header { display: none; }
.auth-brand { display: flex; justify-content: center; margin-bottom: var(--space-4); color: var(--text-primary); }
.auth-brand .app-wordmark__logo { height: 2.5rem; }
.app-layout:has(.auth-form) .app-intro { text-align: center; }
.app-layout:has(.auth-form) .auth-switch { align-self: center; }
.app-layout:has(.auth-form) > main { width: min(100%, 30rem); margin-inline: auto; }
#main { min-width: 0; outline: none; }
#nav { gap: var(--space-2); align-items: center; }
#nav .btn--quiet { color: var(--text-secondary); text-decoration: none; padding-inline: var(--space-3); }
#nav .btn--quiet:hover { color: var(--text-primary); }
#nav [aria-current="page"] { background: transparent; color: var(--text-primary); text-decoration: none; border-bottom: 2px solid var(--accent); border-radius: 0; }
#nav .btn--icon { width: var(--target-min); min-width: var(--target-min); padding: 0; }
.app-intro { padding-bottom: var(--space-2); }
.answer-group { border: 0; margin: 0; padding: 0; min-width: 0; }
.answer-group legend { margin-bottom: var(--space-5); }
.choice { display: flex; align-items: center; gap: var(--space-3); min-height: var(--target-min); padding: var(--space-4); border: 1px solid var(--border-control); border-radius: var(--radius-md); cursor: pointer; background: var(--surface-raised); }
.choice:has(:checked) { background: var(--surface-accent-quiet); border-color: var(--border-accent); }
.choice.choice--correct { background: var(--status-ok-surface); border-color: var(--status-ok-text); }
.choice.choice--wrong { background: var(--status-error-surface); border-color: var(--status-error-text); box-shadow: inset 0 0 0 1px var(--status-error-text); }
.choice input { accent-color: var(--accent); flex-shrink: 0; }
.choice:focus-within { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.feedback { border: 1px solid var(--border-accent); border-radius: var(--radius-md); padding: var(--space-5); background: var(--surface-accent-quiet); }
/* A failed check keeps the line breaks of its message, and its traceback scrolls inside a code block. */
.check-list { list-style: none; padding: 0; margin: 0; }
.check-message { white-space: pre-line; overflow-wrap: anywhere; }
/* An error is marked by a 2px border as well as its text, never by colour alone. */
.feedback--error { border-color: var(--status-error-text); box-shadow: inset 0 0 0 1px var(--status-error-text); background: var(--status-error-surface); color: var(--status-error-text); }
.stat { font-size: var(--text-2xl); font-weight: var(--weight-semibold); line-height: var(--leading-tight); }
.prose { max-width: var(--measure); overflow-wrap: anywhere; }
.prose p, .prose ul, .prose ol, .prose pre, .prose blockquote, .prose table { margin-block: var(--space-4); }
/* The surrounding stack already spaces blocks, so prose adds no margin at its own edges. */
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.nowrap { white-space: nowrap; }
.prose h2, .prose h3 { margin-block: var(--space-5) var(--space-3); }
.prose ul, .prose ol { padding-left: var(--space-5); list-style: revert; }
.prose li + li { margin-top: var(--space-2); }
.prose blockquote { border-left: 3px solid var(--border-accent); padding-left: var(--space-4); }
.prose table { display: block; overflow-x: auto; border-collapse: collapse; }
.prose th, .prose td { padding: var(--space-2) var(--space-3); border: 1px solid var(--border-subtle); text-align: left; }
.math { font-family: var(--font-math); white-space: pre-wrap; }
summary { cursor: pointer; min-height: var(--target-min); padding-block: var(--space-3); font-weight: var(--weight-semibold); }
.form-status:empty { display: none; }
@media (max-width: 760px) {
  .app-layout { padding-block: var(--space-5); gap: var(--space-5); }
  .app-header .container { gap: var(--space-3); }
  #nav .btn { padding-inline: var(--space-3); }
  .card--lead { padding: var(--space-5); }
}
.choice .prose p { margin: 0; }
.prose th { white-space: nowrap; }

/* Code editor: dark like LeetCode, a highlighted copy under a transparent textarea. */
/* Its colours are the --code-* tokens in tokens.css. */
.code-editor { --code-line: 1.6; border-radius: var(--radius-lg); overflow: hidden; background: var(--code-bg); color: var(--code-text); }
.code-editor__bar { display: flex; justify-content: space-between; align-items: center; padding: var(--space-2) var(--space-4); background: var(--code-bar); color: var(--code-dim); font-size: var(--text-sm); }
.code-editor__reset { padding: 0; border: 0; background: none; color: inherit; font: inherit; cursor: pointer; }
.code-editor__reset:hover { color: var(--code-text); }
.code-editor:has(textarea[readonly]) .code-editor__reset { display: none; }
.code-editor__body { display: flex; padding-block: var(--space-3); }
.code-editor__gutter, .code-editor__copy, .code-editor__input { margin: 0; font-family: var(--font-mono); font-size: 0.875rem; line-height: var(--code-line); tab-size: 4; white-space: pre; }
.code-editor__gutter { flex: none; min-width: 3.5em; padding-inline: var(--space-3) var(--space-4); color: var(--code-dim); text-align: right; user-select: none; }
.code-editor__stack { position: relative; flex: 1; min-width: 0; }
.code-editor__copy { position: absolute; inset: 0; overflow: hidden; padding-right: var(--space-4); pointer-events: none; }
.code-editor__input { display: block; width: 100%; padding: 0 var(--space-4) 0 0; border: 0; outline: none; resize: none; overflow-x: auto; overflow-y: hidden; background: transparent; color: transparent; caret-color: var(--code-caret); }
.code-editor__input::selection { background: var(--code-selection); color: transparent; }
.code-editor:focus-within { box-shadow: 0 0 0 2px var(--border-accent); }
.code-editor__input:focus-visible { outline: none; box-shadow: none; }
.code-editor__input[readonly] { cursor: default; }
.code-keyword { color: var(--code-keyword); }
.code-constant { color: var(--code-constant); }
.code-builtin { color: var(--code-builtin); }
.code-function { color: var(--code-function); }
.code-string { color: var(--code-string); }
.code-number { color: var(--code-number); }
.code-comment { color: var(--code-comment); font-style: italic; }


/* The page sits on the sunken surface and the header on the raised one. */
body { background: var(--surface-sunken); }
.app-header { background: var(--surface-raised); }
.app-intro .t-display { font-size: var(--text-3xl); line-height: var(--leading-snug); }
.app-intro .t-lead { font-size: var(--text-base); max-width: var(--measure); }
@media (max-width: 760px) {
  .app-intro .t-display { font-size: var(--text-2xl); }
}
@media (max-width: 760px) {
  #nav { gap: var(--space-1); }
  #nav .btn { font-size: var(--text-sm); padding-inline: var(--space-2); }
}

/* Lesson graph and its focused neighborhood share the existing semantic palette. */
/* Math Academy style map: filled ellipses for lessons and boxes for builds, dark readable titles, thin dark links with solid arrowheads. */
.lesson-graph { --graph-node-inline: 9; --graph-node-block: 3.75; }
.graph-edges { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; fill: none; stroke: var(--ink-600); stroke-width: 0.06; }
.graph-arrowhead { fill: var(--ink-700); stroke: none; }
.graph-node { position: absolute; width: 9rem; height: 3.75rem; display: flex; align-items: center; justify-content: center; padding: 0 var(--space-4); border: 0; border-radius: 50%; background: var(--ink-100); color: var(--ink-900); font-size: var(--text-xs); font-weight: var(--weight-semibold); line-height: 1.15; text-align: center; overflow: hidden; }
.graph-node__title { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
/* A long title shrinks and takes a fourth line inside the same node, so the layout never changes. */
.graph-node[data-long="true"] { font-size: calc(var(--text-xs) * 0.85); }
.graph-node[data-long="true"] .graph-node__title { -webkit-line-clamp: 4; }
.graph-node[data-build="true"] { border-radius: 0; }
/* A paper is a page with its top right corner folded off, and its title is in italics like a publication title. */
.graph-node[data-paper="true"] { border-radius: 0; clip-path: polygon(0 0, calc(100% - 1.25rem) 0, 100% 1.25rem, 100% 100%, 0 100%); font-style: italic; }
.graph-node[data-group="mastered"] { background: var(--teal-800); color: var(--white); }
/* A finished lesson is light teal until its reviews master it; a mastered lesson is full teal (owner, 7 Oct). */
.graph-node[data-group="done"] { background: var(--teal-200); color: var(--teal-900); }
.graph-node[data-group="ahead"] { background: var(--ink-100); color: var(--ink-900); }
.graph-node[data-status="unavailable"] { background: var(--ink-100); color: var(--ink-700); }
.app-layout:has(aside[hidden]):has(.lesson-graph) { max-width: var(--container); }
.graph-viewport { max-height: var(--container-narrow); border: 1px solid var(--border-control); border-radius: var(--radius-lg); background: var(--surface-sunken); }
.graph-canvas { position: absolute; top: 0; left: 0; transform-origin: 0 0; will-change: transform; }
.graph-viewport { position: relative; overflow: hidden; cursor: grab; touch-action: none; }
.graph-viewport.is-dragging { cursor: grabbing; user-select: none; }
.graph-workspace { position: relative; }

.stack--4 { --stack-gap: var(--space-4); }
#main { --stack-gap: var(--space-5); }

/* Quiet study surfaces — Cognitive load, Hierarchy in two seconds, WCAG POUR. */
.app-layout { gap: var(--space-5); padding-block: var(--space-5); }
.app-layout:has(aside[hidden]) { max-width: var(--container); }
.app-intro .t-display { font-size: var(--text-2xl); line-height: 1.25; }
.t-h2 { font-size: var(--text-xl); }
.app-intro .t-lead { font-size: var(--text-sm); color: var(--text-secondary); line-height: 1.4; }
.btn, .field__input { border-radius: var(--radius-control); }
.btn { padding: var(--space-3) var(--space-4); }
.card, .card--lead { padding: var(--space-5); border: 1px solid var(--border-subtle); box-shadow: none; }
.card .card { border: 0; border-top: 1px solid var(--border-subtle); padding-inline: 0; }
.badge { background: transparent; border: 0; padding: 0; border-radius: 0; color: var(--text-secondary); font-size: var(--text-sm); font-weight: var(--weight-regular); }
form > .btn { align-self: flex-start; }
/* The sign-in/sign-up switch sits under the card as a text link, aligned with the card edge. */
.auth-switch { align-self: flex-start; padding-inline: 0; }
/* login() and signup() read as function calls, so they use the code face. */
.btn--code { font-family: var(--font-mono); }
:focus-visible, .choice:focus-within { outline: 2px solid var(--focus-ring); outline-offset: 2px; box-shadow: none; }
.app-layout.app-layout--graph:has(aside[hidden]) { max-width: none; width: 100%; }
.app-layout--graph #main { gap: var(--space-4); }
.app-layout--graph .app-intro { padding: 0; }
.graph-workspace { display: grid; grid-template-columns: minmax(0, 1fr); min-height: 0; }
.graph-viewport { max-height: none; height: 100%; border: 1px solid var(--border-subtle); border-radius: 0; }
@media (min-width: 960px) {
  .app-layout--graph { height: calc(100dvh - var(--app-header-height, 6rem)); }
  .app-layout--graph #main { display: flex; flex-direction: column; min-height: 0; }
  .app-layout--graph .lesson-graph { flex: 1; min-height: 0; }
  /* Keep the graph content inside the remaining grid and flex height. */
  .graph-workspace { flex: 1; grid-template-rows: minmax(0, 1fr); }
  .graph-viewport { min-height: 0; }
}
@media (max-width: 959px) {
  .graph-viewport { height: max(calc(var(--lesson-node-block) * 2), calc(100dvh - var(--graph-top, 24rem) - var(--space-5))); }
}
@media (max-width: 760px) {
  .container { padding-inline: var(--space-4); }
  .card, .card--lead { padding: var(--space-4); }
  .app-intro .t-display { font-size: var(--text-heading-mobile); }
}

/* Below 560px the header stacks and the three page names stay visible. */
@media (max-width: 560px) {
  .app-header .container { flex-wrap: wrap; }
  .app-wordmark { flex-basis: 100%; }
  #nav { flex: 1; justify-content: flex-end; }
}

/* Settings groups its four forms with one rule between sections; no new tokens. */
.settings-sections { --stack-gap: var(--space-6); }
.settings-sections > .settings-section + .settings-section { border-top: 1px solid var(--border-subtle); padding-top: var(--space-6); }
.settings-section .field { max-width: var(--container-narrow); }
.settings-email { overflow-wrap: anywhere; }
/* Weekly XP schedule: the seven weekday inputs share one row on desktop and wrap to four columns on a phone; the total updates live. */
/* The row is seven 5rem cells plus their gaps, so "1000" fits beside the spinner. */
.schedule-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--space-3); max-width: calc(7 * 5rem + 6 * var(--space-3)); }
@media (max-width: 760px) {
  .schedule-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.schedule-field { max-width: none; }
/* VRI-271: the desktop spinner leaves too little room for a 4-digit goal. */
/* The tighter inline padding keeps "1000" whole without resizing the grid. */
.schedule-field .field__input { width: 100%; padding-inline: var(--space-2); }
.weekly-total { color: var(--text-secondary); }

/* VRI-218: a lesson's code blocks use the whole card, and a wide one says that it scrolls. */
/* Cognitive load: the prose keeps its measure, but a derivation is not clipped by it. */
.lesson .prose { max-width: none; }
.lesson .prose > :not(.code-scroll):not(table) { max-width: var(--measure); }
.lesson .prose table { max-width: 100%; }
/* The scrollbar stays visible on a wide block; the hint names the gesture while it is needed. */
.code-scroll { position: relative; max-width: 100%; margin-block: var(--space-4); }
.code-scroll pre.code { margin: 0; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.code-scroll pre.code::-webkit-scrollbar { height: var(--space-2); }
.code-scroll pre.code::-webkit-scrollbar-track { background: transparent; }
.code-scroll pre.code::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--radius-full); }
.code-scroll__hint { display: none; margin: var(--space-2) 0 0; color: var(--text-secondary); text-align: right; }
.code-scroll[data-overflow="true"] .code-scroll__hint { display: block; }
.code-scroll[data-at-end="true"] .code-scroll__hint { display: none; }
/* Inline code is one token: it moves to the next line whole, never splits at its spaces (VRI-218, B4). */
/* Only code longer than a whole line may wrap inside itself, so nothing overflows the card. */
.prose .code--inline { display: inline-block; max-width: 100%; overflow-wrap: break-word; vertical-align: baseline; }

/* VRI-241: position inside a step lesson: the bar, the step count and the step kind. */
.lesson-progress { display: flex; align-items: center; gap: var(--space-4); }
.lesson-progress .progress { flex: 1 1 auto; }
.lesson-progress > .badge { flex: none; }
.lesson-progress > .t-small { flex: none; color: var(--text-secondary); }

/* The pace table that Home's finish date opens. */
.popover-table { border-collapse: collapse; }
.popover-table th, .popover-table td { padding: var(--space-1) 0; text-align: left; font-weight: var(--weight-regular); }
.popover-table thead th { padding-bottom: var(--space-2); color: var(--text-secondary); }
.popover-table th:first-child, .popover-table td:first-child { padding-right: var(--space-5); }
.popover-table tr.popover-table__current th, .popover-table tr.popover-table__current td { font-weight: var(--weight-semibold); }
.lesson-nav { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.lesson-saved:empty, .step-feedback:empty { display: none; }
/* The question text starts and ends flush with the card padding and lines up with the answer field. */
.answer-group > legend { padding-inline: 0; }
.answer-group > legend :first-child { margin-top: 0; }
.answer-group > legend :last-child { margin-bottom: 0; }
.answer-feedback > .prose > :first-child { margin-top: 0; }
.answer-feedback > .prose > :last-child { margin-bottom: 0; }
/* Headings and regions the app focuses for screen readers cannot be reached with Tab, so they show no focus ring. */
[tabindex="-1"]:focus { outline: none; }
/* An inline formula stays on one line instead of breaking after an equals sign. */
.katex { white-space: nowrap; }

/* A question's topic title links to its lesson, underlined so it reads as a link. */
.topic-link { color: inherit; text-decoration: underline; text-underline-offset: 0.15em; }

/* The lesson graph fills the page under the header, edge to edge, without a frame. */
.app-layout--graph:has(aside[hidden]) { padding: 0; }
.app-layout--graph .graph-viewport { border: 0; background: transparent; }

/* The typeset preview under a numeric answer keeps its height, so typing never moves the button. */
.answer-preview { min-height: 3.5rem; padding-top: var(--space-2); color: var(--text-primary); font-size: 1.15rem; }
