imsubham's picture
Initial DeepSite commit
398933b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#0f172a">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>SpeedGuard Rewards - Smart Speed Tracking</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
font-family: 'Inter', sans-serif;
-webkit-tap-highlight-color: transparent;
}
body {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
min-height: 100vh;
}
.glass-panel {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.speed-gauge {
background: conic-gradient(from 180deg, #10b981 0deg, #f59e0b 120deg, #ef4444 240deg, #dc2626 360deg);
border-radius: 50%;
padding: 8px;
}
.speed-gauge-inner {
background: #0f172a;
border-radius: 50%;
}
.pulse-ring {
animation: pulse-ring 2s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
}
@keyframes pulse-ring {
0% { transform: scale(0.8); opacity: 1; }
100% { transform: scale(1.4); opacity: 0; }
}
.slide-up {
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.shake {
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px); }
75% { transform: translateX(10px); }
}
.coupon-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.fine-alert {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.reward-badge {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
/* Hide scrollbar */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.zone-indicator {
transition: all 0.3s ease;
}
.tracking-active {
box-shadow: 0 0 20px rgba(16, 185, 129, 0.5);
}
</style>
</head>
<body class="text-white overflow-x-hidden">
<!-- App Container -->
<div id="app" class="max-w-md mx-auto min-h-screen relative pb-20">
<!-- Header -->
<header class="sticky top-0 z-50 glass-panel px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="w-10 h-10 bg-gradient-to-br from-emerald-400 to-blue-500 rounded-xl flex items-center justify-center">
<i data-lucide="gauge" class="w-5 h-5 text-white"></i>
</div>
<div>
<h1 class="font-bold text-lg leading-tight">SpeedGuard</h1>
<p class="text-xs text-gray-400">Smart Highway Tracker</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="text-right">
<p class="text-xs text-gray-400">Points</p>
<p class="font-bold text-emerald-400" id="totalPoints">0</p>
</div>
<button onclick="showProfile()" class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center">
<i data-lucide="user" class="w-5 h-5"></i>
</button>
</div>
</header>
<!-- Main Dashboard -->
<main class="p-4 space-y-4">
<!-- Speedometer Section -->
<div class="glass-panel rounded-2xl p-6 relative overflow-hidden">
<div class="absolute top-0 right-0 p-4">
<span id="zoneBadge" class="px-3 py-1 rounded-full text-xs font-semibold bg-gray-700 text-gray-300 zone-indicator">
Highway Zone A
</span>
</div>
<!-- Speed Gauge -->
<div class="flex flex-col items-center justify-center py-4">
<div class="relative w-48 h-48">
<div class="absolute inset-0 speed-gauge"></div>
<div class="absolute inset-2 speed-gauge-inner flex flex-col items-center justify-center">
<span id="currentSpeed" class="text-5xl font-bold tabular-nums">0</span>
<span class="text-gray-400 text-sm mt-1">km/h</span>
</div>
<!-- Speed Indicator Needle (visual) -->
<div id="speedNeedle" class="absolute top-1/2 left-1/2 w-1 h-20 bg-white origin-bottom transform -translate-x-1/2 -translate-y-full rotate-0 transition-transform duration-300" style="transform: translate(-50%, -100%) rotate(-90deg);"></div>
</div>
</div>
<!-- Speed Limit Display -->
<div class="flex justify-center items-center gap-4 mt-2">
<div class="text-center">
<p class="text-xs text-gray-400 mb-1">Speed Limit</p>
<div class="flex items-center gap-1 text-2xl font-bold text-white">
<i data-lucide="alert-circle" class="w-5 h-5 text-amber-400"></i>
<span id="speedLimit">120</span>
<span class="text-sm text-gray-400">km/h</span>
</div>
</div>
</div>
<!-- Camera Status -->
<div class="mt-4 flex items-center justify-center gap-2 text-sm">
<div id="cameraStatus" class="flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-500/20 text-emerald-400">
<div class="w-2 h-2 bg-emerald-400 rounded-full animate-pulse"></div>
<span>Camera Connected</span>
</div>
</div>
</div>
<!-- Control Buttons -->
<div class="grid grid-cols-2 gap-3">
<button id="startTracking" onclick="toggleTracking()" class="glass-panel rounded-xl p-4 flex flex-col items-center gap-2 hover:bg-white/10 transition-all active:scale-95">
<div id="trackingIcon" class="w-12 h-12 rounded-full bg-emerald-500/20 flex items-center justify-center text-emerald-400">
<i data-lucide="play" class="w-6 h-6"></i>
</div>
<span id="trackingText" class="font-medium">Start Tracking</span>
</button>
<button onclick="showHistory()" class="glass-panel rounded-xl p-4 flex flex-col items-center gap-2 hover:bg-white/10 transition-all active:scale-95">
<div class="w-12 h-12 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400">
<i data-lucide="history" class="w-6 h-6"></i>
</div>
<span class="font-medium">History</span>
</button>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-3 gap-3">
<div class="glass-panel rounded-xl p-3 text-center">
<i data-lucide="navigation" class="w-5 h-5 mx-auto mb-1 text-blue-400"></i>
<p class="text-xs text-gray-400">Distance</p>
<p id="totalDistance" class="font-bold text-sm">0 km</p>
</div>
<div class="glass-panel rounded-xl p-3 text-center">
<i data-lucide="clock" class="w-5 h-6 mx-auto mb-1 text-purple-400"></i>
<p class="text-xs text-gray-400">Drive Time</p>
<p id="driveTime" class="font-bold text-sm">00:00</p>
</div>
<div class="glass-panel rounded-xl p-3 text-center">
<i data-lucide="shield-check" class="w-5 h-5 mx-auto mb-1 text-emerald-400"></i>
<p class="text-xs text-gray-400">Safe Drives</p>
<p id="safeDrives" class="font-bold text-sm">0</p>
</div>
</div>
<!-- Rewards Section -->
<div>
<div class="flex items-center justify-between mb-3">
<h2 class="font-semibold text-lg">Your Rewards 🎁</h2>
<button onclick="showAllCoupons()" class="text-sm text-emerald-400">View All</button>
</div>
<div id="couponsList" class="space-y-3">
<!-- Coupons will be dynamically inserted here -->
</div>
</div>
<!-- Recent Alerts -->
<div>
<h2 class="font-semibold text-lg mb-3">Recent Alerts</h2>
<div id="alertsList" class="space-y-2 max-h-40 overflow-y-auto no-scrollbar">
<div class="glass-panel rounded-lg p-3 text-center text-gray-400 text-sm">
No alerts yet. Start driving to receive alerts.
</div>
</div>
</div>
</main>
<!-- Bottom Navigation -->
<nav class="fixed bottom-0 left-0 right-0 glass-panel border-t border-white/10 px-6 py-3 max-w-md mx-auto">
<div class="flex justify-around items-center">
<button onclick="showDashboard()" class="flex flex-col items-center gap-1 text-emerald-400">
<i data-lucide="home" class="w-6 h-6"></i>
<span class="text-xs">Home</span>
</button>
<button onclick="showRewards()" class="flex flex-col items-center gap-1 text-gray-400 hover:text-white transition-colors">
<i data-lucide="gift" class="w-6 h-6"></i>
<span class="text-xs">Rewards</span>
</button>
<button onclick="showFines()" class="flex flex-col items-center gap-1 text-gray-400 hover:text-white transition-colors">
<i data-lucide="receipt" class="w-6 h-6"></i>
<span class="text-xs">Fines</span>
</button>
<button onclick="showSettings()" class="flex flex-col items-center gap-1 text-gray-400 hover:text-white transition-colors">
<i data-lucide="settings" class="w-6 h-6"></i>
<span class="text-xs">Settings</span>
</button>
</div>
</nav>
<!-- Overspeeding Alert Modal -->
<div id="overspeedModal" class="hidden fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4">
<div class="glass-panel rounded-2xl p-6 w-full max-w-sm slide-up">
<div class="flex items-center gap-3 mb-4">
<div class="w-12 h-12 rounded-full bg-red-500/20 flex items-center justify-center animate-pulse">
<i data-lucide="alert-triangle" class="w-6 h-6 text-red-500"></i>
</div>
<div>
<h3 class="font-bold text-lg text-red-400">SPEEDING ALERT!</h3>
<p class="text-sm text-gray-400">Camera detected violation</p>
</div>
</div>
<div class="bg-red-500/10 rounded-xl p-4 mb-4">
<div class="flex justify-between items-center mb-2">
<span class="text-gray-400">Your Speed</span>
<span id="violationSpeed" class="text-2xl font-bold text-red-400">135 km/h</span>
</div>
<div class="flex justify-between items-center mb-2">
<span class="text-gray-400">Speed Limit</span>
<span id="violationLimit" class="font-semibold">120 km/h</span>
</div>
<div class="flex justify-between items-center pt-2 border-t border-white/10">
<span class="text-gray-400">Excess</span>
<span id="excessSpeed" class="font-bold text-red-400">+15 km/h</span>
</div>
</div>
<div class="fine-alert rounded-xl p-4 mb-4 text-center">
<p class="text-sm mb-1">Fine Amount</p>
<p id="fineAmount" class="text-3xl font-bold">$50.00</p>
</div>
<button onclick="acknowledgeFine()" class="w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-3 rounded-xl transition-colors">
Acknowledge & Pay
</button>
</div>
</div>
<!-- Reward Earned Modal -->
<div id="rewardModal" class="hidden fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4">
<div class="glass-panel rounded-2xl p-6 w-full max-w-sm slide-up text-center">
<div class="w-20 h-20 mx-auto mb-4 reward-badge rounded-full flex items-center justify-center animate-bounce">
<i data-lucide="trophy" class="w-10 h-10 text-white"></i>
</div>
<h3 class="font-bold text-2xl mb-2">Reward Earned! 🎉</h3>
<p class="text-gray-400 mb-4">Great job maintaining safe speed!</p>
<div class="coupon-card rounded-xl p-4 mb-4">
<div class="flex items-center justify-between mb-2">
<span class="text-xs bg-white/20 px-2 py-1 rounded">COUPON</span>
<span id="rewardPoints" class="text-2xl font-bold">+50 pts</span>
</div>
<p id="rewardTitle" class="font-semibold text-lg">Fuel Discount</p>
<p id="rewardDesc" class="text-sm text-white/80">10% off at Shell Gas Stations</p>
</div>
<button onclick="closeReward()" class="w-full bg-gradient-to-r from-emerald-500 to-blue-500 hover:opacity-90 text-white font-semibold py-3 rounded-xl transition-opacity">
Awesome!
</button>
</div>
</div>
<!-- Pages (Hidden by default) -->
<!-- History Page -->
<div id="historyPage" class="hidden fixed inset-0 z-40 bg-gray-900 pt-16 pb-20 overflow-y-auto">
<div class="p-4">
<div class="flex items-center gap-3 mb-6">
<button onclick="showDashboard()" class="w-10 h-10 rounded-full glass-panel flex items-center justify-center">
<i data-lucide="arrow-left" class="w-5 h-5"></i>
</button>
<h2 class="font-bold text-xl">Trip History</h2>
</div>
<div id="historyList" class="space-y-3">
<!-- History items -->
</div>
</div>
</div>
<!-- All Coupons Page -->
<div id="couponsPage" class="hidden fixed inset-0 z-40 bg-gray-900 pt-16 pb-20 overflow-y-auto">
<div class="p-4">
<div class="flex items-center gap-3 mb-6">
<button onclick="showDashboard()" class="w-10 h-10 rounded-full glass-panel flex items-center justify-center">
<i data-lucide="arrow-left" class="w-5 h-5"></i>
</button>
<h2 class="font-bold text-xl">All Rewards</h2>
</div>
<div id="allCouponsList" class="grid grid-cols-2 gap-3">
<!-- All coupons -->
</div>
</div>
</div>
</div>
<script>
// Initialize Lucide icons
lucide.createIcons();
// App State
const state = {
isTracking: false,
currentSpeed: 0,
speedLimit: 120,
totalPoints: parseInt(localStorage.getItem('totalPoints')) || 0,
totalDistance: parseFloat(localStorage.getItem('totalDistance')) || 0,
safeDrives: parseInt(localStorage.getItem('safeDrives')) || 0,
driveTime: parseInt(localStorage.getItem('driveTime')) || 0,
fines: JSON.parse(localStorage.getItem('fines')) || [],
trips: JSON.parse(localStorage.getItem('trips')) || [],
coupons: JSON.parse(localStorage.getItem('coupons')) || [
{ id: 1, title: 'Fuel Discount', desc: '10% off at Shell', points: 50, claimed: false, icon: 'fuel' },
{ id: 2, title: 'Coffee Free', desc: 'Free coffee at Starbucks', points: 100, claimed: false, icon: 'coffee' },
{ id: 3, title: 'Car Wash', desc: '50% off premium wash', points: 75, claimed: false, icon: 'droplets' },
{ id: 4, title: 'Parking', desc: '2 hours free parking', points: 30, claimed: false, icon: 'parking-circle' }
],
currentZone: 'Highway Zone A',
watchId: null,
startTime: null,
lastPosition: null,
consecutiveSafeChecks: 0
};
// Speed Zones (Simulated camera zones)
const speedZones = [
{ name: 'Highway Zone A', limit: 120, lat: 0, lng: 0, radius: 5000 },
{ name: 'Urban Zone B', limit: 80, lat: 0, lng: 0, radius: 3000 },
{ name: 'School Zone C', limit: 40, lat: 0, lng: 0, radius: 1000 },
{ name: 'Highway Zone D', limit: 100, lat: 0, lng: 0, radius: 4000 }
];
// Initialize
function init() {
updateUI();
renderCoupons();
renderHistory();
}
// Update UI
function updateUI() {
document.getElementById('totalPoints').textContent = state.totalPoints;
document.getElementById('totalDistance').textContent = state.totalDistance.toFixed(1) + ' km';
document.getElementById('safeDrives').textContent = state.safeDrives;
const minutes = Math.floor(state.driveTime / 60);
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
document.getElementById('driveTime').textContent =
hours > 0 ? `${hours}:${mins.toString().padStart(2, '0')}` : `00:${mins.toString().padStart(2, '0')}`;
}
// Toggle Tracking
function toggleTracking() {
if (!state.isTracking) {
startTracking();
} else {
stopTracking();
}
}
function startTracking() {
if (!navigator.geolocation) {
alert('Geolocation is not supported by your device');
return;
}
state.isTracking = true;
state.startTime = Date.now();
// Update UI
document.getElementById('trackingIcon').className = 'w-12 h-12 rounded-full bg-red-500/20 flex items-center justify-center text-red-400 tracking-active';
document.getElementById('trackingText').textContent = 'Stop Tracking';
document.getElementById('trackingIcon').innerHTML = '<i data-lucide="square" class="w-6 h-6"></i>';
lucide.createIcons();
// Start GPS tracking
state.watchId = navigator.geolocation.watchPosition(
handlePosition,
handleError,
{
enableHighAccuracy: true,
maximumAge: 1000,
timeout: 5000
}
);
// Simulate zone changes and speed variations for demo
simulateDriving();
addAlert('Tracking started - Camera connected', 'success');
}
function stopTracking() {
state.isTracking = false;
if (state.watchId) {
navigator.geolocation.clearWatch(state.watchId);
}
// Calculate trip stats
if (state.startTime) {
const tripDuration = Math.floor((Date.now() - state.startTime) / 60000);
state.driveTime += tripDuration;
const trip = {
date: new Date().toISOString(),
duration: tripDuration,
distance: (state.currentSpeed * tripDuration / 60).toFixed(1),
maxSpeed: state.currentSpeed,
status: 'completed'
};
state.trips.push(trip);
localStorage.setItem('trips', JSON.stringify(state.trips));
}
// Update UI
document.getElementById('trackingIcon').className = 'w-12 h-12 rounded-full bg-emerald-500/20 flex items-center justify-center text-emerald-400';
document.getElementById('trackingText').textContent = 'Start Tracking';
document.getElementById('trackingIcon').innerHTML = '<i data-lucide="play" class="w-6 h-6"></i>';
document.getElementById('currentSpeed').textContent = '0';
document.getElementById('speedNeedle').style.transform = 'translate(-50%, -100%) rotate(-90deg)';
lucide.createIcons();
clearInterval(state.simulationInterval);
localStorage.setItem('driveTime', state.driveTime);
updateUI();
renderHistory();
addAlert('Tracking stopped - Trip saved', 'info');
}
function handlePosition(position) {
const speed = position.coords.speed || 0;
const speedKmh = Math.round(speed * 3.6);
updateSpeed(speedKmh);
// Calculate distance
if (state.lastPosition) {
const distance = calculateDistance(
state.lastPosition.coords.latitude,
state.lastPosition.coords.longitude,
position.coords.latitude,
position.coords.longitude
);
state.totalDistance += distance;
localStorage.setItem('totalDistance', state.totalDistance);
}
state.lastPosition = position;
updateUI();
}
function handleError(error) {
console.error('GPS Error:', error);
addAlert('GPS signal weak - Using simulated data', 'warning');
}
// Calculate distance between coordinates
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // Earth's radius in km
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
// Simulate driving for demo purposes
function simulateDriving() {
let baseSpeed = 90;
let zoneIndex = 0;
state.simulationInterval = setInterval(() => {
// Vary speed
const variation = Math.random() * 20 - 10;
let newSpeed = Math.max(0, baseSpeed + variation);
// Occasionally speed up for demo
if (Math.random() > 0.8) {
newSpeed += 30;
}
newSpeed = Math.round(newSpeed);
updateSpeed(newSpeed);
// Change zones periodically
if (Math.random() > 0.95) {
zoneIndex = (zoneIndex + 1) % speedZones.length;
changeZone(speedZones[zoneIndex]);
}
// Update distance
state.totalDistance += (newSpeed / 3600); // km per second
localStorage.setItem('totalDistance', state.totalDistance);
updateUI();
}, 1000);
}
function updateSpeed(speed) {
state.currentSpeed = speed;
document.getElementById('currentSpeed').textContent = speed;
// Update needle rotation (-90 to 270 degrees)
const maxSpeed = 200;
const rotation = -90 + (speed / maxSpeed) * 360;
document.getElementById('speedNeedle').style.transform = `translate(-50%, -100%) rotate(${rotation}deg)`;
// Check speed limit
checkSpeedLimit(speed);
}
function changeZone(zone) {
state.currentZone = zone.name;
state.speedLimit = zone.limit;
document.getElementById('zoneBadge').textContent = zone.name;
document.getElementById('speedLimit').textContent = zone.limit;
// Visual feedback
document.getElementById('zoneBadge').classList.add('scale-110');
setTimeout(() => {
document.getElementById('zoneBadge').classList.remove('scale-110');
}, 300);
addAlert(`Entered ${zone.name} - Limit: ${zone.limit} km/h`, 'info');
}
function checkSpeedLimit(speed) {
const excess = speed - state.speedLimit;
if (excess > 5) {
// Overspeeding - Issue fine
issueFine(speed, excess);
state.consecutiveSafeChecks = 0;
} else if (speed < state.speedLimit && speed > 20) {
// Safe driving - Accumulate points
state.consecutiveSafeChecks++;
if (state.consecutiveSafeChecks >= 30) { // 30 seconds of safe driving
issueReward();
state.consecutiveSafeChecks = 0;
state.safeDrives++;
localStorage.setItem('safeDrives', state.safeDrives);
}
}
}
function issueFine(speed, excess) {
const fineAmount = excess > 30 ? 200 : excess > 20 ? 100 : 50;
const fine = {
id: Date.now(),
date: new Date().toISOString(),
speed: speed,
limit: state.speedLimit,
excess: excess,
amount: fineAmount,
zone: state.currentZone,
paid: false
};
state.fines.push(fine);
localStorage.setItem('fines', JSON.stringify(state.fines));
// Show modal
document.getElementById('violationSpeed').textContent = speed + ' km/h';
document.getElementById('violationLimit').textContent = state.speedLimit + ' km/h';
document.getElementById('excessSpeed').textContent = '+' + excess + ' km/h';
document.getElementById('fineAmount').textContent = '$' + fineAmount + '.00';
document.getElementById('overspeedModal').classList.remove('hidden');
// Add alert
addAlert(`FINE ISSUED: $${fineAmount} for speeding in ${state.currentZone}`, 'error');
// Haptic feedback if available
if (navigator.vibrate) {
navigator.vibrate([200, 100, 200]);
}
}
function acknowledgeFine() {
document.getElementById('overspeedModal').classList.add('hidden');
addAlert('Fine acknowledged. Drive safely!', 'warning');
}
function issueReward() {
const points = Math.floor(Math.random() * 30) + 20;
state.totalPoints += points;
localStorage.setItem('totalPoints', state.totalPoints);
// Pick random reward
const rewards = [
{ title: 'Fuel Discount', desc: '10% off at Shell Gas Stations', points: points },
{ title: 'Free Coffee', desc: 'Complimentary coffee at Starbucks', points: points },
{ title: 'Car Wash', desc: 'Premium wash 50% off', points: points },
{ title: 'Parking Credit', desc: '2 hours free parking', points: points }
];
const reward = rewards[Math.floor(Math.random() * rewards.length)];
// Show modal
document.getElementById('rewardPoints').textContent = '+' + points + ' pts';
document.getElementById('rewardTitle').textContent = reward.title;
document.getElementById('rewardDesc').textContent = reward.desc;
document.getElementById('rewardModal').classList.remove('hidden');
addAlert(`REWARD EARNED: ${points} points for safe driving!`, 'success');
updateUI();
renderCoupons();
}
function closeReward() {
document.getElementById('rewardModal').classList.add('hidden');
}
function addAlert(message, type) {
const alertsList = document.getElementById('alertsList');
const alertDiv = document.createElement('div');
let bgClass = 'glass-panel';
let icon = 'info';
if (type === 'error') {
bgClass = 'bg-red-500/20 border border-red-500/30';
icon = 'alert-triangle';
} else if (type === 'success') {
bgClass = 'bg-emerald-500/20 border border-emerald-500/30';
icon = 'check-circle';
} else if (type === 'warning') {
bgClass = 'bg-amber-500/20 border border-amber-500/30';
icon = 'alert-circle';
}
alertDiv.className = `${bgClass} rounded-lg p-3 flex items-center gap-3 slide-up`;
alertDiv.innerHTML = `
<i data-lucide="${icon}" class="w-5 h-5 ${type === 'error' ? 'text-red-400' : type === 'success' ? 'text-emerald-400' : 'text-amber-400'}"></i>
<p class="text-sm flex-1">${message}</p>
<span class="text-xs text-gray-400">${new Date().toLocaleTimeString()}</span>
`;
// Remove "no alerts" message
if (alertsList.children.length === 1 && alertsList.children[0].textContent.includes('No alerts')) {
alertsList.innerHTML = '';
}
alertsList.insertBefore(alertDiv, alertsList.firstChild);
lucide.createIcons();
// Keep only last 10 alerts
while (alertsList.children.length > 10) {
alertsList.removeChild(alertsList.lastChild);
}
}
function renderCoupons() {
const container = document.getElementById('couponsList');
const availableCoupons = state.coupons.filter(c => !c.claimed && c.points <= state.totalPoints);
if (availableCoupons.length === 0) {
container.innerHTML = `
<div class="glass-panel rounded-xl p-4 text-center">
<p class="text-gray-400 text-sm">Keep driving safely to earn more rewards!</p>
<p class="text-emerald-400 text-xs mt-1">Next reward at ${Math.ceil(state.totalPoints / 50) * 50} points</p>
</div>
`;
return;
}
container.innerHTML = availableCoupons.slice(0, 2).map(coupon => `
<div class="coupon-card rounded-xl p-4 relative overflow-hidden">
<div class="absolute top-0 right-0 -mt-2 -mr-2 w-16 h-16 bg-white/20 rounded-full blur-xl"></div>
<div class="flex items-start justify-between relative z-10">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-white/20 flex items-center justify-center">
<i data-lucide="${coupon.icon}" class="w-5 h-5"></i>
</div>
<div>
<h4 class="font-semibold">${coupon.title}</h4>
<p class="text-xs text-white/80">${coupon.desc}</p>
</div>
</div>
<div class="text-right">
<span class="text-xs bg-white/20 px-2 py-1 rounded">${coupon.points} pts</span>
</div>
</div>
<button onclick="claimCoupon(${coupon.id})" class="mt-3 w-full bg-white text-purple-600 font-semibold py-2 rounded-lg text-sm hover:bg-gray-100 transition-colors">
Claim Reward
</button>
</div>
`).join('');
lucide.createIcons();
}
function claimCoupon(id) {
const coupon = state.coupons.find(c => c.id === id);
if (coupon && state.totalPoints >= coupon.points) {
state.totalPoints -= coupon.points;
coupon.claimed = true;
localStorage.setItem('totalPoints', state.totalPoints);
localStorage.setItem('coupons', JSON.stringify(state.coupons));
updateUI();
renderCoupons();
addAlert(`Coupon claimed: ${coupon.title}!`, 'success');
}
}
function renderHistory() {
const container = document.getElementById('historyList');
if (state.trips.length === 0) {
container.innerHTML = '<p class="text-center text-gray-400 py-8">No trips recorded yet</p>';
return;
}
container.innerHTML = state.trips.slice().reverse().map(trip => `
<div class="glass-panel rounded-xl p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium">${new Date(trip.date).toLocaleDateString()}</span>
<span class="text-xs px-2 py-1 rounded-full ${trip.status === 'completed' ? 'bg-emerald-500/20 text-emerald-400' : 'bg-amber-500/20 text-amber-400'}">
${trip.status}
</span>
</div>
<div class="flex items-center justify-between text-sm text-gray-400">
<span><i data-lucide="clock" class="w-4 h-4 inline mr-1"></i> ${trip.duration} min</span>
<span><i data-lucide="navigation" class="w-4 h-4 inline mr-1"></i> ${trip.distance} km</span>
<span><i data-lucide="gauge" class="w-4 h-4 inline mr-1"></i> ${trip.maxSpeed} km/h</span>
</div>
</div>
`).join('');
lucide.createIcons();
}
// Navigation functions
function showDashboard() {
document.getElementById('historyPage').classList.add('hidden');
document.getElementById('couponsPage').classList.add('hidden');
}
function showHistory() {
document.getElementById('historyPage').classList.remove('hidden');
}
function showAllCoupons() {
const container = document.getElementById('allCouponsList');
container.innerHTML = state.coupons.map(coupon => `
<div class="glass-panel rounded-xl p-3 ${coupon.claimed ? 'opacity-50' : ''}">
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center mb-2">
<i data-lucide="${coupon.icon}" class="w-5 h-5"></i>
</div>
<h4 class="font-semibold text-sm">${coupon.title}</h4>
<p class="text-xs text-gray-400 mb-2">${coupon.desc}</p>
<div class="flex items-center justify-between">
<span class="text-xs text-emerald-400">${coupon.points} pts</span>
${coupon.claimed ?
'<span class="text-xs bg-gray-700 px-2 py-1 rounded">Claimed</span>' :
`<button onclick="claimCoupon(${coupon.id})" class="text-xs bg-emerald-500 px-2 py-1 rounded text-white" ${state.totalPoints < coupon.points ? 'disabled class="opacity-50"' : ''}>Claim</button>`
}
</div>
</div>
`).join('');
lucide.createIcons();
document.getElementById('couponsPage').classList.remove('hidden');
}
function showRewards() {
showAllCoupons();
}
function showFines() {
alert(`You have ${state.fines.length} fines totaling $${state.fines.reduce((a, b) => a + b.amount, 0)}. Feature coming soon!`);
}
function showSettings() {
alert('Settings: GPS Accuracy, Alert Sounds, Notifications - Coming soon!');
}
function showProfile() {
alert(`Profile Stats:\nTotal Points: ${state.totalPoints}\nSafe Drives: ${state.safeDrives}\nTotal Distance: ${state.totalDistance.toFixed(1)} km\nFines: ${state.fines.length}`);
}
// Initialize app
init();
</script>
<script src="https://deepsite.hf.co/deepsite-badge.js"></script>
</body>
</html>