intro animations and background animation
This commit is contained in:
27
src/App.vue
27
src/App.vue
@@ -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++) {
|
||||
|
||||
144
src/styles.css
144
src/styles.css
@@ -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 ──
|
||||
|
||||
Reference in New Issue
Block a user