/* Percol — color.nanaba.app
 *
 * DESIGN.md governs this file. Every token below is copied from §4; the type
 * rules are §8, including the three Hangul corrections; the forbidden list is
 * §10 and it is the reason there is no gradient, no accent, no icon grid and no
 * uniform bubble radius anywhere in here.
 *
 * ## Why the fonts are fetched from a CDN, which the app is forbidden to do
 *
 * §8 bans runtime font fetching for MaruBuri and Pretendard. Read the reason it
 * gives: "The app is offline-first and the two faces it is actually set in must
 * never be fetched at runtime" — an aeroplane renders tofu and the product has
 * broken its main promise. A website has no such promise to break: there is no
 * page at all without the network, so a font that arrives with it costs
 * nothing that was ever guaranteed.
 *
 * The alternative was measured rather than assumed. The unsubsetted faces are
 * 3.3 MB (MaruBuri) and 1.5 MB (Pretendard) in the repo; the whole-file woff2s
 * on the CDN are 277 KB and ~300 KB. Both projects publish a dynamic-subset
 * stylesheet instead — 27 KB and 13 KB over the wire, after which the browser
 * fetches only the ~10 KB subsets whose unicode-range the page actually uses.
 * That is the cheapest correct answer, so it is the one taken. Do not "fix"
 * this by self-hosting the full files; that is a 600 KB regression.
 *
 * ## No dark mode
 *
 * §4 defers it, and §2.1 is why: colour judgement needs a neutral surround, and
 * the ground that satisfies it is paper. A dark palette here would be a second
 * ground, which §10 forbids by name. `color-scheme: only light` is therefore
 * deliberate.
 */

:root {
  /* §4. Four neutrals and hers. There is no brand accent in this product, and
   * the one `accent` token that exists is confined to the app's paywall
   * badge and may not appear on this site at all. Its hex is deliberately not
   * repeated here, so that grepping the repo for it finds only the one file
   * §4 allows it in. */
  --paper:     #F8F7F4;
  --surface:   #FFFFFF;
  --sunken:    #F0EEE9;
  --ink:       #171614;
  --ink-muted: #6F6B65;
  --line:      #E4E1DA;
  --ink-wash:  rgba(23, 22, 20, .08);

  /* §9. Base unit 4. */
  --gutter: 26px;
  --s-xs: 8px;
  --s-sm: 12px;
  --s-md: 20px;
  --s-lg: 32px;
  --s-xl: 48px;
  --s-section: 88px;

  /* §9. `card: 4` — printed cards are not bubbly. `control: 10`. Prints and
   * palette bands take no radius at all. There is no third value: a uniform
   * radius across every element is §10's "uniform bubble radius". */
  --r-card: 4px;
  --r-control: 10px;

  --display: "Maru Buri", "MaruBuri", Georgia, serif;
  --body: "Pretendard", "Pretendard Variable", -apple-system, BlinkMacSystemFont,
          "Segoe UI", system-ui, sans-serif;

  /* §8's Hangul rules are the default, because Korea is the wedge: no negative
   * tracking, barely any positive tracking, more leading than Latin. The Latin
   * locales relax the tracking below; nothing relaxes the leading. */
  --track-head: 0.04em;
  --lh-display: 1.32;
  --lh-body: 1.65;

  color-scheme: only light;
}

/* Latin-script locales get the Latin reflexes back — wide tracking on a small
 * uppercase label is a small-caps habit that reads as a Hangul word being
 * spelled out slowly (§8), so it is scoped rather than global. */
html[data-script="latin"] {
  --track-head: 0.18em;
}

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

html {
  background: var(--paper);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  font-weight: 400;
  line-height: var(--lh-body);
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* ---- Type ---------------------------------------------------------------- */

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 700;
  line-height: var(--lh-display);
  letter-spacing: 0;   /* §8: no negative tracking on Hangul. */
  margin: 0;
  text-wrap: balance;
}

/* Verdict scale (§8). One per page, in the hero or at the head of an essay. */
.t-verdict { font-size: clamp(2.15rem, 6.2vw, 4.25rem); }
.t-display { font-size: clamp(1.65rem, 3.4vw, 2.6rem); }
.t-sub     { font-size: clamp(1.2rem, 2vw, 1.5rem); }

p { margin: 0 0 1.15em; }
p:last-child { margin-bottom: 0; }

a { color: inherit; text-underline-offset: 0.22em; text-decoration-thickness: 1px; }
a:hover { text-decoration-color: var(--ink-muted); }

.lede {
  font-size: clamp(1.05rem, 1.6vw, 1.25rem);
  color: var(--ink-muted);
}

/* §6's running head, reused here as the site's only repeated label. */
.runhead {
  font-family: var(--body);
  font-weight: 700;
  font-size: 0.6875rem;
  line-height: 1.3;
  letter-spacing: var(--track-head);
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 var(--s-md);
}
html[lang="ko"] .runhead,
html[lang="ja"] .runhead,
html[lang="hi"] .runhead,
html[lang="ar"] .runhead { text-transform: none; }

.muted { color: var(--ink-muted); }
.measure { max-width: 34em; }
.measure-wide { max-width: 46em; }

/* ---- Shell --------------------------------------------------------------- */

.page { overflow-x: clip; }

.band {
  padding-inline: var(--gutter);
  margin-inline: auto;
  max-width: 1180px;
}

.section { padding-block: clamp(52px, 9vw, var(--s-section)); }
.section + .section { border-block-start: 1px solid var(--line); }

/* §10: no centred-everything layout. Copy is ranged left (or right, in ar) and
 * sections are built on an asymmetric grid rather than a stack of centred
 * cards. */
.split {
  display: grid;
  gap: clamp(var(--s-lg), 5vw, 72px);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
@media (min-width: 900px) {
  .split { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
  .split--flip { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  .split--margin { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); }
}

/* ---- Header / footer ----------------------------------------------------- */

.masthead {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-md);
  align-items: baseline;
  justify-content: space-between;
  padding-block: var(--s-md);
  border-block-end: 1px solid var(--line);
}

.masthead nav {
  display: flex;
  gap: var(--s-md);
  font-size: 0.9375rem;
  color: var(--ink-muted);
}
.masthead nav a { text-decoration: none; }
.masthead nav a:hover { text-decoration: underline; }

/* §7. PERC(O)L — the O is an aperture sized to the cap height. Empty is the
 * mark's first state; on this site it holds one colour from the sample
 * analysed palette, which is the only chroma the wordmark is ever allowed. */
.wordmark {
  font-family: var(--body);
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.02em;
  white-space: nowrap;
}
.wordmark__aperture {
  display: inline-block;
  inline-size: 0.72em;
  block-size: 0.72em;
  border-radius: 50%;
  background: var(--aperture, transparent);
  box-shadow: inset 0 0 0 1px var(--ink);
  margin-inline: 0.06em 0.24em;
  translate: 0 0.02em;
}
.wordmark--lg { font-size: clamp(1.5rem, 4.4vw, 2.75rem); letter-spacing: 0.16em; }

.colophon {
  border-block-start: 1px solid var(--line);
  padding-block: var(--s-xl) var(--s-lg);
  color: var(--ink-muted);
  font-size: 0.9375rem;
}
.colophon ul { list-style: none; margin: 0; padding: 0; }
.colophon li { margin-block-end: var(--s-xs); }
.colophon__cols {
  display: grid;
  gap: var(--s-lg);
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  margin-block-end: var(--s-lg);
}

/* Locale switch: a plain list of endonyms, not a control. */
.locales { display: flex; flex-wrap: wrap; gap: var(--s-sm); }
.locales a[aria-current="true"] { color: var(--ink); font-weight: 700; text-decoration: none; }

/* ---- The print (§2.2, §3) ------------------------------------------------ */

/* White border, `line` hairline, near-square corners, generous margin. The
 * image itself is square-cornered; the border is the frame. */
.print {
  display: block;
  margin: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  padding: clamp(10px, 1.4vw, 18px);
}
.print img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  border-radius: 0;
}
.print figcaption {
  margin-block-start: var(--s-sm);
  font-family: var(--display);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-muted);
}

/* ---- Her palette (§4) ---------------------------------------------------- */

/* Full-bleed, butted edge to edge, no radius, no gaps. §10 forbids small round
 * chips, and §4 requires the palette to be the largest colour-bearing area on
 * any surface it appears on — which on this page it is, because nothing else
 * on the page carries colour at all. */
.palette {
  display: flex;
  inline-size: 100vw;
  margin-inline: calc(50% - 50vw);
  block-size: clamp(84px, 13vw, 168px);
}
.palette span { flex: 1 1 0; }
.palette--inline { inline-size: 100%; margin-inline: 0; block-size: clamp(56px, 7vw, 88px); }

.palette-note {
  margin-block-start: var(--s-sm);
  font-size: 0.8125rem;
  color: var(--ink-muted);
}

/* ---- Hero ---------------------------------------------------------------- */

.hero { padding-block: clamp(44px, 7vw, 84px) 0; }
.hero__headline { margin-block-end: var(--s-md); }
.hero__body { max-width: 30em; }
.hero__print { max-width: 420px; }
@media (min-width: 900px) { .hero__print { margin-block-start: 6px; } }

.hero__palette { margin-block-start: clamp(40px, 6vw, 72px); }

/* ---- Actions ------------------------------------------------------------- */

/* §10 forbids gradient CTA buttons. The system's only emphasis is weight, fill
 * and space (§4), so the primary action is a solid `ink` fill and the secondary
 * is a hairline. */
.action {
  display: inline-block;
  font-family: var(--body);
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.2;
  padding: 15px 26px;
  border: 1px solid var(--ink);
  border-radius: var(--r-control);
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  cursor: pointer;
  transition: background 140ms linear, color 140ms linear;
}
.action:hover { background: #000; }
.action--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}
.action--quiet:hover { background: var(--ink-wash); }

/* ---- The dossier (§6) ---------------------------------------------------- */

/* Eight folios, each composed as a margin-and-print row rather than as a card.
 * §6: "no shared card component, no repeated container" — continuity here comes
 * from the folio number, the margin and the caption voice, and the rows
 * alternate side so the section reads as a sequence rather than as a grid. */
.folio {
  display: grid;
  gap: clamp(var(--s-md), 3vw, var(--s-xl));
  grid-template-columns: minmax(0, 1fr);
  padding-block: clamp(var(--s-lg), 4vw, 56px);
  border-block-start: 1px solid var(--line);
  align-items: start;
}
.folio:first-of-type { border-block-start: 0; }
@media (min-width: 820px) {
  .folio { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  .folio:nth-child(even) > .folio__text { order: 2; }
  .folio:nth-child(even) > .folio__art  { order: 1; }
}
.folio__num {
  font-family: var(--display);
  font-weight: 700;
  font-size: 0.9375rem;
  color: var(--ink-muted);
  margin: 0 0 var(--s-xs);
  letter-spacing: 0;
}
.folio__name { font-size: clamp(1.35rem, 2.4vw, 1.85rem); margin-block-end: var(--s-sm); }
.folio__art { max-width: 480px; }

/* ---- Steps (§10: three steps in prose, no icons, not a three-column grid) - */

.steps { list-style: none; margin: 0; padding: 0; counter-reset: step; }
.steps li {
  counter-increment: step;
  display: grid;
  gap: var(--s-md);
  grid-template-columns: 2.75rem minmax(0, 1fr);
  padding-block: var(--s-lg);
  border-block-start: 1px solid var(--line);
}
.steps li:first-child { border-block-start: 0; padding-block-start: 0; }
.steps li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--display);
  font-weight: 700;
  font-size: 1rem;
  color: var(--ink-muted);
  padding-block-start: 0.35em;
}
.steps h3 { font-size: 1.25rem; margin-block-end: var(--s-xs); }

/* ---- Reading list -------------------------------------------------------- */

.readlist { list-style: none; margin: 0; padding: 0; }
.readlist > li { border-block-start: 1px solid var(--line); }
.readlist > li:first-child { border-block-start: 0; }
.readlist a {
  display: grid;
  gap: var(--s-sm) var(--s-lg);
  grid-template-columns: minmax(0, 1fr);
  padding-block: var(--s-lg);
  text-decoration: none;
}
@media (min-width: 760px) {
  .readlist a { grid-template-columns: 8.5rem minmax(0, 1fr); }
}
.readlist a:hover .readlist__title { text-decoration: underline; }
.readlist__meta { font-size: 0.8125rem; color: var(--ink-muted); letter-spacing: var(--track-head); text-transform: uppercase; }
html[lang="ko"] .readlist__meta,
html[lang="ja"] .readlist__meta,
html[lang="hi"] .readlist__meta,
html[lang="ar"] .readlist__meta { text-transform: none; }
.readlist__title { font-family: var(--display); font-weight: 700; font-size: clamp(1.2rem, 2vw, 1.5rem); line-height: var(--lh-display); margin-block-end: var(--s-xs); }
.readlist__dek { color: var(--ink-muted); max-width: 46em; }

/* ---- FAQ ----------------------------------------------------------------- */

.faq { margin: 0; }
.faq > div { padding-block: var(--s-lg); border-block-start: 1px solid var(--line); }
.faq > div:first-child { border-block-start: 0; }
.faq dt {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.15rem, 1.9vw, 1.4rem);
  line-height: var(--lh-display);
  margin-block-end: var(--s-sm);
}
.faq dd { margin: 0; max-width: 42em; color: var(--ink-muted); }

/* ---- Waitlist ------------------------------------------------------------ */

.waitlist form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-sm);
  margin-block-start: var(--s-md);
  max-width: 34em;
}
.waitlist input[type="email"] {
  flex: 1 1 15rem;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-control);
  padding: 14px 16px;
  min-inline-size: 0;
}
.waitlist input[type="email"]:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }
.waitlist__hp {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}
.waitlist__note { margin-block-start: var(--s-sm); font-size: 0.875rem; color: var(--ink-muted); }
/* 700, not 500: this site loads exactly two weights of Pretendard, and a third
 * would pull a whole extra set of Hangul subsets for one line of text. */
.waitlist__status { margin-block-start: var(--s-md); font-weight: 700; }
.waitlist__status:empty { display: none; }

:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ---- Essay --------------------------------------------------------------- */

.article { padding-block: clamp(36px, 5vw, 64px) 0; }
.article__head { max-width: 40em; }
.article__kicker { margin-block-end: var(--s-md); }
.article__title { margin-block-end: var(--s-md); }

/* Answer-first: the dek, set as the summary a reader (or a retrieval engine)
 * meets before anything else. */
.insummary {
  background: var(--sunken);
  border-radius: var(--r-card);
  padding: clamp(18px, 2.4vw, 26px);
  margin-block: var(--s-lg);
  max-width: 42em;
}
.insummary h2 {
  font-family: var(--body);
  font-weight: 700;
  font-size: 0.6875rem;
  line-height: 1.3;
  letter-spacing: var(--track-head);
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-block-end: var(--s-xs);
}
html[lang="ko"] .insummary h2,
html[lang="ja"] .insummary h2,
html[lang="hi"] .insummary h2,
html[lang="ar"] .insummary h2 { text-transform: none; }
.insummary p { font-size: 1.0625rem; }

.prose { max-width: 36em; font-size: 1.0625rem; }
.prose > p { margin-block-end: 1.3em; }
.prose > figure { margin-block: var(--s-xl); }

.article__lead { margin-block: var(--s-lg) var(--s-xl); max-width: 720px; }

.sources { margin-block-start: var(--s-section); border-block-start: 1px solid var(--line); padding-block-start: var(--s-lg); }
.sources ol { margin: 0; padding-inline-start: 1.4em; max-width: 44em; }
.sources li { margin-block-end: var(--s-sm); color: var(--ink-muted); font-size: 0.9375rem; line-height: 1.55; }

.breadcrumb { font-size: 0.8125rem; color: var(--ink-muted); padding-block: var(--s-md); }
.breadcrumb a { text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }

.morereading { margin-block-start: var(--s-section); }

/* ---- RTL ----------------------------------------------------------------- */

/* Almost nothing is needed: the layout is written in logical properties. The
 * exceptions are the two places a physical direction is meant. */
[dir="rtl"] .wordmark { direction: ltr; }
[dir="rtl"] .steps li::before { text-align: end; }

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

/* ---- Script fallbacks ---------------------------------------------------- */

/* §8 records the measurement: between them MaruBuri and Pretendard map NO
 * codepoint of Arabic or Devanagari, and no ideograph at all. The app's answer
 * was to fetch Noto at runtime, because Flutter's engine draws tofu when no
 * registered family covers a codepoint.
 *
 * A browser does not do that. It walks the CSS font stack and then the
 * platform's own fallback chain, and every platform this site is read on ships
 * a face for these three scripts. So the fallback here is a stack, not a
 * download — a real difference in the rendering model, not a corner cut. It
 * also keeps the site to a single font origin, which is what lets the CSP name
 * exactly one host.
 *
 * Order matters: MaruBuri and Pretendard stay first so that the Latin and the
 * digits in a mixed line are still set in the product's own faces, and only the
 * glyphs they do not carry fall through. */

html[data-script="arabic"] {
  --display: "Maru Buri", "SF Arabic", "Geeza Pro", "Noto Naskh Arabic",
             "Traditional Arabic", "Times New Roman", serif;
  --body: "Pretendard", "SF Arabic", "Segoe UI",
          "Noto Sans Arabic", "Geeza Pro", system-ui, sans-serif;
}

html[data-script="devanagari"] {
  --display: "Maru Buri", "Kohinoor Devanagari", "Noto Serif Devanagari",
             "Nirmala UI", Georgia, serif;
  --body: "Pretendard", "Kohinoor Devanagari",
          "Noto Sans Devanagari", "Nirmala UI", system-ui, sans-serif;
}

html[data-script="cjk"] {
  --display: "Maru Buri", "Hiragino Mincho ProN", "Yu Mincho", "Noto Serif JP",
             "Yu Gothic", serif;
  --body: "Pretendard", "Hiragino Sans", "Yu Gothic",
          "Noto Sans JP", system-ui, sans-serif;
}

/* ---- Line breaking -------------------------------------------------------- */

/* Korean breaks anywhere by default, because a browser treats Hangul like any
 * other CJK script and CJK has no spaces to break at. Korean does. Left alone,
 * a display line comes out as "한국이 실제 / 로 인증하는 것" — a word split down
 * the middle, which is the Hangul equivalent of hyphenating "read/ing" and
 * looks exactly as wrong. `keep-all` breaks at spaces only, which is what a
 * Korean typesetter does and what every Korean publication on the web sets.
 *
 * Japanese does NOT get it: with no spaces to fall back on, `keep-all` would
 * refuse to break a line at all and the text would run off the page. What
 * Japanese wants is the kinsoku rules, which is `line-break: strict`. */
html[lang="ko"] {
  word-break: keep-all;
  overflow-wrap: break-word;
}
html[lang="ja"] {
  line-break: strict;
}

/* ---- Locale switcher ----------------------------------------------------- */

/* A `<details>` menu, not a row of eight. Native disclosure means no
 * JavaScript, and every link sits in the HTML whether the menu is open or shut,
 * so a crawler that never clicks still sees all eight. The switcher used to
 * live only in the colophon, 97% down the page below the legal links, which for
 * a Korea-first product is the same as not being localised. */
.locales--masthead {
  position: relative;
  font-size: 0.8125rem;
}

.locales--masthead > summary {
  list-style: none;
  cursor: pointer;
  color: var(--ink-muted);
  padding: 4px 0;
  border-block-end: 1px solid var(--line);
  user-select: none;
}

.locales--masthead > summary::-webkit-details-marker { display: none; }

/* The caret. Turns when the menu opens, which is the only motion here. */
.locales--masthead > summary::after {
  content: "";
  display: inline-block;
  margin-inline-start: 0.5em;
  border: 3px solid transparent;
  border-block-start-color: currentColor;
  transform: translateY(2px);
}

.locales--masthead[open] > summary { color: var(--ink); }
.locales--masthead[open] > summary::after { transform: translateY(-1px) rotate(180deg); }
.locales--masthead > summary:hover { color: var(--ink); }

/* Raised on `surface` with a hairline, like every other lifted plane here.
 * Right-aligned so it opens inward from the masthead's end, and mirrored under
 * `dir="rtl"` by using logical properties rather than left/right. */
.locales__menu {
  position: absolute;
  inset-block-start: calc(100% + 6px);
  inset-inline-end: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-inline-size: 9rem;
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 4px 0;
}

.locales__menu a {
  padding: 6px 14px;
  text-decoration: none;
  color: var(--ink-muted);
  white-space: nowrap;
}

.locales__menu a:hover { background: var(--ink-wash); color: var(--ink); }

.locales__menu a[aria-current="true"] { color: var(--ink); font-weight: 700; }

/* Narrow screens: the masthead already wraps, so the menu drops in place
 * rather than overhanging the viewport edge. */
@media (max-width: 34rem) {
  .locales--masthead { align-self: flex-start; }
  .locales__menu { inset-inline-end: auto; inset-inline-start: 0; }
}

/* The hero CTA sits away from the paragraph above it. The lede runs to two
 * paragraphs and the button is the one thing on this screen to press, so it
 * gets air rather than trailing the copy as a third line. */
.hero__cta {
  margin-block-start: var(--s-lg);
}

/* ---- Section compositions ------------------------------------------------ */

/* §6: "No shared card component, no repeated container. Continuity comes from
 * the running head, the margin, and the caption voice. This is what lets every
 * page compose differently... it is the rule most likely to be violated during
 * implementation." It was. Five sections in a row were the same margin-split,
 * so they read as one section repeated. Each now composes on its own terms;
 * the type scale and the paper are what hold them together. */

/* How it works: three steps across, not a list beside a headline. */
.steps--band {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-xl);
  margin-block-start: var(--s-xl);
  list-style: none;
  padding: 0;
}

/* The band overrides the stacked `.steps` rules above: that variant lays each
 * row out as a 2-column grid with a `::before` counter, which here produced a
 * duplicated number and a title colliding with its body. */
.steps--band > li {
  display: block;
  padding-block: 0;
  border-block-start: 1px solid var(--line);
  padding-block-start: var(--s-md);
}

.steps--band > li:first-child { border-block-start: 1px solid var(--line); padding-block-start: var(--s-md); }
.steps--band > li::before { content: none; }
.steps--band h3 { margin-block: 6px 8px; }

.steps__num {
  font-family: var(--body);
  font-weight: 700;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Reading: the essays are photographed, so the section that sells them shows
 * the photographs. Three tiles, each a print, which is the §2.2 material. */
.reading__head { display: grid; gap: var(--s-sm); max-inline-size: 46rem; }

.readgrid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-xl);
  margin-block: var(--s-xl);
  list-style: none;
  padding: 0;
}

.readgrid a { text-decoration: none; color: inherit; display: block; }
.readgrid__art { margin: 0 0 var(--s-md); }
.readgrid a:hover .readgrid__title { text-decoration: underline; }

.readgrid__meta {
  font-family: var(--body);
  font-weight: 700;
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-block-end: 6px;
}

.readgrid__title { font-family: var(--display); font-size: 1.25rem; line-height: 1.32; }
.readgrid__dek { color: var(--ink-muted); margin-block-start: 6px; }

/* FAQ: two columns of questions under one headline, so it does not read as a
 * third margin-split in a row. */
.faq--cols {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-lg) var(--s-xl);
  margin-block-start: var(--s-xl);
}

.faq--cols > div { border-block-start: 1px solid var(--line); padding-block-start: var(--s-md); }

@media (max-width: 900px) {
  .steps--band, .readgrid, .faq--cols { grid-template-columns: 1fr; gap: var(--s-lg); }
}

/* ---- Before / after ------------------------------------------------------ */

/* The app's template-page comparison, on the web. Two pictures sharing one
 * rectangle, per `lib/ui/template_page.dart`: "it is not a fade between before
 * and after, it is two pictures sharing a rectangle."
 *
 * §4 admits no accent, so the handle is `ink` on `surface` with a `line`
 * hairline, which is what every other control in this system is made of.
 *
 * Nothing below the `.compare--ready` guard renders until `compare.js` runs, so
 * with no JavaScript the figure is exactly the single AFTER picture it was. */
/* `--pos` is a plain custom property, deliberately unregistered.
 *
 * It was registered with `@property { inherits: false }` while the keyframes
 * animated it. The keyframes now animate `clip-path` and `left` directly, so
 * the registration bought nothing -- and `inherits: false` silently broke the
 * drag: the script sets `--pos` on the figure, while the two elements that READ
 * it are children, so they stopped seeing the value and fell back to the
 * initial one. The comparison rendered, and nothing moved when you dragged it. */

/* `--pos` lives here, not in a `style=""` attribute: one inline style would
 * force `style-src 'unsafe-inline'` into the CSP for the whole site, and the
 * build asserts against exactly that. `compare.js` moves it through CSSOM,
 * which CSP does not restrict. */
/* Starts at 0%: the BEFORE half fully clipped, so the first paint is the AFTER
 * picture alone -- identical to what a crawler and a no-JS reader get, and no
 * flash of a half-drawn comparison before the script runs. */
.compare { position: relative; margin: 0; --pos: 0%; }

/* The wipe runs by itself, left to right and back, until the reader takes it.
 *
 * §9 says "Nothing floats persistently. Nothing bounces for personality." This
 * is persistent motion and it is an exception, taken at the founder's
 * instruction on 2026-08-29 after a once-only sweep was built first and judged
 * too easy to miss. It is written down rather than quietly done, so the rule
 * still reads as a rule.
 *
 * What keeps it from being the thing §9 was written against: it is the only
 * animation on the site, it carries the comparison the page is actually
 * selling rather than decorating a card, it stops for good the moment anyone
 * touches it, it is paused whenever the figure is off screen, and it does not
 * exist at all under reduced motion. */
/* The keyframes animate `clip-path` and `left` DIRECTLY, and deliberately do
 * not touch `--pos`.
 *
 * The first version animated the registered custom property and let
 * `clip-path: inset(0 calc(100% - var(--pos)) 0 0)` read it. That works in
 * Chromium and is not dependable elsewhere: it asks the engine to interpolate
 * a registered property AND re-resolve a `calc()` that consumes it inside
 * another property, every frame. Safari does not reliably do the second half,
 * so the comparison rendered correctly and simply never moved. Animating the
 * real properties needs no `@property` support at all.
 *
 * The two must stay in lockstep, so they share a duration, an easing and a
 * direction, and both start the moment `.compare--auto` is added. */
@keyframes compare-wipe {
  from { clip-path: inset(0 96% 0 0); }
  to { clip-path: inset(0 4% 0 0); }
}

@keyframes compare-knob {
  from { left: 4%; }
  to { left: 96%; }
}

.compare--auto .compare__before {
  animation: compare-wipe 3s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
}

.compare--auto .compare__handle {
  animation: compare-knob 3s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
}

/* Off screen it holds its frame rather than burning a compositor pass. Nine
 * comparisons wiping at once while eight are below the fold is work nobody
 * sees, and on a phone it is battery nobody agreed to spend. */
.compare--paused .compare__before,
.compare--paused .compare__handle { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  .compare--auto .compare__before,
  .compare--auto .compare__handle { animation: none; }
  .compare { --pos: 50%; }
}

/* No `overflow: hidden`. The before layer is bounded by its own `clip-path`
 * and `object-fit: cover`, so nothing here spills -- but the handle's knob is a
 * 30px circle centred on a 1px bar, and clipping the pair sliced it in half
 * every time the wipe reached either end of its travel. */
.compare__pair { position: relative; }
.compare__pair img { display: block; width: 100%; height: auto; }

.compare__before,
.compare__handle,
.compare__range,
.compare__labels { display: none; }

.compare--ready .compare__before {
  display: block;
  position: absolute;
  inset: 0;
  /* Clip from the right so the BEFORE half occupies the left of the frame,
   * which is the reading order the labels below promise. */
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

.compare--ready .compare__before img { height: 100%; object-fit: cover; }

.compare--ready .compare__pair { cursor: ew-resize; touch-action: none; }

/* Physical, not logical. The clip below is `inset(0 calc(100% - var(--pos)) 0 0)`,
 * which always eats from the RIGHT, so a logical `inset-inline-start` put the
 * handle at 70% while the seam sat at 30% under `dir="rtl"`. A before/after
 * wipe is a spatial control rather than a line of text: before is on the left
 * in every locale, and the seam and the handle agree because both are physical. */
.compare--ready .compare__handle {
  display: block;
  position: absolute;
  inset-block: 0;
  left: var(--pos);
  width: 1px;
  background: var(--surface);
  box-shadow: 0 0 0 1px rgba(23, 22, 20, 0.28);
  pointer-events: none;
}

.compare--ready .compare__handle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 30px;
  height: 30px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  /* `ink`, not `surface`. §4 gives `ink` to primary control fill, and the white
   * knob with a `line` border disappeared entirely wherever the picture behind
   * it was pale -- on a white studio backdrop the left half of the circle and
   * the chevron inside it were simply not there. A control that vanishes over
   * half its own travel is not a control. */
  background: var(--ink);
  box-shadow: 0 0 0 1px rgba(248, 247, 244, 0.9), 0 1px 4px rgba(23, 22, 20, 0.3);
}

/* A pair of chevrons, drawn rather than set: §10 forbids emoji in UI, and a
 * glyph would inherit the page's type and drift with it.
 *
 * Positioned against the handle rather than laid out inside the knob: the knob
 * is a `::after` pseudo-element and cannot contain a real child, so an SVG left
 * in normal flow rendered at the top of the full-height handle bar instead of
 * in the circle. It is placed on the same centre as the knob and stacked over
 * it. */
.compare--ready .compare__knob {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  color: var(--paper);
  pointer-events: none;
}

/* The real control. Visually the thin bar under the picture; it is what a
 * keyboard and a screen reader actually operate. */
/* The visible track is gone: the wipe demonstrates itself and the picture is
 * draggable, so a second control under every figure was clutter.
 *
 * It is hidden, NOT removed. The range is the only thing here a keyboard can
 * operate, and deleting it would make the comparison mouse-only. Clipped to a
 * pixel keeps it focusable and announced; `:focus-visible` brings it back so a
 * keyboard reader can see what they are driving. */
.compare--ready .compare__range {
  display: block;
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  accent-color: var(--ink);
}

.compare--ready .compare__range:focus-visible {
  position: static;
  inline-size: 100%;
  block-size: auto;
  margin: var(--s-sm) 0 0;
  clip-path: none;
}

.compare--ready .compare__labels {
  display: flex;
  /* Pinned LTR so BEFORE stays under the left half it names, matching the clip. */
  direction: ltr;
  justify-content: space-between;
  margin-block-start: 6px;
  font-family: var(--body);
  font-weight: 700;
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Generated by tools/build_site.ts — see swatchStylesheet(). The only
 * chroma on this site, and the reason the CSP needs no 'unsafe-inline'. */
:root{--aperture:#EC8377}
.sw-fdf6eb{background:#FDF6EB}
.sw-fdba93{background:#FDBA93}
.sw-ec8377{background:#EC8377}
.sw-ebc19b{background:#EBC19B}
.sw-c78d63{background:#C78D63}
.sw-f2dccb{background:#F2DCCB}
.sw-cfa98c{background:#CFA98C}
.sw-b4785f{background:#B4785F}
.sw-8a4a3c{background:#8A4A3C}
.sw-e8a33d{background:#E8A33D}
.sw-c9552a{background:#C9552A}
.sw-f2ce72{background:#F2CE72}
.sw-7a3b1e{background:#7A3B1E}
.sw-17100a{background:#17100A}
.sw-4e3117{background:#4E3117}
.sw-9b6a2e{background:#9B6A2E}
.sw-e6ce9c{background:#E6CE9C}
.sw-5a3a22{background:#5A3A22}
.sw-c98a3e{background:#C98A3E}
.sw-a8332b{background:#A8332B}
.sw-6b2334{background:#6B2334}
.sw-b8412e{background:#B8412E}
.sw-e0a458{background:#E0A458}
.sw-2f5e4e{background:#2F5E4E}
.sw-243a5e{background:#243A5E}
.sw-1b1512{background:#1B1512}
.sw-6b4a2f{background:#6B4A2F}
.sw-b98c63{background:#B98C63}
.sw-e8d6c4{background:#E8D6C4}
.sw-ede6d8{background:#EDE6D8}
.sw-d6c8a6{background:#D6C8A6}
.sw-8c9b8f{background:#8C9B8F}
.sw-2b2b2b{background:#2B2B2B}
.sw-2e3540{background:#2E3540}
.sw-7c8790{background:#7C8790}
.sw-b9ae9b{background:#B9AE9B}
.sw-5c4b3c{background:#5C4B3C}
.sw-13120f{background:#13120F}
.sw-6e6a62{background:#6E6A62}
.sw-b9b3a7{background:#B9B3A7}
.sw-f1ede4{background:#F1EDE4}
