/* wonderleaf — the field.
   Warm paper canvas, near-zero chrome. Everything that is not the art is
   10-11px, hairline, and warm ink so the paintings sit on the page. */

:root{
  --ink:#fbf6e9;
  --paper:#1a1d24;          /* warm off-white, never pure white */
  --paper-dim:rgba(26,29,36,.66);
  --paper-faint:rgba(26,29,36,.62);
  --hair:rgba(26,29,36,.20);
  --hair-soft:rgba(26,29,36,.11);
  --amber:#5a3825;
  /* A stack falls back per CHARACTER, so the CJK faces sit at the end of both
     families: Latin renders exactly as before, and Korean has a real face to
     land on instead of a row of tofu boxes. Same chain as app/hud.js and the
     approved language mockup. */
  --cjk:"Apple SD Gothic Neo","Malgun Gothic","Noto Sans CJK KR","Noto Sans KR","NanumGothic","Nanum Gothic","Hiragino Kaku Gothic ProN","Yu Gothic",Meiryo,"PingFang SC","Microsoft YaHei","Noto Sans CJK JP","Noto Sans CJK SC","WenQuanYi Zen Hei";
  --sans:ui-sans-serif,system-ui,-apple-system,"Helvetica Neue","Liberation Sans",Arial,var(--cjk),sans-serif;
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,"Liberation Serif","DejaVu Serif",Times,var(--cjk),serif;
  --accent:#d8952c;          /* the one amber the chrome is allowed */
  --chrome-h:34px;
}

/* A stack also falls back per LANGUAGE. Korean and Japanese share thousands of
   Han characters, so with the Korean faces first every kanji in a Japanese book
   is drawn with Korean glyph shapes: small per character (骨, 直, 次, 令, 海),
   unmistakable in a paragraph, and the widths move with the face. main.js keeps
   <html lang> in step with the language pill, so the document itself picks the
   stack. Same chains as app/hud.js. The default above is untouched, so English
   and Korean render exactly as they did. */
html[lang^="ja"]{
  --cjk:"Hiragino Sans","Hiragino Kaku Gothic ProN","Yu Gothic Medium","Yu Gothic","Noto Sans CJK JP","Noto Sans JP",Meiryo,IPAexGothic,IPAPGothic,IPAGothic,"Apple SD Gothic Neo","Malgun Gothic","Noto Sans CJK KR","Noto Sans KR","NanumGothic","Nanum Gothic","PingFang SC","Microsoft YaHei","Noto Sans CJK SC","WenQuanYi Zen Hei","Droid Sans Fallback";
  /* the reader's prose is HTML, so the browser applies 禁則処理 itself once the
     document declares Japanese; this asks it to keep whole words together too */
  word-break:normal;
  line-break:strict;
  overflow-wrap:break-word;
}

/* Simplified Chinese draws the same Han characters its own way again, so a
   Chinese reader gets Chinese faces first. Without this row Chinese prose fell
   through to the default above, which is Korean first, and every character was
   drawn with Korean shapes (builder V, translations/REPORT-zh.md section B).
   Same chain as app/hud.js CJK_ZH. */
html[lang^="zh"]{
  --cjk:"PingFang SC","Microsoft YaHei","Noto Sans CJK SC","Noto Sans SC","Source Han Sans SC","Hiragino Sans GB","Heiti SC","WenQuanYi Zen Hei","Droid Sans Fallback","Hiragino Sans","Yu Gothic","Noto Sans CJK JP",IPAexGothic,"Apple SD Gothic Neo","Malgun Gothic","Noto Sans CJK KR";
  /* the reader's prose is HTML, so the browser applies 避頭尾 itself once the
     document declares Chinese; a Chinese line may break between any two Han
     characters, so this only asks it not to split a Latin word */
  word-break:normal;
  line-break:strict;
  overflow-wrap:break-word;
}

*{box-sizing:border-box}

html,body{
  margin:0;padding:0;height:100%;width:100%;
  background:var(--ink);color:var(--paper);
  font-family:var(--sans);
  overflow:hidden;
  overscroll-behavior:none;
  -webkit-text-size-adjust:100%;
  -webkit-tap-highlight-color:transparent;
}

#gl,#hud{
  position:fixed;inset:0;width:100%;height:100%;display:block;
}
/* Exactly two resting cursors over the field, matching the reference
   (forensics/SPEC.md §1.3): `move` means the press will travel the world,
   `pointer` means a card is under you and will come with you. This is the only
   advance notice that a card is grabbable, and it is also the ground-truth
   oracle the acceptance benchmark uses to find cards. They must not be equal. */
#gl{z-index:0;touch-action:none;cursor:move}
#gl.is-panning{cursor:grabbing}
#gl.is-over-card{cursor:pointer}
#gl.is-carrying{cursor:grabbing}
/* the hud is paint only — every event belongs to the gl canvas */
#hud{z-index:1;pointer-events:none}

/* boot ------------------------------------------------------------------ */
#boot{
  position:fixed;inset:0;z-index:60;background:var(--ink);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:26px;
  transition:opacity .45s ease;
}
#boot span{font-size:13px;letter-spacing:.02em;color:var(--paper)}
#boot i{display:block;width:168px;height:1px;background:var(--hair);position:relative;overflow:hidden}
#boot i::after{
  content:"";position:absolute;inset:0;width:28%;background:var(--paper);
  animation:sweep 1.15s cubic-bezier(.4,0,.2,1) infinite;
}
@keyframes sweep{0%{transform:translateX(-100%)}100%{transform:translateX(460%)}}
#boot.gone{opacity:0;pointer-events:none}

/* fixed chrome ---------------------------------------------------------- */
#chrome{
  position:fixed;z-index:20;top:0;left:0;right:0;
  height:var(--chrome-h);
  display:flex;align-items:center;gap:13px;
  padding:0 11px;
  font-size:10.5px;letter-spacing:.01em;
  pointer-events:none;
}
/* the field runs under the chrome, so the chrome carries its own paper */
#chrome::before{
  content:"";position:absolute;left:0;right:0;top:0;height:calc(var(--chrome-h) + 34px);
  background:linear-gradient(180deg,rgba(251,246,233,.96) 0%,rgba(251,246,233,.86) 42%,rgba(251,246,233,0) 100%);
  pointer-events:none;
}
#chrome > *{pointer-events:auto;position:relative}

.mark{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.wordmark{font-size:12px;letter-spacing:-.005em;color:var(--paper)}
.wordmark b{color:var(--amber);font-weight:400}

.pill{
  appearance:none;background:transparent;color:var(--paper-dim);
  border:1px solid var(--hair);border-radius:999px;
  font:inherit;font-size:10px;letter-spacing:.02em;
  padding:3px 9px;cursor:pointer;line-height:1;
  /* A pill is one word on one line. Without this a flex row squeezes a wide
     label until it wraps: the Japanese `find` (さがす) broke into three stacked
     characters on a 390px phone and spilled out of the chrome band. nowrap also
     makes min-content the whole label, so the row overflows honestly and
     ui.js fitRow() spends the count instead. */
  white-space:nowrap;
  transition:color .16s ease,border-color .16s ease;
}
.pill:hover{color:var(--paper);border-color:var(--paper-faint)}
.pill.is-on{color:var(--ink);background:var(--paper);border-color:var(--paper)}
.pill:focus-visible{outline:1px solid var(--amber);outline-offset:2px}

/* the taxonomy matrix — plain text, no chips, no dropdowns */
/* padding-block + the same negative margin-block is a CLIP that is two
   pixels taller top and bottom while the box stays exactly where it was:
   the outer height is 11 + 4 - 4 = 11, so the flex centring in #chrome is
   unchanged, clientWidth and scrollWidth are unchanged, and the row still
   clips horizontally, which is the only thing `overflow:hidden` was for.
   Without it the 10px/11px line box cuts the dots off a CAPITAL umlaut:
   German capitalises every noun, so `Aeonen` on this row lost its umlaut
   and read as a different word (found by builder DE, 2026-09-03, in the
   German desktop shot; a lowercase o umlaut was never affected because
   its dots sit below the cap line). */
#taxonomy{display:flex;gap:11px;flex:1 1 auto;min-width:0;overflow:hidden;
  padding-block:2px;margin-block:-2px}
.axis{display:flex;align-items:baseline;gap:5px;white-space:nowrap}
.axis-label{color:rgba(26,29,36,.58);font-size:8.5px;letter-spacing:.06em;text-transform:lowercase}
.term{
  appearance:none;background:none;border:0;padding:0;margin:0;cursor:pointer;
  font:inherit;font-size:10px;color:var(--paper-dim);line-height:1.1;
  transition:color .14s ease;
}
.term:hover{color:var(--paper)}
.term[aria-pressed="true"]{color:var(--paper);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
.term:focus-visible{outline:1px solid var(--amber);outline-offset:2px}

.right{display:flex;align-items:center;gap:6px;flex:0 0 auto;margin-left:auto}
.count{font-size:10px;color:var(--paper-faint);font-variant-numeric:tabular-nums;min-width:25px;text-align:right}
.sep{color:rgba(26,29,36,.22);font-size:10px}

/* the zoom slider — SUB's thin line with a small handle, top right.
   It replaces the % readout: SUB's chrome states zoom as a position, not a
   number, and the row has no width to spare at 1440. */
#zoomread,.sep{display:none}
#zoomslider{
  -webkit-appearance:none;appearance:none;
  width:74px;height:16px;margin:0 2px;background:transparent;cursor:pointer;
}
#zoomslider::-webkit-slider-runnable-track{height:1px;background:var(--hair)}
#zoomslider::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:9px;height:9px;margin-top:-4px;border-radius:1px;
  background:var(--paper);border:0;
}
#zoomslider::-moz-range-track{height:1px;background:var(--hair)}
#zoomslider::-moz-range-thumb{width:9px;height:9px;border-radius:1px;background:var(--paper);border:0}
#zoomslider:focus-visible{outline:1px solid var(--amber);outline-offset:4px}

.only-narrow{display:none}

/* The row can never overflow and the taxonomy can never clip a term (D12).
   The language pill costs the row 43px on desktop and more in Korean, and the
   association view spends another pill on `clear`, so when the row is full the
   COUNT goes first: a reader who cannot reach the language selector is stuck,
   one without the count has lost a number they can see by looking. */
body.tight .count,body.tight .sep{display:none}
body.tight .right{gap:4px}
body.tight .langbtn{padding-left:7px;padding-right:7px}

/* And when spending the count is NOT enough. Between about 900 and 1200 css
   px the five-axis matrix alone is wider than the room the row has left, and
   `#taxonomy{overflow:hidden}` cut it — B12 section 5.2 measured `sehen`
   sliced through its own letters at 1032 css px in German, `mirar` down to
   one letter in Spanish, and a half-drawn Hangul jamo in Korean, with the
   bottom two axes unreachable at that width in every language. D12's law is
   that the matrix may never clip a TERM, so the matrix steps OFF the row
   into the sheet the `find` pill already opens: every axis reachable, every
   word whole, and no half letter anywhere. `stow` is set by ui.js fitRow()
   from a real measurement of the row, never from a breakpoint. */
body.stow #taxonomy{display:none}
body.stow .only-narrow{display:inline-flex;align-items:center}
/* an iPad is where this fires, and on an iPad the pill that now carries the
   whole matrix is a THUMB target, not a 10px desktop chip */
@media (pointer:coarse){
  body.stow .only-narrow{min-height:38px;padding:0 13px;font-size:11.5px}
}

/* language ---------------------------------------------------------------
   D17 / mockup A: a small pill at the far right of the row, with the other
   global controls, opening a short list in each language's own name. The
   current one carries the field's single accent, one amber dot. No flags. */
.lang{position:relative;display:flex;align-items:center;flex:0 0 auto}
.langbtn{font-family:var(--sans)}
.langbtn i{font-style:normal;color:rgba(26,29,36,.40);margin-left:5px;font-size:8px;vertical-align:1px}
#langlist{
  position:absolute;z-index:40;top:26px;right:0;padding:4px 0;min-width:174px;
  background:#fffdf3;border:1px solid var(--hair);border-radius:8px;
  box-shadow:0 16px 38px rgba(45,24,16,.17);
  animation:drop .18s cubic-bezier(.4,0,.2,1);
}
@keyframes drop{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
#langlist[hidden]{display:none}
#langlist button{
  appearance:none;background:none;border:0;width:100%;text-align:left;cursor:pointer;
  display:flex;align-items:center;gap:8px;padding:6px 12px;
  font:inherit;font-size:11.5px;color:var(--paper-dim);line-height:1.2;
}
#langlist button:hover{color:var(--paper);background:rgba(90,56,37,.055)}
#langlist button[aria-selected="true"]{color:var(--paper)}
#langlist button:focus-visible{outline:1px solid var(--amber);outline-offset:-2px}
#langlist button em{margin-left:auto;width:5px;height:5px;border-radius:99px;background:transparent;flex:0 0 auto}
#langlist button[aria-selected="true"] em{background:var(--accent)}
#langlist .note{
  display:block;margin:4px 0 0;padding:6px 12px 2px;border-top:1px solid var(--hair-soft);
  font-size:8.5px;line-height:1.4;letter-spacing:.03em;color:rgba(26,29,36,.5);
  white-space:nowrap;
}

/* search — a pill at rest; pressed, a hairline field in the row. Typing
   filters the field: matches grow into the grid, the rest fade (the filter
   law), so the field itself is the result. Founder pick 2026-09-03. */
.search{display:flex;align-items:center;gap:7px;flex:0 0 auto}
.search .pill.glass::before{
  content:"";display:inline-block;width:7px;height:7px;border:1px solid currentColor;
  border-radius:99px;margin-right:5px;vertical-align:-1px;
}
.sfield{
  display:flex;align-items:center;gap:9px;height:22px;padding:0 10px;margin:0;
  border:1px solid var(--hair);border-radius:999px;background:rgba(255,253,243,.92);
  min-width:260px;
}
.sfield[hidden]{display:none}
.sfield:focus-within{border-color:var(--paper-faint)}
.sfield input{
  appearance:none;-webkit-appearance:none;background:transparent;border:0;padding:0;margin:0;
  flex:1 1 auto;min-width:0;align-self:stretch;height:100%;
  font-family:var(--serif);font-size:12px;color:var(--paper);outline:none;
}
.sfield input::placeholder{color:rgba(26,29,36,.42)}
.sfield input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
.sfield .esc{margin-left:auto;font-size:9px;color:var(--paper-faint);letter-spacing:.06em;white-space:nowrap}
/* the open field takes the taxonomy's room: the row must never clip a term
   (no-overflow is a floor), so the matrix steps aside until Escape clears */
body.searching #taxonomy{display:none}
#result{
  position:fixed;z-index:15;left:12px;bottom:10px;margin:0;
  font-size:9.5px;letter-spacing:.03em;color:var(--paper-dim);
  padding:5px 9px;border-radius:3px;
  background:rgba(251,246,233,.86);border:1px solid rgba(26,29,36,.12);
}
#result[hidden]{display:none}

/* hint ------------------------------------------------------------------ */
/* A text-shadow cannot lift 9.5px text off a painting — over a bright card the
   legend and the card's own title below it read as one collided block. It is
   chrome, so it carries its own darkness the way #chrome and the scale control
   do, and stays legible wherever the field happens to have been dragged to. */
#hint{
  position:fixed;z-index:15;left:12px;bottom:10px;margin:0;
  font-size:9.5px;letter-spacing:.03em;color:rgba(26,29,36,.66);
  pointer-events:none;transition:opacity .5s ease;
  padding:5px 9px;border-radius:3px;
  background:rgba(251,246,233,.86);
  border:1px solid rgba(26,29,36,.12);
  text-shadow:none;
}
#hint.gone{opacity:0}

.dot{
  position:absolute;top:14px;right:16px;
  width:22px;height:22px;border-radius:999px;
  background:transparent;border:1px solid var(--hair);cursor:pointer;padding:0;
  display:grid;place-items:center;
}
.dot::before{content:"";width:6px;height:6px;border-radius:999px;background:var(--amber);opacity:.85}
.dot:hover{border-color:var(--paper-faint)}
.dot:focus-visible{outline:1px solid var(--amber);outline-offset:2px}

/* index ----------------------------------------------------------------- */
#indexview{
  position:fixed;z-index:25;inset:var(--chrome-h) 0 0 0;
  background:rgba(251,246,233,.97);overflow-y:auto;padding:26px 22px 60px;
}
#indexlist{column-width:330px;column-gap:34px;max-width:1180px;margin:0 auto}
.ix{
  break-inside:avoid;display:block;width:100%;text-align:left;
  background:none;border:0;border-bottom:1px solid var(--hair-soft);
  padding:9px 0;cursor:pointer;color:rgba(26,29,36,.80);
  font-family:var(--serif);font-size:13.5px;line-height:1.42;
}
.ix:hover{color:var(--paper)}
.ix:focus-visible{outline:1px solid var(--amber);outline-offset:1px}
.ix i{display:block;font-family:var(--sans);font-style:normal;font-size:9px;
  letter-spacing:.06em;color:rgba(26,29,36,.58);margin-top:3px}

/* taxonomy sheet (narrow) ----------------------------------------------- */
#taxsheet{
  position:fixed;z-index:35;left:0;right:0;bottom:0;
  background:rgba(251,246,233,.96);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-top:1px solid var(--hair);
  padding:14px 16px calc(18px + env(safe-area-inset-bottom));
  max-height:62vh;overflow-y:auto;
  animation:rise .26s cubic-bezier(.22,1,.36,1);
}
@keyframes rise{from{transform:translateY(20px);opacity:0}to{transform:none;opacity:1}}
.sheet-head{
  display:flex;align-items:center;justify-content:space-between;
  font-size:10px;letter-spacing:.07em;color:rgba(26,29,36,.62);
  margin-bottom:12px;position:relative;height:22px;
}
#taxsheet .dot{top:0;right:0}
/* a thumb target: the field is 44px tall and the input fills it */
#taxsheet .sfield{width:100%;min-width:0;height:44px;margin:0 0 16px;box-sizing:border-box;padding:0 14px}
#taxsheet .sfield input{font-size:15px}
#taxsheet-body{display:flex;flex-direction:column;gap:13px}
#taxsheet .axis{flex-wrap:wrap;white-space:normal;gap:6px 14px}
#taxsheet .axis-label{width:100%;font-size:9px}
#taxsheet .term{font-size:13.5px;padding:5px 0;min-height:24px}
/* FX1 (B12b section 5.4): #result is a body-level FIXED status line, built
   for the open field, at a hand-tuned bottom offset that assumes nothing
   else is on screen. The sheet is fixed too, at a HIGHER z-index and the
   full width, so #result rendered behind it whenever the sheet was open —
   exactly when a reader most needs the sentence that says what a search
   just did. ui.js setSheet() moves the SAME node in here, right after its
   own search field, while the sheet is open; this rule is what makes that
   position honest — in the sheet's own flow, in the sheet's own type, so it
   sits above the sheet's content by construction and never by a guessed
   offset that would have to keep winning against the sheet's real height. */
#taxsheet #result{
  position:static;z-index:auto;left:auto;right:auto;bottom:auto;
  margin:0 0 13px;padding:0;background:none;border:0;
  font-size:11.5px;line-height:1.4;letter-spacing:.02em;
}
#taxsheet #result[hidden]{display:none}

.read-pill{
  color:var(--ink)!important;background:var(--amber);border-color:var(--amber)!important;
  font-size:11.5px;padding:7px 15px;
}
.read-pill:hover{background:#f0b46a;color:var(--ink)}

/* the book ---------------------------------------------------------------
   Illustrations and prose as one flowing read on the night ground. The
   resting state of every scene is fully visible; the rise-in exists only
   for readers who allow motion. */
#reader{
  position:fixed;inset:0;z-index:40;background:#fbf6e9;
  overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
}
#reader .dot{position:fixed;top:14px;right:16px;z-index:5}
.rd-head{
  max-width:34em;margin:0 auto;padding:13vh 22px 4vh;text-align:center;
}
.rd-head .kicker{
  margin:0 0 14px;font-size:10px;letter-spacing:.07em;
  color:rgba(26,29,36,.64);text-transform:lowercase;
}
#reader-title{
  margin:0;font-family:var(--serif);font-weight:400;
  font-size:clamp(26px,4.2vw,40px);line-height:1.22;letter-spacing:-.008em;
  color:var(--paper);
}
.rd-scene{padding:7vh 0 2vh}
.rd-scene figure{margin:0 0 4.5vh;text-align:center}
.rd-scene img{
  display:block;margin:0 auto;width:min(92vw,960px);height:auto;
  border-radius:4px;
}
@media (min-resolution:1.5dppx){
  /* masters are 1000px wide — never render past their own pixels (×1.35) */
  .rd-scene img{width:min(92vw,672px)}
}
.rd-prose{max-width:34em;margin:0 auto;padding:0 22px}
.rd-prose h3{
  margin:0 0 18px;font-family:var(--serif);font-weight:400;font-style:italic;
  font-size:18.5px;line-height:1.4;color:var(--amber);
}
.rd-prose p{
  margin:0 0 1.25em;font-family:var(--serif);
  font-size:17.5px;line-height:1.72;color:rgba(26,29,36,.88);
}
.rd-foot{padding:6vh 0 14vh;text-align:center}
.rd-foot .pill{font-size:11px;padding:8px 18px}

/* the text effects (D30, 2026-09-08) -------------------------------------
 * "we had the text effects in previous version. Can we bring that back?"
 *
 * The previous reader — tools/explainer/static/reader.{css,js}, live in
 * production since 2026-05-07 and reworked for Reader 2026 on 2026-07-29 —
 * carried TWO layers over its prose, and both are here:
 *
 *   1. the prose ARRIVES. Each block fades up out of a 14px rise as the
 *      scene reaches the reader, on a 90ms ladder (the same ladder the
 *      explainer's own /learn pages ship). reader.css:2981 armed the
 *      picture and the paragraph separately, so this arms `figure` and each
 *      child of `.rd-prose` rather than the whole scene: one 14px move per
 *      element, never two stacked on top of each other.
 *   2. words carry a VOICE. Five inline classes the founder named in
 *      2026-05 — pop, marker, handnote, shout, stamp — set below. The sixth,
 *      rainbow, was killed by the founder on 2026-05-19 (commit 095a0dd,
 *      "visually dizzying") and is NOT revived.
 *
 * Three rules hold the whole thing honest:
 *   · the RESTING state is the readable one. Every hiding rule lives inside
 *     `prefers-reduced-motion: no-preference` AND behind `#reader.rv`, a
 *     class ui.js only adds when it has actually armed an observer — so a
 *     reader who asks for no motion, and a reader whose script never ran,
 *     both get the finished page with every word at full ink.
 *   · the voices are STATIC-FIRST: colour, size and tilt carry the meaning,
 *     the entrance only delivers it. Under reduced motion the animation is
 *     gone and the pose stays.
 *   · every colour is measured against this paper (#fbf6e9): amber 9.59:1,
 *     terracotta 6.87:1, the handnote grey 6.01:1 — all clear of the 4.5:1
 *     floor DOCTRINE #6 locks.
 */
.rd-prose .fx{display:inline-block}
/* a voice inside a voice keeps its paint and yields all geometry, or the
   inner word is tilted twice and sized 1.18 x 1.18 (reader.css:3106) */
.rd-prose .fx .fx{transform:none!important;animation:none!important;font-size:1em;margin:0}
.rd-prose .fx-pop{
  color:var(--amber);font-weight:600;transform:rotate(-1.5deg);
  text-shadow:0 1px 0 rgba(0,0,0,.04);
}
.rd-prose .fx-marker{
  /* The band is cut from the SPAN's own box, so that box has to be the
     word's height and not the paragraph's 1.72 leading — otherwise the
     highlighter paints through the middle of the letters instead of under
     them. The negative margin cancels the padding, so a highlighted phrase
     keeps its natural word spacing and the per-word bands (each word is its
     own atom so the line still wraps) read as one continuous stroke.
     D49 (2026-09-09): FX2 darkened this wash to a flat opaque #a38943 and
     added an ink baseline rule (box-shadow) to clear a 3:1 graphical-object
     floor. The founder saw it — "looks ugly, the previous version's text
     effects were better" — and D49 restores the wash and geometry to what
     TX1 shipped: the pale two-stop amber gradient, no colour override on
     the span's own text (it inherits the paragraph's ink), no baseline
     rule. Only FX2's OTHER change survives D49: `.fx-marker-end` below
     (EM1b's punctuation-boundary fix), which is geometry, not colour. */
  line-height:1.15;
  background-image:linear-gradient(180deg,
    transparent 0%,transparent 56%,
    rgba(233,196,96,.80) 56%,rgba(226,178,74,.72) 94%,transparent 98%);
  background-repeat:no-repeat;
  padding:0 .14em;margin:0 -.14em;border-radius:1px;font-weight:500;
}
.rd-prose .fx-marker.fx-marker-end{
  /* the overrun EM1b's addendum caught: the last word of a marked run
     bled its own .14em right-hand padding into whatever punctuation
     followed with no space of its own ("space*." -> the band ran under
     the full stop). The rule's extent — the wash's own background-image —
     is the span's own box, so zeroing the padding and the negative margin
     that pulls the next node in stops the wash at the last marked glyph,
     and nowhere past it. Only the LAST word-span of a marked run carries
     this class (ui.js fmtProse); every earlier word keeps the full padding
     so adjacent marked words still read as one continuous stroke. Kept
     across D49 (FX2's punctuation fix, not FX2's colour). */
  padding-right:0;margin-right:0;
}
.rd-prose .fx-handnote{
  /* the production faces are named first, so if the founder ever approves
     shipping them the voice lights up with no code change; nothing is
     fetched here, and the fallback keeps the size, the lean and the ink */
  font-family:'Caveat','Nanum Pen Script','Yomogi','Liu Jian Mao Cao',var(--serif);
  font-style:italic;font-size:1.06em;color:rgba(26,29,36,.70);
}
.rd-prose .fx-shout{
  font-family:'Bangers','Black Han Sans','Hachi Maru Pop','ZCOOL KuaiLe',var(--sans);
  font-weight:700;font-size:1.12em;letter-spacing:.02em;
  color:#9c2f18;transform:rotate(-2deg);
}
.rd-prose .fx-stamp{
  font-family:'Permanent Marker','Bagel Fat One','RocknRoll One','Ma Shan Zheng',var(--sans);
  font-weight:600;font-size:1.02em;color:var(--amber);transform:rotate(-2.5deg);
}
/* RM1 2026-09-09 (REPORT-EM2s3.md §9.2): every `.fx` atom above is its own
   inline-block, which the line-breaking algorithm treats as an atomic box
   with a break opportunity on both sides — even the side with no space at
   all, so a marked run's closing punctuation can wrap alone onto the next
   line when its last word lands at the right edge. `ui.js fmtProse` wraps
   the run's last word-span and any closing punctuation glued to it with no
   space into one `.fx-glue` unit; `nowrap` here is what keeps that pair on
   one line. It changes NOTHING about a voice's band, ink or extent — those
   stay exactly `.fx-pop`/`.fx-marker`/`.fx-marker-end`/etc.'s own rules,
   unmoved — this is layout only, and it applies to every voice, not only
   the marker (the finding was on a pop and a stamp, not a marker). */
.rd-prose .fx-glue{white-space:nowrap}
@media (prefers-reduced-motion:no-preference){
  #reader.rv .rd-scene figure,
  #reader.rv .rd-scene .rd-prose > *{
    opacity:0;transform:translateY(14px);
    transition:opacity .6s ease,transform .6s cubic-bezier(.16,1,.3,1);
    transition-delay:calc(min(var(--i,0),5) * 90ms);
  }
  #reader.rv .rd-scene.in figure,
  #reader.rv .rd-scene.in .rd-prose > *{opacity:1;transform:none}
  /* the highlighter draws itself on, a quarter-second after its paragraph
     lands (reader.css:3010) */
  #reader.rv .rd-prose .fx-marker{background-size:0% 100%;
    transition:background-size .7s cubic-bezier(.8,0,.2,1) .25s}
  #reader.rv .rd-scene.in .rd-prose .fx-marker{background-size:100% 100%}
  .rd-prose .fx-pop{animation:fx-pop-in .55s cubic-bezier(.34,1.56,.64,1) backwards}
  .rd-prose .fx-handnote{animation:fx-handnote-in .5s ease-out backwards}
  .rd-prose .fx-shout{animation:fx-shout-in .5s cubic-bezier(.34,1.56,.64,1) backwards}
  .rd-prose .fx-stamp{animation:fx-stamp-in .45s cubic-bezier(.34,1.56,.64,1) backwards}
}
@keyframes fx-pop-in{
  0%{transform:scale(.6) rotate(-12deg);opacity:0}
  60%{transform:scale(1.12) rotate(2deg);opacity:1}
  100%{transform:scale(1) rotate(-1.5deg);opacity:1}
}
@keyframes fx-handnote-in{
  0%{opacity:0;transform:translateY(-3px) rotate(-3deg)}
  100%{opacity:1;transform:translateY(0) rotate(0)}
}
@keyframes fx-shout-in{
  0%{transform:scale(.5) rotate(8deg);opacity:0}
  60%{transform:scale(1.18) rotate(-4deg);opacity:1}
  100%{transform:scale(1) rotate(-2deg);opacity:1}
}
@keyframes fx-stamp-in{
  0%{transform:scale(2) rotate(-15deg);opacity:0}
  70%{transform:scale(.92) rotate(-1deg);opacity:1}
  100%{transform:scale(1) rotate(-2.5deg);opacity:1}
}
@media (max-width:860px){
  #reader .dot{top:calc(10px + env(safe-area-inset-top))}
  /* D30: "When opening a wonder, can you make the text fit the width?"
     On a phone the prose now sets to the SAME measure as the painting above
     it, both computed from ONE gutter, so the text and the picture share an
     edge by construction and not by two numbers tuned until they matched
     (page-craft law, 2026-08-24). Measured before: a 22px prose gutter
     against the painting's 4vw left the column 6.4px inside the picture on
     each side at 390 — 346px of 390 (88.7%) against a 358.8px painting, and
     it reads as a narrower block of text. After: 358px of 390 (91.8%), 16px
     each side, 49 characters a line at 17px. Desktop is untouched: every
     rule here is inside the phone query. */
  :root{--rd-gutter:16px}
  .rd-head{padding:9vh var(--rd-gutter) 4vh}
  .rd-prose{max-width:none;padding:0 var(--rd-gutter)}
  .rd-scene img{width:calc(100vw - (var(--rd-gutter) * 2))}
  .rd-prose p{font-size:17px}
  .rd-foot{padding-bottom:calc(12vh + env(safe-area-inset-bottom))}
}

/* association view chrome ------------------------------------------------ */
/* The view carries no bottom-right affordance (founder verdict 2026-09-03):
   any tap opens that card's book, so the `clear` pill and Escape are the
   only chrome the association view needs. */

/* live status for the association view, rendered for assistive tech */
#selinfo{
  position:fixed;margin:0;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;
}

/* screen-reader / keyboard path ----------------------------------------- */
#a11y{position:fixed;z-index:5;inset:0;pointer-events:none}
#a11y button{
  position:absolute;width:1px;height:1px;padding:0;margin:0;border:0;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;
  background:transparent;color:transparent;
}
#a11y button:focus{pointer-events:auto}

/* ---------------------------------------------------------------- mobile */
@media (max-width:860px){
  :root{--chrome-h:56px}
  #chrome{gap:7px;padding:0 8px;padding-top:env(safe-area-inset-top)}
  #taxonomy,.search{display:none}
  .only-narrow{display:inline-flex}
  /* thumb-sized, and ordered so nothing has to share an edge */
  .pill{min-height:42px;padding:0 12px;font-size:11.5px;display:inline-flex;align-items:center}
  /* display:inline-flex above beats the UA's [hidden]{display:none}, which
     left the association view's `clear` pill on screen at all times and
     pushed `reset` off the right edge of a 390px phone. */
  .pill[hidden]{display:none}
  #tab-field,#zoomread,.sep{display:none}
  #tax-open{order:2}
  .right{order:3;gap:8px}
  .dot{width:42px;height:42px;top:10px;right:12px}
  .dot::before{width:8px;height:8px}
  #hint{display:none}
  #indexlist{column-width:auto;column-count:1}
  /* pinch owns zoom on touch; the slider is desktop chrome */
  #zoomslider{display:none}
  #langlist{top:46px;right:0;min-width:210px;padding:5px 0}
  #langlist button{font-size:15px;padding:11px 16px;min-height:44px}
  #langlist .note{font-size:10px;padding:8px 16px 3px}

  /* the result line keeps clear of the bottom edge, above the home
     indicator, so nothing on a phone can cover it */
  #result{left:10px;right:10px;bottom:calc(58px + env(safe-area-inset-bottom));font-size:11px}
}

/* --------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;animation-iteration-count:1 !important;transition-duration:.001ms !important}
  #boot i::after{animation:none;width:100%}
}

/* the paywall moment ------------------------------------------------------ */
/* THE PAGE CONTINUES — winner/evidence/paywall/mock-a.html, the composition in
   the image the founder chose (D30 and its 2026-09-08 amendment; the founder
   calls it B, the file is mock-a, and builders target the file).

   Composition C, which this replaces, put the card's painting full-bleed under
   a sheet that rose from the bottom edge. It read as a modal over the art. Here
   there is no modal and no scrim: page 1 really opens — kicker, title,
   question, the wonder's own painting, the first paragraph of its prose — and
   then the paper runs out. The offer is the NEXT BLOCK of the same page, in
   the same column, and nothing the reader tapped is covered.

   ONE number does it. `--pw-measure` sets the painting's width AND the offer
   column's width, so the offer is aligned to the art by construction and never
   by two values tuned until they matched (page-craft law, 2026-08-24).

   The tokens are the app's own, so this is the same object as the chrome and
   the reader, not a bolted-on billing screen. Note the file's inversion:
   --ink IS the paper and --paper IS the ink.

   Three contrast rules PW1 measured from pixels, and they are load-bearing:
     · --accent #d8952c is 2.36:1 on paper. It can never carry text or a
       control boundary — here it is a 3px rule on a card that carries its own
       --pw-edge border, and a 7px dot, nothing else.
     · --hair rgba(.20) is 1.51:1: a decorative rule, below the 3:1 floor for a
       control boundary. Every control here takes --pw-edge (.55 = 3.72:1).
     · the primary button is cream on --amber #5a3825, 9.60:1. */
#offer{
  --pw-edge:rgba(26,29,36,.55);      /* 3.72:1 — every control boundary   */
  --pw-dim:rgba(26,29,36,.70);       /* 6.01:1 — the floor for body copy  */
  --pw-strong:rgba(26,29,36,.88);
  /* the FLOOR for the price column, never the answer: paywall.js measures the
     widest localised price and widens this. See fitPriceColumn(). */
  --pricecol:92px;
  --pw-measure:calc(100vw - 32px);
  --pw-gutter:16px;
  position:fixed;inset:0;z-index:45;background:var(--ink);
  display:flex;flex-direction:column;overflow:hidden;
}
/* `display:flex` on an ID beats the user agent's `[hidden]{display:none}`, so
   without this line the closed offer stayed on screen over the field at
   z-index 45 and swallowed every press — the field looked alive and nothing
   could be selected (measured 2026-09-08, and the same trap the phone `.pill`
   rule above already carries a note about). */
#offer[hidden]{display:none}
@media (min-width:900px){
  /* D41: the painting is shown WHOLE, so the measure is the painting's own
     room. 76vw/860px was sized for a 190-290px band; a 3:2 plate at that
     width leaves the bottom half of a 1032x1376 tablet bare, which is the
     one objection release/screenshots/REPORT-IPAD-2026-09-09.md section 3.1
     raised and the founder repeated. 90vw/1100px lets the plate hold the
     frame instead of padding the offer with more copy — design RAG
     `raw/design-research-2026-07/mnmm.md` line 119: "whitespace >= 50% of
     any viewport; empty space between items is itself the luxury cue". */
  #offer{--pricecol:120px;--pw-measure:min(90vw,1100px);--pw-gutter:20px}
}

/* the exit: on screen at rest, the first tab stop and the last, and a real
   38px target rather than the chrome's 22px hairline dot */
#offer .dot{
  position:fixed;z-index:5;top:calc(12px + env(safe-area-inset-top));right:14px;
  width:38px;height:38px;background:rgba(251,246,233,.92);border-color:var(--pw-edge);
}
#offer .dot::before{width:7px;height:7px;background:var(--accent);opacity:1}
#offer .dot:hover{background:var(--ink);border-color:var(--paper)}
#offer .dot:focus-visible{outline:2px solid var(--amber);outline-offset:2px}

/* ---- page 1, at the reader's own size and rules ----------------------- */
/* THE PAGE IS THE ELASTIC HALF (D41). `flex:1 1 auto` here against the
   sheet's `flex:0 1 auto` means the painting absorbs every spare pixel of a
   tall viewport, so there is no bare band to distribute and no second number
   to tune (page-craft law, 2026-08-24: one shared measure, never two hand-fit
   values). It is also what answers the iPad objection: on 1032x1376 the plate
   grows instead of the paper going empty under the offer. */
.pw-page{
  flex:1 1 auto;min-height:0;overflow:hidden;
  display:flex;flex-direction:column;justify-content:center;
  padding:16px var(--pw-gutter) 4px;text-align:center;
}
@media (min-width:900px){.pw-page{padding-top:2.6vh}}
/* NO text-transform. The old sheet lower-cased this line, and German
   capitalises every noun: `du hast ein Wunder geöffnet` was painted as
   `du hast ein wunder geöffnet`, which is a spelling mistake in the reader's
   own language (measured in the 390x844 German shot, 2026-09-08 — the same
   class of bug as the capital umlaut the taxonomy row lost). The authored
   string decides its own case; English's is already lower case. */
.pw-kicker{
  margin:0 0 8px;font-size:10px;letter-spacing:.07em;
  color:var(--paper-dim);
}
.pw-title{
  margin:0 auto 4px;max-width:22em;font-family:var(--serif);font-weight:400;
  font-size:clamp(22px,2.3vw,30px);line-height:1.18;letter-spacing:-.008em;
  color:var(--paper);
}
.pw-ask{
  margin:0 auto 13px;max-width:26em;font-family:var(--serif);font-style:italic;
  font-size:clamp(13.5px,1.3vw,16.5px);line-height:1.35;color:var(--amber);
}
.pw-figure{
  margin:0;flex:1 1 auto;min-height:0;position:relative;width:100%;
}
/* THE WHOLE PAINTING, AT ITS OWN ASPECT — the founder's first and
   non-negotiable instruction in D41, said twice: "the first scene image show
   in full". What stood here was a BAND: a fixed 132-290px height with
   `object-fit:cover`, which is a crop however carefully its focal point is
   chosen. Everything about this rule is built so a crop is not expressible:

     · `aspect-ratio` is the CARD's own `ar`, written onto the element by
       paywall.js (`--pw-ar`), so the box is the picture's shape before a byte
       of the image has arrived and the layout never jumps;
     · `width:auto;height:auto` with `max-width`/`max-height` is the plain
       replaced-element fit — the picture shrinks whole, in ratio, and the
       only failure mode left is letterboxing, never a slice;
     · `object-fit:contain` is the belt to that brace: if `ar` ever disagreed
       with the file, the image letterboxes inside the box instead of filling
       and cutting it. `object-position` is gone with the crop it served.
     · absolute + `inset:0` + `margin:auto` inside the relative .pw-figure is
       what lets `max-height:100%` resolve against the space the figure really
       has — a flex item with an auto height cannot answer that question.

   THE CAP IS A WIDTH, NEVER A CROP. On a phone whose aspect would make the
   plate taller than 55% of the screen, `calc(55vh * var(--pw-ar))` takes the
   width down and the height follows; on wide viewports the ceiling is 72vh
   the same way. Both are `min()` terms on max-width: nothing here can ever
   remove a part of the picture. */
#pw-art{
  --pw-ar:1.5;                        /* the fallback; paywall.js sets it */
  position:absolute;inset:0;margin:auto;
  display:block;border-radius:4px;background:#e8dcc4;
  aspect-ratio:var(--pw-ar);
  width:auto;height:auto;
  max-width:min(100%, var(--pw-measure), calc(55vh * var(--pw-ar)));
  max-height:100%;
  object-fit:contain;
}
@media (min-width:900px){
  #pw-art{max-width:min(100%, var(--pw-measure), calc(72vh * var(--pw-ar)))}
}

/* PW4: THE MAT STOPS GROWING ONCE THE OFFER COLLAPSES.
 *
 * `.pw-figure{flex:1 1 auto}` is right for the OFFER pane — D41's whole
 * painting absorbs whatever the plan cards do not need. But the moment the
 * sheet collapses to the email pane or "check your email" (`show()` writes
 * the pane onto `#offer[data-pane]`), those two panes are a head, one row
 * and a line of fine print — a fraction of the plan cards' height. `.pw-sheet`
 * shrinks to fit that, `.pw-page` is still asking for every leftover pixel,
 * and `.pw-figure` is the one thing left to absorb it — a plate sitting in a
 * bare mat far taller than the plate itself, the same defect D41 fixed on
 * the tablet, reopened by a shorter pane. (Measured against PW3's own
 * after-annual shot at 390x844: see REPORT-PW4.md.)
 *
 * The whole-picture rule does not move: nothing below caps by cropping, only
 * `#pw-art`'s own width-only caps do that, and they are untouched. What caps
 * here is the ROOM the figure may ask for — tied to the viewport the same
 * way `#pw-art`'s own ceiling is, so a short phone and a tall tablet each get
 * a mat proportional to their own screen, never a fixed pixel count. `.pw-page`
 * stops stretching alongside it, and `#offer`'s `justify-content:center`
 * splits whatever height is left evenly above and below the page+sheet
 * block — the mat PW2 already established for the tablet's own slack,
 * applied here instead of poured into one figure. */
#offer[data-pane="email"] .pw-page,
#offer[data-pane="sent"] .pw-page{
  flex:0 1 auto;
}
#offer[data-pane="email"] .pw-figure,
#offer[data-pane="sent"] .pw-figure{
  flex:0 1 auto;min-height:0;
  height:min(34vh, 62vw);
}
@media (min-width:900px){
  #offer[data-pane="email"] .pw-figure,
  #offer[data-pane="sent"] .pw-figure{height:min(42vh, 46vw)}
}
#offer[data-pane="email"],
#offer[data-pane="sent"]{
  justify-content:center;
}

/* the prose starts and the paper is cut, not hidden: two lines at full ink
   and the third fading. `.rd-prose` is the reading page's own class, so this
   is the same serif, the same colour and the same D30 text effects the reader
   meets on the other side — the teaser cannot drift from the real page,
   because it IS the real page's rendering. */
/* PW5 (2026-09-10): `max-width` below is the WIDEST this column may be, not
   the width it always takes. When a paragraph comes out as a single line at
   this measure there is nothing for the fade to hold back, so paywall.js
   markCut() writes a narrower inline `max-width` — half the sentence's own
   ink — which turns it into two lines of its own text and lets the cap and
   the mask below work unchanged. No rule here moved; the inline value is
   removed again on every re-measure, so a resize starts from this one. */
.pw-teaser{
  --pw-lines:3;
  max-width:var(--pw-measure);margin:13px auto 0;padding:0;
  text-align:left;
  max-height:calc(var(--pw-lines) * 1.72em);overflow:hidden;
}
/* D40 (2026-09-09), founder: "Yes, always have it fade." The fade still
   means exactly one thing — there is more of this paragraph than is drawn —
   but paywall.js now GUARANTEES that is true: it measures the paragraph and
   caps the box one line short of it, so the mask always rests on real text
   and the reader always sees the page run out. `--pw-lines` below is only
   the pre-measurement default; markCut() writes an exact pixel height. */
.pw-teaser.is-cut{
  /* THE FADE RESTS ON THE LAST LINE THAT IS DRAWN, whichever line that is.
     58% was tuned for a three-line box: it holds line 1 and most of line 2 at
     full ink and fades line 3. Now that markCut() caps the box one line short
     of the paragraph, the box can be two lines or one, and a fixed 58% would
     have washed out half of the only line a short opening gets. `--pw-solid`
     is (drawn - 1) / drawn, written by markCut() from the same measurement
     that chose the height — one number, computed once, for both (page-craft
     law, 2026-08-24: never two hand-tuned values).

     PW6 (2026-09-10): those two numbers have ONE more source now, and it is
     still the same pair. When an emphasis mark would fall on the faded line
     or below the box, markCut() draws to the foot of the mark's own line plus
     a fraction of the next real line and sets `--pw-solid` to exactly that
     foot, so the mark is full ink and the fade rests on the following line
     instead. No rule here moved: the gradient is the same gradient, told a
     different stop. `--pw-lines` is still the ceiling on READABLE lines — the
     lift spills only the fade past it, never a fourth line the reader can
     read. See paywall.js markLift(). */
  -webkit-mask-image:linear-gradient(180deg,#000 0 var(--pw-solid,58%),transparent 99%);
          mask-image:linear-gradient(180deg,#000 0 var(--pw-solid,58%),transparent 99%);
}
.pw-teaser p{margin:0;font-size:15.5px}
@media (min-width:900px){.pw-teaser{max-width:min(34em,var(--pw-measure))}
  .pw-teaser p{font-size:16.5px}}
.pw-teaser[hidden]{display:none}

/* ---- the offer: the next block of the same page ------------------------ */
/* THE OFFER IS THE INELASTIC HALF (D41). It was `flex:1 1 auto` and took
   every pixel page 1 did not, which on a tablet meant 1250px of column for
   400px of copy — bare paper down to the foot. Now it asks for exactly the
   height its own words need (`flex:0 1 auto`), capped at 62vh so a short
   viewport still leaves the wonder on screen, and .pw-body stays a real
   scroller for the phone that genuinely does not fit. */
.pw-sheet{
  flex:0 1 auto;min-height:0;max-height:62vh;
  display:flex;flex-direction:column;overflow:hidden;
  background:var(--ink);border-top:1px solid var(--hair);
  padding:14px var(--pw-gutter) 0;
}
@media (min-width:900px){.pw-sheet{padding-top:18px}}
.pw-col{max-width:34em;margin:0 auto}
@media (min-width:900px){.pw-col{max-width:var(--pw-measure)}}
/* The foot is a FLEX SIBLING of the scroller, never sticky inside it: a
   sticky footer in a scroller is displaced upward by exactly the overflow and
   sits on the sheet's last lines at rest (PW1 measured that collision as a
   phantom 2.66:1). Out of the scroller the collision is impossible. */
.pw-body{flex:1 1 auto;min-height:0;overflow:auto}
.pw-foot{
  flex:0 0 auto;background:var(--ink);border-top:1px solid var(--hair-soft);
  padding:5px 0 calc(11px + env(safe-area-inset-bottom));
}
.pw-foot-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap}

.pw-lapsed{
  margin:0 0 9px;font-size:11.5px;line-height:1.5;color:var(--pw-strong);
  border-left:2px solid var(--accent);padding-left:9px;
}
/* FX1 (B12b section 5.2): at 428 css px, Spanish — the longest trial and
   grown-up paragraphs of the seven — overflowed .pw-body by 11px, exactly
   enough to slice the LAST line of .pw-micro in half against the foot's own
   hairline (measured 2026-09-08: bodyScrollHeight 500 vs clientHeight 489).
   Verify's own 390x844 pass (a shorter, narrower phone than either App
   Store size) then found the SAME class in Chinese — 8.6px of its own
   14px micro line sliced — once the scroller's overflow was measured
   directly instead of by eye. Rather than a per-locale, per-viewport guess
   at a bottom offset, the margins the scroller's own rhythm is built from
   (below, plus .pw-foot's top padding) are each trimmed a few px more —
   27px together against the original rhythm, at every locale and viewport,
   not tuned to either single case — so the ceiling clears both with room.
   Every locale still reads with its own visible rhythm; none of them looks
   cramped at the smaller one, measured the same way (winner/evidence/shots). */
.pw-promise{margin:0 0 5px;font-family:var(--serif);font-size:15.5px;line-height:1.6;color:var(--pw-strong)}

/* THE APP SHELL WITH NO STOREKIT BRIDGE (B4de). The installed binary has no
   plugin to sell through, and guideline 3.1.1 forbids a button or a link out
   to an outside purchase, so this ONE sentence stands where the plans, the
   trial line and the promise were. It is deliberately the promise's own type
   and size: it is the only thing left to read under the wonder, and setting it
   as fine print would have made the sheet look broken rather than honest. It
   is a paragraph and never an anchor, and nothing here gives it a pointer, a
   hover or any other affordance that would read as tappable. */
.pw-managed{
  margin:0 0 12px;font-family:var(--serif);font-size:15.5px;line-height:1.6;
  color:var(--pw-strong);
}

/* the two plans, ANNUAL FIRST, each a card carrying its own button — nothing
   is pre-selected, so there is no box that could have been pre-checked. ONE
   grid and ONE --pricecol for both, so the two prices, the two descriptions
   and the two buttons align by construction rather than by two tuned numbers. */
.pw-plans{display:flex;flex-direction:column;gap:9px;margin:0 0 5px}
/* An AUTHOR `display` beats the user agent's `[hidden]{display:none}` however
   low its specificity, so `plans.hidden = true` left the two plan cards on
   screen — exactly the trap `#offer[hidden]` and `.pill[hidden]` above already
   carry a note about, and here it would have left prices in an app that must
   show none (measured 2026-09-08, B4de). Every box inside the offer that any
   code toggles with `hidden` gets the rule, once, rather than one line per
   element the next person forgets to add. */
#offer [hidden]{display:none}
@media (min-width:900px){.pw-plans{flex-direction:row;gap:12px}}
.pw-plan{
  flex:1 1 0;min-width:0;
  display:grid;grid-template-columns:var(--pricecol) 1fr;align-items:baseline;
  gap:3px 13px;
  border:1px solid var(--pw-edge);border-radius:9px;padding:11px 13px;
}
/* the accent names the recommended plan and carries no text and no control
   boundary of its own: the card's 1px --pw-edge border is still the boundary */
.pw-year{border-left:3px solid var(--accent)}
/* NEVER let a localised price break across two lines. B12 section 5.1 measured
   the Chinese `US$49.99` at 62px in a 31px line box — two lines, at every one
   of the three store sizes — because the CJK faces set those glyphs wider than
   the 92px column. `nowrap` makes the overflow honest and MEASURABLE, and
   paywall.js fitPriceColumn() then widens --pricecol to the real ink. */
.pw-price{
  grid-column:1;font-family:var(--serif);font-size:25px;line-height:1.08;
  color:var(--paper);white-space:nowrap;
}
@media (min-width:900px){.pw-price{font-size:27px}}
/* D41: ONE sub-line per plan — "a year · Save 58%", "a month". The block-level
   <em> that carried a second line under it ("$69.89 less than paying monthly",
   "same trial, same wonders") is gone with the strings it printed. */
.pw-per{grid-column:2;font-size:11.5px;line-height:1.4;color:var(--pw-dim)}

.pw-go{
  appearance:none;cursor:pointer;font:inherit;font-size:12.5px;line-height:1;white-space:nowrap;
  border-radius:999px;padding:0 18px;height:42px;display:inline-flex;align-items:center;
  border:1px solid var(--pw-edge);background:transparent;color:var(--paper);
  transition:background-color .2s ease,border-color .2s ease;
}
/* both plans drop their button FULL WIDTH beneath their own price — the same
   shape on the phone and on the desk, and a 42px thumb target each */
.pw-plan .pw-go{grid-column:1 / -1;justify-content:center;margin-top:9px}
.pw-primary{background:var(--amber);color:var(--ink);border-color:var(--amber)}  /* 9.60:1 */
.pw-primary:hover{background:#452a1c;color:var(--ink)}
.pw-go:not(.pw-primary):hover{background:rgba(90,56,37,.05);border-color:var(--paper)}
.pw-go:focus-visible{outline:2px solid var(--amber);outline-offset:3px}

.pw-fine{margin:0 0 3px;font-size:11.5px;line-height:1.55;color:var(--pw-dim)}
.pw-fine b{font-weight:600;color:var(--pw-strong)}
.pw-micro{margin:6px 0 0;font-size:10px;letter-spacing:.05em;color:var(--paper-dim)}
/* THE COLLAPSED DETAILS (D41). The grown-up sentence and the three-item micro
   line are the two paragraphs the founder called wall of text; they are not
   dropped, because `pwParent` is where the sheet says WHERE a subscription is
   cancelled, but they are folded away behind one word. The summary is a real
   focus stop (paywall.js's focusables() takes `summary`), the marker is
   removed so it reads as a link rather than an OS triangle, and nothing here
   is a control that buys anything. */
.pw-more{margin:8px 0 0}
.pw-more > summary{
  display:inline-block;list-style:none;cursor:pointer;padding:2px 0;
  font-size:11.5px;color:var(--pw-dim);
  text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;
}
.pw-more > summary::-webkit-details-marker{display:none}
.pw-more > summary::marker{content:""}
.pw-more > summary:hover{color:var(--pw-strong)}
.pw-more > summary:focus-visible{outline:2px solid var(--amber);outline-offset:2px;border-radius:2px}
.pw-more[open] > summary{margin-bottom:5px}
.pw-terms{margin:9px 0 0}
/* the two legal links, inside the disclosure. They carry `.pw-link`, the
   sheet's own underlined-link treatment (the restore control is the other one
   wearing it), so they read as the same kind of thing rather than as a new
   one, and they inherit its --pw-dim 6.01:1 colour, its underline — the
   affordance is never colour alone — and its focus ring. All this rule owns
   is the row they sit on. */
.pw-legal{margin:7px 0 0;font-size:11.5px;line-height:1.6;color:var(--pw-dim)}

/* ---- the TALL wide viewport: spend the extra height on BOTH halves ------
   MEASURED 2026-09-09 on 1032x1376 (the frame in
   release/screenshots/REPORT-IPAD-2026-09-09.md section 3.1): with the plate
   as the elastic half, the offer took the 400px its words need and the page
   took the other 950 — but a 3:2 plate at the 929px measure is only 619px
   tall, so 328px of the page's room stayed empty, 24% of the screen. It is
   not the bare band the founder objected to (that was 42% of the viewport
   below the last line of copy, all of it under the offer); it is a mat around
   the picture. It is still slack, and there is no width left to spend it on:
   filling 947px of height with a 3:2 plate would need 1420px of a 1032px
   screen.

   So it is spent on air INSIDE the offer instead — the same copy, set with
   the room a tablet has — which is what the design RAG says a tall frame is
   for (`raw/design-research-2026-07/mnmm.md` line 119: "whitespace >= 50% of
   any viewport; empty space between items is itself the luxury cue"). Nothing
   here adds a word: the founder asked for less text, not more. */
@media (min-width:900px) and (min-height:1100px){
  .pw-page{padding-top:3.6vh}
  .pw-sheet{padding-top:clamp(18px,3.4vh,48px)}
  .pw-promise{margin-bottom:clamp(5px,1.4vh,18px);font-size:17px}
  .pw-plans{gap:clamp(12px,1.8vh,22px)}
  .pw-plan{padding:clamp(11px,1.9vh,24px) clamp(13px,1.7vw,20px)}
  .pw-plan .pw-go{margin-top:clamp(9px,1.5vh,18px)}
  .pw-terms{margin-top:clamp(9px,1.8vh,22px)}
  .pw-more{margin-top:clamp(8px,1.4vh,18px)}
  .pw-foot{padding:clamp(5px,1vh,14px) 0 calc(clamp(11px,2vh,26px) + env(safe-area-inset-bottom))}
}
.pw-note{margin:9px 0 2px;font-size:11.5px;line-height:1.5;color:var(--pw-strong)}
.pw-err{margin:7px 0 0;font-size:11.5px;line-height:1.5;color:var(--amber)}

/* D53: the arithmetic gate pane and its .pw-ask-gate/.pw-help rules are
   gone with it. .pw-head, .pw-row, .pw-field, .pw-err stay — the email pane
   (still reached directly, no gate in front of it) uses all four. */
.pw-head{margin:0 0 7px;font-family:var(--serif);font-weight:400;font-size:19px;line-height:1.25;color:var(--paper)}
.pw-label{display:block;margin:0 0 7px;font-size:11.5px;color:var(--pw-dim)}
/* PW4: the row and the helper paragraph right under it (`#pw-email-why`,
   ".pw-fine", `margin:0 0 3px` — no top margin of its own) had NOTHING
   between them: `.pw-row` carried no bottom margin at all, so the helper
   text's own line-height put its first glyphs flush against the input's
   1px bottom border. VISIBLE in the PW3 shot at 390x844
   (`winner/evidence/shots/PW3/390x844-en-after-annual.png`): the helper's
   first line touches the input's bottom edge, en and ko alike (the input's
   height is a fixed 42px regardless of language, so the collision is not a
   wrap issue). The margin below is the fix — it is the row's own, not the
   helper's, because `.pw-fine` is shared with the offer pane's `pwParent`/
   `pwMicro` paragraphs and must not gain a top gap it never asked for. */
.pw-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 11px}
.pw-field{flex:1 1 12em;min-width:0;display:block}
.pw-field input{
  width:100%;height:42px;box-sizing:border-box;padding:0 13px;
  font:inherit;font-size:15px;color:var(--paper);background:transparent;
  border:1px solid var(--pw-edge);border-radius:6px;
}
.pw-field input:focus-visible{outline:2px solid var(--amber);outline-offset:2px}
.pw-link{
  appearance:none;background:none;border:0;padding:2px 0;margin:0;cursor:pointer;font:inherit;
  font-size:11.5px;color:var(--pw-dim);
  text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;
}
.pw-link:hover{color:var(--paper)}
.pw-link:focus-visible{outline:2px solid var(--amber);outline-offset:2px;border-radius:2px}
.pw-back{
  appearance:none;cursor:pointer;font:inherit;font-size:11.5px;line-height:1;margin-left:auto;
  background:transparent;color:var(--pw-dim);border:1px solid var(--pw-edge);
  border-radius:999px;padding:0 15px;height:38px;display:inline-flex;align-items:center;
}
.pw-back:hover{color:var(--paper);border-color:var(--paper)}
.pw-back:focus-visible{outline:2px solid var(--amber);outline-offset:2px}

@media (max-width:899px){
  #offer .dot{width:42px;height:42px}
  /* The exit is a fixed 42px disc in the top right corner, and on a 390px
     phone a long title's FIRST line runs straight under it: the German
     `Warum werden deine Finger in der Badewanne...` had `der` painted
     behind the disc (measured 2026-09-08). The two lines that share the
     dot's band keep clear of both corners instead — symmetric, so the block
     stays centred, and it costs the page no vertical room. */
  .pw-kicker,.pw-title{padding-left:46px;padding-right:46px}
}
/* The offer lifts in as the next block of the page; the page itself does not
   move, because the page is what the reader already tapped. */
@media (prefers-reduced-motion:no-preference){
  .pw-sheet{animation:lift .34s cubic-bezier(.22,1,.36,1)}
  @keyframes lift{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
}
