implement cinematic 3D entrance experience and initialize core app layout components

This commit is contained in:
Alaguraj0361 2026-07-21 22:55:54 +05:30
parent d2ec0e574d
commit 25064ad8f6
6 changed files with 182 additions and 36 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -326,13 +326,13 @@ export default function ContactPage() {
</div>
<div className="space-y-1">
{[
{ day: 'Friday', time: '5:00 10:00 pm' },
{ day: 'Saturday', time: '12:00 10:00 pm' },
{ day: 'Sunday', time: '12:00 9:00 pm' },
{ day: 'Monday', time: '4:00 10:00 pm' },
{ day: 'Tuesday', time: 'Closed', closed: true },
{ day: 'Wednesday', time: '4:30 10:00 pm' },
{ day: 'Thursday', time: '4:00 10:00 pm' },
{ day: 'Friday', time: '5:00 10:00 pm' },
{ day: 'Saturday', time: '12:00 10:00 pm' },
{ day: 'Sunday', time: '12:00 9:00 pm' },
].map(({ day, time, closed }) => (
<div key={day} className="flex items-center justify-between py-2.5 border-b border-[#bc9e65]/10 last:border-0">
<span className="font-sans text-sm font-semibold text-[#e8dcb5] tracking-wide">{day}</span>

View File

@ -118,3 +118,12 @@ body {
display: none !important;
}
}
/* Scrollbar hiding utility */
.scrollbar-none::-webkit-scrollbar {
display: none;
}
.scrollbar-none {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

View File

@ -86,11 +86,19 @@ function CinematicFlight({ progress }: { progress: number }) {
const { width, height } = useThree((state) => state.viewport);
// Compute baseScale to prevent clipping of "MY DOSA PLACE" sign on portrait/mobile viewports
const isPortrait = width < height;
const baseScale = 1.0; // Keep full height, no empty spaces at top and bottom
// Compute facade dimensions based on dusk photo aspect ratio
const aspect = 1920 / 864;
// Use dynamically compressed aspect ratio on mobile to fit the sign width responsive to screen width
const aspect = isPortrait ? Math.max(1.7, Math.min(1.95, (width / height) * 3.8)) : 1920 / 864;
const planeH = height;
const planeW = planeH * aspect;
// Compute horizontal offset to shift image to left on mobile so text is perfectly centered/visible
const offsetX = isPortrait ? -planeW * 0.025 : 0;
// Door dimensions matching facade coordinate percentages
const dw = 0.04 * planeW;
const dh = 0.42 * planeH;
@ -112,14 +120,18 @@ function CinematicFlight({ progress }: { progress: number }) {
// 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;
// Scale goes from baseScale to 6.5
const targetExtScale = baseScale + progress * (6.5 - baseScale);
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);
const targetY = -doorY * (currentScale - baseScale);
extGroupRef.current.position.y = THREE.MathUtils.lerp(extGroupRef.current.position.y, targetY, 0.08);
// Interpolate horizontal offset from offsetX to 0 as progress goes from 0 to 1
const targetX = offsetX * (1.0 - progress);
extGroupRef.current.position.x = THREE.MathUtils.lerp(extGroupRef.current.position.x, targetX, 0.08);
}
// 2. Door opening rotation (swing inward Y-axis)
@ -206,7 +218,7 @@ function CinematicFlight({ progress }: { progress: number }) {
<directionalLight position={[0, 5, 5]} intensity={1.5} />
{/* Plane 1: Storefront Facade & 3D Swinging Door Panels (Z = 0.02) */}
<group ref={extGroupRef} position={[0, 0, 0.02]}>
<group ref={extGroupRef} position={[offsetX, 0, 0.02]} scale={baseScale}>
<mesh>
<planeGeometry args={[planeW, planeH]} />
<shaderMaterial
@ -393,7 +405,7 @@ export default function EntranceExperience({ onProgress }: { onProgress?: (progr
<div className="w-24 h-[1px] bg-gradient-to-r from-transparent via-[#d8c396] to-transparent mx-auto mb-3" />
<p className="font-sans text-xs sm:text-sm md:text-base text-stone-800 leading-relaxed tracking-wide font-medium text-center">
A royal banquet served on fresh banana leavescrisp ghee dosas, slow-simmered sambar, and chutneys hand-ground fresh every morning. 100% pure vegetarian.
A royal banquet of crisp ghee dosas, slow-simmered sambar, and chutneys hand-ground fresh every morning. 100% pure vegetarian.
</p>
</motion.div>
)}

View File

@ -156,21 +156,6 @@ export default function Footer() {
</div>
<div className="flex flex-col gap-2 font-sans text-sm text-stone-300 tracking-wider">
{/* Friday */}
<div className="flex justify-between items-center select-text">
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Friday</span>
<span>510 pm</span>
</div>
{/* Saturday */}
<div className="flex justify-between items-center select-text">
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Saturday</span>
<span>1210 pm</span>
</div>
{/* Sunday */}
<div className="flex justify-between items-center select-text">
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Sunday</span>
<span>129 pm</span>
</div>
{/* Monday */}
<div className="flex justify-between items-center select-text">
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Monday</span>
@ -197,6 +182,21 @@ export default function Footer() {
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Thursday</span>
<span>410 pm</span>
</div>
{/* Friday */}
<div className="flex justify-between items-center select-text">
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Friday</span>
<span>510 pm</span>
</div>
{/* Saturday */}
<div className="flex justify-between items-center select-text">
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Saturday</span>
<span>1210 pm</span>
</div>
{/* Sunday */}
<div className="flex justify-between items-center select-text">
<span className="text-[#bc9e65] font-semibold uppercase tracking-widest">Sunday</span>
<span>129 pm</span>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
"use client";
import { motion } from "framer-motion";
import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image";
import { useState } from "react";
import AnimatedSection from "@/components/AnimatedSection";
@ -135,6 +135,7 @@ const MENU_ITEMS = [
export default function MenuPageContent() {
const [activeTab, setActiveTab] = useState("classics");
const [isPopupOpen, setIsPopupOpen] = useState(false);
// Map tab ids → item tag values
const TAB_TAG_MAP: Record<string, string> = {
@ -166,23 +167,25 @@ export default function MenuPageContent() {
</div>
{/* Section Title */}
<div className="flex flex-col items-center mb-10">
<div className="flex items-center gap-6">
<span className="text-[#bc9e65]"></span>
<h2 className="font-serif text-3xl font-bold text-[#0f3f26] uppercase tracking-widest">MY DOSA SPECIALTIES</h2>
<span className="text-[#bc9e65] scale-x-[-1]"></span>
<div className="flex flex-col items-center mb-10 w-full px-4">
<div className="flex items-center gap-4 justify-center flex-wrap">
<span className="text-[#bc9e65] hidden sm:inline-block"></span>
<h2 className="font-serif text-2xl sm:text-3xl font-bold text-[#0f3f26] uppercase tracking-widest text-center leading-tight">MY DOSA SPECIALTIES</h2>
<span className="text-[#bc9e65] scale-x-[-1] hidden sm:inline-block"></span>
</div>
<div className="flex items-center gap-3 mt-3 text-sm font-semibold tracking-wide text-stone-600">
<span className="w-8 h-[1px] bg-[#bc9e65]/60"></span>
<div className="flex items-center gap-3 mt-3 text-xs sm:text-sm font-semibold tracking-wide text-stone-600 justify-center">
<span className="w-6 sm:w-8 h-[1px] bg-[#bc9e65]/60"></span>
Crispy <span className="text-[#bc9e65] text-xs"></span> Flavorful <span className="text-[#bc9e65] text-xs"></span> Authentic
<span className="w-8 h-[1px] bg-[#bc9e65]/60"></span>
<span className="w-6 sm:w-8 h-[1px] bg-[#bc9e65]/60"></span>
</div>
</div>
{/* Tabs */}
<AnimatedSection variant="fadeUp" delay={0}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-16 relative z-10">
<div className="flex flex-wrap items-center gap-2 justify-center">
{/* Desktop View (wrapped categories) */}
<div className="hidden md:flex flex-wrap items-center gap-2 justify-center">
{TABS.map((tab) => {
const isActive = activeTab === tab.id;
const isAllItems = tab.id === "all";
@ -216,7 +219,7 @@ export default function MenuPageContent() {
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`relative flex items-center gap-3 px-2 whitespace-nowrap transition-all duration-300 font-serif font-bold text-[11px] uppercase tracking-wider rounded-[20px]
className={`relative flex items-center gap-3 px-2.5 whitespace-nowrap transition-all duration-300 font-serif font-bold text-[11px] uppercase tracking-wider rounded-[20px]
${isActive
? "bg-[#0a4a2b] text-white border-[3px] border-[#bc9e65] py-2 shadow-[0_4px_15px_rgba(188,158,101,0.3)] z-10"
: "bg-[#fdfcf9] text-[#0f3f26] border border-[#eaddc4] py-2.5 hover:bg-white hover:shadow-md"
@ -235,6 +238,45 @@ export default function MenuPageContent() {
)
})}
</div>
{/* Mobile View: Single Category Pill + Distinct 'VIEW ALL' Trigger */}
<div className="md:hidden flex justify-center w-full px-4">
<div className="flex items-center justify-between w-full max-w-[310px] bg-white border-2 border-[#bc9e65] rounded-full pl-5 pr-2 py-1.5 shadow-lg">
{/* Left: Selected Category Details */}
<div className="flex items-center gap-2.5">
{activeTab === "all" ? (
<div className="text-[#0a4a2b]">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="14" width="7" height="7" rx="1.5" />
<rect x="3" y="14" width="7" height="7" rx="1.5" />
</svg>
</div>
) : (
<div className="w-6 h-6 flex items-center justify-center">
<Image src="/images/menu/tab-icon.png" alt="" width={22} height={22} className="object-contain" />
</div>
)}
<span className="font-serif font-bold text-xs tracking-wider text-[#0f3f26] uppercase">
{TABS.find((t) => t.id === activeTab)?.label}
</span>
</div>
{/* Right: "VIEW ALL" Trigger Button */}
<button
onClick={() => setIsPopupOpen(true)}
className="flex items-center gap-1 bg-[#0a4a2b] text-white border border-[#bc9e65] rounded-full px-3.5 py-2 text-[9px] font-serif font-bold tracking-widest hover:bg-[#073820] active:scale-95 transition-all shadow-sm"
>
<span>VIEW ALL</span>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
</div>
</div>
</div>
</AnimatedSection>
@ -312,6 +354,89 @@ export default function MenuPageContent() {
</div>
</div>
{/* Categories Choice Popup Modal for Mobile View */}
<AnimatePresence>
{isPopupOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-md p-4"
onClick={() => setIsPopupOpen(false)}
>
<motion.div
initial={{ scale: 0.9, y: 20, opacity: 0 }}
animate={{ scale: 1, y: 0, opacity: 1 }}
exit={{ scale: 0.9, y: 20, opacity: 0 }}
transition={{ type: "spring", duration: 0.45 }}
className="relative w-full max-w-sm bg-[#faf7f2] border-2 border-double border-[#bc9e65] rounded-3xl p-6 shadow-2xl overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* Gold decorative corners inside */}
<div className="absolute top-2 left-2 right-2 bottom-2 border border-[#bc9e65]/15 pointer-events-none rounded-[22px]" />
{/* Close Button */}
<button
onClick={() => setIsPopupOpen(false)}
className="absolute top-4 right-4 text-stone-500 hover:text-[#0f3f26] p-2 transition-colors z-30"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
{/* Title */}
<div className="flex flex-col items-center mb-5 mt-2">
<h4 className="font-serif text-base font-bold text-[#0f3f26] uppercase tracking-widest">Select Category</h4>
<div className="w-16 h-[1px] bg-[#bc9e65] mt-1.5" />
</div>
{/* Grid of Categories */}
<div className="grid grid-cols-2 gap-2.5 max-h-[60vh] overflow-y-auto scrollbar-none pr-1">
{TABS.map((tab) => {
const isActive = activeTab === tab.id;
const isAllItems = tab.id === "all";
return (
<button
key={tab.id}
onClick={() => {
setActiveTab(tab.id);
setIsPopupOpen(false);
}}
className={`flex items-center gap-2.5 px-3 py-2.5 rounded-xl transition-all duration-300 font-serif font-bold text-[10px] uppercase tracking-wider text-left border
${isActive
? "bg-[#0a4a2b] text-white border-[#bc9e65] shadow-[0_4px_12px_rgba(10,74,43,0.2)]"
: "bg-white text-[#0f3f26] border-[#eaddc4] hover:bg-stone-50"
}`}
>
<div className="shrink-0">
{isAllItems ? (
<div className={`w-7 h-7 flex items-center justify-center rounded-full ${isActive ? 'text-[#bc9e65]' : 'text-[#0a4a2b]'}`}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="14" width="7" height="7" rx="1.5" />
<rect x="3" y="14" width="7" height="7" rx="1.5" />
</svg>
</div>
) : (
<div className="w-7 h-7 flex items-center justify-center rounded-full bg-transparent">
<Image src="/images/menu/tab-icon.png" alt="" width={24} height={24} className="object-contain" />
</div>
)}
</div>
<span className="truncate">{tab.label}</span>
</button>
);
})}
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}