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
+36
View File
@@ -0,0 +1,36 @@
/* XShop base — minimal reset + tokens wiring. Logical properties throughout. */
*, *::before, *::after { box-sizing: border-box; }
html { font-family: var(--xshop-font-sans); line-height: var(--xshop-leading); color: var(--xshop-text); background: var(--xshop-bg); -webkit-font-smoothing: antialiased; }
body { margin: 0; background: var(--xshop-bg); color: var(--xshop-text); }
a { color: var(--xshop-link); text-decoration: none; transition: color var(--xshop-transition); }
a:hover { color: var(--xshop-link-hover); }
a:focus-visible { outline: 2px solid var(--xshop-focus); outline-offset: 2px; }
img { max-width: 100%; height: auto; display: block; }
p { margin-block: 0 var(--xshop-space-4); }
h1, h2, h3, h4, h5, h6 { line-height: var(--xshop-leading-tight); margin-block: 0 var(--xshop-space-3); font-weight: var(--xshop-weight-bold); overflow-wrap: break-word; }
ul, ol { margin: 0; padding: 0; }
/* Container */
.xshop-container { max-width: var(--xshop-container); margin-inline: auto; padding-inline: var(--xshop-space-4); }
.xshop-container--narrow { max-width: var(--xshop-container-narrow); }
/* Skip link */
.xshop-skip-link { position: absolute; inset-inline-start: -9999px; inset-block-start: auto; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.xshop-skip-link:focus { inset-inline-start: var(--xshop-space-4); inset-block-start: var(--xshop-space-4); inline-size: auto; block-size: auto; overflow: visible; clip-path: none; white-space: normal; background: var(--xshop-card); color: var(--xshop-text); padding: var(--xshop-space-2) var(--xshop-space-4); border: 2px solid var(--xshop-focus); border-radius: var(--xshop-radius-sm); z-index: var(--xshop-z-modal); font-weight: var(--xshop-weight-medium); }
/* Focus */
:focus-visible { outline: 2px solid var(--xshop-focus); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }
/* Forms */
input, textarea, select, button { font: inherit; }
input[type="search"], input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="tel"], textarea, select {
background: var(--xshop-input-bg); color: var(--xshop-input-text); border: 1px solid var(--xshop-input-border);
border-radius: var(--xshop-radius-sm); padding: var(--xshop-space-2) var(--xshop-space-3); inline-size: 100%;
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--xshop-focus); outline-offset: 0; border-color: var(--xshop-focus); }
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
@@ -0,0 +1,90 @@
/* XShop Components — buttons, cards, breadcrumbs, pagination, notices, etc. */
/* Buttons */
.xshop-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--xshop-space-2); padding: var(--xshop-space-2) var(--xshop-space-4); border-radius: var(--xshop-radius-sm); border: 1px solid transparent; font-weight: var(--xshop-weight-medium); font-size: var(--xshop-text-sm); cursor: pointer; transition: background var(--xshop-transition), border-color var(--xshop-transition), color var(--xshop-transition); text-decoration: none; line-height: 1.4; }
.xshop-btn--primary { background: var(--xshop-primary); color: #fff; border-color: var(--xshop-primary); }
.xshop-btn--primary:hover { background: var(--xshop-primary-600); border-color: var(--xshop-primary-600); color: #fff; }
.xshop-btn--secondary { background: var(--xshop-card); color: var(--xshop-text); border-color: var(--xshop-border); }
.xshop-btn--secondary:hover { background: var(--xshop-surface); }
.xshop-btn--ghost { background: transparent; color: var(--xshop-text); border-color: transparent; }
.xshop-btn--ghost:hover { background: var(--xshop-surface); }
.xshop-btn:disabled { opacity: .55; cursor: not-allowed; }
/* Card — post-card, product placeholder, generic */
.xshop-card { background: var(--xshop-card); border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-md); overflow: hidden; display: flex; flex-direction: column; transition: box-shadow var(--xshop-transition); }
.xshop-card:hover { box-shadow: var(--xshop-shadow-md); }
.xshop-card__media { aspect-ratio: 16/10; background: var(--xshop-surface); overflow: hidden; }
.xshop-card__media img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.xshop-card__media--placeholder { display: flex; align-items: center; justify-content: center; color: var(--xshop-muted); font-size: var(--xshop-text-sm); }
.xshop-card__body { padding: var(--xshop-space-4); display: flex; flex-direction: column; gap: var(--xshop-space-2); flex: 1; }
.xshop-card__title { font-size: var(--xshop-text-base); margin: 0; line-height: var(--xshop-leading-tight); }
.xshop-card__title a { color: var(--xshop-text); }
.xshop-card__title a:hover { color: var(--xshop-link); }
.xshop-card__meta { font-size: var(--xshop-text-sm); color: var(--xshop-muted); display: flex; gap: var(--xshop-space-2); flex-wrap: wrap; }
.xshop-card__excerpt { font-size: var(--xshop-text-sm); color: var(--xshop-muted); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
/* Breadcrumbs */
.xshop-breadcrumbs { font-size: var(--xshop-text-sm); color: var(--xshop-muted); }
.xshop-breadcrumbs ol { display: flex; flex-wrap: wrap; gap: var(--xshop-space-2); list-style: none; margin: 0; padding: 0; align-items: center; }
.xshop-breadcrumbs li { display: inline-flex; align-items: center; gap: var(--xshop-space-2); }
.xshop-breadcrumbs li + li::before { content: "/"; opacity: .5; }
[dir="rtl"] .xshop-breadcrumbs li + li::before { content: "/"; }
.xshop-breadcrumbs a { color: var(--xshop-muted); }
.xshop-breadcrumbs a:hover { color: var(--xshop-text); }
.xshop-breadcrumbs [aria-current="page"] { color: var(--xshop-text); font-weight: var(--xshop-weight-medium); }
/* Pagination */
.xshop-pagination { display: flex; justify-content: center; margin-block-start: var(--xshop-space-8); }
.xshop-pagination .nav-links { display: flex; gap: var(--xshop-space-1); flex-wrap: wrap; align-items: center; }
.xshop-pagination .page-numbers { display: inline-flex; align-items: center; justify-content: center; min-inline-size: 40px; block-size: 40px; padding-inline: var(--xshop-space-2); border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-sm); background: var(--xshop-card); color: var(--xshop-text); font-size: var(--xshop-text-sm); text-decoration: none; }
.xshop-pagination .page-numbers:hover { background: var(--xshop-surface); }
.xshop-pagination .page-numbers.current { background: var(--xshop-primary); border-color: var(--xshop-primary); color: #fff; font-weight: var(--xshop-weight-bold); }
.xshop-pagination .page-numbers.dots { border-color: transparent; background: transparent; }
/* Search form */
.xshop-search-form { display: flex; gap: var(--xshop-space-2); max-inline-size: 520px; }
.xshop-search-form input[type="search"] { flex: 1; }
/* Empty state */
.xshop-empty { text-align: center; padding: var(--xshop-space-12) var(--xshop-space-6); background: var(--xshop-surface); border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-md); }
.xshop-empty__title { font-size: var(--xshop-text-xl); margin: 0 0 var(--xshop-space-2); }
.xshop-empty__text { color: var(--xshop-muted); margin: 0 0 var(--xshop-space-4); }
/* Loading */
.xshop-loading { display: inline-flex; align-items: center; gap: var(--xshop-space-2); color: var(--xshop-muted); font-size: var(--xshop-text-sm); }
.xshop-loading__spinner { inline-size: 16px; block-size: 16px; border: 2px solid var(--xshop-border); border-block-start-color: var(--xshop-primary); border-radius: 50%; animation: xshop-spin 600ms linear infinite; }
@keyframes xshop-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .xshop-loading__spinner { animation: none; } }
/* Notices */
.xshop-notice { padding: var(--xshop-space-3) var(--xshop-space-4); border-radius: var(--xshop-radius-sm); border: 1px solid var(--xshop-border); background: var(--xshop-surface); font-size: var(--xshop-text-sm); }
.xshop-notice--success { background: var(--xshop-success-bg); border-color: #86efac; color: #14532d; }
[data-theme="dark"] .xshop-notice--success { color: #bbf7d0; }
.xshop-notice--warning { background: var(--xshop-warning-bg); border-color: #fcd34d; }
.xshop-notice--error { background: var(--xshop-danger-bg); border-color: #fca5a5; }
/* Comments */
.xshop-comments { margin-block-start: var(--xshop-space-8); border-block-start: 1px solid var(--xshop-border); padding-block-start: var(--xshop-space-8); }
.xshop-comments__title { font-size: var(--xshop-text-xl); }
.xshop-comment-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--xshop-space-4); }
.xshop-comment { background: var(--xshop-card); border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-md); padding: var(--xshop-space-4); }
.xshop-comment__meta { font-size: var(--xshop-text-sm); color: var(--xshop-muted); margin-block-end: var(--xshop-space-2); }
.xshop-comment__body { font-size: var(--xshop-text-base); }
.xshop-comment .children { list-style: none; margin-block-start: var(--xshop-space-4); padding-inline-start: var(--xshop-space-6); display: flex; flex-direction: column; gap: var(--xshop-space-4); border-inline-start: 2px solid var(--xshop-border); }
@media (max-width: 640px) { .xshop-comment .children { padding-inline-start: var(--xshop-space-3); } }
/* Page header */
.xshop-page-header { margin-block-end: var(--xshop-space-6); }
.xshop-page-header__title { font-size: var(--xshop-text-3xl); margin: 0; }
.xshop-page-header__desc { color: var(--xshop-muted); margin-block-start: var(--xshop-space-2); }
/* 404 */
.xshop-404 { text-align: center; padding: var(--xshop-space-16) var(--xshop-space-4); }
.xshop-404__code { font-size: 5rem; font-weight: var(--xshop-weight-bold); line-height: 1; color: var(--xshop-border); margin: 0; }
.xshop-404__title { font-size: var(--xshop-text-3xl); margin: var(--xshop-space-4) 0 var(--xshop-space-2); }
/* Promo banner foundation */
.xshop-promo { background: var(--xshop-surface); border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-md); padding: var(--xshop-space-6); display: flex; align-items: center; justify-content: space-between; gap: var(--xshop-space-4); flex-wrap: wrap; }
/* Woo notice compat */
.woocommerce-message, .woocommerce-info, .woocommerce-error { border-radius: var(--xshop-radius-sm); }
+82
View File
@@ -0,0 +1,82 @@
/* XShop Layout — header, footer, grid, sections. Logical properties only. */
/* Header */
.xshop-header { position: sticky; inset-block-start: 0; z-index: var(--xshop-z-header); background: var(--xshop-card); border-block-end: 1px solid var(--xshop-border); }
.xshop-header__inner { display: flex; align-items: center; gap: var(--xshop-space-4); padding-block: var(--xshop-space-3); flex-wrap: wrap; }
.xshop-header__branding { display: flex; align-items: center; gap: var(--xshop-space-3); flex-shrink: 0; }
.xshop-header__logo img { max-block-size: 40px; inline-size: auto; }
.xshop-header__title { font-size: var(--xshop-text-xl); font-weight: var(--xshop-weight-bold); margin: 0; }
.xshop-header__title a { color: var(--xshop-text); }
.xshop-header__tagline { font-size: var(--xshop-text-sm); color: var(--xshop-muted); margin: 0; }
.xshop-header__nav { flex: 1 1 auto; display: flex; justify-content: center; }
.xshop-header__actions { display: flex; align-items: center; gap: var(--xshop-space-2); flex-shrink: 0; }
.xshop-header__action-btn { display: inline-flex; align-items: center; justify-content: center; min-inline-size: 40px; min-block-size: 40px; border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-sm); background: var(--xshop-card); color: var(--xshop-text); cursor: pointer; }
.xshop-header__action-btn:hover { background: var(--xshop-surface); }
.xshop-header__search { position: relative; }
.xshop-header__search-form { display: flex; gap: var(--xshop-space-2); }
/* Primary nav */
.xshop-nav { display: flex; }
.xshop-nav__list { display: flex; flex-wrap: wrap; gap: var(--xshop-space-1); list-style: none; margin: 0; padding: 0; align-items: center; }
.xshop-nav__list a { display: block; padding: var(--xshop-space-2) var(--xshop-space-3); color: var(--xshop-text); border-radius: var(--xshop-radius-sm); font-weight: var(--xshop-weight-medium); font-size: var(--xshop-text-sm); }
.xshop-nav__list a:hover, .xshop-nav__list a:focus-visible { background: var(--xshop-surface); color: var(--xshop-text); }
.xshop-nav__list .current-menu-item > a, .xshop-nav__list .current_page_item > a { background: var(--xshop-surface-2); }
/* Mobile nav */
.xshop-mobile-toggle { display: none; align-items: center; justify-content: center; inline-size: 40px; block-size: 40px; border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-sm); background: var(--xshop-card); cursor: pointer; color: var(--xshop-text); }
.xshop-mobile-nav { display: none; border-block-start: 1px solid var(--xshop-border); padding-block: var(--xshop-space-3); }
.xshop-mobile-nav.is-open { display: block; }
.xshop-mobile-nav__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.xshop-mobile-nav__list a { display: block; padding: var(--xshop-space-2) var(--xshop-space-3); color: var(--xshop-text); border-radius: var(--xshop-radius-sm); }
.xshop-mobile-nav__list a:hover { background: var(--xshop-surface); }
@media (max-width: 1024px) {
.xshop-nav--desktop { display: none; }
.xshop-mobile-toggle { display: inline-flex; }
.xshop-header__search--desktop { display: none; }
}
/* Footer */
.xshop-footer { background: var(--xshop-surface); border-block-start: 1px solid var(--xshop-border); margin-block-start: var(--xshop-space-12); padding-block: var(--xshop-space-8) var(--xshop-space-6); }
.xshop-footer__widgets { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--xshop-space-6); margin-block-end: var(--xshop-space-8); }
.xshop-footer__bottom { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--xshop-space-4); border-block-start: 1px solid var(--xshop-border); padding-block-start: var(--xshop-space-4); font-size: var(--xshop-text-sm); color: var(--xshop-muted); }
.xshop-footer__nav-list { display: flex; flex-wrap: wrap; gap: var(--xshop-space-3); list-style: none; margin: 0; padding: 0; }
.xshop-footer__nav-list a { color: var(--xshop-muted); }
.xshop-footer__nav-list a:hover { color: var(--xshop-text); }
@media (max-width: 768px) {
.xshop-footer__widgets { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 640px) {
.xshop-footer__widgets { grid-template-columns: 1fr; }
.xshop-footer__bottom { flex-direction: column; align-items: flex-start; }
}
/* Main layout */
.xshop-main { padding-block: var(--xshop-space-8); }
.xshop-layout { display: grid; grid-template-columns: 1fr 320px; gap: var(--xshop-space-8); align-items: start; }
.xshop-layout--no-sidebar { grid-template-columns: 1fr; }
.xshop-layout__content { min-inline-size: 0; }
.xshop-layout__sidebar { position: sticky; inset-block-start: calc(60px + var(--xshop-space-4)); }
@media (max-width: 1024px) {
.xshop-layout { grid-template-columns: 1fr; }
.xshop-layout__sidebar { position: static; }
}
/* Grids */
.xshop-grid { display: grid; gap: var(--xshop-space-4); }
.xshop-grid--posts { grid-template-columns: repeat(3, 1fr); }
.xshop-grid--products { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 1024px) { .xshop-grid--posts, .xshop-grid--products { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .xshop-grid--posts, .xshop-grid--products { grid-template-columns: 1fr; } }
/* Section */
.xshop-section { padding-block: var(--xshop-space-8); }
.xshop-section__header { display: flex; align-items: baseline; justify-content: space-between; gap: var(--xshop-space-4); margin-block-end: var(--xshop-space-6); flex-wrap: wrap; }
.xshop-section__title { font-size: var(--xshop-text-2xl); margin: 0; }
.xshop-section__link { font-size: var(--xshop-text-sm); font-weight: var(--xshop-weight-medium); }
/* Hero foundation */
.xshop-hero { background: var(--xshop-surface); border: 1px solid var(--xshop-border); border-radius: var(--xshop-radius-lg); padding: var(--xshop-space-8); display: grid; gap: var(--xshop-space-4); }
.xshop-hero__title { font-size: var(--xshop-text-4xl); margin: 0; }
.xshop-hero__text { color: var(--xshop-muted); max-inline-size: 60ch; }
.xshop-hero__actions { display: flex; gap: var(--xshop-space-3); flex-wrap: wrap; }
+91
View File
@@ -0,0 +1,91 @@
/* XShop Design Tokens — CSS custom properties. Logical, RTL-safe. */
:root {
/* Palette */
--xshop-primary: #0ea5e9;
--xshop-primary-600: #0284c7;
--xshop-primary-700: #0369a1;
--xshop-secondary: #111827;
--xshop-bg: #ffffff;
--xshop-surface: #f8fafc;
--xshop-surface-2: #f1f5f9;
--xshop-card: #ffffff;
--xshop-text: #0f172a;
--xshop-muted: #64748b;
--xshop-border: #e2e8f0;
--xshop-input-bg: #ffffff;
--xshop-input-border: #cbd5e1;
--xshop-input-text: #0f172a;
--xshop-focus: #0ea5e9;
--xshop-link: #0ea5e9;
--xshop-link-hover: #0284c7;
--xshop-success: #16a34a;
--xshop-success-bg: #f0fdf4;
--xshop-warning: #f59e0b;
--xshop-warning-bg: #fffbeb;
--xshop-danger: #dc2626;
--xshop-danger-bg: #fef2f2;
/* Typography */
--xshop-font-sans: system-ui, -apple-system, "Vazirmatn", "IRANSans", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--xshop-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
--xshop-text-xs: 0.75rem; /* 12px */
--xshop-text-sm: 0.875rem; /* 14px */
--xshop-text-base: 1rem; /* 16px */
--xshop-text-lg: 1.125rem; /* 18px */
--xshop-text-xl: 1.25rem; /* 20px */
--xshop-text-2xl: 1.5rem; /* 24px */
--xshop-text-3xl: 1.875rem;/* 30px */
--xshop-text-4xl: 2.25rem; /* 36px */
--xshop-weight-regular: 400;
--xshop-weight-medium: 500;
--xshop-weight-bold: 700;
--xshop-leading-tight: 1.25;
--xshop-leading: 1.6;
--xshop-leading-relaxed: 1.75;
/* Spacing / Layout */
--xshop-space-1: 4px; --xshop-space-2: 8px; --xshop-space-3: 12px; --xshop-space-4: 16px;
--xshop-space-5: 20px; --xshop-space-6: 24px; --xshop-space-8: 32px; --xshop-space-10: 40px;
--xshop-space-12: 48px; --xshop-space-16: 64px;
--xshop-container: 1280px;
--xshop-container-narrow: 720px;
--xshop-radius-sm: 8px; --xshop-radius-md: 14px; --xshop-radius-lg: 22px; --xshop-radius-full: 9999px;
--xshop-shadow-sm: 0 1px 2px rgba(0,0,0,.06);
--xshop-shadow-md: 0 8px 24px rgba(0,0,0,.08);
--xshop-shadow-lg: 0 16px 40px rgba(0,0,0,.12);
--xshop-z-header: 40; --xshop-z-overlay: 50; --xshop-z-modal: 60;
--xshop-transition-fast: 120ms ease;
--xshop-transition: 180ms ease;
--xshop-transition-slow: 280ms ease;
/* Breakpoints (for reference; media queries use literal values) */
--xshop-bp-sm: 640px;
--xshop-bp-md: 768px;
--xshop-bp-lg: 1024px;
--xshop-bp-xl: 1280px;
--xshop-bp-2xl: 1536px;
}
/* Dark mode — semantic swap, not inversion */
[data-theme="dark"] {
--xshop-bg: #0b1220;
--xshop-surface: #111a2e;
--xshop-surface-2: #1a2542;
--xshop-card: #151f38;
--xshop-text: #e2e8f0;
--xshop-muted: #94a3b8;
--xshop-border: #1e2d4a;
--xshop-input-bg: #0f1a33;
--xshop-input-border: #24365e;
--xshop-input-text: #e2e8f0;
--xshop-focus: #38bdf8;
--xshop-link: #38bdf8;
--xshop-link-hover: #7dd3fc;
--xshop-success-bg: #052e16;
--xshop-warning-bg: #451a03;
--xshop-danger-bg: #450a0a;
}
@media (prefers-reduced-motion: reduce) {
:root { --xshop-transition: 0ms; --xshop-transition-fast: 0ms; --xshop-transition-slow: 0ms; }
}
+10
View File
@@ -0,0 +1,10 @@
/* XShop Utilities — visually hidden, containers, prose. */
.xshop-sr-only { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.xshop-prose { max-inline-size: 68ch; }
.xshop-prose :where(p, ul, ol, blockquote) { margin-block-end: var(--xshop-space-4); }
.xshop-prose :where(blockquote) { border-inline-start: 3px solid var(--xshop-border); padding-inline-start: var(--xshop-space-4); color: var(--xshop-muted); }
.xshop-prose :where(a) { text-decoration: underline; text-underline-offset: 3px; }
.xshop-prose--rtl { direction: rtl; }
.xshop-text-muted { color: var(--xshop-muted); }
.xshop-text-center { text-align: center; }
.xshop-stack { display: flex; flex-direction: column; gap: var(--xshop-space-4); }
+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();
}
})();