192 lines
5.9 KiB
JavaScript
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();
|
||
|
|
}
|
||
|
|
}());
|