| <!DOCTYPE html> |
|
|
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"/> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <title>The Loco Workshop, Ajmer โ 150 Years of Industrial Majesty</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"/> |
| <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400&family=Cinzel:wght@400;500;600&family=Jost:wght@300;400;500&display=swap" rel="stylesheet"/> |
| <link rel="stylesheet" href="http://127.0.0.1:8000/../css/index.css"> |
| <link rel="stylesheet" href="http://127.0.0.1:8000/../css/booking.css"> |
|
|
| <script id="browser-logger-active"> |
| (function() { |
| const ENDPOINT = 'http://127.0.0.1:8000/_boost/browser-logs'; |
| const logQueue = []; |
| let flushTimeout = null; |
|
|
| console.log('๐ Browser logger active (MCP server detected). Posting to: ' + ENDPOINT); |
|
|
| // Store original console methods |
| const originalConsole = { |
| log: console.log, |
| info: console.info, |
| error: console.error, |
| warn: console.warn, |
| table: console.table |
| }; |
|
|
| // Helper to safely stringify values |
| function safeStringify(obj) { |
| const seen = new WeakSet(); |
| return JSON.stringify(obj, (key, value) => { |
| if (typeof value === 'object' && value !== null) { |
| if (seen.has(value)) return '[Circular]'; |
| seen.add(value); |
| } |
| if (value instanceof Error) { |
| return { |
| name: value.name, |
| message: value.message, |
| stack: value.stack |
| }; |
| } |
| return value; |
| }); |
| } |
|
|
| // Batch and send logs |
| function flushLogs() { |
| if (logQueue.length === 0) return; |
|
|
| const batch = logQueue.splice(0, logQueue.length); |
|
|
| fetch(ENDPOINT, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'X-Requested-With': 'XMLHttpRequest' |
| }, |
| body: JSON.stringify({ logs: batch }) |
| }).catch(err => { |
| // Silently fail to avoid infinite loops |
| originalConsole.error('Failed to send logs:', err); |
| }); |
| } |
|
|
| // Debounced flush (100ms) |
| function scheduleFlush() { |
| if (flushTimeout) clearTimeout(flushTimeout); |
| flushTimeout = setTimeout(flushLogs, 100); |
| } |
|
|
| // Intercept console methods |
| ['log', 'info', 'error', 'warn', 'table'].forEach(method => { |
| console[method] = function(...args) { |
| // Call original method |
| originalConsole[method].apply(console, args); |
|
|
| // Capture log data |
| try { |
| logQueue.push({ |
| type: method, |
| timestamp: new Date().toISOString(), |
| data: args.map(arg => { |
| try { |
| return typeof arg === 'object' ? JSON.parse(safeStringify(arg)) : arg; |
| } catch (e) { |
| return String(arg); |
| } |
| }), |
| url: window.location.href, |
| userAgent: navigator.userAgent |
| }); |
|
|
| scheduleFlush(); |
| } catch (e) { |
| // Fail silently |
| } |
| }; |
| }); |
|
|
| // Global error handlers for uncaught errors |
| const originalOnError = window.onerror; |
| window.onerror = function boostErrorHandler(errorMsg, url, lineNumber, colNumber, error) { |
| try { |
| logQueue.push({ |
| type: 'uncaught_error', |
| timestamp: new Date().toISOString(), |
| data: [{ |
| message: errorMsg, |
| filename: url, |
| lineno: lineNumber, |
| colno: colNumber, |
| error: error ? { |
| name: error.name, |
| message: error.message, |
| stack: error.stack |
| } : null |
| }], |
| url: window.location.href, |
| userAgent: navigator.userAgent |
| }); |
|
|
| scheduleFlush(); |
| } catch (e) { |
| // Fail silently |
| } |
|
|
| // Call original handler if it exists |
| if (originalOnError && typeof originalOnError === 'function') { |
| return originalOnError(errorMsg, url, lineNumber, colNumber, error); |
| } |
|
|
| // Let the error continue to propagate |
| return false; |
| } |
| window.addEventListener('error', (event) => { |
| try { |
| logQueue.push({ |
| type: 'window_error', |
| timestamp: new Date().toISOString(), |
| data: [{ |
| message: event.message, |
| filename: event.filename, |
| lineno: event.lineno, |
| colno: event.colno, |
| error: event.error ? { |
| name: event.error.name, |
| message: event.error.message, |
| stack: event.error.stack |
| } : null |
| }], |
| url: window.location.href, |
| userAgent: navigator.userAgent |
| }); |
|
|
| scheduleFlush(); |
| } catch (e) { |
| // Fail silently |
| } |
|
|
| // Let the error continue to propagate |
| return false; |
| }); |
| window.addEventListener('unhandledrejection', (event) => { |
| try { |
| logQueue.push({ |
| type: 'error', |
| timestamp: new Date().toISOString(), |
| data: [{ |
| message: 'Unhandled Promise Rejection', |
| reason: event.reason instanceof Error ? { |
| name: event.reason.name, |
| message: event.reason.message, |
| stack: event.reason.stack |
| } : event.reason |
| }], |
| url: window.location.href, |
| userAgent: navigator.userAgent |
| }); |
|
|
| scheduleFlush(); |
| } catch (e) { |
| // Fail silently |
| } |
|
|
| // Let the rejection continue to propagate |
| return false; |
| }); |
|
|
| // Flush on page unload |
| window.addEventListener('beforeunload', () => { |
| if (logQueue.length > 0) { |
| navigator.sendBeacon(ENDPOINT, JSON.stringify({ logs: logQueue })); |
| } |
| }); |
| })(); |
| </script> |
| </head> |
|
|
| <div class="cursor" id="cursor"></div> |
| <div class="cursor-ring" id="cursorRing"></div> |
|
|
| |
| <nav id="mainNav"> |
| <a href="#" class="nav-logo"> |
| <div class="nav-logo-wheel">โ</div> |
| <span>The Loco Workshop, Ajmer</span> |
| </a> |
| <ul class="nav-links"> |
| <li><a href="http://127.0.0.1:8000/legacy">Legacy</a></li> |
| <li><a href="#capabilities">Capabilities</a></li> |
| <li><a href="http://127.0.0.1:8000/milestones">Milestones</a></li> |
| <li><a href="http://127.0.0.1:8000/booking">Visit</a></li> |
| <li><a href="#gallery">Gallery</a></li> |
| </ul> |
| <a href="http://127.0.0.1:8000/booking" class="nav-cta">Plan a Visit</a> |
| </nav> |
| <body> |
|
|
| <div class="cursor" id="cursor"></div> |
| <div class="cursor-ring" id="cursorRing"></div> |
|
|
| |
| <div class="form-progress"><div class="form-progress-bar" id="progressBar"></div></div> |
|
|
| |
| <div class="success-overlay" id="successOverlay"> |
| <div class="success-ring"><div class="success-icon">โ</div></div> |
| <div class="success-tag">Booking Confirmed</div> |
| <h2 class="success-title">Your Visit is<br/><em>Reserved</em></h2> |
| <p class="success-body">Thank you for booking a tour at The Loco Workshop, Ajmer. A confirmation with your booking details and visit guidelines has been sent to your registered email address.</p> |
| <div class="success-ref" id="successRef">Booking Reference: LWA-2026-XXXX</div> |
| <div class="success-actions"> |
| <a href="http://127.0.0.1:8000" class="btn-gold">โ Back to Main Site</a> |
| <button onclick="location.reload()" class="btn-outline">Book Another Visit</button> |
| </div> |
| </div> |
|
|
| |
| <nav> |
| <a href="loco_workshop_ajmer.html" class="nav-logo"> |
| <div class="nav-logo-wheel">โ</div> |
| <span>The Loco Workshop, Ajmer</span> |
| </a> |
| <a href="http://127.0.0.1:8000" class="nav-back"><span>โ</span> Back to main site</a> |
| </nav> |
|
|
| |
| <div class="page-header"> |
| <div class="page-header-bg"></div> |
| <div class="page-header-tracks"></div> |
|
|
| |
| <svg class="header-gear" viewBox="0 0 340 340" xmlns="http://www.w3.org/2000/svg" fill="none"> |
| <circle cx="170" cy="170" r="130" stroke="#C9973A" stroke-width="1"/> |
| <circle cx="170" cy="170" r="90" stroke="#C9973A" stroke-width="0.5"/> |
| <circle cx="170" cy="170" r="40" stroke="#C9973A" stroke-width="1"/> |
| <g stroke="#C9973A" stroke-width="8"> |
| <line x1="170" y1="40" x2="170" y2="80"/><line x1="170" y1="260" x2="170" y2="300"/> |
| <line x1="40" y1="170" x2="80" y2="170"/><line x1="260" y1="170" x2="300" y2="170"/> |
| <line x1="72" y1="72" x2="100" y2="100"/><line x1="240" y1="240" x2="268" y2="268"/> |
| <line x1="268" y1="72" x2="240" y2="100"/><line x1="100" y1="240" x2="72" y2="268"/> |
| </g> |
| <g stroke="#C9973A" stroke-width="12" stroke-linecap="square"> |
| <line x1="162" y1="35" x2="178" y2="35"/><line x1="162" y1="305" x2="178" y2="305"/> |
| <line x1="35" y1="162" x2="35" y2="178"/><line x1="305" y1="162" x2="305" y2="178"/> |
| <line x1="64" y1="60" x2="72" y2="72"/><line x1="268" y1="268" x2="276" y2="280"/> |
| <line x1="276" y1="60" x2="268" y2="72"/><line x1="72" y1="268" x2="64" y2="280"/> |
| </g> |
| </svg> |
|
|
| <div class="page-header-inner"> |
| <div class="page-eyebrow">The Loco Workshop, Ajmer ยท Est. 1876</div> |
| <h1 class="page-title">Reserve<br/>Your <em>Visit</em></h1> |
| <p class="page-subtitle">Book a guided tour through 150 years of<br/>India's finest railway engineering heritage</p> |
| </div> |
| <div class="step-indicators"> |
| <div class="step-dot active" id="sd1"></div> |
| <div class="step-dot" id="sd2"></div> |
| <div class="step-dot" id="sd3"></div> |
| <div class="step-dot" id="sd4"></div> |
| <span class="step-label">4 sections</span> |
| </div> |
| </div> |
|
|
| |
| <div class="main-layout"> |
|
|
| |
| <form id="bookingForm" novalidate> |
|
|
| |
| <div class="form-section" id="sec1"> |
| <div class="form-section-title"> |
| <div class="form-section-num">1</div> |
| Select Your Experience |
| </div> |
| <div class="tour-options"> |
| <div> |
| <input type="radio" name="tour_type" id="t1" value="Heritage Museum Tour" class="tour-option" checked/> |
| <label for="t1" class="tour-option-label"> |
| <div class="tour-option-icon">๐๏ธ</div> |
| <div class="tour-option-name">Heritage Museum Tour</div> |
| <div class="tour-option-desc">150-year anniversary exhibition โ artefacts, blueprints, historical photographs</div> |
| <div class="tour-option-price">โน50 per person</div> |
| </label> |
| </div> |
| <div> |
| <input type="radio" name="tour_type" id="t2" value="Workshop Floor Tour" class="tour-option"/> |
| <label for="t2" class="tour-option-label"> |
| <div class="tour-option-icon">๐ง</div> |
| <div class="tour-option-name">Workshop Floor Tour</div> |
| <div class="tour-option-desc">Live overhaul bays, machine shops and forge works โ witnessed close-up</div> |
| <div class="tour-option-price">โน100 per person</div> |
| </label> |
| </div> |
| <div> |
| <input type="radio" name="tour_type" id="t3" value="Student Group Tour" class="tour-option"/> |
| <label for="t3" class="tour-option-label"> |
| <div class="tour-option-icon">๐</div> |
| <div class="tour-option-name">Student Group Tour</div> |
| <div class="tour-option-desc">Educational visits for engineering colleges and schools with guided narration</div> |
| <div class="tour-option-price">โน25 per person</div> |
| </label> |
| </div> |
| <div> |
| <input type="radio" name="tour_type" id="t4" value="Full Day Experience" class="tour-option"/> |
| <label for="t4" class="tour-option-label"> |
| <div class="tour-option-icon">โญ</div> |
| <div class="tour-option-name">Full Day Experience</div> |
| <div class="tour-option-desc">Museum + workshop floor + heritage steam locomotive display โ the complete tour</div> |
| <div class="tour-option-price">โน200 per person</div> |
| </label> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="form-section" id="sec2"> |
| <div class="form-section-title"> |
| <div class="form-section-num">2</div> |
| Date, Time & Visitors |
| </div> |
| <div class="form-grid" style="margin-bottom:24px;"> |
| <div class="field"> |
| <label>Preferred Date <span class="req">*</span></label> |
| <input type="date" id="visit_date" name="visit_date" required min=""/> |
| <span class="error-msg" id="err_date">Please select a visit date.</span> |
| </div> |
| <div class="field"> |
| <label>Preferred Time Slot <span class="req">*</span></label> |
| <div class="time-slots" id="timeSlots"> |
| <input type="radio" name="time_slot" id="ts1" value="9:00 AM" class="slot-input" checked/> |
| <label for="ts1" class="slot-label">9:00 AM</label> |
| <input type="radio" name="time_slot" id="ts2" value="10:30 AM" class="slot-input"/> |
| <label for="ts2" class="slot-label">10:30 AM</label> |
| <input type="radio" name="time_slot" id="ts3" value="12:00 PM" class="slot-input"/> |
| <label for="ts3" class="slot-label">12:00 PM</label> |
| <input type="radio" name="time_slot" id="ts4" value="2:00 PM" class="slot-input"/> |
| <label for="ts4" class="slot-label">2:00 PM</label> |
| <input type="radio" name="time_slot" id="ts5" value="3:30 PM" class="slot-input"/> |
| <label for="ts5" class="slot-label">3:30 PM</label> |
| </div> |
| </div> |
| </div> |
| <div class="form-grid three"> |
| <div class="field"> |
| <label>Adults <span class="req">*</span></label> |
| <div class="stepper" id="adultStepper"> |
| <button type="button" class="stepper-btn" onclick="stepChange('adults',-1)">โ</button> |
| <div class="stepper-val" id="adultsVal">1</div> |
| <button type="button" class="stepper-btn" onclick="stepChange('adults',1)">+</button> |
| </div> |
| <input type="hidden" name="adults" id="adultsInput" value="1"/> |
| </div> |
| <div class="field"> |
| <label>Children (under 12)</label> |
| <div class="stepper"> |
| <button type="button" class="stepper-btn" onclick="stepChange('children',-1)">โ</button> |
| <div class="stepper-val" id="childrenVal">0</div> |
| <button type="button" class="stepper-btn" onclick="stepChange('children',1)">+</button> |
| </div> |
| <input type="hidden" name="children" id="childrenInput" value="0"/> |
| </div> |
| <div class="field"> |
| <label>Senior Citizens (60+)</label> |
| <div class="stepper"> |
| <button type="button" class="stepper-btn" onclick="stepChange('seniors',-1)">โ</button> |
| <div class="stepper-val" id="seniorsVal">0</div> |
| <button type="button" class="stepper-btn" onclick="stepChange('seniors',1)">+</button> |
| </div> |
| <input type="hidden" name="seniors" id="seniorsInput" value="0"/> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="form-section" id="sec3"> |
| <div class="form-section-title"> |
| <div class="form-section-num">3</div> |
| Your Details |
| </div> |
| <div class="form-grid" style="margin-bottom:24px;"> |
| <div class="field"> |
| <label>First Name <span class="req">*</span></label> |
| <input type="text" name="first_name" id="first_name" placeholder="Rajendra" required/> |
| <span class="error-msg" id="err_fname">Please enter your first name.</span> |
| </div> |
| <div class="field"> |
| <label>Last Name <span class="req">*</span></label> |
| <input type="text" name="last_name" id="last_name" placeholder="Kumar" required/> |
| <span class="error-msg" id="err_lname">Please enter your last name.</span> |
| </div> |
| <div class="field"> |
| <label>Email Address <span class="req">*</span></label> |
| <input type="email" name="email" id="email" placeholder="you@example.com" required/> |
| <span class="error-msg" id="err_email">Please enter a valid email address.</span> |
| </div> |
| <div class="field"> |
| <label>Mobile Number <span class="req">*</span></label> |
| <input type="tel" name="phone" id="phone" placeholder="+91 98765 43210" required/> |
| <span class="error-msg" id="err_phone">Please enter a valid mobile number.</span> |
| </div> |
| </div> |
| <div class="form-grid" style="margin-bottom:24px;"> |
| <div class="field"> |
| <label>City</label> |
| <input type="text" name="city" placeholder="Ajmer"/> |
| </div> |
| <div class="field"> |
| <label>Nationality</label> |
| <div class="select-wrap"> |
| <select name="nationality"> |
| <option value="Indian">Indian</option> |
| <option value="Foreign National">Foreign National</option> |
| <option value="NRI">NRI / Overseas Citizen</option> |
| </select> |
| </div> |
| </div> |
| <div class="field"> |
| <label>Category</label> |
| <div class="select-wrap"> |
| <select name="visitor_category" id="visitor_category"> |
| <option value="General">General Public</option> |
| <option value="Student">Student</option> |
| <option value="Researcher">Researcher / Academic</option> |
| <option value="Railway Staff">Railway Staff / Family</option> |
| <option value="Corporate">Corporate / Delegation</option> |
| <option value="Media">Media / Press</option> |
| </select> |
| </div> |
| </div> |
| <div class="field"> |
| <label>ID Proof Type</label> |
| <div class="select-wrap"> |
| <select name="id_proof"> |
| <option value="">Select ID type</option> |
| <option value="Aadhaar">Aadhaar Card</option> |
| <option value="Passport">Passport</option> |
| <option value="Driving Licence">Driving Licence</option> |
| <option value="Voter ID">Voter ID</option> |
| <option value="PAN Card">PAN Card</option> |
| </select> |
| </div> |
| </div> |
| </div> |
| <div class="form-grid single"> |
| <div class="field"> |
| <label>Special Requirements / Notes</label> |
| <textarea name="notes" placeholder="Wheelchair accessibility needed, specific areas of interest, dietary requirements for guided lunch package, language preference for tour guideโฆ"></textarea> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="form-section" id="sec4"> |
| <div class="form-section-title"> |
| <div class="form-section-num">4</div> |
| Preferences & Confirmation |
| </div> |
|
|
| <div class="form-grid" style="margin-bottom:24px;"> |
| <div class="field"> |
| <label>Preferred Tour Language</label> |
| <div class="select-wrap"> |
| <select name="language"> |
| <option value="English">English</option> |
| <option value="Hindi">Hindi</option> |
| <option value="Rajasthani">Rajasthani</option> |
| </select> |
| </div> |
| </div> |
| <div class="field"> |
| <label>How Did You Hear About Us?</label> |
| <div class="select-wrap"> |
| <select name="source"> |
| <option value="">Select source</option> |
| <option value="Indian Railways">Indian Railways Website</option> |
| <option value="Social Media">Social Media</option> |
| <option value="Word of Mouth">Word of Mouth</option> |
| <option value="News Article">News / Media Article</option> |
| <option value="School / College">School / College</option> |
| <option value="Other">Other</option> |
| </select> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="checkbox-group" style="margin-bottom:28px;"> |
| <label class="check-item"> |
| <input type="checkbox" id="chk_guidelines"/> |
| <div class="check-box"></div> |
| <div class="check-text">I have read and agree to the <strong>Visitor Guidelines</strong>, including mandatory closed-toe footwear for workshop floor access, prohibition of photography in restricted zones, and adherence to safety briefings during the tour.</div> |
| </label> |
| <label class="check-item"> |
| <input type="checkbox" id="chk_cancellation"/> |
| <div class="check-box"></div> |
| <div class="check-text">I understand the <strong>cancellation policy</strong> โ bookings cancelled 48 hours before the visit receive a full refund. Cancellations within 48 hours are non-refundable.</div> |
| </label> |
| <label class="check-item"> |
| <input type="checkbox" id="chk_photos"/> |
| <div class="check-box"></div> |
| <div class="check-text">I consent to <strong>photography and video</strong> that may be taken during the tour for promotional and archival purposes by The Loco Workshop, Ajmer. (Optional)</div> |
| </label> |
| <label class="check-item"> |
| <input type="checkbox" id="chk_updates"/> |
| <div class="check-box"></div> |
| <div class="check-text">I would like to receive <strong>event updates, newsletters</strong> and information about the 150th anniversary celebrations via email and SMS. (Optional)</div> |
| </label> |
| </div> |
|
|
| <div class="form-note"> |
| <strong style="color:rgba(250,246,238,0.6);font-weight:400;">Important:</strong> All bookings are subject to availability and confirmation by the Workshop administration. You will receive a confirmation email with your booking reference and visit instructions within 24 hours of submission. Government-issued photo ID is mandatory at the time of visit. |
| </div> |
|
|
| <div id="consentError" style="display:none; color:rgba(220,80,80,0.8); font-size:12px; margin-top:14px; padding-left:4px;"> |
| Please agree to the Visitor Guidelines and Cancellation Policy to proceed. |
| </div> |
| </div> |
|
|
| |
| <div class="submit-row"> |
| <button type="submit" class="submit-btn">Confirm Booking โ</button> |
| <p class="submit-note">You will receive a confirmation email within 24 hours. Booking subject to availability. Please carry a valid government-issued photo ID on the day of visit.</p> |
| </div> |
|
|
| </form> |
|
|
| |
| <aside class="sidebar"> |
|
|
| |
| <div class="summary-card"> |
| <div class="summary-card-head">Booking Summary</div> |
| <div class="summary-card-body"> |
| <div class="summary-row"> |
| <span class="summary-key">Experience</span> |
| <span class="summary-val" id="sum_tour">Heritage Museum Tour</span> |
| </div> |
| <div class="summary-row"> |
| <span class="summary-key">Date</span> |
| <span class="summary-val empty" id="sum_date">Not selected</span> |
| </div> |
| <div class="summary-row"> |
| <span class="summary-key">Time</span> |
| <span class="summary-val" id="sum_time">9:00 AM</span> |
| </div> |
| <div class="summary-row"> |
| <span class="summary-key">Adults</span> |
| <span class="summary-val" id="sum_adults">1</span> |
| </div> |
| <div class="summary-row"> |
| <span class="summary-key">Children</span> |
| <span class="summary-val" id="sum_children">0</span> |
| </div> |
| <div class="summary-row"> |
| <span class="summary-key">Seniors</span> |
| <span class="summary-val" id="sum_seniors">0</span> |
| </div> |
| </div> |
| <div class="summary-total"> |
| <span class="summary-total-label">Estimated Total</span> |
| <span class="summary-total-val gold" id="sum_total">โน50</span> |
| </div> |
| </div> |
|
|
| |
| <div class="info-card"> |
| <div class="info-card-head">Visit Information</div> |
| <ul class="info-list"> |
| <li> |
| <span class="info-icon">๐</span> |
| <span class="info-text"><strong>Railway Colony Road, Ajmer</strong><br/>Rajasthan 305001 ยท 0.8 km from Ajmer Jn.</span> |
| </li> |
| <li> |
| <span class="info-icon">๐</span> |
| <span class="info-text"><strong>Tuesday โ Sunday</strong><br/>9:00 AM โ 5:00 PM (Last entry 4:00 PM)</span> |
| </li> |
| <li> |
| <span class="info-icon">๐</span> |
| <span class="info-text"><strong>Closed-toe shoes mandatory</strong><br/>for workshop floor access</span> |
| </li> |
| <li> |
| <span class="info-icon">๐ชช</span> |
| <span class="info-text"><strong>Photo ID required</strong><br/>Aadhaar, Passport or Driving Licence</span> |
| </li> |
| <li> |
| <span class="info-icon">๐</span> |
| <span class="info-text"><strong>Queries: +91-145-262-0001</strong><br/>tours@locoworkshopajmer.in</span> |
| </li> |
| </ul> |
| </div> |
|
|
| <div class="sidebar-quote"> |
| <p class="sidebar-quote-text">From the age of steam to modern diesel โ 150 years of keeping India on track.</p> |
| <div class="sidebar-quote-attr">The Loco Workshop ยท Est. 1876</div> |
| </div> |
|
|
| </aside> |
| </div> |
|
|
| <script> |
| // Custom cursor |
| const cursor = document.getElementById('cursor'); |
| const ring = document.getElementById('cursorRing'); |
| let mx = 0, my = 0, rx = 0, ry = 0; |
| document.addEventListener('mousemove', e => { |
| mx = e.clientX; my = e.clientY; |
| cursor.style.transform = `translate(${mx-4}px,${my-4}px)`; |
| }); |
| (function animateRing() { |
| rx += (mx-rx)*0.11; ry += (my-ry)*0.11; |
| ring.style.transform = `translate(${rx-18}px,${ry-18}px)`; |
| requestAnimationFrame(animateRing); |
| })(); |
|
|
| // Set min date to today |
| const dateInput = document.getElementById('visit_date'); |
| const today = new Date(); |
| today.setDate(today.getDate() + 1); // min 1 day ahead |
| dateInput.min = today.toISOString().split('T')[0]; |
|
|
| // Prices |
| const prices = { 'Heritage Museum Tour': 50, 'Workshop Floor Tour': 100, 'Student Group Tour': 25, 'Full Day Experience': 200 }; |
| let counts = { adults: 1, children: 0, seniors: 0 }; |
|
|
| function stepChange(type, delta) { |
| const min = (type === 'adults') ? 1 : 0; |
| const max = 30; |
| counts[type] = Math.min(max, Math.max(min, counts[type] + delta)); |
| document.getElementById(type+'Val').textContent = counts[type]; |
| document.getElementById(type+'Input').value = counts[type]; |
| updateSummary(); |
| } |
|
|
| function getSelectedTour() { |
| const sel = document.querySelector('input[name="tour_type"]:checked'); |
| return sel ? sel.value : 'Heritage Museum Tour'; |
| } |
| function getSelectedTime() { |
| const sel = document.querySelector('input[name="time_slot"]:checked'); |
| return sel ? sel.value : '9:00 AM'; |
| } |
| function formatDate(val) { |
| if (!val) return null; |
| const d = new Date(val + 'T00:00:00'); |
| return d.toLocaleDateString('en-IN', { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' }); |
| } |
|
|
| function updateSummary() { |
| const tour = getSelectedTour(); |
| const price = prices[tour] || 50; |
| const total = (counts.adults + counts.seniors) * price + counts.children * Math.round(price * 0.5); |
| document.getElementById('sum_tour').textContent = tour; |
| document.getElementById('sum_adults').textContent = counts.adults; |
| document.getElementById('sum_children').textContent = counts.children; |
| document.getElementById('sum_seniors').textContent = counts.seniors; |
| document.getElementById('sum_time').textContent = getSelectedTime(); |
| document.getElementById('sum_total').textContent = 'โน' + total; |
| const dv = document.getElementById('visit_date').value; |
| const sd = document.getElementById('sum_date'); |
| if (dv) { sd.textContent = formatDate(dv); sd.classList.remove('empty'); } |
| else { sd.textContent = 'Not selected'; sd.classList.add('empty'); } |
| } |
|
|
| // Event listeners for live summary |
| document.querySelectorAll('input[name="tour_type"]').forEach(r => r.addEventListener('change', updateSummary)); |
| document.querySelectorAll('input[name="time_slot"]').forEach(r => r.addEventListener('change', updateSummary)); |
| document.getElementById('visit_date').addEventListener('change', updateSummary); |
|
|
| // Checkbox toggle |
| document.querySelectorAll('.check-item').forEach(item => { |
| item.addEventListener('click', () => { |
| const cb = item.querySelector('input[type="checkbox"]'); |
| cb.checked = !cb.checked; |
| }); |
| }); |
|
|
| // Progress bar on scroll |
| window.addEventListener('scroll', () => { |
| const scrolled = window.scrollY; |
| const maxScroll = document.body.scrollHeight - window.innerHeight; |
| const pct = maxScroll > 0 ? (scrolled / maxScroll) * 100 : 0; |
| document.getElementById('progressBar').style.width = pct + '%'; |
| // Step dots |
| const sections = ['sec1','sec2','sec3','sec4']; |
| let active = 0; |
| sections.forEach((id, i) => { |
| const el = document.getElementById(id); |
| if (el) { |
| const rect = el.getBoundingClientRect(); |
| if (rect.top < window.innerHeight * 0.6) active = i; |
| } |
| }); |
| for (let i = 0; i < 4; i++) { |
| const dot = document.getElementById('sd'+(i+1)); |
| if (!dot) continue; |
| dot.classList.remove('active','done'); |
| if (i === active) dot.classList.add('active'); |
| else if (i < active) dot.classList.add('done'); |
| } |
| }); |
|
|
| // Validation |
| function validateForm() { |
| let valid = true; |
| const fields = [ |
| { id: 'first_name', err: 'err_fname', check: v => v.trim().length > 0 }, |
| { id: 'last_name', err: 'err_lname', check: v => v.trim().length > 0 }, |
| { id: 'email', err: 'err_email', check: v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) }, |
| { id: 'phone', err: 'err_phone', check: v => v.replace(/\D/g,'').length >= 10 }, |
| { id: 'visit_date', err: 'err_date', check: v => v.trim().length > 0 }, |
| ]; |
| fields.forEach(f => { |
| const el = document.getElementById(f.id); |
| const errEl = document.getElementById(f.err); |
| const ok = f.check(el.value); |
| el.classList.toggle('error', !ok); |
| errEl.classList.toggle('show', !ok); |
| if (!ok) valid = false; |
| }); |
| const chkGuide = document.getElementById('chk_guidelines').checked; |
| const chkCancel = document.getElementById('chk_cancellation').checked; |
| const consentErr = document.getElementById('consentError'); |
| if (!chkGuide || !chkCancel) { consentErr.style.display = 'block'; valid = false; } |
| else { consentErr.style.display = 'none'; } |
| return valid; |
| } |
|
|
| // Live clear errors |
| ['first_name','last_name','email','phone','visit_date'].forEach(id => { |
| const el = document.getElementById(id); |
| if (el) el.addEventListener('input', () => { el.classList.remove('error'); }); |
| }); |
|
|
| // Submit |
| document.getElementById('bookingForm').addEventListener('submit', function(e) { |
| e.preventDefault(); |
| if (!validateForm()) { |
| const firstErr = document.querySelector('.error'); |
| if (firstErr) firstErr.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
| return; |
| } |
| // Simulate submission |
| const ref = 'LWA-2026-' + Math.floor(1000 + Math.random() * 9000); |
| document.getElementById('successRef').textContent = 'Booking Reference: ' + ref; |
| const overlay = document.getElementById('successOverlay'); |
| overlay.classList.add('show'); |
| document.body.style.overflow = 'hidden'; |
| }); |
|
|
| updateSummary(); |
| </script> |
|
|
|
|
|
|
|
|
|
|
| <footer> |
| <div class="footer-top"> |
| <div> |
| <div class="footer-brand">The Loco Workshop, Ajmer</div> |
| <div class="footer-brand-sub2">Est. 1876 ยท Sesquicentennial 2026</div> |
| <p class="footer-desc">One of Asia's oldest and most celebrated railway workshops โ serving the Indian Railways for 150 years with skill, dedication and engineering pride. Situated in the heart of Rajasthan, Ajmer.</p> |
| <div class="footer-socials"> |
| <a href="#" class="f-social">๐</a> |
| <a href="#" class="f-social">in</a> |
| <a href="#" class="f-social">โถ</a> |
| <a href="#" class="f-social">๐ท</a> |
| </div> |
| </div> |
| <div> |
| <div class="f-col-title">Visit</div> |
| <ul class="f-links"> |
| <li><a href="#">Heritage Museum</a></li> |
| <li><a href="#">Workshop Floor Tour</a></li> |
| <li><a href="#">Group Bookings</a></li> |
| <li><a href="#">Getting Here</a></li> |
| <li><a href="#">Visitor Guidelines</a></li> |
| </ul> |
| </div> |
| <div> |
| <div class="f-col-title">About</div> |
| <ul class="f-links"> |
| <li><a href="#">150-Year History</a></li> |
| <li><a href="#">Our Capabilities</a></li> |
| <li><a href="#">Workforce & Apprentice</a></li> |
| <li><a href="#">Awards & Recognition</a></li> |
| <li><a href="#">Annual Report</a></li> |
| </ul> |
| </div> |
| <div> |
| <div class="f-col-title">Connect</div> |
| <ul class="f-links"> |
| <li><a href="#">Press & Media</a></li> |
| <li><a href="#">Indian Railways โ</a></li> |
| <li><a href="#">RTI Information</a></li> |
| <li><a href="#">Contact the Workshop</a></li> |
| <li><a href="#">Tenders & Procurement</a></li> |
| </ul> |
| </div> |
| </div> |
| <div class="footer-bottom"> |
| <div class="footer-copy">ยฉ 2026 The Loco Workshop, Ajmer. Under Indian Railways, Ministry of Railways, Government of India.</div> |
| <div class="footer-rail"> |
| <span>โ</span> |
| <span>Ajmer ยท Rajasthan ยท India ยท Est. 1876</span> |
| </div> |
| </div> |
| </footer> |
| <script src="http://127.0.0.1:8000/js/index.js"></script> |
| </body> |
| </html> |