Initial commit: Shahi Kitchen premium website
- Royal cream + gold theme - Playful animated hero with chef mascot - Advanced menu with sidebar + video hover - Multilingual support (EN, SV, HI, UR) - Cart system with WhatsApp ordering - Real restaurant photos integration - Responsive design with proper navbar
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
'use client';
|
||||
|
||||
import { Canvas, useFrame } from '@react-three/fiber';
|
||||
import { OrbitControls, Environment } from '@react-three/drei';
|
||||
import { Suspense, useMemo, useRef } from 'react';
|
||||
import * as THREE from 'three';
|
||||
|
||||
// ================== SMOKE PARTICLES ==================
|
||||
function SmokeParticles({ count = 120 }: { count?: number }) {
|
||||
const pointsRef = useRef<THREE.Points>(null!);
|
||||
|
||||
const particles = useMemo(() => {
|
||||
const positions = new Float32Array(count * 3);
|
||||
const velocities = new Float32Array(count * 3);
|
||||
const ages = new Float32Array(count);
|
||||
const sizes = new Float32Array(count);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const i3 = i * 3;
|
||||
|
||||
// Start around the top of the gravy
|
||||
positions[i3 + 0] = (Math.random() - 0.5) * 2.2;
|
||||
positions[i3 + 1] = 0.6 + Math.random() * 0.3;
|
||||
positions[i3 + 2] = (Math.random() - 0.5) * 2.0;
|
||||
|
||||
velocities[i3 + 0] = (Math.random() - 0.5) * 0.008;
|
||||
velocities[i3 + 1] = 0.012 + Math.random() * 0.018;
|
||||
velocities[i3 + 2] = (Math.random() - 0.5) * 0.008;
|
||||
|
||||
ages[i] = Math.random() * 3.5;
|
||||
sizes[i] = 0.12 + Math.random() * 0.18;
|
||||
}
|
||||
|
||||
return { positions, velocities, ages, sizes };
|
||||
}, [count]);
|
||||
|
||||
useFrame((state, delta) => {
|
||||
const points = pointsRef.current;
|
||||
if (!points) return;
|
||||
|
||||
const pos = points.geometry.attributes.position as THREE.BufferAttribute;
|
||||
const posArray = pos.array as Float32Array;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const i3 = i * 3;
|
||||
|
||||
// Age and reset
|
||||
particles.ages[i] += delta * 0.9;
|
||||
|
||||
if (particles.ages[i] > 3.8) {
|
||||
// Respawn at the top of the gravy
|
||||
particles.ages[i] = 0;
|
||||
posArray[i3 + 0] = (Math.random() - 0.5) * 2.1;
|
||||
posArray[i3 + 1] = 0.55 + Math.random() * 0.15;
|
||||
posArray[i3 + 2] = (Math.random() - 0.5) * 1.9;
|
||||
particles.velocities[i3 + 0] = (Math.random() - 0.5) * 0.009;
|
||||
particles.velocities[i3 + 1] = 0.014 + Math.random() * 0.02;
|
||||
} else {
|
||||
// Rise with some turbulence
|
||||
posArray[i3 + 0] += particles.velocities[i3 + 0] + Math.sin(state.clock.elapsedTime * 1.5 + i) * 0.002;
|
||||
posArray[i3 + 1] += particles.velocities[i3 + 1];
|
||||
posArray[i3 + 2] += particles.velocities[i3 + 2] + Math.cos(state.clock.elapsedTime * 1.2 + i) * 0.002;
|
||||
|
||||
// Slow down as it rises
|
||||
particles.velocities[i3 + 1] *= 0.992;
|
||||
}
|
||||
}
|
||||
|
||||
pos.needsUpdate = true;
|
||||
});
|
||||
|
||||
const geometry = useMemo(() => {
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(particles.positions, 3));
|
||||
return geo;
|
||||
}, [particles.positions]);
|
||||
|
||||
return (
|
||||
<points ref={pointsRef} geometry={geometry}>
|
||||
<pointsMaterial
|
||||
size={0.22}
|
||||
color="#f4e9d8"
|
||||
transparent
|
||||
opacity={0.32}
|
||||
depthWrite={false}
|
||||
sizeAttenuation={true}
|
||||
/>
|
||||
</points>
|
||||
);
|
||||
}
|
||||
|
||||
// ================== BOWL ==================
|
||||
function Bowl() {
|
||||
return (
|
||||
<group>
|
||||
{/* Outer bowl */}
|
||||
<mesh position={[0, -0.25, 0]} castShadow receiveShadow>
|
||||
<cylinderGeometry args={[2.35, 1.95, 1.95, 72, 1, true]} />
|
||||
<meshPhongMaterial
|
||||
color="#2f2723"
|
||||
shininess={45}
|
||||
specular="#3a2f2a"
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* Inner bowl wall */}
|
||||
<mesh position={[0, -0.25, 0]} castShadow receiveShadow>
|
||||
<cylinderGeometry args={[2.1, 1.72, 1.85, 72, 1, true]} />
|
||||
<meshPhongMaterial
|
||||
color="#1f1a17"
|
||||
shininess={25}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* Bowl bottom inside */}
|
||||
<mesh position={[0, -1.15, 0]} receiveShadow>
|
||||
<cylinderGeometry args={[1.72, 1.72, 0.12, 72]} />
|
||||
<meshPhongMaterial color="#1f1a17" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
// ================== GRAVY ==================
|
||||
function Gravy() {
|
||||
return (
|
||||
<group>
|
||||
{/* Main thick gravy */}
|
||||
<mesh position={[0, 0.05, 0]} receiveShadow>
|
||||
<cylinderGeometry args={[2.0, 1.65, 1.35, 72]} />
|
||||
<meshPhongMaterial
|
||||
color="#d46f2e"
|
||||
shininess={95}
|
||||
specular="#ffe8c4"
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* Creamy top layer */}
|
||||
<mesh position={[0, 0.55, 0]}>
|
||||
<cylinderGeometry args={[1.92, 1.62, 0.42, 72]} />
|
||||
<meshPhongMaterial
|
||||
color="#f0a45f"
|
||||
shininess={120}
|
||||
specular="#fff4d9"
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* Glossy highlights layer */}
|
||||
<mesh position={[0, 0.68, 0]}>
|
||||
<cylinderGeometry args={[1.78, 1.55, 0.18, 72]} />
|
||||
<meshPhongMaterial
|
||||
color="#f8c48a"
|
||||
shininess={140}
|
||||
specular="#ffffff"
|
||||
transparent
|
||||
opacity={0.65}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
// ================== CHICKEN PIECES ==================
|
||||
function ChickenPieces() {
|
||||
const pieces = [
|
||||
{ pos: [0.6, 0.45, 0.1], rot: [0.6, 1.2, 0.3], scale: 1.0 },
|
||||
{ pos: [-0.75, 0.38, 0.55], rot: [-0.4, -0.9, 0.5], scale: 0.95 },
|
||||
{ pos: [0.15, 0.52, -0.85], rot: [0.9, 0.4, -0.6], scale: 1.05 },
|
||||
{ pos: [-0.55, 0.42, -0.4], rot: [-0.7, 1.6, 0.2], scale: 0.9 },
|
||||
{ pos: [0.9, 0.35, -0.55], rot: [0.3, -1.1, -0.4], scale: 0.98 },
|
||||
{ pos: [-0.2, 0.48, 0.75], rot: [-0.5, 0.7, 0.8], scale: 0.92 },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{pieces.map((p, i) => (
|
||||
<group key={i} position={p.pos as [number, number, number]} rotation={p.rot as [number, number, number]} scale={p.scale}>
|
||||
{/* Main chicken body */}
|
||||
<mesh castShadow>
|
||||
<capsuleGeometry args={[0.42, 0.65, 8]} />
|
||||
<meshPhongMaterial
|
||||
color="#b84f25"
|
||||
shininess={55}
|
||||
specular="#3a1f14"
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* Extra volume */}
|
||||
<mesh position={[0.12, 0.08, -0.1]} castShadow>
|
||||
<sphereGeometry args={[0.38]} />
|
||||
<meshPhongMaterial color="#a64520" shininess={40} />
|
||||
</mesh>
|
||||
</group>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ================== HERBS & SPICES ==================
|
||||
function Toppings() {
|
||||
return (
|
||||
<>
|
||||
{/* Green herbs */}
|
||||
{Array.from({ length: 18 }).map((_, i) => {
|
||||
const angle = i * 0.7 + (i % 3) * 0.3;
|
||||
const radius = 0.55 + (i % 4) * 0.22;
|
||||
return (
|
||||
<mesh
|
||||
key={`herb-${i}`}
|
||||
position={[
|
||||
Math.cos(angle) * radius,
|
||||
0.82,
|
||||
Math.sin(angle) * radius * 0.9
|
||||
]}
|
||||
rotation={[Math.random() - 0.5, i, Math.random() - 0.5]}
|
||||
>
|
||||
<planeGeometry args={[0.22, 0.09]} />
|
||||
<meshPhongMaterial
|
||||
color="#2d5c3f"
|
||||
side={THREE.DoubleSide}
|
||||
transparent
|
||||
opacity={0.85}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Small spice bits */}
|
||||
{Array.from({ length: 26 }).map((_, i) => (
|
||||
<mesh
|
||||
key={`spice-${i}`}
|
||||
position={[
|
||||
(Math.random() - 0.5) * 2.6,
|
||||
0.78 + Math.random() * 0.12,
|
||||
(Math.random() - 0.5) * 2.3
|
||||
]}
|
||||
>
|
||||
<sphereGeometry args={[0.035 + Math.random() * 0.025]} />
|
||||
<meshPhongMaterial color={i % 4 === 0 ? "#8c3a1f" : "#3a2a1f"} />
|
||||
</mesh>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ================== MAIN MODEL ==================
|
||||
function ButterChickenModel() {
|
||||
return (
|
||||
<group>
|
||||
<Bowl />
|
||||
<Gravy />
|
||||
<ChickenPieces />
|
||||
<Toppings />
|
||||
|
||||
{/* Rising Smoke */}
|
||||
<SmokeParticles count={95} />
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
// ================== MAIN EXPORT ==================
|
||||
export default function ButterChicken3D() {
|
||||
return (
|
||||
<div className="w-full h-full min-h-[420px] rounded-2xl overflow-hidden bg-[#F2EDE4] relative">
|
||||
<Canvas
|
||||
camera={{ position: [0, 3.8, 8.5], fov: 40 }}
|
||||
style={{ background: 'transparent' }}
|
||||
gl={{
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
preserveDrawingBuffer: true,
|
||||
toneMapping: THREE.ACESFilmicToneMapping,
|
||||
toneMappingExposure: 1.05,
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
{/* Warm restaurant lighting */}
|
||||
<ambientLight intensity={0.28} color="#fff4e6" />
|
||||
|
||||
<directionalLight
|
||||
position={[7, 13, 5]}
|
||||
intensity={1.85}
|
||||
color="#fff0d0"
|
||||
castShadow
|
||||
/>
|
||||
|
||||
<directionalLight
|
||||
position={[-7, 5, -8]}
|
||||
intensity={0.95}
|
||||
color="#ffe8c4"
|
||||
/>
|
||||
|
||||
<pointLight position={[-3, 4, 7]} intensity={0.7} color="#fff8e7" />
|
||||
|
||||
<ButterChickenModel />
|
||||
|
||||
<Environment preset="apartment" />
|
||||
</Suspense>
|
||||
|
||||
<OrbitControls
|
||||
enablePan={false}
|
||||
enableZoom={true}
|
||||
minDistance={4.5}
|
||||
maxDistance={13}
|
||||
minPolarAngle={Math.PI * 0.18}
|
||||
maxPolarAngle={Math.PI * 0.82}
|
||||
enableDamping
|
||||
dampingFactor={0.12}
|
||||
autoRotate={false}
|
||||
/>
|
||||
</Canvas>
|
||||
|
||||
<div className="absolute bottom-3 right-3 text-[10px] text-[#8A8478] tracking-widest pointer-events-none">
|
||||
DRAG TO ROTATE • SCROLL TO ZOOM
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user