Compare commits

..

25 Commits

Author SHA1 Message Date
sazzadulalambd
5eae6e5094 feat: update homepage copy and service offerings to reflect new rental plan options 2026-06-23 23:42:32 +06:00
sazzadulalambd
e40faf0418 feat: add Forenizence developer credit to footer with responsive layout adjustments 2026-06-23 23:08:00 +06:00
sazzadulalambd
a2ce7c7b49 refactor: update homepage copy, metrics, and coming-soon page z-index styling 2026-06-23 23:03:48 +06:00
sazzadulalambd
ba725c328d chore: update PWA dependencies, add server whitespace, and replace favicon with icon.png 2026-06-23 22:34:41 +06:00
sazzadulalambd
da2f956de9 chore: update package-lock.json dependencies 2026-06-23 01:09:49 +06:00
sazzadulalambd
7a804e84a1 chore: update dependencies, add glob, and configure npm settings 2026-06-23 01:08:03 +06:00
sazzadulalambd
468a6d48ec chore: downgrade project dependencies and remove package-lock.json 2026-06-23 00:40:29 +06:00
sazzadulalambd
a78ba9b343 perf: preload 3D assets and fix mesh material hex code formatting in HeroBackground3D 2026-06-23 00:36:43 +06:00
sazzadulalambd
241c10fe29 chore: downgrade react, @types/node, eslint, and typescript dependencies 2026-06-22 22:37:12 +06:00
sazzadulalambd
34ce6929e5 chore: update package-lock.json dependencies 2026-06-22 22:00:52 +06:00
sazzadulalambd
5c16cb788d chore: update project dependencies to latest versions 2026-06-22 21:52:00 +06:00
sazzadulalambd
cd156b0ff4 refactor: remove unnecessary eslint disable and reformat server configuration for consistency 2026-06-22 21:46:04 +06:00
sazzadulalambd
8c59ac9944 refactor: apply linting rules and consistent formatting to server configuration 2026-06-22 21:20:05 +06:00
sazzadulalambd
637e46b76f chore: clean up existing deployment artifacts before creating new ones 2026-06-22 21:11:58 +06:00
sazzadulalambd
16c9d9e6f3 refactor: simplify deployment script by updating file copy process and documentation 2026-06-22 21:10:23 +06:00
sazzadulalambd
cdc957832f chore: update deployment script to support Next.js standalone mode for cPanel compatibility 2026-06-22 21:01:36 +06:00
sazzadulalambd
65473392ea feat: import mock users on landing page and loosen StatCard icon type definition 2026-06-22 17:40:15 +06:00
sazzadulalambd
4187c591ad feat: add floating WhatsApp support widget to root layout 2026-06-22 17:25:51 +06:00
sazzadulalambd
5fc14ba492 feat: add app download section to landing page and improve hero 3D background responsiveness 2026-06-22 17:24:30 +06:00
sazzadulalambd
f50b982ae1 feat: redesign service cards with 3D icons, enhanced hover effects, and updated action buttons 2026-06-22 17:17:40 +06:00
sazzadulalambd
24b00fefcd feat: add AnimatedCounter component and update stats section with hero asset color adjustment 2026-06-22 17:12:33 +06:00
sazzadulalambd
79d32a9e83 style: update 3D battery model material colors and adjust handle position 2026-06-22 17:04:33 +06:00
sazzadulalambd
7d320ac4a3 feat: replace Crystal component with detailed Battery model in HeroBackground3D 2026-06-22 17:02:11 +06:00
sazzadulalambd
7976d1375a style: adjust tire material properties and update 3D model positioning in HeroBackground3D 2026-06-22 16:58:46 +06:00
sazzadulalambd
eb38a01976 feat: add 3D scooter model and landing page layout enhancements 2026-06-22 16:51:48 +06:00
20 changed files with 1780324 additions and 1819 deletions

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
legacy-peer-deps=true
fund=false
audit=false

View File

@@ -6,6 +6,7 @@ npm run build
# Create a deployment folder
echo "Preparing deployment files..."
rm -rf deploy deploy.zip
mkdir -p deploy
cp -r .next deploy/
cp -r public deploy/

View File

@@ -20,3 +20,6 @@
## Step 5: Documents
- To be built
"@ducanh2912/next-pwa": "^10.2.9",

2999
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,14 @@
"@ducanh2912/next-pwa": "^10.2.9",
"@react-three/drei": "^10.7.7",
"@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-autotable": "^5.0.7",
"jspdf-autotable": "^5.0.8",
"lucide-react": "^1.8.0",
"next": "16.2.4",
"react": "19.2.4",
"react-dom": "19.2.4",
"next": "^16.2.9",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-hot-toast": "^2.6.0",
"three": "^0.184.0"
},

1428368
public/aerox.obj Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'

View File

@@ -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 showCurrency = label !== 'Transactions';
return (

View File

@@ -94,7 +94,7 @@ export default function ComingSoonPage() {
return (
<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
onClick={() => router.push('/')}
className="text-slate-400 hover:text-white flex items-center gap-2 transition-colors"
@@ -128,7 +128,7 @@ export default function ComingSoonPage() {
</p>
{/* 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">
<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>}
@@ -145,11 +145,10 @@ export default function ComingSoonPage() {
<button
key={card.id}
onClick={() => handleCardClick(index)}
className={`aspect-square rounded-xl flex items-center justify-center text-2xl transition-all duration-300 transform ${
isVisible
? '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'
} ${card.isMatched ? 'opacity-50' : ''}`}
className={`aspect-square rounded-xl flex items-center justify-center text-2xl transition-all duration-300 transform ${isVisible
? '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'
} ${card.isMatched ? 'opacity-50' : ''}`}
>
{isVisible ? <Icon className="w-8 h-8" /> : '?'}
</button>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

BIN
src/app/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 KiB

View File

@@ -28,6 +28,8 @@ export const viewport: Viewport = {
userScalable: false,
};
import WhatsAppFloat from "@/components/ui/WhatsAppFloat";
export default function RootLayout({
children,
}: Readonly<{
@@ -37,6 +39,7 @@ export default function RootLayout({
<html lang="en" className={inter.variable} suppressHydrationWarning>
<body className="min-h-screen bg-slate-50 antialiased" suppressHydrationWarning>
<LayoutContent>{children}</LayoutContent>
<WhatsAppFloat />
<Toaster position="top-right" />
</body>
</html>

View File

@@ -4,9 +4,11 @@ import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import HeroBackground3D from '@/components/ui/3d/HeroBackground3D';
import AnimatedCounter from '@/components/ui/AnimatedCounter';
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 { users } from '@/data/mockData';
export default function LandingPage() {
const router = useRouter();
@@ -60,7 +62,7 @@ export default function LandingPage() {
};
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 */}
<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">
@@ -73,7 +75,7 @@ export default function LandingPage() {
</div>
<div className="flex items-center gap-4">
<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
? 'bg-accent text-white hover:bg-accent-dark shadow-md hover:shadow-lg'
: 'bg-white text-accent hover:bg-slate-100 shadow-xl'
@@ -86,28 +88,30 @@ export default function LandingPage() {
</header>
{/* 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-[url('/noise.png')] opacity-20 mix-blend-overlay z-0"></div>
<HeroBackground3D />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 text-center">
<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">
<Leaf className="w-4 h-4 text-emerald-400" /> Bangladesh&apos;s Leading EV Platform
</div>
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-extrabold text-white mb-6 leading-tight tracking-tight">
Ditch Petrol. <br className="hidden sm:block" />
<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>
<p className="text-slate-300 text-lg sm:text-xl max-w-2xl mx-auto mb-10">
The largest EV Tech platform enabling the Gig Economy. Rent, Rent-to-Own, or Invest in EVs. Join the sustainable mobility revolution today.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<button
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>
<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="max-w-2xl lg:max-w-xl">
<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">
<Leaf className="w-4 h-4 text-emerald-400" /> Bangladesh&apos;s Leading EV Platform
</div>
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-extrabold text-white mb-6 leading-tight tracking-tight drop-shadow-2xl">
Ditch Petrol. <br className="hidden sm:block" />
<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>
<p className="text-slate-300 text-lg sm:text-xl mb-10 drop-shadow-lg">
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>
<div className="flex flex-col sm:flex-row items-center lg:items-start justify-center lg:justify-start gap-4">
<button
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>
</div>
</div>
</div>
@@ -116,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>
</section>
{/* Stats Section (Overlapping Hero) */}
<section className="relative z-20 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 -mt-12 mb-20">
<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="text-center px-4">
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">500+</div>
<div className="text-slate-500 font-medium text-sm">Active Riders</div>
</div>
<div className="text-center px-4">
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">200+</div>
<div className="text-slate-500 font-medium text-sm">EV Fleet Size</div>
</div>
<div className="text-center px-4">
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">18%</div>
<div className="text-slate-500 font-medium text-sm">Avg. Investor ROI</div>
</div>
<div className="text-center px-4">
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">50k+</div>
<div className="text-slate-500 font-medium text-sm">Deliveries Done</div>
{/* Stats Section */}
<section className="py-12 lg:py-16 bg-white border-b border-slate-100">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 divide-x divide-slate-100">
<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={500} suffix="+" />
</h3>
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">Active Riders</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={200} suffix="+" />
</h3>
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">EV Fleet Size</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 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>
</section>
@@ -149,74 +163,69 @@ export default function LandingPage() {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{/* 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="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="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="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 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-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>
<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
onClick={() => router.push('/coming-soon')}
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" />
</button>
</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="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>
*/}
{/* 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="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="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="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-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-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>
<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
onClick={() => router.push('/coming-soon')}
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" />
</button>
</div>
{/* Service 4 (Merchant/Rent to own) */}
<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="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="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">
<Truck className="w-8 h-8" />
</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>
{/* Service 3 (Merchant/Deliveries) */}
<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-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="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="merchant" />
</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>
@@ -294,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>
<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 className="relative text-center">
@@ -318,6 +327,91 @@ export default function LandingPage() {
</div>
</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 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">
@@ -344,9 +438,9 @@ export default function LandingPage() {
<div>
<h4 className="text-white font-bold mb-6">Services</h4>
<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">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>
</ul>
</div>
@@ -363,8 +457,8 @@ export default function LandingPage() {
</div>
<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">
&copy; {new Date().getFullYear()} JAIBEN Mobility Ltd. All rights reserved.
<p className="text-slate-500 text-sm text-center md:text-left">
&copy; {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>
<div className="flex gap-6 text-sm text-slate-500">
<a href="#" className="hover:text-white transition-colors">Privacy Policy</a>

View File

@@ -3,20 +3,56 @@ import { useRef } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { Float, Torus, Cylinder, Box, Octahedron, MeshTransmissionMaterial } from '@react-three/drei';
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) => {
if (mesh.current) {
mesh.current.rotation.y += delta;
mesh.current.rotation.x += delta * 0.5;
if (group.current) {
group.current.rotation.y += delta * 1.5;
}
});
return (
<Float floatIntensity={2} speed={2}>
<Torus ref={mesh} args={[1.2, 0.4, 16, 32]} rotation={[Math.PI / 2, 0, 0]}>
<meshStandardMaterial color="#f97316" roughness={0.1} metalness={0.8} />
</Torus>
<Float floatIntensity={1.5} speed={2}>
<group ref={group}>
<primitive object={model} />
</group>
</Float>
);
}
@@ -38,44 +74,68 @@ function InvestorIcon() {
}
function SwapIcon() {
const mesh = useRef<THREE.Mesh>(null);
const group = useRef<THREE.Group>(null);
useFrame((_, delta) => {
if (mesh.current) {
mesh.current.rotation.x += delta;
mesh.current.rotation.y += delta * 1.2;
if (group.current) {
group.current.rotation.y += delta * 1.2;
}
});
return (
<Float floatIntensity={2} speed={2}>
<Box ref={mesh} args={[1.5, 1.5, 1.5]}>
<MeshTransmissionMaterial
color="#a855f7"
thickness={0.5}
roughness={0.1}
transmission={0.9}
ior={1.5}
/>
</Box>
<Box args={[0.8, 0.8, 0.8]}>
<meshStandardMaterial color="#d8b4fe" emissive="#a855f7" emissiveIntensity={0.5} />
</Box>
<Float floatIntensity={1.5} speed={2}>
<group ref={group} scale={0.7} position={[0, -0.8, 0]}>
{/* Main Station Body */}
<Box args={[1.4, 2.8, 1]} position={[0, 1.4, 0]}>
<meshStandardMaterial color="#e2e8f0" roughness={0.2} metalness={0.8} />
</Box>
{/* Glowing Screen */}
<Box args={[0.9, 0.5, 0.1]} position={[0, 2.2, 0.51]}>
<meshStandardMaterial color="#d8b4fe" emissive="#a855f7" emissiveIntensity={0.8} />
</Box>
{/* Battery Slots Container */}
<Box args={[1.2, 1.2, 0.2]} position={[0, 1.0, 0.45]}>
<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>
);
}
function MerchantIcon() {
const mesh = useRef<THREE.Mesh>(null);
const group = useRef<THREE.Group>(null);
useFrame((_, delta) => {
if (mesh.current) {
mesh.current.rotation.x += delta * 0.5;
mesh.current.rotation.y += delta;
if (group.current) {
group.current.rotation.y += delta * 1.2;
group.current.rotation.z = Math.sin(_.clock.elapsedTime * 3) * 0.05;
}
});
return (
<Float floatIntensity={2} speed={2}>
<Octahedron ref={mesh} args={[1.5]}>
<meshStandardMaterial color="#00bc84" roughness={0.1} metalness={0.7} />
</Octahedron>
<Float floatIntensity={2} speed={2.5}>
<group ref={group} scale={0.9}>
{/* Main Cargo Box */}
<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>
);
}
@@ -87,7 +147,9 @@ export default function CardIcon3D({ type }: { type: 'bike' | 'investor' | 'swap
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} intensity={1} />
<pointLight position={[-5, -5, -5]} intensity={0.5} />
{type === 'bike' && <BikeIcon />}
<Suspense fallback={null}>
{type === 'bike' && <ActualBikeIcon />}
</Suspense>
{type === 'investor' && <InvestorIcon />}
{type === 'swap' && <SwapIcon />}
{type === 'merchant' && <MerchantIcon />}

View File

@@ -1,65 +1,237 @@
'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, useThree } 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';
useLoader.preload(OBJLoader, '/aerox.obj');
useLoader.preload(THREE.TextureLoader, '/jaiben.png');
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);
useFrame((state, delta) => {
if (group.current) {
group.current.rotation.y += delta * 0.2;
group.current.rotation.z = Math.sin(state.clock.elapsedTime * 0.5) * 0.05;
}
const model = useMemo(() => {
const clone = obj.clone(true);
// Center + reorient + scale. OBJ length is along Z; rotate so length lies along X.
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: '#111111',
roughness: 0.9,
metalness: 0.1,
});
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]);
const { size } = useThree();
const isMobile = size.width < 1024;
const targetX = isMobile ? 0 : 2.5;
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;
// Smoothly animate position X to be responsive
group.current.position.x += (targetX - group.current.position.x) * 0.1;
});
return (
<Float speed={2} rotationIntensity={0.5} floatIntensity={1.5}>
<group ref={group} position={[2, 0, -2]} scale={1.2} rotation={[0.2, -0.5, 0]}>
{/* Main Deck */}
<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>
<Float speed={1.2} rotationIntensity={0.1} floatIntensity={0.3}>
<group ref={group} rotation={[0, -0.5, 0]} position={[isMobile ? 0 : 2.5, -0.1, -1]}>
<primitive object={model} />
</group>
</Float>
);
}
function Battery({ position, scale = 1, rotation = [0, 0, 0] }: { position: [number, number, number]; scale?: number; rotation?: [number, number, number] }) {
const group = useRef<THREE.Group>(null);
const logoTexture = useLoader(THREE.TextureLoader, '/jaiben.png');
useFrame((state, delta) => {
if (group.current) {
group.current.rotation.y += delta * 0.4;
group.current.position.y = position[1] + Math.sin(state.clock.elapsedTime + position[0]) * 0.2;
}
});
return (
<Float speed={2} rotationIntensity={0.5} floatIntensity={1}>
<group ref={group} position={position} scale={scale} rotation={new THREE.Euler(...rotation)}>
{/* Main Battery Body */}
<mesh position={[0, 0, 0]} castShadow receiveShadow>
<boxGeometry args={[0.6, 1.2, 0.4]} />
<meshStandardMaterial color="#1e293b" roughness={0.4} metalness={0.8} />
</mesh>
{/* Top Cap */}
<mesh position={[0, 0.65, 0]} castShadow receiveShadow>
<boxGeometry args={[0.62, 0.1, 0.42]} />
<meshStandardMaterial color={ACCENT} roughness={0.3} metalness={0.5} />
</mesh>
{/* Handle */}
<mesh position={[0, 0.70, 0]} castShadow receiveShadow>
<torusGeometry args={[0.15, 0.04, 16, 32, Math.PI]} />
<meshStandardMaterial color="#0d7a33" roughness={0.2} metalness={0.9} />
</mesh>
{/* Glowing Indicator Light */}
<mesh position={[0.2, 0.5, 0.201]}>
<circleGeometry args={[0.03, 16]} />
<meshBasicMaterial color={ACCENT_BRIGHT} />
</mesh>
{/* Logo Decal Plane */}
<mesh position={[0, 0, 0.201]}>
<planeGeometry args={[0.35, 0.35]} />
<meshBasicMaterial map={logoTexture} transparent={true} />
</mesh>
{/* Back Logo Decal Plane */}
<mesh position={[0, 0, -0.201]} rotation={[0, Math.PI, 0]}>
<planeGeometry args={[0.35, 0.35]} />
<meshBasicMaterial map={logoTexture} transparent={true} />
</mesh>
</group>
</Float>
);
}
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 />
<Battery position={[-3.8, 1.6, -1]} scale={1.1} />
<Battery position={[3.8, 1.2, -0.5]} scale={0.8} rotation={[0.2, 0, 0]} />
<Battery position={[3.4, -0.8, 0.6]} scale={0.6} rotation={[-0.2, 0.5, 0]} />
<Battery position={[-4.0, -0.4, 0.8]} scale={0.7} rotation={[0, -0.5, 0.1]} />
</Suspense>
<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 pointer-events-none z-0 overflow-hidden">
<Canvas camera={{ position: [0, 0, 8], fov: 45 }}>
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} intensity={1} />
<directionalLight position={[-10, -10, -5]} intensity={0.5} color="#00bc84" />
<StylizedScooter />
<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>
);
}

View 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>
);
}

View 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>;
}

View 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>
);
}

BIN
zivrehiG Normal file

Binary file not shown.