<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>RideX — On-demand rides (Demo)</title>
<meta name="description" content="RideX — Book reliable rides in minutes. Customer and Driver dashboards. Demo with localStorage persistence."/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&display=swap" rel="stylesheet">
<style>
:root{
--bg:#0b1220; --card:#071029; --muted:#9aa7d6; --accent:#07a0ff; --accent-2:#6ddcff; --glass:rgba(255,255,255,0.04);
--radius:14px; --maxw:1200px;
}
*{box-sizing:border-box}
body{margin:0;font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;background:linear-gradient(180deg,#071028 0%, #08172d 40%, #071028 100%);color:#e7f0ff;min-height:100vh}
a{color:inherit;text-decoration:none}
.container{max-width:var(--maxw);margin:0 auto;padding:28px}
header{display:flex;align-items:center;justify-content:space-between;gap:16px}
.brand{display:flex;gap:12px;align-items:center}
.logo{width:56px;height:56px;border-radius:12px;background:linear-gradient(135deg,var(--accent),var(--accent-2));display:grid;place-items:center;font-weight:800;color:#041027}
.nav{display:flex;gap:10px;align-items:center}
.nav button{background:transparent;border:1px solid rgba(255,255,255,0.06);padding:8px 12px;border-radius:999px;color:var(--muted);cursor:pointer}
.hero{display:grid;grid-template-columns:1fr 520px;gap:28px;align-items:center;margin-top:28px}
.hero-left h1{font-size:36px;margin:0 0 10px 0;line-height:1.02}
.tag{color:var(--muted);margin-bottom:18px}
.cta-row{display:flex;gap:12px;flex-wrap:wrap}
.btn{background:linear-gradient(90deg,var(--accent),var(--accent-2));color:#041027;border:none;padding:12px 18px;border-radius:12px;font-weight:700;cursor:pointer;box-shadow:0 10px 30px rgba(7,160,255,0.12)}
.btn.ghost{background:transparent;border:1px solid rgba(255,255,255,0.06);color:var(--muted);box-shadow:none}
.hero-right .card{background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01)); border-radius:16px;padding:14px;border:1px solid rgba(255,255,255,0.03)}
.search{display:flex;gap:8px;align-items:center}
.search input, .search select{background:transparent;border:1px solid rgba(255,255,255,0.06);padding:10px;border-radius:10px;color:var(--muted);min-width:0}
.search input::placeholder{color:rgba(231,240,255,0.38)}
.small{font-size:13px;color:var(--muted)}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:28px}
.feature{background:var(--card);border-radius:12px;padding:16px;border:1px solid rgba(255,255,255,0.03)}
footer{margin-top:40px;padding-top:22px;border-top:1px solid rgba(255,255,255,0.02);display:flex;justify-content:space-between;align-items:center;color:var(--muted)}
/* Map */
#map{height:420px;border-radius:12px;border:1px solid rgba(255,255,255,0.03);overflow:hidden}
/* Modals */
.modal-back{position:fixed;inset:0;background:rgba(0,0,0,0.6);display:none;align-items:center;justify-content:center;z-index:40}
.modal{background:linear-gradient(180deg,#0b1220,#071028);padding:20px;border-radius:12px;width:420px;border:1px solid rgba(255,255,255,0.04);box-shadow:0 50px 120px rgba(0,0,0,0.6)}
.field{display:flex;flex-direction:column;margin-bottom:10px}
.field label{font-size:13px;color:var(--muted);margin-bottom:6px}
.field input{padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:transparent;color:#e7f0ff}
.row{display:flex;gap:8px}
.muted{color:var(--muted)}
/* responsive */
@media(max-width:1024px){ .hero{grid-template-columns:1fr} #map{height:300px} .hero-right{order:2} }
</style>
</head>
<body>
<div class="container">
<header>
<div class="brand">
<div class="logo">RX</div>
<div>
<div style="font-weight:700">RIDEX</div>
<div class="small">Safe • Fast • Local</div>
</div>
</div>
<nav class="nav" aria-label="Main">
<button class="nav-btn ghost" id="navHome">Home</button>
<button class="nav-btn ghost" id="navHow">How it works</button>
<button class="nav-btn ghost" id="btnOpenLogin">Sign in</button>
</nav>
</header>
<main class="hero" role="main">
<div class="hero-left">
<h1>Book a ride in minutes</h1>
<div class="tag">Quick pickups, reliable drivers, transparent fares. Serving cities across India.</div>
<div class="cta-row">
<button class="btn" id="btnCustomer">Book a ride</button>
<button class="btn ghost" id="btnDriver">Driver sign in</button>
<button class="btn ghost" id="btnSupport">Customer care</button>
</div>
<div class="small" style="margin-top:18px">Contact: +91 9985808902 • info.ghinnovationssolutions@gmail.com</div>
<div class="grid-3" style="margin-top:24px">
<div class="feature">
<div style="font-weight:700">Transparent fares</div>
<div class="muted">Know your fare before you ride — no surprises.</div>
</div>
<div class="feature">
<div style="font-weight:700">Trusted drivers</div>
<div class="muted">Background-checked drivers and verified vehicles.</div>
</div>
<div class="feature">
<div style="font-weight:700">24/7 support</div>
<div class="muted">Help available any time you need it.</div>
</div>
</div>
</div>
<!-- Booking card with small map (right) -->
<div class="hero-right">
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center">
<div style="font-weight:700">Quick Book</div>
<div class="small">Currency: ₹ (INR)</div>
</div>
<div style="margin-top:12px" class="search">
<div style="flex:1">
<input id="pickup" placeholder="Pickup (type or click map)" aria-label="Pickup"/>
</div>
<div style="flex:1">
<input id="drop" placeholder="Drop (type or click map)" aria-label="Drop"/>
</div>
</div>
<div style="display:flex;gap:8px;margin-top:10px;align-items:center">
<select id="rideType" style="padding:10px;border-radius:10px;border:1px solid rgba(255,255,255,0.04);background:transparent;color:var(--muted)">
<option value="mini">Mini</option><option value="sedan">Sedan</option><option value="suv">SUV</option>
</select>
<button class="btn" id="getQuote">Get fare</button>
<div style="flex:1"></div>
<button class="btn ghost" id="bookRide">Book</button>
</div>
<div style="margin-top:12px;display:flex;gap:8px;align-items:center">
<div class="small">Est dist:</div><div id="estKm" style="font-weight:800">—</div>
<div style="flex:1"></div>
<div class="small">Fare:</div><div id="estFare" style="font-weight:800">₹0.00</div>
</div>
</div>
<div style="margin-top:14px">
<div id="map"></div>
</div>
</div>
</main>
<section id="how" style="margin-top:28px">
<div style="display:flex;justify-content:space-between;gap:16px;align-items:flex-start">
<div style="flex:1" class="card">
<h3 style="margin-top:0">How it works</h3>
<ol class="muted">
<li>Enter pickup & drop, get an instant fare.</li>
<li>Confirm booking — nearby driver accepts.</li>
<li>Live map shows driver ETA and route.</li>
</ol>
</div>
<div style="width:320px" class="card">
<h4 style="margin:0">Need help?</h4>
<div class="muted" style="margin-top:8px">Call: +91 9985808902<br/>Email: info.ghinnovationssolutions@gmail.com</div>
</div>
</div>
</section>
<footer>
<div class="small">© 2025 RideX (demo) — Built for GH Innovations</div>
<div class="small">Made with ♥ in India</div>
</footer>
</div>
<!-- Modals -->
<div class="modal-back" id="modalBack">
<div class="modal" id="modalContent" role="dialog" aria-modal="true">
<!-- dynamic content injected -->
</div>
</div>
<!-- ====== SCRIPTS ====== -->
<script>
/*
Demo app behavior:
- Stores users in localStorage.key 'rx_users' (array of {name,email,password})
- Stores drivers in 'rx_drivers'
- Stores rides in 'rx_rides'
- Google Maps (replace YOUR_GOOGLE_MAPS_API_KEY in script tag below)
*/
const INR = new Intl.NumberFormat('en-IN',{style:'currency',currency:'INR'});
const $ = sel => document.querySelector(sel);
const $$ = sel => Array.from(document.querySelectorAll(sel));
const modalBack = document.getElementById('modalBack');
const modalContent = document.getElementById('modalContent');
function read(key){ try{ return JSON.parse(localStorage.getItem(key))||[] }catch(e){ return [] } }
function write(key, v){ localStorage.setItem(key, JSON.stringify(v)) }
// open modal helper
function openModal(html){ modalContent.innerHTML = html; modalBack.style.display='flex'; }
function closeModal(){ modalBack.style.display='none'; modalContent.innerHTML=''; }
// show login/register flows
document.getElementById('btnOpenLogin')?.addEventListener('click', ()=> showLogin());
document.getElementById('btnCustomer').addEventListener('click', ()=> showLogin());
document.getElementById('btnDriver').addEventListener('click', ()=> showDriverOtp());
document.getElementById('btnSupport').addEventListener('click', ()=> alert('Contact: +91 9985808902\\ninfo.ghinnovationssolutions@gmail.com'));
// login modal
function showLogin(){
openModal(`
<h3 style="margin-top:0">Customer Login</h3>
<div class="field"><label>Email</label><input id="m_email" type="email"></div>
<div class="field"><label>Password</label><input id="m_pass" type="password"></div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button class="btn ghost" id="m_forgot">Forgot</button>
<button class="btn" id="m_login">Sign in</button>
</div>
<div style="margin-top:12px" class="small">New to RideX? <a href="#" id="m_register_link">Create account</a></div>
`);
document.getElementById('m_login').onclick = doLogin;
document.getElementById('m_forgot').onclick = doForgot;
document.getElementById('m_register_link').onclick = (e)=>{ e.preventDefault(); showRegister(); };
}
function showRegister(){
openModal(`
<h3 style="margin-top:0">Create account</h3>
<div class="field"><label>Full name</label><input id="r_name"></div>
<div class="field"><label>Email</label><input id="r_email" type="email"></div>
<div class="field"><label>Password</label><input id="r_pass" type="password"></div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button class="btn ghost" id="r_cancel">Cancel</button>
<button class="btn" id="r_signup">Create</button>
</div>
`);
document.getElementById('r_cancel').onclick = closeModal;
document.getElementById('r_signup').onclick = doRegister;
}
function doRegister(){
const name = modalContent.querySelector('#r_name').value.trim();
const email = modalContent.querySelector('#r_email').value.trim().toLowerCase();
const pass = modalContent.querySelector('#r_pass').value;
if(!name||!email||!pass){ alert('Complete all fields'); return; }
let users = read('rx_users');
if(users.find(u=>u.email===email)){ alert('Email already registered'); return; }
users.push({id:'u_'+Math.random().toString(36).slice(2,8),name,email,password:pass});
write('rx_users', users);
alert('Account created — you can now sign in');
closeModal();
showLogin();
}
function doLogin(){
const email = modalContent.querySelector('#m_email').value.trim().toLowerCase();
const pass = modalContent.querySelector('#m_pass').value;
let users = read('rx_users');
const u = users.find(x=>x.email===email);
if(!u){ if(confirm('No account found. Register?')){ closeModal(); showRegister(); } else return; }
if(u.password !== pass){ if(confirm('Wrong password. Reset?')){ doForgot(); } else return; }
// login success: persist current user id
localStorage.setItem('rx_current', JSON.stringify(u));
closeModal();
alert('Signed in — welcome ' + u.name);
// show user name in header
updateUserUI();
}
function doForgot(){
const email = modalContent.querySelector('#m_email')?.value?.trim()?.toLowerCase() || prompt('Enter your email to receive reset instructions');
if(!email) return;
let users = read('rx_users');
const u = users.find(x=>x.email===email);
if(!u) return alert('No account for this email');
// demo: show password (unsafe in real app). In production send reset email.
alert('Demo reset — your password is: ' + u.password);
}
// driver OTP modal
function showDriverOtp(){
openModal(`
<h3 style="margin-top:0">Driver login (OTP)</h3>
<div class="field"><label>Mobile</label><input id="drv_phone" placeholder="+91..."></div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button class="btn ghost" id="drv_send">Send OTP</button>
</div>
`);
document.getElementById('drv_send').onclick = ()=> {
const phone = modalContent.querySelector('#drv_phone').value.trim();
if(!phone){ alert('Enter phone'); return; }
const otp = Math.floor(1000 + Math.random()*9000).toString();
localStorage.setItem('rx_driver_otp', otp);
// In production send via SMS (Twilio/Firebase). Here show in alert for demo.
alert('OTP (demo): ' + otp);
// show OTP entry
modalContent.innerHTML += `<div class="field" style="margin-top:10px"><label>Enter OTP</label><input id="drv_otp" /></div>
<div style="display:flex;gap:8px;justify-content:flex-end"><button class="btn" id="drv_verify">Verify</button></div>`;
document.getElementById('drv_verify').onclick = verifyDriverOtp;
};
}
function verifyDriverOtp(){
const entered = modalContent.querySelector('#drv_otp').value.trim();
const otp = localStorage.getItem('rx_driver_otp');
if(entered === otp){
// mark driver logged in
const driver = {id:'d_'+Math.random().toString(36).slice(2,8),phone: modalContent.querySelector('#drv_phone').value};
let drivers = read('rx_drivers'); drivers.push(driver); write('rx_drivers', drivers);
localStorage.setItem('rx_current_driver', JSON.stringify(driver));
alert('Driver logged in (demo)');
closeModal();
updateUserUI();
// show driver controls (simulate)
openDriverPanel();
} else { alert('Invalid OTP'); }
}
// update user UI
function updateUserUI(){
const cur = JSON.parse(localStorage.getItem('rx_current') || 'null');
const curDrv = JSON.parse(localStorage.getItem('rx_current_driver') || 'null');
if(cur) {
document.querySelector('.small') && (document.querySelector('.small').textContent = 'Hi, ' + cur.name);
document.querySelector('#btnOpenLogin') && (document.querySelector('#btnOpenLogin').textContent = 'Account');
} else if(curDrv){
document.querySelector('.small') && (document.querySelector('.small').textContent = 'Driver • ' + curDrv.phone);
document.querySelector('#btnOpenLogin') && (document.querySelector('#btnOpenLogin').textContent = 'Account');
}
}
updateUserUI();
// ===== Google Maps setup =====
let map, directionsService, directionsRenderer, pickupMarker, dropMarker, pickupPos, dropPos;
function initMap(){
// default center (Visakhapatnam)
const center = {lat:17.6868, lng:83.2185};
map = new google.maps.Map(document.getElementById('map'), { center, zoom:12, disableDefaultUI:false });
directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer({ map });
pickupMarker = new google.maps.Marker({ map, label: 'P' });
dropMarker = new google.maps.Marker({ map, label: 'D' });
// Places autocomplete
const acPickup = new google.maps.places.Autocomplete(document.getElementById('pickup'));
const acDrop = new google.maps.places.Autocomplete(document.getElementById('drop'));
acPickup.bindTo('bounds', map); acDrop.bindTo('bounds', map);
acPickup.addListener('place_changed', ()=> {
const p = acPickup.getPlace(); if(p.geometry) setPickup({ lat: p.geometry.location.lat(), lng: p.geometry.location.lng(), text: p.formatted_address || p.name });
});
acDrop.addListener('place_changed', ()=> {
const p = acDrop.getPlace(); if(p.geometry) setDrop({ lat: p.geometry.location.lat(), lng: p.geometry.location.lng(), text: p.formatted_address || p.name });
});
// click map to toggle pickup / drop
let placing = 'pickup';
map.addListener('click', (ev)=> {
const pos = { lat: ev.latLng.lat(), lng: ev.latLng.lng(), text: 'Pinned location' };
if(placing === 'pickup'){ setPickup(pos); document.getElementById('pickup').value = pos.text; placing = 'drop'; }
else { setDrop(pos); document.getElementById('drop').value = pos.text; placing = 'pickup'; }
});
}
// set positions
function setPickup(p){ pickupPos = p; pickupMarker.setPosition(p); map.panTo(p); }
function setDrop(p){ dropPos = p; dropMarker.setPosition(p); map.panTo(p); }
// Calculate route & fare
document.getElementById('getQuote').addEventListener('click', ()=> {
if(!pickupPos || !dropPos){
// try geocode from text if markers not set
const a = document.getElementById('pickup').value, b = document.getElementById('drop').value;
if(!a||!b){ alert('Set pickup and drop (type or click map)'); return; }
}
if(!pickupPos || !dropPos){
// try geocode quickly via placesService text search (use basic directionsService that accepts strings)
directionsService.route({ origin: document.getElementById('pickup').value, destination: document.getElementById('drop').value, travelMode: 'DRIVING' }, (res, status) => {
if(status==='OK'){ handleRouteResult(res); } else alert('Route error: '+status);
});
} else {
directionsService.route({ origin: pickupPos, destination: dropPos, travelMode: 'DRIVING' }, (res, status) => {
if(status==='OK'){ handleRouteResult(res); } else alert('Route error: '+status);
});
}
});
function handleRouteResult(res){
directionsRenderer.setDirections(res);
const leg = res.routes[0].legs[0];
const km = Number(leg.distance.value/1000).toFixed(1);
document.getElementById('estKm').textContent = km + ' km';
const fare = estimateFare(document.getElementById('rideType').value, km);
document.getElementById('estFare').textContent = INR.format(fare);
}
// simple fare model
function estimateFare(type, km){
const base = { mini:40, sedan:60, suv:80 };
const per = { mini:12, sedan:15, suv:18 };
km = Number(km) || 1;
return Math.round((base[type] + per[type]*km)*100)/100;
}
// Book ride (persist ride and simulate driver notification)
document.getElementById('bookRide').addEventListener('click', ()=> {
const cur = JSON.parse(localStorage.getItem('rx_current')||'null');
if(!cur){ if(!confirm('You are not signed in. Continue as guest?')) return; }
// save ride
const ride = {
id: 'r_'+Math.random().toString(36).slice(2,8),
user: cur ? cur.name : 'Guest',
pickup: document.getElementById('pickup').value,
drop: document.getElementById('drop').value,
rideType: document.getElementById('rideType').value,
fare: document.getElementById('estFare').textContent,
created: new Date().toISOString(),
status: 'requested'
};
let rides = read('rx_rides'); rides.push(ride); write('rx_rides', rides);
alert('Ride requested — searching for drivers (demo).');
// simulate notify drivers
simulateDriverNotification(ride);
});
// Simulate driver notification (mock of real-time push)
// If there's a logged-in driver (rx_current_driver) notify them in this same browser client.
// In production, push via WebSocket / FCM.
function simulateDriverNotification(ride){
// play alarm for driver
const drv = JSON.parse(localStorage.getItem('rx_current_driver')||'null');
if(!drv){
console.log('No driver online (demo). Ride in queue.');
return;
}
// show tiny accept dialog for demo
if(confirm('Driver ' + drv.phone + ' — accept incoming ride? (demo)')){
// accept -> show driver route
const accepted = JSON.parse(localStorage.getItem('rx_rides'))?.find(r=>r.id===ride.id);
accepted.status = 'accepted';
write('rx_rides', read('rx_rides')); // update
alert('Ride accepted — navigate to pickup (demo).');
// optionally render route on map for driver
directionsService.route({ origin: ride.pickup, destination: ride.drop, travelMode: 'DRIVING' }, (res, status) => {
if(status==='OK'){ directionsRenderer.setDirections(res); }
});
} else {
alert('Driver declined (demo).');
}
}
// driver panel (for demo)
function openDriverPanel(){
// For this demo we simply show an alert and update header
alert('Driver panel opened (demo). You will receive ride popups here.');
}
// close modal on background click
modalBack.addEventListener('click', (e)=> { if(e.target === modalBack) closeModal(); });
// start map script loader
function loadGoogleMaps(){
// script tag added in DOM end; function initMap will be invoked by callback
}
// run UI updates
updateUserUI();
</script>
<!-- Google Maps JS: replace key with your API KEY; include places library and callback to initMap -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_MAPS_API_KEY&libraries=places&callback=initMap" async defer></script>
</body>
</html>