Inspiration
Comfort food is more than sustenanceβit is warmth, nostalgia, and relief after hours of intense problem-solving. For this challenge, I transformed one of my absolute favorite comfort meals into digital art built purely with HTML and CSS.WEBSITE URL https://startling-cobbler-355184.netlify.app/
I chose a classic, warm bowl of ramen topped with eggs, scallions, and rich broth. Beyond its visual charm, this dish represents the ultimate balance of textures, colors, and layersβa perfect parallel to constructing intricate, scalable UI designs from scratch without relying on external images or SVGs.
** This build was actually inspired by a recent web development project I created for our neighbor in Ravali. Designing and styling customized, real-world components for their website gave me fresh inspiration to push the boundaries of pure CSS layout, complex gradient textures, and visual storytelling.**
Journey
π‘ The Blueprint & Execution
Translating organic food shapes into digital pixels using standard CSS requires viewing layout structures through a geometric lens.
DOM Architecture: Maintained a clean, declarative tree of semantic elements representing the bowl container, core food layers, and atmospheric accents.
Sculpting Organic Shapes: Combined asymmetric border-radius curves with 3D transform shifts (rotate, skew, scale) to avoid stiff, geometric edges.
Depth & Realistic Illumination: Stacked multi-layered inset box-shadow techniques alongside radial-gradient() maps to generate realistic light reflections, glossy highlights, and ambient shadows.
Atmospheric Motion: Implemented custom @keyframes micro-animations to create dynamic thermal steam and subtle ambient glow effects.
π οΈ Key Technical Takeaways
Maximizing Pseudo-elements: Utilized ::before and ::after structures on almost every node to keep the DOM extremely lightweight while doubling visual detail density.
Mastering Gradient Stacking: Blended multiple background gradient layers to construct complex surface textures without heavy image assets.
Performance Optimization: Leveraged hardware-accelerated CSS properties (transform, opacity) for smooth, 60 FPS animations.
π Whatβs Next?
Introducing interactive micro-interactions triggered on cursor hover (e.g., dynamic steam patterns and interactive ingredients).
Adding CSS custom property toggles to support alternative aesthetic themes like Neon Cyberpunk or Dark Mode lighting.
Code licensed under the MIT License. #
<!DOCTYPE html>
<br> tailwind.config = {<br> darkMode: 'class',<br> theme: {<br> extend: {<br> colors: {<br> swiggy: {<br> 50: '#fff7ed',<br> 100: '#ffedd5',<br> 500: '#fc8019', // Signature Swiggy Orange<br> 600: '#e66b00',<br> 700: '#c25200',<br> },<br> arcade: {<br> 500: '#a855f7',<br> 600: '#9333ea',<br> 700: '#7e22ce',<br> }<br> },<br> fontFamily: {<br> sans: ['Inter', 'sans-serif'],<br> }<br> }<br> }<br> }<br>
<style>
body {
font-family: 'Inter', sans-serif;
user-select: none;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.6);
}
::-webkit-scrollbar-thumb {
background: #fc8019;
border-radius: 9999px;
}
.glass-panel {
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glass-panel-light {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.arcade-glow {
box-shadow: 0 0 20px rgba(168, 85, 247, 0.5);
}
.pulse-orange {
animation: pulse-orange-anim 2s infinite alternate;
}
@keyframes pulse-orange-anim {
0% { box-shadow: 0 0 5px rgba(252, 128, 25, 0.4); }
100% { box-shadow: 0 0 22px rgba(252, 128, 25, 0.9); }
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
<!-- Swiggy-Style Top Navigation Header -->
<header class="sticky top-0 z-40 bg-slate-900/95 backdrop-blur-md border-b border-slate-800 px-4 lg:px-8 py-3 transition-all">
<div class="max-w-7xl mx-auto flex items-center justify-between gap-4">
<!-- Logo & Location Selector -->
<div class="flex items-center space-x-6">
<div class="flex items-center space-x-2 cursor-pointer" onclick="switchTab('tab-order')">
<div class="w-10 h-10 rounded-2xl bg-gradient-to-tr from-swiggy-500 to-amber-500 flex items-center justify-center text-white font-extrabold text-xl shadow-lg shadow-swiggy-500/30">
<i class="fa-solid fa-utensils"></i>
</div>
<div>
<span class="font-extrabold text-xl tracking-tight bg-gradient-to-r from-swiggy-500 via-amber-400 to-white bg-clip-text text-transparent">BiteTrack</span>
<span class="text-[10px] block font-semibold text-slate-400 -mt-1 uppercase tracking-widest">Swiggy Express</span>
</div>
</div>
<!-- Location Picker Pill -->
<div class="hidden sm:flex items-center space-x-2 bg-slate-800/80 hover:bg-slate-800 px-3.5 py-1.5 rounded-2xl text-xs border border-slate-700/60 cursor-pointer transition">
<i class="fa-solid fa-location-dot text-swiggy-500"></i>
<span class="font-semibold text-slate-200">Indiranagar 100ft Road</span>
<span class="text-slate-400">Bengaluru</span>
<i class="fa-solid fa-chevron-down text-[10px] text-slate-400"></i>
</div>
</div>
<!-- Search Bar -->
<div class="hidden md:flex flex-1 max-w-md relative">
<i class="fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400 text-sm"></i>
<input id="search-input" type="text" placeholder="Search for restaurant, cuisine or dish..." class="w-full bg-slate-950/80 border border-slate-700/80 rounded-2xl pl-10 pr-4 py-2 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-swiggy-500 transition">
</div>
<!-- Top Nav Links & Gaming Coin Counter -->
<div class="flex items-center space-x-3">
<!-- Coin Balance Pill (Gamer Sector) -->
<div class="flex items-center space-x-1.5 bg-gradient-to-r from-purple-900/60 to-slate-800 border border-purple-500/40 px-3 py-1.5 rounded-2xl text-xs font-bold text-amber-300 shadow-sm cursor-pointer" onclick="switchTab('tab-gaming')" title="View Gamer Balance">
<i class="fa-solid fa-coins text-amber-400 animate-bounce"></i>
<span id="coin-balance">480</span>
<span class="text-[10px] text-purple-300 font-normal">XP</span>
</div>
<!-- Tab Buttons -->
<button id="nav-order" onclick="switchTab('tab-order')" class="px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-swiggy-500 text-white shadow-lg shadow-swiggy-500/20">
<i class="fa-solid fa-bowl-food"></i>
<span class="hidden sm:inline">Order Food</span>
</button>
<button id="nav-track" onclick="switchTab('tab-track')" class="px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-slate-800 text-slate-300 hover:text-white hover:bg-slate-700 relative">
<i class="fa-solid fa-map-location-dot text-emerald-400"></i>
<span class="hidden sm:inline">Live 3D Track</span>
<span id="active-order-badge" class="w-2.5 h-2.5 rounded-full bg-emerald-500 animate-ping absolute top-1 right-1"></span>
</button>
<button id="nav-gaming" onclick="switchTab('tab-gaming')" class="px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-slate-800 text-purple-300 hover:text-white hover:bg-purple-900/40 border border-purple-500/30">
<i class="fa-solid fa-gamepad text-purple-400"></i>
<span>Bite Arcade</span>
</button>
<!-- Cart Button Drawer Trigger -->
<button id="btn-open-cart" onclick="toggleCartDrawer(true)" class="relative p-2.5 bg-slate-800 hover:bg-slate-700 text-slate-200 rounded-2xl border border-slate-700 transition">
<i class="fa-solid fa-bag-shopping text-base text-swiggy-500"></i>
<span id="cart-count-badge" class="absolute -top-1 -right-1 bg-swiggy-500 text-white text-[10px] font-black w-5 h-5 rounded-full flex items-center justify-center border-2 border-slate-900">0</span>
</button>
</div>
</div>
</header>
<!-- Main Content Container -->
<div class="flex-1 flex flex-col relative overflow-hidden">
<!-- ================= TAB 1: SWIGGY FOOD ORDERING VIEW ================= -->
<div id="tab-order" class="flex-1 overflow-y-auto p-4 lg:p-8 space-y-8 max-w-7xl mx-auto w-full transition-opacity duration-300">
<!-- Hero Gaming & Discount Banners Slider -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="md:col-span-2 rounded-3xl bg-gradient-to-r from-slate-900 via-purple-950 to-slate-900 border border-purple-500/30 p-6 relative overflow-hidden flex flex-col justify-between min-h-[180px] shadow-2xl">
<div class="absolute right-0 top-0 bottom-0 w-1/2 opacity-20 pointer-events-none bg-cover bg-center" style="background-image: url('https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?auto=format&fit=crop&w=600&q=80');"></div>
<div>
<span class="bg-purple-500/20 text-purple-300 border border-purple-500/30 text-[10px] font-extrabold uppercase tracking-wider px-2.5 py-1 rounded-full flex items-center gap-1.5 w-max mb-2">
<i class="fa-solid fa-gamepad"></i> Gaming Special Deal
</span>
<h2 class="text-2xl sm:text-3xl font-black text-white leading-tight">Play Mini-Games & Get <span class="text-swiggy-500">50% OFF</span> Food!</h2>
<p class="text-xs text-slate-300 mt-1 max-w-md">Earn Bite Coins in our Bite Arcade while your order is being prepared and redeem them instantly!</p>
</div>
<div class="pt-4 flex items-center space-x-3">
<button onclick="switchTab('tab-gaming')" class="bg-purple-600 hover:bg-purple-500 text-white px-5 py-2.5 rounded-2xl text-xs font-bold transition shadow-lg shadow-purple-600/30 flex items-center space-x-2">
<i class="fa-solid fa-play"></i> <span>Play Arcade Now</span>
</button>
<span class="text-xs font-mono text-amber-300 font-semibold"><i class="fa-solid fa-coins"></i> +100 Coins Free</span>
</div>
</div>
<div class="rounded-3xl bg-gradient-to-br from-swiggy-600 to-amber-600 p-6 flex flex-col justify-between relative overflow-hidden shadow-2xl">
<div>
<span class="bg-white/20 text-white text-[10px] font-extrabold uppercase tracking-wider px-2.5 py-1 rounded-full w-max mb-2 block">Flash Offer</span>
<h3 class="text-xl font-black text-white">Free Delivery on First 3 Orders</h3>
<p class="text-xs text-orange-100 mt-1">Use code <span class="font-mono font-bold text-white bg-black/20 px-1.5 py-0.5 rounded">SWIGGY3D</span></p>
</div>
<button onclick="filterRestaurants('all')" class="mt-4 bg-white text-slate-900 font-extrabold px-4 py-2 rounded-xl text-xs hover:bg-orange-50 transition w-max">
Explore Now
</button>
</div>
</div>
<!-- Food Categories Carousel -->
<div>
<h3 class="text-lg font-bold text-white mb-4 flex items-center justify-between">
<span>What's on your mind?</span>
<span class="text-xs text-slate-400 font-medium cursor-pointer hover:text-swiggy-500">View All Categories →</span>
</h3>
<div class="flex items-center space-x-4 overflow-x-auto pb-2 hide-scrollbar">
<button onclick="filterCategory('Burger')" class="flex flex-col items-center flex-shrink-0 group cursor-pointer">
<div class="w-16 h-16 rounded-3xl bg-slate-900 border border-slate-800 flex items-center justify-center text-3xl group-hover:scale-105 group-hover:border-swiggy-500 transition shadow-md">π</div>
<span class="text-xs font-semibold text-slate-300 mt-2 group-hover:text-swiggy-500">Burgers</span>
</button>
<button onclick="filterCategory('Pizza')" class="flex flex-col items-center flex-shrink-0 group cursor-pointer">
<div class="w-16 h-16 rounded-3xl bg-slate-900 border border-slate-800 flex items-center justify-center text-3xl group-hover:scale-105 group-hover:border-swiggy-500 transition shadow-md">π</div>
<span class="text-xs font-semibold text-slate-300 mt-2 group-hover:text-swiggy-500">Pizza</span>
</button>
<button onclick="filterCategory('Biryani')" class="flex flex-col items-center flex-shrink-0 group cursor-pointer">
<div class="w-16 h-16 rounded-3xl bg-slate-900 border border-slate-800 flex items-center justify-center text-3xl group-hover:scale-105 group-hover:border-swiggy-500 transition shadow-md">π²</div>
<span class="text-xs font-semibold text-slate-300 mt-2 group-hover:text-swiggy-500">Biryani</span>
</button>
<button onclick="filterCategory('Asian')" class="flex flex-col items-center flex-shrink-0 group cursor-pointer">
<div class="w-16 h-16 rounded-3xl bg-slate-900 border border-slate-800 flex items-center justify-center text-3xl group-hover:scale-105 group-hover:border-swiggy-500 transition shadow-md">N</div>
<span class="text-xs font-semibold text-slate-300 mt-2 group-hover:text-swiggy-500">Asian</span>
</button>
<button onclick="filterCategory('Dessert')" class="flex flex-col items-center flex-shrink-0 group cursor-pointer">
<div class="w-16 h-16 rounded-3xl bg-slate-900 border border-slate-800 flex items-center justify-center text-3xl group-hover:scale-105 group-hover:border-swiggy-500 transition shadow-md">π¦</div>
<span class="text-xs font-semibold text-slate-300 mt-2 group-hover:text-swiggy-500">Desserts</span>
</button>
<button onclick="filterCategory('Drinks')" class="flex flex-col items-center flex-shrink-0 group cursor-pointer">
<div class="w-16 h-16 rounded-3xl bg-slate-900 border border-slate-800 flex items-center justify-center text-3xl group-hover:scale-105 group-hover:border-swiggy-500 transition shadow-md">π₯€</div>
<span class="text-xs font-semibold text-slate-300 mt-2 group-hover:text-swiggy-500">Drinks</span>
</button>
</div>
</div>
<!-- Top Restaurant Chains in Indiranagar -->
<div>
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2 mb-6">
<h3 class="text-xl font-bold text-white">Top Restaurants with Live 3D Delivery</h3>
<div class="flex items-center space-x-2">
<button onclick="sortRestaurants('rating')" class="px-3 py-1.5 rounded-xl bg-slate-800 border border-slate-700 text-xs text-slate-300 hover:text-white transition">Rating 4.5+</button>
<button onclick="sortRestaurants('fast')" class="px-3 py-1.5 rounded-xl bg-slate-800 border border-slate-700 text-xs text-slate-300 hover:text-white transition">Fast Delivery</button>
</div>
</div>
<div id="restaurant-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Restaurant Cards rendered dynamically via JS -->
</div>
</div>
</div>
<!-- ================= TAB 2: LIVE 3D TRACKER VIEW ================= -->
<div id="tab-track" class="hidden flex-1 relative w-full h-full min-h-[550px]">
<!-- 3D WebGL Canvas Container -->
<div id="canvas-container" class="absolute inset-0 w-full h-full z-0 cursor-grab active:cursor-grabbing"></div>
<!-- Top Overlay Controls Toolbar -->
<div class="relative z-10 p-4 flex flex-wrap justify-between items-center gap-3 pointer-events-none">
<div class="glass-panel px-4 py-2.5 rounded-2xl flex items-center space-x-3 pointer-events-auto">
<div class="w-9 h-9 rounded-xl bg-swiggy-500 flex items-center justify-center text-white shadow-md">
<i class="fa-solid fa-motorcycle text-lg"></i>
</div>
<div>
<h4 class="font-extrabold text-sm text-white">Live Order #BT-9821</h4>
<p class="text-[11px] text-slate-400">Artisanal Burger Co. • <span class="text-emerald-400 font-bold">On the Way</span></p>
</div>
</div>
<!-- Theme & View Controls -->
<div class="glass-panel px-3 py-2 rounded-2xl flex items-center space-x-2 pointer-events-auto">
<button id="btn-theme-day" onclick="set3DTheme('day')" class="p-2 rounded-xl text-amber-400 hover:bg-slate-800 transition" title="Day Mode"><i class="fa-solid fa-sun"></i></button>
<button id="btn-theme-sunset" onclick="set3DTheme('sunset')" class="p-2 rounded-xl text-orange-400 hover:bg-slate-800 transition" title="Sunset Mode"><i class="fa-solid fa-cloud-sun"></i></button>
<button id="btn-theme-night" onclick="set3DTheme('night')" class="p-2 rounded-xl text-indigo-400 bg-slate-800 transition" title="Cyberpunk Night"><i class="fa-solid fa-moon"></i></button>
<div class="w-px h-5 bg-slate-700 mx-1"></div>
<button id="btn-sound" onclick="toggleAudio()" class="p-2 rounded-xl text-slate-300 hover:bg-slate-800 transition" title="Audio FX"><i id="sound-icon" class="fa-solid fa-volume-high text-swiggy-500"></i></button>
</div>
</div>
<!-- Bottom Tracking Overlay Floating Dashboard -->
<div class="absolute bottom-4 left-4 right-4 z-10 grid grid-cols-1 md:grid-cols-12 gap-4 pointer-events-none items-end">
<!-- Left: Order Status Timeline & Driver -->
<div class="md:col-span-5 lg:col-span-4 glass-panel p-5 rounded-3xl pointer-events-auto shadow-2xl space-y-4">
<div class="flex justify-between items-center">
<span class="text-xs font-bold text-swiggy-500 uppercase tracking-wider flex items-center gap-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-swiggy-500 animate-ping"></span> Driver En Route
</span>
<span id="eta-badge" class="text-xs font-extrabold bg-swiggy-500/20 text-swiggy-500 border border-swiggy-500/30 px-3 py-1 rounded-full">
ETA ~ 7 Mins
</span>
</div>
<!-- Driver Card -->
<div class="flex items-center justify-between bg-slate-900/80 p-3 rounded-2xl border border-slate-800">
<div class="flex items-center space-x-3">
<img src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=150&q=80" class="w-12 h-12 rounded-2xl object-cover border-2 border-swiggy-500">
<div>
<h5 class="font-bold text-sm text-white flex items-center gap-1">Alex Rivera <span class="bg-amber-400/20 text-amber-300 text-[10px] px-1.5 py-0.5 rounded"><i class="fa-solid fa-star text-[8px]"></i> 4.9</span></h5>
<p class="text-xs text-slate-400">Honda Electric Scooter • <span class="text-slate-200 font-mono">KA-03-EX-99</span></p>
</div>
</div>
<button onclick="triggerHonkSound()" class="bg-amber-500/20 hover:bg-amber-500/30 text-amber-400 p-2.5 rounded-xl border border-amber-500/30 transition" title="Honk Horn">
<i class="fa-solid fa-bullhorn text-sm"></i>
</button>
</div>
<!-- Dynamic Progress Bar -->
<div>
<div class="flex justify-between text-xs font-semibold text-slate-400 mb-1.5">
<span>Restaurant</span>
<span id="progress-percentage" class="text-swiggy-500">55%</span>
<span>Your Doorstep</span>
</div>
<div class="w-full h-2 bg-slate-800 rounded-full overflow-hidden">
<div id="progress-bar-fill" class="h-full bg-gradient-to-r from-amber-500 to-swiggy-500 rounded-full w-[55%] transition-all duration-300"></div>
</div>
</div>
<!-- Gaming Banner inside 3D Tracking -->
<div class="bg-gradient-to-r from-purple-900/60 to-slate-900 border border-purple-500/40 p-3 rounded-2xl flex items-center justify-between">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-xl bg-purple-600 flex items-center justify-center text-white text-sm shadow">
<i class="fa-solid fa-gamepad"></i>
</div>
<div>
<p class="text-xs font-bold text-white">Bored waiting?</p>
<p class="text-[10px] text-purple-300">Play Scooter Dash to earn Coins!</p>
</div>
</div>
<button onclick="switchTab('tab-gaming')" class="bg-purple-600 hover:bg-purple-500 text-white text-xs font-bold px-3 py-1.5 rounded-xl transition">
Play
</button>
</div>
</div>
<!-- Right: Telemetry & Camera Modes -->
<div class="md:col-span-7 lg:col-span-8 flex flex-col items-end gap-3 pointer-events-auto">
<!-- Camera Switcher Pills -->
<div class="glass-panel p-1.5 rounded-2xl flex items-center space-x-1 shadow-2xl">
<button id="cam-chase" onclick="setCamMode('chase')" class="px-3 py-1.5 rounded-xl text-xs font-bold bg-swiggy-500 text-white shadow">Chase</button>
<button id="cam-orbit" onclick="setCamMode('orbit')" class="px-3 py-1.5 rounded-xl text-xs font-bold text-slate-300 hover:bg-slate-800">Free Orbit</button>
<button id="cam-top" onclick="setCamMode('top')" class="px-3 py-1.5 rounded-xl text-xs font-bold text-slate-300 hover:bg-slate-800">Top-Down</button>
<button id="cam-iso" onclick="setCamMode('iso')" class="px-3 py-1.5 rounded-xl text-xs font-bold text-slate-300 hover:bg-slate-800">Isometric</button>
</div>
<!-- Telemetry Pill Card -->
<div class="glass-panel p-3.5 rounded-2xl flex items-center space-x-6 shadow-2xl">
<div class="flex items-center space-x-2">
<i class="fa-solid fa-gauge-high text-amber-400"></i>
<span id="telemetry-speed" class="text-xs font-bold text-white">34 km/h</span>
</div>
<div class="w-px h-4 bg-slate-800"></div>
<div class="flex items-center space-x-2">
<i class="fa-solid fa-temperature-high text-rose-400"></i>
<span class="text-xs font-bold text-white">65Β°C Insulated</span>
</div>
<div class="w-px h-4 bg-slate-800"></div>
<div class="flex items-center space-x-2">
<i class="fa-solid fa-route text-sky-400"></i>
<span id="telemetry-dist" class="text-xs font-bold text-white">1.1 km left</span>
</div>
</div>
</div>
</div>
</div>
<!-- ================= TAB 3: GAMING SECTOR (BITE ARCADE) ================= -->
<div id="tab-gaming" class="hidden flex-1 overflow-y-auto p-4 lg:p-8 space-y-8 max-w-7xl mx-auto w-full transition-opacity duration-300">
<!-- Arcade Header Banner -->
<div class="rounded-3xl bg-gradient-to-r from-purple-900 via-indigo-950 to-slate-900 border border-purple-500/40 p-6 md:p-8 flex flex-col md:flex-row items-center justify-between gap-6 shadow-2xl relative overflow-hidden">
<div class="z-10 max-w-xl">
<span class="bg-purple-500/30 text-purple-300 border border-purple-500/40 text-xs font-extrabold uppercase tracking-widest px-3 py-1 rounded-full inline-block mb-3">
<i class="fa-solid fa-gamepad"></i> Foodie Arcade Sector
</span>
<h2 class="text-3xl sm:text-4xl font-black text-white leading-tight">Play Games, Win Coins & Get Free Food Discounts!</h2>
<p class="text-xs sm:text-sm text-purple-200 mt-2 leading-relaxed">Level up your Foodie XP by playing arcade mini-games. Redeem your Bite Coins directly for real discount vouchers on your orders!</p>
</div>
<!-- Player Stats Widget -->
<div class="z-10 bg-slate-900/90 border border-purple-500/30 p-5 rounded-3xl w-full md:w-auto min-w-[240px] shadow-xl flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-gradient-to-tr from-purple-500 to-amber-400 p-0.5 mb-2 shadow-lg">
<div class="w-full h-full bg-slate-950 rounded-full flex items-center justify-center text-amber-300 font-black text-2xl">
<i class="fa-solid fa-crown"></i>
</div>
</div>
<h4 class="font-extrabold text-sm text-white">Foodie Level 7</h4>
<p class="text-[11px] text-slate-400 mb-3 font-semibold">Master Chef Tier</p>
<div class="w-full bg-slate-800 h-2 rounded-full overflow-hidden mb-2">
<div class="bg-gradient-to-r from-purple-500 to-amber-400 h-full w-[70%]"></div>
</div>
<span class="text-[10px] font-mono text-purple-300">750 / 1000 XP to Level 8</span>
</div>
</div>
<!-- Arcade Games Selection Grid -->
<div>
<h3 class="text-xl font-bold text-white mb-6 flex items-center space-x-2">
<i class="fa-solid fa-trophy text-amber-400"></i>
<span>Playable Mini-Games</span>
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Game 1: Scooter Dash -->
<div class="bg-slate-900 border border-slate-800 rounded-3xl p-5 hover:border-purple-500/60 transition group flex flex-col justify-between shadow-xl">
<div>
<div class="h-40 rounded-2xl bg-gradient-to-br from-purple-900 to-slate-950 flex items-center justify-center relative overflow-hidden mb-4 border border-purple-500/20">
<i class="fa-solid fa-motorcycle text-6xl text-purple-400 group-hover:scale-110 transition duration-300"></i>
<span class="absolute top-3 right-3 bg-amber-500 text-slate-950 font-black text-[10px] px-2.5 py-1 rounded-full uppercase">+50 Coins</span>
</div>
<h4 class="font-bold text-lg text-white">Scooter Dash 3D</h4>
<p class="text-xs text-slate-400 mt-1">Dodge traffic obstacles, collect coin pickups on the highway, and deliver orders in record time!</p>
</div>
<button onclick="launchMiniGame('scooter')" class="mt-5 w-full bg-purple-600 hover:bg-purple-500 text-white font-bold py-3 rounded-2xl text-xs transition flex items-center justify-center space-x-2 shadow-lg shadow-purple-600/30">
<i class="fa-solid fa-gamepad"></i> <span>Play Scooter Dash</span>
</button>
</div>
<!-- Game 2: Wheel of Flavors -->
<div class="bg-slate-900 border border-slate-800 rounded-3xl p-5 hover:border-amber-500/60 transition group flex flex-col justify-between shadow-xl">
<div>
<div class="h-40 rounded-2xl bg-gradient-to-br from-amber-950 to-slate-950 flex items-center justify-center relative overflow-hidden mb-4 border border-amber-500/20">
<i class="fa-solid fa-dharmachakra text-6xl text-amber-400 group-hover:rotate-45 transition duration-500"></i>
<span class="absolute top-3 right-3 bg-emerald-500 text-slate-950 font-black text-[10px] px-2.5 py-1 rounded-full uppercase">Spin Daily</span>
</div>
<h4 class="font-bold text-lg text-white">Wheel of Flavors</h4>
<p class="text-xs text-slate-400 mt-1">Spin the lucky wheel daily to win mystery discount coupons, free desserts, and bonus XP!</p>
</div>
<button onclick="launchMiniGame('wheel')" class="mt-5 w-full bg-amber-500 hover:bg-amber-400 text-slate-950 font-bold py-3 rounded-2xl text-xs transition flex items-center justify-center space-x-2 shadow-lg shadow-amber-500/30">
<i class="fa-solid fa-dharmachakra"></i> <span>Spin Wheel</span>
</button>
</div>
<!-- Game 3: Burger Stacker -->
<div class="bg-slate-900 border border-slate-800 rounded-3xl p-5 hover:border-swiggy-500/60 transition group flex flex-col justify-between shadow-xl">
<div>
<div class="h-40 rounded-2xl bg-gradient-to-br from-orange-950 to-slate-950 flex items-center justify-center relative overflow-hidden mb-4 border border-swiggy-500/20">
<i class="fa-solid fa-burger text-6xl text-swiggy-500 group-hover:scale-110 transition duration-300"></i>
<span class="absolute top-3 right-3 bg-purple-500 text-white font-black text-[10px] px-2.5 py-1 rounded-full uppercase">High Score</span>
</div>
<h4 class="font-bold text-lg text-white">Burger Stacker</h4>
<p class="text-xs text-slate-400 mt-1">Catch falling ingredients to assemble the ultimate tall burger without letting components drop!</p>
</div>
<button onclick="launchMiniGame('burger')" class="mt-5 w-full bg-swiggy-500 hover:bg-swiggy-600 text-white font-bold py-3 rounded-2xl text-xs transition flex items-center justify-center space-x-2 shadow-lg shadow-swiggy-500/30">
<i class="fa-solid fa-layer-group"></i> <span>Stack Burgers</span>
</button>
</div>
</div>
</div>
<!-- Rewards Store Section -->
<div class="bg-slate-900 border border-slate-800 rounded-3xl p-6 md:p-8 shadow-2xl">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2 mb-6">
<div>
<h3 class="text-xl font-bold text-white flex items-center gap-2">
<i class="fa-solid fa-gift text-purple-400"></i>
<span>Gamer Rewards Store</span>
</h3>
<p class="text-xs text-slate-400">Redeem your accumulated Bite Coins for instant food discounts</p>
</div>
<div class="text-xs font-bold text-amber-300 bg-amber-500/20 border border-amber-500/30 px-3 py-1.5 rounded-xl">
Your Coins: <span id="store-coin-display" class="font-mono">480</span> XP
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div class="bg-slate-950 p-4 rounded-2xl border border-slate-800 flex items-center justify-between">
<div>
<span class="text-xs font-bold text-emerald-400">Free Delivery Voucher</span>
<p class="text-[10px] text-slate-400">Cost: 150 Coins</p>
</div>
<button onclick="redeemReward(150, 'Free Delivery')" class="bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold px-3 py-1.5 rounded-xl transition">Redeem</button>
</div>
<div class="bg-slate-950 p-4 rounded-2xl border border-slate-800 flex items-center justify-between">
<div>
<span class="text-xs font-bold text-amber-400">$5 OFF Any Order</span>
<p class="text-[10px] text-slate-400">Cost: 300 Coins</p>
</div>
<button onclick="redeemReward(300, '$5 Discount')" class="bg-amber-500 hover:bg-amber-400 text-slate-950 text-xs font-bold px-3 py-1.5 rounded-xl transition">Redeem</button>
</div>
<div class="bg-slate-950 p-4 rounded-2xl border border-slate-800 flex items-center justify-between">
<div>
<span class="text-xs font-bold text-purple-400">20% Gaming Combo OFF</span>
<p class="text-[10px] text-slate-400">Cost: 450 Coins</p>
</div>
<button onclick="redeemReward(450, '20% OFF Combo')" class="bg-purple-600 hover:bg-purple-500 text-white text-xs font-bold px-3 py-1.5 rounded-xl transition">Redeem</button>
</div>
</div>
</div>
</div>
</div>
<!-- Slide-Out Shopping Cart Drawer -->
<div id="cart-drawer" class="fixed inset-y-0 right-0 z-50 w-full max-w-md bg-slate-900 border-l border-slate-800 shadow-2xl transform translate-x-full transition-transform duration-300 flex flex-col">
<!-- Cart Header -->
<div class="p-5 border-b border-slate-800 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fa-solid fa-bag-shopping text-swiggy-500 text-lg"></i>
<h3 class="font-extrabold text-lg text-white">Your Food Basket</h3>
</div>
<button onclick="toggleCartDrawer(false)" class="w-8 h-8 rounded-full bg-slate-800 text-slate-400 hover:text-white flex items-center justify-center transition">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<!-- Cart Items List -->
<div id="cart-items-container" class="flex-1 overflow-y-auto p-5 space-y-4">
<!-- Items rendered via JS -->
</div>
<!-- Cart Footer Checkout -->
<div class="p-5 border-t border-slate-800 space-y-4 bg-slate-950">
<div class="space-y-2 text-xs text-slate-400">
<div class="flex justify-between">
<span>Subtotal</span>
<span id="cart-subtotal" class="font-mono text-slate-200">$0.00</span>
</div>
<div class="flex justify-between text-emerald-400">
<span>Gaming Voucher Discount</span>
<span id="cart-discount" class="font-mono">-$0.00</span>
</div>
<div class="flex justify-between">
<span>Delivery Fee (Live 3D Express)</span>
<span class="font-mono text-slate-200">$2.99</span>
</div>
<div class="flex justify-between text-sm font-black text-white pt-2 border-t border-slate-800">
<span>Total Amount</span>
<span id="cart-total" class="font-mono text-swiggy-500">$0.00</span>
</div>
</div>
<button onclick="placeOrder()" class="w-full bg-swiggy-500 hover:bg-swiggy-600 text-white font-extrabold py-3.5 rounded-2xl text-xs uppercase tracking-wider transition shadow-lg shadow-swiggy-500/30 flex items-center justify-center space-x-2">
<span>Place Order & Track Live 3D</span>
<i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- Mini-Game Launcher Modal -->
<div id="game-modal" class="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-md flex items-center justify-center p-4 opacity-0 pointer-events-none transition-all duration-300">
<div class="bg-slate-900 border border-purple-500/40 w-full max-w-2xl rounded-3xl p-6 shadow-2xl relative flex flex-col items-center">
<button onclick="closeMiniGame()" class="absolute top-4 right-4 w-9 h-9 rounded-full bg-slate-800 text-slate-400 hover:text-white flex items-center justify-center transition">
<i class="fa-solid fa-xmark"></i>
</button>
<h3 id="game-title" class="text-xl font-black text-white mb-1">Scooter Dash 3D</h3>
<p id="game-desc" class="text-xs text-slate-400 mb-3 text-center">Use Arrow Keys or Touch Screen to steer!</p>
<!-- Game Canvas Frame -->
<div class="w-full h-72 sm:h-80 bg-slate-950 rounded-2xl border border-slate-800 overflow-hidden relative flex items-center justify-center">
<canvas id="arcade-canvas" class="w-full h-full"></canvas>
</div>
<!-- On-Screen Controls Bar -->
<div id="game-controls-bar" class="mt-4 w-full flex flex-wrap items-center justify-between gap-3">
<div id="steer-controls" class="flex items-center space-x-2">
<button id="btn-steer-left" class="bg-slate-800 hover:bg-slate-700 text-white font-extrabold px-4 py-2 rounded-xl text-xs flex items-center space-x-1 border border-slate-700 active:scale-95 transition">
<i class="fa-solid fa-arrow-left"></i> <span>Left</span>
</button>
<button id="btn-steer-right" class="bg-slate-800 hover:bg-slate-700 text-white font-extrabold px-4 py-2 rounded-xl text-xs flex items-center space-x-1 border border-slate-700 active:scale-95 transition">
<span>Right</span> <i class="fa-solid fa-arrow-right"></i>
</button>
</div>
<button id="btn-spin-wheel" onclick="spinWheelAction()" class="hidden bg-gradient-to-r from-amber-500 to-swiggy-500 text-slate-950 font-black px-6 py-2 rounded-xl text-xs shadow-lg shadow-amber-500/30 hover:scale-105 active:scale-95 transition uppercase">
<i class="fa-solid fa-rotate"></i> SPIN WHEEL
</button>
<div class="flex items-center space-x-4">
<span class="text-xs font-bold text-amber-300">Score: <span id="arcade-score" class="font-mono text-sm">0</span></span>
<button onclick="endMiniGame()" class="bg-purple-600 hover:bg-purple-500 text-white text-xs font-bold px-4 py-2 rounded-xl transition shadow">
Collect Coins & Exit
</button>
</div>
</div>
</div>
</div>
<script>
// Sample Restaurant Data (Swiggy Style)
const RESTAURANTS = [
{
id: 1,
name: "Artisanal Burger Co.",
cuisine: "Burger β’ Fast Food β’ American",
rating: 4.8,
time: "20-25 mins",
price: "$15 for two",
category: "Burger",
image: "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?auto=format&fit=crop&w=600&q=80",
discount: "50% OFF up to $10",
menu: [
{ id: 101, name: "Smash Burger Deluxe", price: 12.00, desc: "Double beef patty with melted cheddar, crisp lettuce & house sauce.", emoji: "π" },
{ id: 102, name: "Truffle Parmesan Fries", price: 6.50, desc: "Hand-cut crispy fries tossed in truffle oil and freshly grated parmesan.", emoji: "π" },
{ id: 103, name: "Craft Sparkling Shake", price: 4.50, desc: "Thick vanilla bean ice cream milk shake.", emoji: "π₯€" }
]
},
{
id: 2,
name: "Pizza Napoli Oven",
cuisine: "Pizza β’ Italian β’ Pastas",
rating: 4.6,
time: "25-30 mins",
price: "$20 for two",
category: "Pizza",
image: "https://images.unsplash.com/photo-1513104890138-7c749659a591?auto=format&fit=crop&w=600&q=80",
discount: "Free Garlic Bread",
menu: [
{ id: 201, name: "Woodfired Pepperoni", price: 16.00, desc: "Authentic Neapolitan crust with spicy pepperoni & mozzarella.", emoji: "π" },
{ id: 202, name: "Creamy Alfredo Pasta", price: 14.00, desc: "Fettuccine in garlic parmesan white sauce.", emoji: "π" }
]
},
{
id: 3,
name: "Royal Biryani House",
cuisine: "Biryani β’ North Indian β’ Kebabs",
rating: 4.9,
time: "15-20 mins",
price: "$18 for two",
category: "Biryani",
image: "https://images.unsplash.com/photo-1563379091339-03b21ab4a4f8?auto=format&fit=crop&w=600&q=80",
discount: "20% OFF Gamer Deal",
menu: [
{ id: 301, name: "Hyderabadi Chicken Biryani", price: 15.00, desc: "Slow-cooked dum biryani with aromatic spices and juicy chicken.", emoji: "π²" },
{ id: 302, name: "Butter Chicken Curry", price: 13.50, desc: "Rich tomato cashew gravy with tender tandoori chicken.", emoji: "π₯" }
]
}
];
// Global State
let cart = [];
let coins = 480;
let activeDiscount = 0;
let activeTab = 'tab-order';
// Initialize App
window.addEventListener('load', () => {
renderRestaurants(RESTAURANTS);
init3DEngine();
window.addEventListener('resize', resize3DCanvas);
});
// Tab Navigation Switcher
function switchTab(tabId) {
activeTab = tabId;
['tab-order', 'tab-track', 'tab-gaming'].forEach(id => {
const el = document.getElementById(id);
if (id === tabId) {
el.classList.remove('hidden');
} else {
el.classList.add('hidden');
}
});
// Update Nav Styling
document.getElementById('nav-order').className = tabId === 'tab-order' ? "px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-swiggy-500 text-white shadow-lg" : "px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-slate-800 text-slate-300 hover:text-white";
document.getElementById('nav-track').className = tabId === 'tab-track' ? "px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-swiggy-500 text-white shadow-lg" : "px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-slate-800 text-slate-300 hover:text-white";
document.getElementById('nav-gaming').className = tabId === 'tab-gaming' ? "px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-purple-600 text-white shadow-lg" : "px-3.5 py-2 rounded-2xl text-xs font-bold transition flex items-center space-x-2 bg-slate-800 text-purple-300 hover:text-white";
// Trigger canvas resize when switching to 3D track tab
if (tabId === 'tab-track') {
setTimeout(resize3DCanvas, 50);
}
}
// Render Restaurant Cards
function renderRestaurants(list) {
const grid = document.getElementById('restaurant-grid');
grid.innerHTML = list.map(r => `
<div class="bg-slate-900 border border-slate-800 rounded-3xl overflow-hidden hover:border-swiggy-500/50 transition duration-300 shadow-xl group">
<div class="relative h-48 overflow-hidden">
<img src="${r.image}" class="w-full h-full object-cover group-hover:scale-105 transition duration-500">
<span class="absolute top-3 left-3 bg-swiggy-500 text-white font-extrabold text-[10px] px-2.5 py-1 rounded-full uppercase tracking-wider shadow">
${r.discount}
</span>
<div class="absolute bottom-3 right-3 bg-slate-950/80 backdrop-blur-md px-2.5 py-1 rounded-xl text-amber-400 font-bold text-xs flex items-center gap-1">
<i class="fa-solid fa-star text-[10px]"></i> ${r.rating}
</div>
</div>
<div class="p-5">
<h4 class="font-extrabold text-base text-white group-hover:text-swiggy-500 transition">${r.name}</h4>
<p class="text-xs text-slate-400 mt-0.5">${r.cuisine}</p>
<div class="flex items-center space-x-3 text-xs text-slate-300 font-medium mt-3 pt-3 border-t border-slate-800">
<span><i class="fa-solid fa-clock text-swiggy-500"></i> ${r.time}</span>
<span>•</span>
<span>${r.price}</span>
</div>
<!-- Menu Quick Add Items -->
<div class="mt-4 space-y-2">
<p class="text-[10px] uppercase font-bold text-slate-400">Popular Menu Items:</p>
${r.menu.map(item => `
<div class="flex items-center justify-between text-xs bg-slate-950/60 p-2 rounded-xl border border-slate-800">
<span class="text-slate-200 font-medium">${item.emoji} ${item.name} - <span class="font-mono text-swiggy-500">$${item.price.toFixed(2)}</span></span>
<button onclick="addToCart('${r.name}', ${item.id}, '${item.name}', ${item.price})" class="bg-swiggy-500 hover:bg-swiggy-600 text-white px-2.5 py-1 rounded-lg text-[10px] font-bold transition">
+ ADD
</button>
</div>
`).join('')}
</div>
</div>
</div>
`).join('');
}
// Cart Drawer Handlers
function addToCart(restName, itemId, itemName, price) {
const existing = cart.find(i => i.id === itemId);
if (existing) {
existing.qty++;
} else {
cart.push({ id: itemId, name: itemName, price: price, qty: 1, rest: restName });
}
updateCartUI();
toggleCartDrawer(true);
showToast(`Added ${itemName} to Cart!`, 'fa-bag-shopping', 'text-swiggy-500');
}
function updateCartUI() {
const countBadge = document.getElementById('cart-count-badge');
const totalCount = cart.reduce((sum, item) => sum + item.qty, 0);
countBadge.innerText = totalCount;
const container = document.getElementById('cart-items-container');
if (cart.length === 0) {
container.innerHTML = `
<div class="h-full flex flex-col items-center justify-center text-center p-6 text-slate-400">
<i class="fa-solid fa-basket-shopping text-5xl mb-3 text-slate-600"></i>
<p class="font-bold text-sm text-slate-300">Your basket is empty</p>
<p class="text-xs mt-1">Add items from top restaurants to get started!</p>
</div>
`;
} else {
container.innerHTML = cart.map(item => `
<div class="flex items-center justify-between bg-slate-950 p-3.5 rounded-2xl border border-slate-800">
<div>
<h5 class="text-xs font-bold text-white">${item.name}</h5>
<p class="text-[10px] text-slate-400 font-mono">$${item.price.toFixed(2)} each</p>
</div>
<div class="flex items-center space-x-2">
<button onclick="changeQty(${item.id}, -1)" class="w-6 h-6 rounded-lg bg-slate-800 text-slate-300 font-bold text-xs hover:bg-slate-700">-</button>
<span class="text-xs font-bold text-white font-mono">${item.qty}</span>
<button onclick="changeQty(${item.id}, 1)" class="w-6 h-6 rounded-lg bg-slate-800 text-slate-300 font-bold text-xs hover:bg-slate-700">+</button>
</div>
</div>
`).join('');
}
// Calculation
const subtotal = cart.reduce((sum, item) => sum + (item.price * item.qty), 0);
const total = Math.max(0, subtotal + (cart.length > 0 ? 2.99 : 0) - activeDiscount);
document.getElementById('cart-subtotal').innerText = `$${subtotal.toFixed(2)}`;
document.getElementById('cart-discount').innerText = `-$${activeDiscount.toFixed(2)}`;
document.getElementById('cart-total').innerText = `$${total.toFixed(2)}`;
}
function changeQty(itemId, delta) {
const item = cart.find(i => i.id === itemId);
if (item) {
item.qty += delta;
if (item.qty <= 0) {
cart = cart.filter(i => i.id !== itemId);
}
}
updateCartUI();
}
function toggleCartDrawer(open) {
const drawer = document.getElementById('cart-drawer');
if (open) {
drawer.classList.remove('translate-x-full');
} else {
drawer.classList.add('translate-x-full');
}
}
function placeOrder() {
if (cart.length === 0) {
showToast("Please add items to cart first!", "fa-circle-exclamation", "text-rose-400");
return;
}
toggleCartDrawer(false);
cart = [];
updateCartUI();
switchTab('tab-track');
showToast("Order Placed! Live 3D Tracking active.", "fa-check-circle", "text-emerald-400");
}
// Reward Store Functions
function redeemReward(cost, rewardName) {
if (coins >= cost) {
coins -= cost;
activeDiscount = rewardName.includes('$5') ? 5.00 : 2.99;
document.getElementById('coin-balance').innerText = coins;
document.getElementById('store-coin-display').innerText = coins;
updateCartUI();
showToast(`Redeemed ${rewardName}! Applied at checkout.`, 'fa-gift', 'text-amber-400');
} else {
showToast("Not enough Bite Coins! Play games to earn more.", 'fa-triangle-exclamation', 'text-rose-400');
}
}
// Mini Games Engine & Modal Launchers
let currentGameMode = null;
let miniGameAnimId = null;
let miniGameInterval = null;
let wheelAngle = 0;
let isSpinning = false;
let currentScore = 0;
function launchMiniGame(gameType) {
// Stop any existing game loop
if (miniGameAnimId) cancelAnimationFrame(miniGameAnimId);
if (miniGameInterval) clearInterval(miniGameInterval);
currentGameMode = gameType;
currentScore = 0;
document.getElementById('arcade-score').innerText = "0";
const modal = document.getElementById('game-modal');
modal.classList.remove('opacity-0', 'pointer-events-none');
// Wait for modal display layout pass
setTimeout(() => {
const canvas = document.getElementById('arcade-canvas');
const ctx = canvas.getContext('2d');
canvas.width = canvas.parentElement.clientWidth || 500;
canvas.height = canvas.parentElement.clientHeight || 300;
const steerControls = document.getElementById('steer-controls');
const spinBtn = document.getElementById('btn-spin-wheel');
if (gameType === 'scooter') {
document.getElementById('game-title').innerText = "π΅ Scooter Dash Express";
document.getElementById('game-desc').innerText = "Steer using Arrow Keys or touch buttons. Dodge oncoming traffic and grab pizza slices!";
steerControls.classList.remove('hidden');
spinBtn.classList.add('hidden');
startScooterDashGame(canvas, ctx);
} else if (gameType === 'wheel') {
document.getElementById('game-title').innerText = "π‘ Wheel of Flavors";
document.getElementById('game-desc').innerText = "Click SPIN WHEEL to test your luck and win instant Bite Coins!";
steerControls.classList.add('hidden');
spinBtn.classList.remove('hidden');
initWheelGame(canvas, ctx);
} else if (gameType === 'burger') {
document.getElementById('game-title').innerText = "π Burger Stacker";
document.getElementById('game-desc').innerText = "Move your plate left & right to catch falling burger ingredients and build high towers!";
steerControls.classList.remove('hidden');
spinBtn.classList.add('hidden');
startBurgerStackerGame(canvas, ctx);
}
}, 100);
}
// ================= 1. SCOOTER DASH GAME =================
let scooterX = 0;
function startScooterDashGame(canvas, ctx) {
let lanes = [canvas.width * 0.25, canvas.width * 0.5, canvas.width * 0.75];
let currentLane = 1; // Middle lane
scooterX = lanes[currentLane];
let obstacles = [];
let coinsList = [];
let speed = 5;
let frameCount = 0;
// Touch / Onscreen Controls
document.getElementById('btn-steer-left').onclick = () => {
if (currentLane > 0) currentLane--;
};
document.getElementById('btn-steer-right').onclick = () => {
if (currentLane < lanes.length - 1) currentLane++;
};
window.onkeydown = (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
if (currentLane > 0) currentLane--;
}
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
if (currentLane < lanes.length - 1) currentLane++;
}
};
function gameLoop() {
frameCount++;
ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw 3 Road Lanes
ctx.strokeStyle = '#334155';
ctx.setLineDash([15, 15]);
ctx.lineDashOffset = -frameCount * (speed * 1.5);
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(canvas.width * 0.375, 0);
ctx.lineTo(canvas.width * 0.375, canvas.height);
ctx.moveTo(canvas.width * 0.625, 0);
ctx.lineTo(canvas.width * 0.625, canvas.height);
ctx.stroke();
ctx.setLineDash([]);
// Smoothly interpolate scooter position
const targetX = lanes[currentLane];
scooterX += (targetX - scooterX) * 0.3;
// Draw Player Scooter
const playerY = canvas.height - 50;
ctx.font = '36px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('π΅', scooterX, playerY);
// Spawn Obstacles (Traffic Cars)
if (frameCount % 45 === 0) {
const lIdx = Math.floor(Math.random() * 3);
const carEmojis = ['π', 'π', 'π', 'π'];
const car = carEmojis[Math.floor(Math.random() * carEmojis.length)];
obstacles.push({ x: lanes[lIdx], y: -40, emoji: car });
}
// Spawn Rewards (Pizza Slices / Coins)
if (frameCount % 30 === 0) {
const lIdx = Math.floor(Math.random() * 3);
const rewardEmojis = ['π', 'πͺ', 'π', 'π₯€'];
const rew = rewardEmojis[Math.floor(Math.random() * rewardEmojis.length)];
coinsList.push({ x: lanes[lIdx], y: -40, emoji: rew });
}
// Update & Render Obstacles
for (let i = obstacles.length - 1; i >= 0; i--) {
const obs = obstacles[i];
obs.y += speed;
ctx.font = '32px sans-serif';
ctx.fillText(obs.emoji, obs.x, obs.y);
// Collision check
if (Math.abs(obs.x - scooterX) < 30 && Math.abs(obs.y - playerY) < 30) {
ctx.fillStyle = 'rgba(239, 68, 68, 0.4)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
currentScore = Math.max(0, currentScore - 10);
document.getElementById('arcade-score').innerText = currentScore;
obstacles.splice(i, 1);
showToast("Ouch! Hit traffic car (-10)", "fa-car-burst", "text-rose-400");
} else if (obs.y > canvas.height + 40) {
obstacles.splice(i, 1);
}
}
// Update & Render Rewards
for (let i = coinsList.length - 1; i >= 0; i--) {
const rew = coinsList[i];
rew.y += speed * 0.9;
ctx.font = '28px sans-serif';
ctx.fillText(rew.emoji, rew.x, rew.y);
// Pickup check
if (Math.abs(rew.x - scooterX) < 35 && Math.abs(rew.y - playerY) < 35) {
currentScore += 15;
document.getElementById('arcade-score').innerText = currentScore;
coinsList.splice(i, 1);
} else if (rew.y > canvas.height + 40) {
coinsList.splice(i, 1);
}
}
miniGameAnimId = requestAnimationFrame(gameLoop);
}
gameLoop();
}
// ================= 2. WHEEL OF FLAVORS GAME =================
const PRIZES = [
{ label: "+50 Coins", color: "#a855f7", value: 50 },
{ label: "+100 XP", color: "#fc8019", value: 100 },
{ label: "Free Drink", color: "#3b82f6", value: 30 },
{ label: "+150 Coins", color: "#eab308", value: 150 },
{ label: "+20 Coins", color: "#10b981", value: 20 },
{ label: "JACKPOT +250", color: "#ec4899", value: 250 }
];
function initWheelGame(canvas, ctx) {
isSpinning = false;
drawWheel(canvas, ctx, wheelAngle);
}
function drawWheel(canvas, ctx, angle) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 20;
const numSegments = PRIZES.length;
const arcSize = (Math.PI * 2) / numSegments;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw Segments
for (let i = 0; i < numSegments; i++) {
const segAngle = angle + i * arcSize;
ctx.beginPath();
ctx.fillStyle = PRIZES[i].color;
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, segAngle, segAngle + arcSize);
ctx.lineTo(centerX, centerY);
ctx.fill();
ctx.strokeStyle = '#0f172a';
ctx.lineWidth = 3;
ctx.stroke();
// Draw Text
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(segAngle + arcSize / 2);
ctx.textAlign = 'right';
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 14px Inter, sans-serif';
ctx.fillText(PRIZES[i].label, radius - 15, 5);
ctx.restore();
}
// Draw Center Pin
ctx.beginPath();
ctx.arc(centerX, centerY, 24, 0, Math.PI * 2);
ctx.fillStyle = '#0f172a';
ctx.fill();
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 4;
ctx.stroke();
// Draw Top Arrow Pointer
ctx.beginPath();
ctx.fillStyle = '#ef4444';
ctx.moveTo(centerX - 12, centerY - radius - 8);
ctx.lineTo(centerX + 12, centerY - radius - 8);
ctx.lineTo(centerX, centerY - radius + 15);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.stroke();
}
function spinWheelAction() {
if (isSpinning) return;
isSpinning = true;
const canvas = document.getElementById('arcade-canvas');
const ctx = canvas.getContext('2d');
const spinRotations = (Math.random() * 5 + 5) * Math.PI * 2;
const targetAngle = wheelAngle + spinRotations;
let currentVelocity = 0.25;
function animateSpin() {
if (wheelAngle < targetAngle - 0.1) {
const remaining = targetAngle - wheelAngle;
currentVelocity = Math.max(0.005, remaining * 0.05);
wheelAngle += currentVelocity;
drawWheel(canvas, ctx, wheelAngle);
miniGameAnimId = requestAnimationFrame(animateSpin);
} else {
isSpinning = false;
// Determine winning prize
const normalizedAngle = (2.5 * Math.PI - (wheelAngle % (Math.PI * 2))) % (Math.PI * 2);
const arcSize = (Math.PI * 2) / PRIZES.length;
const winningIdx = Math.floor(normalizedAngle / arcSize) % PRIZES.length;
const prize = PRIZES[winningIdx];
currentScore = prize.value;
document.getElementById('arcade-score').innerText = currentScore;
showToast(`π You Won: ${prize.label}!`, 'fa-trophy', 'text-amber-400');
}
}
animateSpin();
}
// ================= 3. BURGER STACKER GAME =================
function startBurgerStackerGame(canvas, ctx) {
let plateX = canvas.width / 2;
const plateWidth = 90;
const plateHeight = 15;
let stackedItems = [];
let currentFalling = null;
let speed = 3;
const ingredients = ['π', 'π§', 'π₯©', 'π₯¬', 'π
', 'π₯', 'π'];
document.getElementById('btn-steer-left').onclick = () => {
plateX = Math.max(plateWidth / 2, plateX - 35);
};
document.getElementById('btn-steer-right').onclick = () => {
plateX = Math.min(canvas.width - plateWidth / 2, plateX + 35);
};
window.onkeydown = (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
plateX = Math.max(plateWidth / 2, plateX - 35);
}
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
plateX = Math.min(canvas.width - plateWidth / 2, plateX + 35);
}
};
function spawnIngredient() {
const emoji = ingredients[Math.floor(Math.random() * ingredients.length)];
currentFalling = {
x: Math.random() * (canvas.width - 60) + 30,
y: -20,
emoji: emoji
};
}
spawnIngredient();
function gameLoop() {
ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw Plate
const plateY = canvas.height - 30;
ctx.fillStyle = '#cbd5e1';
ctx.beginPath();
ctx.roundRect(plateX - plateWidth / 2, plateY, plateWidth, plateHeight, 8);
ctx.fill();
// Draw Stacked Burger Layers
stackedItems.forEach((item, index) => {
ctx.font = '32px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(item.emoji, plateX, plateY - 15 - (index * 22));
});
// Update Falling Ingredient
if (currentFalling) {
currentFalling.y += speed;
ctx.font = '32px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(currentFalling.emoji, currentFalling.x, currentFalling.y);
const stackTopY = plateY - (stackedItems.length * 22);
// Check catch collision
if (Math.abs(currentFalling.x - plateX) < plateWidth / 1.5 && Math.abs(currentFalling.y - stackTopY) < 20) {
stackedItems.push(currentFalling);
currentScore += 20;
document.getElementById('arcade-score').innerText = currentScore;
if (stackedItems.length >= 8) {
showToast("π Perfect Monster Burger Built! (+50 Bonus)", "fa-burger", "text-amber-400");
currentScore += 50;
document.getElementById('arcade-score').innerText = currentScore;
stackedItems = [];
}
spawnIngredient();
} else if (currentFalling.y > canvas.height + 20) {
spawnIngredient(); // Missed
}
}
miniGameAnimId = requestAnimationFrame(gameLoop);
}
gameLoop();
}
function closeMiniGame() {
if (miniGameAnimId) cancelAnimationFrame(miniGameAnimId);
const modal = document.getElementById('game-modal');
modal.classList.add('opacity-0', 'pointer-events-none');
}
function endMiniGame() {
coins += currentScore;
document.getElementById('coin-balance').innerText = coins;
document.getElementById('store-coin-display').innerText = coins;
closeMiniGame();
if (currentScore > 0) {
showToast(`Collected +${currentScore} Bite Coins!`, 'fa-coins', 'text-amber-400');
}
}
// ================= THREE.JS 3D ENGINE =================
let scene, camera, renderer, vehicleGroup, headlight, routeLine;
let progress = 0.55;
let currentCamMode = 'chase';
let isAudioMuted = false;
let isMouseDown = false;
let mouseX = 0, mouseY = 0;
let orbitAngleX = 0, orbitAngleY = 0.4;
let dirLight, ambientLight;
function init3DEngine() {
const container = document.getElementById('canvas-container');
if (!container) return;
scene = new THREE.Scene();
scene.background = new THREE.Color(0x090d16);
scene.fog = new THREE.FogExp2(0x090d16, 0.015);
const aspect = (container.clientWidth || window.innerWidth) / (container.clientHeight || 550);
camera = new THREE.PerspectiveCamera(50, aspect, 0.1, 1000);
camera.position.set(0, 15, 30);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(container.clientWidth || window.innerWidth, container.clientHeight || 550);
renderer.shadowMap.enabled = true;
// Clear existing canvas if re-initializing
container.innerHTML = '';
container.appendChild(renderer.domElement);
// Orbit drag handlers for free camera mode
container.addEventListener('mousedown', (e) => { isMouseDown = true; mouseX = e.clientX; mouseY = e.clientY; });
window.addEventListener('mouseup', () => { isMouseDown = false; });
container.addEventListener('mousemove', (e) => {
if (!isMouseDown || currentCamMode !== 'orbit') return;
const deltaX = e.clientX - mouseX;
const deltaY = e.clientY - mouseY;
orbitAngleX -= deltaX * 0.005;
orbitAngleY = Math.max(0.1, Math.min(Math.PI / 2.2, orbitAngleY + deltaY * 0.005));
mouseX = e.clientX;
mouseY = e.clientY;
});
// Lighting
ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
scene.add(ambientLight);
dirLight = new THREE.DirectionalLight(0xffb07c, 1.2);
dirLight.position.set(40, 60, 20);
dirLight.castShadow = true;
scene.add(dirLight);
// Ground & City Grid layout
const groundGeo = new THREE.PlaneGeometry(300, 300);
const groundMat = new THREE.MeshStandardMaterial({ color: 0x0f172a, roughness: 0.8 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Generate City Buildings
const buildingMat = new THREE.MeshStandardMaterial({ color: 0x1e293b, roughness: 0.5 });
const windowMat = new THREE.MeshBasicMaterial({ color: 0xfcb319 });
for (let i = -4; i <= 4; i++) {
for (let j = -4; j <= 4; j++) {
if (Math.abs(i) <= 1 && Math.abs(j) <= 1) continue; // Keep center delivery loop clear
const h = 8 + Math.random() * 25;
const bGeo = new THREE.BoxGeometry(12, h, 12);
const bMesh = new THREE.Mesh(bGeo, buildingMat);
bMesh.position.set(i * 28 + (Math.random()*4 - 2), h/2, j * 28 + (Math.random()*4 - 2));
scene.add(bMesh);
// Glowing window accents
if (Math.random() > 0.3) {
const winGeo = new THREE.BoxGeometry(12.2, 1, 12.2);
const winMesh = new THREE.Mesh(winGeo, windowMat);
winMesh.position.set(bMesh.position.x, Math.random() * (h - 4) + 2, bMesh.position.z);
scene.add(winMesh);
}
}
}
// Detailed Delivery Scooter Model
vehicleGroup = new THREE.Group();
// Scooter Frame Body
const bodyGeo = new THREE.BoxGeometry(1.2, 0.7, 2.4);
const bodyMat = new THREE.MeshStandardMaterial({ color: 0xfc8019, metalness: 0.2 });
const body = new THREE.Mesh(bodyGeo, bodyMat);
body.position.y = 0.8;
vehicleGroup.add(body);
// Driver Body
const driverGeo = new THREE.CylinderGeometry(0.4, 0.4, 1.2, 8);
const driverMat = new THREE.MeshStandardMaterial({ color: 0x334155 });
const driver = new THREE.Mesh(driverGeo, driverMat);
driver.position.set(0, 1.4, -0.2);
vehicleGroup.add(driver);
// Driver Helmet (Swiggy Orange)
const helmetGeo = new THREE.SphereGeometry(0.35, 16, 16);
const helmetMat = new THREE.MeshStandardMaterial({ color: 0xfc8019, roughness: 0.3 });
const helmet = new THREE.Mesh(helmetGeo, helmetMat);
helmet.position.set(0, 2.1, -0.2);
vehicleGroup.add(helmet);
// Insulated Swiggy Delivery Box
const boxGeo = new THREE.BoxGeometry(1.3, 1.1, 1.1);
const boxMat = new THREE.MeshStandardMaterial({ color: 0xe66b00, roughness: 0.4 });
const box = new THREE.Mesh(boxGeo, boxMat);
box.position.set(0, 1.4, -1.0);
vehicleGroup.add(box);
// Wheels
const wheelGeo = new THREE.CylinderGeometry(0.35, 0.35, 0.3, 16);
const wheelMat = new THREE.MeshStandardMaterial({ color: 0x020617 });
const frontWheel = new THREE.Mesh(wheelGeo, wheelMat);
frontWheel.rotation.z = Math.PI / 2;
frontWheel.position.set(0, 0.35, 0.9);
vehicleGroup.add(frontWheel);
const rearWheel = new THREE.Mesh(wheelGeo, wheelMat);
rearWheel.rotation.z = Math.PI / 2;
rearWheel.position.set(0, 0.35, -0.9);
vehicleGroup.add(rearWheel);
// Front Headlight Beam
headlight = new THREE.SpotLight(0xfff5c0, 3, 25, Math.PI / 5, 0.5);
headlight.position.set(0, 1.0, 1.2);
headlight.target.position.set(0, 0, 10);
vehicleGroup.add(headlight);
vehicleGroup.add(headlight.target);
scene.add(vehicleGroup);
// Delivery Circular Route Path Line
const curvePoints = [];
const radius = 25;
for (let i = 0; i <= 64; i++) {
const theta = (i / 64) * Math.PI * 2;
curvePoints.push(new THREE.Vector3(Math.sin(theta) * radius, 0.15, Math.cos(theta) * radius));
}
const lineGeo = new THREE.BufferGeometry().setFromPoints(curvePoints);
const lineMat = new THREE.LineDashedMaterial({ color: 0xfc8019, dashSize: 1, gapSize: 0.5, linewidth: 3 });
routeLine = new THREE.Line(lineGeo, lineMat);
routeLine.computeLineDistances();
scene.add(routeLine);
animate3D();
}
function animate3D() {
requestAnimationFrame(animate3D);
progress += 0.0006;
if (progress > 1) progress = 0;
const radius = 25;
const currentAngle = progress * Math.PI * 2;
const nextAngle = (progress + 0.005) * Math.PI * 2;
const posX = Math.sin(currentAngle) * radius;
const posZ = Math.cos(currentAngle) * radius;
const nextX = Math.sin(nextAngle) * radius;
const nextZ = Math.cos(nextAngle) * radius;
vehicleGroup.position.set(posX, 0, posZ);
vehicleGroup.lookAt(nextX, 0, nextZ);
// Camera Viewport Modes
if (currentCamMode === 'chase') {
const camOffset = new THREE.Vector3(0, 8, -16).applyQuaternion(vehicleGroup.quaternion);
camera.position.lerp(vehicleGroup.position.clone().add(camOffset), 0.1);
camera.lookAt(vehicleGroup.position.clone().add(new THREE.Vector3(0, 2, 5).applyQuaternion(vehicleGroup.quaternion)));
} else if (currentCamMode === 'top') {
camera.position.set(posX, 45, posZ + 0.1);
camera.lookAt(posX, 0, posZ);
} else if (currentCamMode === 'iso') {
camera.position.set(posX + 25, 30, posZ + 25);
camera.lookAt(posX, 0, posZ);
} else if (currentCamMode === 'orbit') {
const camX = posX + Math.sin(orbitAngleX) * Math.cos(orbitAngleY) * 35;
const camY = Math.sin(orbitAngleY) * 35;
const camZ = posZ + Math.cos(orbitAngleX) * Math.cos(orbitAngleY) * 35;
camera.position.set(camX, camY, camZ);
camera.lookAt(posX, 1, posZ);
}
// Update UI Telemetry & Progress
const distLeft = ((1 - progress) * 2.4).toFixed(1);
const speed = Math.floor(32 + Math.sin(progress * 20) * 6);
const etaMins = Math.max(1, Math.ceil((1 - progress) * 12));
const pct = Math.floor(progress * 100);
const progressBar = document.getElementById('progress-bar-fill');
const pctDisplay = document.getElementById('progress-percentage');
const speedDisplay = document.getElementById('telemetry-speed');
const distDisplay = document.getElementById('telemetry-dist');
const etaDisplay = document.getElementById('eta-badge');
if (progressBar) progressBar.style.width = `${pct}%`;
if (pctDisplay) pctDisplay.innerText = `${pct}%`;
if (speedDisplay) speedDisplay.innerText = `${speed} km/h`;
if (distDisplay) distDisplay.innerText = `${distLeft} km left`;
if (etaDisplay) etaDisplay.innerText = `ETA ~ ${etaMins} Mins`;
if (renderer && scene && camera) {
renderer.render(scene, camera);
}
}
function resize3DCanvas() {
const container = document.getElementById('canvas-container');
if (!container || !renderer || !camera) return;
const width = container.clientWidth || window.innerWidth;
const height = container.clientHeight || 550;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
function setCamMode(mode) {
currentCamMode = mode;
['cam-chase', 'cam-orbit', 'cam-top', 'cam-iso'].forEach(id => {
const btn = document.getElementById(id);
if (btn) {
btn.className = id === `cam-${mode}` ?
"px-3 py-1.5 rounded-xl text-xs font-bold bg-swiggy-500 text-white shadow" :
"px-3 py-1.5 rounded-xl text-xs font-bold text-slate-300 hover:bg-slate-800";
}
});
}
function set3DTheme(theme) {
if (!scene) return;
if (theme === 'day') {
scene.background = new THREE.Color(0x38bdf8);
scene.fog.color = new THREE.Color(0x38bdf8);
ambientLight.intensity = 1.2;
dirLight.color.setHex(0xffffff);
} else if (theme === 'sunset') {
scene.background = new THREE.Color(0x431407);
scene.fog.color = new THREE.Color(0x431407);
ambientLight.intensity = 0.9;
dirLight.color.setHex(0xf97316);
} else if (theme === 'night') {
scene.background = new THREE.Color(0x090d16);
scene.fog.color = new THREE.Color(0x090d16);
ambientLight.intensity = 0.5;
dirLight.color.setHex(0x818cf8);
}
['btn-theme-day', 'btn-theme-sunset', 'btn-theme-night'].forEach(id => {
const btn = document.getElementById(id);
if (btn) {
btn.classList.toggle('bg-slate-800', id === `btn-theme-${theme}`);
}
});
}
function triggerHonkSound() {
if (isAudioMuted) {
showToast("Audio muted", "fa-volume-xmark", "text-slate-400");
return;
}
try {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc1 = audioCtx.createOscillator();
const osc2 = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc1.type = 'triangle';
osc2.type = 'sawtooth';
osc1.frequency.setValueAtTime(440, audioCtx.currentTime); // A4
osc2.frequency.setValueAtTime(554.37, audioCtx.currentTime); // C#5
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.35);
osc1.connect(gain);
osc2.connect(gain);
gain.connect(audioCtx.destination);
osc1.start();
osc2.start();
osc1.stop(audioCtx.currentTime + 0.35);
osc2.stop(audioCtx.currentTime + 0.35);
showToast("Beep Beep! Scooter Horn Honked π΅", "fa-bullhorn", "text-amber-400");
} catch(e) {
showToast("Scooter Horn Honked! π΅", "fa-bullhorn", "text-amber-400");
}
}
function toggleAudio() {
isAudioMuted = !isAudioMuted;
const soundIcon = document.getElementById('sound-icon');
if (soundIcon) {
soundIcon.className = isAudioMuted ? "fa-solid fa-volume-xmark text-slate-500" : "fa-solid fa-volume-high text-swiggy-500";
}
showToast(isAudioMuted ? "Audio muted" : "Audio FX active", isAudioMuted ? "fa-volume-xmark" : "fa-volume-high", "text-swiggy-500");
}
function showToast(msg, icon, color) {
const toast = document.createElement('div');
toast.className = `fixed bottom-6 left-1/2 -translate-x-1/2 z-50 glass-panel px-4 py-2.5 rounded-2xl shadow-2xl flex items-center space-x-2 text-xs font-bold text-white border border-slate-700 transition duration-300`;
toast.innerHTML = `<i class="fa-solid ${icon} ${color}"></i> <span>${msg}</span>`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 2500);
}
function filterCategory(cat) {
const filtered = RESTAURANTS.filter(r => r.category === cat);
renderRestaurants(filtered.length ? filtered : RESTAURANTS);
}
function filterRestaurants(type) {
renderRestaurants(RESTAURANTS);
}
function sortRestaurants(type) {
const sorted = [...RESTAURANTS].sort((a,b) => b.rating - a.rating);
renderRestaurants(sorted);
}
</script>
`
frontendchallenge, #devchallenge, #css, #cssart

Top comments (0)