intro updates

This commit is contained in:
Dorian
2026-05-10 15:33:31 +01:00
parent 97005f1959
commit efec720012
2 changed files with 31 additions and 18 deletions

View File

@@ -1880,9 +1880,10 @@ async function playIntro() {
// duration + base delay. JS rebuilds spans per play (so language
// switches still split correctly) and measures entry from live word
// count. Each word carries the introWord blur resolve.
const STAGGER = 0.22
const PER_WORD = 1.05
const BASE = 0.05
const SPEED = 1.25
const STAGGER = 0.22 * SPEED
const PER_WORD = 1.05 * SPEED
const BASE = 0.05 * SPEED
const stages = []
for (let n = 1; n <= 3; n++) {
const stageEl = overlay.querySelector('.stage-' + n)
@@ -1926,10 +1927,10 @@ async function playIntro() {
const entryMs = Math.round((BASE + (Math.max(0, words.length - 1)) * STAGGER + PER_WORD) * 1000)
// Bigger hold on stage 1 so the opening line lingers; stage 3 (the
// longest sentence) holds the longest.
const hold = n === 3 ? 1500 : (n === 1 ? 1200 : 900)
const hold = n === 3 ? 1875 : (n === 1 ? 1500 : 1125)
stages.push({ sel: '.stage-' + n, enter: entryMs, hold })
}
const exit = 800 // matches the introOut (smoke out) duration
const exit = 1000 // default matches the slowed introOut smoke-out duration
for (let i = 0; i < stages.length; i++) {
const s = stages[i]
const el = overlay.querySelector(s.sel)
@@ -1945,7 +1946,7 @@ async function playIntro() {
// instantly and the smoke-out on the parent would dissolve into
// an already-empty wrapper.
el.classList.add('leaving')
await sleep(exit)
await sleep(i === 0 ? 1500 : exit)
el.classList.remove('leaving')
el.classList.remove('active')
}
@@ -1955,7 +1956,7 @@ async function playIntro() {
// overlay fade-out as one motion.
overlay.classList.add('done')
document.body.classList.add('intro-done')
await sleep(900)
await sleep(1125)
overlay.classList.add('gone')
}
function maybePlayIntro({ skip = false } = {}) {

View File

@@ -1191,7 +1191,7 @@ input[type=range]::-moz-range-thumb {
align-items: center;
justify-content: center;
pointer-events: none;
transition: opacity 1.1s cubic-bezier(0.22, 0.61, 0.36, 1);
transition: opacity 1.375s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.intro-overlay.done { opacity: 0; }
.intro-overlay.gone { display: none; }
@@ -1262,13 +1262,22 @@ input[type=range]::-moz-range-thumb {
opacity: 1;
transform: scale(1);
transition:
opacity 0.25s ease 0.05s,
transform 0.55s cubic-bezier(0.18, 0.89, 0.32, 1.18) 0.05s;
opacity 0.45s ease 0.06s,
transform 0.75s cubic-bezier(0.18, 0.89, 0.32, 1.18) 0.06s;
}
.intro-stage.stage-1.active .stage-icon {
animation: introIconIn 1.05s 0.18s cubic-bezier(0.18, 0.89, 0.32, 1.18) both;
transition: none;
}
@keyframes introIconIn {
0% { opacity: 0; transform: translateY(10px) scale(0.72); }
70% { opacity: 1; transform: translateY(0) scale(1.04); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
.intro-stage.active .stage-icon .glyph path,
.intro-stage.active .stage-icon .glyph line,
.intro-stage.active .stage-icon .glyph circle {
animation: introDraw 0.9s 0.1s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
animation: introDraw 1.125s 0.125s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
@keyframes introDraw {
to { stroke-dashoffset: 0; }
@@ -1282,7 +1291,7 @@ input[type=range]::-moz-range-thumb {
transform-origin: center;
}
.intro-stage.active .stage-icon .filled-icon path {
animation: introGlyphPop 0.65s 0.18s cubic-bezier(0.18, 0.89, 0.32, 1.18) forwards;
animation: introGlyphPop 0.9s 0.225s cubic-bezier(0.18, 0.89, 0.32, 1.18) forwards;
}
@keyframes introGlyphPop {
to { opacity: 1; transform: scale(1); }
@@ -1314,11 +1323,11 @@ input[type=range]::-moz-range-thumb {
/* Stage 1 — the shield border appears first; the filled check ticks in
near the end of the sentence, after "prepare" has landed. */
.intro-stage.stage-1.active .i-shield-outline {
animation: introDraw 0.78s 0.45s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
animation: introDraw 1.05s 0.56s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
opacity: 1;
}
.intro-stage.stage-1.active .i-secure-tick {
animation: tickFillIn 0.48s 2.6s cubic-bezier(0.18, 0.89, 0.32, 1.18) forwards;
animation: tickFillIn 0.72s 3.25s cubic-bezier(0.18, 0.89, 0.32, 1.18) forwards;
transform-box: fill-box;
transform-origin: center;
}
@@ -1358,7 +1367,7 @@ input[type=range]::-moz-range-thumb {
}
.intro-stage.stage-3.active .icon-clock .i-hand {
animation: introDraw 0.35s 0.70s cubic-bezier(0.22, 0.61, 0.36, 1) forwards,
clockSweep 2.2s 1.35s linear forwards;
clockSweep 2.75s 2.35s linear forwards;
}
@keyframes clockSweep {
from { transform: rotate(0deg); }
@@ -1433,10 +1442,10 @@ input[type=range]::-moz-range-thumb {
.intro-stage .intro-text .mobile-break { display: none; }
}
.intro-stage.active .intro-text > .word {
animation: introWord 1.05s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
animation: introWord 1.3125s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
.intro-stage.stage-1.active .intro-text > .word {
animation-delay: calc(var(--word-delay, 0s) + 0.9s) !important;
animation-delay: calc(var(--word-delay, 0s) + 1.125s) !important;
}
@keyframes introWord {
0% { opacity: 0; transform: translateY(14px); }
@@ -1447,7 +1456,10 @@ input[type=range]::-moz-range-thumb {
Same effect for every stage so each sentence dissolves into haze
before the next one resolves. */
.intro-stage.leaving .intro-text {
animation: introOut 0.8s cubic-bezier(0.4, 0, 1, 1) forwards;
animation: introOut 1s cubic-bezier(0.4, 0, 1, 1) forwards;
}
.intro-stage.stage-1.leaving .intro-text {
animation-duration: 1.5s;
}
@keyframes introOut {
0% { opacity: 1; filter: blur(0); transform: translateY(0); }