/* ============================================================
   MyAbhyas — style.css
   All custom CSS lives here. No inline styles anywhere in the PHP.
   Palette: Petrol & Coral (light). Tokens at the top drive everything —
   change a value there, never a literal in a rule below.
   ============================================================ */

/* ---------- 1. Tokens ---------- */

:root {
  /* ==========================================================
     PETROL & CORAL
     Light theme. Petrol teal carries structure — links, the active
     nav item, section marks, the score ring, headline numbers.
     Coral carries action — every primary button, the streak, the
     quota chip. Cool identity, warm call to action.

     Token NAMES are inherited from two palettes ago and are kept so
     rules do not have to churn. Several now read oddly against their
     value (--teal is petrol, --forest is an alias for it, --amber is
     the coral). Read the comment, not the name.

     NEVER put two coral buttons side by side. If a screen has two
     equally important actions, one of them becomes a text link.

     THE PRINTED PAPER IS SEPARATE. paper.css holds its own light
     values and must never consume these.
     ========================================================== */

  /* grounds */
  --mist:       #F7F9F9;   /* page background */
  --paper:      #FFFFFF;   /* raised surface — inputs, tables, wells */
  --surface-2:  #ECF1F1;   /* inset panels */
  --sidebar-bg: #FFFFFF;   /* navigation rail */
  --track:      #E4ECEC;   /* meter and progress-bar tracks */

  /* ink */
  --ink:        #10201F;
  --ink-90:     #1B2E2D;
  --ink-70:     #354746;
  --muted:      #4A5C5B;
  --muted-soft: #87999A;

  /* text ON a filled ground — never use --paper or --ink for this */
  --on-accent:  #FFFFFF;   /* on petrol */
  --on-accent-2: rgba(255,255,255,.82);   /* secondary text on petrol */
  --on-accent-3: rgba(255,255,255,.22);   /* hairlines and tracks on petrol */
  --on-coral:   #2B0D06;   /* on coral — dark, because coral stays bright */
  --on-dark:    #10201F;   /* the rail is light now; this is its ink */
  --on-dark-2:  #6C7E7E;

  /* lines and washes */
  --line:       #DCE5E5;
  --line-soft:  #E7EDED;
  --line-faint: #E4ECEC;
  --hover:      #F1F6F6;
  --hover-2:    #E4ECEC;

  /* brand */
  --teal:       #0E5C63;   /* PETROL — structure and links */
  --teal-hover: #10737C;
  --forest:     #0E5C63;   /* legacy alias — same petrol */
  --amber:      #E2603F;   /* CORAL — actions */
  --amber-hover:#F0724F;

  /* The testimonial wall's ground, the one saturated surface outside the
     Petrol & Coral pair. It takes white type: 6.3:1 against white, and only
     2.7:1 against the page ink, so --ink must never be set on it. */
  --brick:      #B0322F;
  --on-brick:   #FFFFFF;
  --on-brick-2: rgba(255,255,255,.86);   /* .76 composited to 4.3:1, under AA at 12px */
  --on-brick-3: rgba(255,255,255,.22);

  /* tints */
  --teal-tint:  #E3EFF0;
  --amber-tint: #FCEAE4;
  --forest-tint:#EAF1F1;

  /* state */
  --ok:         #17795C;
  --warn:       #C6902B;
  --err:        #A82B22;   /* pulled well away from the coral */
  --err-tint:   #FBEAE8;
  /* A brighter red than --err, for the two places we ask the student to tell
     us we got something wrong: the disclaimer on the printed paper and the
     same words on the solutions page. --err stays where it is — it is tuned
     against the coral and every other error state in the app wears it. */
  --err-strong: #c11212;
  --grey-tint:  #EDF2F2;
  /* THE CALL-TO-ACTION SURFACES: the upgrade banners and the landing page's
     CTA band. Near-black, the owner's choice, 2026-09-21. They were petrol,
     which made them a louder version of the same colour the whole interface
     already uses for structure; near-black separates them from everything
     around them without adding a hue.
     Both are --ink. The second is a shade deeper so the gradient still reads
     as one, with --promo-lift picking out the top-left corner.
     Measured rather than eyeballed, and it fixed something: the coral eyebrow
     on these banners sat at 2.2:1 against the petrol, which is a fail. On ink
     it is 4.8:1. White body text goes from 7.7:1 to 16.8:1. */
  --promo-from: #10201F;
  --promo-to:   #061312;
  /* The blueprint total row on the landing page borrowed --promo-to and is not
     a CTA. It keeps the petrol it has always had, under its own name, so the
     change above cannot reach it. */
  --band-row:   #12454A;
  --promo-line: #0B4A50;
  --ok-line:    #B9DBD0;
  --warn-line:  #EBD7AE;
  --warn-strong:#8A6417;   /* ochre dark enough to carry white text */
  --on-fill:    #FFFFFF;   /* text on any saturated fill */
  --on-fill-2:  rgba(255,255,255,.78);
  --on-fill-3:  rgba(255,255,255,.55);
  --err-line:   #E8C3BF;
  --scrim:      rgba(2,5,12,.65);   /* behind the mobile sidebar */
  --scrim-deep: rgba(2,5,12,.88);   /* behind a viewed attachment */

  /* Texture. Every one of these is a ruling on paper, not decoration for its
     own sake: this is a product about working problems out on squared paper,
     and the page should feel like the desk it is used at. Kept faint enough
     that nothing measured on these grounds moves -- the strongest is 6% ink. */
  --rule-ink:   rgba(14,92,99,.055);   /* squared paper, on the light ground */
  --rule-ink-2: rgba(14,92,99,.085);   /* its every-fifth heavier rule */
  /* The dots are LIGHTER than the band they sit on, not darker. A petrol
     dot at 10% dropped the muted text over it to 4.17:1 -- under AA, and
     the plain tint only has 4.84:1 of headroom to spend. A light dot
     raises the local ground instead, so the worst case is 5.29:1: better
     than the tint itself, and the texture can be strong enough to see. */
  --rule-dot:   rgba(255,255,255,.6);  /* the dotted grid on a tinted band */
  --rule-matrix:rgba(255,255,255,.08); /* the dot field on the dark band */
  --rule-plaster:rgba(255,255,255,.055);/* striations on the testimonial wall */
  --promo-lift:rgba(255,255,255,.13); /* the lit corner of the upgrade banner */
  --promo-matrix:rgba(255,255,255,.12); /* its dot field — a step up from
                     --rule-matrix, because the banner's teal is lighter
                     than the landing band's and .08 disappears on it */
  --mask-solid: #000;                  /* not a colour: the opaque end of a mask */
  /* The same squared paper, drawn in light instead of petrol, for the hero
     now that its ground is dark. The petrol ruling above is invisible on a
     petrol field — it was still being drawn there, which is why the hero
     looked flat the moment the background changed. */
  --rule-ink-lt:   rgba(255,255,255,.055);
  --rule-ink-lt-2: rgba(255,255,255,.085);

  /* The three stacked landing bands: the hero, the scrolling strip of what
     the product does, and the counters under it. Their own tokens rather
     than --teal directly, so the hero can be re-grounded without dragging
     every link and section mark on the site with it. */
  --hero-bg:     #0E5C63;
  --hero-bg-2:   #0B4A50;
  --hero-line:   rgba(255,255,255,.18);

  --marq-bg:     #FFFFFF;
  --marq-ink:    #0E5C63;
  --marq-line:   #DCE5E5;
  --marq-dot:    #C6D6D6;   /* the ring between two claims */

  /* The owner's colour. It is the same value as --err, and that is worth
     knowing rather than tidying away: --err is a STATE and this is a
     surface, so they must stay two tokens. Move one and the other does not
     follow. White on it is 6.4:1, so --on-fill and --on-fill-2 both clear
     AA here. */
  --count-bg:    #A82B22;
  --count-bg-2:  #8E241C;
  /* The hero's own call to action, on the owner's eye. Same red as the
     counter band, and a THIRD token holding that value rather than an alias
     of either: this is a button, the band is a surface and --err is a state,
     and they have no reason to move together. White on it, not --on-coral —
     the dark ink the coral button wears measures 3.1:1 here and fails, where
     white clears AA at 6.4:1. */
  /* The landing hero's button, near-black with the rest of the CTAs. The name
     is two colours old now -- like --amber and --forest -- and renaming it
     would touch every rule that reads it for no gain. Read the comment. */
  --cta-red:      #10201F;
  --cta-red-hover:#1B2E2D;

  /* The three plan-card headers. A colour each, so the cards read as three
     things rather than one thing repeated — and in the order the page wants
     them taken: petrol leads, the red sits beside it, and Free is a neutral
     that asks for nothing. All three take white type: 6.4:1, 6.4:1 and 8.9:1
     against --on-fill. */
  --plan-topper:  #0E5C63;
  --plan-focus:   #A82B22;
  --plan-free:    #354746;

  /* The corner ribbon: a lit face and a shaded one, so the band has a
     direction rather than being one flat red. A third value held the folded
     tails at each end; those are gone and so is it. */
  --ribbon-lit:   #FF0700;
  --ribbon-shade: #D00500;
  --count-line:  rgba(255,255,255,.28);

  /* type — Archivo does everything; hierarchy from weight and size.
     Maths stays serif-italic to sit with KaTeX, which renders equations
     in its own serif and would clash with Archivo. */
  --font-head: 'Archivo', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Archivo', 'Segoe UI', system-ui, sans-serif;
  --font-math: 'Newsreader', Georgia, serif;

  /* scale */
  --t-xs:   0.75rem;
  --t-sm:   0.8125rem;
  --t-base: 0.9375rem;
  --t-md:   1.0625rem;
  --t-lg:   1.375rem;
  --t-xl:   1.875rem;
  --t-2xl:  2.5rem;

  /* space */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 20px; --s-6: 24px; --s-8: 32px; --s-10: 40px;
  --s-12: 48px; --s-16: 64px;

  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 16px;

  --shadow-sm: 0 1px 2px rgba(16,32,31,.06);
  --shadow-md: 0 6px 20px -12px rgba(16,32,31,.22);
  --shadow-lg: 0 18px 44px -22px rgba(16,32,31,.3);

  --sidebar-w: 260px;
}

/* ---------- DENSITY RULE ----------
   MyAbhyas is compact by default. Padding, gaps and row heights below are
   the system baseline — do not add roomier variants. If something needs more
   air, increase it here so every screen moves together.
   ----------------------------------- */

/* ---------- 2. Reset ---------- */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--t-base);
  line-height: 1.6;
  color: var(--ink);
  background: var(--mist);
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  /* Sora and Manrope ship a discretionary ligature that turns "(c)" into
     the copyright sign. MCQ option keys are exactly "(a) (b) (c) (d)", so
     that silently corrupts every option (c). Allow only common ligatures. */
  font-variant-ligatures: common-ligatures;
}

h1, h2, h3, h4, h5 {
  font-family: var(--font-head);
  font-weight: 700;
  line-height: 1.22;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 0 0 var(--s-3);
  color: var(--ink);
}

h1 { font-size: 1.5625rem; letter-spacing: 0.035em; }  /* 25px */
h2 { font-size: 1.25rem;   letter-spacing: 0.04em; }   /* 20px */
h3 { font-size: 1.125rem;  letter-spacing: 0.05em; }   /* 18px */
h4 { font-size: 1rem;      letter-spacing: 0.06em; }   /* 16px */
h5 { font-size: 1rem;      letter-spacing: 0.06em; }   /* 16px */

p { margin: 0 0 var(--s-4); }
p:last-child { margin-bottom: 0; }

a { color: var(--teal); text-decoration: none; }
a:hover { text-decoration: underline; }

img, svg { max-width: 100%; height: auto; }

:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
  border-radius: 3px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ---------- 3. Layout shell ---------- */

.app { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--sidebar-bg);
  color: var(--on-dark);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.sidebar-brand {
  padding: var(--s-6) var(--s-5) var(--s-5);
  border-bottom: 1px solid var(--line-faint);
}

.sidebar-brand .logo {
  font-family: var(--font-head);
  font-size: 1.375rem;
  font-weight: 400;
  color: var(--on-dark);
  letter-spacing: 0.01em;
  display: block;
}
.sidebar-brand .logo-img {
  display: block;
  width: 100%;
  max-width: 200px;
  height: auto;
  margin: 0 auto 10px;
}
.sidebar-brand .logo:hover { text-decoration: none; }

/* The subject switch, under the wordmark. A select rather than a row of links,
   because the list grows: Maths and Science today, Social Science next, and a
   sidebar cannot spend three lines on something changed twice a term. */

/* The subject row at the top of New Paper. Bigger than the sidebar's switch
   because this is the page where the choice is actually made -- chapters mean
   nothing until the subject is settled, so it reads first and reads large.
   Petrol, not coral: the one coral button on this page is Generate Paper. */
/* The New Paper page when the student has clicked a subject their plan does
   not cover: the chapters are gone and this stands where they were. Big and
   plain, because it is the only thing on that half of the screen and it is
   answering a question the reader has just asked by clicking. */
.gen-locked {
  text-align: center;
  padding-block: var(--s-16);
}
.gen-locked-head {
  font-size: var(--t-xl);
  margin: 0 0 var(--s-3);
}
.gen-locked-sub {
  color: var(--muted);
  font-size: var(--t-md);
  max-width: 48ch;
  margin: 0 auto var(--s-6);
}
/* The same three lines the CTA banner carries, so the offer reads the same
   wherever the student meets it. */
.gen-locked-price {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-1);
  /* Tight to the button: the price and the button it belongs to are one
     offer, and a standard section gap between them read as two things.
     Matches .upgrade-banner-price .btn — the two have to move together. */
  margin-bottom: var(--s-2);
}
/* The banner's price is set for a dark fill — white on petrol. Here the same
   three lines sit on white, so they take the light-surface colours, exactly as
   .paid-lock-price does. And the price stays a step below .gen-locked-head:
   the headline is the answer to what the student just clicked; the price is
   the offer underneath it, not the loudest thing on the panel. */
.gen-locked-price .price-now {
  font-size: var(--t-xl);
  color: var(--forest);
}
.gen-locked-price .price-period {
  color: var(--muted);
}

.subject-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}
.subject-tab {
  flex: 1 1 0;
  min-width: 9rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--s-4) var(--s-5);
  font-family: inherit;
  font-size: var(--t-base);
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  cursor: pointer;
}
/* The picker is a <button> on the New Paper page and a link on the four
   report pages, where it filters instead of switching. Same control, same
   look; a link brings an underline with it that has to go. */
a.subject-tab { text-decoration: none; }
/* A subject the plan does not cover is still OFFERED — the New Paper picker
   offers it too, and answers with "Science is not in your plan" rather than
   pretending the subject is not there. Marked, not hidden: a control that
   vanishes teaches nothing, and the student who is wondering what they are
   missing is exactly the one being asked to upgrade. */
.subject-tab.is-locked { color: var(--muted); }
.subject-tab.is-locked:not(.is-on) { background: var(--surface-2); }
.subject-tab:hover:not(:disabled) { border-color: var(--teal); color: var(--teal); }
.subject-tab.is-on {
  background: var(--teal);
  border-color: var(--teal);
  color: var(--on-accent);
}
.subject-tab.is-on:hover { background: var(--teal-hover); color: var(--on-accent); }

/* Named, so a student knows it is coming; disabled, so they cannot fall into
   a subject with no chapters in it. */
.subject-tab:disabled {
  cursor: default;
  color: var(--muted);
  background: var(--surface-2);
  border-style: dashed;
}
.subject-tab-soon {
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted-soft);
}

@media (max-width: 620px) {
  .subject-tab { flex: 1 1 100%; }
}

.sidebar-brand .tag {
  font-size: var(--t-xs);
  color: var(--on-dark-2);
  margin-top: 2px;
  text-align: center;
}
.sidebar-brand .logo em {
  font-style: normal;
  color: var(--amber);
}

.sidebar-nav { padding: var(--s-4) var(--s-3); flex: 1; }

.nav-group { margin-bottom: var(--s-5); }

.nav-group-title {
  font-size: var(--t-xs);
  color: var(--muted-soft);
  padding: 0 var(--s-3);
  margin-bottom: var(--s-2);
  letter-spacing: .04em;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 9px var(--s-3);
  border-radius: var(--r-sm);
  color: var(--on-dark);
  font-size: var(--t-base);
  margin-bottom: 2px;
  transition: background .15s, color .15s;
}
.nav-item:hover {
  background: var(--hover);
  color: var(--on-dark);
  text-decoration: none;
}
.nav-item.is-active {
  background: transparent;
  color: var(--teal);
  font-weight: 700;
  box-shadow: inset 2px 0 0 var(--teal);
}
.nav-item svg { width: 17px; height: 17px; flex-shrink: 0; opacity: .85; }
.nav-item.is-active svg { opacity: 1; }

.nav-item .nav-count {
  margin-left: auto;
  font-size: var(--t-xs);
  background: var(--hover-2);
  padding: 1px 7px;
  border-radius: 20px;
}

/* Someone is waiting on you. Filled red, not a tint, so it reads across a
   sidebar of quiet grey counts -- the quota chip stays grey because a number
   of papers left is information, not an alarm. */
.nav-item .nav-count.is-alert {
  background: var(--err);
  color: var(--on-fill);
  font-weight: 700;
}

.sidebar-foot {
  padding: var(--s-4);
  border-top: 1px solid var(--line-faint);
}

.user-chip { display: flex; align-items: center; gap: var(--s-3); }
.user-chip .meta { flex: 1; min-width: 0; }
.signout-icon {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  color: var(--on-dark-2);
  display: grid;
  place-items: center;
  border: 2px solid var(--hover-2);
  border-radius: var(--r-sm);
}
.signout-icon svg { width: 18px; height: 18px; }
.signout-icon:hover { color: var(--on-dark); background: var(--line-faint); border-color: var(--hover-2); }

.avatar {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--teal);
  color: var(--on-dark);
  display: grid; place-items: center;
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--t-base);
  flex-shrink: 0;
}

.user-chip .name {
  color: var(--on-dark);
  font-size: var(--t-sm);
  font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.user-chip .sub { font-size: var(--t-xs); color: var(--on-dark-2); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  padding: 15px var(--s-6);
  display: flex;
  align-items: center;
  gap: var(--s-4);
  position: sticky; top: 0; z-index: 20;
}

.topbar h1 { margin: 0; font-size: 1.125rem; letter-spacing: 0.05em; }
.topbar .spacer { flex: 1; }

.hamburger {
  display: none;
  background: none; border: 0; padding: var(--s-2);
  color: var(--ink); cursor: pointer;
}

.content {
  padding: var(--s-5) var(--s-6);
  max-width: 1400px;
  width: 100%;
  margin-inline: auto;
}

/* Narrower reading column for reference pages */
.content.is-reading { max-width: 900px; }

/* ---------- 4. Cards ---------- */

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-4);
}

/* kept as an alias so older markup keeps working — compact is now the default */
.card.is-tight { padding: var(--s-4) var(--s-5); }

.card-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}
.card-head h2, .card-head h3 { margin: 0; }
.card-head .spacer { flex: 1; }
.card-head .note { font-size: var(--t-sm); color: var(--muted); }

.grid { display: grid; gap: var(--s-3); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
.grid-auto { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

/* ---------- 5. Marks rail — the signature element ----------
   Mirrors the CBSE marking scheme's right-hand mark column. */

.rail {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-5);
  align-items: start;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-soft);
}
.rail:last-child { border-bottom: 0; }

.rail-body { min-width: 0; }

.rail-mark {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--t-md);
  color: var(--forest);
  min-width: 46px;
  text-align: right;
  padding-left: var(--s-4);
  border-left: 1px solid var(--line);
  align-self: stretch;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
}

.rail-mark.is-earned { color: var(--teal); }
.rail-mark.is-lost   { color: var(--err); }
.rail-mark .of { color: var(--muted-soft); font-weight: 400; }

/* Compact variant — dense reference lists */
.rail.is-compact { padding: 9px 0; align-items: center; }
.rail.is-compact .rail-mark {
  font-size: var(--t-sm);
  min-width: 40px;
  align-self: center;
  padding-left: var(--s-3);
}
.rail.is-compact .rail-body { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.rail.is-compact .rail-body .meta { font-size: var(--t-sm); color: var(--muted); }
.rail.is-total { border-top: 1px solid var(--line); border-bottom: 0; padding-top: var(--s-3); }

/* The head of a blueprint section: its letter, what it is, and what it is
   worth. The rows beneath it carry only their question numbers, so this line
   has to read as a heading rather than as one more row -- hence the space
   above it and no rule under it. */
.rail.is-head { border-bottom: 0; padding-bottom: 2px; }
.rail.is-head + .rail { padding-top: 0; }
.rail.is-head:not(:first-child) { margin-top: var(--s-3); }
.rail.is-head .rail-mark { color: var(--forest); }

/* Section letter marker */
.sec-tag {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--t-base);
  color: var(--forest);
  background: var(--forest-tint);
  width: 22px; height: 22px;
  border-radius: 5px;
  display: inline-grid; place-items: center;
  flex-shrink: 0;
}

.card.is-tight { padding: var(--s-5) var(--s-6); }
.card.is-tight .card-head { margin-bottom: var(--s-3); }

.rail-label {
  font-size: var(--t-xs);
  color: var(--muted);
  margin-bottom: 2px;
}

/* ---------- 6. Stat tiles ---------- */

.stat {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-4);
}

.stat-label { font-size: var(--t-sm); color: var(--muted); margin-bottom: 5px; }

.stat-value {
  font-family: var(--font-head);
  font-size: var(--t-2xl);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.stat-value .unit { font-size: var(--t-md); color: var(--muted); font-weight: 500; }

.stat-foot { font-size: var(--t-xs); color: var(--muted); margin-top: 6px; }

.stat.is-accent { background: var(--teal-tint); border-color: var(--teal-tint); }
.stat.is-accent .stat-label,
.stat.is-accent .stat-foot { color: var(--muted-soft); }
.stat.is-accent .stat-value { color: var(--teal); }
.stat.is-accent .stat-value .unit { color: var(--muted-soft); }

/* ---------- 7. Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  font-family: var(--font-body);
  font-size: var(--t-base);
  font-weight: 500;
  line-height: 1;
  padding: 9px var(--s-4);
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
  white-space: nowrap;
}
.btn:hover { text-decoration: none; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* Standard across the site: buttons stay rectangular even when they carry an
   icon — no pill or circular icon-only buttons. .btn's flex + gap already
   spaces icon and label; this just fixes the icon size. */
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.btn-sm svg { width: 14px; height: 14px; }
.btn-lg svg { width: 18px; height: 18px; }

.btn-primary { background: var(--amber); color: var(--on-coral); }
.btn-primary:hover:not(:disabled) { background: var(--amber-hover); }

.btn-accent { background: var(--teal); color: var(--on-accent); font-weight: 600; }
.btn-accent:hover:not(:disabled) { background: var(--teal-hover); }

.btn-dark { background: var(--ink); color: var(--on-fill); }
.btn-dark:hover:not(:disabled) { background: var(--ink-70); }

.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover:not(:disabled) { border-color: var(--muted-soft); background: var(--surface-2); }

.btn-quiet { background: none; color: var(--muted); padding-left: var(--s-2); padding-right: var(--s-2); }
.btn-quiet:hover:not(:disabled) { color: var(--ink); }

.btn-danger { background: var(--err); color: var(--on-fill); }

.btn-block { width: 100%; }
.btn-sm { padding: 6px var(--s-3); font-size: var(--t-sm); }
.btn-lg { padding: 11px var(--s-5); font-size: var(--t-base); }

.btn-row { display: flex; gap: var(--s-2); flex-wrap: wrap; }

/* ---------- 8. Forms ---------- */

.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-3); }
.form-grid .full { grid-column: 1 / -1; }

/* Two fields and their save button on one row. align-items:end sits the
   button on the inputs' baseline rather than stretching it to the height
   of the label plus input. The hint spans the full width underneath so it
   cannot make one column taller than the others. */
.upi-form {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: var(--s-3);
  align-items: end;
}
.upi-form .field { margin-bottom: 0; }
.upi-form-hint { grid-column: 1 / -1; margin: 0; }

@media (max-width: 720px) {
  .upi-form { grid-template-columns: 1fr; align-items: stretch; }
}

.field { display: block; margin-bottom: var(--s-3); }
.field:last-child { margin-bottom: 0; }

.field label,
.label {
  display: block;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--ink-70);
  margin-bottom: 5px;
}

.field .req { color: var(--err); }

input[type=text], input[type=email], input[type=password],
input[type=number], input[type=tel], input[type=date],
select, textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--t-base);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 8px var(--s-3);
  transition: border-color .15s, box-shadow .15s;
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-tint);
}

input::placeholder, textarea::placeholder { color: var(--muted-soft); }

input:disabled, select:disabled, textarea:disabled {
  background: var(--surface-2); color: var(--muted); cursor: not-allowed;
}

input[type=file] {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--t-sm);
  color: var(--ink-70);
  padding: var(--s-2) 0;
}

textarea { min-height: 96px; resize: vertical; line-height: 1.55; }

select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%235C6B72' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-4) center;
  padding-right: var(--s-10);
}

.field-hint { font-size: var(--t-xs); color: var(--muted); margin-top: var(--s-2); }
.field-error { font-size: var(--t-xs); color: var(--err); margin-top: var(--s-2); }
.has-error input, .has-error select { border-color: var(--err); }

/* Choice cards — used for Basic/Standard stream picker */

.choice-set { display: grid; gap: var(--s-2); }
.choice-set.cols-2 { grid-template-columns: 1fr 1fr; }
.choice-set.cols-3 { grid-template-columns: repeat(3, 1fr); }

/* ---------- Chapter picker ---------- */

.unit-block { padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.unit-block:last-child { border-bottom: 0; padding-bottom: 0; }
.unit-block:first-of-type { padding-top: 0; }

.unit-head {
  display: flex; align-items: baseline; gap: var(--s-3);
  margin-bottom: 6px; flex-wrap: wrap;
}
.unit-head .meta { font-size: var(--t-xs); color: var(--muted); }

.chap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 6px;
}

.chap {
  position: relative;
  display: flex; align-items: center; gap: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 6px var(--s-2) 6px 8px;
  cursor: pointer;
  background: var(--paper);
  transition: border-color .12s, background .12s;
}
.chap:hover:not(.is-off) { border-color: var(--muted-soft); }
.chap input { position: absolute; opacity: 0; pointer-events: none; }

.chap-no {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  color: var(--muted);
  background: var(--mist);
  width: 20px; height: 20px;
  border-radius: 4px;
  display: grid; place-items: center;
  flex-shrink: 0;
}

.chap-body { min-width: 0; line-height: 1.3; }
.chap-name {
  display: block;
  font-size: var(--t-sm);
  font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chap-meta { display: block; font-size: var(--t-xs); color: var(--muted); }

.chap:has(input:checked) { border-color: var(--teal); background: var(--teal-tint); }
.chap:has(input:checked) .chap-no { background: var(--teal); color: var(--on-fill); }
.chap:has(input:focus-visible) { outline: 2px solid var(--teal); outline-offset: 2px; }

/* :has() fallback — SEPARATE RULES, AND THAT IS THE WHOLE TRICK.

   These cannot be folded into the selector lists above. CSS drops an entire
   rule when any selector in its list is unparseable, so
   `.chap:has(input:checked), .chap.is-checked { ... }` on a browser without
   :has() throws away the .is-checked half too -- which is the failure this
   is here to prevent, reintroduced by the tidier-looking way of writing it.

   What was at stake: every :has() rule in this file styles SELECTION state,
   on the chapter picker, the stream picker and the MCQ answer options. Where
   :has() is unsupported the input still ticks and the form still posts, so
   nothing breaks loudly -- the student simply gets no visual confirmation of
   what they have chosen, on the two screens where that is the only feedback
   there is. Silent, and worse for it. Firefox only shipped :has() in 121.

   `.is-checked` is kept in step by syncChecked() in assets/js/main.js. A
   browser that supports :has() applies both and they agree; one that does not
   gets the class alone. If JavaScript is off AND :has() is missing there is
   no highlight, which is the one gap left and needs both to be true. */
.chap.is-checked { border-color: var(--teal); background: var(--teal-tint); }
.chap.is-checked .chap-no { background: var(--teal); color: var(--on-fill); }
.chap.is-focus { outline: 2px solid var(--teal); outline-offset: 2px; }

.chap.is-off { opacity: .45; cursor: not-allowed; background: var(--surface-2); }

/* ---------- Generate bar ---------- */

.gen-bar { display: flex; align-items: center; gap: var(--s-5); flex-wrap: wrap; }
.gen-summary { display: flex; flex-direction: column; gap: 2px; }
.gen-count {
  font-family: var(--font-head);
  font-size: var(--t-lg);
  color: var(--ink);
}
.gen-summary .meta { font-size: var(--t-sm); color: var(--muted); }
.gen-note { font-size: var(--t-sm); color: var(--muted); }

.choice {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 10px var(--s-4);
  cursor: pointer;
  background: var(--paper);
  transition: border-color .15s, background .15s;
}
.choice:hover { border-color: var(--muted-soft); }

.choice input { position: absolute; opacity: 0; pointer-events: none; }

.choice-title {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--t-md);
  letter-spacing: 0.02em;
  margin-bottom: 2px;
  display: flex; align-items: center; gap: var(--s-2);
}

.choice-desc { font-size: var(--t-sm); color: var(--muted); line-height: 1.45; }

/* Not a description any more but the reason the choice is locked, so it stops
   wearing --muted and says so. Set from PHP on first render and toggled by
   refreshFullPaper() as chapters are ticked, because the text itself is
   swapped there. */
.choice-desc.is-locked-why { color: var(--err-strong); font-weight: 700; }

.choice:has(input:checked) {
  border-color: var(--teal);
  background: var(--teal-tint);
}
.choice:has(input:checked) .choice-title { color: var(--forest); }
.choice:has(input:focus-visible) { outline: 2px solid var(--teal); outline-offset: 2px; }

/* :has() fallback — see the note at .chap.is-checked. Separate rules, never
   merged into the lists above. This one matters most of the three: choosing
   a stream is a once-only decision the student cannot revisit. */
.choice.is-checked {
  border-color: var(--teal);
  background: var(--teal-tint);
}
.choice.is-checked .choice-title { color: var(--forest); }
.choice.is-focus { outline: 2px solid var(--teal); outline-offset: 2px; }

/* ---------- 9. Tables ---------- */

.table-wrap { overflow-x: auto; margin: 0 calc(var(--s-5) * -1) calc(var(--s-4) * -1); padding: 0 var(--s-5) var(--s-4); }

table.data { width: 100%; border-collapse: collapse; font-size: var(--t-base); }

/* A RIGHT-ALIGNED COLUMN NEEDS A RIGHT-ALIGNED HEADER, and the `.right`
   utility cannot do it alone: `table.data th` below is a class plus two
   elements (0,1,2) and `.right` is one class (0,1,0), so the header stayed
   left while its numbers sat right. Reordering does not help -- this is
   specificity, not source order, which is what makes it different from the
   landmine written at the top of section 19.

   Measured on the Influencers page: the header's computed text-align was
   `left` and the cell's was `right`, on every numeric column.

   Same shape as `.plan-compare th.center` further down, and written for
   `table.data` generally because 16 headers across four pages were affected,
   including the student's own referral page. */
table.data th.right,  table.data td.right  { text-align: right; }
table.data th.center, table.data td.center { text-align: center; }

table.data th {
  text-align: left;
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--muted);
  padding: 6px var(--s-3);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

table.data td {
  padding: 8px var(--s-3);
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}

table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--surface-2); }

table.data .num { text-align: right; font-variant-numeric: tabular-nums; }

/* ONE WIDTH FOR THE NUMBER COLUMNS. Sized by their contents they came out all
   different -- "Signed up" is wider than "Paid" -- so the eye had to re-find
   the figures on every row. A fixed width makes them a block that reads down
   as well as across.

   In ch rather than px so it follows the font, and 7ch because the widest
   thing any of them holds is a rupee figure like "Rs 1,19,800". The width sits
   on the header; a table column takes it from there. */
table.data th.num-col,
table.data td.num-col { width: 6.5rem; min-width: 6.5rem; }

/* Compact table — dense reference rows */
table.data.is-tight th { padding: var(--s-2) var(--s-3); }
table.data.is-tight td { padding: 9px var(--s-3); }
table.data .col-divider { border-left: 1px solid var(--line); }
table.data .col-divider-right { border-right: 1px solid var(--line); }
table.data.is-tight th.tpl-col,
table.data.is-tight td.tpl-col { padding-left: var(--s-1); padding-right: var(--s-1); font-size: var(--t-xs); }
table.data.is-tight td .strong { display: block; }
table.data.is-tight td .meta {
  display: block;
  font-size: var(--t-xs);
  color: var(--muted);
  line-height: 1.4;
}

/* ---------- 10. Badges ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--t-xs);
  font-weight: 500;
  padding: 3px var(--s-3);
  border-radius: 20px;
  line-height: 1.5;
}

.badge-result {
  font-size: var(--t-md);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 6px var(--s-4);
}

.badge-teal   { background: var(--teal-tint);   color: var(--teal); }
.badge-amber  { background: var(--amber-tint);  color: var(--warn); }
.badge-forest { background: var(--forest-tint); color: var(--forest); }
.badge-err    { background: var(--err-tint);    color: var(--err); }
.badge-grey   { background: var(--grey-tint);            color: var(--muted); }

/* ---------- 11. Alerts ---------- */

.alert {
  display: flex;
  gap: var(--s-3);
  padding: 10px var(--s-4);
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  margin-bottom: var(--s-3);
  border: 1px solid transparent;
}

.alert-success { background: var(--teal-tint);   border-color: var(--ok-line); color: var(--ok); }
.alert-error   { background: var(--err-tint);    border-color: var(--err-line); color: var(--err); }
.alert-warn    { background: var(--amber-tint);  border-color: var(--warn-line); color: var(--warn); }
.alert-info    { background: var(--forest-tint); border-color: var(--line); color: var(--ink-70); }

/* ---------- 12. Auth pages ---------- */

.auth {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr 480px;
}

.auth-aside {
  background: var(--sidebar-bg);
  color: var(--on-dark);
  padding: var(--s-12) var(--s-10);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  position: relative;
  overflow: hidden;
}

/* An aside carrying only a logo and a pitch.

   .auth-aside is space-between, which is right for the sign-in and sign-up
   pages: logo at the top, pitch, and the marking-scheme demo at the foot,
   three blocks spread down a full-height column. The password pages have
   nothing to put in the third slot, and with two children space-between
   drops the pitch to the floor of the screen, a long way from the logo it
   belongs to. */
.auth-aside.is-brief {
  justify-content: flex-start;
  gap: var(--s-10);
}

.auth-aside::after {
  content: '';
  position: absolute;
  right: -140px; bottom: -140px;
  width: 420px; height: 420px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(56,189,248,.22), transparent 70%);
  pointer-events: none;
}

.auth-aside .logo {
  display: block;
}
.auth-aside .auth-logo-img {
  display: block;
  width: 100%;
  max-width: 220px;
  height: auto;
}
.auth-aside .logo em { font-style: normal; color: var(--amber); }
.auth-aside .logo:hover { text-decoration: none; }

.auth-pitch { position: relative; z-index: 1; max-width: 30ch; }

.auth-pitch h2 {
  color: var(--on-dark);
  font-size: 2.0625rem;
  line-height: 1.22;
  letter-spacing: 0.03em;
  margin-bottom: var(--s-4);
}

.auth-pitch p { color: var(--on-dark-2); font-size: var(--t-md); }

/* Marking-scheme sample, doubles as the visual hook */
.scheme-demo {
  position: relative; z-index: 1;
  background: var(--hover);
  border: 1px solid var(--line-faint);
  border-radius: var(--r-md);
  padding: var(--s-5);
  max-width: 340px;
}
.scheme-demo .scheme-title {
  font-size: var(--t-xs);
  color: var(--on-dark-2);
  margin-bottom: var(--s-3);
}
.scheme-demo .scheme-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-4);
  padding: var(--s-2) 0;
  font-size: var(--t-sm);
  color: var(--on-dark);
  border-bottom: 1px solid var(--line-faint);
}
.scheme-demo .scheme-row:last-child { border-bottom: 0; }
.scheme-demo .scheme-row .m {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--t-md);
  color: var(--amber);
  min-width: 28px;
  text-align: right;
}

.auth-panel {
  background: var(--paper);
  padding: var(--s-12) var(--s-10);
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow-y: auto;
}

.auth-box { width: 100%; max-width: 380px; margin: 0 auto; }
.auth-box h1 { font-size: 1.5625rem; margin-bottom: var(--s-3); }
.auth-box .lede { color: var(--muted); margin-bottom: var(--s-8); }

.auth-foot {
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line-soft);
  font-size: var(--t-sm);
  color: var(--muted);
  text-align: center;
}

.auth-mobile-brand { display: none; }

/* ---------- 13. Utilities ---------- */

.stack > * + * { margin-top: var(--s-4); }
.row { display: flex; align-items: center; gap: var(--s-3); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); }
.spacer { flex: 1; }

.muted { color: var(--muted); }
.small { font-size: var(--t-sm); }
.xs { font-size: var(--t-xs); }
.strong { font-weight: 600; }
.right { text-align: right; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }

/* Utilities live at the end of this file — see section 19. */

.empty {
  text-align: center;
  padding: var(--s-8) var(--s-5);
  color: var(--muted);
}
.empty h3 { color: var(--ink); margin-bottom: var(--s-3); }
.empty p { max-width: 44ch; margin: 0 auto var(--s-4); }

/* ---------- 14. Responsive ---------- */

@media (max-width: 1024px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .auth { grid-template-columns: 1fr 420px; }
  .choice-set.cols-3 { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .auth { grid-template-columns: 1fr; }
  .auth-aside { display: none; }
  .auth-panel { padding: var(--s-8) var(--s-5); }
  .auth-mobile-brand {
    display: block;
    text-align: center;
    margin-bottom: var(--s-8);
  }
  .auth-mobile-brand .logo {
    display: block;
  }
  .auth-mobile-brand .auth-logo-img {
    display: block;
    width: 100%;
    max-width: 200px;
    height: auto;
    margin: 0 auto;
  }
  .auth-mobile-brand .logo em { font-style: normal; color: var(--teal); }
}

@media (max-width: 768px) {
  .sidebar {
    position: fixed;
    left: 0; top: 0;
    z-index: 60;
    transform: translateX(-100%);
    transition: transform .2s ease;
    box-shadow: var(--shadow-lg);
  }
  .sidebar.is-open { transform: translateX(0); }

  .scrim {
    position: fixed; inset: 0;
    background: var(--scrim);
    z-index: 50;
    display: none;
  }
  .scrim.is-open { display: block; }

  .hamburger { display: inline-flex; }

  .topbar { padding: var(--s-3) var(--s-4); }
  .topbar h1 { font-size: 1rem; letter-spacing: 0.05em; }

  .content { padding: var(--s-3); }

  .card { padding: var(--s-3) var(--s-4); border-radius: var(--r-sm); }

  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
  .form-grid { grid-template-columns: 1fr; gap: var(--s-4); }
  .choice-set.cols-2, .choice-set.cols-3 { grid-template-columns: 1fr; }
  .chap-grid { grid-template-columns: 1fr; }
  .gen-bar { flex-direction: column; align-items: stretch; }
  .gen-bar .btn { width: 100%; }

  .table-wrap { margin: 0 calc(var(--s-4) * -1) calc(var(--s-3) * -1); padding: 0 var(--s-4) var(--s-3); }

  .stat-value { font-size: var(--t-xl); }

  .rail { grid-template-columns: 1fr auto; gap: var(--s-4); }
  .rail-mark { min-width: 38px; padding-left: var(--s-3); }

  .btn-row .btn { flex: 1; }

  .step-row { grid-template-columns: 18px 1fr auto; }
  .step-label { display: none; }
  .ans-submit-bar { flex-direction: column; align-items: stretch; bottom: 0; }
  .attempt-toggle { flex-direction: row; }
}

@media (max-width: 420px) {
  .grid-auto { grid-template-columns: 1fr; }
}

/* ---------- 15. Answer Sheet & Self-Marking ---------- */

.ans-section-head {
  font-family: var(--font-head);
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--forest);
  margin: var(--s-5) 0 var(--s-2);
}
.ans-section-head:first-child { margin-top: 0; }

.ans-card { margin-bottom: var(--s-3); }

.q-order {
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--muted);
  margin-right: 4px;
}

.ans-qtext { margin: var(--s-2) 0 var(--s-3); line-height: 1.5; }
/* A case study's passage is the longest thing a student reads on the answering
   screen, and it was set at --t-sm — the caption size — two steps under the
   question above it. Body size, with the line-height long prose needs. The ink
   stays a shade soft so the passage still reads as context rather than as the
   question itself. */
.ans-context {
  font-size: var(--t-base);
  line-height: 1.65;
  color: var(--ink-70);
  margin-bottom: var(--s-3);
}

/* The running total on the answering screen, at the right-hand end of the
   question counter. Headline size: it was set inside the counter line at --t-sm
   and was the smallest thing on a row of small things, which is no use as a
   score somebody is meant to watch climb.

   Petrol, because it is structure — a number the student reads — and never
   coral, which is the single action on a screen.

   It belongs HERE, with the other answering-screen components, not appended to
   the end of the file: section 19 is utilities, and a component written after
   them would outrank a utility of the same specificity and quietly break it. */
.score-so-far { display: flex; align-items: baseline; gap: 6px; }
.score-so-far-num {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-lg);
  line-height: 1;
  color: var(--teal);
  font-variant-numeric: tabular-nums;
}
.score-so-far-label { font-size: var(--t-base); font-weight: 700; color: var(--muted); }

/* The one question a student MUST answer before the rest of the stop means
   anything: which half of an internal choice they did. Uppercase and --err,
   at the owner's direction, because it is a stop sign rather than a caption.
   --err and not --amber: coral is the single action on a screen and a label
   wearing it competes with the button. Letter-spaced, as .ans-or is — set
   caps tight and they read as shouting rather than as a label. */
/* A choice the student tried to skip past. Ringed, not recoloured: the buttons
   inside keep their own states, and the ring clears the moment one is pressed. */
.attempt-toggle.needs-choice {
  outline: 2px solid var(--err);
  outline-offset: 4px;
  border-radius: var(--r-sm);
}

.pick-prompt {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--err);
  margin-bottom: var(--s-2);
}

.ans-or {
  text-align: center;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: var(--s-4) 0;
}

.ans-input {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 8px var(--s-3);
  font-size: var(--t-base);
}
.ans-input:focus { outline: none; border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-tint); }

.ans-options { display: grid; gap: 6px; }
.ans-opt {
  display: flex;
  align-items: baseline;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 8px var(--s-3);
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.ans-opt:hover { border-color: var(--muted-soft); }
.ans-opt:has(input:checked) { border-color: var(--teal); background: var(--teal-tint); }
/* :has() fallback — see the note at .chap.is-checked. Separate rule. Without
   it a student answering an MCQ sees no mark against the option they picked. */
.ans-opt.is-checked { border-color: var(--teal); background: var(--teal-tint); }
.ans-opt input { margin: 0; flex-shrink: 0; }
.opt-key { color: var(--muted); }

.ans-subpart {
  border-top: 1px dashed var(--line);
  padding: var(--s-3) 0 0;
  margin-top: var(--s-3);
}
.ans-subpart:first-of-type { border-top: 0; margin-top: 0; padding-top: 0; }
.ans-subpart-q { margin-bottom: var(--s-2); line-height: 1.5; }
.ans-subpart-mark {
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--forest);
  margin-left: 6px;
}

/* Attempted / Skipped, and the (A) / (B) choice picker.
   These were --paper on a --paper card with a --line border: white on white,
   hairline apart, and stretched edge to edge by flex:1 so they read as two
   empty bars rather than as buttons. They now sit on --surface-2 with a
   --muted-soft edge, sized by their own labels and aligned left under the
   question, which is where the eye already is. */
.attempt-toggle {
  display: flex;
  gap: var(--s-2);
  justify-content: flex-start;
  flex-wrap: wrap;
  margin-top: var(--s-2);
}
.at-btn {
  flex: 0 0 auto;
  border: 1px solid var(--muted-soft);
  background: var(--surface-2);
  color: var(--ink-70);
  border-radius: var(--r-sm);
  padding: 5px var(--s-3);
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition: border-color .12s, background .12s, color .12s;
}
.at-btn:hover { border-color: var(--teal); color: var(--teal); }
.at-btn.is-on { border-color: var(--teal); background: var(--teal); color: var(--on-fill); }

/* The gap between "Which one did you attempt?" and the answer box that appears
   once a half is chosen.

   They were touching: the answer field sat directly under the (A)/(B) buttons
   with nothing between them, so the box read as part of the toggle rather than
   as the next thing to do.

   A dashed rule rather than plain space, because that is already what separates
   one part of an answer from the next here - .ans-subpart uses the same line.

   Scoped two ways on purpose. :not(.hide) means the rule appears only once a
   half has been chosen, so an untouched picker has no line hanging under it.
   :not(.ans-subpart) leaves the case-study part picker alone, where the
   revealed half is itself an .ans-subpart and already carries this exact
   border - without it that one would be divided twice. */
.attempt-toggle[data-picker] ~ .cs-variant:not(.hide):not(.ans-subpart) {
  border-top: 1px dashed var(--line);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
}

.ans-submit-bar {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  position: sticky;
  bottom: var(--s-3);
  box-shadow: var(--shadow-lg);
}

/* Self-marking checklist */

.mark-card { margin-bottom: var(--s-3); }

.step-row {
  display: grid;
  grid-template-columns: 18px 22px 1fr auto;
  gap: var(--s-3);
  align-items: start;
  padding: 8px 0;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
}
.step-row:last-of-type { border-bottom: 0; }

.step-row-disabled {
  opacity: 0.5;
  cursor: default;
}
.step-row-disabled input[type=checkbox] { cursor: default; }

.step-row input[type=checkbox] {
  width: 16px; height: 16px;
  margin-top: 3px;
  accent-color: var(--teal);
  cursor: pointer;
}

.step-label {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-xs);
  color: var(--muted);
  margin-top: 2px;
}

.step-text-wrap { line-height: 1.5; }
.step-text { }

.why-toggle {
  background: none;
  border: 0;
  color: var(--teal);
  font-size: var(--t-xs);
  cursor: pointer;
  padding: 0 0 0 6px;
  text-decoration: underline;
}

.step-reason {
  display: block;
  font-size: var(--t-xs);
  color: var(--muted);
  margin-top: 3px;
  padding-left: 2px;
  border-left: 2px solid var(--line);
  padding-left: 8px;
}

.step-mark {
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--forest);
  margin-top: 2px;
}

.mark-row-total {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

.inline-checklist {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px dashed var(--line);
}

/* Solutions page */

.sol-card { margin-bottom: var(--s-3); }

.sol-answer-row {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  flex-wrap: wrap;
  background: var(--mist);
  border-radius: var(--r-sm);
  padding: var(--s-3);
  margin-top: var(--s-2);
}
.sol-answer-row .strong:first-of-type { margin-right: var(--s-4); }

.sol-options { display: grid; gap: 6px; }
.sol-opt {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 8px var(--s-3);
  line-height: 1.5;
}
.sol-opt.is-correct { border-color: var(--teal); background: var(--teal-tint); }
.sol-opt.is-wrong { border-color: var(--err); background: var(--err-tint); }
.sol-opt .badge { margin-left: 6px; }
.sol-misconception {
  margin-top: 6px;
  font-size: var(--t-xs);
  color: var(--err);
}

.sol-worked { margin-top: var(--s-3); }

.sol-step {
  display: grid;
  grid-template-columns: 20px 22px 1fr auto;
  gap: var(--s-3);
  align-items: start;
  padding: 7px 0;
  border-bottom: 1px solid var(--line-soft);
  color: var(--muted-soft);
}
.sol-step:last-child { border-bottom: 0; }

/* A worked solution is not a marking scheme: there is nothing to tick and no
   marks to award against each line, so it drops the label and mark columns and
   gives the working the width instead. */
.sol-step.is-plain { grid-template-columns: 20px 1fr; }
.sol-step.is-on { color: var(--ink); }
.sol-step.is-on .step-mark { color: var(--teal); }
.sol-step:not(.is-on) .step-mark { color: var(--muted-soft); }

.sol-step-check {
  font-weight: 700;
  text-align: center;
  margin-top: 2px;
}
.sol-step.is-on .sol-step-check { color: var(--teal); }

.step-reason-inline {
  display: block;
  font-size: var(--t-xs);
  color: var(--muted);
  margin-top: 3px;
}

.sol-error-note {
  margin-top: var(--s-3);
  font-size: var(--t-xs);
  color: var(--warn);
  background: var(--amber-tint);
  border-radius: var(--r-sm);
  padding: 6px var(--s-3);
  display: inline-block;
}

/* ---------- 16. Narrative Explanations ---------- */

.explain-block {
  background: var(--forest-tint);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-4);
  margin: var(--s-3) 0;
}

.explain-label {
  font-family: var(--font-head);
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--forest);
  margin-bottom: var(--s-3);
}

.explain-stage {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-bottom: 1px dashed var(--line);
}
.explain-stage:last-child { border-bottom: 0; padding-bottom: 0; }
.explain-stage:first-child { padding-top: 0; }

.explain-num {
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--teal);
  color: var(--on-accent);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-sm);
  display: grid;
  place-items: center;
  flex-shrink: 0;
}

.explain-title {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-base);
  letter-spacing: 0.01em;
  color: var(--ink);
  margin-bottom: 4px;
}

.explain-intro {
  font-size: var(--t-sm);
  color: var(--ink-70);
  line-height: 1.55;
  margin: 0 0 var(--s-2);
}

.explain-bullets {
  margin: 0 0 var(--s-2);
  padding-left: 1.2em;
  font-size: var(--t-sm);
}
.explain-bullets li { margin-bottom: 3px; line-height: 1.5; }

.explain-formula {
  background: var(--paper);
  border-radius: var(--r-sm);
  padding: var(--s-3);
  margin: var(--s-2) 0;
  text-align: center;
  font-size: var(--t-base);
}
.explain-formula div { padding: 2px 0; }

.explain-conclusion {
  display: inline-block;
  background: var(--teal);
  color: var(--on-accent);
  font-size: var(--t-sm);
  font-weight: 500;
  border-radius: var(--r-sm);
  padding: 5px var(--s-3);
  margin-top: 2px;
}

/* ---------- 17. Print ---------- */

@media print {
  /* Deliberately white. The theme is dark; paper is not. Never token this. */
  .sidebar, .topbar, .btn, .scrim { display: none !important; }
  body { background: #fff; color: #000; }
  .content { padding: 0; }
  .card { border: 0; padding: 0; }
}

/* ===========================================================
   Progress section — Score / Chapter Strength / Mistake Book
   Append to assets/css/style.css
   =========================================================== */

/* --- Stat cards (Score page) --- */
.stat-row {
  display: grid !important;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-4);
  margin-bottom: var(--s-5);
}
.stat-card {
  position: relative;
  overflow: hidden;
  min-height: 96px;
  text-align: left;
  margin-bottom: 0; /* .card adds its own margin-bottom; the grid gap already spaces these */
}
.stat-card-text {
  position: relative;
  z-index: 2;
  min-width: 0;
}
.stat-icon {
  position: absolute;
  right: -20px;
  bottom: -20px;
  width: 120px;
  height: 120px;
  z-index: 1;
}
.stat-num {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-2xl);
  line-height: 1.1;
}
.stat-num.is-up   { color: var(--teal); }
.stat-num.is-down { color: var(--err); }
.stat-card .stat-label {
  font-size: var(--t-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-top: var(--s-2);
}
.stat-note {
  font-size: var(--t-xs);
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  opacity: 0.75;
  margin-top: var(--s-1);
}

/* Colour variants */
.stat-card--forest {
  background: var(--forest);
  border-color: var(--forest);
}
.stat-card--forest .stat-num,
.stat-card--forest .stat-label { color: var(--on-accent); opacity: .72; }
.stat-card--forest .stat-num.is-up   { color: var(--on-accent); }
.stat-card--forest .stat-num.is-down { color: var(--err); }

.stat-card--amber {
  background: var(--amber);
  border-color: var(--amber);
}
.stat-card--amber .stat-num,
.stat-card--amber .stat-label { color: var(--ink); }
.stat-card--amber .stat-num.is-up   { color: var(--forest); }
.stat-card--amber .stat-num.is-down { color: var(--err); }

@media (max-width: 720px) {
  .stat-row { grid-template-columns: repeat(2, 1fr) !important; }
  .stat-icon { width: 84px; height: 84px; right: -14px; bottom: -14px; }
}

/* --- Score trend chapter-picker dropdown --- */
.trend-select-form { display: inline-block; }
.trend-select {
  font-family: var(--font-body);
  font-size: var(--t-sm);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
}

/* --- Score trend chart (plain CSS bars, no JS lib) --- */
.trend-chart {
  display: flex;
  align-items: flex-end;
  gap: var(--s-2);
  height: 140px;
  padding-top: var(--s-4);
}
.trend-bar-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
}
.trend-bar {
  width: 100%;
  max-width: 32px;
  background: var(--teal);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  min-height: 4px;
}
.trend-bar-label {
  font-size: var(--t-xs);
  color: var(--muted);
  margin-top: var(--s-2);
}

/* --- Generic horizontal strength/accuracy bar (reused by qtype grid + chapter strength) --- */
.bar-track {
  flex: 1;
  height: 8px;
  background: var(--line-soft);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.bar-fill {
  height: 100%;
  border-radius: var(--r-sm);
  background: var(--muted-soft);
}
.bar-fill.is-strong { background: var(--teal); }
.bar-fill.is-mid    { background: var(--amber); }
.bar-fill.is-weak   { background: var(--err); }

/* --- Accuracy by question type (Score page) --- */
.qtype-grid { display: flex; flex-direction: column; gap: var(--s-3); }
.qtype-row  { display: flex; align-items: center; gap: var(--s-4); }
.qtype-name { width: 160px; font-size: var(--t-sm); color: var(--ink-70); flex-shrink: 0; }
.qtype-pct  { width: 48px; text-align: right; font-size: var(--t-sm); color: var(--muted); flex-shrink: 0; }

/* --- Chapter Strength list --- */
.chapter-strength-list { display: flex; flex-direction: column; gap: var(--s-5); }
.chapter-row { display: flex; flex-direction: column; gap: var(--s-2); }
.chapter-row-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.chapter-row-meta { font-size: var(--t-xs); color: var(--muted); }
.chapter-row.is-muted { opacity: 0.6; }
.chapter-row.is-muted .chapter-row-head { margin-bottom: 0; }

/* --- Filter pills (Mistake Book) --- */
.filter-pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-3);
}
.pill {
  display: inline-block;
  padding: var(--s-1) var(--s-3);
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  font-size: var(--t-sm);
  color: var(--ink-70);
  text-decoration: none;
  white-space: nowrap;
}
.pill:hover     { border-color: var(--forest); color: var(--forest); }
.pill.is-active { background: var(--teal); border-color: var(--teal); color: var(--on-fill); }

/* --- Mistake Book cards --- */
.mistake-card { margin-bottom: var(--s-4); }
.mistake-card.is-reviewed { opacity: 0.65; }
.mistake-question {
  font-size: var(--t-base);
  color: var(--ink);
  margin: var(--s-3) 0;
}
.mistake-note {
  font-size: var(--t-sm);
  color: var(--ink-70);
  margin-bottom: var(--s-2);
}
.mistake-note.muted { color: var(--muted); }
.mistake-actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line-soft);
}
.mistake-actions form.inline { display: inline; }

/* ===========================================================
   Generate-paper gate — pending-submission lock
   Append to assets/css/style.css
   =========================================================== */

/* Reset the browser's default fieldset chrome so it's invisible
   until we actually need the locked state. */
#genForm fieldset {
  border: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}

#genForm fieldset.is-locked {
  opacity: 0.45;
  pointer-events: none; /* belt-and-braces alongside the disabled attribute */
  filter: grayscale(0.4);
}

/* Individual length option disabled by a daily cap (paid plan) — same
   treatment as the existing .chap.is-off for disabled chapters. */
.choice.is-off { opacity: .45; cursor: not-allowed; background: var(--surface-2); }

/* ===========================================================
   Free Plan badge + upgrade CTAs
   Append to assets/css/style.css
   =========================================================== */

/* --- Sidebar plan chip --- */
.plan-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  background: var(--amber-tint);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-3);
}
.plan-chip-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-xs);
  font-weight: 700;
  color: var(--warn);
  white-space: nowrap;
}
.plan-chip-label svg { width: 14px; height: 14px; flex-shrink: 0; }
.plan-chip-upgrade {
  font-size: var(--t-xs);
  font-weight: 700;
  color: var(--forest);
  text-decoration: none;
  white-space: nowrap;
}
.plan-chip-upgrade:hover { text-decoration: underline; }

/* --- Dashboard upgrade banner --- */
.upgrade-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  flex-wrap: wrap;
  position: relative;
  overflow: hidden;

  /* Every other surface in the app is a flat card on a flat ground. This
     is the one that is allowed to be louder: an accent-washed gradient, a
     cyan edge and a soft outer glow, so it reads as an offer rather than
     as another panel. Free-plan students only. */
  background: linear-gradient(135deg, var(--promo-from) 0%, var(--promo-to) 100%);
  border: 1px solid var(--promo-line);
  box-shadow: 0 0 0 1px rgba(56,189,248,.10), 0 16px 38px -20px rgba(56,189,248,.5);
}

/* The dot matrix, the same one the landing page's dark band wears — the
   texture is what stops a large filled panel reading as flat colour. It is on
   ::before because ::after is the sheen, and it is masked away towards the
   right so the dots never crowd the price and the button sitting there. */
.upgrade-banner::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--promo-matrix) 1px, transparent 1px);
  background-size: 12px 12px;
  -webkit-mask-image: linear-gradient(105deg, var(--mask-solid) 0%, var(--mask-solid) 45%, transparent 78%);
          mask-image: linear-gradient(105deg, var(--mask-solid) 0%, var(--mask-solid) 45%, transparent 78%);
  pointer-events: none;
}

/* A slow sheen crossing the banner — enough to catch the eye on a page of
   still cards, slow enough not to nag. */
.upgrade-banner::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(56,189,248,.14) 50%, transparent 65%);
  transform: translateX(-100%);
  animation: promo-sheen 6.5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes promo-sheen {
  0%, 62% { transform: translateX(-100%); }
  92%, 100% { transform: translateX(100%); }
}
.upgrade-banner > * { position: relative; z-index: 1; }
.upgrade-banner-text {
  flex: 1;
  min-width: 240px;
  max-width: 100%;
  text-align: left;
  overflow-wrap: break-word;
  word-break: break-word;
}
.upgrade-banner-eyebrow {
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--amber);
  margin-bottom: var(--s-2);
}
.upgrade-banner-text h2 { color: var(--on-fill); margin: 0 0 var(--s-2); }
.upgrade-banner-text p { color: var(--on-fill-2); margin: 0; max-width: 46ch; }
.upgrade-banner-text p.upgrade-banner-pizza {
  color: var(--amber);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: var(--t-sm);
  margin-top: var(--s-3);
  max-width: none;
}
@media (min-width: 641px) {
  .upgrade-banner-text p.upgrade-banner-pizza { white-space: nowrap; }
}

.upgrade-banner-price {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  /* The badge sits ON the price -- they are one block. The button is a
     separate thing and stands 12px off it, the same 12px the locked panel
     uses, so the offer is spaced identically wherever it is met. Stated as
     margins rather than one gap because the two distances are not the same
     distance and should not be able to drift together. */
  gap: 0;
  text-align: right;
}
.upgrade-banner-price .price-row { margin-top: 1px; }
.upgrade-banner-price .btn { margin-top: var(--s-2); }
.price-row {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
}
.price-was-big {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-2xl);
  color: var(--err);
  text-decoration: line-through;
  text-decoration-color: var(--err);
  text-decoration-thickness: 2px;
}
@keyframes discount-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}
.price-discount-badge {
  color: var(--on-fill);
  font-size: var(--t-lg);
  font-weight: 700;
  line-height: 1.1;
  animation: discount-blink 1.2s ease-in-out infinite;
}
.price-was {
  font-size: var(--t-base);
  color: var(--muted-soft);
  text-decoration: line-through;
}
.price-now {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-2xl);
  color: var(--on-fill);
  line-height: 1;
}
.price-period {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--t-sm);
  color: var(--on-dark-2);
}

@media (max-width: 640px) {
  .upgrade-banner { flex-direction: column; align-items: stretch; text-align: center; }
  .upgrade-banner-text { min-width: 0; }
  .upgrade-banner-price { align-items: center; text-align: center; }
  .price-row { justify-content: center; }
}

/* ===========================================================
   Paid-feature locking — nav, locked pages, comparison table
   Append to assets/css/style.css
   =========================================================== */

/* --- Sidebar: locked Progress nav items --- */
.nav-item.is-locked-nav {
  opacity: 0.5;
}
.nav-item .nav-lock {
  margin-left: auto;
  font-size: var(--t-xs);
  filter: grayscale(1);
  opacity: 0.8;
}

/* --- Paid-feature-lock page (Scores/Chapter Strength/Mistake Book teaser) --- */
.paid-lock-card {
  text-align: center;
  max-width: 480px;
  margin: var(--s-10) auto 0;
  padding: var(--s-8) var(--s-6);
}
.paid-lock-icon { font-size: 2.5rem; margin-bottom: var(--s-3); }
.paid-lock-card h2 { margin: 0 0 var(--s-3); }
.paid-lock-card p { max-width: 40ch; margin: 0 auto var(--s-5); }
.paid-lock-price {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--s-3);
  margin-bottom: 0;
}
.paid-lock-price .price-was { color: var(--err); text-decoration: line-through; font-size: var(--t-xl); font-family: var(--font-head); font-weight: 700; }
.paid-lock-price .price-now { font-family: var(--font-head); font-weight: 700; font-size: var(--t-xl); color: var(--forest); }
.paid-lock-price .price-period { font-family: var(--font-body); font-weight: 400; font-size: var(--t-sm); color: var(--muted); }
.paid-lock-discount { font-weight: 700; font-size: var(--t-base); color: var(--warn); margin-bottom: var(--s-3); }

/* --- Free vs. Paid comparison table --- */
.plan-compare th.center, .plan-compare td.center { text-align: center; }
.plan-compare .check-mark { color: var(--teal); font-weight: 700; font-size: var(--t-md); }
.plan-compare .x-mark { color: var(--muted-soft); font-weight: 700; font-size: var(--t-md); }
table.data th.plan-compare-head { font-size: 1.125rem; font-weight: 700; color: var(--ink); }

/* ===========================================================
   Referral page
   Append to assets/css/style.css
   =========================================================== */

.referral-hero {
  background: linear-gradient(135deg, var(--forest-tint) 0%, var(--teal-tint) 100%);
  border-color: var(--teal-tint);
}
.referral-hero-text h2 { margin: 0 0 var(--s-2); color: var(--ink); }
.referral-hero-text p { margin: 0; max-width: 60ch; }

.referral-link-row {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.referral-link-input {
  flex: 1;
  min-width: 220px;
  font-family: var(--font-body);
  font-size: var(--t-sm);
  color: var(--ink-70);
  background: var(--mist);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: var(--s-3);
}
/* Two referral links per partner: the landing page and the signup form. They
   track identically, so the tag is there to say where each one LANDS, not to
   mark one as preferred. Stacked rather than side by side because a whole URL
   is long and the cell already sits in a wide table. */
.link-pair { display: flex; flex-direction: column; gap: var(--s-2); }
.link-pair-row { display: flex; flex-direction: column; gap: var(--s-1); cursor: text; }
.link-pair-tag {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.referral-note { color: var(--err); font-weight: 700; margin-top: var(--s-3); }
.referral-stat-row { grid-template-columns: repeat(2, 1fr); }

/* ===========================================================
   Subscription page — plan comparison
=========================================================== */
/* All three plans on ONE row, so the page is a comparison rather than a list.
   Read down a column and you see what a plan gives you; read across a row and
   you see what the next one adds -- which is the question the page exists to
   answer, and two-up with the third underneath could not show it.
   Three columns need room, so below 980px they stack. There is deliberately no
   two-column step in between: with three cards it orphans one, and the orphan
   is Topper, which is the one being recommended. */
.plan-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  margin-top: var(--s-5);
}
/* The signed-in Plans page offers two, not three: Focus is not sold in here.
   Without this the third column stays empty and the two cards sit squashed
   against the left. */
.plan-grid.is-two { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 980px) {
  .plan-grid, .plan-grid.is-two { grid-template-columns: 1fr; }
}

.plan-card {
  display: flex;
  flex-direction: column;
  position: relative;
}
/* The grid supplies the spacing, so the card's own bottom margin must go.
   Leaving it in does not add a gap -- it SHORTENS the card. A grid item is
   stretched to the row's height and the margin is taken out of that, so
   Starter and Focus rendered 543px against Topper's 563 and the row had a
   visible step in it. Topper escaped only because a :last-child rule zeroes
   its margin, which is why it looked like the featured card was wrong rather
   than the other two. Two classes, so this beats `.card` in section 18. */
.plan-grid .plan-card { margin-bottom: 0; }
.plan-card--featured {
  border-color: var(--amber);
  border-width: 2px;
  box-shadow: 0 4px 16px rgba(251, 191, 36, 0.18);
}
.plan-card-badge {
  position: absolute;
  top: -12px;
  right: var(--s-5);
  background: var(--amber);
  color: var(--ink);
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 4px var(--s-3);
  border-radius: var(--r-sm);
}

.plan-card-head {
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--line);
}
.plan-card-head h3 { margin: 0 0 var(--s-2); }
.plan-card-price {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-2xl);
  color: var(--ink);
}
.plan-card-period {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--t-sm);
  color: var(--muted);
}

.plan-feature-list {
  list-style: none;
  margin: 0 0 var(--s-5);
  padding: 0;
  flex: 1;
}
.plan-feature-list li {
  padding: var(--s-2) 0;
  color: var(--muted-soft);
  font-size: var(--t-sm);
}
.plan-feature-list li.is-included {
  color: var(--ink);
}
.plan-feature-list li.is-included::before {
  content: "✓ ";
  color: var(--teal);
  font-weight: 700;
}
/* A cross, not a dash. On a comparison the excluded rows have to read as
   excluded at a glance, against the ticks beside them; a dash reads as "no
   information" rather than "you do not get this". The text stays muted so the
   row still recedes -- it is the mark that carries the meaning, not the label. */
.plan-feature-list li:not(.is-included)::before {
  content: "✗ ";
  color: var(--err);
  font-weight: 700;
}

.plan-card-form { margin-top: auto; }

/* Already-subscribed confirmation state — mirrors .paid-lock-card */
.plan-active-card {
  text-align: center;
  padding: var(--s-6);
}
.plan-active-icon { font-size: 2.5rem; margin-bottom: var(--s-3); }
.plan-active-card h2 { margin: 0 0 var(--s-3); }
.plan-active-card p { max-width: 46ch; margin: 0 auto var(--s-5); }

/* ---------- Referral payouts (admin) ---------- */
/* Inline "do the thing" forms: a small text input beside its button, on
   one line, wrapping on narrow screens. Tokens only, no new colours. */
.payout-action {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  margin-bottom: var(--s-2);
}
.payout-action:last-child { margin-bottom: 0; }

.input-sm {
  font-size: var(--t-sm);
  padding: 5px 8px;
  max-width: 150px;
}

/* ============================================================
   18. SECTIONS
   Every section is its own box. An earlier pass tried a cardless,
   rules-and-space layout; in a mockup it reads as restraint, on a real
   page of dense content it reads as one unbroken column of text. The
   box rules further up this file are the ones that apply — this section
   only adds what sits on top of them.
   ============================================================ */

/* A section box: white on the page ground, hairline edge, soft lift. */
.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  padding: var(--s-5);
  margin-bottom: var(--s-5);
}
.card.is-tight { padding: var(--s-4) var(--s-5) var(--s-5); }
.card:last-child { margin-bottom: 0; }

/* The heading sits inside its box, separated by a hairline rather than
   floating in space. No dash, five points larger than the base size. */
.card-head {
  padding-bottom: var(--s-3);
  margin-bottom: var(--s-4);
  border-bottom: 1px solid var(--line-soft);
}
.card-head h2 {
  font-size: calc(var(--t-sm) + 5px);
  letter-spacing: .12em;
  color: var(--teal);
  display: flex;
  align-items: center;
  margin: 0;
}

/* The dashboard greeting is a page heading, not a section label, and it
   sits in the hero rather than a card-head — so the selector is not
   scoped to one. */
.page-greeting, .card-head h2.page-greeting {
  font-size: calc((var(--t-sm) + 5px) * 2);
  font-weight: 800;
  margin-top: 5px;
  letter-spacing: -.02em;
  text-transform: none;
  color: var(--ink);
  line-height: 1.1;
}

/* Each number gets its own box too, so a stat row reads as a set of
   tiles rather than three columns of loose text. */
.stat {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  padding: var(--s-5);
}
.stat-value { font-size: var(--t-xl); font-weight: 800; letter-spacing: -.02em; }

table.data tbody tr:hover { background: var(--hover); }

/* Status reads at a glance: filled, labelled, never colour alone. */
.badge { border-radius: var(--r-sm); font-weight: 800; letter-spacing: .08em; }
.badge-teal  { background: var(--ok);          color: var(--on-fill); }
.badge-amber { background: var(--warn-strong); color: var(--on-fill); }
.badge-err   { background: var(--err);         color: var(--on-fill); }
.badge-grey  { background: var(--surface-2);   color: var(--muted); }
.badge-forest{ background: var(--teal);        color: var(--on-fill); }

/* The upgrade banner is the one surface allowed to be louder than a box. */
.upgrade-banner {
  border: 0;
  border-radius: var(--r-md);
  padding: var(--s-6);
  /* Two gradients, not one. The linear pass sets the direction; the radial one
     lifts the top-left corner, so the panel reads as lit from somewhere rather
     than simply sloping. NOTE: the earlier .upgrade-banner rule also sets a
     background and this one overrides it — change the colour HERE. */
  background:
    radial-gradient(120% 90% at 12% 0%, var(--promo-lift) 0%, transparent 58%),
    linear-gradient(135deg, var(--promo-from) 0%, var(--promo-to) 100%);
  /* Neutral now the panel is near-black. A petrol-tinted shadow under an ink
     panel reads as a colour cast rather than as depth. */
  box-shadow: 0 16px 38px -22px rgba(16,32,31,.55);
}
.upgrade-banner .btn-accent { background: var(--amber); color: var(--on-coral); }
.upgrade-banner .btn-accent:hover { background: var(--amber-hover); }
.upgrade-banner-eyebrow { color: var(--amber); }
.upgrade-banner-text p.upgrade-banner-pizza { color: var(--amber); }
.price-was, .price-was-big { color: var(--on-fill-3); }
.price-discount-badge { color: var(--amber); }
/* The same look WITHOUT the blink. .price-discount-badge animates, which is
   right for "70% OFF · Limited Period Offer" and wrong for a label that is
   simply naming what the plan includes — a blinking "All Three Subjects" is
   flashing at the reader for no reason, and blinking text is a thing WCAG
   asks you to be able to stop. */
.price-plain-badge {
  color: var(--amber);
  font-size: var(--t-lg);
  font-weight: 700;
  line-height: 1.1;
}

/* The rail is a column of type with a hairline edge. */
.sidebar { border-right: 1px solid var(--line); }
.sidebar-brand { border-bottom: 1px solid var(--line); }
.sidebar-foot { border-top: 1px solid var(--line); }
.nav-item:hover { background: var(--hover); }

/* ============================================================
   18b. DASHBOARD
   The greeting, the streak, the score ring and the chapter-strength
   summary. Colour marks meaning here: petrol for the score, coral for
   the streak, and the reserved weak/medium/strong ramp for chapters —
   always beside a word, never colour on its own.
   ============================================================ */

/* TOP-ALIGNED, not centred. With the streak block now carrying a week of dots
   and two lines of text under its number, it is much taller than the greeting
   beside it — and align-items: center pushed the greeting down by half that
   difference. That was the large blank space above "Welcome back", which no
   padding anywhere could explain. */
.hero { display: flex; gap: var(--s-6); align-items: flex-start; flex-wrap: wrap; }
.hero-main { flex: 1 1 280px; min-width: 0; }
.hero-sub { color: var(--muted); font-size: var(--t-sm); margin: var(--s-2) 0 0; }
.hero .btn-row { margin-top: var(--s-4); }

/* The free-tier line under the greeting. Darker than .hero-sub beside it: the
   counter above is a status, this is the sentence telling a student what plan
   they are on and that there is a way off it.
   The link is PETROL, like every other link on the site. Coral was written
   here first, on the reasoning that this is a call to action -- but coral is
   for buttons, and as link text it lands around 3.5:1 on white, under the 4.5
   body text needs. The house rule already had the answer: petrol carries
   links, coral carries buttons. */
.free-tier-note { color: var(--ink); font-size: var(--t-base); }
.free-tier-note a { color: var(--teal); text-decoration: underline; }
.free-tier-note a:hover { text-decoration: none; }

/* The live balance of free papers. Red and a step up from .hero-sub beside
   it, at the owner's instruction: it is the number that decides whether the
   Generate button will work at all, and at --t-sm in muted grey it read as
   part of the greeting rather than as a limit. */
.papers-left {
  color: var(--err-strong);
  font-size: var(--t-base);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .02em;
}



/* The streak is the one warm thing on the page besides the button. */
.streak {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  border-left: 3px solid var(--amber);
  /* Equal above and below, so the coral rule is centred against the block
     rather than running past it at one end. */
  padding: var(--s-4) 0 var(--s-4) var(--s-5);
  /* Was `0 0 auto`, which made this block exactly as wide as its contents —
     right for a single number, cramped once a week of dots and a line of text
     sit under it. A basis with room to shrink keeps it honest on a phone. */
  /* NEVER SHRINK. With `0 1 380px` this block could be squeezed by the
     greeting beside it until only the coral rule and the flame were left —
     the number and the week clipped out of sight, which looks like a broken
     card rather than a narrow one. Fixed width with wrap on the parent means
     it either sits beside the greeting at full size or drops to its own line
     underneath. It cannot end up as a sliver. */
  flex: 0 0 380px;
}
.flame { width: 30px; height: 30px; flex: 0 0 auto; fill: var(--amber); }
.streak-n { font-size: var(--t-xl); font-weight: 800; line-height: 1; color: var(--amber); }
.streak-l { font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase;
            font-weight: 700; color: var(--ink); }

/* Score ring — the figure it draws is also written beside it, so the
   ring is reinforcement rather than the only way to read the number. */
/* The ring sits to the RIGHT of the figures it illustrates. The label, the
   score and the footnote read top to bottom on the left, which is where the
   eye starts; the ring is reinforcement and belongs after them.
   order + margin-left rather than row-reverse, so the MARKUP order stays the
   reading order — a screen reader still hears the score before the chart. */
.stat-ring { display: flex; align-items: center; gap: var(--s-4); }
.stat-ring .ring { order: 2; margin-left: auto; }
.ring { width: 62px; height: 62px; flex: 0 0 auto; }
.ring circle { fill: none; stroke-width: 7; stroke-linecap: round; }
.ring .ring-bg { stroke: var(--track); }
.ring .ring-fg { stroke: var(--teal); }
/* 14px, not 17. "100%" at 17px comes to roughly 44 units against the 47 of
   clear space inside the arc — and the ring is a circle, so the corners of the
   text sit nearer the stroke than its midline does. It touched the arc at a
   full score, which is exactly the moment a student is looking at it. */
.ring text { fill: var(--ink); font-family: var(--font-head); font-size: 14px; font-weight: 800; }

/* Chapter strength summary */
.chap-row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding-block: var(--s-3);
  border-bottom: 1px solid var(--line-soft);
}
.chap-row:last-child { border-bottom: 0; }
.chap-row-name { flex: 1 1 auto; min-width: 0; }
.chap-row-name .meta { display: block; font-size: var(--t-xs); color: var(--muted-soft); margin-top: 2px; }

.bar { flex: 0 0 130px; height: 10px; background: var(--track); border-radius: 999px; overflow: hidden; }
.bar i { display: block; height: 100%; border-radius: 999px; }

.chap-row-pct { flex: 0 0 48px; text-align: right; font-weight: 800; font-size: var(--t-base); }
.chap-row-pill {
  flex: 0 0 84px;
  text-align: center;
  font-size: var(--t-xs);
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 4px 0;
  border-radius: var(--r-sm);
  color: var(--on-fill);
}

/* The reserved three-step ramp. Used on the bar, the number and the
   label together, so the state is never carried by colour alone. */
.bar i.is-weak,   .chap-row-pill.is-weak   { background: var(--err); }
.bar i.is-medium, .chap-row-pill.is-medium { background: var(--warn-strong); }
.bar i.is-strong, .chap-row-pill.is-strong { background: var(--ok); }
.chap-row-pct.is-weak   { color: var(--err); }
.chap-row-pct.is-medium { color: var(--warn-strong); }
.chap-row-pct.is-strong { color: var(--ok); }

@media (max-width: 720px) {
  .streak { border-left: 0; border-top: 3px solid var(--amber); padding: var(--s-3) 0 0; width: 100%; }
  .chap-row { flex-wrap: wrap; gap: var(--s-2) var(--s-3); }
  .chap-row-name { flex: 1 1 100%; }
  .bar { flex: 1 1 auto; }
}

/* ============================================================
   18c. QUESTION DIAGRAMS
   Figures are drawn as plain black line work, the way CBSE prints
   them, so they need no colour of their own here — only room to
   breathe and a cap on how wide they run.
   ============================================================ */

.q-figure {
  margin: var(--s-4) 0;
  text-align: center;
}
.q-figure svg {
  /* Each figure carries its own width — a shape needs less room than a
     graph with numbered axes — so cap on the column, not a fixed size. */
  max-width: 100%;
  height: auto;
}

/* The NCERT figure itself, shown in a worked answer where the question asked
   the student to draw one. A bitmap cut from the textbook, so it has a fixed
   natural size — capped on the column, and never stretched past its own
   resolution, which would make the labels mushy. */
.ncert-fig { margin: 0; }
.ncert-fig img {
  max-width: min(100%, 420px);
  height: auto;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--paper);
}
.ncert-fig figcaption {
  margin-top: var(--s-2);
  font-size: var(--t-xs);
  color: var(--muted);
}

/* The same picture on a QUESTION, with the printed names removed. No frame
   and no caption: it is part of the question, not a citation beside it. */
.ncert-q {
  max-width: min(100%, 460px);
  height: auto;
}

/* ============================================================
   18b. QUESTION TABLES
   A frequency distribution inside a question. CBSE prints these as a
   grid -- classes across the top, frequencies underneath -- and so do
   we, because the same numbers strung along one line are a worse
   question. Sized to its contents, not to the page, and it scrolls
   sideways on a phone rather than squeezing the digits together.
   ============================================================ */

.qtab-wrap { overflow-x: auto; margin: var(--s-3) 0; }

.qtab {
  border-collapse: collapse;
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  background: var(--paper);
  max-width: 100%;
}

/* The two-column form: the left column is a phrase, so it gets to wrap. */
.qtab-col th:first-child { white-space: normal; font-weight: 400; }
.qtab-col tr:first-child th { font-weight: 700; text-align: center; }

.qtab th,
.qtab td {
  border: 1px solid var(--ink);
  padding: 8px 14px;
  text-align: center;
  white-space: nowrap;
  line-height: 1.35;
}

/* The leftmost cell names the quantity, as on the paper, and is the only one
   allowed to wrap onto a second line — "Daily income (in Rs)" is a label, not
   a value. It carries no fill: an exam table is line work, not a web table. */
.qtab th:first-child {
  text-align: left;
  font-weight: 700;
  white-space: normal;
  min-width: 7em;
}

.qtab td { font-weight: 400; }

/* ============================================================
   18d. LANDING PAGE
   The public page at /. It is laid out as a CBSE question paper,
   because a question paper is what the product makes: a marks column
   runs down the right of the specimen, the blueprint and the pricing,
   so the page states what MyAbhyas is instead of describing it.

   The palette rules hold here as everywhere. Petrol carries structure,
   coral is the single primary action on the screen (the sign-up button,
   and nothing else), and the one loud surface is the promo gradient
   already used by the upgrade banner.
   ============================================================ */

.lp { background: var(--mist); }
.lp img { max-width: 100%; }

.lp-wrap {
  max-width: 1140px;
  margin: 0 auto;
  padding-inline: var(--s-6);
}

/* --- top bar --- */

.lp-top {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  /* A petrol rule across the very top. One line, and the bar stops looking
     like a default browser chrome. */
  border-top: 3px solid var(--teal);
}
.lp-top-in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding-block: var(--s-2);
}

/* The logo sits with the product line, the way it does on the app's own rail,
   so the bar says what this is rather than only whose it is. */
.lp-logo { display: flex; align-items: center; gap: var(--s-4); }
.lp-logo:hover { text-decoration: none; }
/* Double the old 40px. The wordmark is the one piece of the brand on this
   page and it was reading as a caption next to the nav. The bar's own block
   padding comes down a step so the masthead grows by the logo's difference
   and not by more -- it is sticky, and every pixel of it is taken off the
   page for the whole visit. */
.lp-top .lp-logo img { display: block; height: 80px; width: auto; }
.lp-logo-tag {
  font-size: var(--t-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  padding-left: var(--s-4);
  border-left: 1px solid var(--line);
  white-space: nowrap;
}

.lp-top-nav { display: flex; align-items: center; gap: var(--s-6); }
.lp-top-nav a {
  font-family: var(--font-head);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
  position: relative;
  padding-block: var(--s-2);
}
.lp-top-nav a:hover { color: var(--teal); text-decoration: none; }
/* The underline grows from the link rather than blinking on, which is the
   only movement anywhere in the bar. */
.lp-top-nav a:not(.btn)::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--teal);
  transform: scaleX(0);
  transition: transform .18s ease;
}
.lp-top-nav a:not(.btn):hover::after { transform: scaleX(1); }
.lp-top-nav .btn { color: var(--on-coral); letter-spacing: .04em; }
.lp-top-nav .btn-accent { color: var(--on-accent); }

@media (prefers-reduced-motion: reduce) {
  .lp-top-nav a:not(.btn)::after { transition: none; }
}

/* --- shared section furniture --- */

.lp-sec { padding-block: var(--s-16); }
.lp-sec.is-tight { padding-block: var(--s-12); }

.lp-eyebrow {
  font-family: var(--font-head);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: var(--s-3);
}
.lp-band .lp-eyebrow { color: var(--on-fill-2); }

.lp-h2 { font-size: var(--t-xl); margin: 0 0 var(--s-3); }
.lp-sub { color: var(--muted); font-size: var(--t-md); max-width: 62ch; margin: 0 0 var(--s-10); }
.lp-band .lp-sub { color: var(--on-fill-2); }
/* The one phrase in a standfirst that must not be skimmed past — the Topper's
   price. .lp-sub is deliberately muted, so a <strong> inside it needs the ink
   back as well as the weight, or it reads as slightly heavier grey. The band
   rule is the same thought on a dark ground: full white, not page ink, which
   would be near-black text on petrol. */
.lp-sub strong { font-weight: 700; color: var(--ink); }
.lp-band .lp-sub strong { color: var(--on-fill); }

/* --- hero --- */

/* Squared paper. The 24px ruling with a heavier line every fifth is the sheet
   a student actually works on, and it fades out down the section so it never
   competes with the specimen card sitting on top of it.
   ------------------------------------------------------------------
   The ruling and its fade live on a pseudo-element, not on the section. A
   mask applies to everything inside the element it is set on, so putting the
   fade on .lp-hero faded the hero -- the button, the three figures and the
   whole specimen card all dimmed towards the bottom of the section. Nothing
   about the stacking order changes that; the texture has to be a layer of
   its own. */
.lp-hero {
  padding-block: var(--s-16) var(--s-12);
  position: relative;
  isolation: isolate;
  background-color: var(--hero-bg);
  background-image: linear-gradient(160deg, var(--hero-bg), var(--hero-bg-2));
  color: var(--on-fill);
}
.lp-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right,  var(--rule-ink-lt) 0 1px, transparent 1px 24px),
    repeating-linear-gradient(to bottom, var(--rule-ink-lt) 0 1px, transparent 1px 24px),
    repeating-linear-gradient(to right,  var(--rule-ink-lt-2) 0 1px, transparent 1px 120px),
    repeating-linear-gradient(to bottom, var(--rule-ink-lt-2) 0 1px, transparent 1px 120px);
  -webkit-mask-image: linear-gradient(to bottom, var(--mask-solid) 0%, var(--mask-solid) 45%, transparent 92%);
          mask-image: linear-gradient(to bottom, var(--mask-solid) 0%, var(--mask-solid) 45%, transparent 92%);
}

/* On the dark ground every one of these was reading as page ink, which is
   nearly the ground itself. The specimen card to the right keeps its own
   light surface and its own ink, so none of this reaches inside it. */
.lp-hero .lp-eyebrow { color: var(--on-fill-2); }
.lp-hero h1          { color: var(--on-fill); }
/* THE SPECIMEN CARD KEEPS ITS OWN INK. It is a white sheet of paper sitting
   on the dark ground, and colour inherits: setting the hero's text to white
   set the card's text to white as well, on a white card. The question, its
   marking scheme and its figure all vanished, leaving three coral marks
   floating in an empty box — and the page still rendered perfectly, which is
   why this is worth a comment rather than a one-line fix. Any future light
   surface dropped into the hero needs the same line. */
.lp-hero .lp-spec { color: var(--ink); }
.lp-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-16);
  align-items: center;
}
.lp-hero h1 {
  font-size: var(--t-2xl);
  margin: 0 0 var(--s-5);
  max-width: 16ch;
  text-wrap: balance;
}
.lp-hero .lp-lede {
  font-size: var(--t-md);
  /* Full white, not the 78% the small labels wear: this is the only
     paragraph of real prose on the screen, and at 78% over the petrol it
     read as greyed-out text — as though it had been disabled.
     Set HERE rather than in the block of hero-on-dark overrides above,
     because that block and this rule have the same specificity and this one
     is written later, so it wins. The override looked correct and did
     nothing. */
  color: var(--on-fill);
  max-width: 46ch;
  margin: 0 0 var(--s-8);
}
/* The hero button is the one place on the page that gets a size of its own.
   It is the single primary action, it carries no supporting line any more,
   and .btn-lg alone was too quiet for it. */
.lp-cta { display: flex; align-items: center; gap: var(--s-6); flex-wrap: wrap; }
/* Scoped to the hero, deliberately. .btn-primary is coral everywhere else in
   the product and on this page — the pricing card's Subscribe Now included —
   and re-pointing the class itself would have repainted every primary button
   in the app from one line on a landing-page rule. */
.lp-hero .lp-cta .btn-primary { background: var(--cta-red); color: var(--on-fill); }
.lp-hero .lp-cta .btn-primary:hover:not(:disabled) { background: var(--cta-red-hover); }
.lp-cta .btn {
  font-size: var(--t-md);
  font-weight: 700;
  padding: var(--s-4) var(--s-8);
  border-radius: var(--r-md);
}

/* .lp-trust, the three figures that sat under the hero headline, is gone
   along with its markup: patterns, questions and marks are on the counter
   band now, and two of the three were the same numbers printed twice on one
   screen. Removed rather than left orphaned — a selector nothing matches is
   a rule the next person has to prove is dead before they can touch it. */

/* --- the scrolling strip of what the product does --- */

/* One list, written once and printed twice, sliding half its own width. That
   is what makes the loop seamless: at -50% the second copy is standing
   exactly where the first began, so the jump back to 0 changes nothing on
   screen. Duplicating the markup in the PHP rather than with JavaScript keeps
   it working on a page that has none.
   The strip has to be WIDER than the viewport or there is a gap at the end of
   each pass, so width: max-content and nowrap on every claim are both
   load-bearing. */
.lp-marq {
  background: var(--marq-bg);
  border-bottom: 1px solid var(--marq-line);
  overflow: hidden;
  padding-block: var(--s-4);
}
.lp-marq-track {
  display: flex;
  width: max-content;
  /* 100s, not the 56s it was built at. The strip is prose, not a ticker:
     at 56s a claim crossed the screen faster than it could be read, which
     is the one thing a band of claims must not do. */
  animation: lp-marq-slide 100s linear infinite;
}
/* Pausing on hover is not decoration: the claims are readable prose and a
   reader who wants to finish one should be able to stop it. */
.lp-marq:hover .lp-marq-track,
.lp-marq:focus-within .lp-marq-track { animation-play-state: paused; }

.lp-marq-run { display: flex; align-items: center; }
.lp-marq-item {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding-inline: var(--s-6);
  font-family: var(--font-head);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--marq-ink);
  white-space: nowrap;
}
.lp-marq-item::before {
  content: '';
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1.5px solid var(--marq-dot);
}
@keyframes lp-marq-slide {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Motion off: the strip stops and becomes something you can push, rather
   than a list truncated at the right edge of the screen. */
@media (prefers-reduced-motion: reduce) {
  .lp-marq-track { animation: none; }
  .lp-marq { overflow-x: auto; }
}

/* --- the counter band --- */

.lp-count {
  background-color: var(--count-bg);
  background-image:
    radial-gradient(var(--rule-matrix) 1px, transparent 1px),
    linear-gradient(135deg, var(--count-bg), var(--count-bg-2));
  background-size: 12px 12px, auto;
  color: var(--on-fill);
  /* 20px taller than the --s-10 it was built at, on the owner's eye. The
     scale has no 50px step and inventing one for a single band would put a
     value in the ruler that nothing else uses, so the ten is added here and
     the reason is written down. */
  padding-block: calc(var(--s-10) + 10px);
}
.lp-count-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  /* Stretch, NOT centre. "Students Practising" is the one label that wraps to
     two lines, and centring made its cell taller and then centred the whole
     cell — so that number sat a line higher than the other four and the
     dividing rules came out different lengths. Stretched, every cell is the
     same height, the five numbers share a top edge and the rules run the
     full height of the band. */
  align-items: stretch;
}
.lp-count-cell { text-align: center; padding-inline: var(--s-4); }
.lp-count-cell + .lp-count-cell { border-left: 1px solid var(--count-line); }
.lp-count-n {
  font-family: var(--font-head);
  font-size: var(--t-xl);
  font-weight: 800;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.lp-count-l {
  margin-top: var(--s-2);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--on-fill-2);
}

/* --- the specimen: a real generated question, marks column and all --- */

.lp-spec {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.lp-spec-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-6);
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-xs);
  color: var(--muted);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.lp-spec-body { padding: var(--s-6); }
.lp-spec-q {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-5);
  align-items: start;
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--line-soft);
}
.lp-spec-q p { margin: 0; line-height: 1.65; }
.lp-spec-mark {
  font-family: var(--font-head);
  font-size: var(--t-md);
  color: var(--teal);
  padding-left: var(--s-4);
  border-left: 1px solid var(--line);
  align-self: stretch;
}
.lp-spec-fig { text-align: center; padding: var(--s-4) 0; }
/* The figure ships at its natural 382px, which dominated the card: the
   specimen is there to show what a question looks like, and the drawing was
   reading as the point of it. Held to 225px -- a little over 40% off -- so
   the wording, the marks and the marking scheme carry the card and the
   diagram supports them. The percentage cap keeps it inside a phone, where
   the card itself is narrower than 225px. */
.lp-spec-fig img,
.lp-spec-fig svg {
  max-width: min(225px, 100%);
  height: auto;
}

.lp-scheme-label {
  font-size: var(--t-xs);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted-soft);
  margin-bottom: var(--s-2);
}
.lp-scheme-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-4);
  padding-block: var(--s-2);
  font-size: var(--t-sm);
  border-bottom: 1px solid var(--line-faint);
}
.lp-scheme-row:last-child { border-bottom: 0; }
.lp-scheme-row .m {
  font-family: var(--font-head);
  color: var(--amber);
  min-width: 30px;
  text-align: right;
}

/* --- numbered steps: a real sequence, so it is numbered --- */

.lp-steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-6);
}
.lp-step {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-6);
}
.lp-step-n {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  font-weight: 800;
  color: var(--teal);
  background: var(--teal-tint);
  width: 26px; height: 26px;
  border-radius: var(--r-sm);
  display: grid; place-items: center;
  margin-bottom: var(--s-4);
}
.lp-step h3 { font-size: var(--t-base); margin: 0 0 var(--s-2); }
.lp-step p { margin: 0; font-size: var(--t-sm); color: var(--muted); line-height: 1.6; }

/* --- the dark band: the real blueprint --- */

/* A dot matrix at 12px. The band holds the blueprint table, and a ruled grid
   behind a ruled table was two grids arguing; a dot field reads as a surface
   instead of as a second set of lines. Chosen in the skin lab as C8. */
.lp-band {
  background-color: var(--promo-from);
  background-image:
    radial-gradient(var(--rule-matrix) 1px, transparent 1px),
    linear-gradient(135deg, var(--promo-from), var(--promo-to));
  background-size: 12px 12px, auto;
  color: var(--on-fill);
}
.lp-band .lp-h2 { color: var(--on-fill); }

.lp-bp {
  background: var(--on-fill-3);
  border-radius: var(--r-md);
  overflow: hidden;
  display: grid;
  gap: 1px;
}
.lp-bp-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) 7.5em 5em;
  gap: var(--s-3);
  align-items: center;

  /* Compacted. Seven rows at the old height ran past the fold on a laptop,
     so the total line — the one number the section exists to land — was the
     part nobody saw. */
  padding: var(--s-2) var(--s-5);
  background: var(--band-row);
  font-size: var(--t-sm);
  line-height: 1.35;
}
.lp-bp-row.is-head {
  font-size: var(--t-xs);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--on-fill-3);
}
.lp-bp-row.is-total { background: var(--promo-line); font-weight: 700; }
.lp-bp-sec {
  font-family: var(--font-head);
  font-weight: 800;
  color: var(--on-fill);
}
.lp-bp-q, .lp-bp-m { text-align: right; color: var(--on-fill-2); }
.lp-bp-each { color: var(--on-fill-3); }
.lp-bp-row.is-total .lp-bp-m { color: var(--on-fill); }

/* --- the chapter picker --- */

.lp-pick-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: var(--s-12);
  align-items: start;
}
/* The tabs and the chips, stacked, as ONE column beside the marks card. The
   tabs carried a top margin from when they sat above the whole grid; in the
   column that margin is what would push them off the card's top edge, so it
   goes and the column's own gap does the spacing. */
.lp-pick-left { display: flex; flex-direction: column; gap: var(--s-5); }
.lp-pick-left .lp-subject-tabs { margin: 0; }

.lp-chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.lp-chip {
  font-family: var(--font-body);
  font-size: var(--t-sm);
  color: var(--ink-70);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 6px var(--s-4);
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.lp-chip:hover { border-color: var(--muted-soft); }
.lp-chip[aria-pressed="true"] {
  background: var(--teal);
  border-color: var(--teal);
  color: var(--on-accent);
}
.lp-chip .w { color: var(--muted-soft); }
.lp-chip[aria-pressed="true"] .w { color: var(--on-fill-2); }

.lp-budget {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-6);
}
.lp-budget-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
}
.lp-budget-n {
  font-family: var(--font-head);
  font-size: var(--t-xl);
  font-weight: 800;
  color: var(--teal);
  line-height: 1;
}
.lp-budget-of { font-size: var(--t-sm); color: var(--muted); }
.lp-meter {
  height: 8px;
  border-radius: 999px;
  background: var(--track);
  overflow: hidden;
  margin-bottom: var(--s-5);
}
.lp-meter span {
  display: block;
  height: 100%;
  background: var(--amber);
  border-radius: 999px;
  transition: width .25s ease;
}
.lp-budget-list { display: grid; gap: 1px; }
.lp-budget-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-4);
  font-size: var(--t-sm);
  padding-block: var(--s-2);
  border-bottom: 1px solid var(--line-faint);
}
.lp-budget-row:last-child { border-bottom: 0; }
.lp-budget-row .m { font-family: var(--font-head); color: var(--teal); }
.lp-budget-empty { font-size: var(--t-sm); color: var(--muted); }

/* --- screenshots of the real thing --- */

.lp-shots { display: grid; gap: var(--s-16); }

.lp-shot {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr);
  gap: var(--s-12);
  align-items: center;
}
/* Alternate which side the screenshot sits on, so three rows of the same
   shape do not read as a list. */
.lp-shot:nth-child(even) .lp-shot-fig { order: 2; }

.lp-shot-fig { margin: 0; }
.lp-shot-fig img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}
.lp-shot-fig figcaption {
  font-size: var(--t-xs);
  color: var(--muted-soft);
  margin-top: var(--s-3);
}

.lp-shot-step {
  font-family: var(--font-head);
  font-size: var(--t-xs);
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: var(--s-3);
}
.lp-shot-copy h3 { font-size: var(--t-lg); margin: 0 0 var(--s-4); }
.lp-shot-copy p { margin: 0; color: var(--muted); font-size: var(--t-md); line-height: 1.65; }

/* --- claims --- */

.lp-claims {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-6);
}
.lp-claim {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-6) var(--s-6) var(--s-6) var(--s-5);
  border-left: 3px solid var(--teal);
}
.lp-claim h3 { font-size: var(--t-base); margin: 0 0 var(--s-2); }
.lp-claim p { margin: 0; color: var(--muted); font-size: var(--t-sm); line-height: 1.65; }

/* --- what students said --- */

/* White polaroids on a brick wall. The card is the photo frame: a fixed 4:3
   slot at the top, the quote under it, the student at the foot.
   ------------------------------------------------------------------
   This was a column flow (columns: 3) and is now a flex row, because those
   are different things and only one of them has rows. A column flow stacks
   cards down each column and their tops never line up; a flex line stretches
   every card in it to the tallest, so a row of polaroids is one height
   whatever the quote does. The quote itself takes the slack (flex: 1), which
   keeps every name on the same baseline instead of floating up under a short
   one. A last row with fewer cards is centred rather than left-hanging. */
/* Fine vertical striations, 5px apart: a rendered and painted wall rather
   than bare brickwork. Chosen in the skin lab as D4. Coursing was tried
   first and drawn out the grid in the polaroids' own edges; a single
   direction sits under them quietly. */
.lp-brick {
  background-color: var(--brick);
  background-image:
    repeating-linear-gradient(to right, var(--rule-plaster) 0 1px, transparent 1px 5px);
}
.lp-brick .lp-h2 { color: var(--on-brick); }
.lp-brick .lp-eyebrow,
.lp-brick .lp-sub { color: var(--on-brick-2); }

/* The wall is a carousel: four prints at a time, arrows at both edges, and it
   moves on its own until someone touches it.
   ------------------------------------------------------------------
   The track is an ordinary horizontal scroller with snap points, not a
   transform rig. That means it works before the script runs, keeps native
   trackpad and touch scrolling, and the arrows only have to call scrollBy.
   Cards do not shrink -- flex-basis is the width, flex-shrink is 0 -- so four
   across stays four across however many testimonials there are.
   justify-content is deliberately absent: centring a flex line that overflows
   puts its first cards out of reach in every browser. */
.lp-carousel {
  --arrow: 44px;
  position: relative;
  /* Exactly enough room for an arrow plus a gap, so neither one ever sits on
     top of a print. Derived from the arrow size rather than guessed, or the
     two drift apart the first time either changes. */
  padding: 0 calc(var(--arrow) + var(--s-3));
}

.lp-quotes {
  --per-view: 4;
  display: flex;
  gap: var(--s-6);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* The shadows under the prints are cast outside the card box; without room
     they get clipped by the scroller. scroll-padding has to match, or the
     snap point lands past that room: the first card sits flush to the edge
     with its shadow cut off, and the track rests at scrollLeft 4 instead of
     0, so the code can never tell it is at the beginning. */
  --edge: 4px;
  padding: var(--s-2) var(--edge) var(--s-5);
  scroll-padding-inline: var(--edge);
  scrollbar-width: none;
}
.lp-quotes::-webkit-scrollbar { display: none; }

/* Whether it glides or jumps is settled here, in one place. The script always
   asks for behavior:'auto', which means "whatever the stylesheet says" -- so
   this media query is the only thing that decides, and the JavaScript has no
   second opinion to keep in step. */
@media (prefers-reduced-motion: reduce) {
  .lp-quotes { scroll-behavior: auto; }
}
.lp-quotes:focus-visible { outline: 2px solid var(--on-brick); outline-offset: 4px; }

.lp-quote {
  flex: 0 0 calc((100% - (var(--per-view) - 1) * var(--s-6)) / var(--per-view));
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 0;
  border-radius: 3px;          /* a photo print, not a card */
  padding: 14px 14px 18px;
  margin: 0;
  box-shadow: var(--shadow-lg);
}

/* The photo slot. Fixed ratio, so every print in a row is the same size
   whether or not a photograph has arrived for it yet. */
.lp-quote-photo {
  aspect-ratio: 4 / 3;
  background: var(--surface-2);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.lp-quote-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lp-quote-slot {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: var(--t-2xl);
  line-height: 1;
  color: var(--muted-soft);
  text-align: center;
}
.lp-quote-slot small {
  display: block;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  margin-top: 6px;
}

.lp-quote q {
  quotes: none;
  display: block;
  flex: 1;                     /* the slack lives here, so names line up */
  margin: var(--s-5) 2px 0;
  font-size: var(--t-sm);
  line-height: 1.65;
  color: var(--ink-70);
  /* Justified at about a 35-character measure. Hyphenation was tried here
     and taken out again: broken words read worse in a polaroid than the
     slightly uneven word spacing they were fixing. */
  text-align: justify;

  /* Nine lines, whatever 300 characters happens to come to at this width.
     The character cut is the same everywhere; the line count is not -- the
     same quote runs longer in a narrow column, and one print growing a
     tenth line drags every print beside it taller. This is the backstop,
     and the browser adds its own ellipsis when it bites. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 9;
  line-clamp: 9;
  overflow: hidden;
}
.lp-quote-by {
  margin: var(--s-4) 2px 0;
  padding-top: var(--s-3);
  border-top: 1px solid var(--line-faint);
}
.lp-quote-name { font-weight: 700; font-size: var(--t-sm); color: var(--ink); }
.lp-quote-city { font-size: var(--t-xs); color: var(--muted); }

/* One at each extreme, vertically centred on the prints. White discs, because
   anything tinted disappears into the brick. */
.lp-carousel-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: var(--arrow); height: var(--arrow);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--paper);
  color: var(--brick);
  box-shadow: var(--shadow-lg);
  cursor: pointer;
}
.lp-carousel-btn svg { width: 20px; height: 20px; }
.lp-carousel-btn.is-prev { left: 0; }
.lp-carousel-btn.is-next { right: 0; }
.lp-carousel-btn:hover { background: var(--on-brick); }

/* At an end there is nothing that way. Dimmed rather than removed, so the
   row does not change shape as it moves. */
.lp-carousel-btn[disabled] { opacity: .35; cursor: default; box-shadow: var(--shadow-md); }

/* Before the script runs there is nothing for them to do. */
.lp-carousel:not(.is-ready) .lp-carousel-btn { display: none; }

/* Four across only while there is room for it; the print gets narrow fast.
   One variable moves, and the flex basis and the arrows both follow it. */
@media (max-width: 1180px) { .lp-quotes { --per-view: 3; } }
@media (max-width: 900px)  { .lp-quotes { --per-view: 2; } }
@media (max-width: 620px) {
  .lp-quotes { --per-view: 1; }
  /* No room to keep the arrows clear of the prints: they sit on the edges. */
  .lp-carousel { padding: 0; }
  .lp-carousel-btn.is-prev { left: calc(var(--s-2) * -1); }
  .lp-carousel-btn.is-next { right: calc(var(--s-2) * -1); }
}

/* --- pricing --- */

/* A petrol wash, so pricing reads as its own band against the page ground.
   The blueprint band above is already the one dark surface; a second would
   fight it, and the cards on this one stay white. */
/* A dotted grid, the other ruling in a maths exercise book. Bigger and
   sparser than the hero's squares so the two bands do not read as the same
   surface. */
.lp-tint {
  background-color: var(--teal-tint);
  background-image: radial-gradient(var(--rule-dot) 1px, transparent 1px);
  background-size: 22px 22px;
}

.lp-plans {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-6);
}
/* The padding moved OFF the card and onto an inner body, because the header
   bar has to reach the card's edges. Doing it with a negative margin on the
   header instead works right up until the border-radius, where the bar's
   square corners then sit outside the card's rounded ones. */
.lp-plan {
  background: var(--paper);
  border: 1px solid var(--line);
  /* The same 3px the testimonial prints wear — enough to take the hardness
     off the corner without the card reading as rounded. */
  border-radius: 3px;
  padding: 0;
  /* NOT overflow:hidden. Clipping here would cut the ribbon off flush with the
     card, and it is meant to stand 2px proud of the top and right edges. The
     clipping moved to .lp-plan-ribbon-wrap, which is the same box grown by
     those 2px. Clipping with clip-path instead was the other way, and it takes
     the lead card's own drop shadow with it. */
  position: relative;
  display: flex;
  flex-direction: column;
}
.lp-plan-body {
  padding: var(--s-8);
  display: flex;
  flex-direction: column;
  flex: 1;
}

.lp-plan-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-6);
  background: var(--plan-free);
  color: var(--on-fill);
  position: relative;
}
.lp-plan-head.is-topper { background: var(--plan-topper); }
.lp-plan-head.is-focus  { background: var(--plan-focus); }
.lp-plan-head.is-free   { background: var(--plan-free); }

.lp-plan-title {
  font-family: var(--font-head);
  font-size: var(--t-lg);
  white-space: nowrap;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  line-height: 1.1;
}
/* Reversed out of the header rather than filled: a second filled chip on a
   filled bar is two backgrounds arguing. */
/* THE DISCOUNT RIBBON, across each paid card's top-RIGHT corner.
   It was a pill inside the header row first, and it did not work there: its
   border and padding made it taller than the title's line box, so the Topper
   bar stood 3px above its neighbours, and it competed for width with the
   title on a narrow card. Out at the corner it costs the header nothing and
   cannot collide with anything.
   The geometry: rotated 45 degrees and pushed past the card's right edge,
   then clipped by the overflow:hidden on the card. The width is deliberately
   generous so both cut ends land outside the card and the visible piece is a
   clean band.
   THE DEPTH is the gradient and the shadow. The gradient lights the top edge
   and shades the bottom, so the band has a direction to it; the shadow lifts
   it off the header underneath. Folded tails were tried at each end, in a
   darker red, and taken out again. */
/* The clipping box: the card's top-right corner, grown 2px up and 2px right,
   so the band is cut 2px OUTSIDE the card on both edges and stands proud of
   it rather than flush with it. Everything past this box is cut, which is
   what keeps the band a folded corner instead of a bar crossing the page. */
.lp-plan-ribbon-wrap {
  position: absolute;
  /* -3px, not -2px. An absolute offset is measured from the containing
     block's PADDING box, and the card has a 1px border, so -2px put the wrap
     2px above the padding edge and only 1px above the edge anyone can see.
     The border has to be added back. */
  top: -3px;
  right: -3px;
  width: 170px;
  height: 170px;
  overflow: hidden;
  z-index: 2;
  pointer-events: none;
}

.lp-plan-ribbon {
  position: absolute;
  top: 28px;
  right: -54px;
  width: 190px;
  transform: rotate(45deg);
  background-image: linear-gradient(160deg, var(--ribbon-lit), var(--ribbon-shade));
  color: var(--on-fill);
  text-align: center;
  padding: 6px 0;
  font-family: var(--font-head);
  font-size: var(--t-xs);
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-shadow: 0 1px 1px rgba(0,0,0,.35);
  /* A dark shadow and nothing else. A light hairline was tried here first,
     to separate the band from the Focus card's red header, and it read as an
     outline drawn round a sticker rather than as a ribbon lying on the card.
     A shadow underneath does the same separating job the way an object does
     it. */
  /* Tight, not soft. A wide blur reads as haze under a sticker; a short
     offset with little spread reads as a solid thing sitting just above the
     card, which is what the reference has. */
  box-shadow: 0 3px 3px -1px rgba(16,32,31,.7);
  pointer-events: none;
}

/* THE CARDS STACK AT 1200px, not at the 980 the rest of the page uses.
   Three cards are at their narrowest just ABOVE the breakpoint, not below it
   — below it they are full width. At 1000px each card was 296px, and
   "TOPPER PLAN" beside its Best Deal flag did not fit on one line: that
   header wrapped to two lines and stood taller than the other two, which is
   the one thing a row of headers must not do. Stacking sooner keeps the
   narrowest three-across card at about 340px, where they fit. */
@media (max-width: 1200px) {
  .lp-plans { grid-template-columns: minmax(0, 1fr); }
}
.lp-plan.is-lead { border-color: var(--teal); box-shadow: var(--shadow-md); }

/* .lp-plan-name is gone: the plan's name is in the header bar now, as
   .lp-plan-title, and it is set in the bar's ink rather than the card's. */

/* The old price, the new one and the offer sit on one baseline; "for full
   year" tucks under the new price rather than trailing it, so the number is
   what the eye lands on. */
.lp-plan-price {
  font-family: var(--font-head);
  font-size: var(--t-2xl);
  font-weight: 800;
  color: var(--ink);
  line-height: 1;
  margin-bottom: var(--s-2);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  /* A tight column gap: the struck price and the offer price are one
     statement — "this was that, now it is this" — and 16px of air between
     them read as two separate figures. The row gap stays, for the wrap. */
  gap: var(--s-2) var(--s-2);
}
/* Struck through at full size, so what the offer is worth is legible rather
   than a footnote. --err is the only red in the palette and is deliberately
   far from the coral, which keeps it from competing with the button. */
.lp-plan-was {
  color: var(--err);
  /* Two rules through it, not one, and a step down from the price it is
     struck against — the old figure should read as the smaller of the two at
     a glance, before anyone has read either number. */
  font-size: var(--t-xl);
  text-decoration: line-through;
  text-decoration-style: double;
  text-decoration-thickness: 1px;
}
.lp-plan-now { display: inline-flex; flex-direction: column; align-items: center; }
.lp-plan-per {
  font-family: var(--font-body);
  /* Caps with letter-spacing run wider than the figure above them, which
     widened the column enough to push the offer chip onto a second line. A
     step down in size brings it back level with the price. */
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
  /* Pulled up under the figure. The price box is 43px tall for digits that
     have no descenders, so most of the space below them is empty glyph box
     rather than margin -- closing it takes a negative value, not a smaller
     positive one. */
  margin-top: -7px;
}
.lp-plan-off {
  /* The row aligns on the text baseline, which is right for the two figures
     but left the chip riding high against the taller price column. Centre
     just the chip against that column. */
  align-self: center;
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--on-coral);
  background: var(--amber-tint);
  border-radius: var(--r-sm);
  padding: 5px var(--s-3);
  white-space: nowrap;
}
.lp-plan-note { font-size: var(--t-sm); color: var(--muted); margin: 0 0 var(--s-6); }
.lp-plan-note.is-strong { font-weight: 700; color: var(--ink-70); }
/* One column, not two. The features sat in two columns while there were two
   cards and the cards were wide; with three, each card is about 300px at a
   laptop width and a two-column list gives each feature roughly half a dozen
   characters before it wraps. */
.lp-plan ul {
  list-style: none;
  margin: 0 0 var(--s-8);
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3) var(--s-6);
}
.lp-plan li {
  font-size: var(--t-sm);
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: var(--s-2);
  align-items: start;
}
/* The tick is page ink and the cross is the error red, on the owner's eye.
   Both were quiet before — petrol and a soft grey — which read as a tidy
   list rather than as two different answers. What the pair has to say at a
   glance is "you get this" and "you do not". */
.lp-plan li svg { width: 16px; height: 16px; margin-top: 2px; color: var(--ink); }

/* What the free tier does NOT include. Grey rather than red: these are not
   errors, they are the reason to upgrade, and a row of red crosses on the
   free plan reads as a warning about the product. */
.lp-plan li.is-off { color: var(--muted); }
.lp-plan li.is-off svg { color: var(--err); }

/* "not included", for a screen reader. Sighted readers have the cross. */
.lp-plan-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.lp-plan-body .btn { margin-top: auto; }

/* Both price blocks reserve the SAME strip, so the note under them starts on
   the same line in either card.
   The rule used to sit on .is-solo alone — the free card reserving a height
   meant to match the paid card's natural one. That makes the value a guess
   about the other card, and the guess was 13px too generous: the free card's
   "For as long as you want it." sat a line below "Enjoy Unlimited Access" for
   as long as the rule existed. On BOTH blocks the number no longer has to be
   right about anything except being taller than either card's content, which
   it is by a few pixels. If a price ever grows past it, the card it is in
   grows and the other does not — so if these two lines drift apart again,
   this is the number to look at. */
.lp-plan-price { min-height: calc(var(--t-2xl) + var(--s-2)); }

/* --- footer --- */

.lp-foot {
  border-top: 1px solid var(--line);
  padding-block: var(--s-8);
  font-size: var(--t-sm);
  color: var(--muted);
}
.lp-foot-in { display: flex; flex-wrap: wrap; gap: var(--s-4) var(--s-6); justify-content: space-between; }
.lp-foot a { color: var(--muted); }
.lp-foot a:hover { color: var(--ink); }

/* --- narrow --- */

@media (max-width: 980px) {
  .lp-hero-grid,
  .lp-pick-grid { grid-template-columns: minmax(0, 1fr); gap: var(--s-10); }
  .lp-shots { gap: var(--s-12); }
  .lp-shot { grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
  .lp-shot:nth-child(even) .lp-shot-fig { order: 0; }
  .lp-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lp-hero h1 { max-width: none; }
  /* Four counters across a tablet leaves each one about a word wide. Two and
     two reads as a block of tiles rather than a row of slivers. */
  .lp-count-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--s-8); }
  .lp-count-cell:nth-child(odd) { border-left: 0; }
}

@media (max-width: 720px) {
  .lp-wrap { padding-inline: var(--s-4); }
  .lp-sec { padding-block: var(--s-12); }
  .lp-hero { padding-block: var(--s-10) var(--s-8); }
  .lp-hero h1 { font-size: var(--t-xl); }
  .lp-count { padding-block: var(--s-8); }
  .lp-count-n { font-size: var(--t-lg); }
  .lp-marq-item { padding-inline: var(--s-5); }
  .lp-steps,
  .lp-claims,
  .lp-plans,
  .lp-plan ul { grid-template-columns: minmax(0, 1fr); }
  /* the three stats stay on one row even on a phone: dropping to two left
     the third sitting alone under the others */
  .lp-bp-row { grid-template-columns: 26px minmax(0, 1fr) 4.5em; }
  .lp-bp-q { display: none; }
  .lp-top-nav { gap: var(--s-3); }
  .lp-top-nav .is-hide-sm { display: none; }
  .lp-logo-tag { display: none; }
  .lp-top .lp-logo img { height: 60px; }
  .lp-top-in { padding-block: var(--s-3); }
}

@media (prefers-reduced-motion: reduce) {
  .lp-meter span { transition: none; }
}

/* ------------------------------------------------------------
   Subjects, and the picker's subject tabs.

   The three subject cards say what is ready and what is not. The state is
   carried by a filled tag and by the number underneath, never by colour
   alone — a card that is "in progress" has to read as in progress in
   greyscale, on a printout, and to somebody who cannot tell the tint from
   the ground.
   ------------------------------------------------------------ */

.lp-subjects {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  margin-top: var(--s-6);
}

.lp-subj {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s-6);
}

.lp-subj.is-soon { background: var(--grey-tint); }

.lp-subj-top {
  display: flex;
  align-items: flex-start;   /* "Social Studies" wraps to two lines; its tag
                                must stay level with the tags either side */
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}

.lp-subj-top h3 {
  margin: 0;
  font-size: calc(var(--t-sm) + 3px);
  color: var(--ink);
}

.lp-subj-tag {
  margin-left: auto;
  flex: none;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  padding: 2px var(--s-3);
  border-radius: var(--r-sm);
  background: var(--grey-tint);
  color: var(--muted);
}

.lp-subj.is-done .lp-subj-tag { background: var(--teal); color: var(--on-accent); }
.lp-subj.is-part .lp-subj-tag { background: var(--amber); color: var(--on-coral); }

.lp-subj-n {
  margin: 0 0 var(--s-2);
  font-size: var(--t-xl);
  font-weight: 800;
  color: var(--ink);
  line-height: 1.1;
}

.lp-subj-n span {
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--muted);
}

.lp-subj-note { margin: 0; font-size: var(--t-sm); line-height: 1.6; color: var(--ink-70); }

.lp-subj-foot {
  margin: var(--s-6) 0 0;
  font-size: var(--t-sm);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  text-align: center;
}

.lp-subject-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: var(--s-5) 0 var(--s-5);
}

.lp-subject-tab {
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--ink-70);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 600;
  padding: var(--s-2) var(--s-5);
  border-radius: var(--r-sm);
  cursor: pointer;
}

.lp-subject-tab:hover { border-color: var(--amber); color: var(--amber-hover); }

/* Coral, not petrol. These are the one thing a visitor is meant to press in
   each of the two sections they sit in, and coral is what this palette uses
   for an action. The house rule against two corals side by side is not
   breached: each strip is its own screenful, and the only other coral on the
   page is Buy Now, far below. */
.lp-subject-tab.is-on {
  background: var(--amber);
  border-color: var(--amber);
  color: var(--on-coral);
}

@media (max-width: 980px) {
  .lp-subjects { grid-template-columns: 1fr; }
}

/* ============================================================
   18e. ADMIN LISTS
   The filter bar and pager shared by the admin screens. Everything else
   on those pages is already in this file -- .card, .data, .stat, .badge,
   .empty -- and they use it rather than growing their own.
   ============================================================ */

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--s-3) var(--s-4);
  padding: var(--s-4) var(--s-5);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  margin-bottom: var(--s-5);
}
.filter { display: flex; flex-direction: column; gap: 3px; }
.filter-label {
  font-size: var(--t-xs);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
.filter-bar .input-sm { min-width: 9rem; }
.filter-bar .filter-search .input-sm { min-width: 16rem; }
.filter-bar .btn { margin-bottom: 1px; }

/* Status as buttons rather than a dropdown: on a queue you work every day,
   what is waiting should be readable without opening anything. Pushed to the
   right of the search box by margin-left, so both sit on one row. */
.seg {
  display: inline-flex;
  margin-left: auto;
  margin-bottom: 1px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--paper);
}
.seg-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  font-size: var(--t-sm);
  font-weight: 500;
  line-height: 1.6;
  color: var(--ink-70);
  border-right: 1px solid var(--line);
  white-space: nowrap;
}
.seg-btn:last-child { border-right: none; }
.seg-btn:hover { background: var(--hover); text-decoration: none; color: var(--ink); }
.seg-btn.is-on { background: var(--teal); color: var(--on-accent); }
.seg-btn.is-on:hover { background: var(--teal-hover); color: var(--on-accent); }

/* The count riding on each button. */
.seg-n {
  font-size: var(--t-xs);
  font-weight: 700;
  padding: 0 6px;
  border-radius: var(--r-sm);
  background: var(--hover-2);
  color: var(--ink-70);
}
/* On the active button the pill inverts: a translucent white pill carrying
   white text measured 2.2:1 and could not be read. */
.seg-btn.is-on .seg-n { background: var(--on-fill); color: var(--teal); }

@media (max-width: 720px) {
  .seg { margin-left: 0; width: 100%; }
  .seg-btn { flex: 1; justify-content: center; padding: var(--s-2) var(--s-2); }
}

.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  padding-top: var(--s-4);
  margin-top: var(--s-4);
  border-top: 1px solid var(--line-soft);
}
.pager-links { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.pager-link {
  display: inline-block;
  min-width: 30px;
  text-align: center;
  padding: 5px var(--s-2);
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  color: var(--ink-70);
  border: 1px solid transparent;
}
.pager-link:hover { background: var(--hover); text-decoration: none; }
.pager-link.is-on {
  background: var(--teal);
  border-color: var(--teal);
  color: var(--on-accent);
  font-weight: 600;
}
.pager-gap { color: var(--muted-soft); padding-inline: 2px; }

/* A cell holding a long question or a reported issue: readable, but never
   pushing the table sideways. */
.cell-text {
  max-width: 44ch;
  white-space: normal;
  line-height: 1.5;
}
.cell-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The worked example on the import screen. Monospace, scrollable, and it
   never widens the card it sits in. */
.code-sample {
  margin: 0;
  padding: var(--s-4);
  background: var(--surface-2);
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  line-height: 1.55;
  white-space: pre;
  overflow-x: auto;
  color: var(--ink-70);
}

/* A row for something switched off -- a hidden chapter, a retired plan. */
table.data tr.is-muted { opacity: .55; }

/* Two or three actions at the end of a row. */
.row-actions { display: flex; gap: var(--s-2); justify-content: flex-end; flex-wrap: wrap; }

@media (max-width: 720px) {
  .filter-bar { flex-direction: column; align-items: stretch; }
  .filter-bar .input-sm,
  .filter-bar .filter-search .input-sm { min-width: 0; width: 100%; }
}

/* ============================================================
   18f. SUPPORT THREAD
   One conversation, read top to bottom. The two sides are told apart by
   which edge they sit against and which ground they carry -- not by
   colour alone, because a thread read in greyscale still has to say who
   said what. Staff messages also carry a label.
   ============================================================ */

.thread { display: flex; flex-direction: column; gap: var(--s-5); }

.msg { display: flex; flex-direction: column; max-width: 46rem; }
.msg.is-staff { align-self: flex-start; }
.msg.is-student { align-self: flex-end; align-items: flex-end; }

.msg-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  margin-bottom: 5px;
  font-size: var(--t-xs);
  color: var(--muted);
}
.msg-who { font-weight: 700; color: var(--ink-70); }

.msg-body {
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-md);
  font-size: var(--t-base);
  line-height: 1.6;
  white-space: pre-wrap;      /* students type line breaks and mean them */
  overflow-wrap: anywhere;
}
.msg.is-staff .msg-body {
  background: var(--teal-tint);
  border: 1px solid var(--line);
  border-top-left-radius: 3px;
}
.msg.is-student .msg-body {
  background: var(--paper);
  border: 1px solid var(--line);
  border-top-right-radius: 3px;
}

.msg-shot { margin-top: var(--s-3); }

/* Where a picture used to be. Saying so beats a message that reads "see the
   screenshot" with nothing under it. */
.msg-gone {
  margin-top: var(--s-2);
  font-size: var(--t-xs);
  font-style: italic;
  color: var(--muted);
}
.msg-shot img {
  display: block;
  max-width: 280px;
  max-height: 200px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
}

/* The line a new ticket's first message sits under, and the divider before
   the reply box. */
.thread-sep {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin: var(--s-6) 0 var(--s-5);
  font-size: var(--t-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted-soft);
}
.thread-sep::before,
.thread-sep::after { content: ''; flex: 1; height: 1px; background: var(--line-soft); }

/* A ticket in a list. The whole row is the link. */
.ticket-list { display: grid; gap: var(--s-3); }
.ticket {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-3) var(--s-5);
  align-items: center;
  padding: var(--s-4) var(--s-5);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink);
}
.ticket:hover { border-color: var(--muted-soft); text-decoration: none; }
.ticket.is-unread { border-left: 3px solid var(--amber); }
.ticket-subject { font-weight: 600; line-height: 1.4; }
.ticket-meta { font-size: var(--t-xs); color: var(--muted); margin-top: 2px; }

/* The reference, PP-YYMMDD-XXXX. Set a shade darker than the rest of the
   meta line and on tabular figures, because it is the one part of that line
   anyone reads character by character -- to quote it back, or to match it
   against what a student has quoted at us. */
.ticket-ref {
  font-weight: 600;
  color: var(--ink-70);
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
}
.ticket-side { display: flex; align-items: center; gap: var(--s-3); justify-content: flex-end; flex-wrap: wrap; }

/* A worked queue is scanned, not read. With the message preview gone each
   row is two lines, so the gaps between them come down to match. */
.ticket-list.is-compact { gap: var(--s-2); }
.ticket-list.is-compact .ticket { padding: var(--s-3) var(--s-4); }
.ticket-list.is-compact .ticket-subject { font-size: var(--t-sm); }

/* While a search is in flight, so a slow query does not look like no result. */
#ticketResults.is-loading { opacity: .5; transition: opacity .12s ease; }

/* The dot on a nav item, and beside an unread ticket. */
.dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--amber);
  flex-shrink: 0;
}

@media (max-width: 720px) {
  .msg { max-width: 100%; }
  .ticket { grid-template-columns: 1fr; }
  .ticket-side { justify-content: flex-start; }
}

/* An attachment, opened over the page rather than in a tab -- a reply being
   typed underneath is never lost, and Escape gives it straight back. Built
   by assets/js/main.js on first use; with no JS the link still opens the
   file normally. */
.shot-view {
  position: fixed; inset: 0;
  z-index: 80;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--s-6);
  background: var(--scrim-deep);
}
.shot-view.is-open { display: flex; }

.shot-view img {
  max-width: min(1100px, 92vw);
  max-height: 84vh;
  width: auto; height: auto;
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-lg);
  cursor: default;
}

.shot-view-close {
  position: absolute;
  top: var(--s-4); right: var(--s-4);
  width: 40px; height: 40px;
  display: inline-flex;
  align-items: center; justify-content: center;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--on-fill);
  background: none;
  border: 1px solid var(--on-fill-3);
  border-radius: 50%;
  cursor: pointer;
}
.shot-view-close:hover { border-color: var(--on-fill); }

/* Says how to get out, for anyone who does not assume Escape. */
.shot-view-hint {
  position: absolute;
  bottom: var(--s-5);
  left: 0; right: 0;
  text-align: center;
  font-size: var(--t-xs);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--on-fill-3);
}

@media (max-width: 720px) {
  .shot-view { padding: var(--s-3); }
  .shot-view img { max-width: 96vw; max-height: 78vh; }
}

/* ============================================================
   18g. POLICY PAGES
   Terms, Privacy, Refund, Contact. One long column of prose, read by
   a parent deciding whether to pay and by a payment gateway's
   reviewer deciding whether this is a real business. Neither of them
   wants a designed page; both want to find one clause quickly.

   So: a single measure a little under 40rem, headings that are
   obviously headings, and generous space between sections. No cards —
   a policy is one continuous document, and boxing its sections would
   invite skimming past the one that matters.
   ============================================================ */

.legal { background: var(--mist); padding: var(--s-8) 0 var(--s-10); }

.legal-wrap {
  max-width: 44rem;
  margin: 0 auto;
  padding: 0 var(--s-5);
}

.legal-title {
  font-size: calc(var(--t-xl) + 4px);
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--ink);
  margin: 0 0 var(--s-2);
}

.legal-meta {
  font-size: var(--t-sm);
  color: var(--muted);
  margin: 0 0 var(--s-6);
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--line);
}

/* The opening paragraph carries the answer for the reader who will not
   read the rest, which on a refund policy is most of them. */
.legal-lede {
  font-size: calc(var(--t-md) + 1px);
  line-height: 1.7;
  color: var(--ink);
  margin: 0 0 var(--s-6);
}

.legal h2 {
  font-size: calc(var(--t-sm) + 5px);
  font-weight: 700;
  color: var(--ink);
  margin: var(--s-8) 0 var(--s-3);

  /* Opting out of the house uppercase, the same way .page-greeting does.
     A clause heading is scanned for, not admired: someone hunting "when do
     I get my money back" reads down the left edge, and a column of capitals
     is slower to read that way, because the word shapes all flatten into
     the same rectangle. The page title above keeps the house style. */
  text-transform: none;
  letter-spacing: 0;
}

.legal p,
.legal li {
  font-size: var(--t-md);
  line-height: 1.75;
  color: var(--ink-70);
  margin: 0 0 var(--s-4);
}

.legal ul { margin: 0 0 var(--s-5); padding-left: var(--s-6); }
.legal li { margin-bottom: var(--s-2); }

.legal strong { color: var(--ink); font-weight: 700; }

.legal a { color: var(--teal); text-decoration: underline; text-underline-offset: 2px; }
.legal a:hover { color: var(--teal-hover); }

/* The revision date at the foot of a policy page. A step down from body copy
   -- it is a fact about the page rather than part of it -- above a hairline
   that closes the text off from the site footer below.

   Scoped to .legal deliberately: `.legal p` is (0,1,1) and would outrank a
   bare `.legal-updated` at (0,1,0), so the size set here would never land. */
.legal .legal-updated {
  font-size: var(--t-sm);
  color: var(--muted);
  margin: var(--s-8) 0 0;
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}

.legal-foot-links a { white-space: nowrap; }

/* The signed-in footer. One quiet line under the page content, not a
   band of links — it exists so the policies are always reachable, not
   to be looked at. */
.app-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  align-items: center;
  justify-content: space-between;
  margin-top: var(--s-10);
  padding: var(--s-5) var(--s-6) var(--s-6);
  border-top: 1px solid var(--line-faint);
  font-size: var(--t-xs);
  color: var(--muted);
}

.app-foot a { color: var(--muted); text-decoration: none; }
.app-foot a:hover { color: var(--teal); text-decoration: underline; }

@media (max-width: 720px) {
  .legal { padding: var(--s-6) 0 var(--s-8); }
  .legal h2 { margin-top: var(--s-6); }
}

/* ------------------------------------------------------------
   The error disclaimer on the solutions page.

   Red, because the student is being asked to act on it, and this is the
   page where they will find something to act on — nobody spots a wrong
   answer while sitting the paper; they spot it while marking themselves
   against the scheme.

   Not an .alert: an alert says something has gone wrong with what the
   student just did, and nothing has. A tinted rule down the side says
   "read this" without claiming a problem.
   ------------------------------------------------------------ */
.sol-disclaimer {
  border-left: 3px solid var(--err-strong);
  background: var(--err-tint);
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-sm);
  font-size: var(--t-base);
  line-height: 1.6;
  color: var(--err-strong);
}

.sol-disclaimer a,
.sol-disclaimer strong { color: var(--err-strong); font-weight: 700; }
.sol-disclaimer a { text-decoration: underline; text-underline-offset: 2px; }

/* The link inside the notice that is an ACTION rather than a reference —
   "Go to My Papers to finish it". Petrol, because that is what a link is
   everywhere else on the site; red on red reads as more warning rather than
   as somewhere to go. Two classes beat the one-class-one-element rule above,
   so no !important is needed. */
.sol-disclaimer .disclaimer-go { color: var(--teal); }

/* ------------------------------------------------------------
   Days to the board exams — the strip above the dashboard greeting.

   PETROL, not coral or red. This number is frightening enough at fifteen and
   the job is to focus a student rather than panic them; the warm colours stay
   for things that need acting on now.

   The weekend count is the half that does the work. "145 days" sounds like
   plenty; "21 weekends" is a number a student can picture.

   At phone width it stacks and the weekend count moves under the text rather
   than being squeezed beside it — a two-digit number and a word do not survive
   sharing a line with a sentence at 360px.
   ------------------------------------------------------------ */
/*
 * CORAL TO GREEN, AND THE CORAL IS --brick RATHER THAN --amber.
 *
 * White on --amber measures 3.51:1, which fails readable text — the palette
 * notes say as much where --on-coral is defined, and this card carries a
 * sentence rather than a button label. Dark ink is no escape either: it reads
 * on the coral end at 4.79 and fails on the green at 3.14, so no single text
 * colour survives a gradient between the two as they stand.
 *
 * --brick is the deeper coral the palette already holds, and it already ships
 * with --on-brick: #FFFFFF because it was drawn for exactly this. White lands
 * at 6.28:1 on it and 5.35:1 on --ok at the other end, so one text colour works
 * the whole way across.
 *
 * The ink border is the card's edge against a page of white ones.
 */
.exam-strip {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  background: linear-gradient(105deg, var(--teal-hover) 0%, var(--teal) 100%);
  border: 1px solid transparent;
  border-left: 5px solid var(--ink);
  border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-5);
  color: var(--on-accent);
}
.exam-n {
  font-size: calc(var(--t-xl) + 4px);
  font-weight: 800;
  line-height: 1;
  color: var(--on-brick);
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}
.exam-t { flex: 1 1 auto; min-width: 0; }
.exam-t b { display: block; font-size: var(--t-base); font-weight: 700; }
.exam-t span {
  display: block;
  font-size: var(--t-sm);
  color: var(--on-accent-2);
  margin-top: 3px;
}
.exam-w {
  flex: 0 0 auto;
  text-align: right;
  font-size: var(--t-sm);
  color: var(--on-accent-2);
}
.exam-w b {
  display: block;
  font-size: var(--t-md);
  font-weight: 700;
  color: var(--on-brick);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 560px) {
  .exam-strip { flex-wrap: wrap; gap: var(--s-3) var(--s-4); }
  .exam-w { text-align: left; flex-basis: 100%; }
  .exam-w b { display: inline; margin-right: 4px; }
}

/* ------------------------------------------------------------
   The streak's week, and the rest day.

   Sits under the streak number in the hero, so nothing a student already knows
   how to read has moved.

   A REST DAY IS A MOON, NOT A CROSS. It is protection, not a failure with a
   plaster on it — and it is petrol rather than coral because it is not a day of
   work. Coral is earned.

   Days still to come are outlined and faint. Opening the dashboard on a Monday
   morning must not show six marks against a week that has barely started.
   ------------------------------------------------------------ */
/* The label and the personal best sit on one line. Baseline-aligned, because
   one is uppercase and tracked out and the other is sentence case — aligning
   their boxes instead would leave them visibly off from each other. */
.streak-meta { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; margin-top: 3px; }
.streak-best { font-size: var(--t-xs); color: var(--muted-soft); }

.streak-week { display: flex; gap: 9px; margin-top: var(--s-3); }
.sw-day { text-align: center; }
.sw-dot {
  /* 26px left the tick competing with the circle around it. */
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: var(--t-sm);
  font-weight: 700;
  line-height: 1;
}
.sw-dot.is-done   { background: var(--amber);     color: var(--on-coral); }
.sw-dot.is-rest   { background: var(--teal-tint); color: var(--teal); border: 1px dashed var(--teal); }
.sw-dot.is-missed { background: var(--track);     color: var(--muted-soft); }
.sw-dot.is-future { background: var(--paper);     color: var(--muted-soft); border: 1px dashed var(--line); }
.sw-day span {
  display: block;
  font-size: 10px;
  font-weight: 600;
  color: var(--muted-soft);
  margin-top: 5px;
  letter-spacing: .04em;
}

/* ------------------------------------------------------------
   The share card.

   Built at 1080x1080 and shrunk to fit with a transform, because html2canvas
   captures the real pixels: styling it at display size would save a small,
   soft image. The stage reserves the scaled height so the page does not leave
   a hole under it.

   The colours are written out rather than taken from tokens, the same
   exception the print stylesheet takes. This picture leaves the site — it must
   look the same in everyone's feed, not follow a theme it will never be inside.
   ------------------------------------------------------------ */
.share-stage {
  width: 100%;
  max-width: 340px;
  height: 340px;                  /* 1080 * 0.3148 */
  overflow: hidden;
}
.share-card {
  width: 1080px;
  height: 1080px;
  padding: 88px;
  border-radius: 0;
  background: linear-gradient(155deg, #23A07A 0%, #15705A 100%);
  color: #FFFFFF;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  transform: scale(.3148);
  transform-origin: top left;
  font-family: var(--font-body);
}
.share-logo { height: 150px; width: auto; align-self: flex-start; }
.share-count {
  font-size: 240px;
  font-weight: 800;
  line-height: .9;
  font-variant-numeric: tabular-nums;
}
.share-count i { font-style: normal; color: #A82B22; }
.share-cap {
  font-size: 42px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255,255,255,.72);
  margin-top: 34px;
}
.share-rule { height: 12px; width: 150px; background: #A82B22; border-radius: 6px; margin: 50px 0; }
.share-facts { display: flex; gap: 76px; }
.share-facts b { display: block; font-size: 56px; font-weight: 700; }
.share-facts span {
  font-size: 33px;
  color: rgba(255,255,255,.64);
  letter-spacing: .05em;
  text-transform: uppercase;
}
.share-site { font-size: 50px; font-weight: 700; color: #FFFFFF; }

@media (max-width: 400px) {
  /* 0.2685 of 1080 is 290, which clears a 360px screen with the page gutter. */
  .share-stage { max-width: 290px; height: 290px; }
  .share-card  { transform: scale(.2685); }
}
/* The offer to share, shown under the hero only when the card has been
   earned. Coral, because it is the one thing on the page worth acting on
   in that moment. */
.streak-share {
  margin-top: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  background: var(--amber-tint);
  border-left: 3px solid var(--amber);
  font-size: var(--t-sm);
}
.streak-share a { color: var(--teal); font-weight: 600; margin-left: 6px; }

/* The rest-day rule, under the week. Muted and small: it is a standing fact,
   not news, and it must not compete with the number above it. */
.streak-rule {
  margin: var(--s-3) 0 0;
  font-size: var(--t-xs);
  color: var(--muted);
  line-height: 1.5;
}

/* ------------------------------------------------------------
   The dashboard's three Progress tiles, in petrol.

   SCOPED TO THIS ONE GRID, not to .stat. That class is on twenty pages —
   every admin screen, the solutions page, the blueprint — and repainting it
   would turn the whole back office green.

   The score ring needs its own colours here: its track is --track and its arc
   is --teal, both of which disappear on a petrol ground. White at a low alpha
   for the track, full white for the arc.
   ------------------------------------------------------------ */
.stats-petrol .stat {
  background: linear-gradient(140deg, var(--teal-hover) 0%, var(--teal) 100%);
  border-color: transparent;
  color: var(--on-accent);
}
.stats-petrol .stat-label,
.stats-petrol .stat-foot { color: var(--on-accent-2); }
.stats-petrol .stat-value { color: var(--on-accent); }
.stats-petrol .stat-value .unit { color: var(--on-accent-2); }
.stats-petrol .stat-value.muted { color: var(--on-accent-2); }
.stats-petrol .ring .ring-bg { stroke: var(--on-accent-3); }
.stats-petrol .ring .ring-fg { stroke: var(--on-accent); }
.stats-petrol .ring text { fill: var(--on-accent); }
/* What to do next, under the streak. Same muted weight as the rest-day rule
   above it — together they are the small print under the number, and neither
   should pull the eye off it. */
/* The reason the Generate button is out of reach, under the buttons.
   The first line is the fact; the second is why the product works this way,
   and it is loud on purpose — a rule that looks like a limitation loses a
   student, and one that looks like a decision keeps them. */
/* The two lines under a disabled Generate button on the dashboard.

   Their own names, because .gen-locked is the paywall PANEL on generate.php
   and in the subject filter — centred text with --s-16 padding block, which
   is correct for a panel filling the page and wrong for a caption under a
   button. Sharing the name inherited both: the first line sat centred in the
   card and the second was pushed a long way clear of the button it explains.

   Left-aligned and close to the button on purpose. They answer the question
   that button raises, so they read as part of it. */
.open-paper-note {
  margin: var(--s-3) 0 0;
  text-align: left;
  font-size: var(--t-sm);
  color: var(--muted);
}
.open-paper-why {
  margin: var(--s-1) 0 0;
  text-align: left;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.5;
}

.streak-next {
  margin: var(--s-2) 0 0;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.5;
}

.streak-rest {
  margin-top: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  background: var(--teal-tint);
  border-left: 3px solid var(--teal);
  font-size: var(--t-sm);
  line-height: 1.6;
}
/* -----------------------------------------------------------
   The Mathematics level: Basic 241 or Standard 041

   Asked on the Generate screen rather than at registration, so it
   sits directly under the subject tabs. Two states: the question,
   and the answer once it is locked.

   NOT CORAL. Coral is the single primary action on a screen and the
   Generate button already has it; two coral choices beside it would
   read as three competing primaries.
   ----------------------------------------------------------- */
.stream-ask {
  border: 1px solid var(--teal);
  border-radius: var(--r-md);
  background: var(--paper);
  padding: var(--s-5);
  margin-bottom: var(--s-5);
}
.stream-ask-title { font-size: calc(var(--t-sm) + 5px); margin: 0; }
.stream-ask-lede  { color: var(--muted); margin: var(--s-3) 0 var(--s-4); }

.stream-ask-options { display: flex; gap: var(--s-4); flex-wrap: wrap; }
.stream-opt {
  flex: 1 1 220px;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  text-align: left;
  cursor: pointer;
  padding: var(--s-4);
  border: 2px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  font: inherit;
  color: inherit;
}
.stream-opt:hover, .stream-opt:focus-visible {
  border-color: var(--teal);
  background: var(--teal-tint);
}
.stream-opt-name { font-weight: 800; font-size: var(--t-base); }
.stream-opt-code { font-weight: 700; color: var(--teal); font-size: var(--t-sm); }
.stream-opt-desc { color: var(--muted); font-size: var(--t-sm); }

.stream-ask-later { margin: var(--s-4) 0 0; font-size: var(--t-sm); }

/* The answer, once given. A statement, not a control -- but a prominent one,
   because a student who picked the wrong paper must find out here and not in
   the exam hall. */
.stream-set {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  flex-wrap: wrap;
  padding: var(--s-3) var(--s-4);
  margin-bottom: var(--s-5);
  border-left: 3px solid var(--teal);
  background: var(--mist);
  border-radius: var(--r-sm);
}
.stream-set-label { font-size: var(--t-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.stream-set-value { font-size: var(--t-base); }
.stream-set-note  { font-size: var(--t-sm); color: var(--muted); }

/* -----------------------------------------------------------
   "Confirm your email", on the dashboard

   Two states: a reminder while nothing is blocked, and a plain
   statement once the generator is closed. They must not look
   alike -- the first is information, the second is the reason
   the button below it will not work.

   NOT CORAL either way. Coral is the single primary action on a
   screen and Generate a Paper already has it.
   ----------------------------------------------------------- */
.verify-notice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-5);
  border: 1px solid var(--teal);
  border-left: 4px solid var(--teal);
  border-radius: var(--r-md);
  background: var(--teal-tint);
}
.verify-notice-body { flex: 1 1 320px; }
.verify-notice-head { display: block; font-size: var(--t-base); }
.verify-notice-text { margin: var(--s-2) 0 0; font-size: var(--t-sm); color: var(--ink-70); }

/* Locked: the generator is shut until they act. */
.verify-notice.is-locked {
  border-color: var(--err);
  border-left-color: var(--err);
  background: var(--paper);
}
.verify-notice.is-locked .verify-notice-head { color: var(--err); }

/* ============================================================
   19. UTILITIES — MUST STAY LAST

   [hidden] belongs here and nowhere else.

   The attribute's own rule is `display: none` at the weakest possible
   specificity, so ANY class that sets a display beats it. The blueprint
   tables on the landing page are `.lp-bp { display: grid }`, and setting
   el.hidden on two of the three did exactly nothing: all three subjects'
   tables stacked down the page at once, which is how a visitor found it
   rather than a check. The same trap is waiting for every future
   `display:` rule, so the fix is one line here with the weight to win,
   not a `[hidden]` variant bolted onto each component.
   ============================================================ */

[hidden] { display: none !important; }

/* ============================================================
   19. UTILITIES — MUST STAY LAST
   A utility carries the same specificity as a component class, so the
   one written later wins. When these sat in the middle of the file,
   section 18's `.card { margin-bottom }` quietly beat `.mb-0`, and two
   cards sharing a grid row came out different heights because only one
   of them actually had its margin removed.

   Add new utilities here, at the bottom. Never above a component.
   ============================================================ */

.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.mb-3 { margin-bottom: var(--s-3); }
.mb-5 { margin-bottom: var(--s-5); }
.mb-8 { margin-bottom: var(--s-8); }

.hide { display: none; }
.invisible { visibility: hidden; }

.mt-2 { margin-top: var(--s-2); }
.mt-3 { margin-top: var(--s-3); }
.mt-4 { margin-top: var(--s-4); }
.mt-5 { margin-top: var(--s-5); }
.gap-xs { gap: 6px; }
.gap-5  { gap: var(--s-5); }
.full   { width: 100%; }

.t-base { font-size: var(--t-base); }
.t-lg   { font-size: var(--t-lg); }
.measure-narrow { max-width: 36ch; }

.text-err { color: var(--err); }

/* Buttons wrapped in their own <form> so they sit on a row with siblings. */
.form-inline { display: inline; }

/* Quiet inline link on a dark rail — the sign-out link in the admin sidebar. */
.link-quiet { color: var(--on-dark-2); }
.link-quiet:hover { color: var(--on-dark); }

/* A rail used purely as a heading row, with its divider suppressed. */
.rail.is-bare       { border-bottom: 0; padding-bottom: 0; }
.rail.is-bare-tight { border-bottom: 0; padding-bottom: var(--s-2); }

/* Fixed table column widths — admin coverage table. */
.col-icon { width: 32px; }
.col-bar  { width: 100px; }

/* Holds the page still while something is open over it. */
.is-locked { overflow: hidden; }
