210 lines
7.8 KiB
JavaScript
210 lines
7.8 KiB
JavaScript
|
|
/* ============================================================
|
|||
|
|
Storm Cloud Jumpers — site interactions
|
|||
|
|
============================================================ */
|
|||
|
|
(function () {
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
/* ----------------------------------------------------------
|
|||
|
|
FORM DELIVERY CONFIG
|
|||
|
|
------------------------------------------------------------
|
|||
|
|
By default the inquiry form opens the visitor's email app
|
|||
|
|
addressed to Storm Cloud Jumpers (mailto:). No server needed.
|
|||
|
|
|
|||
|
|
To have submissions land straight in the Gmail inbox WITHOUT
|
|||
|
|
the visitor needing a mail app, deploy the included
|
|||
|
|
`form-handler.gs` as a Google Apps Script web app (it sends
|
|||
|
|
from the shop's own Gmail) and paste its /exec URL below.
|
|||
|
|
When FORM_ENDPOINT is set, the form POSTs to it and falls
|
|||
|
|
back to mailto only if the request fails.
|
|||
|
|
---------------------------------------------------------- */
|
|||
|
|
var CONTACT_EMAIL = 'stormcloudjumpers@gmail.com';
|
|||
|
|
var FORM_ENDPOINT = ''; // e.g. 'https://script.google.com/macros/s/XXXX/exec'
|
|||
|
|
|
|||
|
|
var $ = function (s, c) { return (c || document).querySelector(s); };
|
|||
|
|
var $$ = function (s, c) { return Array.prototype.slice.call((c || document).querySelectorAll(s)); };
|
|||
|
|
|
|||
|
|
/* ---------- Sticky nav shadow ---------- */
|
|||
|
|
var nav = $('#nav');
|
|||
|
|
var onScroll = function () {
|
|||
|
|
if (nav) nav.classList.toggle('scrolled', window.scrollY > 12);
|
|||
|
|
};
|
|||
|
|
onScroll();
|
|||
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|||
|
|
|
|||
|
|
/* ---------- Mobile menu ---------- */
|
|||
|
|
var burger = $('#burger');
|
|||
|
|
var links = $('#navLinks');
|
|||
|
|
var closeMenu = function () {
|
|||
|
|
if (!links) return;
|
|||
|
|
links.classList.remove('open');
|
|||
|
|
nav.classList.remove('menu-open');
|
|||
|
|
burger.setAttribute('aria-expanded', 'false');
|
|||
|
|
};
|
|||
|
|
if (burger && links) {
|
|||
|
|
burger.addEventListener('click', function () {
|
|||
|
|
var open = links.classList.toggle('open');
|
|||
|
|
nav.classList.toggle('menu-open', open);
|
|||
|
|
burger.setAttribute('aria-expanded', String(open));
|
|||
|
|
});
|
|||
|
|
$$('a', links).forEach(function (a) { a.addEventListener('click', closeMenu); });
|
|||
|
|
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeMenu(); });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ---------- Reveal on scroll ---------- */
|
|||
|
|
var reveals = $$('.reveal');
|
|||
|
|
if ('IntersectionObserver' in window && reveals.length) {
|
|||
|
|
var io = new IntersectionObserver(function (entries) {
|
|||
|
|
entries.forEach(function (en) {
|
|||
|
|
if (en.isIntersecting) { en.target.classList.add('in'); io.unobserve(en.target); }
|
|||
|
|
});
|
|||
|
|
}, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' });
|
|||
|
|
reveals.forEach(function (el) { io.observe(el); });
|
|||
|
|
} else {
|
|||
|
|
reveals.forEach(function (el) { el.classList.add('in'); });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ---------- Hero parallax (subtle, respects reduced motion) ---------- */
|
|||
|
|
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|||
|
|
var heroBg = $('.hero__bg');
|
|||
|
|
if (heroBg && !reduce) {
|
|||
|
|
window.addEventListener('scroll', function () {
|
|||
|
|
var y = window.scrollY;
|
|||
|
|
if (y < window.innerHeight) heroBg.style.transform = 'translateY(' + (y * 0.25) + 'px)';
|
|||
|
|
}, { passive: true });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ---------- "Reserve" buttons prefill the spider field ---------- */
|
|||
|
|
$$('[data-inquire]').forEach(function (btn) {
|
|||
|
|
btn.addEventListener('click', function () {
|
|||
|
|
var field = $('#spiderField');
|
|||
|
|
if (field) field.value = btn.getAttribute('data-inquire') || '';
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/* ---------- Inquiry form ---------- */
|
|||
|
|
var form = $('#inquiryForm');
|
|||
|
|
var ok = $('#formOk');
|
|||
|
|
|
|||
|
|
var buildMailto = function (data) {
|
|||
|
|
var subjSpider = data.spider ? ' — ' + data.spider : '';
|
|||
|
|
var subject = 'Spider Inquiry' + subjSpider + ' (' + data.name + ')';
|
|||
|
|
var body =
|
|||
|
|
'Name: ' + data.name + '\n' +
|
|||
|
|
'Email: ' + data.email + '\n' +
|
|||
|
|
'Spider of interest: ' + (data.spider || '(not specified)') + '\n\n' +
|
|||
|
|
'Message:\n' + (data.message || '(none)') + '\n';
|
|||
|
|
return 'mailto:' + CONTACT_EMAIL +
|
|||
|
|
'?subject=' + encodeURIComponent(subject) +
|
|||
|
|
'&body=' + encodeURIComponent(body);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (form) {
|
|||
|
|
form.addEventListener('submit', function (e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
if (!form.checkValidity()) { form.reportValidity(); return; }
|
|||
|
|
|
|||
|
|
var data = {
|
|||
|
|
name: form.name.value.trim(),
|
|||
|
|
email: form.email.value.trim(),
|
|||
|
|
spider: form.spider.value.trim(),
|
|||
|
|
message: form.message.value.trim()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var finishMailto = function () {
|
|||
|
|
window.location.href = buildMailto(data);
|
|||
|
|
if (ok) ok.hidden = false;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (FORM_ENDPOINT) {
|
|||
|
|
// Progressive enhancement: POST to Google Apps Script (sends via Gmail).
|
|||
|
|
var body = new URLSearchParams(data).toString();
|
|||
|
|
fetch(FORM_ENDPOINT, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|||
|
|
body: body
|
|||
|
|
}).then(function (r) {
|
|||
|
|
if (!r.ok) throw new Error('bad status');
|
|||
|
|
if (ok) { ok.textContent = 'Thanks! Your inquiry has been sent — we’ll reply within a day or two.'; ok.hidden = false; }
|
|||
|
|
form.reset();
|
|||
|
|
}).catch(finishMailto);
|
|||
|
|
} else {
|
|||
|
|
finishMailto();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ---------- Mamas lightbox ---------- */
|
|||
|
|
var lb = $('#lightbox');
|
|||
|
|
var lbImg = $('#lightboxImg');
|
|||
|
|
var lbCap = $('#lightboxCap');
|
|||
|
|
var lbClose = $('#lightboxClose');
|
|||
|
|
var lastFocused = null;
|
|||
|
|
|
|||
|
|
var openLightbox = function (trigger, img, capHTML) {
|
|||
|
|
if (!img || !lb) return;
|
|||
|
|
lastFocused = trigger;
|
|||
|
|
lbImg.src = img.getAttribute('src');
|
|||
|
|
lbImg.alt = img.getAttribute('alt') || '';
|
|||
|
|
lbCap.innerHTML = capHTML || '';
|
|||
|
|
lb.hidden = false;
|
|||
|
|
requestAnimationFrame(function () { lb.classList.add('open'); });
|
|||
|
|
document.body.style.overflow = 'hidden';
|
|||
|
|
lbClose.focus();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var wireZoom = function (trigger, getImg, getCap, label) {
|
|||
|
|
trigger.setAttribute('role', 'button');
|
|||
|
|
trigger.setAttribute('tabindex', '0');
|
|||
|
|
trigger.setAttribute('aria-label', label);
|
|||
|
|
var open = function () { openLightbox(trigger, getImg(), getCap()); };
|
|||
|
|
trigger.addEventListener('click', open);
|
|||
|
|
trigger.addEventListener('keydown', function (e) {
|
|||
|
|
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); }
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var closeLightbox = function () {
|
|||
|
|
if (!lb || lb.hidden) return;
|
|||
|
|
lb.classList.remove('open');
|
|||
|
|
document.body.style.overflow = '';
|
|||
|
|
var done = function () {
|
|||
|
|
lb.hidden = true;
|
|||
|
|
lbImg.src = '';
|
|||
|
|
lb.removeEventListener('transitionend', done);
|
|||
|
|
if (lastFocused && lastFocused.focus) lastFocused.focus();
|
|||
|
|
};
|
|||
|
|
if (reduce) { done(); } else { lb.addEventListener('transitionend', done); }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (lb) {
|
|||
|
|
// Meet the Mamas gallery
|
|||
|
|
$$('.mama').forEach(function (fig) {
|
|||
|
|
var name = $('.mama__cap b', fig);
|
|||
|
|
wireZoom(fig,
|
|||
|
|
function () { return $('img', fig); },
|
|||
|
|
function () { var c = $('.mama__cap', fig); return c ? c.innerHTML : ''; },
|
|||
|
|
'Enlarge photo' + (name ? ' of ' + name.textContent : ''));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Available spiders — click the photo to enlarge
|
|||
|
|
$$('#available .card__media').forEach(function (media) {
|
|||
|
|
var card = media.closest('.card');
|
|||
|
|
wireZoom(media,
|
|||
|
|
function () { return $('img', media); },
|
|||
|
|
function () {
|
|||
|
|
var h = $('h3', card), sci = $('.card__sci', card);
|
|||
|
|
return (h ? '<b>' + h.textContent + '</b>' : '') + (sci ? '<span>' + sci.textContent + '</span>' : '');
|
|||
|
|
},
|
|||
|
|
'Enlarge photo' + (card && $('h3', card) ? ' of ' + $('h3', card).textContent : ''));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
lbClose.addEventListener('click', closeLightbox);
|
|||
|
|
lb.addEventListener('click', function (e) { if (e.target === lb) closeLightbox(); });
|
|||
|
|
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeLightbox(); });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ---------- Footer year ---------- */
|
|||
|
|
var y = $('#year');
|
|||
|
|
if (y) y.textContent = String(new Date().getFullYear());
|
|||
|
|
})();
|