/* Viator Collector — front-end interactions for the "Hop On Hop Off" design. Ported from the mockup's main.js, trimmed to what the plugin renders inside #vc-app. Listing filtering itself stays server-side (GET form submit); this file only adds UX: card rails, FAQ accordion, testimonials marquee, the date field, and auto-submit on quick filter toggles. */ (function () { "use strict"; // The design is scoped to .vc-app wrappers; there can be several on one page // (e.g. multiple homepage section shortcodes), so query document-wide. var app = document; /* ---------- card rails (prev / next) ---------- */ function railStep(rail) { var card = rail.firstElementChild; if (!card) return rail.clientWidth; var gap = parseFloat(getComputedStyle(rail).columnGap) || 0; return card.getBoundingClientRect().width + gap; } function syncRailButtons(rail, prev, next) { var max = rail.scrollWidth - rail.clientWidth - 1; if (prev) prev.disabled = rail.scrollLeft <= 0; if (next) next.disabled = rail.scrollLeft >= max; } Array.prototype.forEach.call(app.querySelectorAll(".rail"), function (rail) { var prev = document.querySelector('[data-rail-prev="' + rail.id + '"]'); var next = document.querySelector('[data-rail-next="' + rail.id + '"]'); if (prev) prev.addEventListener("click", function () { rail.scrollBy({ left: -railStep(rail), behavior: "smooth" }); }); if (next) next.addEventListener("click", function () { rail.scrollBy({ left: railStep(rail), behavior: "smooth" }); }); rail.addEventListener("scroll", function () { syncRailButtons(rail, prev, next); }, { passive: true }); window.addEventListener("resize", function () { syncRailButtons(rail, prev, next); }); syncRailButtons(rail, prev, next); }); /* ---------- testimonials marquee: duplicate for a seamless loop ---------- */ Array.prototype.forEach.call(app.querySelectorAll("[data-marquee] .marquee__track"), function (track) { Array.prototype.slice.call(track.children).forEach(function (node) { var copy = node.cloneNode(true); copy.setAttribute("aria-hidden", "true"); track.appendChild(copy); }); }); /* ---------- FAQ accordion ---------- */ Array.prototype.forEach.call(app.querySelectorAll("[data-accordion]"), function (accordion) { accordion.addEventListener("click", function (e) { var btn = e.target.closest(".acc__q"); if (!btn) return; var open = btn.getAttribute("aria-expanded") === "true"; Array.prototype.forEach.call(accordion.querySelectorAll(".acc__q"), function (other) { other.setAttribute("aria-expanded", "false"); }); btn.setAttribute("aria-expanded", String(!open)); }); }); /* ---------- date field: text placeholder, native picker on focus ---------- */ Array.prototype.forEach.call(app.querySelectorAll("[data-datefield]"), function (dateField) { dateField.addEventListener("focus", function () { dateField.type = "date"; if (typeof dateField.showPicker === "function") { try { dateField.showPicker(); } catch (err) { /* not user-activated */ } } }); dateField.addEventListener("blur", function () { if (!dateField.value) dateField.type = "text"; }); }); /* ---------- listing: auto-submit on quick toggles (checks / sort) ---------- */ Array.prototype.forEach.call(app.querySelectorAll("form.filters, form#vc-filter-form"), function (form) { form.addEventListener("change", function (e) { if (e.target.closest(".vc-autosubmit")) form.submit(); }); }); /* ---------- city guide: long About text folds behind "Read more" ---------- */ Array.prototype.forEach.call(app.querySelectorAll("[data-vc-clamp]"), function (text) { var btn = text.nextElementSibling; text.classList.add("is-clamped"); if (!btn || text.scrollHeight <= text.clientHeight + 8) { text.classList.remove("is-clamped"); return; } btn.hidden = false; btn.addEventListener("click", function () { var open = text.classList.toggle("is-clamped") === false; btn.setAttribute("aria-expanded", open ? "true" : "false"); btn.querySelector("span").textContent = open ? "Read less" : "Read more"; }); }); /* ---------- single tour: "view all photos" scrolls the gallery ---------- */ var viewAll = app.querySelector(".tg-viewall, .gallery__viewall"); if (viewAll) viewAll.addEventListener("click", function () { var g = app.querySelector(".gallery, .tour-gallery"); if (g) g.scrollIntoView({ behavior: "smooth" }); }); /* ---------- gallery lightbox (zoom / view photos) ---------- */ (function () { var imgs = Array.prototype.slice.call(app.querySelectorAll(".gallery img")); if (!imgs.length) return; var host = imgs[0].closest(".vc-app") || document.body; var box = document.createElement("div"); box.className = "vc-lightbox" + (imgs.length < 2 ? " vc-lightbox--single" : ""); box.setAttribute("role", "dialog"); box.setAttribute("aria-modal", "true"); box.innerHTML = '' + '' + '' + '' + ''; host.appendChild(box); var bigImg = box.querySelector(".vc-lightbox__img"); var count = box.querySelector(".vc-lightbox__count"); var idx = 0; function show(i) { idx = (i + imgs.length) % imgs.length; bigImg.src = imgs[idx].currentSrc || imgs[idx].src; bigImg.alt = imgs[idx].alt || ""; count.textContent = (idx + 1) + " / " + imgs.length; } function open(i) { show(i); box.classList.add("is-open"); document.documentElement.style.overflow = "hidden"; } function close() { box.classList.remove("is-open"); document.documentElement.style.overflow = ""; } imgs.forEach(function (img, i) { img.addEventListener("click", function () { open(i); }); }); box.querySelector(".vc-lightbox__prev").addEventListener("click", function () { show(idx - 1); }); box.querySelector(".vc-lightbox__next").addEventListener("click", function () { show(idx + 1); }); box.querySelector(".vc-lightbox__close").addEventListener("click", close); box.addEventListener("click", function (e) { if (e.target === box) close(); }); /* tour page top gallery: "All N photos" opens at the first photo not on show */ var allBtn = app.querySelector(".tgal__all"); if (allBtn) allBtn.addEventListener("click", function () { open(Math.min(3, imgs.length - 1)); }); document.addEventListener("keydown", function (e) { if (!box.classList.contains("is-open")) return; if (e.key === "Escape") close(); else if (e.key === "ArrowLeft") show(idx - 1); else if (e.key === "ArrowRight") show(idx + 1); }); })(); })();