/* Reset and document shell. */

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

html {
  /* Scrolling is real, native scrolling. Anything that smooths, snaps or
     hijacks it fights the spring in scroll.js and produces double-easing. */
  scroll-behavior: auto;
  background: #d9a897;
  -webkit-text-size-adjust: 100%;

  /*
   * There is nothing to scroll until the letter becomes the page — the intro is
   * played, not scrubbed, so the document starts with no scroll range at all.
   * Reserving the gutter now means the viewport does not change width at the
   * moment the letter arrives and grows a real scrollbar; without this, the
   * whole scene reframes itself by a scrollbar's width mid match-cut.
   */
  scrollbar-gutter: stable;

  /* Kept, not hidden, once it exists: the scrollbar is the honest edge of the
     reading experience — it is how far down the letter you are. Tinted into
     the surface instead of removed. */
  scrollbar-width: thin;
  scrollbar-color: hsl(14 32% 58% / 0.5) transparent;
}

::-webkit-scrollbar {
  width: 9px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: hsl(14 32% 58% / 0.45);
  border-radius: 9px;
}

::-webkit-scrollbar-thumb:hover {
  background: hsl(14 34% 52% / 0.62);
}

body {
  margin: 0;
  min-height: 100%;
  background: #d9a897;
  color: var(--c-ink);
  font-family: var(--font-serif);
  overflow-x: hidden;
  /* Overscroll bounce on iOS would pull the fixed scene away from the edge and
     reveal the page background behind it. */
  overscroll-behavior-y: none;
}

/*
 * The scene is pinned; the scroll track behind it supplies the document height.
 * Using a real, tall document rather than a transform-scrolled container keeps
 * native scrollbars, keyboard paging, find-in-page and momentum intact.
 */
.scene {
  position: fixed;
  inset: 0;
  overflow: hidden;
  contain: strict;
  background: #d9a897;
  /* Establishes the containing block for every absolutely-positioned layer and
     gives the compositor one stable root to work against. */
  isolation: isolate;
}

.scroll-track {
  height: 0; /* nothing to scroll during the intro; becomes the sheet's height
                once the letter is the page (set by script) */
  pointer-events: none;
}

/* ── the page act ────────────────────────────────────────────────────────────
   Once the letter has taken the viewport, the room recedes a little: the
   vignette loosens so the corners of the paper stay paper, and the sheet starts
   accepting the pointer again so the words can be selected like the real text
   they are. */
html.is-page {
  --vignette-strength: 0.2;
  /* The scrollbar's lane sits outside the scene. Once the letter is the page,
     that lane should read as the margin of the paper, not as a strip of the
     table showing through a full-bleed sheet. */
  background: var(--c-paper);
}

.is-page .paper {
  pointer-events: auto;
}

.is-page .letter {
  cursor: text;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── the gate ────────────────────────────────────────────────────────────────
   The scene waits behind an unbroken wax seal, softly out of focus. One tap
   breaks the wax, starts the song and lets the story begin. The blur lives on
   this overlay as a backdrop-filter and is transitioned DOWN alongside the
   fade — a backdrop-filter does not fade with opacity, so leaving it static
   would keep the world blurred behind an invisible veil. */
.gate {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: grid;
  place-content: center;
  justify-items: center;
  row-gap: clamp(10px, 2vh, 20px);
  background: hsl(20 50% 78% / 0.16);
  -webkit-backdrop-filter: blur(16px) saturate(1.05);
  backdrop-filter: blur(16px) saturate(1.05);
  transition:
    opacity 1s cubic-bezier(0.4, 0, 0.2, 1),
    -webkit-backdrop-filter 1s cubic-bezier(0.4, 0, 0.2, 1),
    backdrop-filter 1s cubic-bezier(0.4, 0, 0.2, 1);
}

.gate.is-leaving {
  opacity: 0;
  -webkit-backdrop-filter: blur(0px) saturate(1);
  backdrop-filter: blur(0px) saturate(1);
  pointer-events: none;
}

.gate__seal {
  width: clamp(104px, 16vmin, 168px);
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.gate__seal:focus-visible {
  outline: 2px solid hsl(14 40% 45% / 0.8);
  outline-offset: 8px;
  border-radius: 50%;
}

/* The breathing lives on the svg, not the button: the button's own box must
   hold still so automation (and assistive tech hit-testing) sees a stable
   target — a control that never stops moving is a control some tools can
   never press. */
.gate__seal svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  transform-origin: 50% 50%;
  animation: gate-breathe 3.2s cubic-bezier(0.42, 0, 0.58, 1) infinite;
}

.gate.is-breaking .gate__seal svg {
  animation: none;
}

.gate__hint {
  margin: 0;
  font-family: var(--font-fine);
  font-size: clamp(15px, 2.2vmin, 19px);
  letter-spacing: 0.09em;
  color: hsl(16 30% 32% / 0.85);
  text-shadow: 0 1px 8px hsl(30 60% 90% / 0.6);
  transition: opacity 0.4s ease;
}

.gate.is-breaking .gate__hint {
  opacity: 0;
}

@keyframes gate-breathe {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.045);
  }
}

/* ── reduced motion ──────────────────────────────────────────────────────────
   Ambient motion is removed entirely, and script skips the autoplaying film:
   the letter arrives whole and written. The spring is switched off in script so
   reading tracks the finger exactly. */
@media (prefers-reduced-motion: reduce) {
  .grain,
  .motes,
  .bloom {
    display: none;
  }

  .dapple--under,
  .dapple--over {
    animation: none !important;
  }

  .gate__seal svg {
    animation: none;
  }
}
