/* docs/records/design-system.md is the specification. This file is the truth. */

@font-face {
  font-family: Vazirmatn;
  src: url("/f/vazirmatn.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}

@font-face {
  font-family: Libron;
  src: url("/f/libron.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0, U+00C4-00C5, U+00D6, U+00E4-00E5, U+00F6,
    U+2010-2014, U+2018-201D, U+2026, U+20AC;
  /* Measured: Vazirmatn x-height 0.5283 em, Libron 0.5315. scripts/fonts.sh
     reprints this after any upstream bump. */
  size-adjust: 99.4%;
}

:root {
  color-scheme: light dark;

  --text-meta: 0.8125rem;
  --text-body: 1.03125rem;
  --text-lead: 1.25rem;
  --text-title: 1.625rem;
  --text-display: 2.125rem;

  --leading-prose: 1.9;
  --leading-ui: 1.7;
  --leading-title: 1.2;

  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-12: 48px;

  --radius: 0.75rem;

  --motion-fast: 150ms;
  --motion-base: 220ms;
  --motion-slow: 420ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  /* paper */
  --background: #E9F1EA;
  --foreground: #142F24;
  --muted: #DCE8DD;
  --muted-foreground: #2E4C36;
  --border: rgb(20 47 36 / 15%);
  --ring: #2E4C36;
  /* 0.55, not 0.68 — the accent is link text, so it owes 4.5:1 and not
     the 3:1 a border owes. scripts/contrast.py is the check. */
  --brand: #8C6142;

  /* One light source for the whole site, from above. Depth comes from every
     raised surface agreeing on the angle, not from a shadow chosen per
     component. Three steps and no more. decisions/0016. */
  --surface-1: #F4F8F2;
  --surface-2: #FAFCF9;
  /* every colour below is one the valley is made of — scene/palette.py */
  --soft: #DFEFE3;
  /* Iranian cedar. Deep, green, slightly cool — the colour the sarv is. */
  --deep: #1C3A2C;
  --deep-foreground: #DFEFE3;
  --deep-muted: #B8CEB4;
  --deep-brand: #E8B894;
  --cedar-deep: #0E2119;
  /* Glass. 76% is not a taste: scripts/scrim.py --band hero --text brand
     --blur 28 says the accent clears 4.5:1 there and fails below it. The blur
     is what buys the transparency — it flattens the extreme pixels that a
     sharp background would push under the text. */
  --glass: rgb(20 47 36 / 76%);
  --glass-blur: blur(30px) saturate(175%);
  --glass-rim: oklch(1 0 0 / 20%);
  --glass-lift: inset 0 1px 0 oklch(1 0 0 / 14%), 0 24px 70px rgb(8 20 15 / 46%);
  --lift-1: inset 0 1px 0 rgb(255 255 255 / 60%), 0 1px 2px rgb(20 47 36 / 10%);
  --lift-2: inset 0 1px 0 rgb(255 255 255 / 70%), 0 2px 10px rgb(20 47 36 / 14%);
}

@media (prefers-color-scheme: dark) {
  :root {
    /* graphite — picked separately, never inverted */
    --background: #142F24;
    --foreground: #DFEFE3;
    --muted: #1E3E2F;
    --muted-foreground: #A3BD9F;
    --border: rgb(223 239 227 / 16%);
    --ring: #2E4C36;
    --brand: #D59A6C;

    --surface-1: #1C3A2C;
    --surface-2: #234534;
    --soft: #18352A;
    --lift-1: inset 0 1px 0 rgb(255 255 255 / 7%), 0 1px 2px rgb(0 0 0 / 38%);
    --lift-2: inset 0 1px 0 rgb(255 255 255 / 9%), 0 4px 14px rgb(0 0 0 / 48%);
  }
}

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

/* The platform scrollbar is a grey slab with arrows and it lands right on the
   glass. Thin and tinted everywhere, including the page itself. */
* {
  scrollbar-width: thin;
  scrollbar-color: oklch(0.5 0.03 160 / 45%) transparent;
}

*::-webkit-scrollbar { inline-size: 10px; block-size: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: oklch(0.5 0.03 160 / 45%);
  border: 3px solid transparent;
  border-radius: 999px;
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: oklch(0.6 0.03 160 / 60%); background-clip: padding-box; }
*::-webkit-scrollbar-button { display: none; }

/* Not on the dialogs. A scrollbar painted at the edge of a rounded panel
   crosses the corner and sits on the glass; these scroll by wheel, touch and
   keyboard, and fade their last line so there is still a cue there is more. */
.ask.open, .reader-inner, .second-screen .road, .wide .first-screen .hero {
  scrollbar-width: none;
}

.ask.open::-webkit-scrollbar,
.reader-inner::-webkit-scrollbar,
.second-screen .road::-webkit-scrollbar,
.wide .first-screen .hero::-webkit-scrollbar { display: none; }

body {
  margin: 0;
  background: var(--background);
  color: var(--foreground);
  font-family: Vazirmatn, system-ui, sans-serif;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  letter-spacing: 0; /* tracking breaks Persian glyph joining */
}

/* A Finnish run inside Persian prose. The serif is the signal: this is the
   exact string on the reader's form. */
.fi, blockquote[lang="fi"] {
  font-family: Libron, Georgia, serif;
  unicode-bidi: isolate;
}

.chrome {
  display: flex;
  align-items: center;
  padding: var(--space-4);
  border-block-end: 1px solid var(--border);
}

/* On the landing the chrome sits in the scene, not above it — a pale strip
   across the top is a second horizon competing with the painted one.

   The top of the painting is not one colour: pale mist and near-black cedar
   tops share it, so no text colour clears 4.5:1 unaided. scripts/scrim.py
   --band chrome: mist needs 65% of --cedar-deep, muted needs 80%. Hence a
   scrim held at full strength behind the text and faded below it, and mist
   rather than muted for the nav. */
.wide .chrome {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 3;
  border-block-end: 0;
  color: var(--deep-foreground);
  background: linear-gradient(
    to bottom,
    rgb(14 33 25 / 78%),
    rgb(14 33 25 / 78%) 74px,
    rgb(14 33 25 / 0%) 190px
  );
}

.wide .chrome-nav a { color: var(--deep-foreground); }

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: 44px;
  color: inherit;
  text-decoration: none;
}

/* A station on the thread, at 28px. The site's own structural idea standing in
   for the character, so the slot reads as drawn rather than empty. */
.mark {
  position: relative;
  flex: none;
  inline-size: 28px;
  block-size: 28px;
}

.mark::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 13px;
  border-inline-start: 1px solid var(--brand);
  opacity: 0.45;
}

.mark::after {
  content: "";
  position: absolute;
  inset-block-start: 8px;
  inset-inline-start: 8px;
  inline-size: 12px;
  block-size: 12px;
  border: 1px solid var(--brand);
  border-radius: 50%;
  background: var(--surface-1);
  box-shadow: var(--lift-1);
}

/* The wordmark is the serif, which is the same argument the Finnish terms make:
   Latin belongs to Libron. */
/* The name is Persian, so the logotype is. Vazirmatn carries it, the نیم‌فاصله
   in فین‌لاگپت is a real ZWNJ, and tracking stays at zero — spacing Persian
   letters breaks the joins that hold a word together. */
.wordmark {
  font-weight: 700;
  font-size: var(--text-lead);
  letter-spacing: 0;
}

/* The domain, not a second name. Quiet enough to read as a label. */
.wordmark-latin {
  font-family: Libron, Georgia, serif;
  font-size: var(--text-meta);
  color: var(--muted-foreground);
  letter-spacing: 0.02em;
}

.band-deep .wordmark-latin, .wide .wordmark-latin { color: var(--deep-muted); }

main { max-inline-size: 42rem; margin-inline: auto; padding: var(--space-6) var(--space-4) var(--space-12); }
body.wide main { max-inline-size: none; margin: 0; padding: 0; }

.station {
  margin: 0 0 var(--space-2);
  color: var(--muted-foreground);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
}

/* A base link colour, because there was none: every link was coloured by its
   component and anything missed fell through to the browser's blue. That is
   how a breadcrumb ended up the one blue thing on the page. Components with
   their own colour still win — this is only the floor. */
a {
  color: var(--brand);
  text-underline-offset: 0.2em;
}

a:hover, a:focus-visible { text-decoration-thickness: 2px; }

h1 {
  margin: 0 0 var(--space-8);
  font-size: var(--text-title);
  line-height: var(--leading-title);
  font-weight: 600;
}

/* An answer's title is a question someone actually asked. */
.answer h1 {
  font-size: var(--text-display);
  font-weight: 500;
  margin-block-end: var(--space-6);
}

@media (max-width: 420px) {
  .answer h1 { font-size: var(--text-title); }
}

/* Two voices, kept apart. This is the product's one original idea and it was
   rendered as a paragraph above a grey box.

   The institution has no colour. We do. That is the whole visual claim: a
   neutral marker over quoted text, a warm one over ours. */
.block { margin-block-end: var(--space-8); }

.attribution {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
}

.attribution::before {
  content: "";
  flex: none;
  inline-size: 7px;
  block-size: 7px;
  border-radius: 50%;
  background: currentColor;
  transform: translateY(-0.1em);
}

/* The authority speaking. Quoted from elsewhere, so it carries the start-edge
   rule a quotation carries, and nothing warm. */
.block-source {
  border-inline-start: 2px solid var(--border);
  padding-inline-start: var(--space-4);
}

.block-source .attribution { color: var(--muted-foreground); }
.block-source p:not(.attribution) { margin: 0 0 var(--space-3); }
.block-source ul { margin: 0; padding-inline-start: var(--space-5); }
.block-source li { margin-block-end: var(--space-2); }

/* Us. Warm, lifted, and offered rather than stated. */
.block-ours {
  border: 1px solid var(--border);
  border-inline-start: 2px solid var(--brand);
  border-radius: var(--radius);
  padding: var(--space-4);
  background: var(--surface-1);
  box-shadow: var(--lift-1);
}

.block-ours summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 44px;
  font-size: var(--text-lead);
  line-height: var(--leading-ui);
  color: var(--brand);
  cursor: pointer;
}

.block-ours summary { list-style: none; }
.block-ours summary::-webkit-details-marker { display: none; }

.block-ours summary::after {
  content: "";
  inline-size: 8px;
  block-size: 8px;
  border-inline-end: 1.5px solid currentColor;
  border-block-end: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--motion-base) var(--ease-out);
}

.block-ours[open] summary::after { transform: rotate(-135deg); }

.block-ours[open] > :not(summary) {
  animation: unfold var(--motion-base) var(--ease-out);
}

@keyframes unfold {
  from { opacity: 0; transform: translateY(-4px); }
}

.block-ours p { margin: var(--space-3) 0 0; }

.notice {
  color: var(--muted-foreground);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
}

.footnote {
  border-block-start: 1px solid var(--border);
  padding-block-start: var(--space-6);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
  color: var(--muted-foreground);
}

.footnote h2 { margin: 0 0 var(--space-3); font-size: var(--text-meta); font-weight: 600; }
.footnote p { margin: 0 0 var(--space-2); }
.footnote a { color: inherit; }

/* The thing a reader holds up at a counter. Not a footnote — an object. */
.verbatim {
  margin: var(--space-4) 0 0;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  box-shadow: var(--lift-1);
}

.verbatim figcaption {
  margin-block-end: var(--space-3);
  color: var(--muted-foreground);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
}

blockquote[lang="fi"] {
  margin: 0;
  font-family: Libron, Georgia, serif;
  font-size: var(--text-body);
  line-height: 1.7;
  color: var(--foreground);
  user-select: all;
}

.copy { min-block-size: 44px; margin-block-start: var(--space-2); }

:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--space-1);
}

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

/* A Finnish term is a link to its gloss. Underline only on interaction —
   permanent underlines through Persian prose fight the baseline. */
a.fi {
  color: inherit;
  text-decoration: none;
  border-block-end: 1px dotted var(--border);
}
a.fi:hover, a.fi:focus-visible { border-block-end-style: solid; }

.term {
  font-size: var(--text-display);
  line-height: var(--leading-title);
  font-weight: 400;
  margin-block-end: var(--space-2);
}

.gloss {
  margin: 0 0 var(--space-6);
  font-size: var(--text-lead);
  color: var(--muted-foreground);
}

@media (max-width: 360px) {
  .term { font-size: var(--text-title); }
}

h2 {
  margin: var(--space-8) 0 var(--space-3);
  font-size: var(--text-lead);
  line-height: var(--leading-ui);
  font-weight: 600;
}

.listing { list-style: none; margin: 0; padding: 0; }

.listing li {
  border-block-end: 1px solid var(--border);
}

.listing a {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
  min-block-size: 44px;
  padding-block: var(--space-3);
  color: inherit;
  text-decoration: none;
}

.listing a:hover, .listing a:focus-visible { color: var(--brand); }
.listing .station, .listing .gloss-inline {
  color: var(--muted-foreground);
  font-size: var(--text-meta);
}

.lede {
  margin: 0 0 var(--space-8);
  font-size: var(--text-lead);
  line-height: var(--leading-prose);
  color: var(--muted-foreground);
}

.stations { list-style: none; margin: 0; padding: 0; }

/* One line through every station. Solid where an answer exists, dashed where it
   does not, so 0005's honesty is a property of the drawing. It is the only place
   the accent runs as a line. */
.station {
  --pad: var(--space-6);
  position: relative;
  display: flex;
  gap: var(--space-4);
  padding-block: var(--pad);
}

.station::before {
  content: "";
  position: absolute;
  inset-block: 0;
  /* the centre of the 28px ordinal */
  inset-inline-start: 14px;
  border-inline-start: 1px solid var(--brand);
  opacity: 0.45;
}

.station.empty::before {
  border-inline-start-style: dashed;
  opacity: 0.22;
}

/* The line begins and ends at an ordinal, not in the padding above one. */
.station:first-child::before { inset-block-start: calc(var(--pad) + 14px); }
.station:last-child::before { inset-block-end: calc(100% - var(--pad) - 14px); }

.ordinal {
  position: relative;
  flex: none;
  inline-size: 34px;
  block-size: 34px;
  display: grid;
  place-items: center;
  /* masks the thread behind it */
  background: var(--background);
  color: var(--brand);
  font-size: var(--text-meta);
}

/* A step on the road sits inside the star, not a plain circle. */
.ordinal::before {
  content: "";
  position: absolute;
  inset: 0;
  background: currentColor;
  -webkit-mask: url("/img/star.svg") no-repeat center / 100%;
  mask: url("/img/star.svg") no-repeat center / 100%;
}

.station.empty .ordinal {
  border-color: var(--border);
  color: var(--muted-foreground);
}

.station-body { min-inline-size: 0; padding-block-start: var(--space-1); }

.station h3 { margin: 0 0 var(--space-1); font-size: var(--text-lead); font-weight: 600; }
.station h3 a { color: inherit; text-decoration: none; }
.station h3 a:hover, .station h3 a:focus-visible { color: var(--brand); }

.station.empty h3 {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0;
  font-size: var(--text-body);
  font-weight: 400;
}
.station.empty h3 a { color: var(--muted-foreground); }
.station.empty .pending { margin: 0; }

.summary { margin: 0; color: var(--muted-foreground); font-size: var(--text-body); }


.pending {
  margin: var(--space-2) 0 0;
  font-size: var(--text-meta);
  color: var(--muted-foreground);
}

.block-pending {
  margin-block-start: var(--space-6);
  padding: var(--space-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  font-size: var(--text-body);
  line-height: var(--leading-prose);
}

.station .listing { margin-block-start: var(--space-2); }
.station .listing li { border: 0; }
.station .listing a { min-block-size: 36px; padding-block: var(--space-1); color: var(--brand); }

/* 50ms down the thread: six stations is 300ms, inside the 400ms the craft rules
   allow. 150ms would take 900ms and a reader would watch it finish. */
@media (prefers-reduced-motion: no-preference) {
  .station {
    animation: arrive var(--motion-base) var(--ease-out) backwards;
  }
  .station:nth-child(1) { animation-delay: 0ms; }
  .station:nth-child(2) { animation-delay: 50ms; }
  .station:nth-child(3) { animation-delay: 100ms; }
  .station:nth-child(4) { animation-delay: 150ms; }
  .station:nth-child(5) { animation-delay: 200ms; }
  .station:nth-child(6) { animation-delay: 250ms; }
}

@keyframes arrive {
  from { opacity: 0; transform: translateY(6px); }
}

/* reduce means less motion, not none */
@media (prefers-reduced-motion: reduce) {
  .station { animation: fade 120ms linear backwards; }
}

@keyframes fade { from { opacity: 0; } }

.aside {
  margin-block-start: var(--space-8);
  font-size: var(--text-meta);
  color: var(--muted-foreground);
}
.aside a { color: var(--brand); }

.words .fi { font-size: var(--text-body); }

.copy {
  min-block-size: 44px;
  margin-block-start: var(--space-2);
  padding-inline: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  box-shadow: var(--lift-1);
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-meta);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out);
}

.copy:hover { border-color: var(--ring); }
.copy[data-state="done"] { border-color: var(--brand); color: var(--brand); }
.copy[data-state="failed"] { border-color: var(--border); color: var(--muted-foreground); }

/* The thread continues off the road and terminates at the source. Same 44px
   geometry as a station — 28px ordinal plus a 16px gap — so the line reads as
   one line rather than three decorated pages. */
.threaded {
  position: relative;
  padding-inline-start: 44px;
}

.threaded > .run,
.threaded.no-run {
  position: relative;
}

/* The run carries the line, so the footnote caps it without any arithmetic. */
.threaded > .run::before,
.threaded.no-run::before {
  content: "";
  position: absolute;
  inset-block-start: 0.6em;
  inset-block-end: 0;
  inset-inline-start: -30px;
  border-inline-start: 1px solid var(--brand);
  opacity: 0.45;
}

/* The terminal. Arriving at the source is visibly the end of something. */
.footnote { position: relative; }

.footnote::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline-start: -30px;
  inline-size: 9px;
  block-size: 9px;
  border: 1px solid var(--brand);
  border-radius: 50%;
  background: var(--surface-1);
  box-shadow: var(--lift-1);
  opacity: 0.6;
}

@media (max-width: 420px) {
  main { padding-inline: var(--space-3); }
}

.tagline {
  color: var(--muted-foreground);
  font-size: var(--text-meta);
}

@media (max-width: 420px) {
  .tagline { display: none; }
}

.free {
  display: inline-block;
  margin-inline-start: var(--space-2);
  padding: 2px var(--space-2);
  border: 1px solid var(--brand);
  border-radius: 999px;
  color: var(--brand);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
}

/* A stranger needs one place to go, not six — and four of the six are empty. */
.entry {
  display: block;
  margin-block: var(--space-8) var(--space-12);
  padding: var(--space-4);
  border: 1px solid var(--brand);
  border-radius: var(--radius);
  background: var(--surface-1);
  box-shadow: var(--lift-2);
  color: inherit;
  text-decoration: none;
  transition: box-shadow var(--motion-base) var(--ease-out);
}

.entry:hover, .entry:focus-visible { box-shadow: var(--lift-2), 0 0 0 1px var(--brand); }

.entry-label {
  display: block;
  margin-block-end: var(--space-2);
  color: var(--brand);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
}

.entry-title {
  display: block;
  font-size: var(--text-lead);
  line-height: var(--leading-ui);
}

.entry-where {
  display: block;
  margin-block-start: var(--space-1);
  color: var(--muted-foreground);
  font-size: var(--text-meta);
}

/* A section header, so the road reads as its own thing rather than more page. */
.road-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-block-end: var(--space-2);
  padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--border);
}

.road-head h2 {
  margin: 0;
  font-size: var(--text-lead);
  line-height: var(--leading-ui);
}

/* Honest, and useful to someone coming back: has anything landed? */
.road-progress {
  margin: 0;
  color: var(--muted-foreground);
  font-size: var(--text-meta);
}

/* The ask box, in the hero. It is the way in, so it carries no card of its
   own — the band is the card. */
.ask { max-inline-size: 34rem; margin-inline: auto; }

/* The composer. One surface that owns the question: the field, the send and
   the key hint, with the plate as its ground. */
.composer {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4) var(--space-3);
  border: 1px solid oklch(1 0 0 / 14%);
  border-radius: calc(var(--radius) + 12px);
  background: oklch(1 0 0 / 7%);
  transition: border-color var(--motion-fast) var(--ease-out),
              background-color var(--motion-fast) var(--ease-out),
              box-shadow var(--motion-fast) var(--ease-out);
}

.composer:focus-within {
  border-color: oklch(1 0 0 / 30%);
  background: oklch(1 0 0 / 11%);
  box-shadow: 0 0 0 3px oklch(1 0 0 / 10%);
}

.composer textarea {
  display: block;
  inline-size: 100%;
  min-block-size: 2.5rem;
  max-block-size: 9rem;
  padding: var(--space-1) var(--space-1) 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  /* 16px floor or Safari zooms the page on focus */
  font-size: max(1rem, var(--text-body));
  line-height: var(--leading-prose);
  resize: none;
  overflow-y: auto;
  /* grows with its content where supported; app.js does it everywhere else */
  field-sizing: content;
}

.composer textarea:focus { outline: none; }
.composer textarea::placeholder { color: var(--deep-muted); }

.composer-bar {
  display: flex;
  align-items: center;
  flex-direction: row-reverse;
  justify-content: space-between;
  gap: var(--space-3);
}

/* Not a label: the control is read by its aria-label, and a hint that wraps on
   a phone is noise next to a 44px target. */
.composer-hint {
  color: var(--deep-muted);
  font-size: var(--text-meta);
}

@media (width <= 30rem) {
  .composer-hint { display: none; }
}

.send {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 40px;
  block-size: 40px;
  border: 0;
  border-radius: 50%;
  background: var(--deep-brand);
  color: oklch(0.22 0.04 160);
  cursor: pointer;
  transition: opacity var(--motion-fast) var(--ease-out),
              transform var(--motion-fast) var(--ease-out);
}

.send:hover { transform: translateY(-1px); }
.send:disabled { opacity: 0.4; cursor: default; transform: none; }

/* Questions, not tags. A tag filters; these are things to ask. */
.suggestions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
}

.suggestion {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 40px;
  padding-inline: var(--space-3);
  border: 1px solid oklch(1 0 0 / 12%);
  border-radius: 999px;
  background: oklch(1 0 0 / 6%);
  color: var(--deep-muted);
  transition: background-color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out),
              color var(--motion-fast) var(--ease-out);
  font: inherit;
  font-size: var(--text-meta);
  cursor: pointer;
}

/* Forward is left in RTL. */
.suggestion::before {
  content: "←";
  flex: none;
  color: var(--brand);
  font-size: var(--text-meta);
}

.suggestion:hover, .suggestion:focus-visible {
  color: var(--deep-foreground);
  border-color: oklch(1 0 0 / 26%);
  background: oklch(1 0 0 / 11%);
}

.ask-out {
  max-inline-size: 34rem;
  margin: var(--space-6) auto 0;
  padding: var(--space-4);
  border-radius: var(--radius);
  /* An inset well, not a lighter patch: over the plate, 8% white dropped the
     answer links to 4.29:1. Darkening instead puts them at 5.98:1. */
  background: oklch(0 0 0 / 14%);
  text-align: start;
  line-height: var(--leading-prose);
  white-space: pre-wrap;
}

.ask-out.thinking { color: var(--deep-muted); }
.ask-out.refused { color: var(--deep-muted); }

.ask-sources {
  margin: var(--space-3) 0 0;
  font-size: var(--text-meta);
  color: var(--deep-muted);
}
.ask-sources a { color: var(--brand); }

.ask-note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-meta);
  color: var(--deep-muted);
}



.ask-list { margin: var(--space-3) 0 0; padding-inline-start: var(--space-4); }
.ask-list li { margin-block-end: var(--space-2); }
.ask-list a { color: var(--deep-brand); }
.ask-what { color: var(--deep-muted); font-size: var(--text-meta); }

/* The word as it appears on paper, before anything is said about it. */
.term-card {
  margin-block-end: var(--space-8);
  padding: var(--space-6) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  box-shadow: var(--lift-1);
  text-align: center;
}

.term-card .term {
  margin: 0 0 var(--space-2);
  font-size: var(--text-display);
  font-weight: 400;
  line-height: var(--leading-title);
}

.term-card .gloss {
  margin: 0;
  font-size: var(--text-body);
  color: var(--muted-foreground);
}

.seen-in { margin-block-start: var(--space-8); }

.seen-in h2 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-meta);
  font-weight: 600;
  color: var(--muted-foreground);
}

.seen-in .listing a { color: var(--brand); }

/* Looked up, not read through, so a grid rather than a list. */
.term-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.term-grid a {
  display: block;
  min-block-size: 44px;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  box-shadow: var(--lift-1);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--motion-fast) var(--ease-out);
}

.term-grid a:hover, .term-grid a:focus-visible { border-color: var(--brand); }

.term-grid .fi {
  display: block;
  font-size: var(--text-lead);
  line-height: var(--leading-ui);
}

.term-grid .gloss-inline {
  display: block;
  margin-block-start: var(--space-1);
  color: var(--muted-foreground);
  font-size: var(--text-meta);
}

.still-stuck {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--border);
  font-size: var(--text-meta);
  line-height: var(--leading-prose);
  color: var(--muted-foreground);
}

.still-stuck a { color: var(--brand); }

.where {
  margin-inline-start: var(--space-3);
  padding-inline-start: var(--space-3);
  border-inline-start: 1px solid var(--border);
}

.pending-why { margin-block-start: var(--space-2); font-size: var(--text-meta); }
.pending-why a { color: var(--brand); }

/* The road is walked, so a station offers the step either side of it. */
.walk {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  margin-block-start: var(--space-12);
}

.walk a {
  display: block;
  min-block-size: 44px;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  box-shadow: var(--lift-1);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--motion-fast) var(--ease-out);
}

.walk a:hover, .walk a:focus-visible { border-color: var(--brand); }

.walk-label {
  display: block;
  margin-block-end: var(--space-1);
  color: var(--muted-foreground);
  font-size: var(--text-meta);
}

/* Forward is the start edge in RTL, so the next step sits on the right. Both
   are pinned to row 1 or auto-placement drops the second into a new row. */
.walk-next { grid-area: 1 / 1; text-align: start; }
.walk-prev { grid-area: 1 / 2; text-align: end; }

@media (max-width: 30rem) {
  .walk { grid-template-columns: 1fr; }
  .walk-next { grid-area: 1 / 1; }
  .walk-prev { grid-area: 2 / 1; text-align: start; }
}

/* ── the landing ───────────────────────────────────────────────────
   Full-bleed bands, each doing one job. A marketing page and a reading
   page are not the same surface, so only `body.wide` gets this. */

.band { padding-block: clamp(3.5rem, 9vw, 7rem); }
.band-inner { position: relative; z-index: 1; max-inline-size: 46rem; margin-inline: auto; padding-inline: var(--space-4); }

.band-soft { background: var(--soft); }

.band-deep {
  position: relative;
  background: var(--deep);
  color: var(--deep-foreground);
  overflow: hidden;
}

.band-deep .eyebrow,
.band-deep .band-lede,
.band-deep .hero-lede { color: var(--deep-muted); }

.band-deep .accent { color: var(--deep-brand); }

/* Khatam frames rather than fills. In Persian work the star band is a border,
   so it lives on the seams between cedar and ground and nowhere else. */
.seam {
  block-size: 34px;
  background: var(--deep);
  opacity: 0.35;
  -webkit-mask: url("/img/khatam.svg") center / auto 34px repeat-x;
  mask: url("/img/khatam.svg") center / auto 34px repeat-x;
}

.eyebrow {
  margin: 0 0 var(--space-3);
  color: var(--muted-foreground);
  font-size: var(--text-meta);
  line-height: var(--leading-ui);
}

.hero { position: relative; z-index: 1; text-align: center; }

.hero h1 {
  margin: 0 0 var(--space-4);
  font-size: clamp(2rem, 6vw, 3.25rem);
  line-height: 1.18;
  font-weight: 600;
}

.hero-lede {
  max-inline-size: 34rem;
  margin: 0 auto var(--space-8);
  font-size: var(--text-lead);
  line-height: var(--leading-prose);
}

.band h2 {
  margin: 0 0 var(--space-3);
  font-size: clamp(1.5rem, 4vw, 2.125rem);
  line-height: var(--leading-title);
  font-weight: 600;
}

.band-lede {
  margin: 0 0 var(--space-8);
  font-size: var(--text-lead);
  line-height: var(--leading-prose);
  color: var(--muted-foreground);
}

/* The promise, shown. Two panes side by side is the argument. */
.demo {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

.demo-pane {
  padding: var(--space-4);
  border-radius: var(--radius);
  background: var(--surface-1);
  box-shadow: var(--lift-1);
  font-size: var(--text-body);
  line-height: var(--leading-prose);
}

.demo-pane p:last-child { margin: 0; }
.demo-source { border-inline-start: 2px solid var(--border); }
.demo-source .attribution { color: var(--muted-foreground); }
.demo-ours { border-inline-start: 2px solid var(--brand); }
.demo-ours .attribution { color: var(--brand); }

.demo-link { margin-block-start: var(--space-4); font-size: var(--text-meta); }
.demo-link a { color: var(--brand); }

.authorities {
  margin-block-end: clamp(70px, 10vw, 120px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  position: relative;
  z-index: 1;
}

.authorities li {
  padding: var(--space-3);
  border: 1px solid oklch(1 0 0 / 14%);
  border-radius: var(--radius);
  background: oklch(1 0 0 / 5%);
}

.authorities .fi {
  display: block;
  font-family: Libron, Georgia, serif;
  font-size: var(--text-lead);
}

.authorities span + span {
  display: block;
  margin-block-start: var(--space-1);
  color: var(--deep-muted);
  font-size: var(--text-meta);
}

.band-cta { background: var(--soft); text-align: center; }
.cta h2 { margin-block-end: var(--space-2); }
.cta p { margin: 0 0 var(--space-6); color: var(--muted-foreground); }

.cta-button {
  display: inline-block;
  min-block-size: 48px;
  padding: var(--space-3) var(--space-6);
  border-radius: var(--radius);
  background: var(--brand);
  color: var(--background);
  font-size: var(--text-lead);
  text-decoration: none;
  box-shadow: var(--lift-2);
}

.cta-aside { margin-block-start: var(--space-6); font-size: var(--text-meta); }
.cta-aside a { color: var(--brand); }

.chrome-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  max-inline-size: 46rem;
  margin-inline: auto;
}

.chrome-nav a { color: var(--muted-foreground); font-size: var(--text-meta); }

.foot {
  padding-block: var(--space-8);
  border-block-start: 1px solid var(--border);
  background: var(--soft);
}

.foot-inner {
  max-inline-size: 46rem;
  margin-inline: auto;
  padding-inline: var(--space-4);
  font-size: var(--text-meta);
  line-height: var(--leading-prose);
  color: var(--muted-foreground);
}

.foot-what { margin: 0 0 var(--space-2); color: var(--foreground); }
.foot-note { margin: 0; }

.visually-hidden {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ── motion ────────────────────────────────────────────────────────
   Scroll-driven, so there is no JavaScript behind any of it. Where the
   browser has no view() timeline the content is simply there, which is
   the correct fallback rather than a hidden element waiting on a script. */

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .band:not(:first-of-type) .band-inner > * {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 8% cover 26%;
    }

    /* Children of a group arrive after their parent, not with it. */
    .band:not(:first-of-type) .demo-pane,
    .band:not(:first-of-type) .authorities li,
    .band:not(:first-of-type) .stations .station {
      animation: rise-soft linear both;
      animation-timeline: view();
      animation-range: entry 4% cover 24%;
    }

    .seam {
      animation: slide linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
  }

  .cta-button { transition: transform var(--motion-base) var(--ease-out); }
  .cta-button:hover { transform: translateY(-2px); }

  .demo-pane, .authorities li, .term-grid a, .walk a {
    transition: transform var(--motion-base) var(--ease-out);
  }
  .term-grid a:hover, .walk a:hover { transform: translateY(-2px); }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

@keyframes rise-soft {
  from { opacity: 0; transform: translateY(10px) scale(0.99); }
  to { opacity: 1; transform: none; }
}

@keyframes drift {
  from { transform: translateY(8%); }
  to { transform: translateY(-4%); }
}

/* The star band slides, so the seam reads as a woven edge rather than a rule. */
@keyframes slide {
  from { -webkit-mask-position-x: 0; mask-position-x: 0; }
  to { -webkit-mask-position-x: 108px; mask-position-x: 108px; }
}

/* The hero is above the fold, so a scroll timeline never fires for it. */
@media (prefers-reduced-motion: no-preference) {
  .hero > * { animation: rise var(--motion-slow, 420ms) var(--ease-out) backwards; }
  .hero > :nth-child(1) { animation-delay: 60ms; }
  .hero > :nth-child(2) { animation-delay: 120ms; }
  .hero > :nth-child(3) { animation-delay: 180ms; }
  .hero > :nth-child(4) { animation-delay: 260ms; }
  .hero > :nth-child(5) { animation-delay: 320ms; }

}

@keyframes settle {
  from { opacity: 0; transform: translateY(14px); }
}

/* ── the ask, opening ──────────────────────────────────────────────
   Collapsed it is one field. Focused it becomes the surface you are
   talking to, which is the difference between a docs search and this. */

/* One width, one padding, both states. Opening used to widen this from 34rem
   to 44rem and grow its padding, so the field moved out from under the cursor
   that had just been put in it. Only the surface changes now. */
.ask {
  position: relative;
  max-inline-size: 34rem;
  margin-inline: auto;
  padding: var(--space-4);
  border: 1px solid transparent;
  border-radius: calc(var(--radius) + 6px);
  transition:
    background-color var(--motion-slow) var(--ease-out),
    border-color var(--motion-slow) var(--ease-out),
    box-shadow var(--motion-slow) var(--ease-out);
}

/* Open, the ask leaves the page and becomes a dialog. A phone gets a bottom
   sheet, because that is where the thumb and the keyboard are; a desktop gets
   a centred card, because a sheet pinned to the bottom of a 900px window is
   mostly empty space with the content pushed into one corner. Same surface,
   two shapes. */
.ask.open {
  position: fixed;
  z-index: 60;
  inset-inline: 0;
  inset-block-end: 0;
  margin-inline: auto;
  max-inline-size: 46rem;
  min-block-size: 54svh;
  max-block-size: 92svh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-6) var(--space-4);
  border: 1px solid var(--glass-rim);
  border-block-end: 0;
  border-radius: calc(var(--radius) + 20px) calc(var(--radius) + 20px) 0 0;
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  /* The dialog is moved to <body> when it opens, which took it out of the
     cedar band and left it inheriting the light scheme: the answer text
     computed to ink on a dark panel. It carries its own scheme now. */
  color: var(--deep-foreground);
  /* No transition: a dialog arrives opaque. Tweening the background meant it
     faded up from transparent and the composer floated on the backdrop. */
  transition: none;
  box-shadow: inset 0 1px 0 oklch(1 0 0 / 14%), 0 -30px 90px rgb(8 20 15 / 52%);
  animation: rise var(--motion-slow) var(--ease-out);
}

@media (width >= 48rem) {
  .ask.open {
    inset-block: 0;
    margin-block: auto;
    block-size: fit-content;
    min-block-size: 0;
    max-inline-size: min(92vw, 56rem);
    max-block-size: min(88svh, 48rem);
    padding: clamp(1.75rem, 3vw, 2.5rem);
    border-block-end: 1px solid var(--glass-rim);
    border-radius: calc(var(--radius) + 22px);
    box-shadow: var(--glass-lift);
    animation: expand 380ms cubic-bezier(0.2, 0.9, 0.25, 1);
  }
}

@keyframes rise {
  from { transform: translateY(18px); opacity: 0; }
}

/* It grows into place rather than appearing: the field the reader just
   touched becomes the window they are now looking through. */
@keyframes expand {
  from { transform: scale(0.94) translateY(10px); opacity: 0; }
}

/* While a dialog is open the page behind it is a backdrop, not a surface:
   it does not scroll and it does not answer a pointer. The scene keeps its
   own weather — that is the part worth seeing through the glass. */
html:has(body.asking), html:has(body.reading) { overflow: hidden; }

body.asking .valley, body.asking .valley *,
body.reading .valley, body.reading .valley * { pointer-events: none; }

body.asking::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgb(8 20 15 / 40%);
  backdrop-filter: blur(7px) saturate(115%);
  animation: fade var(--motion-base) var(--ease-out);
}

@keyframes fade { from { opacity: 0; } }

/* The sheet carries the deep scheme on every page, including the light ones. */
.dock .ask.open { background: var(--deep); border-color: oklch(1 0 0 / 12%); }
.dock .ask.open .composer { background: oklch(1 0 0 / 7%); border-color: oklch(1 0 0 / 14%); }
.dock .ask.open .composer textarea { color: var(--deep-foreground); }
.dock .ask.open .composer textarea::placeholder { color: var(--deep-muted); }
.dock .ask.open .composer-hint, .dock .ask.open .ask-hint { color: var(--deep-muted); }
.dock .ask.open .send { background: var(--deep-brand); color: oklch(0.22 0.04 160); }
.dock .ask.open .suggestion { border-color: oklch(1 0 0 / 12%); color: var(--deep-muted); }
.dock .ask.open .ask-out { background: oklch(0 0 0 / 14%); }
.dock .ask.open .ask-note, .dock .ask.open .ask-what { color: var(--deep-muted); }
.dock .ask.open .ask-list a { color: var(--deep-brand); }
.dock .ask.open .ask-close { color: var(--deep-muted); }

/* The panel is laid out at full height and clipped, so opening is a
   transition rather than a jump. */
.ask-panel {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition:
    grid-template-rows var(--motion-slow) var(--ease-out),
    opacity var(--motion-base) var(--ease-out);
}

/* One child, or the rest land in auto-sized implicit rows and the
   panel never collapses. */
.ask-panel-inner { overflow: hidden; min-block-size: 0; }
.ask.open .ask-panel { grid-template-rows: 1fr; opacity: 1; }

/* The landing does not scroll, so an answer longer than the view would be
   unreachable. The plate scrolls inside itself instead — one scroller, not two,
   which is why the panel is not capped as well. */
.wide .first-screen .hero { max-block-size: 100%; overflow-y: auto; }

.ask-hint {
  margin: var(--space-4) 0 0;
  color: var(--deep-muted);
  font-size: var(--text-meta);
  line-height: var(--leading-prose);
}

.ask-close {
  position: absolute;
  inset-block-start: calc(var(--space-12) * -1);
  inset-inline-start: 0;
  inline-size: 36px;
  block-size: 36px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--deep-muted);
  font-size: var(--text-lead);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-base) var(--ease-out);
}

.ask.open .ask-close { opacity: 1; pointer-events: auto; }

/* The hero steps back so the surface in front has room. */
.hero > .eyebrow, .hero > h1, .hero > .hero-lede {
  transition: opacity var(--motion-slow) var(--ease-out),
              transform var(--motion-slow) var(--ease-out);
}

.hero.asking > .eyebrow, .hero.asking > .hero-lede { opacity: 0.35; }
.hero.asking > h1 { opacity: 0.55; transform: scale(0.94) translateY(-6px); }

/* On the landing the plate is a bounded scroller, so the greeting gives back
   its spacing as well as its weight. */
.wide .hero { scroll-behavior: smooth; }
.wide .hero.asking > h1 { margin-block-end: 0; }
.wide .hero > h1 { transition: margin var(--motion-slow) var(--ease-out),
                               opacity var(--motion-slow) var(--ease-out),
                               transform var(--motion-slow) var(--ease-out); }

/* Collapsed they are chips; open they become a list of things to ask. */
.ask.open .suggestions {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
}

.first-screen .suggestions { margin-block-start: var(--space-4); }

.ask.open .suggestion {
  justify-content: flex-start;
  min-block-size: 44px;
  border-radius: var(--radius);
  font-size: var(--text-body);
  color: var(--deep-foreground);
}

/* ── the dock ──────────────────────────────────────────────────────
   A reading page still belongs to something you ask. The field follows
   you rather than living on one page. */

.dock {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 20;
  padding: var(--space-3) var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--background) 62%, transparent);
  pointer-events: none;
}

.dock > .ask { pointer-events: auto; }

/* Docked, the field sits on a surface rather than on a cedar band. */
.dock .ask { background: var(--surface-1); border-color: var(--border); box-shadow: var(--lift-2); }
.dock .ask.open { background: var(--surface-1); }
.dock .composer { background: var(--background); border-color: var(--border); }
.dock .composer:focus-within { border-color: var(--ring); box-shadow: 0 0 0 3px oklch(0.55 0.08 160 / 18%); }
.dock .composer textarea { color: var(--foreground); }
.dock .composer textarea::placeholder { color: var(--muted-foreground); }
.dock .composer-hint { color: var(--muted-foreground); }
.dock .send { background: var(--brand); color: var(--background); }
.dock .ask-hint, .dock .suggest-label { color: var(--muted-foreground); }
.dock .suggestion { border-color: var(--border); color: var(--muted-foreground); }
.dock .ask.open .suggestion { color: var(--foreground); }
.dock .ask-close { color: var(--muted-foreground); }
.dock .ask-out { background: var(--muted); }
.dock .ask-out.thinking, .dock .ask-out.refused,
.dock .ask-sources, .dock .ask-note, .dock .ask-what { color: var(--muted-foreground); }
.dock .ask-list a { color: var(--brand); }

/* So the dock never sits on top of the last line of an answer. */
body:not(.wide) main { padding-block-end: calc(var(--space-12) + 72px); }

@media (max-width: 420px) {
  .dock { padding-inline: var(--space-3); }
}

/* ── the first screen ──────────────────────────────────────────────
   One viewport: the mark, one line, the field. Everything that explains
   the product is below the fold, because a landing that explains before
   it asks is a marketing page with an input in it. */

/* Two views, snapped. Not free scroll: the page moves a whole screen at a
   time, so it is still one view at any moment. The valley is fixed behind
   both, which is why the road stands in the same wood as the ask. */
.wide { scroll-snap-type: y mandatory; }
.wide body { block-size: auto; overflow-x: hidden; }
.wide .first-screen, .wide .second-screen { scroll-snap-align: start; }

.wide .valley {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.wide .valley .deer { pointer-events: auto; }

.first-screen {
  display: grid;
  /* an auto track sizes to max-content and overflows a narrow viewport: the
     ask row's intrinsic width held the plate at 433px inside 358px of phone */
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  block-size: 100svh;
  /* the chrome floats over the scene, so the plate clears it rather than the
     band starting below it */
  padding-block: clamp(5rem, 11vh, 7rem) clamp(4rem, 9vh, 5.5rem);
  /* the plate floats in the clearing; edge to edge it reads as a band */
  padding-inline: var(--space-4);
}

.first-screen .hero { inline-size: 100%; }

/* A greeting, not a billboard: the composer is what the reader came for, so
   the line above it stays smaller than the field it introduces. */
.first-screen h1 {
  margin: 0 0 var(--space-6);
  font-size: clamp(1.5rem, 3.4vw, 2.1rem);
  line-height: 1.35;
}

.hero-note {
  margin: var(--space-6) 0 0;
  color: var(--deep-muted);
  font-size: var(--text-meta);
  line-height: var(--leading-prose);
}

.ask.open ~ .hero-note { opacity: 0; }

/* The one line that has to be on the landing and is not the ask. */
.scene-foot {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  margin: 0;
  padding: var(--space-3) var(--space-4) var(--space-4);
  text-align: center;
  color: var(--deep-foreground);
  font-size: var(--text-meta);
  z-index: 2;
  background: linear-gradient(to top, rgb(14 33 25 / 72%), rgb(14 33 25 / 0%));
}


/* The field is the page, so it is bigger here than anywhere else. */
.first-screen .composer textarea { font-size: max(1rem, var(--text-lead)); }

/* ── the valley ────────────────────────────────────────────────────
   The painting is the ground of the first screen. Text never sits on it:
   scripts/scrim.py --band hero: a 65% wash of --cedar-deep would clear
   4.5:1 at the worst pixel, but 65% over the whole frame flattens the wood
   to one colour. The plate buys the same contrast locally and leaves the
   scene alone, which is the trade. */

.valley {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.valley-svg {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}

/* ── the scene is alive ────────────────────────────────────────────
   Nothing here follows the pointer. The valley has its own weather, and it
   answers a tap: a deer bolts, a tree sheds. */

/* A deer is a control, so it says so and takes focus like one. No hover
   state: the herd should not flinch at a pointer crossing the window. */
.deer {
  cursor: pointer;
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

/* :focus as well as :focus-visible — a deer that has bolted is invisible and
   aria-hidden, and Chrome was still drawing a focus ring around its bounding
   box: an empty white rectangle standing where the deer used to be. */
.deer:focus, .deer:focus-visible { outline: none; }
.deer:focus-visible .deer-shadow { opacity: 0.3; }

/* Each one on its own clock, so the herd never breathes in unison. */
@media (prefers-reduced-motion: no-preference) {
  .deer { animation: graze calc(5.5s * var(--dash, 1)) ease-in-out infinite alternate; }
}

@keyframes graze {
  from { transform: translateY(0) rotate(0deg); }
  to   { transform: translateY(-1.5px) rotate(0.5deg); }
}

/* Startled, it goes — and stays gone. A herd that trots back to its mark is
   scenery; one that remembers being disturbed is a place. */
.deer.fled {
  animation: bolt 2.8s cubic-bezier(0.32, 0, 0.2, 1) forwards;
  pointer-events: none;
}

@keyframes bolt {
  0%   { transform: translateX(0) translateY(0); }
  10%  { transform: translateX(10px) translateY(-4px); }
  100% { transform: translateX(760px) translateY(-26px); opacity: 0; }
}

.deer-legs { transform-box: fill-box; transform-origin: 50% 8%; }
.deer.fled .deer-legs { animation: trot 0.2s linear infinite; }
.deer.fled .deer-legs-far { animation-delay: 0.1s; }

@keyframes trot {
  0%, 100% { transform: rotate(16deg); }
  50%      { transform: rotate(-16deg); }
}

/* Wind. Each tree carries its own period and direction from the generator, so
   the canopy never pulses in unison, and the gust below leans the whole wood
   at once on a slower cycle — which is what makes it read as weather rather
   than as every tree fidgeting on its own. */
.cedar, .birch {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

@media (prefers-reduced-motion: no-preference) {
  .cedar { animation: sway var(--period, 9s) ease-in-out infinite alternate; }
  .birch { animation: sway calc(var(--period, 9s) * 0.8) ease-in-out infinite alternate; }
  .trees-mid, .trees-near { animation: gust 17s ease-in-out infinite alternate; }
  .trees-far { animation: gust 21s ease-in-out infinite alternate; }
}

@keyframes sway {
  from { transform: rotate(calc(var(--sway, 0) * -0.5deg)); }
  to   { transform: rotate(calc(var(--sway, 0) * 0.5deg)); }
}

@keyframes gust {
  0%, 100% { transform: skewX(0deg); }
  38%      { transform: skewX(-0.5deg); }
  72%      { transform: skewX(0.35deg); }
}

/* Tapped, a tree shakes once and drops a few leaves. */
.cedar, .birch { cursor: pointer; }
.cedar.shaken, .birch.shaken { animation: shaken 0.7s cubic-bezier(0.2, 0.8, 0.3, 1); }

@keyframes shaken {
  0%   { transform: rotate(0deg); }
  22%  { transform: rotate(1.6deg); }
  48%  { transform: rotate(-1.1deg); }
  74%  { transform: rotate(0.5deg); }
  100% { transform: rotate(0deg); }
}

/* Leaves come down on their own clocks, so the air is never empty and never
   busy. Positioned by the generator; the fall is entirely CSS. */
.leaf {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: fall var(--fall, 14s) linear var(--wait, 0s) infinite;
  opacity: 0;
}

@keyframes fall {
  0%   { transform: translate(var(--x), var(--y0, -40px)) rotate(0deg); opacity: 0; }
  8%   { opacity: 0.85; }
  88%  { opacity: 0.7; }
  100% { transform: translate(calc(var(--x) + var(--drift)), var(--y1, 1420px))
                    rotate(var(--spin)); opacity: 0; }
}

/* A shed leaf runs once and is removed; it must not loop like the ambient ones. */
.leaf-shed { animation-iteration-count: 1; }

@media (prefers-reduced-motion: reduce) {
  .leaf { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .cedar, .birch, .trees-mid, .trees-near, .trees-far { animation: none; }
  .layer { transition: none; transform: none; }
  .deer, .deer.fled, .deer.fled .deer-legs { animation: none; }
  .deer.fled { opacity: 0; }
}

/* A light wash for unity, not for contrast — the plate does contrast. */
.first-screen::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    to bottom,
    rgb(14 33 25 / 34%),
    rgb(14 33 25 / 14%) 42%,
    rgb(14 33 25 / 52%)
  );
  pointer-events: none;
}

.first-screen .band-inner { z-index: 1; }

/* The plate. Everything the reader has to read lives on it. */
.first-screen .hero {
  max-inline-size: 42rem;
  padding: clamp(1.5rem, 4vw, 2.75rem) clamp(1.25rem, 3.5vw, 2.5rem);
  border-radius: calc(var(--radius) + 10px);
  /* 92%: at 88% the accent fell to 4.10:1 where the plate crossed the
     painting's lightest pixel. Measured, not chosen. */
  background: var(--glass);
  border: 1px solid var(--glass-rim);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lift);
}

/* No backdrop-filter means no blur, and the measurement that allowed 76%
   assumed the blur. Without it the panel has to carry the contrast itself. */
@supports not (backdrop-filter: blur(2px)) {
  .first-screen .hero, .second-screen .road, .ask.open { background: rgb(20 47 36 / 94%); }
}


/* ── the way through ───────────────────────────────────────────────
   The second view. Same wood, different question: not "what do you want to
   ask" but "where are you standing". */

.second-screen {
  position: relative;
  display: grid;
  place-items: center;
  block-size: 100svh;
  padding: clamp(5rem, 11vh, 7rem) var(--space-4) clamp(3rem, 7vh, 4.5rem);
  background: transparent;
}

.second-screen .road {
  inline-size: 100%;
  max-inline-size: 58rem;
  max-block-size: 100%;
  overflow-y: auto;
  padding: clamp(1.25rem, 3.5vw, 2.25rem);
  border: 1px solid var(--glass-rim);
  border-radius: calc(var(--radius) + 14px);
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lift);
}

.second-screen h2 { margin: 0 0 var(--space-2); font-size: var(--text-title); }
.road-lede { margin: 0 0 var(--space-6); color: var(--deep-muted); }

/* On a wide screen a single column of five cards is a tall thin scroller —
   the shape of a phone, rendered on a desktop. Two columns, with the written
   stage spanning both because it carries the road inside it. */
.stages { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; }

@media (width >= 52rem) {
  .stages { grid-template-columns: 1fr 1fr; }
  .stage.live { grid-column: 1 / -1; }
  .stage.live .stage-road {
    grid-template-columns: 1fr 1fr;
    column-gap: var(--space-6);
  }
}

/* An unwritten stage is a title, a line and a label — three stacked lines was
   a third of the panel spent saying nothing yet. */
.stage:not(.live) {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  column-gap: var(--space-3);
}

/* Explicit, because auto-placement put the label on a third row and the
   point of the grid was to save that row. */
.stage:not(.live) h3 { grid-area: 1 / 1; }
.stage:not(.live) .stage-pending { grid-area: 1 / 2; margin-block-start: 0; }
.stage:not(.live) .stage-what { grid-area: 2 / 1 / 3 / -1; }

.stage {
  padding: var(--space-3) var(--space-4);
  border: 1px solid oklch(1 0 0 / 10%);
  border-radius: var(--radius);
  background: oklch(0 0 0 / 10%);
}

.stage h3 { margin: 0; font-size: var(--text-body); }
.stage-what { margin: var(--space-1) 0 0; color: var(--deep-muted); font-size: var(--text-meta); }

.stage.live { border-color: oklch(1 0 0 / 22%); background: oklch(0 0 0 / 18%); }

.stage-pending {
  display: inline-block;
  margin-block-start: var(--space-2);
  color: var(--deep-muted);
  font-size: var(--text-meta);
  opacity: 0.75;
}

.stage-road { margin: var(--space-3) 0 0; padding: 0; list-style: none; display: grid; gap: var(--space-2); }
.stage-road li { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.stage-road a { color: var(--deep-brand); }
.stage-road .stage-pending { margin: 0; font-size: var(--text-meta); }

/* The way down. Not a scroll cue — a thing to do, which is what the two chips
   under the field were pretending to be. */
.cta-browse {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-5);
  min-block-size: 44px;
  padding-inline: var(--space-4);
  border: 1px solid oklch(1 0 0 / 18%);
  border-radius: 999px;
  color: var(--deep-foreground);
  text-decoration: none;
  font-size: var(--text-meta);
  transition: background-color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out);
}

.cta-browse:hover, .cta-browse:focus-visible {
  background: oklch(1 0 0 / 10%);
  border-color: oklch(1 0 0 / 30%);
}

.cta-arrow { color: var(--deep-brand); }

/* Collapsed, the prompts are not the invitation — the CTA is. They belong to
   the sheet, where someone is already writing. */
.first-screen .ask:not(.open) .suggestions { display: none; }

/* ── the reader ────────────────────────────────────────────────────
   A station or an answer, opened over the valley instead of replacing it.
   Same glass as everything else that sits on the scene. */

.reader {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  place-items: end center;
  padding: clamp(3rem, 7vh, 5rem) var(--space-3) 0;
  overflow: hidden;
}

@media (width >= 48rem) {
  .reader { place-items: center; padding: clamp(2rem, 5vh, 3.5rem) var(--space-4); }
}

.reader[hidden] { display: none; }

body.reading::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 65;
  background: rgb(8 20 15 / 40%);
  backdrop-filter: blur(7px) saturate(115%);
  animation: fade var(--motion-base) var(--ease-out);
}

.reader-inner {
  position: relative;
  inline-size: 100%;
  max-inline-size: 48rem;
  max-block-size: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: clamp(1rem, 3.5vw, 2.75rem);
  border: 1px solid var(--glass-rim);
  border-block-end: 0;
  border-radius: calc(var(--radius) + 20px) calc(var(--radius) + 20px) 0 0;
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 oklch(1 0 0 / 14%), 0 -30px 90px rgb(8 20 15 / 52%);
  color: var(--deep-foreground);
  animation: rise var(--motion-slow) var(--ease-out);
}

@media (width >= 48rem) {
  .reader-inner {
    max-inline-size: min(94vw, 60rem);
    max-block-size: 100%;
    border-block-end: 1px solid var(--glass-rim);
    border-radius: calc(var(--radius) + 22px);
    box-shadow: var(--glass-lift);
    animation: expand 380ms cubic-bezier(0.2, 0.9, 0.25, 1);
  }
}

/* Focus lands on the heading so a screen reader starts at the top of what
   just opened. It is a reading position, not a control, so it does not wear
   a focus ring — that ring was the pale box around every title. */
.reader-body [tabindex="-1"]:focus { outline: none; }

/* Not floated. `container-type` on the body makes it an independent
   formatting context, so it shrank to clear the float and every reading page
   lost 42px of measure with a gap where the button was. */
.reader-close {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  display: block;
  margin-inline-start: auto;
  margin-block-end: calc(var(--space-2) * -1);
  inline-size: 40px;
  block-size: 40px;
  border: 0;
  border-radius: 50%;
  background: oklch(1 0 0 / 8%);
  color: var(--deep-muted);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.reader-close:hover { background: oklch(1 0 0 / 14%); color: var(--deep-foreground); }

.reader-wait { color: var(--deep-muted); }

/* The reading pages are written for a light page. Rather than restate every
   component in deep colours, the tokens themselves are remapped here — one
   block instead of a list that goes stale the next time a page grows a
   surface. */
.reader-body {
  --background: transparent;
  --surface-1: oklch(0 0 0 / 16%);
  --surface-2: oklch(0 0 0 / 10%);
  --soft: oklch(0 0 0 / 14%);
  --muted: oklch(0 0 0 / 16%);
  --foreground: var(--deep-foreground);
  --muted-foreground: var(--deep-muted);
  --border: oklch(1 0 0 / 12%);
  --brand: var(--deep-brand);
  --lift-1: none;
  --lift-2: 0 10px 30px rgb(8 20 15 / 30%);
  color: var(--deep-foreground);
}

/* Remapping tokens only reaches components that use them. A link that was
   never given a colour falls back to the browser's blue, which is how the
   breadcrumb ended up the one blue thing in the valley. */
.reader-body a { color: var(--deep-brand); }

/* A reading page is written inside <main>, which gives it its measure. The
   router injects the children, so the measure has to be restated here or the
   text runs the full width of a 72rem panel. */
/* Pinned to the start edge, not centred. In Persian the eye expects the text
   to begin at the right edge of the panel; a column floating in the middle of
   a wide modal reads as a page that has lost its margin. The slack falls on
   the end side, where a reader is not looking for the start of a line. */
.reader-body {
  container-type: inline-size;
  max-inline-size: 46rem;
  margin-inline: 0 auto;
}

/* The thread rail pads one side only, which left the content sitting 44px off
   centre inside the panel. Matched on the end side so the panel is even. */
.reader-body .threaded { padding-inline-end: 44px; }

/* The walk responds to the panel, not the window: at 440px the viewport rule
   did not fire but the panel was only 340px wide and the two cards were
   three words to a line. */
@container (width < 30rem) {
  .walk { grid-template-columns: 1fr; }
  .walk-next { grid-area: 1 / 1; }
  .walk-prev { grid-area: 2 / 1; text-align: start; }

  /* 44px of rail on each side is 88px of a 360px panel — a quarter of the
     width spent on a decorative line, with the Finnish quote breaking every
     three words. The rail narrows with the panel. */
  .reader-body .threaded { padding-inline: 18px; }
  .threaded > .run::before, .threaded.no-run::before { inset-inline-start: -12px; }
}

@supports not (backdrop-filter: blur(2px)) {
  .reader-inner { background: rgb(20 47 36 / 94%); }
}

/* ── how it sounds ─────────────────────────────────────────────────
   A reader who has only seen "Kela" written says it wrong at the counter.
   The vowels are marked because that is the whole point — «کِلا», not «کلا». */

.say {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
}

.say-label {
  color: var(--muted-foreground);
  font-size: var(--text-meta);
}

.say-word {
  font-size: var(--text-lead);
  /* tracking stays at zero: spacing Persian breaks the joins, and a word
     carrying harakat is the last place to break them */
  letter-spacing: 0;
}

.say-inline {
  margin-inline-start: var(--space-2);
  color: var(--muted-foreground);
}

/* ── thinking out loud ─────────────────────────────────────────────
   The steps are the server's real work, in order. They stay on screen after
   the answer arrives, because "where did this come from" is the question this
   product exists to answer. */

.think {
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-meta);
  color: var(--deep-muted);
}

.think li {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.think li::before {
  content: "";
  flex: none;
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.5;
}

.think-now { color: var(--deep-foreground); }

@media (prefers-reduced-motion: no-preference) {
  .think-now::before { animation: pulse 1.1s ease-in-out infinite; }
}

@keyframes pulse {
  0%, 100% { opacity: 0.35; transform: scale(0.8); }
  50%      { opacity: 1; transform: scale(1.25); }
}

.said {
  font-size: var(--text-lead);
  line-height: var(--leading-prose);
  white-space: pre-wrap;
}

.said-source {
  margin: var(--space-4) 0 0;
  font-size: var(--text-meta);
  color: var(--deep-muted);
}

.said-verbatim { opacity: 0.8; }

.said-withdrawn {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border-inline-start: 2px solid var(--deep-brand);
  background: oklch(0 0 0 / 18%);
  font-size: var(--text-meta);
  color: var(--deep-muted);
}

.dock .think, .dock .said-source { color: var(--muted-foreground); }

/* ── when we have not written it ───────────────────────────────────
   A refusal is still a place to be. What we have comes first; the
   authorities stay reachable behind a disclosure, because offering them
   first is doing the reading and then handing the reader away. */

.refused-note { margin: 0; font-size: var(--text-lead); }

.refused-filed {
  margin: var(--space-2) 0 var(--space-4);
  color: var(--deep-muted);
  font-size: var(--text-meta);
}

.refused-lead { margin: 0 0 var(--space-2); color: var(--deep-muted); font-size: var(--text-meta); }

.refused-away {
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-3);
  border-block-start: 1px solid oklch(1 0 0 / 10%);
}

.refused-away summary {
  cursor: pointer;
  color: var(--deep-muted);
  font-size: var(--text-meta);
}

.refused-away summary::marker { color: var(--deep-brand); }
.refused-away[open] summary { margin-block-end: var(--space-2); }

.dock .refused-filed, .dock .refused-lead, .dock .refused-away summary { color: var(--muted-foreground); }

/* ── the road ──────────────────────────────────────────────────────
   Five steps in the order they have to happen. The one that is next is the
   only one anyone needs to read, so it is the only one that looks loud. */

.tasks {
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  counter-reset: step;
}

.task {
  padding: var(--space-3);
  border: 1px solid oklch(1 0 0 / 10%);
  border-radius: var(--radius);
  background: oklch(0 0 0 / 12%);
  transition: border-color var(--motion-fast) var(--ease-out),
              opacity var(--motion-fast) var(--ease-out);
}

.task label {
  display: flex;
  align-items: start;
  gap: var(--space-3);
  cursor: pointer;
}

.task-tick {
  flex: none;
  inline-size: 20px;
  block-size: 20px;
  margin-block-start: 2px;
  accent-color: var(--deep-brand);
}

.task-title { font-size: var(--text-body); line-height: var(--leading-ui); }

.task-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: var(--space-2) 0 0;
  padding-inline-start: calc(20px + var(--space-3));
  color: var(--deep-muted);
  font-size: var(--text-meta);
}

.task-who { color: var(--deep-brand); }

/* Why the order is not negotiable, where it can be read. */
.task-blocks::before { content: "می‌بنده: "; opacity: 0.7; }

.task.next { border-color: oklch(1 0 0 / 26%); background: oklch(0 0 0 / 20%); }
.task.done { opacity: 0.5; }
.task.done .task-title { text-decoration: line-through; }

.tasks-note {
  margin: var(--space-3) 0 0;
  color: var(--deep-muted);
  font-size: var(--text-meta);
}

.dock .task-who, .dock .tasks-note { color: var(--muted-foreground); }
