/* =========================================================
   A letter for you — "soft morning light"
   ========================================================= */

:root{
  /* ground */
  --bg:         #eddfd8;
  --bg-warm:    #e4d0c8;
  --bg-blush:   #d9bfb8;

  /* rose family */
  --rose:       #d09a95;
  --rose-light: #e6bdb7;
  --rose-deep:  #b0736f;
  --clay:       #8d6360;
  --clay-soft:  #a8817e;

  /* papers */
  --env:        #fdfaf6;
  --env-2:      #f4e8e0;
  --env-3:      #e9d8cf;
  --paper:      #ffffff;
  --paper-2:    #f6f6f4;
  --paper-edge: #e4e4e0;
  --rule-line:  rgba(28,36,54,.15);
  --margin-line:rgba(40,40,46,.18);

  /* ink */
  --ink:        #16161a;
  --ink-soft:   rgba(22,22,26,.45);

  /* soft warm shadow, never grey */
  --sh-1:       rgba(150,106,100,.13);
  --sh-2:       rgba(150,106,100,.20);
  --sh-3:       rgba(130,88,84,.26);

  --font-hand:  "Caveat", "Segoe Script", "Bradley Hand", "Brush Script MT", cursive;
  --font-script:"Parisienne", "Snell Roundhand", "Apple Chancery", cursive;
  --font-serif: "Cormorant Garamond", "Iowan Old Style", Georgia, serif;

  --ease-out:   cubic-bezier(.16,1,.3,1);
  --ease-soft:  cubic-bezier(.32,.72,0,1);
  --ease-back:  cubic-bezier(.34,1.56,.64,1);

  --env-w: min(430px, 90vw);
}

*,*::before,*::after{ box-sizing:border-box; }
html,body{ height:100%; }

body{
  margin:0;
  min-height:100svh;
  background:
    radial-gradient(70% 55% at 22% 10%, rgba(255,252,248,.85) 0%, transparent 58%),
    radial-gradient(85% 65% at 88% 94%, var(--bg-blush) 0%, transparent 60%),
    radial-gradient(60% 50% at 94% 4%,  #f0ddd5 0%, transparent 56%),
    linear-gradient(168deg, var(--bg) 0%, #ead9d1 52%, var(--bg-warm) 100%);
  background-attachment: fixed;
  color: var(--ink);
  font-family: var(--font-serif);
  overflow:hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
body.reading{ overflow-y:auto; overflow-x:hidden; }

/* ── atmosphere ──────────────────────────────────────── */

#petals,.light-wash,.linen{ position:fixed; inset:0; pointer-events:none; }
#petals{ z-index:1; }

/* morning light falling in from the upper left */
.light-wash{
  z-index:0;
  background:
    linear-gradient(118deg, rgba(255,255,255,.75) 0%, rgba(255,255,255,.18) 30%, transparent 52%),
    radial-gradient(50% 40% at 18% -6%, rgba(255,252,246,.9), transparent 70%);
  animation: breathe 11s ease-in-out infinite;
}
@keyframes breathe{
  0%,100%{ opacity:.9; }
  50%    { opacity:1; }
}

/* a whisper of linen weave, so the ground isn't flat colour */
.linen{
  z-index:2; opacity:.055; mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── stage ───────────────────────────────────────────── */

.stage{
  position:relative; z-index:4;
  display:grid; grid-template-columns:1fr;
  align-items:start; justify-items:center;
  padding: clamp(14px,3.6vw,48px);
  transition: opacity .5s ease;
}
body.is-loading .stage{ opacity:0; }

.scene{
  grid-area:1/1;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  width:100%;
}
/* min-height, never height: the scene grows past the fold rather than
   centring itself into a box whose top can't be scrolled back to. */
.scene--letter  { min-height: calc(100svh - clamp(28px,7.2vw,96px)); }
.scene--envelope{ min-height: calc(100svh - clamp(28px,7.2vw,96px)); }

.scene--letter{
  opacity:0; visibility:hidden; pointer-events:none;
  transform: translateY(26px) scale(.94);
  filter: blur(6px);
  transition: opacity .7s var(--ease-out), transform .9s var(--ease-out), filter .7s var(--ease-out), visibility 0s .9s;
}
body.reading .scene--letter{
  opacity:1; visibility:visible; pointer-events:auto;
  transform:none; filter:none;
  transition: opacity .9s .12s var(--ease-out), transform 1.05s .12s var(--ease-out), filter .9s .12s var(--ease-out), visibility 0s;
}

.scene--envelope{
  transition: opacity .55s var(--ease-out), transform .8s var(--ease-out), filter .6s var(--ease-out);
}
body.reading .scene--envelope{
  opacity:0; transform: translateY(-30px) scale(.86); filter: blur(8px); pointer-events:none;
}

/* ── framing type ────────────────────────────────────── */

.eyebrow{
  margin:0 0 clamp(30px,5.5vh,58px);
  font-family:var(--font-serif); font-weight:300; font-style:italic;
  font-size: clamp(.82rem, 2.6vw, 1.02rem);
  letter-spacing:.26em; text-transform:lowercase;
  color: var(--clay-soft);
  text-align:center;
}

.addressee{
  margin: clamp(30px,4.6vh,46px) 0 0;
  font-family: var(--font-script);
  font-size: clamp(2rem, 7.5vw, 3.1rem);
  line-height:1;
  color: var(--clay);
  text-align:center;
}

.hint{
  margin: clamp(30px,6.5vh,68px) 0 0;
  display:flex; align-items:center; gap:.7em;
  font-size:.74rem; letter-spacing:.34em; text-transform:uppercase;
  color: var(--clay-soft);
}
.hint-dot{
  width:5px; height:5px; border-radius:50%; background:var(--rose);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse{ 0%,100%{opacity:.3; transform:scale(.8)} 50%{opacity:1; transform:scale(1.2)} }

/* the entrance uses fill:forwards, so fading these out later
   also has to be an animation — a plain opacity rule loses. */
body.ready .eyebrow.fade,
body.ready .hint.gone{
  animation: fade-away .6s var(--ease-out) forwards;
  pointer-events:none;
}
@keyframes fade-away{
  from{ opacity:1; transform:none; }
  to  { opacity:0; transform:translateY(-10px); }
}

/* ── entrance ────────────────────────────────────────── */

.eyebrow,.envelope-scene,.hint{ opacity:0; }
body.ready .eyebrow      { animation: rise .9s .15s var(--ease-out) forwards; }
body.ready .envelope-scene{ animation: settle 1.4s .38s var(--ease-out) forwards; }
body.ready .hint         { animation: rise .9s 1.6s var(--ease-out) forwards; }

@keyframes rise{ from{opacity:0; transform:translateY(14px)} to{opacity:1; transform:none} }
@keyframes settle{
  0%  { opacity:0; transform:translateY(-40px) rotate(-2.5deg) scale(.94); }
  65% { opacity:1; transform:translateY(6px)   rotate(.6deg)   scale(1.005); }
  100%{ opacity:1; transform:none; }
}

/* ── the envelope ────────────────────────────────────── */

.envelope-scene{
  position:relative;
  perspective: 1500px;
  perspective-origin: 50% 38%;
  display:flex; flex-direction:column; align-items:center;
}

/* soft cast shadow, thrown down-right by the morning light */
.env-cast{
  position:absolute; left:50%; top:0;
  width: calc(var(--env-w) * .96);
  height: calc(var(--env-w) / 1.45);
  transform: translateX(-46%) translateY(14px) rotate(-1.5deg) skewX(-9deg) scaleY(.96);
  background: radial-gradient(58% 54% at 50% 52%, var(--sh-2), transparent 74%);
  filter: blur(22px);
  animation: cast 7.5s ease-in-out infinite;
}
@keyframes cast{
  0%,100%{ opacity:1;   transform: translateX(-46%) translateY(14px) rotate(-1.5deg) skewX(-9deg) scaleY(.96); }
  50%    { opacity:.72; transform: translateX(-46%) translateY(24px) rotate(-1.5deg) skewX(-11deg) scaleY(.9); }
}

.envelope{
  position:relative;
  width: var(--env-w);
  aspect-ratio: 1.45 / 1;
  cursor:pointer;
  user-select:none; -webkit-user-select:none;
  transform-style: preserve-3d;
  -webkit-transform-style: preserve-3d;
  /* seen slightly from above, resting on the surface */
  animation: hover 7.5s ease-in-out infinite;
  transition: filter .6s var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}
@keyframes hover{
  0%,100%{ transform: rotateX(13deg) rotateZ(-1.4deg) translateY(0); }
  50%    { transform: rotateX(10deg) rotateZ(-.6deg)  translateY(-10px); }
}
.envelope:focus-visible{ outline:2px solid var(--rose-deep); outline-offset:20px; border-radius:6px; }
.envelope.opening,.envelope.opened{ animation-play-state:paused; cursor:default; }

@media (hover:hover){
  .envelope:hover .ribbon{ transform: translateZ(4px) scale(1.035) rotate(-.6deg); }
  .envelope:hover .bow-loop--l{ transform: rotate(-3deg); }
  .envelope:hover .bow-loop--r{ transform: rotate(3deg); }
}

/* the back panel */
.env-shell{
  position:absolute; inset:0;
  transform: translateZ(0);
  border-radius:4px;
  background:
    linear-gradient(150deg, var(--env) 0%, #faf1ea 46%, var(--env-2) 100%);
  box-shadow:
    0 1px 0 rgba(255,255,255,.9) inset,
    0 18px 30px -16px var(--sh-2),
    0 40px 60px -34px var(--sh-3);
}

.env-shell::after{
  content:""; position:absolute; inset:0; border-radius:4px;
  background: linear-gradient(126deg,
    rgba(255,255,255,.6) 0%, rgba(255,255,255,0) 38%,
    rgba(186,144,136,.05) 72%, rgba(186,144,136,.13) 100%);
}

/* her initial, blind-embossed into the paper */
.env-monogram{
  position:absolute; right:7%; bottom:7%;
  font-family: var(--font-script);
  font-size: clamp(1.1rem, 3.4vw, 1.5rem);
  color: rgba(141,99,96,.16);
  text-shadow: 0 1px 0 rgba(255,255,255,.95);
  line-height:1;
}

/* the letter, tucked inside */
.env-letter{
  position:absolute; left:6.5%; right:6.5%; top:9%;
  height:88%;
  transform: translateZ(-1px);
  border-radius:2px 2px 0 0;
  background: linear-gradient(180deg, #fffefb 0%, var(--paper) 62%, var(--paper-2) 100%);
  box-shadow: 0 -5px 14px -8px var(--sh-2);
  transition: transform 1s var(--ease-soft), opacity .5s ease;
}
.env-letter-lines{
  position:absolute; inset: 13% 12% auto 12%;
  height:48%;
  background: repeating-linear-gradient(
    to bottom,
    rgba(133,112,126,.30) 0 1.4px,
    transparent 1.4px 13px
  );
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
  opacity:.55;
}
.envelope.slide-out .env-letter{ transform: translateZ(-1px) translateY(-66%); }
.envelope.opened .env-letter{ opacity:0; }

/* the bottom flap, folded up over the letter */
.env-pocket{
  position:absolute; left:0; right:0; bottom:0;
  height:51%;
  transform: translateZ(2px);
  border-radius:0 0 4px 4px;
  clip-path: polygon(0 4%, 50% 0, 100% 4%, 100% 100%, 0 100%);
  background:
    linear-gradient(168deg, #f8ece5 0%, var(--env-2) 58%, var(--env-3) 100%);
  box-shadow: 0 -8px 16px -12px var(--sh-2);
}
/* the crease where it was folded, plus the same raking light */
.env-pocket::after{
  content:""; position:absolute; inset:0;
  background:
    linear-gradient(to bottom, rgba(190,150,140,.20) 0%, transparent 9%),
    linear-gradient(126deg, rgba(255,255,255,.42) 0%, rgba(255,255,255,0) 42%, rgba(186,144,136,.12) 100%);
}

/* the paper's own thickness, lit along the bottom edge */
.env-thickness{
  position:absolute; left:1%; right:1%; bottom:-5px;
  height:7px;
  transform: translateZ(2.6px);
  border-radius:0 0 5px 5px;
  background: linear-gradient(to bottom, var(--env-3) 0%, #dcc7bd 55%, #cdb3a8 100%);
  box-shadow: 0 3px 6px -3px var(--sh-2);
}

/* the triangular flap */
.env-flap{
  position:absolute; top:0; left:0; right:0;
  height:57%;
  transform: translateZ(3px);
  transform-origin: 50% 0;
  transition: transform 1.05s var(--ease-soft);
  will-change: transform;
}
.env-flap-face{
  position:absolute; inset:0;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  background: linear-gradient(196deg, #fffdfa 0%, #f9efe8 52%, var(--env-2) 100%);
  border-radius:4px 4px 0 0;
  filter: drop-shadow(0 5px 6px var(--sh-1));
}
.envelope.flap-open .env-flap{ transform: translateZ(-2px) rotateX(-172deg); }
.envelope.flap-open .env-flap-face{
  background: linear-gradient(196deg, var(--env-3) 0%, #f0e2d9 55%, #fbf4ee 100%);
  filter: none;
}

/* ── the ribbon ──────────────────────────────────────── */

.ribbon{
  position:absolute; left:0; right:0; top:0; bottom:0;
  transform: translateZ(4px);
  transition: transform .5s var(--ease-out), opacity .6s ease;
  pointer-events:none;
}

/* the band, wrapping right around the envelope */
.ribbon-band{
  position:absolute; left:-2.5%; right:-2.5%; top:50.5%;
  height:6.4%;
  background:
    /* the ribbon turns over the envelope edges and loses the light there */
    linear-gradient(to right,
      rgba(84,38,36,.5) 0, rgba(84,38,36,0) 3.5%,
      rgba(84,38,36,0) 96.5%, rgba(84,38,36,.5) 100%),
    /* selvedge: a thin darker line along each edge, nothing more */
    linear-gradient(to bottom,
      rgba(92,44,42,.55) 0, rgba(92,44,42,0) 9%,
      rgba(92,44,42,0) 88%, rgba(92,44,42,.5) 100%),
    /* one broad, low-contrast sheen sitting just above centre */
    linear-gradient(to bottom,
      rgba(255,255,255,0) 8%,
      rgba(255,255,255,.22) 33%,
      rgba(255,255,255,.07) 56%,
      rgba(255,255,255,0) 74%),
    /* and a flat base colour underneath it all */
    linear-gradient(to bottom, #c98a85 0%, #c2827d 55%, #b97b76 100%);
  box-shadow: 0 4px 6px -4px rgba(120,70,66,.45);
}


.ribbon-bow{
  position:absolute; left:50%; top:50.5%;
  width:53%; height:auto;
  transform: translate(-50%, -36%);
  overflow:visible;
  filter: drop-shadow(0 4px 6px var(--sh-2));
}

.bow-loop,.bow-tail,.bow-knot{
  transform-box: fill-box;
  transform-origin: center;
  transition: transform .5s var(--ease-soft), opacity .45s ease;
}
/* Everything pivots on the knot, because that is where a bow actually
   comes undone — the loops are drawn back through it. */
.bow-loop--l{ transform-origin: 100% 70%; }
.bow-loop--r{ transform-origin: 0%   69%; }
.bow-tail--l{ transform-origin: 95%  5%;  }
.bow-tail--r{ transform-origin: 5%   5%;  }
.bow-knot   { transform-origin: 50%  50%; }

/* 1. The pull. The right tail is drawn, which feeds the LEFT loop back
      through the knot — so that loop is the first to disappear, and it
      collapses into the knot rather than shrinking where it stands. */
.envelope.pulling .bow-tail--r{ transform: translate(7%, 11%) scaleY(1.55) rotate(-4deg); }
.envelope.pulling .bow-tail--l{ transform: translate(2%, -4%) scaleY(.8); }
.envelope.pulling .bow-loop--l{ transform: scale(.07) rotate(-18deg); opacity:.45; }
.envelope.pulling .bow-knot   { transform: scaleX(.78) scaleY(1.08); }

/* 2. The knot gives, and the second loop follows the first through it.
      The tails must stay hanging FROM the knot — their origin is up there,
      so they only lengthen and swing; translating them detaches the ribbon. */
.envelope.slipping .bow-loop--l{ transform: scale(.03) rotate(-24deg); opacity:0; }
.envelope.slipping .bow-loop--r{ transform: scale(.05) rotate(18deg);  opacity:.22; }
.envelope.slipping .bow-knot   { transform: scaleX(.58) scaleY(.86) rotate(-6deg); }
.envelope.slipping .bow-tail--l{ transform: rotate(9deg)  scaleY(1.22); }
.envelope.slipping .bow-tail--r{ transform: rotate(-11deg) scaleY(1.5); }
/* with nothing holding it, the band goes slack */
.envelope.slipping .ribbon-band{ transform: translateY(5%) rotate(.8deg) scaleY(.86); }

/* 2. the ribbon slides off the envelope and out of the frame */
.envelope.untied .ribbon{
  transform: translateZ(4px) translateY(72%) rotate(6deg);
  opacity:0;
  transition: transform 1s var(--ease-soft), opacity .75s .3s ease;
}
.envelope.untied .ribbon-band{ transform: translateY(10%) rotate(1.6deg) scaleY(.7); }
.envelope.untied .bow-loop{ opacity:0; }

/* ── the letter itself ───────────────────────────────── */

/* The sheet is torn from a notebook: a ragged binding edge with the
   remains of the spiral holes, feint rules, and a margin line.
   The wrapper carries the shadow so it follows the torn silhouette
   instead of a rectangle. */
.paper-wrap{
  width:100%;
  max-width: 580px;
  margin: clamp(8px,1.6vw,18px) 0 clamp(20px,4vw,36px);
  transform: rotate(-.45deg);
  filter:
    drop-shadow(0 2px 1px rgba(140,96,90,.10))
    drop-shadow(0 14px 16px rgba(140,96,90,.20))
    drop-shadow(0 38px 46px rgba(120,80,76,.18));
}

.paper{
  --tear: 24px;
  --rule: 32px;                            /* JS locks this to the type size */
  --pad-x: clamp(16px, 5vw, 48px);
  --pad-l: calc(var(--tear) + clamp(18px, 6vw, 56px));

  position:relative;
  padding: clamp(18px,4vw,30px) var(--pad-x) clamp(18px,3.6vw,28px) var(--pad-l);
  color: var(--ink);
  background:
    /* torn fibres along the binding edge */
    linear-gradient(to right, rgba(0,0,0,.11) 0, rgba(0,0,0,.028) 13px, rgba(0,0,0,0) 32px),
    /* the sheet is not perfectly flat: light falls across it unevenly */
    radial-gradient(140% 55% at 16% 4%,  rgba(255,255,255,1), rgba(255,255,255,0) 62%),
    radial-gradient(110% 45% at 92% 99%, rgba(0,0,0,.05), rgba(0,0,0,0) 58%),
    linear-gradient(172deg, var(--paper) 0%, #fcfcfb 52%, var(--paper-2) 100%);

  /* two mask layers: the tiling tear down the left, everything else solid */
  -webkit-mask-image:
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='144' viewBox='0 0 24 144'><path d='M24 0V144H6L2 140.5L8 137L3.5 133L7 129.5C17.5 128 17.5 112 6 110.5L2.5 107L8.5 103L3 99L6.5 95.5L2 92L7.5 88L3 84.5L6 81.5C16.5 80 16.5 64 5.5 62.5L2 59L7 55.5L3.5 51.5L8 48L2.5 44.5L6.5 41L3 37L7 33.5C17.5 32 17.5 16 6 14.5L2.5 11L7.5 7.5L3 4L6 0Z' fill='black'/></svg>"),
    linear-gradient(#000, #000);
  -webkit-mask-size: var(--tear) 144px, calc(100% - var(--tear) + 1px) 100%;
  -webkit-mask-position: left top, right top;
  -webkit-mask-repeat: repeat-y, no-repeat;
  mask-image:
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='144' viewBox='0 0 24 144'><path d='M24 0V144H6L2 140.5L8 137L3.5 133L7 129.5C17.5 128 17.5 112 6 110.5L2.5 107L8.5 103L3 99L6.5 95.5L2 92L7.5 88L3 84.5L6 81.5C16.5 80 16.5 64 5.5 62.5L2 59L7 55.5L3.5 51.5L8 48L2.5 44.5L6.5 41L3 37L7 33.5C17.5 32 17.5 16 6 14.5L2.5 11L7.5 7.5L3 4L6 0Z' fill='black'/></svg>"),
    linear-gradient(#000, #000);
  mask-size: var(--tear) 144px, calc(100% - var(--tear) + 1px) 100%;
  mask-position: left top, right top;
  mask-repeat: repeat-y, no-repeat;
}
.paper:focus{ outline:none; }

/* the margin line, ruled down the page in faded red */
.paper-margin{
  position:absolute; top:0; bottom:0;
  left: calc(var(--tear) + clamp(9px, 3.2vw, 32px));
  width:1px;
  background: linear-gradient(to bottom,
    rgba(40,40,46,0) 0,
    var(--margin-line) 3%,
    var(--margin-line) 97%,
    rgba(40,40,46,0) 100%);
  z-index:2; pointer-events:none;
}

.paper-grain{
  position:absolute; inset:0; z-index:3; pointer-events:none;
  opacity:.34; mix-blend-mode:multiply;
  background-image:
    /* fibre */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23f)' opacity='.42'/%3E%3C/svg%3E"),
    /* the slow blotchiness of pulp */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='460' height='460'%3E%3Cfilter id='m'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.014' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='460' height='460' filter='url(%23m)' opacity='.30'/%3E%3C/svg%3E");
  background-size: 200px 200px, 460px 460px;
}

.paper-head{ position:relative; z-index:1; margin:0; }
.paper-date{
  margin:0; text-align:right;
  font-family: var(--font-hand);
  font-size: clamp(1.05rem,3.2vw,1.28rem);
  line-height: var(--rule);
  color: var(--ink-soft);
}

.paper-body{
  position:relative;
  font-size: clamp(1.2rem, 4.5vw, 1.78rem);
}
/* Ruled paper. The gradient period is --rule and so is the line-height, so
   every line of handwriting lands on a rule. Offsetting the layer by whole
   multiples of --rule keeps that alignment while letting the rules run on
   past the text, up into the date and down past the signature. */
.paper-body::before{
  content:""; position:absolute; z-index:0; pointer-events:none;
  left: calc(-1 * var(--pad-l)); right: calc(-1 * var(--pad-x));
  top: calc(-4 * var(--rule)); bottom: calc(-4 * var(--rule));
  background: repeating-linear-gradient(
    to bottom,
    transparent 0, transparent calc(var(--rule) - 1px),
    var(--rule-line) calc(var(--rule) - 1px), var(--rule-line) var(--rule)
  );
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 calc(var(--rule) * 3), #000 calc(100% - var(--rule) * 3), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 calc(var(--rule) * 3), #000 calc(100% - var(--rule) * 3), transparent 100%);
}
.line-ruler{
  position:absolute; visibility:hidden; pointer-events:none;
  white-space:pre; left:-9999px; top:0;
  font-family: var(--font-hand); font-size:100px; letter-spacing:.004em;
}

.line{
  display:block;
  position:relative; z-index:1;
  font-family: var(--font-hand);
  font-size: 1em;
  line-height: var(--rule);
  white-space: pre-wrap;
  color: var(--ink);
  letter-spacing:.004em;

  /* the ink flows in from the left with a feathered edge */
  -webkit-mask-image: linear-gradient(90deg, #000 46%, rgba(0,0,0,0) 56%);
          mask-image: linear-gradient(90deg, #000 46%, rgba(0,0,0,0) 56%);
  -webkit-mask-size: 230% 100%;  mask-size: 230% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: 100% 0;  mask-position: 100% 0;
  opacity:0;
}
.line.blank{ height: var(--rule); }

/* pressing harder, and leaning a word over */
.line b{ font-weight:600; letter-spacing:.01em; }
.line i{ font-style:italic; }

/* she tapped: stop writing and show the whole letter at once */
.paper-body.done .line{
  animation:none !important;
  opacity:1;
  -webkit-mask-position:0% 0; mask-position:0% 0;
}
.line.writing{ animation: write var(--dur,1s) var(--ease-soft) var(--delay,0s) forwards; }
@keyframes write{
  0%  { opacity:1; -webkit-mask-position:100% 0; mask-position:100% 0; }
  100%{ opacity:1; -webkit-mask-position:0% 0;   mask-position:0% 0;   }
}
/* every line gets its own small drift, set per line in app.js — nobody
   writes two lines at exactly the same angle */
.line{
  transform: rotate(var(--jr, 0deg)) translate(var(--jx, 0), var(--jy, 0));
}

.paper-foot{ position:relative; z-index:1; margin-top: var(--rule); }
.signoff{
  margin:0; text-align:right;
  font-family: var(--font-hand);
  font-size: clamp(1.3rem,4.2vw,1.6rem);
  color: var(--ink); opacity:0;
}
.signature{
  margin:.1em 0 0; text-align:right;
  font-family: var(--font-script);
  font-size: clamp(2.1rem,7vw,3rem);
  line-height:1.15;
  color: #16161a; opacity:0;
}
.signoff.in{ animation: rise-ink .9s var(--ease-out) forwards; }
.signature.in{ animation: sign 1.5s var(--ease-out) forwards; }
@keyframes rise-ink{ from{opacity:0; transform:translateY(8px)} to{opacity:1; transform:none} }
@keyframes sign{
  0%  { opacity:0; transform: translateY(10px) rotate(-2deg) scale(.94); }
  60% { opacity:1; }
  100%{ opacity:1; transform:none; }
}

.paper-actions{
  position:relative; z-index:1;
  display:flex; gap:14px; justify-content:center; flex-wrap:wrap;
  margin-top: calc(var(--rule) * 1.5);
  padding-top: clamp(12px,2vw,18px);
  border-top: 1px solid rgba(0,0,0,.10);
  opacity:0;
}
.paper-actions.in{ animation: rise-ink .8s var(--ease-out) forwards; }

.btn{
  appearance:none; cursor:pointer;
  font-family: var(--font-serif);
  font-size:.78rem; letter-spacing:.22em; text-transform:uppercase;
  padding:.82em 1.5em;
  color: rgba(22,22,26,.66);
  background: rgba(255,255,255,.6);
  border:1px solid rgba(0,0,0,.20);
  border-radius:2px;
  transition: transform .3s var(--ease-out), background .3s ease, color .3s ease, box-shadow .3s ease;
}
.btn:hover{
  transform: translateY(-2px);
  background: #16161a;
  color:#fff; border-color:#16161a;
  box-shadow: 0 8px 16px -8px rgba(0,0,0,.4);
}
.btn--ghost{ background:transparent; }
.btn:focus-visible{ outline:2px solid #16161a; outline-offset:3px; }

/* ── music ───────────────────────────────────────────── */

/* Chrome will not start a cross-origin media frame that isn't actually
   rendered — parked at left:-9999px it never even reaches "unstarted".
   So the player stays inside the viewport, just imperceptible. */
#music-host{
  position:fixed; right:0; bottom:0;
  width:200px; height:120px;
  opacity:.01;
  pointer-events:none;
  border:0;
  z-index:0;
}

/* a quiet way out of a long reveal */
.skip-hint{
  position:fixed; left:50%; bottom: clamp(14px,3vh,28px);
  transform: translateX(-50%);
  margin:0; z-index:12;
  padding:.5em 1.1em;
  border-radius:99px;
  background: rgba(255,255,255,.6);
  border:1px solid rgba(0,0,0,.08);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  font-family: var(--font-serif);
  font-size:.68rem; letter-spacing:.22em; text-transform:uppercase;
  color: var(--clay-soft);
  opacity:0; pointer-events:none;
  transition: opacity .5s ease;
}
.skip-hint.on{ opacity:1; }

/* ── sound toggle ────────────────────────────────────── */

.sound-toggle{
  position:fixed; top: clamp(14px,2.6vw,26px); right: clamp(14px,2.6vw,26px);
  z-index:20;
  display:flex; align-items:center; gap:.6em;
  padding:.6em .95em;
  background: rgba(255,255,255,.55);
  border:1px solid rgba(176,115,111,.28);
  border-radius:99px;
  color: var(--clay-soft);
  font-family: var(--font-serif);
  font-size:.68rem; letter-spacing:.24em; text-transform:uppercase;
  cursor:pointer;
  backdrop-filter: blur(9px); -webkit-backdrop-filter: blur(9px);
  transition: color .3s ease, border-color .3s ease;
  opacity:0;
}
body.ready .sound-toggle{ animation: rise .8s 1.8s var(--ease-out) forwards; }
.sound-toggle:hover{ color: var(--rose-deep); border-color: var(--rose); }
.sound-toggle:disabled{ opacity:.35 !important; cursor:default; }
.sound-icon{ display:flex; align-items:flex-end; gap:2px; height:11px; }
.sound-icon i{ width:2px; height:3px; background:currentColor; border-radius:1px; transition:height .3s ease; }
.sound-toggle[aria-pressed="true"] .sound-icon i{ animation: eq 1.1s ease-in-out infinite; }
.sound-toggle[aria-pressed="true"] .sound-icon i:nth-child(2){ animation-delay:.18s; }
.sound-toggle[aria-pressed="true"] .sound-icon i:nth-child(3){ animation-delay:.36s; }
@keyframes eq{ 0%,100%{height:3px} 50%{height:11px} }
.sound-label{ padding-bottom:1px; }
@media (max-width:430px){ .sound-label{ display:none; } }

/* ── reduced motion ──────────────────────────────────── */

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.12s !important;
  }
  .line{ -webkit-mask-image:none; mask-image:none; opacity:1 !important; }
  .signoff,.signature,.paper-actions{ opacity:1 !important; }
  .envelope{ animation:none; transform: rotateX(11deg) rotateZ(-1deg); }
}
