Skip to content

Commit 2750e44

Browse files
committed
feat: sync hero text warp to video
1 parent 745f2ec commit 2750e44

File tree

4 files changed

+118
-26
lines changed

4 files changed

+118
-26
lines changed

src/app/globals.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,30 @@ body {
4646
.hero-warp {
4747
display: inline-block;
4848
filter: url(#hero-warp-filter);
49+
transform-origin: center;
4950
will-change: filter;
5051
}
5152

53+
@property --hero-warp {
54+
syntax: '<number>';
55+
inherits: true;
56+
initial-value: 0;
57+
}
58+
59+
.hero-pull-down {
60+
letter-spacing: calc(var(--hero-warp) * -0.02em);
61+
transform: translate3d(0, calc(var(--hero-warp) * 24px), 0)
62+
scaleX(calc(1 - var(--hero-warp) * 0.08))
63+
scaleY(calc(1 + var(--hero-warp) * 0.06));
64+
}
65+
66+
.hero-pull-up {
67+
letter-spacing: calc(var(--hero-warp) * -0.02em);
68+
transform: translate3d(0, calc(var(--hero-warp) * -24px), 0)
69+
scaleX(calc(1 - var(--hero-warp) * 0.08))
70+
scaleY(calc(1 + var(--hero-warp) * 0.06));
71+
}
72+
5273
.hero-title {
5374
filter: drop-shadow(0 8px 28px rgba(0, 0, 0, 0.65));
5475
}
@@ -61,6 +82,11 @@ body {
6182
.hero-warp {
6283
filter: none;
6384
}
85+
86+
.hero-pull-down,
87+
.hero-pull-up {
88+
transform: none;
89+
}
6490
}
6591

6692
video:fullscreen::-webkit-media-text-track-display,

src/app/layout.tsx

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -116,37 +116,26 @@ export default async function RootLayout({
116116
colorInterpolationFilters="sRGB"
117117
>
118118
<feTurbulence
119+
id="hero-warp-turbulence"
119120
type="fractalNoise"
120-
baseFrequency="0.004 0.012"
121+
baseFrequency="0.002 0.006"
121122
numOctaves="1"
122123
seed="2"
123124
result="noise"
124-
>
125-
<animate
126-
attributeName="baseFrequency"
127-
dur="14s"
128-
repeatCount="indefinite"
129-
values="0.004 0.012;0.004 0.012;0.007 0.02;0.004 0.012;0.004 0.012"
130-
keyTimes="0;0.78;0.84;0.9;1"
131-
calcMode="linear"
132-
/>
133-
</feTurbulence>
125+
/>
126+
<feGaussianBlur
127+
in="noise"
128+
stdDeviation="1.35"
129+
result="noiseSmooth"
130+
/>
134131
<feDisplacementMap
132+
id="hero-warp-displacement"
135133
in="SourceGraphic"
136-
in2="noise"
134+
in2="noiseSmooth"
137135
scale="0"
138136
xChannelSelector="R"
139137
yChannelSelector="G"
140-
>
141-
<animate
142-
attributeName="scale"
143-
dur="14s"
144-
repeatCount="indefinite"
145-
values="0;0;22;8;0"
146-
keyTimes="0;0.78;0.84;0.9;1"
147-
calcMode="linear"
148-
/>
149-
</feDisplacementMap>
138+
/>
150139
</filter>
151140
</defs>
152141
</svg>

src/app/page.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export default function Home() {
1111

1212
<div className="absolute top-1/2 left-1/2 z-10 -mt-[3.375rem] w-full max-w-4xl -translate-x-1/2 -translate-y-full text-center">
1313
<h1 className="hero-title font-display text-4xl font-semibold tracking-tight text-balance sm:text-6xl md:text-7xl">
14-
<span className="hero-warp">Push AI further, faster.</span>
14+
<span className="hero-warp hero-pull-down">
15+
Push AI further, faster.
16+
</span>
1517
</h1>
1618
</div>
1719

@@ -27,9 +29,11 @@ export default function Home() {
2729

2830
<div className="absolute top-1/2 left-1/2 z-10 mt-[3.375rem] w-full max-w-3xl -translate-x-1/2 px-2">
2931
<div className="hero-title font-display text-center text-2xl tracking-tight sm:text-3xl">
30-
When you move at light speed,
31-
<br />
32-
<b>every</b> bold idea can ship.
32+
<span className="hero-warp hero-pull-up">
33+
When you move at light speed,
34+
<br />
35+
<b>every</b> bold idea can ship.
36+
</span>
3337
</div>
3438
</div>
3539
</div>

src/components/HeroMedia.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,79 @@ export function HeroMedia() {
103103
};
104104
}, []);
105105

106+
useEffect(() => {
107+
if (
108+
typeof window !== 'undefined' &&
109+
window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
110+
) {
111+
return;
112+
}
113+
114+
const doc = document.documentElement;
115+
const displacement = document.getElementById(
116+
'hero-warp-displacement'
117+
) as SVGFEDisplacementMapElement | null;
118+
const turbulence = document.getElementById(
119+
'hero-warp-turbulence'
120+
) as SVGFETurbulenceElement | null;
121+
122+
if (!displacement || !turbulence) return;
123+
124+
const clamp01 = (value: number) => Math.max(0, Math.min(1, value));
125+
const pulse = (time: number, center: number, width: number) => {
126+
const x = (time - center) / width;
127+
return Math.exp(-x * x);
128+
};
129+
130+
let rafId = 0;
131+
let lastWarp = -1;
132+
133+
const tick = () => {
134+
const intro = introRef.current;
135+
const loop = loopRef.current;
136+
137+
const introActive = !!intro && !introEndedRef.current;
138+
const activeVideo = introActive ? intro : loop;
139+
const activeTime = activeVideo?.currentTime ?? 0;
140+
141+
let warp = 0;
142+
if (introActive) {
143+
warp = pulse(activeTime, 2, 0.5);
144+
} else if (loop) {
145+
warp = pulse(activeTime, 5, 0.7);
146+
}
147+
148+
warp = clamp01(Math.pow(warp, 1.65));
149+
150+
if (Math.abs(warp - lastWarp) > 0.01) {
151+
lastWarp = warp;
152+
153+
doc.style.setProperty('--hero-warp', warp.toFixed(3));
154+
155+
const scale = 22 * warp;
156+
displacement.setAttribute('scale', scale.toFixed(2));
157+
158+
const freqX = 0.002 + 0.004 * warp;
159+
const freqY = 0.006 + 0.01 * warp;
160+
turbulence.setAttribute(
161+
'baseFrequency',
162+
`${freqX.toFixed(4)} ${freqY.toFixed(4)}`
163+
);
164+
}
165+
166+
rafId = window.requestAnimationFrame(tick);
167+
};
168+
169+
rafId = window.requestAnimationFrame(tick);
170+
171+
return () => {
172+
window.cancelAnimationFrame(rafId);
173+
doc.style.setProperty('--hero-warp', '0');
174+
displacement.setAttribute('scale', '0');
175+
turbulence.setAttribute('baseFrequency', '0.002 0.006');
176+
};
177+
}, []);
178+
106179
return (
107180
<div
108181
aria-hidden

0 commit comments

Comments
 (0)