From efec720012902b599b7dfaea998cbe082b07d4c9 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 10 May 2026 15:33:31 +0100 Subject: [PATCH] intro updates --- src/App.vue | 15 ++++++++------- src/styles.css | 34 +++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/App.vue b/src/App.vue index 3a10453..2c54966 100644 --- a/src/App.vue +++ b/src/App.vue @@ -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 } = {}) { diff --git a/src/styles.css b/src/styles.css index f32a1f5..239ab5f 100644 --- a/src/styles.css +++ b/src/styles.css @@ -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); }