/* Yajña Today — the landing page.
   ────────────────────────────────────────────────────────────────────────────────────────────
   The theme tokens are lifted verbatim from the app's own src/style.css, which draws them from
   branding/yajna-logo-kit/BRAND.md, so the site and the app are one palette rather than two
   that nearly match. Dark is the default (:root) because dark is the app's default; light is an
   override on [data-theme="light"], set by the inline script in <head> and by the toggle.

   Do not retune a value here on its own. If one moves, it moves in the app first and this file
   follows — the whole point is that a visitor who downloads the app recognises the screen.

   The layout half below is Saṅkalpa Today's landing page, which is the closest sibling: one
   static column, a sticky bar, an auto-fit grid. Four blocks are this page's own and each says
   why where it stands: the two-audience `.side` cards (no sister site has two audiences), the
   `.legend` swatches (which have to be the app's calendar colours, not near-misses), the
   `.reasons` list under Privacy, and the whole `.fb-*` form (neither sister site posts anything
   from the page at all).

   ── The trap this family keeps re-learning ──────────────────────────────────────────────────
   **A class that sets `display` silently defeats the `hidden` attribute**, because a class-level
   declaration outranks the UA's `[hidden] { display: none }` rule. Every class here that sets
   `display` and is also hidden by attribute therefore carries its own `[hidden]` companion.
   `.dl-version` is the one that was actually wrong: both sister sites declare
   `display: inline-block` on it with no re-assert, so an empty version chip is a bordered pill
   with nothing in it whenever `VERSION` is unset. Fixed here — see the note at that rule. */
:root {
  color-scheme: dark; /* native controls (the select's glyph, scrollbars) follow the theme */

  /* The app's dark ramp: night #2F4665 warmed and taken down with the warm-black ink #2A1F17,
     so the ramp gets warmer as it gets deeper — the right direction for an app whose subject is
     a fire. BRAND.md reserves #22314A and #1A2538 for the brand sheet's own chrome and says in
     as many words that nothing shipped may use them, which is why this ramp is not Saṅkalpa's. */
  --bg: #1D2839;
  --surface: #263449;
  --surface-2: #2F4665;      /* the brand's night exactly — the ground the mark is drawn on */
  --border: #3C5273;         /* night lightened; a rule, not a highlight */
  --text: #F2E9D8;           /* the family cream */
  --muted: #C3B8A6;          /* the cream desaturated to a warm grey */

  /* The fire is the accent, and it changes with the ground because a single value cannot serve
     both: #F98601 is unusable as text on cream (2.06:1) and #682E0D is unusable on night
     (1.40:1). So the light theme moves to the ember below — the same fire, lower down.

     **Both hexes outlived the artwork they were named from, and that is on purpose.** They were
     quotations from a drawing two redraws back, whose flame was the flat #F98601 and whose ember
     role was #682E0D. Nothing since carries either: the mark is tonally modelled, its fire is the
     gold family (#e07329 / #fd9617 / #fdb22d, with #f43304 at the arch tips and #fed449 as the
     flame's highest highlight) and its browns are the hair and skin families, and BRAND.md's
     palette contains neither of these two. They stay anyway, because they are UI values in the
     family's hue rather than samples of the mark, and because the app's own src/style.css still
     declares exactly these two — this page follows the app, not the kit. If the app ever
     re-derives its accent from the new gold family, this file follows it there and not before.
     The ratios are consequently measured here rather than read out of BRAND.md, which no longer
     states them: 5.96:1 for --accent on --bg, 8.78:1 for the light theme's on cream.

     No retune for this page beyond that. The sister sites both shift --accent off the app's
     value because the app's accent fails as *link text* (My Sādhana's #7C5CFF is 4.31:1 /
     3.97:1), and that is the only sanctioned reason to diverge. Here the numbers are already
     good, so the app's values are used unchanged. */
  --accent: #F98601;         /* the fire, at a value that reads on the dark ramp */
  --accent-ink: #2A1F17;     /* what may be written on an --accent fill; 6.46:1. Never white */

  /* The calendar's two verdict colours. They are here because the legend in the When section
     shows them, and they are the app's own values so the swatch and the cell match. Neither is
     ever text: they are fills with var(--text) written over them. */
  --good: #7CBB88;           /* auspicious */
  --bad: #F2A0AE;            /* inauspicious. A rose rather than a red, so it cannot be
                                confused with the orange accent above it */

  /* Card and toast shadows. Warm-black rather than neutral black, because a neutral shadow
     greys cream paper. */
  --shadow-ink: rgba(20, 14, 9, .55);
  --radius: 14px;

  /* The wordmark's face. BRAND.md pins Palatino for the outlined lockups and forbids a display
     or script face, so the live-text wordmark is set in the same family, with the metric
     compatible clones next in line for Linux and Georgia as the last resort that still carries
     the ñ. Also used for the headings, as Saṅkalpa's does. */
  --serif: "Palatino Linotype", Palatino, "Book Antiqua", "URW Palladio L", P052,
           Georgia, "Noto Serif", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* This page's own two, and they are layout rather than colour. The bar is 60px here against
     the app's 52px: the app's is a phone chrome bar over a full-height view, this one carries
     seven nav links and a brand wordmark on a desktop page. */
  --topbar-h: 60px;
  --page: 1120px;            /* content column; the bands are centred inside it */
}
[data-theme="light"] {
  color-scheme: light;
  --bg: #F2E9D8;             /* cream — the family ground */
  --surface: #FBF7EF;        /* the kit's off-white. Never pure #FFF. It used to be named here
                                as "the line role"; the redraw has no `line` role, and #FBF7EF is
                                now the declared substitute for the puṇḍras' pure #fff — same
                                value, same rule, different thing in the drawing */
  --surface-2: #EADFC9;      /* the paper mid-tone, shared with Saṅkalpa Today by rule */
  --border: #DCCBA9;         /* the kit's tile edge, used as the page's rule */
  --text: #2A1F17;           /* the warm black. Never pure #000 */
  --muted: #6B6152;
  /* The same fire as the accent above, low enough to read on paper. See the note there: this is
     the old drawing's ember value kept as a UI token, not a colour sampled from the mark. */
  --accent: #682E0D;
  --accent-ink: #F2E9D8;
  --good: #2F6B41; --bad: #A6273E;
  --shadow-ink: rgba(42, 31, 23, .16);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; }
/* Anchored sections must not slide under the sticky bar. */
:target, section[id] { scroll-margin-top: calc(var(--topbar-h) + 12px); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg); color: var(--text);
  font-family: var(--sans);
  font-size: 16px; line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Theme-aware scrollbars (the default track is white, which glares in dark mode). */
* { scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--surface-2); border: 3px solid var(--bg); border-radius: 8px; }

a { color: var(--accent); }
a:hover { text-decoration: none; }
h1, h2, h3 { line-height: 1.25; }
img { max-width: 100%; }

/* Visually hidden but readable by assistive tech. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 60;
  background: var(--accent); color: var(--accent-ink);
  padding: 10px 16px; border-radius: 0 0 10px 10px; font-weight: 600; text-decoration: none;
}
.skip-link:focus { top: 0; }

/* Keyboard focus, once, in one place — every control below inherits it. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* ── The brand ───────────────────────────────────────── */
/* The mark plus the name as live text, and `--mark` is the one number a slot sets: the gap and
   both type sizes follow from it. Copied from the app's own stylesheet so the two match.

   **The floor is 87 px and it is not negotiable.** It has survived two redraws unchanged, but
   what it is protecting changed again, so the numbers here are the current drawing's — one
   purohita facing a household of four across the fire. BRAND.md renders the committed vector at
   every size and looks: at 87 px the mark is 78 × 48 px of its tile and everything still reads,
   and that is "the number to plan around"; by 60 px each ūrdhva-puṇḍra has dropped to a single
   tick and the children's faces to dots, though four heads on the right are still four; by
   48 px the children merge into the adults and the household reads as a mass of about three;
   by 40 px it is a cluster you can tell is more than one person and cannot count. It is a
   tableau of small objects, not a monogram.

   **And this mark is paler again than the one it replaces, which makes the floor matter more
   rather than less.** The drawing is contained rather than cropped — the owner's instruction,
   that the whole scene be visible in the icon — so 45% of the tile is ground and BRAND.md
   measures **6.21% dark ink at 87 px against the previous cut's 14.74%**, under half, with the
   tile 36.3% covered where it was 54.1%. What improved is the *shape* of the decay: dark-ink
   share is flat across six doublings (retention 109%, against 73% for the cut it replaces),
   because the 0.55-unit contour is sub-pixel below 473 px and holds the silhouette's weight
   constant all the way down. It reads smaller everywhere; it does not collapse anywhere. So
   `--mark` is 112px in the hero and 96px on a phone, and it never follows Saṅkalpa's 84/64
   ramp, whose mark holds its ink flat and can.

   The two lockup files in assets/ are consequently not placed on this page at all, and that is
   the same rule applied one step further: BRAND.md's floor for the horizontal lockup is 520px
   and for the stacked one 320px — set by the tagline inside them, not by the mark — and this
   page has no slot that is unconditionally that wide on a 360px phone. Its instruction for
   exactly that case is "below 520 px use the mark alone", which is what the hero does. */
.brand {
  --mark: 40px;
  display: flex; align-items: center; gap: calc(var(--mark) * .37);
  text-decoration: none; color: inherit;
}
/* NOT square, and `--mark` is a WIDTH. The cuts are `yajna-mark-plain-*` — the artwork with no
   ground and the box cropped to it, 234.502 × 144.069, aspect 1.6277 — so pinning both
   dimensions to one number would squash the drawing to 68% of its height.

   `aspect-ratio` rather than `height: auto`, and the markup still pins both attributes, because
   these files are ~256 kB of path data and a row that reflows when the mark lands is what the
   pinned pair existed to prevent.

   **This row gains far less from the crop than the app's column does, and that is worth knowing
   before copying a number across.** The tiled cut's dead margins were 13.299 units at the sides
   against 63.196 under the art (`SCENE_BASE` seats the vedī at 0.7569 of the tile height). The
   app stacks its wordmark UNDER the mark, so it was eating the big one — 48 px of apparent gap
   at a 96 px slot against the 25 px asked for. Here the wordmark sits BESIDE the mark, so the
   crop recovers only the 5.1% side margin: about 6 px at this size. The gap below is therefore
   left alone rather than retuned to match the app's. */
.brand-mark {
  width: var(--mark); height: auto; aspect-ratio: 234.502 / 144.069;
  display: block; flex: none;
}
/* Theme-matched art: the night cut is the default, light swaps in the cream one.
   **The night cut *is* the cream one on a dark tile**, and that reverses what once stood here.
   An early drawing had to lighten its ember role to #C79A6E on night, because ember on night is
   1.10:1 and the figures would not have been there at all. The rule since is "only the tile
   changes": every object keeps the colour it was drawn in, and BRAND.md walks all 76 colour
   fields demanding equality. It holds because the four shapes that own the ink box's edges
   measure 8.23, 5.30, 5.08 and 2.59 to one on night, so the silhouette is the most legible
   thing on the tile — and because the values that *do* lose contrast there (the mālā and the
   necklaces, six of them under 1.3:1) are all interior: removing the whole hair role costs
   97 px of a 2048px silhouette, 0.010%. The one declared exception is the puṇḍras, which go
   #FBF7EF → #F2ECDF so five small marks do not read as light leaks. What is genuinely lost is
   the vedī's modelling — on cream three stepped courses, on night one mass with a lit top edge.

   **Both files are still needed even though neither carries a tile any more**, and the reason is
   no longer the ground — it is the puṇḍras above and the contour. The light cut carries #351505
   at 0.55 units because `cloth-4`/`cloth-5` measure 1.03:1 against cream and the figure would
   have no outer edge without it; the night cut carries none, because the same boundary sweep
   gives 4.5% under 1.50:1 there against 25.8% on cream. Two cuts, two contour rules, one swap. */
.brand-mark-light { display: none; }
[data-theme="light"] .brand-mark-dark { display: none; }
[data-theme="light"] .brand-mark-light { display: block; }

/* Live text, not artwork: assistive tech reads "Yajña Today", the ñ cannot be lost to a
   rasteriser, and the name is selectable — which is what My Sādhana's all-artwork lockup gives
   up. The caps are a `text-transform` for the same reason: the DOM keeps the real spelling.
   Serif, regular weight, with the tracking build_brand.py gives the kit's own wordmarks.
   `display: block` because this is a <p> in the hero and a <span> inside the header's link. */
.brand-words {
  display: block; margin: 0; font-family: var(--serif); font-weight: 400;
  font-size: max(15px, calc(var(--mark) * .235)); line-height: 1.1;
}
/* Tracking adds its space after the last letter too; the negative margin takes that back. */
.brand-name { display: block; text-transform: uppercase; letter-spacing: .16em; margin-right: -.16em; }
/* The one place gold appears on this page. BRAND.md allows #D69A2C as a typographic accent and
   nothing else — it is 2.05:1 on cream, so it can never be body copy — and a wordmark's second
   line is precisely that use. Written as a literal rather than tokenised so it cannot spread to
   anything a reader has to act on. */
.brand-sub {
  display: block; text-transform: uppercase; letter-spacing: .26em; margin-right: -.26em;
  font-size: max(11px, .62em); color: #D69A2C;
}
/* The bar carries the wordmark alone: 87px of mark will not fit a 60px row, and every lockup cut
   has a floor wider than the slot. `--mark` here sizes the type and nothing else — the same
   trick, and the same number, as the app's `.topbar-words`.

   ONE ROW, and at ONE SIZE, which is what the app's bar does and is why both are stated here
   rather than inherited. Everywhere the wordmark sits under artwork — this page's hero, the
   app's drawer, splash and gates — `.brand-name` and `.brand-sub` stack and the sub is `.62em`,
   a second line subordinate to the name above it. In a bar there is no second line: two runs on
   one baseline at two sizes read as a name with something appended rather than as one wordmark,
   and two stacked lines spend the whole 60px on type. Side by side at one size they are one
   object.

   `align-items: baseline`, not `center`: a wordmark set on two optical centres is the thing that
   looks accidental. `font-size: inherit` rather than a literal so it tracks `.brand-words`' own
   max(15px, --mark * .235) and cannot drift from the run beside it.

   The tracking stays UNEQUAL — .26em against the name's .16em. With the size drop gone that
   difference, plus the gold, is what keeps TODAY subordinate; equalising both would give two
   identical runs and lose the hierarchy. The negative right margins are doing more work here
   than stacked: without them the gap would be .42em plus YAJÑA's trailing .16em, and the row
   would sit left of centre by half of TODAY's trailing .26em. */
.brand-row { --mark: 68px; }
.brand-row .brand-words {
  display: flex; align-items: baseline; gap: .42em;
}
.brand-row .brand-name, .brand-row .brand-sub { display: inline; }
.brand-row .brand-sub { font-size: inherit; }

/* ── Top bar ─────────────────────────────────────────── */
#topbar {
  position: sticky; top: 0; z-index: 50;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: 16px;
  padding: 0 max(16px, calc((100% - var(--page)) / 2));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
/* `color-mix` is recent enough to want a floor: an opaque bar is a fine outcome without it. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  #topbar { background: var(--bg); }
}
.topnav { display: flex; gap: 4px; margin-left: auto; }
.topnav a {
  color: var(--muted); text-decoration: none; font-size: 14px;
  padding: 8px 12px; border-radius: 10px; white-space: nowrap;
}
.topnav a:hover { background: var(--surface-2); color: var(--text); }
.topbar-controls { display: flex; align-items: center; gap: 8px; }

/* ── The language picker ─────────────────────────────── */
.lang { position: relative; }
/* Flag only when closed, which is why this is a hand-built listbox and not a <select>. */
.lang-btn { font-size: 17px; line-height: 1; }
.lang-flag { line-height: 1; }
.lang-list {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 60;
  margin: 0; padding: 5px; list-style: none;
  min-width: 12.5em;
  /* One option today and twenty-two when the translations land; the list scrolls rather than
     running off a short window. */
  max-height: min(70vh, 460px); overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 10px 30px var(--shadow-ink);
}
/* `hidden` is how the picker is closed, and `position: absolute` alone would not defeat it —
   but this rule is kept from the sister sites anyway, because a later `display` on this class
   would, and silently. */
.lang-list[hidden] { display: none; }
.lang-opt {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 8px;
  font-size: 14px; cursor: pointer;
}
.lang-opt:hover { background: var(--surface-2); }
/* The endonyms will be in twelve scripts, several with tall ascenders and descenders; a fixed
   line-height keeps every row the same height so the list does not look ragged. */
.lang-opt-flag { font-size: 16px; line-height: 1.4; flex: none; }
.lang-opt-name { line-height: 1.4; }
.lang-opt[aria-selected="true"] { color: var(--accent); font-weight: 600; }
.icon-btn {
  width: 36px; height: 36px; display: grid; place-items: center;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px; cursor: pointer;
}
.icon-btn:hover { border-color: var(--accent); color: var(--accent); }
/* The hamburger. Absent above the breakpoint, where the nav is a row and there is nothing to
   disclose — `display: none` rather than visually hidden, so it is out of the tab order too. */
.nav-toggle { display: none; }
.nav-ico::before { content: '☰'; font-size: 17px; line-height: 1; }
/* Turns into a close glyph while the panel is open, so the button says what pressing it does
   now rather than what it did a moment ago. */
.nav-toggle[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
.nav-toggle[aria-expanded="true"] .nav-ico::before { content: '✕'; font-size: 15px; }
/* One glyph, two states: the sun is what dark mode offers to leave, the moon what light does.
   Content, not an <img>, so it inherits `color` and needs no second asset. */
.theme-ico::before { content: '☀'; font-size: 17px; line-height: 1; }
[data-theme="light"] .theme-ico::before { content: '☾'; }

/* ── Layout ──────────────────────────────────────────── */
#main { display: block; }
.band, .hero, #foot {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 16px;
}
.band { padding-top: 56px; }
.band:last-child { padding-bottom: 56px; }
.section-title {
  margin: 0 0 6px;
  font-family: var(--serif); font-weight: 400; font-size: 26px;
}
.band-hint { margin: 0 0 16px; }
.section-title + .card, .band-hint + .card, .band-hint + .grid { margin-top: 16px; }
/* A hint that closes a band rather than opening it — the one-role-per-account note under the
   two side cards. Same type, opposite margins. */
.band-hint-after { margin: 16px 0 0; }

.card {
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 18px 20px;
  box-shadow: 0 1px 2px var(--shadow-ink);
}
.prose p { margin: 0 0 12px; }
.prose p:last-child { margin-bottom: 0; }
/* A subheading inside a card of running prose — the colour legend, and the Crypto block. Needs
   air above it and almost none below. */
.prose h3 { margin: 22px 0 8px; font-size: 17px; font-weight: 600; }
.prose > h3:first-child { margin-top: 0; }
.hint { color: var(--muted); font-size: 14px; }
.muted { color: var(--muted); }

.grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

/* ── Hero ────────────────────────────────────────────── */
/* One column. The mark, the headline, the lede, three facts and the two buttons — nothing to
   set beside them, so there is no grid here and the measure is held by .lede's own max-width
   rather than by a column. */
.hero { padding-top: 64px; padding-bottom: 8px; }
/* The largest the mark gets. Side by side with the wordmark rather than stacked, because the
   hero copy below it ranges left and a centred lockup over ragged-left text reads as a mistake. */
.hero-brand { --mark: 112px; margin-bottom: 20px; }
.hero h1 {
  margin: 0 0 14px;
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(30px, 5vw, 44px);
}
.lede { margin: 0 0 20px; font-size: 17px; color: var(--text); max-width: 46em; }

/* Three plain facts, not a feature list. Note what is not among them: this app is not
   end-to-end encrypted and must never carry a badge saying otherwise. */
.badges { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 24px; padding: 0; }
.badges li {
  font-size: 13px; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; padding: 5px 12px;
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 10px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 20px; border-radius: 12px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border);
  font-family: inherit; font-size: 15px; font-weight: 600;
  text-decoration: none; cursor: pointer;
}
.btn:hover { border-color: var(--accent); }
.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.btn.primary:hover { filter: brightness(1.06); }
/* A channel that has no URL yet. Rendered as a <div>, so there is nothing to click and nothing
   to focus — "coming soon" is information, not a control. Never given .primary either: a filled
   flame button is the page's strongest call to action and must not point nowhere. */
.btn.soon { opacity: .6; cursor: default; gap: 8px; }
.btn.soon:hover { border-color: var(--border); }
.btn-tag { font-size: 12px; font-weight: 400; color: var(--accent); white-space: nowrap; }
/* A button mid-request. Not `.soon`: that state means "there is nothing here", and this one
   means "there is, and it is in flight". */
.btn:disabled { opacity: .6; cursor: default; }
.btn:disabled:hover { border-color: var(--border); filter: none; }

/* ── Features ────────────────────────────────────────── */
.feat h3 { margin: 0 0 8px; font-size: 16px; display: flex; align-items: baseline; gap: 8px; }
.feat-ico { color: var(--accent); font-size: 15px; line-height: 1; flex: none; }
.feat p { margin: 0 0 10px; font-size: 15px; }
.feat p:last-child { margin-bottom: 0; }

/* ── Both sides ──────────────────────────────────────── */
/* Two cards, one per audience — the block neither sister site needed. A wider `minmax` than
   .grid's 280px because each card holds five numbered sentences rather than one: at 280px the
   steps wrap to four lines each and the two columns stop reading as two paths.
   360px × 2 + 16px gap = 736px, so they fall to one column below that, which is where the
   sentences would start to break anyway. */
.grid-sides { grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); }
.side h3 { margin: 0 0 12px; font-size: 16px; }
/* The Sanskrit role name: a proper noun, so it carries no i18n key and is written into the
   markup. Set in the serif with the wordmark's tracking, at the weight of a heading. */
.side-name {
  display: block; font-family: var(--serif); font-weight: 400; font-size: 20px;
  letter-spacing: .04em;
}
/* The gloss under it, which does translate. --muted and small, because it explains the word
   above rather than competing with it. */
.side-role { display: block; font-size: 13px; font-weight: 400; color: var(--muted); }

/* Numbered because the order is the argument: a purohita cannot accept anything before an
   administrator has approved them. A counter rather than the list's own markers so the number
   can take --accent and sit outside the text block, which is what keeps five long steps
   readable as steps. */
.steps { list-style: none; counter-reset: step; margin: 0; padding: 0; display: grid; gap: 10px; }
.steps li {
  counter-increment: step;
  position: relative; padding-left: 30px; font-size: 15px;
}
.steps li::before {
  content: counter(step) '.';
  position: absolute; left: 2px; top: 0;
  color: var(--accent); font-weight: 600; font-variant-numeric: tabular-nums;
}

/* ── The colour legend ───────────────────────────────── */
/* Set off from the prose the way the Crypto block is: a rule above it, because it is a key to a
   screen rather than another paragraph about one. */
.legend { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
.legend-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 10px; }
.legend-list li { display: flex; align-items: flex-start; gap: 12px; font-size: 15px; }
/* A cell, at the size the calendar draws one, so the swatch reads as the thing it explains. The
   border is the calendar's own cell edge; without it the unmarked swatch would be invisible
   against the card. */
.legend-swatch {
  flex: none; width: 34px; height: 34px; border-radius: 8px;
  background: var(--surface-2); border: 1px solid var(--border);
}
/* The app's three verdict tints, arrived at the same way js/02-calendar.js arrives at them:
   `color-mix` onto --surface-2 rather than a table of six hard-coded colours, which is what lets
   one declaration work in both themes and keeps a day number legible over the fill. Do not
   replace these with hexes — they would then be a fifth and sixth place the palette lives. */
.swatch-auspicious { background: color-mix(in srgb, var(--good) 55%, var(--surface-2)); }
.swatch-inauspicious { background: color-mix(in srgb, var(--bad) 55%, var(--surface-2)); }
/* A day that holds both kinds of window. The app splits the cell at the boundary *minute*, so
   the gradient's stop is wherever the tithi turns; a legend has no minute to point at, so it
   splits at the middle and the caption says what the line means. Hard stops, not a blend: a
   gradient that fades between the two would read as a third state. */
.swatch-both {
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--good) 55%, var(--surface-2)) 0 50%,
    color-mix(in srgb, var(--bad) 55%, var(--surface-2)) 50% 100%);
}
/* No colour at all, which is the honest answer for a day the source is silent about. It is the
   bare cell surface, and it is here as a swatch precisely so "blank" reads as a state rather
   than as a rendering failure. */
.swatch-unmarked { background: var(--surface-2); }
/* Without color-mix the mix collapses to the token itself: stronger than intended, still the
   right colour, and still three distinguishable states. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .swatch-auspicious { background: var(--good); }
  .swatch-inauspicious { background: var(--bad); }
  .swatch-both { background: linear-gradient(to bottom, var(--good) 0 50%, var(--bad) 50% 100%); }
}

/* ── Privacy ─────────────────────────────────────────── */
/* The sentence that has to be read before any of the reasoning that softens it. Larger and in
   full --text rather than --muted, which is the only typographic emphasis on this page that is
   there to make something harder to skip. */
.privacy-plain { font-size: 17px; color: var(--text); }
/* The four features that are readable-by-the-server by construction. A list rather than four
   paragraphs, because the point is that it is an enumeration and a closed one — these four and
   no more. Dashes rather than ticks: a tick reads as a feature won, and each of these is a
   thing given up. */
.reasons { margin: 12px 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.reasons li { position: relative; padding-left: 22px; font-size: 15px; }
.reasons li::before {
  content: '—'; position: absolute; left: 0; top: 0; color: var(--accent);
}

/* ── Downloads ───────────────────────────────────────── */
/* The version the cards below lead to. A chip rather than another line of prose: it is a fact
   about the files, not a sentence about them, and the band already opens with a hint in --muted
   that it would otherwise disappear into. `inline-block` so the border wraps the words and not
   the column. */
.dl-version {
  display: inline-block;
  margin: 0 0 16px;
  padding: 3px 11px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface);
  font-size: 13px; color: var(--muted);
  /* The number is the payload, and it is read against the last one the visitor saw. Tabular
     figures keep 0.1.0 and 0.10.0 from shifting the digits around under it. */
  font-variant-numeric: tabular-nums;
}
/* The rule both sister sites are missing, and the reason the file's header names it. The chip
   ships `hidden` and stays that way while VERSION is empty — but `display: inline-block` above
   outranks the UA's `[hidden] { display: none }`, so without this line the empty state is a
   bordered pill with nothing in it, sitting above the grid. tools/smoke.mjs asserts the
   attribute, which a DOM stub can see and this rule's absence would not have shown it. */
.dl-version[hidden] { display: none; }
/* Wide enough for a 64-character checksum to wrap in two lines rather than four. */
.grid-dl { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
/* The card owns the border; the link and the checksum row inside it are borderless panels
   divided by a rule, so the two read as one object. */
.dl-card {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden;
}
.dl-card:not(.soon):hover { border-color: var(--accent); }
.dl {
  display: flex; align-items: center; gap: 12px;
  color: var(--text);
  padding: 14px 16px; text-decoration: none;
  flex: 1;  /* so a card with no checksum row still matches the height of one that has */
}
.dl-ico { font-size: 22px; line-height: 1; flex: none; width: 26px; text-align: center; }
.dl-body { min-width: 0; }
.dl-name { display: block; font-weight: 600; font-size: 15px; }
.dl-hint { display: block; font-size: 13px; color: var(--muted); }
/* A channel with no URL yet. Rendered as a <div>, so there is nothing to click and nothing to
   focus — "coming soon" is information, not a control. Dimmed per-part rather than on the whole
   card, because a row can carry a published checksum before its download URL exists. */
.dl.soon { opacity: .6; cursor: default; }
.dl.soon .dl-hint { color: var(--accent); }

/* The checksum row. What makes an unsigned build checkable, so it is given a line of its own
   rather than being folded into the hint. */
.dl-sum {
  display: flex; align-items: baseline; gap: 8px;
  width: 100%; padding: 9px 16px;
  background: var(--bg); color: var(--text);
  border: 0; border-top: 1px solid var(--border);
  font-family: inherit; font-size: 11px; text-align: left; cursor: pointer;
}
.dl-sum:hover { color: var(--accent); }
.dl-sum-label { color: var(--muted); letter-spacing: .06em; flex: none; }
/* Wraps rather than truncating — see the note in js/10-main.js on why a part-shown digest is
   worse than an ugly one. */
.dl-sum-hash {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 11px; color: var(--muted);
  overflow-wrap: anywhere; min-width: 0;
}
.dl-sum-copy { margin-left: auto; color: var(--accent); flex: none; }
.dl-sum.soon { opacity: .6; cursor: default; }
.dl-sum.soon:hover { color: var(--text); }
/* No digest yet: the placeholder is prose, not a hash, so it is not set in the mono face. */
.dl-sum.soon .dl-sum-hash { font-family: inherit; color: var(--accent); }

/* ── Support ─────────────────────────────────────────── */
.support-links { display: flex; flex-wrap: wrap; gap: 10px; margin: 18px 0; }
.support-thanks { margin: 18px 0 0; font-family: var(--serif); font-size: 17px; color: var(--accent); }
/* The thanks line follows the wallet rows directly, and at 18px it read as the last row's
   caption rather than as the section closing. This is the one gap on the page that has to be
   larger than the rhythm around it. */
.crypto + .support-thanks { margin-top: 32px; }
.crypto { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
.crypto h3 { margin: 0 0 4px; font-size: 16px; }
.crypto-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }
.crypto-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; cursor: pointer; text-align: left;
  font-family: inherit;
}
.crypto-row:hover { border-color: var(--accent); }
/* No address set. Same treatment as .btn.soon, and the "coming soon" sits in the slot the
   address would have filled, so the row still says which chain is planned. */
.crypto-row.soon { opacity: .6; cursor: default; }
.crypto-row.soon:hover { border-color: var(--border); }
.crypto-row.soon .crypto-addr { font-family: inherit; font-size: 13px; color: var(--accent); }
.crypto-coin { font-weight: 600; font-size: 14px; flex: none; min-width: 5.5em; }
/* The address is the one string on the page that must be read character by character, so it is
   monospaced — and it wraps rather than truncating, because a half-shown wallet address that
   looks complete is worse than an ugly one. */
.crypto-addr {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12.5px; color: var(--muted);
  overflow-wrap: anywhere; min-width: 0;
}
.crypto-copy { margin-left: auto; font-size: 13px; color: var(--accent); flex: none; }

/* ── The feedback form ───────────────────────────────── */
/* This page's own block: neither sister site has a form at all. The controls are the app's own
   idiom rather than a second one invented here — a field on --surface-2 inside a --surface card,
   a 1px --border rule, 8px radius — so somebody who fills this in and then installs the app
   recognises the inputs. */
.fb-form { margin: 16px 0 0; display: grid; gap: 16px; max-width: 46em; }
/* A caption, its control, and — for the email — a description under it, stacked. A <div> rather
   than a wrapping <label>, because everything inside a label joins its control's accessible name
   and that hint would then be read out every time focus landed on the field; see the note in
   index.html. `gap: 6px` is what keeps the three parts reading as one field against the form's
   own 16px between fields. */
.fb-field { display: grid; gap: 6px; }
/* No `display` here on purpose: a grid item already fills its column, so the caption's click
   target is the whole line without one — and every `display` in this file is a `[hidden]`
   companion waiting to be forgotten. */
.fb-label { font-size: 14px; font-weight: 600; }
.fb-form select, .fb-form input, .fb-form textarea {
  font: inherit;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 10px; width: 100%;
}
/* Vertical only: a horizontally resizable textarea inside a fixed content column can be dragged
   out over the page. */
.fb-form textarea { resize: vertical; min-height: 120px; line-height: 1.5; }
.fb-form select:hover, .fb-form input:hover, .fb-form textarea:hover { border-color: var(--accent); }

/* The honeypot. Off-screen rather than `display: none` or `hidden`, because those two are what a
   well-written bot skips — the field has to look real to a script and be unreachable for a
   person. `aria-hidden` and `tabindex="-1"` in the markup keep it out of the accessibility tree
   and out of the tab order; this rule only takes it off the canvas. */
.fb-decoy {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}
.fb-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
/* The outcome line. `display: block` is what a <p> has anyway, and it is written out here so the
   `[hidden]` companion below has something to answer — the alternative is a rule that looks
   redundant until somebody adds `display: flex` to align an icon into it and silently defeats
   the attribute. */
.fb-status { display: block; margin: 0; font-size: 14px; color: var(--muted); }
.fb-status[hidden] { display: none; }
/* Two outcomes, and the colour is the *second* signal in both: the sentence itself says which
   happened, because a red line and a green line are the same line to a visitor who cannot tell
   them apart.

   These are the only two places on this page where --good and --bad are TEXT rather than a
   calendar fill, and the app's own stylesheet warns in as many words not to assume the pair is
   safe on any ground ("they are calendar fills with var(--text) written over them, never text
   themselves"). So the four pairs this rule actually forms were measured with the WCAG formula
   rather than assumed, against --surface, which is the card these sit in:

     dark   --good #7CBB88 on #263449  5.6:1     --bad #F2A0AE on #263449  6.2:1
     light  --good #2F6B41 on #FBF7EF  6.0:1     --bad #A6273E on #FBF7EF  6.6:1

   All four clear 4.5:1. Re-measure if either token moves in the app — and if one ever fails,
   the fix is to drop the colour here, not to retune a token on the landing page. */
.fb-status.ok { color: var(--good); }
.fb-status.err { color: var(--bad); }

/* ── Footer ──────────────────────────────────────────── */
#foot {
  margin-top: 56px; padding-top: 24px; padding-bottom: 40px;
  border-top: 1px solid var(--border);
  font-size: 14px;
}
#foot p { margin: 0 0 6px; }

/* ── Toast ───────────────────────────────────────────── */
.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 70;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
  padding: 10px 18px; font-size: 14px;
  box-shadow: 0 6px 20px var(--shadow-ink);
}
.toast[hidden] { display: none; }

/* ── Narrow ──────────────────────────────────────────── */
@media (max-width: 900px) {
  /* The nav stops being a row and becomes the panel the hamburger opens: full width under the
     bar, one link per line. It is the same markup and the same links — only the shape changes,
     which is why the toggle has nothing to build. */
  .topnav {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 2px;
    padding: 8px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 10px 30px var(--shadow-ink);
    /* Seven links needs it on a landscape phone. */
    max-height: min(70vh, 420px); overflow-y: auto;
  }
  .topnav[data-open="true"] { display: flex; }
  .topnav a { padding: 12px; font-size: 15px; }
  .nav-toggle { display: grid; }
  .topbar-controls { margin-left: auto; }
  .hero { padding-top: 40px; }
  /* 96px, and no smaller anywhere on this page: BRAND.md's floor is 87px and this mark starts
     losing the household below it — the puṇḍras go to ticks by 60px and the four figures stop
     being countable by 48px. Saṅkalpa's landing drops to 84px here and 64px below, which its
     flatter mark can take and this one cannot. */
  .hero-brand { --mark: 96px; }
  .band { padding-top: 44px; }
  /* One column below the two-card width. The 360px minmax would fall back on its own; this makes
     the intent explicit and keeps a 380px phone from getting one 360px column plus a sliver. */
  .grid-sides { grid-template-columns: 1fr; }
}
@media (max-width: 420px) {
  /* The popup is anchored to the button's right edge, so on a narrow screen it grows inward
     from there and cannot push the layout wider. */
  .lang-list { min-width: 11em; }
  .crypto-row { flex-wrap: wrap; }
  .crypto-copy { margin-left: 0; }
  /* The mark stays at 96px — see the note above. What gives instead is the wordmark beside it,
     which `--mark` also sizes, so the pair is narrowed by the type rather than by the artwork. */
  .brand-words { font-size: max(15px, calc(var(--mark) * .2)); }
  /* Send, and the line that reports what it did, stop sharing a row: at this width the sentence
     is two or three lines and sits badly beside a button. */
  .fb-actions { flex-direction: column; align-items: stretch; }
}
