(function () { const API = 'https://epictravelexpeditions.com/api'; /* ── Styles ── */ const style = document.createElement('style'); style.textContent = ` .et-scroll-track { overflow: hidden; position: relative; } .et-scroll-track::before, .et-scroll-track::after { content: ''; position: absolute; top: 0; bottom: 0; width: 80px; z-index: 2; pointer-events: none; } .et-scroll-track::before { left: 0; background: linear-gradient(to right, white, transparent); } .et-scroll-track::after { right: 0; background: linear-gradient(to left, white, transparent); } .et-scroll-inner { display: flex; gap: 24px; width: max-content; animation: et-slide 40s linear infinite; } .et-scroll-inner:hover { animation-play-state: paused; } @keyframes et-slide { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } .et-card { width: 300px; flex-shrink: 0; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.06); transition: box-shadow .2s; } .et-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.12); } .et-avatar { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; background: #e5e7eb; } .et-avatar-placeholder { width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(135deg, #3b82f6, #2563eb); display: flex; align-items: center; justify-content: center; color: #fff; font-size: 22px; font-weight: 700; } .et-name { font-weight: 700; font-size: 15px; color: #111827; margin: 0; } .et-loc { font-size: 13px; color: #6b7280; margin: 2px 0 12px; } .et-msg { font-size: 14px; color: #374151; line-height: 1.6; font-style: italic; } /* ── Form ── */ #et-form-section { background: #f9fafb; padding: 64px 24px; text-align: center; } #et-form-section h2 { font-size: 2rem; font-weight: 700; color: #111827; margin-bottom: 8px; } #et-form-section p { color: #6b7280; margin-bottom: 32px; } #et-form { max-width: 560px; margin: 0 auto; text-align: left; display: flex; flex-direction: column; gap: 16px; } #et-form input, #et-form textarea { width: 100%; box-sizing: border-box; padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; color: #111827; background: #fff; outline: none; transition: border-color .15s; } #et-form input:focus, #et-form textarea:focus { border-color: #3b82f6; } #et-form textarea { resize: vertical; min-height: 100px; } .et-file-label { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border: 1px dashed #d1d5db; border-radius: 8px; cursor: pointer; font-size: 14px; color: #6b7280; background: #fff; transition: border-color .15s; } .et-file-label:hover { border-color: #3b82f6; color: #3b82f6; } #et-file-input { display: none; } #et-preview { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; display: none; } #et-submit { padding: 12px 24px; background: #2563eb; color: #fff; border: none; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; transition: background .15s; align-self: flex-end; } #et-submit:hover { background: #1d4ed8; } #et-submit:disabled { background: #93c5fd; cursor: not-allowed; } #et-msg { font-size: 14px; text-align: center; } .et-success { color: #16a34a; } .et-error { color: #dc2626; } #et-img-row { display: flex; align-items: center; gap: 12px; } `; document.head.appendChild(style); /* ── Build a testimonial card ── */ function buildCard(t) { const card = document.createElement('div'); card.className = 'et-card'; let avatarHtml; if (t.image_path) { avatarHtml = `${t.full_name}`; } else { const initial = t.full_name.charAt(0).toUpperCase(); avatarHtml = `
${initial}
`; } card.innerHTML = `
${avatarHtml}

${t.full_name}

${t.location}

“${t.message}”

`; return card; } /* ── Replace static testimonials section ── */ function replaceTestimonials(section, testimonials) { const inner = document.createElement('div'); inner.className = 'et-scroll-inner'; const cards = testimonials.map(buildCard); cards.forEach(c => inner.appendChild(c)); // Duplicate for seamless loop cards.forEach(c => inner.appendChild(c.cloneNode(true))); const track = document.createElement('div'); track.className = 'et-scroll-track'; track.appendChild(inner); // Keep the heading, replace the grid const heading = section.querySelector('[class*="grid"]') || section.lastElementChild; if (heading) heading.replaceWith(track); else section.appendChild(track); } /* ── Upload image (no auth required for testimonials) ── */ async function uploadImage(file) { const fd = new FormData(); fd.append('file', file); const res = await fetch(`${API}/testimonials/upload`, { method: 'POST', body: fd }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Upload failed'); return data.url; } /* ── Submission form ── */ function buildForm() { const section = document.createElement('section'); section.id = 'et-form-section'; section.innerHTML = `

Share Your Experience

Traveled with us? We'd love to hear about it.

preview
`; setTimeout(() => { const form = document.getElementById('et-form'); const nameEl = document.getElementById('et-name'); const locEl = document.getElementById('et-location'); const msgEl = document.getElementById('et-msg-input'); const fileEl = document.getElementById('et-file-input'); const preview = document.getElementById('et-preview'); const statusEl = document.getElementById('et-msg'); const submitBtn = document.getElementById('et-submit'); fileEl.addEventListener('change', () => { const f = fileEl.files[0]; if (f) { preview.src = URL.createObjectURL(f); preview.style.display = 'block'; } }); form.addEventListener('submit', async (e) => { e.preventDefault(); statusEl.textContent = ''; const name = nameEl.value.trim(); const loc = locEl.value.trim(); const msg = msgEl.value.trim(); if (!name || !loc || !msg) { statusEl.textContent = 'Please fill in all required fields.'; statusEl.className = 'et-error'; return; } submitBtn.disabled = true; submitBtn.textContent = 'Submitting…'; try { let imagePath = null; if (fileEl.files[0]) { imagePath = await uploadImage(fileEl.files[0]); } const res = await fetch(`${API}/testimonials`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ full_name: name, location: loc, message: msg, image_path: imagePath }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Submission failed'); statusEl.textContent = 'Thank you! Your testimonial has been submitted for review.'; statusEl.className = 'et-success'; form.reset(); preview.style.display = 'none'; } catch (err) { statusEl.textContent = err.message; statusEl.className = 'et-error'; } finally { submitBtn.disabled = false; submitBtn.textContent = 'Submit Testimonial'; } }); }, 0); return section; } /* ── Main: observe DOM until static testimonials appear ── */ async function init() { let testimonials = []; try { const res = await fetch(`${API}/testimonials`); testimonials = await res.json(); } catch (_) {} let done = false; const observer = new MutationObserver(() => { if (done) return; const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { if (node.nodeValue && node.nodeValue.includes('What Our Travelers Say')) { // Walk up to the section/div container let el = node.parentElement; for (let i = 0; i < 6; i++) { if (el && (el.tagName === 'SECTION' || (el.className && el.className.includes('py-')))) break; el = el ? el.parentElement : null; } if (!el) break; done = true; observer.disconnect(); if (testimonials.length > 0) { replaceTestimonials(el, testimonials); } // Inject form after the section const formSection = buildForm(); el.after(formSection); break; } } }); observer.observe(document.body, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();