/* Public-site chrome: the brand bar, the page column and the policy footer
   shared by the landing page and the four policy pages.

   The two viewer pages and the two dashboards use hub.css / shell.css instead
   — those are app chrome (sticky masthead, theme toggle, drawers) and this is
   the marketing and legal surface Razorpay reads. Keeping them apart is what
   lets the policy pages stay plain documents. */

:root { --page-max: 1080px; }

.wrap { max-width: var(--page-max); margin: 0 auto; padding: 0 24px; }

.topbar {
  display: flex; align-items: center; gap: 12px;
  max-width: var(--page-max); margin: 0 auto; padding: 14px 24px;
  /* width: 100%, because the syllabus and exams pages make <body> a column
     flex container, and an auto cross-axis margin turns off the default
     stretch — so the bar sized itself to its 444px of content and pushed a
     390px phone into sideways scroll. With an explicit width it fills the
     column and max-width still caps it on a desktop.

     box-sizing with it, and not optional: the landing page carries no global
     border-box reset, so width: 100% plus this 24px padding made the bar 48px
     wider than the viewport there. Same reason app.css states it on the two
     rules that set a percentage width. */
  width: 100%;
  box-sizing: border-box;
}
/* Pages with a section nav wrap the row in .topbar-inner; the policy pages put
   their two children straight in .topbar. Both have to come out as one
   horizontal row, so .topbar stays flex and the wrapper is a full-width flex
   item inside it. The landing page additionally overrides .topbar to a
   full-bleed block because its bar is sticky and has to span the viewport.

   This lived only in index.html until the syllabus and exams pages adopted the
   same bar — without it their brand, nav and theme toggle stacked into three
   centred rows. */
.topbar-inner {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  box-sizing: border-box;
  /* min-width: 0, because this is itself a flex item of .topbar and a flex
     item's default min-width: auto refuses to shrink below its content. The
     nav's five links measure 412px, so on a 390px phone the bar grew to 444px
     and took the whole page into sideways scroll instead of letting the nav
     scroll inside itself. The landing page never hit this: it overrides
     .topbar to display: block, where the wrapper is an ordinary block. */
  min-width: 0;
}

/* min-height, because the brand is a link home on every page and the 30px mark
   inside it set the whole target's height — the same floor .topbar-link and the
   footer links already keep. */
.topbar-brand { display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit; min-height: var(--tap-target); }
.topbar-brand img { width: 30px; height: 30px; display: block; }
.topbar-brand span {
  font-family: var(--font-display); font-size: 1.02rem; font-weight: 600; letter-spacing: -0.015em;
}
/* Pushes whatever follows the brand to the right edge — the account menu on
   the landing page, the back link on a policy page. */
.topbar-brand + * { margin-left: auto; }

.topbar-link {
  font-size: 0.88rem; color: var(--text-muted); text-decoration: none;
  min-height: var(--tap-target); display: inline-flex; align-items: center;
}
.topbar-link:hover { color: var(--primary-text); }

/* The section nav, shared by the landing page and the two reference pages
   (syllabus map, exams covered). It moved here from index.html's own <style>
   when those two pages adopted the same bar: a reader who follows Syllabus out
   of the landing page should not lose the way back to Pricing or Contact, and
   three private copies of one bar is how they drift apart.

   The landing page keeps the sticky positioning and the scroll spy in its own
   <style>, because only it has sections to spy on. */
.section-nav { display: flex; gap: 2px; margin-left: 18px; }
.section-nav a {
  font-size: 0.86rem; font-weight: 500;
  color: var(--text-muted); text-decoration: none;
  padding: 0 12px;
  min-height: var(--tap-target);
  display: inline-flex; align-items: center;
  border-radius: var(--radius);
  border-bottom: 2px solid transparent;
}
.section-nav a:hover { color: var(--primary-text); background: var(--surface-sunken); }
/* [hidden] is display: none from the UA sheet, and the rule above overrides it
   with inline-flex — so account-bar.js hiding the sales links for a student who
   has paid would have left them exactly where they were. Any rule that sets
   display on an element something else hides needs this pair. */
.section-nav a[hidden] { display: none; }
/* Set by the landing page's scroll spy, and used here for the page a reader is
   already on when the bar appears on the reference pages. */
.section-nav a.is-current {
  color: var(--primary-text);
  border-bottom-color: var(--primary);
}

/* The account slot sits at the right end of the bar on every page that carries
   the section nav. */
#account-slot { margin-left: auto; }

/* Sign in is the only action in the bar and it read as a disabled control:
   muted ink on card white, the same as the jump links beside it. Filled, at
   the pairing app.css's .cta-btn uses — --primary-text with white ink, because
   white on --primary is 3.96:1 and this label is 13px. */
#account-slot .nav-btn {
  padding: 0 16px;
  min-height: 36px;
  font-size: 0.84rem;
  font-weight: 600;
  background: var(--primary-text);
  border-color: var(--primary-text);
  color: #FFFFFF;
}
#account-slot .nav-btn:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
}
/* --primary-text lightens in dark mode, so white ink on it stops being
   readable and the fill takes dark ink instead. */
html.dark-mode #account-slot .nav-btn,
body.dark-mode #account-slot .nav-btn { color: #0B1A1C; }

/* Signed out the slot holds one button, and it belongs on the brand row where
   the theme toggle is. Signed in it holds three controls of three different
   heights — a bold greeting, a small pill and a bordered button — and squeezing
   them into the space left over beside the brand is what put the greeting on a
   different baseline from the mark next to it and let the pill hang below the
   row. Given a row of its own they line up against one another instead of
   against whatever width the brand happened to leave. */
#account-slot .account-menu {
  /* The same floor the brand, the theme button and the nav links keep, so all
     four sit on one line rather than three near-misses. */
  min-height: var(--tap-target);
  align-items: center;
  /* No wrapping inside the menu: it wraps as a row, or not at all. */
  flex-wrap: nowrap;
}

/* A student's name has no length limit, so the bar cannot assume one at ANY
   width. These three used to live inside the narrow-screen media query, which
   meant a long name was ellipsized on a phone and pushed the row off-screen on
   a tablet — "Priyadharshini Raman" signed in overflowed a 901px viewport by
   139px, and 1024px by 16.
   
   Unconditional now. They cost nothing when there is room: a flex item only
   shrinks once the row runs short, so a short name still renders in full on a
   wide screen. min-width: 0 is the part that does the work — a flex item
   refuses to shrink below its content without it, which is why nowrap on the
   name turned into overflow rather than an ellipsis. */
#account-slot,
#account-slot .account-menu {
  min-width: 0;
}

.account-menu .account-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Sign out keeps its full width — it is the control, and a clipped action is
   worse than a clipped greeting. */
.account-menu .nav-btn { flex-shrink: 0; }

/* The sign-in button's two labels. Full text wherever the bar has room for it;
   the short one below 520px, where the extra 61px would otherwise push the
   account slot onto a row of its own and add ~50px to a sticky bar. That
   reasoning still holds for the signed-OUT bar, which is one button and stays
   on the brand row. */
.label-narrow { display: none; }
@media (max-width: 520px) {
  .label-wide { display: none; }
  .label-narrow { display: inline; }
}

/* The account menu stays on the brand row at every width. It was briefly given
   a full-width second row below 900px, on a misreading of a note about the
   signed-in bar; a sticky bar that grows a whole row the moment somebody signs
   in costs that height on every screen of every page, which is the thing the
   budget below exists to prevent.

   So the space is bought back from the two least valuable things in the row
   instead. The expiry chip goes first — a date is reference information a
   student can find on the account page, and it is not worth a fifth of a
   390px viewport. The wordmark goes with it, but only while the account menu
   is present: the mark itself stays and still links home. If :has() is
   unsupported the wordmark simply stays and the bar is taller — worse, but
   nothing breaks. */
@media (max-width: 520px) {
  .account-status { display: none; }
  .topbar-inner:has(.account-menu) .topbar-brand span { display: none; }

  /* And once the wordmark is hidden, the brand must stop growing.
     
     Both the brand and the account slot are given `flex: 1 1 0` on a narrow
     row, each for its own good reason — the brand so a scaled-up wordmark
     ellipsizes instead of breaking the row, the slot so the greeting always
     fits before it shrinks. Under 520px with a student signed in those two
     reasons collide: the rule above has already hidden the wordmark, so the
     brand has nothing left to show but a 30px mark, yet it still takes an
     equal share of the free space. Measured at 360px it held 118px for that
     mark while "Hi, Jawahar" — which wants 91px — was ellipsized down to 22px
     and rendered as the single letter "H".
     
     Basis auto here, so the mark asks for its own width and the rest goes to
     the greeting. Scoped to :has(.account-menu) because it is only correct
     while the wordmark is hidden: signed out the span is still there, still
     needs to give way by ellipsis, and keeps the growing brand above. */
  .topbar-inner:has(.account-menu) .topbar-brand { flex: 0 0 auto; }
}

/* Staying on one row cannot depend on how long the student's name is.
   Hiding the wordmark and the chip above left roughly 312px on a 360px
   screen for a mark, a theme button and the menu — which fits "Hi, Priya"
   with about 12px to spare and does not fit "Hi, Jawahar". Two characters
   decided the layout, and Android's text-scaling setting moves the same
   number again.

   flex-wrap places an item on the next line when its CONTENT size does not
   fit; shrinking only happens afterwards, within a line. So the slot is
   given flex-basis 0 — it always fits, then grows into whatever is left —
   and the greeting inside it is allowed to ellipsis. The button never
   shrinks, because "Sign ou…" is not a control. The result holds for any
   name at any font scale.

   Specificity is deliberate: index.html's own <style> sets
   `#account-slot { margin-left: auto }` at (1,0,0) and loads after this
   file, so a plainer selector here would be overruled on that one page. */
/* 900, not 720. Between those two widths the bar had to fit the brand, the
   Printed books button, five jump links, the theme toggle and the account
   control on ONE row, and it does not: at 721px the page overflowed by 107px
   and Sign in was clipped by the viewport edge. Signed in it was worse — the
   name and Sign out pushed the row to 953px, so every tablet under 1024
   scrolled sideways.
   
   That band is most iPads held upright (Mini 768, Air 820, Pro 11 834), so it
   was not an edge case. Nothing new is invented here: the wrapping row and the
   sideways-scrolling nav below already solved this shape, and this simply lets
   them run up to the width where the single row genuinely fits. */
@media (max-width: 900px) {
  .topbar-inner:has(.account-menu) #account-slot {
    flex: 1 1 0;
    min-width: 0;
    /* Replaces margin-left: auto — flex-grow does the pushing now, and an
       auto margin alongside it would eat the free space growth needs. */
    margin-left: 8px;
  }

  /* Right-aligned only once the slot is growing to fill the row; the shrink
     and ellipsis rules that used to sit here are unconditional now, above. */
  .topbar-inner:has(.account-menu) .account-menu {
    justify-content: flex-end;
  }
}

/* The signed-in bar carries more than the signed-out one — a greeting, a status
   pill and Sign out, where a visitor gets a single button — so it runs out of
   row sooner and has to wrap sooner.
   
   Measured rather than guessed: signed out fits on one row from about 900px,
   signed in not until about 1024. Giving the signed-in bar its own band is why
   the base breakpoint above stays at 900 — raising that instead would hand
   every signed-out visitor a two-row bar between 901 and 1023, where one row
   fits perfectly well. */
@media (min-width: 901px) and (max-width: 1023px) {
  .topbar-inner:has(.account-menu) { flex-wrap: wrap; position: relative; }

  .topbar-inner:has(.account-menu) .section-nav {
    margin-left: 0;
    order: 3;
    width: 100%;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .topbar-inner:has(.account-menu) .section-nav::-webkit-scrollbar { display: none; }
  .topbar-inner:has(.account-menu) .section-nav a { padding: 0 10px; white-space: nowrap; }
}

/* Below this the five links need more width than a phone gives the row, so it
   scrolls sideways — and a scrollable row with no visible cut-off edge reads as
   a finished row, which is how Syllabus and Exams came to be invisible. The
   fade and chevron are painted by .topbar-inner::after; assets/js/nav.js
   measures the row and sets the two classes. */
@media (max-width: 900px) {
  .topbar-inner { flex-wrap: wrap; position: relative; }

  /* Row one carries the brand, the theme toggle and the account control at
     every text scale — the jump links are the only thing meant to drop to a
     row of their own.

     flex-wrap breaks a line on an item's HYPOTHETICAL size, which for the
     brand is its content, and only shrinks what survived on the line
     afterwards. So the wordmark asked for its full width first and Sign in
     was pushed onto a row by itself: right-aligned, alone above the links,
     with the bar standing three rows and 167px tall on a 360px screen — and
     it is sticky, so that is gone for the whole scroll. Nothing was wrong
     with the px budget the phone rules tune; Android's font-size setting
     simply moves the wordmark out of it, and a bar that only survives at
     100% text is a bar that breaks for the readers who most need the setting.

     Basis 0 means the brand asks for nothing and grows into whatever is left,
     so the WORDMARK gives way — by ellipsis, mark and link intact — instead
     of the row breaking. The same reasoning already keeps the signed-in
     account menu on its row a few rules above. */
  .topbar-brand { flex: 1 1 0; min-width: 0; }
  .topbar-brand img { flex-shrink: 0; }
  .topbar-brand span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Signed out this is one button and it holds its size; signed in the
     :has(.account-menu) rule above overrides this to grow instead. */
  #account-slot { flex-shrink: 0; }

  .topbar .section-nav {
    margin-left: 0;
    order: 3;
    width: 100%;
    /* Same reason as .topbar-inner above — without it the nav is a flex item
       that will not shrink under its own content, so overflow-x never has
       anything to scroll. */
    min-width: 0;
    overflow-x: auto;
    /* Hides the horizontal scrollbar chrome on the platforms that draw one
       over the links; the row still scrolls by touch and by wheel. */
    scrollbar-width: none;
  }
  .topbar .section-nav::-webkit-scrollbar { display: none; }
  .section-nav a { padding: 0 10px; white-space: nowrap; }

  .topbar-inner::after {
    content: '›';
    position: absolute;
    right: 0;
    bottom: 0;
    width: 46px;
    height: var(--tap-target);
    display: none;
    align-items: center;
    justify-content: flex-end;
    padding-right: 6px;
    font-size: 1.15rem;
    line-height: 1;
    color: var(--primary-text);
    background: linear-gradient(to left, var(--surface-card) 46%, transparent);
    /* The links underneath must stay tappable through the hint. */
    pointer-events: none;
    transition: opacity 0.2s;
  }
  .topbar-inner.nav-overflows::after { display: flex; }
  .topbar-inner.nav-overflows.nav-at-end::after { opacity: 0; }
}

/* Theme switch. Named .theme-btn rather than reusing hub.css's
   .theme-toggle-btn: that rule carries margin-left: auto, which on this bar
   would shove the button and everything after it to the far right. */
.theme-btn {
  width: var(--tap-target); height: var(--tap-target);
  display: grid; place-items: center;
  flex-shrink: 0;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-card);
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
}
.theme-btn:hover {
  border-color: var(--primary);
  background: var(--primary-glow);
  color: var(--primary-text);
}
/* One button, two glyphs: show the theme the click will switch TO, which is the
   convention readers expect from a single toggle. */
.theme-btn svg { display: none; }
.theme-btn .icon-moon { display: block; }
html.dark-mode .theme-btn .icon-moon,
body.dark-mode .theme-btn .icon-moon { display: none; }
html.dark-mode .theme-btn .icon-sun,
body.dark-mode .theme-btn .icon-sun { display: block; }

/* Contact rows appear on the landing page and again, in full, on contact.html
   — Razorpay wants the address, phone and email visible, not one-hop away. */
.contact-line { display: flex; gap: 11px; align-items: flex-start; margin-bottom: 12px; }
.contact-line:last-child { margin-bottom: 0; }
.contact-line svg { flex-shrink: 0; margin-top: 2px; color: var(--primary-text); }
.contact-line .label {
  display: block;
  font-size: 0.6rem; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 2px;
}
/* :not(.social-icon) — the FOLLOW US row is a .contact-line too, so this
   underline was being drawn under each social glyph as well. It went unseen
   only while those anchors carried a box border of their own; the landing page
   dropped the box and the stray rules appeared. */
.contact-line a:not(.social-icon) { color: var(--text-main); text-decoration: none; border-bottom: 1px solid var(--border-strong); }
.contact-line a:not(.social-icon):hover { color: var(--primary-text); border-bottom-color: var(--primary); }
.contact-line address { font-style: normal; line-height: 1.5; font-size: 0.92rem; color: var(--text-main); }

.site-footer {
  margin-top: 48px;
  border-top: 1px solid var(--border-color);
  background: var(--surface-card);
  padding: 22px 0 28px;
}
.footer-inner { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center; }
.footer-links { display: flex; flex-wrap: wrap; gap: 4px 18px; }
.footer-links a {
  color: var(--text-muted); text-decoration: none; font-size: 0.85rem;
  min-height: var(--tap-target); display: inline-flex; align-items: center;
}
.footer-links a:hover { color: var(--primary-text); }
.footer-legal { margin-left: auto; font-size: 0.8rem; color: var(--text-faint); }

/* Policy documents. One measure, generous leading, no cards — these are read
   start to finish, not scanned, and a card per clause would fight that. */
.legal { padding: 8px 0 4px; }
.legal h1 {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.6vw, 2.3rem);
  font-weight: 600; letter-spacing: -0.025em;
  margin: 0 0 6px;
}
.legal .updated {
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-muted); margin: 0 0 28px;
}
.legal section { max-width: 74ch; margin-bottom: 28px; }
.legal h2 {
  font-family: var(--font-display);
  font-size: 1.2rem; font-weight: 600; letter-spacing: -0.015em;
  margin: 0 0 8px;
}
.legal p, .legal li { font-size: 0.95rem; line-height: 1.65; color: var(--text-main); }
.legal p { margin: 0 0 10px; }
.legal ul, .legal ol { margin: 0 0 10px; padding-left: 20px; }
.legal li { margin-bottom: 6px; }
.legal a { color: var(--primary-text); }

/* A definition table beats six paragraphs for the refund timelines and the
   data-we-collect list, which are the two things a reader actually looks up. */
.legal-table { width: 100%; border-collapse: collapse; margin: 0 0 12px; font-size: 0.92rem; }
.legal-table th, .legal-table td {
  text-align: left; padding: 9px 12px;
  border: 1px solid var(--border-color);
  vertical-align: top;
}
.legal-table th { background: var(--surface-sunken); font-weight: 600; }

/* The one box on a policy page: the clause a reader must not miss. */
.legal-callout {
  border: 1px solid var(--accent-amber-line);
  background: var(--accent-amber-bg);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin: 0 0 12px;
  max-width: 74ch;
}
.legal-callout p { margin: 0; }

@media (min-width: 1100px) {
  :root { --page-max: 1200px; }
}

@media (max-width: 480px) {
  .wrap { padding: 0 16px; }
  .topbar { padding: 12px 16px; }
  .footer-legal { margin-left: 0; }
  /* A three-column policy table does not fit 360px; let it scroll rather than
     shrink the type below readable. */
  .legal-table { display: block; overflow-x: auto; white-space: nowrap; }

  /* The email and phone links stood 21px tall — under half the tap floor the
     footer links in this file already keep, and these two are the ones a
     student actually reaches for on a phone. */
  .contact-line a:not(.social-icon) {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-target);
    /* The 1px border-bottom that draws their underline would sit at the bottom
       of a 44px box, a rule floating well clear of the text. A text decoration
       tracks the glyphs instead, at the same weight and colour. */
    border-bottom: none;
    text-decoration: underline;
    text-decoration-color: var(--border-strong);
    text-underline-offset: 3px;
  }
  .contact-line a:not(.social-icon):hover { text-decoration-color: var(--primary); }
}

/* ── Nav preview panels ───────────────────────────────────────────────────
   Built and positioned by assets/js/nav.js, appended to <body> rather than to
   the nav — below 900px .section-nav is an overflow-x container and would clip
   anything inside it. Desktop only; nav.js never wires these up on a coarse
   pointer. */
.nav-preview {
  position: fixed;
  z-index: 60;
  width: min(300px, calc(100vw - 24px));
  padding: 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  box-shadow: var(--shadow-lg);
}

.nav-preview-title {
  margin: 0 0 8px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Twelve syllabus units is a tall panel; on a short laptop screen it scrolls
   inside itself rather than running off the bottom of the viewport. */
.nav-preview-list {
  list-style: none; margin: 0; padding: 0; display: grid; gap: 1px;
  max-height: min(52vh, 420px);
  overflow-y: auto;
}

.nav-preview-list a {
  display: block;
  padding: 8px 10px;
  border-radius: var(--radius);
  font-size: 0.86rem;
  color: var(--text-main);
  text-decoration: none;
}
.nav-preview-list a:hover { background: var(--primary-glow); color: var(--primary-text); }

.nav-preview-foot {
  display: block;
  margin-top: 8px;
  padding-top: 9px;
  border-top: 1px solid var(--border-color);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--primary-text);
  text-decoration: none;
}
.nav-preview-foot:hover { text-decoration: underline; }

/* ── The printed-books link ───────────────────────────────────────────────
   Tamizhi Books sells physical books at tamizhibooks.com, and this site is the
   question bank. A reader who wants the printed catalogue had no way to reach
   it from here at all.

   Deliberately NOT the brand mark: that link is how every page gets home, and
   pointing it off-site would strand people. This is a separate, labelled link
   that says where it goes, and it takes the accent so it does not read as a
   sixth jump link beside Pricing and Contact us. */
/* Qualified with .section-nav, and that is load-bearing: the link lives inside
   that row, and `.section-nav a` is (0,1,1) — a bare `.nav-shop` at (0,1,0)
   loses to it and the accent treatment silently does nothing. */
.section-nav .nav-shop {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 6px;
  padding: 0 12px;
  min-height: var(--tap-target);
  border: 1px solid var(--primary);
  border-bottom: 1px solid var(--primary);
  border-radius: var(--radius);
  background: var(--primary-glow);
  color: var(--primary-text);
  font-size: 0.84rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
}
.section-nav .nav-shop:hover {
  background: var(--primary-tint);
  border-color: var(--primary-text);
  color: var(--primary-text);
}
.section-nav .nav-shop svg { flex-shrink: 0; }

/* The footer's Share control sits in the policy nav because that row is the
   one thing on every page of the site, and sharing is not a policy link — it
   is styled as a link rather than a button so it does not shout over four
   things a visitor may legally need to find. */
.footer-share {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--primary-text);
  cursor: pointer;
  /* Matches .footer-links a so the row's baseline and tap height do not break
     where the anchors end and this begins. */
  min-height: var(--tap-target);
  display: inline-flex;
  align-items: center;
}

.footer-share:hover {
  color: var(--primary-hover);
  text-decoration: underline;
}


/* The hero call to action, while we do not yet know who is looking.
   Held rather than hidden: collapsing the row would move everything below it
   and then move it back, which is worse than a moment of stillness. It keeps
   its exact size and stops being readable or clickable, so nothing can be
   acted on before it is true. Only ever applied when a session is already in
   this browser — an anonymous visitor never waits. */
.cta-row-pending {
  opacity: 0.35;
  pointer-events: none;
  filter: blur(1.5px);
  transition: opacity 0.18s ease, filter 0.18s ease;
}

@media (prefers-reduced-motion: reduce) {
  .cta-row-pending { transition: none; }
}


/* ── The hero ───────────────────────────────────────────────────────────────
   One definition, for every page that opens with a title.

   There were three. The landing page set clamp(1.75rem … 3.1rem) at weight
   600 in the display face; the syllabus and exams pages set a fixed 2.4rem
   dropping to 1.9rem on a phone; the study hub set 3rem at weight 900 with a
   gradient poured through the letterforms. Navigating between them, the
   heading changed size, weight, colour and — on a phone, where a clamped
   1.75rem met a fixed 1.9rem — direction: the same journey made the title
   bigger on one step and smaller on the next.

   The pages keep their own content under it. What they no longer keep is
   their own idea of what a page title looks like. */
.hero {
  text-align: center;
  padding: 24px 0 8px;
}

.hero-title {
  font-family: var(--font-display);
  /* Fluid, not stepped. A fixed size with a phone override is two numbers to
     keep in step across four files, and they had already drifted. */
  font-size: clamp(1.75rem, 4.6vw, 3.1rem);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin: 0 0 14px;
  text-wrap: balance;
}

/* The line under the title. --measure caps the text and nothing else, so a
   page whose hero also carries a stat row or a pair of buttons keeps the full
   column for those. */
.hero-sub {
  max-width: var(--measure);
  margin: 0 auto;
  font-size: 1rem;
  /* Not --text-muted: every hero sits on the page ground rather than in a
     card, and that ink is calibrated against card white — on the ground it
     measured 4.21:1, under AA, on all four pages carrying this line. */
  color: var(--text-muted-on-page);
  line-height: 1.6;
}

@media (max-width: 900px) {
  .hero { padding: 30px 0 10px; }
}

@media (max-width: 520px) {
  .hero { padding: 12px 0 4px; }
}


/* The sales links, off for a student who has paid. The class is put on <html>
   by entitlement-hint.js before the first paint and corrected by
   account-bar.js once /api/me answers — see the note at the top of the hint
   file for why it is not simply link.hidden. */
.is-entitled .section-nav [data-nav-role="sell"] { display: none; }


/* ── Sub-page header ───────────────────────────────────────────────────────
   Progress, Syllabus and Exams are leaves hanging off the study hub, not
   front doors of their own. Each used to open with the full centred .hero —
   a display title at up to 3.1rem and a sub-line under it — so a reader
   walking Study → Syllabus → Exams met three different mastheads, none of
   them the one on the landing page, each announcing a page they had already
   chosen to be on.

   What they carry instead is the way back and the name of the room. The
   title stays an <h1>: it is still the page's top-level heading, and dropping
   it would leave three pages with none. It is simply no longer dressed as a
   hero. The back link is .back-btn from hub.css — the same up-one-level
   control paper-wise already uses, so there is one of these in the codebase
   and not two.

   Left-aligned, because it is a header rather than a hero: centring is what
   says "this is the top of something", and these are pages you arrive at
   mid-journey. */
.subpage-head {
  /* main is a flex column on the exams and syllabus pages, so a block with no
     stated width shrink-wraps to its content and the title stops being the
     full column. */
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
}

.subpage-title {
  font-family: var(--font-display);
  /* Fluid like .hero-title and for the same reason, but a register down: it
     tops out where the hero starts. A page reached from the hub should not
     shout louder than the hub did. */
  font-size: clamp(1.45rem, 3.4vw, 1.9rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0;
  color: var(--text-main);
  text-wrap: balance;
}


/* ── Progress bar ──────────────────────────────────────────────────────────
   The first bar in this codebase, and deliberately a small one. Progress here
   is a fact printed beside a topic, not the hero of any page — a thick bar
   would out-shout the topic name it belongs to.

   Three segments, always in the same order: what you got right first time,
   what you have to come back to, what you have not opened. Widths are a share
   of the questions the reader can actually open, so two topics side by side
   are comparable rather than each being normalised to its own full width.

   COLOUR IS NEVER THE ONLY CARRIER. Every bar is printed beside its counts and
   labelled by barLabel() in topic-progress.js, which is what a screen reader
   and a colour-blind reader get instead of the segments. */
.prog-bar {
  display: flex;
  height: 6px;
  min-width: 48px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-sunken);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}

.prog-seg { display: block; height: 100%; }

/* --primary is 3.96:1 and may never carry text (tokens.css). As a bar fill it
   is exactly what it is for. */
.seg-correct { background: var(--primary); }
.seg-incorrect { background: var(--accent-amber); }
/* Left to do is the track showing through, not a fourth colour. */
.seg-left { background: transparent; }

/* The counts under or beside a bar. Teal numbers use --primary-text, never
   --primary. */
.prog-figs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  /* A <ul> because it is a list of counts, and every list style off because it
     is read as a line. Both are needed: the marker box otherwise sits between
     two items that are already separated by the gap. */
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.prog-figs li { white-space: nowrap; }

.prog-figs b { font-weight: 600; color: var(--text-main); }
.prog-figs .is-correct { color: var(--primary-text); }
.prog-figs .is-incorrect { color: var(--accent-amber); }
.prog-figs .is-saved { color: var(--accent-blue); }

/* A topic with nothing answered. Printed rather than hidden: "not started" is
   one of the three things a student came to this page to find out. */
.prog-bar.is-empty { opacity: 0.55; }
