/* Design system */

/* Lexend, the Chalklet face (owner, 2026-10-17), served from this origin rather than Google's
   CDN: a stylesheet on fonts.googleapis.com is render-blocking and sends every visitor's IP to
   Google. Chosen from six student-first sans fonts on a real phone page; its capital I has
   serifs, so `I = 10 A` and `l = 1.0 m` stay distinct.

   One VARIABLE file covers every weight, 40 KB, cached after the first page. Latin only: it has
   no rupee sign, Greek or sub/superscripts, which fall through to the system face in the stack
   below until formulas get KaTeX or <sub>/<sup> (brand handoff 2026-10-18, item 4).

   Lexend is SIL Open Font License 1.1, which permits self-hosting and redistribution. */
@font-face {
  font-family: 'Lexend';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/lexend-latin.woff2') format('woff2');
}

/* The contract that keeps an admin-chosen colour usable:
     - a coloured SURFACE uses --color-primary, with --color-on-primary on top of it;
     - coloured TEXT on a light background always uses --color-primary-dark, which is
       darkened until it clears WCAG AA.
   Without this, picking a pale accent (a yellow, a mint) silently produces links and labels
   nobody can read, and the failure only shows up on someone else's screen. */

:root {
  /* The locked Chalklet palette (owner, 2026-10-17; site_brand.py holds the same values).
     ONE red for the logo, buttons and links; warm ink on paper; stone for quiet fills and
     hairlines. Replaced the blue sampled from the Manim frames. */
  --color-ink: #1F1C1A;
  --color-primary: #C93A2F;        /* Chalklet red: 5.1:1 under white text, 5.0:1 as text on paper */
  --color-primary-dark: #C93A2F;   /* already clears AA on white, so the derived shade is itself */
  --color-primary-light: #FAEBEA;
  --color-stone: #F2ECE3;
  /* The "moment" brown (owner, 2026-10-18): home hero and inner-page moments only. Paper text
     on it; red appears on it only as a white-text button, never as text (1.3-2.3:1). */
  /* BLACK since 2026-10-27 (owner, with the decided black / white / red logo); the token keeps its old name. */
  --color-brown: #1F1C1A;
  --color-brown-bg: linear-gradient(158deg, #2A2623 0%, #24211E 45%, #1F1C1A 75%, #1A1816 100%);
  --color-on-brown-muted: #F4EEE6;
  /* The logo's two colours, switched to a paper knockout in dark mode: the full-colour logo
     goes on light grounds only. */
  --logo-ink: #1F1C1A;
  --logo-accent: #C93A2F;
  --color-on-primary: #FFFFFF;   /* recomputed per theme - see branding.theme_tokens */
  --color-highlight: #E8B93B;      /* the yellow Manim titles use - emphasis only, never a surface */

  --color-text: var(--color-ink);
  --color-text-muted: #6B625C;
  --color-border: #E6DED2;         /* between stone #F2ECE3 and stone-dark #DCD3C5: a hairline */
  --color-bg: #FFFDF9;             /* paper */
  --color-surface: #ffffff;

  --color-success: #16a34a;
  --color-success-bg: #f0fdf4;
  --color-error: #dc2626;
  --color-error-bg: #fef2f2;
  --color-warning: #b45309;
  --color-warning-bg: #fffbeb;
  --color-warning-border: #fcd34d;

  /* Tightened. Large radii plus a shadow plus a border is what makes an interface look
     inflated; separation now comes from space, with one hairline where it is genuinely needed. */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;

  --shadow-sm: 0 1px 2px rgba(15, 20, 25, 0.04);
  --shadow-md: 0 2px 8px rgba(15, 20, 25, 0.05);
  --shadow-lg: 0 16px 48px rgba(15, 20, 25, 0.10);

  /* One spacing scale, so vertical rhythm is a decision rather than an accident. The large
     steps are the ones that matter - generous space between sections is most of what reads
     as considered rather than cramped. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;
  --space-32: 128px;

  --font-sans: 'Lexend', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

* { box-sizing: border-box; }

body {
  /* Native checkboxes, radios and range sliders paint themselves in the browser's own blue
     unless told otherwise. Without this the theme stops at the edge of every form control -
     which is exactly where a student is looking when they answer a question. */
  accent-color: var(--color-primary);
  font-family: var(--font-sans);
  color: var(--color-text);
  background: var(--color-bg);
  margin: 0;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--color-primary-dark); }

/* Navbar */
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 28px;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}
.navbar .brand {
  display: flex;
  align-items: center;
  color: var(--color-text);
  text-decoration: none;
  /* Never squeezed: signed in, the search box and the nav took all the room and the logo shrank
     to 0 px wide at 1024 px (caught by the website-testing session, 2026-10-18). */
  flex-shrink: 0;
}
/* The outlined lockup from templates/_logo.html. Sized by height: 100 SVG units are the
   wordmark's font size, so 40px renders "chalk" at ~22px, the size the locked mock uses. */
/* Width is set from the viewBox aspect (543.5 / 182.6 = 2.976) rather than left to auto, so the
   box can never collapse whatever the flex container does. */
.logo-svg { display: block; height: 40px; width: 123px; flex-shrink: 0; }   /* lockup.py name + AI LABS: 3.07:1 */
.navbar .brand svg { flex-shrink: 0; }
.navbar-search { flex: 1; max-width: 420px; margin: 0 24px; }
.navbar-search .browse-search { margin: 0; max-width: none; }
.navbar-search .search-box .input { padding: 9px 14px 9px 38px; font-size: 14px; }
.navbar-search .search-box svg { left: 12px; }

.navbar nav { display: flex; align-items: center; gap: 20px; }
.navbar nav a {
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;
}
.navbar nav a:hover { color: var(--color-text); }
.navbar nav a.signout { color: var(--color-text-muted); }
/* Sign-out is a POST (SEC-14), so it is a button. These rules exist only to stop it looking like
   one - the control did not change, the verb did. */
.signout-form { display: inline; margin: 0; }
.navbar nav .signout-form button,
.admin-bar .signout-form button {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--color-text-muted);
}
.navbar nav .signout-form button:hover,
.admin-bar .signout-form button:hover { color: var(--color-text); text-decoration: underline; }
.admin-bar .signout-form button { color: inherit; }
.navbar nav a.active { color: var(--color-primary-dark); }
.navbar nav a.nav-cta {
  background: var(--color-primary); color: var(--color-on-primary);
  padding: 7px 14px; border-radius: var(--radius-sm); font-weight: 600;
}
.navbar nav a.nav-cta:hover { background: var(--color-primary-dark); color: #fff; }
/* The signed-in student's name. Truncated rather than wrapped: a long name must not push
   the nav onto a second line. */
.navbar nav .nav-user {
  color: var(--color-text);
  font-size: 14px;
  font-weight: 600;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 900px) {
  /* Search drops to its own full-width line rather than being squeezed between the brand and
     the nav, where it would be too narrow to type a concept name into. */
  .navbar { flex-wrap: wrap; }
  .navbar-search { order: 3; flex-basis: 100%; max-width: none; margin: 10px 0 0; }
}
@media (max-width: 640px) {
  .navbar { padding: 12px 16px; gap: 10px; }
  .navbar nav { gap: 4px; flex-wrap: wrap; }
  .navbar .logo-svg { height: 32px; width: 98px; }

  /* Five links will not sit on one line at 360px, so the bar was 118px tall - 16% of the screen,
     on every page, before anything the student came for. About and Contact are in the footer of
     every page that carries this nav, and they are the two nobody needs mid-study. They go here
     and stay in the footer, rather than being lost.

     Not a hamburger: it would hide "Get started" behind a tap on the one page whose job is to
     earn that tap, and three visible links do not need a menu to contain them. */
  .navbar nav a.nav-aside { display: none; }

  /* 17px of bare text is not a tap target - that was the height of every one of these, because
     they had no padding at all and the font is 14px. Thumbs are not cursors. */
  .navbar nav a { padding: 10px 8px; min-height: 40px; }
  .navbar nav a.nav-cta { padding: 10px 14px; }
}

/* Footer - the locked home mock's (owner, 2026-10-17): stone ground, the logo with the tagline
   set large, then Library / Chalklet / Legal, and a legal line with the exams on the right. */
.site-footer {
  border-top: 1px solid var(--color-border);
  background: var(--color-stone);
  color: var(--color-text-muted);
  padding: 64px 28px 36px;
  margin-top: auto;
}
.footer-inner { max-width: 1104px; margin: 0 auto; }
.footer-top { display: grid; grid-template-columns: 1.4fr 3fr; gap: 40px; }
.footer-identity { max-width: 320px; }
.footer-brand { display: flex; align-items: center; }
.footer-brand .logo-svg { height: 64px; width: 197px; }
/* The footer carries the FULL lockup from lockup.py, tagline included, so it can never sit off the system. */
.footer-brand .lockup-svg { display: block; width: 236px; height: auto; }
/* As on the video end card (2026-10-27): ONE line, light weight, starting at the lockup's left edge and running
   about 10% wider than it. 20px/regular wrapped after "a little" in the 320px column. */
.footer-tagline { color: var(--color-text-muted); font-size: 14.5px; font-weight: 300; line-height: 1.35; margin: 12px 0 0;
  white-space: nowrap; letter-spacing: 0.005em; }
.footer-address { color: var(--color-text-muted); font-size: 13px; margin: 10px 0 0; line-height: 1.5; white-space: pre-line; }
.footer-links { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px 40px; }
.footer-col { display: flex; flex-direction: column; }
.footer-col h4 { font-size: 14px; font-weight: 500; color: var(--color-text); margin: 0 0 12px; }
.footer-links a { color: var(--color-text-muted); text-decoration: none; font-size: 15px; line-height: 2; }
.footer-links a:hover { color: var(--color-text); }
.footer-bottom {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px;
  border-top: 1px solid var(--color-border); margin-top: 48px; padding-top: 22px; font-size: 13px;
}
@media (max-width: 760px) {
  .site-footer { padding: 48px 16px 28px; }
  .footer-top { grid-template-columns: 1fr; gap: 32px; }
  .footer-links { grid-template-columns: repeat(2, 1fr); }
}

/* An uploaded logo stands in for the drawn mark in the header and the footer. Constrained by
   height and letting width follow, so a wide wordmark and a square icon both sit on the line
   without either being distorted. */
.brand-logo { height: 22px; width: auto; max-width: 150px; object-fit: contain; display: block; }
.footer-brand .brand-logo { height: 20px; }

/* Content pages (About / Contact / Privacy / Terms) */
.page-hero {
  /* Flat. The gradient wash that was here is the single most generic thing an interface can
     do - it dates a page instantly and competes with the diagrams for attention. */
  background: var(--color-bg);
  padding: var(--space-24) 20px var(--space-16);
  text-align: center;
}
.page-hero h1 { font-size: clamp(34px, 5vw, 52px); font-weight: 700; margin: 0 0 var(--space-4); letter-spacing: -0.03em; line-height: 1.08; }
.page-hero p { color: var(--color-text-muted); font-size: 18px; line-height: 1.6; max-width: 560px; margin: 0 auto; }

@media (max-width: 640px) {
  /* --space-24 above and --space-16 below is generous room on a desktop and half a phone
     everywhere else: /about's hero measured 376px at 360px wide, which is 51% of the screen
     spent before the first sentence. /contact was 290px, 39%, for a page whose entire content is
     a short form.

     The restraint is right and stays. What was wrong is that "restrained" had been executed as
     "spacious", and spacious costs a fixed number of pixels that a 740px screen cannot spare the
     way a 1000px one can. The type sizes are untouched; only the air around them moves. */
  .page-hero { padding: var(--space-8) 16px var(--space-6); }
  .page-hero p { font-size: 16px; }
}
.content-wrap { max-width: 680px; margin: 0 auto; padding: var(--space-16) 20px var(--space-32); }
/* The hero already closes with 64px of its own. Doubling up left a visible hole between the
   subtitle and the first line of text. */
.page-hero + .content-wrap { padding-top: var(--space-8); }
.content-wrap ul { padding-left: 20px; margin: var(--space-3) 0; }
.content-wrap li { margin-bottom: var(--space-2); }
.content-wrap h2 { font-size: 22px; font-weight: 700; letter-spacing: -0.01em; margin: var(--space-12) 0 var(--space-3); }
.content-wrap h2:first-child { margin-top: 0; }
.content-wrap p, .content-wrap li { color: var(--color-text); line-height: 1.75; font-size: 16px; }
.content-wrap .updated { color: var(--color-text-muted); font-size: 13px; margin-bottom: 8px; }

.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; max-width: 900px; margin: 0 auto; padding: 0 20px 64px; }
.feature-card { padding: 24px; text-align: left; }
.feature-card .icon-badge {
  width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center;
  background: var(--color-primary-light); color: var(--color-primary-dark); margin-bottom: 14px;
}
.feature-card h3 { font-size: 16px; margin: 0 0 6px; }
.feature-card p { color: var(--color-text-muted); font-size: 14px; margin: 0; line-height: 1.6; }

@media (max-width: 720px) {
  .feature-grid { grid-template-columns: 1fr; }
  .page-hero h1 { font-size: 26px; }
}

/* Contact form */
.contact-form { display: flex; flex-direction: column; gap: 14px; max-width: 480px; margin: 0 auto; }
.contact-form label { font-size: 13px; font-weight: 600; color: var(--color-text-muted); margin-bottom: -8px; }
textarea.input { resize: vertical; min-height: 110px; font-family: var(--font-sans); }
.contact-info { display: flex; align-items: center; gap: 8px; justify-content: center; margin-top: 24px; color: var(--color-text-muted); font-size: 14px; }

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 22px;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  /* .btn is used on <a> as well as <button>, and an anchor underlines by default - which read
     as a broken link sitting inside a filled button. */
  text-decoration: none;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.btn-primary {
  /* Flat, not a gradient. One solid confident colour beats a gradient that is trying to be
     interesting on a button's behalf. */
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.btn-primary:hover:not(:disabled) { background: var(--color-primary-dark); color: #FFFFFF; }
.btn-primary:disabled { background: #D8B4AF; cursor: not-allowed; }

/* Inputs */
.input, select.input {
  width: 100%;
  padding: 13px 16px;
  font-family: var(--font-sans);
  font-size: 15px;
  /* A hairline. 1.5px reads as a form to be filled in; 1px reads as a field on a page. */
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  /* A token, not `white`. A literal here is what made a dark-mode override necessary in the
     first place; with the token, the input follows whatever the theme sets. */
  background: var(--color-surface);
  color: var(--color-text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.input:focus, select.input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-light);
  /* Focus is the one place the accent should be loud - it is the only ring a keyboard user
     has to find. */
}

/* Cards. A hairline only - no drop shadow. A page of floating boxes reads as a dashboard;
   these should read as content sitting on a page. Anything that genuinely overlays the page
   (menus, dialogs) still takes --shadow-lg. */
.card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
}

/* Night, on the two pages that get used at night.
   ---------------------------------------------------------------------------------------------
   Not a site-wide dark mode, and deliberately so. The argument is specific rather than general:
   the content on these two pages is a near-black Manim frame, and it sits inside a #FAFAF8
   surround. On an AMOLED phone in an unlit room that is the worst pairing available - the eye
   adapts to the video, and the page around it glares. A landing page read once in daylight has
   no such problem, and darkening it would be taste rather than a fix.

   So: the concept page and the test player, and only when the device asks. Everything else keeps
   the light palette it was designed in.

   Scoped to body.night rather than :root, so the class on those two templates is what turns it
   on. Because it lands on <body>, the navbar and footer inside it come too - a dark video frame
   under a white navbar would look broken rather than considered.

   The BRAND tokens are deliberately untouched. --color-primary and its family come from
   branding.theme_css on :root and belong to whoever set them in Admin; a dark mode that quietly
   repaints an institute's colour is a bug. Only the neutrals move.

   The surfaces are lifted off pure black, which reads as a hole on OLED and makes white text
   bloom against it. */
/* DARK MODE, now a choice rather than only a setting the phone makes.

   What changed on 2026-09-14: this used to live inside `@media (prefers-color-scheme: dark)` and
   apply only to `body.night`, which two templates carried. So it followed the phone and covered
   the concept page and the test player. A student who wanted dark had no say, and a student whose
   phone was light could not have it at all.

   Now there are three states, which is one more than most sites offer and the right number:

     data-theme absent   follow the phone. Still the default, because the device already knows
                         whether it is night, and UI-13's argument for that has not changed.
     data-theme="light"  light, whatever the phone says.
     data-theme="dark"   dark, whatever the phone says.

   On <html> rather than <body>, and written there by a tiny script in <head> BEFORE first paint.
   Setting it any later means a white flash on every page load, which is worst at exactly the
   moment somebody is using dark mode - at night, in bed.

   THE BRAND TOKENS ARE STILL DELIBERATELY UNTOUCHED. --color-primary and its family come from
   branding.theme_css and belong to whoever set them in Admin; a dark mode that quietly repaints
   somebody's accent is a bug. Only the neutrals move, plus the logo, which turns into a paper
   knockout because the full-colour Chalklet logo goes on light grounds only (owner, 2026-10-17).
   The neutrals are warm, from the brand's brown family (2026-10-18), and lifted off pure black,
   which reads as a hole on OLED and makes white text bloom.

   The two blocks below are duplicates ON PURPOSE - CSS cannot share a declaration block between a
   selector and a media query - and tests/test_stylesheet_structure.py fails if they ever drift
   apart. Edit both or neither. */

:root[data-theme="dark"] {
    --color-bg: #1A1714;           /* warm near-black from the brand's brown family, not cold blue */
    --color-surface: #24201C;      /* one step up, so a card is still a card */
    --color-text: #F2ECE3;         /* stone, not #FFF: full white on a dark ground vibrates */
    --color-text-muted: #AEA49A;
    --color-border: #3A332D;
    --color-stone: #2E2925;
    --logo-ink: #FFFDF9;           /* paper on dark; the rule keeps a lifted red */
    --logo-accent: #E8584B;        /* the lockup's lifted red on dark (logo-final dark master, 2026-10-27) */
    --color-success-bg: #0E2A18;
    --color-error-bg: #2C1416;
    --color-warning-bg: #2B2110;
    --color-warning-border: #6B4E12;
    --shadow-sm: none;
    --shadow-md: none;
    --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5);
    /* The accent's TINT, not the accent. --color-primary-light is a pale wash meant to sit on
       white; on a dark ground it glares (the active tier pill was a white-blue patch). Derived
       from whatever accent Admin holds, so it still belongs to the brand rather than being a
       second colour chosen here. */
    --color-primary-light: color-mix(in oklab, var(--color-primary) 22%, #1A1714);
    /* And the mirror of the accent contract. The rule at the top of this file says coloured TEXT
       uses --color-primary-dark, which is darkened until it clears AA *against white*. On a dark
       ground that is the wrong direction: measured at 3.57:1 on the active tier pill, below AA.
       Lightening instead keeps the contract's promise - "coloured text always clears AA" - rather
       than breaking it in the dark half of the site. */
    --color-primary-dark: color-mix(in oklab, var(--color-primary) 55%, #FFFFFF);
    color-scheme: dark;
}
:root[data-theme="dark"] .video-stage { background: var(--color-bg); }
/* Correct / wrong answer rows keep their light-mode text colours otherwise: dark green on the dark
   success wash was unreadable on the result page (website test 2026-10-18, L9). */
:root[data-theme="dark"] .pq-options li.is-correct, :root[data-theme="dark"] .review-options li.is-correct { color: #8FD3A4; }
:root[data-theme="dark"] .pq-options li.is-picked-wrong { color: #F1A9A0; }
:root[data-theme="dark"] .concept-header-band { border-bottom-color: var(--color-border); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg: #1A1714;           /* warm near-black from the brand's brown family, not cold blue */
    --color-surface: #24201C;      /* one step up, so a card is still a card */
    --color-text: #F2ECE3;         /* stone, not #FFF: full white on a dark ground vibrates */
    --color-text-muted: #AEA49A;
    --color-border: #3A332D;
    --color-stone: #2E2925;
    --logo-ink: #FFFDF9;           /* paper on dark; the rule keeps a lifted red */
    --logo-accent: #E8584B;        /* the lockup's lifted red on dark (logo-final dark master, 2026-10-27) */
    --color-success-bg: #0E2A18;
    --color-error-bg: #2C1416;
    --color-warning-bg: #2B2110;
    --color-warning-border: #6B4E12;
    --shadow-sm: none;
    --shadow-md: none;
    --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5);
    /* The accent's TINT, not the accent. --color-primary-light is a pale wash meant to sit on
       white; on a dark ground it glares (the active tier pill was a white-blue patch). Derived
       from whatever accent Admin holds, so it still belongs to the brand rather than being a
       second colour chosen here. */
    --color-primary-light: color-mix(in oklab, var(--color-primary) 22%, #1A1714);
    /* And the mirror of the accent contract. The rule at the top of this file says coloured TEXT
       uses --color-primary-dark, which is darkened until it clears AA *against white*. On a dark
       ground that is the wrong direction: measured at 3.57:1 on the active tier pill, below AA.
       Lightening instead keeps the contract's promise - "coloured text always clears AA" - rather
       than breaking it in the dark half of the site. */
    --color-primary-dark: color-mix(in oklab, var(--color-primary) 55%, #FFFFFF);
    color-scheme: dark;
  }
  :root:not([data-theme="light"]) .video-stage { background: var(--color-bg); }
  :root:not([data-theme="light"]) .pq-options li.is-correct, :root:not([data-theme="light"]) .review-options li.is-correct { color: #8FD3A4; }
  :root:not([data-theme="light"]) .pq-options li.is-picked-wrong { color: #F1A9A0; }
  :root:not([data-theme="light"]) .concept-header-band { border-bottom-color: var(--color-border); }
}

/* The control itself. An icon button in the navbar, 40px to meet the tap floor from UI-7. */
.theme-toggle {
  background: none; border: 0; cursor: pointer; padding: 0;
  min-width: 40px; min-height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--color-text-muted); border-radius: var(--radius-sm);
}
.theme-toggle:hover { color: var(--color-text); }
.theme-toggle svg { display: block; }

/* Motion, for people who have asked their device for less of it. Vestibular disorders make
   repeated movement genuinely unpleasant, and the setting is a request this site was not
   answering at all.

   Deliberately not the blanket `animation: none on everything` version. Two of these carry
   information and two do not:

   .exam-timer.is-low pulses under a minute. It also turns red, and the red says the same thing
   without moving - so the pulse goes and nothing is lost. This matters more than the rest: it is
   the one piece of motion a student cannot look away from, because it sits beside the clock they
   are watching.

   The hover lifts are decoration on a pointer device. Gone.

   Transitions become instant rather than removed, so a state change still registers as a change.

   The spinner is left alone on purpose. It is the only signal that work is happening, and a
   frozen spinner reads as a hung page - which is a worse experience than a moving one for
   everybody, including the person who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .exam-timer.is-low { animation: none; }
  .book-card:hover, .tier-pill:hover { transform: none; }
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

/* Moving through a topic. Three parts: back, where you are, on. The count is a link to the topic
   list, so the middle is not dead space - it is the way out to the full list, which is where a
   student goes when "next" is not what they wanted.

   Next is weighted heavier than Previous. Both are the same control, but the forward one is the
   one a student wants after finishing a video, and making them identical would mean reading both
   labels every time. */
.concept-nav {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: stretch; gap: 10px;
  margin-top: var(--space-6);
}
.concept-nav-link {
  display: flex; flex-direction: column; gap: 2px; justify-content: center;
  padding: 12px 16px; min-height: 44px;
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  background: var(--color-surface); text-decoration: none; color: var(--color-text);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.concept-nav-link:hover { border-color: var(--color-primary); }
.concept-nav-link.is-next { text-align: right; }
.concept-nav-link.is-next .concept-nav-dir { color: var(--color-primary-dark); }
/* An empty cell rather than nothing, so the count stays centred on the first and last concept
   instead of sliding to one side. */
.concept-nav-link.is-empty { border-color: transparent; background: none; }
.concept-nav-link.is-last {
  color: var(--color-text-muted); font-size: 13px; text-align: right; justify-content: center;
}
.concept-nav-dir {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.concept-nav-title {
  font-size: 14px; font-weight: 600; line-height: 1.35;
  /* Two lines then ellipsis: concept titles run long ("Behaviour of x^n sin(1/x) Near x = 0"),
     and a nav that grows to five lines stops looking like a nav. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.concept-nav-count {
  display: flex; align-items: center; padding: 0 14px;
  font-size: 13px; font-weight: 600; color: var(--color-text-muted);
  text-decoration: none; white-space: nowrap;
}
.concept-nav-count:hover { color: var(--color-primary-dark); text-decoration: underline; }

/* When the video ends. Not autoplay - the next video is a decision, and on a metered connection
   an unasked-for one is somebody's data. The link simply stops being quiet. */
.concept-nav-link.is-ready {
  border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-primary-light);
}

@media (max-width: 640px) {
  /* At 360px three columns leaves ~90px for a title, which is not enough for either. The
     directions and the count survive; the titles are what a tap reveals. */
  .concept-nav { gap: 8px; }
  .concept-nav-title { display: none; }
  .concept-nav-link { padding: 12px; align-items: center; }
  .concept-nav-link.is-next { text-align: center; }
}

/* The way out of a wrong answer. Sits under the solution, so a student reads the explanation
   first and follows the link if the explanation was not enough - which is the honest order. An
   inline link rather than a button: it should be findable, not louder than the solution it
   follows. */
.result-watch {
  display: inline-block; margin-top: 10px;
  font-size: 14px; font-weight: 600; text-decoration: none;
  color: var(--color-primary-dark);
}
.result-watch:hover { text-decoration: underline; }
.result-watch::before { content: '\25B8'; margin-right: 6px; }

/* Keyboard focus. .input:focus already draws a deliberate ring; nothing else did, so every
   button, link and option fell back to whatever the browser felt like - which on a flat filled
   button is often nothing visible at all. A student sitting at a coaching-centre PC with a real
   keyboard, or anyone who cannot use a trackpad, was navigating blind.

   :focus-visible rather than :focus on purpose: it fires for keyboard navigation and not for a
   mouse click, so the ring appears for the person who needs it without putting a box around
   every button the moment it is pressed.

   The same accent the inputs use, so focus reads as one idea across the site. */
.btn:focus-visible,
.control-opt:focus-visible,
.view-btn:focus-visible,
.exam-option:focus-visible,
.palette-grid button:focus-visible,
summary:focus-visible,
a:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Secondary text. Fourteen elements across doubt.html and review_queue.html asked for .muted and
   nothing defined it, so every line meant to recede was rendering at full ink and competing with
   the text it sits under - the search results page reading as one flat wall rather than a result
   and its source. The neighbouring .sub does the same job and already resolves to this token;
   this is that, available anywhere rather than only inside a browse header. */
.muted { color: var(--color-text-muted); }

/* Badges */
.badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 999px;
  text-transform: capitalize;
  letter-spacing: 0.02em;
}
/* One treatment, not seven. Seven pastel badge colours carry no information - nobody learns
   that pink means JEE - while guaranteeing every screen looks like a sticker sheet. The label
   already says which exam it is. */
.badge-elementary, .badge-middle-school, .badge-high-school, .badge-college,
.badge-JEE, .badge-NEET, .badge-Foundation, .badge-free, .badge-paid {
  background: transparent;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}

/* Spinner */
.spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2.5px solid var(--color-primary-light);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  vertical-align: middle;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Status text */
.status-error { color: var(--color-error); }
.status-ok { color: var(--color-success); }

/* Quiz */
.quiz-box {
  margin-top: 0;
  padding: var(--space-8);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
}
.quiz-box h3 {
  margin: 0 0 var(--space-4); font-size: 13px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--color-text-muted);
}
.quiz-box p { color: var(--color-text); margin: 0 0 var(--space-4); font-size: 17px; line-height: 1.5; }
.quiz-box label {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  margin-bottom: var(--space-2);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
  font-size: 15px;
}
.quiz-box label:hover { background: var(--color-primary-light); border-color: var(--color-primary); }
.quiz-feedback { margin-top: var(--space-4); font-weight: 700; font-size: 14px; display: flex; align-items: center; gap: 6px; }
.quiz-feedback.correct { color: var(--color-success); }
.quiz-feedback.incorrect { color: var(--color-error); }

/* Chapters */
.chapters { margin: 0 0 12px; padding: 0; list-style: none; }
.chapters li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--color-text);
  cursor: pointer;
  font-size: 14px;
  transition: background 0.15s ease;
}
.chapters li:hover { background: var(--color-primary-light); }
.chapters li .play-icon { color: var(--color-primary-dark); flex-shrink: 0; }
.chapters li .time { color: var(--color-text-muted); margin-left: auto; font-variant-numeric: tabular-nums; }

/* Library tabs */
.lib-tabs { display: flex; gap: 4px; padding: 10px 10px 0; }
.lib-tab {
  flex: 1; text-align: center; padding: 8px 6px; font-size: 13px; font-weight: 600;
  color: var(--color-text-muted); background: none; border: none; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px;
}
.lib-tab.active { color: var(--color-primary-dark); background: var(--color-primary-light); }
.lib-tab .count { font-variant-numeric: tabular-nums; }

/* Library list items with hover actions */
#video-list li { position: relative; padding-right: 84px; }
#video-list li .item-actions {
  position: absolute; top: 10px; right: 8px; display: none; gap: 2px;
}
#video-list li:hover .item-actions, #video-list li.renaming .item-actions { display: flex; }
.icon-btn {
  width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: 6px; cursor: pointer; color: var(--color-text-muted);
}
.icon-btn:hover { background: rgba(15, 23, 42, 0.08); color: var(--color-text); }
.icon-btn.danger:hover { background: var(--color-error-bg); color: var(--color-error); }
.rename-input {
  width: 100%; font-size: 14px; font-family: var(--font-sans); padding: 4px 6px;
  border: 1px solid var(--color-primary); border-radius: 6px; margin-bottom: 4px;
}
.trash-empty-btn { margin: 10px; width: calc(100% - 20px); }

/* Browse: header + breadcrumb */
/* --page-max lets the Back rows line up with whatever container the page actually uses;
   without it a 900px Back sits visibly inset on the 1180px chapter and concept pages. */
:root { --page-max: 900px; }
body.wide-page { --page-max: 1180px; }

.browse-wrap { max-width: var(--page-max); margin: 0 auto; padding: var(--space-6) 20px var(--space-32); }
.browse-header { margin-bottom: 28px; }
.browse-header h1 { font-size: clamp(28px, 4vw, 40px); font-weight: 700; margin: 0 0 var(--space-3); letter-spacing: -0.03em; line-height: 1.1; }
.browse-header p.sub { color: var(--color-text-muted); font-size: 15px; margin: 0; }
/* Back sits on the breadcrumb's line rather than in a row of its own: it is the same kind
   of control, it needs no vertical space of its own, and it lines up with the content it
   returns from. */
.header-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-bottom: var(--space-3);
}
.header-row .breadcrumb { margin-bottom: 0; flex: 1; min-width: 0; }
.header-row .back-link { flex-shrink: 0; }

.breadcrumb { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--color-text-muted); margin-bottom: 10px; flex-wrap: wrap; }
.breadcrumb a { color: var(--color-text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--color-primary-dark); }
.breadcrumb .sep { opacity: 0.5; }
.breadcrumb .current { color: var(--color-text); font-weight: 600; }

/* Chapter/concept pages: persistent sidebar so jumping between chapters doesn't need a trip
   back through the book page */
.chapter-page-layout {
  max-width: var(--page-max); margin: 0 auto; padding: var(--space-6) 20px var(--space-32);
  display: flex; align-items: flex-start; gap: 32px;
}
.chapter-page-layout .browse-wrap { flex: 1; min-width: 0; max-width: none; margin: 0; padding: 0; }

.chapter-sidebar {
  flex: 0 0 240px; background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); padding: 16px 0; position: sticky; top: 20px;
  max-height: calc(100vh - 40px); overflow-y: auto;
}
.chapter-sidebar-title {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-text-muted); padding: 0 16px 12px; border-bottom: 1px solid var(--color-border); margin-bottom: 8px;
}
.chapter-sidebar-list { display: flex; flex-direction: column; }
.sidebar-chapter {
  display: flex; align-items: center; gap: 10px; padding: 9px 16px; text-decoration: none;
  color: var(--color-text); font-size: 13px; border-left: 3px solid transparent;
}
.sidebar-chapter:hover { background: var(--color-bg); }
.sidebar-chapter.active { background: var(--color-primary-light); border-left-color: var(--color-primary); font-weight: 600; }
.sidebar-chapter-num {
  flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%; background: var(--color-bg);
  color: var(--color-text-muted); display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700;
}
.sidebar-chapter.active .sidebar-chapter-num { background: var(--color-primary); color: white; }
.sidebar-chapter-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-chapter-progress { flex-shrink: 0; font-size: 11px; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }

@media (max-width: 860px) {
  .chapter-page-layout { flex-direction: column; padding: 20px 16px 60px; gap: 16px; }
  /* Stacked, the layout's align-items:flex-start means a child is sized by its content
     rather than the container - so a single long line inside would stretch the page far
     past the viewport. The sidebar already declared this; the content column needs it too. */
  .chapter-page-layout .browse-wrap { width: 100%; }

  /* Source order puts the sidebar first, which is right when it is a sidebar. Stacked, it means
     a student who tapped a concept lands on a horizontal row of OTHER topics, with the video
     they asked for below it. The strip is a way to leave this page; it belongs after the page.
     Ordered rather than moved in the template, because the markup order is correct for the
     desktop layout and for anything reading the page linearly. */
  .chapter-page-layout .browse-wrap { order: 1; }
  .chapter-page-layout .chapter-sidebar { order: 2; }
  .chapter-sidebar {
    flex: none; position: static; max-height: none; width: 100%; padding: 12px 0;
  }
  .chapter-sidebar-title { padding: 0 16px 10px; }
  .chapter-sidebar-list { flex-direction: row; overflow-x: auto; gap: 4px; padding: 0 16px; }
  .sidebar-chapter {
    flex-shrink: 0; border-left: none; border-bottom: 3px solid transparent; border-radius: var(--radius-sm);
    white-space: nowrap;
  }
  .sidebar-chapter.active { border-left-color: transparent; border-bottom-color: var(--color-primary); }
  .sidebar-chapter-title { max-width: 140px; }
}

/* Browse: search */
.browse-search { position: relative; max-width: 480px; margin: 0 0 32px; }
.search-box { position: relative; display: flex; align-items: center; }
.search-box svg { position: absolute; left: 14px; color: var(--color-text-muted); pointer-events: none; }
.search-box .input { padding-left: 40px; }
.search-results {
  display: none; position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20;
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg); max-height: 360px; overflow-y: auto; padding: 6px;
}
.search-results.open { display: block; }
.search-result { display: block; padding: 10px 12px; border-radius: var(--radius-sm); text-decoration: none; color: var(--color-text); }
.search-result:hover { background: var(--color-primary-light); }
.search-result .sr-title { font-size: 14px; font-weight: 600; }
.search-result .sr-meta { font-size: 12px; color: var(--color-text-muted); margin-top: 2px; }
.search-result .sr-pending { color: #c2410c; }
.search-empty { padding: 14px 12px; font-size: 13px; color: var(--color-text-muted); text-align: center; }

/* Browse: subject groups + book cards */
.class-group { margin-bottom: var(--space-12); }
.class-heading {
  font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-text-muted); margin: 0 0 var(--space-4);
}
.book-meta { color: var(--color-text-muted); font-size: 13px; margin-top: 4px; }
.book-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.book-card { padding: 20px; text-decoration: none; color: var(--color-text); display: block; transition: transform 0.15s ease, box-shadow 0.15s ease; }
.book-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.book-card h3 { font-size: 16px; margin: 0 0 10px; }
.book-card .progress-track { width: 100%; height: 6px; background: var(--color-primary-light); border-radius: 999px; overflow: hidden; margin-bottom: 8px; }
.book-card .progress-fill { height: 100%; background: var(--color-primary); border-radius: 999px; }
.book-card .progress-label { font-size: 12px; color: var(--color-text-muted); }

/* Browse: chapter + concept rows */
.row-list { display: flex; flex-direction: column; gap: 10px; }
.row-card { padding: 16px 20px; display: flex; align-items: center; gap: 14px; text-decoration: none; color: var(--color-text); transition: box-shadow 0.15s ease; }
.row-card:hover { box-shadow: var(--shadow-md); }
.row-card .row-index { width: 30px; height: 30px; flex-shrink: 0; border-radius: 50%; background: var(--color-primary-light); color: var(--color-primary-dark); display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; }
.row-card .row-main { flex: 1; min-width: 0; }
.row-card .row-title { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.row-card .row-meta { font-size: 12px; color: var(--color-text-muted); }
.row-card .row-progress { width: 90px; flex-shrink: 0; }
.row-card .row-progress .progress-track { width: 100%; height: 5px; background: var(--color-primary-light); border-radius: 999px; overflow: hidden; margin-bottom: 4px; }
.row-card .row-progress .progress-fill { height: 100%; background: var(--color-primary); border-radius: 999px; }
.row-card .row-progress .progress-label { font-size: 11px; color: var(--color-text-muted); text-align: right; }

.concept-row { padding: 14px 20px; }
.row-clickable {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 14px;
  text-decoration: none; color: inherit;
}
.concept-row:hover .row-clickable .row-title { color: var(--color-primary-dark); }

.row-thumb {
  flex-shrink: 0; width: 96px; height: 54px; border-radius: var(--radius-sm); overflow: hidden;
  background: var(--color-bg);
}
.row-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.row-thumb-placeholder {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  color: var(--color-text-muted); opacity: 0.5;
}
.concept-row .status-pill { flex-shrink: 0; display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; padding: 5px 12px; border-radius: 999px; }
.concept-row .status-pill.ready { background: var(--color-success-bg); color: var(--color-success); }
.concept-row .status-pill.pending { background: var(--color-bg); color: var(--color-text-muted); border: 1px solid var(--color-border); }

/* Difficulty tier pills - one row card can offer up to 3 (Foundation/NEET/JEE) videos */
.tier-pills { display: flex; gap: 6px; flex-shrink: 0; }
.tier-pill {
  font-size: 12px; font-weight: 600; padding: 5px 12px; border-radius: 999px;
  text-decoration: none; transition: transform 0.1s ease;
}
.tier-pill:hover { transform: translateY(-1px); }
.tier-pill-pending {
  background: var(--color-bg); color: var(--color-text-muted); opacity: 0.5; border: 1px solid var(--color-border);
  cursor: default;
}
.tier-pill-pending:hover { transform: none; }

/* Browse: concept detail / player */

/* ==============================================================================================
   THE LESSON PAGE
   ----------------------------------------------------------------------------------------------
   Reference, measured rather than imagined: 3blue1brown.com's lesson page — the closest peer
   that exists, since Grant Sanderson wrote Manim, which renders these videos. A student who has
   seen one of his videos already reads that layout as "serious maths".

   What was measured there, and what is borrowed:

     reading text   Source Serif 4 at 18.9px with a 2.0 line-height. Borrowed as a SYSTEM serif
                    stack - Georgia on iOS/desktop, Noto Serif on Android - because a webfont is
                    50KB on a metered connection and this audience is the reason that matters.
     headings       weight 350 with POSITIVE tracking, centred. The site had 700 with -0.035em,
                    which is the 2023 SaaS voice and the one thing here that said "software"
                    rather than "study".
     ground         white, with the section behind the lesson header tinted in their own blue at
                    15% alpha. Not a chosen pastel - the page is tinted with the colour the
                    videos are drawn in, so the page and the product are one thing.

   The line-height and measure are the substance of this, not the colour. Long reading text at
   1.6/68ch reads as an interface; at 1.9/60ch it reads as a page.
   ============================================================================================== */

/* The blue the diagrams are drawn in, at the strength 3b1b tints with. color-mix keeps it tied to
   the accent token, so an admin who changes the accent changes this too rather than leaving a
   stranded pastel behind. */
.concept-header-band {
  /* Stone, not a tint of the accent: a red wash read as an alert (brand pass, 2026-10-18). */
  background: var(--color-stone);
  border-bottom: 1px solid var(--color-border);
  margin: 0 0 var(--space-8);
  padding: var(--space-8) var(--space-6) var(--space-7);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  text-align: center;
}

/* "Concept 4 of 21" — the chapter label 3b1b puts above every title. The number already exists
   in `neighbours`; it was only ever shown down beside the pager. */
.concept-eyebrow {
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-3);
}

/* V19: "JEE extension of <base> - watch that first", under an extension's title. */
.concept-extends {
  font-size: 14px;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-4);
}
.concept-extends a { color: var(--color-primary); }

.concept-page .concept-title {
  font-size: clamp(26px, 3.2vw, 34px);
  font-weight: 600;              /* Lexend headline weight, as locked 2026-10-17 */
  letter-spacing: -0.01em;
  line-height: 1.25;
  margin: 0 auto var(--space-4);
  max-width: 22ch;
}

/* The one-line exam brief, centred under the title the way a standfirst sits under a headline.
   Lexend like the rest of the site: the brand sets text in it too, and the serif alternatives
   were rejected by the owner (2026-10-17). */
.concept-page .concept-summary {
  font-size: 17px;
  line-height: 1.75;
  color: var(--color-text);
  max-width: 58ch;
  margin: 0 auto;
  text-align: center;
}

/* Reading text gets the open leading. This is the paragraph a student falls back to when the
   connection cannot carry the video, so it is the one place on the site where comfort beats
   density. Lexend, not a serif, since the 2026-10-17 font lock. */
.transcript p {
  font-size: 17px;
  line-height: 1.85;
  max-width: 62ch;
}

@media (max-width: 640px) {
  .concept-header-band { padding: var(--space-6) var(--space-4); border-radius: 0;
                         margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); }
  .concept-page .concept-title { max-width: none; }
  .concept-page .concept-summary { font-size: 16px; text-align: left; }
  .transcript p { font-size: 16px; line-height: 1.8; }
}

.concept-page .concept-title { font-size: clamp(24px, 3.5vw, 34px); font-weight: 700; margin: 0 0 var(--space-4); letter-spacing: -0.03em; line-height: 1.15; }
/* OBS-3: the one-line summary under the title, and the transcript below the quiz. */
/* The subscribe page and the claim queue. */
.pay-grid { display: flex; gap: var(--space-8); align-items: flex-start; flex-wrap: wrap; }
.pay-grid > div:first-child { flex: 1; min-width: 240px; }
.pay-qr img { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); }
.pay-vpa {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--color-primary-light); color: var(--color-primary-dark);
  padding: 2px 6px; border-radius: 4px; word-break: break-all;
}
.claim-list { list-style: none; padding: 0; margin: var(--space-3) 0 var(--space-4); }
.claim-list li { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 6px 0; }
.claim-status {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 8px; border-radius: 999px;
}
.claim-status.is-pending { background: var(--color-primary-light); color: var(--color-primary-dark); }
.claim-status.is-verified { background: #E7F6EC; color: #1B7F3B; }
.claim-status.is-rejected { background: var(--color-error-bg); color: var(--color-error); }

/* The four specifics of what "checked" means, moved to /about when the institutions page was
   dropped. A list rather than a grid of cards: on /about it is read, not scanned. */
.about-checks { list-style: none; padding: 0; margin: var(--space-4) 0 0; max-width: 68ch; }
.about-checks li {
  padding: 0 0 var(--space-4) var(--space-5);
  position: relative;
  line-height: 1.7;
}
.about-checks li::before {
  content: "";
  position: absolute; left: 0; top: 11px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--color-primary);
}
.concept-page .concept-summary {
  margin: calc(-1 * var(--space-2)) 0 var(--space-5);
  color: var(--color-text-muted);
  font-size: 15px;
  line-height: 1.6;
  max-width: 68ch;
}
.transcript {
  margin: var(--space-6) 0 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  padding: 0 var(--space-5);
}
/* The disclosure was a bare triangle nobody would notice. It is a control, so it looks like one
   and has a tap target - a student on a connection too poor to stream needs to FIND this. */
.transcript > summary {
  cursor: pointer;
  padding: 14px 0;
  font-weight: 600;
  font-size: 15px;
  list-style-position: inside;
}
.transcript > summary:hover { color: var(--color-primary-dark); }
.transcript[open] > summary { border-bottom: 1px solid var(--color-border); margin-bottom: var(--space-4); }
.transcript p { margin: 0 0 var(--space-4); line-height: 1.7; max-width: 68ch; }
.transcript p:last-child { padding-bottom: var(--space-4); }
.transcript .hint { color: var(--color-text-muted); font-size: 13px; }
.concept-page .card { overflow: hidden; margin-bottom: var(--space-6); }

/* The video sits on the same near-black it was rendered against, with a margin of that
   colour around it. The diagram then appears to extend into the page instead of being a
   rectangle pasted onto a white card - the product and the page stop looking like two
   different things. This is the one place a dark surface belongs. */
.video-stage {
  background: var(--color-ink);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  margin-bottom: var(--space-8);
  overflow: hidden;
}
/* PIR-7: the session mark. Deliberately small and faint - legible when looked for, ignorable
   while studying. It is a deterrent, not a punishment for paying.
   2026-10-28, owner: "keep it fainter and with slow speed slightly outside, should not distract
   student". So 6% (was 35%), a slow continuous drift instead of a jump every 30 s, and only in
   the empty strip along the bottom of the picture - between the two corner brand marks, below
   everything a lesson draws (geometry.CONTENT keeps that band empty). Lessons stream only; there
   is no download anywhere, so this overlay is the whole of the per-user ID (plan H72). */
.video-stage { position: relative; }
.session-mark {
  position: absolute;
  font-size: 11px;
  line-height: 1;
  color: #FFFFFF;
  opacity: 0.06;
  pointer-events: none;          /* never intercepts a tap meant for the player */
  user-select: none;
  white-space: nowrap;
  transform: translate(-50%, -50%);   /* left/top name the mark's centre */
  transition: left 1s linear;         /* the script steps it each second; this joins the steps */
  z-index: 2;
}
/* V5: the voice disclosure under the player, on the stage's own dark ground. */
.voice-disclosure {
  margin: var(--space-2) 0 0;
  /* The stage drops its padding at phone width (the video IS the page there), so the line keeps
     its own 16 px gutter and a bottom margin, rather than running edge to edge. */
  padding: 0 16px var(--space-2);
  font-size: 11px;
  line-height: 1.4;
  font-weight: 300;                     /* owner 2026-11-06: "keep it little light" */
  color: rgba(255, 255, 255, 0.5);
  text-align: center;
  text-wrap: balance;                    /* on a phone: two even lines, not "Labs." alone */
}
.video-stage video {
  width: 100%;
  display: block;
  border-radius: calc(var(--radius-lg) - var(--space-3));
  background: var(--color-ink);
}

.concept-page .concept-body { padding: 0; }
.concept-pending {
  padding: var(--space-24) var(--space-6);
  text-align: center;
  color: var(--color-text-muted);
  background: var(--color-ink);
  border-radius: var(--radius-lg);
  border: none;
}
.concept-pending svg { opacity: 0.4; margin-bottom: var(--space-3); }
.concept-pending svg { display: block; margin: 0 auto 14px; opacity: 0.5; }

/* The paywall card. Sits where the player would be, so the page keeps its shape rather than
   collapsing into a stub - a locked concept should read as a page with one thing withheld, not
   as a broken page.

   These rules were dead from 2026-09-11 to 2026-09-14: they were pasted INSIDE the still-open
   .concept-pending block above, so a browser read every one of them as `.concept-pending .paywall`
   and none of them matched. The card rendered with padding 0 and text-align start - lock icon
   adrift in the top-left corner, heading ragged, buttons unstyled - on the page most visitors
   meet, since with 4.3% coverage and a free-five rule the locked card IS the front door. It
   survived a screenshot review because a left-aligned card still looks like a card. */
.paywall { padding: 32px; text-align: center; }
.paywall svg { color: var(--color-text-muted); margin-bottom: 12px; }
.paywall h3 { margin: 0 0 8px; font-size: 19px; }
.paywall p { margin: 0 auto 18px; max-width: 46ch; color: var(--color-text-muted); line-height: 1.6; }
.paywall-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* `.hint` was only ever defined as `.transcript .hint` and the dead `.paywall .hint`, so every
   other use on the site - the paywall's two lines, subscribe, register - rendered as body text. */
.hint { color: var(--color-text-muted); font-size: 13px; line-height: 1.6; }
.paywall .hint { margin-top: 18px; margin-bottom: 0; }

/* Difficulty tab switcher on the concept player page */
.difficulty-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-6); }
.difficulty-tab {
  font-size: 13px; font-weight: 600; padding: 8px 18px; border-radius: var(--radius-sm);
  text-decoration: none; color: var(--color-text-muted); background: var(--color-surface);
  border: 1px solid var(--color-border); transition: border-color 0.15s ease, color 0.15s ease;
}
.difficulty-tab:hover { border-color: var(--color-primary); color: var(--color-primary-dark); }
.difficulty-tab.active {
  background: var(--color-primary-light);
  border-color: var(--color-primary);
  color: var(--color-primary-dark);
}
.difficulty-tab.disabled {
  color: var(--color-text-muted); opacity: 0.5; background: var(--color-bg); cursor: default; pointer-events: none;
}
.trash-hint { padding: 0 18px 12px; color: var(--color-text-muted); font-size: 12px; }

/* ---------------------------------------------------------------- Admin */
.admin-wrap { max-width: 900px; }
/* Admin bar, in the site's own colour rather than the amber alert it used to be. It is a
   different privilege, not a warning, and it lives on its own pages behind its own login - the
   ADMIN badge and the heavy left edge carry that distinction without a second palette.
   Everything here derives from the theme, so it follows whatever colour Admin picks. */
.admin-bar {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  background: var(--color-primary-light);
  border: 1px solid var(--color-primary);
  border-left: 5px solid var(--color-primary);
  color: var(--color-primary-dark);
  border-radius: var(--radius-md);
  padding: 12px 18px; margin-bottom: var(--space-8);
  font-family: var(--font-sans); font-size: 14px; font-weight: 500;
}
.admin-bar a { color: var(--color-primary-dark); text-decoration: none; font-weight: 600; opacity: .8; }
.admin-bar a:hover, .admin-bar a.active { opacity: 1; }
.admin-bar a.active { text-decoration: underline; text-underline-offset: 5px; }
.admin-bar .admin-signout { margin-left: auto; font-weight: 500; opacity: .65; }
.admin-badge {
  background: var(--color-primary); color: var(--color-on-primary); font-weight: 800;
  font-size: 11px; letter-spacing: 0.08em; padding: 4px 9px; border-radius: var(--radius-sm);
}
.admin-flash {
  padding: 12px 16px; border-radius: var(--radius-sm); margin-bottom: 18px;
  background: var(--color-success-bg); color: var(--color-success); font-size: 14px; font-weight: 500;
}
.admin-flash.is-error { background: var(--color-error-bg); color: var(--color-error); }

.admin-filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 24px; }
.filter-pill {
  font-size: 13px; font-weight: 600; padding: 6px 14px; border-radius: 999px;
  text-decoration: none; color: var(--color-text-muted);
  background: var(--color-surface); border: 1px solid var(--color-border);
}
.filter-pill:hover { border-color: var(--color-primary); color: var(--color-primary-dark); }
.filter-pill.active { background: var(--color-primary); border-color: var(--color-primary); color: white; }
.filter-pill .count { opacity: 0.75; font-variant-numeric: tabular-nums; }
.filter-sep { width: 100%; height: 0; }

/* Question review */
.review-card { padding: 20px 24px; margin-bottom: 14px; }
.review-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12px; margin-bottom: 12px; }
.review-type {
  background: var(--color-bg); border: 1px solid var(--color-border); color: var(--color-text-muted);
  padding: 2px 8px; border-radius: 999px; font-weight: 600;
}
.review-concept { color: var(--color-text-muted); }
.review-status { margin-left: auto; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.status-draft { color: #c2410c; }
.status-approved { color: var(--color-success); }
.status-rejected { color: var(--color-error); }
.review-question { font-size: 15px; line-height: 1.6; margin-bottom: 14px; }
.review-options, .pq-options { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.review-options li, .pq-options li {
  display: flex; gap: 10px; align-items: flex-start; font-size: 14px;
  padding: 7px 10px; border-radius: var(--radius-sm); background: var(--color-bg);
}
.review-options li.is-correct, .pq-options li.is-correct {
  background: var(--color-success-bg); color: #14532d; font-weight: 600;
}
.opt-letter {
  flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%; background: var(--color-surface);
  border: 1px solid var(--color-border); display: flex; align-items: center;
  justify-content: center; font-size: 11px; font-weight: 700; text-transform: uppercase;
}
.review-answer, .pq-answer { font-size: 14px; margin-bottom: 12px; }
.review-answer .tol, .pq-answer { color: var(--color-text-muted); }
.review-solution { margin-bottom: 14px; }
.review-solution summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--color-primary-dark); }
.review-solution p { font-size: 14px; line-height: 1.65; color: var(--color-text); margin: 10px 0 0; }
.review-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.review-notes { flex: 1; min-width: 200px; font-size: 13px; padding: 9px 12px; }
.btn-approve { background: var(--color-success); color: white; font-size: 14px; padding: 10px 18px; }
.btn-reject { background: var(--color-error); color: white; font-size: 14px; padding: 10px 18px; }
.review-reviewed {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 13px; color: var(--color-text-muted); border-top: 1px solid var(--color-border); padding-top: 12px;
}
.linklike { background: none; border: none; color: var(--color-primary-dark); cursor: pointer; font-size: 13px; padding: 0; text-decoration: underline; }

/* Paper builder */
.build-card { padding: 24px; margin-bottom: 28px; }
.build-card h2 { font-size: 16px; margin: 0 0 16px; }
.build-form { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.build-form label { display: flex; flex-direction: column; gap: 6px; font-size: 12px; font-weight: 600; color: var(--color-text-muted); }
.build-form .input { padding: 9px 12px; font-size: 14px; }
.build-form input[type=number] { width: 92px; }
.build-form select.input { min-width: 190px; }
.build-title { flex: 1; min-width: 180px; }
.build-hint, .build-empty { font-size: 13px; color: var(--color-text-muted); margin: 14px 0 0; line-height: 1.6; }

.paper-header form { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
.published-note { font-size: 13px; color: var(--color-text-muted); }
.section-card { padding: 20px 24px; margin-bottom: 16px; }
.section-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.section-head h2 { font-size: 15px; margin: 0; }
.section-marks { font-size: 12px; color: var(--color-text-muted); }
.scheme-tag { background: #fef3c7; color: #92400e; padding: 2px 8px; border-radius: 999px; font-weight: 600; margin-left: 6px; }
.paper-questions { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 18px; }
.paper-questions li { font-size: 14px; }
.pq-text { line-height: 1.6; margin-bottom: 10px; }

/* ------------------------------------------------------- Tests (student) */
.test-row .row-main { flex: 1; }
.test-score { text-align: right; margin-right: 4px; }
.btn-secondary { background: var(--color-surface); border: 1px solid var(--color-border); color: var(--color-text); font-size: 14px; padding: 11px 18px; }
.btn-secondary:hover { border-color: var(--color-primary); color: var(--color-primary-dark); }

/* Exam player */
/* The exam shell fills the screen, and on a phone "the screen" is not what 100vh means. Mobile
   Chrome measures 100vh against the viewport with the URL bar HIDDEN, so while the bar is still
   showing - which is exactly when the page first loads - the bottom ~56px of the shell sits
   below the fold. .exam-body clips it (overflow: hidden), so the last option of the last visible
   question is cut off, and then jumps into place once the bar retracts. 100dvh is the dynamic
   height, which is the one that matches what the student can see. The plain 100vh stays first as
   a fallback for anything that does not know dvh.

   The other 100vh in this file, on .chapter-sidebar, is deliberately left alone: it is a sticky
   element, and dvh changes value as the URL bar animates, so using it there would make the
   sidebar resize on every scroll. */
.exam-shell { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }
.exam-bar {
  display: flex; align-items: center; gap: 16px; padding: 12px 20px;
  background: var(--color-brown); color: #FFFDF9; flex-shrink: 0;   /* the brand brown, a "moment" bar */
}
.exam-title { font-weight: 600; font-size: 15px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exam-timer {
  font-variant-numeric: tabular-nums; font-size: 20px; font-weight: 700;
  background: rgba(255,255,255,0.1); padding: 6px 14px; border-radius: var(--radius-sm);
}
.exam-timer.is-low { background: var(--color-error); animation: pulse 1s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: 0.65; } }
.btn-submit { background: var(--color-success); color: white; font-size: 14px; padding: 10px 20px; }

.exam-body { flex: 1; display: flex; overflow: hidden; }
/* overscroll-behavior: contain stops a scroll that reaches the top of the question list from
   continuing into the page behind it and triggering pull-to-refresh. Reloading mid-paper is not
   something a student should be able to do by scrolling up too enthusiastically. */
.exam-main { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 24px; max-width: 820px; margin: 0 auto; }
.exam-section-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap;
  font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--color-text-muted); margin: 26px 0 12px;
}
.exam-section-marks { font-weight: 500; text-transform: none; letter-spacing: 0; }
.exam-q { padding: 22px 24px; margin-bottom: 14px; scroll-margin-top: 16px; }
.exam-q-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.exam-q-num { font-size: 12px; font-weight: 700; color: var(--color-primary-dark); text-transform: uppercase; letter-spacing: 0.05em; }
.exam-review { font-size: 12px; color: var(--color-text-muted); display: flex; align-items: center; gap: 6px; cursor: pointer; }
.exam-q-text { font-size: 15px; line-height: 1.65; margin-bottom: 16px; }
.exam-options { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.exam-option {
  display: flex; align-items: flex-start; gap: 10px; padding: 11px 14px;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  cursor: pointer; font-size: 14px; transition: border-color 0.12s ease, background 0.12s ease;
}
.exam-option:hover { border-color: var(--color-primary); }
.exam-option.is-picked { border-color: var(--color-primary); background: var(--color-primary-light); }
.exam-option input { margin-top: 3px; }
.exam-numeric { max-width: 260px; }

.exam-palette {
  width: 236px; flex-shrink: 0; border-left: 1px solid var(--color-border);
  background: var(--color-surface); padding: 20px 18px; overflow-y: auto;
}
.palette-title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-muted); margin-bottom: 12px; }
.palette-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 7px; margin-bottom: 18px; }
.palette-grid button {
  aspect-ratio: 1; border: 1px solid var(--color-border); background: white;
  border-radius: var(--radius-sm); cursor: pointer; font-size: 13px; font-weight: 600;
  font-family: var(--font-sans); color: var(--color-text-muted);
}
.palette-grid button:hover { border-color: var(--color-primary); }
.palette-grid button.answered { background: var(--color-success); border-color: var(--color-success); color: white; }
.palette-grid button.review { background: #f59e0b; border-color: #f59e0b; color: white; }
.palette-key { list-style: none; margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 7px; font-size: 12px; color: var(--color-text-muted); }
.palette-key li { display: flex; align-items: center; gap: 8px; }
.palette-key .dot { width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--color-border); background: white; }
.palette-key .dot.answered { background: var(--color-success); border-color: var(--color-success); }
.palette-key .dot.review { background: #f59e0b; border-color: #f59e0b; }
/* Save state, in the exam bar beside the clock. It used to be .palette-save, the last child of
   the question palette - which on a phone becomes a 168px scroll box sitting above the
   questions, so at ninety questions the words "answer not saved" were roughly 330px down inside
   a 168px box. Invisible, on precisely the connection that produces them.

   Quiet when there is nothing to say and loud when there is: a student mid-paper should not be
   distracted by "Saved", and must not be able to miss the other one. */
.exam-save {
  font-size: 12px; font-weight: 600; white-space: nowrap;
  padding: 5px 10px; border-radius: var(--radius-sm);
  color: rgba(255, 255, 255, 0.55);
}
.exam-save.is-busy { color: rgba(255, 255, 255, 0.85); }
/* Amber on the dark bar, with dark text on it - the one state that has to survive a glance in
   sunlight on a cheap screen. */
.exam-save.is-warn { background: #fbbf24; color: #0f172a; }
.exam-save.is-error { background: var(--color-error); color: #FFFFFF; }

@media (max-width: 640px) {
  /* The bar has four things in it and 360px to do it in. The paper's title is the one a student
     already knows, so it yields first - the clock and the save state do not. */
  .exam-bar { gap: 8px; padding: 10px 12px; }
  .exam-title { font-size: 13px; flex: 0 1 auto; }
  .exam-save { padding: 4px 8px; }
  .exam-timer { font-size: 17px; padding: 5px 10px; }
  .btn-submit { font-size: 13px; padding: 8px 12px; }
}

@media (max-width: 780px) {
  .exam-body { flex-direction: column-reverse; }
  .exam-palette { width: 100%; border-left: none; border-top: 1px solid var(--color-border); max-height: 168px; }
  .palette-grid { grid-template-columns: repeat(8, 1fr); }
  .palette-key { display: none; }
}

/* Result */
.result-summary { padding: 26px 28px; margin-bottom: 24px; display: flex; align-items: center; gap: 28px; flex-wrap: wrap; }
.result-score, .test-score { display: flex; align-items: baseline; gap: 4px; }
.score-value { font-size: 38px; font-weight: 800; letter-spacing: -0.02em; }
.test-score .score-value { font-size: 22px; }
.score-max { font-size: 16px; color: var(--color-text-muted); font-weight: 600; }
.result-title { flex: 1; min-width: 200px; }
.result-title h1 { font-size: 19px; margin: 0 0 4px; }
.result-title p { margin: 0; color: var(--color-text-muted); font-size: 13px; }
.result-tallies { display: flex; gap: 22px; }
.result-tallies div { display: flex; flex-direction: column; align-items: center; }
.result-tallies strong { font-size: 22px; font-weight: 800; }
.result-tallies span { font-size: 11px; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.t-correct { color: var(--color-success); }
.t-wrong { color: var(--color-error); }
.t-skip { color: var(--color-text-muted); }

.result-q { padding-left: 6px; border-left: 3px solid transparent; }
.result-q.is-right { border-left-color: var(--color-success); }
.result-q.is-wrong { border-left-color: var(--color-error); }
.result-q.is-skipped { border-left-color: var(--color-border); }
.result-q-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; }
.result-verdict { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-muted); }
.is-right .result-verdict { color: var(--color-success); }
.is-wrong .result-verdict { color: var(--color-error); }
.result-marks { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pq-options li.is-picked-wrong { background: var(--color-error-bg); color: #991b1b; }
.pick-tag { margin-left: auto; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.75; font-weight: 700; }

/* ---- Admin: generate + queue ---------------------------------------- */
.gen-form { max-width: 620px; margin-bottom: 40px; }
.admin-wrap h2 { font-size: 20px; font-weight: 700; margin: 36px 0 4px; letter-spacing: -0.01em; }
.gen-modes { display: flex; gap: 24px; flex-wrap: wrap; margin-bottom: 20px; font-size: 15px; }
.gen-modes label { display: flex; align-items: center; gap: 7px; cursor: pointer; }
.gen-block { margin-bottom: 16px; }
.gen-label { display: block; margin: 16px 0 6px; font-weight: 600; font-size: 14px; color: var(--color-text); }
.gen-checks { display: flex; flex-wrap: wrap; gap: 8px 20px; font-size: 15px; }
.gen-checks label { display: flex; align-items: center; gap: 7px; cursor: pointer; }
.gen-hint { font-size: 13px; color: var(--color-text-muted); margin: 8px 0 0; line-height: 1.55; }
.gen-form .btn { margin-top: 24px; }

.gen-tag {
  margin-left: 8px; padding: 2px 6px; border-radius: var(--radius-sm);
  font-size: 11px; background: var(--color-border); color: var(--color-text-muted);
}
.job-in_progress { background: var(--color-primary-light); }
.job-failed .has-error { color: var(--color-error); cursor: help; }
.job-completed { opacity: .6; }
.job-cancelled { opacity: .45; text-decoration: line-through; }
.btn-link {
  background: none; border: none; padding: 0; font: inherit;
  color: var(--color-primary-dark); cursor: pointer; text-decoration: underline;
}

/* Library: registration + provenance badges */
.badge-orphan { background: #fef3c7; color: #92400e; }
.badge-lang { background: var(--color-border); opacity: .9; }
.lib-concept { font-size: 12px; color: var(--color-text-muted); margin: 3px 0 5px; line-height: 1.4; }
.lib-size { font-size: 11px; color: var(--color-text-muted); margin-left: 6px; }

.gen-dim { color: var(--color-text-muted); font-size: 13px; }

/* ---- Admin: media library ------------------------------------------- */
.lib-wrap { max-width: 1180px; }
.lib-filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; }
.lib-filters .input { width: auto; min-width: 150px; padding: 9px 12px; font-size: 14px; }
.lib-search { flex: 1 1 260px; min-width: 220px; }

/* Grid and preview sit side by side; the preview sticks so it stays in view while the grid
   is paged through. Stacks on narrow screens. */
.lib-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 24px; align-items: start; }
.lib-main { min-width: 0; }
.data-grid-scroll {
  overflow: auto; max-height: 620px;
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
}
.data-grid { width: 100%; min-width: 640px; border-collapse: collapse; font-size: 14px; background: var(--color-surface); }
/* Tables that carry a control per row need more than 640px before the columns start
   crushing each other - the role select on the students page collapsed to an arrow
   the moment two columns were added. They scroll instead. */
.data-grid.is-wide { min-width: 900px; }
.data-grid th, .data-grid td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--color-border); }
.data-grid tbody tr:last-child td { border-bottom: none; }
.data-grid th {
  position: sticky; top: 0; z-index: 1; background: var(--color-bg);
  font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-text-muted); font-weight: 600; cursor: pointer; user-select: none; white-space: nowrap;
}
.data-grid th.sorted { color: var(--color-primary-dark); }
.data-grid th.sorted::after { content: ' \2193'; font-size: .75em; }
.data-grid th.sorted[data-dir="asc"]::after { content: ' \2191'; }
.data-row { cursor: pointer; }
.data-row:hover { background: var(--color-bg); }
.data-row.is-selected { background: var(--color-primary-light); }
.data-row.is-selected td { font-weight: 500; }

/* Credits. The markup asked for .data-table, which was never defined - so the table rendered at
   the browser's own 16px with no cell padding and no rules, and the card around it had no
   padding either, leaving text against the border.

   Modelled on .data-grid's cell treatment so the two read as one system, but deliberately not
   .data-grid itself: that one is admin furniture and carries a sticky header, a 620px height
   cap, a sort cursor on every th, and min-width 640px. The last would force a phone to scroll
   sideways, and three columns of short text should wrap instead.

   Padding is on .credits-card rather than on .card, because six card variants already set their
   own and adding it to the base would double every one of them. */
.credits-card { padding: 20px 24px; }
.credits-card + .credits-card { margin-top: var(--space-4); }
/* The table supplies its own inset through cell padding, so the card adds none - and clips to
   the card's radius rather than squaring off its corners. */
.credits-table-card { padding: 0; overflow: hidden; }
.data-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.data-table th, .data-table td {
  text-align: left; padding: 11px 14px; vertical-align: top;
  border-bottom: 1px solid var(--color-border);
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table th {
  font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-text-muted); font-weight: 600;
}
/* Attribution strings are long and often a single unbroken URL or author string. Let them break
   rather than widen the table past the screen. */
.data-table td { overflow-wrap: anywhere; }
@media (max-width: 640px) {
  .credits-card { padding: 16px; }
  .data-table { font-size: 13px; }
  .data-table th, .data-table td { padding: 9px 10px; }
}

.data-pager { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-top: 14px; }
.data-pager-btns { display: flex; align-items: center; gap: 14px; }
.data-pager-btns .btn-link:disabled { opacity: .35; cursor: default; text-decoration: none; }

.lib-preview { position: sticky; top: 20px; border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-surface); padding: 16px; }
.lib-preview-empty { color: var(--color-text-muted); font-size: 14px; text-align: center; padding: 40px 12px; }
.lib-player { width: 100%; border-radius: var(--radius-sm); background: #000; display: block; }
.lib-preview h3 { font-size: 15px; font-weight: 700; margin: 14px 0 10px; line-height: 1.4; }
.lib-meta { display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; margin: 0; font-size: 13px; }
.lib-meta dt { color: var(--color-text-muted); }
.lib-meta dd { margin: 0; color: var(--color-text); }

@media (max-width: 900px) {
  .lib-layout { grid-template-columns: 1fr; }
  .lib-preview { position: static; }
}

/* Question generation panel - collapsed by default so it does not push the review queue
   (the page's actual job) below the fold. */
.gen-panel {
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  background: var(--color-surface); padding: 0 18px; margin-bottom: 24px;
}
.gen-panel > summary {
  cursor: pointer; padding: 14px 0; font-weight: 600; font-size: 15px;
  list-style: none; display: flex; align-items: center; gap: 8px;
}
.gen-panel > summary::-webkit-details-marker { display: none; }
.gen-panel > summary::before { content: '\25B8'; color: var(--color-text-muted); transition: transform .15s ease; }
.gen-panel[open] > summary::before { transform: rotate(90deg); }
.gen-panel[open] > summary { border-bottom: 1px solid var(--color-border); }
.gen-panel .gen-form { padding: 18px 0 22px; margin-bottom: 0; }
.gen-row { display: flex; flex-wrap: wrap; gap: 16px; }
.gen-row > div { flex: 1 1 150px; }
.gen-panel-queue { margin-bottom: 20px; border: 1px solid var(--color-border); border-radius: var(--radius-md); }




/* Back navigation. Sits above and below the content on the right, on every page except the
   ones with nowhere to go back to (sign-in, sign-up, and the two section roots).

   It is an anchor with a real href, not a button calling history.back(). The href is the
   parent section, so it still works with JavaScript off, on a middle-click, and - the case
   that actually matters - when the page was opened directly from a shared link, where there
   is no history to go back to and a scripted button would do nothing at all. */
.back-nav {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 20px;
  display: flex;
  justify-content: flex-end;
}
.back-nav-top { padding-top: var(--space-6); }
.back-nav-bottom { padding-bottom: var(--space-16); margin-top: calc(var(--space-8) * -1); }
.back-link {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 14px; font-weight: 600; text-decoration: none;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  border-radius: var(--radius-sm);
  padding: 9px 16px;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.back-link:hover { color: var(--color-primary-dark); border-color: var(--color-primary); }
.back-link svg { flex-shrink: 0; }

@media (max-width: 640px) {
  .back-nav { padding: 0 16px; }
  .back-nav-bottom { padding-bottom: var(--space-12); }
}

/* ---------------------------------------------------------------- Catalogue colour
   A subject has a hue; a chapter has a shade of its subject's hue (see palette.py). It is
   always an edge accent, never a fill: a 4px rule tells two chapters apart at a glance and
   still leaves the page quiet enough for the diagrams to be the colourful thing on it. */
.has-rule { position: relative; overflow: hidden; }
.has-rule::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--hue, var(--color-border));
}
.book-card.has-rule, .row-card.has-rule { padding-left: 22px; }

.sidebar-chapter .sidebar-chapter-num {
  /* Tinted rather than filled - the sidebar shows every chapter at once, and fourteen solid
     blocks of colour would be louder than the page they sit beside. */
  color: var(--hue, var(--color-text-muted));
  font-weight: 700;
}

/* ---------------------------------------------------------------- List / tile switch */
.list-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: var(--space-4);
}
.list-count { font-size: 13px; color: var(--color-text-muted); font-weight: 500; }
.view-toggle {
  display: inline-flex; border: 1px solid var(--color-border);
  border-radius: var(--radius-sm); overflow: hidden; background: var(--color-surface);
}
.view-btn {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 30px; padding: 0; border: 0; cursor: pointer;
  background: transparent; color: var(--color-text-muted);
  transition: background 0.15s ease, color 0.15s ease;
}
.view-btn + .view-btn { border-left: 1px solid var(--color-border); }
.view-btn:hover { color: var(--color-text); }
.view-btn.is-active { background: var(--color-primary-light); color: var(--color-primary-dark); }

/* ---------------------------------------------------------------- Concept list + tiles */
.concept-item { position: relative; }
.ci-face {
  display: block; position: relative; overflow: hidden; flex-shrink: 0;
  background: var(--color-ink); border-radius: var(--radius-sm);
}
.ci-face img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* An ungenerated concept recedes rather than looming. Fading the dark tile with opacity
   blends it toward the light page and produces a heavy grey block - on a chapter that is
   1-of-8 generated, seven of those dominate the page and bury the one real video. Quiet and
   light is the correct direction: attention should go to what exists. */
.concept-item.is-empty .ci-face {
  background: var(--color-bg);
  border: 1px dashed var(--color-border);
}
.concept-item.is-empty .ci-soon { color: var(--color-text-muted); opacity: 0.85; }
.concept-item.is-empty .ci-soon-icon { color: var(--color-text-muted); opacity: 0.45; }
.ci-soon, .ci-soon-icon {
  position: absolute; inset: 0; margin: auto;
  display: flex; align-items: center; justify-content: center;
}
.ci-soon { padding: 12px; text-align: center; font-size: 12.5px; line-height: 1.35; font-weight: 500; }
.ci-soon-icon { width: 16px; height: 16px; }
.ci-title { font-weight: 600; text-decoration: none; color: inherit; }
.ci-title:hover { color: var(--color-primary-dark); }
.ci-meta {
  color: var(--color-text-muted); font-size: 13px; margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The corpus's own tier. In a topic of 25 concepts this is what tells a student where to start
   and what to come back to, so it has to read as a label rather than as another line of prose -
   without competing with the title for attention. */
.ci-depth {
  display: inline-block; margin-top: 5px;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
  color: var(--color-text-muted); background: var(--color-surface-alt, #f1f3f5);
  border: 1px solid var(--color-border);
}
.ci-depth.is-foundation { color: #1b7f5e; background: #e7f5ef; border-color: #bfe3d5; }
.ci-depth.is-core       { color: #1f5f9e; background: #e8f1fa; border-color: #c3dcf1; }
.ci-depth.is-advanced   { color: #9a4a12; background: #fdf0e6; border-color: #f3d5bb; }
/* V19: a JEE extension, in place of the depth chip. Chalklet red, the brand accent. */
.ci-depth.is-jee-extension { color: #C93A2F; background: #fbecea; border-color: #f0c6c2; }

/* --- list --- */
.concept-list[data-view="list"] { display: flex; flex-direction: column; gap: var(--space-2); }
.concept-list[data-view="list"] .concept-item {
  /* A flex item defaults to min-width:auto, which refuses to shrink below its content. The
     one-line excerpt inside would otherwise set the row's width and blow out the page. */
  min-width: 0;
  display: flex; align-items: center; gap: 14px;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-left: 4px solid var(--hue, var(--color-border));
  border-radius: var(--radius-md); padding: 10px 14px 10px 12px;
}
.concept-list[data-view="list"] .ci-num {
  width: 20px; flex-shrink: 0; font-size: 13px; font-weight: 600;
  color: var(--color-text-muted); text-align: right;
}
.concept-list[data-view="list"] .ci-face { width: 96px; height: 54px; }
.concept-list[data-view="list"] .ci-soon { display: none; }   /* no room for a title at 96px */
.concept-list[data-view="list"] .ci-body { flex: 1; min-width: 0; }
.concept-list[data-view="list"] .ci-title { font-size: 14px; }

/* --- tile --- */
.concept-list[data-view="tile"] {
  display: grid; gap: var(--space-6);
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
/* Column, so the pills can be pushed to the bottom of each tile. Titles run to one, two or
   three lines, and without this the pill rows stagger across the grid. */
.concept-list[data-view="tile"] .concept-item { display: flex; flex-direction: column; }
.concept-list[data-view="tile"] .ci-face { width: 100%; aspect-ratio: 16 / 9; }
.concept-list[data-view="tile"] .ci-soon-icon { display: none; }  /* the title says it better */
.concept-list[data-view="tile"] .ci-num {
  position: absolute; top: 8px; left: 8px; z-index: 1;
  font-size: 11px; font-weight: 700; color: rgba(255, 255, 255, 0.8);
  background: rgba(0, 0, 0, 0.45); border-radius: 4px; padding: 2px 6px;
}
/* On a light empty tile the same badge would be white-on-white. */
.concept-list[data-view="tile"] .concept-item.is-empty .ci-num {
  color: var(--color-text-muted); background: transparent; padding: 2px 0;
}
.concept-list[data-view="tile"] .ci-body {
  margin-top: 9px; padding-left: 9px;
  border-left: 3px solid var(--hue, var(--color-border));
}
.concept-list[data-view="tile"] .ci-meta { white-space: normal; display: none; }
.concept-list[data-view="tile"] .ci-title { font-size: 14px; line-height: 1.35; }
.concept-list[data-view="tile"] .tier-pills { margin-top: auto; padding-top: 10px; }
.concept-list[data-view="tile"] .concept-item.is-empty .ci-title { color: var(--color-text-muted); }

@media (max-width: 520px) {
  .concept-list[data-view="tile"] { grid-template-columns: 1fr 1fr; gap: var(--space-4); }
  .concept-list[data-view="tile"] .ci-title { font-size: 13px; }
}

/* ---------------------------------------------------------------- Watch controls
   Tier and voice, sitting under the concept title. They live here rather than in the top bar
   because this is where they take effect - and because the catalogue is cleaner without three
   tier pills on every row that a student never needed to choose between. */
.watch-controls {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-6);
}
.control-set {
  display: inline-flex; border: 1px solid var(--color-border);
  border-radius: var(--radius-sm); overflow: hidden; background: var(--color-surface);
}
.control-opt {
  padding: 7px 15px; font-size: 13px; font-weight: 600; text-decoration: none;
  color: var(--color-text-muted); background: transparent; border: 0; cursor: pointer;
  font-family: var(--font-sans); transition: background 0.15s ease, color 0.15s ease;
}
.control-opt + .control-opt { border-left: 1px solid var(--color-border); }
.control-opt:hover:not(.is-off):not(.is-on) { color: var(--color-text); }
.control-opt.is-on { background: var(--color-primary-light); color: var(--color-primary-dark); }
.control-opt.is-off { opacity: 0.4; cursor: default; }

/* "Other track" - this concept exists, but not yet for the student's exam tier. Quiet, because
   it is a note about what we have made, not something the student did wrong. */
.ci-note {
  font-size: 11px; font-weight: 600; color: var(--color-text-muted);
  border: 1px solid var(--color-border); border-radius: 999px; padding: 3px 9px;
  flex-shrink: 0; align-self: center;
}
.concept-list[data-view="tile"] .ci-note { align-self: flex-start; margin-top: 8px; }

/* ---------------------------------------------------------------- Watched
   The whole of progress: a tick. Green rather than the theme colour, because "done" is a
   different kind of signal from "this is a link" - and it has to stay legible whatever accent
   an admin picks. */
.ci-tick {
  position: absolute; top: 8px; right: 8px; z-index: 1;
  width: 20px; height: 20px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-success); color: #fff;
}
.concept-list[data-view="list"] .ci-tick { top: 4px; right: 4px; width: 16px; height: 16px; }
.concept-item.is-watched .ci-title { color: var(--color-text-muted); }

.sidebar-chapter-progress.is-done { color: var(--color-success); font-weight: 700; }

/* ---------------------------------------------------------------- Home page (/welcome)
   THE LOCKED HOME PAGE (owner, 2026-10-17), from branding_assets/concepts-2026-10-17/site-mock/
   build.py, section for section. Values are the mock's; colours go through the tokens so the
   page follows dark mode, where the mock had none. */
.home-announce {
  background: var(--color-stone); color: var(--color-text); font-size: 14px; font-weight: 500;
  text-align: center; padding: 10px 16px;
}
.home-wrap { max-width: 1200px; margin: 0 auto; padding: 0 48px; }
.home-display { font-weight: 500; letter-spacing: -0.025em; margin: 0; }
.home-h1 { font-size: 66px; line-height: 1.04; margin-top: 16px; }
.home-h2 { font-size: 54px; line-height: 1.08; }
.home-h3 { font-weight: 600; letter-spacing: -.015em; font-size: 30px; line-height: 1.15; margin: 0; }
.home-h4 { font-weight: 600; letter-spacing: -.015em; font-size: 24px; margin: 0; }
.home-body { font-size: 17px; line-height: 1.6; margin: 14px 0 0; }
.home-muted { color: var(--color-text-muted); }
.home-red { color: var(--color-primary-dark); }
.home-faint { color: #AEA49A; }
.home-on-dark { color: #F1EBE3; }
.home-eyebrow { font-size: 14px; font-weight: 500; letter-spacing: 0.02em; }
.home .home-btn { padding: 14px 24px; border-radius: 2px; font-size: 16px; font-weight: 500;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12) inset, 0 6px 18px rgba(120, 20, 10, 0.25); }
.home .home-ghost { background: transparent; color: var(--color-text); border: 1px solid var(--color-text); box-shadow: none; }
.home .home-ghost:hover { background: var(--color-stone); }

/* 1. Hero */
.home-hero { padding: 96px 0 120px; }
.home-hero-grid { display: grid; grid-template-columns: 1.05fr 1.15fr; gap: 48px; align-items: center; }
.home-lede { font-size: 19px; line-height: 1.55; color: #F1EBE3; margin: 26px 0 0; max-width: 30em; }
.home-cta { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; margin-top: 36px; }
.home-hero .home-link { font-weight: 500; font-size: 16px; }
.home-hero-note { font-size: 14px; color: #EAE2D8; margin: 18px 0 0; }
.home-scene { position: relative; padding: 92px 10px 60px; }
.home-device {
  position: relative; background: #0B0B0C; border-radius: 30px; padding: 14px 14px 46px; transform: rotate(-5deg);
  box-shadow: inset -2px 2px 0 rgba(255, 255, 255, 0.10), 0 2px 3px rgba(0, 0, 0, 0.55),
              -28px 46px 60px rgba(10, 6, 3, 0.55), -70px 110px 120px rgba(10, 6, 3, 0.35);
}
.home-device img, .home-device video { display: block; border-radius: 16px; width: 100%; }
.home-glare { position: absolute; inset: 14px 14px 46px; border-radius: 16px; pointer-events: none;
  background: linear-gradient(118deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.04) 32%, transparent 46%); }
.home-devbar { position: absolute; left: 26px; right: 26px; bottom: 14px; display: flex; align-items: center; gap: 12px; }
.home-play { width: 22px; height: 22px; border-radius: 11px; background: #FFFDF9; display: flex; align-items: center; justify-content: center; flex: none; }
.home-track { flex: 1; height: 3px; background: #34383F; border-radius: 2px; }
.home-track i { display: block; height: 3px; width: 62%; background: #C93A2F; border-radius: 2px; }
/* The floating cards are paper on the brown in both themes - light grounds, so red text is allowed on them. */
.home-float {
  position: absolute; z-index: 2; display: flex; flex-direction: column; gap: 2px;
  background: rgba(255, 253, 249, 0.94); color: #1F1C1A; border-radius: 14px; padding: 12px 16px;
  font-size: 14px; line-height: 1.35; backdrop-filter: blur(8px);
  box-shadow: 0 18px 40px rgba(20, 12, 6, 0.35), 0 1px 0 rgba(255, 255, 255, 0.6) inset;
}
.home-float b { font-weight: 600; font-size: 13px; color: #C93A2F; }
.home-float span { color: #1F1C1A; }
.home-f1 { left: 0; top: 0; max-width: 260px; }
.home-f2 { right: -10px; bottom: 6px; flex-direction: row; align-items: center; gap: 12px; max-width: 330px; }
.home-f2 img { width: 72px; height: 46px; object-fit: cover; border-radius: 8px; flex: none; }
.home-f2 span { display: flex; flex-direction: column; }
.home-f3 { right: 24px; top: 10px; align-items: flex-start; }

/* 2. Animation */
.home-animate { padding: 120px 0;
  background: radial-gradient(1100px 560px at 85% 20%, #FFFFFF, transparent 70%), linear-gradient(180deg, #FBF6EF, var(--color-bg)); }
.home-anim-grid { display: grid; grid-template-columns: 1fr 1.15fr; gap: 64px; align-items: center; }
.home-animate .home-h2 { margin-top: 14px; }
.home-points { display: grid; gap: 26px; margin-top: 36px; }
.home-points .home-body { margin-top: 6px; }
.home-anim-fig { margin: 0; }
.home-player { background: #0D1117; border-radius: 16px; padding: 10px;
  box-shadow: 0 40px 80px rgba(60, 38, 20, 0.25), 0 2px 6px rgba(60, 38, 20, 0.15); }
.home-player video, .home-player img { display: block; width: 100%; border-radius: 10px; }
.home-anim-fig figcaption { font-size: 14px; color: var(--color-text-muted); margin-top: 14px; }

/* 3. Why + exams, and 7. pricing */
.home-pad { padding: 110px 0; background: radial-gradient(1300px 520px at 50% 0%, #FFFFFF, transparent 70%), var(--color-bg); }
.home-why { display: grid; grid-template-columns: repeat(3, 1fr); gap: 48px; margin-top: 64px; }
.home-exam-row { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 72px; padding-top: 28px; border-top: 1px solid var(--color-border); }
.home-chip { font-size: 15px; font-weight: 500; border: 1px solid #DCD3C5; border-radius: 999px; padding: 9px 18px; }

/* 4. Gallery */
.home-gallery { padding: 110px 0;
  background: radial-gradient(1200px 520px at 80% 0%, #FFFFFF, transparent 70%), linear-gradient(180deg, var(--color-bg), #F8F3EC); }
.home-subject-row { margin-top: 56px; }
.home-subject { font-size: 14px; font-weight: 600; color: var(--color-primary-dark); letter-spacing: 0.04em; text-transform: uppercase; margin-bottom: 16px; }
.home-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
.home-tile { margin: 0; }
.home-frame { background: #0D1117; border-radius: 10px; padding: 6px; box-shadow: 0 24px 50px rgba(0, 0, 0, 0.4); }
.home-tile .home-frame { box-shadow: 0 26px 50px rgba(60, 38, 20, 0.18), 0 2px 4px rgba(60, 38, 20, 0.12); }
.home-frame img, .home-frame video { display: block; width: 100%; border-radius: 6px; }
.home-plate { background: #fff; border-radius: 12px; aspect-ratio: 4 / 3; padding: 16px; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 26px 50px rgba(60, 38, 20, 0.14), 0 2px 4px rgba(60, 38, 20, 0.08); }
.home-plate img { max-height: 100%; max-width: 100%; object-fit: contain; }
.home-tile figcaption { margin-top: 12px; font-size: 14px; color: var(--color-text-muted); line-height: 1.45; }
.home-credits { font-size: 13px; color: var(--color-text-muted); margin: 28px 0 0; }

/* 5. How a lesson builds - the mock's warm charcoal, paper text */
.home-lesson { color: #FFFDF9; padding: 110px 0;
  background: radial-gradient(1000px 520px at 50% 62%, rgba(255, 222, 190, 0.09), transparent 65%), #1F1C1A; }
.home-lesson .home-h2 { margin-top: 14px; }
.home-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 56px; }
.home-frame-card { margin: 0; }
.home-frame-card figcaption { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin-top: 16px; font-size: 15px; line-height: 1.5; color: #DED6CC; }
.home-frame-card b { font-weight: 600; letter-spacing: -.015em; font-size: 22px; color: #FFFDF9; }
.home-frame-card figcaption span:last-child { grid-column: 2; }
.home-step { width: 26px; height: 26px; border-radius: 13px; border: 1px solid #55504B; display: flex; align-items: center; justify-content: center; font-size: 13px; color: #DED6CC; margin-top: 2px; }

/* 6. What is live today */
.home-live { padding: 110px 0;
  background: radial-gradient(1100px 480px at 20% 0%, rgba(255, 255, 255, 0.7), transparent 65%), linear-gradient(180deg, var(--color-stone), #ECE4D8); }
.home-live-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 40px; }
.home-live-head .home-body { margin: 0; max-width: 26em; }
.home-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; margin-top: 56px; }
/* Tests and Narration take exactly the subject row's type. A smaller second row put a 40px
   figure under 64px ones with the units left at 22px, which read as a mistake (owner, 2026-10-18). */
.home-stats-small { margin-top: 48px; }
.home-stat { border-top: 1px solid #DCD3C5; padding-top: 22px; }
.home-stat-name { font-size: 15px; font-weight: 500; }
.home-stat-num { font-weight: 500; letter-spacing: -.015em; font-size: 64px; line-height: 1.05; margin-top: 8px; }
.home-stat-num span { font-size: 22px; color: var(--color-text-muted); }
.home-bar { height: 3px; background: #DCD3C5; margin-top: 14px; }
.home-bar i { display: block; height: 3px; background: var(--color-text); }
.home-stat-ex { font-size: 14px; color: var(--color-text-muted); margin-top: 12px; line-height: 1.5; }

/* 7. Pricing */
.home-plans { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 56px; }
.home-plan { border: 1px solid #DCD3C5; border-radius: 4px; padding: 36px; background: var(--color-surface); box-shadow: 0 24px 48px rgba(70, 45, 25, 0.08); }
.home-plan-main { color: #FFFDF9; border-color: #2A2623;
  background: radial-gradient(600px 320px at 85% 0%, rgba(255, 255, 255, 0.08), transparent 65%), #2A2623; }   /* black, 2026-10-27 */
.home-price { font-weight: 500; letter-spacing: -.015em; font-size: 56px; line-height: 1; margin-top: 18px; }
.home-price small { font-size: 20px; opacity: 0.75; }
.home-plan ul { margin: 22px 0 28px; padding: 0; list-style: none; font-size: 16px; line-height: 1.9; }
.home-plan li::before { content: ''; display: inline-block; width: 9px; height: 6px; border-left: 2px solid #C93A2F; border-bottom: 2px solid #C93A2F;
  transform: rotate(-45deg) translateY(-3px); margin-right: 12px; }
.home-plan-main li::before { border-color: #FFFDF9; }

/* The FAQ, kept from the page this replaced */
.home-faq { max-width: 720px; margin-top: 96px; }
.home-faq .home-h3 { margin-bottom: 12px; }
.home-faq details { border-bottom: 1px solid var(--color-border); padding: var(--space-4) 0; }
.home-faq summary { cursor: pointer; font-size: 16px; font-weight: 600; list-style: none; min-height: 44px; display: flex; align-items: center; justify-content: space-between; }
.home-faq summary::-webkit-details-marker { display: none; }
.home-faq summary::after { content: "+"; color: var(--color-text-muted); font-weight: 400; }
.home-faq details[open] summary::after { content: "\2212"; }
.home-faq p { color: var(--color-text-muted); font-size: 15px; line-height: 1.7; margin: var(--space-3) 0 0; }

/* Dark mode: the mock's light gradients become the dark ground; the brown, charcoal and plate
   sections keep their own colours, as the moment does. Both blocks on purpose - see the dark-mode
   note near the top of this file. */
:root[data-theme="dark"] .home-animate, :root[data-theme="dark"] .home-pad,
:root[data-theme="dark"] .home-gallery { background: var(--color-bg); }
:root[data-theme="dark"] .home-live { background: var(--color-surface); }
:root[data-theme="dark"] .home-stat, :root[data-theme="dark"] .home-plan, :root[data-theme="dark"] .home-chip { border-color: var(--color-border); }
:root[data-theme="dark"] .home-bar { background: var(--color-border); }
:root[data-theme="dark"] .home-faint { color: #AEA49A; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .home-animate, :root:not([data-theme="light"]) .home-pad,
  :root:not([data-theme="light"]) .home-gallery { background: var(--color-bg); }
  :root:not([data-theme="light"]) .home-live { background: var(--color-surface); }
  :root:not([data-theme="light"]) .home-stat, :root:not([data-theme="light"]) .home-plan, :root:not([data-theme="light"]) .home-chip { border-color: var(--color-border); }
  :root:not([data-theme="light"]) .home-bar { background: var(--color-border); }
}

@media (max-width: 760px) {
  .home-wrap { padding: 0 16px; }
  .home-hero { padding: 48px 0 72px; }
  .home-scene { padding: 84px 0 70px; }
  .home-h1 { font-size: 46px; }
  .home-h2 { font-size: 36px; }
  .home-h3 { font-size: 26px; }
  .home-lede { font-size: 17px; }
  .home-f1 { left: 0; max-width: calc(100% - 178px); } .home-f2 { right: 0; } .home-f3 { right: 0; }
  .home-hero-grid, .home-why, .home-strip, .home-stats, .home-plans, .home-anim-grid { grid-template-columns: 1fr; }
  .home-hero-grid { gap: 40px; }
  .home-tiles { grid-template-columns: 1fr 1fr; }
  .home-animate, .home-gallery { padding: 64px 0; }
  .home-pad, .home-lesson, .home-live { padding: 64px 0; }
  .home-why { gap: 36px; margin-top: 40px; }
  .home-live-head { flex-direction: column; align-items: flex-start; gap: 16px; }
  .home-stats { gap: 28px; }
  .home-stat-num { font-size: 52px; }
  .home-plan { padding: 26px; }
  .home-exam-row { margin-top: 48px; }
  .home-faq { margin-top: 64px; }
}

/* The dark-brown "moment" (owner, 2026-10-18): #2B2724 with soft window light from the upper
   right and a little grain, from branding_assets/concepts-2026-10-17/pages/kit.py. Used for the
   landing hero only so far. Paper text on it passes comfortably; red TEXT never sits on it
   (1.3-2.3:1), so links become paper and the red appears only as the white-text button. Same in
   light and dark mode, as in the mock. */
.moment {
  position: relative; overflow: hidden; isolation: isolate;
  background: var(--color-brown-bg); color: #FFFDF9;
  --color-text-muted: var(--color-on-brown-muted);
}
.moment > * { position: relative; z-index: 1; }
.moment > .moment-light {
  position: absolute; z-index: 0; inset: -20% -10% auto auto; width: 75%; height: 140%; pointer-events: none;
}
.moment > .moment-light i {
  position: absolute; top: 0; width: 22%; height: 100%; filter: blur(26px); transform: skewX(-28deg);
  background: linear-gradient(180deg, rgba(255, 232, 200, 0.09), rgba(255, 232, 200, 0.02) 70%, transparent);
}
.moment > .moment-light i:nth-child(1) { right: 8%; }
.moment > .moment-light i:nth-child(2) { right: 34%; }
.moment > .moment-light i:nth-child(3) { right: 60%; opacity: 0.6; }
.moment::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; opacity: .10; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}
.moment a:not(.btn) { color: #FFFDF9; text-decoration: underline; text-underline-offset: 3px; }
.moment .btn-primary { box-shadow: 0 1px 0 rgba(255, 255, 255, .12) inset, 0 8px 20px rgba(0, 0, 0, .25); }
.moment .video-stage { box-shadow: 0 18px 40px rgba(0, 0, 0, .35); }

@media (max-width: 640px) {
  /* The video, edge to edge.
     ------------------------------------------------------------------------------------------
     The stage was 320px wide inside a 360px screen and spent 12px of that on its own padding, so
     a 1280px render arrived at 296px - a scale of 0.231. Labels are drawn at font_size 26-32,
     which is roughly 22-28px at 720p and therefore about five to seven pixels on the phone most
     of these students actually own. The page was spending 18% of its width on a dark margin to
     achieve that.

     Full width gives 360px: 22% more linear size, for nothing. On a page whose entire claim is
     "watch the idea, not the lecture", a diagram nobody can read is the product failing rather
     than the layout being tight.

     The rounded corners and the inner padding go with it. They are a card's manners, and at this
     width the video is not a card sitting on a page - it is the page. calc(50% - 50vw) rather
     than a fixed negative margin, so it does not need to know what padding its parent happens to
     have. */
  .video-stage {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    border-radius: 0;
    padding: 0;
  }
}


/* Notices and form pages
   -----------------------------------------------------------------------------------------
   One notice component rather than a differently-shaped message on each page. The three
   variants reuse the status colours already defined at the top, so a warning here and a
   warning in the admin queue are the same yellow. */
.notice {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  font-size: 14px;
  line-height: 1.6;
  margin-bottom: 18px;
}
.notice.is-success { background: var(--color-success-bg); border-color: #bbf7d0; color: #14532d; }
.notice.is-error { background: var(--color-error-bg); border-color: #fecaca; color: var(--color-error); }
.notice.is-warning { background: var(--color-warning-bg); border-color: var(--color-warning-border); color: var(--color-warning); }
.notice a { color: inherit; }
.notice code { font-size: 13px; background: rgba(0, 0, 0, 0.06); padding: 1px 5px; border-radius: 4px; }

/* The single-column pages that are mostly one form - contact, and the enquiry forms that will
   follow it. Narrow on purpose: a form field the width of the page is harder to read down. */
.form-page { max-width: 560px; margin: 0 auto; padding: 0 20px 80px; }
.form-page .contact-form { display: flex; flex-direction: column; gap: 18px; }
.form-page .contact-form > div { display: flex; flex-direction: column; gap: 6px; }
.form-page label { font-size: 13px; font-weight: 600; }
.field-hint { font-size: 12px; color: var(--color-text-muted); line-height: 1.55; margin: 0; }


/* ---------------------------------------------------------------------------------------------
   Deliberately last in the file.

   Media queries add no specificity, so a plain `.field-hint { font-size: 12px }` declared later
   than an identical selector inside a media block simply wins - which is exactly what happened
   on the first attempt here: .field-hint was defined below the mobile block
   and stayed at 12px while everything else moved. Putting the floor last is what makes it a
   floor rather than a suggestion.
--------------------------------------------------------------------------------------------- */
@media (max-width: 640px) {
/* A type floor, in two tiers.
   ------------------------------------------------------------------------------------------
   Forty-one declarations in this file sit between 10.5px and 12px. The audience reads them on
   5.5-6.5" panels, at arm's length, often at night - and one of them is .ci-depth, the tier
   pill whose own comment says it "tells a student where to start", set at 10.5px uppercase.

   Two tiers rather than one number, because raising everything to 13px would erase the
   hierarchy that makes a label a label:

     LABELS - uppercase, letter-spaced - go to 12px. They are read as shapes, not sentences,
     and they earn their smallness by not being prose.

     READING TEXT goes to 13px. It is read as words, so it gets the floor.

   Admin-only classes are deliberately untouched - .trash-hint, .lib-size, .build-form label,
   .review-meta. An operator is at a desk with a mouse; a student is on a bus. */
.ci-depth, .result-verdict, .pick-tag, .result-tallies span,
.exam-q-num, .palette-title, .sidebar-chapter-progress { font-size: 12px; }

.ci-soon, .row-card .row-meta, .search-result .sr-meta, .field-hint,
.exam-review, .section-marks { font-size: 13px; }
}

/* ---------------------------------------------------------------------------------------------
   Tap targets, on the phone.

   Also last in the file, and for the same reason as the type floor above: several of these
   selectors are declared further down than the mobile block they would otherwise live in, and a
   media query adds no specificity.

   Measured at 360px before this rule existed:

     Mark for review        19px tall     the control a student uses most during a paper
     Clear response         88 x 16       a 13px underlined link doing a button's job
     View toggle            34 x 30
     Tier / voice pills     64 x 29       how a student picks NEET over JEE
     FAQ summaries          19px tall     on the page that has to answer objections
     Palette squares        39 x 39

   Forty is the floor rather than Apple's 44, to stay consistent with the nav in UI-5a and
   because these sit inside dense rows where 44 would force a reflow. Nothing here changes a
   font size or a colour - only the box around it grows, which is the cheapest possible fix for
   a thumb and invisible to a mouse.
--------------------------------------------------------------------------------------------- */
@media (max-width: 640px) {
  /* A label wrapping a checkbox: give the whole row the height, so the words are tappable too
     and not just the 13px box. */
  .exam-review { min-height: 40px; padding: 8px 0; align-items: center; }

  /* "Clear response" is a button that was dressed as a link. It stays looking like a link. */
  .linklike { min-height: 40px; padding: 12px 6px; }

  .view-btn { min-width: 40px; min-height: 40px; }

  .control-opt { min-height: 40px; padding: 10px 15px; }

  .palette-grid button { min-height: 40px; }


}

/* The way back in. Sits above the subject grid on Browse, and only when there is somewhere to
   continue to - see _resume_card in app.py.

   Weighted heavier than a subject card on purpose. A returning student has exactly one thing
   they want from this page, and every second spent finding it is a second of the study session
   spent on navigation. The subject grid is for the first visit and for changing subject; this is
   for the other nine times out of ten. */
.resume-card {
  display: flex; flex-direction: column; gap: 3px;
  padding: 16px 18px; margin-bottom: var(--space-6);
  border: 1px solid var(--color-primary); border-left-width: 4px;
  border-radius: var(--radius-md);
  background: var(--color-surface); text-decoration: none; color: var(--color-text);
  transition: box-shadow 0.15s ease;
}
.resume-card:hover { box-shadow: 0 0 0 3px var(--color-primary-light); }
.resume-label {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-primary-dark);
}
.resume-title { font-size: 17px; font-weight: 600; line-height: 1.3; }
.resume-meta { font-size: 13px; color: var(--color-text-muted); }

@media (max-width: 640px) {
  .resume-card { padding: 14px 16px; }
  .resume-title { font-size: 16px; }
}

/* Choosing an exam, at the door.

   This was a select marked "Exam (optional)", which is an instruction to skip - and skipping it
   hands a NEET student the whole 2,224-concept corpus instead of the ~1,576 on their paper.
   Three options shown are three decisions made; a closed dropdown is one more tap between a
   student and the thing they came for. */
.exam-choice { border: 0; padding: 0; margin: 0 0 var(--space-4); }
.exam-choice legend {
  padding: 0; margin-bottom: 8px;
  font-size: 14px; font-weight: 600; color: var(--color-text);
}
.exam-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.exam-tile {
  position: relative; display: flex; align-items: center; justify-content: center;
  min-height: 46px; padding: 10px 8px; cursor: pointer;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-size: 15px; font-weight: 600; text-align: center;
  transition: border-color 0.15s ease, background 0.15s ease;
}
/* The radio itself is covered rather than removed: hiding it with display:none would take it
   out of the tab order and off a screen reader, which is the whole control gone for anyone not
   using a mouse. */
.exam-tile input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.exam-tile:hover { border-color: var(--color-primary); }
.exam-tile:has(input:checked) {
  border-color: var(--color-primary); background: var(--color-primary-light);
  color: var(--color-primary-dark);
}
.exam-tile:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* The unread count in the admin bar. Small, but it is the whole difference between an enquiry
   being stored and an enquiry being noticed - one sat unread for a day because the bar looked
   identical whether the table was empty or not. */
.admin-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; margin-left: 6px; padding: 0 5px;
  border-radius: 999px;
  background: var(--color-error); color: #FFFFFF;
  font-size: 11px; font-weight: 700; line-height: 1;
}
