intro updates
This commit is contained in:
69
src/App.vue
69
src/App.vue
@@ -228,13 +228,44 @@
|
||||
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"/>
|
||||
</svg>
|
||||
<span class="intro-text" data-i18n="intro_l2">Even if crisis is not here yet</span>
|
||||
</div>
|
||||
<div class="intro-stage stage-3" data-stage="3">
|
||||
<span class="intro-text" data-i18n="intro_l3">Figure out your Plan B in less than two minutes.</span>
|
||||
<!-- 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. -->
|
||||
<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">
|
||||
<!-- Face circle, centred in the badge. -->
|
||||
<circle class="i-face" cx="48" cy="48" r="22"/>
|
||||
<!-- Cardinal hour ticks. -->
|
||||
<line class="i-tick" x1="48" y1="30" x2="48" y2="34"/>
|
||||
<line class="i-tick" x1="66" y1="48" x2="62" y2="48"/>
|
||||
<line class="i-tick" x1="48" y1="66" x2="48" y2="62"/>
|
||||
<line class="i-tick" x1="30" y1="48" x2="34" y2="48"/>
|
||||
<!-- Minute hand pointing to 12 — rotates 720° over the line. -->
|
||||
<line class="i-hand" x1="48" y1="48" x2="48" y2="32"/>
|
||||
<!-- Center pin. -->
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -613,8 +644,8 @@ const T = {
|
||||
hero_sub: "Preparedness, refined.",
|
||||
hero_cta: "Begin",
|
||||
intro_l1: "Wisdom is to prepare",
|
||||
intro_l2: "Even if crisis is not here yet",
|
||||
intro_l3: "Figure out your Plan B in less than two minutes.",
|
||||
intro_l2: "Even if\ncrisis is not here yet",
|
||||
intro_l3: "Figure out your Plan B in less than 2 Minutes.",
|
||||
stat_scenarios: "Scenarios",
|
||||
stat_questions: "Questions",
|
||||
stat_free: "Free Forever",
|
||||
@@ -733,8 +764,8 @@ const T = {
|
||||
hero_sub: "Vorsorge, verfeinert.",
|
||||
hero_cta: "Beginnen",
|
||||
intro_l1: "Weise ist, wer vorsorgt.",
|
||||
intro_l2: "Auch wenn noch keine Krise da ist.",
|
||||
intro_l3: "Finden Sie Ihren Plan B in weniger als zwei Minuten.",
|
||||
intro_l2: "Auch wenn\nnoch keine Krise da ist.",
|
||||
intro_l3: "Finden Sie Ihren Plan B in weniger als 2 Minuten.",
|
||||
stat_scenarios: "Szenarien",
|
||||
stat_questions: "Fragen",
|
||||
stat_free: "Kostenlos",
|
||||
@@ -1861,17 +1892,39 @@ 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
|
||||
words.forEach((word, i) => {
|
||||
const span = document.createElement('span')
|
||||
span.className = 'word'
|
||||
const isBold = i >= boldFromIdx || (emphasisRe && emphasisRe.test(stripPunct(word)))
|
||||
span.className = 'word' + (isBold ? ' bold' : '')
|
||||
span.style.animationDelay = (BASE + i * STAGGER) + 's'
|
||||
span.textContent = word
|
||||
textEl.appendChild(span)
|
||||
// Stage 2 — insert a mobile-only line break after the leading
|
||||
// conjunction so "crisis is not here yet" / "noch keine Krise
|
||||
// da ist." sits on its own row on small viewports. Hidden on
|
||||
// desktop via CSS.
|
||||
if (n === 2 && /^(if|wenn)$/i.test(stripPunct(word))) {
|
||||
textEl.appendChild(document.createElement('br'))
|
||||
.className = 'mobile-break'
|
||||
}
|
||||
})
|
||||
}
|
||||
const entryMs = Math.round((BASE + (Math.max(0, words.length - 1)) * STAGGER + PER_WORD) * 1000)
|
||||
// Stage 3 holds longest because it's the longest line.
|
||||
const hold = n === 3 ? 1500 : 700
|
||||
// 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)
|
||||
stages.push({ sel: '.stage-' + n, enter: entryMs, hold })
|
||||
}
|
||||
const exit = 800 // matches the introOut (smoke out) duration
|
||||
|
||||
298
src/styles.css
298
src/styles.css
@@ -90,7 +90,11 @@ body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); f
|
||||
.lang-btn.active { background: var(--red); color: #FFFFFF; }
|
||||
|
||||
/* ── APP ── */
|
||||
.app { padding-top: 0; height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.app { padding-top: 0; height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; position: relative; isolation: isolate; }
|
||||
.app > :not(.page-bg-pattern) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ── HERO ── */
|
||||
.hero {
|
||||
@@ -117,21 +121,13 @@ body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); f
|
||||
.page-bg-pattern {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
/* z-index:-1 keeps the pattern below normal-flow content (hero / quiz
|
||||
/ results) without forcing those sections into their own stacking
|
||||
context. That matters because creating a context on .results-section
|
||||
would trap the capture-modal (a position:fixed descendant) inside
|
||||
it, and its z-index:200 wouldn't escape that context to sit above
|
||||
the body-level site-header. Body bg is the canvas; the pattern
|
||||
paints above the canvas and below positioned content. */
|
||||
z-index: -1;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
background: #FAFAFA;
|
||||
/* Parent is always visible — the intro overlay covers it during the
|
||||
intro, and the per-row animations handle the staggered reveal once
|
||||
intro-done lands. Nothing to fade on the wrapper. */
|
||||
/* Parent is always visible. The intro overlay is translucent, so the
|
||||
animated emboss pattern is already present behind the first line. */
|
||||
opacity: 1;
|
||||
}
|
||||
.page-bg-pattern .bg-tilt {
|
||||
@@ -178,28 +174,59 @@ body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); f
|
||||
sits inside the rotated .bg-tilt, so the slide is slightly off-axis
|
||||
in screen space (matches the diagonal tilt). */
|
||||
.page-bg-pattern .bg-row {
|
||||
--row-delay: 0s;
|
||||
opacity: 0;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
.page-bg-pattern .bg-row:nth-child(odd) { transform: translateX(-160px); }
|
||||
.page-bg-pattern .bg-row:nth-child(even) { transform: translateX( 160px); }
|
||||
.page-bg-pattern .bg-row:nth-child(odd) {
|
||||
transform: translateX(-160px);
|
||||
animation:
|
||||
bgRowSlideLeft 1.1s var(--row-delay) cubic-bezier(0.22, 0.61, 0.36, 1) forwards,
|
||||
bgRowDriftLeft 14s calc(var(--row-delay) + 2.1s) ease-in-out infinite;
|
||||
}
|
||||
.page-bg-pattern .bg-row:nth-child(even) {
|
||||
transform: translateX(160px);
|
||||
animation:
|
||||
bgRowSlideRight 1.1s var(--row-delay) cubic-bezier(0.22, 0.61, 0.36, 1) forwards,
|
||||
bgRowDriftRight 14s calc(var(--row-delay) + 2.1s) ease-in-out infinite;
|
||||
}
|
||||
body:not(.intro-done) .page-bg-pattern .bg-row {
|
||||
opacity: 1;
|
||||
}
|
||||
body:not(.intro-done) .page-bg-pattern .bg-row:nth-child(odd) {
|
||||
animation: bgRowDriftLeft 14s ease-in-out infinite;
|
||||
}
|
||||
body:not(.intro-done) .page-bg-pattern .bg-row:nth-child(even) {
|
||||
animation: bgRowDriftRight 14s ease-in-out infinite;
|
||||
}
|
||||
body:not(.intro-done) .page-bg-pattern .bg-row {
|
||||
text-shadow:
|
||||
-1px -1px 0 rgba(0, 0, 0, 0.075),
|
||||
-2px -2px 1px rgba(0, 0, 0, 0.035),
|
||||
1px 1px 0 rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(odd) {
|
||||
animation: bgRowSlideLeft 1.1s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
|
||||
/* Simple slide-in per row, alternating direction, followed by a perpetual
|
||||
alternating drift. This starts immediately so the pattern is visible
|
||||
underneath the intro overlay from the first sentence onward. */
|
||||
@keyframes bgRowDriftLeft {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
50% { transform: translateX(-60px); }
|
||||
}
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(even) {
|
||||
animation: bgRowSlideRight 1.1s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
|
||||
@keyframes bgRowDriftRight {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
50% { transform: translateX(60px); }
|
||||
}
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(1) { animation-delay: 0.00s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(2) { animation-delay: 0.10s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(3) { animation-delay: 0.20s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(4) { animation-delay: 0.30s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(5) { animation-delay: 0.40s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(6) { animation-delay: 0.50s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(7) { animation-delay: 0.60s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(8) { animation-delay: 0.70s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(9) { animation-delay: 0.80s; }
|
||||
body.intro-done .page-bg-pattern .bg-row:nth-child(10) { animation-delay: 0.90s; }
|
||||
.page-bg-pattern .bg-row:nth-child(1) { --row-delay: 0.00s; }
|
||||
.page-bg-pattern .bg-row:nth-child(2) { --row-delay: 0.10s; }
|
||||
.page-bg-pattern .bg-row:nth-child(3) { --row-delay: 0.20s; }
|
||||
.page-bg-pattern .bg-row:nth-child(4) { --row-delay: 0.30s; }
|
||||
.page-bg-pattern .bg-row:nth-child(5) { --row-delay: 0.40s; }
|
||||
.page-bg-pattern .bg-row:nth-child(6) { --row-delay: 0.50s; }
|
||||
.page-bg-pattern .bg-row:nth-child(7) { --row-delay: 0.60s; }
|
||||
.page-bg-pattern .bg-row:nth-child(8) { --row-delay: 0.70s; }
|
||||
.page-bg-pattern .bg-row:nth-child(9) { --row-delay: 0.80s; }
|
||||
.page-bg-pattern .bg-row:nth-child(10) { --row-delay: 0.90s; }
|
||||
@keyframes bgRowSlideLeft {
|
||||
from { opacity: 0; transform: translateX(-160px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
@@ -211,10 +238,7 @@ body.intro-done .page-bg-pattern .bg-row:nth-child(10) { animation-delay: 0.90s;
|
||||
|
||||
/* (Per-page bg override removed — every page now uses #FAFAFA.) */
|
||||
|
||||
/* No section-level stacking context needed — the pattern lives at
|
||||
z-index:-1 so normal-flow sections paint above it automatically.
|
||||
Avoiding a stacking context on .results-section also lets the
|
||||
capture-modal (z-index:200) reach above the site-header. */
|
||||
/* Hero copy sits above the fixed pattern layer. */
|
||||
.hero-eyebrow {
|
||||
position: relative; z-index: 1;
|
||||
font-family: var(--font-body);
|
||||
@@ -345,7 +369,7 @@ html[lang="en"] .hero h1 { font-size: calc((100vw - 32px) / 5.2); }
|
||||
max-width: 520px;
|
||||
margin: 0 auto 40px;
|
||||
line-height: 1.6;
|
||||
animation: fadeUp 1s 1.3s ease both;
|
||||
animation: fadeUp 1s 2.0s ease both;
|
||||
}
|
||||
.cta-btn {
|
||||
display: inline-flex;
|
||||
@@ -360,7 +384,7 @@ html[lang="en"] .hero h1 { font-size: calc((100vw - 32px) / 5.2); }
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease, var(--trans);
|
||||
animation: fadeUp 1s 1.45s ease both;
|
||||
animation: fadeUp 1s 2.45s ease both;
|
||||
/* Lift above .hero::after radial fade so the button isn't washed out */
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -782,14 +806,26 @@ input[type=range]::-moz-range-thumb {
|
||||
to dark for legibility on the light background. */
|
||||
.rec-cards { animation: fadeUp 0.4s 0.15s ease both; }
|
||||
.rec-card {
|
||||
background: #F0F0F0;
|
||||
/* Lighter card body — was #F0F0F0 (read as grey on the page); now
|
||||
#FAFAFA so the product info sits on a near-white surface. The
|
||||
header above takes the light sage tone as a visual section break. */
|
||||
background: #FAFAFA;
|
||||
border: 1px solid rgba(0,0,0,0.06);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.7);
|
||||
}
|
||||
.rec-header { padding: 14px 16px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid rgba(0,0,0,0.06); }
|
||||
/* Header strip wears the same light sage as the .rp-hint banner so
|
||||
the rec-card sections read as a coordinated family. */
|
||||
.rec-header {
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: #E5F0E0;
|
||||
border-bottom: 1px solid rgba(90,154,120,0.22);
|
||||
}
|
||||
/* Green-paint icon chip — matches the painted CTA buttons. ::before
|
||||
carries the dark-green fill with the gloss filter; the icon SVG inside
|
||||
inherits the warm-cream stroke colour via currentColor. */
|
||||
@@ -1143,7 +1179,7 @@ input[type=range]::-moz-range-thumb {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: #FAFAFA;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1157,12 +1193,177 @@ input[type=range]::-moz-range-thumb {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 28px;
|
||||
text-align: center;
|
||||
padding: 0 32px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* Stage icons — paint-style badge that visually persists across all
|
||||
three stages (same circle, same paint filter, same position) so the
|
||||
inner glyph appears to morph through it as stages crossfade. The
|
||||
glyph itself draws in stroke-by-stroke per stage. Cream stroke on
|
||||
dark green paint matches the CTA buttons. */
|
||||
.stage-icon {
|
||||
display: block;
|
||||
width: clamp(72px, 13vw, 100px);
|
||||
height: clamp(72px, 13vw, 100px);
|
||||
fill: none;
|
||||
opacity: 0;
|
||||
transform: scale(0.78);
|
||||
transform-origin: center;
|
||||
}
|
||||
.stage-icon .badge {
|
||||
fill: #2a3010;
|
||||
stroke: none;
|
||||
filter: url(#paintGlossBtn);
|
||||
-webkit-filter: url(#paintGlossBtn);
|
||||
}
|
||||
.stage-icon .glyph {
|
||||
color: #f4ecd8;
|
||||
stroke: currentColor;
|
||||
/* Slightly thicker strokes so the cream icons read with confidence
|
||||
against the dark green paint chip. */
|
||||
stroke-width: 3.2;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
/* Per-element overrides — accent strokes (ridge, hour hand, side
|
||||
buttons) are thinner so they read as detail rather than primary. */
|
||||
.stage-icon .glyph .i-ridge { stroke-width: 1.4; opacity: 0.55; }
|
||||
.stage-icon .glyph .i-side { stroke-width: 2.2; }
|
||||
.stage-icon .glyph .i-hour { stroke-width: 2.6; }
|
||||
.stage-icon .glyph .i-tick { stroke-width: 2.4; }
|
||||
.stage-icon .glyph .i-pin { fill: currentColor; stroke: none; }
|
||||
/* Bolder mark / bolt — the punctuation strokes inside each icon. */
|
||||
.stage-icon .glyph .i-mark { stroke-width: 4.2; }
|
||||
.stage-icon .glyph .i-bolt { stroke-width: 4.0; }
|
||||
/* Glyph paths/lines/circles draw in stroke-by-stroke. pathLength: 1
|
||||
normalises the dashoffset so each piece draws over the same time. */
|
||||
.stage-icon .glyph path,
|
||||
.stage-icon .glyph line,
|
||||
.stage-icon .glyph circle {
|
||||
stroke-dasharray: 1;
|
||||
stroke-dashoffset: 1;
|
||||
pathLength: 1;
|
||||
}
|
||||
.intro-stage.active .stage-icon {
|
||||
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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
@keyframes introDraw {
|
||||
to { stroke-dashoffset: 0; }
|
||||
}
|
||||
.stage-icon .filled-icon path {
|
||||
fill: currentColor;
|
||||
stroke: none;
|
||||
opacity: 0;
|
||||
transform: scale(0.84);
|
||||
transform-box: fill-box;
|
||||
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;
|
||||
}
|
||||
@keyframes introGlyphPop {
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
.stage-icon .glyph-stroke {
|
||||
fill: none;
|
||||
stroke: #fff8dc;
|
||||
stroke-width: 4.2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-dasharray: 1;
|
||||
stroke-dashoffset: 1;
|
||||
pathLength: 1;
|
||||
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;
|
||||
opacity: 0;
|
||||
filter:
|
||||
drop-shadow(0 0 5px rgba(255, 248, 220, 0.95))
|
||||
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 0.78s 0.18s 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.35s 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;
|
||||
}
|
||||
@keyframes lightningStrike {
|
||||
0% { opacity: 0; }
|
||||
7% { opacity: 1; }
|
||||
12% { opacity: 0.16; }
|
||||
18% { opacity: 1; }
|
||||
25% { opacity: 0; }
|
||||
42% { opacity: 0.92; }
|
||||
50% { opacity: 0.18; }
|
||||
64% { opacity: 1; }
|
||||
72% { opacity: 0; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Stage 3 — face + ticks draw, then the minute hand takes the sentence
|
||||
timing to sweep two full rotations and land back at 12. */
|
||||
.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; }
|
||||
.icon-clock .i-hand {
|
||||
transform-origin: 48px 48px;
|
||||
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 2.2s 1.35s linear forwards;
|
||||
}
|
||||
@keyframes clockSweep {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(720deg); }
|
||||
}
|
||||
|
||||
/* Stage exit — keep the badge/icon present while text dissolves, so the
|
||||
circle feels continuous instead of outroing between sentences. */
|
||||
.intro-stage.leaving .stage-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
/* Intro text — uppercase Barlow at heading scale with wide tracking,
|
||||
matching the "Preparedness. Refined." sub-line typeface. No paint
|
||||
filter / no gradient text-fill (those tanked render performance during
|
||||
@@ -1201,6 +1402,29 @@ 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). */
|
||||
.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; }
|
||||
}
|
||||
/* 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.05s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user