Files
OTSSigns-Website/theme/assets/js/kiosks-animator.js
Matt Batchelder 942f02a1c1 Add feature animations and update styles for kiosks and about pages
- Introduced `featuresAnim` option in editor and index files to enable feature ticker animations.
- Updated rendering logic to include feature animations in the about section.
- Added new JavaScript files for handling animations on the about page and kiosks page.
- Adjusted CSS classes to accommodate new animations and ensure proper visual representation.
- Changed primary color in theme defaults for a more cohesive design.
2026-04-09 11:06:17 -04:00

192 lines
5.9 KiB
JavaScript

/**
* Kiosks Page Animators
* 1. Self Check-In — cycles through three visitor check-in scenarios
* 2. Self Order — cycles active category + highlighted product in a catalogue kiosk
*
* Both respect prefers-reduced-motion and pause via IntersectionObserver.
* Mirrors the patterns and conventions of solutions-animator.js.
*/
/* ── 1. Self Check-In Kiosk Animator ───────────────────────────────────── */
(function () {
'use strict';
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var CYCLE_MS = 3500; /* ms per scene */
function initCheckin(stage) {
var scenes = stage.querySelectorAll('.checkin-scene');
if (!scenes.length) return;
var state = { idx: 0, timer: null, paused: false };
/* Show the first scene immediately */
scenes[0].classList.add('is-active');
function advance() {
if (state.paused) return;
scenes[state.idx].classList.remove('is-active');
state.idx = (state.idx + 1) % scenes.length;
scenes[state.idx].classList.add('is-active');
}
function startTimer() {
if (state.timer) return;
state.timer = setInterval(advance, CYCLE_MS);
}
function stopTimer() {
clearInterval(state.timer);
state.timer = null;
}
if ('IntersectionObserver' in window) {
new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
state.paused = !e.isIntersecting;
e.isIntersecting ? startTimer() : stopTimer();
});
}, { rootMargin: '200px', threshold: 0.05 }).observe(stage);
}
startTimer();
}
function boot() {
var stages = document.querySelectorAll('.checkin-stage');
if (!stages.length) return;
for (var i = 0; i < stages.length; i++) {
if (stages[i]._checkinAnim) continue;
stages[i]._checkinAnim = true;
initCheckin(stages[i]);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
}());
/* ── 2. Self-Order / Product Lookup Kiosk Animator ─────────────────────── */
(function () {
'use strict';
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
/* In reduced-motion: just show all products for the first category */
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.selforder-stage').forEach(function (stage) {
var first = stage.querySelector('.selforder-cat');
if (first) first.classList.add('is-active');
var cat = first ? first.getAttribute('data-so-cat') : null;
if (cat) {
stage.querySelectorAll('.selforder-product[data-so-cat="' + cat + '"]').forEach(function (p) {
p.classList.add('is-visible');
});
}
});
});
return;
}
/* Category rotation order */
var CATS = ['electronics', 'clothing', 'food', 'home'];
var CYCLE_MS = 4000; /* ms per category */
/* Which product index within the visible set to highlight each cycle */
var HIGHLIGHT_IDX = [0, 1, 0, 1];
function initSelfOrder(stage) {
var catEls = stage.querySelectorAll('.selforder-cat');
var productEls = stage.querySelectorAll('.selforder-product');
var addBtn = stage.querySelector('.selforder-add');
if (!catEls.length || !productEls.length) return;
var state = { idx: 0, timer: null, paused: false };
function showCategory(catIdx) {
var cat = CATS[catIdx % CATS.length];
/* Update category tabs */
for (var i = 0; i < catEls.length; i++) {
catEls[i].classList.toggle('is-active', catEls[i].getAttribute('data-so-cat') === cat);
}
/* Show matching products, hide others */
var visible = [];
for (var j = 0; j < productEls.length; j++) {
var matches = productEls[j].getAttribute('data-so-cat') === cat;
productEls[j].classList.toggle('is-visible', matches);
productEls[j].classList.remove('is-highlighted');
if (matches) visible.push(productEls[j]);
}
/* Highlight one product after a short delay (simulates tap) */
var hlIdx = HIGHLIGHT_IDX[catIdx % HIGHLIGHT_IDX.length];
var toHighlight = visible[hlIdx % (visible.length || 1)];
if (toHighlight) {
setTimeout(function () {
toHighlight.classList.add('is-highlighted');
if (addBtn) {
addBtn.removeAttribute('disabled');
addBtn.classList.add('is-enabled');
}
}, 900);
}
/* Reset button when next cycle starts */
if (addBtn) {
addBtn.setAttribute('disabled', '');
addBtn.classList.remove('is-enabled');
}
}
/* Show first category immediately */
showCategory(0);
function advance() {
if (state.paused) return;
state.idx++;
showCategory(state.idx);
}
function startTimer() {
if (state.timer) return;
state.timer = setInterval(advance, CYCLE_MS);
}
function stopTimer() {
clearInterval(state.timer);
state.timer = null;
}
if ('IntersectionObserver' in window) {
new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
state.paused = !e.isIntersecting;
e.isIntersecting ? startTimer() : stopTimer();
});
}, { rootMargin: '200px', threshold: 0.05 }).observe(stage);
}
startTimer();
}
function boot() {
var stages = document.querySelectorAll('.selforder-stage');
if (!stages.length) return;
for (var i = 0; i < stages.length; i++) {
if (stages[i]._selforderAnim) continue;
stages[i]._selforderAnim = true;
initSelfOrder(stages[i]);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
}());