intro sentences change

This commit is contained in:
Dorian
2026-05-10 16:06:12 +01:00
parent 2cbd0b03fe
commit 390b795560
2 changed files with 85 additions and 98 deletions

View File

@@ -228,27 +228,20 @@
loads in the same tab via sessionStorage. -->
<div class="intro-overlay" id="intro-overlay" aria-hidden="true">
<div class="intro-stage stage-1" data-stage="1">
<svg class="stage-icon icon-shield" viewBox="0 0 96 96" aria-hidden="true">
<circle class="badge" cx="48" cy="48" r="42"/>
<path class="glyph-stroke i-shield-outline" d="M48 20 C42.8 23.4 36 25.8 29.2 25.6 C28.5 48.2 35.4 67.2 48 75.6 C60.6 67.2 67.5 48.2 66.8 25.6 C60 25.8 53.2 23.4 48 20 Z"/>
<path class="glyph-fill i-secure-tick" d="M44.8 56.1 L38.9 50.1 L42 47 L45.1 50.2 L55.6 39.5 L58.8 42.6 Z"/>
</svg>
<span class="intro-text" data-i18n="intro_l1">Wisdom is to prepare</span>
</div>
<div class="intro-stage stage-2" data-stage="2">
<svg class="stage-icon icon-storm" viewBox="0 0 96 96" aria-hidden="true">
<circle class="badge" cx="48" cy="48" r="42"/>
<g class="glyph filled-icon" transform="translate(19.75 21.15) scale(0.46)">
<path class="i-cloud" d="M44.2,71.76 c2.03,0,3.68,1.65,3.68,3.68c0,2.03-1.65,3.68-3.68,3.68H23.38l-0.46-0.04c-2.67-0.34-5.09-0.97-7.29-1.88 c-2.25-0.93-4.26-2.14-6.04-3.63H9.58c-1.68-1.4-3.15-2.99-4.4-4.72C1.84,64.25,0.04,58.63,0,53.03 c-0.04-5.66,1.72-11.29,5.52-15.85c1.23-1.48,2.68-2.84,4.34-4.04c1.93-1.4,4.14-2.58,6.64-3.55c1.72-0.67,3.56-1.23,5.5-1.68 c2.2-8.74,6.89-15.47,12.92-20.14c5.64-4.37,12.43-6.92,19.42-7.59c6.96-0.67,14.12,0.51,20.55,3.6 c7.37,3.54,13.43,9.56,17.11,16.87c1.6-0.25,3.2-0.38,4.79-0.36c6.72,0.05,13.2,2.45,18.3,7.95c5.31,5.72,7.88,14.14,7.79,21.82 c-0.07,6.31-1.77,12.59-5.25,17.22c-2.27,3.02-5.18,5.47-8.67,7.42c-3.36,1.88-7.28,3.31-11.68,4.33c-1.98,0.45-3.95-0.78-4.4-2.76 c-0.45-1.98,0.78-3.95,2.76-4.4c3.71-0.86,6.97-2.04,9.72-3.58c2.63-1.47,4.78-3.26,6.39-5.41c2.5-3.33,3.73-8.04,3.78-12.87 c0.06-5.07-1.18-10.16-3.59-13.86c-0.69-1.07-1.44-2.03-2.25-2.89c-3.61-3.89-8.19-5.59-12.95-5.62 c-3.46-0.02-7.02,0.81-10.41,2.31c-0.75,0.37-1.51,0.78-2.25,1.21c-2.25,1.32-4.48,2.93-6.74,4.78l-4.84-5.54 c1.67-1.55,3.48-2.96,5.4-4.21c1.53-1,3.13-1.88,4.77-2.65c0.66-0.33,1.33-0.64,2-0.93c-3.19-5.65-7.78-9.7-12.98-12.2 c-5.2-2.49-11.02-3.45-16.69-2.9c-5.63,0.54-11.1,2.59-15.62,6.1c-5.23,4.06-9.2,10.11-10.73,18.14l-0.48,2.51l-2.5,0.44 c-2.45,0.43-4.64,1.02-6.56,1.77c-1.86,0.72-3.52,1.61-4.97,2.66c-1.16,0.84-2.16,1.78-3.01,2.8c-2.63,3.15-3.85,7.1-3.82,11.1 c0.03,4.06,1.35,8.16,3.79,11.53c0.91,1.25,1.96,2.4,3.16,3.4l-0.01,0.01c1.2,1,2.58,1.83,4.13,2.47c1.53,0.63,3.22,1.08,5.09,1.34 H44.2L44.2,71.76z"/>
</g>
<path class="glyph-fill i-lightning-flash" d="M47.2 49.5 L57.6 49.5 L54 56.5 L60.2 56.6 L45.2 74.9 L48.1 62.9 L43.2 62.9 Z"/>
<g class="sun-glyph" aria-hidden="true">
<circle class="sun-core" cx="48" cy="48" r="13"/>
<path class="sun-rays" d="M48 24 V31 M48 65 V72 M24 48 H31 M65 48 H72 M31 31 L36 36 M60 60 L65 65 M65 31 L60 36 M36 60 L31 65"/>
</g>
</svg>
<span class="intro-text" data-i18n="intro_l2">Even if crisis is not here yet</span>
<span class="intro-text" data-i18n="intro_l1">For crisis bows to him who plans with care</span>
</div>
<div class="intro-stage stage-3" data-stage="3">
<!-- Clock face centred in the badge with no crown above it.
The minute hand sweeps 720° (two full revolutions) during
the second half of the sentence, ending back at the top. -->
<div class="intro-stage stage-2" data-stage="2">
<svg class="stage-icon icon-clock" viewBox="0 0 96 96" aria-hidden="true">
<circle class="badge" cx="48" cy="48" r="42"/>
<g class="glyph">
@@ -265,7 +258,7 @@
<circle class="i-pin" cx="48" cy="48" r="2.2"/>
</g>
</svg>
<span class="intro-text" data-i18n="intro_l3">Figure out your Plan B in less than 2 Minutes.</span>
<span class="intro-text" data-i18n="intro_l2">And fortune favors those who well prepare</span>
</div>
</div>
@@ -643,9 +636,9 @@ const T = {
hero_eyebrow: "Crisis Preparedness Advisor",
hero_sub: "Crisis Preparedness",
hero_cta: "Begin",
intro_l1: "Wisdom is to prepare",
intro_l2: "Even if Crisis\nis not here yet",
intro_l3: "Figure out your Plan B in less than 2 Minutes.",
intro_l1: "For crisis bows to him who plans with care",
intro_l2: "And fortune favors those who well prepare",
intro_l3: "And fortune favors those who well prepare.",
stat_scenarios: "Scenarios",
stat_questions: "Questions",
stat_free: "Free Forever",
@@ -763,9 +756,9 @@ const T = {
hero_eyebrow: "⚡ Krisenvorsorge-Berater",
hero_sub: "Krisenvorsorge",
hero_cta: "Beginnen",
intro_l1: "Weise ist, wer vorsorgt.",
intro_l2: "Auch wenn Krise\nnoch nicht da ist.",
intro_l3: "Finden Sie Ihren Plan B in weniger als 2 Minuten.",
intro_l1: "Die Krise beugt sich dem, der wohl bedacht",
intro_l2: "Und Glück gehört dem, der sich stark gemacht",
intro_l3: "Und Glück gehört dem, der sich stark gemacht",
stat_scenarios: "Szenarien",
stat_questions: "Fragen",
stat_free: "Kostenlos",
@@ -1885,7 +1878,7 @@ async function playIntro() {
const PER_WORD = 1.2 * SPEED
const BASE = 0.05 * SPEED
const stages = []
for (let n = 1; n <= 3; n++) {
for (let n = 1; n <= 2; n++) {
const stageEl = overlay.querySelector('.stage-' + n)
const textEl = stageEl ? stageEl.querySelector('.intro-text') : null
let words = []
@@ -1893,47 +1886,33 @@ async function playIntro() {
const text = textEl.textContent.trim()
textEl.innerHTML = ''
words = text.split(/\s+/)
// Per-stage emphasis. Stage 1 bolds "prepare" / "vorsorgt"; stage
// 2 bolds "crisis" / "krise"; stage 3 bolds the final two words
// ("2 Minutes." / "2 Minuten.") — both EN and DE land at the
// same word index. Word matching is case-insensitive and trailing
// punctuation is stripped before testing.
const emphasisRe = {
1: /^(prepare|vorsorgt)$/i,
2: /^(crisis|krise)$/i,
}[n] || null
const stripPunct = w => w.replace(/[.,;:!?]+$/, '')
// Stage 3 — bold "less than 2 Minutes." (last four words).
const boldFromIdx = (n === 3) ? 6 : Infinity
const boldFromIdx = currentLang === 'de' && n === 1
? Math.max(0, words.length - 2)
: Math.max(0, words.length - 3)
words.forEach((word, i) => {
const span = document.createElement('span')
const isBold = i >= boldFromIdx || (emphasisRe && emphasisRe.test(stripPunct(word)))
const isBold = i >= boldFromIdx
span.className = 'word' + (isBold ? ' bold' : '')
const delay = (BASE + i * STAGGER) + 's'
span.style.animationDelay = delay
span.style.setProperty('--word-delay', delay)
span.textContent = word
textEl.appendChild(span)
// Stage 2 — keep "Even if Crisis" / "Auch wenn Krise" together
// on the first line, then break before the second clause.
if (n === 2 && /^(crisis|krise)$/i.test(stripPunct(word))) {
textEl.appendChild(document.createElement('br'))
.className = 'intro-line-break'
}
})
}
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 ? 1875 : (n === 1 ? 1500 : 1125)
const hold = n === 1 ? 2600 : 100
stages.push({ sel: '.stage-' + n, enter: entryMs, hold })
}
const exit = 1000 // default matches the slowed introOut smoke-out duration
let activeLead = 0
for (let i = 0; i < stages.length; i++) {
const s = stages[i]
const el = overlay.querySelector(s.sel)
el.classList.add('active')
await sleep(s.enter + s.hold)
await sleep(Math.max(0, s.enter + s.hold - activeLead))
activeLead = 0
// Skip the smoke-out on the last stage — the overlay's own fade
// dissolves the final line, so we don't get two competing outros
// (one on the text, one on the FAFAFA panel).
@@ -1943,8 +1922,12 @@ async function playIntro() {
// Removing .active here would drop the words back to opacity:0
// instantly and the smoke-out on the parent would dissolve into
// an already-empty wrapper.
const next = overlay.querySelector(stages[i + 1].sel)
if (next) next.classList.add('active')
el.classList.add('leaving')
await sleep(i === 0 ? 1500 : exit)
const transitionWait = i === 0 ? 2800 : exit
activeLead = transitionWait
await sleep(transitionWait)
el.classList.remove('leaving')
el.classList.remove('active')
}
@@ -1954,7 +1937,7 @@ async function playIntro() {
// overlay fade-out as one motion.
overlay.classList.add('done')
document.body.classList.add('intro-done')
await sleep(1125)
await sleep(350)
overlay.classList.add('gone')
}
function maybePlayIntro({ skip = false } = {}) {

View File

@@ -1310,9 +1310,6 @@ input[type=range]::-moz-range-thumb {
opacity: 0;
filter: drop-shadow(0 0 5px rgba(255, 248, 220, 0.55));
}
.stage-icon .i-shield-outline {
stroke-width: 3.8;
}
.stage-icon .glyph-fill {
fill: #fff8dc;
stroke: none;
@@ -1322,27 +1319,8 @@ input[type=range]::-moz-range-thumb {
drop-shadow(0 0 14px rgba(244, 236, 216, 0.7));
}
/* 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 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.72s 3.25s cubic-bezier(0.18, 0.89, 0.32, 1.18) forwards;
transform-box: fill-box;
transform-origin: center;
}
@keyframes tickFillIn {
0% { opacity: 0; transform: scale(0.55) rotate(-8deg); }
65% { opacity: 1; transform: scale(1.13) rotate(0deg); }
100% { opacity: 1; transform: scale(1) rotate(0deg); }
}
/* Stage 2 — the supplied cloud icon appears first; the bolt flashes like
lightning once the crisis sentence is readable. */
.intro-stage.stage-2.active .i-lightning-flash {
animation: lightningStrike 1.08s 0.45s steps(1, end) infinite;
.intro-stage.stage-1.active .i-lightning-flash {
animation: lightningStrike 1.08s 0.45s steps(1, end) 4;
}
@keyframes lightningStrike {
0% { opacity: 0; }
@@ -1356,9 +1334,58 @@ input[type=range]::-moz-range-thumb {
72% { opacity: 0; }
100% { opacity: 0; }
}
.sun-glyph {
color: #fff8dc;
fill: none;
opacity: 0;
transform: scale(0.72);
transform-box: fill-box;
transform-origin: center;
filter:
drop-shadow(0 0 5px rgba(255, 248, 220, 0.75))
drop-shadow(0 0 16px rgba(244, 236, 216, 0.45));
}
.sun-glyph .sun-core {
fill: currentColor;
stroke: none;
}
.sun-glyph .sun-rays {
stroke: currentColor;
stroke-width: 3.2;
stroke-linecap: round;
stroke-linejoin: round;
}
.intro-stage.stage-1.active .sun-glyph {
animation: sunRiseIn 1.25s 3.85s cubic-bezier(0.18, 0.89, 0.32, 1.18) forwards;
}
.intro-stage.stage-1.leaving .sun-glyph {
animation: sunToClockOut 2.35s cubic-bezier(0.4, 0, 1, 1) forwards;
}
.intro-stage.stage-2.active .stage-icon {
transition:
opacity 2.05s ease 0.35s,
transform 2.25s cubic-bezier(0.18, 0.89, 0.32, 1.08) 0.35s;
}
.intro-stage.stage-1.active .i-cloud {
animation:
introGlyphPop 0.9s 0.225s cubic-bezier(0.18, 0.89, 0.32, 1.18) forwards,
cloudZoomOut 0.95s 2.9s cubic-bezier(0.4, 0, 1, 1) forwards !important;
}
@keyframes sunRiseIn {
0% { opacity: 0; transform: scale(0.72); }
70% { opacity: 1; transform: scale(1.06); }
100% { opacity: 1; transform: scale(1); }
}
@keyframes sunToClockOut {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.62); }
}
@keyframes cloudZoomOut {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.62); }
}
/* Stage 3face + ticks draw, then the minute hand takes the sentence
timing to sweep two full rotations and land back at 12. */
/* Stage 2clock hand sweeps smoothly through the whole second sentence. */
.icon-clock .i-crown { animation-delay: 0.10s !important; }
.icon-clock .i-face { animation-delay: 0.20s !important; animation-duration: 0.7s !important; }
.icon-clock .i-tick { animation-delay: 0.70s !important; animation-duration: 0.3s !important; }
@@ -1367,9 +1394,9 @@ input[type=range]::-moz-range-thumb {
animation-delay: 0.70s !important;
animation-duration: 0.35s !important;
}
.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 4.7s 0.7s linear forwards;
.intro-stage.stage-2.active .icon-clock .i-hand {
animation: introDraw 0.35s 0.125s cubic-bezier(0.22, 0.61, 0.36, 1) forwards,
clockSweep 4.9s 0.125s linear forwards;
}
@keyframes clockSweep {
from { transform: rotate(0deg); }
@@ -1420,33 +1447,10 @@ input[type=range]::-moz-range-thumb {
was the lag source. */
will-change: opacity, transform;
}
/* Per-stage emphasis — JS adds .bold to specific words per intro line
("prepare" / "vorsorgt", "crisis" / "krise", and the closing clause
on stage 3). */
/* Optional per-stage emphasis hook. */
.intro-stage .intro-text > .word.bold {
font-weight: 700;
}
/* Stage 2 mobile line break — JS inserts <br class="mobile-break">
after the leading conjunction ("if" / "wenn") so the second clause
("crisis is not here yet" / "noch keine Krise da ist.") wraps onto
its own line on small viewports. Hidden on desktop via display:none
so the sentence renders on a single line. */
.intro-stage br.mobile-break { display: inline; }
@media (min-width: 768px) {
.intro-stage br.mobile-break { display: none; }
}
.intro-stage br.intro-line-break { display: block; }
@media (min-width: 768px) {
.intro-stage br.intro-line-break { display: none; }
}
/* Mobile-only line break — JS inserts a <br class="mobile-break">
after "if" / "wenn" on stage 2. Display:none on wider viewports so
the line stays one row on desktop / tablet. */
.intro-stage .intro-text .mobile-break { display: inline; }
@media (min-width: 768px) {
.intro-stage .intro-text .mobile-break { display: none; }
}
.intro-stage.active .intro-text > .word {
animation: introWord 1.5s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}