| |
| |
| |
| 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); |
|
|
| |
| 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> |
| ); |
| } |
|
|