intro animations and background animation

This commit is contained in:
Dorian
2026-05-10 12:43:41 +01:00
parent acd3d2fa61
commit e3851603ab
2 changed files with 126 additions and 45 deletions

View File

@@ -271,12 +271,11 @@
<main class="app">
<!-- HERO -->
<section class="hero" id="hero-section">
<!-- Embossed "Plan B" background diagonal grid of translations
that fades in alongside the brand wordmark after the intro
overlay clears. Pure decoration, hidden from screen readers. -->
<div class="hero-bg-pattern" aria-hidden="true">
<!-- Embossed "Plan B" page background fixed full-viewport, sits
behind every page (homepage, quiz, results). Hidden from screen
readers; only the per-page content sits above this layer. -->
<div class="page-bg-pattern" aria-hidden="true">
<div class="bg-tilt">
<div class="bg-row"><span>Plan B</span><span>Piano B</span><span>Plano B</span><span>Plan B</span><span>План Б</span><span>プランB</span></div>
<div class="bg-row"><span>Plan B</span><span>备用计划</span><span>Plan B</span><span>플랜 B</span><span>Plan B</span><span>जन </span></div>
<div class="bg-row"><span>Piano B</span><span>Plan B</span><span>Σχέδιο Β</span><span>Plano B</span><span>خطة ب</span><span>Plan B</span></div>
@@ -285,6 +284,10 @@
<div class="bg-row"><span>Plan B</span><span>जन </span><span>Plan B</span><span>خطة ب</span><span>Σχέδιο Β</span><span>Plan B</span></div>
<div class="bg-row"><span>Plan B</span><span>プランB</span><span>Plan B</span><span>План Б</span><span>Plan B</span><span>Piano B</span></div>
</div>
</div>
<!-- HERO -->
<section class="hero" id="hero-section">
<h1 class="paint-3d" data-i18n="brand">Deepstock</h1>
<p class="hero-sub" data-i18n="hero_sub">Preparedness, refined.</p>
<button class="cta-btn" @click="startQuiz">
@@ -1798,12 +1801,12 @@ async function playIntro() {
// stages 1 and 3). Stage 2 needs ~2.6s to land both staggered lines
// (line-a 0.1+1.5s, line-b 1.1+1.5s). Holds are tuned so the viewer
// can read each line after it lands.
// Every stage word-cascades, half speed so each line lands more
// deliberately. Entry = words × 0.32s stagger + 1.5s per-word + 0.05s
// base delay. JS rebuilds the spans per play so language switches
// still split correctly and measures entry from live word count.
const STAGGER = 0.32
const PER_WORD = 1.5
// Every stage word-cascades. Entry = words × stagger + per-word
// 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 stages = []
for (let n = 1; n <= 3; n++) {

View File

@@ -98,7 +98,7 @@ body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); f
and compensates for the scenario-strip below — net effect: visually centered on the viewport */
padding: 100px 16px 24px;
text-align: center;
background: #F0F0F0;
background: transparent;
overflow: hidden;
flex: 1;
display: flex;
@@ -107,12 +107,26 @@ body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); f
justify-content: center;
}
/* Embossed "Plan B" background pattern — diagonal grid of the phrase
in different languages, carved into the hero panel. Text colour
matches the bg so only the highlight + shadow text-shadows render,
giving a subtle pressed-into-the-surface look. Sits behind hero
content (z-index 0; .hero h1 / cta etc. are z-index 1). */
.hero-bg-pattern {
/* Embossed "Plan B" page background — fixed full-viewport, sits behind
every screen (hero, quiz, results). The translucent fixed headers
and footers cover their own areas so the pattern doesn't bleed
through where the user expects clean nav. Text colour matches the
#F0F0F0 base so only the highlight + shadow text-shadows render,
giving a subtle pressed-into-the-surface look. */
.page-bg-pattern {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
user-select: none;
overflow: hidden;
background: #F0F0F0;
/* 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. */
opacity: 1;
}
.page-bg-pattern .bg-tilt {
position: absolute;
inset: -20%;
display: flex;
@@ -121,12 +135,8 @@ body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); f
align-items: stretch;
transform: rotate(-18deg);
transform-origin: center;
pointer-events: none;
user-select: none;
z-index: 0;
opacity: 0;
}
.hero-bg-pattern .bg-row {
.page-bg-pattern .bg-row {
display: flex;
justify-content: space-around;
align-items: center;
@@ -134,27 +144,88 @@ body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); f
white-space: nowrap;
font-family: var(--font-display);
font-weight: 400;
/* Mobile floor bumped up from 28px → 42px so the embossed pattern reads
comfortably on small screens; desktop ceiling bumped slightly. */
/* Mobile floor 42px → desktop ceiling 60px so the embossed pattern
reads comfortably across viewport sizes. */
font-size: clamp(42px, 8vw, 60px);
letter-spacing: 0.04em;
/* Embossed effect: text colour matches the panel so only the
shadows render. Highlight on top-left, faint shadow on bottom-right. */
/* Emboss: text colour matches the panel; only the shadows render —
highlight top-left, faint dark on bottom-right. */
color: #F0F0F0;
text-shadow:
-1px -1px 0 rgba(255, 255, 255, 0.95),
1px 1px 0 rgba(0, 0, 0, 0.06);
}
.hero-bg-pattern .bg-row span { display: inline-block; }
.page-bg-pattern .bg-row span { display: inline-block; }
/* Reveal the pattern alongside the hero wordmark after the intro
overlay clears. Slow ease-in so it feels like the surface emerges
underneath rather than appearing as a new layer. */
body.intro-done .hero-bg-pattern {
animation: heroBgFade 1.6s 0.3s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
/* Lighter pattern on the assessment + results pages — homepage stays
on the slightly darker #F0F0F0 panel; quiz / results lift to #FAFAFA
so the questions and result cards (also #FAFAFA white-paint) sit on
a softer background. Text colour follows so the emboss effect still
relies on shadows alone. */
body.quiz-active .page-bg-pattern,
body.results-active .page-bg-pattern {
background: #FAFAFA;
}
@keyframes heroBgFade {
to { opacity: 1; }
body.quiz-active .page-bg-pattern .bg-row,
body.results-active .page-bg-pattern .bg-row {
color: #FAFAFA;
text-shadow:
-1px -1px 0 rgba(255, 255, 255, 0.95),
1px 1px 0 rgba(0, 0, 0, 0.05);
}
/* Rows slide in horizontally with alternating direction — odd rows
come from the left, even rows from the right — so the pattern weaves
itself together rather than fading in as one slab. The translateX
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 {
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); }
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;
}
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;
}
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.12s; }
body.intro-done .page-bg-pattern .bg-row:nth-child(3) { animation-delay: 0.24s; }
body.intro-done .page-bg-pattern .bg-row:nth-child(4) { animation-delay: 0.36s; }
body.intro-done .page-bg-pattern .bg-row:nth-child(5) { animation-delay: 0.48s; }
body.intro-done .page-bg-pattern .bg-row:nth-child(6) { animation-delay: 0.60s; }
body.intro-done .page-bg-pattern .bg-row:nth-child(7) { animation-delay: 0.72s; }
@keyframes bgRowSlideLeft {
from { opacity: 0; transform: translateX(-160px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes bgRowSlideRight {
from { opacity: 0; transform: translateX(160px); }
to { opacity: 1; transform: translateX(0); }
}
/* Lighter background on the quiz/results pages — FAFAFA matches the
white-paint cards, and the embossed text colour shifts to match so
only the highlight + shadow text-shadows render (same trick as the
homepage, just on a lighter base). */
body.quiz-active .page-bg-pattern,
body.results-active .page-bg-pattern {
background: #FAFAFA;
}
body.quiz-active .page-bg-pattern .bg-row,
body.results-active .page-bg-pattern .bg-row {
color: #FAFAFA;
}
/* Sections that sit above the pattern need to live in a higher
stacking context. Position:relative + z-index:1 puts hero / quiz /
results / about content above the fixed pattern. */
.hero, .quiz-section, .results-section, .about-section {
position: relative;
z-index: 1;
}
.hero-eyebrow {
position: relative; z-index: 1;
@@ -164,7 +235,9 @@ body.intro-done .hero-bg-pattern {
text-transform: uppercase;
color: #0a0a0a;
margin-bottom: 12px;
animation: fadeUp 1s ease both;
/* 1.0s delay so the embossed pattern settles first; the rest of
the hero (h1 / sub / cta) chains off this with their own offsets. */
animation: fadeUp 1s 1.0s ease both;
font-weight: 400;
}
.hero h1 {
@@ -180,7 +253,7 @@ body.intro-done .hero-bg-pattern {
letter-spacing: 0.05em;
color: #1A1A18;
margin-bottom: 40px;
animation: fadeUp 1s 0.15s ease both;
animation: fadeUp 1s 1.15s ease both;
}
/* Mobile uses the same flat #F0F0F0 background as desktop now that
the bg image has been removed. */
@@ -225,7 +298,7 @@ html[lang="en"] .hero h1 { font-size: calc((100vw - 32px) / 5.2); }
white-space: nowrap;
position: relative; z-index: 1;
margin: 0 auto 40px;
animation: fadeUp 1s 0.15s ease both;
animation: fadeUp 1s 1.15s ease both;
}
.hero .brand-line h1.paint-3d {
display: inline-block;
@@ -276,7 +349,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 0.3s ease both;
animation: fadeUp 1s 1.3s ease both;
}
.cta-btn {
display: inline-flex;
@@ -291,7 +364,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 0.45s ease both;
animation: fadeUp 1s 1.45s ease both;
/* Lift above .hero::after radial fade so the button isn't washed out */
z-index: 2;
}
@@ -1126,13 +1199,18 @@ input[type=range]::-moz-range-thumb {
display: inline-block;
margin-right: 0.28em;
opacity: 0;
/* Promote each word to its own GPU layer so the staggered reveals
don't compound layout work as more words come in. Removed the
blur from the keyframe — animating filter on 10 words at once
was the lag source. */
will-change: opacity, transform;
}
.intro-stage.active .intro-text > .word {
animation: introWord 1.5s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
animation: introWord 1.05s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
@keyframes introWord {
0% { opacity: 0; transform: translateY(14px); filter: blur(8px); }
100% { opacity: 1; transform: translateY(0); filter: blur(0); }
0% { opacity: 0; transform: translateY(14px); }
100% { opacity: 1; transform: translateY(0); }
}
/* Exit — smoke out: re-blur to 16px, drift upward 14px, fade to 0.
@@ -1165,7 +1243,7 @@ body:not(.intro-done) .hero .cta-btn {
body:not(.intro-done) .site-header { opacity: 0; }
body.intro-done .site-header {
opacity: 1;
transition: opacity 0.6s ease 0.2s;
transition: opacity 0.6s ease 1.0s;
}
/* ── SINGLE FOLD: hide non-essential sections on load ──