/* igotchu.app: monochrome, light and dark, system font, mobile first.
   People's words and the envelope's names are handwritten (Patrick Hand, the
   app's HANDWRITTEN; "Handwriting" below).
   Type sizes are in rem, written as calc(<px at the default size> / 17): the
   root is 17px by default and follows the reader's text size setting. */

:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --fg: #0b0b0b;
  --muted: #6b6b70;
  --line: #e4e4e4;
  /* Borders that mark a control (text fields, outlined buttons): 3:1 or more
     against the page and the message card. */
  --field: #8e8e8e;
  --surface: #f6f6f6;
  --radius: 16px;
  --space: 8px;
  /* The spacing scale (docs/build-notes/web.md, "Spacing"), the app's rhythm:
     related things (a heading and its line, a button and the quiet link
     under it) 8 apart; paragraphs in one block 12; a block's words and the
     button they explain 16; sections of a page 24; the page's big parts (the
     message, the keep invitation, the quiet controls) 40. */
  --related: 8px;
  --paragraph: 12px;
  --group: 16px;
  --section: 24px;
  --region: 40px;
  /* The envelope (an unopened message): neutral paper a step off the card
     gray, the flap a step further, the flap's inside darker again; the
     stamp's paper; the ink of the cancel lines and folds. */
  --env: #f2f2f2;
  --env-flap: #e9e9e9;
  --env-inside: #dadada;
  --stamp: #ffffff;
  --ink: #8e8e8e;
  /* Handwriting (the app's HANDWRITTEN): people's words and the envelope's
     names, in Patrick Hand (SIL Open Font License, /fonts/OFL.txt). Until the
     font arrives, and when it can't, the system font (font-display: swap). */
  --system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --handwritten: "Patrick Hand", var(--system);
  /* Sized by look, not by number: Patrick Hand runs small, so 22 sits where
     the system font's 20 would (the app's HANDWRITTEN_TYPE and ENVELOPE_TYPE). */
  --words-size: calc(22rem / 17);
  --words-leading: calc(29 / 22);
  --env-from-size: calc(18rem / 17);
  --env-to-size: calc(29rem / 17);
  /* The card's signature (the app's SIGNATURE_TYPE): a step under the words. */
  --sign-size: calc(20rem / 17);
}

@font-face {
  font-family: "Patrick Hand";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/PatrickHand-Regular.woff2") format("woff2");
}

/* The fallback to the system font. The app also falls back for iOS Bold Text
   and the accessibility text sizes; a web page can't see either, so here the
   one signal is Increase Contrast (prefers-contrast: more), someone asking
   for legibility over character. */
@media (prefers-contrast: more) {
  :root {
    --handwritten: var(--system);
    --words-size: calc(20rem / 17);
    --words-leading: 1.4;
    --env-from-size: calc(15rem / 17);
    --env-to-size: calc(24rem / 17);
    --sign-size: calc(16rem / 17);
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000000;
    --fg: #f2f2f2;
    --muted: #9c9ca1;
    --line: #2a2a2a;
    --field: #6e6e6e;
    /* The app's one card gray in dark mode (Apple's secondary background). */
    --surface: #1c1c1e;
    --env: #262628;
    --env-flap: #2e2e30;
    --env-inside: #141415;
    --stamp: #3a3a3c;
    --ink: #8a8a8e;
  }
}

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

[hidden] {
  display: none !important;
}

html {
  /* 17px at the browser's default 16px, and scales with its text size setting. */
  font-size: 106.25%;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* iPhone and iPad Safari: follow Settings > Display & Brightness > Text Size
   (17px at the default size). Not on a Mac, where this keyword is 13px. */
@supports (font: -apple-system-body) and (-webkit-touch-callout: none) {
  html {
    font: -apple-system-body;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--system);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.page {
  max-width: calc(576rem / 17);
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0 auto;
  padding: 0 max(20px, env(safe-area-inset-left)) 0 max(20px, env(safe-area-inset-right));
  display: flex;
  flex-direction: column;
}

/* 16px under the wordmark on a phone, so the gate's Open fits an iPhone SE. */
main {
  flex: 1;
  padding: calc(var(--space) * 2) 0 calc(var(--space) * 6);
}

a {
  color: inherit;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
  border-radius: 6px;
}

[tabindex="-1"]:focus {
  outline: none;
}

h1,
h2 {
  margin: 0;
  font-weight: 650;
  letter-spacing: -0.015em;
}

h1 {
  font-size: calc(28rem / 17);
  line-height: 1.2;
}

h2 {
  font-size: calc(19rem / 17);
  line-height: 1.3;
}

p {
  margin: var(--paragraph) 0 0;
}

.muted {
  color: var(--muted);
}

.small {
  font-size: calc(14rem / 17);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Header and footer */

.header {
  padding-top: max(20px, env(safe-area-inset-top));
}

/* The handwritten wordmark (public/brand/, 360 x 142), about the height of a
   line of text; white ink in dark mode. */
.wordmark {
  display: block;
  position: relative;
  width: 76px;
  height: 30px;
  background: url("/brand/wordmark-black.png") no-repeat left center / contain;
}

@media (prefers-color-scheme: dark) {
  .wordmark {
    background-image: url("/brand/wordmark-white.png");
  }
}

.footer {
  padding: calc(var(--space) * 3) 0 max(32px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
}

.footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 20px;
}

.footer a {
  color: var(--muted);
  font-size: calc(14rem / 17);
  text-decoration: none;
  padding: 8px 0;
}

.footer a:hover {
  text-decoration: underline;
}

/* Buttons */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space);
  min-height: 50px;
  padding: 0 22px;
  border: 1px solid transparent;
  border-radius: 999px;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 120ms ease-out, transform 120ms ease-out;
}

.button:active:not(:disabled) {
  transform: scale(0.98);
}

.button:disabled {
  opacity: 0.55;
  cursor: default;
}

.primary {
  background: var(--fg);
  color: var(--bg);
}

.secondary {
  background: transparent;
  color: var(--fg);
  border-color: var(--field);
}

.quiet {
  min-height: 44px;
  padding: 0;
  background: none;
  color: var(--muted);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.quiet.small {
  font-size: calc(14rem / 17);
}

/* A quiet button inside a line of text: it reads as part of the sentence, and
   its tap area reaches past the line without making the line taller. */
.quiet.inline {
  display: inline-block;
  min-height: 0;
  padding: 10px 0;
  margin: -10px 0;
  border: 0;
  font-size: inherit;
  line-height: inherit;
  vertical-align: baseline;
}

/* The gate */

.eyebrow {
  margin: 0 0 var(--related);
  color: var(--muted);
  font-size: calc(15rem / 17);
}


.actions {
  display: grid;
  gap: calc(var(--space) * 1.5);
  margin-top: calc(var(--space) * 3);
}

.status {
  min-height: 1.5em;
  margin-top: calc(var(--space) * 1.5);
  font-size: calc(15rem / 17);
}

.status:empty {
  min-height: 0;
  margin: 0;
}

/* The message: one card, the app's card, as a letter (Cards, v2): one quiet
   meta line (the type and its one date), the words, the photos, then the
   signature ("— Jordan Lee") with the reaction's small muted icon at its end,
   and "Written …" under it beside a moment or an occasion. The letter is
   inside the envelope until it's opened (below, "The envelope"); the two
   share one corner (--radius, the app's radius.card). 20 inside; 16 between
   the parts, 8 between the meta line and the words. */

.card {
  margin-top: calc(var(--space) * 2);
  padding: calc(var(--space) * 2.5);
  border-radius: var(--radius);
  background: var(--surface);
}

.card:focus {
  outline: none;
}

/* With the page script, a tap on the envelope opens it, like Open. */
.stage.can-open {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* The meta line: the type (a touch firmer) and its one date, small and
   muted, in the system font. No outlined tag: outlines are for controls. */
.meta {
  margin: 0;
  color: var(--muted);
  font-size: calc(13rem / 17);
  line-height: calc(18 / 13);
}

.meta .type {
  font-weight: 500;
}

/* The signature, as a letter ends: the giver's name, handwritten and muted,
   a step under the words; the reaction that counts on this link at its end. */
.sign {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space);
  margin-top: calc(var(--space) * 2);
}

.signature {
  margin: 0;
  color: var(--muted);
  font-family: var(--handwritten);
  font-size: var(--sign-size);
  font-weight: 400;
  line-height: calc(25 / 20);
}

/* The reaction: the solid icon, small, in the muted gray, with no circle. */
.mark {
  display: inline-flex;
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--muted);
}

.mark[hidden] {
  display: none;
}

.mark svg {
  width: 100%;
  height: 100%;
}

/* When it was written, under the signature (8 apart: it belongs to it). */
.written {
  margin-top: var(--space);
}

/* People's words, handwritten (one weight: never bold). */
.words {
  position: relative;
  margin-top: var(--space);
  font-family: var(--handwritten);
  font-size: var(--words-size);
  font-weight: 400;
}

.body {
  margin: 0;
  font-size: 1em;
  line-height: var(--words-leading);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.photos {
  display: grid;
  gap: var(--space);
  margin-top: calc(var(--space) * 2);
}

.photo {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
  background: var(--line);
  animation: fade-in 300ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

/* A photo still on its way holds a 4:3 place, so the ones further down wait
   to load until they're near the screen (loading="lazy"). */
.photo.is-pending {
  aspect-ratio: 4 / 3;
}

/* The envelope (brand platform, Part 7; the app's Envelope.tsx and
   RevealCard.tsx). Sealed, the stage shows the envelope's front, with the card
   (the letter) out of sight inside it. Opening (message.js adds .is-opening
   and sets, in px: --x, --y and --w, where the stage sits on screen; --t, how
   far the envelope travels to the middle of the screen; --f, the flap's depth;
   --fall, how far it falls to leave the screen; --out, how far the letter
   comes out; --home, the letter's last move to its place; --room and --he,
   the clipping window): the moving parts are held fixed where the stage is,
   and the stage keeps the letter's place in the page. Then, as the app
   (Jacob, October 25: the real message comes out, no cross-fade; "the
   envelope moves down (off the screen) while the letter moves up (into
   place)"):

     1. 0–460 ms: the envelope moves to the middle of the screen as it turns
        over (rotateY) to its back, the flap shut.
     2. 500–900: the flap folds open toward you (rotateX) and its inside rises
        behind the letter, while the envelope steps down by half the flap's
        depth, so the open envelope stays centered.
     3. 880–1300: the letter, the card itself with its words, at 0.92 scale,
        slides up out of the envelope's mouth.
     4. 1180–1600: the envelope falls off the bottom of the screen, picking up
        speed; 1220–1780 the letter travels to its place in the page and grows
        to full width; the page fades back in around it (1220–1640).

   1800 ms to still. Transforms and opacity only: the letter is clipped to the
   envelope's mouth by a window that moves with the envelope; inside it, the
   hold undoes the fall and the lift brings the letter out, so the clip edge
   follows the envelope with no layout per frame. Without the page script it
   stays sealed and Open posts the form; under reduced motion it's a
   cross-fade (.is-fading). */

.stage {
  position: relative;
  margin-top: calc(var(--space) * 2);
}

.stage .card {
  margin-top: 0;
}

.env-window {
  position: relative;
}

.stage.is-sealed .env-window {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
}

.stage.is-sealed .env-hold,
.stage.is-sealed .env-lift {
  position: absolute;
  inset: 0;
}

.stage.is-sealed .card {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  transform-origin: 50% 0;
}

.env-front {
  position: relative;
  z-index: 3;
}

.env-back {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
}

.env-face {
  /* The letter's own corners: the envelope and the card that comes out of it are one shape. */
  border-radius: var(--radius);
  background: var(--env);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* The front, addressed like a letter: the sender (top left, small), the
   stamp (top right), and the address block in the middle of the envelope
   (a spacer the stamp's height under it keeps it centered). A wide
   greeting-card envelope, 1.9 : 1 (the app's ENVELOPE.heightRatio; Jacob,
   October 24: shorter than most opened cards); taller only for more names. */
.env-face.front {
  position: relative;
  display: flex;
  flex-direction: column;
  aspect-ratio: 1.9 / 1;
  padding: calc(var(--space) * 1.75);
  border: 0.5px solid var(--line);
}

.env-face.front::after {
  content: "";
  height: 44px;
}

.env-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(var(--space) * 2);
}

.env-from {
  margin: 0;
  padding-top: 2px;
  font-family: var(--handwritten);
  font-size: var(--env-from-size);
  font-weight: 400;
  line-height: 1.3;
  /* Who it's from, underlined with a squiggle in ink, as in the app (October 25). */
  text-decoration: underline wavy;
  text-decoration-color: color-mix(in srgb, var(--ink) 75%, transparent);
  text-decoration-thickness: 1.3px;
  text-underline-offset: 2px;
}

/* One stamp: a small rectangle whose edge is perforated (a dotted border in
   the envelope's color), the "i" logo mark, and three wavy cancel lines over its edge. */
.stamp {
  position: relative;
  display: flex;
  flex: none;
  width: 36px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border: 3px dotted var(--env);
  background: var(--stamp);
}

/* The "i" logo mark (public/brand/mark.png, 226 x 240), in the muted ink:
   the image is the mask, the color shows through it. */
.stamp-mark {
  width: 21px;
  height: 22px;
  background-color: var(--muted);
  -webkit-mask: url("/brand/mark.png") no-repeat center / contain;
  mask: url("/brand/mark.png") no-repeat center / contain;
}

.cancel {
  position: absolute;
  left: -26px;
  top: 9px;
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.1;
  stroke-opacity: 0.7;
}

.env-address {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: calc(var(--space) / 2) 0;
  text-align: center;
}

.env-to {
  margin: 0;
  font-family: var(--handwritten);
  font-size: var(--env-to-size);
  font-weight: 400;
  line-height: 1.3;
}

.env-line {
  margin: calc(var(--space) / 2) 0 0;
  color: var(--muted);
  font-size: calc(13rem / 17);
}

/* While it opens, only the envelope: the rest of the page fades away around
   it (at once under reduced motion, back in once the letter has settled;
   moving, back in around the letter as it travels to its place). */
.header,
.footer,
main > * {
  transition: opacity 250ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.envelope-only .header,
.envelope-only .footer,
.envelope-only main > :not(.stage) {
  opacity: 0;
  transition-duration: 160ms;
}

.envelope-moving .header,
.envelope-moving .footer,
.envelope-moving main > :not(.stage) {
  animation: page-curtain 1800ms linear both;
}

/* The back, out of sight until it turns over, closed (the app's
   EnvelopeBack): the side folds, the bottom flap to 44% of the height, and
   the top flap down over it to 60% (past the bottom flap's point), its point
   softly rounded, a soft shadow under its edges so it reads as shut. A
   hairline edge, as the front. No seal (Jacob, October 25). */
.env-face.back {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border: 0.5px solid var(--line);
  opacity: 0;
  transform: perspective(1050px) rotateY(90deg);
}

/* The top flap: 60% of the height, and its shadow under it (the app's ENVELOPE.flapRatio). */
.flap {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 61.6%;
  transform-origin: 50% 0;
}

.flap svg,
.flap-in svg,
.folds {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.flap .fill,
.folds .bottom {
  fill: var(--env-flap);
}

.flap .shadow {
  fill: var(--ink);
  fill-opacity: 0.22;
}

.folds .fold,
.folds .edge,
.flap .edge {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.folds .fold {
  stroke-opacity: 0.18;
}

.folds .edge {
  stroke-opacity: 0.3;
}

.flap .edge {
  stroke-opacity: 0.4;
}

/* The flap's inside, rising from the envelope's top edge behind the letter. */
.flap-in {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: var(--f, 0px);
  transform-origin: 50% 100%;
  opacity: 0;
}

.flap-in svg {
  fill: var(--env-inside);
}

/* Opening: the envelope, the flap's inside and the letter's window held where
   the stage is on screen, above everything, each moving with the envelope. */
.stage.is-opening > .env-back,
.stage.is-opening > .env-window,
.stage.is-opening > .env-front {
  position: fixed;
  top: var(--y, 0px);
  right: auto;
  bottom: auto;
  left: var(--x, 0px);
  width: var(--w, 100%);
  animation: env-move 1800ms linear both;
}

.stage.is-opening > .env-back {
  z-index: 11;
}

.stage.is-opening > .env-window {
  z-index: 12;
  top: calc(var(--y, 0px) - var(--room, 0px));
  height: calc(var(--room, 0px) + var(--he, 0px));
}

.stage.is-opening > .env-front {
  z-index: 13;
}

.is-opening .env-hold {
  animation: env-hold 1800ms linear both;
}

.is-opening .env-lift {
  animation: letter-lift 1800ms linear both;
}

.stage.is-opening .card {
  top: var(--room, 0px);
  animation: letter-home 1800ms linear both;
}

.is-opening .env-face.front {
  animation: flip-away 1800ms linear both;
}

.is-opening .env-face.back {
  animation: flip-in 1800ms linear both;
}

.is-opening .flap {
  animation: flap-open 1800ms linear both;
}

.is-opening .flap-in {
  animation: flap-rise 1800ms linear both;
}

/* The beats, as shares of 1800 ms. The easings: the app's "pull" (an even
   in-and-out, like a hand doing it), cubic-bezier(0.45, 0, 0.2, 1), and its
   "away" (picking up speed as it leaves), cubic-bezier(0.4, 0, 1, 1). */

/* The envelope (and what moves with it): to the middle 0–460, a step down by
   half the flap 500–900, off the bottom of the screen 1180–1600. */
@keyframes env-move {
  0% {
    transform: translateY(0);
    animation-timing-function: cubic-bezier(0.45, 0, 0.2, 1);
  }
  25.556%,
  27.778% {
    transform: translateY(var(--t, 0px));
    animation-timing-function: cubic-bezier(0.45, 0, 0.2, 1);
  }
  50%,
  65.556% {
    transform: translateY(calc(var(--t, 0px) + var(--f, 0px) / 2));
    animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
  }
  88.889%,
  100% {
    transform: translateY(calc(var(--t, 0px) + var(--f, 0px) / 2 + var(--fall, 0px)));
  }
}

/* Inside the window: the letter doesn't fall with the envelope. */
@keyframes env-hold {
  0%,
  65.556% {
    transform: translateY(0);
    animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
  }
  88.889%,
  100% {
    transform: translateY(calc(0px - var(--fall, 0px)));
  }
}

/* Out of the envelope's mouth, 880–1300: from 16 inside it until most of it is out. */
@keyframes letter-lift {
  0%,
  48.889% {
    transform: translateY(16px);
    animation-timing-function: cubic-bezier(0.45, 0, 0.2, 1);
  }
  72.222%,
  100% {
    transform: translateY(calc(0px - var(--out, 0px)));
  }
}

/* The letter itself: out of sight until the envelope has turned over, a little
   narrower than the envelope while it's in it, then to its place and full
   width, 1220–1780. Never faded: it is the card. */
@keyframes letter-home {
  0%,
  25.55% {
    transform: translateY(0) scale(0.92);
    opacity: 0;
  }
  25.556% {
    opacity: 1;
  }
  67.778% {
    transform: translateY(0) scale(0.92);
    animation-timing-function: cubic-bezier(0.45, 0, 0.2, 1);
  }
  98.889%,
  100% {
    transform: translateY(var(--home, 0px)) scale(1);
    opacity: 1;
  }
}

/* The turn, 0–460: the front away to edge-on, then the back in from edge-on. */
@keyframes flip-away {
  0% {
    transform: perspective(1050px) rotateY(0);
    opacity: 1;
    animation-timing-function: cubic-bezier(0.45, 0, 1, 1);
  }
  12.778% {
    transform: perspective(1050px) rotateY(-90deg);
    opacity: 1;
  }
  12.789%,
  100% {
    transform: perspective(1050px) rotateY(-90deg);
    opacity: 0;
  }
}

@keyframes flip-in {
  0%,
  12.778% {
    transform: perspective(1050px) rotateY(90deg);
    opacity: 0;
    animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
  }
  12.789% {
    opacity: 1;
  }
  25.556%,
  100% {
    transform: perspective(1050px) rotateY(0);
    opacity: 1;
  }
}

/* The flap, 500–900: down to edge-on toward you, then its inside up from edge-on behind the letter. */
@keyframes flap-open {
  0%,
  27.778% {
    transform: perspective(700px) rotateX(0);
    opacity: 1;
    animation-timing-function: cubic-bezier(0.45, 0, 1, 1);
  }
  38.889% {
    transform: perspective(700px) rotateX(90deg);
    opacity: 1;
  }
  38.9%,
  100% {
    transform: perspective(700px) rotateX(90deg);
    opacity: 0;
  }
}

@keyframes flap-rise {
  0%,
  38.889% {
    transform: perspective(700px) rotateX(-90deg);
    opacity: 0;
    animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
  }
  38.9% {
    opacity: 1;
  }
  50%,
  100% {
    transform: perspective(700px) rotateX(0);
    opacity: 1;
  }
}

/* The rest of the page: away at once (160 ms), back around the letter as it travels home. */
@keyframes page-curtain {
  0% {
    opacity: 1;
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  8.889%,
  67.778% {
    opacity: 0;
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  91.111%,
  100% {
    opacity: 1;
  }
}

/* Reduced motion: the envelope fades as the letter fades in, in place.
   Opened without JavaScript: the words fade in. */
.is-fading .env-front,
.is-fading .env-back {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  animation: fade-out 250ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

.stage.is-fading .card,
.is-served .body {
  animation: fade-in 250ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fade-out {
  to {
    opacity: 0;
  }
}

/* After Open, the reactions and the keep invitation take their place at once
   (so nothing below jumps later) but stay out of sight and out of reach while
   the words come in; they fade in together once the card settles, so the
   words are the one thing that moves (message.js). */
.react,
.keep {
  transition: opacity 250ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.is-arriving {
  visibility: hidden;
  opacity: 0;
}

/* The reactions: one tap per link. The chosen one's circle fills with the
   text color and its icon inverts; the others keep the muted icon. */

.react {
  margin-top: var(--section);
}

.reactions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space);
}

.reaction {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 52px;
  height: 52px;
  padding: 0;
  border: 1px solid var(--field);
  border-radius: 50%;
  background: var(--bg);
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 250ms ease, border-color 250ms ease, color 250ms ease;
}

.reaction[aria-pressed="true"] {
  border-color: var(--fg);
  background: var(--fg);
  color: var(--bg);
}

.reaction:disabled,
.reaction[aria-disabled="true"] {
  cursor: default;
}

.react-line {
  margin-top: var(--related);
}

/* Reactions, v4 (October 24): four, love, moved, laugh, blush, drawn with
   Font Awesome Free solid icons (the app's paths), each with its name under
   it. The icon is always solid; the button shows the choice (above). */

.reaction-item {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 52px;
}

.reaction-item .caption {
  color: var(--muted);
  font-size: calc(12rem / 17);
  line-height: 1.3;
  transition: color 250ms ease, opacity 250ms ease;
}

.reaction-item:has([aria-pressed="true"]) .caption {
  color: var(--fg);
}

.reaction .glyph {
  position: relative;
  display: block;
  width: 24px;
  height: 24px;
  font-size: 0;
}

.icon {
  display: block;
  width: 100%;
  height: 100%;
}

/* Choosing one gets one small moment on its icon, fitting its feeling, then
   it rests (the app's REACTION_MOMENTS; message.js adds .pop): love beats,
   moved swells, laugh giggles, blush ducks shyly. Transforms
   only; none under reduced motion (the circle just fills). */

.reaction.pop[data-kind="love"] .glyph {
  animation: beat 480ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@keyframes beat {
  23% {
    transform: scale(1.2);
  }
  46% {
    transform: scale(0.95);
  }
  67% {
    transform: scale(1.1);
  }
}

.reaction.pop[data-kind="moved"] .glyph {
  animation: swell 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@keyframes swell {
  37%,
  60% {
    transform: translateY(-2px) scale(1.14);
  }
}

.reaction.pop[data-kind="laugh"] .glyph {
  animation: giggle 370ms ease-in-out;
}

@keyframes giggle {
  19% {
    transform: scale(1.06) rotate(-10deg);
  }
  38% {
    transform: scale(1.06) rotate(10deg);
  }
  57% {
    transform: scale(1.04) rotate(-6deg);
  }
  76% {
    transform: scale(1.02) rotate(4deg);
  }
}

.reaction.pop[data-kind="blush"] .glyph {
  animation: shy 540ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@keyframes shy {
  33%,
  56% {
    transform: translateY(1px) scale(0.86) rotate(-8deg);
  }
}

.keep {
  margin-top: var(--region);
}

/* The heading and its line read as one. */
.keep h2 + p {
  margin-top: var(--related);
}

/* The words sit close above the button they explain. */
.keep .store {
  margin-top: var(--group);
  width: 100%;
}

.keep .open-in-app {
  margin-top: var(--related);
}

/* The soft pay-it-forward line is its own thought, a section under the rest. */
.keep #pay-it-forward {
  margin-top: var(--section);
}

/* Panels: don't contact me again, report */

.panel {
  margin-top: var(--region);
  padding: calc(var(--space) * 3);
  border: 1px solid var(--line);
  border-radius: 24px;
}

fieldset {
  margin: calc(var(--space) * 2) 0 0;
  padding: 0;
  border: 0;
}

legend {
  padding: 0;
  margin-bottom: var(--space);
  font-weight: 600;
}

.choice {
  display: flex;
  gap: calc(var(--space) * 1.5);
  align-items: flex-start;
  padding: calc(var(--space) * 1.5) 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}

.choice:last-of-type {
  border-bottom: 0;
}

input[type="radio"],
input[type="checkbox"] {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  accent-color: var(--fg);
}

.field {
  display: grid;
  gap: 6px;
  margin-top: calc(var(--space) * 2);
}

.field > span:first-child {
  font-weight: 600;
  font-size: calc(15rem / 17);
}

input[type="text"],
input[type="email"],
input[type="url"],
textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--field);
  border-radius: 12px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  /* Never under 16px, or iPhone Safari zooms in when the field is focused. */
  font-size: max(1rem, 16px);
}

input:focus,
textarea:focus {
  border-color: var(--fg);
  outline: none;
}

textarea {
  resize: vertical;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--space) * 1.5);
  align-items: center;
  margin-top: calc(var(--space) * 2);
}

/* The person check runs out of sight ('interaction-only'); it takes room only if it needs a tap. */
.turnstile,
.cf-turnstile {
  margin-top: calc(var(--space) * 2);
}

.page-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--group);
  margin-top: var(--region);
}

/* Plain pages */

.plain {
  padding-top: 12vh;
}

/* A plain page's one button ("Try again") sits a group under the words it answers. */
.plain .action {
  margin-top: var(--group);
}

/* A line that explains the one above it ("Include it if you write to us…" under the reference). */
.hint {
  margin-top: var(--related);
}

.lead {
  font-size: calc(20rem / 17);
  line-height: 1.4;
}

.hero .button {
  margin-top: calc(var(--space) * 3);
}

section + section {
  margin-top: calc(var(--space) * 6);
}

.list {
  margin: calc(var(--space) * 1.5) 0 0;
  padding: 0;
  list-style: none;
}

.list li {
  padding: calc(var(--space) * 1.25) 0;
  border-bottom: 1px solid var(--line);
}

.list li:last-child {
  border-bottom: 0;
}

.list .muted {
  display: block;
  font-size: calc(14rem / 17);
}

.prose h2 {
  margin-top: calc(var(--space) * 4);
}

.prose section + section {
  margin-top: 0;
}

/* The policy pages served from the launch drafts. */
.draft-note {
  padding: 12px 16px;
  border-radius: 12px;
  background: var(--surface);
  font-size: calc(15rem / 17);
}

.draft-note + h1 {
  margin-top: calc(var(--space) * 3);
}

.legal h3 {
  margin-top: calc(var(--space) * 3);
  font-size: 1rem;
}

.legal ul,
.legal ol {
  /* A list sits a paragraph's gap under the line that leads into it ("It does:"), like any p, not
     the browser's 1em above and below. */
  margin: var(--paragraph) 0 0;
  padding-left: 1.25em;
}

.legal li + li {
  margin-top: var(--related);
}

.legal li > ul {
  margin-top: var(--related);
}

.legal blockquote {
  margin: calc(var(--space) * 2) 0;
  padding-left: calc(var(--space) * 2);
  border-left: 2px solid var(--line);
  color: var(--muted);
}

.legal hr {
  margin: calc(var(--space) * 4) 0;
  border: 0;
  border-top: 1px solid var(--line);
}

.legal a {
  overflow-wrap: anywhere;
}

.table-wrap {
  margin: calc(var(--space) * 2) 0;
  overflow-x: auto;
}

.legal table {
  width: 100%;
  border-collapse: collapse;
  font-size: calc(15rem / 17);
}

.legal th,
.legal td {
  padding: calc(var(--space) * 1.25) calc(var(--space) * 1.5) calc(var(--space) * 1.25) 0;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

.legal th {
  font-weight: 600;
}

.placeholder {
  padding: 12px 16px;
  border-radius: 12px;
  background: var(--surface);
  color: var(--muted);
  font-size: calc(15rem / 17);
}

.help {
  margin-top: calc(var(--space) * 3);
}

.stores {
  display: grid;
  gap: calc(var(--space) * 1.5);
  justify-items: start;
  margin-top: calc(var(--space) * 3);
}

.stores .button,
.stores p {
  margin-top: 0;
}

.errors {
  margin-top: calc(var(--space) * 2);
  padding: 12px 16px;
  border: 1px solid var(--fg);
  border-radius: 12px;
}

.errors ul {
  margin: 0;
  padding-left: 20px;
}

.form .button {
  margin-top: calc(var(--space) * 2);
  width: 100%;
}

.form .choice {
  border-bottom: 0;
  margin-top: var(--space);
}

code.reference {
  padding: 2px 6px;
  border-radius: 6px;
  background: var(--surface);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: calc(15rem / 17);
  word-break: break-all;
}

@media (min-width: 640px) {
  main {
    padding-top: calc(var(--space) * 8);
  }

  h1 {
    font-size: calc(32rem / 17);
  }

  .actions {
    grid-template-columns: 1fr 1fr;
  }

  .card {
    margin-top: calc(var(--space) * 3);
  }

  .keep .store,
  .form .button {
    width: auto;
  }
}

/* The home page (views/home.tsx): one column, the brand platform's order.
   The wordmark is the hero; sections are the page's big parts (--region
   apart, a little more on a computer). */

.home-hero {
  padding-top: var(--section);
}

/* Jacob's wordmark, in the page's ink, written on as a pen would write it:
   the word (1.3 s, the app's pen pace), a lift, the t's crossbar, a lift,
   the i's dot. Each stroke starts hidden (its dash pushed off by its own
   length plus the pen's width) and is drawn by its dash offset. Without
   animation (reduced motion, or a browser without it) it's simply there. */
.hero-mark {
  display: block;
  width: 168px;
  height: auto;
  margin: 0 0 var(--section) -6px;
  color: var(--fg);
  overflow: visible;
}

.hero-mark .pen-0 {
  animation: pen-word 1300ms cubic-bezier(0.3, 0.05, 0.3, 1) 250ms both;
}

.hero-mark .pen-1 {
  animation: pen-crossbar 180ms cubic-bezier(0.4, 0, 0.6, 1) 1600ms both;
}

.hero-mark .pen-2 {
  animation: pen-dot 110ms ease-out 1830ms both;
}

@keyframes pen-word {
  0% { stroke-dashoffset: 5578; stroke-opacity: 0; }
  1% { stroke-opacity: 1; }
  100% { stroke-dashoffset: 0; stroke-opacity: 1; }
}

@keyframes pen-crossbar {
  0% { stroke-dashoffset: 425; stroke-opacity: 0; }
  1% { stroke-opacity: 1; }
  100% { stroke-dashoffset: 0; stroke-opacity: 1; }
}

@keyframes pen-dot {
  0% { stroke-dashoffset: 86; stroke-opacity: 0; }
  1% { stroke-opacity: 1; }
  100% { stroke-dashoffset: 0; stroke-opacity: 1; }
}

.home-hero h1 {
  max-width: 18em;
  text-wrap: balance;
}

.home-hero .lead {
  margin-top: var(--paragraph);
  text-wrap: pretty;
}

/* "Coming soon to iPhone." A quiet label with a hairline rule before it,
   not a button: there's nothing to tap yet. */
.soon {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: var(--section);
  font-size: calc(15rem / 17);
  font-weight: 500;
}

.soon::before {
  content: "";
  width: 20px;
  height: 1px;
  background: var(--fg);
}

section.home-section {
  margin-top: calc(var(--region) * 1.5);
}

.home-section h2 + * {
  margin-top: var(--group);
}

/* The example message: the envelope as the message page draws it, then the
   letter that came out of it, a related step under it. */
.example {
  margin: 0;
}

.example-envelope .env-face {
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.04), 0 8px 24px rgb(0 0 0 / 0.05);
}

.example .example-card {
  margin-top: var(--group);
}

.steps {
  margin: var(--group) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
  display: grid;
  gap: var(--section);
}

.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 28px 1fr;
  column-gap: var(--paragraph);
}

.steps li::before {
  content: counter(step);
  grid-row: 1 / span 2;
  color: var(--muted);
  font-size: calc(15rem / 17);
  line-height: calc(25 / 15);
  font-variant-numeric: tabular-nums;
}

h3 {
  margin: 0;
  font-size: calc(17rem / 17);
  font-weight: 600;
  line-height: 1.45;
}

.steps p {
  margin-top: 2px;
  color: var(--muted);
}

.promises {
  margin: var(--group) 0 0;
  padding: 0;
  list-style: none;
}

.promises li {
  padding: var(--paragraph) 0;
  border-top: 1px solid var(--line);
}

.promises li:last-child {
  border-bottom: 1px solid var(--line);
}

.copyright {
  margin-top: var(--paragraph);
  color: var(--muted);
  font-size: calc(13rem / 17);
}

/* Reduced motion: the wordmark is simply there. */
@media (prefers-reduced-motion: reduce) {
  .hero-mark .pen {
    animation: none;
  }
}

@media (min-width: 640px) {
  .home-hero {
    padding-top: var(--region);
  }

  .hero-mark {
    width: 200px;
  }

  section.home-section {
    margin-top: calc(var(--region) * 2);
  }
}

@media (prefers-reduced-motion: reduce) {
  .stage.is-opening,
  .stage.is-opening *,
  .envelope-moving .header,
  .envelope-moving .footer,
  .envelope-moving main > *,
  .reaction.pop .glyph {
    animation: none !important;
  }

  .button,
  .reaction {
    transition: none;
  }
}

/* /licenses: each license's full text, small and muted, its paragraphs reflowed. */
.license-text p {
  overflow-wrap: anywhere;
  font-size: calc(13rem / 17);
  line-height: 1.45;
  color: var(--muted);
}
