/* Written by scripts/landing-scenes/build.py from css/*.css and the scene modules: edit those. */

/* Dai Novel — the landing page, for busy grown-ups who still daydream.
   One valley that changes lives, a few sentences, and nothing that moves with
   the scroll: every change happens on the clock. */

/* Fonts: Instrument Serif (headlines) and Inter (text), both SIL Open Font License, served from here (assets/fonts/,
   latin subsets: the page is English) so no third-party stylesheet or origin stands between the page and its first paint. */
@font-face { font-family: 'Instrument Serif'; font-style: normal; font-weight: 400; font-display: swap; src: url(assets/fonts/instrument-serif-latin.woff2) format('woff2'); }
@font-face { font-family: 'Instrument Serif'; font-style: italic; font-weight: 400; font-display: swap; src: url(assets/fonts/instrument-serif-italic-latin.woff2) format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400 600; font-display: swap; src: url(assets/fonts/inter-latin.woff2) format('woff2'); }

:root {
    --night: #070a1b;
    --ivory: #f5efe4;
    --dim: rgba(245, 239, 228, .74);
    --faint: rgba(245, 239, 228, .5);
    --gold: #f1c88f;
    --line: rgba(245, 239, 228, .12);
    --foot: #121325; /* where film and page meet (.how); without WebGL2, the flat ground */
    --serif: 'Instrument Serif', 'Iowan Old Style', 'Palatino Linotype', Georgia, serif;
    --sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --gut: clamp(22px, 6vw, 96px);
    --bar-h: 68px;
    --ease: cubic-bezier(.2, .7, .1, 1);
    /* silhouettes and the near hill: the cast takes them from its mood (film.py: cast) */
    --you: #080b1e;
    --near: #080b1e;
}

.no-gl { --foot: var(--near); }

*,
*::before,
*::after { box-sizing: border-box; }

html { background: var(--night); color-scheme: dark; scrollbar-width: none; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
html::-webkit-scrollbar { display: none; } /* no scrollbar: the film turns slide by slide, and a gutter would cut the picture's edge */

body { margin: 0; background: var(--night); color: var(--ivory); font: 400 16px/1.6 var(--sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }

a { color: inherit; }
h1, h2, h3, p, ol, figure, dl, dd { margin: 0; padding: 0; }
ol { list-style: none; }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; border-radius: 4px; }

.skip { position: fixed; z-index: 40; left: 12px; top: -80px; padding: 10px 18px; background: var(--ivory); color: var(--night); font-weight: 600; text-decoration: none; border-radius: 999px; }
.skip:focus { top: 12px; }

/* ---------------------------------------------------------------- type */

h1,
h2 {
    font: 400 clamp(2.5rem, 5.1vw, 4.9rem)/1.03 var(--serif);
    letter-spacing: -.012em;
    text-wrap: balance;
}
h1 em,
h2 em { font-style: italic; color: var(--gold); }

.eyebrow { margin-bottom: 20px; font: 500 .72rem/1 var(--sans); letter-spacing: .22em; text-transform: uppercase; color: var(--faint); }
.lede { max-width: 30em; margin-top: 24px; font-size: clamp(1rem, 1.2vw, 1.16rem); color: var(--dim); text-wrap: pretty; }
.tale { max-width: 24em; margin-top: 22px; font: italic 400 clamp(1.3rem, 1.9vw, 1.75rem)/1.3 var(--serif); color: rgba(245, 239, 228, .9); text-wrap: pretty; }
/* The closing slide's last line, lowest and nearest the horizon: brighter, with its own shade. */
.aside { max-width: 30em; margin-top: 18px; font-size: .94rem; color: rgba(245, 239, 228, .94); text-shadow: 0 0 14px rgba(7, 10, 27, .7), 0 1px 3px rgba(7, 10, 27, .5); }
.aside .link { color: inherit; white-space: nowrap; }
.js.is-ready .slide.is-on > .aside { opacity: 1; transition: opacity 1.2s var(--ease) 1.8s; }
/* That slide has a line more: beside the picture it starts higher, to end in the sky (the far
   ridge crosses at .56 of the height); low windows set it closer. --tail: all but the headline. */
.slide.end { --tail: 126px; --tight: 0px; }
@media (max-width: 1100px) {
    .aside { max-width: 19em; }
    .slide.end { --tail: 150px; }
}
@media (max-height: 880px) and (min-width: 861px), (max-height: 700px) {
    .slide.end { --tight: 20px; }
    .slide.end .eyebrow { margin-bottom: 14px; }
    .slide.end .lede { margin-top: 16px; }
    .aside { margin-top: 12px; }
    .js .slide.end { padding-top: 84px; }
}
@media (min-width: 861px) and (min-aspect-ratio: 9/10) {
    .js .slide.end { padding-top: max(76px, min(23vh, 51vh - min(15.8vw, 15.2rem) - var(--tail) + var(--tight))); }
}

.ctas { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 26px; margin-top: 34px; }
.btn { display: inline-flex; align-items: center; height: 50px; padding: 0 26px; border-radius: 999px; background: var(--ivory); color: #11132a; font: 600 .96rem/1 var(--sans); text-decoration: none; transition: background-color .4s ease, transform .4s var(--ease); }
.btn:hover { background: #fff; transform: translateY(-1px); }
.link { font-size: .94rem; color: var(--dim); text-decoration: none; border-bottom: 1px solid rgba(245, 239, 228, .3); padding-bottom: 2px; transition: color .3s, border-color .3s; }
.link:hover { color: var(--ivory); border-color: var(--ivory); }

/* ---------------------------------------------------------------- chrome */

.bar { position: fixed; z-index: 30; inset: 0 0 auto; height: var(--bar-h); display: flex; align-items: center; justify-content: space-between; padding: 0 var(--gut); padding-top: env(safe-area-inset-top); pointer-events: none; }
.bar > * { pointer-events: auto; }
.bar::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: rgba(7, 10, 27, .78);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid rgba(245, 239, 228, .08);
    opacity: 0;
    visibility: hidden; /* over the film it is not there at all: a blur nobody sees is still paid for every frame */
    transition: opacity .5s ease, visibility 0s .5s;
}
.bar.is-solid::before { opacity: 1; visibility: visible; transition-delay: 0s; }
.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font: 400 1.32rem/1 var(--serif);
    text-decoration: none;
}
.brand svg { width: 17px; height: 20px; fill: currentColor; }
.bar-nav { display: flex; align-items: center; gap: 26px; font-size: .88rem; font-weight: 500; }
.bar-link { color: var(--dim); text-decoration: none; transition: color .3s; }
.bar-link:hover { color: var(--ivory); }
.bar-cta { padding: 8px 16px; border-radius: 999px; box-shadow: inset 0 0 0 1px rgba(245, 239, 228, .5); text-decoration: none; transition: background-color .3s, color .3s; }
.bar-cta:hover { background: var(--ivory); color: #11132a; }

/* ---------------------------------------------------------------- the plain part */

/* The film's stock: one tile of its grain, still (--grain: page.py), a device pixel a grain. */
.more,
.foot { --tile: 64px; position: relative; z-index: 2; background: var(--grain) var(--night); background-size: var(--tile); }
@media (min-resolution: 2dppx) {
    .more,
    .foot { --tile: 32px; }
}
.more { --pad: clamp(84px, 14vh, 160px); padding: var(--pad) var(--gut); }
.how h2,
.begin h2 { font-size: clamp(2.2rem, 4.2vw, 4rem); }

/* How it works: three steps beside one turn of a story. */
.how,
.note { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); column-gap: clamp(40px, 8vw, 130px); align-items: start; }
/* Film and page meet on one colour (--foot): the stage's last row is uneven, and of any life.
   Over the stage's foot lie its last rows under the closing slide (::after, below the ticks,
   coming with that slide's light) and, while the film leaves (no tick on), a taller fade (::before). */
.js .how { background-image: var(--grain), linear-gradient(var(--foot), var(--night) var(--pad)); background-size: var(--tile), auto; }
.js .how::before,
.js .how::after { content: ''; position: absolute; inset: auto 0 100%; height: 24px; background: linear-gradient(transparent, var(--foot)); opacity: 0; transition: opacity .6s ease; pointer-events: none; }
.js .how::before { height: 30vh; }
.film:has(.stage[data-mood="night"]) + .how::after { opacity: 1; transition: opacity 1.2s ease 1.2s; }
.film:not(:has(.ticks .on)) + .how::before { opacity: 1; }
.how-head { grid-column: 1 / -1; margin-bottom: clamp(44px, 8vh, 84px); }
.how h2 em { white-space: nowrap; } /* one line, or broken before the gold */

.steps { border-bottom: 1px solid var(--line); }
.steps li { display: grid; grid-template-columns: 3.4rem 1fr; align-items: baseline; padding: 26px 0 30px; border-top: 1px solid var(--line); }
.steps i { font: italic 400 1.2rem/1 var(--serif); color: var(--gold); }
.steps h3 { font: 400 1.7rem/1.1 var(--serif); }
.steps p { max-width: 31rem; margin-top: 10px; color: var(--dim); font-size: .98rem; text-wrap: pretty; }

/* One turn: a page under a lamp (::after) on the turns before it (::before). Pseudo-elements,
   so the paper stays under the words as it arrives. */
.page { position: relative; isolation: isolate; padding: clamp(28px, 3.4vw, 46px) clamp(26px, 3.4vw, 46px) clamp(26px, 3vw, 40px); font: 400 clamp(1.22rem, 1.6vw, 1.5rem)/1.42 var(--serif); }
.page::before,
.page::after { content: ''; position: absolute; z-index: -1; inset: 0; border: 1px solid var(--line); border-radius: 6px; }
.page::before { background: #0a0d22; transform: rotate(1.2deg); opacity: .8; }
.page::after {
    background: var(--grain), radial-gradient(120% 90% at 0 0, rgba(241, 200, 143, .085), rgba(241, 200, 143, 0) 62%), #0c0f27;
    background-size: var(--tile), auto, auto;
    box-shadow: 0 40px 70px -40px rgba(0, 0, 0, .8);
}
/* its lines come as a turn does, once it is seen (--at: page.py) */
.js .page > p { opacity: 0; transition: opacity .7s var(--ease) var(--at); }
.js .page.in > p { opacity: 1; }
.page figcaption,
.page .me span { font: 500 .68rem/1 var(--sans); letter-spacing: .2em; text-transform: uppercase; color: var(--faint); }
.page figcaption { margin-bottom: 26px; }
.page p + p { margin-top: 18px; }
.page .me { padding-left: 18px; border-left: 1px solid var(--gold); font-style: italic; color: var(--gold); }
.page .me span { display: block; margin-bottom: 6px; font-size: .66rem; }
.page .later { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line); font: 400 .9rem/1.5 var(--sans); color: var(--dim); text-wrap: balance; }

/* The afterword: a signed note beside what is open and what is not. One hairline, no card. */
.note { padding-top: 0; }
.note::before { content: ''; grid-column: 1 / -1; height: 1px; margin-bottom: var(--pad); background: var(--line); }
.note h2 { font-size: clamp(2rem, 3.5vw, 3.3rem); }
.note-body { max-width: 34rem; margin-top: clamp(26px, 4.4vh, 40px); font: 400 clamp(1.2rem, 1.42vw, 1.34rem)/1.5 var(--serif); color: rgba(245, 239, 228, .86); text-wrap: pretty; }
.note-body p + p { margin-top: .9em; }
.note-body a { color: var(--ivory); text-decoration-color: rgba(245, 239, 228, .38); text-decoration-thickness: 1px; text-underline-offset: .2em; transition: text-decoration-color .3s; }
.note-body a,
.nb { white-space: nowrap; }
.note-body a:hover { text-decoration-color: var(--ivory); }
.note-body a:focus-visible { outline-offset: 2px; }
.sign { display: flex; align-items: flex-end; gap: 22px; margin-top: clamp(30px, 5vh, 46px); }
.sign p { padding-bottom: 5px; font: italic 400 1.22rem/1.3 var(--serif); color: rgba(245, 239, 228, .9); }
.sign small { display: block; margin-top: 7px; font: 400 .82rem/1.4 var(--sans); color: var(--faint); }

/* The cabin draws itself once: each stroke is a path of length 1 that waits --d and takes --t
   (page.py), at opacity 0 until then (Safari paints an undrawn dash's cap). Always 132 px: a
   scaled dash would not close. */
.sketch { flex: none; width: 132px; height: 62px; overflow: visible; }
.sketch .k { stroke-dasharray: 1 2; }
.js .sketch .k { stroke-dashoffset: 1; opacity: 0; }
.js .sketch .lit { opacity: 0; }
.js .sign.in .k { stroke-dashoffset: 0; opacity: 1; transition: stroke-dashoffset var(--t) cubic-bezier(.4, 0, .3, 1) calc(var(--d) + .6s), opacity 1ms linear calc(var(--d) + .6s); }
.js .sign.in .lit { opacity: 1; transition: opacity .5s ease 2.3s; }

.note-facts .eyebrow { margin-bottom: 22px; }
.qa .ledger > div { grid-template-columns: minmax(0, 1fr); row-gap: 6px; }
.ledger { border-bottom: 1px solid var(--line); }
.ledger > div { display: grid; grid-template-columns: clamp(6.8rem, 9vw, 8.6rem) 1fr; column-gap: 20px; align-items: baseline; padding: 22px 0 25px; border-top: 1px solid var(--line); }
.ledger dt { font: 500 .86rem/1.5 var(--sans); }
.ledger dd { color: var(--dim); font-size: .94rem; text-wrap: pretty; }
.ledger em { margin-right: .1em; font: italic 400 1.15rem/1 var(--serif); }
.ledger .not { color: var(--gold); }

/* The last call, over six doors on still water (page.py: dawn): 640 x 314 film units, as wide
   as the page; the land is its lower .65 (the words stand in the sky above it). */
.begin { --dawn: max(300px, 49.0625vw); display: grid; justify-items: center; text-align: center; overflow: hidden; padding-bottom: calc(var(--dawn) * .65); background: var(--night); }
.begin > * { position: relative; z-index: 2; }
.begin .lede { text-wrap: balance; }
.begin .lede span { white-space: nowrap; } /* breaks between sentences */
.begin .ctas { justify-content: center; }
.begin .link { color: rgba(245, 239, 228, .9); border-color: rgba(245, 239, 228, .42); } /* a lighter sky than the hero's */
.begin .dawn { position: absolute; z-index: 0; left: 0; bottom: 0; width: 100%; height: var(--dawn); }
/* (here the grain lies over the picture) */
.begin::after { content: ''; position: absolute; z-index: 1; inset: 0; background: var(--grain); background-size: var(--tile); pointer-events: none; }
/* It moves only while on screen (home.js sets .live), on transform and opacity alone, on periods that
   never line up (page.py lists what moves). Every loop starts and ends on the still the markup draws,
   so paused, or without script, it is that still. */
.dawn *{animation:0s ease-in-out infinite alternate paused}
.begin.live .dawn *{animation-play-state:running}
.dw-swell,.dw-lift{animation-name:dw-call;animation-duration:33s;animation-direction:normal}
.dw-ik-runes{animation-name:dw-dim;animation-duration:3.5s}
.dw-tw{animation-name:dw-dim;animation-duration:2.3s}
.dw-far{animation-name:dw-dim;animation-duration:5.3s}
.dw-ik-dragon{animation-name:dw-glide;animation-duration:9s;transform-box:fill-box;transform-origin:center}
.dw-sa-lanterns,.dw-sa-paper,.dw-sa-low,.dw-glint,.dw-me-neon,.dw-me-signs,.dw-me-visor{animation-timing-function:step-end;animation-direction:normal}
.dw-sa-lanterns{animation-name:dw-flick;animation-duration:2.3s}
.dw-sa-paper{animation-name:dw-flick;animation-duration:3.1s;animation-delay:1.2s}
.dw-glint{animation-name:dw-flick;animation-duration:1.7s}
.dw-sa-low{animation-name:dw-out;animation-duration:13s}
.dw-me-neon{animation-name:dw-glitch;animation-duration:5.7s}
.dw-me-signs{animation-name:dw-glitch;animation-duration:4.3s;animation-delay:2s}
.dw-me-visor{animation-name:dw-blink;animation-duration:6.3s}
.dw-me-train,.dw-jd-cranes,.dw-sc-petals,.dw-pt{animation-direction:normal;animation-timing-function:linear}
.dw-me-train{animation-name:dw-train;animation-duration:14s}
.dw-jd-cranes{animation-name:dw-cross;animation-duration:21s}
.dw-sc-petals{animation-name:dw-fall;animation-duration:7s}
.dw-pt{animation-name:dw-esc;animation-duration:9.5s;transform-box:fill-box;transform-origin:center}
.dw-se-waves,.dw-jd-clouds,.dw-rip,.dw-refl{animation-name:dw-drift}
.dw-se-waves{animation-duration:3s;--a:1.6px}
.dw-jd-clouds{animation-duration:11s;--a:2.4px}
.dw-rip-a{animation-duration:13s;--a:6px}
.dw-rip-b{animation-duration:9s;--a:-5px}
.dw-rip-c{animation-name:dw-drift,dw-dim;animation-duration:7s,2.6s;--a:4px}
.dw-refl{--a:.4px}
.dw-se-galleon{animation-name:dw-bob;animation-duration:4.4s;transform-box:fill-box;transform-origin:50% 100%}
.dw-se-serpent{animation-name:dw-rise;animation-duration:6.2s}
.dw-sc-branch{animation-name:dw-sway;animation-duration:8s;transform-box:fill-box;transform-origin:100% 40%}
@keyframes dw-call{0%,18%,100%{opacity:0}7%{opacity:.8}}
@keyframes dw-dim{to{opacity:.45}}
@keyframes dw-glide{to{transform:translate(-3px,1.2px) rotate(-3deg)}}
@keyframes dw-flick{0%{opacity:1}21%{opacity:.78}25%{opacity:1}61%{opacity:.9}64%{opacity:1}}
@keyframes dw-out{0%{opacity:1}70%{opacity:.5}72%{opacity:0}86%{opacity:.6}88%{opacity:1}}
@keyframes dw-glitch{0%{opacity:1}61%{opacity:.2}62.5%{opacity:1}64%{opacity:.1}65.5%{opacity:1}}
@keyframes dw-blink{0%{opacity:1}47%{opacity:.15}49%{opacity:1}52%{opacity:.15}54%{opacity:1}}
@keyframes dw-train{40%{transform:translateX(48px)}40.1%{transform:translateX(-26px)}}
@keyframes dw-cross{35%{opacity:1}45%{transform:translate(-26px,-3px);opacity:0}45.1%{transform:translate(22px,2px)}60%{opacity:1}}
@keyframes dw-fall{70%{transform:translate(1.4px,6px);opacity:0}70.1%{transform:translateY(-4px)}85%{opacity:1}}
@keyframes dw-esc{0%,100%{opacity:.9}80%{transform:translate(var(--x),var(--y)) rotate(160deg);opacity:0}80.1%{transform:none;opacity:0}}
@keyframes dw-drift{to{transform:translateX(var(--a))}}
@keyframes dw-bob{to{transform:translateY(.5px) rotate(-1.6deg)}}
@keyframes dw-rise{to{transform:translateY(1.1px)}}
@keyframes dw-sway{to{transform:rotate(.7deg)}}

/* things below the story arrive once, quietly */
.js .rv { opacity: 0; transform: translateY(14px); transition: opacity 1.1s var(--ease), transform 1.3s var(--ease); }
.js .rv.in { opacity: 1; transform: none; }

/* ---------------------------------------------------------------- footer: the colophon */

/* (1px under it: the page must end past film.css's free-scroll area however heights round, or
   End snaps back to the film) */
.foot { display: grid; grid-template-columns: auto 1fr; gap: 8px 40px; align-items: baseline; margin-bottom: 1px; padding: 30px var(--gut) max(34px, env(safe-area-inset-bottom)); font-size: .86rem; color: var(--dim); }
.foot-brand { display: flex; align-items: center; gap: 10px; font: 400 1.2rem/1 var(--serif); color: var(--ivory); }
.foot-brand svg { width: 15px; height: 18px; fill: currentColor; }
.foot-links { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px 24px; }
.foot-links a { text-decoration: none; scroll-margin-block: 24px; transition: color .3s; } /* (room for the focus ring) */
.foot-links a:hover { color: var(--ivory); }
.foot-tag,
.foot-copy { grid-column: 1 / -1; color: var(--faint); }
.foot-copy { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); font-size: .8rem; text-wrap: balance; }

@media (max-width: 860px), (max-aspect-ratio: 9/10) {
    :root { --bar-h: 58px; }
    .bar-link { display: none; }
    .how,
    .note,
    .foot { grid-template-columns: minmax(0, 1fr); }
    .page { margin-top: 56px; }
    .note-facts { margin-top: 64px; }
    .foot-links { justify-content: flex-start; margin-top: 12px; order: 1; }
    .foot-copy { order: 2; }
}
@media (max-width: 520px) {
    .foot-links { display: grid; grid-template-columns: repeat(3, max-content); justify-items: start; }
    .sign { flex-direction: column; align-items: flex-start; gap: 14px; }
    .ledger > div { grid-template-columns: minmax(0, 1fr); row-gap: 6px; }
}

@media (prefers-reduced-motion: reduce) {
    /* (the script gives every reveal its delay inline) */
    .js .rv { transition-duration: .01s; transition-delay: 0s !important; transform: none; }
    .dawn * { animation: none !important; }
    .js.is-ready .slide.is-on > .aside { transition-delay: 0s; transition-duration: .01s; }
    .how::before,
    .how::after { transition: none !important; }
    .js .page > p,
    .js .sketch .k,
    .js .sketch .lit { stroke-dashoffset: 0; opacity: 1; transition: none; }
}

/* ---------------------------------------------------------------- the view */

/* The film turns like pages. Its scroll positions snap: one mark per slide,
   never skipped, so one gesture is one slide; then the page below it, as one
   area taller than any screen, inside which the scroll is free again.
   (Only where a tall snap area is known to scroll freely: the same browsers
   that know aspect-ratio.) */
@supports (aspect-ratio: 1) {
    html.js { scroll-snap-type: y mandatory; }
}
.rest { position: absolute; left: 0; width: 1px; height: 1px; pointer-events: none; scroll-snap-align: start; scroll-snap-stop: always; }
.rest.below { height: auto; bottom: 0; scroll-snap-stop: normal; }
/* The film's end, closed (home.js: shut): while a hand that set out before the last slide is still
   scrolling, the page ends with the film. The scroll, which may run on under the pinned view as far
   as a hard or repeated gesture takes it, then stops at the last slide instead of carrying the view
   out below, over slides that were never shown. (No bounce against that end: the view is pinned.) */
.js.is-held .more,
.js.is-held .foot { display: none; }
.js.is-held { overscroll-behavior-y: none; }
/* Under a mouse or a trackpad the page never overscrolls: a wheel spun on against the top of the page
   would leave the browser travel it could not apply, and it spends that later, on the page the scroll
   was put back on (a slide turned by nobody). A finger keeps the top's pull-to-refresh. */
@media (hover: hover) and (pointer: fine) {
    html.js { overscroll-behavior-y: none; }
}
.js body { position: relative; }

.js .film { position: relative; }
.js .stage {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100lvh;
    overflow: hidden;
    /* (its background is the mood's sky: film.stage_css, at the end of this file) */
    background: var(--night);
}
/* The picture. Two canvases (gl.js) with every scene's SVG between them, as layers:
   one div per depth plane, as big as its art. The script lays every box of SVG
   (data-b, in scene units) on whole device pixels with the same map the shaders read,
   and owns each layer's transform (the camera) and opacity (presence).
   .world itself only ever takes the zoom: as a transform of the compositor's (will-change), so that a zoom
   that changes every frame moves layers and never rasters what lies in them again. */
.world { position: absolute; inset: 0; transform-origin: 0 0; pointer-events: none; will-change: transform; }
.view { display: none; }
/* The picture arrives with its first frame (.gl-ready, home.js); the words do not wait for it. */
.js .view { position: absolute; inset: 0; display: block; width: 100%; height: 100%; opacity: 0; transition: opacity .5s linear; }
.gl-ready .view { opacity: 1; }
/* The canvas on top paints only while a page turns: the edge a new world arrives behind (gl-over.js). */
.js .view.over { visibility: hidden; transition: none; }
.js .is-wiping .view.over { visibility: visible; }
.defs { position: absolute; }
/* A scene's layers lie in a set: the one box a wipe cuts to its world's part of the picture (home.js
   writes its clip-path, or for a soft front its mask, frame by frame; nothing else ever styles it). */
.set { position: absolute; inset: 0; pointer-events: none; }
.layer { position: absolute; overflow: hidden; contain: strict; pointer-events: none; display: none; opacity: 0; }
/* only the scene that is up (and the one arriving) is laid out at all */
.layer.live { display: block; will-change: transform, opacity; }
.layer > .plate { position: absolute; left: 0; top: 0; width: 100%; height: 100%; }
.layer > .actor,
.cast > svg { position: absolute; overflow: hidden; }
/* What changes is drawn on a layer of its own: an actor's next pose, or the cloth you wear, then rasters
   that small box again, and not the tile of the plate it stands on. */
.layer > .actor,
.cast > .actor,
.cast > .tufts,
.cast > .plate.live { will-change: transform; }
/* an actor's drawings (lib.poses): only the one that is up is laid out */
.pose:not(.on) { display: none; }
/* You, and what stands beside you on the nearest hill: above the front canvas, never moved. */
.cast { position: absolute; left: 0; top: 0; opacity: 0; transition: opacity .3s linear; }
.gl-ready .cast { opacity: 1; }
.cast > .plate[data-s] { display: none; opacity: 0; }
.cast > .plate.live { display: block; }
/* What the cast paints as part of the hill (lib.near: grass over a foot) takes the hill's colour, and
   over it the vignette the front shader lays on the hill around it (#ca-vg, at the mood's amount). */
.cast .near { fill: var(--near); }
.cast .vg { fill: url(#ca-vg); opacity: var(--vg); }
.no-gl .world { display: none; }

/* Night behind the words, and only there: the horizon keeps its light. The front shader
   paints it; this one returns only when there is no WebGL2. There the words stand on the bare
   sky of their mood, so it is deeper than the shader's and reaches down to the horizon (--hz). */
.shade { display: none; }
.no-gl .shade { display: block; }
.shade,
.shade::after {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse 90% 110% at 0 30%, rgba(5, 7, 22, .62), rgba(5, 7, 22, .5) 40%, rgba(5, 7, 22, .2) 70%, rgba(5, 7, 22, 0) 92%);
    -webkit-mask-image: linear-gradient(#000 calc(var(--hz, 66%) - 60px), transparent var(--hz, 66%));
    mask-image: linear-gradient(#000 calc(var(--hz, 66%) - 60px), transparent var(--hz, 66%));
}
/* the bright lives need more of it */
.shade::after { content: ''; opacity: 0; transition: opacity 1.8s ease; }
[data-mood="isekai"] .shade::after,
[data-mood="sea"] .shade::after,
[data-mood="jade"] .shade::after { opacity: 1; }
.slide { text-shadow: 0 1px 16px rgba(5, 7, 20, .36); }
.slide .btn { text-shadow: none; }

/* ---------------------------------------------------------------- without the film (no JS) */

html:not(.js) .stage { padding: 130px var(--gut) 40px; background: linear-gradient(#0b1030, #1c2a66 60%, #070a1b); }
html:not(.js) .world,
html:not(.js) .shade { display: none; }
html:not(.js) .slide { max-width: 46rem; margin-bottom: 96px; }

@media (max-width: 860px), (max-aspect-ratio: 9/10) {
    .shade,
    .shade::after { background: linear-gradient(rgba(5, 7, 20, .46), rgba(5, 7, 20, .2) 40%, rgba(5, 7, 20, 0) 62%); -webkit-mask-image: none; mask-image: none; }
}
/* a view this tall shows the valley from x = 212 on; a low, wide window sees all of it */
@media (max-aspect-ratio: 9/10) {
    .wide-only { display: none !important; }
}

/* Nothing may move (reduced motion, or ?t=: home.js sets .is-still): the picture is simply there, each
   scene at its poster (cel.js freezes the poses), and no loop of a scene's CSS runs. */
.is-still .view,
.is-still .cast,
.is-still .shade::after { transition: none; }
.is-still .world * { animation: none !important; }

/* Over a hot place (home.js, stepPen) the cursor is a hand. */
.js .stage.is-hot { cursor: pointer; }

/* ---------------------------------------------------------------- you (figures.py, played by cel.js) */

/* One body in every life. The silhouette (#ca-you) is painted twice: in the rim's colour, then again
   in your own a little away from the light, which leaves a crescent on every edge that faces it.
   (Its shapes are filled; the few that are lines, a staff or a scabbard, carry their own width.)
   Only these two <use> are styled here: a rule that names an ancestor does not reach into what a
   <use> paints, so whatever changes inside the silhouette is written as attributes (cel.js). */
.you .rim { fill: url(#ca-rimlight) var(--rim); stroke: url(#ca-rimlight) var(--rim); } /* the ramp (figures.RIMFADE), else the flat colour */
.you .body,
.tufts .shadow { fill: var(--you); stroke: var(--you); }
.you .acc { fill: var(--acc); }
.you .mid { fill: var(--near); }
/* The light is right of you in every life, but higher in some (BIBLE 5's keys): there the copy in --you also moves
   further down and less far left, so that more of the crescent lies on the tops of head, shoulders and hat. A nudge
   on top of cel.js's offset (figures.OFFSET, the hero's and the pilot's low afterglow), the same length in all. */
.cast[data-m="isekai"] .you .lean { transform: translate(.03px, .07px); }
.cast[data-m="samurai"] .you .lean { transform: translate(.08px, .13px); }
.cast[data-m="sea"] .you .lean { transform: translate(.14px, .19px); }
.cast[data-m="jade"] .you .lean { transform: translate(.08px, .12px); }
.cast[data-m="night"] .you .lean { transform: translate(.1px, .15px); }

/* ---------------------------------------------------------------- the words: one slide at a time, part of the cut */

/* home.js takes .is-on away when a turn begins: everything leaves together, in 140 ms. It gives
   .is-on to the new slide on the cut frame, so every delay below is counted from the cut:
   eyebrow, the gold hook, the headline line by line (--l, numbered by home.js), then the rest;
   the tale line last, with its picture's beat (--tale). Slides already passed are .past: their
   headline rests above its line, the ones ahead below theirs, so going forward the words leave
   upward and arrive from below, and going back the other way. Nothing here is a keyframe:
   a turn that is interrupted carries on from wherever the words are. */
.js .slide {
    position: absolute;
    left: var(--gut);
    right: var(--gut);
    top: 0;
    max-width: 46rem;
    padding-top: clamp(118px, 23vh, 250px);
    visibility: hidden;
    pointer-events: none;
    transition: visibility 0s .4s;
    --eb: 140ms; /* eyebrow */
    --hk: 200ms; /* the gold hook: it leads its line */
    --ln: 343ms; /* the first line of the headline */
    --ls: 90ms;  /* from one line to the next */
    --ld: 700ms; /* lede */
    --ct: 900ms; /* buttons */
}
/* 861-1366 px wide: the headline wraps before it reaches the hero band (scene x 400-560) */
@media (min-width: 861px) and (max-width: 1366px) { .js .slide { max-width: min(46rem, 52vw); } }
/* the hero as the page opens: two beats, and time to read the first */
html:not(.is-turned) #top { --eb: 120ms; --ln: 200ms; --ls: 360ms; --ld: 900ms; --ct: 1100ms; }
.js .slide.is-on { visibility: visible; pointer-events: auto; transition-delay: 0s; }
/* Compositor layers, and no more of them than a turn needs (each one is memory, and work on every frame).
   The slide that is up keeps one for each of its blocks: they leave by fading, on layers that exist.
   A slide whose words are about to arrive (.soon: home.js gives it to the slide a turn goes to and, at
   rest on a fast enough film, to the one ahead) has one for every part that animates, each word too:
   nothing is promoted or rastered in the middle of a wipe. */
.js .slide.is-on > * { will-change: opacity; }
.js .slide.soon { visibility: visible; transition-delay: 0s; }
.js .slide.soon > *,
.js .slide.soon .w > i { will-change: opacity, transform; }
.js .slide > * { opacity: 0; transition: opacity .14s linear; }
/* A headline arrives word by word, each in a box (home.js splits it, and labels it with its sentence);
   the box clips its word, with room for an italic f or g. It leaves as one block, like the rest. */
.is-ready .is-on > [aria-label] { opacity: 1; transition: none; }
.w { display: inline-block; clip-path: inset(-.2em -.35em -.22em -.35em); }
/* (Leaving, the words stay where they stand while their headline fades, and only then go to their rest
   below or above the line, in one step: a transform that eased away for 400 ms kept a dozen compositor
   animations running, unseen, into the first third of the wipe, and on a 120 Hz screen that halved the
   wipe's frame rate; a fade of each word needed a layer for each word of the slide that is up.) */
.w > i {
    display: inline-block;
    font-style: inherit;
    opacity: 0;
    transform: translateY(128%);
    transition: opacity 0s linear .14s, transform 0s linear .14s;
}
.past .w > i { transform: translateY(-128%); }
.is-ready .is-on .w > i {
    --d: calc(var(--ln) + var(--l, 0) * var(--ls));
    opacity: 1;
    transform: none;
    transition: opacity .22s linear var(--d), transform .62s cubic-bezier(.16, 1, .3, 1) var(--d);
}
.is-ready .is-on em:first-child i { --d: var(--hk); }
.is-ready .is-on > .eyebrow { opacity: 1; transition: opacity .3s linear var(--eb); }
.is-ready .is-on > .lede { opacity: 1; transition: opacity .6s ease var(--ld); }
.is-ready .is-on > .ctas { opacity: 1; transition: opacity .5s ease var(--ct); }
.is-ready .is-on > .tale { opacity: 1; transition: opacity .9s ease var(--tale, 1.7s); }
/* a slide reached by a dissolve: its light is slow, and the words wait until it is half way */
.js .slide.late { --eb: 400ms; --hk: 460ms; --ln: 600ms; --ld: 960ms; --ct: 1160ms; }
/* a turn that was called off: the same words return in one short fade, not line by line */
.is-ready .again.is-on > *,
.is-ready .again.is-on .w > i { transition-duration: .4s; }
.is-ready .again.is-on > [aria-label] { transition: opacity .4s linear; }
.is-ready .again.is-on *,
.is-still .slide,
.is-still .slide * { transition-delay: 0s !important; }
.is-still .slide,
.is-still .slide * { transition-duration: .01s !important; }

.ticks {
    position: absolute;
    left: var(--gut);
    bottom: max(30px, env(safe-area-inset-bottom));
    display: flex;
    gap: 6px;
    transition: opacity .4s ease;
}
.ticks li { width: 18px; height: 2px; border-radius: 1px; background: var(--ivory); opacity: .26; transition: opacity .4s ease; }
.ticks li.on { opacity: 1; }
.ticks:not(:has(.on)) { opacity: 0; }
html:not(.js) .ticks,
html:not(.js) .cue { display: none; }

.cue {
    position: absolute;
    right: var(--gut);
    bottom: max(26px, env(safe-area-inset-bottom));
    display: flex;
    align-items: center;
    gap: 12px;
    font: 500 .68rem/1 var(--sans);
    letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--faint);
    transition: opacity .4s ease;
}
.cue::after { content: ''; width: 34px; height: 1px; background: currentColor; transform-origin: left; animation: cue 2.6s var(--ease) infinite; }
@keyframes cue { 0% { transform: scaleX(0); } 55%, 100% { transform: scaleX(1); } }
.cue .narrow { display: none; }
/* As the page opens the ticks and the cue come last, when the hero has spoken. The cue is gone for
   good after the first turn (home.js sets .is-turned), and nothing ticks for it any more. */
html:not(.is-ready) .ticks,
html:not(.is-ready) .cue,
.is-turned .cue { opacity: 0; }
html:not(.is-turned) .ticks,
html:not(.is-turned) .cue { transition-delay: 2.6s; }
.is-turned .cue::after,
.is-still .cue::after { animation: none; }
.is-still .ticks,
.is-still .ticks li,
.is-still .cue { transition: none; }
/* narrow screens: the short wording and a short line, then the words alone, then nothing */
@media (max-width: 860px), (max-aspect-ratio: 9/10) {
    .js .slide { padding-top: clamp(96px, 15vh, 150px); }
    .cue { gap: 8px; }
    .cue::after { width: 18px; }
    .cue .wide { display: none; }
    .cue .narrow { display: inline; }
}
@media (max-width: 380px) {
    .cue::after { display: none; }
}
@media (max-width: 340px) {
    .cue { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .cue::after { animation: none; }
}

/* ---------------------------------------------------------------- what several scenes share */

.l-amber { fill: #ffd9a0; }
.l-pink { fill: #ff6fb0; }
.l-cyan { fill: #6fe6ff; }
.flame { fill: #ffcf8a; }
.glow { fill: #ffb35a; opacity: .22; }
.glow.cyan { fill: #7ff0ff; opacity: .28; }
.glow.red { fill: #ff5a4a; opacity: .3; }
.beacon { fill: #ff5a4a; }

/* blue */
.bl-night .bl-wl { fill: #2d2546; }
.bl-night .bl-rl { fill: #362b4e; }
.bl-night .bl-ws { fill: #251f3c; }
.bl-night .bl-rm { fill: #8c6676; }
.bl-night .bl-gr { fill: url(#bl-kn2); }
.bl-night .bl-st { fill: #5661b4; fill-opacity: .5; }

/* isekai */
.ik-n, .ik-f { stroke-width: .9px; stroke-linejoin: round; stroke-linecap: round; }
.ik-n { fill: url(#ik-mn); stroke: #1b1f4e; }
.ik-f { fill: url(#ik-mf); stroke: #3a3276; }
.actor[data-a="sails"] { animation: ik-sails 16s linear infinite; }
@keyframes ik-sails { to { transform: rotate(-360deg); } }
@media (prefers-reduced-motion: reduce) { .actor[data-a="sails"] { animation: none; } }

/* sea */
.actor[data-a="ship"] { transform-origin: 67.12% 77.44%; animation: se-swell 6.4s linear infinite; }
@keyframes se-swell { 0% { transform: translateY(0) rotate(-.55deg); } 25% { transform: translateY(-.45%) rotate(0deg); } 50% { transform: translateY(0) rotate(.6deg); } 75% { transform: translateY(.45%) rotate(0deg); } 100% { transform: translateY(0) rotate(-.55deg); } }
.is-still .actor[data-a="ship"] { animation: none; }
@media (prefers-reduced-motion: reduce) { .actor[data-a="ship"] { animation: none; } }

/* school */
.cast[data-m="school"] .tufts { display: none; }

/* vend */
.is-ready .is-on .ascii-tail .w > i { opacity: 0; transform: translateY(128%); transition: opacity .3s linear, transform 0s linear .3s; }
.is-ready .is-on.ascii .ascii-tail .w > i { opacity: 1; transform: none; transition: opacity .22s linear var(--td, 0s), transform .62s cubic-bezier(.16, 1, .3, 1) var(--td, 0s); }
.ascii-tail .w:nth-child(2) > i { --td: .3s; } .ascii-tail .w:nth-child(3) > i { --td: .6s; } .ascii-tail .w:nth-child(4) > i { --td: .9s; }

/* the cast takes its colours from the mood */
.cast[data-m="blue"], .cast > .plate[data-m="blue"] { --you: #070a1c; --near: #080b1e; --rim: #ffb483; --acc: #ffcf8a; --vg: 0.22; }
.cast[data-m="isekai"], .cast > .plate[data-m="isekai"] { --you: #0a2118; --near: #0e281e; --rim: #ffe7b6; --acc: #ff6a5a; --vg: 0.16; }
.cast[data-m="samurai"], .cast > .plate[data-m="samurai"] { --you: #05030a; --near: #07040c; --rim: #ff5236; --acc: #ffd9c8; --vg: 0.3; }
.cast[data-m="sea"], .cast > .plate[data-m="sea"] { --you: #061815; --near: #081e1b; --rim: #e9fcff; --acc: #e8433a; --vg: 0.12; }
.cast[data-m="mecha"], .cast > .plate[data-m="mecha"] { --you: #06071a; --near: #05061a; --rim: #c9a8ff; --acc: #7ff0ff; --vg: 0.24; }
.cast[data-m="jade"], .cast > .plate[data-m="jade"] { --you: #0c2224; --near: #0c2224; --rim: #fff6d8; --acc: #e9f3df; --vg: 0.18; }
.cast[data-m="school"], .cast > .plate[data-m="school"] { --you: #0e0a18; --near: #120c1e; --rim: #ffdcb4; --acc: #ff7f9c; --vg: 0.18; }
.cast[data-m="vend"], .cast > .plate[data-m="vend"] { --you: #070a0e; --near: #05080b; --rim: #ffe6b0; --acc: #ecfbff; --vg: 0.26; }
.cast[data-m="night"], .cast > .plate[data-m="night"] { --you: #04061a; --near: #05071a; --rim: #b98a8c; --acc: #ffcf8a; --vg: 0.26; }

/* the stage before the first frame, and without WebGL2: one sky per mood */
.js .stage[data-mood="blue"] { background: linear-gradient(#060a24 calc(var(--hz, 66%) * 0), #111c55 calc(var(--hz, 66%) * 0.3), #2a3d8f calc(var(--hz, 66%) * 0.56), #7a68a8 calc(var(--hz, 66%) * 0.74), #f0a37a calc(var(--hz, 66%) * 0.9), #ffd0a0 calc(var(--hz, 66%) * 1), #080b1e calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="isekai"] { background: linear-gradient(#0e2c78 calc(var(--hz, 66%) * 0), #153a8a calc(var(--hz, 66%) * 0.28), #1f4fae calc(var(--hz, 66%) * 0.54), #2f66bd calc(var(--hz, 66%) * 0.72), #8fbdea calc(var(--hz, 66%) * 0.84), #ffe6bc calc(var(--hz, 66%) * 0.93), #0e281e calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="samurai"] { background: linear-gradient(#040308 calc(var(--hz, 66%) * 0), #0a0512 calc(var(--hz, 66%) * 0.28), #1a0818 calc(var(--hz, 66%) * 0.5), #3a0a1e calc(var(--hz, 66%) * 0.7), #8a1a1c calc(var(--hz, 66%) * 0.88), #c8361f calc(var(--hz, 66%) * 1), #07040c calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="sea"] { background: linear-gradient(#062a6e calc(var(--hz, 66%) * 0), #08327f calc(var(--hz, 66%) * 0.3), #1259c0 calc(var(--hz, 66%) * 0.64), #3d9be6 calc(var(--hz, 66%) * 0.86), #a9dcf6 calc(var(--hz, 66%) * 0.955), #c4ecf8 calc(var(--hz, 66%) * 1), #081e1b calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="mecha"] { background: linear-gradient(#090c30 calc(var(--hz, 66%) * 0), #23267a calc(var(--hz, 66%) * 0.34), #5a48b4 calc(var(--hz, 66%) * 0.62), #c064b0 calc(var(--hz, 66%) * 0.82), #ff9f8c calc(var(--hz, 66%) * 0.93), #ffc69a calc(var(--hz, 66%) * 1), #05061a calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="jade"] { background: linear-gradient(#0a2326 calc(var(--hz, 66%) * 0), #0f3437 calc(var(--hz, 66%) * 0.3), #1b4f4e calc(var(--hz, 66%) * 0.56), #286b64 calc(var(--hz, 66%) * 0.74), #579d8b calc(var(--hz, 66%) * 0.9), #88c4ad calc(var(--hz, 66%) * 1), #0c2224 calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="school"] { background: linear-gradient(#172b66 calc(var(--hz, 66%) * 0), #34428e calc(var(--hz, 66%) * 0.3), #7e5ea0 calc(var(--hz, 66%) * 0.55), #e38893 calc(var(--hz, 66%) * 0.73), #ffa878 calc(var(--hz, 66%) * 0.87), #ffd99c calc(var(--hz, 66%) * 1), #120c1e calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="vend"] { background: linear-gradient(#03060d calc(var(--hz, 66%) * 0), #08111e calc(var(--hz, 66%) * 0.3), #0f1c2c calc(var(--hz, 66%) * 0.56), #1b2a39 calc(var(--hz, 66%) * 0.75), #2f3b45 calc(var(--hz, 66%) * 0.9), #4d5050 calc(var(--hz, 66%) * 1), #05080b calc(var(--hz, 66%) + 5vh)); }
.js .stage[data-mood="night"] { background: linear-gradient(#03051a calc(var(--hz, 66%) * 0), #0a1140 calc(var(--hz, 66%) * 0.3), #1a2466 calc(var(--hz, 66%) * 0.56), #33357a calc(var(--hz, 66%) * 0.74), #6a4a78 calc(var(--hz, 66%) * 0.9), #b06a6a calc(var(--hz, 66%) * 1), #05071a calc(var(--hz, 66%) + 5vh)); }
