:root {
  --paper: #FAF6EC;
  --paper-deep: #F4EFDD;   /* breath-cycle low point — barely visible shift */
  --ink: #1A1A1A;
  --ink-faint: #B5B0A4;
  --ink-dim: rgba(26, 26, 26, 0.22);
  --ink-italic: #6E6A60;

  --measure: 32rem;
  --line-height: 1.85;

  --serif: "Crimson Pro", "Source Serif Pro", Garamond, Georgia, serif;

  --pace: cubic-bezier(0.22, 0.61, 0.36, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-feature-settings: "kern", "liga", "onum";
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
}

/* The room breathes — three synchronized layers at the same 15s
   rhythm. 15s/cycle = 4 breaths/min, the entry point of traditional
   pranayama and a comfortable Daoist meditation pace. Coherent
   breathing (Brown & Gerbarg, 6 bpm) is the HRV-peak rate, but for
   "Linger" with Tao Te Ching the slower 4 bpm sits closer to the
   contemplative register the product is aiming at. Apple Watch
   Breathe defaults to 7 bpm; this is roughly half that — long, open
   inhales and exhales.
   The three layers:
   1) Page background pulses subtly (paper → paper-deep).
   2) A horizontal ink mark below the page-mark grows and shrinks
      with the same cycle, giving a visible breath reference.
   3) The caret breath (further down) shares the same 15s cycle.
   All three active only during the typing state, all at a constant
   rate — no rush detection, no rate changes based on typing speed. */
.breath-mark {
  position: fixed;
  top: 4rem;
  left: 50%;
  transform-origin: center;
  width: 20rem;
  aspect-ratio: 5.36 / 1; /* preserves the source image's aspect */
  background-image: url("breath-mark.png");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  background-color: transparent;
  opacity: 0;
  pointer-events: none;
  z-index: 1;
}

body[data-state="typing"] .breath-mark {
  animation: breath-mark 15000ms cubic-bezier(0.4, 0.0, 0.6, 1.0) infinite;
}

@keyframes breath-mark {
  0%, 100% {
    transform: translateX(-50%) scaleX(0.55);
    opacity: 0.45;
  }
  50% {
    transform: translateX(-50%) scaleX(1.0);
    opacity: 0.9;
  }
}

@media (max-width: 720px) {
  .breath-mark { top: 3rem; width: 13rem; }
}

@media (prefers-reduced-motion: reduce) {
  body[data-state="typing"] .breath-mark {
    animation: none;
    opacity: 0.6;
    transform: translateX(-50%) scaleX(0.7);
  }
}

body[data-state="typing"] {
  animation: paper-breath 15000ms cubic-bezier(0.4, 0.0, 0.6, 1.0) infinite;
}

@keyframes paper-breath {
  0%, 100% { background-color: var(--paper); }
  50%      { background-color: var(--paper-deep); }
}

body {
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr;
  padding: 2rem clamp(1.5rem, 6vw, 6rem) 4rem;
  gap: 3rem;
}

/* Page mark — restrained wordmark + locator */
.page-mark {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 2rem;
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
}
.wordmark {
  font-style: italic;
  color: var(--ink);
}
.locator {
  font-size: 0.75rem;
  color: var(--ink-italic);
  letter-spacing: 0.04em;
}

/* Reading desk — single continuous surface, no cards */
.desk {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 4rem;
  justify-items: center;
  align-content: start;
  padding-top: clamp(1rem, 6vh, 4rem);
}

.chapter {
  position: relative;
  width: min(var(--measure), 100%);
  font-size: clamp(1.0625rem, 1vw + 0.875rem, 1.25rem);
  line-height: var(--line-height);
}

/* Arrival as a preview of the typing state — chapter at full reading
   size with every char in the dim color the typing-untyped chars use.
   The visual continuity tells the visitor "this is the text you're
   about to bring to life, line by line" without saying it. When they
   click Begin slowly, nothing resizes; the active line just brightens
   into ink. Earlier we tried to compress arrival to keep Begin slowly
   above the fold, which solved discoverability at the cost of the
   preview metaphor — this version solves it through the dim-vs-bright
   contrast instead (the dim chapter is obviously passive, the bright
   Begin slowly is obviously the action).

   The chapter still scrolls if it exceeds the viewport on shorter
   screens; the threshold sticks to the bottom of the visible area so
   the action stays reachable regardless. */
body[data-state="arrival"] .char {
  color: var(--ink-dim);
}

body[data-state="arrival"] .threshold {
  position: sticky;
  bottom: 1.5rem;
  margin-top: auto;
  background: linear-gradient(to bottom, transparent, var(--paper) 30%, var(--paper));
  padding-top: 1.5rem;
  z-index: 5;
}

.ink {
  position: relative;
  white-space: pre-wrap;
  word-break: keep-all;
  /* No overflow-wrap: break-word. With per-char spans the browser would
     otherwise break a word like "unchanging" at any char, and the wrap
     point would jitter every keystroke as the cursor changes which span
     it attaches to. Keep whole words together; overflow only if the
     word is genuinely too long for the column (none in Tao Te Ching). */
  overflow-wrap: normal;
}

.line {
  display: block;
  transition: opacity 700ms var(--pace), color 700ms var(--pace);
}

.char {
  display: inline;
  color: var(--ink);
  transition: color 380ms var(--pace);
  position: relative; /* anchor for ::after cursor */
}

/* During typing, untyped chars dim back so the user feels the
   ink-reveal one character at a time. Outside typing state, the
   chapter reads as a fully present page. */
body[data-state="typing"] .char {
  color: var(--ink-dim);
}
body[data-state="typing"] .char.struck {
  color: var(--ink);
}
body[data-state="completion"] .char {
  color: var(--ink);
}
/* Cursor pseudo-element is absolutely positioned so it contributes
   zero width to the line. The .cursor class lives on the next char to
   be typed; with left near 0 the caret sits at that char's leading edge,
   which visually is right after the last typed char — the standard
   text-editor caret position.
   Caret breath cycle matches the paper-breath (15s) so cursor and
   room exhale together. */
.char.cursor::after {
  content: "";
  position: absolute;
  left: -0.05em;
  top: 0.13em;
  width: 1.5px;
  height: 1em;
  background: var(--ink);
  pointer-events: none;
  animation: breathe 15000ms cubic-bezier(0.4, 0.0, 0.6, 1.0) infinite;
}

@keyframes breathe {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1.0; }
}

/* Active typing — non-current lines fade back */
body[data-state="typing"] .line:not(.line-active) {
  opacity: 0.25;
}

body[data-state="typing"] .threshold,
body[data-state="completion"] .threshold {
  display: none;
}

/* Threshold — single quiet invitation, no button-y button */
.threshold {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  width: min(var(--measure), 100%);
  margin-top: 2rem;
  padding-left: 0.05em;
}

/* Begin slowly is THE primary action on the arrival page. Earlier
   iterations kept it small to match the contemplative tone, but
   first-time testers couldn't find what to click — the action
   blended into the surrounding "this is just reading material"
   visual register. Restraint is good; invisibility is a bug.
   Bigger size + a heavier two-line underline makes it read as an
   intentional action without crossing into SaaS-button territory. */
.begin {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0.1em 0 0.18em;
  font: inherit;
  font-style: italic;
  font-size: 1.625rem;
  line-height: 1.2;
  color: var(--ink);
  cursor: pointer;
  border-bottom: 2px solid var(--ink);
  letter-spacing: 0.005em;
  transition: color 320ms var(--pace), border-color 320ms var(--pace), transform 320ms var(--pace);
}
.begin:hover { transform: translateY(-1px); }
.begin:focus-visible {
  outline: none;
  border-bottom-style: dashed;
}

.rhythm-note {
  margin: 0.35rem 0 0;
  font-style: italic;
  font-size: 0.875rem;
  color: var(--ink-italic);
  letter-spacing: 0.02em;
}

/* Breath visibility preset switch — a *secondary* control, clearly
   demoted from the primary "Begin slowly" action above. Labelled and
   separated by extra whitespace so a first-time visitor reads it as
   "an optional preference" rather than "another thing I might need
   to click before starting." */
.breath-presets {
  margin: 1.4rem 0 0;
  display: inline-flex;
  align-items: baseline;
  gap: 0.4em;
  font-style: italic;
  font-size: 0.75rem;
  color: var(--ink-faint);
}

.breath-presets::before {
  content: "Breath mark:";
  font-style: italic;
  color: var(--ink-faint);
  margin-right: 0.3em;
}

.breath-presets .preset {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--ink-faint);
  cursor: pointer;
  text-decoration: underline dashed 1px;
  text-underline-offset: 0.2em;
  transition: color 320ms var(--pace);
}
.breath-presets .preset:hover { color: var(--ink-italic); }
.breath-presets .preset:focus-visible {
  outline: none;
  text-decoration-style: solid;
}
.breath-presets .preset[aria-current="true"] {
  color: var(--ink);
  text-decoration: none;
}
.breath-presets .dot {
  color: var(--ink-faint);
  font-style: normal;
}

/* Settle phrase — appears after Begin slowly, one phrase per
   10-second breath cycle, three cycles total (~30s). Positioned
   just under the breath mark so the user sees mark + phrase as
   a single guidance moment. Fixed so it stays in place as the
   user starts typing and reads down the chapter.
   Soft fade transitions; never blocks. */
.settle-phrase {
  position: fixed;
  top: 6rem;
  left: 0;
  right: 0;
  margin: 0 auto;
  text-align: center;
  font-style: italic;
  font-size: 0.9375rem;
  color: var(--ink-italic);
  letter-spacing: 0.01em;
  opacity: 0;
  pointer-events: none;
  z-index: 2;
  transition: opacity 2400ms cubic-bezier(0.4, 0.0, 0.6, 1.0);
}

.settle-phrase[data-active="1"] {
  opacity: 0.7;
}

@media (max-width: 720px) {
  .settle-phrase {
    top: 4.5rem;
    font-size: 0.875rem;
  }
}

/* Stronger breath-mark preset. Same image, wider span, deeper
   opacity arc. The mix-blend-mode multiply means the underlying
   paper-breath color shift still reaches the ink. */
body[data-breath="strong"] .breath-mark {
  width: 26rem;
  animation-name: breath-mark-strong;
}

@keyframes breath-mark-strong {
  0%, 100% {
    transform: translateX(-50%) scaleX(0.55);
    opacity: 0.65;
  }
  50% {
    transform: translateX(-50%) scaleX(1.0);
    opacity: 1.0;
  }
}

@media (max-width: 720px) {
  body[data-breath="strong"] .breath-mark { width: 16rem; }
}

/* The hidden attribute must always win against display rules. */
[hidden] { display: none !important; }

/* Afterword — completion is a quiet ending, never a celebration */
.afterword {
  width: min(var(--measure), 100%);
  display: none;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
  margin-top: 4rem;
  opacity: 0;
}

body[data-state="completion"] .afterword {
  display: flex;
  animation: settle 1600ms var(--pace) forwards;
}

.dwell-line {
  margin: 0;
  font-style: italic;
  font-size: 0.875rem;
  color: var(--ink-italic);
  letter-spacing: 0.02em;
}

.dwell-time {
  margin: 0;
  font-size: 0.75rem;
  color: var(--ink-faint);
  letter-spacing: 0.05em;
  font-feature-settings: "tnum";
}

.afterword-actions {
  display: flex;
  gap: 0.5em;
  flex-wrap: wrap;
  align-items: baseline;
  margin-top: 0.5rem;
}

.afterword-actions button {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  font-style: italic;
  color: var(--ink);
  cursor: pointer;
  border-bottom: 1px solid transparent;
  transition: border-color 320ms var(--pace), color 320ms var(--pace);
}
.afterword-actions button:hover { border-bottom-color: var(--ink); }
.afterword-actions button:focus-visible { outline: none; border-bottom: 1px dashed var(--ink); }

.dot {
  color: var(--ink-faint);
  font-style: italic;
}

@keyframes settle {
  from { opacity: 0; transform: translateY(0.35em); }
  to { opacity: 1; transform: translateY(0); }
}

/* Capture: invisible textarea catches keystrokes */
#capture {
  position: fixed;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
  border: 0;
  outline: none;
  resize: none;
  top: 0;
  left: 0;
  caret-color: transparent;
}

/* IME hint — shown only if Hangul/composition input is detected, then
   auto-hides. Lives outside the contemplative reading area on purpose. */
.ime-hint {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  max-width: 36ch;
  padding: 0.7rem 1rem;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.8125rem;
  font-style: italic;
  line-height: 1.45;
  border-radius: 2px;
  z-index: 10;
  animation: settle 600ms var(--pace) forwards;
}
.ime-hint p { margin: 0; }

/* ─────────────────────────────────────────────────────────────────────
   MOBILE MODE — a different ritual, not a stacked desktop.
   Decision D6=A: line-by-line, no full-chapter split, generous pause,
   current phrase large and centered above where the keyboard sits.
   ───────────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  :root {
    --measure: 100%;
    --line-height: 1.55;
  }

  body {
    padding: 1.25rem 1.25rem 2rem;
    gap: 1.5rem;
    min-height: 100svh;
  }

  .page-mark {
    font-size: 0.75rem;
  }
  .locator { font-size: 0.6875rem; }

  /* Reading desk becomes a focused stage. The active line floats in the
     upper portion of the viewport, well clear of the keyboard. */
  .desk {
    gap: 1.5rem;
    padding-top: 0;
    min-height: 60svh;
    justify-content: flex-start;
  }

  .chapter {
    font-size: 1.5rem;       /* current line is large */
    line-height: 1.5;
    gap: 0;
    width: 100%;
  }

  /* The threshold (Begin slowly) stays quiet, but rises up so it's
     reachable above any virtual keyboard preview. */
  .threshold {
    margin-top: 0.5rem;
  }
  .begin {
    font-size: 1.125rem;
  }

  /* During typing, ALL lines collapse to zero size except the active
     one. This is the core mobile decision: one phrase at a time. */
  body[data-state="typing"] .line {
    display: none;
    font-size: 1.625rem;
    line-height: 1.55;
    letter-spacing: 0.01em;
  }
  body[data-state="typing"] .line.line-active {
    display: block;
    opacity: 1;
    animation: line-breath 700ms var(--pace) both;
  }

  /* Char dimming still applies during typing, but the line is the unit
     of attention here, not the chapter. */
  body[data-state="typing"] .char { color: var(--ink-dim); }
  body[data-state="typing"] .char.struck { color: var(--ink); }

  /* Inter-line pause: when the active line is being swapped out, fade
     it down briefly. JS sets data-pacing="resting" during the pause. */
  body[data-state="typing"][data-pacing="resting"] .line.line-active {
    opacity: 0.18;
    transition: opacity 1200ms var(--pace);
  }

  /* Completion in mobile: full chapter re-emerges at readable size,
     same quiet ending as desktop. */
  body[data-state="completion"] .line {
    display: block;
    font-size: 1.0625rem;
    line-height: 1.7;
  }
  body[data-state="completion"] .chapter { font-size: 1.0625rem; }

  .afterword { margin-top: 2rem; gap: 1rem; }
  .afterword-actions {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.65rem;
  }
  .afterword-actions .dot { display: none; }
}

@keyframes line-breath {
  from { opacity: 0; transform: translateY(0.4em); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Reduced motion respect */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
