File size: 3,532 Bytes
4fb0ce9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | /**
* Liquid effect adapted from https://www.framer.com/@kevin-levron/
*/
import { useEffect, useRef, useCallback, useState } from "react";
import LiquidBackground from "../utils/liquid1.min.js";
type LiquidApp = ReturnType<typeof LiquidBackground>;
interface LiquidIntroProps {
onEnter: () => void;
}
export function LiquidIntro({ onEnter }: LiquidIntroProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const appRef = useRef<LiquidApp | null>(null);
const [fading, setFading] = useState(false);
const [ready, setReady] = useState(false);
useEffect(() => {
let disposed = false;
async function init() {
if (disposed || !canvasRef.current) return;
const app = LiquidBackground(canvasRef.current);
appRef.current = app;
app.loadImage("/liquid-dark.webp");
app.liquidPlane.material.metalness = 0.75;
app.liquidPlane.material.roughness = 0.58;
app.liquidPlane.uniforms.displacementScale.value = 5;
app.setRain(false);
// Small delay to let the first frame render
setTimeout(() => {
if (!disposed) setReady(true);
}, 300);
}
init();
return () => {
disposed = true;
if (appRef.current) {
appRef.current.dispose();
appRef.current = null;
}
};
}, []);
const handleClick = useCallback(() => {
if (fading) return;
setFading(true);
setTimeout(() => {
if (appRef.current) {
appRef.current.dispose();
appRef.current = null;
}
onEnter();
}, 600);
}, [fading, onEnter]);
return (
<div
className="absolute inset-0 z-30 cursor-pointer"
onClick={handleClick}
>
<canvas
ref={canvasRef}
className={`absolute inset-0 w-full h-full transition-opacity duration-700 ${
ready ? "opacity-100" : "opacity-0"
}`}
/>
<div
className={`absolute inset-0 flex flex-col items-center justify-end transition-opacity duration-500 pb-10 ${
fading ? "opacity-0" : ready ? "opacity-100" : "opacity-0"
}`}
>
<h1 className="text-6xl sm:text-7xl font-bold tracking-tight select-none">
<span className="text-black">LFM2.5</span>{" "}
<span className="text-gray-900">WebGPU</span>
</h1>
<p className="mt-6 text-lg text-gray-800 select-none animate-pulse-gentle">
Click anywhere to start
</p>
</div>
<div
className={`absolute bottom-4 right-4 text-right text-gray-500/70 select-none transition-opacity duration-500 flex flex-col space-y-[4px] ${
fading ? "opacity-0" : ready ? "opacity-100" : "opacity-0"
}`}
>
<a
href="https://codepen.io/soju22/pen/myVWBGa"
target="_blank"
rel="noreferrer"
className="hover:text-gray-800 transition-colors text-[14px]"
onClick={(e) => e.stopPropagation()}
>
Liquid effect by Kevin Levron
</a>
<a
href="https://creativecommons.org/licenses/by-nc-sa/4.0/"
target="_blank"
rel="noreferrer"
className="hover:text-gray-800 transition-colors text-[12px]"
onClick={(e) => e.stopPropagation()}
>
Licensed under CC BY-NC-SA 4.0
</a>
</div>
<div
className={`absolute inset-0 bg-white transition-opacity duration-600 pointer-events-none ${
fading ? "opacity-100" : "opacity-0"
}`}
/>
</div>
);
}
|