"use client"; import { useEffect, useRef, useState, Suspense, useMemo } from "react"; import gsap from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import { motion, AnimatePresence } from "framer-motion"; import { Canvas, useFrame, useThree } from "@react-three/fiber"; import { useTexture } from "@react-three/drei"; import * as THREE from "three"; import Image from "next/image"; gsap.registerPlugin(ScrollTrigger); // Custom shader source for storefront door clipping const vertexShader = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `; const facadeFragmentShader = ` uniform sampler2D uTexture; uniform float uOpacity; varying vec2 vUv; void main() { vec4 col = texture2D(uTexture, vUv); // Discard double doors in center of storefront if (vUv.x >= 0.468 && vUv.x <= 0.532 && vUv.y >= 0.15 && vUv.y <= 0.50) { discard; } gl_FragColor = vec4(col.rgb, col.a * uOpacity); } `; const leftDoorFragmentShader = ` uniform sampler2D uTexture; uniform float uOpacity; varying vec2 vUv; void main() { // Crop texture to left door leaf boundaries vec2 uv = vec2(0.468 + vUv.x * 0.032, 0.15 + vUv.y * 0.35); vec4 col = texture2D(uTexture, uv); gl_FragColor = vec4(col.rgb, col.a * uOpacity); } `; const rightDoorFragmentShader = ` uniform sampler2D uTexture; uniform float uOpacity; varying vec2 vUv; void main() { // Crop texture to right door leaf boundaries vec2 uv = vec2(0.50 + vUv.x * 0.032, 0.15 + vUv.y * 0.35); vec4 col = texture2D(uTexture, uv); gl_FragColor = vec4(col.rgb, col.a * uOpacity); } `; // Helper notifier to hide CSS loader once textures resolve function LoaderNotifier({ onLoad }: { onLoad: () => void }) { useEffect(() => { onLoad(); }, [onLoad]); return null; } // 3D Cinematic flight renderer function CinematicFlight({ progress }: { progress: number }) { const exteriorTex = useTexture("/images/my_dosa_place_exterior_webgl.jpg"); const interiorTex = useTexture("/images/my_dosa_place_interior_webgl.jpg"); const foodTex = useTexture("/images/my_dosa_place_food_webgl.jpg"); const leftDoorRef = useRef(null); const rightDoorRef = useRef(null); // Set texture filtering to high quality linear useEffect(() => { [exteriorTex, interiorTex, foodTex].forEach((tex) => { tex.minFilter = THREE.LinearFilter; tex.magFilter = THREE.LinearFilter; tex.generateMipmaps = false; }); }, [exteriorTex, interiorTex, foodTex]); const { width, height } = useThree((state) => state.viewport); // Compute facade dimensions based on dusk photo aspect ratio const aspect = 1920 / 864; const planeH = height; const planeW = planeH * aspect; // Door dimensions matching facade coordinate percentages const dw = 0.032 * planeW; const dh = 0.35 * planeH; const doorY = -0.175 * planeH; const leftHingeX = -0.032 * planeW; const rightHingeX = 0.032 * planeW; const extGroupRef = useRef(null); const intMeshRef = useRef(null); const foodMeshRef = useRef(null); // Refs for materials to update opacities in the high-performance frame loop const facadeMatRef = useRef(null); const leftDoorMatRef = useRef(null); const rightDoorMatRef = useRef(null); const intMatRef = useRef(null); const foodMatRef = useRef(null); // Smooth frame loop camera zoom and door swing animations useFrame(() => { // 1. Storefront zoom and centering translation // Scale goes from 1.0 to 5.5 const targetExtScale = 1.0 + progress * 4.5; if (extGroupRef.current) { extGroupRef.current.scale.setScalar(THREE.MathUtils.lerp(extGroupRef.current.scale.x, targetExtScale, 0.08)); // Pan Y upward to center the doors as we scale const currentScale = extGroupRef.current.scale.x; const targetY = -doorY * (currentScale - 1.0); extGroupRef.current.position.y = THREE.MathUtils.lerp(extGroupRef.current.position.y, targetY, 0.08); } // 2. Door opening rotation (swing inward Y-axis) let doorProgress = 0; if (progress >= 0.08 && progress <= 0.25) { doorProgress = (progress - 0.08) / 0.17; } else if (progress > 0.25) { doorProgress = 1; } const angle = (Math.PI / 1.8) * doorProgress; if (leftDoorRef.current) { leftDoorRef.current.rotation.y = THREE.MathUtils.lerp(leftDoorRef.current.rotation.y, angle, 0.08); } if (rightDoorRef.current) { rightDoorRef.current.rotation.y = THREE.MathUtils.lerp(rightDoorRef.current.rotation.y, -angle, 0.08); } // 3. Interior Dining Hall zoom // Scale goes from 1.0 to 2.6 let intProgress = 0; if (progress > 0.08) { intProgress = Math.min(1, (progress - 0.08) / 0.62); } const targetIntScale = 1.0 + intProgress * 1.6; if (intMeshRef.current) { intMeshRef.current.scale.setScalar(THREE.MathUtils.lerp(intMeshRef.current.scale.x, targetIntScale, 0.08)); } // 4. Food Platter landing zoom // Scale goes from 0.8 to 1.15 let foodProgress = 0; if (progress > 0.58) { foodProgress = Math.min(1, (progress - 0.58) / 0.27); } const targetFoodScale = 0.8 + foodProgress * 0.35; if (foodMeshRef.current) { foodMeshRef.current.scale.setScalar(THREE.MathUtils.lerp(foodMeshRef.current.scale.x, targetFoodScale, 0.08)); } // 5. Reactive Opacity Updates inside useFrame to prevent unmounting stutters const extOpacity = progress < 0.25 ? 1 : Math.max(0, 1 - (progress - 0.25) / 0.15); let intOpacity = 0; if (progress >= 0.08 && progress <= 0.25) { intOpacity = (progress - 0.08) / 0.17; } else if (progress > 0.25 && progress < 0.60) { intOpacity = 1; } else if (progress >= 0.60 && progress <= 0.78) { intOpacity = Math.max(0, 1 - (progress - 0.60) / 0.18); } else if (progress > 0.78) { intOpacity = 0; } const foodOpacity = progress < 0.58 ? 0 : Math.min(1, (progress - 0.58) / 0.20); // Apply opacities to materials if (facadeMatRef.current) facadeMatRef.current.uniforms.uOpacity.value = extOpacity; if (leftDoorMatRef.current) leftDoorMatRef.current.uniforms.uOpacity.value = extOpacity; if (rightDoorMatRef.current) rightDoorMatRef.current.uniforms.uOpacity.value = extOpacity; if (intMatRef.current) intMatRef.current.opacity = intOpacity; if (foodMatRef.current) foodMatRef.current.opacity = foodOpacity; }); // Memoize initial shader uniforms const facadeUniforms = useMemo(() => ({ uTexture: { value: exteriorTex }, uOpacity: { value: 1 } }), [exteriorTex]); const leftDoorUniforms = useMemo(() => ({ uTexture: { value: exteriorTex }, uOpacity: { value: 1 } }), [exteriorTex]); const rightDoorUniforms = useMemo(() => ({ uTexture: { value: exteriorTex }, uOpacity: { value: 1 } }), [exteriorTex]); return ( <> {/* Plane 1: Storefront Facade & 3D Swinging Door Panels (Z = 0.02) */} {/* Left Door Hinge Pivot Group */} {/* Right Door Hinge Pivot Group */} {/* Plane 2: Pillars Dining Hall (Z = 0.01) */} {/* Plane 3: South Indian Platter Table (Z = 0.0) */} ); } export default function EntranceExperience() { const containerRef = useRef(null); const scrollIndicatorRef = useRef(null); const [scrollProgress, setScrollProgress] = useState(0); const [loaded, setLoaded] = useState(false); const [isMounted, setIsMounted] = useState(false); useEffect(() => { setIsMounted(true); }, []); // Sync GSAP pinning scrub triggers useEffect(() => { const container = containerRef.current; if (!container) return; const ctx = gsap.context(() => { const scrollIndicator = scrollIndicatorRef.current; const tl = gsap.timeline({ scrollTrigger: { trigger: container, start: "top top", end: "+=450%", scrub: 1.2, pin: true, onUpdate: (self) => { setScrollProgress(self.progress); }, }, }); // Initial fade-out of the scroll indicator if (scrollIndicator) { tl.to(scrollIndicator, { opacity: 0, y: -25, duration: 1, }); } }, container); return () => ctx.revert(); }, []); return (
{/* Traditional loading spinner overlay */} {!loaded && (
Entering My Dosa Place...
)} {/* Three.js R3F WebGL Scene */} {isMounted && (
setLoaded(true)} />
)} {/* Screen edge lighting overlays */}
{/* ----------------- OVERLAY HERO ELEMENT (OUTSIDE STATE) ----------------- */}
{/* Scroll Indicator */}
{/* ----------------- FOOD PLATTER STORYTELLING CARD (REVEALS AT END) ----------------- */} {scrollProgress > 0.75 && ( {/* Inner traditional gold frame */}
P U R E V E G E T A R I A N F E A S T

A Royal Banquet

Your table is set. Experience a royal banquet served on fresh banana leaves, where crispy ghee-roasted dosas, steaming lentil sambar, and hand-ground chutneys await you.

)} {/* Styled inline keyframes animations */}
); }