🌍 English|$ USD
HelpManage BookingAvis RewardsFleet Portal

BOOK YOUR CAR RENTAL

Find rental cars, SUVs and premium vehicles at over 500 locations across the Middle East & Europe.

PICK-UP LOCATION
✈️
Amman Airport
Queen Alia International · AMM
Airport
📍
🏙️
Amman City Centre
Amman, Jordan
City
📍
✈️
Dubai International Airport
Dubai, UAE · DXB
Airport
📍
🏙️
Dubai Downtown
Dubai, UAE
City
📍
✈️
Rafic Hariri Airport
Beirut, Lebanon · BEY
Airport
📍
✈️
King Khalid Airport
Riyadh, Saudi Arabia · RUH
Airport
📍
✈️
Hamad International Airport
Doha, Qatar · DOH
Airport
📍
✈️
Heathrow Airport
London, UK · LHR
Airport
📍
🗺️
Hover a location
to see map & details
Open in Google Maps ↗
📍
🕐
📞
PICK-UP DATE & TIME
Add date
DROP-OFF DATE & TIME
Add date
Select pick-up date
Return to a different location
Driver's Age: 25+
Apply Wizard Number +
Add Discount +
DR1VN F1RST

EFFORTLESS ARRIVALS.
NO MATTER THE
FORECAST.

CURBSIDE PICKUP & DROPOFF
No counters. No chaos. Just a premium ride waiting for you at arrival.
PREMIUM VEHICLES
Explore our first-class collection from Mercedes, Cadillac, Genesis and more.
PERSONAL CONCIERGE, ON CALL
Need something mid-journey? Simply message us in the app for instant support.

SUMMER, IN FULL SWING.

Longer days. Open roads. Savings to get you moving.

COMPLIMENTARY UPGRADE
Enjoy a better car this summer — and save up to 25% on your next rental.

THE RIGHT CAR. RIGHT NOW.

From quick trips to long weekends, explore cars for every kind of trip.

SIMPLE. TRANSPARENT. PRICING.

No hidden fees. Every rate shown below is all-inclusive — taxes, insurance and unlimited mileage included.

Protection & Coverage — Add-ons
🔒
Theft Protection
$12/day
🚗
Collision Damage Waiver
$15/day
🏥
Personal Accident Insurance
$5/day
🪟
Windshield Coverage
$9/day
🔄
Tires Coverage
$7/day
✅ All prices include taxes
✅ Unlimited free mileage
✅ 10% off when booking online
✅ Free cancellation

MORE EASE. MORE
REWARDS. NO
DOWNSIDE.

SKIP THE COUNTER
Your car, no lines, no waiting.
EARN REWARDS
Every dollar gets you closer to your next reward.
FREE UPGRADES
A better car, just for being you.

ENJOY BENEFITS WITH OUR TRAVEL PARTNERS

WHERE TO NEXT?

Top destinations. Easy booking. Travel your way.

AMMAN, JORDAN
DUBAI, UAE
Our Network

ALL PICKUP LOCATIONS

Our Offices

FIND US NEAR YOU

Conveniently located at major airports and city centres across the region. Walk in or book ahead.

✈️ Airport
Amman, Jordan
Queen Alia International Airport
AMM
📍
Address
Terminal 1, Arrivals Hall, Counter 14
Queen Alia International Airport
Amman 11184, Jordan
🕐
Opening Hours
Open 24 Hours · 7 days a week
📞
✉️
👤
Location Manager
Khalid Al-Rashidi
🌐
GPS Coordinates
31.7224° N, 35.9933° E
Get Directions
🏙️ City Centre
Amman, Jordan
Amman City Centre
AMM-CITY
📍
Address
3rd Floor, City Mall, Wadi Saqra St
Amman 11194, Jordan
🕐
Opening Hours
Mon – Fri: 08:00 – 22:00
Sat – Sun: 09:00 – 20:00
📞
✉️
👤
Location Manager
Rania Haddad
🌐
GPS Coordinates
31.9505° N, 35.9284° E
Get Directions
✈️ Airport
Dubai, UAE
Dubai International Airport
DXB
📍
Address
Terminal 1 & 3, Arrivals Level
Dubai International Airport
Dubai, UAE
🕐
Opening Hours
Open 24 Hours · 7 days a week
📞
✉️
👤
Location Manager
Mohammed Al-Mansoori
🌐
GPS Coordinates
25.2532° N, 55.3657° E
Get Directions
✈️ Airport
Beirut, Lebanon
Rafic Hariri International Airport
BEY
📍
Address
Arrivals Hall, Ground Floor
Rafic Hariri International Airport
Beirut, Lebanon
🕐
Opening Hours
Open 24 Hours · 7 days a week
📞
✉️
👤
Location Manager
Nadia Khoury
🌐
GPS Coordinates
33.8208° N, 35.4881° E
Get Directions
✈️ Airport
London, United Kingdom
London Heathrow Airport
LHR
📍
Address
Car Rental Centre, Nelson Rd
Heathrow Airport, Terminal 5
Hounslow TW6 2GW, UK
🕐
Opening Hours
Open 24 Hours · 7 days a week
📞
✉️
👤
Location Manager
James Whitfield
🌐
GPS Coordinates
51.4700° N, 0.4543° W
Get Directions
✈️ Airport
Doha, Qatar
Hamad International Airport
DOH
📍
Address
Car Rental Zone, Level 0
Hamad International Airport
Doha, Qatar
🕐
Opening Hours
Open 24 Hours · 7 days a week
📞
✉️
👤
Location Manager
Tariq Al-Emadi
🌐
GPS Coordinates
25.2731° N, 51.6081° E
Get Directions
📞
24/7 Customer Support
+962 6 500 1000
✉️
General Enquiries
hello@drivn.com
🌐
New Location Enquiries
expand@drivn.com
9:41
GOOD AFTERNOON
Plan Your Next Trip →
DRIVN
PREFERRED®
The Preferred Experience
Expedited Services
Go straight to your car and drive off at Avis Rewards locations.
🏠
HOME
🔍
RESERVE
📋
TRIPS
👤
ACCOUNT

THE DRIVN APP.
TRAVEL, SIMPLIFIED.

Unlock deals, skip the line, manage your rental – all in one place.

🍎 App Store
▶ Google Play
📋
Find your reservation
Enter your last name and confirmation number above to view, modify or cancel your reservation.

Try: Rutherford / DRV-2841

Avis Precheck

Save time at the counter by uploading your documents and completing check-in online before your rental begins.

🪪
Driving Licence
Upload your licence for verification in advance
🛂
Passport / ID
Verify your identity before arriving at the counter
✍️
Digital Signature
Sign your rental agreement digitally — skip all paperwork

CAR RENTAL RECEIPT

To request a receipt, please complete the fields below, or log in to your DRIVN profile and access your Past Rentals page. If you are unable to retrieve your e-Receipt within 24 hours, please contact customer service at +962 6 500 1000 for further information.

Note: Miles/Points can be added when viewing receipt.

Please Note: Adobe Reader is required to view receipt. If you don't have Adobe Reader, please click here to download it.

Avis Rewards

Welcome Back

Sign in to view your bookings, points and exclusive offers.

Forgot password?  ·  New member? Join Now →
Contact your account manager if you need access.
Member Benefits
${[['⭐','Earn Points','1 point per $1 spent. Redeem for free rentals.'], ['📋','Booking History','All your past and upcoming rentals in one place.'], ['🎁','Exclusive Offers','Member-only deals and early access to promotions.'], ['⚡','Skip the Counter','Pre-check documents for seamless pick-up.'], ['🚗','Free Upgrades','Tier rewards including complimentary vehicle upgrades.'], ].map(([ico,t,d])=>`
${ico}
${t}
${d}
`).join('')}
DRIVN Fleet Services

FLEET MAINTENANCE PORTAL

Manage your entire fleet's maintenance schedule, service history, and raise service requests — all in one place.

${[['🔧','Full Maintenance History','Complete service log for every vehicle in your fleet'], ['📋','Raise Service Requests','Submit maintenance requests directly to our workshop'], ['📊','Fleet Health Overview','Dashboard showing overdue, upcoming and completed services'], ['🔔','Service Reminders','Track next service dates and mileage milestones'], ].map(([i,t,d])=>`
${i}
${t}
${d}
`).join('')}
Fleet Account Login

Sign In

Enter your fleet account credentials provided by DRIVN.

Contact your fleet account manager if you need access.
📍
|
📅
|
🕐
9 vehicles available
Max daily rate
$0$500+
Recommended
Price: Low → High
Price: High → Low
Name A–Z
Make a Reservation
Pick your vehicle
3
Add protections
4
Select add-ons
5
Review & Checkout
Selected Vehicle
or similar vehicle
Booking Details
📍 Pick-up
⏱ Duration
📅 Pick-up Date
📅 Return Date
ℹ️Free cancellation up to 48h before pick-up. No charge until you collect the vehicle.
Price Summary
Taxes & feesIncluded
InsuranceIncluded
Total
🔒 Secure✓ No hidden fees
Processing payment…
Please do not close this window
drivn
E-Receipt · ${bk.ref}
Total Charged
$${grandTotal.toFixed(2)}
Renter
${bk.customer}
${bk.email}
Vehicle
${bk.vehicle}
${bk.vehicleCat}
Rental Period
${bk.pickupDate} → ${bk.dropoffDate}
${bk.days} day${bk.days!==1?'s':''}
Pick-up Location
${bk.pickup}
Payment Method
${bk.payMethod}
Issue Date
${issueDate}
Charges Breakdown
${lines}
TOTAL CHARGED $${grandTotal.toFixed(2)}
DRIVN Car Rental · support@drivn.com · +962 6 500 1000 · www.drivn.com
`; // Open in new tab for printing/saving as PDF const w = window.open('','_blank'); w.document.write(html); w.document.close(); setTimeout(()=>w.print(), 400); } function mbGoPaperless(){ showPage('booking'); bkGo(5); } function mbResendEmail(ref){ showEmailSentToast(ref); } function mbAmendBooking(ref){ alert(`To amend booking ${ref}, please call us on +962 6 500 1000 or email support@drivn.com`); } function mbCancelBooking(ref){ if(!confirm(`Are you sure you want to cancel booking ${ref}?\n\nCancellation policy: Free cancellation up to 24 hours before pick-up.`)) return; // Update local booking status const bk = MB_BOOKINGS.find(b=>b.ref===ref); if(bk) bk.status='cancelled'; // Re-render const refEl=document.getElementById('mbRef'); const surEl=document.getElementById('mbSurname'); if(refEl&&surEl){ mbSearch(); } showEmailSentToast(ref, 'Cancellation confirmed. A confirmation email has been sent.'); } function showEmailSentToast(ref, msg){ const toast=document.createElement('div'); toast.style.cssText=`position:fixed;bottom:24px;right:24px;z-index:9999;background:#1a2a1a;border:1.5px solid #22c55e;border-radius:10px;padding:14px 18px;font-family:var(--font);display:flex;align-items:center;gap:12px;box-shadow:0 8px 24px rgba(0,0,0,.3);animation:fadeSlideIn .3s ease;max-width:340px`; toast.innerHTML=`
${msg||'Confirmation email sent!'}
Ref: ${ref}
`; document.body.appendChild(toast); setTimeout(()=>{ toast.style.opacity='0'; toast.style.transition='opacity .4s'; setTimeout(()=>toast.remove(),400); },4000); } function goSearch(cat) { updateSearchSummary(); if (cat) { // Pre-check the category filter document.querySelectorAll('#pageCars .flt-opt input[type=checkbox]').forEach(cb => { cb.checked = cb.value === cat; }); currentFleet = FLEET.filter(v => v.cat === cat); } else { document.querySelectorAll('#pageCars .flt-opt input[type=checkbox]').forEach(cb => cb.checked = false); currentFleet = [...FLEET]; } loadFromAdmin(); applySort('recommended'); showPage('cars'); } // ══════════════════════════════════════════════════ // FLEET DATA // ══════════════════════════════════════════════════ const FLEET = [ {id:1,make:'Toyota',model:'Yaris',cat:'Economy',price:39,seats:5,bags:2,trans:'Automatic',features:['A/C','Bluetooth','USB'],badge:'Best Value',svg:'eco',color:'#d1d5db'}, {id:2,make:'Honda',model:'Fit',cat:'Economy',price:42,seats:5,bags:2,trans:'Automatic',features:['A/C','Bluetooth'],badge:'',svg:'eco',color:'#9ca3af'}, {id:3,make:'Volkswagen',model:'Golf',cat:'Compact',price:58,seats:5,bags:2,trans:'Automatic',features:['A/C','Bluetooth','Parking Sensors'],badge:'',svg:'eco',color:'#6b7280'}, {id:4,make:'Toyota',model:'Camry',cat:'Sedan',price:75,seats:5,bags:3,trans:'Automatic',features:['A/C','Bluetooth','Cruise Control'],badge:'Most Popular',svg:'sedan',color:'#e31837'}, {id:5,make:'Honda',model:'Accord',cat:'Sedan',price:79,seats:5,bags:3,trans:'Automatic',features:['A/C','Bluetooth','Navigation'],badge:'',svg:'sedan',color:'#374151'}, {id:6,make:'Toyota',model:'RAV4',cat:'SUV',price:95,seats:5,bags:4,trans:'Automatic',features:['A/C','Bluetooth','AWD'],badge:'',svg:'suv',color:'#78716c'}, {id:7,make:'Land Rover',model:'Range Rover Sport',cat:'SUV',price:220,seats:5,bags:4,trans:'Automatic',features:['A/C','Leather','AWD'],badge:'',svg:'suv',color:'#3f3f46'}, {id:8,make:'Mercedes-Benz',model:'S-Class',cat:'Luxury Sedan',price:185,seats:5,bags:4,trans:'Automatic',features:['A/C','Leather','Navigation'],badge:'Premium',svg:'luxury',color:'#1e293b'}, {id:9,make:'BMW',model:'7 Series',cat:'Luxury Sedan',price:195,seats:5,bags:3,trans:'Automatic',features:['A/C','Leather','Navigation'],badge:'',svg:'luxury',color:'#0f172a'}, ]; const SVG = { eco: c=>``, sedan: c=>``, suv: c=>``, luxury: c=>``, }; // ══════════════════════════════════════════════════ // SEARCH STATE // ══════════════════════════════════════════════════ let S = { loc:'', pv:'', rv:'', pt:'08:00', rt:'11:00' }; const MONTHS=['January','February','March','April','May','June','July','August','September','October','November','December']; const DNAMES=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; function fmtDate(s){if(!s)return'';const d=new Date(s+'T00:00:00');return DNAMES[d.getDay()]+', '+d.getDate()+' '+MONTHS[d.getMonth()]+' '+d.getFullYear();} function getDays(){return S.pv&&S.rv?Math.round((new Date(S.rv)-new Date(S.pv))/86400000):0;} // Called by calendar when dates are chosen function onDateChange(){ const days=getDays(); setTxt('pdHint', S.pv ? fmtDate(S.pv) : ''); setTxt('rdHint', S.rv ? fmtDate(S.rv) : ''); const wdur = document.getElementById('wdur'); if(days>0){ setTxt('wdurTxt',`${days} day${days!==1?'s':''} rental`); if(wdur) wdur.classList.add('show'); } else { if(wdur) wdur.classList.remove('show'); } updateDateBar(); } // ══════════════════════════════════════════════════ // SIXT-STYLE CALENDAR ENGINE — AVIS THEME // ══════════════════════════════════════════════════ let calViewYear = 0; let calViewMonth = 0; let calSelecting = 'pickup'; // 'pickup' | 'return' | 'done' let calHoverDate = null; let calIsOpen = false; const DSHORT = ['Su','Mo','Tu','We','Th','Fr','Sa']; const MNAMES = ['January','February','March','April','May','June','July', 'August','September','October','November','December']; function todayStr(){ return new Date().toISOString().split('T')[0]; } function dateStr(y,m,d){ return `${y}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`; } function openCal(){ if(calIsOpen){ closeCal(); return; } calIsOpen = true; calSelecting = S.pv ? 'return' : 'pickup'; const now = new Date(); calViewYear = now.getFullYear(); calViewMonth = now.getMonth(); renderCal(); // Position the fixed panel just below the widget const widget = document.querySelector('.avis-widget') || document.querySelector('.wbody'); const panel = document.getElementById('calPanel'); if(panel && widget){ const rect = widget.getBoundingClientRect(); panel.style.top = (rect.bottom + 4) + 'px'; } panel.classList.add('open'); document.getElementById('calDateBar')?.classList.add('cal-open'); setTimeout(() => document.addEventListener('click', calOutside), 0); } function calOutside(e){ const panel = document.getElementById('calPanel'); const bar = document.getElementById('calDateBar'); // Also allow clicks on the new Avis-style date trigger cells if(panel && panel.contains(e.target)) return; if(bar && bar.contains(e.target)) return; if(e.target.closest('.avis-dt-inp')) return; closeCal(); } function closeCal(){ if(!calIsOpen) return; calIsOpen = false; calHoverDate = null; document.removeEventListener('click', calOutside); const panel = document.getElementById('calPanel'); if(panel) panel.classList.remove('open'); document.getElementById('calDateBar')?.classList.remove('cal-open'); updateDateBar(); } function calNav(dir){ calViewMonth += dir; if(calViewMonth > 11){ calViewMonth=0; calViewYear++; } if(calViewMonth < 0 ){ calViewMonth=11; calViewYear--; } renderCal(); } function updateDateBar(){ const pdv = document.getElementById('pdDisplay'); const rdv = document.getElementById('rdDisplay'); if(pdv){ if(S.pv){pdv.textContent=fmtDateShort(S.pv);pdv.classList.remove('empty');}else{pdv.textContent='Add date';pdv.classList.add('empty');} } if(rdv){ if(S.rv){rdv.textContent=fmtDateShort(S.rv);rdv.classList.remove('empty');}else{rdv.textContent='Add date';rdv.classList.add('empty');} } // Also sync pdField/rdField if they exist (calendar active state) const pdf=document.getElementById('pdField'), rdf=document.getElementById('rdField'); if(pdf) pdf.classList.toggle('picking', calIsOpen && calSelecting==='pickup'); if(rdf) rdf.classList.toggle('picking', calIsOpen && calSelecting==='return'); } function renderCal(){ const m2month = calViewMonth+1 > 11 ? 0 : calViewMonth+1; const m2year = calViewMonth+1 > 11 ? calViewYear+1 : calViewYear; document.getElementById('calMonths').innerHTML = `
${MNAMES[calViewMonth]} ${calViewYear}
${MNAMES[m2month]} ${m2year}
`; document.getElementById('calGrids').innerHTML = buildMonthGrid(calViewYear, calViewMonth) + buildMonthGrid(m2year, m2month); const instr = document.getElementById('calInstruction'); if(instr){ if(calSelecting==='pickup') instr.textContent = 'Select your pick-up date'; else if(calSelecting==='return') instr.textContent = 'Now select your return date'; else instr.textContent = '✓ Dates selected — click Done to confirm'; } // Footer const pdv = document.getElementById('calPickupVal'); const rdv = document.getElementById('calReturnVal'); if(S.pv){ pdv.textContent=fmtDateShort(S.pv); pdv.classList.remove('empty'); } else { pdv.textContent='Select date'; pdv.classList.add('empty'); } if(S.rv){ rdv.textContent=fmtDateShort(S.rv); rdv.classList.remove('empty'); } else { rdv.textContent='Select date'; rdv.classList.add('empty'); } const days = getDays(); const badge = document.getElementById('calDurBadge'); if(days>0){ badge.textContent=`${days} day${days!==1?'s':''}`; badge.style.display='inline-flex'; } else badge.style.display='none'; document.getElementById('calDoneBtn').disabled = !(S.pv && S.rv); updateDateBar(); } function buildMonthGrid(year, month){ const today = todayStr(); const firstDay = new Date(year, month, 1).getDay(); const numDays = new Date(year, month+1, 0).getDate(); let html = `
`; html += `
` + DSHORT.map(d=>`
${d}
`).join('') + `
`; html += `
`; for(let i=0;i
`; for(let d=1; d<=numDays; d++){ const ds = dateStr(year, month, d); const isPast = ds < today; const isToday = ds === today; const isStart = ds === S.pv; const isEnd = ds === S.rv; const inRange = S.pv && S.rv && ds > S.pv && ds < S.rv; const hoverEnd = calHoverDate && S.pv && !S.rv && ds === calHoverDate && ds > S.pv; const hoverRng = calHoverDate && S.pv && !S.rv && ds > S.pv && ds < calHoverDate; let cls = 'cal-day'; if(isPast) cls += ' cal-day-past'; if(isToday) cls += ' cal-day-today'; if(isStart) cls += ' cal-day-start'; if(isEnd) cls += ' cal-day-end'; if(inRange) cls += ' cal-day-range'; if(hoverEnd) cls += ' cal-day-hover-end'; if(hoverRng) cls += ' cal-day-hover-range'; html += `
${d}
`; } html += `
`; return html; } function pickDay(ds){ if(ds < todayStr()) return; calHoverDate = null; if(calSelecting === 'pickup'){ // First click: set pickup, clear return, switch to return mode S.pv = ds; S.rv = ''; setTxt('rdHint', ''); calSelecting = 'return'; onDateChange(); renderCal(); } else if(calSelecting === 'return'){ if(ds <= S.pv){ // Clicked before or on pickup — restart: set new pickup S.pv = ds; S.rv = ''; calSelecting = 'return'; onDateChange(); renderCal(); } else { // Valid return — complete the selection S.rv = ds; calSelecting = 'done'; onDateChange(); renderCal(); setTimeout(closeCal, 500); } } } function calHover(ds){ if(calSelecting==='return' && S.pv && ds > S.pv && ds !== calHoverDate){ calHoverDate = ds; renderCal(); } } function calHoverLeave(){ if(calHoverDate !== null){ calHoverDate=null; renderCal(); } } function fmtDateShort(s){ if(!s) return ''; const d = new Date(s+'T00:00:00'); return `${d.getDate()} ${MNAMES[d.getMonth()].slice(0,3)} ${d.getFullYear()}`; } function fmtDateLong(s){ if(!s) return ''; const d = new Date(s+'T00:00:00'); const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; return `${days[d.getDay()]}, ${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`; } function setWTab(el){document.querySelectorAll('.wtab').forEach(t=>t.classList.remove('act'));if(el)el.classList.add('act');} function toggleDiffRet(){ document.getElementById('retLocRow').style.display=document.getElementById('diffRetTog').checked?'block':'none'; } // Location dropdown function openLocDrop(field){ closeAllDrops(); const drop = document.getElementById(field==='pickup' ? 'pickupDrop' : 'retLocDrop'); if(!drop) return; drop.classList.add('open'); filterLocDrop(field); } function filterLocDrop(field){ const input = document.getElementById(field==='pickup' ? 'pickupInput' : 'retLocInput'); const drop = document.getElementById(field==='pickup' ? 'pickupDrop' : 'retLocDrop'); if(!input || !drop) return; drop.classList.add('open'); const q = input.value.toLowerCase(); let first = null; drop.querySelectorAll('.loc-opt').forEach(o => { const name = (o.dataset.name || o.querySelector('.loc-opt-name')?.textContent || '').toLowerCase(); const sub = (o.querySelector('.loc-opt-sub')?.textContent || '').toLowerCase(); const show = !q || name.includes(q) || sub.includes(q); o.style.display = show ? 'flex' : 'none'; if(show && !first) first = o; }); if(first && field==='pickup') hoverLoc(first); } function hoverLoc(el){ document.querySelectorAll('#pickupList .loc-opt').forEach(o => o.classList.remove('hvr')); el.classList.add('hvr'); const lat = el.dataset.lat, lng = el.dataset.lng; const frame = document.getElementById('pickupMapFrame'); const ph = document.getElementById('pickupMapPh'); const link = document.getElementById('pickupMapLink'); const card = document.getElementById('pickupInfoCard'); if(lat && lng){ const src = `https://maps.google.com/maps?q=${lat},${lng}&z=15&output=embed`; if(frame.src !== src) frame.src = src; frame.style.display = 'block'; ph.style.display = 'none'; link.href = `https://www.google.com/maps?q=${lat},${lng}`; link.style.display = 'block'; } if(document.getElementById('pickupInfoName')) document.getElementById('pickupInfoName').textContent = el.dataset.name || ''; if(document.getElementById('pickupInfoAddr')) document.getElementById('pickupInfoAddr').textContent = el.dataset.address || ''; if(document.getElementById('pickupInfoHours')) document.getElementById('pickupInfoHours').textContent = el.dataset.hours || ''; if(document.getElementById('pickupInfoPhone')) document.getElementById('pickupInfoPhone').textContent = el.dataset.phone || ''; const codeRow = document.getElementById('pickupInfoCodeRow'); if(codeRow){ if(el.dataset.code){ document.getElementById('pickupInfoCode').textContent = 'IATA: ' + el.dataset.code; codeRow.style.display = 'flex'; } else codeRow.style.display = 'none'; } if(card) card.style.display = 'block'; } // Use mousedown so selection fires BEFORE the input's blur event closes the dropdown function chooseLoc(field, el){ const inputId = field==='pickup' ? 'pickupInput' : 'retLocInput'; const val = el.dataset.val || el.dataset.name || ''; document.getElementById(inputId).value = val; if(field==='pickup') S.loc = val; closeAllDrops(); } function closeAllDrops(){ document.querySelectorAll('.loc-drop').forEach(d => d.classList.remove('open')); } // Close when clicking completely outside the widget document.addEventListener('click', function(e){ if(!e.target.closest('.winp') && !e.target.closest('.avis-loc-input') && !e.target.closest('.loc-drop')){ closeAllDrops(); } }); // Prevent the dropdown from closing when clicking INSIDE it document.querySelectorAll('.loc-drop').forEach(drop => { drop.addEventListener('click', function(e){ e.stopPropagation(); }); }); // ══════════════════════════════════════════════════ // SEARCH — navigate to car selection page // ══════════════════════════════════════════════════ function doSearch(){ const loc = document.getElementById('pickupInput').value.trim(); const pv = S.pv; const rv = S.rv; const pt = document.getElementById('pickupTime').value; const rt = document.getElementById('returnTime').value; const err = document.getElementById('werr'); let errs = []; if(!loc) errs.push('pick-up location'); if(!pv) errs.push('pick-up date'); if(!rv) errs.push('return date'); if(pv && rv && rv <= pv) errs.push('return must be after pick-up'); if(errs.length){ err.textContent='⚠ Please enter: '+errs.join(', '); return; } err.textContent = ''; S = { loc, pv, rv, pt, rt }; const btn = document.getElementById('wsearchBtn'); btn.innerHTML = '⏳ Searching…'; btn.disabled = true; setTimeout(()=>{ btn.innerHTML='🔍 Search Vehicles'; btn.disabled=false; goSearch(); }, 600); } // Homepage "Select" buttons — validate search first, then go function selectFleetCar(id){ const loc = document.getElementById('pickupInput').value.trim(); const pv = S.pv; const rv = S.rv; const pt = document.getElementById('pickupTime').value; const rt = document.getElementById('returnTime').value; const err = document.getElementById('werr'); let errs = []; if(!loc) errs.push('pick-up location'); if(!pv) errs.push('pick-up date'); if(!rv) errs.push('return date'); if(pv&&rv&&rv<=pv) errs.push('return date must be after pick-up'); if(errs.length){ err.textContent='⚠ Please fill your search first: '+errs.join(', '); window.scrollTo({top:0,behavior:'smooth'}); return; } err.textContent = ''; S = { loc, pv, rv, pt, rt }; currentFleet = [...FLEET]; applySort('recommended'); updateSearchSummary(); showPage('cars'); setTimeout(()=>openBooking(id), 300); } // ══════════════════════════════════════════════════ // SEARCH SUMMARY // ══════════════════════════════════════════════════ function updateSearchSummary(){ document.getElementById('sbLoc').textContent=S.loc||'Location not set'; document.getElementById('sbDates').textContent=S.pv?`${fmtDate(S.pv)} → ${fmtDate(S.rv)}`:'Dates not set'; document.getElementById('sbTimes').textContent=`${S.pt} – ${S.rt}`; const d=getDays(); const badge=document.getElementById('sbDays'); if(d>0){badge.textContent=`${d} day${d!==1?'s':''}`;badge.style.display='inline-flex';} else badge.style.display='none'; } // ══════════════════════════════════════════════════ // CAR SELECTION FILTER & RENDER // ══════════════════════════════════════════════════ let currentFleet=[...FLEET]; let maxPrice=500; let viewMode='grid'; let currentSortVal='recommended'; // ── Filter pill dropdowns ── function toggleFltDrop(id){ const wrap = document.getElementById(id); const isOpen = wrap.classList.contains('open'); closeFltDrops(); if(!isOpen) wrap.classList.add('open'); } function closeFltDrops(){ document.querySelectorAll('.flt-pill-wrap.open').forEach(w=>w.classList.remove('open')); } document.addEventListener('click', e=>{ if(!e.target.closest('.flt-pill-wrap')) closeFltDrops(); }); function applyFilters(){ const catChecks=[...document.querySelectorAll('.flt-drop-opt input[type=checkbox]')].filter(cb=>['Economy','Compact','Sedan','SUV','Luxury Sedan'].includes(cb.value)&&cb.checked).map(cb=>cb.value); const trans=document.querySelector('input[name=trns]:checked')?.value||'all'; currentFleet=FLEET.filter(v=>{ if(catChecks.length&&!catChecks.includes(v.cat))return false; if(trans!=='all'&&v.trans!==trans)return false; if(v.price>maxPrice)return false; return true; }); // Update pill active states document.getElementById('fpVehicle')?.querySelector('.flt-pill').classList.toggle('has-sel', catChecks.length>0); document.getElementById('fpTrans')?.querySelector('.flt-pill').classList.toggle('has-sel', trans!=='all'); applySort(currentSortVal||'recommended'); } function applySort(val){ currentSortVal=val; if(val==='price-asc')currentFleet.sort((a,b)=>a.price-b.price); else if(val==='price-desc')currentFleet.sort((a,b)=>b.price-a.price); else if(val==='name')currentFleet.sort((a,b)=>(a.make+a.model).localeCompare(b.make+b.model)); else currentFleet.sort((a,b)=>a.id-b.id); // Update recommended pill label const labels={recommended:'Recommended','price-asc':'Price ↑','price-desc':'Price ↓',name:'Name A–Z'}; const pill = document.getElementById('fpSort')?.querySelector('.flt-pill'); if(pill) pill.innerHTML = `${labels[val]||'Recommended'} `; renderFleet(); } function onPriceRng(el){maxPrice=parseInt(el.value);document.getElementById('priceRngVal').textContent=maxPrice>=500?'$500+':'$'+maxPrice;applyFilters();} function clearFilters(){ document.querySelectorAll('.flt-drop-opt input[type=checkbox]').forEach(i=>i.checked=false); document.querySelectorAll('.flt-drop-opt input[type=radio][value=all]').forEach(i=>i.checked=true); document.getElementById('priceRng').value=500;maxPrice=500; document.getElementById('priceRngVal').textContent='$500+'; document.querySelectorAll('.flt-pill.has-sel').forEach(p=>p.classList.remove('has-sel')); currentFleet=[...FLEET];applySort('recommended'); } function setView(v){viewMode=v;} function renderFleet(){ const grid = document.getElementById('vehGrid'); document.getElementById('resCount').textContent = currentFleet.length; if(!currentFleet.length){ grid.innerHTML=`
🚗
No vehicles match your filters
Try adjusting your criteria
`; return; } const days = getDays(); let html = ''; currentFleet.forEach((v, i) => { const total = days > 0 ? v.price * days : null; const discTotal = total ? Math.round(total * 0.9) : null; const bc = v.badge==='Most Popular'?'g':v.badge==='Premium'?'b':'r'; const dots = `
`; // Card HTML html += `
${v.cat}
${v.make} ${v.model} OR SIMILAR
${v.seats}
${v.bags}
${v.doors||4}
${v.badge?`${v.badge}`:''} ${SVG[v.svg]?.(v.color)||''}
${dots}
$${v.price}.00 /day
${total?`
$${total}.00 Vehicle total rate
`:`
Enter dates for total
`}
`; // After every 3rd card (or last card), insert the detail panel if((i+1) % 3 === 0 || i === currentFleet.length-1){ html += buildDetailPanels(currentFleet.slice(Math.max(0,i-((i%3)||0)), i+1), days); } }); grid.innerHTML = html; } function buildDetailPanels(rowCars, days){ // One hidden detail panel per card in this row return rowCars.map(v => { const total = days > 0 ? v.price * days : null; const discTotal= total ? Math.round(total * 0.9) : null; const features = [ {ico:``, lbl:`${v.seats} Seats`}, {ico:``, lbl:`${v.doors||4} Doors`}, {ico:``, lbl:`${v.bags} Bags`}, {ico:``, lbl:v.trans}, {ico:``, lbl:'A/C'}, ...v.features.slice(0,3).map(f=>({ico:``,lbl:f})) ]; const pillsHtml = features.map(f=>`
${f.ico} ${f.lbl}
`).join(''); const [pInt, pDec] = v.price.toString().includes('.') ? v.price.toString().split('.') : [String(v.price), '00']; return `
${v.cat}
${v.make} ${v.model} OR SIMILAR
${SVG[v.svg]?.(v.color)||''}
${pillsHtml}
Choose your rate
Best for Savings
10% online discount applied.
Pay now & confirm instantly.
$${Math.round(v.price*0.9)}.00/day
${discTotal?`
$${discTotal}.00
Vehicle total rate
`:''}
Best for Flexibility
No credit card required to book and free cancellation before your scheduled pick-up time.
$${pInt}.${pDec}/day
${total?`
$${total}.00
Vehicle total rate
`:''}
`; }).join(''); } // ══════════════════════════════════════════════════ // BOOKING FLOW // ══════════════════════════════════════════════════ let BK={}; let stripe=null,stripeCard=null,cardOk=false; const STRIPE_PK='pk_test_YOUR_PUBLISHABLE_KEY_HERE'; // ── PAPERLESS / DIGITAL SIGNATURE ── // ════════════════════════════════════════════════════ // BOOKING REFERENCE GENERATOR // ════════════════════════════════════════════════════ function generateBookingRef(){ let s={}; try{ s=JSON.parse(localStorage.getItem('drivn_ref_settings')||'{}'); }catch(e){} try{ const d=JSON.parse(localStorage.getItem('drivn_site_data')||'{}'); if(d.refSettings) s={...d.refSettings,...s}; }catch(e){} const prefix=(s.prefix||'DRV').toUpperCase().replace(/[^A-Z0-9]/g,'').slice(0,8)||'DRV'; const sep =s.sep!==undefined?s.sep:'-'; const format=s.format||'alpha'; const start =parseInt(s.start)||100001; let suffix; if(format==='numeric'){ const key=`drivn_ref_counter_${prefix}`; let n=parseInt(localStorage.getItem(key)||String(start)); suffix=String(n).padStart(6,'0'); localStorage.setItem(key,String(n+1)); } else { suffix=Date.now().toString(36).toUpperCase().slice(-8); } return `${prefix}${sep}${suffix}`; } // ════════════════════════════════════════════════════ // AUTO EMAIL SYSTEM // ════════════════════════════════════════════════════ const EMAIL_TEMPLATES_SITE = { confirmation: b => ({ subject: `Your DRIVN Booking Confirmation – ${b.ref}`, html: `
drivn
Car Rental
Booking Confirmed!
Your reservation is confirmed and ready to go.
Booking Reference
${b.ref}
👤 Driver ${b.first} ${b.last}
🚗 Vehicle ${b.vehicle} (${b.category})
📍 Pick-up Location ${b.pickup}
📅 Pick-up Date ${b.pickupDate} at ${b.pickupTime}
🏁 Drop-off Date ${b.dropoffDate} at ${b.dropoffTime}
⏱ Duration ${b.days} day${b.days!==1?'s':''}
💳 Payment ${b.payMethod}
💰 Total ${b.total}
📋 What to Bring at Pick-up
📄 Go Paperless — Skip the Queue!
Upload your licence, passport and sign digitally. Be at the car in under 60 seconds.
Complete Paperless Check-in →
DRIVN Car Rental
📞 +962 6 500 1000  ·  ✉ support@drivn.com  ·  🌐 www.drivn.com
This confirmation was sent to ${b.email} · Ref ${b.ref}
` }), reminder: b => ({ subject: `⏰ Pickup Reminder – ${b.ref} | Tomorrow at ${b.pickupTime}`, html: `
drivn
Your pickup is in 12 hours!
Get ready — your ${b.vehicle} will be waiting for you.
🔖 Reference ${b.ref}
🚗 Vehicle ${b.vehicle}
📍 Pick-up Location ${b.pickup}
⏰ Pick-up Time ${b.pickupDate} · ${b.pickupTime}
✅ Quick Checklist
☐  Driving licence (original)
☐  Passport or national ID
☐  Credit card in driver's name
☐  Booking reference: ${b.ref}
Haven't gone paperless yet?
Skip the counter entirely by submitting your documents now.
Submit Documents →
DRIVN Car Rental
📞 +962 6 500 1000  ·  ✉ support@drivn.com
Reminder for booking ${b.ref} · Sent to ${b.email}
` }) }; // ── Email log (in-page, stored in sessionStorage) ── function logEmail(type, ref, email, subject){ const logs = JSON.parse(sessionStorage.getItem('drivn_email_log')||'[]'); logs.unshift({ type, ref, email, subject, sentAt: new Date().toISOString(), status:'sent' }); sessionStorage.setItem('drivn_email_log', JSON.stringify(logs.slice(0,50))); } // ── Core email sender ── function sendAutoEmail(type, booking){ const tpl = EMAIL_TEMPLATES_SITE[type]; if(!tpl) return; const { subject, html } = tpl(booking); // ── Resolve designated copy-to email from admin settings (localStorage) ── const adminSettings = (() => { try { return JSON.parse(localStorage.getItem('drivn_admin_settings')||'{}'); } catch(e){ return {}; } })(); const opsEmail = adminSettings.opsEmail || 'reservations@drivn.com'; // In production: POST /api/email with to + bcc + subject + html body // Simulated here — logs both recipients console.log(`📧 [DRIVN EMAIL — ${type.toUpperCase()}]`); console.log(`To: ${booking.email} ← Customer`); if(type === 'confirmation'){ console.log(`BCC: ${opsEmail} ← Operations copy`); } console.log(`Subject: ${subject}`); console.log('Body (HTML):', html); logEmail(type, booking.ref, booking.email, subject); // If confirmation — also log the ops copy if(type === 'confirmation'){ logEmail('confirmation_ops', booking.ref, opsEmail, `[OPS COPY] ${subject}`); // Show a second toast for the ops copy showEmailToast(type, booking); setTimeout(()=>showOpsEmailToast(opsEmail, booking), 700); } else { showEmailToast(type, booking); } } function showOpsEmailToast(opsEmail, booking){ const el=document.createElement('div'); el.style.cssText=` position:fixed;bottom:100px;right:24px;z-index:999999; background:#0d1520;border:1.5px solid #3b82f6;border-radius:12px; padding:16px 20px;max-width:340px;min-width:280px; box-shadow:0 8px 32px rgba(0,0,0,.5); font-family:'Source Sans 3',Arial,sans-serif; animation:slideInRight .35s cubic-bezier(.2,.8,.3,1); display:flex;align-items:flex-start;gap:12px; `; el.innerHTML=`
📋
Operations Copy Sent
BCC copy delivered to
${opsEmail}
${booking.ref}
`; document.body.appendChild(el); setTimeout(()=>{ if(el.parentNode){ el.style.opacity='0'; el.style.transition='opacity .4s'; setTimeout(()=>el.remove(),400); } }, 6000); } // ════════════════════════════════════════════════════ // WIZARD MEMBER AUTO-RECOGNITION // ════════════════════════════════════════════════════ // Mirror of admin member list — in production this would be a server lookup const WIZARD_DB = [ { num:'AR-100001', name:'James Rutherford', email:'james@email.com', tier:'Gold', discount:20, applies:'all', status:'active', expiry:'2027-01-15' }, { num:'AR-100002', name:'Ahmed Khalil', email:'ahmed@email.com', tier:'Gold', discount:20, applies:'all', status:'active', expiry:'2027-06-20' }, { num:'AR-100003', name:'Sara Castellano', email:'sara@email.com', tier:'Silver', discount:15, applies:'all', status:'active', expiry:'2026-02-10' }, { num:'AR-100004', name:'Omar Al-Rashid', email:'omar@email.com', tier:'Silver', discount:15, applies:'no_luxury',status:'active', expiry:'2026-05-01' }, { num:'AR-100005', name:'Lina Nasser', email:'lina@email.com', tier:'Bronze', discount:10, applies:'all', status:'active', expiry:'2026-01-08' }, { num:'AR-100006', name:'Fatima Al-Zahra', email:'fatima@email.com', tier:'Bronze', discount:10, applies:'economy', status:'expired',expiry:'2025-11-01' }, { num:'AR-100007', name:'Maya Saleh', email:'maya@email.com', tier:'Gold', discount:20, applies:'all', status:'active', expiry:'2026-09-15' }, { num:'AR-100008', name:'Rami Khoury', email:'rami@email.com', tier:'Custom', discount:25, applies:'all', status:'inactive',expiry:'2027-08-01'}, ]; const WIZARD_TIER_ICONS = { Gold:'⭐', Silver:'🥈', Bronze:'🥉', Custom:'✏️' }; let _wizardLookupTimer = null; function wizardEmailLookup(email){ // Debounce — only run 600ms after user stops typing clearTimeout(_wizardLookupTimer); const banner = getEl('wizardBanner'); const notFound = getEl('wizardNotFound'); const spinner = getEl('cfEmailSpinner'); // Hide both while typing if(banner) banner.style.display = 'none'; if(notFound) notFound.style.display = 'none'; if(!email || !email.includes('@') || !email.includes('.')) { // Not a valid email yet — clear wizard state silently clearWizardState(); return; } // Show spinner while "looking up" if(spinner) spinner.style.display = 'block'; _wizardLookupTimer = setTimeout(()=>{ if(spinner) spinner.style.display = 'none'; performWizardLookup(email.trim().toLowerCase()); }, 600); } function performWizardLookup(email){ const today = new Date().toISOString().split('T')[0]; // Find member by email (case-insensitive) const member = WIZARD_DB.find(m => m.email.toLowerCase() === email); const banner = getEl('wizardBanner'); const notFound = getEl('wizardNotFound'); if(!member){ // Not a member clearWizardState(); if(notFound) notFound.style.display = 'flex'; return; } // Check if expired or inactive const isExpired = member.expiry && member.expiry < today; const isInactive = member.status !== 'active'; if(isExpired || isInactive){ clearWizardState(); if(notFound){ notFound.style.display = 'flex'; notFound.querySelector('span').innerHTML = isExpired ? `Your Avis Preferred membership (${member.num}) has expired. Renew →` : `Your Avis Preferred membership is currently inactive. Contact support@drivn.com`; } return; } // ✅ Valid member found — apply discount BK.wizardMember = member; // Populate banner const icon = WIZARD_TIER_ICONS[member.tier] || '⭐'; setTxt('wzBannerName', `Welcome back, ${member.name.split(' ')[0]}! ${icon}`); setTxt('wzBannerTier', `${member.tier} Avis Preferred Member · ${member.num}`); setTxt('wzBannerDisc', `${member.discount}%`); const appliesText = { all: 'Applies to all vehicles', economy: 'Applies to Economy vehicles only', standard: 'Applies to Economy & Sedan', no_luxury: 'Applies to all except Luxury Sedan', }[member.applies] || 'Applies to qualifying vehicles'; setTxt('wzBannerDetail', `Your ${member.discount}% member discount has been applied automatically. ${appliesText}.`); if(banner){ banner.style.display = 'block'; banner.style.animation = 'none'; setTimeout(()=>{ banner.style.animation = 'fadeSlideIn .35s ease'; }, 10); } if(notFound) notFound.style.display = 'none'; updateRvTotal(); } function clearWizardState(){ BK.wizardMember = null; BK.wizardDisc = 0; const wzRow = getEl('rvWizardRow'); if(wzRow) wzRow.style.display = 'none'; updateRvTotal(); } function removeWizardDiscount(){ clearWizardState(); const banner = getEl('wizardBanner'); const notFound = getEl('wizardNotFound'); if(banner) banner.style.display = 'none'; if(notFound) notFound.style.display = 'none'; } // ════════════════════════════════════════════════════ // AUTO EMAIL SYSTEM // ════════════════════════════════════════════════════ function showEmailToast(type, booking){ // Remove any old toast of same type const oldId = `emailToast_${type}`; document.getElementById(oldId)?.remove(); const el = document.createElement('div'); el.id = oldId; document.body.appendChild(el); const configs = { confirmation: { icon:'✉️', title:'Confirmation Email Sent', subtitle:`Delivered to ${booking.email}`, color:'#22c55e', bg:'#0d1f13', bottom:'24px' }, reminder: { icon:'⏰', title:'Reminder Email Scheduled', subtitle:`12h before pickup · ${booking.email}`, color:'#f59e0b', bg:'#1a1400', bottom:'90px' }, }; const cfg = configs[type] || { icon:'📧', title:'Email Sent', subtitle:booking.email, color:'#22c55e', bg:'#0d1f13', bottom:'24px' }; el.style.cssText=` position:fixed;bottom:${cfg.bottom};right:24px;z-index:999999; background:${cfg.bg};border:1.5px solid ${cfg.color};border-radius:12px; padding:16px 20px;max-width:340px;min-width:280px; box-shadow:0 8px 32px rgba(0,0,0,.5),0 0 0 1px rgba(255,255,255,.04); font-family:'Source Sans 3',Arial,sans-serif; animation:slideInRight .35s cubic-bezier(.2,.8,.3,1); display:flex;align-items:flex-start;gap:12px; `; el.innerHTML=`
${cfg.icon}
${cfg.title}
${cfg.subtitle}
${booking.ref}
`; // Auto-dismiss after 6 seconds setTimeout(()=>{ if(el.parentNode){ el.style.opacity='0'; el.style.transition='opacity .4s'; setTimeout(()=>el.remove(),400); } }, 6000); } // ── Schedule 12-hour reminder ── function scheduleReminderEmail(booking){ if(!booking.pickupMs) return; const reminderMs = booking.pickupMs - (12 * 60 * 60 * 1000); // 12h before const nowMs = Date.now(); const delayMs = reminderMs - nowMs; // Persist to localStorage so it survives page reload const reminders = JSON.parse(localStorage.getItem('drivn_reminders')||'[]'); reminders.push({ ref: booking.ref, email: booking.email, booking, triggerAt: reminderMs, sent: false, }); localStorage.setItem('drivn_reminders', JSON.stringify(reminders)); if(delayMs > 0 && delayMs < 86400000 * 7){ // only schedule if within 7 days setTimeout(()=>{ sendAutoEmail('reminder', booking); markReminderSent(booking.ref); }, delayMs); // Show "scheduled" toast showEmailToast('reminder', { ...booking, email: booking.email }); console.log(`⏰ Reminder scheduled for ${new Date(reminderMs).toLocaleString()} (in ${Math.round(delayMs/3600000)}h)`); } else if(delayMs <= 0) { // Pickup is imminent or past — send reminder now if not already sent sendAutoEmail('reminder', booking); markReminderSent(booking.ref); } } function markReminderSent(ref){ try { const reminders = JSON.parse(localStorage.getItem('drivn_reminders')||'[]'); const r = reminders.find(x=>x.ref===ref); if(r) r.sent = true; localStorage.setItem('drivn_reminders', JSON.stringify(reminders)); } catch(e){} } // ── On page load: fire any pending reminders ── (function checkPendingReminders(){ try { const reminders = JSON.parse(localStorage.getItem('drivn_reminders')||'[]'); const now = Date.now(); reminders.forEach(r=>{ if(!r.sent && r.triggerAt <= now){ sendAutoEmail('reminder', r.booking); r.sent = true; } else if(!r.sent && r.triggerAt > now){ const delay = r.triggerAt - now; setTimeout(()=>{ sendAutoEmail('reminder', r.booking); markReminderSent(r.ref); }, delay); } }); localStorage.setItem('drivn_reminders', JSON.stringify(reminders)); } catch(e){} })(); // CSS for email toast slide-in (function(){ const s=document.createElement('style'); s.textContent=`@keyframes slideInRight{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}`; document.head.appendChild(s); })(); (function(){ const saved = localStorage.getItem('drivn_logo'); if(saved){ document.querySelectorAll('.logo-svg').forEach(el=>el.innerHTML=saved); } })(); // ════════════════════════════════════════════════════ // LOCATIONS DATA & RENDERING // ════════════════════════════════════════════════════ const SITE_LOCATIONS = [ { id:1, name:'Amman — Queen Alia International Airport', code:'AMM', type:'Airport', city:'Amman', country:'Jordan', address:'Queen Alia International Airport, Terminal 1, Amman 11184, Jordan', phone:'+962 6 445 2200', phone2:'+962 77 800 0001', email:'amm@drivn.com', hours:'Open 24 hours, 7 days a week', hours24:true, manager:'Khaled Al-Omari', managerTitle:'Branch Manager', lat:31.7224, lng:35.9933, fleet:25, status:'active', note:'Counter at Arrivals Hall, Gate 4. Free shuttle from Terminal 2.' }, { id:2, name:'Amman — City Centre', code:'AMM-C', type:'City', city:'Amman', country:'Jordan', address:'King Abdullah II St, 3rd Circle, Jabal Amman, Amman 11183, Jordan', phone:'+962 6 461 0000', phone2:'', email:'amman.city@drivn.com', hours:'Sun – Thu: 08:00 – 20:00 · Fri – Sat: 09:00 – 18:00', hours24:false, manager:'Rania Tawil', managerTitle:'Branch Manager', lat:31.9505, lng:35.9284, fleet:15, status:'active', note:'Free parking available on-site for up to 30 minutes during pickup.' }, { id:3, name:'Dubai — International Airport (DXB)', code:'DXB', type:'Airport', city:'Dubai', country:'UAE', address:'Dubai International Airport, Terminal 1 & 3, Arrivals Hall, Dubai, UAE', phone:'+971 4 224 5678', phone2:'+971 50 800 0002', email:'dxb@drivn.com', hours:'Open 24 hours, 7 days a week', hours24:true, manager:'Mohammed Al-Rashidi', managerTitle:'Country Manager — UAE', lat:25.2532, lng:55.3657, fleet:30, status:'active', note:'Counters in Terminal 1 (Level 2) and Terminal 3 (Arrivals). Call ahead for after-hours pickup.' }, { id:4, name:'Dubai — Downtown', code:'DXB-D', type:'City', city:'Dubai', country:'UAE', address:'Sheikh Mohammed Bin Rashid Blvd, Downtown Dubai, Dubai, UAE', phone:'+971 4 388 1000', phone2:'', email:'dubai.city@drivn.com', hours:'Daily: 07:00 – 23:00', hours24:false, manager:'Sara Al-Mansoori', managerTitle:'Branch Manager', lat:25.2048, lng:55.2708, fleet:20, status:'active', note:'Valet parking available. Located adjacent to The Dubai Mall.' }, { id:5, name:'Beirut — Rafic Hariri International Airport', code:'BEY', type:'Airport', city:'Beirut', country:'Lebanon', address:'Rafic Hariri International Airport, Beirut, Lebanon', phone:'+961 1 628 000', phone2:'+961 70 800 003', email:'bey@drivn.com', hours:'Open 24 hours, 7 days a week', hours24:true, manager:'Nadia Khoury', managerTitle:'Branch Manager', lat:33.8208, lng:35.4881, fleet:18, status:'active', note:'Counter in the Arrivals Hall, next to the currency exchange.' }, { id:6, name:'London — Heathrow Airport (LHR)', code:'LHR', type:'Airport', city:'London', country:'United Kingdom', address:'Heathrow Airport, Terminal 2 & 5, London TW6 1EW, United Kingdom', phone:'+44 20 8745 9800', phone2:'+44 7700 800 004', email:'lhr@drivn.com', hours:'Open 24 hours, 7 days a week', hours24:true, manager:'James Whitfield', managerTitle:'Regional Manager — Europe', lat:51.4700, lng:-0.4543, fleet:22, status:'active', note:'Vehicle collection from the Heathrow Car Rental Centre (free bus from all terminals).' }, { id:7, name:'Riyadh — King Khalid International Airport', code:'RUH', type:'Airport', city:'Riyadh', country:'Saudi Arabia', address:'King Khalid International Airport, Terminal 1, Riyadh 13413, Saudi Arabia', phone:'+966 11 221 1000', phone2:'+966 54 800 0005', email:'ruh@drivn.com', hours:'Open 24 hours, 7 days a week', hours24:true, manager:'Abdullah Al-Zahrani', managerTitle:'Branch Manager', lat:24.9578, lng:46.6989, fleet:28, status:'active', note:'Counter at Level 1, Arrivals Hall, near Gate 3.' }, { id:8, name:'Doha — Hamad International Airport', code:'DOH', type:'Airport', city:'Doha', country:'Qatar', address:'Hamad International Airport, Arrivals Terminal, Doha, Qatar', phone:'+974 4010 1010', phone2:'+974 3300 0006', email:'doh@drivn.com', hours:'Open 24 hours, 7 days a week', hours24:true, manager:'Fatima Al-Sulaiti', managerTitle:'Branch Manager', lat:25.2731, lng:51.6081, fleet:16, status:'active', note:'Located in the Rental Car Centre, a 5-minute walk from the Arrivals Hall.' }, ]; let locsFilterType = 'all'; function filterLocs(type, btn){ locsFilterType = type; document.querySelectorAll('.hp-loc-filter-btn').forEach(b=>b.classList.remove('hp-loc-filter-act')); if(btn) btn.classList.add('hp-loc-filter-act'); renderLocs(); } function renderLocs(){ const grid = document.getElementById('locsGrid'); if(!grid) return; const filtered = SITE_LOCATIONS.filter(l=> l.status==='active' && (locsFilterType==='all' || l.type===locsFilterType) ); const typeIcon = {Airport:'✈️', City:'🏙️', Hotel:'🏨'}; const typeClass = {Airport:'hp-loc-type-airport', City:'hp-loc-type-city', Hotel:'hp-loc-type-hotel'}; const initials = name => name.split(' ').map(w=>w[0]).join('').slice(0,2).toUpperCase(); grid.innerHTML = filtered.map(l => { const mapsUrl = `https://www.google.com/maps?q=${l.lat},${l.lng}`; const mapEmbed= `https://maps.google.com/maps?q=${l.lat},${l.lng}&z=15&output=embed`; const isOpen24 = l.hours24; return `
${l.city}, ${l.country}
Open in Maps
${l.name}
${l.code}
${typeIcon[l.type]||'📍'} ${l.type} ${isOpen24?'🕐 24 Hours':'✅ Open'}
${l.address}
${l.hours}
${l.phone} ${l.phone2?` · ${l.phone2}`:''}
${l.lat.toFixed(4)}°N, ${l.lng.toFixed(4)}°${l.lng>=0?'E':'W'}
${l.fleet} vehicles available at this location
${l.note?`
${l.note}
`:''}
${initials(l.manager)}
${l.manager}
${l.managerTitle}
Contact →
Directions
`; }).join(''); } // Initialise locations on page load function initLocations(){ renderLocs(); } function filterLocTab(type, btn){ document.querySelectorAll('.hp-loc-tab').forEach(t=>t.classList.remove('hp-loc-tab-act')); btn.classList.add('hp-loc-tab-act'); document.querySelectorAll('.hp-loc-card').forEach(card=>{ if(type==='all'){ card.style.display='flex'; return; } card.style.display = card.dataset.type===type ? 'flex' : 'none'; }); } // ── HOMEPAGE PRICING MATRIX ── let currentPricingPeriod = 'daily'; const SITE_FLEET_PRICING = [ {id:1, make:'Toyota', model:'Yaris', cat:'Economy', daily:39, weekly:234, monthly:975, seats:5,bags:2,trans:'Auto',featured:false}, {id:3, make:'VW', model:'Golf', cat:'Compact', daily:58, weekly:348, monthly:1450, seats:5,bags:3,trans:'Auto',featured:false}, {id:4, make:'Toyota', model:'Camry', cat:'Sedan', daily:75, weekly:450, monthly:1875, seats:5,bags:3,trans:'Auto',featured:true}, {id:6, make:'Toyota', model:'RAV4', cat:'SUV', daily:95, weekly:570, monthly:2375, seats:5,bags:4,trans:'Auto',featured:false}, {id:8, make:'Mercedes',model:'S-Class', cat:'Luxury Sedan', daily:185, weekly:1110, monthly:4625, seats:5,bags:4,trans:'Auto',featured:false}, {id:9, make:'BMW', model:'7 Series', cat:'Luxury Sedan', daily:195, weekly:1170, monthly:4875, seats:5,bags:4,trans:'Auto',featured:false}, ]; function setPricingPeriod(period, btn){ currentPricingPeriod = period; document.querySelectorAll('.hp-pt-btn').forEach(b=>b.classList.remove('act')); if(btn) btn.classList.add('act'); renderPricingMatrix(); } function renderPricingMatrix(){ const grid = document.getElementById('pricingGrid'); if(!grid) return; const period = currentPricingPeriod; grid.innerHTML = SITE_FLEET_PRICING.map(v=>{ const price = period==='daily'?v.daily:period==='weekly'?v.weekly:v.monthly; const perLabel = period==='daily'?'per day':period==='weekly'?'for 7 days':'for 30 days'; const onlinePrice = Math.round(price*0.9); let saveLabel=''; if(period==='weekly') saveLabel=`Save $${v.daily} vs 7 individual days`; if(period==='monthly') saveLabel=`Save $${(v.daily*30-v.monthly)} vs daily rate`; const tag=v.featured?`
Most Popular
`:''; return `
${tag}
${v.cat}
${v.make} ${v.model}
or similar
$${price.toLocaleString()}
${perLabel} · online $${onlinePrice}
${saveLabel?`
🎉 ${saveLabel}
`:'
'}
👤 ${v.seats} 🧳 ${v.bags} ⚙️ ${v.trans} ✅ Unlimited km
`; }).join(''); } // ── HOMEPAGE FLEET TABS ── const HP_FLEET = { popular:[ {id:6, cat:'PREMIUM ELITE SUV', name:'Toyota RAV4 or Similar', svg:'suv', color:'#1e293b'}, {id:4, cat:'SIGNATURE SERIES', name:'Toyota Camry or Similar', svg:'sedan', color:'#1a1a1a'}, {id:8, cat:'STANDARD ELITE SPORT', name:'Mercedes S-Class or Similar', svg:'luxury', color:'#111'}, ], suvs:[ {id:6, cat:'INTERMEDIATE SUV', name:'Toyota RAV4 or Similar', svg:'suv', color:'#78716c'}, {id:7, cat:'PREMIUM ELITE SUV', name:'Range Rover Sport or Similar', svg:'suv', color:'#1e293b'}, {id:6, cat:'STANDARD SUV', name:'Honda CR-V or Similar', svg:'suv', color:'#374151'}, ], luxury:[ {id:8, cat:'LUXURY SEDAN', name:'Mercedes S-Class or Similar', svg:'luxury',color:'#111'}, {id:9, cat:'LUXURY SEDAN', name:'BMW 7 Series or Similar', svg:'luxury',color:'#1a1a1a'}, {id:7, cat:'LUXURY SUV', name:'Range Rover Sport or Similar', svg:'suv', color:'#1e293b'}, ], sedan:[ {id:4, cat:'STANDARD SEDAN', name:'Toyota Camry or Similar', svg:'sedan', color:'#e31837'}, {id:5, cat:'FULL-SIZE SEDAN', name:'Honda Accord or Similar', svg:'sedan', color:'#1a1a1a'}, {id:3, cat:'COMPACT', name:'VW Golf or Similar', svg:'compact',color:'#9ca3af'}, ], economy:[ {id:1, cat:'ECONOMY', name:'Toyota Yaris or Similar', svg:'economy',color:'#d1d5db'}, {id:2, cat:'ECONOMY ELITE', name:'Honda Fit or Similar', svg:'economy',color:'#9ca3af'}, {id:3, cat:'COMPACT', name:'VW Golf or Similar', svg:'compact',color:'#78716c'}, ], }; const HP_SVG={ economy:(c)=>``, compact:(c)=>``, sedan:(c)=>``, suv:(c)=>``, luxury:(c)=>``, }; function setFleetTab(btn,key){ document.querySelectorAll('.hp-fleet-tab').forEach(t=>t.classList.remove('act')); btn.classList.add('act'); renderHpFleet(key); } function renderHpFleet(key){ const grid=document.getElementById('hpFleetGrid'); if(!grid) return; const cars=HP_FLEET[key]||HP_FLEET.popular; grid.innerHTML=cars.map(c=>`
${c.cat}
${(HP_SVG[c.svg]||HP_SVG.sedan)(c.color)}
${c.name}
`).join(''); } // ════════════════════════════════════════════════════ // MAINTENANCE MODE // ════════════════════════════════════════════════════ const MTN_KEY = 'drivn_maintenance'; const MTN_BYPASS_KEY = 'drivn_mtn_bypass'; function checkMaintenanceMode(){ // Already bypassed this session — skip if(sessionStorage.getItem(MTN_BYPASS_KEY)==='true') return; // Load maintenance config — prefer direct localStorage (same browser as admin) let mtn = {}; try{ const direct = JSON.parse(localStorage.getItem(MTN_KEY)||'{}'); if(direct && Object.keys(direct).length) mtn = direct; }catch(e){} // Fallback: check the sync payload if(!mtn.enabled){ try{ const syncData = JSON.parse(localStorage.getItem('drivn_site_data')||'{}'); if(syncData.maintenance && syncData.maintenance.enabled) mtn = syncData.maintenance; }catch(e){} } const overlay = document.getElementById('maintenanceOverlay'); if(!overlay) return; if(!mtn.enabled){ overlay.style.display = 'none'; return; } // ── Show overlay ── overlay.style.display = 'block'; document.body.style.overflow = 'hidden'; // Populate text const setTxt = (id,v) => { const el=document.getElementById(id); if(el&&v) el.innerHTML=v; }; setTxt('mtnHeadlineSite', mtn.headline || "We'll be back shortly"); setTxt('mtnSubMsgSite', mtn.subMsg || "We're performing scheduled maintenance to improve your experience."); // Contact email if(mtn.email){ const strip = document.getElementById('mtnEmailStrip'); const link = document.getElementById('mtnEmailLink'); if(strip) strip.style.display = 'block'; if(link) { link.href = `mailto:${mtn.email}`; link.textContent = mtn.email; } } // Countdown timer if(mtn.eta){ const wrap = document.getElementById('mtnCountdownWrap'); if(wrap) wrap.style.display = 'block'; mtnStartCountdown(mtn.eta); } // Store users for bypass login window._mtnUsers = Array.isArray(mtn.users) ? mtn.users : []; } function mtnStartCountdown(etaStr){ if(!etaStr) return; const tick = () => { const diff = Math.max(0, new Date(etaStr).getTime() - Date.now()); const pad = n => String(Math.floor(n)).padStart(2,'0'); const hh = pad(diff / 3600000); const mm = pad((diff % 3600000) / 60000); const ss = pad((diff % 60000) / 1000); ['cdHH','cdMM','cdSS'].forEach((id,i)=>{ const el = document.getElementById(id); if(el) el.textContent = [hh,mm,ss][i]; }); if(diff > 0) setTimeout(tick, 1000); }; tick(); } function mtnBypassLogin(){ const username = (document.getElementById('mtnBypassUser')?.value || '').trim(); const password = (document.getElementById('mtnBypassPass')?.value || '').trim(); const errEl = document.getElementById('mtnBypassErr'); const showErr = msg => { if(errEl){ errEl.style.display='block'; errEl.textContent=msg; } }; if(!username || !password){ showErr('Please enter your username and password.'); return; } const users = window._mtnUsers || []; // Also reload from localStorage in case users were added after page load try{ const fresh = JSON.parse(localStorage.getItem(MTN_KEY)||'{}'); if(fresh.users) window._mtnUsers = fresh.users; }catch(e){} const match = (window._mtnUsers||[]).find( u => u.username.toLowerCase()===username.toLowerCase() && u.password===password ); if(match){ sessionStorage.setItem(MTN_BYPASS_KEY, 'true'); const overlay = document.getElementById('maintenanceOverlay'); if(overlay) overlay.style.display = 'none'; document.body.style.overflow = ''; if(errEl) errEl.style.display = 'none'; // Brief bypass badge const badge = document.createElement('div'); badge.style.cssText = 'position:fixed;top:68px;right:16px;z-index:9000;background:#fff8e1;border:1.5px solid #f59e0b;color:#b45309;font-size:12px;font-weight:700;padding:8px 16px;border-radius:4px;font-family:var(--font);box-shadow:0 2px 8px rgba(0,0,0,.12)'; badge.textContent = '🔧 Maintenance bypass active — Welcome, ' + (match.role||match.username); document.body.appendChild(badge); setTimeout(()=>badge.remove(), 6000); } else { showErr('Incorrect username or password. Please try again.'); // Shake animation on the error if(errEl){ errEl.style.animation='none'; setTimeout(()=>errEl.style.animation='',10); } // Clear password field for retry const passEl = document.getElementById('mtnBypassPass'); if(passEl){ passEl.value=''; passEl.focus(); } } } // ════════════════════════════════════════════════════ // LOAD DATA SYNCED FROM ADMIN PANEL // ════════════════════════════════════════════════════ const SYNC_KEY = 'drivn_site_data'; function loadFromAdmin(){ try{ const raw = localStorage.getItem(SYNC_KEY); if(!raw) return; // No admin data yet — use defaults const data = JSON.parse(raw); if(!data) return; // ── Fleet ── if(data.fleet && data.fleet.length){ // Rebuild HP_FLEET (homepage display) from synced data const syncedFleet = data.fleet.map(v=>{ // Resolve photo from separate key const photo = v.photoKey ? localStorage.getItem(v.photoKey) : null; return {...v, photo}; }); // Update homepage fleet tabs HP_FLEET.popular = syncedFleet .filter(v=>v.websiteAvail!=='sold_out') .slice(0,6) .map(v=>({ id:v.id, name:`${v.make} ${v.model}`, cat:v.cat, price:v.price, seats:v.seats||5, bags:v.bags||3, trans:v.trans||'Auto', fuel:v.fuel||'Petrol', avail:v.websiteAvail, color:v.color||'#333', features:(v.features||'').split(',').map(f=>f.trim()).filter(Boolean), photo:v.photo||null, })); HP_FLEET.suvs = HP_FLEET.popular.filter(v=>v.cat==='SUV'); HP_FLEET.luxury = HP_FLEET.popular.filter(v=>v.cat==='Luxury Sedan'); HP_FLEET.sedan = HP_FLEET.popular.filter(v=>v.cat==='Sedan'); HP_FLEET.economy = HP_FLEET.popular.filter(v=>v.cat==='Economy'||v.cat==='Compact'); // Update site vehicle selector SITE_VEHICLES_SYNCED = syncedFleet; } // ── Pricing matrix ── if(data.pricing && data.pricing.length){ SITE_FLEET_PRICING = data.pricing .filter(p=>p.active!==false) .map(p=>({ id:p.id, make:p.make, model:p.model, cat:p.cat, daily:p.daily, weekly:p.weekly, monthly:p.monthly, discount:p.discount||10, featured: p.cat==='Sedan' && p.make==='Toyota', seats:5, bags:3, trans:'Auto', })); } // ── Coverage pricing ── if(data.covPricing && data.covPricing.length){ COV_RATES = {}; data.covPricing.forEach(c=>{ COV_RATES[c.id] = c.daily; }); } // ── Avis Preferred members ── if(data.wizardMembers && data.wizardMembers.length){ // Replace WIZARD_DB with admin data WIZARD_DB.splice(0, WIZARD_DB.length, ...data.wizardMembers); } // ── Promo codes ── if(data.deals && data.deals.length){ PROMO_CODES_SYNCED = {}; data.deals.forEach(d=>{ if(d.code){ const discPct = typeof d.discount==='string'&&d.discount.includes('%') ? parseInt(d.discount) : typeof d.discount==='number' ? d.discount : 10; PROMO_CODES_SYNCED[d.code.toUpperCase()] = discPct; } }); } // ── Seasons (for pricing) ── if(data.seasons) SITE_SEASONS = data.seasons; console.log(`[DRIVN] Loaded admin data synced at ${data._syncedAt}`); // Show a subtle "synced" indicator showSyncedIndicator(data._syncedAt); } catch(e){ console.warn('[DRIVN] Could not load admin sync data:', e); } } function showSyncedIndicator(syncedAt){ // Small non-intrusive badge in bottom-left for development const el = document.createElement('div'); el.style.cssText='position:fixed;bottom:12px;left:12px;z-index:9999;background:rgba(0,0,0,.6);color:rgba(255,255,255,.7);font-size:10px;padding:4px 10px;border-radius:4px;font-family:monospace;pointer-events:none'; el.textContent='Admin sync: '+(syncedAt?new Date(syncedAt).toLocaleTimeString():'unknown'); document.body.appendChild(el); setTimeout(()=>el.remove(), 3000); } // Declare synced data containers (fallback to hardcoded defaults if no admin data) let SITE_VEHICLES_SYNCED = null; let PROMO_CODES_SYNCED = null; let SITE_SEASONS = []; let COV_RATES = { tp:12, cdw:15, pai:5, ws:9, ti:7, bun:14 }; // ── Load on startup ── document.addEventListener('DOMContentLoaded', ()=>{ // Check maintenance mode first (blocks site if active) checkMaintenanceMode(); // Load admin synced data and render sections loadFromAdmin(); renderHpFleet('popular'); renderPricingMatrix(); initLocations(); rwUpdateHeaderState(); // Live sync from admin panel (same browser) window.addEventListener('storage', e=>{ if(e.key === MTN_KEY || e.key === 'drivn_site_data'){ checkMaintenanceMode(); } if(e.key === SYNC_KEY){ loadFromAdmin(); renderHpFleet('popular'); renderPricingMatrix(); } }); }); // ── PAPERLESS / DIGITAL SIGNATURE ── let sigDrawing=false, sigHasSig=false; function initSigCanvas(){ const canvas = document.getElementById('sigCanvas'); if(!canvas) return; // Ensure canvas has CSS height before measuring canvas.style.display = 'block'; canvas.style.width = '100%'; canvas.style.height = '160px'; setTimeout(()=>{ const w = canvas.offsetWidth || 680; const h = canvas.offsetHeight || 160; const dpr = window.devicePixelRatio || 1; canvas.width = w * dpr; canvas.height = h * dpr; const ctx = canvas.getContext('2d'); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.strokeStyle = '#1a1a1a'; ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; // Store ctx on canvas element so clearSig can access it canvas._ctx = ctx; function getXY(e){ const r = canvas.getBoundingClientRect(); const src = e.touches ? e.touches[0] : e; return { x: (src.clientX - r.left), y: (src.clientY - r.top) }; } canvas.onmousedown = e => { sigDrawing = true; ctx.beginPath(); const p = getXY(e); ctx.moveTo(p.x, p.y); e.preventDefault(); }; canvas.onmousemove = e => { if(!sigDrawing) return; const p = getXY(e); ctx.lineTo(p.x, p.y); ctx.stroke(); sigHasSig = true; }; canvas.onmouseup = () => sigDrawing = false; canvas.onmouseleave = () => sigDrawing = false; canvas.ontouchstart = e => { e.preventDefault(); sigDrawing = true; ctx.beginPath(); const p = getXY(e); ctx.moveTo(p.x, p.y); }; canvas.ontouchmove = e => { e.preventDefault(); if(!sigDrawing) return; const p = getXY(e); ctx.lineTo(p.x, p.y); ctx.stroke(); sigHasSig = true; }; canvas.ontouchend = e => { e.preventDefault(); sigDrawing = false; }; }, 80); } function clearSig(){ const canvas = document.getElementById('sigCanvas'); if(!canvas) return; const ctx = canvas._ctx || canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); sigHasSig = false; } function submitPaperless(){ const err=document.getElementById('paperErr'); const missing=[]; if(!document.getElementById('plDob')?.value) missing.push('Date of Birth'); if(!document.getElementById('plNat')?.value) missing.push('Nationality'); if(!document.getElementById('plPassport')?.value.trim()) missing.push('Passport Number'); if(!document.getElementById('plPassportExp')?.value) missing.push('Passport Expiry'); if(!document.getElementById('plLicense')?.value.trim()) missing.push('License Number'); if(!document.getElementById('plLicenseExp')?.value) missing.push('License Expiry'); if(!document.getElementById('plLicenseCountry')?.value) missing.push('License Country'); if(!sigHasSig) missing.push('Digital Signature'); if(!document.getElementById('plAgree')?.checked) missing.push('Agreement to Terms'); if(missing.length){ if(err) err.textContent='⚠ Please complete: '+missing.join(', '); return; } if(err) err.textContent=''; const btn=document.querySelector('.paper-submit-btn'); if(btn){btn.innerHTML='⏳ Submitting…';btn.disabled=true;} setTimeout(()=>{ const form=document.getElementById('paperForm'); if(form)form.style.display='none'; const succ=document.getElementById('paperSuccess'); if(succ)succ.style.display='flex'; // Show booking ref in success card const refEl=document.getElementById('plSuccessRef'); if(refEl) refEl.textContent=document.getElementById('confRef')?.textContent||'—'; },1400); } function skipPaperless(){ goHome(); } // ── Vehicle detail panel ── let activeDetailId = null; let selectedDetailRate = {}; // carId → 'online'|'counter' function expandCard(id){ // Close any open detail if(activeDetailId && activeDetailId !== id){ const prev = document.getElementById('vd'+activeDetailId); if(prev){ prev.classList.remove('open'); document.getElementById('vc'+activeDetailId)?.classList.remove('hl'); } } const detail = document.getElementById('vd'+id); const card = document.getElementById('vc'+id); if(!detail) return; const isOpen = detail.classList.contains('open'); if(isOpen){ closeDetail(id); return; } detail.classList.add('open'); card?.classList.add('hl'); activeDetailId = id; // Default to online rate selected if(!selectedDetailRate[id]) selectedDetailRate[id] = 'online'; selectDetailRate(id, selectedDetailRate[id]); // Scroll detail into view setTimeout(()=>detail.scrollIntoView({behavior:'smooth',block:'nearest'}), 50); } function closeDetail(id){ const detail = document.getElementById('vd'+id); const card = document.getElementById('vc'+id); if(detail) detail.classList.remove('open'); if(card) card.classList.remove('hl'); if(activeDetailId === id) activeDetailId = null; } function selectDetailRate(carId, rate){ selectedDetailRate[carId] = rate; ['online','counter'].forEach(r=>{ const card = document.getElementById(`vdrc${carId}_${r}`); if(card) card.classList.toggle('selected', r===rate); }); } function bookFromDetail(carId){ const rate = selectedDetailRate[carId] || 'online'; if(getDays()<=0){ alert('Please go back and enter pick-up and return dates.'); return; } const car = FLEET.find(v=>v.id===carId); if(!car) return; BK = {car, days:getDays(), base:car.price*getDays(), extras:[], extTotal:0, promo:0, payOnline: rate==='online', onlineDisc:0, wizardMember: null, wizardDisc: 0, coverage:{windshield:false,tires:false,bundle:false,total:0}}; ['covWindshield','covTires','covBundle'].forEach(id=>{const el=document.getElementById(id);if(el)el.checked=false;}); const balloon = document.getElementById('upgradeBalloon'); if(balloon){balloon.style.display='none';balloon.innerHTML='';} fillBkStep1(); currentBkStep=1; updateBkSteps(1); showPage('booking'); updateBkBackLabel(); } // Safe DOM helpers — never crash if element doesn't exist function setTxt(id, val){ const el=document.getElementById(id); if(el) el.textContent=val; } function setEl(id, val){ const el=document.getElementById(id); if(el) el.innerHTML=val; } function getEl(id){ return document.getElementById(id); } function setHtml(id, val){ const el=document.getElementById(id); if(el) el.innerHTML=val; } function fillBkStep1(){ const{car,base,days}=BK; setHtml('bkCarImg', SVG[car.svg]?.(car.color)||''); setTxt('bkCarCat', car.cat); setTxt('bkCarName', car.make+' '+car.model); setHtml('bkCarSpecs', `
${car.seats}
Seats
${car.trans}
Trans.
${car.bags}
Bags
A/C
Climate
`); setTxt('bkLoc', S.loc||'—'); setTxt('bkPickup', fmtDate(S.pv)+' at '+S.pt); setTxt('bkReturn', fmtDate(S.rv)+' at '+S.rt); setTxt('bkDur', days+' day'+(days!==1?'s':'')); const lbl=car.make+' '+car.model+' × '+days+' day'+(days!==1?'s':''); setTxt('bkRateLbl', lbl); setTxt('bkBaseAmt', '$'+base); setTxt('bkTotal1', '$'+base); // Reset step 3 extras (no sidebar to update now) document.querySelectorAll('#bkP3 .pkg-card-opt input').forEach(cb=>cb.checked=false); BK.extras=[]; BK.extTotal=0; } function calcCoverage(){ const tp = document.getElementById('covTP'); const cdw = document.getElementById('covCDW'); const pai = document.getElementById('covPAI'); const ws = document.getElementById('covWindshield'); const ti = document.getElementById('covTires'); const bu = document.getElementById('covBundle'); // Mutual exclusion: bundle vs individual windshield/tires if(bu && bu.checked){ if(ws) ws.checked = false; if(ti) ti.checked = false; } else if((ws && ws.checked) || (ti && ti.checked)){ if(bu) bu.checked = false; } const days = BK.days || 1; const bundleRate = getBundleRate(); const covTotal = (tp && tp.checked ? 12 : 0) * days + (cdw && cdw.checked ? 15 : 0) * days + (pai && pai.checked ? 5 : 0) * days + (bu && bu.checked ? bundleRate : 0) * days + (ws && ws.checked ? 9 : 0) * days + (ti && ti.checked ? 7 : 0) * days; BK.coverage = { tp: !!(tp && tp.checked), cdw: !!(cdw && cdw.checked), pai: !!(pai && pai.checked), windshield:!!(ws && ws.checked), tires: !!(ti && ti.checked), bundle: !!(bu && bu.checked), total: covTotal }; updateCov2Total(); refreshBundlePriceDisplay(); updateCovLines(); updateRvTotal(); } function getBundleRate(){ // 15% discount on Full Protection Bundle when paying online return BK.payOnline ? Math.round(14 * 0.85 * 100) / 100 : 14; } function refreshBundlePriceDisplay(){ const online = BK.payOnline; const badge = document.getElementById('covOnlineDiscBadge'); const priceEl = document.getElementById('bundlePriceDisplay'); const strikeEl = document.getElementById('bundleStrike'); if (badge) badge.style.display = online ? 'flex' : 'none'; if (priceEl) priceEl.textContent = online ? `$${getBundleRate()}` : '$14'; if (strikeEl) strikeEl.textContent = online ? '$14/day' : '$16/day'; } function updateCovLines(){ const cov = BK.coverage || {}; const days = BK.days || 1; const lines = []; if(cov.tp) lines.push({ name: '🔒 Theft Protection (TP)', amt: 12*days }); if(cov.cdw) lines.push({ name: '🚗 Collision Damage Waiver (CDW)', amt: 15*days }); if(cov.pai) lines.push({ name: '🏥 Personal Accident Insurance (PAI)', amt: 5*days }); if(cov.bundle){ const bundleAmt = Math.round(getBundleRate() * days * 100) / 100; lines.push({ name: '🛡️ Full Protection Bundle', amt: bundleAmt }); } if(cov.windshield) lines.push({ name: '🪟 Windshield Coverage', amt: 9*days }); if(cov.tires) lines.push({ name: '🔄 Tires Coverage', amt: 7*days }); const el = document.getElementById('rvCovLines'); if(el) el.innerHTML = lines.map(l=> `
${l.name}+$${l.amt.toFixed(2)}
` ).join(''); } function initCoverageDisplay(){ const days = BK.days; const ws = document.getElementById('covWindshield'); const ti = document.getElementById('covTires'); const bu = document.getElementById('covBundle'); if (!BK.coverage) BK.coverage = { windshield: false, tires: false, bundle: false, total: 0 }; const bundleRate = getBundleRate(); const buAmt = Math.round(bundleRate * days * 100) / 100; setTxt('windshieldTotal', ws?.checked ? `$${9*days} total` : `$${9*days} for ${days} day${days!==1?'s':''}`); setTxt('tiresTotal', ti?.checked ? `$${7*days} total` : `$${7*days} for ${days} day${days!==1?'s':''}`); setTxt('bundleTotal', bu?.checked ? `$${buAmt} total` : `$${buAmt} for ${days} day${days!==1?'s':''}`); refreshBundlePriceDisplay(); } function calcExt(){ let ext=0;const lines=[]; document.querySelectorAll('#bkP3 .pkg-card-opt input:checked').forEach(cb=>{const a=parseInt(cb.dataset.price)*BK.days;ext+=a;lines.push({name:cb.dataset.name,a});}); BK.extTotal=ext;BK.extras=lines; updateExtBottomTotal(); // Also update review sidebar exLines if visible const exLinesEl=document.getElementById('exLines'); if(exLinesEl) exLinesEl.innerHTML=lines.map(l=>`
${l.name}+$${l.a}
`).join(''); const exTotEl=document.getElementById('exTotal'); if(exTotEl) exTotEl.textContent='$'+(BK.base+ext+(BK.coverage?.total||0)); } let currentBkStep = 1; function bkGo(step){ currentBkStep = step; [1,2,3,4,5].forEach(s=>{const p=document.getElementById('bkP'+s);if(p)p.style.display=s===step?'block':'none';}); updateBkSteps(step); if(step===2){ initCovStep2(); } if(step===3){ initExtStep3(); } if(step===4){ fillReview(); if(BK.payOnline)setTimeout(initStripe,50); } updateBkBackLabel(); window.scrollTo(0,0); } function initCovStep2(){ updateCov2Total(); initCoverageDisplay(); } function updateCov2Total(){ const days = BK.days || 1; const covTotal = BK.coverage?.total || 0; const total = BK.base + covTotal; const el = document.getElementById('covBottomTotal'); if(el) el.textContent = '$' + total.toFixed(2); } function initExtStep3(){ updateExtBottomTotal(); } function updateExtBottomTotal(){ const covTotal=BK.coverage?.total||0; const total=BK.base+BK.extTotal+covTotal; const el=document.getElementById('extBottomTotal'); if(el) el.textContent='$'+total.toFixed(2); } function updateBkBackLabel(){ const back = document.getElementById('hdrBack'); if(!back) return; const arrow = ``; const labels = {1:'Vehicles', 2:'Your Vehicle', 3:'Protection', 4:'Extras'}; if(currentBkStep >= 5){ back.style.visibility='hidden'; return; } back.innerHTML = `${arrow} ${labels[currentBkStep]||'Back'}`; back.style.visibility = 'visible'; } function updateBkSteps(step){ // Step bar: bsi1=Protection(2), bsi2=Extras(3), bsi3=Review(4) ['bsi1','bsi2','bsi3'].forEach((id,i)=>{ const el=document.getElementById(id); if(!el) return; const s=i+2; el.classList.remove('act','done'); if(s===step) el.classList.add('act'); else if(s{ const el=document.getElementById(id); if(el) el.classList.toggle('done',step>i+2); }); // Header step bar (if present) ['hsn1','hsn2','hsn3'].forEach((id,i)=>{ const n=document.getElementById(id); if(!n) return; const s=i+2; n.classList.remove('act','done','dim'); if(s`
${e.name}+$${e.a}
`).join('')); updateRvTotal(); // Confirm button label const btn = getEl('confirmBtn'); if(btn) btn.innerHTML = BK.payOnline ? ` Pay & Confirm` : ` Book Now`; populateUpgrades(); startReviewTimer(); } function toggleTaxBreakdown(){ const bd = getEl('rvTaxBreakdown'); const arrow = getEl('rvTaxArrow'); if(!bd) return; const open = bd.style.display !== 'none'; bd.style.display = open ? 'none' : 'block'; if(arrow) arrow.style.transform = open ? 'rotate(0deg)' : 'rotate(180deg)'; } // Countdown timer let rvTimerInterval = null; function startReviewTimer(){ clearInterval(rvTimerInterval); let secs = 25 * 60; // 25 minutes function tick(){ const m = Math.floor(secs/60), s = secs%60; setTxt('rvTimerVal', `${m}:${String(s).padStart(2,'0')}`); if(secs<=0){ clearInterval(rvTimerInterval); setTxt('rvTimerVal','0:00'); } secs--; } tick(); rvTimerInterval = setInterval(tick, 1000); } function updateRvTotal(){ const sub = BK.base + BK.extTotal + (BK.coverage?.total || 0); const tax = 15; const disc = BK.payOnline ? Math.round(sub * .1) : 0; BK.onlineDisc = disc; // Avis Preferred member discount (applied on base rate only, after online disc) const wzDisc = BK.wizardMember ? Math.round((sub - disc) * (BK.wizardMember.discount / 100) * 100) / 100 : 0; BK.wizardDisc = wzDisc; // Protection total display const protTotal = BK.extTotal + (BK.coverage?.total||0); setTxt('rvProtTotal', '$'+protTotal.toFixed(2)); const emptyEl = getEl('rvProtEmpty'); if(emptyEl) emptyEl.style.display = protTotal>0 ? 'none' : 'block'; // Savings rows setTxt('rvOnlineAmt', disc>0 ? '-$'+disc.toFixed(2) : '$0.00'); const promoRow = getEl('rvPromoRow'); if(promoRow) promoRow.style.display = BK.promo>0 ? 'flex' : 'none'; if(BK.promo>0) setTxt('rvPromoAmt', '-$'+BK.promo.toFixed(2)); // Wizard discount row const wzRow = getEl('rvWizardRow'); if(wzRow){ wzRow.style.display = wzDisc>0 ? 'flex' : 'none'; if(wzDisc>0){ setTxt('rvWizardLabel', `${BK.wizardMember.tier} member (${BK.wizardMember.discount}%)`); setTxt('rvWizardAmt', '-$'+wzDisc.toFixed(2)); } } // Tax setTxt('rvTaxAmt', '$'+tax.toFixed(2)); // Grand total const total = Math.max(0, sub - disc - BK.promo - wzDisc + tax); setTxt('rvTotal', '$'+total.toFixed(2)); setTxt('rvBaseAmt', '$'+sub.toFixed(2)); } function selectPay(m){ BK.payOnline=m==='online'; document.getElementById('payOnlineOpt').classList.toggle('act',BK.payOnline); document.getElementById('payCounterOpt').classList.toggle('act',!BK.payOnline); document.getElementById('payBanner').style.display=BK.payOnline?'flex':'none'; document.getElementById('payCounter').style.display=BK.payOnline?'none':'flex'; document.getElementById('cardFields').style.display=BK.payOnline?'block':'none'; if(BK.payOnline) setTimeout(initStripe,50); // Update confirm button label const btn = document.getElementById('confirmBtn'); if(btn){ if(BK.payOnline){ btn.innerHTML = ` Pay & Confirm`; } else { btn.innerHTML = ` Book Now`; } } // Recalculate bundle cost if bundle is selected if(BK.coverage?.bundle){ BK.coverage.total = getBundleRate()*BK.days; } refreshBundlePriceDisplay(); initCoverageDisplay(); updateCovLines(); updateRvTotal(); } const PROMOS_DEFAULT={WEEKEND20:20,SUMMER25:25,WEEK7FREE:15,AIRPORT25:10}; function getPromoCodes(){ return Object.assign({}, PROMOS_DEFAULT, PROMO_CODES_SYNCED||{}); } function applyPromo(){ const code=document.getElementById('cfPromo').value.trim().toUpperCase(); const msg=document.getElementById('promoMsg'); const row=document.getElementById('rvPromoRow'); const codes=getPromoCodes(); if(codes[code]){ const pct=codes[code];const disc=Math.round((BK.base+BK.extTotal)*pct/100); BK.promo=disc;msg.style.color='var(--green)';msg.textContent='✓ '+pct+'% applied — saving $'+disc; document.getElementById('rvPromoAmt').textContent='-$'+disc;row.style.display='flex';updateRvTotal(); } else {BK.promo=0;row.style.display='none';msg.style.color='#c00';msg.textContent='✗ Invalid code';updateRvTotal();} } // ── STRIPE ────────────────────────────────────────── function initStripe(){ if(!window.Stripe||stripe)return; stripe=Stripe(STRIPE_PK); const els=stripe.elements({fonts:[{cssSrc:'https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600&display=swap'}]}); stripeCard=els.create('card',{style:{base:{fontFamily:"'Source Sans 3',Arial,sans-serif",fontSize:'15px',color:'#1a1a1a','::placeholder':{color:'#aaa'}},invalid:{color:'#c00'}},hidePostalCode:true}); stripeCard.mount('#stripe-card-element'); stripeCard.on('change',e=>{cardOk=e.complete;document.getElementById('stripe-card-errors').textContent=e.error?e.error.message:'';}); } async function confirmBooking(){ const first=document.getElementById('cfFirst').value.trim(); const last=document.getElementById('cfLast').value.trim(); const email=document.getElementById('cfEmail').value.trim(); const phone=document.getElementById('cfPhone').value.trim(); const tc=document.getElementById('cfTc').checked; const errEl=document.getElementById('payErrMsg'); errEl.textContent=''; if(!first||!last||!email||!phone){errEl.textContent='⚠ Please fill in all required driver details.';return;} if(!email.includes('@')){errEl.textContent='⚠ Please enter a valid email address.';return;} if(!tc){errEl.textContent='⚠ Please agree to the Terms & Conditions.';return;} if(!BK.payOnline){finishBooking(first,last,email,phone,null);return;} if(!stripeCard||!cardOk){errEl.textContent='⚠ Please complete your card details.';return;} const total=Math.max(0,BK.base+BK.extTotal+(BK.coverage?.total||0)-BK.onlineDisc-BK.promo); showPayModal('processing'); try{ if(STRIPE_PK.includes('YOUR_PUBLISHABLE_KEY')){await simulatePay();finishBooking(first,last,email,phone,'DEMO-'+Date.now());return;} const res=await fetch('/api/create-payment-intent',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({amount:Math.round(total*100),currency:'usd'})}); const{clientSecret}=await res.json(); if(!clientSecret)throw new Error('No client secret'); const result=await stripe.confirmCardPayment(clientSecret,{payment_method:{card:stripeCard,billing_details:{name:first+' '+last,email,phone}}}); if(result.error){showPayModal('failed',result.error.message);} else if(result.paymentIntent.status==='succeeded'){showPayModal('success');setTimeout(()=>{closePayModal();finishBooking(first,last,email,phone,result.paymentIntent.id);},1600);} }catch(err){showPayModal('failed',err.message||'Unexpected error. Please try again.');} } function finishBooking(first,last,email,phone,payId){ const ref = generateBookingRef(); const total=Math.max(0,BK.base+BK.extTotal+(BK.coverage?.total||0)-BK.onlineDisc-BK.promo-(BK.wizardDisc||0)); document.getElementById('confEmail').textContent=email; document.getElementById('confRef').textContent=ref; const payLine=BK.payOnline?`
Payment: Charged online${payId?` · Ref: ${String(payId).slice(0,20)}…`:''}
`:'
Payment: Due at pick-up counter
'; document.getElementById('confDtls').innerHTML= `
Driver: ${first} ${last}
Vehicle: ${BK.car.make} ${BK.car.model} (${BK.car.cat})
Pick-up: ${S.loc}
Dates: ${fmtDate(S.pv)} ${S.pt} → ${fmtDate(S.rv)} ${S.rt}
Duration: ${BK.days} day${BK.days!==1?'s':''}
${BK.payOnline&&BK.onlineDisc>0?`
Online discount (10%): -$${BK.onlineDisc}
`:''} ${BK.promo>0?`
Promo discount: -$${BK.promo}
`:''}
Total: $${total}
${payLine}`; bkGo(5); updateBkSteps(5); sigHasSig=false; setTimeout(initSigCanvas, 100); // Fade in email status cards on confirmation page setTimeout(()=>{ const c1=document.getElementById('confEmailCard1'); const c2=document.getElementById('confEmailCard2'); if(c1) c1.style.opacity='1'; if(c2) c2.style.opacity='1'; }, 300); // ── AUTO EMAILS ── const booking = { ref, first, last, email, phone, vehicle: `${BK.car.make} ${BK.car.model}`, category: BK.car.cat, pickup: S.loc, dropoff: S.rv_loc || S.loc, pickupDate: fmtDate(S.pv), pickupTime: S.pt, dropoffDate: fmtDate(S.rv), dropoffTime: S.rt, days: BK.days, base: BK.base, extTotal: BK.extTotal, covTotal: BK.coverage?.total || 0, onlineDisc: BK.onlineDisc || 0, promoDisc: BK.promo || 0, wizardDisc: BK.wizardDisc || 0, wizardMember: BK.wizardMember ? `${BK.wizardMember.name} (${BK.wizardMember.num})` : null, total: `$${total}`, totalNum: total, payMethod: BK.payOnline ? 'Online (Card)' : 'Pay at Counter', promoCode: document.getElementById('cfPromo')?.value || '', protection: Object.entries(BK.coverage||{}).filter(([k,v])=>v===true).map(([k])=>k).join(', ') || 'None', paperlessLink: `https://drivn.com/paperless?ref=${ref}`, pickupMs: S.pv ? S.pv.getTime() : null, bookedAt: new Date().toLocaleString(), }; // 1. Send instant confirmation email to customer + ops copy sendAutoEmail('confirmation', booking); // 2. Schedule 12-hour reminder scheduleReminderEmail(booking); // 3. Award Avis Rewards points if member is logged in rwAwardBookingPoints(booking); rwUpdateHeaderState(); } function showPayModal(state,msg=''){ const m=document.getElementById('payModal');m.classList.add('open'); document.getElementById('paySpinner').style.display=state==='processing'?'block':'none'; document.getElementById('payOk').style.display=state==='success'?'block':'none'; document.getElementById('payFail').style.display=state==='failed'?'block':'none'; if(state==='failed'&&msg)document.getElementById('payFailMsg').textContent=msg; const btn=document.getElementById('confirmBtn');if(btn)btn.disabled=(state==='processing'); } function closePayModal(){document.getElementById('payModal').classList.remove('open');const btn=document.getElementById('confirmBtn');if(btn)btn.disabled=false;} function simulatePay(){ return new Promise(resolve=>{ let i=0;const msgs=['Processing payment…','Authorising card…','Verifying with bank…','Confirming…']; const t=()=>{if(i v.price > current.price) .sort((a,b) => a.price - b.price); if(!upgrades.length){ balloon.style.display = 'block'; balloon.innerHTML = `
You Picked Our Best!
Enjoy the top-tier experience
You're already in our premium tier. Enjoy every mile!
`; return; } upgradeSlideIndex = 0; upgradeTotalSlides = upgrades.length; // One car per slide const slidesHtml = upgrades.map((v, i) => { const diff = (v.price - current.price) * days; const svgHtml = SVG[v.svg] ? SVG[v.svg](v.color) : ''; return `
${svgHtml}
${v.make} ${v.model}
${v.cat}
+$${diff} for ${days} day${days!==1?'s':''}
`; }).join(''); const dotsHtml = upgrades.length > 1 ? upgrades.map((_,i) => `
`).join('') : ''; balloon.style.display = 'block'; balloon.innerHTML = `
🚗
Upgrade Your Ride
Treat yourself to something better
Special Offer
${slidesHtml}
${upgrades.length > 1 ? `
${dotsHtml}
` : ''}
Only the price difference applies — current booking stays confirmed.
`; } function shiftUpgrade(dir){ upgradeSlideIndex = Math.max(0, Math.min(upgradeTotalSlides - 1, upgradeSlideIndex + dir)); goUpgradeSlide(upgradeSlideIndex); } function goUpgradeSlide(idx){ upgradeSlideIndex = idx; const track = document.getElementById('upgradeTrack'); if(track) track.style.transform = `translateX(-${idx * 100}%)`; // Update dots document.querySelectorAll('.upgrade-dot').forEach((d,i) => d.classList.toggle('act', i===idx)); // Update arrow states const prev = document.getElementById('upgradeArrPrev'); const next = document.getElementById('upgradeArrNext'); if(prev) prev.disabled = idx === 0; if(next) next.disabled = idx === upgradeTotalSlides - 1; } function requestUpgrade(carId){ const car = FLEET.find(v => v.id === carId); if(!car) return; const diff = (car.price - BK.car.price) * BK.days; // Update BK with the new car BK.car = car; BK.base = car.price * BK.days; BK.extras = []; BK.extTotal= 0; BK.promo = 0; BK.coverage= { tp:false, cdw:false, pai:false, windshield:false, tires:false, bundle:false, total:0 }; ['covTP','covCDW','covPAI','covWindshield','covTires','covBundle'].forEach(id=>{const el=document.getElementById(id);if(el)el.checked=false;}); // Hide balloon, go to step 1 to confirm new car const balloon = document.getElementById('upgradeBalloon'); if(balloon){ balloon.style.display='none'; balloon.innerHTML=''; } fillBkStep1(); bkGo(1); // Show a brief toast showUpgradeToast(car.make + ' ' + car.model); } function showUpgradeToast(name){ let toast = document.getElementById('upgradeToast'); if(!toast){ toast = document.createElement('div'); toast.id = 'upgradeToast'; toast.style.cssText = ` position:fixed;bottom:24px;left:50%;transform:translateX(-50%); background:var(--green);color:#fff;padding:11px 24px;border-radius:2px; font-size:13px;font-weight:800;z-index:9999; box-shadow:0 4px 16px rgba(0,0,0,.2); transition:opacity .3s;pointer-events:none; `; document.body.appendChild(toast); } toast.textContent = `✓ Switched to ${name} — review your booking`; toast.style.opacity = '1'; setTimeout(()=>{ toast.style.opacity='0'; }, 3000); } // Init applySort('recommended'); // ════════ FLEET PORTAL ADDITIONS ════════ var COMPLIANCE_TYPES=['🪪 Registration Renewal','🛡️ Insurance Renewal','🔍 Vehicle Inspection (Annual)','📋 Roadworthiness Certificate']; const BODY_DAMAGE_TYPES=['Body Repair / Dent','Body Repair','Windshield / Glass']; let bdCurrentView='top',bdMarkers={top:[],side:[],rear:[]},bdPhotos=[],bdMarkerId=0; function getBdSummary(){ const allMarkers = [...bdMarkers.top, ...bdMarkers.side, ...bdMarkers.rear]; if(!allMarkers.length && !bdPhotos.length) return ''; let summary = ''; if(allMarkers.length){ const points = allMarkers.map(function(m){ return m.view.charAt(0).toUpperCase()+m.view.slice(1)+': '+m.label; }).join(', '); summary += 'Damage locations: '+points; } if(bdPhotos.length){ summary += (summary?'. ':'')+(bdPhotos.length)+' damage photo'+(bdPhotos.length!==1?'s':'')+' attached'; } return summary; } function renderBdMarkers(){ const overlay = document.getElementById('bdMarkersOverlay'); if(!overlay) return; const vbMap = {top:'0 0 340 520', side:'0 0 580 220', rear:'0 0 340 300'}; overlay.setAttribute('viewBox', vbMap[bdCurrentView]||'0 0 340 520'); const markers = bdMarkers[bdCurrentView]||[]; // Build SVG via string concat (not template literals) to avoid browser parse issues with < in scripts overlay.innerHTML = markers.map(function(m){ var g = ''; g += ''; g += ''; g += ''; g += ''; g += ''+m.label+''; g += ''; return g; }).join(''); } function placeBdMarker(event, view){ const svg = event.currentTarget; const rect = svg.getBoundingClientRect(); const vb = svg.viewBox.baseVal; const scaleX = vb.width / rect.width; const scaleY = vb.height / rect.height; const x = Math.round((event.clientX - rect.left) * scaleX); const y = Math.round((event.clientY - rect.top) * scaleY); const nearby = bdMarkers[view].findIndex(m=>Math.abs(m.x-x)<18 && Math.abs(m.y-y)<18); if(nearby>=0){ bdMarkers[view].splice(nearby,1); renderBdMarkers(); updateBdMarkerList(); return; } const label = getBdAreaLabel(x, y, view); bdMarkers[view].push({ id:++bdMarkerId, x, y, view, label }); renderBdMarkers(); updateBdMarkerList(); } function getBdAreaLabel(x, y, view){ if(view==='top'){ // viewBox 340\u00d7520 const zone = y < 160 ? 'Bonnet' : y < 200 ? 'Windscreen' : y < 310 ? 'Roof' : y < 340 ? 'Rear Screen' : y < 418 ? 'Boot' : 'Rear Bumper'; const side = x < 100 ? ' (L)' : x > 240 ? ' (R)' : ''; return zone+side; } else if(view==='side'){ const zone = x < 130 ? 'Front Bumper' : x < 200 ? 'Bonnet' : x < 290 ? 'Front Door' : x < 410 ? 'Rear Door' : x < 480 ? 'Boot' : 'Rear Bumper'; const part = y < 80 ? ' Upper' : y > 155 ? ' Lower' : ''; return zone+part; } else { const zone = x < 110 ? 'Rear Left' : x > 230 ? 'Rear Right' : 'Rear Centre'; const part = y < 80 ? ' Roof' : y > 200 ? ' Bumper' : y > 130 ? ' Lower' : ' Upper'; return zone+part; } } function fleetSyncToStorage(){ try{ const m = fleetCurrentAccount; if(!m) return; const accounts = loadFleetAccountsFromAdmin(); const idx = accounts.findIndex(a=> a.email===m.email || a.id===m.id ); if(idx>=0){ // Merge: keep admin-managed fields, update requests and vehicle statuses accounts[idx].requests = m.requests; accounts[idx].vehicles = m.vehicles; localStorage.setItem('drivn_fleet_accounts', JSON.stringify(accounts)); } }catch(e){ console.warn('[Fleet sync]', e); } } function loadFleetAccountsFromAdmin(){ try{ const raw=localStorage.getItem('drivn_fleet_accounts'); if(!raw) return []; return JSON.parse(raw)||[]; }catch(e){ return []; } } function formatRequestNotes(notes){ if(!notes) return '
\u2014
'; const damageMatch = notes.match(/Damage locations: ([^.]+)/); const photoMatch = notes.match(/(\\\\d+) damage photo/); let cleanNote = notes .replace(/\\\\.?\\\\s*Damage locations:[^.|]*/g,'') .replace(/\\\\.?\\\\s*\\\\d+ damage photos? (attached|submitted)[^.]*/g,'') .replace(/\\\\.?\\\\s*\\\\| [^|]+/g,'') .trim().replace(/^[\\\\.\\\\|]\\\\s*/,'').trim(); const locs = damageMatch && damageMatch[1] ? damageMatch[1].split(',').map(function(s){ return s.trim(); }).filter(Boolean) : []; const photoCount = photoMatch ? parseInt(photoMatch[1]) : 0; var html = ''; if(cleanNote && cleanNote !== '\u2014'){ html += '
' + cleanNote + '
'; } if(locs.length){ var pills = locs.map(function(l){ return '\u2715 '+l+''; }).join(''); var photoSpan = photoCount > 0 ? '\ud83d\udcf7 '+photoCount+' photo'+(photoCount!==1?'s':'')+'' : ''; html += '
' + '\ud83d\ude97 Damage:' + pills + photoSpan + '
'; } return html || '
\u2014
'; } function renderBdPhotos(){ const wrap = document.getElementById('bdPhotoPreviews'); if(!wrap) return; wrap.innerHTML = bdPhotos.map(function(p,i){ var s = '
'; s += '\"'+p.name+'\"'; s += ''; s += '
'+p.name+'
'; s += '
'; return s; }).join(''); const zone=document.getElementById('bdPhotoZone'); if(zone) zone.style.display = bdPhotos.length>=5 ? 'none' : 'block'; } function handleBdPhotoUpload(input){ Array.from(input.files).slice(0, 5-bdPhotos.length).forEach(file=>{ if(!file.type.startsWith('image/')){ showToast('\u26a0 Image files only'); return; } if(file.size>10*1024*1024){ showToast('\u26a0 Max 10MB per photo'); return; } const reader = new FileReader(); reader.onload=e=>{ bdPhotos.push({name:file.name, dataUrl:e.target.result}); renderBdPhotos(); }; reader.readAsDataURL(file); }); input.value=''; } function removeBdPhoto(idx){ bdPhotos.splice(idx,1); renderBdPhotos(); } function setBdView(view){ bdCurrentView = view; const vbMap = {top:'0 0 340 520', side:'0 0 580 220', rear:'0 0 340 300'}; ['top','side','rear'].forEach(v=>{ const svg = document.getElementById('bdDiagram'+v.charAt(0).toUpperCase()+v.slice(1)); const btn = document.getElementById('bdView'+v.charAt(0).toUpperCase()+v.slice(1)); if(svg) svg.style.display = v===view ? 'block' : 'none'; if(btn){ btn.style.background = v===view ? '#4338ca' : '#f5f5f5'; btn.style.color = v===view ? '#fff' : '#555'; } }); const overlay = document.getElementById('bdMarkersOverlay'); if(overlay) overlay.setAttribute('viewBox', vbMap[view]||'0 0 340 520'); renderBdMarkers(); } function resetBdPanel(){ bdMarkers={top:[],side:[],rear:[]}; bdPhotos=[]; bdMarkerId=0; bdCurrentView='top'; setBdView('top'); renderBdPhotos(); const markerList=document.getElementById('bdMarkerList'); if(markerList) markerList.style.display='none'; const zone=document.getElementById('bdPhotoZone'); if(zone) zone.style.display='block'; } function handleSrTypeChange(val){ const regPanel = document.getElementById('srRegFields'); const regTitle = document.getElementById('srRegFieldsTitle'); const bodyPanel = document.getElementById('srBodyPanel'); const isCompliance = COMPLIANCE_TYPES.includes(val); const isBodyDamage = BODY_DAMAGE_TYPES.includes(val); if(regPanel) regPanel.style.display = isCompliance ? 'block' : 'none'; if(bodyPanel) bodyPanel.style.display = isBodyDamage ? 'block' : 'none'; // Update registration panel title const titles = { 'Registration Renewal': '\ud83e\udeaa Registration Renewal Details', 'Insurance Renewal': '\ud83d\udee1\ufe0f Insurance Renewal Details', 'Vehicle Inspection (Annual)': '\ud83d\udd0d Annual Inspection Details', 'Roadworthiness Certificate': '\ud83d\udccb Roadworthiness Certificate Details', }; if(regTitle) regTitle.textContent = titles[val] || 'Compliance Details'; // Pre-fill plate from selected vehicle for compliance if(isCompliance){ const sel = document.getElementById('srVehicle'); const m = fleetCurrentAccount; if(sel && m){ const v = m.vehicles.find(x=>x.id===sel.value); const plateEl = document.getElementById('srRegPlate'); if(v && plateEl) plateEl.value = v.plate; } const expiryEl = document.getElementById('srRegExpiry'); if(expiryEl) expiryEl.addEventListener('change', checkRegExpiryWarning); } // Reset body damage panel when switching to it if(isBodyDamage) resetBdPanel(); } function updateBdMarkerList(){ const allMarkers = [...bdMarkers.top, ...bdMarkers.side, ...bdMarkers.rear]; const listWrap = document.getElementById('bdMarkerList'); const listEl = document.getElementById('bdMarkerItems'); if(!listWrap||!listEl) return; if(!allMarkers.length){ listWrap.style.display='none'; return; } listWrap.style.display='block'; listEl.innerHTML = allMarkers.map(function(m){ var label = m.view.charAt(0).toUpperCase()+m.view.slice(1)+' \u2014 '+m.label; return '' + '\u2715 '+label + '' + ''; }).join(''); } function fltReqRow(r){ var rc=reqSC[r.status]||reqSC.scheduled, pc=priorCls[r.priority]||priorCls.normal; var hasDamage=r.notes&&(r.notes.includes('Damage locations:')||r.notes.includes('damage photo')); var dmg=hasDamage?'\ud83d\ude97 Body Damage':''; var cancel=r.status!=='completed'&&r.status!=='cancelled' ?'' :''; var s='
'; s+='
'+r.id+'
'; s+='
'+r.type+dmg+'
'; s+='
'+r.vehicle+'
'; s+='
'+formatRequestNotes(r.notes)+'
'; s+='
By '+r.contact+' \u00b7 '+r.submitted+'
'; s+='
'+r.priority.toUpperCase()+'
'; s+='
'+rc.label+'
Pref: '+r.date+'
'; s+='
'+cancel+'
'; s+='
'; return s; } function fltVehRow(v){ var sc=statusCfg[v.status]||statusCfg.ok; var barPct=Math.min(100,Math.round((v.mileage/v.nextService)*100)); var bc=fltVehBarColor(v); return '
' +'
'+v.plate+'
'+v.make+' '+v.model+'
' +'
'+v.mileage.toLocaleString()+' km'+v.nextService.toLocaleString()+' km
' +'
' +'
'+(v.nextService-v.mileage).toLocaleString()+' km left
' +'
'+sc.label+'
' +'
'; } function fltSchedRow(v){ var sc=statusCfg[v.status]||statusCfg.ok; var barPct=Math.min(100,Math.round((v.mileage/v.nextService)*100)); var bc=fltVehBarColor(v); var kmLeft=v.nextService-v.mileage; var due=v.status==='overdue'?'Overdue now':v.status==='due'?'Due within 500km':'~'+kmLeft.toLocaleString()+' km remaining'; var btnBg=v.status!=='ok'?'var(--red)':'#f5f5f5', btnClr=v.status!=='ok'?'#fff':'#555'; var s='
'; s+='
'+v.plate+'
'; s+='
'+v.make+' '+v.model+' '+v.year+'
'+v.assigned+'
'; s+='
'+v.mileage.toLocaleString()+' / '+v.nextService.toLocaleString()+' km
'; s+='
'; s+='
'+due+'
'; s+='
'+sc.label+''; s+='
'; s+='
'; return s; } function fltHistRow(h){ return '
' +'
'+h.date+'
' +'
'+h.type+'
'+h.plate+' \u2014 '+h.vname+'
'+h.notes+'
' +'
'+h.mileage.toLocaleString()+' km
' +'
$'+h.cost+'
' +'
\u2713 Done
' +'
'; } function parseDamageMarkers(notes){ if(!notes) return []; const match = notes.match(/Damage locations: ([^.]+)/); if(!match) return []; const parts = match[1].split(',').map(s=>s.trim()).filter(Boolean); return parts.map(function(part){ // \"Top: Bonnet (L)\" \u2192 {view:'top', label:'Bonnet (L)'} const colon = part.indexOf(':'); if(colon < 0) return null; const view = part.slice(0,colon).trim().toLowerCase(); const label = part.slice(colon+1).trim(); // Estimate x,y from label using same logic as getBdAreaLabel (reverse-mapped) const pos = estimateMarkerPos(view, label); return {view, label, x:pos.x, y:pos.y}; }).filter(Boolean); } function estimateMarkerPos(view, label){ const l = label.toLowerCase(); if(view==='top'){ // viewBox 340\u00d7520 let y = 260, x = 170; if(l.includes('bonnet')) y=100; else if(l.includes('windscreen')) y=180; else if(l.includes('roof')) y=255; else if(l.includes('rear screen')) y=320; else if(l.includes('boot')) y=380; else if(l.includes('rear bumper')) y=470; if(l.includes('(l)')) x=70; else if(l.includes('(r)')) x=270; return {x, y}; } else if(view==='side'){ // viewBox 580\u00d7220 let x=290, y=120; if(l.includes('front bumper')) x=80; else if(l.includes('bonnet')) x=165; else if(l.includes('front door')) x=245; else if(l.includes('rear door')) x=350; else if(l.includes('boot')) x=430; else if(l.includes('rear bumper')) x=520; if(l.includes('upper')) y=60; else if(l.includes('lower')) y=168; return {x, y}; } else { // viewBox 340\u00d7300 let x=170, y=150; if(l.includes('left')) x=70; else if(l.includes('right')) x=270; if(l.includes('roof')) y=50; else if(l.includes('upper')) y=100; else if(l.includes('lower')) y=165; else if(l.includes('bumper')) y=240; return {x, y}; } } function escDq(s){return(s||'').replace(/"/g,'"').replace(/'/g,''');} function removeBdMarker(id){['top','side','rear'].forEach(function(v){bdMarkers[v]=bdMarkers[v].filter(function(m){return m.id!==id;});});renderBdMarkers();updateBdMarkerList();} function openCustDamageView(notes,title,sub,reqId){var markers=parseDamageMarkers(notes);document.getElementById('custDvTitle').textContent=title||'Damage Report';document.getElementById('custDvSub').textContent=sub||'';document.getElementById('custDvMarkerList').innerHTML=markers.length?markers.map(function(m,i){return '
'+(i+1)+'
'+m.label+'
'+m.view+'
';}).join(''):'
No damage locations recorded.
';custDvRenderMarkers(markers);var counts={top:0,side:0,rear:0};markers.forEach(function(m){if(counts[m.view]!==undefined)counts[m.view]++;});custDvSwitch(Object.entries(counts).sort(function(a,b){return b[1]-a[1];})[0][0]);document.getElementById('mCustDamageView').style.display='flex';document.body.style.overflow='hidden';} function closeCustDamageView(){document.getElementById('mCustDamageView').style.display='none';document.body.style.overflow='';} function custDvSwitch(view){['top','side','rear'].forEach(function(v){var p=document.getElementById('custDv-pnl-'+v),b=document.getElementById('custDv-tab-'+v);if(p)p.style.display=v===view?'block':'none';if(b){b.style.background=v===view?'#dc2626':'#f0f0f0';b.style.color=v===view?'#fff':'#555';}});} function custDvRenderMarkers(markers){var oids={top:'custDv-overlayTop',side:'custDv-overlaySide',rear:'custDv-overlayRear'};Object.values(oids).forEach(function(id){var el=document.getElementById(id);if(el)el.innerHTML='';});var ns='http://www.w3.org/2000/svg';markers.forEach(function(m,i){var oel=document.getElementById(oids[m.view]);if(!oel)return;var g=document.createElementNS(ns,'g');function mk(tag,attrs){var el=document.createElementNS(ns,tag);Object.entries(attrs).forEach(function(a){el.setAttribute(a[0],a[1]);});return el;}g.appendChild(mk('circle',{cx:m.x,cy:m.y,r:16,fill:'rgba(220,38,38,.15)',stroke:'rgba(220,38,38,.3)','stroke-width':1.5}));g.appendChild(mk('circle',{cx:m.x,cy:m.y,r:11,fill:'rgba(220,38,38,.18)',stroke:'#dc2626','stroke-width':2}));g.appendChild(mk('line',{x1:m.x-6,y1:m.y-6,x2:m.x+6,y2:m.y+6,stroke:'#dc2626','stroke-width':2.5,'stroke-linecap':'round'}));g.appendChild(mk('line',{x1:m.x+6,y1:m.y-6,x2:m.x-6,y2:m.y+6,stroke:'#dc2626','stroke-width':2.5,'stroke-linecap':'round'}));var bb=mk('circle',{cx:m.x+10,cy:m.y-10,r:8,fill:'#dc2626'});g.appendChild(bb);var bt=mk('text',{x:m.x+10,y:m.y-6,'text-anchor':'middle','font-size':9,fill:'#fff','font-family':'Arial,sans-serif','font-weight':700});bt.textContent=String(i+1);g.appendChild(bt);var lw=Math.min(m.label.length*5.5+8,90);g.appendChild(mk('rect',{x:m.x-lw/2,y:m.y+13,width:lw,height:13,rx:3,fill:'rgba(220,38,38,.85)'}));var lt=mk('text',{x:m.x,y:m.y+23,'text-anchor':'middle','font-size':7.5,fill:'#fff','font-family':'Arial,sans-serif','font-weight':700});lt.textContent=m.label.length>14?m.label.slice(0,13)+'…':m.label;g.appendChild(lt);oel.appendChild(g);});} document.addEventListener('click',function(e){var btn=e.target.closest('.custDvBtn');if(btn){e.stopPropagation();openCustDamageView(btn.dataset.notes||'',btn.dataset.type||'Damage Report',btn.dataset.veh||'',btn.dataset.reqid||'');}var vbtn=e.target.closest('.custDvViewBtn');if(vbtn){e.stopPropagation();custDvSwitch(vbtn.dataset.view);}}); function sendNewRequestNotification(account,vehicle,reqId,serviceType,notes,contact,date,mileage){try{var cfg=JSON.parse(localStorage.getItem('drivn_email_settings')||'{}');if(!cfg.autoNotify||!cfg.serviceId||!cfg.publicKey)return;var toEmail=cfg.notifyEmail||'';if(!toEmail)return;if(typeof emailjs==='undefined')return;emailjs.init(cfg.publicKey);var templateId=cfg.notifyTemplate||cfg.templateId;if(!templateId)return;emailjs.send(cfg.serviceId,templateId,{to_email:toEmail,to_name:'Maintenance Team',company_name:account.company,customer_name:account.contact,customer_email:account.email||'',ref_number:reqId,vehicle:vehicle?(vehicle.plate+' — '+vehicle.make+' '+vehicle.model):'Unknown',service_type:serviceType,service_date:date||new Date().toISOString().split('T')[0],mileage:mileage?mileage.toLocaleString()+' km':'—',priority:'normal',notes:notes||'—',contact:contact||'—',from_name:'AVIS Jordan Fleet Portal',subject:'New Service Request — '+serviceType+' | '+reqId}).then(function(){}).catch(function(err){console.warn('Notification failed:',err);});}catch(ex){console.warn('sendNewRequestNotification error:',ex);}} function goHelp(){showPage('manage');} function goDeals(){showPage('cars');} function goLocations(){showPage('home');setTimeout(function(){var el=document.querySelector('.hp-locs');if(el)el.scrollIntoView({behavior:'smooth'});},300);} // Run on page load loadFromAdmin(); // ════════════════════════════════════════════════════ // ADMIN OVERLAY — UPLOAD HANDLERS // ════════════════════════════════════════════════════ function showAdmTab(btn, tabId) { // Deactivate all tabs and panels document.querySelectorAll('.adm-tab-btn').forEach(function(b){ b.classList.remove('act'); b.style.color='#555'; b.style.borderBottom='2px solid transparent'; }); document.querySelectorAll('.adm-tab-panel').forEach(function(p){ p.style.display='none'; }); // Activate clicked tab btn.classList.add('act'); btn.style.color='#e31837'; btn.style.borderBottom='2px solid #e31837'; var panel = document.getElementById(tabId); if(panel) panel.style.display='block'; } function handleLogoUpload(input) { if(!input.files || !input.files[0]) return; var file = input.files[0]; var reader = new FileReader(); var infoEl = document.getElementById('logoFileInfo'); if(infoEl){ infoEl.style.display='flex'; infoEl.querySelector && (infoEl.querySelector('.adm-file-name') || {}).innerText; } reader.onload = function(e) { var data = e.target.result; // Save to localStorage localStorage.setItem('drivn_logo', data); // Apply immediately to all logo elements if(data.startsWith('data:image/svg') || data.includes(''; }); } // Update preview in admin panel var preview = document.getElementById('admLogoPreview'); if(preview){ preview.innerHTML = ''; } // Update file info if(infoEl){ infoEl.style.display='flex'; var nameEl = infoEl.querySelector('.adm-file-name'); if(nameEl) nameEl.textContent = file.name + ' ('+Math.round(file.size/1024)+'KB)'; } showToast('\u2705 Logo uploaded successfully!'); }; reader.readAsDataURL(file); } function handleBannerUpload(input, slot) { if(!input.files || !input.files[0]) return; var file = input.files[0]; var reader = new FileReader(); reader.onload = function(e) { var data = e.target.result; var key = 'drivn_' + slot + '_banner'; localStorage.setItem(key, data); // Apply to correct element var targets = { hero: ['.hero', '.hero-bg'], first: ['#adminPromoBanner1', '.promo-banner-1'], deals: ['#adminPromoBanner2', '.promo-banner-2'], preferred: ['#adminPromoBanner3', '.promo-banner-3'], app: ['#adminPromoBanner4', '.promo-banner-4'], }; var selectors = targets[slot] || []; selectors.forEach(function(sel){ document.querySelectorAll(sel).forEach(function(el){ el.style.backgroundImage = 'url('+data+')'; el.style.backgroundSize = 'cover'; el.style.backgroundPosition = 'center'; }); }); // Update preview var previewId = slot + 'BannerPreview'; var preview = document.getElementById(previewId); if(preview){ preview.style.backgroundImage='url('+data+')'; preview.style.backgroundSize='cover'; preview.style.backgroundPosition='center'; preview.style.display='block'; } // Update info var infoEl = document.getElementById(slot + 'BannerInfo'); if(infoEl){ infoEl.style.display='flex'; var nameEl = infoEl.querySelector('.adm-file-name'); if(nameEl) nameEl.textContent = file.name + ' ('+Math.round(file.size/1024)+'KB)'; } showToast('\u2705 ' + slot.charAt(0).toUpperCase()+slot.slice(1) + ' banner uploaded!'); }; reader.readAsDataURL(file); } function admPreview() { // Preview hero background colour var bg = document.getElementById('adm-hero-bg'); if(bg){ document.querySelectorAll('.hero').forEach(function(el){ el.style.background = bg.value; }); } } function openAdminPanel() { var overlay = document.getElementById('adminOverlay'); if(overlay){ overlay.style.display='flex'; document.body.style.overflow='hidden'; } // Load saved logo into preview var savedLogo = localStorage.getItem('drivn_logo'); var preview = document.getElementById('admLogoPreview'); if(savedLogo && preview){ preview.innerHTML = ''; } } function showAdmToast(msg) { showToast(msg || 'Saved!'); } // ── Restore uploads on page load ── (function restoreUploads(){ // Logo var logo = localStorage.getItem('drivn_logo'); if(logo){ if(logo.startsWith(''; }); } } // Banners var bannerSlots = { hero: ['.hero'], first: ['#adminPromoBanner1','.promo-banner-1'], deals: ['#adminPromoBanner2','.promo-banner-2'], preferred: ['#adminPromoBanner3','.promo-banner-3'], app: ['#adminPromoBanner4','.promo-banner-4'], }; Object.keys(bannerSlots).forEach(function(slot){ var data = localStorage.getItem('drivn_'+slot+'_banner'); if(data){ bannerSlots[slot].forEach(function(sel){ document.querySelectorAll(sel).forEach(function(el){ el.style.backgroundImage='url('+data+')'; el.style.backgroundSize='cover'; el.style.backgroundPosition='center'; }); }); } }); // Favicon var fav = localStorage.getItem('drivn_favicon'); if(fav){ var link = document.querySelector("link[rel*='icon']") || document.createElement('link'); link.rel='shortcut icon'; link.href=fav; document.head.appendChild(link); } })();