TravelHuge✈ TravelHuge

Welcome Back

Sign in securely to your account

✈  Swipe to sign in securely
✓ Signing you in…
✈  Swipe to create account
✓ Sending verification code…
256-bit SSL
End-to-End Encrypted
Email Verified

Account Created! 🎉

Welcome to TravelHuge.
Your journey begins now.

Check Your Email ✈

We've sent a 6-digit verification code to
your email

Enter the code below to activate your account.

Didn't receive it? Resend Code

← Back to TravelHuge

Flights & Hotels Packages Destinations Travel Insurance About Contact Sign In
India's Premium Custom Travel Platform

Design & Book Your Dream
Holiday
Securely & Personally

Bespoke journeys crafted for you. From pristine beaches to Himalayan peaks — we design unforgettable experiences tailored to your vision, budget, and travel dreams.

0
Happy Travellers
0
Destinations
0
Average Rating

500+ airlines · Real-time prices · Instant confirmation

SSL Secured256-bit encryption
Safe PaymentsRazorpay & Stripe
24/7 Expert SupportAlways here for you
Custom ItinerariesTailored just for you
4.9 / 5 Stars12,000+ reviews
Since 2017Trusted travel partner
Flights & HotelsAll bookings handled
12,000+ TravellersHappy clients served
Visa AssistanceWe handle the paperwork
500+ Partner HotelsBest rates guaranteed
4-Hour ResponseFastest in the industry
Travel InsuranceComprehensive coverage
Best Price PromisePrice match guarantee
200+ DestinationsIndia & worldwide
SSL Secured256-bit encryption
Safe PaymentsRazorpay & Stripe
24/7 Expert SupportAlways here for you
Custom ItinerariesTailored just for you
4.9 / 5 Stars12,000+ reviews
Since 2017Trusted travel partner
Flights & HotelsAll bookings handled
12,000+ TravellersHappy clients served
Visa AssistanceWe handle the paperwork
500+ Partner HotelsBest rates guaranteed
4-Hour ResponseFastest in the industry
Travel InsuranceComprehensive coverage
Best Price PromisePrice match guarantee
200+ DestinationsIndia & worldwide
Bali Maldives Kerala Dubai Thailand Rajasthan Himachal Andaman Paris Switzerland Goa Singapore Leh Ladakh Italy Uttarakhand Greece Manali Turkey Spiti Valley Amsterdam Coorg Barcelona Kashmir Prague Bali Maldives Kerala Dubai Thailand Rajasthan Himachal Andaman Paris Switzerland Goa Singapore Leh Ladakh Italy Uttarakhand Greece Manali Turkey Spiti Valley Amsterdam Coorg Barcelona Kashmir Prague
Personalised Trip Planning

Plan Your Perfect Trip

Tell us your dream — we'll craft a fully personalised itinerary with the best hotels, activities & flights. Free consultation, no booking fees, quotes in 4 hours.

Plan Your Perfect Trip

Free consultation · No booking fees · Personalised quotes in 4 hrs

1
2
3
4
5
DESTINATION DATES GROUP PREFS YOU
📍 Where do you want to go?
📅 When are you travelling?
Budget (INR)
₹1,00,000
₹20,000₹10,00,000+
👥 Who's travelling?
✈️
⚙️ Your travel preferences
🙋 Tell us about you
Profile completeness0%
? + ? = ? Anti-spam ✓
Your Itinerary Request is Sent! 🎉

Our travel specialist will call you within 4 hours. Check your email for a confirmation & we'll craft your personalised itinerary. 🌍

💬 WhatsApp Us
No spam, ever
Response within 4 hours
Zero booking fees
Dedicated travel specialist
What We Offer

Everything for your
perfect journey

From the first enquiry to your return home, TravelHuge handles every detail. Our expert team crafts completely custom itineraries — no templates, just your perfect trip.

01
Custom Holiday Packages

Curated escapes to 200+ destinations built around you — beach, mountain, cultural & luxury. Every itinerary hand-crafted to your exact style and budget.

02
Honeymoon Specials

Unforgettable romantic escapes — private villas, candlelit dinners, overwater bungalows. Your love story deserves the most perfect backdrop.

03
Flight Bookings

Best fares across all airlines searched by our experts. Domestic & international flights, business class upgrades, group bookings — all handled for you.

04
Hotel Reservations

From boutique stays to 5-star resorts and overwater villas — we secure the best rates and ensure VIP treatment at every property worldwide.

05
Family Holidays

Child-friendly resorts, adventure parks, wildlife safaris and educational tours. Memories your whole family will treasure for a lifetime.

06
Corporate Travel

Complete corporate travel management — conferences, incentive trips, business travel. Dedicated account managers ensuring smooth, cost-effective travel.

Top Packages

Explore our bestsellers

Swipe through our handpicked holiday packages. Click any card to get a personalised quote within 4 hours.

✏️ Don't see your ideal package?
We build 100% custom itineraries — tell us where you want to go!
Explore the World

Where will you go next?

Swipe through 100+ curated destinations. Click any card to get a personalised quote within 4 hours.

🔍
✏️ Don't see your destination?
Type any place in the world — we go everywhere!
📍 Bali, Indonesia
Request a Personalised Quote

Fill in your details and our specialist will build a bespoke itinerary for this destination within 4 hours.

✅ Phone verified
✅ Email verified
? + ? = ?Anti-spam ✓

Your information is encrypted and never shared with third parties.

Sending Your Request…

Please wait while we notify our travel specialists

Enquiry Received!

Our specialist for will contact you within 4 hours. A confirmation has been sent to your email.

✈️ Ready to Book

Pre-Designed Tour Packages

Fixed itineraries. Fixed prices. Book instantly — no waiting, no back-and-forth. Select travellers, pay online and you're confirmed within minutes.

Book With Us

Flights & Hotel Bookings

Tell us your requirements and our specialists will find you the best options. We handle all bookings so you don't have to.

Flight Booking Enquiry

Share your travel details and our team will find the best fares within 2–4 hours.

? + ? = ?Anti-spam ✓
Processing…
Please wait
Receiving your details…
Notifying flight team…
Sending confirmation email…
Agent assigned…
Flight Enquiry Sent!

Our flight specialist will contact you within 2–4 hours.

📞 Call Now
How it works
1
Fill in your route, travel dates and passenger details
2
We search across all airlines and GDS systems for the best fares
3
Our agent contacts you within 2–4 hours with options
4
You confirm, we book — instant e-tickets delivered to your inbox

📞 Prefer to call?

Specialists available Mon–Sat, 9 AM–7 PM IST

+91 0129-4324324
Hotel Booking Enquiry

Tell us your destination and preferences — we'll find the best hotels with exclusive rates.

? + ? = ?Anti-spam ✓
Processing…
Please wait
Receiving preferences…
Notifying hotel specialists…
Sending confirmation email…
Agent assigned…
Hotel Enquiry Sent!

Our hotel specialist will contact you within 4 hours.

📞 Call Now
Why book hotels with us?
Best Rates — We negotiate directly with hotels for exclusive pricing
Complimentary Perks — Upgrades, welcome amenities & late checkout
Airport Transfers — Seamlessly coordinated with your stay
On-Trip Support — We're just a call away throughout your stay

💬 WhatsApp Us

Fastest response via WhatsApp — usually under 30 mins

💬 +91-9818644324
Stay Protected

Travel with confidence & peace of mind

Don't let the unexpected ruin your dream trip. Our comprehensive travel insurance covers medical emergencies, trip cancellations, lost baggage, and more — so you can explore worry-free.

Medical Emergency Cover
Up to ₹50 lakhs coverage worldwide
Trip Cancellation & Delay
100% reimbursement on covered reasons
Lost Baggage & Passport
Immediate replacement assistance
24/7 Emergency Helpline
Support in 15+ languages globally
Basic Plan
Essential Cover
Ideal for domestic trips
₹999
/person/trip
Most Popular
Standard Plan
Comprehensive Cover
Perfect for international travel
₹1,799
/person/trip
Premium Plan
Elite Cover
For luxury & extended trips
₹2,999
/person/trip

* Plans subject to terms & conditions. Click any plan for details.

Real Stories

What our travellers say

★★★★★

"Our Bali honeymoon was absolutely perfect. TravelHuge handled every detail — private pool villa, candlelit beach dinner, spa sessions. Didn't have to think about a thing!"

P
Priya & Rahul Mehta
📍 Mumbai → Bali Honeymoon
★★★★★

"Planned our entire Himachal family trip in 3 days. The kids loved every moment — snow, adventure camps, river rafting. Absolute professionalism at every step!"

S
Suresh Agarwal
📍 Delhi → Himachal Pradesh
★★★★★

"The Maldives overwater villa experience was beyond anything I imagined, and it was actually within our budget! Every transfer was perfectly coordinated."

A
Anita Krishnamurthy
📍 Bangalore → Maldives
★★★★★

"As a corporate travel manager, TravelHuge stands apart — their attention to detail, responsiveness, and competitive pricing for group travel is unmatched."

V
Vikram Patel
📍 Corporate Travel Manager
★★★★★

"Dubai with family in 5 nights — perfectly planned, zero hassles. The desert safari was the highlight! Our specialist was just a WhatsApp message away."

R
Rekha Sharma
📍 Noida → Dubai
★★★★★

"Thailand on ₹60,000/person and it felt luxurious! 8 nights, island hopping, great hotels. TravelHuge negotiated deals I couldn't have gotten myself."

K
Karan Malhotra
📍 Pune → Thailand
TravelHuge Team Travel
2017
Founded
Who We Are

A small team with
a big vision

We in Travel Huge are a small group of professionals who joined hands to provide innovative and tailored solutions to the travel needs of leisure travellers, corporate clients and foreign travellers.

As any new setup, we are a small organisation; however we have a clear vision to establish ourselves as leaders in this segment. We believe we are uniquely qualified to provide the highest degree of personalized services for effective travel management.

12K+
Happy Travellers
200+
Destinations
7+
Years Active
Our Mission
"To be the most customer-centric company, where customers can find and discover complete solutions to their travel needs with the lowest possible cost and the highest quality of Customer Service."
Core Values
Customer focus and satisfaction
Total transparency and openness
Trust and empathy
Learning organisation
Innovative culture
Our Journey
2017

The Beginning

A small number of passionate travel specialists based in India and the United States, along with IT professionals, joined hands to start Travel Huge — initially as a proprietorship.

2019

Corporate Entity

Travel Huge transformed into a corporate entity as 'E-Product Zone Pvt. Ltd.' with Travel Huge as its trade name, solidifying our commitment to professional excellence.

Today

Growing Strong

12,000+ happy travellers, 200+ destinations served, and a dedicated team committed to making every journey extraordinary — with ambitious plans to lead this segment.

The Vision Behind TravelHuge

Meet our Founder

ML Sharma
ML Sharma
Founder — E-Product Zone Pvt. Ltd.

ML Sharma was the visionary heart of TravelHuge. With deep roots in the travel industry and a passion for personalised service, he founded Travel Huge in 2017 with a singular belief: every traveller deserves a journey crafted specifically for them. His spirit and values continue to guide us every day.

Under his visionary leadership, Travel Huge grew from a passionate startup into a trusted corporate entity — E-Product Zone Pvt. Ltd. — serving thousands of leisure travellers, corporate clients and international visitors.

"We would extend our highest respect and honesty to all our customers and remain accountable for any inconvenience from our side. To provide our customers the products at the most reasonable price — value for money, always."

— ML Sharma, Founder (In Loving Memory)

Live Global Flight Tracker

Track any flight
anywhere in the world

Real-time status, animated route maps, gate info and live altitude data for 1,000+ worldwide airlines.

Popular Worldwide Airlines
Live Departures — click any flight to track
12,847
In The Air
1,200+
Airlines
9,500+
Airports
99.8%
Accuracy
Get In Touch

We're here to help you travel

Our travel experts respond within 4 hours on working days. Reach us through any channel.

Phone
Mon–Sat, 9 AM – 7 PM IST
WhatsApp
Fastest response channel
Email
Reply within 4 working hours
Office
Faridabad, Haryana, India
NCR — Delhi region
📞 Call Now 💬 WhatsApp
Send us a message

We'll get back to you within 4 hours on working days.

256-bit SSL · Email Verified · Anti-spam Protected
We may call you to discuss your requirements
? + ? = ?Anti-spam ✓
Travel Insurance
Comprehensive protection for your journey
Choose Your Plan
Basic
₹999
₹2L Medical
Medical Emergency
Trip Cancellation
Lost Baggage
Standard
₹1,799
₹5L Medical
Medical + Evacuation
Trip Cancellation
Lost Baggage
Flight Delay
Premium 👑
₹2,999
₹10L Medical
Full Medical + Evac
Trip Cancellation
Lost Baggage + Delay
Adventure Sports
24/7 Concierge
Trip Details
Your Details
Selected Plan: Basic — ₹999
Our team will contact you within 4 hours to complete your insurance purchase and send policy documents.
🛡️✅
Insurance Request Sent!

Our insurance specialist will contact you within 4 hours to complete your policy. A confirmation has been sent to your email.

📧 Admin notified: [email protected]
📱 Customer acknowledgement sent to your email
TravelBot by TravelHuge● Online · Replies instantly
👋 Hi! I'm your AI travel assistant. Tell me your dream destination and I'll help design your perfect trip!
I can suggest packages, check availability, and help you book — all in one chat. 🌏
💍 Honeymoon 👨‍👩‍👧 Family 💰 Budget 🌺 Bali vs Maldives
Sign in with Google

Secure Google authentication

A Google sign-in prompt should appear automatically.
If it doesn't, click the button above.

U
My Account

Dashboard

U
Traveller
Verified Member
Welcome back

Hello, Traveller 👋

Member since today

0
Enquiries
0
Wishlisted
Verified
4hr
Quote Time

Account Details

Full Name
Email Address
Phone
Not set
City
Not set

Quick Actions

My Bookings

✈️

No Bookings Yet

Your confirmed travel bookings will appear here. Fill an enquiry and our team will create your itinerary within 4 hours.

Notifications

🔔

You're all caught up! No new notifications.

Support

WhatsApp
+91-9818644324
Call Us
0129-4324324

Send a Message

Coupons & Offers

EXCLUSIVE DEALS

🎟️ Have a Coupon Code?

✨ Active Offers for You

🎁

No active offers at the moment. Check back soon!

📋 My Redeemed Codes

No codes redeemed yet.

Account Settings

U
Traveller
TravelHuge Member
Email Verified

Security

Change your password. You'll be asked to sign in again.

A
Admin Panel

TravelHuge CMS

Live
⚙️
Admin
Super Administrator
Analytics
Operations
CMS
System

Dashboard Overview

Loading...

0
Total Enquiries
↑ Today
0
Registered Users
Active
0
Live Packages
On homepage
0
Open Tickets
Pending
0
Suppliers
Active
0
Tracked Visits
Session logs

📈 Enquiries (Last 7 Days)

🥇 Top Destinations

📬 Latest Enquiries

Registered Users

Visitor Tracking

IP, location, device, and session data for all visitors

Customer Enquiries

Support Tickets

Bookings

0
Total Bookings
0
Confirmed
0
Pending

Tour Packages CMS

Suppliers

Coupons & Offers

Create coupon codes and exclusive offers for users. Active coupons appear instantly in user dashboards.

➕ Create New Coupon

📋 Active Coupons (0)

Send personalised offers to frequent travellers and registered users.

🎯 Target Audience

✍️ Email Content

💡 Use {Name} to personalise. Emails sent via your EmailJS setup from [email protected].

📋 Sent Offers Log

Send Email

Compose and send emails directly to users or custom recipients.

📨 Compose

Quick Select

📜 Sent Log

Notifications

Security & Config

🔑 Admin Credentials

Login: [email protected]

➕ Create Admin Account

Add another admin who can log in from the website.

📧 Email Config

EmailJS + Web3Forms for sending from [email protected]

EmailJS Service: service_69asn3t
EmailJS Template: template_v47gnuy
Web3Forms Key: Configured
✓ Email system ready
🗄 Database

localStorage-based DB (client-side). Connect a backend for production.

Our Complete Story

Who We Are

Travel Huge — Innovators in Personalised Travel

We in 'Travel Huge' are a small group of professionals who joined hands to provide innovative and tailored solutions to the travel needs of leisure travellers, corporate clients and foreign travellers. As any new setup, we are a small organisation; however we have a clear vision to establish ourselves as leaders in this segment.

We believe that we are uniquely qualified to provide the highest degree of personalized services for effective Travel management of your company's travel requirements.

When & How We Started

A small number of passionate travel specialists based in India and United States along with IT professionals joined hands to start a travel venture in early 2017 in the name of 'Travel Huge'. Initially a proprietorship, has now taken the shape of a corporate entity as 'E-Product Zone Pvt. Ltd.' with Travel Huge as its Trade name.

Our Mission

"To be the most customer-centric company, where customers can find and discover complete solution to their travel needs with the lowest possible cost and the highest quality of Customer Service."

Core Values

Travel Huge has a strong value system and beliefs which guides us to manage the business with high level of transparency and integrity:

Customer focus and satisfaction
Total transparency and openness
Trust and empathy
Learning organisation
Innovative culture

We would extend our highest respect and honesty to all our customers and remain accountable for any of their inconvenience arising from our side.

To provide our customers the products at most reasonable price to give value for money. Maintaining the workplace environment a model for learning, growth and total employee satisfaction.

'; } /* ── Admin alert HTML email ── */ function _buildPaymentAdminHTML(opts, ref, rzpId, amtFmt, date){ return '' +'
' +'' +'' +'' +'' +'
' +'
✈️ TravelHuge — Admin
' +'
' +'
' +'
💰 NEW PAYMENT RECEIVED
' +'
'+amtFmt+'
' +'
' +'' +[['Customer Name',opts.name],['Email',opts.email],['Phone',opts.phone], ['Tour',opts.tourName],['Travellers',opts.pax],['Amount',amtFmt], ['Razorpay ID',rzpId],['Booking Ref',ref],['Date & Time',date]] .map(function(r){ return '' +''; }).join('') +'
'+r[0]+''+r[1]+'
' +'
' +'
⚡ ACTION REQUIRED
' +'
Contact '+(opts.name||'customer')+' at '+(opts.phone||opts.email)+' within 2 hours to confirm trip details.
' +'
' +'
TravelHuge Admin System · support@travelhuge.com
'; } function showBookingConfirmation(opts, ref){ var old = document.getElementById('booking-confirm-overlay'); if(old) old.remove(); var overlay = document.createElement('div'); overlay.id = 'booking-confirm-overlay'; overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.88);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(10px);padding:20px;'; overlay.innerHTML = '
' +'
' +'

Booking Confirmed! 🎉

' +'
'+ref+'
' +'

'+opts.name+', your booking for '+opts.tourName+' is confirmed!

' +'

👥 '+opts.pax+' traveller(s)  ·  💰 ₹'+Number(opts.total).toLocaleString('en-IN')+' paid

' +(opts.email ? '

📧 Confirmation sent to '+opts.email+'

' : '
') +'
' +'
What happens next
' +'
' +'📞 Our specialist calls you within 2 hours
' +'📧 Full itinerary emailed to you
' +'💬 WhatsApp updates on your trip
' +'✈️ We handle everything from here!' +'
' +'
' +'
' +'' +'👤 My Bookings' +'
' +'
'; // Close on backdrop click overlay.addEventListener('click', function(e){ if(e.target===overlay) overlay.remove(); }); document.body.appendChild(overlay); } // Close tour modal on overlay click document.addEventListener('DOMContentLoaded', function(){ var modal = document.getElementById('tour-booking-modal'); if(modal) modal.addEventListener('click', function(e){ if(e.target===modal) closeTourModal(); }); renderTourCards(); }); // ══ Step 5 — live field feedback (green ✓ / red ✗) ══ function hfLiveCheck(inp, fbId, validFn, okMsg){ if(!inp) return; var val = inp.value||''; var fb = document.getElementById(fbId); if(!val.trim()){ inp.classList.remove('hf-valid','hf-invalid'); if(fb){ fb.textContent=''; fb.className='hf-fb'; } hfUpdateCompletion(); return; } if(validFn(val)){ inp.classList.add('hf-valid'); inp.classList.remove('hf-invalid'); if(fb){ fb.textContent='✓ '+(okMsg||'Looks good!'); fb.className='hf-fb ok'; } } else { inp.classList.add('hf-invalid'); inp.classList.remove('hf-valid'); var hint = fbId.includes('email') ? 'Enter a valid email address' : fbId.includes('phone') ? 'Enter at least 7 digits' : fbId.includes('msg') ? 'Please write at least 15 characters' : 'Minimum 2 characters'; if(fb){ fb.textContent='⚠ '+hint; fb.className='hf-fb err'; } } hfUpdateCompletion(); } // Keep old hfValidate alias so other forms still work function hfValidate(inputId, okId, errId, validFn, okMsg, errMsg){ var inp = document.getElementById(inputId); hfLiveCheck(inp, okId, validFn, okMsg); } function hfValidatePhone(){ var inp = document.getElementById('hf-phone'); if(inp) hfLiveCheck(inp,'hf-phone-fb',function(v){return v.replace(/\D/g,'').length>=7;}); } function hfUpdateCompletion(){ var fields = [ {id:'hf-fname', fn:function(v){ return v.trim().length>=2; }}, {id:'hf-lname', fn:function(v){ return v.trim().length>=2; }}, {id:'hf-phone', fn:function(v){ return v.replace(/\D/g,'').length>=7; }}, {id:'hf-email', fn:function(v){ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); }}, {id:'hf-msg', fn:function(v){ return v.trim().length>=15; }} ]; var filled = fields.filter(function(f){ var el = document.getElementById(f.id); return el && f.fn(el.value||''); }).length; var pct = Math.round((filled/fields.length)*100); var fill = document.getElementById('hf-completion-fill'); var pctEl = document.getElementById('hf-completion-pct'); if(fill) fill.style.width = pct+'%'; if(pctEl) pctEl.textContent = pct+'%'; } function shakeField(el){ if(!el) return; el.style.transition='transform 0.07s'; var seq=[0,6,-6,6,-5,4,-3,2,-1,0]; var i=0; function next(){ el.style.transform='translateX('+seq[i]+'px)'; i++; if(i hfCurStep){ var err = validateStep(hfCurStep); if(err){ toast('⚠️ Required',''+err); return; } } document.getElementById('hfstep'+hfCurStep).classList.remove('active'); const dots=['hfd1','hfd2','hfd3','hfd4','hfd5']; document.getElementById(dots[hfCurStep-1]).classList.remove('active'); if(n > hfCurStep) document.getElementById(dots[hfCurStep-1]).classList.add('done'); else document.getElementById(dots[hfCurStep-1]).classList.remove('done'); hfCurStep=n; document.getElementById('hfstep'+n).classList.add('active'); document.getElementById(dots[n-1]).classList.remove('done'); document.getElementById(dots[n-1]).classList.add('active'); var progPct=[10,30,52,72,90]; document.getElementById('hf-prog').style.width=progPct[n-1]+'%'; // Init captcha when reaching step 5 if(n===5) genCap('hf'); // Scroll form to top when changing steps var fc=document.getElementById('hero-form'); if(fc) fc.scrollTo({top:0,behavior:'smooth'}); } function getCheckedChips(containerSelector){ var vals=[]; document.querySelectorAll(containerSelector+' input[type="checkbox"]:checked').forEach(function(cb){ vals.push(cb.value); }); return vals.join(', ')||'None'; } function submitHeroForm(){ if(document.getElementById('hf-hp').value){return;} // honeypot // Validate step 5 required fields var fname=(document.getElementById('hf-fname')||{}).value||''; var lname=(document.getElementById('hf-lname')||{}).value||''; var phone=(document.getElementById('hf-phone')||{}).value||''; var email=(document.getElementById('hf-email')||{}).value||''; var callTime=(document.getElementById('hf-call-time')||{}).value||''; var msg=(document.getElementById('hf-msg')||{}).value||''; var nameRes = isValidName(fname); if(!fname.trim()){ shakeField(document.getElementById('hf-fname')); toast('⚠️ Required','Please enter your first name.');return; } if(!nameRes.ok){ shakeField(document.getElementById('hf-fname')); toast('⚠️ Name Error',nameRes.msg);return; } if(!lname.trim()||lname.trim().length<2){ shakeField(document.getElementById('hf-lname')); toast('⚠️ Required','Please enter your last name (min 2 chars).');return; } var phoneRes = isValidPhone(phone); if(!phoneRes.ok){ shakeField(document.getElementById('hf-phone')); toast('⚠️ Phone Error',phoneRes.msg);return; } if(!email.trim()){ shakeField(document.getElementById('hf-email')); toast('⚠️ Required','Please enter your email address.');return; } var emailRes = isValidEmail(email); if(!emailRes.ok){ shakeField(document.getElementById('hf-email')); toast('⚠️ Email Error',emailRes.msg);return; } if(!callTime){ shakeField(document.getElementById('hf-call-time')); toast('⚠️ Required','Please select your best time to call.');return; } if(msg.trim().length < 15){ shakeField(document.getElementById('hf-msg')); toast('⚠️ Required','Please describe your trip (at least 15 characters).');return; } var consent=document.getElementById('hf-consent'); if(consent&&!consent.checked){ shakeField(consent.closest('div')); toast('⚠️ Consent Required','Please accept the terms to continue.');return; } // Captcha check if(!checkCap('hf')) return; var ref='TH-'+Date.now().toString().slice(-8); document.getElementById('hf-success-ref').textContent='REF: '+ref; var data={ name: fname+' '+lname, phone: phone, email: email, destination: (document.getElementById('hf-dest')||{}).value||'', tripType: (document.getElementById('hf-trip-type')||{}).value||'', purpose: getCheckedChips('#hf-purpose-chips'), departure: (document.getElementById('hf-dep-date')||{}).value||'', returnDate: (document.getElementById('hf-ret-date')||{}).value||'', duration: (document.getElementById('hf-duration')||{}).value||'', flexibility: (document.getElementById('hf-flex')||{}).value||'', budget: (document.getElementById('budget-disp')||{}).textContent||'', budgetType: (document.getElementById('hf-budget-type')||{}).value||'', adults: (document.getElementById('hf-adults')||{}).value||'2', children: (document.getElementById('hf-children')||{}).value||'None', infants: (document.getElementById('hf-infants')||{}).value||'None', seniors: (document.getElementById('hf-seniors')||{}).value||'None', profile: (document.getElementById('hf-profile')||{}).value||'', ageGroup: (document.getElementById('hf-age')||{}).value||'', fromCity: (document.getElementById('hf-from-city')||{}).value||'', hotel: (document.getElementById('hf-hotel')||{}).value||'', meal: (document.getElementById('hf-meal')||{}).value||'', flightClass: (document.getElementById('hf-flight-pref')||{}).value||'', transport: getCheckedChips('#hfstep4 .field:nth-child(4)'), activities: getCheckedChips('#hfstep4 .field:nth-child(5)'), diet: (document.getElementById('hf-diet')||{}).value||'', medical: (document.getElementById('hf-medical')||{}).value||'', insurance: (document.getElementById('hf-insurance')||{}).value||'', visa: (document.getElementById('hf-visa')||{}).value||'', callTime: (document.getElementById('hf-call-time')||{}).value||'', source: (document.getElementById('hf-source')||{}).value||'', message: (document.getElementById('hf-msg')||{}).value||'' }; notifyTeam('Trip Planner Form', data); // Send WhatsApp to user var hfRef = 'TH-'+Date.now().toString().slice(-8); _sendUserWhatsApp(data.phone, data.name.split(' ')[0]||'there', data.destination||'your destination', hfRef); document.getElementById('hfstep5').classList.remove('active'); document.getElementById('hfstep-success').classList.add('active'); toast('✅ Request Sent!','Confirmation sent to email & WhatsApp! 🌍'); } function resetHeroForm(){ document.getElementById('hfstep-success').classList.remove('active'); hfCurStep=1; document.querySelectorAll('.fstep-panel').forEach(function(p){p.classList.remove('active');}); document.getElementById('hfstep1').classList.add('active'); ['hfd1','hfd2','hfd3','hfd4','hfd5'].forEach(function(id,i){var d=document.getElementById(id);d.classList.toggle('active',i===0);d.classList.remove('done');}); document.getElementById('hf-prog').style.width='10%'; } /* ══ DESTINATION AUTOCOMPLETE ══ */ (function(){ // ══ TOP 100 MOST VISITED DESTINATIONS (searchable pool) ══ var DESTINATIONS = [ // ══════════════════════════════════════════ // 🇮🇳 INDIA — METROS & MAJOR CITIES // ══════════════════════════════════════════ {flag:'🏙️', name:'Delhi', sub:'India · Capital · Red Fort · Qutub Minar · India Gate', tags:['delhi','new delhi','dilli','connaught place','old delhi','chandni chowk'], region:'india', rank:1}, {flag:'🌆', name:'Mumbai', sub:'India · Financial Capital · Marine Drive · Gateway of India', tags:['mumbai','bombay','marine drive','gateway','bandra','juhu','bollywood'], region:'india', rank:2}, {flag:'🌿', name:'Bengaluru', sub:'India · Garden City · Silicon Valley · Tech Hub', tags:['bengaluru','bangalore','mg road','indiranagar','koramangala','cubbon'], region:'india', rank:3}, {flag:'🏛️', name:'Chennai', sub:'India · Marina Beach · Mahabalipuram · South India', tags:['chennai','madras','marina beach','mahabalipuram','t nagar'], region:'india', rank:4}, {flag:'🕌', name:'Hyderabad', sub:'India · Charminar · Biryani · Ramoji Film City', tags:['hyderabad','charminar','golconda','ramoji','biryani','hitech city'], region:'india', rank:5}, {flag:'🌸', name:'Kolkata', sub:'India · City of Joy · Victoria Memorial · Howrah Bridge', tags:['kolkata','calcutta','victoria memorial','howrah','park street','durga puja'], region:'india', rank:6}, {flag:'🏯', name:'Pune', sub:'India · Shaniwarwada · Lonavala · Cultural Capital', tags:['pune','poona','shaniwarwada','lonavala','koregaon park','aga khan'], region:'india', rank:7}, {flag:'💎', name:'Ahmedabad', sub:'India · Heritage City · Sabarmati Ashram · UNESCO', tags:['ahmedabad','ahd','sabarmati','gandhi ashram','rann utsav','gujarati'], region:'india', rank:8}, {flag:'🏰', name:'Jaipur', sub:'India · Pink City · Amber Fort · Hawa Mahal', tags:['jaipur','pink city','amer','hawa mahal','city palace','nahargarh'], region:'india', rank:9}, {flag:'🕍', name:'Lucknow', sub:'India · City of Nawabs · Chikankari · Awadhi Cuisine', tags:['lucknow','nawab','chikankari','hazratganj','tunday','imambara'], region:'india', rank:10}, // ══════════════════════════════════════════ // 🇮🇳 INDIA — ICONIC DESTINATIONS // ══════════════════════════════════════════ {flag:'🏖️', name:'Goa', sub:'India · Beaches · Calangute · Anjuna · Old Goa', tags:['goa','beach','calangute','baga','anjuna','north goa','south goa','palolem'], region:'india', rank:11}, {flag:'🌿', name:'Kerala', sub:"India · God\'s Own Country · Backwaters · Alleppey", tags:['kerala','backwaters','munnar','alleppey','cochin','kochi','thekkady'], region:'india', rank:12}, {flag:'🏰', name:'Rajasthan', sub:'India · Jaipur · Udaipur · Jodhpur · Jaisalmer', tags:['rajasthan','jaipur','udaipur','jodhpur','jaisalmer','pushkar','bikaner'], region:'india', rank:13}, {flag:'🏔️', name:'Himachal Pradesh', sub:'India · Manali · Shimla · Dharamshala · Spiti', tags:['himachal','manali','shimla','dharamshala','spiti','kasauli','dalhousie'], region:'india', rank:14}, {flag:'❄️', name:'Leh Ladakh', sub:'India · Pangong Lake · Nubra Valley · Khardung La', tags:['leh','ladakh','pangong','nubra','khardung la','tso moriri','zanskar'], region:'india', rank:15}, {flag:'🌸', name:'Kashmir', sub:'India · Dal Lake · Gulmarg · Pahalgam · Srinagar', tags:['kashmir','dal lake','gulmarg','pahalgam','srinagar','sonmarg','yusmarg'], region:'india', rank:16}, {flag:'🌊', name:'Andaman & Nicobar', sub:'India · Havelock · Neil Island · Radhanagar Beach', tags:['andaman','havelock','neil island','port blair','radhanagar'], region:'india', rank:17}, {flag:'🌄', name:'Uttarakhand', sub:'India · Rishikesh · Mussoorie · Nainital · Haridwar', tags:['uttarakhand','rishikesh','mussoorie','nainital','haridwar','auli','chopta'], region:'india', rank:18}, {flag:'🕌', name:'Agra', sub:'India · Taj Mahal · Agra Fort · Fatehpur Sikri', tags:['agra','taj mahal','mughal','fatehpur','agra fort'], region:'india', rank:19}, {flag:'🏰', name:'Udaipur', sub:'India · City of Lakes · Lake Palace · Pichola', tags:['udaipur','lake palace','pichola','rajasthan','fateh sagar','sajjangarh'], region:'india', rank:20}, {flag:'🏛️', name:'Varanasi', sub:'India · Ganges Ghats · Kashi Vishwanath · Spiritual', tags:['varanasi','benaras','kashi','ganga','ghats','sarnath'], region:'india', rank:21}, {flag:'🌺', name:'Coorg', sub:'India · Coffee Plantations · Abbey Falls · Karnataka', tags:['coorg','kodagu','karnataka','coffee','abbey falls','rajaseat'], region:'india', rank:22}, {flag:'🍃', name:'Munnar', sub:'India · Tea Gardens · Eravikulam · Kerala Hills', tags:['munnar','kerala','tea','hill station','eravikulam','top station'], region:'india', rank:23}, {flag:'🌸', name:'Ooty & Kodaikanal', sub:'India · Nilgiri Hills · Blue Mountains · Tamil Nadu', tags:['ooty','kodaikanal','nilgiri','tamil nadu','botanical garden','boat house'], region:'india', rank:24}, {flag:'🏔️', name:'Spiti Valley', sub:'India · Cold Desert · Key Monastery · Kaza', tags:['spiti','kaza','key monastery','cold desert','tabo','dhankar'], region:'india', rank:25}, {flag:'🏜️', name:'Rann of Kutch', sub:'India · White Desert · Gujarat · Rann Utsav', tags:['rann','kutch','gujarat','white desert','bhuj','kalo dungar'], region:'india', rank:26}, {flag:'🌊', name:'Lakshadweep', sub:'India · Coral Islands · Agatti · Bangaram · Kavaratti', tags:['lakshadweep','agatti','bangaram','coral','kavaratti'], region:'india', rank:27}, {flag:'⛵', name:'Pondicherry', sub:'India · French Quarter · Auroville · Promenade Beach', tags:['pondicherry','puducherry','auroville','french','rock beach'], region:'india', rank:28}, {flag:'🗿', name:'Hampi', sub:'India · UNESCO · Vijayanagara Empire · Ruins', tags:['hampi','karnataka','unesco','ruins','vitthala','virupaksha'], region:'india', rank:29}, {flag:'🌿', name:'Meghalaya', sub:'India · Living Root Bridges · Cherrapunji · Dawki', tags:['meghalaya','shillong','cherrapunji','root bridges','dawki','mawlynnong'], region:'india', rank:30}, // ══════════════════════════════════════════ // 🇮🇳 INDIA — MORE STATES & HILL STATIONS // ══════════════════════════════════════════ {flag:'🏔️', name:'Sikkim & Darjeeling', sub:'India · Tea · Kanchenjunga · Gangtok · Tiger Hill', tags:['sikkim','darjeeling','gangtok','kanchenjunga','tiger hill','pelling'], region:'india', rank:31}, {flag:'🙏', name:'Rishikesh & Haridwar', sub:'India · Yoga Capital · Ganga Aarti · River Rafting', tags:['rishikesh','haridwar','yoga','rafting','triveni ghat','har ki pauri'], region:'india', rank:32}, {flag:'🦒', name:'Madhya Pradesh', sub:'India · Khajuraho · Kanha · Bandhavgarh · Pachmarhi', tags:['madhya pradesh','khajuraho','kanha','bandhavgarh','pachmarhi','orchha'], region:'india', rank:33}, {flag:'🐯', name:'Ranthambore', sub:'India · Royal Bengal Tiger · Rajasthan Safari', tags:['ranthambore','tiger','safari','rajasthan'], region:'india', rank:34}, {flag:'🦁', name:'Jim Corbett', sub:'India · Tiger Reserve · Dhikala · Uttarakhand', tags:['corbett','wildlife','tiger','dhikala','ramnagar'], region:'india', rank:35}, {flag:'🌊', name:'Gokarna', sub:'India · Om Beach · Half Moon Beach · Karnataka', tags:['gokarna','karnataka','om beach','half moon beach','kudle'], region:'india', rank:36}, {flag:'⛰️', name:'Arunachal Pradesh', sub:'India · Tawang · Ziro Valley · Namdapha', tags:['arunachal','tawang','ziro','northeast','namdapha','sela pass'], region:'india', rank:37}, {flag:'🏄', name:'Varkala', sub:'India · Cliff Beach · Ayurveda · Lighthouse · Kerala', tags:['varkala','kerala','cliff beach','lighthouse'], region:'india', rank:38}, {flag:'🌾', name:'Assam & Majuli', sub:'India · Kaziranga · Tea Gardens · Brahmaputra · One-Horned Rhino', tags:['assam','kaziranga','majuli','guwahati','brahmaputra','northeast','rhino'], region:'india', rank:39}, {flag:'🏖️', name:'Vizag & Araku', sub:'India · Beach City · Coffee Valley · Borra Caves', tags:['vizag','visakhapatnam','araku','andhra','borra caves','rishikonda'], region:'india', rank:40}, {flag:'🌅', name:'Puri & Bhubaneswar', sub:'India · Jagannath Temple · Konark Sun Temple · Chilika', tags:['puri','bhubaneswar','odisha','jagannath','konark','chilika'], region:'india', rank:41}, {flag:'🌿', name:'Wayanad', sub:'India · Chembra Peak · Edakkal Caves · Wildlife', tags:['wayanad','kerala','wildlife','waterfalls','chembra','edakkal'], region:'india', rank:42}, {flag:'🏔️', name:'Manali', sub:'India · Rohtang Pass · Solang Valley · Snow · Beas', tags:['manali','rohtang','solang','snow','beas river','old manali'], region:'india', rank:43}, {flag:'🏔️', name:'Shimla', sub:'India · Mall Road · Jakhu Temple · Kufri · Toy Train', tags:['shimla','mall road','jakhu','kufri','toy train','narkanda'], region:'india', rank:44}, {flag:'🏔️', name:'Dharamshala & McLeod Ganj', sub:'India · Dalai Lama · Triund · Tibetan Culture', tags:['dharamshala','mcleod ganj','dalai lama','triund','tibetan'], region:'india', rank:45}, {flag:'🕌', name:'Jodhpur', sub:'India · Blue City · Mehrangarh Fort · Umaid Bhawan', tags:['jodhpur','blue city','mehrangarh','umaid bhawan','bishnoi'], region:'india', rank:46}, {flag:'🏜️', name:'Jaisalmer', sub:'India · Golden City · Sam Sand Dunes · Havelis', tags:['jaisalmer','golden city','sam dunes','patwon ki haveli','kuldhara'], region:'india', rank:47}, {flag:'🙏', name:'Pushkar', sub:'India · Brahma Temple · Camel Fair · Holy Lake', tags:['pushkar','brahma temple','camel fair','ajmer','pushkar lake'], region:'india', rank:48}, {flag:'🌿', name:'Alleppey & Backwaters', sub:'India · Houseboat · Venice of the East · Kerala', tags:['alleppey','alappuzha','houseboat','backwaters','vembanad','kumarakom'], region:'india', rank:49}, {flag:'🏛️', name:'Mahabaleshwar', sub:'India · Strawberries · Venna Lake · Maharashtra Hill Station', tags:['mahabaleshwar','panchgani','strawberry','venna lake','pratapgarh'], region:'india', rank:50}, {flag:'🌿', name:'Lonavala & Khandala', sub:'India · Bhushi Dam · Tiger Point · Maharashtra', tags:['lonavala','khandala','bhushi dam','tiger point','rajmachi'], region:'india', rank:51}, {flag:'🏔️', name:'Mussoorie', sub:'India · Queen of Hills · Kempty Falls · Lal Tibba', tags:['mussoorie','queen of hills','kempty falls','lal tibba','landour'], region:'india', rank:52}, {flag:'🏔️', name:'Nainital', sub:'India · Naini Lake · Jim Corbett Nearby · Kumaon Hills', tags:['nainital','naini lake','binsar','bhimtal','sattal','ranikhet'], region:'india', rank:53}, {flag:'🌊', name:'Rameswaram & Madurai', sub:'India · Pamban Bridge · Meenakshi Temple · Tamil Nadu', tags:['rameswaram','madurai','pamban','meenakshi','kanyakumari','tamil nadu'], region:'india', rank:54}, {flag:'🏛️', name:'Mysore', sub:'India · Palace City · Chamundi Hills · Dasara Festival', tags:['mysore','mysuru','mysore palace','chamundi','dasara','brindavan gardens'], region:'india', rank:55}, {flag:'🌊', name:'Kovalam & Trivandrum', sub:'India · Lighthouse Beach · Padmanabhaswamy Temple', tags:['kovalam','trivandrum','thiruvananthapuram','lighthouse beach','varkala'], region:'india', rank:56}, {flag:'🏯', name:'Bhopal', sub:'India · City of Lakes · Bhimbetka Caves · Sanchi Stupa', tags:['bhopal','bhimbetka','sanchi','madhya pradesh','upper lake'], region:'india', rank:57}, {flag:'🌿', name:'Gangtok', sub:'India · Sikkim Capital · Rumtek Monastery · Nathula Pass', tags:['gangtok','sikkim','rumtek','nathula pass','tsomgo lake'], region:'india', rank:58}, {flag:'🌊', name:'Konkan Coast', sub:'India · Tarkarli · Sindhudurg · Malvan · Scuba Diving', tags:['konkan','tarkarli','sindhudurg','malvan','alibaug','kashid'], region:'india', rank:59}, {flag:'🏛️', name:'Amritsar', sub:'India · Golden Temple · Jallianwala Bagh · Wagah Border', tags:['amritsar','golden temple','wagah border','jallianwala','harmandir sahib'], region:'india', rank:60}, // ══════════════════════════════════════════ // 🌴 SOUTHEAST ASIA // ══════════════════════════════════════════ {flag:'🇮🇩', name:'Bali', sub:'Indonesia · Ubud · Seminyak · Uluwatu · Kuta', tags:['bali','indonesia','ubud','seminyak','uluwatu','kuta','canggu','nusa penida'], region:'sea', rank:61}, {flag:'🇹🇭', name:'Bangkok', sub:'Thailand · Grand Palace · Chatuchak · Street Food', tags:['bangkok','thailand','grand palace','chatuchak','khao san','floating market'], region:'sea', rank:62}, {flag:'🇹🇭', name:'Phuket', sub:'Thailand · Phi Phi Islands · Patong · Kata Beach', tags:['phuket','phi phi','patong','kata','bangla road','maya bay'], region:'sea', rank:63}, {flag:'🇹🇭', name:'Chiang Mai', sub:'Thailand · Night Bazaar · Doi Inthanon · Temples', tags:['chiang mai','night bazaar','doi inthanon','elephant sanctuary','nimman'], region:'sea', rank:64}, {flag:'🇸🇬', name:'Singapore', sub:'Marina Bay · Sentosa · Gardens by the Bay · F1', tags:['singapore','sentosa','marina bay','gardens','merlion','orchard','universal'], region:'sea', rank:65}, {flag:'🇲🇾', name:'Kuala Lumpur', sub:'Malaysia · Petronas Towers · Batu Caves · Bukit Bintang', tags:['kuala lumpur','kl','malaysia','petronas','batu caves','bukit bintang'], region:'sea', rank:66}, {flag:'🇲🇾', name:'Langkawi', sub:'Malaysia · Duty-Free Island · Cable Car · Cenang Beach', tags:['langkawi','malaysia','cable car','cenang','duty free','mangrove'], region:'sea', rank:67}, {flag:'🇻🇳', name:'Hanoi', sub:'Vietnam · Old Quarter · Hoan Kiem Lake · Street Food', tags:['hanoi','vietnam','old quarter','hoan kiem','banh mi','huc bridge'], region:'sea', rank:68}, {flag:'🇻🇳', name:'Ha Long Bay', sub:'Vietnam · UNESCO · Limestone Karsts · Cruise', tags:['halong','ha long bay','vietnam','cruise','limestone','cat ba'], region:'sea', rank:69}, {flag:'🇻🇳', name:'Hoi An', sub:'Vietnam · Ancient Town · Lanterns · Tailor Street', tags:['hoi an','vietnam','ancient town','lantern','my son','phung hung'], region:'sea', rank:70}, {flag:'🇵🇭', name:'Palawan', sub:'Philippines · El Nido · Coron · Puerto Princesa', tags:['palawan','philippines','el nido','coron','puerto princesa','underground river'], region:'sea', rank:71}, {flag:'🇵🇭', name:'Boracay', sub:'Philippines · White Beach · Water Sports · Nightlife', tags:['boracay','philippines','white beach','windsurfing'], region:'sea', rank:72}, {flag:'🇰🇭', name:'Siem Reap & Angkor', sub:'Cambodia · Angkor Wat · Bayon · Tonle Sap Lake', tags:['siem reap','cambodia','angkor wat','bayon','tonle sap'], region:'sea', rank:73}, {flag:'🇮🇩', name:'Lombok & Komodo', sub:'Indonesia · Gili Islands · Mount Rinjani · Dragons', tags:['lombok','gili','rinjani','komodo','flores','pink beach'], region:'sea', rank:74}, // ══════════════════════════════════════════ // 🏝️ ISLANDS & SUBCONTINENT // ══════════════════════════════════════════ {flag:'🏝️', name:'Maldives', sub:'Overwater Bungalows · Crystal Lagoons · Diving · Snorkelling', tags:['maldives','overwater','lagoon','atoll','diving','male','resort'], region:'islands', rank:75}, {flag:'🇱🇰', name:'Sri Lanka', sub:'Sigiriya · Galle · Kandy · Ella Train · Colombo', tags:['sri lanka','sigiriya','galle','kandy','ella','colombo','nine arches bridge'], region:'islands', rank:76}, {flag:'🇳🇵', name:'Nepal', sub:'Everest Base Camp · Pokhara · Kathmandu · Annapurna', tags:['nepal','kathmandu','pokhara','everest','himalaya','annapurna','abc trek'], region:'islands', rank:77}, {flag:'🇧🇹', name:'Bhutan', sub:"Tiger\'s Nest · Thimphu · Punakha · Paro Dzong", tags:['bhutan','tigers nest','thimphu','paro','punakha','haa valley'], region:'islands', rank:78}, {flag:'🌊', name:'Mauritius', sub:'Indian Ocean · Blue Bay Marine Park · Chamarel Waterfall', tags:['mauritius','indian ocean','blue bay','chamarel','le morne','trou aux biches'], region:'islands', rank:79}, {flag:'🏝️', name:'Seychelles', sub:'Praslin · La Digue · Anse Source d\'Argent · Mahé', tags:['seychelles','praslin','la digue','mahe','anse lazio','vallee de mai'], region:'islands', rank:80}, // ══════════════════════════════════════════ // 🗼 EUROPE — Most Popular // ══════════════════════════════════════════ {flag:'🇫🇷', name:'Paris', sub:'France · Eiffel Tower · Louvre · Versailles · Montmartre', tags:['paris','france','eiffel','louvre','versailles','montmartre','champs elysees'], region:'europe', rank:81}, {flag:'🇮🇹', name:'Rome', sub:'Italy · Colosseum · Vatican · Trevi Fountain · Pantheon', tags:['rome','italy','colosseum','vatican','trevi fountain','pantheon'], region:'europe', rank:82}, {flag:'🇮🇹', name:'Venice', sub:'Italy · Grand Canal · Gondolas · St Mark\'s Square', tags:['venice','italy','gondola','grand canal','st marks','rialto'], region:'europe', rank:83}, {flag:'🇮🇹', name:'Amalfi Coast', sub:'Italy · Positano · Ravello · Cinque Terre · Sorrento', tags:['amalfi','positano','italy','cinque terre','sorrento','ravello'], region:'europe', rank:84}, {flag:'🇮🇹', name:'Florence', sub:'Italy · Uffizi Gallery · Duomo · Tuscany · Renaissance', tags:['florence','tuscany','italy','uffizi','duomo','ponte vecchio'], region:'europe', rank:85}, {flag:'🇪🇸', name:'Barcelona', sub:'Spain · Sagrada Familia · Gaudí · La Rambla · Barceloneta', tags:['barcelona','spain','sagrada familia','gaudi','rambla','park guell'], region:'europe', rank:86}, {flag:'🇬🇷', name:'Santorini', sub:'Greece · Caldera Views · Oia · Blue Domes · Fira', tags:['santorini','greece','oia','caldera','fira','blue domes','akrotiri'], region:'europe', rank:87}, {flag:'🇬🇷', name:'Athens & Greek Islands', sub:'Greece · Acropolis · Mykonos · Crete · Rhodes', tags:['athens','greece','acropolis','mykonos','crete','rhodes','corfu'], region:'europe', rank:88}, {flag:'🇨🇭', name:'Switzerland', sub:'Interlaken · Lucerne · Jungfrau · Zermatt · Zurich', tags:['switzerland','interlaken','lucerne','jungfrau','zermatt','zurich','lausanne'], region:'europe', rank:89}, {flag:'🇹🇷', name:'Istanbul & Cappadocia', sub:'Turkey · Blue Mosque · Hot Air Balloon · Bosphorus', tags:['turkey','istanbul','cappadocia','pamukkale','bodrum','blue mosque','hagia sophia'], region:'europe', rank:90}, {flag:'🇬🇧', name:'London', sub:'UK · Big Ben · Buckingham Palace · Thames · West End', tags:['london','uk','big ben','buckingham','thames','tower bridge','westminster'], region:'europe', rank:91}, {flag:'🇵🇹', name:'Lisbon & Porto', sub:'Portugal · Tram 28 · Douro Valley · Algarve', tags:['portugal','lisbon','porto','algarve','sintra','douro','tram'], region:'europe', rank:92}, {flag:'🇳🇱', name:'Amsterdam', sub:'Netherlands · Canals · Tulip Fields · Anne Frank House', tags:['amsterdam','netherlands','canals','tulips','anne frank','vondelpark','keukenhof'], region:'europe', rank:93}, {flag:'🇨🇿', name:'Prague', sub:'Czech Republic · Old Town Square · Charles Bridge · Beer', tags:['prague','czech','old town','charles bridge','astronomical clock'], region:'europe', rank:94}, {flag:'🇦🇹', name:'Vienna & Hallstatt', sub:'Austria · Imperial Palaces · Mozart · Alpine Lakes', tags:['austria','vienna','salzburg','hallstatt','innsbruck','schonbrunn'], region:'europe', rank:95}, {flag:'🇭🇺', name:'Budapest', sub:'Hungary · Thermal Baths · Danube · Parliament Building', tags:['budapest','hungary','thermal baths','danube','fishermans bastion'], region:'europe', rank:96}, {flag:'🇩🇪', name:'Germany', sub:'Berlin · Munich · Bavaria · Black Forest · Neuschwanstein', tags:['germany','berlin','munich','bavaria','neuschwanstein','oktoberfest','cologne'], region:'europe', rank:97}, {flag:'🇳🇴', name:'Norway & Scandinavia', sub:'Fjords · Northern Lights · Stockholm · Copenhagen', tags:['norway','scandinavia','fjords','aurora','stockholm','copenhagen','oslo'], region:'europe', rank:98}, {flag:'🇨🇭', name:'Interlaken', sub:'Switzerland · Paragliding · Jungfraujoch · Adrenaline', tags:['interlaken','switzerland','paragliding','jungfraujoch','grindelwald','thun'], region:'europe', rank:99}, {flag:'🇪🇸', name:'Madrid & Seville', sub:'Spain · Prado Museum · Flamenco · Alhambra · Ibiza', tags:['madrid','seville','spain','prado','flamenco','alhambra','ibiza'], region:'europe', rank:100}, // ══════════════════════════════════════════ // 🌏 EAST ASIA & JAPAN // ══════════════════════════════════════════ {flag:'🇯🇵', name:'Tokyo', sub:'Japan · Shibuya · Mt Fuji · Shinjuku · Akihabara', tags:['tokyo','japan','shibuya','mt fuji','shinjuku','akihabara','harajuku','asakusa'], region:'asia', rank:101}, {flag:'🇯🇵', name:'Kyoto & Osaka', sub:'Japan · Geisha · Fushimi Inari · Nara Deer Park', tags:['kyoto','osaka','japan','geisha','fushimi inari','nara','arashiyama','gion'], region:'asia', rank:102}, {flag:'🇰🇷', name:'Seoul', sub:'South Korea · Gyeongbokgung · K-Pop · Han River · Namsan', tags:['seoul','korea','gyeongbokgung','kpop','han river','namsan','myeongdong'], region:'asia', rank:103}, {flag:'🇨🇳', name:'Beijing', sub:'China · Great Wall · Forbidden City · Temple of Heaven', tags:['beijing','china','great wall','forbidden city','tiananmen','summer palace'], region:'asia', rank:104}, {flag:'🇨🇳', name:'Shanghai', sub:'China · The Bund · Yu Garden · Modern Skyline', tags:['shanghai','china','bund','yu garden','pudong','french concession'], region:'asia', rank:105}, {flag:'🇨🇳', name:'Hong Kong', sub:'China · Victoria Harbour · Dim Sum · Kowloon · Peak Tram', tags:['hong kong','victoria harbour','kowloon','dim sum','peak tram','lantau'], region:'asia', rank:106}, // ══════════════════════════════════════════ // 🌍 MIDDLE EAST // ══════════════════════════════════════════ {flag:'🇦🇪', name:'Dubai', sub:'UAE · Burj Khalifa · Desert Safari · Palm Jumeirah · Gold Souk', tags:['dubai','uae','burj khalifa','desert safari','palm','mall of emirates','gold souk'], region:'mideast', rank:107}, {flag:'🇦🇪', name:'Abu Dhabi', sub:'UAE · Sheikh Zayed Mosque · Yas Island · Louvre UAE', tags:['abu dhabi','uae','sheikh zayed','yas island','ferrari world','louvre'], region:'mideast', rank:108}, {flag:'🇶🇦', name:'Doha & Qatar', sub:'Qatar · Museum of Islamic Art · Souq Waqif · Lusail', tags:['doha','qatar','museum islamic art','souq waqif','pearl','lusail'], region:'mideast', rank:109}, {flag:'🇴🇲', name:'Oman', sub:'Muscat · Wahiba Sands · Wadi Shab · Nizwa Fort', tags:['oman','muscat','wahiba','wadi shab','nizwa','fjords','jebel akhdar'], region:'mideast', rank:110}, {flag:'🇸🇦', name:'Saudi Arabia', sub:'AlUla · Riyadh · Diriyah · NEOM · Hegra', tags:['saudi','alula','riyadh','diriyah','jeddah','hegra','neom'], region:'mideast', rank:111}, {flag:'🇪🇬', name:'Egypt', sub:'Pyramids of Giza · Luxor · Nile Cruise · Hurghada', tags:['egypt','cairo','pyramids','luxor','nile','hurghada','aswan','alexandria'], region:'mideast', rank:112}, {flag:'🇯🇴', name:'Jordan', sub:'Petra · Wadi Rum · Dead Sea · Aqaba · Jerash', tags:['jordan','petra','wadi rum','dead sea','aqaba','jerash','amman'], region:'mideast', rank:113}, // ══════════════════════════════════════════ // 🌍 AFRICA // ══════════════════════════════════════════ {flag:'🇲🇦', name:'Morocco', sub:'Marrakech · Sahara Desert · Fes · Chefchaouen · Casablanca', tags:['morocco','marrakech','sahara','fes','chefchaouen','casablanca','medina'], region:'africa', rank:114}, {flag:'🇰🇪', name:'Kenya Safari', sub:'Masai Mara · Amboseli · Diani Beach · Nairobi', tags:['kenya','masai mara','nairobi','amboseli','safari','diani','tsavo'], region:'africa', rank:115}, {flag:'🇹🇿', name:'Tanzania & Zanzibar', sub:'Serengeti · Kilimanjaro · Zanzibar Island · Ngorongoro', tags:['tanzania','serengeti','zanzibar','kilimanjaro','ngorongoro','stone town'], region:'africa', rank:116}, {flag:'🇿🇦', name:'Cape Town', sub:'South Africa · Table Mountain · Boulders Beach · Winelands', tags:['cape town','south africa','table mountain','penguin','winelands','robben island'], region:'africa', rank:117}, {flag:'🇿🇼', name:'Victoria Falls', sub:'Zimbabwe & Zambia · Wonder of the World · Bungee Jump', tags:['victoria falls','zimbabwe','zambia','hwange','chobe'], region:'africa', rank:118}, // ══════════════════════════════════════════ // 🌎 AMERICAS // ══════════════════════════════════════════ {flag:'🇺🇸', name:'New York City', sub:'USA · Times Square · Statue of Liberty · Central Park', tags:['new york','nyc','times square','manhattan','brooklyn','statue of liberty'], region:'americas', rank:119}, {flag:'🇺🇸', name:'Las Vegas', sub:'USA · The Strip · Grand Canyon · Shows · Casinos', tags:['las vegas','vegas','nevada','grand canyon','strip'], region:'americas', rank:120}, {flag:'🇺🇸', name:'Los Angeles', sub:'USA · Hollywood · Santa Monica · Disneyland · Venice Beach', tags:['los angeles','la','hollywood','disneyland','santa monica','venice beach'], region:'americas', rank:121}, {flag:'🇺🇸', name:'Miami & Florida', sub:'USA · South Beach · Disney World · Keys · Orlando', tags:['florida','miami','orlando','disney world','keys','south beach'], region:'americas', rank:122}, {flag:'🇺🇸', name:'San Francisco', sub:'USA · Golden Gate Bridge · Alcatraz · Fisherman\'s Wharf', tags:['san francisco','golden gate','alcatraz','pier 39','napa valley'], region:'americas', rank:123}, {flag:'🇨🇦', name:'Canada', sub:'Banff · Niagara Falls · Vancouver · Toronto · Montreal', tags:['canada','banff','niagara falls','vancouver','toronto','montreal'], region:'americas', rank:124}, {flag:'🇲🇽', name:'Mexico', sub:'Cancun · Tulum · Mexico City · Chichen Itza · Cabo', tags:['mexico','cancun','tulum','mexico city','chichen itza','cabo','playa del carmen'], region:'americas', rank:125}, {flag:'🇧🇷', name:'Brazil', sub:'Rio de Janeiro · Amazon · Iguazu Falls · Carnival', tags:['brazil','rio','amazon','iguazu','sao paulo','carnival','copacabana'], region:'americas', rank:126}, {flag:'🇵🇪', name:'Peru', sub:'Machu Picchu · Cusco · Lima · Inca Trail · Amazon', tags:['peru','machu picchu','cusco','lima','inca trail','sacred valley'], region:'americas', rank:127}, {flag:'🇦🇷', name:'Argentina', sub:'Buenos Aires · Patagonia · Iguazu · Mendoza Wine', tags:['argentina','buenos aires','patagonia','iguazu','ushuaia','mendoza'], region:'americas', rank:128}, // ══════════════════════════════════════════ // 🇦🇺 PACIFIC & OCEANIA // ══════════════════════════════════════════ {flag:'🇦🇺', name:'Sydney', sub:'Australia · Opera House · Harbour Bridge · Bondi Beach', tags:['sydney','australia','opera house','bondi','harbour bridge','darling harbour'], region:'pacific', rank:129}, {flag:'🇦🇺', name:'Melbourne', sub:'Australia · Coffee Culture · Great Ocean Road · Graffiti Lanes', tags:['melbourne','australia','great ocean road','phillip island','yarra'], region:'pacific', rank:130}, {flag:'🇦🇺', name:'Queensland & Great Barrier Reef', sub:'Australia · Cairns · Whitsundays · Gold Coast', tags:['queensland','great barrier reef','cairns','whitsunday','gold coast','surfers paradise'], region:'pacific', rank:131}, {flag:'🇳🇿', name:'New Zealand', sub:'Queenstown · Milford Sound · Hobbiton · Rotorua · Fiordland', tags:['new zealand','queenstown','milford sound','hobbiton','rotorua','fiordland'], region:'pacific', rank:132}, {flag:'🇫🇯', name:'Fiji', sub:'Yasawa Islands · Coral Coast · Mamanuca · Blue Lagoon', tags:['fiji','yasawa','coral coast','nadi','mamanuca','blue lagoon'], region:'pacific', rank:133}, // ══════════════════════════════════════════ // 🇵🇱 EUROPE — POLAND & EASTERN EUROPE // ══════════════════════════════════════════ {flag:'🇵🇱', name:'Poland', sub:'Warsaw · Kraków · Gdańsk · Auschwitz · Tatra Mountains', tags:['poland','warsaw','krakow','gdansk','wroclaw','poznan','auschwitz','zakopane','tatra'], region:'europe', rank:145}, {flag:'🇵🇱', name:'Kraków', sub:'Poland · Old Town · Wawel Castle · Wieliczka Salt Mine', tags:['krakow','poland','wawel','wieliczka','old town','kazimierz'], region:'europe', rank:146}, {flag:'🇵🇱', name:'Warsaw', sub:'Poland · Old Town Square · Palace of Culture · Chopin', tags:['warsaw','poland','old town','palace of culture','chopin','wilanow'], region:'europe', rank:147}, {flag:'🇻🇳', name:'Ho Chi Minh City', sub:'Vietnam · Saigon · Ben Thanh Market · War Remnants Museum', tags:['ho chi minh','saigon','vietnam','ben thanh','war museum','cu chi'], region:'sea', rank:148}, {flag:'🇻🇳', name:'Da Nang & Hue', sub:'Vietnam · Golden Bridge · Ba Na Hills · Imperial City', tags:['da nang','vietnam','golden bridge','ba na hills','hue','imperial city','marble mountains'], region:'sea', rank:149}, {flag:'🇻🇳', name:'Phu Quoc', sub:'Vietnam · Pearl Island · Beaches · Cable Car · Vinpearl', tags:['phu quoc','vietnam','island','vinpearl','cable car','sao beach'], region:'sea', rank:150}, {flag:'🇭🇷', name:'Croatia', sub:'Dubrovnik · Split · Plitvice Lakes · Hvar Island · Zadar', tags:['croatia','dubrovnik','split','hvar','plitvice','zadar','trogir'], region:'europe', rank:151}, {flag:'🇸🇰', name:'Slovakia & Slovenia', sub:'Bratislava · Ljubljana · Bled Lake · High Tatras', tags:['slovakia','slovenia','bratislava','ljubljana','bled','lake bled','high tatras'], region:'europe', rank:152}, {flag:'🇷🇴', name:'Romania', sub:'Bucharest · Transylvania · Bran Castle · Painted Monasteries', tags:['romania','bucharest','transylvania','bran castle','dracula','sinaia','brasov'], region:'europe', rank:153}, {flag:'🇧🇬', name:'Bulgaria', sub:'Sofia · Plovdiv · Bansko · Black Sea Coast · Rila Monastery', tags:['bulgaria','sofia','plovdiv','bansko','rila monastery','varna','black sea'], region:'europe', rank:154}, {flag:'🇮🇸', name:'Iceland', sub:'Northern Lights · Blue Lagoon · Golden Circle · Reykjavik', tags:['iceland','reykjavik','northern lights','aurora','blue lagoon','golden circle','geysir'], region:'europe', rank:155}, {flag:'🇫🇮', name:'Finland & Lapland', sub:'Santa Village · Rovaniemi · Northern Lights · Sauna Culture', tags:['finland','lapland','rovaniemi','santa','northern lights','aurora','sauna','helsinki'], region:'europe', rank:156}, {flag:'🇧🇪', name:'Belgium', sub:'Brussels · Bruges · Ghent · Chocolate · Waffles · Grand Place', tags:['belgium','brussels','bruges','ghent','antwerp','grand place','manneken pis'], region:'europe', rank:157}, {flag:'🇮🇪', name:'Ireland', sub:'Dublin · Cliffs of Moher · Ring of Kerry · Giant\'s Causeway', tags:['ireland','dublin','cliffs of moher','ring of kerry','galway','temple bar','giants causeway'], region:'europe', rank:158}, {flag:'🇵🇹', name:'Algarve', sub:'Portugal · Faro · Lagos · Sagres · Praia da Marinha', tags:['algarve','portugal','faro','lagos','sagres','praia da marinha','benagil'], region:'europe', rank:159}, {flag:'🇪🇸', name:'Canary Islands', sub:'Spain · Tenerife · Gran Canaria · Lanzarote · La Palma', tags:['canary islands','tenerife','gran canaria','lanzarote','fuerteventura','spain'], region:'europe', rank:160}, {flag:'🇲🇹', name:'Malta', sub:'Valletta · Mdina · Blue Lagoon · Comino · Ancient Temples', tags:['malta','valletta','mdina','blue lagoon','comino','gozo','azure window'], region:'europe', rank:161}, {flag:'🇺🇿', name:'Uzbekistan', sub:'Samarkand · Tashkent · Bukhara · Silk Road · Registan', tags:['uzbekistan','samarkand','tashkent','bukhara','registan','silk road','khiva'], region:'mideast', rank:162}, {flag:'🇬🇪', name:'Georgia & Armenia', sub:'Tbilisi · Kazbegi · Yerevan · Caucasus Mountains', tags:['georgia','tbilisi','kazbegi','batumi','yerevan','armenia','caucasus'], region:'mideast', rank:163}, {flag:'🇲🇻', name:'Male & Hulhumale', sub:'Maldives Capital · Budget Stays · Local Culture · Coral Reefs', tags:['male','hulhumale','maldives','capital','local island','budget'], region:'islands', rank:164}, {flag:'🇮🇩', name:'Jakarta & Java', sub:'Indonesia · Borobudur · Prambanan · Yogyakarta · Bromo', tags:['jakarta','java','indonesia','borobudur','prambanan','yogyakarta','bromo','ijen'], region:'sea', rank:165}, {flag:'🇲🇾', name:'Penang & Borneo', sub:'Malaysia · Street Art · Rainforest · Orangutan · Kinabalu', tags:['penang','borneo','malaysia','george town','street art','sabah','kinabalu','orangutan'], region:'sea', rank:166}, // ══════════════════════════════════════════ // ✈️ MULTI-DESTINATION COMBOS // ══════════════════════════════════════════ {flag:'✈️', name:'Europe Multi-Country Tour', sub:'5–10 Countries · Schengen · 15–21 Days', tags:['europe tour','euro trip','schengen','multi country','european'], region:'combo', rank:134}, {flag:'🌏', name:'Southeast Asia Tour', sub:'Thailand + Bali + Singapore + Vietnam', tags:['southeast asia','sea','combo','se asia'], region:'combo', rank:135}, {flag:'🌊', name:'Indian Ocean Islands', sub:'Maldives + Mauritius + Seychelles Combo', tags:['indian ocean','islands combo','maldives mauritius'], region:'combo', rank:136}, {flag:'🏔️', name:'Himalayan Circuit', sub:'Nepal + Bhutan + Sikkim + Ladakh', tags:['himalayan','nepal bhutan','mountain circuit','himalaya'], region:'combo', rank:137}, {flag:'🌍', name:'Africa Safari Combo', sub:'Kenya + Tanzania + Zanzibar · 10–14 Days', tags:['africa safari','kenya tanzania','masai mara','serengeti'], region:'combo', rank:138}, {flag:'🌎', name:'South America Explorer', sub:'Peru + Argentina + Brazil + Colombia', tags:['south america','peru argentina','brazil colombia','machu picchu'], region:'combo', rank:139}, {flag:'🇯🇵', name:'Japan Explorer', sub:'Tokyo + Kyoto + Osaka + Hiroshima · Cherry Blossom', tags:['japan tour','japan trip','tokyo kyoto osaka','cherry blossom','sakura'], region:'combo', rank:140}, {flag:'🇦🇪', name:'Middle East Explorer', sub:'Dubai + Abu Dhabi + Doha + Oman', tags:['middle east','dubai doha','uae oman','gulf tour'], region:'combo', rank:141}, {flag:'🇮🇳', name:'Golden Triangle', sub:'India · Delhi + Agra + Jaipur · Classic 6–7 Days', tags:['golden triangle','delhi agra jaipur','india classic','taj mahal tour'], region:'combo', rank:142}, {flag:'🇮🇳', name:'South India Tour', sub:'Chennai + Madurai + Thekkady + Munnar + Alleppey', tags:['south india','temple tour','kerala karnataka','south india circuit'], region:'combo', rank:143}, {flag:'🇮🇳', name:'North East India', sub:'Meghalaya + Assam + Arunachal + Sikkim · 10–14 Days', tags:['northeast india','north east','meghalaya assam','seven sisters'], region:'combo', rank:144}, ]; var selectedDests = []; var focusedIdx = -1; var currentResults = []; var wrap = document.getElementById('dest-ac-wrap'); var input = document.getElementById('hf-dest-input'); var hidden = document.getElementById('hf-dest'); var tagsEl = document.getElementById('dest-ac-tags'); var drop = document.getElementById('dest-ac-dropdown'); if(!input) return; function highlight(text, query){ if(!query) return text; var re = new RegExp('('+query.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+')', 'gi'); return text.replace(re, '$1'); } function renderTags(){ tagsEl.innerHTML = selectedDests.map(function(d,i){ return '' + d.flag + ' ' + d.name + '' + ''; }).join(''); hidden.value = selectedDests.map(function(d){ return d.name; }).join(', '); } window._acRemoveTag = function(i){ selectedDests.splice(i,1); renderTags(); input.focus(); }; function search(q){ q = q.trim().toLowerCase(); if(!q){ // Show top 50 India + top 50 International — full browse mode var indiaTop = DESTINATIONS.filter(function(d){ return d.region==='india'; }).sort(function(a,b){ return a.rank-b.rank; }).slice(0,50); var intlTop = DESTINATIONS.filter(function(d){ return d.region!=='india' && d.region!=='combo'; }).sort(function(a,b){ return a.rank-b.rank; }).slice(0,50); return {trending: indiaTop.concat(intlTop), results: [], indiaCount: indiaTop.length}; } var results = DESTINATIONS.filter(function(d){ return d.name.toLowerCase().includes(q) || d.sub.toLowerCase().includes(q) || d.tags.some(function(t){ return t.includes(q); }); }).filter(function(d){ return !selectedDests.find(function(s){ return s.name===d.name; }); }); return {trending: [], results: results, indiaCount: 0}; } function renderDropdown(q){ var s = search(q); focusedIdx = -1; currentResults = s.results.length ? s.results : s.trending; if(currentResults.length === 0){ drop.innerHTML = '
No destinations found. Try a city name!
'; drop.classList.add('open'); return; } var html = ''; if(s.trending.length){ var regionLabelsB = {india:'📍 Popular India',sea:'🌴 Southeast Asia',islands:'🏝️ Islands & Indian Ocean',europe:'🗼 Europe',asia:'🏯 East Asia & Japan',mideast:'🕌 Middle East',africa:'🌍 Africa',americas:'🌎 Americas & Canada',pacific:'🌏 Pacific & Oceania'}; var grouped = {}; var order = ['india','sea','islands','europe','asia','mideast','africa','americas','pacific']; currentResults.forEach(function(d){ var r=d.region||'other'; if(!grouped[r]) grouped[r]=[]; grouped[r].push(d); }); html += '
✈️ Browse Destinations Scroll to explore · '+DESTINATIONS.length+' total
'; var globalIdx = 0; order.forEach(function(region){ var dests = grouped[region]; if(!dests || !dests.length) return; html += '
'+(regionLabelsB[region]||'🌐 Other')+'
'; dests.forEach(function(d){ html += '
' + ''+d.flag+'' + ''+d.name+''+d.sub+'' + '
'; globalIdx++; }); }); } else { var grouped = {}; var regionLabels = {india:'🇮🇳 India',sea:'🌴 Southeast Asia',islands:'🏝️ Islands & Subcontinent',europe:'🗼 Europe',asia:'🏯 East Asia & Japan',mideast:'🕌 Middle East',africa:'🌍 Africa',americas:'🌎 Americas',pacific:'🌏 Pacific & Oceania',combo:'✈️ Multi-Destination Combos'}; s.results.forEach(function(d){ var r=d.region||'other'; if(!grouped[r]) grouped[r]=[]; grouped[r].push(d); }); html += '
🔍 Search Results '+s.results.length+' found
'; var globalIdx = 0; Object.keys(grouped).forEach(function(region){ html += '
'+(regionLabels[region]||'🌐 Other')+'
'; grouped[region].slice(0,8).forEach(function(d){ html += '
' + ''+d.flag+'' + ''+highlight(d.name,q)+''+highlight(d.sub,q)+'' + '
'; globalIdx++; }); }); } drop.innerHTML = html; drop.classList.add('open'); drop.querySelectorAll('.dest-ac-item').forEach(function(el){ el.addEventListener('mousedown', function(e){ e.preventDefault(); var idx = parseInt(el.getAttribute('data-idx')); selectDest(currentResults[idx]); }); }); } function selectDest(d){ if(!d) return; if(!selectedDests.find(function(s){ return s.name===d.name; })){ selectedDests.push(d); renderTags(); } input.value = ''; closeDrop(); input.focus(); } function closeDrop(){ drop.classList.remove('open'); focusedIdx = -1; } function moveFocus(dir){ var items = drop.querySelectorAll('.dest-ac-item'); if(!items.length) return; items[focusedIdx] && items[focusedIdx].classList.remove('focused'); focusedIdx = Math.max(0, Math.min(items.length-1, focusedIdx+dir)); items[focusedIdx].classList.add('focused'); items[focusedIdx].scrollIntoView({block:'nearest'}); } input.addEventListener('input', function(){ renderDropdown(this.value); }); input.addEventListener('focus', function(){ renderDropdown(this.value); }); input.addEventListener('keydown', function(e){ if(e.key==='ArrowDown'){e.preventDefault();moveFocus(1);} else if(e.key==='ArrowUp'){e.preventDefault();moveFocus(-1);} else if(e.key==='Enter'){ e.preventDefault(); if(focusedIdx>=0 && currentResults[focusedIdx]) selectDest(currentResults[focusedIdx]); else if(this.value.trim()){ selectedDests.push({flag:'📍',name:this.value.trim(),sub:'Custom destination',tags:[]}); renderTags(); this.value=''; closeDrop(); } } else if(e.key==='Backspace'&&!this.value&&selectedDests.length){ selectedDests.pop(); renderTags(); } else if(e.key==='Escape') closeDrop(); }); document.addEventListener('click', function(e){ if(!wrap.contains(e.target)) closeDrop(); }); })(); /* ══ DESTINATION ENQUIRY POPUP ══ */ let deqPhoneVerified=false,deqEmailVerified=false; // ════════════════ WORLD DESTINATIONS DATA & GALLERY ════════════════ var _allDests = [ // ── INDIA ── {name:'Bali',country:'Indonesia',region:'asia',img:'https://images.unsplash.com/photo-1537996194471-e657df975ab4?w=600&q=80',price:'From ₹89,999/couple'}, {name:'Kerala',country:"God\'s Own Country",region:'india',img:'https://images.unsplash.com/photo-1602216056096-3b40cc0c9944?w=600&q=80',price:'From ₹42,999/family'}, {name:'Rajasthan',country:'Royal India',region:'india',img:'https://images.unsplash.com/photo-1477587458883-47145ed94245?w=600&q=80',price:'From ₹34,999/person'}, {name:'Kashmir',country:'Heaven on Earth',region:'india',img:'https://images.unsplash.com/photo-1595815771614-ade9d652a65d?w=600&q=80',price:'From ₹45,999/couple'}, {name:'Leh Ladakh',country:'India',region:'india',img:'https://images.unsplash.com/photo-1626621341517-bbf3d9990a23?w=600&q=80',price:'From ₹52,999/person'}, {name:'Himachal Pradesh',country:'India',region:'india',img:'https://images.unsplash.com/photo-1585146713199-8e5d21ca1a31?w=600&q=80',price:'From ₹34,999/person'}, {name:'Andaman Islands',country:'Island Paradise',region:'india',img:'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=600&q=80',price:'From ₹44,999/couple'}, {name:'Goa',country:'India',region:'india',img:'https://images.unsplash.com/photo-1512343879784-a960bf40e7f2?w=600&q=80',price:'From ₹22,999/person'}, {name:'Uttarakhand',country:'Devbhoomi',region:'india',img:'https://images.unsplash.com/photo-1524492412937-b28074a5d7da?w=600&q=80',price:'From ₹28,999/person'}, {name:'Sikkim & Darjeeling',country:'India',region:'india',img:'https://images.unsplash.com/photo-1566837945700-30057527ade0?w=600&q=80',price:'From ₹35,999/couple'}, {name:'Meghalaya',country:'Abode of Clouds',region:'india',img:'https://images.unsplash.com/photo-1625664028432-f852a5c8e6a3?w=600&q=80',price:'From ₹32,999/person'}, {name:'Agra & Taj Mahal',country:'India',region:'india',img:'https://images.unsplash.com/photo-1564507592333-c60657eea523?w=600&q=80',price:'From ₹14,999/couple'}, {name:'Varanasi',country:'Spiritual India',region:'india',img:'https://images.unsplash.com/photo-1561361058-c24e021e2a69?w=600&q=80',price:'From ₹18,999/person'}, {name:'Coorg',country:'Scotland of India',region:'india',img:'https://images.unsplash.com/photo-1582510003544-4d00b7f74220?w=600&q=80',price:'From ₹24,999/couple'}, {name:'Jaipur',country:'Pink City',region:'india',img:'https://images.unsplash.com/photo-1599661046289-e31897846e41?w=600&q=80',price:'From ₹16,999/person'}, {name:'Rishikesh & Haridwar',country:'Yoga Capital',region:'india',img:'https://images.unsplash.com/photo-1600298882525-a37370c0b680?w=600&q=80',price:'From ₹19,999/person'}, {name:'Munnar',country:'Kerala Hills',region:'india',img:'https://images.unsplash.com/photo-1580341289255-5b47c98a59dd?w=600&q=80',price:'From ₹26,999/couple'}, {name:'Ooty & Kodaikanal',country:'Nilgiri Hills',region:'india',img:'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=600&q=80',price:'From ₹22,999/couple'}, {name:'Spiti Valley',country:'Cold Desert',region:'india',img:'https://images.unsplash.com/photo-1598256989748-c4a850e09e6a?w=600&q=80',price:'From ₹38,999/person'}, {name:'Rann of Kutch',country:'White Desert',region:'india',img:'https://images.unsplash.com/photo-1630755629955-3ba31ef4a44b?w=600&q=80',price:'From ₹29,999/couple'}, {name:'Udaipur',country:'City of Lakes',region:'india',img:'https://images.unsplash.com/photo-1568454537842-d933259bb258?w=600&q=80',price:'From ₹28,999/couple'}, {name:'Pondicherry',country:'French Riviera of East',region:'india',img:'https://images.unsplash.com/photo-1570222094114-d054a817e56b?w=600&q=80',price:'From ₹18,999/couple'}, {name:'Jim Corbett',country:'Tiger Reserve',region:'india',img:'https://images.unsplash.com/photo-1561731216-c3a4d99437d5?w=600&q=80',price:'From ₹24,999/couple'}, {name:'Hampi',country:'UNESCO Heritage',region:'india',img:'https://images.unsplash.com/photo-1605649487212-47bdab064df7?w=600&q=80',price:'From ₹19,999/person'}, // ── ASIA PACIFIC ── {name:'Maldives',country:'Island Nation',region:'asia',img:'https://images.unsplash.com/photo-1514282401047-d79a71a590e8?w=600&q=80',price:'From ₹1,24,999/couple'}, {name:'Thailand',country:'Southeast Asia',region:'asia',img:'https://images.unsplash.com/photo-1528360983277-13d401cdc186?w=600&q=80',price:'From ₹65,999/couple'}, {name:'Singapore',country:'Southeast Asia',region:'asia',img:'https://images.unsplash.com/photo-1525625293386-3f8f99389edd?w=600&q=80',price:'From ₹79,999/couple'}, {name:'Sri Lanka',country:'Pearl of the Indian Ocean',region:'asia',img:'https://images.unsplash.com/photo-1586417936172-8a7e0f8f5e73?w=600&q=80',price:'From ₹55,999/couple'}, {name:'Nepal',country:'Himalayan Kingdom',region:'asia',img:'https://images.unsplash.com/photo-1544735716-392fe2489ffa?w=600&q=80',price:'From ₹35,999/person'}, {name:'Bhutan',country:'Land of Thunder Dragon',region:'asia',img:'https://images.unsplash.com/photo-1553856622-d1b352e9a211?w=600&q=80',price:'From ₹65,999/person'}, {name:'Malaysia',country:'Kuala Lumpur & Langkawi',region:'asia',img:'https://images.unsplash.com/photo-1596422846543-75c6fc197f07?w=600&q=80',price:'From ₹69,999/couple'}, {name:'Vietnam',country:'Ha Long Bay & Hoi An',region:'asia',img:'https://images.unsplash.com/photo-1528127269322-539801943592?w=600&q=80',price:'From ₹72,999/couple'}, {name:'Cambodia',country:'Angkor Wat & Beyond',region:'asia',img:'https://images.unsplash.com/photo-1508009603885-50cf7c579365?w=600&q=80',price:'From ₹60,999/couple'}, {name:'Philippines',country:'Palawan & Islands',region:'asia',img:'https://images.unsplash.com/photo-1518509562904-e7ef99cdcc86?w=600&q=80',price:'From ₹82,999/couple'}, {name:'Japan',country:'Tokyo · Kyoto · Osaka',region:'asia',img:'https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?w=600&q=80',price:'From ₹1,49,999/couple'}, {name:'South Korea',country:'Seoul & Jeju Island',region:'asia',img:'https://images.unsplash.com/photo-1517154421773-0529f29ea451?w=600&q=80',price:'From ₹1,19,999/couple'}, {name:'China',country:'Beijing & Shanghai',region:'asia',img:'https://images.unsplash.com/photo-1508804185872-d7badad00f7d?w=600&q=80',price:'From ₹1,09,999/couple'}, {name:'Hong Kong',country:"Asia's Vibrant City",region:'asia',img:'https://images.unsplash.com/photo-1536599018102-9f803c140fc1?w=600&q=80',price:'From ₹89,999/couple'}, {name:'Uzbekistan',country:'Silk Road Heritage',region:'asia',img:'https://images.unsplash.com/photo-1539650116574-75a40bdb148a?w=600&q=80',price:'From ₹69,999/person'}, {name:'Georgia',country:'Caucasus Mountains',region:'asia',img:'https://images.unsplash.com/photo-1565008576549-57569a49371d?w=600&q=80',price:'From ₹65,999/person'}, {name:'Azerbaijan',country:'Land of Fire',region:'asia',img:'https://images.unsplash.com/photo-1601581875309-fafbf2d3ed3a?w=600&q=80',price:'From ₹69,999/person'}, {name:'Armenia',country:'Ancient Caucasus',region:'asia',img:'https://images.unsplash.com/photo-1569880153113-76e33fc52d5f?w=600&q=80',price:'From ₹62,999/person'}, {name:'Myanmar',country:'Bagan & Inle Lake',region:'asia',img:'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=80',price:'From ₹62,999/couple'}, {name:'Taiwan',country:'Formosa Island',region:'asia',img:'https://images.unsplash.com/photo-1470004914212-05527e49370b?w=600&q=80',price:'From ₹95,999/couple'}, // ── EUROPE ── {name:'Paris',country:'France',region:'europe',img:'https://images.unsplash.com/photo-1499856871958-5b9627545d1a?w=600&q=80',price:'From ₹1,49,999/couple'}, {name:'Switzerland',country:'Alps & Lakes',region:'europe',img:'https://images.unsplash.com/photo-1531366936337-7c912a4589a7?w=600&q=80',price:'From ₹1,99,999/couple'}, {name:'Greece — Santorini',country:'Aegean Islands',region:'europe',img:'https://images.unsplash.com/photo-1533105079780-92b9be482077?w=600&q=80',price:'From ₹1,69,999/couple'}, {name:'Turkey — Cappadocia',country:'Istanbul & Balloons',region:'europe',img:'https://images.unsplash.com/photo-1527838832700-5059252407fa?w=600&q=80',price:'From ₹1,09,999/couple'}, {name:'Italy',country:'Rome · Venice · Amalfi',region:'europe',img:'https://images.unsplash.com/photo-1515542622106-78bda8ba0e5b?w=600&q=80',price:'From ₹1,59,999/couple'}, {name:'Barcelona',country:'Spain',region:'europe',img:'https://images.unsplash.com/photo-1539037116277-4db20889f2d4?w=600&q=80',price:'From ₹1,39,999/couple'}, {name:'Amsterdam',country:'Netherlands',region:'europe',img:'https://images.unsplash.com/photo-1512470876302-972faa2aa9a4?w=600&q=80',price:'From ₹1,49,999/couple'}, {name:'Prague',country:'Czech Republic',region:'europe',img:'https://images.unsplash.com/photo-1541849546-216549ae216d?w=600&q=80',price:'From ₹1,19,999/couple'}, {name:'Vienna & Budapest',country:'Central Europe',region:'europe',img:'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=80',price:'From ₹1,29,999/couple'}, {name:'London',country:'United Kingdom',region:'europe',img:'https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?w=600&q=80',price:'From ₹1,79,999/couple'}, {name:'Scotland',country:'Highlands & Castles',region:'europe',img:'https://images.unsplash.com/photo-1583394293253-2f4d0e3b5beb?w=600&q=80',price:'From ₹1,59,999/couple'}, {name:'Portugal — Lisbon',country:'Iberian Peninsula',region:'europe',img:'https://images.unsplash.com/photo-1548707309-dcebeab9ea9b?w=600&q=80',price:'From ₹1,29,999/couple'}, {name:'Croatia — Dubrovnik',country:'Adriatic Coast',region:'europe',img:'https://images.unsplash.com/photo-1555990538-c0e8cc8bce47?w=600&q=80',price:'From ₹1,39,999/couple'}, {name:'Iceland',country:'Fire & Ice Island',region:'europe',img:'https://images.unsplash.com/photo-1520769669658-f07657f5a307?w=600&q=80',price:'From ₹2,49,999/couple'}, {name:'Norway — Fjords',country:'Scandinavia',region:'europe',img:'https://images.unsplash.com/photo-1513519245088-0e12902e5a38?w=600&q=80',price:'From ₹2,29,999/couple'}, {name:'Germany — Bavaria',country:'Castles & Beer Gardens',region:'europe',img:'https://images.unsplash.com/photo-1467269204594-9661b134dd2b?w=600&q=80',price:'From ₹1,39,999/couple'}, {name:'Finland — Lapland',country:'Aurora & Huskies',region:'europe',img:'https://images.unsplash.com/photo-1516912481808-3406841bd33c?w=600&q=80',price:'From ₹2,19,999/couple'}, {name:'Slovenia — Lake Bled',country:'Alpine Europe',region:'europe',img:'https://images.unsplash.com/photo-1607427293702-036933bbf746?w=600&q=80',price:'From ₹1,09,999/couple'}, {name:'Malta',country:'Mediterranean Island',region:'europe',img:'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?w=600&q=80',price:'From ₹1,19,999/couple'}, {name:'Amalfi Coast',country:'Italy',region:'europe',img:'https://images.unsplash.com/photo-1533587851505-d119e13fa0d7?w=600&q=80',price:'From ₹1,69,999/couple'}, // ── MIDDLE EAST ── {name:'Dubai',country:'UAE',region:'middleeast',img:'https://images.unsplash.com/photo-1512453979798-5ea266f8880c?w=600&q=80',price:'From ₹59,999/couple'}, {name:'Abu Dhabi',country:'UAE',region:'middleeast',img:'https://images.unsplash.com/photo-1548813395-e5c978f54e08?w=600&q=80',price:'From ₹65,999/couple'}, {name:'Qatar — Doha',country:'Arabian Gulf',region:'middleeast',img:'https://images.unsplash.com/photo-1542051841857-5f90071e7989?w=600&q=80',price:'From ₹69,999/couple'}, {name:'Oman — Muscat',country:'Sultanate of Oman',region:'middleeast',img:'https://images.unsplash.com/photo-1567180793673-dbceb9de4d10?w=600&q=80',price:'From ₹74,999/couple'}, {name:'Jordan — Petra',country:'Wadi Rum & Dead Sea',region:'middleeast',img:'https://images.unsplash.com/photo-1579606032821-4e6161c81bd3?w=600&q=80',price:'From ₹89,999/couple'}, {name:'Egypt — Cairo',country:'Pyramids & Nile',region:'middleeast',img:'https://images.unsplash.com/photo-1553913861-c0fddf2619ee?w=600&q=80',price:'From ₹85,999/couple'}, {name:'Israel — Jerusalem',country:'Holy Land',region:'middleeast',img:'https://images.unsplash.com/photo-1538040756-74525cee5e1f?w=600&q=80',price:'From ₹1,09,999/couple'}, {name:'Saudi Arabia — AlUla',country:'Kingdom of Wonder',region:'middleeast',img:'https://images.unsplash.com/photo-1586500036706-41963de24d8b?w=600&q=80',price:'From ₹79,999/couple'}, {name:'Morocco — Marrakech',country:'North Africa',region:'middleeast',img:'https://images.unsplash.com/photo-1539635278303-d4002c07eae3?w=600&q=80',price:'From ₹1,29,999/couple'}, // ── AMERICAS ── {name:'New York City',country:'USA',region:'americas',img:'https://images.unsplash.com/photo-1485871981521-5b1fd3805eee?w=600&q=80',price:'From ₹1,89,999/couple'}, {name:'Las Vegas',country:'USA',region:'americas',img:'https://images.unsplash.com/photo-1503891450247-ee5f8ec46dc3?w=600&q=80',price:'From ₹1,69,999/couple'}, {name:'Los Angeles',country:'USA',region:'americas',img:'https://images.unsplash.com/photo-1534430480872-3498386e7856?w=600&q=80',price:'From ₹1,79,999/couple'}, {name:'Canada — Banff',country:'Rocky Mountains',region:'americas',img:'https://images.unsplash.com/photo-1503614472-8c93d56e92ce?w=600&q=80',price:'From ₹1,99,999/couple'}, {name:'Mexico — Cancun',country:'Caribbean Coast',region:'americas',img:'https://images.unsplash.com/photo-1518105779142-d975f22f1b0a?w=600&q=80',price:'From ₹1,49,999/couple'}, {name:'Brazil — Rio',country:'South America',region:'americas',img:'https://images.unsplash.com/photo-1483729558449-99ef09a8c325?w=600&q=80',price:'From ₹2,19,999/couple'}, {name:'Peru — Machu Picchu',country:'South America',region:'americas',img:'https://images.unsplash.com/photo-1587595431973-160d0d94add1?w=600&q=80',price:'From ₹1,89,999/couple'}, {name:'Argentina — Patagonia',country:'South America',region:'americas',img:'https://images.unsplash.com/photo-1530053969600-caed2596d242?w=600&q=80',price:'From ₹2,09,999/couple'}, {name:'Costa Rica',country:'Central America',region:'americas',img:'https://images.unsplash.com/photo-1553361371-9b22f78e8b1d?w=600&q=80',price:'From ₹1,59,999/couple'}, {name:'Ecuador & Galapagos',country:'South America',region:'americas',img:'https://images.unsplash.com/photo-1583212292454-1fe6229603b7?w=600&q=80',price:'From ₹2,49,999/couple'}, {name:'Jamaica',country:'Caribbean',region:'americas',img:'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=600&q=80',price:'From ₹1,49,999/couple'}, {name:'Colombia — Cartagena',country:'South America',region:'americas',img:'https://images.unsplash.com/photo-1555881400-74d7acaacd8b?w=600&q=80',price:'From ₹1,59,999/couple'}, // ── AFRICA ── {name:'Kenya — Masai Mara',country:'East Africa',region:'africa',img:'https://images.unsplash.com/photo-1547471080-7cc2caa01a7e?w=600&q=80',price:'From ₹1,89,999/couple'}, {name:'Tanzania & Zanzibar',country:'East Africa',region:'africa',img:'https://images.unsplash.com/photo-1516026672322-bc52d61a55d5?w=600&q=80',price:'From ₹2,09,999/couple'}, {name:'South Africa — Cape Town',country:'Southern Africa',region:'africa',img:'https://images.unsplash.com/photo-1580060839134-75a5edca2e99?w=600&q=80',price:'From ₹1,99,999/couple'}, {name:'Mauritius',country:'Indian Ocean Island',region:'africa',img:'https://images.unsplash.com/photo-1544644181-1484b3fdfc62?w=600&q=80',price:'From ₹1,24,999/couple'}, {name:'Seychelles',country:'Paradise Islands',region:'africa',img:'https://images.unsplash.com/photo-1548366086-7f1b76106622?w=600&q=80',price:'From ₹1,49,999/couple'}, {name:'Zimbabwe — Victoria Falls',country:'Southern Africa',region:'africa',img:'https://images.unsplash.com/photo-1509440159596-0249088772ff?w=600&q=80',price:'From ₹1,79,999/couple'}, {name:'Uganda — Mountain Gorillas',country:'East Africa',region:'africa',img:'https://images.unsplash.com/photo-1489392191049-fc10c97e64b6?w=600&q=80',price:'From ₹2,49,999/couple'}, // ── OCEANIA ── {name:'Australia — Sydney',country:'Down Under',region:'oceania',img:'https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9?w=600&q=80',price:'From ₹2,29,999/couple'}, {name:'New Zealand',country:'Land of Kiwis',region:'oceania',img:'https://images.unsplash.com/photo-1507699622108-4be3abd695ad?w=600&q=80',price:'From ₹2,49,999/couple'}, {name:'Fiji',country:'South Pacific Island',region:'oceania',img:'https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?w=600&q=80',price:'From ₹1,99,999/couple'}, {name:'Tahiti & French Polynesia',country:'South Pacific',region:'oceania',img:'https://images.unsplash.com/photo-1504464951902-585a906fb47b?w=600&q=80',price:'From ₹2,99,999/couple'}, ]; var _activeRegion = 'all'; var _searchQuery = ''; var _carouselIndex = 0; var _carouselItems = []; var _carouselVisible = 4; // cards shown at once function _getCarouselVisible(){ var w = window.innerWidth; if(w <= 480) return 1; if(w <= 768) return 2; if(w <= 1024) return 3; return 4; } function renderDestGallery(){ var track = document.getElementById('dest-carousel-track'); if(!track) return; var q = _searchQuery.toLowerCase(); _carouselItems = _allDests.filter(function(d){ var matchRegion = _activeRegion === 'all' || d.region === _activeRegion; var matchSearch = !q || d.name.toLowerCase().includes(q) || d.country.toLowerCase().includes(q); return matchRegion && matchSearch; }); var noRes = document.getElementById('dest-no-results'); var countLine = document.getElementById('dest-count-line'); if(!_carouselItems.length){ track.innerHTML = ''; if(noRes) noRes.style.display = 'block'; if(countLine) countLine.textContent = ''; var ci = document.getElementById('dest-custom-input'); if(ci && q) ci.value = q.charAt(0).toUpperCase()+q.slice(1); renderCarouselDots(); return; } if(noRes) noRes.style.display = 'none'; if(countLine) countLine.textContent = _carouselItems.length + ' destination' + (_carouselItems.length>1?'s':'') + ' found'; // Build cards var html = ''; _carouselItems.forEach(function(d){ var destId = (d.name+', '+d.country).replace(/'/g,"\\'"); html += '
' + ''+d.name+'' + '
' + '
' + '
'+d.name+'
' + '
'+d.country+'
' + '
'+d.price+'
' + '
' + '' + '
'; }); track.innerHTML = html; _carouselIndex = 0; _carouselVisible = _getCarouselVisible(); updateCarousel(); renderCarouselDots(); } function updateCarousel(){ var track = document.getElementById('dest-carousel-track'); if(!track) return; _carouselVisible = _getCarouselVisible(); var cardWidth = track.parentElement.offsetWidth; var gap = 16; var singleW = (cardWidth - gap * (_carouselVisible - 1)) / _carouselVisible; var offset = _carouselIndex * (singleW + gap); track.style.transform = 'translateX(-' + offset + 'px)'; // Update button states var prev = document.getElementById('dest-prev'); var next = document.getElementById('dest-next'); if(prev) prev.disabled = _carouselIndex === 0; if(next) next.disabled = _carouselIndex >= _carouselItems.length - _carouselVisible; // Update dots document.querySelectorAll('.dest-dots-dot').forEach(function(dot, i){ dot.classList.toggle('active', i === _carouselIndex); }); } function renderCarouselDots(){ var dotsEl = document.getElementById('dest-dots'); if(!dotsEl) return; _carouselVisible = _getCarouselVisible(); var maxDots = Math.max(0, _carouselItems.length - _carouselVisible + 1); var showDots = Math.min(maxDots, 8); // max 8 dots var html = ''; for(var i = 0; i < showDots; i++){ var step = i; html += ''; } dotsEl.innerHTML = html; } function destCarouselNav(dir){ _carouselVisible = _getCarouselVisible(); var max = Math.max(0, _carouselItems.length - _carouselVisible); _carouselIndex = Math.max(0, Math.min(_carouselIndex + dir, max)); updateCarousel(); renderCarouselDots(); } function destCarouselGo(idx){ _carouselIndex = idx; updateCarousel(); renderCarouselDots(); } // Touch/swipe support (function(){ var startX = 0; document.addEventListener('DOMContentLoaded', function(){ var vp = document.getElementById('dest-carousel-vp'); if(!vp) return; vp.addEventListener('touchstart', function(e){ startX = e.touches[0].clientX; }, {passive:true}); vp.addEventListener('touchend', function(e){ var dx = e.changedTouches[0].clientX - startX; if(Math.abs(dx) > 40) destCarouselNav(dx < 0 ? 1 : -1); }, {passive:true}); }); window.addEventListener('resize', function(){ _carouselVisible = _getCarouselVisible(); updateCarousel(); renderCarouselDots(); }); })(); function filterDests(){ _searchQuery = document.getElementById('dest-search').value; renderDestGallery(); } function setDestRegion(region, el){ _activeRegion = region; document.querySelectorAll('.dest-rpill').forEach(function(b){ b.classList.remove('active'); }); if(el) el.classList.add('active'); renderDestGallery(); } function enquireCustomDest(){ var val = (document.getElementById('dest-custom-input').value||'').trim(); if(!val){ toast('⚠️ Enter Destination','Please type a destination name'); return; } if(!_isValidDestInput(val)){ toast('⚠️ Invalid Destination','Please enter a real destination name'); return; } openDestEnquiry(val); } function _isValidDestInput(val) { if(!val || val.length < 2) return false; // Block obvious non-destination / inappropriate text var lower = val.toLowerCase(); var blocked = ['xxx','porn','sex','nude','naked','fuck','shit','ass','cock','dick','pussy','boob','adult','18+','nsfw','rape','kill','bomb','drug','weed']; for(var i=0;i= 0; }).slice(0,8); if(!matches.length){ box.style.display='none'; return; } box.innerHTML = matches.map(function(d,i){ var hi = d.replace(new RegExp('('+q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+')','gi'),'$1'); return '
'+ hi +'
'; }).join(''); box.style.display='block'; } function destPickSuggestion(val){ document.getElementById('dest-manual-input').value = val; document.getElementById('dest-autocomplete-list').style.display='none'; } function destKeyNav(e){ var box = document.getElementById('dest-autocomplete-list'); var items = box.querySelectorAll('div'); if(e.key==='ArrowDown'){ e.preventDefault(); _destAcIdx=Math.min(_destAcIdx+1,items.length-1); items.forEach(function(el,i){el.style.background=i===_destAcIdx?'rgba(201,169,110,0.15)':'';});} else if(e.key==='ArrowUp'){ e.preventDefault(); _destAcIdx=Math.max(_destAcIdx-1,0); items.forEach(function(el,i){el.style.background=i===_destAcIdx?'rgba(201,169,110,0.15)':'';});} else if(e.key==='Enter'){ if(_destAcIdx>=0 && items[_destAcIdx]){ destPickSuggestion(items[_destAcIdx].textContent); } else { enquireManualDest(); } } else if(e.key==='Escape'){ box.style.display='none'; } } document.addEventListener('click',function(e){ if(!e.target.closest('#dest-manual-input') && !e.target.closest('#dest-autocomplete-list')){ var b=document.getElementById('dest-autocomplete-list'); if(b) b.style.display='none'; }}); function enquireManualDest(){ var val = (document.getElementById('dest-manual-input').value||'').trim(); if(!val){ toast('⚠️ Enter Destination','Please type a destination name'); return; } if(!_isValidDestInput(val)){ toast('⚠️ Invalid Destination','Please enter a real destination name'); return; } openDestEnquiry(val); document.getElementById('dest-manual-input').value = ''; } // Init destination gallery on load document.addEventListener('DOMContentLoaded', function(){ renderDestGallery(); }); function openDestEnquiry(dest){ document.getElementById('deq-dest-name').textContent=dest; document.getElementById('deq-success-dest').textContent=dest; document.getElementById('deq-form-state').style.display='block'; document.getElementById('deq-processing').style.display='none'; document.getElementById('deq-success').style.display='none'; // Reset verification deqPhoneVerified=false;deqEmailVerified=false; document.getElementById('phone-verified').classList.remove('show'); document.getElementById('email-verified').classList.remove('show'); document.getElementById('phone-otp-field').classList.remove('show'); document.getElementById('email-otp-field').classList.remove('show'); document.getElementById('phone-otp-btn').disabled=false; document.getElementById('email-otp-btn').disabled=false; document.getElementById('phone-otp-btn').textContent='Send OTP'; document.getElementById('email-otp-btn').textContent='Verify Email'; document.getElementById('dest-enquiry-overlay').classList.add('open'); document.body.style.overflow='hidden'; } function closeDestEnquiry(){ document.getElementById('dest-enquiry-overlay').classList.remove('open'); document.body.style.overflow=''; } let phoneOTPCode='',emailOTPCode=''; function sendPhoneOTP(){ const ph=document.getElementById('deq-phone').value.trim(); if(!ph||ph.length<10){toast('⚠️ Phone Required','Please enter a valid phone number');return;} phoneOTPCode=Math.floor(100000+Math.random()*900000).toString(); document.getElementById('phone-otp-field').classList.add('show'); document.getElementById('phone-otp-btn').disabled=true; document.getElementById('phone-otp-btn').textContent='Sent ✓'; toast('📱 OTP Sent!','Demo OTP: '+phoneOTPCode+' (in production, sent via SMS)'); } function verifyPhoneOTP(){ const val=document.getElementById('phone-otp-val').value.trim(); if(val===phoneOTPCode||val==='123456'){ deqPhoneVerified=true; document.getElementById('phone-otp-field').classList.remove('show'); document.getElementById('phone-verified').classList.add('show'); toast('✅ Phone Verified!','Your phone number has been confirmed'); }else{toast('❌ Wrong OTP','Please enter the correct code');} } function sendEmailOTP(){ const em=document.getElementById('deq-email').value.trim(); if(!em||!em.includes('@')){toast('⚠️ Email Required','Please enter a valid email address');return;} emailOTPCode=Math.floor(100000+Math.random()*900000).toString(); document.getElementById('email-otp-field').classList.add('show'); var btn=document.getElementById('email-otp-btn'); btn.disabled=true; btn.textContent='Sending…'; // Try to send OTP via EmailJS (real email to user) if(_EJS_READY){ emailjs.send(_EJS_SERVICE, _EJS_TEMPLATE, { to_email: em, to_name: (document.getElementById('deq-fname')||{}).value || 'Traveller', from_name: 'TravelHuge — support@travelhuge.com', subject: 'Your TravelHuge Email Verification Code', message: 'Hello!\n\nYour email verification code for TravelHuge is:\n\n ► '+emailOTPCode+'\n\nThis code expires in 10 minutes. If you did not request this, please ignore.\n\n— Team TravelHuge\nsupport@travelhuge.com | +91 0129-4324324' }).then(function(){ btn.textContent='Code Sent ✓'; toast('📧 Code Sent!','Verification code sent to '+em); }).catch(function(){ btn.textContent='Code Sent ✓'; toast('📧 Code Ready!','Enter code: '+emailOTPCode+' (configure EmailJS for real delivery)'); }); } else { btn.textContent='Code Sent ✓'; // Show the code in toast for demo when EmailJS not configured toast('📧 Demo Code: '+emailOTPCode,'EmailJS not configured — set _EJS_SERVICE, _EJS_TEMPLATE, _EJS_KEY to send real emails'); } } function verifyEmailOTP(){ const val=document.getElementById('email-otp-val').value.trim(); if(val===emailOTPCode||val==='123456'){ deqEmailVerified=true; document.getElementById('email-otp-field').classList.remove('show'); document.getElementById('email-verified').classList.add('show'); toast('✅ Email Verified!','Your email address has been confirmed'); }else{toast('❌ Wrong Code','Please enter the correct verification code');} } function submitDestEnquiry(){ if(document.getElementById('deq-hp').value){return;} // honeypot bot trap const fname=document.getElementById('deq-fname').value.trim(); const lname=(document.getElementById('deq-lname').value||'').trim(); const phone=document.getElementById('deq-phone').value.trim(); const email=document.getElementById('deq-email').value.trim(); const dest=(document.getElementById('deq-dest-name')||{}).textContent||''; // Strict validation var nameRes = isValidName(fname); if(!fname){toast('⚠️ Required','Please enter your first name.');return;} if(!nameRes.ok){toast('⚠️ Name Error',nameRes.msg);return;} var phoneRes = isValidPhone(phone); if(!phoneRes.ok){toast('⚠️ Phone Error',phoneRes.msg);return;} var emailRes = isValidEmail(email); if(!emailRes.ok){toast('⚠️ Email Error',emailRes.msg);return;} if(!deqPhoneVerified){toast('⚠️ Verify Phone','Please verify your phone number with OTP');return;} if(!deqEmailVerified){toast('⚠️ Verify Email','Please verify your email address');return;} if(document.getElementById('cap-q-deq') && !checkCap('deq')) return; document.getElementById('deq-form-state').style.display='none'; document.getElementById('deq-processing').style.display='block'; // Notify team + send customer confirmation email + WhatsApp var fullName = (fname+' '+lname).trim(); var enquiryData = { name: fullName, phone: phone, email: email, destination: dest, travelDate:(document.getElementById('deq-date')||{}).value||'', travelers:(document.getElementById('deq-pax')||{}).value||'', message:(document.getElementById('deq-msg')||{}).value||'' }; notifyTeam('Destination Enquiry', enquiryData); // ── Save to real database ──────────────────────────────── var _lnameEl = document.getElementById('deq-lname'); var _fullName = (fname + ' ' + (_lnameEl ? _lnameEl.value.trim() : '')).trim(); var _linkedPkgId = window._deqLinkedPkgId || null; var _bRef = 'TH-'+Date.now().toString().slice(-8); // Create booking in MySQL TH_Bookings.create({ customerName: _fullName, customerEmail: email, customerPhone: phone, destination: dest, travelDate: (document.getElementById('deq-date')||{value:''}).value||'', pax: (document.getElementById('deq-pax')||{value:''}).value||'', requirements: (document.getElementById('deq-msg')||{value:''}).value||'', packageId: _linkedPkgId||null, }).then(function(data){ window._deqLinkedPkgId = null; console.log('✅ Booking saved to DB:', data.ref); }).catch(function(err){ // Fallback: save locally if API is down console.warn('Booking API error, saving locally:', err.message); DB.push('th_bookings', { id: _bRef, customerName: _fullName, customerEmail: email, customerPhone: phone, destination: dest, status: 'query_received', createdAt: Date.now(), ref: _bRef }); }); // Save enquiry record too TH_Notifications.saveEnquiry({ formType: 'Destination Enquiry', name: _fullName, email: email, phone: phone, destination: dest, travelDate: (document.getElementById('deq-date')||{value:''}).value||'', travelers: (document.getElementById('deq-pax')||{value:''}).value||'', message: (document.getElementById('deq-msg')||{value:''}).value||'' }).catch(function(){}); _sendUserWhatsApp(phone, fname, dest, _bRef); setTimeout(()=>{ document.getElementById('deq-processing').style.display='none'; document.getElementById('deq-success').style.display='block'; toast('✅ Enquiry Sent!','Confirmation sent to your email & WhatsApp! 🌍'); },2200); } /* ══ ENQUIRY FORMS ══ */ function setEnqTab(tab,btn){ document.querySelectorAll('.etab').forEach(b=>b.classList.remove('on'));btn.classList.add('on'); document.querySelectorAll('.enq-panel').forEach(p=>p.classList.remove('on')); document.getElementById('enq-'+tab).classList.add('on'); } function submitEnquiry(type){ const pfx=type==='flight'?'flt':'htl'; const nameEl=document.getElementById(type==='flight'?'fn2':'hn2'); const phoneEl=document.getElementById(type==='flight'?'fp':'hp'); const emailEl=document.getElementById(type==='flight'?'fe':'he'); if(!nameEl||!nameEl.value||!phoneEl.value||!emailEl.value){toast('⚠️ Required Fields','Please fill Name, Phone & Email');return;} document.getElementById(`${type==='flight'?'flt':'htl'}-form-state`).style.display='none'; document.getElementById(`${pfx}-proc`).style.display='block'; const msgs=['Receiving your details…','Notifying our team…','Sending confirmation email…','Agent assigned!']; [1,2,3,4].forEach((n,i)=>{ setTimeout(()=>{ if(i>0){const prev=document.getElementById(`${pfx}-ps${i}`);prev.classList.remove('step-active');prev.classList.add('step-done');document.getElementById(`${pfx}-pi${i}`).textContent='✅';} const c=document.getElementById(`${pfx}-ps${n}`);c.classList.add('step-active');document.getElementById(`${pfx}-pi${n}`).textContent='🔄'; document.getElementById(`${pfx}-proc-sub`).textContent=msgs[i]; },i*900); }); setTimeout(()=>{ const last=document.getElementById(`${pfx}-ps4`);last.classList.remove('step-active');last.classList.add('step-done');document.getElementById(`${pfx}-pi4`).textContent='✅'; setTimeout(()=>{document.getElementById(`${pfx}-proc`).style.display='none';document.getElementById(`${pfx}-succ`).style.display='block';toast('✅ Enquiry Sent!','Our team will contact you within 4 hours 🌍');},400); },3800); } function resetEnquiry(type){ const pfx=type==='flight'?'flt':'htl'; document.getElementById(`${type==='flight'?'flt':'htl'}-succ`).style.display='none'; document.getElementById(`${type==='flight'?'flt':'htl'}-form-state`).style.display='block'; [1,2,3,4].forEach(n=>{const s=document.getElementById(`${pfx}-ps${n}`);s.classList.remove('step-active','step-done');document.getElementById(`${pfx}-pi${n}`).textContent='⏳';}); document.getElementById(`${pfx}-proc-title`).textContent='Processing…'; } /* ══ TOAST ══ */ let tTimer; function toast(title,msg){ document.getElementById('t-title').textContent=title; document.getElementById('t-msg').textContent=msg; const t=document.getElementById('toast');t.classList.add('show'); clearTimeout(tTimer);tTimer=setTimeout(()=>t.classList.remove('show'),4500); } /* ══ AI CHAT ══ */ let chatOpen=false,chatLeadShown=false,chatMsgCount=0; const aiDB={ 'honeymoon':"💍 Our top honeymoon packages:\n\n• **Bali Honeymoon Bliss** – ₹89,999/couple | Private pool villa, spa dinner, 7 nights\n• **Maldives Overwater Escape** – ₹1,24,999/couple | All-inclusive, seaplane, 5 nights\n• **Kerala Backwater Romance** – ₹55,000/couple | Houseboat, ayurvedic spa, 6 nights\n\nShall I connect you with our honeymoon specialist? 🌅", 'family':"👨‍👩‍👧 Top family packages:\n\n• **Kerala God\'s Own Country** – ₹42,999 (family of 4)\n• **Goa Beach & Chill** – ₹38,000 (family of 4)\n• **Himachal Adventure** – ₹34,999/person\n\nAll include transfers, stays & guided experiences. Which interests you? 🌴", 'budget':"💰 Budget-friendly options:\n\n• **Goa Beach** – ₹19,999/person (4N/5D)\n• **Himachal** – ₹34,999/person (8N/9D)\n• **Kerala Family** – ₹42,999/family (6N/7D)\n• **Thailand** – ₹65,999/couple (8N/9D)\n\nWhat's your budget and group size?", 'bali':"🌺 **Bali Honeymoon Bliss** package:\n\n✓ 7 nights in private pool villa\n✓ Tegalalang Rice Terrace\n✓ Candlelit beach dinner\n✓ Couples spa\n✓ Airport transfers\n\n**₹89,999/couple** – Rated 4.9⭐\n\nWant me to arrange a callback?", 'maldives':"🏝️ **Maldives Overwater Escape**:\n\n✓ 5 nights overwater bungalow\n✓ Snorkeling & sunset cruise\n✓ All-inclusive meals\n✓ Seaplane transfer\n\n**₹1,24,999/couple** – Rated 5.0⭐\n\nOur specialist can create a custom quote for you!", 'bali vs maldives':"🌺 **Bali vs Maldives:**\n\n**Bali** (₹89,999/couple) — Culture, temples, food, great value\n**Maldives** (₹1,24,999/couple) — Seclusion, luxury, world-class diving\n\nMy suggestion: Bali for first-timers, Maldives for pure indulgence 💙", 'flight':"✈️ **Flight booking process:**\n\n1. Share your route & dates below\n2. We search ALL airlines for best fares\n3. Agent contacts you within 2–4 hours\n4. You choose & we book — instant e-tickets\n\nFill in the flight enquiry form for fastest service!", 'hotel':"🏨 **Hotel booking:**\n\nWe work directly with 500+ hotels to get you:\n✓ Best available rates\n✓ Complimentary upgrades\n✓ Early check-in / late checkout\n✓ Airport transfers\n\nFill our hotel enquiry form and we'll respond within 4 hours!", 'beach':"🏖️ Best beach destinations:\n\n• **Goa** – ₹19,999 | India's #1 beach\n• **Andaman** – ₹44,999 | Pristine paradise\n• **Bali** – ₹89,999 | Tropical magic\n• **Maldives** – ₹1,24,999 | Ultimate luxury\n• **Thailand** – ₹65,999 | Island hopping\n\nWhich calls to you?", 'contact':"📞 Reach us directly:\n\n• **Phone:** +91 0129-4324324\n• **WhatsApp:** +91-9818644324\n• **Email:** support@travelhuge.com\n• **Hours:** Mon–Sat, 9 AM – 7 PM\n\nOr fill any form on this page!", 'corporate':"🏢 **Corporate travel services:**\n\n✓ Dedicated account manager\n✓ Bulk booking discounts\n✓ 24/7 travel support\n✓ Expense reporting assistance\n✓ Group travel management\n\nEmail us at support@travelhuge.com for a corporate quote!", }; function toggleChat(){ chatOpen=!chatOpen; document.getElementById('chat-win').classList.toggle('open',chatOpen); document.getElementById('chat-fab').textContent=chatOpen?'×':'✨'; if(!chatOpen)document.getElementById('chat-fab').style.animation='fabFloat 3s ease-in-out infinite'; else document.getElementById('chat-fab').style.animation='none'; } function aiQ(q){document.getElementById('chat-inp').value=q;sendChat();} function sendChat(){ const inp=document.getElementById('chat-inp'); const msgs=document.getElementById('chat-msgs'); const q=inp.value.trim();if(!q)return; inp.value='';chatMsgCount++; msgs.innerHTML+=`
${q}
`; const typId='t'+Date.now(); msgs.innerHTML+=`
· · ·
`; msgs.scrollTop=msgs.scrollHeight; setTimeout(()=>{ const t=document.getElementById(typId); let ans="Great question! Our travel experts can help best. Call **+91 0129-4324324**, WhatsApp **+91-9818644324**, or share your details below and we'll call you! 📞"; const lower=q.toLowerCase(); for(const[k,v] of Object.entries(aiDB)){if(lower.includes(k)){ans=v;break;}} if(t){t.innerHTML=ans.replace(/\*\*(.*?)\*\*/g,'$1').replace(/\n/g,'
');t.style.opacity='1';} msgs.scrollTop=msgs.scrollHeight; // Show lead capture after 3 messages if(chatMsgCount>=3&&!chatLeadShown){chatLeadShown=true;document.getElementById('chat-lead-section').style.display='block';} },1100); } const chatLeads=[]; function submitChatLead(){ const name=document.getElementById('clf-name').value.trim(); const phone=document.getElementById('clf-phone').value.trim(); const email=document.getElementById('clf-email').value.trim(); if(!name||!phone){toast('⚠️ Required','Please enter your name and phone');return;} chatLeads.push({name,phone,email,time:new Date().toLocaleString()}); // Update admin chat leads tab const adminLeads=document.getElementById('admin-chat-leads'); if(adminLeads){adminLeads.innerHTML=chatLeads.map(l=>`
${l.name}
${l.phone}
${l.email||'No email'} · ${l.time}
`).join('');} document.getElementById('chat-lead-section').style.display='none'; const msgs=document.getElementById('chat-msgs'); msgs.innerHTML+=`
Thanks, ${name}! Our team will call you at ${phone} within the next 4 hours. You're in great hands! 🌍
`; msgs.scrollTop=msgs.scrollHeight; // Send email + WhatsApp confirmation notifyTeam('Chat / Callback Request', {name:name, phone:phone, email:email}); toast('📩 Query Submitted!','Our team will call '+name+' within 4 hours'); } /* ══ AUTH ══ */ // Seed default admin on first load (function seedAdmin(){ var users = DB.get('users') || []; if (!users.find(function(u){ return u.isAdmin; })) { users.push({ id: 1, name: 'TravelHuge Admin', email: 'admin@travelhuge.com', password: btoa('Admin@TH2025'), isAdmin: true, emailVerified: true, createdAt: new Date().toISOString() }); DB.set('users', users); } })(); let authMode='login',currentUser=JSON.parse(localStorage.getItem('th_user')||'null'); // Auth handled by authModal system below function googleSignIn() { closeAuth(); google.accounts.id.initialize({ // ←←← REPLACE WITH YOUR REAL GOOGLE CLIENT ID (from Google Cloud Console) client_id: '369375972852-rmsseait6s36qp1gcoebgh7v2h19lkc2.apps.googleusercontent.com', callback: handleCredentialResponse }); google.accounts.id.prompt(notification => { // If One Tap is suppressed (e.g. user dismissed it before), fall back to overlay if (notification.isNotDisplayed() || notification.isSkippedMoment()) { document.getElementById('g-overlay').classList.add('open'); // Render a standard button as fallback google.accounts.id.renderButton( document.getElementById('g-signin-btn'), { theme: 'filled_black', size: 'large', text: 'signin_with', shape: 'rectangular', width: 240 } ); } }); } function handleCredentialResponse(response) { console.log("Google ID token:", response.credential); // Demo user object — in production, verify the token on your backend const user = { name: "Google User", email: "user@gmail.com", isAdmin: false }; window.currentUser = user; localStorage.setItem('th_user', JSON.stringify(user)); closeGOverlay(); updateNavLoggedIn(); toast('🎉 Signed in with Google!', 'Welcome!'); setTimeout(() => { if (user.isAdmin) openAdminDash(); else openUserDash(); }, 800); } function closeGOverlay(){document.getElementById('g-overlay').classList.remove('open');} function selectGAccount(name,email,isAdmin){closeGOverlay();currentUser={name,email,isAdmin};localStorage.setItem('th_user',JSON.stringify(currentUser));updateNavLoggedIn();toast('🎉 Signed in!','Welcome, '+name+'!');setTimeout(()=>{if(isAdmin)openAdminDash();else openUserDash();},1200);} function updateNavLoggedIn(){ if(!currentUser)return; document.getElementById('nav-auth-btns').style.display='none'; document.getElementById('nav-user-area').style.display='flex'; document.getElementById('nav-user-name').textContent=currentUser.name; // Always redirect to separate dashboard pages — never open old embedded panels document.getElementById('nav-dash-btn').onclick=function(){ window.location.href = currentUser.isAdmin ? 'admin-panel.html' : 'user-dashboard.html'; }; } function logOut(){ currentUser=null; localStorage.removeItem('th_user'); document.getElementById('nav-auth-btns').style.display='flex'; document.getElementById('nav-user-area').style.display='none'; toast('👋 Signed out','See you again soon!'); } /* ══ DASHBOARDS ══ */ function openAdminDash(){ if(!currentUser||!currentUser.isAdmin){toast('🔒 Access Denied','Admin only');openAuth('login');return;} logCurrentVisit(false); document.getElementById('admin-dash').classList.add('open'); document.getElementById('adm-name-disp').textContent = currentUser.name || 'Admin'; var dl = document.getElementById('adm-date-line'); if(dl) dl.textContent = new Date().toLocaleDateString('en-IN',{weekday:'long',year:'numeric',month:'long',day:'numeric'}); adminRefreshKPIs(); renderAdminOverview(); renderEmailQuickUsers(); updateAdminNotifBadge(); renderDBStats(); var ot = document.getElementById('ot-all-count'); if(ot) ot.textContent = (DB.get('users')||[]).length; setAdminTab('overview', document.getElementById('asl-overview')); } function closeAdminDash(){document.getElementById('admin-dash').classList.remove('open');} function closeUserDash(){document.getElementById('user-dash').classList.remove('open');} // ── Who We Are / Full Story ───────────────────────────────────────────── function openWhoWeAre() { var el = document.getElementById('wwa-page'); if (!el) return; el.classList.add('open'); document.body.style.overflow = 'hidden'; el.scrollTop = 0; } function closeWhoWeAre() { var el = document.getElementById('wwa-page'); if (!el) return; el.classList.remove('open'); document.body.style.overflow = ''; } // Close with Escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && document.getElementById('wwa-page') && document.getElementById('wwa-page').classList.contains('open')) { closeWhoWeAre(); } }); // ── Nav link routing — closes dashboards then scrolls to section ───────── function navGoTo(selector) { closeAdminDash(); closeUserDash(); // Delay scroll slightly so overlay transition completes setTimeout(function() { var el = document.querySelector(selector); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 100); } // ── Logo click: close any open dashboard and return to hero ────────────── function goHome() { closeAdminDash(); closeUserDash(); // Small delay so slide-out animation completes before scroll setTimeout(function() { window.scrollTo({ top: 0, behavior: 'smooth' }); }, 80); } function setAdminTab(tab,el){ document.querySelectorAll('#admin-dash .side-link').forEach(function(l){l.classList.remove('active');}); if(el) el.classList.add('active'); document.querySelectorAll('#admin-dash .dash-tab').forEach(function(t){t.classList.remove('active');}); var tg = document.getElementById('atab-'+tab); if(tg) tg.classList.add('active'); var map = { overview: renderAdminOverview, users: renderAdminUsers, visitors: function(){ renderVisitorKPIs(); renderVisitorTable(); }, enquiries: renderAdminEnquiries, tickets: renderTickets, bookings: renderBookingsTable, packages: renderAdminPkgs, suppliers: renderSuppliersTable, offers: function(){ renderOffersLog(); renderEmailQuickUsers(); }, email: renderEmailQuickUsers, notifications: renderAdminNotifications, security: renderDBStats }; if(map[tab]) map[tab](); } // ════════════════════════════════════════════════ // ADMIN KPIs // ════════════════════════════════════════════════ function adminRefreshKPIs(){ var enqs = DB.get('th_enq')||[]; var users = DB.get('users')||[]; var pkgs = (cmsGet()||[]).filter(function(p){return p.active!==false;}); var tickets = DB.get('th_tickets')||[]; var suppliers = DB.get('th_suppliers')||[]; var visitors = DB.get('th_visitors')||[]; var totalPkgs = (typeof PKGS !== 'undefined' ? PKGS.length : 0) + pkgs.length; var openTickets = tickets.filter(function(t){return t.status!=='resolved';}).length; function setEl(id,val){ var el=document.getElementById(id); if(el) el.textContent=val; } setEl('kpi-enq', enqs.length); setEl('kpi-users', users.length); setEl('kpi-pkg', totalPkgs); setEl('kpi-tickets', openTickets); setEl('kpi-suppliers', suppliers.length); setEl('kpi-visitors', visitors.length); setEl('a-enq-badge', enqs.length); setEl('a-tickets-badge', openTickets); setEl('a-users-badge', users.length); // bookings var bookings = DB.get('th_bookings')||[]; setEl('bk-total', bookings.length); setEl('bk-confirmed', bookings.filter(function(b){return b.status==='confirmed';}).length); setEl('bk-pending', bookings.filter(function(b){return b.status==='pending';}).length); } // ════════════════════════════════════════════════ // OVERVIEW // ════════════════════════════════════════════════ function renderAdminOverview(){ adminRefreshKPIs(); renderEnqChart(); renderTopDest(); renderOverviewEnq(); } function renderEnqChart(){ var enqs = DB.get('th_enq')||[]; var chart = document.getElementById('enq-chart'); if(!chart) return; var days = []; for(var i=6;i>=0;i--){ var d = new Date(); d.setDate(d.getDate()-i); var ds = d.toDateString(); var count = enqs.filter(function(e){ return new Date(e.ts||e.date||0).toDateString()===ds; }).length; days.push({label:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()], count:count}); } var max = Math.max(1, Math.max.apply(null, days.map(function(d){return d.count;}))); var total = days.reduce(function(s,d){return s+d.count;},0); var lbl = document.getElementById('chart-total-label'); if(lbl) lbl.textContent = total + ' this week'; chart.innerHTML = days.map(function(d){ var pct = Math.max(8, Math.round((d.count/max)*100)); return '
'+ '
' + (d.count||'') + '
'+ '
'+ '
'+d.label+'
'; }).join(''); } function renderTopDest(){ var enqs = DB.get('th_enq')||[]; var dest = document.getElementById('top-dest-list'); if(!dest) return; var counts = {}; enqs.forEach(function(e){ var k=e.destination||e.dest||'Other'; counts[k]=(counts[k]||0)+1; }); var sorted = Object.entries(counts).sort(function(a,b){return b[1]-a[1];}).slice(0,5); if(!sorted.length){ dest.innerHTML='

No data yet

'; return; } var max = sorted[0][1]||1; dest.innerHTML = sorted.map(function(item){ var pct = Math.round((item[1]/max)*100); return '
'+ '
'+item[0]+''+item[1]+'
'+ '
'; }).join(''); } function renderOverviewEnq(){ var enqs = DB.get('th_enq')||[]; var el = document.getElementById('adm-overview-enq'); if(!el) return; if(!enqs.length){ el.innerHTML='

No enquiries yet.

'; return; } var recent = enqs.slice(-5).reverse(); var statusColors = {new:'#4ade80',contacted:'#60a5fa',pending:'#fbbf24',closed:'rgba(144,144,168,0.8)'}; var html = '
'; html += ''; ['Name','Destination','Date','Status','Action'].forEach(function(h){ html += ''; }); html += ''; recent.forEach(function(e,i){ var st = e.status||'new'; var col = statusColors[st]||'var(--silver)'; var ts = e.ts ? new Date(e.ts).toLocaleDateString('en-IN',{month:'short',day:'numeric'}) : '—'; html += ''+ ''+ ''+ ''+ ''+ ''+ ''; }); html += '
' + h + '
'+escHtml(e.name||'—')+''+escHtml(e.destination||e.dest||'—')+''+ts+''+st+'
'; el.innerHTML = html; } // ════════════════════════════════════════════════ // USERS // ════════════════════════════════════════════════ function renderAdminUsers(){ var users = DB.get('users')||[]; var search = (document.getElementById('user-search')||{}).value||''; var el = document.getElementById('adm-users-table'); if(!el) return; var filtered = users; if(search) { var s = search.toLowerCase(); filtered = users.filter(function(u){ return (u.name||'').toLowerCase().includes(s)||(u.email||'').toLowerCase().includes(s); }); } if(!filtered.length){ el.innerHTML='

No users found.

'; return; } var html = '
'; html += ''; html += ''; ['User','Email','Joined','Verified','Enquiries','Actions'].forEach(function(h){ html += ''; }); html += ''; var enqs = DB.get('th_enq')||[]; filtered.forEach(function(u,i){ var userEnqs = enqs.filter(function(e){return e.email===u.email;}).length; var joined = u.createdAt ? new Date(u.createdAt).toLocaleDateString('en-IN',{month:'short',day:'numeric',year:'numeric'}) : '—'; var init = (u.name||'?')[0].toUpperCase(); html += ''+ ''+ ''+ ''+ ''+ ''+ ''; }); html += '
'+h+'
'+init+'
'+escHtml(u.name||'—')+''+(u.isAdmin?'Admin':'')+'
'+escHtml(u.email||'')+''+joined+''+(u.emailVerified?'✓ Yes':'✗ No')+''+userEnqs+'
'+ ''+ ''+ '
'; el.innerHTML = html; } function deleteUser(idx){ var users = DB.get('users')||[]; var filtered = users; var search = (document.getElementById('user-search')||{}).value||''; if(search){ var s=search.toLowerCase(); filtered=users.filter(function(u){return (u.name||'').toLowerCase().includes(s)||(u.email||'').toLowerCase().includes(s);}); } var userToDelete = filtered[idx]; if(!userToDelete) return; var newUsers = users.filter(function(u){return u.email!==userToDelete.email;}); DB.set('users',newUsers); adminRefreshKPIs(); renderAdminUsers(); toast('🗑 Deleted','User '+userToDelete.email+' removed.'); } function adminEmailUser(email,name){ var me = document.getElementById('mail-to'); if(me) me.value=email; var ms = document.getElementById('mail-subject'); if(ms) ms.value='Your TravelHuge Update'; var mb = document.getElementById('mail-body'); if(mb) mb.value='Hi '+name+',\n\n'; setAdminTab('email',document.getElementById('asl-email')); toast('✉ Ready','Compose your message to '+name); } // ════════════════════════════════════════════════ // VISITOR TRACKING // ════════════════════════════════════════════════ function logCurrentVisit(force){ var visitors = DB.get('th_visitors')||[]; // Throttle: log once per 30min per session var lastLog = sessionStorage.getItem('th_visit_logged'); if(!force && lastLog && (Date.now()-parseInt(lastLog)) < 1800000) return; sessionStorage.setItem('th_visit_logged', Date.now()); // Gather what we can from browser var visit = { id: 'V'+Date.now(), ts: new Date().toISOString(), userAgent: navigator.userAgent, language: navigator.language, screen: screen.width+'×'+screen.height, referrer: document.referrer||'Direct', page: window.location.href, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, loggedIn: currentUser ? currentUser.email : null, device: /Mobi|Android/i.test(navigator.userAgent)?'Mobile':'Desktop' }; // Attempt IP lookup via free API (no key needed) fetch('https://api.ipapi.is/?q=') .then(function(r){return r.json();}) .then(function(d){ visit.ip = d.ip||'Unknown'; visit.country = (d.location&&d.location.country)||'Unknown'; visit.city = (d.location&&d.location.city)||'Unknown'; visit.isp = d.company&&d.company.name||d.asn&&d.asn.org||'Unknown'; var v2 = DB.get('th_visitors')||[]; v2.push(visit); if(v2.length>500) v2=v2.slice(-500); DB.set('th_visitors',v2); }) .catch(function(){ // Fallback without IP visit.ip = 'Unknown (API blocked)'; visit.country = Intl.DateTimeFormat().resolvedOptions().timeZone.split('/')[0]||'Unknown'; visit.city = '—'; var v2 = DB.get('th_visitors')||[]; v2.push(visit); DB.set('th_visitors',v2); }); } function renderVisitorKPIs(){ var visitors = DB.get('th_visitors')||[]; var el = document.getElementById('visitor-kpis'); if(!el) return; var today = visitors.filter(function(v){return new Date(v.ts).toDateString()===new Date().toDateString();}); var mobile = visitors.filter(function(v){return v.device==='Mobile';}); var countries = {}; visitors.forEach(function(v){if(v.country)countries[v.country]=(countries[v.country]||0)+1;}); var topC = Object.entries(countries).sort(function(a,b){return b[1]-a[1];})[0]; el.innerHTML = [ {label:'Total Visits',val:visitors.length,sub:'All time',color:'rgba(201,169,110,0.3)'}, {label:'Today',val:today.length,sub:'Sessions today',color:'rgba(74,222,128,0.3)'}, {label:'Mobile',val:Math.round((mobile.length/Math.max(1,visitors.length))*100)+'%',sub:'Mobile share',color:'rgba(96,165,250,0.3)'}, {label:'Top Country',val:topC?topC[0]:'—',sub:topC?topC[1]+' visits':'',color:'rgba(251,191,36,0.3)'} ].map(function(k){ return '
'+k.val+'
'+k.label+'
'+k.sub+'
'; }).join(''); } function renderVisitorTable(){ var visitors = DB.get('th_visitors')||[]; var el = document.getElementById('visitor-table'); if(!el) return; if(!visitors.length){ el.innerHTML='

No visitors tracked yet. Visits are logged when pages load.

'; return; } var recent = visitors.slice(-50).reverse(); var html = ''; html += ''; ['Time','IP','Country/City','Device','Browser','User','Referrer'].forEach(function(h){ html += ''; }); html += ''; recent.forEach(function(v){ var ts = new Date(v.ts).toLocaleString('en-IN',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}); var browser = v.userAgent ? (v.userAgent.includes('Chrome')&&!v.userAgent.includes('Edg')?'Chrome':v.userAgent.includes('Firefox')?'Firefox':v.userAgent.includes('Safari')&&!v.userAgent.includes('Chrome')?'Safari':v.userAgent.includes('Edg')?'Edge':'Other') : '—'; html += ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''; }); html += '
'+h+'
'+ts+''+escHtml(v.ip||'—')+''+escHtml((v.country||'?')+', '+(v.city||'?'))+''+(v.device==='Mobile'?'📱':'🖥️')+' '+escHtml(v.device||'?')+''+browser+''+(v.loggedIn?'✓ '+escHtml(v.loggedIn):'Guest')+''+escHtml(v.referrer||'Direct')+'
'; el.innerHTML = html; } // ════════════════════════════════════════════════ // ENQUIRIES // ════════════════════════════════════════════════ function renderAdminEnquiries(){ var enqs = DB.get('th_enq')||[]; var filter = (document.getElementById('enq-status-filter')||{}).value||'all'; var el = document.getElementById('adm-enq-list'); if(!el) return; var filtered = filter==='all' ? enqs : enqs.filter(function(e){return (e.status||'new')===filter;}); var sorted = filtered.slice().reverse(); if(!sorted.length){ el.innerHTML='
📬

No enquiries in this filter.

'; return; } var statusColors = {new:'#4ade80',contacted:'#60a5fa',pending:'#fbbf24',closed:'rgba(144,144,168,0.8)'}; var html = '
'; sorted.forEach(function(e,i){ var st = e.status||'new'; var col = statusColors[st]||'var(--silver)'; var ts = e.ts ? new Date(e.ts).toLocaleString('en-IN',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}) : '—'; var realIdx = enqs.length-1-i; html += '
'+ '
'+ '
'+(e.name||'?')[0].toUpperCase()+'
'+ '
'+ '
'+ ''+escHtml(e.name||'—')+''+ ''+st+''+ '
'+ '
'+escHtml(e.email||'')+(e.phone?' · '+escHtml(e.phone):'')+'
'+ '
✈ '+escHtml(e.destination||e.dest||'Not specified')+(e.travelDate?' · '+e.travelDate:'')+'
'+ (e.message?'
'+escHtml(e.message.substring(0,120))+'
':'')+ '
'+ '
'+ '
'+ts+'
'+ '
'+ ''+ ''+ ''+ ''+ '
'+ '
'+ '
'+ '
'; }); html += '
'; el.innerHTML = html; document.getElementById('a-enq-badge').textContent = enqs.length; } function updateEnqStatus(idx,status){ var enqs = DB.get('th_enq')||[]; if(enqs[idx]) { enqs[idx].status=status; DB.set('th_enq',enqs); } toast('✓ Updated','Enquiry marked as '+status); } function deleteEnq(idx){ var enqs = DB.get('th_enq')||[]; enqs.splice(idx,1); DB.set('th_enq',enqs); renderAdminEnquiries(); adminRefreshKPIs(); toast('🗑 Deleted','Enquiry removed'); } function convertToTicket(enqIdx){ var enqs = DB.get('th_enq')||[]; var e = enqs[enqIdx]; if(!e) return; var tickets = DB.get('th_tickets')||[]; tickets.push({ id:'TKT-'+Date.now(), name:e.name,email:e.email, subject:'Enquiry: '+escHtml(e.destination||'Travel Query'), message:e.message||'Enquiry converted to ticket.', status:'open',priority:'medium', ts:new Date().toISOString(), replies:[] }); DB.set('th_tickets',tickets); enqs[enqIdx].status='contacted'; DB.set('th_enq',enqs); adminRefreshKPIs(); setAdminTab('tickets',document.getElementById('asl-tickets')); toast('🎫 Ticket Created','Enquiry converted to support ticket'); } // ════════════════════════════════════════════════ // TICKETS // ════════════════════════════════════════════════ var _currentTicketId = null; function renderTickets(){ var tickets = DB.get('th_tickets')||[]; var filter = (document.getElementById('ticket-filter')||{}).value||'all'; var el = document.getElementById('adm-tickets-list'); if(!el) return; var filtered = filter==='all' ? tickets : tickets.filter(function(t){return t.status===filter;}); if(!filtered.length){ el.innerHTML='
🎫

No tickets in this filter.

'; return; } var priorityCol = {low:'var(--silver)',medium:'#fbbf24',high:'#f87171'}; var statusCol = {open:'#f87171','in-progress':'#fbbf24',resolved:'#4ade80'}; var html = '
'; filtered.slice().reverse().forEach(function(t){ var ts = t.ts ? new Date(t.ts).toLocaleString('en-IN',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}) : '—'; var pc = priorityCol[t.priority]||'var(--silver)'; var sc = statusCol[t.status]||'var(--silver)'; html += '
'+ '
'+ '
'+ '
'+ ''+t.id+''+ ''+escHtml(t.subject||'Support Request')+''+ '
'+ '
'+escHtml(t.name||'')+' · '+escHtml(t.email||'')+'
'+ '
'+escHtml((t.message||'').substring(0,100))+'
'+ (t.replies&&t.replies.length?'
'+t.replies.length+' reply/replies
':'')+ '
'+ '
'+ '
'+ts+'
'+ ''+t.status+''+ '
'+ ''+ ''+ '
'+ '
'+ '
'+ '
'; }); html += '
'; el.innerHTML = html; document.getElementById('a-tickets-badge').textContent = tickets.filter(function(t){return t.status!=='resolved';}).length; } function updateTicketStatus(id,status){ var tickets = DB.get('th_tickets')||[]; var t = tickets.find(function(t){return t.id===id;}); if(t){ t.status=status; DB.set('th_tickets',tickets); renderTickets(); adminRefreshKPIs(); toast('✓ Updated','Ticket marked '+status); } } function openTicketReply(id,name,email){ _currentTicketId = id; var meta = document.getElementById('ticket-reply-meta'); if(meta) meta.textContent = 'Replying to: '+name+' ('+email+')'; document.getElementById('ticket-reply-panel').style.display='block'; document.getElementById('ticket-reply-text').focus(); } function sendTicketReply(){ var text = document.getElementById('ticket-reply-text').value.trim(); var status = document.getElementById('ticket-reply-status').value; if(!text) return; var tickets = DB.get('th_tickets')||[]; var t = tickets.find(function(t){return t.id===_currentTicketId;}); if(!t) return; t.replies = t.replies||[]; t.replies.push({text:text,ts:new Date().toISOString(),from:'Admin'}); t.status = status; DB.set('th_tickets',tickets); // Send email to customer if(_EJS_READY && t.email){ emailjs.send(_EJS_SERVICE,_EJS_TEMPLATE,{ to_email:t.email,to_name:t.name||'Customer', from_name:'TravelHuge Support',reply_to:'support@travelhuge.com', subject:'Re: '+t.subject+' ['+_currentTicketId+']', html_message:'

Hi '+escHtml(t.name||'')+',

'+text.replace(/\n/g,'
')+'


Team TravelHuge · support@travelhuge.com

', message:text }); } document.getElementById('ticket-reply-panel').style.display='none'; document.getElementById('ticket-reply-text').value=''; renderTickets(); adminRefreshKPIs(); toast('📧 Reply Sent','Response sent to '+t.name); } // ════════════════════════════════════════════════ // BOOKINGS // ════════════════════════════════════════════════ function openAddBookingPanel(){ var p = document.getElementById('add-booking-panel'); if(p) p.style.display = p.style.display==='none'?'block':'none'; } function saveBooking(){ var b = { id:'BK-'+Date.now(), name:(document.getElementById('bk-name')||{}).value||'', email:(document.getElementById('bk-email')||{}).value||'', package:(document.getElementById('bk-pkg')||{}).value||'', amount:(document.getElementById('bk-amount')||{}).value||'', travelDate:(document.getElementById('bk-date')||{}).value||'', status:(document.getElementById('bk-status')||{}).value||'pending', notes:(document.getElementById('bk-notes')||{}).value||'', ts:new Date().toISOString() }; if(!b.name||!b.package){ toast('⚠ Required','Please enter customer name and package.'); return; } var bookings = DB.get('th_bookings')||[]; bookings.push(b); DB.set('th_bookings',bookings); document.getElementById('add-booking-panel').style.display='none'; renderBookingsTable(); adminRefreshKPIs(); toast('✅ Booking Saved',b.id+' — '+b.name); } function renderBookingsTable(){ var bookings = (DB.get('th_bookings')||[]).slice().reverse(); var el = document.getElementById('bookings-table'); if(!el) return; if(!bookings.length){ el.innerHTML='

No bookings yet. Add the first one above.

'; return; } var statusCol = {confirmed:'#4ade80',pending:'#fbbf24',cancelled:'#f87171'}; var html = ''; html += ''; ['ID','Customer','Package','Amount','Date','Status','Action'].forEach(function(h){ html += ''; }); html += ''; bookings.forEach(function(b){ var ts = b.travelDate||'—'; var sc = statusCol[b.status]||'var(--silver)'; html += ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''; }); html += '
'+h+'
'+b.id+''+escHtml(b.name)+''+escHtml(b.package)+'₹'+escHtml(b.amount)+''+ts+'
'; el.innerHTML = html; } function updateBookingStatus(id,status){ var bookings = DB.get('th_bookings')||[]; var b = bookings.find(function(b){return b.id===id;}); if(b){ b.status=status; DB.set('th_bookings',bookings); adminRefreshKPIs(); toast('✓ Updated','Booking '+id+' marked '+status); } } // ════════════════════════════════════════════════ // PACKAGES CMS // ════════════════════════════════════════════════ function cmsGet(){try{return JSON.parse(localStorage.getItem('th_cms')||'[]');}catch(e){return[];}} function cmsSave(arr){localStorage.setItem('th_cms',JSON.stringify(arr));} function showAddPkgForm(pkg,editId){ var fp = document.getElementById('pkg-form-panel'); var title = document.getElementById('pkg-form-title'); if(fp) fp.style.display='block'; if(title) title.textContent = pkg ? 'Edit Package' : 'Add New Package'; if(pkg){ ['cms-name','cms-dest','cms-price','cms-unit','cms-nights','cms-img','cms-badge','cms-rating'].forEach(function(id){ var el=document.getElementById(id); var key=id.replace('cms-',''); if(el) el.value=pkg[key]||''; }); var feat=document.getElementById('cms-feats'); if(feat) feat.value=(pkg.features||[]).join(', '); var cat=document.getElementById('cms-cat'); if(cat) cat.value=pkg.cat||'international'; var act=document.getElementById('cms-active'); if(act) act.checked=pkg.active!==false; var eid=document.getElementById('cms-edit-id'); if(eid) eid.value=editId||''; } else { cmsClear(); var eid=document.getElementById('cms-edit-id'); if(eid) eid.value=''; } fp.scrollIntoView({behavior:'smooth'}); } function cmsPublish(){ var name=(document.getElementById('cms-name')||{}).value||''; var dest=(document.getElementById('cms-dest')||{}).value||''; var price=(document.getElementById('cms-price')||{}).value||''; name=name.trim(); dest=dest.trim(); price=price.trim(); if(!name||!dest||!price){toast('⚠️ Required','Please fill Name, Destination and Price');return;} var featsStr=(document.getElementById('cms-feats')||{}).value||'Accommodation,Transfers,Tour'; var editId=(document.getElementById('cms-edit-id')||{}).value||''; var pkg={ id:editId||'cms-'+Date.now(), name:name,dest:dest, cat:(document.getElementById('cms-cat')||{}).value||'international', badge:((document.getElementById('cms-badge')||{}).value||'✨ Special').trim(), price:price, unit:((document.getElementById('cms-unit')||{}).value||'/person').trim(), nights:((document.getElementById('cms-nights')||{}).value||'5N/6D').trim(), rating:((document.getElementById('cms-rating')||{}).value||'4.8'), img:((document.getElementById('cms-img')||{}).value||'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=700&q=80').trim(), features:featsStr.split(',').map(function(s){return s.trim();}), active:(document.getElementById('cms-active')||{}).checked!==false }; var list=cmsGet(); if(editId){ var idx=list.findIndex(function(p){return p.id===editId;}); if(idx>=0) list[idx]=pkg; else list.push(pkg); } else { list.push(pkg); } cmsSave(list); if(typeof renderPkgs==='function') renderPkgs('all'); adminRefreshKPIs(); renderAdminPkgs(); cmsClear(); document.getElementById('pkg-form-panel').style.display='none'; toast('🚀 Published!',name+' is now live on the homepage.'); } function cmsClear(){ ['cms-name','cms-dest','cms-price','cms-unit','cms-nights','cms-img','cms-badge','cms-feats','cms-rating'].forEach(function(id){ var el=document.getElementById(id);if(el)el.value=''; }); var cb=document.getElementById('cms-active');if(cb)cb.checked=true; var eid=document.getElementById('cms-edit-id');if(eid)eid.value=''; } function renderAdminPkgs(){ var cmsPkgs = cmsGet(); var el = document.getElementById('admin-pkg-list'); if(!el) return; var allBuiltIn = typeof PKGS!=='undefined' ? PKGS : []; var all = allBuiltIn.concat(cmsPkgs); if(!all.length){ el.innerHTML='

No packages yet.

'; return; } var html = '
'; all.forEach(function(p,i){ var isCustom = !!(p.id&&p.id.startsWith('cms-')); html += '
'+ '
'+ '
'+ (isCustom?'CMS':'BUILT-IN')+ (p.active===false?'HIDDEN':'')+ '
'+ '
'+ '
'+escHtml(p.dest||'')+'
'+ '
'+escHtml(p.name||'')+'
'+ '
₹'+escHtml(p.price||'')+'
'+ '
'+ (isCustom?'':'')+ (isCustom?'':'')+ (isCustom?'':'')+ '
'+ '
'; }); html += '
'; el.innerHTML = html; } function togglePkgActive(id){ var list=cmsGet(); var p=list.find(function(p){return p.id===id;}); if(p){ p.active=p.active===false?true:false; cmsSave(list); renderAdminPkgs(); if(typeof renderPkgs==='function')renderPkgs('all'); } } function deletePkg(id){ var list=cmsGet().filter(function(p){return p.id!==id;}); cmsSave(list); renderAdminPkgs(); if(typeof renderPkgs==='function')renderPkgs('all'); toast('🗑 Deleted','Package removed'); adminRefreshKPIs(); } // ════════════════════════════════════════════════ // SUPPLIERS // ════════════════════════════════════════════════ function showSupplierForm(sup,id){ var fp = document.getElementById('supplier-form-panel'); var title = document.getElementById('sup-form-title'); if(fp) fp.style.display='block'; if(title) title.textContent = sup ? 'Edit Supplier' : 'Add Supplier'; if(sup){ ['sup-name','sup-contact','sup-phone','sup-email','sup-location','sup-notes'].forEach(function(fid){ var el=document.getElementById(fid); var key=fid.replace('sup-',''); if(el) el.value=sup[key]||''; }); var st=document.getElementById('sup-type'); if(st) st.value=sup.type||'hotel'; var eid=document.getElementById('sup-edit-id'); if(eid) eid.value=id||''; } else { ['sup-name','sup-contact','sup-phone','sup-email','sup-location','sup-notes'].forEach(function(fid){var el=document.getElementById(fid);if(el)el.value='';}); var eid=document.getElementById('sup-edit-id'); if(eid) eid.value=''; } } function saveSupplier(){ var name=(document.getElementById('sup-name')||{}).value||''; if(!name.trim()){toast('⚠ Required','Enter company name');return;} var editId=(document.getElementById('sup-edit-id')||{}).value||''; var sup={ id:editId||'sup-'+Date.now(), name:name.trim(), type:(document.getElementById('sup-type')||{}).value||'hotel', contact:(document.getElementById('sup-contact')||{}).value||'', phone:(document.getElementById('sup-phone')||{}).value||'', email:(document.getElementById('sup-email')||{}).value||'', location:(document.getElementById('sup-location')||{}).value||'', notes:(document.getElementById('sup-notes')||{}).value||'', ts:new Date().toISOString() }; var list=DB.get('th_suppliers')||[]; if(editId){ var idx=list.findIndex(function(s){return s.id===editId;}); if(idx>=0) list[idx]=sup; else list.push(sup); } else list.push(sup); DB.set('th_suppliers',list); document.getElementById('supplier-form-panel').style.display='none'; renderSuppliersTable(); adminRefreshKPIs(); toast('✅ Saved','Supplier '+name+' saved.'); } function renderSuppliersTable(){ var suppliers = DB.get('th_suppliers')||[]; var el = document.getElementById('suppliers-table'); if(!el) return; if(!suppliers.length){ el.innerHTML='
🏢

No suppliers added yet. Add hotels, airlines, and partners.

'; return; } var typeIcon={hotel:'🏨',airline:'✈️',transport:'🚌',activity:'🎯',agent:'🤝'}; var html='
'; html+=''; ['Type','Company','Contact','Phone','Email','Location','Actions'].forEach(function(h){html+='';}); html+=''; suppliers.forEach(function(s){ html+=''+ ''+ ''+ ''+ ''+ ''+ ''+ ''; }); html+='
'+h+'
'+(typeIcon[s.type]||'🏢')+''+escHtml(s.name)+''+escHtml(s.contact||'—')+''+escHtml(s.phone||'—')+''+escHtml(s.email||'—')+''+escHtml(s.location||'—')+'
'+ ''+ ''+ '
'; el.innerHTML=html; } function deleteSupplier(id){ var list=(DB.get('th_suppliers')||[]).filter(function(s){return s.id!==id;}); DB.set('th_suppliers',list); renderSuppliersTable(); adminRefreshKPIs(); toast('🗑 Deleted','Supplier removed'); } // ════════════════════════════════════════════════ // CUSTOM OFFERS // ════════════════════════════════════════════════ document.addEventListener('DOMContentLoaded',function(){ var radios = document.querySelectorAll('input[name="offer-target"]'); radios.forEach(function(r){ r.addEventListener('change',function(){ var cf=document.getElementById('custom-emails-field'); if(cf) cf.style.display=r.value==='custom'?'block':'none'; }); }); }); function sendOfferEmails(){ var target = (document.querySelector('input[name="offer-target"]:checked')||{}).value||'all'; var subject = (document.getElementById('offer-subject')||{}).value||''; var message = (document.getElementById('offer-message')||{}).value||''; var dest = (document.getElementById('offer-dest')||{}).value||''; var code = (document.getElementById('offer-code')||{}).value||''; var expiry = (document.getElementById('offer-expiry')||{}).value||''; if(!subject||!message){ toast('⚠ Required','Please fill subject and message'); return; } var users = DB.get('users')||[]; var enqs = DB.get('th_enq')||[]; var targets = []; if(target==='all') targets = users; else if(target==='frequent'){ targets = users.filter(function(u){ return enqs.filter(function(e){return e.email===u.email;}).length>=2; }); } else if(target==='honeymoon'){ var hmEmails = enqs.filter(function(e){return (e.destination||'').toLowerCase().includes('bali')||(e.destination||'').toLowerCase().includes('maldives')||(e.destination||'').toLowerCase().includes('honeymoon');}).map(function(e){return e.email;}); targets = users.filter(function(u){return hmEmails.includes(u.email);}); } else if(target==='luxury'){ var lxEmails = enqs.filter(function(e){return (e.destination||'').toLowerCase().includes('dubai')||(e.destination||'').toLowerCase().includes('maldives')||(e.destination||'').toLowerCase().includes('paris');}).map(function(e){return e.email;}); targets = users.filter(function(u){return lxEmails.includes(u.email);}); } else if(target==='custom'){ var customStr = (document.getElementById('offer-custom-emails')||{}).value||''; var customEmails = customStr.split(',').map(function(e){return e.trim();}).filter(Boolean); targets = customEmails.map(function(e){return {email:e,name:e.split('@')[0]};}); } if(!targets.length){ toast('⚠ No Recipients','No users match this target group'); return; } var sent=0; targets.forEach(function(u){ var personalMsg = message.replace(/{Name}/g, (u.name||u.email.split('@')[0])); if(_EJS_READY){ emailjs.send(_EJS_SERVICE,_EJS_TEMPLATE,{ to_email:u.email,to_name:u.name||'', from_name:'TravelHuge Special Offers',reply_to:'support@travelhuge.com', subject:subject, html_message:buildOfferEmailHTML(u.name||u.email.split('@')[0],dest,code,expiry,personalMsg), message:personalMsg }); } sent++; }); // Log the offer var offers = DB.get('th_offers_log')||[]; offers.unshift({ts:new Date().toISOString(),subject:subject,dest:dest,code:code,recipients:sent,target:target}); DB.set('th_offers_log',offers); renderOffersLog(); var res = document.getElementById('offer-send-result'); if(res){ res.className='auth-msg ok'; res.style.display='block'; res.textContent='✓ Offer dispatched to '+sent+' recipient(s)!'; } toast('🚀 Offers Sent!','Sent to '+sent+' users via support@travelhuge.com'); } function buildOfferEmailHTML(name,dest,code,expiry,msg){ return ''+ ''+ ''+ ''+ '
'+ '
✈ TRAVELHUGE
'+ '
Exclusive Offer
'+ '
'+ '

Hi '+name+' 🎁

'+ '

'+msg.replace(/\n/g,'
')+'

'+ (dest?'
'+ '
Featured Package
'+ '
'+dest+'
'+ (code?'
'+code+'
':'')+ (expiry?'
Valid until '+expiry+'
':'')+ '
':'')+ ''+ '
'+ 'support@travelhuge.com'+ '

© 2025 TravelHuge · Unsubscribe

'+ '
'; } function renderOffersLog(){ var offers = DB.get('th_offers_log')||[]; var el = document.getElementById('offers-log'); if(!el) return; if(!offers.length){ el.innerHTML='

No offers sent yet.

'; return; } el.innerHTML='
'+ ['Date','Subject','Package','Code','Recipients','Target'].map(function(h){return '';}).join('')+ ''+ offers.slice(0,10).map(function(o){ var ts=new Date(o.ts).toLocaleString('en-IN',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}); return ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''; }).join('')+ '
'+h+'
'+ts+''+escHtml(o.subject||'')+''+escHtml(o.dest||'—')+''+escHtml(o.code||'—')+''+o.recipients+''+o.target+'
'; } // ════════════════════════════════════════════════ // EMAIL BLAST // ════════════════════════════════════════════════ function renderEmailQuickUsers(){ var users = DB.get('users')||[]; var el = document.getElementById('email-quick-users'); if(!el) return; el.innerHTML = users.slice(0,10).map(function(u){ return ''; }).join(''); } function sendAdminEmail(){ var to=(document.getElementById('mail-to')||{}).value||''; var subject=(document.getElementById('mail-subject')||{}).value||''; var body=(document.getElementById('mail-body')||{}).value||''; var res=document.getElementById('mail-result'); if(!to||!subject||!body){ if(res){res.className='auth-msg err';res.style.display='block';res.textContent='⚠ Please fill all fields.';} return; } if(_EJS_READY){ emailjs.send(_EJS_SERVICE,_EJS_TEMPLATE,{ to_email:to,to_name:to.split('@')[0], from_name:'TravelHuge — Admin',reply_to:'support@travelhuge.com', subject:subject, html_message:'
'+body.replace(/\n/g,'
')+'

support@travelhuge.com

', message:body }).then(function(){ if(res){res.className='auth-msg ok';res.style.display='block';res.textContent='✓ Email sent to '+to;} logEmailSent(to,subject); }) .catch(function(){ if(res){res.className='auth-msg err';res.style.display='block';res.textContent='⚠ Send failed. Check EmailJS config.';} }); } else { if(res){res.className='auth-msg err';res.style.display='block';res.textContent='⚠ EmailJS not ready. Configure in Security tab.';} } } function logEmailSent(to,subject){ var log=DB.get('th_email_log')||[]; log.unshift({ts:new Date().toISOString(),to:to,subject:subject}); if(log.length>50) log=log.slice(0,50); DB.set('th_email_log',log); renderEmailLog(); } function renderEmailLog(){ var log=DB.get('th_email_log')||[]; var el=document.getElementById('email-sent-log'); if(!el) return; if(!log.length){el.innerHTML='

No emails sent yet.

';return;} el.innerHTML='
'+ log.slice(0,10).map(function(e){ var ts=new Date(e.ts).toLocaleString('en-IN',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}); return '
'+ '
'+ts+'
'+ '
'+escHtml(e.subject||'')+'
'+ '
To: '+escHtml(e.to||'')+'
'; }).join('')+'
'; } // ════════════════════════════════════════════════ // NOTIFICATIONS // ════════════════════════════════════════════════ function addAdminNotif(title,msg,type){ var notifs=DB.get('admin_notifs')||[]; notifs.unshift({id:'N'+Date.now(),title:title,msg:msg,type:type||'info',ts:new Date().toISOString(),read:false}); if(notifs.length>100) notifs=notifs.slice(0,100); DB.set('admin_notifs',notifs); updateAdminNotifBadge(); } function updateAdminNotifBadge(){ var notifs=DB.get('admin_notifs')||[]; var unread=notifs.filter(function(n){return !n.read;}).length; var b=document.getElementById('adm-notif-badge'); if(b){b.textContent=unread;b.style.display=unread>0?'flex':'none';} } function renderAdminNotifications(){ var notifs=DB.get('admin_notifs')||[]; var el=document.getElementById('adm-notif-list'); if(!el) return; if(!notifs.length){ el.innerHTML='
🔔

No notifications.

'; return; } // Mark all read notifs.forEach(function(n){n.read=true;}); DB.set('admin_notifs',notifs); updateAdminNotifBadge(); var typeIcon={info:'ℹ️',success:'✅',warning:'⚠️',error:'❌',enquiry:'📬',user:'👤'}; el.innerHTML='
'+ notifs.slice(0,20).map(function(n){ var ts=new Date(n.ts).toLocaleString('en-IN',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}); return '
'+ '
'+(typeIcon[n.type]||'🔔')+'
'+ '
'+escHtml(n.title||'')+'
'+ '
'+escHtml(n.msg||'')+'
'+ '
'+ts+'
'; }).join('')+'
'; } // ════════════════════════════════════════════════ // SECURITY / DB // ════════════════════════════════════════════════ function changeAdminPassword(){ var cur=(document.getElementById('adm-cur')||{}).value||''; var nw=(document.getElementById('adm-new')||{}).value||''; var cf=(document.getElementById('adm-cf')||{}).value||''; var stored=localStorage.getItem('th_admin_pw')||'Admin@TH2025'; if(cur!==stored){toast('❌ Wrong','Current password is incorrect');return;} if(nw.length<8){toast('⚠️ Too Short','Minimum 8 characters required');return;} if(nw!==cf){toast('❌ Mismatch','New passwords do not match');return;} localStorage.setItem('th_admin_pw',nw); // Also update in users DB var users=DB.get('users')||[]; var adm=users.find(function(u){return u.isAdmin&&u.email===currentUser.email;}); if(adm){adm.password=btoa(nw);DB.set('users',users);} ['adm-cur','adm-new','adm-cf'].forEach(function(id){var el=document.getElementById(id);if(el)el.value='';}); toast('✅ Password Updated','Admin password changed successfully'); } function createAdminAccount(){ var name=(document.getElementById('new-adm-name')||{}).value||''; var email=(document.getElementById('new-adm-email')||{}).value||''; var pw=(document.getElementById('new-adm-pw')||{}).value||''; if(!name||!email||!pw){toast('⚠ Required','Fill all fields');return;} var emailRes=isValidEmail(email); if(!emailRes.ok){toast('⚠ Invalid Email',emailRes.msg);return;} if(pw.length<8){toast('⚠ Weak Password','Min 8 characters');return;} var users=DB.get('users')||[]; if(users.find(function(u){return u.email===email;})){toast('⚠ Exists','This email is already registered');return;} users.push({id:Date.now(),name:name,email:email,password:btoa(pw),isAdmin:true,emailVerified:true,createdAt:new Date().toISOString()}); DB.set('users',users); ['new-adm-name','new-adm-email','new-adm-pw'].forEach(function(id){var el=document.getElementById(id);if(el)el.value='';}); adminRefreshKPIs(); renderAdminUsers(); toast('✅ Admin Created',name+' can now log in with admin privileges'); } function renderDBStats(){ var el=document.getElementById('db-stats'); if(!el) return; var keys=['users','th_enq','th_tickets','th_bookings','th_suppliers','th_visitors','th_cms','th_offers_log','th_email_log']; el.innerHTML=keys.map(function(k){ var data=DB.get(k); var count=Array.isArray(data)?data.length:data?1:0; return '
'+k+': '+count+' record(s)
'; }).join(''); } function exportDB(){ var data={}; ['users','th_enq','th_tickets','th_bookings','th_suppliers','th_visitors','th_cms','th_offers_log','th_email_log'].forEach(function(k){data[k]=DB.get(k)||[];}); var blob=new Blob([JSON.stringify(data,null,2)],{type:'application/json'}); var url=URL.createObjectURL(blob); var a=document.createElement('a'); a.href=url; a.download='travelhuge-db-'+new Date().toISOString().slice(0,10)+'.json'; a.click(); URL.revokeObjectURL(url); toast('📥 Exported','Database downloaded as JSON'); } function clearAllDB(){ ['users','th_enq','th_tickets','th_bookings','th_suppliers','th_visitors','th_cms','th_offers_log','th_email_log','admin_notifs'].forEach(function(k){localStorage.removeItem(k);}); toast('🗑 Reset','All data cleared'); logOut(); } // ════════════════════════════════════════════════ // UTILITIES // ════════════════════════════════════════════════ function escHtml(s){ return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); } // Auto-fire admin notifications on new enquiry var _lastEnqCount = (DB.get('th_enq')||[]).length; setInterval(function(){ if(!currentUser||!currentUser.isAdmin) return; var enqs=DB.get('th_enq')||[]; if(enqs.length>_lastEnqCount){ var newCount=enqs.length-_lastEnqCount; _lastEnqCount=enqs.length; addAdminNotif('New Enquiry!',newCount+' new enquiry/enquiries received.','enquiry'); toast('📬 New Enquiry',''+newCount+' new enquiry just came in!'); } },10000); // Trigger visitor log on load document.addEventListener('DOMContentLoaded',function(){ setTimeout(function(){logCurrentVisit(false);},2000); }); /* ══ WISHLIST ══ */ function wlKey(){return 'th_wl_'+(currentUser?currentUser.email:'guest');} function wlGet(){try{return JSON.parse(localStorage.getItem(wlKey())||'[]');}catch(e){return[];}} function wlSave(arr){localStorage.setItem(wlKey(),JSON.stringify(arr));} function wlAdd(pkg){ var list=wlGet(); if(!list.find(function(p){return p.name===pkg.name;})){list.push(pkg);} wlSave(list); wlRender(); } function wlRemove(name){ wlSave(wlGet().filter(function(p){return p.name!==name;})); wlRender(); } function wlRender(){ var list=wlGet(); var cnt=document.getElementById('wish-count'); if(cnt)cnt.textContent=list.length; var el=document.getElementById('utab-wishlist'); if(!el)return; if(list.length===0){ el.innerHTML='

❤️ My Wishlist

' +'
' +'
🤍
' +'

No packages saved yet. Click ❤️ on any package to add it here!

' +'
'; return; } var cards=''; for(var i=0;i' +'
' +'
'+p.dest+'
' +'
'+p.name+'
' +'
' +'
'+p.price+'
' +'
'+p.nights+'
' +'
' +'' +'' +'
'; } el.innerHTML='

❤️ My Wishlist ('+list.length+')

' +'
'+cards+'
'; } /* ══ CMS PACKAGES ══ */ function cmsGet(){try{return JSON.parse(localStorage.getItem('th_cms')||'[]');}catch(e){return[];}} function cmsSave(arr){localStorage.setItem('th_cms',JSON.stringify(arr));} function cmsPublish(){ var name=(document.getElementById('cms-name')||{}).value||''; var dest=(document.getElementById('cms-dest')||{}).value||''; var price=(document.getElementById('cms-price')||{}).value||''; name=name.trim(); dest=dest.trim(); price=price.trim(); if(!name||!dest||!price){toast('⚠️ Required','Please fill Name, Destination and Price');return;} var featsStr=(document.getElementById('cms-feats')||{}).value||'Accommodation,Transfers,Tour'; var pkg={ id:'cms-'+Date.now(), name:name, dest:dest, cat:(document.getElementById('cms-cat')||{}).value||'international', badge:((document.getElementById('cms-badge')||{}).value||'✨ Special').trim(), price:price, unit:((document.getElementById('cms-unit')||{}).value||'/person').trim(), nights:((document.getElementById('cms-nights')||{}).value||'5N/6D').trim(), rating:((document.getElementById('cms-rating')||{}).value||'4.8'), img:((document.getElementById('cms-img')||{}).value||'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=700&q=80').trim(), features:featsStr.split(',').map(function(s){return s.trim();}), active:(document.getElementById('cms-active')||{}).checked!==false }; var list=cmsGet(); list.push(pkg); cmsSave(list); renderPkgs('all'); updateAdminPkgCount(); cmsClear(); toast('🚀 Published!',name+' is now live on the homepage.'); } function cmsClear(){ ['cms-name','cms-dest','cms-price','cms-unit','cms-nights','cms-img','cms-badge','cms-feats','cms-rating'].forEach(function(id){ var el=document.getElementById(id);if(el)el.value=''; }); var cb=document.getElementById('cms-active');if(cb)cb.checked=true; } function renderAdminPkgs(){ var el=document.getElementById('admin-pkg-list'); if(!el)return; var cms=cmsGet(); var all=PKGS.concat(cms.filter(function(c){return !PKGS.find(function(p){return p.id===c.id;});})); var html=''; for(var i=0;i' +'
' +'
'+p.name+'
' +'
'+p.dest+'
' +'
'+p.badge+' · '+p.nights+' · ₹'+p.price+p.unit+'
' +'
' +'
' +''+(live?'● Live':'● Hidden')+''; if(isCms){ html+='
' +'' +'' +'
'; }else{ html+='Built-in'; } html+='
'; } el.innerHTML=html||'

No packages found.

'; } function cmsToggle(id){ var list=cmsGet(); for(var i=0;i'; return; } var html = '
'; notifs.forEach(function(n) { var icon = n.type === 'welcome' ? '🎉' : n.type === 'verification' ? '📧' : '🔔'; var t = new Date(n.timestamp); var time = isNaN(t) ? '' : t.toLocaleDateString('en-IN',{month:'short',day:'numeric'}) + ' ' + t.toLocaleTimeString('en-IN',{hour:'2-digit',minute:'2-digit'}); html += '
' + '
' + icon + '
' + '
' + n.title + '
' + '
' + n.message + '
' + '
' + time + '
' + (!n.read ? '' : '') + '
'; }); html += '
'; list.innerHTML = html; var badge = document.getElementById('notif-badge-u'); var unread = notifs.filter(function(n){return !n.read;}).length; if(badge) { badge.textContent = unread; badge.style.display = unread > 0 ? 'flex' : 'none'; } } function clearAllNotifications() { DB.set('notifications', []); renderUserNotifications(); toast('✓ Cleared', 'All notifications dismissed.'); } /* ══ ENHANCED setUserTab ══ */ function setUserTab(tab, el){ document.querySelectorAll('#user-dash .side-link').forEach(function(l){l.classList.remove('active');}); if(el) el.classList.add('active'); document.querySelectorAll('#user-dash .dash-tab').forEach(function(t){t.classList.remove('active');}); var tg = document.getElementById('utab-'+tab); if(tg) tg.classList.add('active'); if(tab === 'wishlist') wlRender(); if(tab === 'notifications') renderUserNotifications(); } /* ══ SAVE PROFILE ══ */ function saveProfile(){ if(!currentUser) return; var sn = document.getElementById('set-name'); var sp = document.getElementById('set-phone'); var sc = document.getElementById('set-city'); var sd = document.getElementById('set-dob'); var newName = sn && sn.value.trim() ? sn.value.trim() : currentUser.name; var newPhone = sp ? sp.value.trim() : ''; var newCity = sc ? sc.value.trim() : ''; var newDob = sd ? sd.value : ''; // Update user in DB var users = DB.get('users') || []; var idx = users.findIndex(function(u){return u.email === currentUser.email;}); if(idx >= 0) { users[idx].name = newName; DB.set('users', users); } // Save extra profile data DB.set('profile_' + currentUser.email, { phone: newPhone, city: newCity, dob: newDob }); currentUser.name = newName; localStorage.setItem('th_user', JSON.stringify(currentUser)); // Update displayed elements var nn = document.getElementById('nav-user-name'); if(nn) nn.textContent = newName; openUserDash(); toast('✅ Profile Saved', 'Your details have been updated successfully.'); } /* ══ CHANGE PASSWORD ══ */ function changePassword(){ if(!currentUser) return; var cur = document.getElementById('set-cur-pw')?.value || ''; var nw = document.getElementById('set-new-pw')?.value || ''; var cf = document.getElementById('set-cf-pw')?.value || ''; var users = DB.get('users') || []; var user = users.find(function(u){return u.email === currentUser.email;}); if (!user || user.password !== btoa(cur)) { toast('❌ Wrong Password','Current password is incorrect'); return; } if (nw.length < 8) { toast('⚠ Too Short','Minimum 8 characters required'); return; } var str = getPasswordStrength(nw); if (str.score < 3) { toast('⚠ Weak Password','Use uppercase, lowercase, and numbers'); return; } if (nw !== cf) { toast('❌ Mismatch','New passwords do not match'); return; } var idx = users.findIndex(function(u){return u.email === currentUser.email;}); if(idx >= 0) { users[idx].password = btoa(nw); DB.set('users', users); } ['set-cur-pw','set-new-pw','set-cf-pw'].forEach(function(id){ var el=document.getElementById(id);if(el)el.value=''; }); toast('✅ Password Updated','Your password has been changed. Please sign in again.'); setTimeout(function(){ logOut(); }, 1500); } /* ══ SEND SUPPORT MESSAGE ══ */ function sendSupportMsg(){ var subj = document.getElementById('sup-subject')?.value || 'General Query'; var msg = document.getElementById('sup-message')?.value.trim() || ''; if (!msg) { toast('⚠ Empty Message','Please describe your query before sending.'); return; } // Send via Web3Forms to support@travelhuge.com _sendW3F({ access_key: _W3F_KEY, subject: '[Support] ' + subj + ' — ' + (currentUser?.name || 'Member'), name: currentUser?.name || 'Member', email: currentUser?.email || '', message: msg }); document.getElementById('sup-message').value = ''; toast('✅ Message Sent!','Our team will reply to ' + (currentUser?.email||'your email') + ' within 4 hours.'); NOTIFICATIONS.add({ type:'support', title:'Support request sent', message:'We\'ve received your message about: ' + subj, read:false }); renderUserNotifications(); } /* ══ WISHLIST RENDER (init) ══ */ window.addEventListener('DOMContentLoaded',function(){ if(currentUser) wlRender(); }); /* ══════════════════════════════════════════ TRAVELHUGE v7 — NEW FEATURES ══════════════════════════════════════════ */ /* ── CLICK SOUNDS ── */ var _audioCtx = null; function getAudioCtx(){ if(!_audioCtx){ try{ _audioCtx = new (window.AudioContext||window.webkitAudioContext)(); }catch(e){} } return _audioCtx; } function playClick(type){ var ctx = getAudioCtx(); if(!ctx) return; try{ var osc = ctx.createOscillator(); var gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); if(type==='btn'){ osc.type='sine'; osc.frequency.setValueAtTime(880, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(440, ctx.currentTime+0.1); gain.gain.setValueAtTime(0.08, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime+0.12); osc.start(); osc.stop(ctx.currentTime+0.12); } else if(type==='nav'){ osc.type='sine'; osc.frequency.setValueAtTime(660, ctx.currentTime); gain.gain.setValueAtTime(0.05, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime+0.08); osc.start(); osc.stop(ctx.currentTime+0.08); } else if(type==='success'){ [523, 659, 784].forEach(function(freq, i){ var o2 = ctx.createOscillator(); var g2 = ctx.createGain(); o2.connect(g2); g2.connect(ctx.destination); o2.type='sine'; o2.frequency.value=freq; g2.gain.setValueAtTime(0, ctx.currentTime+i*0.08); g2.gain.linearRampToValueAtTime(0.07, ctx.currentTime+i*0.08+0.02); g2.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime+i*0.08+0.15); o2.start(ctx.currentTime+i*0.08); o2.stop(ctx.currentTime+i*0.08+0.15); }); } }catch(e){} } document.addEventListener('DOMContentLoaded', function(){ // Add click sound to all buttons document.addEventListener('click', function(e){ var el = e.target; if(el.tagName==='BUTTON'||el.tagName==='A'||el.classList.contains('fpill')||el.classList.contains('etab')||el.classList.contains('map-dest-tag')||el.classList.contains('chm-dest-item')||el.closest('.btn-gold')||el.closest('.btn-ghost')){ playClick('btn'); } }, true); }); /* ── MAP DESTINATION SEARCH ── */ var _mapDestData = { 'bali': {q:'Bali+Indonesia', pkgs:[ {dest:'Bali, Indonesia',name:'Bali Honeymoon Bliss',price:'₹89,999/couple'}, {dest:'Bali, Indonesia',name:'Bali Adventure Pack',price:'₹64,999/person'}, {dest:'Bali, Indonesia',name:'Bali Family Tour',price:'₹1,09,999/family'} ]}, 'maldives': {q:'Maldives', pkgs:[ {dest:'Maldives',name:'Maldives Overwater Escape',price:'₹1,24,999/couple'}, {dest:'Maldives',name:'Maldives Budget Retreat',price:'₹79,999/couple'} ]}, 'kerala': {q:'Kerala+India', pkgs:[ {dest:'Kerala, India',name:'Kerala God\'s Own Country',price:'₹42,999/family'}, {dest:'Kerala, India',name:'Kerala Honeymoon',price:'₹55,000/couple'} ]}, 'dubai': {q:'Dubai+UAE', pkgs:[ {dest:'Dubai, UAE',name:'Dubai Glamour Escape',price:'₹59,999/couple'}, {dest:'Dubai, UAE',name:'Dubai Family Fun',price:'₹84,999/family'} ]}, 'thailand': {q:'Thailand', pkgs:[ {dest:'Thailand',name:'Thailand Island Hopping',price:'₹65,999/couple'}, {dest:'Thailand',name:'Bangkok + Phuket',price:'₹55,999/person'} ]}, 'rajasthan': {q:'Rajasthan+India', pkgs:[ {dest:'Rajasthan, India',name:'Royal Rajasthan Tour',price:'₹38,999/person'} ]}, 'himachal': {q:'Himachal+Pradesh', pkgs:[ {dest:'Himachal Pradesh',name:'Himachal Adventure',price:'₹34,999/person'} ]}, 'andaman': {q:'Andaman+Islands', pkgs:[ {dest:'Andaman & Nicobar',name:'Andaman Beach Paradise',price:'₹44,999/couple'} ]}, 'goa': {q:'Goa+India', pkgs:[ {dest:'Goa, India',name:'Goa Sun & Chill',price:'₹19,999/person'} ]} }; function selectMapDest(el, destName, coords, key){ document.querySelectorAll('.map-dest-tag').forEach(function(t){t.classList.remove('sel');}); el.classList.add('sel'); document.getElementById('map-search-inp').value = destName; var lat = coords.split(',')[0], lng = coords.split(',')[1]; var frame = document.getElementById('map-frame'); frame.src = 'https://maps.google.com/maps?q='+lat+','+lng+'&z=8&output=embed&hl=en'; // Update package cards var data = _mapDestData[key]; if(data && data.pkgs){ var html=''; data.pkgs.forEach(function(p){ html+='
' +'
'+p.dest+'
' +'
'+p.name+'
' +'
'+p.price+'
' +'
'; }); document.getElementById('map-pkg-overlay').innerHTML=html; } playClick('nav'); } function filterMapDest(val){ var v = val.toLowerCase(); document.querySelectorAll('.map-dest-tag').forEach(function(t){ t.style.display = (!v || t.textContent.toLowerCase().includes(v)) ? '' : 'none'; }); } /* ── CUSTOM HOLIDAY MAP ── */ window._chmDest = 'Bali, Indonesia'; function selectChmDest(el, destName, coords, pkgName, price, nights){ document.querySelectorAll('.chm-dest-item').forEach(function(i){i.classList.remove('active');}); el.classList.add('active'); window._chmDest = destName; var lat = coords.split(',')[0], lng = coords.split(',')[1]; document.getElementById('chm-map').src = 'https://maps.google.com/maps?q='+lat+','+lng+'&z=7&output=embed&hl=en'; var info = document.getElementById('chm-selected-info'); info.style.display='block'; document.getElementById('chm-sel-name').textContent = pkgName; document.getElementById('chm-sel-price').textContent = price+' · '+nights; playClick('nav'); } /* ── TRAVEL INSURANCE ── */ var _insSelectedPlan = {type:'basic', price:'₹999'}; var _insStep = 1; function openInsuranceModal(){ _insStep=1; insShowStep(1); document.getElementById('ins-overlay').classList.add('open'); document.body.style.overflow='hidden'; playClick('btn'); } function closeInsuranceModal(){ document.getElementById('ins-overlay').classList.remove('open'); document.body.style.overflow=''; } function insShowStep(n){ _insStep=n; document.querySelectorAll('.ins-step').forEach(function(s){s.classList.remove('active');}); var step = document.getElementById('ins-step-'+n); if(step) step.classList.add('active'); // Update progress var bars = document.querySelectorAll('#ins-prog span'); bars.forEach(function(b,i){b.classList.toggle('done', i t.classList.remove('active')); document.querySelectorAll('.auth-form').forEach(f => f.classList.remove('active')); if(btn) btn.classList.add('active'); const form = document.getElementById(tab + 'Form'); if(form) form.classList.add('active'); const title = document.getElementById('authModalTitle'); const desc = document.getElementById('authModalDesc'); if(tab === 'signin') { if(title) title.textContent = 'Welcome Back'; if(desc) desc.textContent = 'Sign in securely to your account'; } else { if(title) title.textContent = 'Create Account'; if(desc) desc.textContent = 'Join thousands of happy travellers'; // Reset swipe button var sc = document.getElementById('swipeContainer'); if(sc) { sc.classList.remove('completed'); var thumb = document.getElementById('swipeThumb'); var fill = document.getElementById('swipeFill'); var contrail = document.getElementById('swipeContrail'); if(thumb) { thumb.style.transition=''; thumb.style.left='4px'; } if(fill) { fill.style.transition=''; fill.style.width='52px'; } if(contrail) { contrail.style.transition=''; contrail.style.width='0px'; } } // Reset error messages var msg = document.getElementById('signupMsg'); if(msg) { msg.className='auth-msg'; msg.textContent=''; } } } // ─── VALIDATION HELPERS ─────────────────────────────────────── const BLOCKED_DOMAINS = ['mailinator','guerrillamail','trashmail','10minutemail','yopmail','throwam','fakeinbox','tempmail','dispostable','sharklasers','maildrop','spamgourmet','getairmail','spambox','spam4','mytemp','owlpic','tempr','mailnesia']; const DISPOSABLE_PATTERNS = /^[a-z]{1,3}\d{4,}@|^test\d*@|^fake\d*@|^noreply@|^admin@|^bot\d*@/i; const COMMON_PASSWORDS = ['password','123456','password1','12345678','qwerty','abc123','letmein','welcome','monkey','dragon']; function isValidEmail(email) { // Strict RFC 5322 simplified - no special chars at start, proper domain const re = /^[a-zA-Z0-9][a-zA-Z0-9._%+\-]{0,63}@[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/; if (!re.test(email)) return { ok: false, msg: 'Enter a valid email address.' }; const domain = email.split('@')[1]?.toLowerCase() || ''; const domainName = domain.split('.')[0]; if (BLOCKED_DOMAINS.some(b => domain.includes(b))) return { ok: false, msg: 'Disposable email addresses are not allowed.' }; if (DISPOSABLE_PATTERNS.test(email)) return { ok: false, msg: 'Please use a real email address.' }; // Block sequential keyboard patterns if (/^(asdf|qwert|zxcv|1234|abcd)/i.test(email.split('@')[0])) return { ok: false, msg: 'Please enter your real email address.' }; return { ok: true }; } function isValidName(name) { name = name.trim(); if (name.length < 2) return { ok: false, msg: 'Name must be at least 2 characters.' }; if (name.length > 60) return { ok: false, msg: 'Name is too long.' }; if (!/^[a-zA-Z\u00C0-\u024F\u0900-\u097F\s\-'.]+$/.test(name)) return { ok: false, msg: 'Name should contain only letters, spaces, hyphens or apostrophes.' }; if (/\d/.test(name)) return { ok: false, msg: 'Name should not contain numbers.' }; // Detect keyboard smash: 5+ consonants in a row if (/[^aeiou\s]{6,}/i.test(name.replace(/[^a-z]/gi,''))) return { ok: false, msg: 'Please enter your real name.' }; return { ok: true }; } // ─── PASSWORD SHOW/HIDE TOGGLE ─────────────────────────────────────────── function togglePw(inputId, btn) { var input = document.getElementById(inputId); if (!input) return; var isHidden = input.type === 'password'; input.type = isHidden ? 'text' : 'password'; // Swap eye icon btn.innerHTML = isHidden ? '' : ''; btn.style.color = isHidden ? 'rgba(201,169,110,0.7)' : 'rgba(255,255,255,0.3)'; } function isValidPhone(phone) { phone = phone.trim(); if (!phone) return { ok: false, msg: 'Phone number is required.' }; const clean = phone.replace(/[\s\-\(\)\+]/g, ''); if (!/^\d{7,15}$/.test(clean)) return { ok: false, msg: 'Enter a valid phone number (7–15 digits).' }; if (/^(.)\1{6,}$/.test(clean)) return { ok: false, msg: 'Enter a real phone number.' }; return { ok: true }; } function getPasswordStrength(pass) { if (pass.length < 6) return { score: 0, label: 'Too short', color: '#f87171' }; let score = 0; if (pass.length >= 8) score++; if (pass.length >= 12) score++; if (/[A-Z]/.test(pass)) score++; if (/[a-z]/.test(pass)) score++; if (/[0-9]/.test(pass)) score++; if (/[^A-Za-z0-9]/.test(pass)) score++; if (COMMON_PASSWORDS.includes(pass.toLowerCase())) score = 1; if (score <= 2) return { score: score, label: 'Weak', color: '#f87171' }; if (score <= 4) return { score: score, label: 'Fair', color: '#fbbf24' }; if (score === 5) return { score: score, label: 'Strong', color: '#4ade80' }; return { score: score, label: 'Very Strong', color: '#22c55e' }; } function validateSignupName(input) { const res = isValidName(input.value); const hint = document.getElementById('nameHint'); if (!input.value) { input.className='auth-field input'; hint.textContent=''; return; } if (res.ok) { input.classList.remove('err'); input.classList.add('ok'); hint.style.color='#4ade80'; hint.textContent='✓ Looks good!'; } else { input.classList.remove('ok'); input.classList.add('err'); hint.style.color='#f87171'; hint.textContent='⚠ ' + res.msg; } } function validateSignupEmail(input) { const res = isValidEmail(input.value.trim()); const hint = document.getElementById('emailHint'); if (!input.value) { input.className=''; hint.textContent=''; return; } if (res.ok) { input.classList.remove('err'); input.classList.add('ok'); hint.style.color='#4ade80'; hint.textContent='✓ Email looks valid'; } else { input.classList.remove('ok'); input.classList.add('err'); hint.style.color='#f87171'; hint.textContent='⚠ ' + res.msg; } } function checkPassStrength(input) { const bar = document.getElementById('passBar'); const hint = document.getElementById('passHint'); if (!input.value) { bar.style.width='0%'; hint.textContent=''; return; } const s = getPasswordStrength(input.value); const pct = Math.max(10, Math.min(100, (s.score / 6) * 100)); bar.style.width = pct + '%'; bar.style.background = s.color; hint.style.color = s.color; hint.textContent = 'Strength: ' + s.label; } function checkPassMatch(input) { const pass = document.getElementById('signupPassword').value; const hint = document.getElementById('passMatchHint'); if (!input.value) { hint.textContent=''; return; } if (input.value === pass) { input.classList.remove('err'); input.classList.add('ok'); hint.style.color='#4ade80'; hint.textContent='✓ Passwords match'; } else { input.classList.remove('ok'); input.classList.add('err'); hint.style.color='#f87171'; hint.textContent='⚠ Passwords do not match'; } } // ─── SWIPE TO SUBMIT ───────────────────────────────────────── (function initSwipe() { let isDragging = false; let startX = 0; let currentX = 0; let maxSlide = 0; let completed = false; function getSwipeEl() { return { container: document.getElementById('swipeContainer'), thumb: document.getElementById('swipeThumb'), fill: document.getElementById('swipeFill'), contrail: document.getElementById('swipeContrail'), plane: document.getElementById('planeSvg') }; } function onStart(e) { const el = getSwipeEl(); if (!el.container || completed) return; if(!validateBeforeSwipe()) return; isDragging = true; startX = (e.touches ? e.touches[0].clientX : e.clientX); maxSlide = el.container.offsetWidth - 52; el.thumb.style.transition = 'none'; el.fill.style.transition = 'none'; el.contrail.style.transition = 'none'; e.preventDefault(); } function onMove(e) { if (!isDragging) return; const el = getSwipeEl(); const clientX = e.touches ? e.touches[0].clientX : e.clientX; currentX = Math.max(0, Math.min(maxSlide, clientX - startX)); const pct = currentX / maxSlide; el.thumb.style.left = (4 + currentX) + 'px'; el.fill.style.width = (52 + currentX) + 'px'; el.contrail.style.width = Math.max(0, currentX - 22) + 'px'; // Rotate plane with direction if(el.plane) el.plane.style.transform = `rotate(${pct * 15}deg)`; if (currentX >= maxSlide - 2) completeSwipe(); e.preventDefault(); } function onEnd() { if (!isDragging) return; isDragging = false; const el = getSwipeEl(); if (!completed) { el.thumb.style.transition = 'left 0.4s cubic-bezier(0.4,0,0.2,1)'; el.fill.style.transition = 'width 0.4s cubic-bezier(0.4,0,0.2,1)'; el.contrail.style.transition = 'width 0.4s ease'; el.thumb.style.left = '4px'; el.fill.style.width = '52px'; el.contrail.style.width = '0px'; if(el.plane) el.plane.style.transform = 'rotate(0deg)'; } } function completeSwipe() { if (completed) return; completed = true; const el = getSwipeEl(); el.container.classList.add('completed'); el.thumb.style.left = (el.container.offsetWidth - 48) + 'px'; el.fill.style.width = '100%'; // Trigger signup after brief animation setTimeout(() => { const result = handleSignup(); // If signup failed (returned false or didn't open OTP), reset swipe if (result === false) { completed = false; el.container.classList.remove('completed'); el.thumb.style.transition = 'left 0.4s cubic-bezier(0.4,0,0.2,1)'; el.fill.style.transition = 'width 0.4s cubic-bezier(0.4,0,0.2,1)'; el.thumb.style.left = '4px'; el.fill.style.width = '52px'; } }, 600); } function validateBeforeSwipe() { const name = document.getElementById('signupName')?.value.trim() || ''; const email = document.getElementById('signupEmail')?.value.trim() || ''; const pass = document.getElementById('signupPassword')?.value || ''; const confirm = document.getElementById('signupPassConfirm')?.value || ''; const terms = document.getElementById('termsCheck')?.checked; const msgEl = document.getElementById('signupMsg'); function showErr(msg) { if(msgEl){ msgEl.className='auth-msg err'; msgEl.textContent=msg; } return false; } const nameRes = isValidName(name); if (!nameRes.ok) return showErr('⚠ Name: ' + nameRes.msg); const emailRes = isValidEmail(email); if (!emailRes.ok) return showErr('⚠ Email: ' + emailRes.msg); const str = getPasswordStrength(pass); if (pass.length < 8) return showErr('⚠ Password must be at least 8 characters.'); if (str.score < 3) return showErr('⚠ Password is too weak. Mix uppercase, lowercase, and numbers.'); if (pass !== confirm) return showErr('⚠ Passwords do not match.'); if (!terms) return showErr('⚠ Please accept the Terms & Conditions to continue.'); if(msgEl) msgEl.className='auth-msg'; return true; } document.addEventListener('DOMContentLoaded', function() { setTimeout(function() { const thumb = document.getElementById('swipeThumb'); const container = document.getElementById('swipeContainer'); if (!thumb || !container) return; thumb.addEventListener('mousedown', onStart, { passive: false }); thumb.addEventListener('touchstart', onStart, { passive: false }); document.addEventListener('mousemove', onMove, { passive: false }); document.addEventListener('touchmove', onMove, { passive: false }); document.addEventListener('mouseup', onEnd); document.addEventListener('touchend', onEnd); // Reset swipe when modal reopens const observer = new MutationObserver(() => { if (!document.getElementById('authModal').classList.contains('active')) { completed = false; currentX = 0; const el = getSwipeEl(); if(el.container) el.container.classList.remove('completed'); if(el.thumb) el.thumb.style.left = '4px'; if(el.fill) el.fill.style.width = '52px'; if(el.contrail) el.contrail.style.width = '0px'; } }); const modal = document.getElementById('authModal'); if(modal) observer.observe(modal, { attributes: true, attributeFilter: ['class'] }); }, 500); }); })(); // ─── SWIPE TO SIGN IN ──────────────────────────────────────────── (function initLoginSwipe() { let isDragging = false, startX = 0, currentX = 0, maxSlide = 0, completed = false; function getEl() { return { container: document.getElementById('swipeLoginContainer'), thumb: document.getElementById('swipeLoginThumb'), fill: document.getElementById('swipeLoginFill'), contrail: document.getElementById('swipeLoginContrail'), plane: document.getElementById('planeLoginSvg') }; } function onStart(e) { const el = getEl(); if (!el.container || completed) return; isDragging = true; startX = (e.touches ? e.touches[0].clientX : e.clientX); maxSlide = el.container.offsetWidth - 52; el.thumb.style.transition = 'none'; el.fill.style.transition = 'none'; el.contrail.style.transition = 'none'; e.preventDefault(); } function onMove(e) { if (!isDragging) return; const el = getEl(); const clientX = e.touches ? e.touches[0].clientX : e.clientX; currentX = Math.max(0, Math.min(maxSlide, clientX - startX)); const pct = currentX / maxSlide; el.thumb.style.left = (4 + currentX) + 'px'; el.fill.style.width = (52 + currentX) + 'px'; el.contrail.style.width = Math.max(0, currentX - 22) + 'px'; if (el.plane) el.plane.style.transform = 'rotate(' + (pct * 15) + 'deg)'; if (currentX >= maxSlide - 2) completeSwipe(); e.preventDefault(); } function onEnd() { if (!isDragging) return; isDragging = false; const el = getEl(); if (!completed) { el.thumb.style.transition = 'left 0.4s cubic-bezier(0.4,0,0.2,1)'; el.fill.style.transition = 'width 0.4s cubic-bezier(0.4,0,0.2,1)'; el.contrail.style.transition = 'width 0.4s ease'; el.thumb.style.left = '4px'; el.fill.style.width = '52px'; el.contrail.style.width = '0px'; if (el.plane) el.plane.style.transform = 'rotate(0deg)'; } } function resetSwipe() { completed = false; currentX = 0; const el = getEl(); if (!el.container) return; el.container.classList.remove('completed'); el.thumb.style.transition = 'left 0.4s cubic-bezier(0.4,0,0.2,1)'; el.fill.style.transition = 'width 0.4s cubic-bezier(0.4,0,0.2,1)'; el.thumb.style.left = '4px'; el.fill.style.width = '52px'; el.contrail.style.width = '0px'; } function completeSwipe() { if (completed) return; completed = true; const el = getEl(); el.container.classList.add('completed'); el.thumb.style.left = (el.container.offsetWidth - 48) + 'px'; el.fill.style.width = '100%'; setTimeout(function() { handleSignin(); setTimeout(resetSwipe, 1800); }, 500); } document.addEventListener('DOMContentLoaded', function() { setTimeout(function() { const thumb = document.getElementById('swipeLoginThumb'); const container = document.getElementById('swipeLoginContainer'); if (!thumb || !container) return; thumb.addEventListener('mousedown', onStart, { passive: false }); thumb.addEventListener('touchstart', onStart, { passive: false }); document.addEventListener('mousemove', onMove, { passive: false }); document.addEventListener('touchmove', onMove, { passive: false }); document.addEventListener('mouseup', onEnd); document.addEventListener('touchend', onEnd); }, 500); }); })(); // ─── OTP SYSTEM ──────────────────────────────────────────────── let _otpCode = ''; let _otpEmail = ''; let _otpName = ''; let _otpPass = ''; let _otpTimer = null; let _otpAttempts = 0; const MAX_OTP_ATTEMPTS = 5; const OTP_EXPIRY_SECS = 300; // 5 min function generateOTP() { // Cryptographically random 6-digit code const arr = new Uint32Array(1); crypto.getRandomValues(arr); return String(arr[0] % 900000 + 100000); } function openOTPOverlay(email, name, pass) { _otpEmail = email.toLowerCase().trim(); // always store lowercase _otpName = name; _otpPass = pass; _otpCode = generateOTP(); _otpAttempts = 0; document.getElementById('otpEmailDisplay').textContent = email; // Clear boxes for(let i=0;i<6;i++) { const b = document.getElementById('otp'+i); if(b) { b.value=''; b.className='otp-box'; } } document.getElementById('otpMsg').className='otp-msg'; document.getElementById('otpVerifyBtn').disabled = true; document.getElementById('otpSuccess').classList.remove('show'); document.getElementById('otpOverlay').classList.add('open'); document.getElementById('otp0').focus(); startOTPTimer(); // Simulate sending — in production, call your backend API here sendAuthOTPEmail(_otpEmail, _otpName, _otpCode); toast('📧 Verification code sent!', 'Check ' + email + ' for your 6-digit code.'); try { console.log('%c[TravelHuge] OTP sent to ' + _otpEmail, 'color:#c9a96e;font-weight:bold'); } catch(e) {} } function closeOTPOverlay() { // Stop the timer if (typeof _otpTimer !== 'undefined' && _otpTimer) { clearInterval(_otpTimer); _otpTimer = null; } // Invalidate any pending code _otpCode = ''; // Hide overlay var overlay = document.getElementById('otpOverlay'); if (overlay) overlay.classList.remove('open'); // Reset swipe button so user can try again var sc = document.getElementById('swipeContainer'); if (sc) { sc.classList.remove('completed'); var thumb = document.getElementById('swipeThumb'); var fill = document.getElementById('swipeFill'); if (thumb) { thumb.style.transition = ''; thumb.style.left = '4px'; } if (fill) { fill.style.transition = ''; fill.style.width = '52px'; } } } // Close OTP with Escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { var overlay = document.getElementById('otpOverlay'); if (overlay && overlay.classList.contains('open')) closeOTPOverlay(); } }); function startOTPTimer() { if (_otpTimer) clearInterval(_otpTimer); let secs = OTP_EXPIRY_SECS; const timerEl = document.getElementById('otpTimer'); const resendBtn = document.getElementById('otpResendBtn'); resendBtn.style.pointerEvents = 'none'; resendBtn.style.opacity = '0.4'; function update() { const m = Math.floor(secs/60), s = secs%60; if(timerEl) timerEl.textContent = `Code expires in ${m}:${s.toString().padStart(2,'0')}`; if (secs === 0) { clearInterval(_otpTimer); if(timerEl) timerEl.textContent = 'Code expired. Please request a new one.'; resendBtn.style.pointerEvents = 'auto'; resendBtn.style.opacity = '1'; _otpCode = ''; // invalidate } secs--; } update(); _otpTimer = setInterval(update, 1000); // Re-enable resend after 30s setTimeout(() => { resendBtn.style.pointerEvents = 'auto'; resendBtn.style.opacity = '1'; }, 30000); } function otpInput(el, idx) { // Only allow digits el.value = el.value.replace(/[^0-9]/g,'').slice(-1); if (el.value) { el.classList.add('filled'); if (idx < 5) document.getElementById('otp'+(idx+1))?.focus(); } else { el.classList.remove('filled'); } // Enable verify when all 6 filled const all = Array.from({length:6}, (_,i) => document.getElementById('otp'+i)?.value || ''); document.getElementById('otpVerifyBtn').disabled = all.some(v=>!v); } function otpKeydown(e, idx) { if (e.key === 'Backspace' && !document.getElementById('otp'+idx).value && idx > 0) { document.getElementById('otp'+(idx-1))?.focus(); } // Block non-numeric except control keys if (!['Backspace','Delete','Tab','ArrowLeft','ArrowRight'].includes(e.key) && !/^[0-9]$/.test(e.key)) { e.preventDefault(); } } function otpPaste(e) { e.preventDefault(); const text = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g,''); for(let i=0;i<6&&i document.getElementById('otp'+i)?.value || ''); document.getElementById('otpVerifyBtn').disabled = all.some(v=>!v); document.getElementById('otp'+Math.min(5,text.length-1))?.focus(); } function verifyOTP() { if (_otpAttempts >= MAX_OTP_ATTEMPTS) { showOTPMsg('⚠ Too many failed attempts. Please request a new code.', 'err'); return; } const entered = Array.from({length:6}, (_,i) => document.getElementById('otp'+i)?.value || '').join(''); if (!_otpCode) { showOTPMsg('⚠ Code has expired. Please request a new one.', 'err'); return; } if (entered === _otpCode) { // ✅ SUCCESS clearInterval(_otpTimer); completeRegistration(); } else { _otpAttempts++; for(let i=0;i<6;i++) { const b = document.getElementById('otp'+i); if(b) { b.classList.add('err'); setTimeout(()=>b.classList.remove('err'),600); } } const left = MAX_OTP_ATTEMPTS - _otpAttempts; showOTPMsg(`⚠ Incorrect code. ${left} attempt${left===1?'':'s'} remaining.`, 'err'); } } function completeRegistration() { // Show success overlay inside OTP card document.getElementById('otpSuccess').classList.add('show'); // Register user in DB var users = DB.get('users') || []; var isNew = !users.find(function(u){ return u.email === _otpEmail; }); if (isNew) { var user = { id: Date.now(), name: _otpName, email: _otpEmail.toLowerCase(), password: btoa(_otpPass), isAdmin: false, emailVerified: true, createdAt: new Date().toISOString() }; DB.push('users', user); currentUser = { name: _otpName, email: _otpEmail.toLowerCase(), isAdmin: false }; localStorage.setItem('th_user', JSON.stringify(currentUser)); updateNavLoggedIn(); } // Add welcome notification NOTIFICATIONS.add({ type:'welcome', title:'🎉 Welcome to TravelHuge!', message:'Your account is verified and active. Welcome aboard, ' + _otpName + '!', read:false }); // Send warm welcome email in background if (isNew) { _sendWelcomeEmail(_otpEmail, _otpName); // Notify admin: email + WhatsApp _notifyAdminNewSignup(_otpName, _otpEmail); } // Close OTP overlay → show welcome modal setTimeout(function() { document.getElementById('otpOverlay').classList.remove('open'); closeAuth(); // Show on-site welcome modal setTimeout(function() { showWelcomeModal(_otpName); }, 400); }, 2000); } // ─── On-site welcome modal ──────────────────────────────────────────────── function showWelcomeModal(name) { var modal = document.getElementById('welcomeModal'); if (!modal) return; var el = document.getElementById('wm-name'); if (el) el.textContent = 'Welcome, ' + (name.split(' ')[0] || name) + '! 🎉'; modal.style.display = 'flex'; } function closeWelcomeModal() { var modal = document.getElementById('welcomeModal'); if (modal) modal.style.display = 'none'; } // ─── Warm welcome email ─────────────────────────────────────────────────── function _sendWelcomeEmail(toEmail, toName) { var firstName = toName.split(' ')[0] || toName; var html = _buildWelcomeEmailHTML(toName); var plain = 'Dear ' + firstName + ',\n\nWelcome to TravelHuge!\n\nYour account is now active. We are thrilled to have you on board.\n\nStart exploring our packages at travelhuge.com\n\nWarm regards,\nML Sharma\nFounder, TravelHuge\nsupport@travelhuge.com'; // Try EmailJS function tryEJS() { return new Promise(function(resolve, reject) { if (typeof emailjs === 'undefined') return reject(new Error('no SDK')); try { emailjs.init(_EJS_KEY); } catch(e) {} emailjs.send(_EJS_SERVICE, _EJS_TEMPLATE, { to_email: toEmail, to_name: firstName, from_name: 'ML Sharma — TravelHuge', reply_to: 'support@travelhuge.com', subject: '\u2708 Welcome to TravelHuge, ' + firstName + '!', html_message: html, message: plain }).then(resolve).catch(reject); }); } // Try Web3Forms function tryW3F() { return fetch('https://api.web3forms.com/submit', { method:'POST', headers:{'Content-Type':'application/json','Accept':'application/json'}, body: JSON.stringify({ access_key: _W3F_KEY, from_name: 'TravelHuge', email: toEmail, subject: '\u2708 Welcome to TravelHuge, ' + firstName + '!', replyto: 'support@travelhuge.com', message: plain, botcheck: false }) }).then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); }) .then(function(r){ if(!r.success) throw new Error(r.message||'failed'); }); } tryEJS().catch(function(){ tryW3F().catch(function(){}); }); } // ─── Admin new-signup notification (email + WhatsApp) ──────────────────── function _notifyAdminNewSignup(name, email) { var ts = new Date().toLocaleString('en-IN', { dateStyle:'medium', timeStyle:'short' }); var waNum = '919818644324'; // TravelHuge WhatsApp business number // ── 1. Fetch user's approximate location from IP ── fetch('https://ipapi.co/json/') .then(function(r){ return r.json(); }) .catch(function(){ return {}; }) .then(function(geo) { var city = geo.city || 'Unknown City'; var region = geo.region || ''; var country = geo.country_name || 'Unknown Country'; var ip = geo.ip || 'N/A'; var isp = geo.org || ''; var location = [city, region, country].filter(Boolean).join(', '); // ── 2. Send admin notification email via Web3Forms ── var adminSubject = '\uD83D\uDC64 New Sign-Up: ' + name + ' | TravelHuge'; var adminMsg = 'NEW USER SIGN-UP ALERT\n' + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' + 'Name : ' + name + '\n' + 'Email : ' + email + '\n' + 'Time : ' + ts + '\n' + 'Location: ' + location + '\n' + 'IP : ' + ip + '\n' + (isp ? 'ISP : ' + isp + '\n' : '') + '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + 'View dashboard: travelhuge.com (admin login)\n'; var adminHtml = '' + '' + '
' + '' + // Top bar '' + // Header '' + // Alert banner '' + // Details '' + // Footer '' + '' + '
' + '
✈ TRAVELHUGE
' + '
Admin Alert
' + '
' + '
🎉 New User Signed Up!
' + '
Someone just created a TravelHuge account.
' + '
' + '' + _adminDetailRow('👤 Name', name) + _adminDetailRow('📧 Email', email) + _adminDetailRow('🕐 Time', ts) + _adminDetailRow('📍 Location', location) + _adminDetailRow('🌐 IP Address', ip) + (isp ? _adminDetailRow('📡 ISP', isp) : '') + '
' + '
' + '

TravelHuge Admin System  ·  support@travelhuge.com

' + '
'; // Send via Web3Forms to support@travelhuge.com fetch('https://api.web3forms.com/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ access_key: _W3F_KEY, from_name: 'TravelHuge System', subject: adminSubject, message: adminMsg, botcheck: false }) }).catch(function(){}); // Also try EmailJS for HTML email to admin if (typeof emailjs !== 'undefined') { try { emailjs.init(_EJS_KEY); } catch(e) {} emailjs.send(_EJS_SERVICE, _EJS_TEMPLATE, { to_email: 'support@travelhuge.com', to_name: 'TravelHuge Admin', from_name: 'TravelHuge System', reply_to: email, subject: adminSubject, html_message: adminHtml, message: adminMsg }).catch(function(){}); } // ── 3. WhatsApp notification via wa.me click-to-chat API ── // Uses WhatsApp Business API (free tier via wa.me) var waMsg = encodeURIComponent( '\uD83D\uDC64 *New TravelHuge Sign-Up*\n\n' + '*Name:* ' + name + '\n' + '*Email:* ' + email + '\n' + '*Location:* ' + location + '\n' + '*Time:* ' + ts + '\n\n' + '_Sent by TravelHuge system_' ); // Open silently in hidden iframe to trigger WhatsApp message // (Works with WhatsApp Business API; for personal numbers use wa.me link) try { var waFrame = document.createElement('iframe'); waFrame.style.cssText = 'position:absolute;width:0;height:0;border:0;opacity:0;pointer-events:none;'; waFrame.src = 'https://api.whatsapp.com/send?phone=' + waNum + '&text=' + waMsg + '&app_absent=0'; document.body.appendChild(waFrame); setTimeout(function(){ if(waFrame.parentNode) waFrame.parentNode.removeChild(waFrame); }, 5000); } catch(e) {} }); } // Helper: admin email detail row function _adminDetailRow(label, value) { return '' + '' + label + '' + '' + value + '' + ''; } function _buildWelcomeEmailHTML(name) { var firstName = name.split(' ')[0] || name; return '' + '' + '' + '
' + '' + '' // Header + '' // Gold divider + '' // Body + '' // Divider + '' // Footer + '' + '' + '
' + '
' + '
TRAVELHUGE
' + '
Design & Book Your Dream Journey
' + '
' + '

Dear ' + firstName + ',

' + '

A very warm welcome from all of us at TravelHuge! We are truly delighted to have you as part of our travel family. Your account is now fully active and ready for your first adventure.

' // Feature boxes + '' + '' + '' + '' + '
' + '
' + '
Custom Packages
' + '
Tailored to your dream
' + '
' + '
' + '
4-Hour Quotes
' + '
Fast, personalised
' + '
' + '
🛡
' + '
Trusted & Safe
' + '
10,000+ happy trips
' + '
' + '

Ready to start planning? Browse our curated packages — from romantic honeymoons to family adventures, international escapes to spiritual journeys. Just share your dream and we\'ll do the rest.

' // CTA button + '
' + '✈ Explore Packages' + '
' // Personal note + '
' + '

"At TravelHuge, every journey we plan carries a piece of our heart. We do not just book trips — we craft memories that last a lifetime. Welcome to the family, \' + firstName + \'."

' + '

— ML Sharma, Founder & Director

' + '
' + '
' + '

Questions? We\'re always here for you.

' + 'support@travelhuge.com' + '·' + 'WhatsApp Us' + '

© 2025 TravelHuge  ·  Faridabad, Haryana, India

' + '
'; } function resendOTP() { _otpCode = generateOTP(); _otpAttempts = 0; for(let i=0;i<6;i++) { const b = document.getElementById('otp'+i); if(b){ b.value=''; b.className='otp-box'; } } document.getElementById('otpVerifyBtn').disabled = true; document.getElementById('otpMsg').className='otp-msg'; startOTPTimer(); toast('📧 New code sent!', 'Check ' + _otpEmail); sendAuthOTPEmail(_otpEmail, _otpName, _otpCode); try { console.log('[TravelHuge] OTP resent to ' + _otpEmail); } catch(e) {} } // ─── SEND OTP EMAIL ───────────────────────────────────────────────────────── // Strategy: fire in-modal fallback IMMEDIATELY so user is never blocked. // Then attempt EmailJS + Web3Forms silently in background. // If either succeeds, clear the fallback UI. function sendAuthOTPEmail(toEmail, toName, code) { var firstName = toName.split(' ')[0] || toName; var html = buildOTPEmailHTML(toName, code); var plainText = 'Hello ' + firstName + ',\n\n' + 'Your TravelHuge verification code is: ' + code + '\n\n' + 'This code expires in 5 minutes. Do not share it with anyone.\n\n' + '— Team TravelHuge | support@travelhuge.com'; // ── STEP 1: Show code in message area (not pre-filled in boxes) ── setTimeout(function() { _showCodeFallback(code); }, 600); // ── STEP 2: Try to send real email in background (silent) ── _trySendEmailBackground(toEmail, firstName, code, html, plainText); } // Silently attempt email delivery — clears pre-fill if successful function _trySendEmailBackground(toEmail, firstName, code, html, plainText) { // ── Try EmailJS SDK first ── function tryEmailJS() { return new Promise(function(resolve, reject) { if (typeof emailjs === 'undefined') { return reject(new Error('EmailJS SDK not loaded')); } try { emailjs.init(_EJS_KEY); } catch(e) {} emailjs.send(_EJS_SERVICE, _EJS_TEMPLATE, { to_email: toEmail, to_name: firstName, from_name: 'TravelHuge', reply_to: 'support@travelhuge.com', subject: '\u2708 ' + code + ' \u2014 Your TravelHuge verification code', html_message: html, message: plainText }).then(resolve).catch(reject); }); } // ── Try Web3Forms second ── function tryWeb3Forms() { return fetch('https://api.web3forms.com/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ access_key: _W3F_KEY, from_name: 'TravelHuge', subject: '\u2708 ' + code + ' \u2014 Your TravelHuge verification code', email: toEmail, replyto: 'support@travelhuge.com', message: plainText, botcheck: false }) }) .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) .then(function(r) { if (!r.success) throw new Error(r.message || 'failed'); }); } tryEmailJS() .then(function() { console.log('[TravelHuge] OTP email delivered via EmailJS to ' + toEmail); // Email sent — clear the pre-fill notice, keep boxes filled _clearOTPFallbackNotice(toEmail); }) .catch(function(e1) { console.warn('[TravelHuge] EmailJS failed:', e1.message, '\u2014 trying Web3Forms'); tryWeb3Forms() .then(function() { console.log('[TravelHuge] OTP email delivered via Web3Forms to ' + toEmail); _clearOTPFallbackNotice(toEmail); }) .catch(function(e2) { // Both failed — user already has the pre-filled boxes, nothing more to do console.warn('[TravelHuge] Web3Forms also failed:', e2.message, '\u2014 user has pre-filled code'); }); }); } // ─── Show code in message area as fallback (user still types it) ───────── function _showCodeFallback(code) { var msg = document.getElementById('otpMsg'); if (msg) { msg.style.cssText = [ 'display:block', 'background:rgba(201,169,110,0.07)', 'border:1px solid rgba(201,169,110,0.25)', 'border-radius:10px', 'padding:13px 16px', 'margin-top:10px', 'font-size:13px', 'color:#e8c98a', 'line-height:1.7', 'text-align:center' ].join(';'); msg.innerHTML = '
Email delivery issue — use this code
' + '
' + code + '
' + '
Type the code above into the boxes
'; } } // ─── Pre-fill boxes (kept for backward compat — not called automatically) ── function _fillOTPBoxes(code) { for (var i = 0; i < 6; i++) { var box = document.getElementById('otp' + i); if (box) { box.value = code[i] || ''; box.classList.add('filled'); } } var btn = document.getElementById('otpVerifyBtn'); if (btn) btn.disabled = false; } // ─── Called if email actually delivered — clears the "pre-filled" notice ── function _clearOTPFallbackNotice(toEmail) { var msg = document.getElementById('otpMsg'); if (msg) { msg.style.cssText = [ 'display:block', 'background:rgba(74,222,128,0.07)', 'border:1px solid rgba(74,222,128,0.2)', 'border-radius:10px', 'padding:11px 14px', 'margin-top:10px', 'font-size:12px', 'color:#4ade80', 'line-height:1.65' ].join(';'); msg.innerHTML = '✓  Verification email sent to ' + toEmail + ''; } } // ─── In-modal fallback: show code visibly, never auto-fill ─────────────── function _showOTPInModalFallback(toEmail, code) { _showCodeFallback(code); } function buildOTPEmailHTML(name, code) { var firstName = name.split(' ')[0] || name; // Render each digit as a styled box var digits = code.split('').map(function(d) { return '' + '
' + d + '
' + ''; }).join(''); return '' + '' + '' + '' + 'Verify your TravelHuge account' + '' + '' + '' + '
' // ── Outer card ── + '' // ── Gold top bar ── + '' // ── Header ── + '' // ── Golden divider ── + '' // ── Body ── + '' // ── Divider ── + '' // ── Footer ── + '' // ── Gold bottom bar ── + '' + '
' + '
' + '
' + '
TRAVELHUGE
' + '
Design & Book Your Dream Journey
' + '
' + '
' // Greeting + '

' + 'Hi ' + firstName + ' 👋

' + '

' + 'Thank you for joining TravelHuge — India\'s premium custom travel platform. ' + 'Use the verification code below to confirm your email address and activate your account.' + '

' // Code box + '' + '
' + '

Your Verification Code

' + '' + digits + '
' + '

⌛ Expires in 5 minutes  ·  Do not share this code with anyone

' + '
' // CTA note + '

' + 'Enter this code on the TravelHuge verification screen to complete your registration. ' + 'Once verified, you\'ll have full access to personalised packages, exclusive deals, and our 24/7 travel concierge.' + '

' // Security notice + '' + '
' + '

' + '🔒 Security Notice: ' + 'TravelHuge will never ask for your password, OTP, or card details via phone, WhatsApp, or chat. ' + 'If you did not create this account, please ignore this email — no action is needed.' + '

' + '
' + '' + '' + '' + '
' + 'support@travelhuge.com' + '·' + '+91 0129-4324324' + '
' + '

' + '© 2025 TravelHuge  ·  Faridabad, Haryana, India
' + 'You\'re receiving this because you created a TravelHuge account.' + '

' + '
' + ' '; } function showOTPMsg(msg, type) { const el = document.getElementById('otpMsg'); el.textContent = msg; el.className = 'otp-msg ' + type; } function showAdminGuide() { document.getElementById('adminGuideModal').style.display = 'flex'; } function showTermsModal() { document.getElementById('termsModal').style.display='flex'; } // ─── SIGN IN ──────────────────────────────────────────────────── function handleSignin() { const email = (document.getElementById('signinEmail')?.value||'').trim().toLowerCase(); const pass = document.getElementById('signinPassword')?.value||''; const msgEl = document.getElementById('signinMsg'); function showErr(m){ msgEl.className='auth-msg err'; msgEl.textContent=m; } if (!email || !pass) return showErr('⚠ Please fill in all fields.'); const emailRes = isValidEmail(email); if (!emailRes.ok) return showErr('⚠ ' + emailRes.msg); loginUser(email, pass); } // ─── SIGN UP ──────────────────────────────────────────────────── function handleSignup() { const name = (document.getElementById('signupName')?.value||'').trim(); const email = (document.getElementById('signupEmail')?.value||'').trim().toLowerCase(); const pass = document.getElementById('signupPassword')?.value||''; const confirm = document.getElementById('signupPassConfirm')?.value||''; const msgEl = document.getElementById('signupMsg'); function showErr(m){ msgEl.className='auth-msg err'; msgEl.textContent=m; if(document.getElementById('swipeContainer')) document.getElementById('swipeContainer').classList.remove('completed'); return false; } const nameRes = isValidName(name); if (!nameRes.ok) return showErr('⚠ Name: ' + nameRes.msg); const emailRes = isValidEmail(email); if (!emailRes.ok) return showErr('⚠ Email: ' + emailRes.msg); if (pass.length < 8) return showErr('⚠ Password must be at least 8 characters.'); const str = getPasswordStrength(pass); if (str.score < 3) return showErr('⚠ Password too weak. Use uppercase, lowercase, and numbers.'); if (pass !== confirm) return showErr('⚠ Passwords do not match.'); if (!document.getElementById('termsCheck')?.checked) return showErr('⚠ Please accept Terms & Conditions.'); // Check if already registered const users = DB.get('users')||[]; if (users.find(u=>u.email===email)) return showErr('⚠ This email is already registered. Try signing in.'); msgEl.className='auth-msg'; closeAuth(); setTimeout(() => openOTPOverlay(email, name, pass), 400); } // Legacy aliases for backward compat function handleGoogleSignin() {} function handleGoogleSignup() {} function showGoogleAuthInfo() {} function toggleNotifications() { const panel = document.getElementById('notificationsPanel'); if (panel) { panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; renderNotifications(); } } function renderNotifications() { const notifsList = NOTIFICATIONS.get(); const dropdown = document.getElementById('notificationsDropdown'); if (!dropdown) return; if (notifsList.length === 0) { dropdown.innerHTML = '

No notifications

'; return; } let html = ''; notifsList.slice(0, 5).forEach(notif => { html += '
'; html += '
' + notif.title + '
'; html += '
' + notif.message + '
'; html += ''; html += '
'; }); dropdown.innerHTML = html; } // Close notification panel when clicking outside document.addEventListener('click', function(e) { const panel = document.getElementById('notificationsPanel'); const btn = document.querySelector('button[onclick="toggleNotifications()"]'); if (panel && !panel.contains(e.target) && e.target !== btn) { panel.style.display = 'none'; } }); function registerUser(name, email, password) { // ── Real API signup ───────────────────────────────────── TH_Auth.signup(name, email, password).then(function(user){ currentUser = {name: user.name, email: user.email, isAdmin: false, role: 'customer'}; localStorage.setItem('th_user', JSON.stringify(currentUser)); updateNavLoggedIn(); closeAuth(); toast('🎉 Account Created!', 'Welcome to TravelHuge, ' + user.name.split(' ')[0] + '!'); setTimeout(function(){ window.location.href = 'user-dashboard.html'; }, 1000); }).catch(function(err){ showAuthMsg('signupMsg', '⚠️ ' + (err.message || 'Could not create account.'), 'err'); }); return true; } function showVerificationPending(name, email, token) { closeAuth(); const existing = document.getElementById('verif-pending-modal'); if (existing) existing.remove(); const m = document.createElement('div'); m.id = 'verif-pending-modal'; m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.9);z-index:99992;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(10px);'; m.innerHTML = `
📧

Verify Your Email

Hi ${name} — one last step!

We've sent a verification link to
${email}

Click the link in that email to activate your account. Your account will not be created until you verify.

📌 Didn't receive it? Check your spam folder. The link expires in 24 hours.

`; document.body.appendChild(m); // Also send simulated notification NOTIFICATIONS.add({ type:'verification', title:'Verify Your Email', message:'Verification link sent to ' + email + '. Check your inbox.', read:false }); toast('📧 Verification email sent!', 'Check ' + email); } function resendVerificationEmail(email) { const pending = DB.get('pending_users') || []; const p = pending.find(u => u.email === email); if (!p) { toast('⚠️ No pending account', 'Please sign up again.'); return; } toast('📧 Resent!', 'Check ' + email + ' for the verification link.'); NOTIFICATIONS.add({ type:'verification', title:'Verification Resent', message:'New verification link sent to ' + email, read:false }); } function simulateVerifyEmail(token) { // In production this is triggered by clicking the link in the email const pending = DB.get('pending_users') || []; const p = pending.find(u => u.token === token); if (!p) { toast('⚠️ Link expired', 'Please sign up again.'); return; } // Now create the real account const users = DB.get('users') || []; if (users.find(u => u.email === p.email)) { toast('✅ Already verified', 'Please sign in.'); return; } const user = { id: Date.now(), name: p.name, email: p.email, password: p.password, isAdmin: false, emailVerified: true, createdAt: p.createdAt }; DB.push('users', user); // Remove from pending DB.set('pending_users', pending.filter(u => u.token !== token)); // Log in currentUser = { name: user.name, email: user.email, isAdmin: false }; localStorage.setItem('th_user', JSON.stringify(currentUser)); updateNavLoggedIn(); NOTIFICATIONS.add({ type:'welcome', title:'🎉 Welcome to TravelHuge!', message:'Your account is verified and active. Welcome, ' + user.name + '!', read:false }); toast('🎉 Email Verified!', 'Welcome to TravelHuge, ' + user.name + '!'); setTimeout(() => window.location.href='user-dashboard.html', 1200); } function loginUser(email, password) { const emailLower = (email||'').trim().toLowerCase(); // ── Real API login ────────────────────────────────────── TH_Auth.login(emailLower, password).then(function(user){ currentUser = {name: user.name, email: user.email, isAdmin: user.role === 'admin', role: user.role}; localStorage.setItem('th_user', JSON.stringify(currentUser)); updateNavLoggedIn(); closeAuth(); toast('✅ Welcome back, ' + user.name.split(' ')[0] + '!', 'Redirecting to your dashboard…'); setTimeout(function(){ if(currentUser.isAdmin) window.location.href = 'admin-panel.html'; else window.location.href = 'user-dashboard.html'; }, 900); }).catch(function(err){ showAuthMsg('signinMsg', '⚠️ ' + (err.message || 'Invalid email or password.'), 'err'); }); return true; // async, always return true to avoid premature UI change } function showAuthMsg(id, msg, type) { let el = document.getElementById(id); if (!el) { el = document.createElement('div'); el.id = id; el.style.cssText = 'font-size:12px;padding:8px 12px;border-radius:8px;margin-top:8px;text-align:center;'; const formId = id === 'signinMsg' ? 'signinForm' : 'signupForm'; const form = document.getElementById(formId); if (form) form.appendChild(el); } el.textContent = msg; el.style.background = type === 'err' ? 'rgba(248,113,113,0.1)' : 'rgba(74,222,128,0.1)'; el.style.color = type === 'err' ? '#f87171' : '#4ade80'; el.style.border = '1px solid ' + (type === 'err' ? 'rgba(248,113,113,0.3)' : 'rgba(74,222,128,0.3)'); } function handleGoogleSignin() { // Show Google OAuth instructions modal since OAuth requires backend setup showGoogleAuthInfo(); } function handleGoogleSignup() { // Show Google OAuth instructions modal since OAuth requires backend setup showGoogleAuthInfo(); } function showGoogleAuthInfo() { closeAuth(); // Create and show info modal const existing = document.getElementById('google-info-modal'); if (existing) { existing.classList.add('open'); return; } const m = document.createElement('div'); m.id = 'google-info-modal'; m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:99990;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(8px);'; m.innerHTML = `
🔐

Google Sign-In Setup Required

To enable Google Sign-In, your Google OAuth Client ID must be verified in Google Cloud Console with your live domain travelhuge.com as an authorised origin.

For now, please use email/password registration — it's quick, secure, and just as easy!

`; m.classList.add('open'); document.body.appendChild(m); m.addEventListener('click', (e) => { if (e.target === m) m.remove(); }); } function handleGoogleLogin(name, email) { let users = DB.get('users') || []; let user = users.find(u => u.email === email); if (!user) { user = { id: Date.now(), name, email, password: btoa('google_oauth'), createdAt: new Date().toISOString(), provider: 'google', emailVerified: true }; DB.push('users', user); } currentUser = user; DB.set('currentUser', user); updateNavLoggedIn(); } document.addEventListener('DOMContentLoaded', function() { updateNavLoggedIn(); // Check if URL has a verification token (simulate clicking emailed link) const urlParams = new URLSearchParams(window.location.search); const verifyToken = urlParams.get('verify'); if (verifyToken) { setTimeout(() => simulateVerifyEmail(verifyToken), 600); } // Legacy listeners (now handled by new auth system) }); // ════════════════════════════════════════════════════════════ /* ── NOTIFICATIONS & COUPONS ── */ // ════════════════════════════════════════════════════════════ // NOTIFICATION & EMAIL VERIFICATION SYSTEM // ════════════════════════════════════════════════════════════ const NOTIFICATIONS = { get() { return DB.get('notifications') || []; }, add(notification) { const notifs = this.get(); notifs.unshift({ id: Date.now(), ...notification, timestamp: new Date().toISOString() }); DB.set('notifications', notifs); updateNotificationBadge(); }, remove(id) { const notifs = this.get().filter(n => n.id !== id); DB.set('notifications', notifs); updateNotificationBadge(); } }; const COUPONS = { get() { return DB.get('coupons') || []; }, getActive() { return this.get().filter(c => c.active && new Date(c.expiry) > new Date()); } }; // Initialize default coupons if (DB.get('coupons') === null) { DB.set('coupons', [ { id: 1, code: 'WELCOME20', discount: 20, expiry: '2025-12-31', description: 'Welcome Offer - 20% Off', active: true }, { id: 2, code: 'SAVE50', discount: 50, expiry: '2025-12-31', description: 'Save 50 on bookings above 5000', active: true } ]); } function updateNotificationBadge() { const badge = document.getElementById('notificationBadge'); const count = NOTIFICATIONS.get().length; if (badge) { if (count > 0) { badge.style.display = 'inline-block'; badge.textContent = count; } else { badge.style.display = 'none'; } } } function sendEmailVerification(email) { // Simulate sending verification email NOTIFICATIONS.add({ type: 'verification', title: 'Verify Your Email', message: 'Verification email sent to ' + email + '. Check your inbox.', read: false }); alert('Verification email sent to ' + email); } function updateDashboardCoupons() { const couponsDiv = document.querySelector('[data-section="coupons"]'); if (!couponsDiv) return; const activeCoupons = COUPONS.getActive(); let html = '

Current Offers & Coupons

'; if (activeCoupons.length === 0) { html += '

No active coupons at the moment.

'; } else { html += '
'; activeCoupons.forEach(coupon => { html += '
' + '
' + '

' + coupon.code + '

' + '

' + coupon.description + '

' + '
' + '
' + coupon.discount + '%
' + '' + '
'; }); html += '
'; } couponsDiv.innerHTML = html; } function copyCouponCode(code) { navigator.clipboard.writeText(code); alert('Coupon code copied: ' + code); } // Initialize dashboard on load document.addEventListener('DOMContentLoaded', function() { updateNotificationBadge(); updateDashboardCoupons(); }); /* ════════ CUSTOM SELECT INIT ════════ */ (function(){ function initCustomSelect(sel){ if(sel._cselInit) return; sel._cselInit = true; var wrap = document.createElement('div'); wrap.className = 'csel-wrap'; var parent = sel.parentNode; parent.insertBefore(wrap, sel); wrap.appendChild(sel); var trigger = document.createElement('div'); trigger.className = 'csel-trigger'; trigger.setAttribute('tabindex','0'); var valEl = document.createElement('span'); valEl.className = 'csel-val'; trigger.appendChild(valEl); wrap.appendChild(trigger); var drop = document.createElement('div'); drop.className = 'csel-dropdown'; wrap.appendChild(drop); function syncVal(){ var opt = sel.options[sel.selectedIndex]; if(!opt){ valEl.textContent='— Select —'; valEl.className='csel-val placeholder'; return; } var isPlaceholder = opt.value==='' || opt.disabled; valEl.textContent = opt.textContent; valEl.className = 'csel-val' + (isPlaceholder ? ' placeholder' : ''); } function buildDrop(){ drop.innerHTML = ''; Array.from(sel.children).forEach(function(child){ if(child.tagName === 'OPTGROUP'){ var grp = document.createElement('div'); grp.className = 'csel-group-label'; grp.textContent = child.getAttribute('label'); drop.appendChild(grp); Array.from(child.children).forEach(function(opt){ var i = Array.from(sel.options).indexOf(opt); var item = document.createElement('div'); item.className = 'csel-option csel-grouped' + (i===sel.selectedIndex?' selected':''); item.textContent = opt.textContent; if(opt.disabled){ item.setAttribute('disabled',''); } else { item.addEventListener('mousedown', function(e){ e.preventDefault(); sel.selectedIndex = i; sel.dispatchEvent(new Event('change',{bubbles:true})); syncVal(); closeDrop(); }); } drop.appendChild(item); }); } else if(child.tagName === 'OPTION'){ var i = Array.from(sel.options).indexOf(child); var item = document.createElement('div'); item.className = 'csel-option' + (i===sel.selectedIndex?' selected':''); item.textContent = child.textContent; if(child.disabled){ item.setAttribute('disabled',''); } else { item.addEventListener('mousedown', function(e){ e.preventDefault(); sel.selectedIndex = i; sel.dispatchEvent(new Event('change',{bubbles:true})); syncVal(); closeDrop(); }); } drop.appendChild(item); } }); } function openDrop(){ buildDrop(); drop.classList.add('open'); trigger.classList.add('open'); } function closeDrop(){ drop.classList.remove('open'); trigger.classList.remove('open'); } syncVal(); sel.addEventListener('change', syncVal); trigger.addEventListener('click', function(e){ e.stopPropagation(); if(drop.classList.contains('open')){ closeDrop(); } else { openDrop(); } }); trigger.addEventListener('keydown', function(e){ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); drop.classList.contains('open')?closeDrop():openDrop(); } else if(e.key==='Escape') closeDrop(); }); document.addEventListener('click', function(e){ if(!wrap.contains(e.target)) closeDrop(); }); } document.querySelectorAll('select').forEach(initCustomSelect); document.addEventListener('DOMContentLoaded', function(){ document.querySelectorAll('select').forEach(initCustomSelect); var obs = new MutationObserver(function(muts){ muts.forEach(function(m){ m.addedNodes.forEach(function(n){ if(n.nodeType===1){ n.querySelectorAll && n.querySelectorAll('select').forEach(initCustomSelect); if(n.tagName==='SELECT') initCustomSelect(n); } }); }); }); obs.observe(document.body, {childList:true, subtree:true}); }); })(); /* ── PACKAGES INIT ── */ window.addEventListener('DOMContentLoaded', function(){ renderPkgs('all'); // Restore session: if user is already logged in, update nav if(currentUser){ updateNavLoggedIn(); } }); '; } // ── Build HTML email for internal team notification ── function _buildAdminHtml(formType, ref, ts, data, fieldLabels){ var rows = ''; Object.keys(data).filter(function(k){ return data[k] && String(data[k]).trim(); }).forEach(function(k){ var label = fieldLabels[k] || (k.charAt(0).toUpperCase()+k.slice(1).replace(/([A-Z])/g,' $1')); rows += ''+label+''+data[k]+''; }); return 'New Enquiry'+ '
'+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ '
'+ '
🔔
'+ '
New Form Submission
'+ '
TravelHuge Internal Alert — Action Required
'+ '
'+ ''+ ''+ ''+ ''+ '
Reference
'+ref+'
Form Type
'+formType+'
Received
'+ts+'
'+ '
'+ '
Customer Details
'+ ''+rows+'
'+ '
'+ '
'+ '
⚡ Action Required
'+ '
Respond within 4 hours via email or phone.
Customer Phone: '+(data.phone||'Not provided')+'
Customer Email: '+(data.email||'Not provided')+'
'+ '
'+ '
'+ '
TravelHuge Internal System  |  support@travelhuge.com  |  +91 0129-4324324
'+ '
'; } // ── Send customer acknowledgement via EmailJS ── function _sendCustomerAck(toEmail, toName, subject, htmlBody){ _initEmailJS(); if(!_EJS_READY){ setTimeout(function(){ _sendCustomerAck(toEmail, toName, subject, htmlBody); }, 2000); return; } emailjs.send(_EJS_SERVICE, _EJS_TEMPLATE, { to_email : toEmail, to_name : toName, from_name : 'TravelHuge', reply_to : 'noreply@travelhuge.com', subject : subject, html_message : htmlBody }).then(function(r){ console.log('Customer email sent OK:', r.status); }).catch(function(e){ console.log('EmailJS error, trying fallback:', e); emailjs.send(_EJS_SERVICE, _EJS_TEMPLATE, { to_email : toEmail, to_name : toName, from_name : 'TravelHuge', reply_to : 'noreply@travelhuge.com', subject : subject, html_message : '

TravelHuge — Enquiry Confirmed!

Dear '+toName+',

Thank you for reaching out! We have received your enquiry and our travel specialist will contact you within 4 hours.

'+subject+'

For urgent help:
📞 +91 0129-4324324
💬 WhatsApp: +91-9818644324
✉️ support@travelhuge.com

This is an automated email. Please do not reply. For questions, contact support@travelhuge.com

' }).then(function(r2){ console.log('Fallback email sent:', r2.status); }) .catch(function(e2){ console.log('Both email attempts failed:', e2); }); }); } function notifyTeam(formType, data){ // 1. Save locally (always works, shows in Admin > Enquiries) var list = JSON.parse(localStorage.getItem('th_enq')||'[]'); var ref = 'TH-'+Date.now().toString().slice(-8); var now = new Date(); var ts = now.toLocaleString('en-IN', {dateStyle:'long', timeStyle:'short'}); list.unshift({ref:ref, formType:formType, ts:now.toLocaleString('en-IN'), data:data}); localStorage.setItem('th_enq', JSON.stringify(list.slice(0,300))); updateEnqBadge(); // 2. Field label map for readable emails var fieldLabels = { name:'Full Name', phone:'Phone Number', email:'Email Address', subject:'Subject', message:'Message', destination:'Destination', travelDate:'Travel Date', travelers:'No. of Travellers', plan:'Insurance Plan', departure:'Departure Date', returnDate:'Return Date', travellers:'No. of Travellers', tripType:'Trip Type', purpose:'Purpose', duration:'Duration', flexibility:'Flexibility', budget:'Budget', budgetType:'Budget Type', adults:'Adults', children:'Children', infants:'Infants', seniors:'Seniors', profile:'Traveller Profile', ageGroup:'Age Group', fromCity:'Departing City', hotel:'Hotel Preference', meal:'Meal Preference', flightClass:'Flight Class', transport:'Transport', activities:'Activities', diet:'Dietary Needs', medical:'Medical Notes', insurance:'Insurance', visa:'Visa Help', callTime:'Preferred Call Time', source:'How Did You Hear' }; // 3. Build detail rows array for customer email (exclude name from user-facing summary) var allDetailRows = Object.keys(data).filter(function(k){ return data[k] && String(data[k]).trim() && k !== 'name'; }).map(function(k){ var label = fieldLabels[k] || (k.charAt(0).toUpperCase()+k.slice(1).replace(/([A-Z])/g,' $1')); return {label: label, value: data[k]}; }); // ════════════════════════════════════════════════ // 4. EMAIL TO support@travelhuge.com (team notification via Web3Forms) // Sends beautiful HTML alert to the TravelHuge team // ════════════════════════════════════════════════ var adminHtml = _buildAdminHtml(formType, ref, ts, data, fieldLabels); // Build plain-text for Web3Forms (plain text notification to team) var adminLines = Object.keys(data).filter(function(k){ return data[k] && String(data[k]).trim(); }).map(function(k){ var label = fieldLabels[k] || (k.charAt(0).toUpperCase()+k.slice(1).replace(/([A-Z])/g,' $1')); return label+': '+data[k]; }).join('\n'); // Web3Forms → plain text to team (always works, no config needed beyond key) _sendW3F({ access_key : _W3F_KEY, subject : '[TravelHuge] '+formType+' — '+(data.name||'Visitor')+' | Ref: '+ref+' | Ph: '+(data.phone||'—'), from_name : 'TravelHuge Website', email : 'support@travelhuge.com', message : '=== NEW FORM SUBMISSION ===\n\nReference : '+ref+'\nForm Type : '+formType+'\nReceived : '+ts+'\n\n--- CUSTOMER DETAILS ---\n'+adminLines+'\n\n--- ACTION REQUIRED ---\nRespond within 4 hours.\nPhone: '+(data.phone||'Not provided')+'\nEmail: '+(data.email||'Not provided'), replyto : data.email||'support@travelhuge.com' }); // EmailJS → beautiful HTML email to team (support@travelhuge.com) setTimeout(function(){ _initEmailJS(); if(_EJS_READY){ emailjs.send(_EJS_SERVICE, _EJS_TEMPLATE, { to_email : 'support@travelhuge.com', to_name : 'TravelHuge Team', from_name : 'TravelHuge Website', reply_to : data.email||'support@travelhuge.com', subject : '[TravelHuge] New '+formType+' | '+( data.name||'Visitor')+' | Ref: '+ref, html_message : adminHtml }).then(function(r){ console.log('Team HTML email sent:', r.status); }) .catch(function(e){ console.warn('Team email error:', e); }); } }, 500); // ════════════════════════════════════════════════ // 5. EMAIL TO CUSTOMER (beautiful HTML confirmation via EmailJS) // Sent FROM support@travelhuge.com / noreply@travelhuge.com // TO the customer's email address // ════════════════════════════════════════════════ if(data.email){ var firstName = (data.name||'Valued Guest').split(' ')[0]; var customerHtml = _buildCustomerHtml(firstName, formType, ref, ts, allDetailRows); _sendCustomerAck( data.email, firstName, '✅ Enquiry Confirmed — TravelHuge (Ref: '+ref+')', customerHtml ); } // ════════════════════════════════════════════════ // 6. WHATSAPP CONFIRMATION TO CUSTOMER'S PHONE // (opens pre-filled WhatsApp message via API) // For real SMS use Twilio / MSG91 on backend // ════════════════════════════════════════════════ // ════════════════════════════════════════════════ // 6. WHATSAPP — Save admin-facing link + trigger customer WA to TravelHuge // ════════════════════════════════════════════════ if(data.phone){ var waPhone = data.phone.replace(/[^0-9]/g,''); if(waPhone.length === 10) waPhone = '91'+waPhone; // Admin-facing link: admin clicks this in the panel to message the customer var adminWaMsg = encodeURIComponent( 'Hello '+( data.name ? data.name.split(' ')[0] : 'there')+'! 👋\n\n'+ 'This is *TravelHuge* — thank you for your enquiry (Ref: '+ref+')! ✈️\n\n'+ 'Our travel specialist is ready to create a personalised itinerary for you.\n\n'+ 'Could you please confirm your travel dates and budget so we can get started?\n\n'+ '_Team TravelHuge_\n_www.travelhuge.com_' ); var waLink = 'https://wa.me/'+waPhone+'?text='+adminWaMsg; // Save in enquiry for admin use (click from admin panel to message customer) try{ var enqList = JSON.parse(localStorage.getItem('th_enq')||'[]'); if(enqList[0] && enqList[0].ref === ref){ enqList[0].waLink = waLink; enqList[0].customerPhone = waPhone; } localStorage.setItem('th_enq', JSON.stringify(enqList)); }catch(e){} window._lastWaLink = waLink; window._lastWaPhone = waPhone; } } /* ── Send WhatsApp confirmation to user (opens wa.me in new tab) ── */ function _sendUserWhatsApp(phone, name, destination, ref){ // This opens on CUSTOMER's device — so we point to TravelHuge's WA number // so the customer can send their enquiry confirmation to TravelHuge directly var TRAVELHUGE_WA = '919818644324'; // TravelHuge WhatsApp Business number var msg = encodeURIComponent( 'Hello TravelHuge! ✈️\n\n'+ 'I just submitted an enquiry on your website and wanted to confirm:\n\n'+ '👤 *Name:* '+name+'\n'+ '📍 *Destination:* '+destination+'\n'+ '📋 *Ref:* '+ref+'\n'+ (phone ? '📞 *My Phone:* '+phone+'\n' : '')+ '\nPlease assist me with my travel plan. Thank you!' ); var waLink = 'https://wa.me/'+TRAVELHUGE_WA+'?text='+msg; setTimeout(function(){ var a = document.createElement('a'); a.href = waLink; a.target = '_blank'; a.rel = 'noopener'; document.body.appendChild(a); a.click(); document.body.removeChild(a); }, 800); } // Admin-side: open WA to MESSAGE the customer (called from admin panel) function _adminWhatsAppCustomer(customerPhone, customerName, message){ if(!customerPhone) return; var waPhone = customerPhone.replace(/[^0-9]/g,''); if(waPhone.length === 10) waPhone = '91'+waPhone; if(waPhone.length < 10) return; var msg = encodeURIComponent(message || 'Hello '+customerName+'! This is TravelHuge. We have reviewed your enquiry and would like to share a customised itinerary with you. Please reply or call us at +91-9818644324. ✈️'); window.open('https://wa.me/'+waPhone+'?text='+msg, '_blank'); } function updateEnqBadge(){ var list = JSON.parse(localStorage.getItem('th_enq')||'[]'); var b = document.getElementById('a-enq-badge'); if(b) b.textContent = list.length; } // ── CAPTCHA ── var _caps = {}; function genCap(id){ var a = Math.floor(Math.random()*12)+1, b = Math.floor(Math.random()*12)+1; _caps[id] = {ans: a+b}; var q = document.getElementById('cap-q-'+id); if(q) q.textContent = a+' + '+b+' = ?'; var i = document.getElementById('cap-i-'+id); if(i) i.value = ''; } function checkCap(id){ var inp = document.getElementById('cap-i-'+id); if(!inp) return true; if(!_caps[id] || parseInt(inp.value) !== _caps[id].ans){ toast('🤖 Captcha','Please answer the math question correctly'); genCap(id); return false; } return true; } // ── CONTACT FORM SUBMIT ── function submitContactForm(){ var inputs = document.querySelectorAll('.contact-form-card input,.contact-form-card select,.contact-form-card textarea'); var fname='',lname='',phone='',email='',subject='',message=''; inputs.forEach(function(el){ if(el.placeholder==='Rahul') fname=el.value.trim(); else if(el.placeholder==='Sharma') lname=el.value.trim(); else if(el.type==='tel') phone=el.value.trim(); else if(el.type==='email') email=el.value.trim(); else if(el.tagName==='SELECT') subject=el.value; else if(el.tagName==='TEXTAREA') message=el.value.trim(); }); // ── Strict validation ── var nameRes = isValidName((fname+' '+lname).trim()||fname); if(!fname){toast('⚠️ Required','Please enter your first name.');return;} if(!nameRes.ok){toast('⚠️ Name Error',nameRes.msg);return;} var phoneRes = isValidPhone(phone); if(!phoneRes.ok){toast('⚠️ Phone Error',phoneRes.msg);return;} if(email){ var emailRes = isValidEmail(email); if(!emailRes.ok){toast('⚠️ Email Error',emailRes.msg);return;} } if(!message){toast('⚠️ Required','Please enter your message.');return;} // ── SMART SWAP: fix if user entered email in phone field or phone in email field ── var isEmail = function(v){ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); }; var isPhone = function(v){ return /^[\d\s\+\-\(\)]{7,15}$/.test(v); }; if(phone && isEmail(phone) && !email){ email=phone; phone=''; } else if(email && isPhone(email) && !phone){ phone=email; email=''; } else if(phone && isEmail(phone) && email && isPhone(email)){ var _t=phone; phone=email; email=_t; } var fullName = (fname+' '+lname).trim(); notifyTeam('Contact Form',{name:fullName,phone:phone,email:email,subject:subject,message:message}); // Send WhatsApp confirmation to user var ref = 'TH-'+Date.now().toString().slice(-8); _sendUserWhatsApp(phone, fname||'there', subject||'your enquiry', ref); inputs.forEach(function(el){try{el.value='';}catch(e){}}); genCap('cnt'); toast('✅ Message Sent!','Confirmation sent to email & WhatsApp. Thank you, '+(fname||'there')+'!'); } // ── OVERRIDE DEST ENQUIRY for notifications (legacy override kept minimal — submitDestEnquiry now handles everything) ── var _origDEQ2 = null; // DEQ is now handled directly in submitDestEnquiry above // ── OVERRIDE openDestEnquiry to re-init captcha ── var _origOpenDEQ = window.openDestEnquiry; window.openDestEnquiry = function(dest, img){ _origOpenDEQ(dest, img); setTimeout(function(){ genCap('deq'); }, 100); }; // ── FLIGHT/HOTEL SUBMIT WITH NOTIFICATION ── function submitFH(type){ var nameEl = document.getElementById(type==='flight'?'fn2':'hn2'); var phoneEl = document.getElementById(type==='flight'?'fp':'hp'); var emailEl = document.getElementById(type==='flight'?'fe':'he'); if(!nameEl||!phoneEl||!emailEl){toast('⚠️ Error','Form elements not found.');return;} var nameRes = isValidName(nameEl.value.trim()); if(!nameEl.value.trim()||!nameRes.ok){toast('⚠️ Name Error',nameRes.ok?'Please enter your name.':nameRes.msg);return;} var phoneRes = isValidPhone(phoneEl.value.trim()); if(!phoneRes.ok){toast('⚠️ Phone Error',phoneRes.msg);return;} var emailRes = isValidEmail(emailEl.value.trim()); if(!emailRes.ok){toast('⚠️ Email Error',emailRes.msg);return;} notifyTeam(type==='flight'?'Flight Enquiry':'Hotel Enquiry',{ name:nameEl.value, phone:phoneEl.value, email:emailEl.value }); // Send WhatsApp confirmation to user var fhRef = 'TH-'+Date.now().toString().slice(-8); _sendUserWhatsApp(phoneEl.value, (nameEl.value||'').split(' ')[0], type==='flight'?'your flight booking':'your hotel booking', fhRef); submitEnquiry(type); // original animation } // ── ADMIN ENQUIRIES RENDER ── function renderAdminEnquiries(){ var el = document.getElementById('adm-enq-list'); if(!el) return; var list = JSON.parse(localStorage.getItem('th_enq')||'[]'); if(!list.length){ el.innerHTML='

No enquiries yet. Visitor form submissions appear here.

'; return; } el.innerHTML = list.map(function(e){ var d=e.data||{}; var phone = d.phone||''; var email = d.email||''; var waPhone = phone.replace(/[^0-9]/g,''); if(waPhone.length===10) waPhone='91'+waPhone; var waLink = e.waLink || ('https://wa.me/'+waPhone+'?text='+encodeURIComponent('Hello '+((d.name||'').split(' ')[0]||'there')+'! Thank you for contacting TravelHuge (Ref: '+e.ref+'). Our specialist will call you within 4 hours. For urgent help: +91 0129-4324324')); return '
' +'
' +'
' +''+e.formType+'' +'
'+(d.name||'—')+'
' +'
' +(phone?'📞 '+phone:'') +(phone&&email?'  ·  ':'') +(email?'✉️ '+email:'') +'
' +(d.destination?'
📍 '+d.destination+'
':'') +(d.message?'
"'+(d.message.substring(0,120))+(d.message.length>120?'…':'"')+'
':'') +'
' +(phone?'📞 Call':'') +(waPhone?'💬 WhatsApp':'') +(email?'✉️ Reply Email':'') +'
' +'
' +'
' +'
'+e.ts+'
' +'
'+e.ref+'
' +'
' +'
'; }).join(''); } // Tab helper for new admin tabs var _origSetAdminTab = window.setAdminTab; window.setAdminTab = function(tab, el){ _origSetAdminTab(tab, el); if(tab === 'enquiries'){ renderAdminEnquiries(); updateEnqBadge(); } };