feat: add 3D scooter model and landing page layout enhancements
This commit is contained in:
1428368
public/aerox.obj
Normal file
1428368
public/aerox.obj
Normal file
File diff suppressed because it is too large
Load Diff
349875
public/uploads_files_3634924_uploads_files_850048_aerox.obj
Normal file
349875
public/uploads_files_3634924_uploads_files_850048_aerox.obj
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ import { useRouter } from 'next/navigation';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import HeroBackground3D from '@/components/ui/3d/HeroBackground3D';
|
import HeroBackground3D from '@/components/ui/3d/HeroBackground3D';
|
||||||
import CardIcon3D from '@/components/ui/3d/CardIcon3D';
|
import CardIcon3D from '@/components/ui/3d/CardIcon3D';
|
||||||
import { users } from '@/data/mockData';
|
|
||||||
import { Zap, ArrowRight, Bike, Wallet, Leaf, ShieldCheck, MapPin, Truck, ChevronRight, BarChart3, BatteryCharging, Clock, Activity } from 'lucide-react';
|
import { Zap, ArrowRight, Bike, Wallet, Leaf, ShieldCheck, MapPin, Truck, ChevronRight, BarChart3, BatteryCharging, Clock, Activity } from 'lucide-react';
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
@@ -73,7 +72,7 @@ export default function LandingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/login')}
|
onClick={() => router.push('/coming-soon')}
|
||||||
className={`px-5 py-2.5 rounded-full font-semibold transition-all flex items-center gap-2 ${scrolled
|
className={`px-5 py-2.5 rounded-full font-semibold transition-all flex items-center gap-2 ${scrolled
|
||||||
? 'bg-accent text-white hover:bg-accent-dark shadow-md hover:shadow-lg'
|
? 'bg-accent text-white hover:bg-accent-dark shadow-md hover:shadow-lg'
|
||||||
: 'bg-white text-accent hover:bg-slate-100 shadow-xl'
|
: 'bg-white text-accent hover:bg-slate-100 shadow-xl'
|
||||||
@@ -86,28 +85,30 @@ export default function LandingPage() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative pt-32 pb-20 lg:pt-48 lg:pb-32 overflow-hidden bg-slate-900">
|
<section className="relative pt-32 pb-24 lg:pt-48 lg:pb-40 overflow-hidden bg-slate-900 min-h-[88vh] lg:min-h-screen flex items-center">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-slate-900 via-slate-800 to-accent/20 z-0" />
|
<div className="absolute inset-0 bg-gradient-to-br from-slate-900 via-slate-800 to-accent/20 z-0" />
|
||||||
<div className="absolute inset-0 bg-[url('/noise.png')] opacity-20 mix-blend-overlay z-0"></div>
|
<div className="absolute inset-0 bg-[url('/noise.png')] opacity-20 mix-blend-overlay z-0"></div>
|
||||||
<HeroBackground3D />
|
<HeroBackground3D />
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 text-center">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 w-full text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-md border border-white/20 rounded-full text-white text-sm font-medium mb-8">
|
<div className="max-w-2xl lg:max-w-xl">
|
||||||
<Leaf className="w-4 h-4 text-emerald-400" /> Bangladesh's Leading EV Platform
|
<div className="inline-flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-md border border-white/20 rounded-full text-white text-sm font-medium mb-8">
|
||||||
</div>
|
<Leaf className="w-4 h-4 text-emerald-400" /> Bangladesh's Leading EV Platform
|
||||||
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-extrabold text-white mb-6 leading-tight tracking-tight">
|
</div>
|
||||||
Ditch Petrol. <br className="hidden sm:block" />
|
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-extrabold text-white mb-6 leading-tight tracking-tight drop-shadow-2xl">
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-accent via-emerald-400 to-accent-light">Go Electric</span> with JAIBEN.
|
Ditch Petrol. <br className="hidden sm:block" />
|
||||||
</h1>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-accent via-emerald-400 to-accent-light">Go Electric</span> with JAIBEN.
|
||||||
<p className="text-slate-300 text-lg sm:text-xl max-w-2xl mx-auto mb-10">
|
</h1>
|
||||||
The largest EV Tech platform enabling the Gig Economy. Rent, Rent-to-Own, or Invest in EVs. Join the sustainable mobility revolution today.
|
<p className="text-slate-300 text-lg sm:text-xl mb-10 drop-shadow-lg">
|
||||||
</p>
|
The largest EV Tech platform enabling the Gig Economy. Rent, Rent-to-Own, or Invest in EVs. Join the sustainable mobility revolution today.
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
</p>
|
||||||
<button
|
<div className="flex flex-col sm:flex-row items-center lg:items-start justify-center lg:justify-start gap-4">
|
||||||
onClick={() => router.push('/coming-soon')}
|
<button
|
||||||
className="w-full sm:w-auto px-8 py-4 bg-accent text-white rounded-full font-bold text-lg hover:bg-accent-dark transition-all transform hover:scale-105 shadow-xl shadow-accent/30 flex items-center justify-center gap-2"
|
onClick={() => router.push('/coming-soon')}
|
||||||
>
|
className="w-full sm:w-auto px-8 py-4 bg-accent text-white rounded-full font-bold text-lg hover:bg-accent-dark transition-all transform hover:scale-105 shadow-xl shadow-accent/30 flex items-center justify-center gap-2"
|
||||||
Get Started Now <ArrowRight className="w-5 h-5" />
|
>
|
||||||
</button>
|
Get Started Now <ArrowRight className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ export default function LandingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<h4 className="text-2xl font-bold text-slate-900 mb-3 relative z-10">Biker Rentals</h4>
|
<h4 className="text-2xl font-bold text-slate-900 mb-3 relative z-10">Biker Rentals</h4>
|
||||||
<p className="text-slate-600 mb-8 flex-grow relative z-10">Save on petrol costs and rent a high-performance e-scooter for your daily commute or delivery gigs.</p>
|
<p className="text-slate-600 mb-8 flex-grow relative z-10">Save on petrol costs and rent a high-performance e-scooter for your daily commute or delivery gigs.</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/coming-soon')}
|
onClick={() => router.push('/coming-soon')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="inline-flex items-center gap-2 text-biker font-bold group-hover:translate-x-2 transition-transform relative z-10"
|
className="inline-flex items-center gap-2 text-biker font-bold group-hover:translate-x-2 transition-transform relative z-10"
|
||||||
@@ -190,7 +191,7 @@ export default function LandingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<h4 className="text-2xl font-bold text-slate-900 mb-3 relative z-10">Swap Stations</h4>
|
<h4 className="text-2xl font-bold text-slate-900 mb-3 relative z-10">Swap Stations</h4>
|
||||||
<p className="text-slate-600 mb-8 flex-grow relative z-10">Never wait to charge. Swap your depleted battery for a fully charged one in less than 2 minutes.</p>
|
<p className="text-slate-600 mb-8 flex-grow relative z-10">Never wait to charge. Swap your depleted battery for a fully charged one in less than 2 minutes.</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/coming-soon')}
|
onClick={() => router.push('/coming-soon')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="inline-flex items-center gap-2 text-purple-600 font-bold group-hover:translate-x-2 transition-transform relative z-10"
|
className="inline-flex items-center gap-2 text-purple-600 font-bold group-hover:translate-x-2 transition-transform relative z-10"
|
||||||
@@ -209,7 +210,7 @@ export default function LandingPage() {
|
|||||||
<div>
|
<div>
|
||||||
<h4 className="text-2xl font-bold text-slate-900 mb-3">Merchants & Deliveries</h4>
|
<h4 className="text-2xl font-bold text-slate-900 mb-3">Merchants & Deliveries</h4>
|
||||||
<p className="text-slate-600 mb-6 max-w-xl">Are you a delivery partner or company? Request any amount of riders for your delivery needs. Partner with us for dedicated EV riders.</p>
|
<p className="text-slate-600 mb-6 max-w-xl">Are you a delivery partner or company? Request any amount of riders for your delivery needs. Partner with us for dedicated EV riders.</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/coming-soon')}
|
onClick={() => router.push('/coming-soon')}
|
||||||
className="inline-flex items-center gap-2 text-accent font-bold group-hover:translate-x-2 transition-transform"
|
className="inline-flex items-center gap-2 text-accent font-bold group-hover:translate-x-2 transition-transform"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,65 +1,192 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useRef } from 'react';
|
|
||||||
import { Canvas, useFrame } from '@react-three/fiber';
|
|
||||||
import { Float, Box, Cylinder } from '@react-three/drei';
|
|
||||||
import * as THREE from 'three';
|
|
||||||
|
|
||||||
function StylizedScooter() {
|
import { Suspense, useMemo, useRef } from 'react';
|
||||||
|
import { Canvas, useFrame, useLoader } from '@react-three/fiber';
|
||||||
|
import {
|
||||||
|
Float,
|
||||||
|
ContactShadows,
|
||||||
|
Environment,
|
||||||
|
Lightformer,
|
||||||
|
Sparkles,
|
||||||
|
Html,
|
||||||
|
useProgress,
|
||||||
|
} from '@react-three/drei';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { OBJLoader } from 'three-stdlib';
|
||||||
|
|
||||||
|
const ACCENT = '#00bc84';
|
||||||
|
const ACCENT_BRIGHT = '#10b981';
|
||||||
|
|
||||||
|
function Loader() {
|
||||||
|
const { progress } = useProgress();
|
||||||
|
return (
|
||||||
|
<Html center>
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<div className="w-12 h-12 border-4 border-emerald-500/30 border-t-emerald-500 rounded-full animate-spin" />
|
||||||
|
<p className="text-emerald-400 text-sm font-semibold">{Math.round(progress)}%</p>
|
||||||
|
</div>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AeroxModel() {
|
||||||
|
const obj = useLoader(OBJLoader, '/uploads_files_3634924_uploads_files_850048_aerox.obj');
|
||||||
|
useLoader.preload(OBJLoader, '/uploads_files_3634924_uploads_files_850048_aerox.obj');
|
||||||
const group = useRef<THREE.Group>(null);
|
const group = useRef<THREE.Group>(null);
|
||||||
|
|
||||||
useFrame((state, delta) => {
|
const model = useMemo(() => {
|
||||||
if (group.current) {
|
const clone = obj.clone(true);
|
||||||
group.current.rotation.y += delta * 0.2;
|
// Center + reorient + scale. OBJ length is along Z; rotate so length lies along X.
|
||||||
group.current.rotation.z = Math.sin(state.clock.elapsedTime * 0.5) * 0.05;
|
const box = new THREE.Box3().setFromObject(clone);
|
||||||
}
|
const size = new THREE.Vector3();
|
||||||
|
box.getSize(size);
|
||||||
|
const center = new THREE.Vector3();
|
||||||
|
box.getCenter(center);
|
||||||
|
const maxDim = Math.max(size.x, size.y, size.z);
|
||||||
|
const scale = 3.2 / maxDim;
|
||||||
|
|
||||||
|
clone.position.sub(center.multiplyScalar(scale));
|
||||||
|
clone.scale.setScalar(scale);
|
||||||
|
// Lay the scooter along X and stand it upright (Y up).
|
||||||
|
clone.rotation.y = -Math.PI / 2;
|
||||||
|
clone.rotation.z = 0;
|
||||||
|
|
||||||
|
// Apply a cohesive, premium material across all meshes.
|
||||||
|
const bodyMat = new THREE.MeshStandardMaterial({
|
||||||
|
color: ACCENT,
|
||||||
|
roughness: 0.2,
|
||||||
|
metalness: 0.7,
|
||||||
|
});
|
||||||
|
const accentMat = new THREE.MeshStandardMaterial({
|
||||||
|
color: '#e2e8f0',
|
||||||
|
roughness: 0.1,
|
||||||
|
metalness: 0.9,
|
||||||
|
});
|
||||||
|
const tireMat = new THREE.MeshStandardMaterial({
|
||||||
|
color: '#0f172a',
|
||||||
|
roughness: 0.8,
|
||||||
|
metalness: 0.15,
|
||||||
|
});
|
||||||
|
|
||||||
|
clone.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
// Heuristic: pick material by geometry size.
|
||||||
|
const b = new THREE.Box3().setFromObject(child);
|
||||||
|
const s = new THREE.Vector3();
|
||||||
|
b.getSize(s);
|
||||||
|
const maxLocal = Math.max(s.x, s.y, s.z);
|
||||||
|
if (maxLocal < 0.35) child.material = accentMat;
|
||||||
|
else if (s.y < 0.25 && maxLocal > 0.5) child.material = tireMat;
|
||||||
|
else child.material = bodyMat;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return clone;
|
||||||
|
}, [obj]);
|
||||||
|
|
||||||
|
useFrame((state) => {
|
||||||
|
if (!group.current) return;
|
||||||
|
const t = state.clock.elapsedTime;
|
||||||
|
// Continuous turntable so all sides are appreciated.
|
||||||
|
group.current.rotation.y = t * 0.4;
|
||||||
|
group.current.position.y = Math.sin(t * 1.5) * 0.08;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Float speed={2} rotationIntensity={0.5} floatIntensity={1.5}>
|
<Float speed={1.2} rotationIntensity={0.1} floatIntensity={0.3}>
|
||||||
<group ref={group} position={[2, 0, -2]} scale={1.2} rotation={[0.2, -0.5, 0]}>
|
<group ref={group} rotation={[0, -0.5, 0]} position={[0, -0.1, 0]}>
|
||||||
{/* Main Deck */}
|
<primitive object={model} />
|
||||||
<Box args={[4, 0.3, 1]} position={[0, 0, 0]}>
|
|
||||||
<meshStandardMaterial color="#334155" roughness={0.4} metalness={0.6} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Front Wheel */}
|
|
||||||
<Cylinder args={[0.8, 0.8, 0.4, 32]} rotation={[Math.PI / 2, 0, 0]} position={[2, 0, 0]}>
|
|
||||||
<meshStandardMaterial color="#f97316" roughness={0.2} metalness={0.8} />
|
|
||||||
</Cylinder>
|
|
||||||
|
|
||||||
{/* Rear Wheel */}
|
|
||||||
<Cylinder args={[0.8, 0.8, 0.4, 32]} rotation={[Math.PI / 2, 0, 0]} position={[-2, 0, 0]}>
|
|
||||||
<meshStandardMaterial color="#f97316" roughness={0.2} metalness={0.8} />
|
|
||||||
</Cylinder>
|
|
||||||
|
|
||||||
{/* Steering Column */}
|
|
||||||
<Cylinder args={[0.1, 0.1, 3, 16]} position={[1.5, 1.5, 0]} rotation={[0, 0, -0.2]}>
|
|
||||||
<meshStandardMaterial color="#cbd5e1" roughness={0.3} metalness={0.9} />
|
|
||||||
</Cylinder>
|
|
||||||
|
|
||||||
{/* Handlebars */}
|
|
||||||
<Box args={[0.2, 1.5, 0.2]} position={[1.2, 3, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
|
||||||
<meshStandardMaterial color="#0f172a" roughness={0.8} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Accent / Battery block */}
|
|
||||||
<Box args={[1.5, 0.6, 1.1]} position={[-0.5, 0.4, 0]}>
|
|
||||||
<meshStandardMaterial color="#00bc84" emissive="#00bc84" emissiveIntensity={0.5} roughness={0.2} />
|
|
||||||
</Box>
|
|
||||||
</group>
|
</group>
|
||||||
</Float>
|
</Float>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HeroBackground3D() {
|
function Crystal({ position, color, scale = 1 }: { position: [number, number, number]; color: string; scale?: number }) {
|
||||||
|
const ref = useRef<THREE.Mesh>(null);
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (ref.current) {
|
||||||
|
ref.current.rotation.y += delta * 0.5;
|
||||||
|
ref.current.position.y = position[1] + Math.sin(state.clock.elapsedTime + position[0]) * 0.3;
|
||||||
|
}
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 pointer-events-none z-0 overflow-hidden">
|
<Float speed={3} rotationIntensity={1} floatIntensity={2}>
|
||||||
<Canvas camera={{ position: [0, 0, 8], fov: 45 }}>
|
<mesh ref={ref} position={position} scale={scale}>
|
||||||
<ambientLight intensity={0.5} />
|
<octahedronGeometry args={[0.4, 0]} />
|
||||||
<directionalLight position={[10, 10, 5]} intensity={1} />
|
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.3} roughness={0.1} metalness={0.3} flatShading />
|
||||||
<directionalLight position={[-10, -10, -5]} intensity={0.5} color="#00bc84" />
|
</mesh>
|
||||||
<StylizedScooter />
|
</Float>
|
||||||
</Canvas>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Floor() {
|
||||||
|
return (
|
||||||
|
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -1.1, 0]} receiveShadow>
|
||||||
|
<circleGeometry args={[9, 64]} />
|
||||||
|
<meshStandardMaterial color="#0f172a" roughness={0.6} metalness={0.2} transparent opacity={0.4} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Scene() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ambientLight intensity={0.4} />
|
||||||
|
<directionalLight position={[6, 8, 5]} intensity={1.4} color="#f8fafc" castShadow />
|
||||||
|
<directionalLight position={[-8, -4, -6]} intensity={0.6} color={ACCENT_BRIGHT} />
|
||||||
|
<pointLight position={[0, 3, 4]} intensity={0.8} color={ACCENT_BRIGHT} />
|
||||||
|
<spotLight position={[0, 10, 0]} angle={0.5} intensity={1.2} color="#ffffff" penumbra={1} />
|
||||||
|
|
||||||
|
<Suspense fallback={<Loader />}>
|
||||||
|
<AeroxModel />
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<Crystal position={[-3.8, 1.6, -1]} color={ACCENT_BRIGHT} scale={1.1} />
|
||||||
|
<Crystal position={[3.8, 1.2, -0.5]} color={ACCENT} scale={0.8} />
|
||||||
|
<Crystal position={[3.4, -0.8, 0.6]} color="#34d399" scale={0.6} />
|
||||||
|
<Crystal position={[-4.0, -0.4, 0.8]} color="#34d399" scale={0.7} />
|
||||||
|
|
||||||
|
<Sparkles count={90} scale={[11, 7, 7]} size={3} speed={0.4} color={ACCENT_BRIGHT} opacity={0.7} />
|
||||||
|
|
||||||
|
<ContactShadows position={[0, -1.06, 0]} opacity={0.55} scale={14} blur={2.5} far={4} color={ACCENT} />
|
||||||
|
<Floor />
|
||||||
|
|
||||||
|
<Environment resolution={256}>
|
||||||
|
<Lightformer intensity={2} position={[0, 5, 5]} scale={[10, 5, 1]} color="#ffffff" />
|
||||||
|
<Lightformer intensity={1.2} position={[-5, 2, 3]} scale={[8, 6, 1]} color={ACCENT_BRIGHT} />
|
||||||
|
<Lightformer intensity={0.8} position={[5, 1, 3]} scale={[8, 6, 1]} color="#0ea5e9" />
|
||||||
|
<Lightformer intensity={0.6} position={[0, -5, 2]} scale={[10, 5, 1]} color="#1e293b" />
|
||||||
|
</Environment>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Rig({ children }: { children: React.ReactNode }) {
|
||||||
|
useFrame((state) => {
|
||||||
|
const { camera, pointer } = state;
|
||||||
|
camera.position.x += (pointer.x * 1.4 - camera.position.x) * 0.04;
|
||||||
|
camera.position.y += (-pointer.y * 0.7 + 0.4 - camera.position.y) * 0.04;
|
||||||
|
camera.lookAt(0, 0, 0);
|
||||||
|
});
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HeroBackground3D() {
|
||||||
|
return (
|
||||||
|
<div className="absolute inset-0 z-0 overflow-hidden">
|
||||||
|
<Canvas
|
||||||
|
shadows
|
||||||
|
dpr={[1, 2]}
|
||||||
|
camera={{ position: [0, 0.4, 8], fov: 42 }}
|
||||||
|
gl={{ antialias: true, alpha: true, powerPreference: 'high-performance' }}
|
||||||
|
onCreated={({ gl }) => gl.setClearColor(0x000000, 0)}
|
||||||
|
>
|
||||||
|
<Rig>
|
||||||
|
<Scene />
|
||||||
|
</Rig>
|
||||||
|
</Canvas>
|
||||||
|
<div className="pointer-events-none absolute inset-0 bg-gradient-to-r from-slate-900/70 via-slate-900/20 to-transparent lg:from-slate-900/80 lg:via-slate-900/30 lg:to-transparent" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
52
src/components/ui/3d/Logo3D.tsx
Normal file
52
src/components/ui/3d/Logo3D.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Canvas, useFrame } from '@react-three/fiber';
|
||||||
|
import { Float, Box, Sphere } from '@react-three/drei';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
function RotatingLogo({ isScrolled }: { isScrolled: boolean }) {
|
||||||
|
const group = useRef<THREE.Group>(null);
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (group.current) {
|
||||||
|
group.current.rotation.y += delta;
|
||||||
|
group.current.rotation.x = Math.sin(state.clock.elapsedTime * 2) * 0.1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const color = isScrolled ? '#00bc84' : '#10b981';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Float speed={3} rotationIntensity={0.5} floatIntensity={2}>
|
||||||
|
<group ref={group} scale={1.2}>
|
||||||
|
{/* Abstract "J" shape for Jaiben */}
|
||||||
|
<Box args={[0.25, 1.2, 0.25]} position={[0.2, 0, 0]}>
|
||||||
|
<meshStandardMaterial color={color} roughness={0.1} metalness={0.8} />
|
||||||
|
</Box>
|
||||||
|
<Box args={[0.8, 0.25, 0.25]} position={[-0.075, -0.475, 0]}>
|
||||||
|
<meshStandardMaterial color={color} roughness={0.1} metalness={0.8} />
|
||||||
|
</Box>
|
||||||
|
<Sphere args={[0.2, 32, 32]} position={[-0.3, 0.5, 0]}>
|
||||||
|
<meshStandardMaterial color="#f97316" roughness={0.2} metalness={0.5} emissive="#f97316" emissiveIntensity={0.2} />
|
||||||
|
</Sphere>
|
||||||
|
</group>
|
||||||
|
</Float>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Logo3D({ scrolled = false, className = "w-12 h-12" }: { scrolled?: boolean, className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={`${className} pointer-events-none relative z-10 flex-shrink-0`}>
|
||||||
|
<Canvas
|
||||||
|
camera={{ position: [0, 0, 3.5], fov: 50 }}
|
||||||
|
gl={{ alpha: true, antialias: true }}
|
||||||
|
>
|
||||||
|
<ambientLight intensity={0.6} />
|
||||||
|
<directionalLight position={[2, 5, 2]} intensity={1.5} />
|
||||||
|
<directionalLight position={[-2, -2, 2]} intensity={0.5} color="#00bc84" />
|
||||||
|
<RotatingLogo isScrolled={scrolled} />
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user