/* Gyaan Daily — the only stylesheet on the site.
 *
 * Skeleton from bharometer.com (our own site), skin from this app. House rule is shared skeleton,
 * unique skin: the components below are Bharometer's `.sec-head`, `.feat-grid`, `.india-card`,
 * `.reveal`, the CSS phone bezel, the drifting `.gallery` rail, `.foot-grid` and the vignette and
 * film grain. None of its navy-and-sunshine palette comes with them.
 *
 * ⛔ DO NOT INVENT A COLOUR. Every value in the two palettes below is lifted from
 * `ios/GyaanDaily/Design/Tokens.swift`: the dark half from `Palette.dark` (Night Study) and the
 * light half from `Palette.light` (Ink and Paper, Warmed). The design run that preceded the first
 * version of this page invented one placeholder grey, used it nineteen times, and it failed AA at
 * 3.26:1.
 *
 * ⭐ THREE THEME STATES, and all three are needed.
 *   1. The full LIGHT palette on bare `:root`, so a page with no JavaScript and no system
 *      preference still has every token defined.
 *   2. The dark palette under `@media (prefers-color-scheme: dark)`, guarded by
 *      `:root:not([data-theme="light"])`, so an explicit light choice beats the phone.
 *   3. The dark palette again under `:root[data-theme="dark"]`, so an explicit dark choice beats a
 *      light phone. Without this third block the toggle only ever wins in one direction.
 * A colour whose ONLY definition sits inside a media query is a colour that is undefined half the
 * time. Nothing below is defined that way.
 */

:root {
  /* ── LIGHT · Palette.light, "Ink and Paper, Warmed" ─────────────────────────
   * Measured against this ground: text 16.01:1, textSecondary 8.24:1,
   * textMuted 5.33:1, primary 5.40:1, primaryDeep 7.41:1, complement 7.49:1.
   */
  --bg: #F6F1E7;
  --surface: #FFFAF1;
  --raised: #FFFFFF;
  --border: #E0D8C8;
  --border-strong: #CCC1AA;

  --text: #191612;
  --text-secondary: #4E463B;
  --text-muted: #6B6253;

  /* ⚠️ The eyebrow and the hero's small print are the ONLY two things that stopped the light
   * theme carrying a visible band photograph. Measured over the real pixels, --primary under an
   * eyebrow fell to 4.08:1 and --text-muted under the hero note to 3.95:1, both against a 4.5
   * floor, while every other element cleared. So in light they are inked one step darker, out of
   * colours already in Palette.light: primaryDeep is 7.41:1 and textSecondary 8.24:1 on this
   * ground. Dark keeps the lighter pair, which measured clean at the thin wash. */
  --glass-tint: rgba(255, 250, 241, 0.86);
  --glass-solid: rgba(255, 250, 241, 0.97);
  --glass-edge: rgba(255, 255, 255, 0.60);

  --eyebrow-ink: var(--primary-deep);
  --note-ink: var(--text-secondary);

  --primary: #7F5C1E;
  --primary-deep: #654917;
  --on-primary: #F8F3E9;

  --complement: #2F4E7A;
  --tint-bg: #E4EAF3;
  --tint-text: #28456E;

  /* The two atmospherics. On paper the vignette is a faint warm shade and the grain is nearly off,
   * because printed paper does not have a dark halo round it. */
  --vignette: rgba(90, 74, 46, 0.13);
  --grain: 0.035;
  --card-shadow: 0 22px 60px -34px rgba(60, 48, 28, 0.55);
  --spotlight: rgba(127, 92, 30, 0.10);

  color-scheme: light;

  /* The hero's phone. Dark titanium on paper. */
  --rail: linear-gradient(150deg, #8c877e 0%, #2b2825 14%, #191715 46%, #24211e 74%, #7d786f 100%);
  --rail-edge: inset 0 0 0 1.5px rgba(255, 255, 255, 0.22);
  --rail-button: linear-gradient(180deg, #55524b, #2c2a26);
  /* The hero phone's OUTER width, and therefore the width of the whole right-hand column: the
   * chips and the caption take it too. Tally, 12 August: fill the column properly. It is a max, not
   * a fixed size — below about 420px of column the phone simply takes what there is. */
  --phone-w: 404px;
  /* ⛔ ONE HEIGHT FOR BOTH NAV CONTROLS, and it is a variable because they were two independent
   * numbers and drifted apart. The language control was 9px of padding on a 0.92rem line, ≈38-39px;
   * the theme button was a hard 42x42. Three pixels of difference reads as misalignment the moment
   * they are not touching, and WD-9 stopped them touching. Change this and both move together. */
  --tool-h: 42px;
  --radius: 14px;            /* chips, not pills. Never 999px. */
  --radius-lg: 20px;
  --measure: 34rem;          /* reading width for body prose */
  --gutter: clamp(20px, 5vw, 40px);
  --section-gap: clamp(66px, 10vw, 118px);

  --serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  --sans: "Source Sans 3", system-ui, -apple-system, "Segoe UI", sans-serif;
  --deva: "Noto Serif Devanagari", var(--serif);
  --tamil: "Noto Serif Tamil", var(--serif);
  /* ⭐ The SANS Indic faces. Interface text in Hindi and Tamil is not a verse and must not be set in
   * a display serif: the whole navigation, every button and both legal pages are now written in
   * these scripts. `subset_fonts.py` measures the built pages, so these two faces pick up the UI
   * vocabulary by themselves. */
  --deva-sans: "Noto Sans Devanagari", var(--sans);
  --tamil-sans: "Noto Sans Tamil", var(--sans);
}

/* ── DARK · Palette.dark, "Night Study" ──────────────────────────────────────
 * text 15.50:1, textSecondary 9.43:1, textMuted 5.78:1, primary 9.97:1,
 * primaryDeep 6.60:1, complement 7.78:1, all on this ground.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0C0C0B;
    --surface: #151412;
    --raised: #1E1C18;
    --border: #26241F;
    --border-strong: #38342C;

    --text: #F3E3C8;
    --text-secondary: #C3B295;
    --text-muted: #978A74;

    --glass-tint: rgba(21, 20, 18, 0.76);
    --glass-solid: rgba(21, 20, 18, 0.95);
    --glass-edge: rgba(243, 227, 200, 0.16);

    --eyebrow-ink: var(--primary);
    --note-ink: var(--text-secondary);

    --primary: #E0B25F;
    --primary-deep: #B98F45;
    --on-primary: #0C0C0B;

    --complement: #7FA6D8;
    --tint-bg: #16202E;
    --tint-text: #9CBCE6;

    /* ⛔ Brighter metal on the night theme, or the phone disappears into the page. */
    --rail: linear-gradient(150deg, #d8d2c6 0%, #6f6a61 16%, #46423b 48%, #6b665e 76%, #cfc8bb 100%);
    --rail-edge: inset 0 0 0 1.5px rgba(255, 255, 255, 0.42), 0 0 0 1px rgba(243, 227, 200, 0.10);
    --rail-button: linear-gradient(180deg, #cbc4b7, #6b665e);

    --vignette: rgba(2, 3, 5, 0.55);
    --grain: 0.07;
    --card-shadow: 0 26px 70px -34px rgba(0, 0, 0, 0.9);
    --spotlight: rgba(224, 178, 95, 0.09);

    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg: #0C0C0B;
  --surface: #151412;
  --raised: #1E1C18;
  --border: #26241F;
  --border-strong: #38342C;

  --text: #F3E3C8;
  --text-secondary: #C3B295;
  --text-muted: #978A74;

  --glass-tint: rgba(21, 20, 18, 0.76);
  --glass-solid: rgba(21, 20, 18, 0.95);
  --glass-edge: rgba(243, 227, 200, 0.16);

  --eyebrow-ink: var(--primary);
  --note-ink: var(--text-secondary);

  --primary: #E0B25F;
  --primary-deep: #B98F45;
  --on-primary: #0C0C0B;

  --complement: #7FA6D8;
  --tint-bg: #16202E;
  --tint-text: #9CBCE6;

  /* ⛔ Brighter metal on the night theme, or the phone disappears into the page. */
  --rail: linear-gradient(150deg, #d8d2c6 0%, #6f6a61 16%, #46423b 48%, #6b665e 76%, #cfc8bb 100%);
  --rail-edge: inset 0 0 0 1.5px rgba(255, 255, 255, 0.42), 0 0 0 1px rgba(243, 227, 200, 0.10);
  --rail-button: linear-gradient(180deg, #cbc4b7, #6b665e);

  --vignette: rgba(2, 3, 5, 0.55);
  --grain: 0.07;
  --card-shadow: 0 26px 70px -34px rgba(0, 0, 0, 0.9);
  --spotlight: rgba(224, 178, 95, 0.09);

  color-scheme: dark;
}

/* ── Faces ────────────────────────────────────────────────────────────────────
 * Self-hosted, subsetted from the same OFL files the app ships, so the page makes no third-party
 * request. A site whose argument is that nothing leaves the device should not phone a font CDN to
 * say so, and the privacy page says as much in words.
 *
 * ⭐ `opsz` is PINNED at 16 on Source Serif 4. Left automatic it reads about half a weight light at
 * display sizes, which is the defect the typography run caught.
 * ⭐ `font-display: block` on the Indic faces, not swap. A Latin fallback for Devanagari is tofu,
 * not a substitute, and on the Hindi and Tamil pages that is now the whole interface.
 */
@font-face {
  font-family: "Source Serif 4";
  src: url("fonts/SourceSerif4.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-variation-settings: "opsz" 16;
  font-display: swap;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/SourceSans3.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-display: swap;
}
@font-face {
  font-family: "Noto Serif Devanagari";
  src: url("fonts/NotoSerifDevanagari.woff2") format("woff2-variations");
  font-weight: 400 900;
  font-display: block;
}
@font-face {
  font-family: "Noto Serif Tamil";
  src: url("fonts/NotoSerifTamil.woff2") format("woff2-variations");
  font-weight: 400 900;
  font-display: block;
}
@font-face {
  font-family: "Noto Sans Devanagari";
  src: url("fonts/NotoSansDevanagari.woff2") format("woff2-variations");
  font-weight: 400 900;
  font-display: block;
}
@font-face {
  font-family: "Noto Sans Tamil";
  src: url("fonts/NotoSansTamil.woff2") format("woff2-variations");
  font-weight: 400 900;
  font-display: block;
}

/* ── Base ─────────────────────────────────────────────────────────────────── */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  font-variation-settings: "opsz" 16;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ⭐ The whole interface, not only the verses, switches face with the page language. Set on <html>
 * by the generator, so a Hindi page's buttons and legal prose are in Noto Sans Devanagari rather
 * than a Latin face improvising. */
html[lang="hi"] body { font-family: var(--deva-sans); }
html[lang="ta"] body { font-family: var(--tamil-sans); }
html[lang="hi"] h1, html[lang="hi"] h2, html[lang="hi"] h3 { font-family: var(--deva); }
html[lang="ta"] h1, html[lang="ta"] h2, html[lang="ta"] h3 { font-family: var(--tamil); }

/* Bharometer's two atmospherics, and much of why that site looks made rather than generated.
 * Both are fixed, both are pointer-transparent, and both are tokenised so the light palette gets a
 * warm shade instead of a black halo. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 190;
  background: radial-gradient(120% 95% at 50% 42%, transparent 58%, var(--vignette) 100%);
}
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 200;
  opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

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

::selection { background: var(--primary); color: var(--on-primary); }

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.012em;
  margin: 0 0 0.5em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.05rem, 5.8vw, 3.4rem); }
h2 { font-size: clamp(1.65rem, 4vw, 2.4rem); }
h3 { font-size: clamp(1.08rem, 2.2vw, 1.28rem); }
/* Bharometer's accent word inside a heading. One pattern, and most of the family resemblance. */
h1 em, h2 em { font-style: italic; color: var(--primary); }
p { margin: 0 0 1.1em; }

.wrap {
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding-inline: var(--gutter);
}
.narrow { max-width: 760px; }

.lede {
  font-size: clamp(1.05rem, 2vw, 1.2rem);
  color: var(--text-secondary);
  max-width: var(--measure);
}

.eyebrow {
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--eyebrow-ink);
  font-weight: 700;
  margin: 0 0 0.9em;
}
/* ⚠️ Uppercasing is a Latin idea. Devanagari and Tamil have no case, and `letter-spacing` on either
 * pulls a conjunct apart from the mark that belongs to it. Both are switched off by script. */
html[lang="hi"] .eyebrow,
html[lang="ta"] .eyebrow { text-transform: none; letter-spacing: 0.04em; }

.sec-head { max-width: 42rem; margin-bottom: clamp(30px, 5vw, 48px); }
/* ⚠️ WD-14. TWO section heads on the whole page are centred, S4 and S5, and no others. A single
 * centred head among six ranged-left ones reads as a mistake; a pair of adjacent ones reads as a
 * decision, which is why the ruling for S4 was applied to S5 in the same round. ⛔ Do not hand this
 * class to a third section without deciding what the page is then saying. */
.sec-head-mid { margin-inline: auto; text-align: center; }

section { padding-block: var(--section-gap); }
section + section { border-top: 1px solid var(--border); }

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: var(--gutter);
  top: 12px;
  z-index: 300;
  background: var(--primary);
  color: var(--on-primary);
  padding: 10px 16px;
  border-radius: var(--radius);
}

/* Bharometer's scroll-in. One IntersectionObserver, threshold .12, unobserved after it fires.
 * ⛔ The starting state is only applied once the page has said it has JavaScript. Without that,
 * every revealed block on the page is invisible forever to a reader with scripting off. */
.js .reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1); }
.js .reveal.in { opacity: 1; transform: none; }

/* ── Chips and buttons ────────────────────────────────────────────────────── */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: inherit;
  font-size: 0.97rem;
  font-weight: 600;
  padding: 11px 20px;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--raised);
  color: var(--text);
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
}
.chip:hover { border-color: var(--text-muted); text-decoration: none; }
.chip:active { transform: translateY(1px); }

.chip-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}
.chip-primary:hover { background: var(--primary-deep); border-color: var(--primary-deep); color: var(--on-primary); }

.chip[aria-pressed="true"], .chip[aria-selected="true"] {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}

/* ── Nav ──────────────────────────────────────────────────────────────────── */

.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
/* ⚠️ 96%, not 88%. The nav is sticky over the hero's photograph, and at 88% the picture came
 * through it: the gold chip and the links were sitting on a lit wall rather than on a ground. It
 * still reads as glass over scrolled content, which is the point of the blur, without the top of
 * the page being the one place where a photograph is behind a button. */
@supports (backdrop-filter: blur(12px)) {
  .nav { background: color-mix(in srgb, var(--bg) 96%, transparent); backdrop-filter: saturate(140%) blur(12px); }
}
.nav-in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-height: 62px;
}
.wordmark {
  font-family: var(--serif);
  font-size: 1.22rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  white-space: nowrap;
}
.wordmark:hover { text-decoration: none; }
.wordmark b { color: var(--primary); font-weight: 600; }

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.8vw, 22px);
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav-links > li { display: flex; }
/* ⛔ WD-9. The language control and the theme button share one <li>, and `.nav-links { gap }` spaces
 * the <li>s from EACH OTHER and never reaches inside one. So on desktop the two controls sat flush,
 * separated by nothing but their own two 1px borders.
 *
 * ⚠️ Why nothing caught it for a month: the mobile drawer sets its own `gap: 10px` on this same
 * element, so every phone capture looked right. The fault existed in exactly one viewport and
 * verification only ever photographed the other. This rule is OUTSIDE the media query on purpose —
 * one gap that holds at all widths, rather than a second number to keep in sync with the drawer's. */
.nav-links > li.nav-tools { align-items: center; gap: 10px; }
.nav-links a {
  color: var(--text-secondary);
  font-size: 0.95rem;
  font-weight: 500;
}
.nav-links a:hover { color: var(--text); text-decoration: none; }

/* ⛔ `.nav-links a` outranks `.chip-primary` on specificity, so the waitlist chip in the nav once
 * painted textSecondary onto the gold, at 1.15:1. It looked like an empty gold slab, and it was
 * caught by screenshotting the page rather than by reading the stylesheet. */
.nav-links a.chip-primary,
.nav-links a.chip-primary:hover { color: var(--on-primary); }
/* ⛔ `:not(.chip-primary)`, and the `:not` is the whole point. Written as a bare `.nav-links a.chip`
 * this rule has the SAME specificity as the two above and sits after them, so it won them and
 * repainted the gold waitlist chip in body text: 2.96:1 in light, 1.55:1 in dark. That is the same
 * defect as 12 August, in the same place, introduced by the line written to fix it. Caught again by
 * recomputing contrast from the rendered DOM, and again invisible in the stylesheet. */
.nav-links a.chip:not(.chip-primary) { color: var(--text); }

/* ── The language control ─────────────────────────────────────────────────────
 * Tally's ruling: obvious, not a subtle chip. The word "Language" is spelled out beside the current
 * choice, in the current language, so it can never be mistaken for a menu item.
 *
 * ⭐ Built on <details>, so it opens with no JavaScript and each entry is a plain <a> to the same
 * page in that language. It can be middle-clicked, copied, and read by anything.
 * ⛔ No language CODES anywhere a reader looks. The links go to /hindi/ and /tamil/, and every label
 * writes the language out in full.
 */
.langpick { position: relative; }
.langpick > summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  list-style: none;
  cursor: pointer;
  /* WD-10. Height comes from --tool-h, not from padding, so it cannot drift from the theme
   * button's. The padding here is inline only. */
  min-height: var(--tool-h);
  padding: 0 14px;
  border-radius: var(--radius);
  border: 1px solid var(--text-muted);
  background: var(--surface);
  color: var(--text);
  font-size: 0.92rem;
  font-weight: 600;
  white-space: nowrap;
}
.langpick > summary::-webkit-details-marker { display: none; }
.langpick > summary .lp-label { color: var(--text-secondary); font-weight: 500; }
.langpick > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--primary);
  border-bottom: 2px solid var(--primary);
  transform: rotate(45deg) translate(-2px, -2px);
  flex: none;
}
.langpick[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.langpick > summary:hover { border-color: var(--primary); }

.lp-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  min-width: 232px;
  list-style: none;
  margin: 0;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--card-shadow);
  z-index: 60;
}
.lp-menu a {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 11px 14px;
  border-radius: 10px;
  color: var(--text);
  font-size: 1rem;
}
.lp-menu a:hover { background: var(--raised); text-decoration: none; }
.lp-menu .lp-endonym { font-weight: 600; }
.lp-menu .lp-english { color: var(--text-muted); font-size: 0.86rem; }
/* ⭐ The current language is MARKED, not merely highlighted, so it survives being read in
 * greyscale. A drawn tick, because no face this repo ships has a glyph for one. */
.lp-menu a[aria-current="true"] { background: var(--raised); }
.lp-menu a[aria-current="true"]::after {
  content: "";
  margin-left: auto;
  width: 12px;
  height: 6px;
  border-left: 2px solid var(--primary);
  border-bottom: 2px solid var(--primary);
  transform: rotate(-45deg);
  flex: none;
  align-self: center;
}

/* ── The theme toggle ─────────────────────────────────────────────────────────
 * ⭐ Hidden until the page says it has JavaScript. With scripting off there is no button and the
 * page simply follows the phone, which is the right fallback rather than a dead control.
 */
.theme-btn { display: none; }
.js .theme-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: var(--tool-h);
  height: var(--tool-h);
  padding: 0;
  flex: none;
  border-radius: var(--radius);
  border: 1px solid var(--text-muted);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}
.theme-btn:hover { border-color: var(--primary); }
.theme-btn svg { width: 19px; height: 19px; display: block; }
.theme-btn .ic-moon { display: none; }
/* The button shows the state it will move TO, which is the one an icon-only control can express. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-btn .ic-sun { display: none; }
  :root:not([data-theme="light"]) .theme-btn .ic-moon { display: block; }
}
:root[data-theme="dark"] .theme-btn .ic-sun { display: none; }
:root[data-theme="dark"] .theme-btn .ic-moon { display: block; }
:root[data-theme="light"] .theme-btn .ic-sun { display: block; }
:root[data-theme="light"] .theme-btn .ic-moon { display: none; }

/* ⭐ CSS-ONLY drawer. Tally reads these pages by sending the file to himself on his phone, and that
 * viewer runs no JavaScript at all. A JavaScript menu is a dead menu there. */
#navck, .nav-toggle, .nav-scrim { display: none; }

@media (max-width: 900px) {
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--tool-h);
    height: var(--tool-h);
    flex: none;
    border-radius: var(--radius);
    border: 1px solid var(--text-muted);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
  }
  /* ⛔ Three drawn bars, not the U+2630 character. No face this project ships has a glyph for it,
   * so the character version borrowed whatever the operating system offered, at whatever weight. */
  .nav-toggle i { display: block; width: 17px; height: 2px; background: currentColor; position: relative; }
  .nav-toggle i::before, .nav-toggle i::after {
    content: "";
    position: absolute;
    left: 0;
    width: 17px;
    height: 2px;
    background: currentColor;
  }
  .nav-toggle i::before { top: -5px; }
  .nav-toggle i::after  { top: 5px; }

  /* ⛔ CLOSED MEANS `display: none`, NOT PARKED OFF-SCREEN.
   *
   * This panel used to sit at `translateX(105%)` when closed, and a phone-width measurement showed
   * the document was 705px wide inside a 390px window: every section could be dragged sideways to
   * reveal the hidden menu. `overflow-x: clip` on <html> did NOT fix it, because a `position: fixed`
   * element is laid out against the viewport and an ancestor cannot clip it. Taking it out of
   * layout is the only thing that worked, which is why there is no slide. Re-measured, not reasoned
   * about. */
  .nav-links {
    display: none;
    position: fixed;
    inset: 62px 0 auto auto;
    width: min(86vw, 330px);
    max-height: calc(100dvh - 62px);
    overflow-y: auto;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--surface);
    border-left: 1px solid var(--border-strong);
    border-bottom: 1px solid var(--border-strong);
    border-radius: 0 0 0 var(--radius-lg);
    padding: 12px 0 16px;
    z-index: 45;
    box-shadow: var(--card-shadow);
  }
  .nav-links > li { display: block; border-top: 1px solid var(--border); }
  /* ⛔ `> li >`, not a descendant selector. Written loosely, this rule also caught the three links
   * INSIDE the language menu and turned each one from a flex row into a block: the `gap` went, so
   * the endonym and the English name ran together as "हिन्दीHindi", and the drawn tick on the
   * current language uses `margin-left: auto`, which does nothing in a block, so the marking
   * disappeared exactly where it matters most. Both were invisible until the drawer was opened in
   * a screenshot. */
  .nav-links > li > a, .nav-links > li > .chip { display: block; padding: 14px 20px; font-size: 1rem; }
  .nav-links > li > .chip { margin: 12px 16px 4px; text-align: center; padding: 12px 18px; }
  #navck:checked ~ .nav-links { display: flex; }
  #navck:checked ~ .nav-scrim {
    display: block;
    position: fixed;
    inset: 62px 0 0;
    background: color-mix(in srgb, var(--bg) 72%, transparent);
    z-index: 44;
  }

  /* ⭐ THE LANGUAGE CONTROL AND THE THEME TOGGLE SIT AT THE TOP OF THE DRAWER, not buried under the
   * section links and not desktop-only. They are the first row a thumb reaches. */
  .nav-links > li.nav-tools {
    order: -1;
    border-top: 0;
    display: flex;
    /* Top-aligned, because the language control grows tall when its menu is open and a centred
     * theme button then floats halfway down beside it, unattached to anything.
     * ⚠️ The `gap` that used to be here has gone up to the base rule. It was the only reason WD-9
     * never showed on a phone, and one gap in one place cannot disagree with itself. */
    align-items: flex-start;
    padding: 4px 16px 14px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px;
  }
  .nav-tools .langpick { flex: 1 1 auto; min-width: 0; }
  /* Full width in the drawer, but the HEIGHT still comes from --tool-h, so the two controls line up
   * here for the same reason they line up on the desktop bar. */
  .nav-tools .langpick > summary { width: 100%; padding: 0 14px; }
  .lp-menu { position: static; width: 100%; min-width: 0; margin-top: 8px; box-shadow: none; }
}

/* ── Hero ─────────────────────────────────────────────────────────────────── */

.hero { padding-block: clamp(46px, 8vw, 92px) var(--section-gap); border-top: 0; }
.hero-grid {
  display: grid;
  gap: clamp(34px, 5vw, 58px);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 940px) {
  /* ⚠️ `stretch`, not `center`. Tally, 12 August: the caption under the phone had to come down and
   * finish level with the glass panel opposite. Both columns are now the same height, the caption is
   * pushed to the floor of its own column, and the two ends of the hero line up. */
  .hero-grid { grid-template-columns: minmax(0, 0.92fr) minmax(0, 1fr); align-items: stretch; }
  .hero-grid > .glass { align-self: stretch; }
  .card-stage .proof-line { margin-top: auto; }
}
.hero h1 { margin-bottom: 0.42em; }
.hero-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }
.hero-note { font-size: 0.9rem; color: var(--note-ink); margin-top: 14px; }

/* ── The live quote card ──────────────────────────────────────────────────── */

/* ⚠️ ONE COLUMN, ONE WIDTH, ONE AXIS. The chips were flush left, the phone was centred by its own
 * `margin-inline: auto` and the line underneath ran the full width of the column, so three things
 * that belong together lined up with nothing. Tally, 12 August: "it is not even matching left or
 * right". The stage sets the width, the chips and the caption inherit it, and all three share a
 * centre line. */
.card-stage {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.card-stage > * {
  width: 100%;
  max-width: var(--phone-w);
}

.scripts { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; justify-content: center; }
.scripts .chip { padding: 8px 15px; font-size: 0.92rem; }

/* The hero's phone. Real captures, one per language, stacked and crossfaded.
 *
 * ⛔ THE RATIO LIVES ON THE STAGE, NOT ON THE IMAGE. Tally's complaint on 12 August was movement:
 * the four text cards it replaced were four different heights, so the right-hand column jumped on
 * every switch and again on every auto-advance. The stage is 720/1564 whatever is inside it, and
 * every frame is absolutely positioned within it, so the box cannot resize even if a future capture
 * comes back at the wrong size.
 *
 * ⚠️ `aspect-ratio` INHERITS, and it broke the lead photograph on this page once already. It is set
 * here on the stage and reset to `auto` on the frames for exactly that reason. */
/* ⭐ A DEVICE, not a rounded rectangle. Tally, 12 August: put the screenshots in an iPhone. The
 * captures already carry the status bar and the Dynamic Island, so what was missing was the body
 * around them: a titanium rail, the tapered corner radius, and the four buttons. Drawn in CSS rather
 * than dropped in as a picture — the iOS kits hold components and tokens, no device art, and a PNG
 * bezel would have to be redrawn for every width anyway.
 *
 * ⛔ THE RAIL IS A TOKEN, and it has to be. A dark titanium body on the paper theme reads instantly,
 * and the SAME body on the night theme vanishes: black metal, on a black page, behind a black
 * screenshot. Tally saw no phone at all in dark. So the metal brightens in dark the way a real phone
 * catches light in a dark room. It is still a NEUTRAL in both, never a theme colour. */
.phone-stage {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  padding: 13px;
  border: 0;
  border-radius: 58px;
  background: var(--rail);
  box-shadow: var(--rail-edge), 0 30px 70px -30px rgba(0, 0, 0, 0.5);
}
/* ⛔ THE SCREEN OWNS THE RATIO, NOT THE BODY.
 *
 * The body carried `aspect-ratio` until 12 August, which is only correct at one width: the rail is a
 * fixed 13px while everything else scales, so at any other size the screen no longer matched the
 * capture and the screenshot ended up SEVEN PIXELS TALLER than the phone around it. Tally: "the size
 * of the screenshot is different than the size of the iphone". Now the screen is 720/1564 at every
 * width and the body is simply whatever wraps it. */
.phone-screen {
  position: relative;
  aspect-ratio: 720 / 1564;
  border-radius: 45px;
  overflow: hidden;
  background: #080807;
  /* the black rim between the metal and the glass */
  box-shadow: 0 0 0 3px #080807;
}
.phone-stage::before,
.phone-stage::after {
  content: "";
  position: absolute;
  width: 4px;
  border-radius: 2px;
  background: var(--rail-button);
  pointer-events: none;
}
.phone-stage::before {
  left: -3px;
  top: 16%;
  height: 20%;
  /* one strip drawn as three buttons: action, then the two volume keys */
  background:
    var(--rail-button) 0 0 / 100% 22% no-repeat,
    var(--rail-button) 0 42% / 100% 26% no-repeat,
    var(--rail-button) 0 100% / 100% 26% no-repeat;
}
.phone-stage::after { right: -3px; top: 25%; height: 15%; }
/* ⚠️ TWO CLASSES, deliberately. `.phone` caps itself at 300px for the gallery rail further down the
 * page and is declared LATER in this file, so a single-class rule here loses to it and the frame
 * sits 16px narrower than the chips above it. */
/* ⚠️ TWO CLASSES for all of it. `.phone` is declared LATER in this file (it dresses the gallery rail
 * further down) and would otherwise repaint this one in `--raised`, which in the light palette is
 * plain white: the titanium body vanished and the frame read as a white border. */
.card-stage .phone-stage {
  max-width: var(--phone-w);
  /* ⚠️ A FLOOR under the gap. The caption is pushed to the bottom of the column by `margin-top:auto`,
   * which gives a generous gap only while the column has slack — widen the phone and the slack goes
   * to zero and the caption touches the tab bar again, which is the complaint that started this. */
  margin-bottom: 30px;
  box-sizing: border-box;
  /* ⚠️ The rail's thickness, and it is 13px rather than 10 for a reason: on the paper theme a thin
   * rail over a pale ground read as a hairline border and Tally could not see a phone at all. A
   * real device has a visible body. `.phone` sets 9px for the gallery and wins on order without
   * this line. */
  padding: 13px;
  border-radius: 58px;
  /* Titanium: a dark body with two bright edges where the light catches it. Neutral in both
   * palettes, like the real thing, and never a theme colour. */
  background: linear-gradient(150deg, #8c877e 0%, #2b2825 14%, #191715 46%, #24211e 74%, #7d786f 100%);
  border: 0;
  box-shadow:
    inset 0 0 0 1.5px rgba(255, 255, 255, 0.22),
    0 30px 70px -30px rgba(0, 0, 0, 0.55);
}
/* ⚠️ `inset` is the bezel, not zero. Absolute positioning is measured from the PADDING box, so
 * `inset: 0` painted every frame straight over `.phone`'s 9px rim and the device frame vanished. */
.phone-stage .shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  object-fit: cover;
  opacity: 0;
  transition: opacity 520ms ease;
}
/* ⭐ `is-on` is written into the MARKUP for the first language's pair. With scripting off nothing
 * ever adds the class, and a hero whose only content is eight invisible images is a blank column. */
.phone-stage .shot.is-on { opacity: 1; }

/* ⛔ THE PALETTE PICKS THE FRAME, AND IT TAKES ALL THREE THEME STATES like every other colour here.
 * Tally, 12 August: a dark phone on the paper theme is wrong, so each language ships both. The
 * unused twin is `display: none` AND `opacity: 0` on purpose — the checker counts a lit frame by
 * computed opacity, and a hidden element still reports whatever opacity it was given. */
/* ⚠️ Light is the BARE `:root` on this page, the same way every colour above is: a browser with no
 * stated preference gets paper, so the paper frame is the one that needs no rule to be chosen. */
.phone-stage .shot-dark { display: none; opacity: 0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .phone-stage .shot-dark { display: block; }
  :root:not([data-theme="light"]) .phone-stage .shot-dark.is-on { opacity: 1; }
  :root:not([data-theme="light"]) .phone-stage .shot-light { display: none; opacity: 0; }
}
:root[data-theme="dark"] .phone-stage .shot-dark { display: block; }
:root[data-theme="dark"] .phone-stage .shot-dark.is-on { opacity: 1; }
:root[data-theme="dark"] .phone-stage .shot-light { display: none; opacity: 0; }
:root[data-theme="light"] .phone-stage .shot-dark { display: none; opacity: 0; }
:root[data-theme="light"] .phone-stage .shot-light { display: block; }
:root[data-theme="light"] .phone-stage .shot-light.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .phone-stage .shot { transition: none; } }

/* The stack. The cards behind the current one stay visible, so tapping a second script leaves two
 * visibly DIFFERENT verses on screen at once. A card that merely swapped would leave the "one verse
 * translated four times" assumption exactly where it was, which is what this section argues with. */
.stack { position: relative; isolation: isolate; }
.stack::before, .stack::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface);
  z-index: -1;
}
.stack::before { transform: translate(10px, 10px); opacity: 0.55; }
.stack::after  { transform: translate(20px, 20px); opacity: 0.28; }

.qcard {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: clamp(22px, 3.4vw, 32px);
  box-shadow: var(--card-shadow);
}
.qcard + .qcard { margin-top: 18px; }

/* ⭐ The quote never sets below 22px, and Indic never below weight 500. Both come straight out of
 * the app, where they were arrived at on a device rather than in a browser. */
.verse {
  font-family: var(--serif);
  font-size: clamp(22px, 2.4vw, 26px);
  line-height: 1.5;
  font-weight: 500;
  color: var(--text);
  margin: 0 0 14px;
  text-wrap: pretty;
}
.verse[lang="ta"] { font-family: var(--tamil); font-weight: 500; line-height: 1.72; }
.verse[lang="hi"], .verse[lang="sa"] { font-family: var(--deva); font-weight: 500; line-height: 1.68; }

/* ⛔ The transliterations stay in Source Sans 3 and must not be moved. Source Serif 4 has no `ṁ`,
 * and `ṁ` appears in most Hindi transliterations. `subset_fonts.py` checks this on every run. */
.translit {
  font-family: "Source Sans 3", system-ui, sans-serif;
  font-size: 0.94rem;
  font-style: italic;
  color: var(--text-muted);
  margin: 0 0 14px;
}
.gloss-en {
  font-family: var(--serif);
  font-size: 1.02rem;
  color: var(--text-secondary);
  margin: 0 0 16px;
}
.byline { font-size: 0.9rem; color: var(--text-secondary); margin: 0; }
.byline .cite { color: var(--text-muted); }

/* ⚠️ THE TAKEN-APART CARD NEEDS A MEASURE. At 1440px it ran the full width of the page and set
 * about 140 characters to the line, which is roughly twice what anyone reads comfortably, and it
 * also finished a good 300px to the right of the Pro card directly beneath it — two blocks in the
 * same section with two different right edges. Both are fixed by capping it at the same 46rem the
 * Pro card already uses. */
#verse .qcard { max-width: 46rem; }

/* ── WD-13 · the exploded card, and the same card whole beside it ─────────────
 *
 * ⚠️ THE TWO COLUMNS DO NOT NATURALLY AGREE ON HEIGHT, and that is what drives this whole layout.
 * The phone column is about 480px wide, so a card stretched to its full length is roughly 2,000px
 * tall against a ~900px column of text. Matching them by shrinking the phone puts the card's OWN
 * text at about 9px, which on a page whose argument is "here is what we wrote under the verse" is
 * self-defeating.
 *
 * ⭐ So the seven rows are SPREAD DOWN THE PHONE'S HEIGHT instead, each label landing level with the
 * band of the screen it names. The text becomes annotation on a tall picture, and the picture stops
 * looking lopsided. Tally: "Add both for now, and then maybe we can decide later" — so the labels
 * are a whole column and can be deleted later without any of this being rebuilt.
 *
 * ⛔ `has-phone` is written by the build from the same test that decides whether the phone renders.
 * Never make this `:has(.verse-phone)`: the card must lay out identically whether or not a capture
 * happens to be on disk, and it has to be verifiable from the HTML alone. */
.verse-grid { display: grid; gap: clamp(28px, 4vw, 48px); }
@media (min-width: 940px) {
  .verse-grid.has-phone {
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--phone-w));
    align-items: stretch;
  }
  /* ⛔ The label column loses its reading-width cap here ONLY because it is now half the row. */
  .verse-grid.has-phone .qcard {
    max-width: none;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }
  /* Each row takes an equal share of the phone's height and centres its own words in it, which is
   * what makes a label sit level with the part of the screen it is describing. */
  .verse-grid.has-phone .layer {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}
/* ⭐ WD-13 ON A PHONE, ruled 12 August: THE PICTURE FIRST, then the seven rows. The reader meets the
 * real card and then the labels explaining it, which is the order the section argues in. Below the
 * two-column width the grid is one column, and `order` puts the photograph above the text without
 * changing the source order — so a screen reader and `index.md` still get the words in their own
 * order, and dropping the labels later (Tally: "maybe we can decide later") is still one deletion. */
@media (max-width: 939px) {
  .verse-grid.has-phone .verse-phone { order: -1; }
  .verse-grid.has-phone .phone { max-width: 100%; }
}
.verse-phone .phone { max-width: var(--phone-w); }
/* ⛔ NO HEIGHT AND NO CROP. The capture is a whole card, top to bottom, and Tally ruled that nothing
 * is cut off and there is no fade at the bottom. So the image takes its natural aspect and the
 * column grows to it. */
.verse-phone img { width: 100%; height: auto; }
.layer { border-top: 1px solid var(--border); margin-top: 16px; padding-top: 14px; }
.layer-label {
  /* Not tappable, so not gold. Two golds in one viewport and gold stops reading as an instruction. */
  font-size: 0.72rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-muted);
  font-weight: 700;
  margin: 0 0 6px;
}
html[lang="hi"] .layer-label,
html[lang="ta"] .layer-label { text-transform: none; letter-spacing: 0.03em; }
.layer p { margin: 0; font-size: 0.98rem; color: var(--text-secondary); }

/* The proof row: all four sources visible AT ONCE, the active one lit. A rotation alone could never
 * make the point, because a rotation is a claim and this is a comparison. */
.sources {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(122px, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  margin-top: 18px;
}
.sources div {
  background: var(--bg);
  padding: 11px 13px;
  font-size: 0.8rem;
  color: var(--text-muted);
  transition: color 160ms ease, background 160ms ease;
}
.sources div strong { display: block; font-weight: 600; font-size: 0.86rem; color: var(--text-secondary); }
.sources div[data-active="true"] { background: var(--surface); color: var(--text-secondary); }
.sources div[data-active="true"] strong { color: var(--primary); }

/* ⚠️ The phone's own width, and a real gap above it. At full width it ran wider than the frame on
 * both sides and sat hard against the tab bar, so it read as part of the screenshot rather than as a
 * line about it. Tally, 12 August: line it up, and stop it touching. */
.proof-line {
  margin: 28px auto 0;
  font-size: 0.96rem;
  color: var(--text-secondary);
}

/* ── Bharometer's feature grid, with the pointer-following spotlight ──────── */

.feat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 960px) { .feat-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .feat-grid { grid-template-columns: 1fr; } }

.feat-card {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 26px 24px;
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.feat-card::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.35s;
  pointer-events: none;
  background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 50%), var(--spotlight), transparent 65%);
}
.feat-card:hover { transform: translateY(-4px); border-color: var(--border-strong); }
.feat-card:hover::before { opacity: 1; }
.feat-card .idx {
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.6rem;
  color: var(--text-muted);
  margin-bottom: 12px;
}
.feat-card h3 { margin-bottom: 0.4em; }
.feat-card p { font-size: 0.97rem; color: var(--text-secondary); margin: 0; }

/* ⭐ WD-12. The screenshot under the words. The card roughly doubles in height, which was ruled and
 * accepted: the point of the section is that these three claims are visibly true, and a card that
 * only asserts them is the thing Tally objected to.
 *
 * ⛔ The cards must stay the SAME height as each other, so the grid stretches them and the shot is
 * pinned to the bottom with `margin-top: auto`. Without that the three photographs at the top still
 * line up but the three screens below do not, and a row of phones at three different heights is more
 * obviously wrong than no phones at all. */
.feat-card { display: flex; flex-direction: column; }
/* ⛔ The GAP lives on the paragraph, not on the phone, and that is not a style preference. `.phone`
 * is a metal rail drawn with `padding`, so a `padding-top` here would open a gap INSIDE the bezel and
 * push the screen off-centre in its own frame. `margin-top: auto` on the phone does the pinning and
 * cannot also be the spacing, since one property cannot hold both. */
.feat-card > p:not(.idx) { margin-bottom: 22px; }
.feat-card .feat-shot {
  margin-top: auto;
  max-width: 190px;
  /* The rail is smaller here than in the hero, and the radii come down with it or the corners of
   * the metal and the glass stop being concentric. */
  border-radius: 30px;
  padding: 7px;
}
.feat-card .feat-shot img { width: 100%; height: auto; border-radius: 23px; }

/* ── Bharometer's numbered India card, accent stripe down the left edge ───── */

/* ⚠️ TWO COLUMNS, NOT FOUR, since WD-14. The four blocks were one line apiece and sat happily in a
 * row of four; Tally ruled that this section is under-weighted and that the emphasis has to come out
 * of the content itself, so each one now names its real texts in two or three sentences. Four
 * columns of that is a column of about 22 characters, which is a newspaper classified, not a
 * paragraph. */
.india-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
@media (max-width: 720px) { .india-grid { grid-template-columns: 1fr; } }

.india-card {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 24px 22px 22px 26px;
  background: var(--surface);
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.india-card::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 5px;
  background: linear-gradient(180deg, var(--primary), var(--primary-deep));
  border-radius: 0 3px 3px 0;
}
.india-card:hover { transform: translateY(-4px); border-color: var(--border-strong); }
.india-card .script {
  font-size: 1.5rem;
  line-height: 1.4;
  color: var(--text);
  margin: 0 0 2px;
  font-family: var(--serif);
  font-weight: 600;
}
.india-card .script[lang="ta"] { font-family: var(--tamil); }
.india-card .script[lang="hi"], .india-card .script[lang="sa"] { font-family: var(--deva); }
.india-card .named { font-size: 0.82rem; color: var(--text-muted); margin: 0 0 12px; letter-spacing: 0.06em; text-transform: uppercase; }
html[lang="hi"] .india-card .named,
html[lang="ta"] .india-card .named { text-transform: none; letter-spacing: 0.02em; }
.india-card p:last-child { font-size: 0.95rem; color: var(--text-secondary); margin: 0; }

/* ── The sample verses ────────────────────────────────────────────────────────
 * Six cards, and the column count is FIXED at each width rather than left to `auto-fit`, so the
 * grid is always 3x2, 2x3 or 6x1 and never a full row with one card stranded underneath it.
 * `align-items: stretch` is the default and is wanted here: the cards are wildly different heights
 * in four scripts, and equal-height rows are the whole point of the tidier grid.
 */
.samples-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-top: 30px;
}
@media (max-width: 980px) { .samples-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .samples-grid { grid-template-columns: 1fr; } }
.samples-grid .qcard { display: flex; flex-direction: column; }
/* The byline sits on the floor of the card rather than under the last line of prose, so six cards
 * of six different lengths still line their attributions up along the bottom of each row. */
.samples-grid .qcard .byline { margin-top: auto; padding-top: 14px; }

/* ── WD-15 · the same grid holding six PHONES ─────────────────────────────────
 * Tally: "I don't want text boxes or text cards. I want full iPhone screenshots."
 *
 * ⛔ NOTHING IS PRINTED UNDER THEM. No caption, no byline, no name of the language: everything is
 * inside the shot, which is why the verses were picked short enough to fit one screen without
 * scrolling. `.shot-caption` is deliberately not used here.
 *
 * ⚠️ The section therefore contains NO TEXT AT ALL, and the `alt` on every frame carries the verse
 * and who wrote it. Delete an alt here and the section becomes literally nothing to a screen reader,
 * a search engine or a model. `index.md` still writes four of the verses out in full for the same
 * reason.
 *
 * ⭐ Two frames per card, one per palette, exactly as the hero does it — the CSS picks, not the
 * script, so a reader on the paper theme is never shown a phone in the night theme. The three-state
 * dance is required: bare :root is light, the media query is guarded against an explicit light
 * choice, and :root[data-theme=…] wins over both. */
.samples-grid.is-phones { gap: clamp(18px, 2.4vw, 30px); align-items: start; }
.samples-grid.is-phones .phone { max-width: 260px; }
.samples-grid.is-phones .phone img { width: 100%; height: auto; }
/* ⭐ `.s-shot` IS SHARED, and deliberately so. WD-15's phones, WD-12's three feature-card screens
 * and WD-13's tall card all carry it, because on 12 August Tally ruled both palettes EVERYWHERE
 * after a capture showed three night phones sitting on the cream page. One class, one pair of rules,
 * so a fourth place that needs it cannot get a fifth copy of this logic that drifts.
 *
 * ⛔ The BARE rule is the light one, because bare `:root` is the light palette on this site. Writing
 * the dark case as the default would show a night phone to anyone whose browser expresses no
 * preference at all, which is the exact fault the hero ruling exists to prevent. */
.s-shot .shot-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .s-shot .shot-dark { display: block; }
  :root:not([data-theme="light"]) .s-shot .shot-light { display: none; }
}
:root[data-theme="dark"] .s-shot .shot-dark { display: block; }
:root[data-theme="dark"] .s-shot .shot-light { display: none; }
:root[data-theme="light"] .s-shot .shot-dark { display: none; }
:root[data-theme="light"] .s-shot .shot-light { display: block; }

/* ── Generic cards and grids ──────────────────────────────────────────────── */

.grid {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
  margin-top: 30px;
}
.box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px 22px;
}
.box h3 { margin-bottom: 0.35em; }
.box p { color: var(--text-secondary); font-size: 0.97rem; }
.box p:last-child { margin-bottom: 0; }

/* ── The Pro line. One honest sentence, no price grid. ────────────────────── */

.pro-line {
  margin-top: 30px;
  padding: 20px 22px;
  border: 1px solid var(--border-strong);
  border-left: 4px solid var(--primary);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-secondary);
  max-width: 46rem;
}
.pro-line p { margin: 0; }
.pro-line strong { color: var(--text); font-weight: 600; }

/* ── The other apps ───────────────────────────────────────────────────────── */

/* `auto-fit`, not a fixed column count. Only the apps that are actually out go in this section, so
 * the number of cards changes when one ships and a hard `repeat(3, 1fr)` would leave a hole. */
.apps-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  max-width: 780px;
}

.app-card {
  display: block;
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 22px 20px;
  background: var(--surface);
  color: var(--text);
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.app-card:hover { transform: translateY(-4px); border-color: var(--border-strong); text-decoration: none; }
/* The real App Store mark, at its own aspect, with the iOS corner. `alt=""` on purpose: the app's
 * name is the very next line, so an alt text would make a screen reader say it twice. */
.app-icon {
  width: 56px;
  height: 56px;
  border-radius: 13px;
  border: 1px solid var(--border);
  flex: none;
}
/* ⭐ WD-18. The icon and the name now sit on ONE LINE, which buys back the height the screenshot
 * costs, and the screenshot goes under the text. ⛔ ONE per app, not two or three: this block is a
 * footnote at the bottom of somebody else's page and it stays one. */
.app-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.app-head h3 { margin: 0; }
.app-shot {
  display: block;
  width: 100%;
  max-width: 168px;
  height: auto;
  margin-top: 16px;
  border-radius: 14px;
  border: 1px solid var(--border);
}
/* ⛔ QUIETER THAN THE BADGE ABOVE IT, on purpose. Tally's ruling is that the download badge stands
 * alone and ends the page's argument, and that these two sit below it in a strip near the footer
 * where nothing competes with it. So the strip loses its own section rule and sits closer to the
 * colophon than to the App Store block. */
.apps-strip { padding-top: clamp(34px, 5vw, 56px); border-top: 0; }
.apps-strip .sec-head { margin-bottom: clamp(20px, 3vw, 30px); }
.app-card h3 { margin-bottom: 0.3em; font-size: 1.1rem; }
.app-card p { font-size: 0.93rem; color: var(--text-secondary); margin: 0 0 10px; }
.app-card .app-meta { font-size: 0.8rem; color: var(--text-muted); margin: 0; }

/* ── Phone frames and the drifting gallery rail ───────────────────────────── */

/* ⭐ ONE DEVICE, USED TWICE. The gallery rail further down the page carried a pale rounded
 * rectangle while the hero carried a phone, so the same product appeared in two different objects on
 * one page. Same rail, same bezel, same tokens, smaller. */
.phone {
  border: 0;
  border-radius: 44px;
  padding: 10px;
  background: var(--rail);
  max-width: 300px;
  margin-inline: auto;
  box-shadow: var(--rail-edge), 0 24px 56px -28px rgba(0, 0, 0, 0.5);
}
.phone img {
  display: block;
  border-radius: 34px;
  /* the black rim between the metal and the glass, as in the hero */
  box-sizing: border-box;
  border: 2px solid #080807;
}
/* ⚠️ `--note-ink`, not `--text-muted`. These captions arrived on 12 August and the very first
 * measurement put the light theme at 4.48:1 against a 4.5 floor — two hundredths short, on 2% of one
 * caption's box. That is the third time this exact colour has come up short on paper: the hero note
 * and the section eyebrows both did the same thing earlier the same day. `--note-ink` is the token
 * that already resolves to a darker ink in light and the lighter one in dark. */
.shot-caption {
  font-size: 0.87rem;
  color: var(--note-ink);
  text-align: center;
  margin: 12px auto 0;
  max-width: 30ch;
}

.gallery { overflow: hidden; }
.gallery-scroll {
  display: flex;
  gap: 20px;
  overflow-x: auto;
  padding: 14px 4px 26px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.gallery-scroll::-webkit-scrollbar { display: none; }
.g-shot { flex: none; width: 218px; }
.g-shot:nth-child(odd) .phone { transform: rotate(-1.1deg); }
.g-shot:nth-child(even) .phone { transform: rotate(1.1deg); }
.g-shot .phone { transition: transform 0.3s ease; }
.g-shot:hover .phone { transform: translateY(-8px) rotate(0deg); }
.g-shot .phone img { width: 100%; height: auto; }

/* ⭐ WD-16. The rail now holds FIVE GROUNDS rather than a tour of six screens. One per subject —
 * ocean, road, jungle, mountain, rain — because the 24 ground files are only five places, and one
 * apiece means no two phones are the same place twice.
 *
 * ⭐ NO SECOND PALETTE HERE, unlike WD-15's phones. A ground is a photograph and carries its own
 * background into both themes, so a light and a dark capture of one would be the same picture.
 *
 * ⛔ WD-21. THEY DID NOT FIT, AND CENTRING MADE THAT UNRECOVERABLE. Five 232px frames plus four
 * 20px gaps is 1240px against the 1040px the wrap actually offers at 1120 — so the rail overflowed,
 * and `justify-content: center` on a scrolling flex row pushes the overflow out of BOTH ends, where
 * the left half cannot be reached by any scroll in any browser. The first ground could not be seen
 * whole at any width; at 390 the section opened centred on the THIRD phone with two behind the
 * reader and no way back to them.
 *
 * ⚠️ It was invisible in the numbers. The build reports which captures are missing, not whether the
 * ones that arrived fit, and the contrast checker cannot see inside an image. Only the photograph of
 * the section showed it — the first phone sliced down its middle at the section edge.
 *
 * So: packed from the left everywhere, which is the only arrangement whose overflow is scrollable,
 * and centred at 188px only from 1120 up, where all five genuinely fit (5x188 + 80 = 1020 <= 1040)
 * and there is no overflow left to strand. */
.grounds-row { justify-content: flex-start; flex-wrap: nowrap; }
.grounds-row .g-shot { width: 232px; }

@media (min-width: 1120px) {
  .grounds-row { justify-content: center; }
  .grounds-row .g-shot { width: 188px; }
}

/* ── Liquid glass ─────────────────────────────────────────────────────────────
 * The app's own material, in CSS. `ios/GyaanDaily/Design/Glass.swift` is a blur plus a theme tint
 * washed over it at 0.18, a continuous corner radius, and a soft popup shadow; this is the same
 * idea with `backdrop-filter` doing the blur.
 *
 * ⛔ IT IS LOad-BEARING, NOT DECORATION. The band photographs run at FULL strength now, on Tally's
 * call of 12 August, and a word sitting straight on one measured 1.01:1 against a 3.0 floor. Every
 * run of text over a photograph sits on this panel, and the panel is what the contrast checker
 * measures. Thinning the tint past the values below fails the page.
 *
 * ⚠️ THE FALLBACK IS NOT COSMETIC EITHER. Where `backdrop-filter` is unsupported the tint alone is
 * all that stands between the text and the picture, so it goes nearly opaque there. A translucent
 * panel with no blur behind it is exactly the state that measured 1.01:1.
 */
.glass {
  background: var(--glass-tint);
  -webkit-backdrop-filter: blur(26px) saturate(1.25);
  backdrop-filter: blur(26px) saturate(1.25);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius-lg);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.18);
  padding: clamp(22px, 3.4vw, 34px);
}

/* No blur available: the tint carries the contrast on its own. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background: var(--glass-solid); }
}

/* The hero's picture already fades at its foot, and a panel with a hard edge sitting on that read as
 * two unrelated rectangles. */
.hero .glass { padding-inline: clamp(22px, 3vw, 30px); }

/* One line of text needs a line's worth of panel, not a section's. */
.glass-inline { padding: 12px 18px; border-radius: var(--radius); display: inline-block; }

/* The photograph at the head of the screens section. Wider than the reading column, because it is
   the only picture on the page that shows the product in a real place and it earns the room. */
/* ⚠️ `.art.screens-lead`, not `.screens-lead`. `.art img` further down sets `max-height: 380px` with
   `object-fit: cover`, and at equal specificity the later rule wins, so a plain `.screens-lead img`
   would have been silently cropped to a 380px letterbox. The cap is undone explicitly here. */
.art.screens-lead { margin: 0 0 clamp(28px, 4vw, 44px); }
/* ⛔ `aspect-ratio: auto` is the important line. `.art img` above forces 2.15/1 so the two small
 * plates crop consistently, and this image inherited it — with `object-fit: fill` on top, which
 * STRETCHED the photograph sideways to fit rather than cropping it. Tally spotted the distortion.
 * The lead photograph is shown whole, at its own proportions, and is cropped by nothing. */
.art.screens-lead img { width: 100%; height: auto; aspect-ratio: auto; max-height: none;
                        object-fit: contain; border-radius: var(--radius-lg); display: block; }

/* ── Photographic section bands ───────────────────────────────────────────────
 * A section with `class="band"` and `--band: url(...)` gets that photograph behind everything in
 * it, under a wash heavy enough that the picture reads as light and texture rather than as a
 * subject. Two pseudo-elements rather than one: the image on `::before`, the wash on `::after`, so
 * the wash is a flat colour rather than a `background-blend-mode`, which behaves differently across
 * browsers and cannot be reasoned about.
 *
 * ⭐ ONE IMAGE, TWO THEMES. `--band-wash` is a token, so the same photograph sits under a near-black
 * wash in dark and a warm paper wash in light. Generating a light variant of every picture would
 * have doubled the cost and let the two versions drift apart in feel.
 *
 * ⛔ EVERY BAND IS RE-MEASURED AGAINST REAL PIXELS. `getComputedStyle().backgroundColor` is blind to
 * a background image, so the contrast checker could not see any of this. It now paints the page's
 * text transparent, screenshots that, and reads the worst actual pixel behind every word. Without
 * that pass this entire block would be unverified, and would have reported clean.
 */
/* ⚠️ 0.96, not 0.94, and the two hundredths were measured rather than chosen. At 0.94 the lit patch
 * of the plaster wall came through under the hero's small print at 4.48:1, against a 4.5 floor. Paper
 * is a less forgiving ground than near-black: the same photograph under the dark wash clears easily
 * at 0.93, because a dark picture on a dark wash barely moves the luminance at all.
 *
 * ⭐ THE TWO THEMES ARE NOT SYMMETRICAL, and the gap is much wider than 0.96 against 0.93 suggests.
 * Tally, 12 August: the band photographs were invisible. They were. At 0.93 in dark, a dark
 * photograph under a near-black wash contributes almost nothing and the hero rendered as a flat
 * field. The checker was then run against a deliberately thin wash to find out what a VISIBLE
 * picture actually costs, and the answer differed completely by theme:
 *
 *   dark  at 0.80 / 0.74 -> 15 pages x 3 languages, ZERO contrast failures. Free.
 *   light at 0.88 / 0.84 -> 15 failures, all of them p.hero-note and p.eyebrow, 3.95:1 to 4.29:1.
 *
 * So dark is set to the measured 0.80 / 0.74 and light stays where it was. Light cannot follow
 * without either strengthening those two small-text colours or scrimming them locally, which is a
 * design decision rather than a number, and it is open. ⛔ Do not lower the light wash on the
 * assumption that what passed in dark passes here. It does not. */
:root { --band-filter: none; --band-opacity: 0.85;
        --band-wash: linear-gradient(rgba(246, 241, 231, 0), rgba(246, 241, 231, 0)); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --band-filter: none; --band-opacity: 0.85;
                                     --band-wash: linear-gradient(rgba(12, 12, 11, 0), rgba(12, 12, 11, 0)); }
}
:root[data-theme="dark"] { --band-filter: none; --band-opacity: 0.85;
                           --band-wash: linear-gradient(rgba(12, 12, 11, 0), rgba(12, 12, 11, 0)); }

.band { position: relative; isolation: isolate; }
.band::after { content: ""; position: absolute; inset: 0; pointer-events: none; }

/* ⭐ THE PHOTOGRAPH IS AN <img> NOW, NOT A BACKGROUND, and the reason is weight rather than taste.
 * A CSS background is NEVER lazy-loaded — the browser fetches it as soon as the rule matches — so
 * all five of these, about 760 KB of photograph, were downloaded before a reader had scrolled past
 * the hero. `loading="lazy"` only exists for elements, so the picture had to become one.
 *
 * ⛔ It keeps `aria-hidden` and an empty `alt`: it is the ground behind a section, and every one of
 * these sections says in words what it is about. A screen reader announcing "dawn through a jali
 * screen" before the App Store block would be describing the wallpaper.
 *
 * ⚠️ `z-index: -2` and the section's `isolation: isolate` are what keep it behind the text and
 * inside its own section. Do not remove either: without the isolation this paints behind the PAGE,
 * and the wash at `::after` stops working as a wash.
 * ⚠️ It is `position: absolute; inset: 0` with `object-fit: cover`, which is the exact behaviour of
 * `background-size: cover; background-position: center` it replaced — the framing of all five
 * photographs is unchanged, and the contrast readings that were taken over them still stand. */
.band-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  z-index: -2;
  filter: var(--band-filter);
  /* ⭐ Tally, 12 August: 85% in both palettes. The photographs ran at full strength from that
   * morning, which was a correction to a 0.93 wash that had flattened them into a field of colour;
   * this is the settling point between the two. It composites against the theme's own ground, so
   * one number serves both palettes and neither can be forgotten when the other changes.
   * ⚠️ It moves every contrast reading on the page. Re-run verify_site.py after touching it. */
  opacity: var(--band-opacity);
}
.band::after { z-index: -1; background: var(--band-wash); }

/* The hero's band fades out at the bottom so the next section does not begin on a hard seam.
 * ⚠️ This mask used to sit on `.band::before`, which no longer carries the picture. It moved with
 * the photograph rather than being deleted with the element it happened to live on. */
.hero.band .band-img { -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent);
                       mask-image: linear-gradient(to bottom, #000 55%, transparent); }

/* The three plates that head the feature cards. Real <img> elements rather than backgrounds: they
 * are content, they carry no text over them, and an <img> is what lazy-loading understands. */
.feat-card .plate {
  /* ⛔ `max-width: none`, or the global `img { max-width: 100% }` quietly caps this at the CONTENT
   * width and the photograph stops 50px short of the card's right edge — which is what it has been
   * doing since the plates were added. A negative margin cannot pull an image wider than a cap it
   * cannot see. Caught by looking at a phone screenshot, not by any check. */
  max-width: none;
  width: calc(100% + 48px);
  margin: -26px -24px 20px;
  height: 128px;
  object-fit: cover;
  border-radius: 17px 17px 0 0;
  display: block;
}

/* ── Generated art plates ─────────────────────────────────────────────────────
 * ⚠️ Both stills are dark images on a page that now has a light mode. They are framed as deliberate
 * dark plates that keep their own background in both themes, the way a photograph does in a printed
 * book. Generating light variants would double the cost and tends to look washed out.
 */
.art {
  margin: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: #0C0C0B;
}
/* ⚠️ Cropped rather than left at its natural height. At full height the plate was taller than the
 * four language cards above it put together and read as the subject of the section rather than as
 * its illustration.
 *
 * ⛔ BUT A FLAT `max-height` CROPPED BY A THIRD AND CUT THE SUBJECT IN HALF. `max-height: 380px` on a
 * 1038px-wide column is a 2.73:1 window taken out of a 1.79:1 photograph, centred, so the bottom of
 * the inkpot was simply gone — and only above about 700px wide, because narrower columns never reach
 * the cap. That is the worst kind of fault: correct on the phone it was checked on, broken on the
 * desktop it was not. Tally caught it.
 *
 * So the window is an ASPECT RATIO, not a pixel height: it crops the same amount at every width, and
 * 2.15:1 against the picture's own 1.79:1 is a trim rather than an amputation. `object-position` then
 * biases what survives towards the bottom, because in both plates the subject sits low in the frame
 * and the top is empty air. ⚠️ Re-check both plates in a WIDE browser after touching either number.
 */
.art img { width: 100%; height: auto; aspect-ratio: 2.15 / 1; max-height: none;
           object-fit: cover; object-position: center 78%; display: block; }
.art figcaption {
  padding: 12px 16px;
  font-size: 0.86rem;
  color: var(--text-muted);
  background: var(--surface);
  border-top: 1px solid var(--border);
}

/* ── Word-by-word gloss row ───────────────────────────────────────────────── */

.gloss-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.gloss-word {
  background: var(--raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 9px 13px;
  min-width: 84px;
}
.gloss-word .src { font-size: 1.02rem; color: var(--text); display: block; }
.gloss-word .src[lang="ta"] { font-family: var(--tamil); }
.gloss-word .src[lang="hi"], .gloss-word .src[lang="sa"] { font-family: var(--deva); }
.gloss-word .tr { font-family: "Source Sans 3", system-ui, sans-serif; font-size: 0.78rem; color: var(--text-muted); font-style: italic; display: block; }
.gloss-word .sn { font-size: 0.84rem; color: var(--text-secondary); display: block; margin-top: 2px; }

/* ── The App Store block ──────────────────────────────────────────────────────
 *
 * ⭐ WD-17. Everything that was here — the email field, the submit chip, the honeypot, the status
 * line, and the `input` rules that dressed them — went with the waitlist on 12 August. The site now
 * has NO FORM ANYWHERE, which is also why the privacy page can say the site collects nothing without
 * an exception clause after it.
 *
 * ⛔ ONE badge. Native iOS only, so a Play badge would be a link to nothing.
 * ⛔ Apple's artwork is used UNMODIFIED, at its own aspect. Black on the paper palette, white on the
 * night one, which is Apple's own rule and not a preference: a black badge on the night ground would
 * be a rectangle with a hole in it. Both files are in the markup and the stylesheet picks between
 * them, so the right one shows with no script running.
 */
/* Inside the glass panel, under the lede, so no word and no badge sits on the photograph. */
.get-app { display: flex; justify-content: flex-start; margin-top: 26px; }
.store-badge { display: inline-block; line-height: 0; }
.store-badge img { height: 56px; width: auto; display: block; }
/* Bare :root is the light palette, so the black badge is the default one. */
.store-badge .badge-white { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .store-badge .badge-black { display: none; }
  :root:not([data-theme="light"]) .store-badge .badge-white { display: block; }
}
:root[data-theme="dark"] .store-badge .badge-black { display: none; }
:root[data-theme="dark"] .store-badge .badge-white { display: block; }
:root[data-theme="light"] .store-badge .badge-black { display: block; }
:root[data-theme="light"] .store-badge .badge-white { display: none; }
/* ⚠️ Drawn but not linked, because there is no App Store id yet. Dimmed and not a pointer so it does
 * not read as a button that has stopped working, with the reason said in words underneath. */
.store-badge.is-waiting { opacity: 0.55; filter: grayscale(1); cursor: default; }
.badge-note { margin-top: 14px; font-size: 0.9rem; color: var(--note-ink); }

/* ── Colophon and footer ──────────────────────────────────────────────────── */

.colophon {
  border-top: 1px solid var(--border);
  padding-block: 28px;
  font-size: 0.9rem;
  color: var(--text-muted);
}

.footer {
  border-top: 1px solid var(--border);
  padding-block: 44px 52px;
  background: var(--surface);
}
.foot-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr 1fr;
  gap: 30px;
  margin-bottom: 34px;
}
/* ⚠️ Two columns of UNEQUAL length leave craters. At 860px the four blocks pair up as a 2x2 grid
 * whose rows are as tall as their tallest cell, so a three-link column beside a five-link one opens
 * a 120px hole under it, twice. On a phone the same list reads perfectly well in one column and the
 * craters go with it. */
@media (max-width: 860px) { .foot-grid { grid-template-columns: 1fr 1fr; align-items: start; } }
@media (max-width: 560px) { .foot-grid { grid-template-columns: 1fr; gap: 26px; } }
.foot-brand p { color: var(--text-muted); font-size: 0.9rem; margin-top: 12px; max-width: 30em; }
.foot-col h4 {
  font-family: inherit;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 12px;
}
html[lang="hi"] .foot-col h4,
html[lang="ta"] .foot-col h4 { text-transform: none; letter-spacing: 0.03em; }
.foot-col a { display: block; color: var(--text-secondary); font-size: 0.93rem; padding: 5px 0; }
.foot-col a:hover { color: var(--text); }
.foot-bottom {
  border-top: 1px solid var(--border);
  padding-top: 22px;
  display: flex;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  color: var(--text-muted);
  font-size: 0.86rem;
}

/* ── Prose pages (privacy, terms, support) ────────────────────────────────── */

.doc { padding-block: clamp(44px, 6vw, 74px); }
.doc h2 { font-size: clamp(1.32rem, 2.9vw, 1.66rem); margin-top: 2.1em; }
.doc h2:first-of-type { margin-top: 1.3em; }
.doc p, .doc li { color: var(--text-secondary); max-width: var(--measure); }
.doc ul { padding-left: 1.15em; }
.doc li { margin-bottom: 0.5em; }
.doc .updated { font-size: 0.9rem; color: var(--text-muted); }
.doc strong { color: var(--text); font-weight: 600; }

/* ── Motion ───────────────────────────────────────────────────────────────── */

.fade-in { animation: rise 420ms ease both; }
@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  .js .reveal { opacity: 1 !important; transform: none !important; }
}

/* ── No JavaScript ────────────────────────────────────────────────────────────
 * All four hero verses are in the HTML, not fetched. With scripting off they stack and stay
 * readable and indexable; only the chip interactivity is lost. */
.js .qcard[hidden] { display: none; }
.no-js .scripts { display: none; }


