/* Scroll scenes for the subpages and the closing on every page.

   One driver, app.js `initPublicScrollScenes`, writes `--p` (0 → 1) on each
   [data-otf-scene] and sets `.is-live` on it while motion is allowed; every
   rule here is derived from that number. scripts/scenes.js only marks which
   steps a reading line has reached and when an Explore day has come into
   view. Without `.is-live` (reduced motion, no IntersectionObserver, the
   script not running) each scene shows its finished state, and `--p`
   defaults to 1 for the same reason. Transform and opacity only. */

/* ── How it works: a line that fills as you read ─────────────────────────────
   The list is a "read" scene: `--p` is how far a line 62% down the window has
   moved through it, so the red fill ends exactly at that line. A step lights
   when its icon reaches the same line (scenes.js), and unlights going back. */
.otf-howto-list[data-otf-scene] {
    position: relative;
    padding-left: 48px;
}

.otf-howto-list[data-otf-scene] .otf-howto-step { position: relative; }

.otf-howto-line {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 15px;
    width: 2px;
    border-radius: 2px;
    background: var(--otf-public-line);
    /* From the first step's icon to the last step's: scenes.js measures the
       last one, the first is always 46px down (24px padding + half of 44). */
    clip-path: inset(46px 0 var(--otf-howto-line-end, 46px) 0);
    pointer-events: none;
}

.otf-howto-line > span {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(var(--otf-public-red), #c0392b 70%, var(--otf-public-sand));
    transform: scaleY(var(--p, 1));
    transform-origin: 50% 0;
}

.otf-howto-list:not(.is-live) .otf-howto-line > span { transform: none; }

/* A node on the line beside each step's icon: a ring that fills when lit. */
.otf-howto-list[data-otf-scene] .otf-howto-step::before,
.otf-howto-list[data-otf-scene] .otf-howto-step::after {
    content: "";
    position: absolute;
    top: 39px;
    left: -40px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    pointer-events: none;
}

.otf-howto-list[data-otf-scene] .otf-howto-step::before {
    border: 2px solid var(--otf-public-line);
    background: var(--otf-public-canvas);
}

.otf-howto-list[data-otf-scene] .otf-howto-step::after {
    background: var(--otf-public-red);
    box-shadow: 0 0 0 4px rgba(140, 21, 21, 0.28);
}

.otf-howto-list.is-live .otf-howto-step::after {
    opacity: 0;
    transform: scale(0.4);
    transition: opacity 220ms ease, transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.otf-howto-list.is-live .otf-howto-step .otf-howto-icon {
    opacity: 0.38;
    transform: scale(0.9);
    transition: opacity 320ms ease, transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
}

.otf-howto-list.is-live .otf-howto-step.is-lit::after,
.otf-howto-list.is-live .otf-howto-step.is-lit .otf-howto-icon {
    opacity: 1;
    transform: none;
}

@media (max-width: 640px) {
    .otf-howto-list[data-otf-scene] { padding-left: 30px; }
    .otf-howto-line { left: 6px; }
    .otf-howto-list[data-otf-scene] .otf-howto-step::before,
    .otf-howto-list[data-otf-scene] .otf-howto-step::after { left: -30px; }
}

/* ── Press: the dated updates read the same way ──────────────────────────── */
.otf-press-releases.is-live::before { background: var(--otf-public-line); }

.otf-press-releases.is-live::after {
    content: "";
    position: absolute;
    top: 10px;
    bottom: 10px;
    left: 5px;
    width: 2px;
    border-radius: 2px;
    background: linear-gradient(var(--otf-public-red), var(--otf-public-sand));
    transform: scaleY(var(--p, 1));
    transform-origin: 50% 0;
    pointer-events: none;
}

/* The first dot is already red; the others warm to red as the line passes. */
.otf-press-releases.is-live .otf-press-release::before {
    transition: background-color 260ms ease, box-shadow 260ms ease;
}

.otf-press-releases.is-live .otf-press-release.is-lit::before {
    background: var(--otf-public-red);
    box-shadow: 0 0 0 4px rgba(140, 21, 21, 0.3);
}

/* ── For hosts: create, invite, check in, dealt like cards ───────────────────
   Wide: the three start as one deck in the middle column and deal out to
   their places. Two columns: the second and third slide out from under the
   first. A phone: they stack, each one sliding up over the last as you
   scroll (sticky, so it is the browser's own scrolling). */
/* The deal is over by the time the cards are a little past halfway in: they
   are the first thing on the page, and dealt any later they sat overlapped,
   their text cut off, until the first scroll. */
.otf-hosts-tools {
    --otf-deal: clamp(0, calc((var(--p, 1) - 0.2) / 0.4), 1);
    --otf-dealt: calc(var(--otf-deal) * var(--otf-deal) * (3 - 2 * var(--otf-deal)));
    --otf-rise: clamp(0, calc(var(--p, 1) / 0.2), 1);
}

.otf-hosts-tools.is-live .otf-public-preview-grid {
    opacity: var(--otf-rise);
    translate: 0 calc((1 - var(--otf-rise)) * 40px);
}

.otf-hosts-tools.is-live .otf-public-preview-card {
    z-index: calc(3 - var(--otf-sc-i));
    translate:
        calc((1 - var(--otf-dealt)) * (1 - var(--otf-sc-i)) * (100% + 16px))
        calc((1 - var(--otf-dealt)) * var(--otf-sc-i) * 10px);
    rotate: calc((1 - var(--otf-dealt)) * (var(--otf-sc-i) - 1) * 3deg);
}

@media (max-width: 980px) {
    .otf-hosts-tools.is-live .otf-public-preview-card {
        translate: 0 calc((1 - var(--otf-dealt)) * min(1, var(--otf-sc-i)) * (-100% - 16px));
        rotate: none;
    }
}

@media (max-width: 640px) {
    .otf-hosts-tools.is-live .otf-public-preview-grid { opacity: 1; translate: none; }

    /* Each card slides over the one before it, so later cards are on top. */
    .otf-hosts-tools.is-live .otf-public-preview-card {
        position: sticky;
        z-index: calc(1 + var(--otf-sc-i));
        top: calc(96px + var(--otf-sc-i) * 14px);
        translate: none;
        box-shadow: 0 -16px 36px -18px rgba(0, 0, 0, 0.75);
    }

    .otf-hosts-tools.is-live .otf-public-preview-card:first-child { box-shadow: none; }
}

/* ── Card grids that build up (For schools, About) ───────────────────────────
   Each card is its own "pass" scene, so on a phone each one builds as it
   arrives; side by side, a row builds left to right. Its icon comes first,
   then the title, then the text. The card's own lines never move. */
.otf-sc-build {
    --otf-sc-lag: var(--otf-sc-c3, 0);
    --otf-sc-b: clamp(0, calc(var(--p, 1) * 1.7 - var(--otf-sc-lag) * 0.2), 1);
}

@media (max-width: 980px) { .otf-sc-build { --otf-sc-lag: var(--otf-sc-c2, 0); } }
@media (max-width: 640px) { .otf-sc-build { --otf-sc-lag: 0; } }

.otf-sc-build.is-live > * {
    --otf-sc-k: clamp(0, calc(var(--otf-sc-b) * 1.6 - var(--otf-sc-n, 0) * 0.3), 1);
    opacity: var(--otf-sc-k);
    translate: 0 calc((1 - var(--otf-sc-k)) * 26px);
}

.otf-sc-build.is-live > :nth-child(2) { --otf-sc-n: 1; }
.otf-sc-build.is-live > :nth-child(n + 3) { --otf-sc-n: 2; }

.otf-sc-build.is-live > span { scale: calc(0.72 + var(--otf-sc-k) * 0.28); }

/* For schools: the pieces a campus is built from, so each icon grows up
   from its base like a block being set down. */
.otf-sc-build.is-blocks.is-live > span {
    scale: 1 var(--otf-sc-k);
    transform-origin: 50% 100%;
    translate: none;
}

/* ── About: the founder photo drifts against the page ────────────────────── */
.otf-about-founder.is-live .otf-about-photo {
    translate: 0 calc((var(--p, 0.5) - 0.5) * 64px);
}

/* ── Explore: each day arrives with its heading ──────────────────────────── */
.otf-explore-day.otf-sc-day .otf-explore-day-title {
    position: relative;
    border-bottom-color: transparent;
    transition: opacity 420ms ease, translate 620ms cubic-bezier(0.22, 1, 0.36, 1);
}

.otf-explore-day.otf-sc-day .otf-explore-day-title::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 1px;
    background: linear-gradient(90deg, var(--otf-public-red), var(--otf-public-line) 38%);
    transform-origin: 0 50%;
    transition: transform 900ms cubic-bezier(0.22, 1, 0.36, 1) 120ms;
}

.otf-explore-day.otf-sc-day .otf-explore-day-title small {
    transition: opacity 420ms ease 220ms;
}

.otf-explore-day.otf-sc-day:not(.is-in) .otf-explore-day-title {
    opacity: 0;
    translate: -18px 0;
}

.otf-explore-day.otf-sc-day:not(.is-in) .otf-explore-day-title::after { transform: scaleX(0); }
.otf-explore-day.otf-sc-day:not(.is-in) .otf-explore-day-title small { opacity: 0; }

/* ── The closing, on every page ──────────────────────────────────────────────
   The red glow rises from the bottom edge as the section arrives, then the
   heading, the line under it and the button follow it up. */
.otf-closing {
    --otf-close: clamp(0, calc(var(--p, 1) * 1.45 - 0.08), 1);
}

.otf-closing.is-live {
    isolation: isolate;
    background: var(--otf-public-canvas);
}

.otf-closing.is-live::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 0;
    background: radial-gradient(70% 90% at 50% 100%, rgba(140, 21, 21, 0.26), transparent 70%);
    opacity: var(--otf-close);
    transform: scale(calc(0.72 + var(--otf-close) * 0.28));
    transform-origin: 50% 100%;
    pointer-events: none;
}

.otf-closing.is-live > :not(.sprout-mascot) {
    --otf-close-k: clamp(0, calc(var(--otf-close) * 1.5 - var(--otf-close-n, 0) * 0.16), 1);
    opacity: var(--otf-close-k);
    translate: 0 calc((1 - var(--otf-close-k)) * 30px);
}

.otf-closing.is-live > h2 { --otf-close-n: 0.6; }
.otf-closing.is-live > h2 + p { --otf-close-n: 1.5; }
.otf-closing.is-live > a { --otf-close-n: 2.4; }
.otf-closing.is-live > .otf-closing-note { --otf-close-n: 3; }

/* ── The story map's trip (app.js `_otfStoryDrawRoute`) ───────────────────────
   Two markers on the live map: a Caltrain seen from beside the line, and
   Sprout walking the route. Their positions are set by the script; these
   only draw them. The train is never turned to the compass: `is-west`
   mirrors it to face the way it goes on screen. */
.otf-map-train {
    /* The marker stands on its bottom centre; the train sits across the line. */
    translate: 0 50%;
    filter: drop-shadow(0 5px 6px rgba(20, 12, 6, 0.42));
}

.otf-map-train-body {
    display: flex;
    gap: 2px;
    padding-top: 5px;
    /* The couplers, seen through the gaps between the cars. */
    background: linear-gradient(#3a434b, #3a434b) 0 16px / 100% 2px no-repeat;
}

.otf-map-train.is-west .otf-map-train-body { scale: -1 1; }

/* A bi-level car, as Caltrain's electric trains are: two decks of windows, a
   door amidships, a red stripe low down, and a bogie at each end. The
   leading end is on the right. */
.otf-map-train-car {
    position: relative;
    width: 46px;
    height: 20px;
    border-radius: 3px;
    background:
        linear-gradient(#a7b0b8, #a7b0b8) 20px 9px / 5px 9px no-repeat,
        repeating-linear-gradient(90deg, #26323e 0 5px, transparent 5px 8px) 5px 3px / 37px 4.5px no-repeat,
        repeating-linear-gradient(90deg, #26323e 0 5px, transparent 5px 8px) 5px 10px / 37px 4px no-repeat,
        linear-gradient(#d0142c, #d0142c) 0 15.5px / 100% 2px no-repeat,
        linear-gradient(180deg, #ffffff, #e4e8eb 60%, #c8cfd5);
    box-shadow: inset 0 0 0 0.6px rgba(20, 28, 36, 0.45);
}

.otf-map-train-car::after {
    content: "";
    position: absolute;
    left: 3px;
    right: 3px;
    bottom: -3px;
    height: 4px;
    background: radial-gradient(circle, #20262c 0 1.7px, transparent 2.1px) 0 0 / 5px 4px repeat-x;
    -webkit-mask: linear-gradient(90deg, #000 0 10px, transparent 10px calc(100% - 10px), #000 calc(100% - 10px));
    mask: linear-gradient(90deg, #000 0 10px, transparent 10px calc(100% - 10px), #000 calc(100% - 10px));
}

/* The cab: a raked nose, its windscreen and a headlight. */
.otf-map-train-car.is-lead {
    border-radius: 3px 14px 5px 3px;
    background:
        radial-gradient(circle at calc(100% - 3.5px) 13px, #fffbe0 0 1.6px, transparent 2.3px),
        linear-gradient(#a7b0b8, #a7b0b8) 16px 9px / 5px 9px no-repeat,
        repeating-linear-gradient(90deg, #26323e 0 5px, transparent 5px 8px) 4px 3px / 29px 4.5px no-repeat,
        repeating-linear-gradient(90deg, #26323e 0 5px, transparent 5px 8px) 4px 10px / 29px 4px no-repeat,
        linear-gradient(#d0142c, #d0142c) 0 15.5px / 100% 2px no-repeat,
        linear-gradient(180deg, #ffffff, #e4e8eb 60%, #c8cfd5);
}

.otf-map-train-car.is-lead::before {
    content: "";
    position: absolute;
    top: 2.5px;
    right: 1.5px;
    width: 9px;
    height: 7px;
    border-radius: 2px 8px 2px 2px;
    background: linear-gradient(160deg, #3d5163, #1d2731);
}

/* The far end, with a tail light. */
.otf-map-train-car.is-tail {
    border-radius: 14px 3px 3px 5px;
    background:
        radial-gradient(circle at 3.5px 13px, #ff5a4f 0 1.5px, transparent 2.2px),
        linear-gradient(#a7b0b8, #a7b0b8) 25px 9px / 5px 9px no-repeat,
        repeating-linear-gradient(90deg, #26323e 0 5px, transparent 5px 8px) 12px 3px / 30px 4.5px no-repeat,
        repeating-linear-gradient(90deg, #26323e 0 5px, transparent 5px 8px) 12px 10px / 30px 4px no-repeat,
        linear-gradient(#d0142c, #d0142c) 0 15.5px / 100% 2px no-repeat,
        linear-gradient(180deg, #ffffff, #e4e8eb 60%, #c8cfd5);
}

/* The pantograph on the middle car's roof. */
.otf-map-train-car:not(.is-lead, .is-tail)::before {
    content: "";
    position: absolute;
    top: -5px;
    left: 50%;
    width: 14px;
    height: 5px;
    translate: -50% 0;
    background:
        linear-gradient(#4a5560, #4a5560) 50% 0 / 100% 1.2px no-repeat,
        linear-gradient(to top right, transparent 42%, #4a5560 42% 62%, transparent 62%) 0 0 / 50% 100% no-repeat,
        linear-gradient(to top left, transparent 42%, #4a5560 42% 62%, transparent 62%) 100% 0 / 50% 100% no-repeat;
}

.otf-map-walker {
    transform-origin: 50% 100%;
    opacity: 0;
    scale: 0.4;
    filter: drop-shadow(0 5px 6px rgba(20, 12, 6, 0.45));
    transition: opacity 180ms ease, scale 420ms cubic-bezier(0.3, 1.45, 0.5, 1);
}

.otf-map-walker.is-in {
    opacity: 1;
    scale: 1;
}

/* At the venue Sprout stands beside its pin, clear of the pin and its name. */
.otf-map-walker.is-beside { translate: 34px 0; }

@media (prefers-reduced-motion: no-preference) {
    /* The train pops onto the map; it rumbles while it moves, each car on its
       own beat, and stands still at the platform. */
    .otf-map-train.is-in .otf-map-train-body {
        animation: otfTrainIn 520ms cubic-bezier(0.3, 1.45, 0.5, 1) both;
    }

    .otf-map-train.is-moving .otf-map-train-car {
        animation: otfTrainRumble 170ms ease-in-out infinite alternate;
    }

    .otf-map-train.is-moving .otf-map-train-car:nth-child(2) { animation-delay: -60ms; }
    .otf-map-train.is-moving .otf-map-train-car:nth-child(3) { animation-delay: -115ms; }

    /* Sprout hops down off the train, and steps aside at the venue. */
    .otf-map-walker.is-in {
        animation: otfWalkerHop 560ms cubic-bezier(0.3, 0.7, 0.3, 1);
    }

    .otf-map-walker.is-beside {
        transition: opacity 180ms ease, scale 420ms cubic-bezier(0.3, 1.45, 0.5, 1), translate 420ms cubic-bezier(0.3, 1.3, 0.5, 1);
    }
}

@keyframes otfTrainIn {
    from { opacity: 0; transform: scale(0.5); }
}

@keyframes otfTrainRumble {
    to { translate: 0 -0.7px; }
}

@keyframes otfWalkerHop {
    0% { translate: 0 0; }
    45% { translate: 0 -18px; }
    100% { translate: 0 0; }
}

/* On a phone's map Sprout's plan bubble covered the pins at the top of the
   "Sprout AI" stage; there it rises into the free band at the bottom,
   above Google's logo and attribution. */
@media (max-width: 640px) {
    .otf-story-sprout {
        top: auto;
        right: 12px;
        bottom: 30px;
    }

    .otf-story-sprout:not(.is-visible) { transform: translateY(8px) scale(0.96); }

    /* Its four places pair up at this size and their names ran together;
       every other stage names them. */
    .otf-story-map[data-active-stage="sprout"] .otf-map-marker-label { opacity: 0; }
}

/* While the train stands at the platform it covers the station's name. */
.otf-map-marker.is-train-in .otf-map-marker-label { opacity: 0; }

/* An arrival lands on a pin: the train's on the station, Sprout's on the venue. */
.otf-map-marker.is-arrived .otf-map-marker-pin { animation: otfPinDrop 520ms cubic-bezier(0.16, 1, 0.3, 1); }

@media (prefers-reduced-motion: reduce) {
    .otf-map-walker { transition: none; }
    .otf-map-marker.is-arrived .otf-map-marker-pin { animation: none; }
}

/* ── Page changes ────────────────────────────────────────────────────────────
   A page drawn inside a cross-fade is already fading in as a whole, so it
   skips its own slide-in for good (`is-swapped`, set by _gotoPublicPage):
   both at once dipped to dark mid-change, and skipping it only while the
   fade ran played it again, from nothing, the moment the fade ended. The
   nav is the same on both pages: it changes at once instead of fading. */
.otf-page-enter.is-swapped { animation: none; }

::view-transition-group(otf-public-nav) { animation: none; }
::view-transition-old(otf-public-nav) { display: none; }
::view-transition-new(otf-public-nav) { animation: none; }

@media (prefers-reduced-motion: reduce) {
    .otf-explore-day.otf-sc-day .otf-explore-day-title,
    .otf-explore-day.otf-sc-day .otf-explore-day-title::after,
    .otf-explore-day.otf-sc-day .otf-explore-day-title small { transition: none; }
}
