/* secure-letter — layout, components and motion.
   Reads tokens.css for every colour, size and duration. No inline styles anywhere
   (the CSP forbids them), so anything dynamic is driven by a data-* attribute or,
   where a value can only be known at runtime, by a CSSOM property assignment. */

/* ============================================================ fonts */
/* Self-hosted, latin subset, variable. Nothing is fetched from a font CDN at
   runtime — the files are committed under /fonts. See README for how they were
   obtained and how to re-subset them. */

@font-face {
  font-family: 'Fraunces';
  src: url('/fonts/fraunces-latin-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Fraunces';
  src: url('/fonts/fraunces-italic-latin-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Instrument Sans';
  src: url('/fonts/instrument-sans-latin-var.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Caveat';
  src: url('/fonts/caveat-latin-var.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  /* Decorative sign-off: a late swap is worse than one frame of nothing. */
  font-display: optional;
}

/* ============================================================ reset */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg-canvas);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-variation-settings: 'wdth' 100;
  font-size: 15px;
  line-height: 22px;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

button,
input,
textarea {
  font: inherit;
  color: inherit;
  margin: 0;
}

img,
svg {
  display: block;
  max-width: 100%;
}

:where(a) {
  color: var(--text-primary);
}

:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 3px;
  border-radius: 4px;
}

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

[hidden] {
  display: none !important;
}

/* ============================================================ type */

.display-xl,
.display-m,
.quote,
.letter-body {
  font-family: var(--font-display);
  font-variation-settings: var(--fraunces-axes);
  font-optical-sizing: auto;
  word-break: break-word;
}

.display-xl {
  font-size: 40px;
  line-height: 44px;
  letter-spacing: -1.2px;
  font-weight: 600;
  margin: 0;
}

.display-m {
  font-size: 24px;
  line-height: 30px;
  letter-spacing: -0.4px;
  font-weight: 600;
  margin: 0;
}

/* Letter/Quote — the italic explainer on the first screen. */
.quote {
  font-size: 17px;
  line-height: 28px;
  font-style: italic;
  font-weight: 400;
  margin: 0;
}

/* Letter/Body — a real letter: the reply, and the writer's own text. */
.letter-body {
  font-size: 17px;
  line-height: 30px;
  font-weight: 400;
  margin: 0;
}

.caption {
  font-size: 13px;
  line-height: 18px;
  color: var(--text-tertiary);
  margin: 0;
}

.label {
  font-size: 15px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: -0.1px;
  margin: 0;
}

.overline {
  font-size: 11px;
  line-height: 14px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin: 0;
}

.signature {
  font-family: var(--font-hand);
  font-size: 22px;
  line-height: 26px;
  font-weight: 700;
  color: var(--text-accent);
  margin: 0;
}

.text-accent {
  color: var(--text-accent);
}

/* ============================================================ page shell */

.page {
  min-height: 100dvh;
}

/* The Sent screen and the personal pages take the letter's paper colour. */
body[data-paper] {
  background: var(--letter-bg);
}

.stage {
  position: relative;
}

/* The 393px design frame becomes a centred column that still reads as a sheet of
   paper on a desktop, rather than a letterboxed phone. */
.screen {
  --pad-top: var(--sp-12);
  width: min(100%, var(--column));
  margin-inline: auto;
  padding: var(--pad-top) var(--gutter) var(--sp-10);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  min-height: 100dvh;
}

.screen--centered {
  justify-content: center;
  gap: var(--sp-6);
  text-align: left;
}

/* Pushes the last action to the foot of a tall viewport without stretching a short
   one — the elastic spacer the design uses on 04 and 05A2. */
.spacer-flex {
  flex: 1 1 auto;
  min-height: var(--sp-10);
}

/* ============================================================ cards */

.paper-card {
  background: var(--bg-paper);
  border: 0.5px solid var(--border-subtle);
  border-radius: var(--r-md);
  box-shadow: var(--elev-paper);
  padding: var(--sp-6);
}

/* On a tinted page the card's edge follows the letter's own line colour. */
body[data-paper] .paper-card {
  border-color: var(--letter-line);
}

/* ============================================================ buttons */

.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 58px;
  padding: 0 var(--sp-6);
  border: 0;
  border-radius: var(--r-full);
  background: transparent;
  font-size: 16px;
  line-height: 20px;
  font-weight: 600;
  letter-spacing: -0.1px;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur-micro) var(--ease-soft), background-color var(--dur-quick) var(--ease-soft),
    border-color var(--dur-quick) var(--ease-soft), opacity var(--dur-quick) linear;
}

.btn:active:not(:disabled) {
  transform: scale(0.97);
}

.btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.btn--primary {
  background: var(--bg-inverse);
  color: var(--text-inverse);
}

/* Outline on a warm page or a paper card: follows the letter's line colour. */
.btn--outline {
  border: 1px solid var(--letter-line);
  color: var(--text-primary);
}

/* Outline on the cool canvas screens (the "Edit" step). */
.btn--outline-cool {
  border: 1px solid var(--border-subtle);
  color: var(--text-primary);
}

.btn__icon {
  width: 11px;
  height: 9px;
  flex: none;
  overflow: clip;
}

/* The arrow's stroke bleeds past its box in the export; the negative inset keeps
   the drawn glyph the size Figma shows. */
.btn__icon img {
  width: 118%;
  height: 118%;
  margin: -9% 0 0 -7%;
}

/* ============================================================ 01 / 02 — write */

.masthead {
  display: flex;
  flex-direction: column;
}

#screen-write {
  --card-gap: 36px; /* the gap above the writing surface, in both states */
  --card-peek: 96px; /* how much of the surface shows above the bottom edge, at rest */
  /* The band the headline lives in — the same height in both states, see below. */
  --hero-h: calc(100dvh - var(--pad-top) - var(--card-gap) - var(--card-peek));
  --title-m-h: 90px; /* the headline in the write state: three lines of display-m */
  /* What the write state is actually tall enough to need: the headline, the gap, the
     surface, the button row and the foot padding. js/index.js scrolls a tap to the
     position this describes, so the two stay in step. */
  --write-h: calc(
    var(--pad-top) + var(--title-m-h) + var(--card-gap) + 434px + var(--sp-4) + 58px + var(--sp-10)
  );
}

/* Screens 01 and 02 are one scrolling screen, and the scroll position *is* the state:
   01 is the top of it, with the writing surface clipped to a peek at the bottom edge —
   that peek is the affordance — and 02 is what you have once the surface has risen far
   enough to mean it. js/index.js watches that with two IntersectionObservers, so the
   state goes back as readily as it goes forward. The screen scrolls inside itself: the
   observers get a root they can measure against, and the page itself never scrolls. */
body[data-screen='intro'] #screen-write,
body[data-screen='write'] #screen-write {
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior-y: contain;
  /* The band below carries the top padding as a margin instead. Engines disagree about
     whether a sticky box's offset is measured from a scroll container's padding edge or
     its content edge, and with no top padding here there is no difference to disagree
     about: #write-title pins at exactly --pad-top everywhere. */
  padding-top: 0;
}

/* The headline band is exactly as tall in the write state as in the intro state. That is
   load-bearing rather than cosmetic: the observers read a scroll position, and a band
   that collapsed when the state flipped would shorten the scrollable height under them
   and flip it straight back. Only the arrangement inside the band changes.

   No CSS transition on anything here, on purpose: the move between the two states is
   animated by the FLIP in js/motion.js, and a competing transition would fight it. */
body[data-screen='intro'] .masthead,
body[data-screen='write'] .masthead {
  flex: none;
  height: var(--hero-h);
  margin-top: var(--pad-top); /* the screen's top padding, moved here — see above */
}

/* 01: the headline sits in the middle of the first screen — it centres itself in the
   room the peek leaves it, rather than being pushed down by a fixed band of empty
   canvas. Centring holds on a short phone and on a desktop alike, where a fixed band
   either collapsed to nothing or overshot. */
body[data-screen='intro'] .masthead {
  justify-content: center;
}

/* 02: the headline starts at the top of the band, which is also where it sticks. */
body[data-screen='write'] .masthead {
  justify-content: flex-start;
}

/* The headline holds its place while the rest of the screen scrolls past it, so by the
   time the writing surface is up it is already pinned where 02 wants it and the FLIP has
   only its size left to animate. The background is the canvas it sits on: the explainer,
   and then the surface, pass behind it rather than through it. */
#write-title {
  position: sticky;
  top: var(--pad-top);
  z-index: 1;
  background: var(--bg-canvas);
}

/* Runway. On a tall viewport the surface runs out of scroll well short of the headline;
   this lets it ride all the way up. On a phone the write state already fills the screen
   and this collapses to nothing. */
.write-runway {
  flex: none;
  height: max(0px, calc(100dvh - var(--write-h)));
}

/* 05A1 sits the passcode block in the lower-middle third, under a band of quiet
   paper. */
#gate-hero {
  flex: 0 1 256px;
  min-height: 0;
}

.masthead__intro {
  display: flex;
  flex-direction: column;
  margin-top: var(--sp-6);
}

/* flex: none matters now that the screen is a scroll container with a fixed height: the
   default flex-shrink would squeeze the surface back down to its min-height instead of
   letting it grow with the letter, and the letter would scroll inside the textarea while
   the screen scrolled around it. The surface grows; the screen scrolls. */
.write-card {
  display: flex;
  flex-direction: column;
  flex: none;
  padding: var(--sp-6) var(--sp-6) var(--sp-5);
  min-height: 434px;
  margin-top: var(--card-gap);
}

.letter-input {
  flex: 1 1 auto;
  width: 100%;
  min-height: 358px;
  margin-top: 10px;
  border: 0;
  padding: 0;
  background: transparent;
  resize: none;
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 22px;
  outline: none;
}

.letter-input::placeholder {
  color: var(--text-tertiary);
  opacity: 1;
}

.char-count {
  margin-top: var(--sp-3);
  text-align: right;
  font-size: 13px;
  line-height: 18px;
  color: var(--text-tertiary);
}

.char-count[data-low='true'] {
  color: var(--text-accent);
}

.write-actions {
  flex: none; /* same reason as .write-card: never squeezed to fit the scrollport */
  margin-top: var(--sp-4);
}

/* The nudge that says "there is more below". Suppressed for reduced motion. */
.peek-cue {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: var(--sp-4);
  padding: var(--sp-3);
  border: 0;
  background: transparent;
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 18px;
  cursor: pointer;
}

.peek-cue__chevron {
  width: 10px;
  height: 10px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-135deg);
  animation: nudge 2.4s var(--ease-soft) infinite;
}

@keyframes nudge {
  0%,
  100% {
    transform: rotate(-135deg) translate(0, 0);
  }
  50% {
    transform: rotate(-135deg) translate(3px, 3px);
  }
}

/* ============================================================ 03 — envelope */

.envelope {
  background: var(--letter-bg);
  border: 0.5px solid var(--letter-line);
  /* Larger radius on top than bottom — that asymmetry is what reads as an envelope. */
  border-radius: var(--r-md) var(--r-md) var(--r-sm) var(--r-sm);
  padding: var(--sp-6);
  min-height: 206px;
  margin-top: 30px;
  overflow: hidden;
  transition: background-color var(--dur-quick) var(--ease-soft), border-color var(--dur-quick) var(--ease-soft);
}

.fields {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.field__input {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--letter-line);
  padding: 0 0 6px;
  background: transparent;
  font-size: 15px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: -0.1px;
  border-radius: 0;
  transition: border-color var(--dur-quick) var(--ease-soft);
}

.field__input::placeholder {
  color: var(--text-tertiary);
  opacity: 1;
}

.field__input:focus {
  outline: none;
  border-bottom-color: var(--text-primary);
}

/* ---- paper picker ---- */

.swatches {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 26px 0 0;
  padding: 0;
  border: 0;
}

.swatch {
  width: 42px;
  height: 42px;
  flex: none;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}

.swatch::before {
  content: '';
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--letter-bg);
  border: 1px solid var(--letter-line);
  transition: transform var(--dur-quick) var(--ease-soft), box-shadow var(--dur-quick) var(--ease-soft);
}

.swatch[aria-checked='true']::before {
  transform: scale(1.06);
  box-shadow: 0 0 0 2px var(--bg-canvas), 0 0 0 4px var(--ink-900);
}

.envelope-actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  margin-top: 22px;
}

/* ============================================================ 04 — sent */

.qr-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-top: var(--sp-6);
}

.qr-card__code {
  width: 152px;
  height: 152px;
  flex: none;
  overflow: clip;
  border-radius: 4px;
}

.qr-card__code img {
  width: 100%;
  height: 100%;
}

.qr-card__url {
  width: 100%;
  text-align: center;
  word-break: break-all;
}

.qr-card .btn {
  margin-top: var(--sp-3);
}

/* ---- passcode ---- */

.passcode {
  position: relative;
  margin-top: var(--sp-3);
}

/* One real input behind four presentational boxes: native paste, backspace,
   selection and screen-reader behaviour for free. 16px prevents the iOS focus zoom
   without disabling pinch zoom. */
.passcode__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: transparent;
  caret-color: transparent;
  font-size: 16px;
  letter-spacing: 4em;
  text-indent: -9999px;
  opacity: 0.01;
  z-index: 1;
}

.digits {
  display: flex;
  gap: var(--sp-3);
}

.digit {
  flex: 1 1 0;
  min-width: 0;
  height: 64px;
  display: grid;
  place-items: center;
  border: 1px solid var(--letter-line);
  border-radius: var(--r-md);
  background: var(--bg-paper);
  transition: border-color var(--dur-micro) var(--ease-soft), border-width var(--dur-micro) linear;
}

/* Entered digits are shown as dots, never as the numerals — the code is typed in
   public, on a phone someone may be holding for you. */
.digit[data-state='filled']::before {
  content: '';
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--text-primary);
}

.digit[data-state='focused'] {
  border: 1.5px solid var(--border-strong);
}

.digit[data-state='focused']::before {
  content: '';
  width: 2px;
  height: 24px;
  border-radius: 1px;
  background: var(--text-primary);
  animation: caret 1.1s steps(1, end) infinite;
}

@keyframes caret {
  0%,
  49% {
    opacity: 0;
  }
  50%,
  100% {
    opacity: 1;
  }
}

.passcode-help {
  margin-top: 10px;
}

.form-error {
  margin-top: var(--sp-3);
  font-size: 13px;
  line-height: 18px;
  color: var(--text-accent);
}

/* A wrong passcode: a short shake, then the boxes clear. */
.passcode[data-shake='true'] .digits {
  animation: shake 300ms var(--ease-fold);
}

@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-4px);
  }
  75% {
    transform: translateX(4px);
  }
}

/* ============================================================ personal pages */

.reply-card {
  margin-top: 34px;
  display: flex;
  flex-direction: column;
}

.reply-card__body {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 30px; /* one blank 30px line between paragraphs, as the design draws it */
}

/* The gap alone provides the rhythm — a paragraph's own default margin would double it. */
.reply-card__body p {
  margin: 0;
}

.reply-card__signature {
  margin-top: 18px;
}

/* The compact inbox strip: QR beside its label, action underneath. */
.inbox-card {
  margin-top: var(--sp-4);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.inbox-card__row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.inbox-card__code {
  width: 64px;
  height: 64px;
  flex: none;
  overflow: clip;
  border-radius: 2px;
}

.inbox-card__code img {
  width: 100%;
  height: 100%;
}

.inbox-card__label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.inbox-card__url {
  word-break: break-all;
}

.section-gap {
  height: 56px;
  flex: none;
}

.section-gap--sm {
  height: 44px;
}

/* Vertical rhythm utilities. The design builds its spacing from explicit empty
   "spacer" frames, so these mirror it one-for-one where a component's own default
   margin is not the right value. Declared last so they win over component margins. */
.mt-10 {
  margin-top: 10px;
}

.mt-24 {
  margin-top: var(--sp-6);
}

.skeleton--fill {
  width: 100%;
  height: 100%;
}

.skeleton {
  border-radius: var(--r-md);
  background: linear-gradient(90deg, rgba(30, 27, 46, 0.05), rgba(30, 27, 46, 0.09), rgba(30, 27, 46, 0.05));
  background-size: 300% 100%;
  animation: shimmer 1.4s linear infinite;
}

@keyframes shimmer {
  from {
    background-position: 100% 0;
  }
  to {
    background-position: -100% 0;
  }
}

/* ============================================================ scene motion */

/* 03 → 04: a full-viewport pane in the letter's colour rises over the screen. Once
   it is up, the page itself becomes that colour and the pane is removed with no exit
   animation — the two are identical, so the removal is invisible and it reads as the
   page having changed colour. */
.sweep {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--letter-bg);
  transform: translateY(100%);
  pointer-events: none;
  will-change: transform;
}

.sweep[data-state='in'] {
  transform: translateY(0);
  transition: transform var(--dur-scene) var(--ease-soft);
}

/* 02 → 03: the written page folds up before it becomes an envelope — in half, or in as
   many panels as a long letter needs. A static clone is folded, never the live textarea. */
.fold-layer {
  position: fixed;
  inset: 0;
  z-index: 30;
  perspective: 1200px;
  pointer-events: none;
}

/* The top panel, which stays put. Every other panel folds up onto it, so the packet is
   left at the top of where the page was — which is where the envelope is. */
.fold--top {
  position: absolute;
  overflow: hidden;
  background: var(--bg-paper);
  border: 0.5px solid var(--border-subtle);
  border-radius: var(--r-md) var(--r-md) 0 0;
  box-shadow: var(--elev-paper);
}

/* A folding panel, hinged on its own top edge — the crease. It is two-sided, because the
   point of folding a letter is that the writing ends up on the inside: the written side
   turns away, the underside turns toward the reader and covers what it came over. The
   container is a bare holder for the two faces — no overflow and no background, since
   `overflow: hidden` here would flatten the 3D and there would be no underside. */
.fold--flap {
  position: absolute;
  transform-origin: 50% 0;
  transform-style: preserve-3d;
  will-change: transform;
}

/* Panels past the first are simply the next panel down inside their parent's box, so they
   need no measuring — and they inherit its turn, which is what rolls the sheet up. */
.fold--flap--nested {
  top: 100%;
  left: 0;
  width: 100%;
  height: 100%;
}

.fold__face {
  position: absolute;
  inset: 0;
  overflow: hidden;
  backface-visibility: hidden;
  background: var(--bg-paper);
  border: 0.5px solid var(--border-subtle);
}

/* Only the last panel has the page's own bottom edge; every crease above it is straight. */
.fold__face--tail {
  border-radius: 0 0 var(--r-md) var(--r-md);
}

/* Pre-turned, so this is the face pointing at the reader once its panel has come over.
   The panel's own turn cancels this one out, so everything here is laid out the ordinary
   way up: the crease is the bottom edge, and the rounded top edge is the edge the panel
   brought over. Paper, shaded along the fold — these are the insides of the letter, seen
   for a moment on the way past. */
.fold__face--back {
  transform: rotateX(180deg);
  border-radius: var(--r-md) var(--r-md) 0 0;
  background: linear-gradient(to top, rgba(30, 27, 46, 0.09), rgba(30, 27, 46, 0.02) 20%, rgba(30, 27, 46, 0) 42%),
    var(--bg-paper);
}

/* The one exception: the underside still facing the reader when the folding is done. It
   takes the letter's own paper colour and carries the addressing, because what lands in
   the envelope's place has to be the thing the envelope then is. */
.fold__face--outer {
  border-color: var(--letter-line);
  background: linear-gradient(to top, rgba(30, 27, 46, 0.09), rgba(30, 27, 46, 0.02) 20%, rgba(30, 27, 46, 0) 42%),
    var(--letter-bg);
}

/* The addressing, on the outside of the folded letter — so the packet that travels
   already reads as the envelope. Deliberately the same metrics as the fields on 03, so
   the hand-over needs nothing to move. */
.fold__address {
  position: absolute;
  inset: 0;
  padding: var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.fold__field {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

/* An empty From is a ruled blank, the way an unaddressed envelope is. */
.fold__value {
  min-height: 20px;
  margin: 0;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--letter-line);
  font-size: 15px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: -0.1px;
}

/* The shadow a panel casts on the surface it is closing onto — the top panel, or the
   written side of the panel above, since rolling a letter up brings every fold down onto
   writing. It creeps up the page with the panel's leading edge: a shadow that arrived
   everywhere at once would read as the light changing rather than as paper moving. */
.fold--top::after,
.fold__face--front::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(30, 27, 46, 0.24), rgba(30, 27, 46, 0) 70%);
  opacity: 0;
}

/* Lifted off the page: a soft shadow under the packet as it sets off for the envelope.
   Its own element rather than a shadow on a panel, whose overflow clip — the thing that
   keeps the text inside the fold — would otherwise eat it. */
.fold__lift {
  position: absolute;
  border-radius: var(--r-md);
  box-shadow: 0 20px 44px -14px rgba(30, 27, 46, 0.34);
  opacity: 0;
}

.fold__inner {
  position: absolute;
  left: 0;
  width: 100%;
  padding: var(--sp-6) var(--sp-6) var(--sp-5);
  font-size: 15px;
  line-height: 22px;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-primary);
}

/* Entrance for a screen's contents: a short rise, staggered by position. */
.stagger > * {
  opacity: 0;
  transform: translateY(12px);
}

.stagger[data-state='in'] > * {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-move) var(--ease-soft), transform var(--dur-move) var(--ease-soft);
}

.stagger[data-state='in'] > :nth-child(2) {
  transition-delay: 60ms;
}
.stagger[data-state='in'] > :nth-child(3) {
  transition-delay: 120ms;
}
.stagger[data-state='in'] > :nth-child(4) {
  transition-delay: 180ms;
}
.stagger[data-state='in'] > :nth-child(n + 5) {
  transition-delay: 220ms;
}

/* ============================================================ desktop */

@media (min-width: 720px) {
  .screen {
    --pad-top: 72px;
  }
}

/* ============================================================ reduced motion */

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

  .peek-cue__chevron {
    animation: none;
  }

  /* The caret's last keyframe is the visible one, so stopping the animation leaves
     it drawn rather than blinked away. */
  .digit[data-state='focused']::before {
    animation: none;
    opacity: 1;
  }
}
