/* Round 3 · Thread. One ink line draws itself down an edge-to-edge page,
   turning into a small drawing at each chapter. Soft watercolour washes,
   paper grain and floating prints give it depth. */
:root {
  --paper: #F8F7F4;
  --ink: #26262B;
  --ink-2: #56565E;
  --sub: #85858D;
  --accent: #2F4A8A;
  --serif: "Cormorant Garamond", "Iowan Old Style", Georgia, serif;
  --sans: "Hanken Grotesk", system-ui, sans-serif;
  --pad: clamp(20px, 6vw, 96px);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; color: var(--ink); font: 17px/1.6 var(--sans); -webkit-font-smoothing: antialiased; overflow-x: hidden;
  background-color: var(--paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.15 0 0 0 0 0.15 0 0 0 0 0.17 0 0 0 .05 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
a { color: inherit; text-decoration: underline; text-decoration-color: rgba(38,38,43,.3); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:hover { text-decoration-color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.text a { white-space: nowrap; }
.ext { font-size: .8em; margin-left: 1px; display: inline-block; color: var(--sub); }

/* The line */
.ink { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 1; overflow: visible; }
.ink path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.ink-main { stroke: var(--ink); stroke-width: 1.6; }
.ink-echo { stroke: var(--ink); stroke-width: .8; opacity: .28; transform: translate(1.6px, 1.1px); }
.pen { fill: var(--ink); }
.ink .dot { fill: var(--ink); opacity: .7; }

/* Chapters: full width, washes melt into the next */
.ch { position: relative; overflow-x: clip; width: 100%; padding: 18vh var(--pad); min-height: 0; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 2vw; align-content: center; }
.wash { position: absolute !important; z-index: 0 !important; pointer-events: none; width: 160%; height: auto; aspect-ratio: 5 / 4; left: 50%; top: 50%; translate: -50% -50%; mix-blend-mode: multiply; opacity: 0; transition: opacity 1.6s ease; }
.wash.in { opacity: 1; }
.ch > * { position: relative; z-index: 2; }
.mark { position: absolute !important; width: 1px; height: 1px; z-index: 0 !important; }
.right .text { grid-column: 7 / span 5; }
.left .text { grid-column: 2 / span 5; }
/* Washes bleed off the page edge on the pictures side, like Oria's */
.right { --wx: 13%; } .left { --wx: 87%; }

.when { margin: 0 0 6px; font-size: 13px; letter-spacing: .04em; color: var(--sub); }
h1, h2 { font-family: var(--serif); font-weight: 500; margin: 0; letter-spacing: -.01em; }
h2 { font-size: clamp(44px, 5.4vw, 84px); line-height: 1.06; margin-bottom: 22px; }
.text p { margin: 0 0 14px; color: var(--ink-2); max-width: 34em; }
.aside { font-size: 15px; color: var(--sub) !important; }
.credit { font-size: 12.5px; color: var(--sub) !important; }

/* Hero */
.hero { min-height: 100vh; min-height: 100svh; padding-top: 18vh; padding-bottom: 4vh; align-content: start; }
.hero h1 { grid-column: 1 / span 7; grid-row: 1; align-self: end; margin: 0; white-space: nowrap; font: 400 clamp(96px, 13.5vw, 222px)/1.1 "Parisienne", "Snell Roundhand", cursive; letter-spacing: 0; color: var(--ink-name); --ink-name: #4D4554; }
.hero .lede { grid-column: 1 / span 6; grid-row: 2; max-width: 34em; margin: 7vh 0 0 .4vw; font-size: clamp(18px, 1.5vw, 21px); color: var(--ink-2); line-height: 1.6; }
.hero .mark { left: 57vw; top: calc(20vh + clamp(88px, 15vw, 240px) * .9); }

/* Marks: where each drawing sits */
.right > .mark { left: 6.5vw; top: 9vh; }
.left > .mark { right: auto; left: 94.5vw; top: 9vh; }
.seq > .mark { top: 10vh; }
.seq.left > .mark { left: 95vw; }
.seq.right > .mark { left: 4.5vw; }

/* Prints: photos dropped near the line */
.prints { grid-column: 7 / span 6; position: relative; height: 70vh; }
.left .prints { grid-column: 7 / span 6; }
.print { position: absolute; margin: 0; padding: 10px 10px 26px; background: #fff; box-shadow: 0 1px 1px rgba(0,0,0,.05), 0 24px 40px -22px rgba(30,30,40,.4); transition: transform .5s cubic-bezier(.2,.8,.2,1); will-change: transform; }
.print img { display: block; width: 100%; height: auto; }
.print.bare { padding: 0; background: none; box-shadow: none; filter: drop-shadow(0 24px 22px rgba(30,30,40,.24)); transform: rotate(var(--r)); }
.dote .print.bare { width: min(205px, 14vw); }
.dote .d2 { left: 4%; top: 12vh; z-index: 1; }
.dote .d3 { left: 34%; top: 4vh; z-index: 3; }
.dote .d4 { left: 64%; top: 12vh; z-index: 2; }
.print.handset.light { padding: 6px; background: #EDEDF1; border-radius: 26px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06); }
.print.handset.light img { border-radius: 20px; }
.hearts { position: absolute; left: 66%; top: 2vh; width: 26%; height: 60vh; pointer-events: none; z-index: 5; }
.heart { position: absolute; bottom: 0; width: 16px; height: 16px; opacity: 0; animation: rise 3.6s ease-out forwards; }
@keyframes rise { 0% { opacity: 0; transform: translate(0, 0) scale(.6); } 15% { opacity: .9; } 100% { opacity: 0; transform: translate(var(--dx, 20px), -46vh) scale(1.1); } }

/* Wealthfront fan */
.fan { grid-column: 1 / span 5; grid-row: 1; position: relative; height: 72vh; }
.fan img { -webkit-mask-image: linear-gradient(#000 62%, transparent 96%); mask-image: linear-gradient(#000 62%, transparent 96%); position: absolute; left: 50%; top: 50%; width: min(230px, 16vw); border-radius: 26px; box-shadow: 0 30px 50px -28px rgba(30,40,30,.5); transform-origin: 50% 120%; translate: -50% -50%; transition: rotate .1s linear; }


/* Story beats: company and years sit above a heading that reads as part of the story */
.when { font-weight: 500; letter-spacing: .02em; }

/* Hero portrait */
.hero .portrait { grid-column: 9 / span 4; grid-row: 1 / span 2; align-self: center; justify-self: center; position: relative; width: min(290px, 20vw); margin: 0; }
.ph { aspect-ratio: 4 / 5; background: #ECEAE5; display: grid; place-items: center; color: var(--sub); font: italic 16px var(--serif); }

/* Prints beside a chapter */
.prints.right-side { grid-column: 7 / span 6; grid-row: 1; position: relative; height: 66vh; }
.prints.left-side { grid-column: 1 / span 6; grid-row: 1; position: relative; height: 72vh; }
.print.solo { width: min(500px, 36vw); left: 14%; top: 8vh; }
.print figcaption { font-size: 11px; color: var(--sub); padding-top: 8px; }
.print figcaption a { color: inherit; }
.print.handset { padding: 8px; background: #2C2C2E; border-radius: 30px; }
.print.handset img { border-radius: 22px; }
.ch[data-motif="apple"] { min-height: 0; padding-top: 7vh; padding-bottom: 10vh; }
.ch[data-motif="book"] { padding-bottom: 6vh; }
.ch.oria { padding-bottom: 22vh; }
.ch[data-motif="apple"] .text { align-self: center; }
.prints.left-side.edmodo { height: 62vh; }
.edmodo .e1 { width: min(250px, 17vw); left: 8%; top: 2vh; z-index: 1; }
.edmodo .e2 { width: min(250px, 17vw); left: 42%; top: 10vh; z-index: 2; }

/* Oria */
.oria { align-content: start; }
.oria > .mark { left: 3vw !important; top: 16vh !important; }


/* Oria: one clean circle, the loop the work goes round */
.oria .text { grid-row: 1; }
.oria { --wx: 22%; } .oria .wash.in { opacity: .5; } .fun { --wx: 87%; } .hero { --wx: 72%; }
.orbit { grid-column: 1 / span 6; grid-row: 1; margin: 0; align-self: center; justify-self: center; width: min(520px, 40vw); }
.orbit > svg:not(.wash) { width: 100%; height: auto; display: block; overflow: visible; }
.orbit .ring { fill: none; stroke: var(--ink); stroke-width: 1.2; opacity: .55; }
.orbit .stop circle { fill: var(--ink); transition: fill .5s, r .5s; }
.orbit .stop text { font: 600 15px var(--sans); fill: var(--ink); transition: fill .5s; }
.orbit .stop text.sub { font: italic 15px var(--serif); fill: var(--ink-2); }
.orbit .stop.lit circle { fill: #D99A1E; r: 5px; }
.orbit .stop.lit text:not(.sub) { fill: #8A5A00; }
.orbit .runner { fill: #D99A1E; stroke: #fff; stroke-width: 1.5; filter: drop-shadow(0 0 5px rgba(217,154,30,.55)); }
.orbit .core { font: 500 40px var(--serif); fill: var(--ink); }
.orbit .core-sub { font: italic 17px var(--serif); fill: var(--ink-2); }

/* Off the clock */
.fun .text { align-self: center; }
.hobbies { font: italic 30px/1.3 var(--serif) !important; color: var(--ink) !important; margin-bottom: 26px !important; }
.fun .prints { grid-column: 7 / span 6; grid-row: 1; align-self: stretch; position: relative; min-height: 64vh; }
.fun .art { width: min(340px, 24vw); left: 26%; top: 50%; translate: 0 -50%; }
/* Boba's snapshot is tucked into the illustration's corner and moves with it */
.fun .snap { position: absolute; left: -42%; top: 8%; width: 46%; margin: 0; padding: 7px 7px 20px; background: #fff; rotate: -6deg; box-shadow: 0 1px 1px rgba(0,0,0,.05), 0 18px 30px -16px rgba(30,30,40,.45); }
.fun .snap figcaption { font: italic 14px var(--serif); color: var(--ink-2); padding-top: 5px; }
.notes { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.notes li { color: var(--ink-2); font-size: 16px; max-width: 34em; }
@media (min-width: 1100px) { .notes li.one-line { white-space: nowrap; max-width: none; } }
.s1 { left: 6%; top: 12%; rotate: -8deg; }
.s2 { left: 44%; top: 2%; rotate: 10deg; animation-delay: -1.6s; animation-duration: 5.8s; }
.s3 { left: 30%; top: 50%; rotate: -4deg; animation-delay: -3s; animation-duration: 6.4s; }
@keyframes bob { from { translate: 0 0; } to { translate: 0 -14px; } }

/* Words written along the line (on phones they sit above each chapter instead) */
.beat { display: none; }
/* The journey lines are handwritten, in the same hand as "Say hello!" */
.caps text { font: 600 25px "Caveat", "Bradley Hand", cursive; fill: var(--ink); letter-spacing: .01em; white-space: pre; }
.caps tspan { fill-opacity: 0; transition: fill-opacity .4s ease; }
.caps tspan.on { fill-opacity: .82; }
.caps path { fill: none; stroke: none; }

/* Scroll sequences: the stage sticks while the page scrolls through it */
.seq { display: block; align-content: normal; min-height: 0; height: 320vh; padding-top: 0; padding-bottom: 0; }
.seq-stage { position: sticky; top: 0; height: 100vh; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 2vw; align-items: center; }
.seq.left .text { grid-column: 2 / span 4; }
.seq.right .text { grid-column: 8 / span 4; grid-row: 1; }
.caption { min-height: 3.4em; font-family: var(--serif); font-size: 24px !important; line-height: 1.3; color: var(--ink) !important; font-style: italic; transition: opacity .4s; }
.phone, .fold-screen, .pm { position: relative; }
.phone { grid-column: 8 / span 3; aspect-ratio: 402 / 874; height: min(78vh, 760px); justify-self: center; border-radius: 40px; box-shadow: 0 0 0 7px #1D1B1A, 0 50px 80px -40px rgba(20,30,25,.55); overflow: hidden; background: #111; }
.fold-screen { grid-column: 2 / span 6; grid-row: 1; aspect-ratio: 1902 / 1338; width: min(44vw, 64vh * 1.42, 780px); justify-self: center; align-self: center; transform-style: preserve-3d; }
.seq.right .seq-stage { perspective: 1600px; }
.fold-screen img { border-radius: 18px; box-shadow: 0 0 0 6px #1D1B1A, 0 50px 80px -40px rgba(30,25,70,.55); }
.pm { grid-column: 7 / span 5; aspect-ratio: 669 / 951; height: min(80vh, 780px); justify-self: center; }
[data-frames] > img, .pm-frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity .5s ease; }
[data-frames] > img.on, .pm-frame.on { opacity: 1; }
.pm-frame { border-radius: 22px; box-shadow: 0 0 0 6px #1D1B1A, 0 50px 80px -40px rgba(80,50,30,.5); overflow: hidden; }
img.pm-frame { object-fit: cover; }
.pm-sun img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity .35s ease; }
.pm-sun img.on { opacity: 1; }
.pm-frame:last-child { object-fit: contain; background: #E7DDCF; }

/* Seat Nine: three screens that slide apart as they arrive */
.cascade { grid-column: 7 / span 6; position: relative; height: 78vh; }
.cascade img { position: absolute; left: 18%; top: 6vh; width: min(260px, 18vw); border-radius: 30px; box-shadow: 0 0 0 6px #1D1B1A, 0 34px 60px -30px rgba(20,40,30,.55); will-change: transform; }

/* The end: the line strings across the page and hangs four charms */
.end { min-height: 0; align-content: start; text-align: center; padding-top: 24vh; padding-bottom: 9vh; }
.end > * { grid-column: 1 / -1; }
.end .mark { left: 7vw; top: 8vh; }
.end h2 { font-size: clamp(30px, 3.2vw, 46px); margin: 0 0 2vh; }
/* "Say hello!" is handwritten, and writes itself across when the charms arrive */
.end h2.hello { justify-self: center; padding: 0 .3em; font: 600 clamp(46px, 4.6vw, 68px)/1 "Caveat", "Bradley Hand", cursive; letter-spacing: .01em; rotate: -3deg; clip-path: inset(-20% 100% -20% 0); }
.end h2.hello.write { animation: write-in 1.6s cubic-bezier(.45,.05,.35,1) forwards, hello-bob 3.2s ease-in-out 1.8s infinite; }
@keyframes write-in { to { clip-path: inset(-40% -25% -40% -10%); } }
@keyframes hello-bob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -4px; } }
.charms { list-style: none; margin: 0; padding: 0; position: relative; height: 140px; }

.charms > li { position: absolute; translate: -50% -8px; opacity: 0; transition: opacity .5s ease, translate .7s cubic-bezier(.3,1.5,.5,1); }
.charms > li.on { opacity: 1; translate: -50% 0; }
.charm { all: unset; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 6px; transform-origin: 50% 0; animation: sway 4.6s ease-in-out infinite alternate; }
.charms > li:nth-child(2) .charm { animation-duration: 5.2s; animation-delay: -1.3s; }
.charms > li:nth-child(3) .charm { animation-duration: 4.2s; animation-delay: -2.1s; }
.charms > li:nth-child(4) .charm { animation-duration: 5.6s; animation-delay: -.7s; }
.charm:hover, .charm:focus-visible { animation: swing 1.1s cubic-bezier(.3,1.6,.5,1) both; }
.charm:focus-visible { outline: none; }
.charm:focus-visible svg { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 8px; }
.charm svg { width: 72px; height: 81px; overflow: visible; fill: none; stroke: var(--ink); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.charm svg .string { stroke-width: 1; opacity: .7; }
.charm svg .fill { fill: var(--ink); stroke: none; }
.charm svg text { fill: var(--ink); stroke: none; font: italic 600 22px var(--serif); }
.charm > span { font-family: var(--serif); font-style: italic; font-size: 19px; color: var(--ink-2); }
@keyframes sway { from { rotate: -3deg; } to { rotate: 3deg; } }
@keyframes swing { 0% { rotate: 0deg; } 25% { rotate: 14deg; } 55% { rotate: -9deg; } 80% { rotate: 4deg; } 100% { rotate: 0deg; } }
.note { position: absolute; left: 50%; top: calc(100% + 10px); translate: -50% 0; margin: 0; white-space: nowrap; display: flex; align-items: center; gap: 12px; padding: 12px 16px 12px 18px; background: #fff; rotate: -2deg; box-shadow: 0 1px 1px rgba(0,0,0,.05), 0 20px 34px -18px rgba(30,30,40,.4); font-size: 15px; animation: pop .45s cubic-bezier(.3,1.5,.5,1); }
.note[hidden] { display: none; }
.note a { text-decoration: none; }
.copy { font: inherit; font-size: 13px; border: 0; background: #F2ECE3; border-radius: 999px; padding: 4px 11px; cursor: pointer; color: var(--ink-2); }
@keyframes pop { from { opacity: 0; transform: translateY(-8px) scale(.9); } }

@media (prefers-reduced-motion: reduce) { .print, .loop-labels li, [data-frames] > img, .pm-frame, .charms > li { transition: none; } .heart { display: none; } .charm, .charm:hover, .note { animation: none; } }

/* Phones: the line runs down the left margin, content stacks */
@media (max-width: 820px) {
  :root { --pad: 20px; }
  body { font-size: 16px; }
  .ch { display: block; padding: 14vh 20px 10vh 56px; min-height: 0; }
  .hero { padding: 16vh 20px 10vh; }
  .hero .lede { margin-top: 4vh; }
  .hero .mark { left: 26px; top: auto; bottom: 4vh; }
  .right > .mark, .left > .mark, .seq > .mark, .seq.left > .mark, .seq.right > .mark { left: 26px; top: 10vh; }
  .prints { height: auto; margin-top: 28px; }
  .print { position: relative; width: 86% !important; left: auto !important; right: auto !important; top: auto !important; margin-bottom: 16px; }
  .fun .art { translate: none; margin-bottom: 48px; }
  .fun .snap { left: -4%; }
  .dote .print.bare { width: 31% !important; display: inline-block; margin: 0 1% 16px; vertical-align: top; }
  .fan { height: 62vh; margin-top: 12px; }
  .fan img { width: 44vw; }
  .loop-wrap { height: 110vh; }
  .loop-wrap .mark { left: 50%; top: 8vh; }
  .loop-labels li { width: 150px; font-size: 13px; }
  .loop-labels b { font-size: 22px; }
  .seq { height: 300vh; padding: 0; }
  .seq-stage { display: flex; flex-direction: column; justify-content: center; gap: 18px; padding: 0 20px 0 56px; }
  .seq .text h2 { font-size: 40px; margin-bottom: 8px; }
  .seq .text p:not(.caption):not(.when):not(.credit) { display: none; }
  .caption { font-size: 19px !important; min-height: 2.8em; }
  .phone { height: 56vh; align-self: center; }
  .fold-screen { width: 100%; }
  .seq.right .seq-stage { perspective: none; }
  .pm { height: 54vh; align-self: center; }
  .end { padding: 22vh 16px 10vh; }
  .hero .portrait.print { width: 150px !important; margin: 4vh 0 0 auto; display: block; }
  .hero .lede { margin-left: 0; }
  .prints.right-side, .prints.left-side { height: auto; margin-top: 24px; }
  .print.solo { width: 92% !important; }
  .edmodo .print.handset { width: 44% !important; display: inline-block; margin: 0 2% 16px; vertical-align: top; }
  .oria > .mark { left: 26px !important; top: 8vh !important; }
  .orbit { width: 100%; margin-top: 28px; }
  .beat { display: block; margin: 0 0 18px !important; font: 600 23px/1.2 "Caveat", "Bradley Hand", cursive !important; color: var(--ink) !important; opacity: .82; }
  .js .beat { clip-path: inset(-30% 100% -30% 0); transition: clip-path 2.2s cubic-bezier(.45,.05,.35,1); }
  .js .beat.on { clip-path: inset(-30% -4% -30% -4%); }
  .caps { display: none; }
  .hobbies { font-size: 24px !important; }
  .s1 { left: 0; top: 10px; } .s2 { left: 36%; top: 0; } .s3 { left: 70%; top: 20px; }
  .end .mark { left: 26px; top: 6vh; }
  .cascade { height: 64vh; margin-top: 16px; }
  .cascade img { width: 42vw; left: 0; }
  .charms { height: 150px; }
  .charm svg { width: 54px; height: 61px; }
  .charm > span { font-size: 12.5px; }
}

/* Opening, one beat at a time so the eye always knows where to look:
   the name is written left to right (0–2.1s), the title follows (1.6s), the
   photo is set down and develops (2s), the tape sticks it to the wall (2.8s),
   and only then the pen sets off down the page (3.4s), inviting the scroll. */
@keyframes rise-in { from { opacity: 0; translate: 0 22px; } to { opacity: 1; translate: 0 0; } }
@keyframes setdown { from { opacity: 0; translate: 0 -14px; rotate: 2.5deg; } to { opacity: 1; translate: 0 0; rotate: 0deg; } }
.hero h1 span { display: inline-block; animation: rise-in 1.1s cubic-bezier(.2,.7,.2,1) both; }
.hero h1 span + span { animation-delay: .18s; }
/* The opening is the name and Ivy. On the first scroll her story writes
   itself in, word by word, and the places it names paint in behind it. */
.js .hero .lede .w { opacity: 0; filter: blur(3px); transition: opacity .6s ease calc(var(--i) * 35ms), filter .8s ease calc(var(--i) * 35ms); }
.js .hero.awake .lede .w { opacity: 1; filter: none; }
.hero .portrait { animation: setdown .9s cubic-bezier(.22,.61,.36,1) 1.8s both, press .35s ease-out 3.1s; }
/* Wait for the photo to be decoded, so it is set down whole, never half-loaded */
.js .hero .portrait:not(.go) { animation: none; opacity: 0; }
/* Nothing in the opening starts until the name starts being written, so the
   order holds even when the web font is slow: name, title, photo, tape, pen. */

.hero .portrait:not(.go)::before { animation: none; opacity: 0; }
/* Then it develops like an instant photo */
.hero .portrait.go img { animation: develop 1.2s cubic-bezier(.22,.61,.36,1) 1.9s both; }
@keyframes develop { from { filter: grayscale(1) brightness(1.35) contrast(.7); } to { filter: grayscale(0) brightness(1) contrast(1); } }
/* The pen writes the name: outlines draw in, then the ink fills */
.hero h1 { position: relative; }
/* The real text never flashes before the ink: it stays clear until the pen has written it */
.ink-wait .hero h1, .hero h1.inking { color: transparent; }
.ink-wait .hero h1 span { animation: none; }
.hero h1.inking span { animation: none; }
.name-ink { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
/* The name is written stroke by stroke: each pen path is a wide, round
   brush in the mask, drawn at a steady hand's pace (timing set from script). */
.name-ink .pen-paths path { fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1 1; stroke-dashoffset: 1;
  animation: write var(--d) cubic-bezier(.4,.1,.6,.9) var(--at) forwards; }
@keyframes write { to { stroke-dashoffset: 0; } }
.name-ink .pen-paths .dry { fill: #fff; opacity: 0; animation: dry .5s ease var(--at) forwards; }
@keyframes dry { to { opacity: 1; } }
.name-ink.static .pen-paths path, .name-ink.static .pen-paths .dry { animation: none; stroke-dashoffset: 0; opacity: 1; }
/* Watercolor: the letters go down wet and soft, then settle. */
.name-ink text.main { fill: url(#nameWash); filter: url(#nameWater); }
.name-ink text.bloom { fill: url(#nameWash); opacity: 0; filter: blur(8px); animation: wash-bloom 3s ease .3s forwards; }
@keyframes wash-bloom { 0% { opacity: 0; } 40% { opacity: .35; } 100% { opacity: 0; } }
.name-ink.static text.bloom { display: none; }
@media (prefers-reduced-motion: reduce) { .hero h1 span, .hero .lede, .hero .portrait, .hero .portrait img, .hero .portrait::before { animation: none !important; opacity: 1 !important; } }
.hero .portrait::before { content: ""; position: absolute; z-index: 2; top: -16px; left: 50%; margin-left: -52px; width: 104px; height: 30px; rotate: -5deg;
  background: linear-gradient(90deg, rgba(0,0,0,.03), transparent 20%, transparent 80%, rgba(0,0,0,.03)), rgba(232, 214, 226, .78);
  clip-path: polygon(2% 8%, 8% 0, 16% 6%, 26% 0, 38% 5%, 50% 0, 62% 6%, 74% 0, 86% 5%, 94% 0, 100% 10%, 98% 92%, 90% 100%, 80% 94%, 68% 100%, 56% 95%, 44% 100%, 32% 94%, 20% 100%, 10% 95%, 0 100%);
  animation: tape-stick .6s cubic-bezier(.22,.61,.36,1) 2.55s both; }
/* The tape is carried in, pressed onto the photo's top edge, and the photo
   gives a little under the press: stuck to the wall. */
@keyframes tape-stick {
  0% { opacity: 0; translate: 34px -30px; rotate: -18deg; scale: 1.18; }
  55% { opacity: 1; translate: 2px -4px; rotate: -7deg; scale: 1.03; }
  78% { translate: 0 1px; rotate: -5deg; scale: .98; }
  100% { opacity: 1; translate: 0 0; rotate: -5deg; scale: 1; }
}
@keyframes press { 0%, 100% { translate: 0 0; } 45% { translate: 0 3px; } }

/* Text settles in slowly as each chapter arrives, one block after another */
.fade { opacity: 0; translate: 0 10px; transition: opacity .8s ease, translate .9s cubic-bezier(.2,.7,.2,1); transition-delay: var(--fd, 0s); }
.fade.in { opacity: 1; translate: 0 0; }
@media (prefers-reduced-motion: reduce) { .fade { opacity: 1; translate: none; transition: none; } }

@media (prefers-reduced-motion: reduce) { .end h2.hello, .js .beat { clip-path: none; animation: none; transition: none; } }

/* Phones: tighter rhythm, nothing overlapping the text, no dead space */
@media (max-width: 820px) {
  .hero { min-height: 0; padding: 12vh 20px 9vh; }
  .hero .portrait.print { width: 200px !important; margin: 6vh 0 0 16%; }
  /* On phones the story sits above the photo, so it comes first: name, story, photo. */
  .hero .lede { animation-delay: 1.9s; }
  .hero .portrait { animation-delay: 2.6s, 3.9s; }
  .hero .portrait.go img { animation-delay: 2.7s; }
  .hero .portrait::before { animation-delay: 3.35s; }
  .ch { padding: 14vh 20px 9vh 56px; }
  .ch[data-motif="apple"] { padding-top: 6vh; padding-bottom: 5vh; }
  .ch[data-motif="book"] { padding-bottom: 4vh; }
  .ch.oria { padding-bottom: 12vh; }
  .prints.dote { display: flex; gap: 3%; align-items: flex-start; }
  .dote .print.bare { width: 31% !important; margin: 0 !important; display: block; }
  .fan { height: 34vh; margin-top: 8px; }
  .hearts { height: 26vh; top: 0; }
  .fan img { width: 34vw; }
  .cascade { height: 58vh; }
  .cascade img { width: 44vw; left: 8%; }
  .fun .prints { min-height: 0; margin-top: calc(40px + 14vw); } /* room for the cat photo, which sits above the illustration */
  .fun .art { width: 62vw !important; left: auto; margin-left: 24%; }
  .fun .snap { left: -30%; top: 12%; width: 44%; }
  .end { padding: 12vh 16px 8vh; }
}

/* ---- Moments ---- */
/* Dote: the live phone is on air */
.dote .d4 { overflow: visible; }
.live { position: absolute; top: -18px; right: -26px; z-index: 6; display: flex; align-items: center; gap: 7px; padding: 6px 11px 6px 8px; border-radius: 999px; background: #fff; box-shadow: 0 8px 20px -10px rgba(30,30,40,.45); font: 600 12px/1 var(--sans); color: var(--ink); white-space: nowrap; rotate: -3deg; }
.live b { display: inline-flex; align-items: center; gap: 5px; padding: 3px 6px; border-radius: 5px; background: #F0505A; color: #fff; font-size: 10.5px; letter-spacing: .06em; }
.live b::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: #fff; animation: onair 1.4s ease-in-out infinite; }
@keyframes onair { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
.live [data-viewers] { font-variant-numeric: tabular-nums; }
/* Wealthfront: a layout grid the phones snap onto */
.fan .grid { position: absolute; inset: -4% -4%; z-index: 0; opacity: 0; pointer-events: none;
  background-image: linear-gradient(to right, rgba(110,96,200,.16) 1px, transparent 1px), linear-gradient(to bottom, rgba(110,96,200,.16) 1px, transparent 1px);
  background-size: 16px 16px; -webkit-mask-image: radial-gradient(closest-side, #000 55%, transparent); mask-image: radial-gradient(closest-side, #000 55%, transparent); }
.fan img { z-index: 1; }
/* Seat Nine: screens are dealt like cards */
.cascade { perspective: 1400px; }
/* Oria: each lap teaches the system a rule */
.orbit .ring { transition: stroke .6s, opacity .6s, stroke-width .6s; }
.orbit .ring.pulse { stroke: #D99A1E; opacity: 1; stroke-width: 2; }
.orbit .rule-chip { font: italic 600 15px var(--serif); fill: #8A5A00; animation: rule-drop 2.6s ease forwards; }
@keyframes rule-drop { 0% { opacity: 0; translate: 0 -14px; } 18% { opacity: 1; translate: 0 0; } 70% { opacity: 1; } 100% { opacity: 0; translate: 0 6px; } }
/* Ink trail behind the cursor */
.ink-trail { position: fixed; left: 0; top: 0; width: 100vw; height: 100vh; z-index: 50; pointer-events: none; mix-blend-mode: multiply; }
@media (max-width: 820px) { .live { top: -14px; right: -6px; font-size: 10.5px; padding: 4px 8px 4px 6px; } .live b { font-size: 9px; } }
@media (prefers-reduced-motion: reduce) { .live b::before { animation: none; } .orbit .rule-chip { animation: none; opacity: 0; } }

/* ---- Immersion ---- */
/* The whole page takes on each chapter's colour as you travel through it */
.ambient { position: fixed; inset: 0; z-index: -1; pointer-events: none; transition: background .2s linear; }

/* Landing scene: Guilin's karst peaks fade into the Bay, painted in watercolor,
   edge to edge behind the name. */
.hero .scene { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 100%; z-index: 0; pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 26%, #000 84%, transparent 100%); mask-image: linear-gradient(to bottom, transparent 0, #000 26%, #000 84%, transparent 100%);
  opacity: .95; }
/* It paints in from left to right, starting just before the name has fully appeared. */
.js .hero .scene { -webkit-mask-image: linear-gradient(to bottom, transparent 0, transparent 46%, #000 70%, #000 86%, transparent 100%), linear-gradient(to right, #000 40%, transparent 60%);
  mask-image: linear-gradient(to bottom, transparent 0, transparent 46%, #000 70%, #000 86%, transparent 100%), linear-gradient(to right, #000 40%, transparent 60%);
  -webkit-mask-size: 100% 100%, 260% 100%; mask-size: 100% 100%, 260% 100%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: 0 0, 100% 0; mask-position: 0 0, 100% 0; -webkit-mask-composite: source-in; mask-composite: intersect;
  transition: -webkit-mask-position 1.7s cubic-bezier(.45,.1,.4,1) .8s, mask-position 1.7s cubic-bezier(.45,.1,.4,1) .8s; }
.js .hero.named .scene { -webkit-mask-position: 0 0, 0 0; mask-position: 0 0, 0 0; }
.hero > h1, .hero > .lede { position: relative; z-index: 2; }
.hero .lede { color: var(--ink); text-shadow: 0 0 14px rgba(248,247,244,.95), 0 0 4px rgba(248,247,244,.9); }
.hero .me { position: absolute; z-index: 1; right: 14vw; bottom: 3vh; width: min(150px, 10.5vw); margin: 0; pointer-events: none; }
/* The ending: a faint Porto riverside under "Say hello!" */
.end { position: relative; }
.end .porto { position: absolute; left: 0; bottom: 0; width: 100%; height: 46vh; object-fit: cover; object-position: 50% 100%; z-index: 0; pointer-events: none; opacity: .5;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 45%); mask-image: linear-gradient(to bottom, transparent 0, #000 45%); }
.end > :not(.porto):not(.mark) { position: relative; z-index: 1; }
@media (max-width: 820px) {
  .hero { min-height: 100svh; }
  .hero .scene { object-position: 38% 100%; }
  .hero .me { position: relative; right: auto; bottom: auto; width: 24vw; margin: 4vh 16vw 0 auto; display: block; }
  .end .porto { height: 32vh; }
}
@media (prefers-reduced-motion: reduce) { .js .hero .scene { transition: none; } .js .hero .me { animation: none; } .js .hero .lede .w { transition: none; } }

/* The name, written by hand in one connected line from the I, in a soft rose ink. */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.hero h1 .sig { display: block; width: clamp(300px, 40vw, 620px); height: auto; overflow: visible; }
.sig path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.sig .ink path { stroke: #232328; stroke-width: 3.6; }
.sig .wash { display: none; }
.js .sig path { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.js .sig.go path { animation: sig-write var(--d, .8s) cubic-bezier(.42,.08,.58,.92) var(--at, 0s) both; }
/* hidden until its own stroke begins, so no dots wait at the starting points */
@keyframes sig-write { 0% { stroke-dashoffset: 1; opacity: 0; } 1% { opacity: 1; } 100% { stroke-dashoffset: 0; opacity: 1; } }
@media (max-width: 820px) { .hero h1 .sig { width: 84vw; } }
@media (prefers-reduced-motion: reduce) { .js .sig path { stroke-dashoffset: 0; animation: none !important; } }

/* Ivy, drawn by the same pen as her name: lines sketch in, then soft washes bloom. */
.hero .me .me-art { display: block; width: 100%; height: auto; overflow: visible;
  }
.me-art .lines { stroke: #B07A80; } .me-art .ln { stroke-width: 1.9; } .me-art .ft { stroke-width: 1.3; stroke-opacity: .6; } .me-art .eye { stroke: #8C5E67; stroke-width: 2.8; }
.me-art .ft { stroke-width: 1.9; stroke-opacity: .8; } .me-art .lip { stroke: #B86E78; stroke-width: 1.9; } .me-art .iris { fill: #8C5E67; fill-opacity: .85; }
.js .me-art .ln { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.js .me-art.go .ln { animation: sig-write var(--d, .8s) cubic-bezier(.4,.1,.5,.95) var(--at, 1s) both; }
.js .me-art .washes, .js .me-art .iris { opacity: 0; }
.js .me-art.go .washes { animation: wash-in 2.4s ease var(--wash-at, 2.4s) forwards; }
.js .me-art.go .iris { animation: wash-in .6s ease var(--iris-at, 3s) forwards; }
@keyframes wash-in { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .js .me-art .ln { stroke-dashoffset: 0; animation: none !important; } .js .me-art .washes, .js .me-art .iris { opacity: 1; animation: none !important; } }


/* The way down to college: a cold, misty wash where snow starts to fall. */
.ramp { position: relative; height: 46vh; margin-top: -6vh; pointer-events: none;
  background: linear-gradient(to bottom, rgba(222,228,238,0) 0%, rgba(222,228,238,.55) 35%, rgba(226,231,240,.6) 70%, rgba(226,231,240,0) 100%); }
.ramp canvas { position: absolute; left: 0; top: -10vh; width: 100%; height: calc(100% + 30vh); }
@media (max-width: 820px) { .ramp { height: 30vh; } }


/* Oria: as the light passes each stage, the words in the paragraph that name it light up too. */
.oria [data-stage] { background: linear-gradient(transparent 62%, rgba(255,185,160,0) 62%); transition: background .5s ease; }
/* The orbit alone shows the stages; the paragraph stays still. */
.oria [data-stage].lit { background: none; }

/* Numbers that carry the story, so a skimming eye lands on them. */
.text .num { font-weight: 600; color: var(--ink); font-variant-numeric: lining-nums tabular-nums; }
/* Always within reach: the two things a visitor most often wants. */
.corner { position: fixed; top: 18px; right: 22px; z-index: 60; display: flex; gap: 6px; font: 500 14px/1 "Hanken Grotesk", system-ui, sans-serif; letter-spacing: .02em; }
.corner a { color: var(--ink); text-decoration: underline; text-decoration-color: rgba(38,38,43,.3); text-underline-offset: 4px; padding: 6px 2px; }
.corner a:hover, .corner a:focus-visible { text-decoration-color: currentColor; }
/* Where she has been, in one glance, under the story. */
.hero .path { grid-column: 1 / span 6; grid-row: 4; position: relative; z-index: 2; margin: 2.6vh 0 0 .4vw; font: 500 14px/1.5 "Hanken Grotesk", system-ui, sans-serif; letter-spacing: .03em; color: var(--ink-2); }
.hero .path span { margin: 0 .45em; color: var(--sub); }
.js .hero .path { opacity: 0; transition: opacity .8s ease var(--path-at, 3s); }
.js .hero.awake .path { opacity: 1; }
@media (max-width: 820px) { .corner { top: 12px; right: 16px; font-size: 14px; } }

/* Bowdoin: the first fall on the Quad turns to the snowstorms the caption promises. */
.season .frames { position: relative; aspect-ratio: 3 / 2; overflow: hidden; }
.season .frames img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.season .flurry { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .season .fall { display: none; } }

/* Each chapter enters once, in step with its story. */
/* Edmodo: a chalk classroom is drawn, then becomes the app. */
.edmodo .chalk { position: absolute; left: 2%; top: 5vh; width: 84%; z-index: 4; pointer-events: none; stroke: #97A0AE; stroke-width: 2.6; opacity: 0; }
.js .edmodo .chalk path { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.edmodo.go .chalk { opacity: 1; animation: chalk-out .9s ease var(--chalk-done, 1.6s) forwards; }
.edmodo.go .chalk path { animation: sig-write var(--d, .4s) cubic-bezier(.4,.1,.6,.9) var(--at, 0s) both; }
@keyframes chalk-out { to { opacity: 0; filter: blur(3px); } }
.js .edmodo .handset { opacity: 0; translate: 0 18px; filter: blur(4px); transition: opacity .9s ease, translate 1s cubic-bezier(.25,.8,.3,1), filter .9s ease; }
.edmodo.go .handset { opacity: 1; translate: 0 0; filter: none; }
.edmodo.go .e1 { transition-delay: calc(var(--chalk-done, 1.6s) - .1s); } .edmodo.go .e2 { transition-delay: calc(var(--chalk-done, 1.6s) + .15s); }
/* Dote: the three screens arrive like shopping, then the stream goes live.
   Three ways in to compare (?dote=rack|swipe|pop). They move the figure with the
   individual translate/rotate/scale properties, so the parallax transform is left alone. */
.js .dote .print { opacity: 0; }
.dote.go .print { animation: var(--in) var(--dur) var(--ease, ease) calc(var(--i) * var(--gap)) both; }
.dote .d2 { --i: 0; } .dote .d3 { --i: 1; } .dote .d4 { --i: 2; }
/* Rack: each screen slides along like a hanger on a rail and sways to rest.
   The sway pivots at the top; the translate keeps that pivot still (h is about 2w). */
.dote.v-rack { --in: dote-rack; --dur: 1.7s; --gap: .16s; --ease: linear; }
@keyframes dote-rack {
  0% { opacity: 0; translate: 77% 0; rotate: -4deg; animation-timing-function: cubic-bezier(.22,.62,.36,1); }
  14% { opacity: 1; }
  40% { translate: -5.2% 0; rotate: 3deg; animation-timing-function: ease-in-out; }
  58% { translate: 3.5% 0; rotate: -2deg; animation-timing-function: ease-in-out; }
  74% { translate: -2.1% 0; rotate: 1.2deg; animation-timing-function: ease-in-out; }
  88% { translate: .9% 0; rotate: -.5deg; animation-timing-function: ease-in-out; }
  100% { opacity: 1; translate: 0 0; rotate: 0deg; }
}
/* Swipe: each screen flicks up into place, like swiping to the next live stream. */
.dote.v-swipe { --in: dote-swipe; --dur: .8s; --gap: .17s; --ease: cubic-bezier(.2,.8,.3,1); }
@keyframes dote-swipe { 0% { opacity: 0; translate: 0 32%; } 45% { opacity: 1; } 60% { translate: 0 -2%; } 100% { opacity: 1; translate: 0 0; } }
/* Pop: each screen springs up like a product pinned during a live show. */
.dote.v-pop { --in: dote-pop; --dur: 1s; --gap: .22s; --ease: linear; }
@keyframes dote-pop {
  0% { opacity: 0; scale: .74; translate: 0 10%; animation-timing-function: cubic-bezier(.25,.8,.4,1); }
  40% { opacity: 1; scale: 1.045; translate: 0 -1.5%; animation-timing-function: ease-in-out; }
  64% { scale: .985; translate: 0 .4%; animation-timing-function: ease-in-out; }
  84% { scale: 1.006; translate: 0 0; animation-timing-function: ease-in-out; }
  100% { opacity: 1; scale: 1; translate: 0 0; }
}
/* Once the live screen has landed, it goes on air. */
.js .dote .live { opacity: 0; scale: .6; transition: opacity .35s ease, scale .45s cubic-bezier(.3,1.6,.5,1); }
.dote.go .live { opacity: 1; scale: 1; transition-delay: var(--live-at, 1.4s); }
.dote.v-rack { --live-at: 1.75s; } .dote.v-swipe { --live-at: 1.15s; } .dote.v-pop { --live-at: 1.3s; }
@media (max-width: 820px) { .edmodo .chalk { left: 0; top: 0; width: 100%; } }
@media (prefers-reduced-motion: reduce) { .edmodo .chalk { display: none; } .js .edmodo .handset, .js .dote .print, .js .dote .live { opacity: 1; translate: none; scale: 1; transition: none; } .dote.go .print { animation: none; } }

/* Story order, for pictures too: a chapter's images and motion wait until its
   line has been written, then arrive just after its words. */
.js main .ch[data-cap] > :not(.text):not(.mark):not(.wash):not(.beat) { opacity: 0; transition: opacity .8s ease; }
.js main .ch[data-cap].told > :not(.text):not(.mark):not(.wash):not(.beat) { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .js main .ch[data-cap] > * { opacity: 1 !important; transition: none !important; } }

.text strong { font-weight: 600; color: var(--ink); }

/* The name's wet ink runs in from the left, word by word. */
.name-ink .wipe { transform: translateX(var(--to)); }
.js .name-ink:not(.static) .wipe { animation: name-wipe var(--dur, 2.4s) cubic-bezier(.42,.08,.5,1) .25s both; }
@keyframes name-wipe { 0% { transform: translateX(0); } 44% { transform: translateX(var(--mid)); } 54% { transform: translateX(var(--mid)); } 100% { transform: translateX(var(--to)); } }

/* Gentle ambient motion stays, with a quiet way to stop it (WCAG 2.2.2). */
.motion-toggle { position: fixed; left: 16px; bottom: 16px; z-index: 70; width: 30px; height: 30px; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
  background: rgba(248,247,244,.7); box-shadow: 0 0 0 1px rgba(38,38,43,.12); opacity: 0; visibility: hidden; transition: opacity .4s ease, visibility 0s linear .4s; }
.motion-toggle.show { opacity: .55; visibility: visible; transition: opacity .4s ease; }
.motion-toggle.show:hover, .motion-toggle.show:focus-visible { opacity: 1; }
.motion-toggle span { display: block; width: 8px; height: 10px; margin: auto; border-left: 2.5px solid var(--ink-2); border-right: 2.5px solid var(--ink-2); }
.motion-toggle[aria-pressed="true"] span { width: 0; height: 0; border: 0; border-left: 9px solid var(--ink-2); border-top: 6px solid transparent; border-bottom: 6px solid transparent; margin-left: 11px; }
/* Pausing stops only the ongoing, decorative motion. One-time entrances still finish,
   so pausing never leaves a picture or a line hidden. */
.motion-paused .live b::before, .motion-paused .charm, .motion-paused .orbit .rule-chip { animation-play-state: paused !important; }
.motion-paused .end h2.hello.write { animation-play-state: running, paused !important; }
.motion-paused .hearts { visibility: hidden; }
@media (prefers-reduced-motion: reduce) { .motion-toggle { display: none; } }

.name-ink text.bloom { display: none; }
/* Story order: the line, then the picture and its motion, then the words. */
/* Text timing is scheduled per chapter in app.js (show()). */
.ch[data-motif="apple"] { --text-after: 2.4s; }

@media (max-width: 820px) { .dote .print { position: relative; } }

/* Each pen stroke is hidden until its own moment, so no dots wait at the starts. */
.js .name-ink:not(.static) .pen-paths path { animation: pen-write var(--d, .4s) cubic-bezier(.35,.1,.55,.92) var(--at, 0s) both; }
@keyframes pen-write { 0% { stroke-dashoffset: 1; opacity: 0; } 2% { opacity: 1; } 100% { stroke-dashoffset: 0; opacity: 1; } }
.name-ink .pen-paths .dry { animation-duration: .35s; }

/* Story version: each chapter opens with a hook. */
.text .hook { font: 500 clamp(21px, 1.7vw, 25px)/1.45 var(--serif); color: var(--ink); margin-bottom: 18px; }

/* The opening: the name simply fades in, in plain ink (no pen, no watercolor). */
.hero h1 span { animation: none; }
.js .hero h1 { opacity: 0; }
.js .hero.named h1 { animation: name-in 1.4s ease both; }
@keyframes name-in { from { opacity: 0; filter: blur(2px); } to { opacity: 1; filter: none; } }
@media (prefers-reduced-motion: reduce) { .js .hero h1 { opacity: 1; animation: none; } }

/* Seat Nine: the short text sits level with the phones, and the phones' box is
   only as tall as the phones, so no empty column is left below the words. */
@media (min-width: 821px) {
  .seat .text { align-self: center; }
  .seat .cascade { height: calc(6vh + min(260px, 18vw) * 2.2); }
}
/* On phones the name scales with the screen, so "Ivy Xing" always fits on one line. */
@media (max-width: 820px) { .hero h1 { font-size: min(96px, 17.5vw); } }
/* No bold in the story text: the numbers and phrases stand on their own. */
.text .num, .text strong { font-weight: inherit; color: inherit; }

/* Each chapter leads with its hook as the heading; the company, years and role
   sit under the pictures as a quiet caption. */
.text h2.hook { font: 500 clamp(30px, 2.6vw, 40px)/1.22 var(--serif); letter-spacing: -.005em; color: var(--ink); margin: 0 0 22px; }
.ch > p.when { grid-row: 2; margin: 18px 0 0; text-align: center; font-size: 13px; letter-spacing: .03em; color: var(--ink-2); align-self: start; }
.left > p.when { grid-column: 7 / span 6; }
.right > p.when { grid-column: 1 / span 6; }
.print.solo figcaption { font-size: 13px; letter-spacing: .03em; }
/* Photo credits, quietly, at the very end. */
.end .credits { grid-column: 1 / -1; margin: 6vh 0 0; font-size: 11.5px; color: var(--sub); text-align: center; }
.end .credits a { color: inherit; }
/* Room to breathe after Edmodo. */
.ch[data-motif="apple"] { padding-bottom: 26vh; }
@media (max-width: 820px) {
  .ch > p.when { margin-top: 10px; }
  .text h2.hook { font-size: 27px; margin-bottom: 16px; }
  .ch[data-motif="apple"] { padding-bottom: 18vh; }
}
/* Pull each caption up to just under its pictures (their boxes run taller than the pictures). */
/* On wide screens the caption is placed by script just under the pictures as they
   actually sit (parallax, fans and stacks move them), so it never overlaps them. */
@media (min-width: 821px) {
  .ch > p.when, .left > p.when, .right > p.when { position: absolute; grid-row: auto; grid-column: auto; margin: 0; top: 0; left: 0; width: 100%; }
}
@media (max-width: 820px) {
  .prints.left-side.edmodo { height: auto; }
  .ch[data-motif="sprout"] > p.when, .ch[data-motif="card"] > p.when { margin-top: -44px; }
  .end .credits { margin-bottom: 44px; padding: 0 40px; }
}
/* Photo credits as fine print in the corner, not a closing line. */
.end .credits { justify-self: end; margin: 4vh 0 0; max-width: 22em; font-size: 10px; line-height: 1.4; text-align: right; color: var(--sub); opacity: .6; }
@media (max-width: 820px) { .end .credits { margin: 3vh 0 0; padding: 0; max-width: 16em; } }
/* Lining figures in the hooks, so "1" never reads as an "I". */
.text h2.hook { font-variant-numeric: lining-nums; }
.end .credits a:last-child { white-space: nowrap; }
@media (max-width: 820px) { .end .credits { max-width: 19em; } }
.hearts { overflow: hidden; }
/* The opener spans the same width as the name, so it reads in 3 lines. */
@media (min-width: 821px) { .hero .lede { grid-column: 1 / span 8; max-width: 38em; } }
/* The Porto skyline runs edge to edge: the painting has a margin at each side, so the
   image is drawn a little wider than the page and the margins fall outside it. */
.end .porto { left: -7%; width: 114%; }
/* A line break chosen for wide screens only; phones wrap naturally. */
@media (max-width: 1080px) { br.wide-only { display: none; } }
.vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

.print.solo figcaption { color: var(--ink-2); }

/* The opener: the name, one line on who I am, then a short paragraph. */
.hero .role { grid-column: 1 / span 8; grid-row: 2; margin: 5vh 0 0 .4vw; max-width: 30em; font: 500 clamp(22px, 2vw, 30px)/1.3 var(--serif); color: var(--ink); text-shadow: 0 0 14px rgba(248,247,244,.95); }
.hero .lede { grid-row: 3; margin-top: 12px; }
.js .hero .role { opacity: 0; filter: blur(3px); transition: opacity .8s ease, filter .9s ease; }
.js .hero.awake .role { opacity: 1; filter: none; }
.hero > .role { position: relative; z-index: 2; }
/* A quiet way to the contact charms, top right. */
.hero > .hello-link { position: absolute; top: 28px; right: var(--pad); z-index: 5; font-size: 15px; color: var(--ink); text-decoration: underline; text-decoration-color: rgba(38,38,43,.3); text-underline-offset: 5px; }
.hero > .hello-link:hover, .hero > .hello-link:focus-visible { text-decoration-color: currentColor; }
.js .hero > .hello-link { opacity: 0; transition: opacity .8s ease .4s; }
.js .hero.named > .hello-link { opacity: 1; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
@media (max-width: 820px) {
  .hero .role { margin-top: 3vh; font-size: 22px; }
  .hero .lede { margin-top: 10px; }
  .hero > .hello-link { top: 18px; right: 20px; font-size: 14px; }
}
@media (prefers-reduced-motion: reduce) { .js .hero .role, .js .hero > .hello-link { opacity: 1; filter: none; transition: none; } }
.hero .role { text-wrap: balance; }
/* "Say hello" in the same hand as the one at the end, so it reads as an invitation. */
.hero > .hello-link { font: 600 23px/1 "Caveat", "Bradley Hand", cursive; letter-spacing: .01em; text-decoration-thickness: 1.5px; text-decoration-color: rgba(38,38,43,.45); rotate: -2deg; }
@media (max-width: 820px) { .hero > .hello-link { font-size: 20px; } }
/* The Wealthfront hook runs a column wider, so its first line holds before the break. */
@media (min-width: 1081px) { .ch[data-motif="sprout"] .text h2.hook { margin-right: calc(-1 * (22% + 2vw)); } }
/* Less empty space between the opening and Maine. */
.ramp { height: 24vh; }
@media (max-width: 820px) { .ramp { height: 12vh; } }
@media (max-width: 820px) { .ramp { height: 6vh; } .ch[data-motif="book"] { padding-top: 7vh; } }
/* Edmodo: back to an even gap before Dote. */
.ch[data-motif="apple"] { padding-bottom: 17vh; }
@media (max-width: 820px) { .ch[data-motif="apple"] { padding-bottom: 8vh; } }

/* The opening, staged: the name, then the subtitle rises in, then the story writes itself. */
.js .hero .role { opacity: 0; translate: 0 10px; filter: blur(2px); transition: opacity 1s ease, translate 1.1s cubic-bezier(.2,.7,.2,1), filter 1s ease; }
.js .hero.awake .role { opacity: 1; translate: 0 0; filter: none; }
.js .hero .lede .w { transition: opacity .6s ease calc(.9s + var(--i) * 35ms), filter .8s ease calc(.9s + var(--i) * 35ms); }
/* A little more air between the name, the subtitle and the story. */
@media (min-width: 821px) { .hero .role { margin-top: 4.5vh; } .hero .lede { margin-top: 18px; } }
@media (max-width: 820px) { .hero .lede { margin-top: 14px; } }
@media (prefers-reduced-motion: reduce) { .js .hero .role { opacity: 1; translate: none; filter: none; transition: none; } }
/* The animated digits are for the eye only: copying or reading gets the real figure once. */
.num .tick { -webkit-user-select: none; user-select: none; }
/* Wealthfront: even out the gap before Seat Nine. */
@media (min-width: 821px) { .ch[data-motif="sprout"] { padding-bottom: 4vh; } }
/* The opener's personal beat sits on its own line. */
.hero .lede .line { display: block; margin-top: 6px; }
/* Oria's paragraphs fade in more slowly, one after another. */
.js main .ch[data-cap] .text .fade { transition-duration: 1.1s, 1.2s; }

/* The role and dates under each chapter's pictures write themselves in as the pictures
   land, then the words follow. */
.when-ink .c { white-space: pre; }
.js .when-ink .c { opacity: 0; transition: opacity .3s ease calc(var(--when-at, .55s) + var(--k) * 18ms); }
.js main .ch.told .when-ink .c { opacity: 1; }
.js main .ch[data-cap] { --text-after: 1.2s; }
@media (prefers-reduced-motion: reduce) { .js .when-ink .c { opacity: 1; transition: none; } }
.js main .ch[data-motif="boba"] { --text-after: .5s; }
.when-ink { -webkit-user-select: none; user-select: none; }

/* Every chapter on the same beat: pictures, then the hook, then each paragraph. */
.js main .ch[data-cap] { --text-after: 1s; }
.js main .ch[data-motif="boba"] { --text-after: .6s; }
.js main .ch[data-cap] { --text-after: .6s; }
.js main .ch[data-motif="boba"] { --text-after: .4s; }
.js main .ch[data-cap] .text .fade { transition-duration: .8s, .9s; }

/* Paragraphs arrive line by line (app.js wraps the words and numbers the lines). */
.lines main .text .fade { opacity: 1; translate: none; }
.lines main .text .fade .lw { opacity: 0; transition: opacity .5s ease calc(var(--bd, 0s) + var(--ln, 0) * 70ms); }
.lines main .text .fade.in .lw { opacity: 1; }
.lines main .text .fade .lw .lw { opacity: 1; }
/* The hook follows the pictures quickly; the body lines follow it. */
.js main .ch[data-cap] { --text-after: .4s; }
.js main .ch[data-motif="boba"] { --text-after: .3s; }

/* Until the opening starts (fonts ready, script running), nothing in it shows, so the
   story never flashes in before it is written and nothing jumps when fonts arrive. */
.ink-wait .hero .role, .ink-wait .hero .lede, .ink-wait .hero > .hello-link { visibility: hidden; }
/* The painted scene comes up as the name fades in. */
.js .hero .scene { transition-duration: 1.4s, 1.4s; transition-delay: .3s, .3s; }

/* Mobile composition: text, artwork and caption each reserve their own space. */
.prints, .fan, .cascade, .orbit { isolation: isolate; }
.prints > .wash, .fan > .wash, .cascade > .wash, .orbit > .wash { z-index: -1 !important; }
@media (max-width: 820px) {
  .ch:not(.hero):not(.end) { padding: 88px 24px 64px 52px; }
  .ch[data-motif="book"], .ch[data-motif="apple"], .ch.oria { padding-top: 88px; padding-bottom: 64px; }
  .ch .text { min-width: 0; }
  .text h2.hook { font-size: clamp(26px, 6.8vw, 34px); line-height: 1.2; margin-bottom: 24px; }
  .text p { line-height: 1.7; margin-bottom: 20px; }
  .beat { margin-bottom: 24px !important; line-height: 1.3 !important; }
  .right > .mark, .left > .mark, .oria > .mark { top: 32px !important; left: 22px !important; }
  .wash { width: 155%; max-width: 680px; height: auto; aspect-ratio: 5 / 4; }
  .fan { height: auto; aspect-ratio: 1 / 1.15; margin-top: 40px; }
  .fan > img { width: 46%; border-radius: 18px; }
  .cascade { height: auto; aspect-ratio: 1 / 1.65; margin-top: 40px; }
  .cascade > img { width: 57%; left: 4%; top: 16px; }
  .ch > p.when, .ch[data-motif="sprout"] > p.when, .ch[data-motif="card"] > p.when { position: relative; margin: 24px 0 0; line-height: 1.6; }
  .prints.dote { margin-top: 40px; }
  .fun .prints { margin-top: 48px; }
  .fun .art { margin-left: 22%; width: 70% !important; margin-bottom: 24px; }
  .fun .snap { left: -28%; top: 8%; width: 48%; }
  .motion-toggle { width: 44px; height: 44px; left: 2px; bottom: 12px; }
  .motion-toggle[aria-pressed="true"] span { margin-left: 18px; }
  .hello-link, .charm, .copy { min-height: 44px; }
  .hello-link { display: inline-flex; align-items: center; }
  /* Avoid blur/line-by-line paint work while a finger is scrolling. */
  .lines main .text .fade .lw { filter: none; transition: opacity .45s ease; transition-delay: 0s !important; }
  .fade { filter: none; transition-duration: .45s; transition-delay: 0s !important; }
  .js .hero .lede .w, .js .hero .role { filter: none; }
}

/* Intrinsic dimensions reserve layout without changing the artwork ratio. */
img { height: auto; }
