Files
deepstock/public/intro-test.html
2026-05-10 12:13:03 +01:00

457 lines
19 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Intro Reveal Tests — In + Out</title>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=Barlow:wght@300;400;600;700&display=swap" rel="stylesheet"/>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--paint: #0a0a0a;
--bg: #FAFAFA;
--text: #1A1A18;
--muted: #5A5A54;
--green: #2a3010;
--cream: #f4ecd8;
--easeOut: cubic-bezier(0.22, 0.61, 0.36, 1);
--easeStrong: cubic-bezier(0.16, 1, 0.3, 1);
--easeIn: cubic-bezier(0.4, 0, 1, 1);
}
body {
background: var(--bg);
font-family: 'Barlow', sans-serif;
color: var(--text);
padding: 40px 20px 80px;
min-height: 100vh;
}
header {
max-width: 800px;
margin: 0 auto 32px;
text-align: center;
}
h1 {
font-family: 'DM Serif Display', serif;
font-weight: 400;
font-size: 40px;
margin-bottom: 8px;
letter-spacing: 0.01em;
}
.lede {
color: var(--muted);
font-size: 14px;
letter-spacing: 0.04em;
margin-bottom: 8px;
}
.lede small {
display: block;
margin-top: 6px;
color: var(--muted);
opacity: 0.85;
}
.controls {
text-align: center;
margin-bottom: 32px;
}
.btn-all {
font-family: 'Barlow', sans-serif;
font-weight: 600;
font-size: 12px;
letter-spacing: 0.18em;
text-transform: uppercase;
background: var(--green);
color: var(--cream);
border: none;
padding: 12px 22px;
border-radius: 4px;
cursor: pointer;
box-shadow: 0 3px 6px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,236,200,0.15);
}
.btn-all:hover { background: #1a2008; }
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 18px;
max-width: 800px;
margin: 0 auto;
}
@media (min-width: 720px) {
.grid { grid-template-columns: 1fr 1fr; }
}
.card {
background: #FFFFFF;
border: 1px solid rgba(0,0,0,0.08);
border-radius: 14px;
overflow: hidden;
cursor: pointer;
transition: border-color 0.15s ease, transform 0.15s ease;
}
.card:hover { border-color: rgba(0,0,0,0.18); transform: translateY(-2px); }
.card-stage {
position: relative;
height: 240px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 0 20px;
overflow: hidden;
background: var(--bg);
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.card-stage .demo-text {
display: inline-block;
font-family: 'Barlow', sans-serif;
font-weight: 400;
font-size: clamp(18px, 3.4vw, 26px);
text-transform: uppercase;
letter-spacing: 0.22em;
line-height: 1.3;
color: var(--paint);
opacity: 0; /* default hidden until .play-in or .play-out applied */
will-change: opacity, transform, filter, clip-path;
}
.card-stage.is-resting .demo-text {
opacity: 1; /* resting state for outro previews */
filter: none;
transform: none;
clip-path: inset(0 0 0 0);
}
.card-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 18px;
}
.card-name {
font-weight: 600;
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text);
display: flex;
align-items: center;
gap: 10px;
}
.card-name small {
display: block;
font-weight: 400;
font-size: 11px;
letter-spacing: 0.02em;
text-transform: none;
color: var(--muted);
margin-top: 2px;
}
.card-num {
display: inline-block;
width: 26px; height: 26px;
border-radius: 50%;
background: var(--green);
color: var(--cream);
font-size: 11px;
font-weight: 700;
line-height: 26px;
text-align: center;
flex-shrink: 0;
}
.card-actions { display: flex; gap: 6px; }
.btn {
font-family: 'Barlow', sans-serif;
font-weight: 600;
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
background: transparent;
color: var(--text);
border: 1px solid rgba(0,0,0,0.18);
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
}
.btn:hover { background: rgba(0,0,0,0.04); }
.btn.btn-out { border-color: rgba(192,48,48,0.4); color: #C03030; }
.btn.btn-out:hover { background: rgba(192,48,48,0.06); }
.tip {
text-align: center;
margin-top: 32px;
color: var(--muted);
font-size: 12px;
font-family: 'Barlow', monospace;
letter-spacing: 0.06em;
}
/* ─────────────────────────────────────────────────────────────
IN + OUT animations — every style now has a paired exit so the
user can preview the full cycle. Click the card or "Cycle" to
run In → hold → Out; "In" / "Out" buttons fire one direction.
───────────────────────────────────────────────────────────── */
/* 1. SMOKE */
@keyframes a-smoke-in { 0% { opacity: 0; filter: blur(16px); transform: translateY(14px); } 35% { opacity: 0.6; } 100% { opacity: 1; filter: blur(0); transform: translateY(0); } }
@keyframes a-smoke-out { 0% { opacity: 1; filter: blur(0); transform: translateY(0); } 100% { opacity: 0; filter: blur(16px); transform: translateY(-14px); } }
.demo-1.play-in { animation: a-smoke-in 1.6s var(--easeOut) forwards; }
.demo-1.play-out { animation: a-smoke-out 1.0s var(--easeIn) forwards; }
/* 2. FOCUS PULL */
@keyframes a-focus-in { 0% { opacity: 0; filter: blur(28px); } 40% { opacity: 1; filter: blur(28px); } 100% { opacity: 1; filter: blur(0); } }
@keyframes a-focus-out { 0% { opacity: 1; filter: blur(0); } 60% { opacity: 1; filter: blur(28px); } 100% { opacity: 0; filter: blur(28px); } }
.demo-2.play-in { animation: a-focus-in 1.8s var(--easeStrong) forwards; }
.demo-2.play-out { animation: a-focus-out 1.4s var(--easeIn) forwards; }
/* 3. SLOW ZOOM (entry: 1.08 → 1.00 ; exit: 1.00 → 1.10 outward) */
@keyframes a-zoom-in { 0% { opacity: 0; transform: scale(1.08); filter: blur(2px); } 100% { opacity: 1; transform: scale(1); filter: blur(0); } }
@keyframes a-zoom-out { 0% { opacity: 1; transform: scale(1); filter: blur(0); } 100% { opacity: 0; transform: scale(1.10); filter: blur(2px); } }
.demo-3.play-in { animation: a-zoom-in 2.2s var(--easeStrong) forwards; }
.demo-3.play-out { animation: a-zoom-out 1.0s var(--easeIn) forwards; }
/* 4. PUSH-IN (entry: 0.92 → 1.00 ; exit: 1.00 → 0.92 reverse dolly) */
@keyframes a-push-in { 0% { opacity: 0; transform: scale(0.92); filter: blur(3px); } 100% { opacity: 1; transform: scale(1); filter: blur(0); } }
@keyframes a-push-out { 0% { opacity: 1; transform: scale(1); filter: blur(0); } 100% { opacity: 0; transform: scale(0.92); filter: blur(3px); } }
.demo-4.play-in { animation: a-push-in 1.6s var(--easeOut) forwards; }
.demo-4.play-out { animation: a-push-out 1.0s var(--easeIn) forwards; }
/* 5. MASK UP (entry: clip bottom → top ; exit: clip top → bottom) */
@keyframes a-mask-in { 0% { opacity: 1; clip-path: inset(100% 0 0 0); } 10% { opacity: 1; } 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes a-mask-out { 0% { opacity: 1; clip-path: inset(0 0 0 0); } 100% { opacity: 1; clip-path: inset(0 0 100% 0); } }
.demo-5.play-in { animation: a-mask-in 1.4s var(--easeStrong) forwards; }
.demo-5.play-out { animation: a-mask-out 1.0s var(--easeIn) forwards; }
/* 6. IRIS (entry: circle 0% → 75% ; exit: circle 75% → 0%) */
@keyframes a-iris-in { 0% { opacity: 1; clip-path: circle(0% at 50% 50%); } 100% { opacity: 1; clip-path: circle(75% at 50% 50%); } }
@keyframes a-iris-out { 0% { opacity: 1; clip-path: circle(75% at 50% 50%); } 100% { opacity: 1; clip-path: circle(0% at 50% 50%); } }
.demo-6.play-in { animation: a-iris-in 1.4s var(--easeStrong) forwards; }
.demo-6.play-out { animation: a-iris-out 1.0s var(--easeIn) forwards; }
/* 7. WORD CASCADE (each word in/out with stagger; JS sets per-word delay) */
@keyframes a-word-in { 0% { opacity: 0; transform: translateY(14px); filter: blur(8px); } 100% { opacity: 1; transform: translateY(0); filter: blur(0); } }
@keyframes a-word-out { 0% { opacity: 1; transform: translateY(0); filter: blur(0); } 100% { opacity: 0; transform: translateY(-12px); filter: blur(6px); } }
.card-stage.style-7 .demo-text { opacity: 1; }
.card-stage.style-7 .demo-text > .word {
display: inline-block;
margin-right: 0.25em;
opacity: 0;
}
.card-stage.style-7.is-resting .demo-text > .word { opacity: 1; }
.card-stage.style-7.play-in .demo-text > .word { animation: a-word-in 0.7s var(--easeOut) forwards; }
.card-stage.style-7.play-out .demo-text > .word { animation: a-word-out 0.55s var(--easeIn) forwards; }
/* 8. GLOW HALO */
@keyframes a-glow-in { 0% { opacity: 0; text-shadow: 0 0 0 rgba(90,154,120,0); filter: blur(2px); transform: scale(0.97); } 45% { opacity: 1; text-shadow: 0 0 28px rgba(90,154,120,0.65), 0 0 12px rgba(90,154,120,0.45); } 100% { opacity: 1; text-shadow: 0 0 0 rgba(90,154,120,0); filter: blur(0); transform: scale(1); } }
@keyframes a-glow-out { 0% { opacity: 1; text-shadow: 0 0 0 rgba(90,154,120,0); filter: blur(0); } 50% { opacity: 1; text-shadow: 0 0 32px rgba(90,154,120,0.55); } 100% { opacity: 0; text-shadow: 0 0 4px rgba(90,154,120,0); filter: blur(2px); transform: scale(1.04); } }
.demo-8.play-in { animation: a-glow-in 1.9s var(--easeOut) forwards; }
.demo-8.play-out { animation: a-glow-out 1.2s var(--easeIn) forwards; }
/* 9. CHROMATIC */
.card-stage.style-9 .demo-text { position: relative; }
.card-stage.style-9 .demo-text::before,
.card-stage.style-9 .demo-text::after {
content: attr(data-text);
position: absolute;
inset: 0;
pointer-events: none;
}
.card-stage.style-9 .demo-text::before { color: rgba(220,40,40,0.85); }
.card-stage.style-9 .demo-text::after { color: rgba(40,120,220,0.85); }
@keyframes a-chrom-base-in { 0% { opacity: 0; } 20% { opacity: 1; } 100% { opacity: 1; } }
@keyframes a-chrom-base-out { 0% { opacity: 1; } 100% { opacity: 0; } }
@keyframes a-chrom-r-in { 0% { opacity: 0; transform: translate3d(-22px,0,0); } 20% { opacity: 0.85; } 100% { opacity: 0; transform: translate3d(0,0,0); } }
@keyframes a-chrom-r-out { 0% { opacity: 0; transform: translate3d(0,0,0); } 30% { opacity: 0.85; transform: translate3d(-22px,0,0); } 100% { opacity: 0; transform: translate3d(-32px,0,0); } }
@keyframes a-chrom-b-in { 0% { opacity: 0; transform: translate3d(22px,0,0); } 20% { opacity: 0.85; } 100% { opacity: 0; transform: translate3d(0,0,0); } }
@keyframes a-chrom-b-out { 0% { opacity: 0; transform: translate3d(0,0,0); } 30% { opacity: 0.85; transform: translate3d(22px,0,0); } 100% { opacity: 0; transform: translate3d(32px,0,0); } }
.card-stage.style-9.play-in .demo-text { animation: a-chrom-base-in 1.4s var(--easeStrong) forwards; }
.card-stage.style-9.play-in .demo-text::before { animation: a-chrom-r-in 1.4s var(--easeStrong) forwards; }
.card-stage.style-9.play-in .demo-text::after { animation: a-chrom-b-in 1.4s var(--easeStrong) forwards; }
.card-stage.style-9.play-out .demo-text { animation: a-chrom-base-out 1.0s var(--easeIn) forwards; }
.card-stage.style-9.play-out .demo-text::before { animation: a-chrom-r-out 1.0s var(--easeIn) forwards; }
.card-stage.style-9.play-out .demo-text::after { animation: a-chrom-b-out 1.0s var(--easeIn) forwards; }
/* 10. VAPOR RISE (entry: rises into focus ; exit: continues rising into haze) */
@keyframes a-vapor-in { 0% { opacity: 0; filter: blur(0) brightness(1.3); transform: translateY(28px); } 20% { opacity: 0.45; filter: blur(8px) brightness(1.2); } 100% { opacity: 1; filter: blur(0) brightness(1); transform: translateY(0); } }
@keyframes a-vapor-out { 0% { opacity: 1; filter: blur(0) brightness(1); transform: translateY(0); } 100% { opacity: 0; filter: blur(8px) brightness(1.2); transform: translateY(-28px); } }
.demo-10.play-in { animation: a-vapor-in 2.0s var(--easeOut) forwards; }
.demo-10.play-out { animation: a-vapor-out 1.0s var(--easeIn) forwards; }
</style>
</head>
<body>
<header>
<h1>Intro Reveal Tests — In + Out</h1>
<p class="lede">
Ten cinematic reveal styles, each with a matching exit.
<small>Tap the card for a full <em>In → hold → Out</em> cycle. "In" / "Out" preview each direction independently.</small>
</p>
</header>
<div class="controls">
<button class="btn-all" id="play-all">▶ Cycle All Together</button>
</div>
<div class="grid">
<div class="card" data-num="1">
<div class="card-stage style-1"><span class="demo-text demo-1">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">01</span><div>Smoke<small>blur 16px → focus → blur out</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="2">
<div class="card-stage style-2"><span class="demo-text demo-2">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">02</span><div>Focus pull<small>blur snaps to focus, then defocuses</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="3">
<div class="card-stage style-3"><span class="demo-text demo-3">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">03</span><div>Slow zoom<small>1.08 → 1.00 → 1.10 (camera pulls back)</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="4">
<div class="card-stage style-4"><span class="demo-text demo-4">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">04</span><div>Push-in<small>0.92 → 1.00 → 0.92 (reverse dolly)</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="5">
<div class="card-stage style-5"><span class="demo-text demo-5">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">05</span><div>Mask up<small>clip bottom → top; exits top → bottom</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="6">
<div class="card-stage style-6"><span class="demo-text demo-6">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">06</span><div>Iris<small>circle opens, then closes</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="7">
<div class="card-stage style-7"><span class="demo-text demo-7" data-text="Wisdom is to prepare">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">07</span><div>Word cascade<small>words in & out, staggered up/down</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="8">
<div class="card-stage style-8"><span class="demo-text demo-8">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">08</span><div>Glow halo<small>halo in, halo flares & fades out</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="9">
<div class="card-stage style-9"><span class="demo-text demo-9" data-text="Wisdom is to prepare">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">09</span><div>Chromatic<small>RGB split converges; splits apart on out</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
<div class="card" data-num="10">
<div class="card-stage style-10"><span class="demo-text demo-10">Wisdom is to prepare</span></div>
<div class="card-foot">
<div class="card-name"><span class="card-num">10</span><div>Vapor rise<small>rises into focus; continues rising into haze</small></div></div>
<div class="card-actions"><button class="btn btn-in">In</button><button class="btn btn-out">Out</button></div>
</div>
</div>
</div>
<p class="tip">Click a card for the full cycle, or use In / Out buttons to preview each direction.</p>
<script>
// Word cascade — split text into <span class="word"> chunks with stagger.
function buildWords() {
const t = document.querySelector('.demo-7')
if (!t) return
const text = t.dataset.text || t.textContent || ''
t.innerHTML = ''
text.split(' ').forEach((w, i) => {
const span = document.createElement('span')
span.className = 'word'
span.dataset.idx = i
span.dataset.total = text.split(' ').length
span.style.animationDelay = (i * 0.18) + 's'
span.textContent = w
t.appendChild(span)
})
}
buildWords()
// Stages 7 and 9 toggle classes on the .card-stage; the rest toggle on
// the .demo-N inner element.
function targetFor(num) {
const stageLevel = ['7', '9'].includes(String(num))
return stageLevel
? document.querySelector('.style-' + num)
: document.querySelector('.demo-' + num)
}
function setClass(num, cls) {
const t = targetFor(num)
if (!t) return
t.classList.remove('play-in', 'play-out')
void t.offsetWidth
t.classList.add(cls)
}
function reset(num) {
const t = targetFor(num)
if (!t) return
t.classList.remove('play-in', 'play-out')
// Park stage 7 (cascade) and 5/6 (clip) in resting state for outro previews.
const stage = (['7','9'].includes(String(num))) ? t : t.closest('.card-stage')
if (stage) stage.classList.add('is-resting')
}
function playIn(num) { document.querySelector('.style-' + num)?.classList.remove('is-resting'); setClass(num, 'play-in') }
function playOut(num) { document.querySelector('.style-' + num)?.classList.add('is-resting'); setClass(num, 'play-out') }
function cycle(num) {
playIn(num)
// Hold for a moment after in completes (~1.6s baseline + 0.7s read)
setTimeout(() => playOut(num), 2400)
}
document.querySelectorAll('.card').forEach(card => {
card.addEventListener('click', () => cycle(card.dataset.num))
})
document.querySelectorAll('.btn-in').forEach(btn => {
btn.addEventListener('click', e => { e.stopPropagation(); playIn(btn.closest('.card').dataset.num) })
})
document.querySelectorAll('.btn-out').forEach(btn => {
btn.addEventListener('click', e => { e.stopPropagation(); playOut(btn.closest('.card').dataset.num) })
})
document.getElementById('play-all').addEventListener('click', () => {
for (let n = 1; n <= 10; n++) cycle(n)
})
// Auto-cycle each card once on load with a stagger.
window.addEventListener('load', () => {
for (let n = 1; n <= 10; n++) {
setTimeout(() => cycle(n), 300 + n * 150)
}
})
</script>
</body>
</html>