feat(theme): About, Contact, FAQ, blog list and post pages from new front

- 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>
This commit is contained in:
2026-10-29 16:20:11 +03:30
parent 930b7e0534
commit f58ed49d07
30 changed files with 5932 additions and 14 deletions

View File

@@ -0,0 +1,175 @@
/**
* 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); });
}
})();