intro sentences change
This commit is contained in:
73
src/App.vue
73
src/App.vue
@@ -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 } = {}) {
|
||||
|
||||
110
src/styles.css
110
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 <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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user