diff --git a/src/App.vue b/src/App.vue
index b076604..c850e96 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -228,27 +228,20 @@
loads in the same tab via sessionStorage. -->
-
- Wisdom is to prepare
-
-
- Even if crisis is not here yet
+ For crisis bows to him who plans with care
-
-
+
- Figure out your Plan B in less than 2 Minutes.
+ And fortune favors those who well prepare
@@ -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 } = {}) {
diff --git a/src/styles.css b/src/styles.css
index 4c73613..74515ff 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -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 3 — face + ticks draw, then the minute hand takes the sentence
- timing to sweep two full rotations and land back at 12. */
+/* Stage 2 — clock 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
- 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
- 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;
}