feat: cinematic viewport-filling splash tagline

Logo draws in, shrinks up, then words slam in one-by-one:
EVERYTHING / YOU / LOVE / IS A — each with blur-to-sharp animation.
PSYOP lands last in italic serif (Georgia) with Bitcoin orange,
oversized at 15vw, with a bounce-slam keyframe.
Responsive: 4-line layout on mobile, 2-line on desktop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-17 00:51:25 +00:00
parent 814957cd37
commit 88a373da80

View File

@@ -5,6 +5,7 @@ const emit = defineEmits<{ complete: [] }>()
const isAnimating = ref(false)
const showTagline = ref(false)
const showPsyop = ref(false)
const isFading = ref(false)
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
@@ -13,28 +14,34 @@ onMounted(() => {
if (prefersReducedMotion) {
isAnimating.value = true
showTagline.value = true
showPsyop.value = true
setTimeout(() => emit('complete'), 800)
return
}
// Phase 1: Logo draws in
requestAnimationFrame(() => {
isAnimating.value = true
})
// Logo finishes at ~1700ms (6 paths * 200ms stagger + 700ms draw)
// Show tagline after logo is fully revealed
// Phase 2: Logo shrinks up, tagline words slam in one by one
setTimeout(() => {
showTagline.value = true
}, 1900)
}, 1800)
// Start fading out after tagline has been visible
// Phase 3: "PSYOP" lands last with extra punch
setTimeout(() => {
showPsyop.value = true
}, 2800)
// Phase 4: Hold, then fade
setTimeout(() => {
isFading.value = true
}, 3800)
}, 4200)
setTimeout(() => {
emit('complete')
}, 4200)
}, 4700)
})
</script>
@@ -43,7 +50,7 @@ onMounted(() => {
class="splash-overlay"
:class="{ 'splash-fade-out': isFading }"
>
<div class="splash-content">
<div class="splash-content" :class="{ 'logo-shrunk': showTagline }">
<svg
width="309"
height="121"
@@ -60,12 +67,17 @@ onMounted(() => {
<path class="logo-path" :class="{ animating: isAnimating }" style="--i: 5" d="M189.921 51.7524C184.441 56.3024 180.001 61.3624 175.351 66.5424L172.661 69.5424C172.151 70.1124 171.031 70.6424 170.181 70.5524C169.551 70.4924 168.661 69.9524 168.321 69.5124C167.981 69.0724 168.111 67.9324 168.491 67.3624L175.621 56.7024L179.141 51.9724C181.891 48.2824 184.051 44.5524 186.211 40.3724C187.151 39.7624 188.831 40.6424 189.881 41.3024C190.281 41.5524 194.551 41.6324 196.871 43.1824L197.911 45.7624C197.311 47.0624 195.961 47.2324 194.921 47.7924C193.161 48.7324 191.621 50.3524 189.911 51.7724L189.921 51.7524ZM182.321 54.0124L190.771 46.3624C190.281 45.3424 188.511 45.4324 187.761 45.8424C187.201 46.1424 186.671 47.0824 186.161 47.8124L181.181 54.8824C181.911 55.3924 181.951 54.3624 182.321 54.0224V54.0124Z" fill="currentColor" />
</svg>
<h2
class="tagline"
:class="{ visible: showTagline }"
>
EVERYTHING YOU LOVE IS A PSYOP
</h2>
<div class="tagline-block" :class="{ visible: showTagline }">
<span class="word" style="--w: 0">EVERYTHING</span>
<span class="word" style="--w: 1">YOU</span>
<span class="word" style="--w: 2">LOVE</span>
<span class="word" style="--w: 3">IS A</span>
<span
class="word psyop"
:class="{ landed: showPsyop }"
style="--w: 4"
>PSYOP</span>
</div>
</div>
</div>
</template>
@@ -79,7 +91,8 @@ onMounted(() => {
align-items: center;
justify-content: center;
background: var(--bg-primary);
transition: opacity 400ms ease;
transition: opacity 500ms ease;
overflow: hidden;
}
.splash-fade-out {
@@ -91,12 +104,24 @@ onMounted(() => {
display: flex;
flex-direction: column;
align-items: center;
gap: 2.5rem;
gap: 2rem;
transition: transform 600ms cubic-bezier(0.16, 1, 0.3, 1);
}
.splash-content.logo-shrunk {
transform: translateY(-8vh);
}
.splash-logo {
width: min(70vw, 400px);
width: min(60vw, 340px);
height: auto;
transition: transform 600ms cubic-bezier(0.16, 1, 0.3, 1),
opacity 600ms ease;
}
.logo-shrunk .splash-logo {
transform: scale(0.7);
opacity: 0.6;
}
.logo-path {
@@ -122,22 +147,107 @@ onMounted(() => {
to { opacity: 1; }
}
.tagline {
font-family: system-ui, -apple-system, 'Helvetica Neue', sans-serif;
font-size: clamp(0.75rem, 2.5vw, 1.125rem);
font-weight: 900;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--text-primary);
opacity: 0;
transform: translateY(12px);
transition: opacity 600ms ease, transform 600ms cubic-bezier(0.16, 1, 0.3, 1);
white-space: nowrap;
/* --- Tagline --- */
.tagline-block {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0 0.4em;
width: 90vw;
max-width: 900px;
text-align: center;
line-height: 1.05;
}
.tagline.visible {
opacity: 1;
transform: translateY(0);
.word {
font-family: system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
font-size: clamp(2.5rem, 10vw, 7rem);
font-weight: 900;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: -0.02em;
opacity: 0;
transform: translateY(40px) scale(1.1);
filter: blur(8px);
}
.tagline-block.visible .word {
animation: slamIn 400ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--w) * 150ms) forwards;
}
@keyframes slamIn {
0% {
opacity: 0;
transform: translateY(40px) scale(1.1);
filter: blur(8px);
}
60% {
opacity: 1;
transform: translateY(-4px) scale(1.0);
filter: blur(0);
}
100% {
opacity: 1;
transform: translateY(0) scale(1.0);
filter: blur(0);
}
}
/* PSYOP — different font, extra punch */
.psyop {
font-family: 'Georgia', 'Times New Roman', 'Playfair Display', serif;
font-style: italic;
color: var(--accent);
display: block;
width: 100%;
font-size: clamp(3.5rem, 15vw, 10rem);
letter-spacing: 0.08em;
margin-top: 0.1em;
transform: translateY(60px) scale(1.3);
filter: blur(12px);
}
.psyop.landed {
animation: psyopSlam 600ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes psyopSlam {
0% {
opacity: 0;
transform: translateY(60px) scale(1.3);
filter: blur(12px);
}
40% {
opacity: 1;
transform: translateY(-6px) scale(1.02);
filter: blur(0);
}
55% {
transform: translateY(2px) scale(0.99);
}
70% {
transform: translateY(-1px) scale(1.0);
}
100% {
opacity: 1;
transform: translateY(0) scale(1.0);
filter: blur(0);
}
}
/* Mobile: 4-line layout */
@media (max-width: 600px) {
.word {
font-size: clamp(2rem, 14vw, 4rem);
}
.psyop {
font-size: clamp(3rem, 20vw, 5.5rem);
}
.tagline-block {
width: 95vw;
}
}
@media (prefers-reduced-motion: reduce) {
@@ -146,14 +256,18 @@ onMounted(() => {
opacity: 1;
}
.tagline {
.word,
.psyop {
opacity: 1;
transform: none;
transition: none;
filter: none;
animation: none !important;
}
.splash-overlay {
transition-duration: 0ms;
.splash-overlay,
.splash-content,
.splash-logo {
transition: none;
}
}
</style>