- Route pages by address to theme templates (about-us, contact-us, faq, blogs, posts) - Scoped design CSS (pages.css) + Astra/WordPress fixes (pages-wp.css), page JS - Contact form emails the new Settings -> Viator Collector -> Contact email (fallback: admin email), Reply-To the visitor, honeypot spam trap - Blog list shows real posts with search, sort and paging; posts use the article design with comments as reviews and related articles - Design images converted to WebP (~15MB PNG -> ~1MB) - Theme 1.0.10, plugin 1.9.1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
6.9 KiB
JavaScript
176 lines
6.9 KiB
JavaScript
/**
|
|
* Behaviour for the theme's design pages (About, Contact, FAQ, blog list and
|
|
* post) — the parts of "new front/js/main.js" these pages use. Header/footer
|
|
* behaviour stays in chrome.js.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var cfg = window.hohoPages || {};
|
|
var page = document.querySelector('main.hoho-page');
|
|
if (!page) return;
|
|
|
|
var each = function (list, fn) { Array.prototype.forEach.call(list, fn); };
|
|
|
|
/* ---------- testimonials marquee: duplicate for a seamless loop ---------- */
|
|
var marquee = page.querySelector('[data-marquee] .marquee__track');
|
|
if (marquee) {
|
|
each(Array.prototype.slice.call(marquee.children), function (node) {
|
|
var copy = node.cloneNode(true);
|
|
copy.setAttribute('aria-hidden', 'true');
|
|
marquee.appendChild(copy);
|
|
});
|
|
}
|
|
|
|
/* ---------- FAQ accordion ---------- */
|
|
each(page.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';
|
|
each(accordion.querySelectorAll('.acc__q'), function (other) {
|
|
other.setAttribute('aria-expanded', 'false');
|
|
});
|
|
btn.setAttribute('aria-expanded', String(!open));
|
|
});
|
|
});
|
|
|
|
/* ---------- FAQ page: category nav follows the scroll ---------- */
|
|
var catLinks = Array.prototype.slice.call(page.querySelectorAll('.catnav__link'));
|
|
if (catLinks.length && 'IntersectionObserver' in window) {
|
|
var setCat = function (id) {
|
|
catLinks.forEach(function (a) {
|
|
var on = a.getAttribute('href') === '#' + id;
|
|
a.classList.toggle('is-active', on);
|
|
if (on) a.setAttribute('aria-current', 'true');
|
|
else a.removeAttribute('aria-current');
|
|
});
|
|
};
|
|
var catObserver = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (entry.isIntersecting) setCat(entry.target.id);
|
|
});
|
|
}, { rootMargin: '-30% 0px -60% 0px' });
|
|
catLinks.forEach(function (a) {
|
|
var target = document.querySelector(a.getAttribute('href'));
|
|
if (target) catObserver.observe(target);
|
|
});
|
|
}
|
|
|
|
/* ---------- blog list: sort applies on change ---------- */
|
|
each(page.querySelectorAll('select[data-autosubmit]'), function (select) {
|
|
select.addEventListener('change', function () { select.form.submit(); });
|
|
});
|
|
|
|
/* ---------- single article: share links, copy link, more reviews ---------- */
|
|
var post = document.getElementById('post');
|
|
if (post) {
|
|
var pageUrl = encodeURIComponent(window.location.href.split('#')[0]);
|
|
var shareTargets = {
|
|
telegram: 'https://t.me/share/url?url=',
|
|
facebook: 'https://www.facebook.com/sharer/sharer.php?u=',
|
|
whatsapp: 'https://wa.me/?text='
|
|
};
|
|
each(post.querySelectorAll('[data-share]'), function (a) {
|
|
var base = shareTargets[a.getAttribute('data-share')];
|
|
if (base) a.href = base + pageUrl;
|
|
});
|
|
|
|
var copyBtn = post.querySelector('[data-copy-link]');
|
|
if (copyBtn && navigator.clipboard) {
|
|
var copyLabel = copyBtn.textContent;
|
|
copyBtn.addEventListener('click', function () {
|
|
navigator.clipboard.writeText(window.location.href.split('#')[0]).then(function () {
|
|
copyBtn.textContent = cfg.copied || 'Link copied';
|
|
copyBtn.classList.add('is-done');
|
|
window.setTimeout(function () {
|
|
copyBtn.textContent = copyLabel;
|
|
copyBtn.classList.remove('is-done');
|
|
}, 2000);
|
|
}, function () {});
|
|
});
|
|
}
|
|
|
|
var reviewList = document.getElementById('reviewList');
|
|
var reviewMore = document.getElementById('reviewMore');
|
|
if (reviewList && reviewMore) {
|
|
var moreLabel = reviewMore.querySelector('span');
|
|
reviewMore.addEventListener('click', function () {
|
|
var open = reviewList.classList.toggle('is-collapsed') === false;
|
|
reviewMore.setAttribute('aria-expanded', String(open));
|
|
moreLabel.textContent = open ? cfg.fewer : cfg.more;
|
|
});
|
|
}
|
|
}
|
|
|
|
/* ---------- contact form: validate, then send without leaving the page ---------- */
|
|
var ticketForm = document.getElementById('ticketForm');
|
|
var ticketStatus = document.getElementById('ticketStatus');
|
|
if (ticketForm && ticketStatus && window.fetch && window.FormData) {
|
|
var submitBtn = ticketForm.querySelector('[type="submit"]');
|
|
ticketForm.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var bad = null;
|
|
each(ticketForm.querySelectorAll('.rinput'), function (field) {
|
|
var ok = field.value.trim() !== '' && field.checkValidity();
|
|
field.setAttribute('aria-invalid', String(!ok));
|
|
if (!ok && !bad) bad = field;
|
|
});
|
|
ticketStatus.classList.toggle('is-error', !!bad);
|
|
if (bad) {
|
|
ticketStatus.textContent = cfg.invalid;
|
|
bad.focus();
|
|
return;
|
|
}
|
|
|
|
ticketStatus.textContent = cfg.sending;
|
|
submitBtn.disabled = true;
|
|
fetch(ticketForm.getAttribute('data-ajax'), { method: 'POST', credentials: 'same-origin', body: new FormData(ticketForm) })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (res) {
|
|
var ok = res && res.success;
|
|
ticketStatus.classList.toggle('is-error', !ok);
|
|
ticketStatus.textContent = (res && res.data && res.data.message) || cfg.failed;
|
|
if (ok) ticketForm.reset();
|
|
})
|
|
.catch(function () {
|
|
ticketStatus.classList.add('is-error');
|
|
ticketStatus.textContent = cfg.failed;
|
|
})
|
|
.then(function () { submitBtn.disabled = false; });
|
|
});
|
|
}
|
|
|
|
/* ---------- scroll reveal: sections fade/rise in, cards cascade after ---------- */
|
|
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
if (!reduceMotion && 'IntersectionObserver' in window) {
|
|
var sections = Array.prototype.slice.call(page.querySelectorAll(':scope > section, :scope > article, :scope > div.section'));
|
|
var cardSelector = '.dcard, .acard, .titem, .rcard, .quote, .statcard';
|
|
var groupCounts = new Map();
|
|
|
|
sections.forEach(function (section) {
|
|
section.classList.add('reveal');
|
|
each(section.querySelectorAll(cardSelector), function (card) {
|
|
var group = card.parentElement;
|
|
var i = groupCounts.get(group) || 0;
|
|
groupCounts.set(group, i + 1);
|
|
card.classList.add('reveal-item');
|
|
card.style.animationDelay = (Math.min(i, 7) * 0.07) + 's';
|
|
});
|
|
});
|
|
|
|
var revealObserver = new IntersectionObserver(function (entries, obs) {
|
|
entries.forEach(function (entry) {
|
|
if (!entry.isIntersecting) return;
|
|
entry.target.classList.add('is-visible');
|
|
each(entry.target.querySelectorAll('.reveal-item'), function (item) {
|
|
item.classList.add('is-visible');
|
|
});
|
|
obs.unobserve(entry.target);
|
|
});
|
|
}, { threshold: 0.15, rootMargin: '0px 0px -8% 0px' });
|
|
|
|
sections.forEach(function (section) { revealObserver.observe(section); });
|
|
}
|
|
})();
|