Compare commits
21 Commits
79d32a9e83
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eae6e5094 | ||
|
|
e40faf0418 | ||
|
|
a2ce7c7b49 | ||
|
|
ba725c328d | ||
|
|
da2f956de9 | ||
|
|
7a804e84a1 | ||
|
|
468a6d48ec | ||
|
|
a78ba9b343 | ||
|
|
241c10fe29 | ||
|
|
34ce6929e5 | ||
|
|
5c16cb788d | ||
|
|
cd156b0ff4 | ||
|
|
8c59ac9944 | ||
|
|
637e46b76f | ||
|
|
16c9d9e6f3 | ||
|
|
cdc957832f | ||
|
|
65473392ea | ||
|
|
4187c591ad | ||
|
|
5fc14ba492 | ||
|
|
f50b982ae1 | ||
|
|
24b00fefcd |
@@ -6,6 +6,7 @@ npm run build
|
|||||||
|
|
||||||
# Create a deployment folder
|
# Create a deployment folder
|
||||||
echo "Preparing deployment files..."
|
echo "Preparing deployment files..."
|
||||||
|
rm -rf deploy deploy.zip
|
||||||
mkdir -p deploy
|
mkdir -p deploy
|
||||||
cp -r .next deploy/
|
cp -r .next deploy/
|
||||||
cp -r public deploy/
|
cp -r public deploy/
|
||||||
|
|||||||
5
note.md
5
note.md
@@ -19,4 +19,7 @@
|
|||||||
- To be built
|
- To be built
|
||||||
|
|
||||||
## Step 5: Documents
|
## Step 5: Documents
|
||||||
- To be built
|
- To be built
|
||||||
|
|
||||||
|
|
||||||
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
2999
package-lock.json
generated
2999
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -12,13 +12,14 @@
|
|||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@react-three/drei": "^10.7.7",
|
"@react-three/drei": "^10.7.7",
|
||||||
"@react-three/fiber": "^9.6.1",
|
"@react-three/fiber": "^9.6.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.4.0",
|
||||||
|
"glob": "^13.0.6",
|
||||||
"jspdf": "^4.2.1",
|
"jspdf": "^4.2.1",
|
||||||
"jspdf-autotable": "^5.0.7",
|
"jspdf-autotable": "^5.0.8",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
"next": "16.2.4",
|
"next": "^16.2.9",
|
||||||
"react": "19.2.4",
|
"react": "^19.2.7",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "^19.2.7",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"three": "^0.184.0"
|
"three": "^0.184.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { createServer } = require('http')
|
const { createServer } = require('http')
|
||||||
const { parse } = require('url')
|
const { parse } = require('url')
|
||||||
|
|
||||||
const next = require('next')
|
const next = require('next')
|
||||||
|
|
||||||
const dev = process.env.NODE_ENV !== 'production'
|
const dev = process.env.NODE_ENV !== 'production'
|
||||||
@@ -16,4 +17,4 @@ app.prepare().then(() => {
|
|||||||
if (err) throw err
|
if (err) throw err
|
||||||
console.log(`> Ready on http://localhost:${port}`)
|
console.log(`> Ready on http://localhost:${port}`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -998,7 +998,7 @@ function DashboardView({ stats, accounts, monthLabel = 'This Month' }: { stats:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatCard({ label, value, icon: Icon, color }: { label: string; value: number; icon: React.ElementType; color: string }) {
|
function StatCard({ label, value, icon: Icon, color }: { label: string; value: number; icon: any; color: string }) {
|
||||||
const colors: Record<string, string> = { green: 'bg-green-50 text-green-600', red: 'bg-red-50 text-red-600', blue: 'bg-blue-50 text-blue-600', purple: 'bg-purple-50 text-purple-600', slate: 'bg-slate-100 text-slate-600' };
|
const colors: Record<string, string> = { green: 'bg-green-50 text-green-600', red: 'bg-red-50 text-red-600', blue: 'bg-blue-50 text-blue-600', purple: 'bg-purple-50 text-purple-600', slate: 'bg-slate-100 text-slate-600' };
|
||||||
const showCurrency = label !== 'Transactions';
|
const showCurrency = label !== 'Transactions';
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const ICONS = [Bike, Zap, Battery, Shield, Bike, Zap, Battery, Shield];
|
|||||||
|
|
||||||
export default function ComingSoonPage() {
|
export default function ComingSoonPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Mini Game State (Memory Match)
|
// Mini Game State (Memory Match)
|
||||||
const [cards, setCards] = useState<{ id: number; Icon: any; isFlipped: boolean; isMatched: boolean }[]>([]);
|
const [cards, setCards] = useState<{ id: number; Icon: any; isFlipped: boolean; isMatched: boolean }[]>([]);
|
||||||
const [flippedIndices, setFlippedIndices] = useState<number[]>([]);
|
const [flippedIndices, setFlippedIndices] = useState<number[]>([]);
|
||||||
@@ -23,11 +23,11 @@ export default function ComingSoonPage() {
|
|||||||
startNewGame();
|
startNewGame();
|
||||||
|
|
||||||
const targetDate = new Date('2026-08-01T00:00:00').getTime();
|
const targetDate = new Date('2026-08-01T00:00:00').getTime();
|
||||||
|
|
||||||
const updateCountdown = () => {
|
const updateCountdown = () => {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
const distance = targetDate - now;
|
const distance = targetDate - now;
|
||||||
|
|
||||||
if (distance > 0) {
|
if (distance > 0) {
|
||||||
setTimeLeft({
|
setTimeLeft({
|
||||||
days: Math.floor(distance / (1000 * 60 * 60 * 24)),
|
days: Math.floor(distance / (1000 * 60 * 60 * 24)),
|
||||||
@@ -37,7 +37,7 @@ export default function ComingSoonPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateCountdown();
|
updateCountdown();
|
||||||
const interval = setInterval(updateCountdown, 1000);
|
const interval = setInterval(updateCountdown, 1000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
@@ -94,8 +94,8 @@ export default function ComingSoonPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-900 flex flex-col items-center justify-center p-4 text-center">
|
<div className="min-h-screen bg-slate-900 flex flex-col items-center justify-center p-4 text-center">
|
||||||
<div className="absolute top-6 left-6">
|
<div className="absolute top-6 left-6 z-50">
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/')}
|
onClick={() => router.push('/')}
|
||||||
className="text-slate-400 hover:text-white flex items-center gap-2 transition-colors"
|
className="text-slate-400 hover:text-white flex items-center gap-2 transition-colors"
|
||||||
>
|
>
|
||||||
@@ -107,7 +107,7 @@ export default function ComingSoonPage() {
|
|||||||
<h1 className="text-4xl sm:text-6xl font-black text-transparent bg-clip-text bg-gradient-to-r from-accent to-emerald-400 mb-6 animate-pulse">
|
<h1 className="text-4xl sm:text-6xl font-black text-transparent bg-clip-text bg-gradient-to-r from-accent to-emerald-400 mb-6 animate-pulse">
|
||||||
COMING SOON
|
COMING SOON
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* Countdown Timer */}
|
{/* Countdown Timer */}
|
||||||
<div className="flex justify-center gap-3 sm:gap-4 mb-8">
|
<div className="flex justify-center gap-3 sm:gap-4 mb-8">
|
||||||
{[
|
{[
|
||||||
@@ -128,7 +128,7 @@ export default function ComingSoonPage() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Mini Game Container */}
|
{/* Mini Game Container */}
|
||||||
<div className="bg-slate-800 p-6 rounded-3xl border border-slate-700 shadow-2xl">
|
<div className="bg-slate-800 p-6 rounded-3xl border border-slate-700 shadow-2xl z-[9999]">
|
||||||
<div className="flex justify-between items-center mb-6 text-slate-300">
|
<div className="flex justify-between items-center mb-6 text-slate-300">
|
||||||
<span className="font-semibold">Moves: <span className="text-accent">{moves}</span></span>
|
<span className="font-semibold">Moves: <span className="text-accent">{moves}</span></span>
|
||||||
{won && <span className="text-yellow-400 font-bold flex items-center gap-1"><Trophy className="w-5 h-5" /> You Won!</span>}
|
{won && <span className="text-yellow-400 font-bold flex items-center gap-1"><Trophy className="w-5 h-5" /> You Won!</span>}
|
||||||
@@ -145,11 +145,10 @@ export default function ComingSoonPage() {
|
|||||||
<button
|
<button
|
||||||
key={card.id}
|
key={card.id}
|
||||||
onClick={() => handleCardClick(index)}
|
onClick={() => handleCardClick(index)}
|
||||||
className={`aspect-square rounded-xl flex items-center justify-center text-2xl transition-all duration-300 transform ${
|
className={`aspect-square rounded-xl flex items-center justify-center text-2xl transition-all duration-300 transform ${isVisible
|
||||||
isVisible
|
? 'bg-accent/20 border-2 border-accent text-accent scale-100 rotate-0'
|
||||||
? 'bg-accent/20 border-2 border-accent text-accent scale-100 rotate-0'
|
: 'bg-slate-700 border-2 border-slate-600 hover:bg-slate-600 scale-95'
|
||||||
: 'bg-slate-700 border-2 border-slate-600 hover:bg-slate-600 scale-95'
|
} ${card.isMatched ? 'opacity-50' : ''}`}
|
||||||
} ${card.isMatched ? 'opacity-50' : ''}`}
|
|
||||||
>
|
>
|
||||||
{isVisible ? <Icon className="w-8 h-8" /> : '?'}
|
{isVisible ? <Icon className="w-8 h-8" /> : '?'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
BIN
src/app/icon.png
Normal file
BIN
src/app/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 981 KiB |
@@ -28,6 +28,8 @@ export const viewport: Viewport = {
|
|||||||
userScalable: false,
|
userScalable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
import WhatsAppFloat from "@/components/ui/WhatsAppFloat";
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
@@ -37,6 +39,7 @@ export default function RootLayout({
|
|||||||
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
||||||
<body className="min-h-screen bg-slate-50 antialiased" suppressHydrationWarning>
|
<body className="min-h-screen bg-slate-50 antialiased" suppressHydrationWarning>
|
||||||
<LayoutContent>{children}</LayoutContent>
|
<LayoutContent>{children}</LayoutContent>
|
||||||
|
<WhatsAppFloat />
|
||||||
<Toaster position="top-right" />
|
<Toaster position="top-right" />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
233
src/app/page.tsx
233
src/app/page.tsx
@@ -4,8 +4,11 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
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 AnimatedCounter from '@/components/ui/AnimatedCounter';
|
||||||
import CardIcon3D from '@/components/ui/3d/CardIcon3D';
|
import CardIcon3D from '@/components/ui/3d/CardIcon3D';
|
||||||
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';
|
||||||
|
import { users } from '@/data/mockData';
|
||||||
|
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -59,7 +62,7 @@ export default function LandingPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-50 font-sans selection:bg-accent selection:text-white pb-20 lg:pb-0">
|
<div className="min-h-screen bg-slate-50 font-sans selection:bg-accent selection:text-white">
|
||||||
{/* Dynamic Header */}
|
{/* Dynamic Header */}
|
||||||
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${scrolled ? 'bg-white/90 backdrop-blur-md shadow-sm py-3' : 'bg-transparent py-5'}`}>
|
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${scrolled ? 'bg-white/90 backdrop-blur-md shadow-sm py-3' : 'bg-transparent py-5'}`}>
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between">
|
||||||
@@ -99,7 +102,7 @@ export default function LandingPage() {
|
|||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-accent via-emerald-400 to-accent-light">Go Electric</span> with JAIBEN.
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-accent via-emerald-400 to-accent-light">Go Electric</span> with JAIBEN.
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-slate-300 text-lg sm:text-xl mb-10 drop-shadow-lg">
|
<p className="text-slate-300 text-lg sm:text-xl mb-10 drop-shadow-lg">
|
||||||
The largest EV Tech platform enabling the Gig Economy. Rent, Rent-to-Own, or Invest in EVs. Join the sustainable mobility revolution today.
|
The largest EV Tech platform enabling the Gig Economy. Choose from Single Rent, Rent to Own, or Share an EV. Join the sustainable mobility revolution today.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row items-center lg:items-start justify-center lg:justify-start gap-4">
|
<div className="flex flex-col sm:flex-row items-center lg:items-start justify-center lg:justify-start gap-4">
|
||||||
<button
|
<button
|
||||||
@@ -117,24 +120,34 @@ export default function LandingPage() {
|
|||||||
<div className="absolute top-24 -right-24 w-96 h-96 bg-emerald-500/20 rounded-full blur-3xl mix-blend-screen pointer-events-none"></div>
|
<div className="absolute top-24 -right-24 w-96 h-96 bg-emerald-500/20 rounded-full blur-3xl mix-blend-screen pointer-events-none"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Stats Section (Overlapping Hero) */}
|
{/* Stats Section */}
|
||||||
<section className="relative z-20 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 -mt-12 mb-20">
|
<section className="py-12 lg:py-16 bg-white border-b border-slate-100">
|
||||||
<div className="bg-white rounded-2xl shadow-2xl p-6 lg:p-10 grid grid-cols-2 lg:grid-cols-4 gap-8 divide-x divide-slate-100 border border-slate-100">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center px-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 divide-x divide-slate-100">
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">500+</div>
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
<div className="text-slate-500 font-medium text-sm">Active Riders</div>
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2">
|
||||||
</div>
|
<AnimatedCounter end={500} suffix="+" />
|
||||||
<div className="text-center px-4">
|
</h3>
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">200+</div>
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">Active Riders</p>
|
||||||
<div className="text-slate-500 font-medium text-sm">EV Fleet Size</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
<div className="text-center px-4">
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2">
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">18%</div>
|
<AnimatedCounter end={200} suffix="+" />
|
||||||
<div className="text-slate-500 font-medium text-sm">Avg. Investor ROI</div>
|
</h3>
|
||||||
</div>
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">EV Fleet Size</p>
|
||||||
<div className="text-center px-4">
|
</div>
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">50k+</div>
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
<div className="text-slate-500 font-medium text-sm">Deliveries Done</div>
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2 text-accent">
|
||||||
|
<AnimatedCounter end={18} suffix="%" />
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">Avg. Rider Savings</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2">
|
||||||
|
<AnimatedCounter end={50} suffix="k+" />
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">Deliveries Done</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -150,74 +163,69 @@ export default function LandingPage() {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{/* Service 1 */}
|
{/* Service 1 */}
|
||||||
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
<div className="group bg-white rounded-3xl p-8 shadow-lg hover:shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-all duration-500 border border-slate-100 relative overflow-hidden flex flex-col h-full transform hover:-translate-y-2">
|
||||||
<div className="absolute top-0 right-0 w-32 h-32 bg-biker/10 rounded-bl-full -mr-8 -mt-8 transition-transform group-hover:scale-110"></div>
|
<div className="absolute top-0 left-0 w-64 h-64 bg-gradient-to-br from-biker/10 to-transparent rounded-full blur-3xl -ml-16 -mt-16 transition-transform duration-700 group-hover:scale-150"></div>
|
||||||
<div className="w-16 h-16 bg-biker/10 rounded-2xl flex items-center justify-center mb-6 relative z-10 text-biker group-hover:bg-biker group-hover:text-white transition-colors">
|
|
||||||
<Bike className="w-8 h-8" />
|
<div className="w-24 h-24 bg-gradient-to-br from-white to-slate-50 shadow-inner rounded-2xl flex items-center justify-center mb-8 relative z-10 border border-slate-100/50">
|
||||||
|
<CardIcon3D type="bike" />
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<h4 className="text-2xl font-extrabold text-slate-900 mb-4 relative z-10">Biker Rentals</h4>
|
||||||
|
<p className="text-slate-600 mb-8 flex-grow relative z-10 leading-relaxed">Save money on fuel. Pick the plan that works for you: Single Rent for quick trips, Rent to Own to keep the bike later, or Share an EV for flexibility.</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-white bg-biker px-6 py-3 rounded-full font-bold shadow-lg shadow-biker/30 hover:bg-biker/90 hover:shadow-biker/50 transition-all relative z-10 w-fit"
|
||||||
>
|
>
|
||||||
Login as Biker <ChevronRight className="w-5 h-5" />
|
Login as Biker <ChevronRight className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Service 2 */}
|
{/* Service 2 - FICO Partners - User will redesign this section */}
|
||||||
|
{/*
|
||||||
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
||||||
<div className="absolute top-0 right-0 w-32 h-32 bg-investor/10 rounded-bl-full -mr-8 -mt-8 transition-transform group-hover:scale-110"></div>
|
...
|
||||||
<div className="w-16 h-16 bg-investor/10 rounded-2xl flex items-center justify-center mb-6 relative z-10 text-investor group-hover:bg-investor group-hover:text-white transition-colors">
|
|
||||||
<Wallet className="w-8 h-8" />
|
|
||||||
</div>
|
|
||||||
<h4 className="text-2xl font-bold text-slate-900 mb-3 relative z-10">FICO Investment</h4>
|
|
||||||
<p className="text-slate-600 mb-8 flex-grow relative z-10">Invest in fractional EV ownership. Let us manage the fleet while you enjoy guaranteed monthly returns.</p>
|
|
||||||
{/* <button
|
|
||||||
onClick={() => handleLogin('investor@email.com')}
|
|
||||||
disabled={loading}
|
|
||||||
className="inline-flex items-center gap-2 text-investor font-bold group-hover:translate-x-2 transition-transform relative z-10"
|
|
||||||
>
|
|
||||||
Login as Investor <ChevronRight className="w-5 h-5" />
|
|
||||||
</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
|
|
||||||
{/* Service 3 */}
|
{/* Service 3 */}
|
||||||
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
<div className="group bg-white rounded-3xl p-8 shadow-lg hover:shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-all duration-500 border border-slate-100 relative overflow-hidden flex flex-col h-full transform hover:-translate-y-2">
|
||||||
<div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/10 rounded-bl-full -mr-8 -mt-8 transition-transform group-hover:scale-110"></div>
|
<div className="absolute top-0 right-0 w-64 h-64 bg-gradient-to-bl from-purple-500/10 to-transparent rounded-full blur-3xl -mr-16 -mt-16 transition-transform duration-700 group-hover:scale-150"></div>
|
||||||
<div className="w-16 h-16 bg-purple-500/10 rounded-2xl flex items-center justify-center mb-6 relative z-10 text-purple-500 group-hover:bg-purple-500 group-hover:text-white transition-colors">
|
|
||||||
<Zap className="w-8 h-8" />
|
<div className="w-24 h-24 bg-gradient-to-br from-white to-slate-50 shadow-inner rounded-2xl flex items-center justify-center mb-8 relative z-10 border border-slate-100/50">
|
||||||
|
<CardIcon3D type="swap" />
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<h4 className="text-2xl font-extrabold text-slate-900 mb-4 relative z-10">Swap Stations</h4>
|
||||||
|
<p className="text-slate-600 mb-8 flex-grow relative z-10 leading-relaxed">Never wait hours to charge your bike. Find a swap station near you, drop off your empty battery, and get a full one in under 2 minutes so you can keep riding.</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-white bg-purple-600 px-6 py-3 rounded-full font-bold shadow-lg shadow-purple-600/30 hover:bg-purple-700 hover:shadow-purple-600/50 transition-all relative z-10 w-fit"
|
||||||
>
|
>
|
||||||
Login as Swap Station <ChevronRight className="w-5 h-5" />
|
Login as Swap Station <ChevronRight className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Service 4 (Merchant/Rent to own) */}
|
{/* Service 3 (Merchant/Deliveries) */}
|
||||||
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full md:col-span-2 lg:col-span-3 lg:w-2/3 lg:mx-auto">
|
<div className="group bg-white rounded-3xl p-8 shadow-lg hover:shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-all duration-500 border border-slate-100 relative overflow-hidden flex flex-col h-full transform hover:-translate-y-2">
|
||||||
<div className="absolute top-0 right-0 w-32 h-32 bg-accent/10 rounded-bl-full -mr-8 -mt-8 transition-transform group-hover:scale-110"></div>
|
<div className="absolute top-0 right-0 w-64 h-64 bg-gradient-to-bl from-accent/10 to-transparent rounded-full blur-3xl -mr-16 -mt-16 transition-transform duration-700 group-hover:scale-150"></div>
|
||||||
<div className="flex flex-col sm:flex-row gap-6 relative z-10">
|
|
||||||
<div className="w-16 h-16 shrink-0 bg-accent/10 rounded-2xl flex items-center justify-center text-accent group-hover:bg-accent group-hover:text-white transition-colors">
|
<div className="w-24 h-24 bg-gradient-to-br from-white to-slate-50 shadow-inner rounded-2xl flex items-center justify-center mb-8 relative z-10 border border-slate-100/50">
|
||||||
<Truck className="w-8 h-8" />
|
<CardIcon3D type="merchant" />
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<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>
|
|
||||||
<button
|
|
||||||
onClick={() => router.push('/coming-soon')}
|
|
||||||
className="inline-flex items-center gap-2 text-accent font-bold group-hover:translate-x-2 transition-transform"
|
|
||||||
>
|
|
||||||
Partner With Us <ChevronRight className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h4 className="text-2xl font-extrabold text-slate-900 mb-4 relative z-10">Merchants & Deliveries</h4>
|
||||||
|
<p className="text-slate-600 mb-8 flex-grow relative z-10 leading-relaxed">Grow your delivery business with ease. Request verified EV riders whenever you need them, deliver more orders smoothly, and help keep the environment green.</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => router.push('/coming-soon')}
|
||||||
|
className="inline-flex items-center gap-2 text-white bg-accent px-6 py-3 rounded-full font-bold shadow-lg shadow-accent/30 hover:bg-accent-dark hover:shadow-accent/50 transition-all relative z-10 w-fit"
|
||||||
|
>
|
||||||
|
Partner With Us <ChevronRight className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -295,7 +303,7 @@ export default function LandingPage() {
|
|||||||
<div className="absolute -top-2 -right-2 w-8 h-8 bg-accent rounded-full flex items-center justify-center font-bold text-sm">2</div>
|
<div className="absolute -top-2 -right-2 w-8 h-8 bg-accent rounded-full flex items-center justify-center font-bold text-sm">2</div>
|
||||||
</div>
|
</div>
|
||||||
<h4 className="text-xl font-bold mb-3">Choose Your Plan</h4>
|
<h4 className="text-xl font-bold mb-3">Choose Your Plan</h4>
|
||||||
<p className="text-slate-400">Select your EV model and subscription plan (Daily, Weekly, or Rent-to-Own).</p>
|
<p className="text-slate-400">Select your EV model and preferred plan (Single Rent, Rent to Own, or Share an EV).</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative text-center">
|
<div className="relative text-center">
|
||||||
@@ -319,6 +327,91 @@ export default function LandingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Download App Section */}
|
||||||
|
<section className="py-20 bg-accent relative overflow-hidden">
|
||||||
|
<div className="absolute inset-0 bg-[url('/noise.png')] opacity-10 mix-blend-overlay"></div>
|
||||||
|
|
||||||
|
{/* Background decorative circles */}
|
||||||
|
<div className="absolute top-0 right-0 w-96 h-96 bg-white/10 rounded-full blur-3xl -mr-20 -mt-20"></div>
|
||||||
|
<div className="absolute bottom-0 left-0 w-64 h-64 bg-emerald-900/30 rounded-full blur-3xl -ml-20 -mb-20"></div>
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 flex flex-col md:flex-row items-center justify-between gap-12">
|
||||||
|
<div className="w-full md:w-1/2 text-center md:text-left text-white">
|
||||||
|
<h2 className="text-3xl md:text-5xl font-extrabold mb-6 leading-tight drop-shadow-sm">Ready to ride? <br />Download the JAIBEN App</h2>
|
||||||
|
<p className="text-white/90 text-lg mb-8 max-w-lg mx-auto md:mx-0 drop-shadow-sm">Get full access to biker rentals, swap station locations, and real-time earnings directly from your smartphone.</p>
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center md:justify-start gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => router.push('/coming-soon')}
|
||||||
|
className="flex items-center gap-3 bg-black text-white px-6 py-3 rounded-xl hover:bg-slate-900 hover:scale-105 transition-all shadow-xl shadow-black/20 w-full sm:w-auto justify-center"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 512 512" className="w-6 h-6 fill-current"><path d="M325.3 234.3L104.6 13l280.8 161.2-60.1 60.1zM47 0C34 6.8 25.3 19.2 25.3 35.3v441.3c0 16.1 8.7 28.5 21.7 35.3l256.6-256L47 0zm425.2 225.6l-58.9-34.1-65.7 64.5 65.7 64.5 60.1-34.1c18-14.3 18-46.5-1.2-60.8zM104.6 499l280.8-161.2-60.1-60.1L104.6 499z" /></svg>
|
||||||
|
<div className="flex flex-col text-left">
|
||||||
|
<span className="text-[10px] leading-none text-slate-300 font-semibold">GET IT ON</span>
|
||||||
|
<span className="text-lg font-bold leading-none mt-1">Google Play</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => router.push('/coming-soon')}
|
||||||
|
className="flex items-center gap-3 bg-black text-white px-6 py-3 rounded-xl hover:bg-slate-900 hover:scale-105 transition-all shadow-xl shadow-black/20 w-full sm:w-auto justify-center"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 384 512" className="w-6 h-6 fill-current"><path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" /></svg>
|
||||||
|
<div className="flex flex-col text-left">
|
||||||
|
<span className="text-[10px] leading-none text-slate-300 font-semibold">Download on the</span>
|
||||||
|
<span className="text-lg font-bold leading-none mt-1">App Store</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full md:w-1/2 flex justify-center">
|
||||||
|
{/* Abstract Phone Mockup */}
|
||||||
|
<div className="w-64 h-[450px] bg-slate-900 rounded-[3rem] border-[8px] border-slate-800 shadow-[0_30px_60px_rgba(0,0,0,0.4)] relative overflow-hidden flex flex-col -mb-32 md:-mb-40 transform rotate-12 hover:rotate-6 transition-transform duration-500">
|
||||||
|
<div className="h-6 w-1/2 bg-slate-800 absolute top-0 left-1/2 -translate-x-1/2 rounded-b-xl z-20"></div>
|
||||||
|
<div className="flex-1 bg-slate-50 p-4 pt-12 relative flex flex-col gap-4">
|
||||||
|
{/* Mock App Header */}
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="w-10 h-10 bg-accent rounded-full flex items-center justify-center">
|
||||||
|
<Image src="/jaiben.png" alt="Logo" width={24} height={24} className="brightness-0 invert" />
|
||||||
|
</div>
|
||||||
|
<div className="w-8 h-8 bg-slate-200 rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mock App Banner */}
|
||||||
|
<div className="w-full h-32 bg-gradient-to-br from-accent to-emerald-700 rounded-2xl p-4 shadow-sm relative overflow-hidden">
|
||||||
|
<div className="w-32 h-32 bg-white/10 rounded-full absolute -right-10 -bottom-10 blur-xl"></div>
|
||||||
|
<div className="w-1/2 h-3 bg-white/40 rounded-full mb-2 relative z-10"></div>
|
||||||
|
<div className="w-1/3 h-3 bg-white/20 rounded-full relative z-10"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mock App Grid */}
|
||||||
|
<div className="grid grid-cols-2 gap-3 flex-grow">
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-3 flex flex-col gap-2">
|
||||||
|
<div className="w-8 h-8 bg-orange-100 rounded-lg"></div>
|
||||||
|
<div className="w-full h-2 bg-slate-200 rounded-full"></div>
|
||||||
|
<div className="w-1/2 h-2 bg-slate-100 rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-3 flex flex-col gap-2">
|
||||||
|
<div className="w-8 h-8 bg-purple-100 rounded-lg"></div>
|
||||||
|
<div className="w-full h-2 bg-slate-200 rounded-full"></div>
|
||||||
|
<div className="w-1/2 h-2 bg-slate-100 rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-3 flex flex-col gap-2">
|
||||||
|
<div className="w-8 h-8 bg-blue-100 rounded-lg"></div>
|
||||||
|
<div className="w-full h-2 bg-slate-200 rounded-full"></div>
|
||||||
|
<div className="w-1/2 h-2 bg-slate-100 rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-3 flex flex-col gap-2">
|
||||||
|
<div className="w-8 h-8 bg-emerald-100 rounded-lg"></div>
|
||||||
|
<div className="w-full h-2 bg-slate-200 rounded-full"></div>
|
||||||
|
<div className="w-1/2 h-2 bg-slate-100 rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="bg-slate-950 pt-20 pb-10 border-t border-slate-800">
|
<footer className="bg-slate-950 pt-20 pb-10 border-t border-slate-800">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
@@ -345,9 +438,9 @@ export default function LandingPage() {
|
|||||||
<div>
|
<div>
|
||||||
<h4 className="text-white font-bold mb-6">Services</h4>
|
<h4 className="text-white font-bold mb-6">Services</h4>
|
||||||
<ul className="space-y-3 text-slate-400">
|
<ul className="space-y-3 text-slate-400">
|
||||||
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">EV for Deliveries</button></li>
|
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">Single Rent</button></li>
|
||||||
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">Rent to Own</button></li>
|
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">Rent to Own</button></li>
|
||||||
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">Investor Platform</button></li>
|
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">Share an EV</button></li>
|
||||||
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">Swap Stations</button></li>
|
<li><button onClick={() => router.push('/coming-soon')} className="hover:text-accent transition-colors">Swap Stations</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -364,8 +457,8 @@ export default function LandingPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-800 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
|
<div className="border-t border-slate-800 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
<p className="text-slate-500 text-sm">
|
<p className="text-slate-500 text-sm text-center md:text-left">
|
||||||
© {new Date().getFullYear()} JAIBEN Mobility Ltd. All rights reserved.
|
© {new Date().getFullYear()} JAIBEN Mobility Ltd. All rights reserved. <span className="hidden md:inline">|</span><br className="md:hidden" /> Developed by <a href="https://forenizence.com/" target="_blank" rel="noopener noreferrer" className="hover:text-accent transition-colors font-medium">Forenizence</a>
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-6 text-sm text-slate-500">
|
<div className="flex gap-6 text-sm text-slate-500">
|
||||||
<a href="#" className="hover:text-white transition-colors">Privacy Policy</a>
|
<a href="#" className="hover:text-white transition-colors">Privacy Policy</a>
|
||||||
|
|||||||
@@ -3,20 +3,56 @@ import { useRef } from 'react';
|
|||||||
import { Canvas, useFrame } from '@react-three/fiber';
|
import { Canvas, useFrame } from '@react-three/fiber';
|
||||||
import { Float, Torus, Cylinder, Box, Octahedron, MeshTransmissionMaterial } from '@react-three/drei';
|
import { Float, Torus, Cylinder, Box, Octahedron, MeshTransmissionMaterial } from '@react-three/drei';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import { useLoader } from '@react-three/fiber';
|
||||||
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
||||||
|
import { Suspense, useMemo } from 'react';
|
||||||
|
|
||||||
|
function ActualBikeIcon() {
|
||||||
|
const obj = useLoader(OBJLoader, '/uploads_files_3634924_uploads_files_850048_aerox.obj');
|
||||||
|
const group = useRef<THREE.Group>(null);
|
||||||
|
|
||||||
|
const model = useMemo(() => {
|
||||||
|
const clone = obj.clone();
|
||||||
|
|
||||||
|
const box = new THREE.Box3().setFromObject(clone);
|
||||||
|
const center = box.getCenter(new THREE.Vector3());
|
||||||
|
const size = box.getSize(new THREE.Vector3());
|
||||||
|
const maxDim = Math.max(size.x, size.y, size.z);
|
||||||
|
|
||||||
|
const scale = 2.0 / maxDim;
|
||||||
|
clone.scale.setScalar(scale);
|
||||||
|
clone.position.sub(center.multiplyScalar(scale));
|
||||||
|
clone.rotation.y = -Math.PI / 4;
|
||||||
|
|
||||||
|
const bodyMat = new THREE.MeshStandardMaterial({ color: '#00bc84', roughness: 0.2, metalness: 0.8 });
|
||||||
|
const darkMat = new THREE.MeshStandardMaterial({ color: '#111111', roughness: 0.9 });
|
||||||
|
|
||||||
|
clone.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
const b = new THREE.Box3().setFromObject(child);
|
||||||
|
const s = new THREE.Vector3();
|
||||||
|
b.getSize(s);
|
||||||
|
const minLocal = Math.min(s.x, s.y, s.z);
|
||||||
|
const maxLocal = Math.max(s.x, s.y, s.z);
|
||||||
|
if (minLocal < 0.3 && maxLocal > 0.4) child.material = darkMat;
|
||||||
|
else child.material = bodyMat;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}, [obj]);
|
||||||
|
|
||||||
function BikeIcon() {
|
|
||||||
const mesh = useRef<THREE.Mesh>(null);
|
|
||||||
useFrame((_, delta) => {
|
useFrame((_, delta) => {
|
||||||
if (mesh.current) {
|
if (group.current) {
|
||||||
mesh.current.rotation.y += delta;
|
group.current.rotation.y += delta * 1.5;
|
||||||
mesh.current.rotation.x += delta * 0.5;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Float floatIntensity={2} speed={2}>
|
<Float floatIntensity={1.5} speed={2}>
|
||||||
<Torus ref={mesh} args={[1.2, 0.4, 16, 32]} rotation={[Math.PI / 2, 0, 0]}>
|
<group ref={group}>
|
||||||
<meshStandardMaterial color="#f97316" roughness={0.1} metalness={0.8} />
|
<primitive object={model} />
|
||||||
</Torus>
|
</group>
|
||||||
</Float>
|
</Float>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -38,44 +74,68 @@ function InvestorIcon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SwapIcon() {
|
function SwapIcon() {
|
||||||
const mesh = useRef<THREE.Mesh>(null);
|
const group = useRef<THREE.Group>(null);
|
||||||
useFrame((_, delta) => {
|
useFrame((_, delta) => {
|
||||||
if (mesh.current) {
|
if (group.current) {
|
||||||
mesh.current.rotation.x += delta;
|
group.current.rotation.y += delta * 1.2;
|
||||||
mesh.current.rotation.y += delta * 1.2;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Float floatIntensity={2} speed={2}>
|
<Float floatIntensity={1.5} speed={2}>
|
||||||
<Box ref={mesh} args={[1.5, 1.5, 1.5]}>
|
<group ref={group} scale={0.7} position={[0, -0.8, 0]}>
|
||||||
<MeshTransmissionMaterial
|
{/* Main Station Body */}
|
||||||
color="#a855f7"
|
<Box args={[1.4, 2.8, 1]} position={[0, 1.4, 0]}>
|
||||||
thickness={0.5}
|
<meshStandardMaterial color="#e2e8f0" roughness={0.2} metalness={0.8} />
|
||||||
roughness={0.1}
|
</Box>
|
||||||
transmission={0.9}
|
{/* Glowing Screen */}
|
||||||
ior={1.5}
|
<Box args={[0.9, 0.5, 0.1]} position={[0, 2.2, 0.51]}>
|
||||||
/>
|
<meshStandardMaterial color="#d8b4fe" emissive="#a855f7" emissiveIntensity={0.8} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box args={[0.8, 0.8, 0.8]}>
|
{/* Battery Slots Container */}
|
||||||
<meshStandardMaterial color="#d8b4fe" emissive="#a855f7" emissiveIntensity={0.5} />
|
<Box args={[1.2, 1.2, 0.2]} position={[0, 1.0, 0.45]}>
|
||||||
</Box>
|
<meshStandardMaterial color="#1e293b" />
|
||||||
|
</Box>
|
||||||
|
{/* Battery 1 inside slot */}
|
||||||
|
<Box args={[0.3, 0.6, 0.2]} position={[-0.3, 1.2, 0.5]}>
|
||||||
|
<meshStandardMaterial color="#a855f7" />
|
||||||
|
</Box>
|
||||||
|
{/* Battery 2 inside slot */}
|
||||||
|
<Box args={[0.3, 0.6, 0.2]} position={[0.3, 0.8, 0.5]}>
|
||||||
|
<meshStandardMaterial color="#a855f7" />
|
||||||
|
</Box>
|
||||||
|
</group>
|
||||||
</Float>
|
</Float>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MerchantIcon() {
|
function MerchantIcon() {
|
||||||
const mesh = useRef<THREE.Mesh>(null);
|
const group = useRef<THREE.Group>(null);
|
||||||
useFrame((_, delta) => {
|
useFrame((_, delta) => {
|
||||||
if (mesh.current) {
|
if (group.current) {
|
||||||
mesh.current.rotation.x += delta * 0.5;
|
group.current.rotation.y += delta * 1.2;
|
||||||
mesh.current.rotation.y += delta;
|
group.current.rotation.z = Math.sin(_.clock.elapsedTime * 3) * 0.05;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Float floatIntensity={2} speed={2}>
|
<Float floatIntensity={2} speed={2.5}>
|
||||||
<Octahedron ref={mesh} args={[1.5]}>
|
<group ref={group} scale={0.9}>
|
||||||
<meshStandardMaterial color="#00bc84" roughness={0.1} metalness={0.7} />
|
{/* Main Cargo Box */}
|
||||||
</Octahedron>
|
<Box args={[1.4, 1.4, 1.4]}>
|
||||||
|
<meshStandardMaterial color="#fcd34d" roughness={0.8} />
|
||||||
|
</Box>
|
||||||
|
{/* Tape / Ribbon 1 */}
|
||||||
|
<Box args={[1.42, 0.15, 1.42]} position={[0, 0, 0]}>
|
||||||
|
<meshStandardMaterial color="#00bc84" roughness={0.3} />
|
||||||
|
</Box>
|
||||||
|
{/* Tape / Ribbon 2 */}
|
||||||
|
<Box args={[1.42, 1.42, 0.15]} position={[0, 0, 0]}>
|
||||||
|
<meshStandardMaterial color="#00bc84" roughness={0.3} />
|
||||||
|
</Box>
|
||||||
|
{/* Delivery Logo / Detail */}
|
||||||
|
<Box args={[0.5, 0.5, 0.05]} position={[0, 0.3, 0.71]}>
|
||||||
|
<meshStandardMaterial color="#ffffff" emissive="#00bc84" emissiveIntensity={0.3} />
|
||||||
|
</Box>
|
||||||
|
</group>
|
||||||
</Float>
|
</Float>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -87,7 +147,9 @@ export default function CardIcon3D({ type }: { type: 'bike' | 'investor' | 'swap
|
|||||||
<ambientLight intensity={0.5} />
|
<ambientLight intensity={0.5} />
|
||||||
<directionalLight position={[5, 5, 5]} intensity={1} />
|
<directionalLight position={[5, 5, 5]} intensity={1} />
|
||||||
<pointLight position={[-5, -5, -5]} intensity={0.5} />
|
<pointLight position={[-5, -5, -5]} intensity={0.5} />
|
||||||
{type === 'bike' && <BikeIcon />}
|
<Suspense fallback={null}>
|
||||||
|
{type === 'bike' && <ActualBikeIcon />}
|
||||||
|
</Suspense>
|
||||||
{type === 'investor' && <InvestorIcon />}
|
{type === 'investor' && <InvestorIcon />}
|
||||||
{type === 'swap' && <SwapIcon />}
|
{type === 'swap' && <SwapIcon />}
|
||||||
{type === 'merchant' && <MerchantIcon />}
|
{type === 'merchant' && <MerchantIcon />}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Suspense, useMemo, useRef } from 'react';
|
import { Suspense, useMemo, useRef } from 'react';
|
||||||
import { Canvas, useFrame, useLoader } from '@react-three/fiber';
|
import { Canvas, useFrame, useLoader, useThree } from '@react-three/fiber';
|
||||||
import {
|
import {
|
||||||
Float,
|
Float,
|
||||||
ContactShadows,
|
ContactShadows,
|
||||||
@@ -14,6 +14,9 @@ import {
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { OBJLoader } from 'three-stdlib';
|
import { OBJLoader } from 'three-stdlib';
|
||||||
|
|
||||||
|
useLoader.preload(OBJLoader, '/aerox.obj');
|
||||||
|
useLoader.preload(THREE.TextureLoader, '/jaiben.png');
|
||||||
|
|
||||||
const ACCENT = '#00bc84';
|
const ACCENT = '#00bc84';
|
||||||
const ACCENT_BRIGHT = '#10b981';
|
const ACCENT_BRIGHT = '#10b981';
|
||||||
|
|
||||||
@@ -85,17 +88,24 @@ function AeroxModel() {
|
|||||||
return clone;
|
return clone;
|
||||||
}, [obj]);
|
}, [obj]);
|
||||||
|
|
||||||
|
const { size } = useThree();
|
||||||
|
const isMobile = size.width < 1024;
|
||||||
|
const targetX = isMobile ? 0 : 2.5;
|
||||||
|
|
||||||
useFrame((state) => {
|
useFrame((state) => {
|
||||||
if (!group.current) return;
|
if (!group.current) return;
|
||||||
const t = state.clock.elapsedTime;
|
const t = state.clock.elapsedTime;
|
||||||
// Continuous turntable so all sides are appreciated.
|
// Continuous turntable so all sides are appreciated.
|
||||||
group.current.rotation.y = t * 0.4;
|
group.current.rotation.y = t * 0.4;
|
||||||
group.current.position.y = Math.sin(t * 1.5) * 0.08;
|
group.current.position.y = Math.sin(t * 1.5) * 0.08;
|
||||||
|
|
||||||
|
// Smoothly animate position X to be responsive
|
||||||
|
group.current.position.x += (targetX - group.current.position.x) * 0.1;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Float speed={1.2} rotationIntensity={0.1} floatIntensity={0.3}>
|
<Float speed={1.2} rotationIntensity={0.1} floatIntensity={0.3}>
|
||||||
<group ref={group} rotation={[0, -0.5, 0]} position={[2.5, -0.1, -1]}>
|
<group ref={group} rotation={[0, -0.5, 0]} position={[isMobile ? 0 : 2.5, -0.1, -1]}>
|
||||||
<primitive object={model} />
|
<primitive object={model} />
|
||||||
</group>
|
</group>
|
||||||
</Float>
|
</Float>
|
||||||
@@ -119,7 +129,7 @@ function Battery({ position, scale = 1, rotation = [0, 0, 0] }: { position: [num
|
|||||||
{/* Main Battery Body */}
|
{/* Main Battery Body */}
|
||||||
<mesh position={[0, 0, 0]} castShadow receiveShadow>
|
<mesh position={[0, 0, 0]} castShadow receiveShadow>
|
||||||
<boxGeometry args={[0.6, 1.2, 0.4]} />
|
<boxGeometry args={[0.6, 1.2, 0.4]} />
|
||||||
<meshStandardMaterial color="#41694cff" roughness={0.4} metalness={0.8} />
|
<meshStandardMaterial color="#1e293b" roughness={0.4} metalness={0.8} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Top Cap */}
|
{/* Top Cap */}
|
||||||
@@ -131,7 +141,7 @@ function Battery({ position, scale = 1, rotation = [0, 0, 0] }: { position: [num
|
|||||||
{/* Handle */}
|
{/* Handle */}
|
||||||
<mesh position={[0, 0.70, 0]} castShadow receiveShadow>
|
<mesh position={[0, 0.70, 0]} castShadow receiveShadow>
|
||||||
<torusGeometry args={[0.15, 0.04, 16, 32, Math.PI]} />
|
<torusGeometry args={[0.15, 0.04, 16, 32, Math.PI]} />
|
||||||
<meshStandardMaterial color="#0d7a33ff" roughness={0.2} metalness={0.9} />
|
<meshStandardMaterial color="#0d7a33" roughness={0.2} metalness={0.9} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Glowing Indicator Light */}
|
{/* Glowing Indicator Light */}
|
||||||
|
|||||||
45
src/components/ui/AnimatedCounter.tsx
Normal file
45
src/components/ui/AnimatedCounter.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
'use client';
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export default function AnimatedCounter({ end, duration = 2000, suffix = "" }: { end: number, duration?: number, suffix?: string }) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const [hasStarted, setHasStarted] = useState(false);
|
||||||
|
const elementRef = useRef<HTMLSpanElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
if (entries[0].isIntersecting && !hasStarted) {
|
||||||
|
setHasStarted(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (elementRef.current) {
|
||||||
|
observer.observe(elementRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [hasStarted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasStarted) return;
|
||||||
|
|
||||||
|
let startTime: number | null = null;
|
||||||
|
const animate = (currentTime: number) => {
|
||||||
|
if (!startTime) startTime = currentTime;
|
||||||
|
const progress = Math.min((currentTime - startTime) / duration, 1);
|
||||||
|
// Ease out cubic function for smooth deceleration
|
||||||
|
const easeOut = 1 - Math.pow(1 - progress, 3);
|
||||||
|
const currentCount = Math.floor(easeOut * end);
|
||||||
|
|
||||||
|
setCount(currentCount);
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}, [end, duration, hasStarted]);
|
||||||
|
|
||||||
|
return <span ref={elementRef}>{count}{suffix}</span>;
|
||||||
|
}
|
||||||
69
src/components/ui/WhatsAppFloat.tsx
Normal file
69
src/components/ui/WhatsAppFloat.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
'use client';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function WhatsAppFloat() {
|
||||||
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Show the tooltip after 3 seconds for better engagement
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setShowTooltip(true);
|
||||||
|
}, 3000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed bottom-6 right-6 z-50 flex flex-col items-end">
|
||||||
|
{/* Tooltip Bubble */}
|
||||||
|
<div
|
||||||
|
className={`bg-white text-slate-800 p-4 rounded-2xl shadow-[0_10px_40px_rgba(0,0,0,0.15)] mb-4 max-w-[260px] relative origin-bottom-right transition-all duration-500 border border-slate-100 ${
|
||||||
|
showTooltip ? 'scale-100 opacity-100 translate-y-0' : 'scale-50 opacity-0 translate-y-10 pointer-events-none'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowTooltip(false)}
|
||||||
|
className="absolute top-2 right-2 text-slate-400 hover:text-slate-600 transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<div className="flex gap-3 items-start">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-accent/10 flex-shrink-0 flex items-center justify-center p-2 mt-1 shadow-inner">
|
||||||
|
<Image src="/jaiben.png" alt="JAIBEN Logo" width={24} height={24} className="object-contain" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-sm text-slate-900 mb-1">JAIBEN Support</h4>
|
||||||
|
<p className="text-xs text-slate-600 leading-relaxed">Hi there! 👋 Have questions about EV rentals or partnerships? Send us a message!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Triangle pointer */}
|
||||||
|
<div className="absolute -bottom-2 right-6 w-4 h-4 bg-white border-b border-r border-slate-100 transform rotate-45"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Floating Button */}
|
||||||
|
<a
|
||||||
|
href="https://wa.me/8801789822502?text=Hi%20Jaiben%20Team!%20%F0%9F%91%8B"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="w-16 h-16 bg-[#25D366] hover:bg-[#20bd5a] text-white rounded-full flex items-center justify-center shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1 relative group"
|
||||||
|
onMouseEnter={() => setShowTooltip(true)}
|
||||||
|
>
|
||||||
|
{/* Pulsing ring */}
|
||||||
|
<span className="absolute inset-0 rounded-full border-2 border-[#25D366] animate-ping opacity-50"></span>
|
||||||
|
|
||||||
|
{/* Inside icon - JAIBEN logo instead of standard whatsapp logo */}
|
||||||
|
<div className="w-12 h-12 bg-white rounded-full flex items-center justify-center p-2 shadow-inner relative z-10 transform group-hover:scale-95 transition-transform">
|
||||||
|
<Image src="/jaiben.png" alt="JAIBEN Logo" width={28} height={28} className="object-contain" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tiny whatsapp badge to show it's whatsapp */}
|
||||||
|
<div className="absolute -top-1 -right-1 w-7 h-7 bg-white rounded-full flex items-center justify-center shadow-md z-20 border border-slate-100">
|
||||||
|
<svg viewBox="0 0 24 24" className="w-5 h-5 fill-[#25D366]">
|
||||||
|
<path d="M12.031 6.172c-3.181 0-5.767 2.586-5.768 5.766-.001 1.298.38 2.27 1.019 3.287l-.582 2.128 2.182-.573c.978.58 1.911.928 3.145.929 3.178 0 5.767-2.587 5.768-5.766.001-3.187-2.575-5.77-5.764-5.771zm3.392 8.244c-.144.405-.837.774-1.17.824-.299.045-.677.063-1.092-.069-.252-.08-.575-.187-.988-.365-1.739-.751-2.874-2.502-2.961-2.617-.087-.116-.708-.94-.708-1.793s.448-1.273.607-1.446c.159-.173.346-.217.462-.217l.332.006c.106.005.249-.04.39.298.144.347.491 1.2.534 1.287.043.087.072.188.014.304-.058.116-.087.188-.173.289l-.26.304c-.087.086-.177.18-.076.354.101.174.449.741.964 1.201.662.591 1.221.774 1.394.86s.274.072.376-.043c.101-.116.433-.506.549-.68.116-.173.231-.145.39-.087s1.011.477 1.184.564.289.13.332.202c.045.072.045.419-.1.824zm-3.423-14.416c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm.029 18.88c-1.161 0-2.305-.292-3.318-.844l-3.677.964.984-3.595c-.607-1.052-.927-2.246-.926-3.468.001-3.825 3.113-6.937 6.937-6.937 3.825.001 6.938 3.113 6.938 6.935.002 3.821-3.11 6.945-6.938 6.945z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user