chore: complete M1.1 runtime QA and release packaging

This commit is contained in:
XShop
2026-09-13 19:04:41 +03:30
commit f11e1d650d
66 changed files with 3094 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
/**
* XShop theme bootstrap – vanilla, no jQuery.
* Feature modules are dynamically imported when DOM markers are present.
*/
(function () {
'use strict';
// Dark mode toggle via [data-theme] if enabled.
function initThemeMode() {
var data = window.xshopData;
if (!data) return;
// Theme mode is server-rendered via data-theme on <html>; this handles toggle clicks.
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-xshop-theme-toggle]');
if (!btn) return;
var html = document.documentElement;
var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', next);
try { localStorage.setItem('xshop_theme', next); } catch (_) {}
// Cookie for server-side rendering on next request.
document.cookie = 'xshop_theme=' + next + '; path=/; max-age=2592000; SameSite=Lax';
});
}
// Minimal focus trap helper for drawers/modals (used by later modules).
window.xshopTrapFocus = function (container) {
var sel = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
var nodes = container.querySelectorAll(sel);
if (!nodes.length) return function () {};
var first = nodes[0], last = nodes[nodes.length - 1];
function onKey(e) {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
}
container.addEventListener('keydown', onKey);
return function () { container.removeEventListener('keydown', onKey); };
};
function initMobileNav() {
var toggle = document.querySelector('[data-xshop-mobile-toggle]');
var nav = document.getElementById('xshop-mobile-nav');
if (!toggle || !nav) return;
var releaseTrap = null;
function setOpen(open) {
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
nav.classList.toggle('is-open', open);
nav.hidden = !open;
if (open) {
// Move focus into nav.
var firstLink = nav.querySelector('a, button');
if (firstLink) firstLink.focus();
if (window.xshopTrapFocus) releaseTrap = window.xshopTrapFocus(nav);
document.addEventListener('keydown', onDocKey);
} else {
if (releaseTrap) { releaseTrap(); releaseTrap = null; }
document.removeEventListener('keydown', onDocKey);
toggle.focus();
}
}
function onDocKey(e) {
if (e.key === 'Escape') setOpen(false);
}
toggle.addEventListener('click', function () {
var open = toggle.getAttribute('aria-expanded') === 'true';
setOpen(!open);
});
// Close when clicking outside.
document.addEventListener('click', function (e) {
if (nav.hidden) return;
if (nav.contains(e.target) || toggle.contains(e.target)) return;
setOpen(false);
});
}
initThemeMode();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMobileNav);
} else {
initMobileNav();
}
})();