diff --git a/css/enterprise-upgrades.css b/css/enterprise-upgrades.css new file mode 100644 index 0000000..bcde8dd --- /dev/null +++ b/css/enterprise-upgrades.css @@ -0,0 +1,546 @@ +/* Enterprise UX upgrade pack: premium interactions, accessibility, utility states */ + +.skip-link { + position: fixed; + left: 10px; + top: -50px; + z-index: 3200; + padding: 10px 14px; + border-radius: 10px; + border: 1px solid var(--line); + background: var(--panel2); + color: var(--text); + transition: top .2s ease; +} +.skip-link:focus { top: 10px; } + +.network-status { + position: fixed; + inset: auto 14px 14px 14px; + z-index: 2800; + display: none; + align-items: center; + justify-content: space-between; + gap: 10px; + border-radius: 16px; + border: 1px solid var(--line); + background: rgba(15, 23, 42, .86); + color: #fff; + padding: 10px 12px; + backdrop-filter: blur(10px); + box-shadow: 0 12px 40px rgba(0,0,0,.4); +} +.network-status.show { display: flex; } +.network-status[data-state="online"] { border-color: rgba(16,185,129,.45); } +.network-status[data-state="offline"] { border-color: rgba(245,158,11,.45); } +.network-status-text { font-size: 13px; line-height: 1.3; } +.network-status-close { + border: 1px solid rgba(255,255,255,.2); + background: rgba(255,255,255,.06); + color: inherit; + border-radius: 999px; + padding: 7px 10px; + min-height: 36px; + cursor: pointer; +} + +.scroll-progress { + position: fixed; + left: 0; + top: 0; + z-index: 2600; + width: var(--progress, 0%); + height: 3px; + background: linear-gradient(90deg, #2563eb, #06b6d4, #22c55e); + transition: width .08s linear; +} + +.cmd-palette { + position: fixed; + inset: 0; + z-index: 3000; + display: none; + align-items: flex-start; + justify-content: center; + padding: 9vh 14px 24px; + background: rgba(2,6,23,.48); + backdrop-filter: blur(6px); +} +.cmd-palette.show { display: flex; } +.cmd-shell { + width: min(760px, 100%); + border-radius: 28px; + border: 1px solid var(--line); + background: rgba(255,255,255,.9); + box-shadow: 0 40px 100px rgba(15,23,42,.28); + overflow: hidden; +} +:root[data-theme="dark"] .cmd-shell { + background: rgba(15,23,42,.92); + border-color: rgba(148,163,184,.24); +} +.cmd-head { + padding: 14px; + border-bottom: 1px solid var(--line); +} +.cmd-input { + width: 100%; + border: 1px solid var(--line); + background: rgba(255,255,255,.6); + color: var(--text); + border-radius: 14px; + padding: 12px 14px; + font-size: 15px; + outline: none; +} +:root[data-theme="dark"] .cmd-input { + background: rgba(30,41,59,.64); +} +.cmd-input:focus { + border-color: rgba(37,99,235,.6); + box-shadow: 0 0 0 3px rgba(37,99,235,.2); +} +.cmd-list { + max-height: min(64vh, 560px); + overflow: auto; + padding: 10px; + display: grid; + gap: 8px; +} +.cmd-item { + border: 1px solid transparent; + border-radius: 14px; + padding: 10px 12px; + display: grid; + gap: 3px; + text-decoration: none; +} +.cmd-item.active, +.cmd-item:hover { + border-color: var(--line); + background: var(--panel); +} +.cmd-item-main { + font-weight: 700; +} +.cmd-item-meta { + color: var(--muted); + font-size: 12px; +} +.cmd-empty { + color: var(--muted); + padding: 10px; +} +.cmd-foot { + border-top: 1px solid var(--line); + padding: 10px 14px; + color: var(--muted); + font-size: 12px; + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +.perf-panel { + position: fixed; + left: 12px; + bottom: 12px; + z-index: 2400; + border-radius: 14px; + border: 1px solid var(--line); + background: rgba(15,23,42,.86); + color: #fff; + padding: 10px; + min-width: 180px; +} +.perf-title { font-weight: 800; margin-bottom: 6px; } +.perf-line { font-size: 12px; opacity: .95; } + +/* high-fidelity cards */ +.card-elevated { + background: linear-gradient(180deg, rgba(255,255,255,.82), rgba(255,255,255,.62)); + border: 1px solid rgba(255,255,255,.58); + border-radius: 24px; + box-shadow: 0 20px 60px rgba(15,23,42,.12); +} +:root[data-theme="dark"] .card-elevated { + background: linear-gradient(180deg, rgba(30,41,59,.74), rgba(15,23,42,.65)); + border-color: rgba(148,163,184,.2); +} + +.badge-soft { + border-radius: 999px; + border: 1px solid var(--line); + background: rgba(255,255,255,.66); + color: var(--text); + padding: 6px 10px; + font-size: 11px; +} + +.stat-strip { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; +} +.stat-item { + border: 1px solid var(--line); + border-radius: 14px; + padding: 10px; + background: rgba(255,255,255,.6); +} +.stat-item b { font-size: 18px; display: block; } +.stat-item span { font-size: 12px; color: var(--muted); } + +/* forms */ +.form-grid-3 { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; +} +.form-grid-2 { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} +@media (max-width: 900px) { + .form-grid-3, + .form-grid-2, + .stat-strip { grid-template-columns: 1fr; } +} + +.input, +.select, +textarea, +button, +.btn, +.icon-btn { + transition: border-color .18s ease, box-shadow .18s ease, background .18s ease; +} + +.input:focus, +.select:focus, +textarea:focus, +button:focus-visible, +.btn:focus-visible, +.icon-btn:focus-visible { + outline: none; + border-color: rgba(37,99,235,.5); + box-shadow: 0 0 0 3px rgba(37,99,235,.2); +} + +/* reusable skeleton variants */ +.skeleton-line { + height: 12px; + border-radius: 10px; + background: linear-gradient(90deg, var(--panel), rgba(255,255,255,.22), var(--panel)); + background-size: 200% 100%; + animation: shimmer 1.2s linear infinite; +} +.skeleton-box { + border-radius: 16px; + border: 1px solid var(--line); + background: linear-gradient(90deg, var(--panel), rgba(255,255,255,.18), var(--panel)); + background-size: 200% 100%; + animation: shimmer 1.2s linear infinite; +} +.skeleton-grid { + display: grid; + gap: 10px; +} +.skeleton-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.skeleton-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + +/* advanced table */ +.table-advanced { + width: 100%; + border-collapse: collapse; + border-spacing: 0; +} +.table-advanced thead th { + position: sticky; + top: 0; + z-index: 1; + background: rgba(255,255,255,.82); + font-size: 12px; + color: var(--muted); + text-align: left; + padding: 10px; + border-bottom: 1px solid var(--line); +} +:root[data-theme="dark"] .table-advanced thead th { + background: rgba(30,41,59,.88); +} +.table-advanced tbody td { + padding: 10px; + border-bottom: 1px solid var(--line); +} +.table-advanced tbody tr:hover { + background: rgba(37,99,235,.07); +} + +/* chips */ +.chip-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.chip { + border: 1px solid var(--line); + border-radius: 999px; + padding: 6px 10px; + font-size: 12px; + background: rgba(255,255,255,.62); +} +.chip.active { + border-color: rgba(37,99,235,.44); + background: rgba(37,99,235,.16); +} + +/* mobile improvements */ +@media (max-width: 860px) { + .cmd-shell { + border-radius: 22px; + } + .cmd-list { + max-height: 58vh; + } + .network-status { + inset: auto 10px 10px 10px; + } + .table-advanced { + min-width: 720px; + } +} + +/* animation utility pack */ +.fade-in { + animation: fadeIn .25s ease both; +} +.slide-up { + animation: slideUp .28s ease both; +} +.pop { + animation: pop .2s ease both; +} +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +@keyframes slideUp { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes pop { + from { transform: scale(.98); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + +/* layout utility classes */ +.stack-xs > * + * { margin-top: 6px; } +.stack-sm > * + * { margin-top: 10px; } +.stack-md > * + * { margin-top: 14px; } +.stack-lg > * + * { margin-top: 18px; } + +.cluster { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; +} + +.cluster-between { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; + justify-content: space-between; +} + +.centered { + display: grid; + place-items: center; +} + +.glass { + background: rgba(255,255,255,.66); + border: 1px solid rgba(255,255,255,.5); + backdrop-filter: blur(14px); +} +:root[data-theme="dark"] .glass { + background: rgba(15,23,42,.66); + border-color: rgba(148,163,184,.2); +} + +/* safety: reduce motion */ +@media (prefers-reduced-motion: reduce) { + .skeleton, + .skeleton-line, + .skeleton-box, + .fade-in, + .slide-up, + .pop { + animation: none !important; + } + .scroll-progress { + transition: none; + } +} + +/* compact typography scale helpers */ +.text-xs { font-size: 11px; } +.text-sm { font-size: 13px; } +.text-md { font-size: 15px; } +.text-lg { font-size: 18px; } +.text-xl { font-size: 22px; } +.text-2xl { font-size: 28px; } + +.weight-500 { font-weight: 500; } +.weight-600 { font-weight: 600; } +.weight-700 { font-weight: 700; } +.weight-800 { font-weight: 800; } + +/* spacing helper classes */ +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: 4px; } +.mt-2 { margin-top: 8px; } +.mt-3 { margin-top: 12px; } +.mt-4 { margin-top: 16px; } +.mt-5 { margin-top: 20px; } + +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: 4px; } +.mb-2 { margin-bottom: 8px; } +.mb-3 { margin-bottom: 12px; } +.mb-4 { margin-bottom: 16px; } +.mb-5 { margin-bottom: 20px; } + +.p-1 { padding: 4px; } +.p-2 { padding: 8px; } +.p-3 { padding: 12px; } +.p-4 { padding: 16px; } +.p-5 { padding: 20px; } + +/* responsive columns */ +.cols-2, +.cols-3, +.cols-4 { + display: grid; + gap: 12px; +} +.cols-2 { grid-template-columns: repeat(2, minmax(0,1fr)); } +.cols-3 { grid-template-columns: repeat(3, minmax(0,1fr)); } +.cols-4 { grid-template-columns: repeat(4, minmax(0,1fr)); } + +@media (max-width: 1080px) { + .cols-4 { grid-template-columns: repeat(2, minmax(0,1fr)); } +} + +@media (max-width: 760px) { + .cols-2, + .cols-3, + .cols-4 { grid-template-columns: 1fr; } +} + +.shortcut-help { + position: fixed; + inset: 0; + z-index: 2950; + display: none; + align-items: center; + justify-content: center; + background: rgba(2,6,23,.45); + backdrop-filter: blur(6px); + padding: 14px; +} +.shortcut-help.show { display: flex; } +.shortcut-help-card { + width: min(540px, 100%); + border-radius: 22px; + border: 1px solid var(--line); + background: rgba(255,255,255,.92); + box-shadow: 0 24px 80px rgba(15,23,42,.25); +} +:root[data-theme="dark"] .shortcut-help-card { + background: rgba(15,23,42,.94); +} +.shortcut-head { + padding: 12px; + border-bottom: 1px solid var(--line); + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.shortcut-head h3 { margin: 0; font-size: 18px; } +.shortcut-body { + padding: 10px; + display: grid; + gap: 8px; +} +.shortcut-row { + border: 1px solid var(--line); + border-radius: 12px; + padding: 10px; + display: grid; + grid-template-columns: 160px 1fr; + gap: 10px; + align-items: center; +} +.shortcut-row kbd { + font-family: var(--mono); + font-size: 12px; + border-radius: 8px; + border: 1px solid var(--line); + background: var(--panel); + padding: 5px 8px; +} +.shortcut-row span { font-size: 13px; } + +.cart-peek { + position: fixed; + top: 78px; + right: 14px; + z-index: 2100; + width: min(280px, calc(100% - 28px)); + border: 1px solid var(--line); + border-radius: 16px; + background: rgba(255,255,255,.92); + box-shadow: 0 20px 60px rgba(15,23,42,.2); + padding: 12px; + display: none; +} +:root[data-theme="dark"] .cart-peek { + background: rgba(15,23,42,.92); +} +.cart-peek.show { display: grid; gap: 8px; } +.cart-peek-head { font-weight: 800; } +.cart-peek-body { font-size: 13px; color: var(--muted); } + +.page-hints { + border: 1px dashed rgba(37,99,235,.35); + background: rgba(37,99,235,.08); + border-radius: 16px; + padding: 12px; + margin-bottom: 10px; +} +.page-hints-title { + font-weight: 800; + margin-bottom: 6px; +} +.page-hints ul { + margin: 0; + padding-left: 18px; + color: var(--muted); + display: grid; + gap: 4px; +} + +@media (max-width: 720px) { + .shortcut-row { + grid-template-columns: 1fr; + } + .cart-peek { + top: 112px; + right: 10px; + } +} diff --git a/js/lib/ux-enhancements.js b/js/lib/ux-enhancements.js new file mode 100644 index 0000000..9a7c868 --- /dev/null +++ b/js/lib/ux-enhancements.js @@ -0,0 +1,535 @@ +import { escapeHtml } from '../../script.js'; + +const LS_KEYS = { + paletteRecent: 'nexus_palette_recent_v1', + formDraftPrefix: 'nexus_form_draft_', + perf: 'nexus_perf_snapshots_v1', +}; + +function qs(sel, root = document) { + return root.querySelector(sel); +} + +function qsa(sel, root = document) { + return [...root.querySelectorAll(sel)]; +} + +function safeParse(json, fallback) { + try { + return JSON.parse(json); + } catch { + return fallback; + } +} + +function saveJSON(key, value) { + try { + localStorage.setItem(key, JSON.stringify(value)); + } catch { + // ignore storage quota + } +} + +function readJSON(key, fallback) { + try { + return safeParse(localStorage.getItem(key), fallback); + } catch { + return fallback; + } +} + +function routeBase() { + return location.pathname.startsWith('/ar/') ? '..' : '.'; +} + +function routeCatalog() { + const b = routeBase(); + return [ + { label: 'Home', href: `${b}/index.html`, tags: ['landing', 'main', 'featured'] }, + { label: 'Products', href: `${b}/products.html`, tags: ['catalog', 'items', 'hardware'] }, + { label: 'Services & Fixing', href: `${b}/services.html`, tags: ['repair', 'troubleshoot', 'support'] }, + { label: 'Blog', href: `${b}/blog.html`, tags: ['guides', 'articles'] }, + { label: 'Build PC', href: `${b}/build.html`, tags: ['configurator', 'builder', 'compatibility'] }, + { label: 'Warranty', href: `${b}/warranty.html`, tags: ['coverage', 'extended', 'terms'] }, + { label: 'Contact', href: `${b}/contact.html`, tags: ['support', 'whatsapp', 'help'] }, + { label: 'Checkout', href: `${b}/checkout.html`, tags: ['cart', 'payment', 'invoice'] }, + { label: 'Admin', href: `${b}/admin.html`, tags: ['dashboard', 'orders', 'kpis'] }, + { label: 'Health', href: `${b}/health.html`, tags: ['status', 'diagnostics', 'functions'] }, + { label: 'Changelog', href: `${b}/changelog.html`, tags: ['updates', 'releases'] }, + ]; +} + +export function initNetworkStatusBanner() { + if (qs('#networkStatusBanner')) return; + + const banner = document.createElement('div'); + banner.id = 'networkStatusBanner'; + banner.className = 'network-status'; + banner.setAttribute('role', 'status'); + banner.setAttribute('aria-live', 'polite'); + + const text = document.createElement('div'); + text.className = 'network-status-text'; + + const closeBtn = document.createElement('button'); + closeBtn.className = 'network-status-close'; + closeBtn.type = 'button'; + closeBtn.setAttribute('aria-label', 'Dismiss network banner'); + closeBtn.textContent = 'Dismiss'; + + banner.append(text, closeBtn); + document.body.appendChild(banner); + + const sync = () => { + const online = navigator.onLine; + if (online) { + text.textContent = 'Back online. Sync and checkout functions are available.'; + banner.dataset.state = 'online'; + banner.classList.add('show'); + setTimeout(() => banner.classList.remove('show'), 2400); + } else { + text.textContent = 'You are offline. Cart remains local and sync resumes when online.'; + banner.dataset.state = 'offline'; + banner.classList.add('show'); + } + }; + + window.addEventListener('online', sync); + window.addEventListener('offline', sync); + closeBtn.addEventListener('click', () => banner.classList.remove('show')); +} + +function scoreMatch(item, query) { + if (!query) return 1; + const q = query.toLowerCase().trim(); + let score = 0; + if (item.label.toLowerCase().startsWith(q)) score += 5; + if (item.label.toLowerCase().includes(q)) score += 3; + for (const t of item.tags || []) { + if (t.startsWith(q)) score += 2; + if (t.includes(q)) score += 1; + } + return score; +} + +function mergeRecent(items) { + const recent = readJSON(LS_KEYS.paletteRecent, []); + if (!Array.isArray(recent) || !recent.length) return items; + const rank = new Map(recent.map((href, idx) => [href, idx])); + return [...items].sort((a, b) => { + const ra = rank.has(a.href) ? rank.get(a.href) : 9999; + const rb = rank.has(b.href) ? rank.get(b.href) : 9999; + if (ra !== rb) return ra - rb; + return a.label.localeCompare(b.label); + }); +} + +function rememberVisit(href) { + const recent = readJSON(LS_KEYS.paletteRecent, []); + const next = [href, ...recent.filter((x) => x !== href)].slice(0, 8); + saveJSON(LS_KEYS.paletteRecent, next); +} + +function createPaletteResult(item, index) { + const row = document.createElement('a'); + row.className = 'cmd-item'; + row.href = item.href; + row.dataset.idx = String(index); + row.innerHTML = ` + ${escapeHtml(item.label)} + ${escapeHtml((item.tags || []).slice(0, 3).join(' • '))} + `; + row.addEventListener('click', () => rememberVisit(item.href)); + return row; +} + +export function initCommandPaletteV2() { + if (qs('#cmdPaletteV2')) return; + + const wrap = document.createElement('div'); + wrap.id = 'cmdPaletteV2'; + wrap.className = 'cmd-palette'; + wrap.innerHTML = ` + + `; + document.body.appendChild(wrap); + + const input = qs('#cmdInputV2'); + const list = qs('#cmdListV2'); + let items = mergeRecent(routeCatalog()); + let focusIndex = 0; + + const render = () => { + const q = input.value; + const out = items + .map((item) => ({ item, score: scoreMatch(item, q) })) + .filter((x) => x.score > 0) + .sort((a, b) => b.score - a.score) + .slice(0, 12) + .map((x) => x.item); + + list.innerHTML = ''; + if (!out.length) { + list.innerHTML = '
No results. Try “checkout”, “warranty”, or “build”.
'; + return; + } + + out.forEach((item, idx) => { + const row = createPaletteResult(item, idx); + if (idx === focusIndex) row.classList.add('active'); + list.appendChild(row); + }); + }; + + const open = () => { + wrap.classList.add('show'); + items = mergeRecent(routeCatalog()); + focusIndex = 0; + render(); + setTimeout(() => input.focus(), 0); + }; + + const close = () => { + wrap.classList.remove('show'); + input.value = ''; + focusIndex = 0; + }; + + const move = (dir) => { + const rows = qsa('.cmd-item', list); + if (!rows.length) return; + focusIndex = (focusIndex + dir + rows.length) % rows.length; + rows.forEach((x) => x.classList.remove('active')); + rows[focusIndex].classList.add('active'); + rows[focusIndex].scrollIntoView({ block: 'nearest' }); + }; + + input.addEventListener('input', () => { + focusIndex = 0; + render(); + }); + + input.addEventListener('keydown', (e) => { + if (e.key === 'ArrowDown') { + e.preventDefault(); + move(1); + } + if (e.key === 'ArrowUp') { + e.preventDefault(); + move(-1); + } + if (e.key === 'Enter') { + const rows = qsa('.cmd-item', list); + if (rows[focusIndex]) rows[focusIndex].click(); + } + }); + + wrap.addEventListener('click', (e) => { + if (e.target === wrap) close(); + }); + + document.addEventListener('keydown', (e) => { + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { + e.preventDefault(); + if (wrap.classList.contains('show')) close(); + else open(); + } + if (e.key === 'Escape') close(); + }); +} + +export function initScrollProgress() { + if (qs('#scrollProgress')) return; + const bar = document.createElement('div'); + bar.id = 'scrollProgress'; + bar.className = 'scroll-progress'; + document.body.appendChild(bar); + + const update = () => { + const h = document.documentElement; + const max = h.scrollHeight - h.clientHeight; + const pct = max > 0 ? (h.scrollTop / max) * 100 : 0; + bar.style.setProperty('--progress', `${pct.toFixed(2)}%`); + }; + + window.addEventListener('scroll', update, { passive: true }); + window.addEventListener('resize', update); + update(); +} + +export function initFormDrafts() { + const forms = qsa('form'); + forms.forEach((form, idx) => { + const id = form.id || `form_${idx}`; + const key = `${LS_KEYS.formDraftPrefix}${id}`; + + const restore = readJSON(key, null); + if (restore && typeof restore === 'object') { + qsa('input,textarea,select', form).forEach((el) => { + if (!el.name) return; + if (el.type === 'password') return; + if (restore[el.name] == null) return; + if (el.type === 'checkbox') el.checked = !!restore[el.name]; + else el.value = String(restore[el.name]); + }); + } + + const persist = () => { + const payload = {}; + qsa('input,textarea,select', form).forEach((el) => { + if (!el.name) return; + if (el.type === 'password') return; + payload[el.name] = el.type === 'checkbox' ? !!el.checked : el.value; + }); + saveJSON(key, payload); + }; + + form.addEventListener('input', persist); + form.addEventListener('change', persist); + form.addEventListener('submit', () => { + try { + localStorage.removeItem(key); + } catch { + // ignore + } + }); + }); +} + +function collectVital() { + const nav = performance.getEntriesByType('navigation')[0]; + if (!nav) return null; + + const snapshot = { + path: location.pathname, + ts: new Date().toISOString(), + dcl: Math.round(nav.domContentLoadedEventEnd), + load: Math.round(nav.loadEventEnd), + ttfb: Math.round(nav.responseStart), + transferKb: Math.round((nav.transferSize || 0) / 1024), + }; + return snapshot; +} + +export function initPerfSnapshots() { + if (!('performance' in window)) return; + const snapshot = collectVital(); + if (!snapshot) return; + + const prev = readJSON(LS_KEYS.perf, []); + const next = [snapshot, ...prev].slice(0, 20); + saveJSON(LS_KEYS.perf, next); + + if (new URLSearchParams(location.search).get('debug') === '1') { + const panel = document.createElement('div'); + panel.className = 'perf-panel'; + panel.innerHTML = ` +
Performance snapshot
+
TTFB: ${snapshot.ttfb}ms
+
DCL: ${snapshot.dcl}ms
+
Load: ${snapshot.load}ms
+
Transfer: ${snapshot.transferKb}kb
+ `; + document.body.appendChild(panel); + } +} + +export function initAccessibilityHelpers() { + if (qs('#skipToContent')) return; + + const main = qs('main'); + if (main && !main.id) main.id = 'main-content'; + + const skip = document.createElement('a'); + skip.id = 'skipToContent'; + skip.className = 'skip-link'; + skip.href = '#main-content'; + skip.textContent = 'Skip to main content'; + document.body.prepend(skip); + + qsa('button, a, input, select, textarea').forEach((el) => { + if (!el.getAttribute('aria-label') && !el.textContent?.trim() && !el.getAttribute('title')) { + el.setAttribute('aria-label', 'Interactive control'); + } + }); +} + +export function initAnnouncementRotator() { + const strip = qs('.top-strip-inner'); + if (!strip) return; + const textNode = qsa('span', strip)[1]; + if (!textNode) return; + + const messages = [ + 'Free UAE shipping over AED 499 • Express support 10am–10pm', + 'Extended warranty options available during checkout', + 'Build PC wizard now includes compatibility checks', + 'Need help fast? Use WhatsApp from header or quick menu', + ]; + + let idx = 0; + setInterval(() => { + idx = (idx + 1) % messages.length; + textNode.textContent = messages[idx]; + }, 5600); +} + +export function initGlobalUXEnhancements() { + initNetworkStatusBanner(); + initCommandPaletteV2(); + initScrollProgress(); + initFormDrafts(); + initPerfSnapshots(); + initAccessibilityHelpers(); + initAnnouncementRotator(); +} + +function shortcutRows(){ + return [ + ['Ctrl/Cmd + K', 'Open quick command palette'], + ['Esc', 'Close open overlay (palette/chat/modals)'], + ['Enter', 'Submit command/search in focused controls'], + ['Tab', 'Navigate interactive controls accessibly'], + ]; +} + +export function initShortcutHelpModal(){ + if (qs('#shortcutHelpModal')) return; + + const shell = document.createElement('div'); + shell.id = 'shortcutHelpModal'; + shell.className = 'shortcut-help'; + shell.setAttribute('aria-hidden', 'true'); + shell.innerHTML = ` + + `; + document.body.appendChild(shell); + + const body = qs('#shortcutBody'); + const rows = shortcutRows() + .map(([keys, desc]) => `
${escapeHtml(keys)}${escapeHtml(desc)}
`) + .join(''); + body.innerHTML = rows; + + const open = () => { + shell.classList.add('show'); + shell.setAttribute('aria-hidden', 'false'); + }; + const close = () => { + shell.classList.remove('show'); + shell.setAttribute('aria-hidden', 'true'); + }; + + document.getElementById('shortcutClose')?.addEventListener('click', close); + shell.addEventListener('click', (e) => { + if (e.target === shell) close(); + }); + + document.addEventListener('keydown', (e) => { + if (e.key === '?') { + const el = document.activeElement; + const typing = el && ['INPUT', 'TEXTAREA'].includes(el.tagName); + if (!typing) { + e.preventDefault(); + open(); + } + } + }); +} + +export function initSmartSectionObserver(){ + const targets = qsa('main section, main .card, main article'); + if (!targets.length || !('IntersectionObserver' in window)) return; + + const obs = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + entry.target.classList.add('fade-in'); + } + } + }, { threshold: .12 }); + + targets.forEach((el) => obs.observe(el)); +} + +export function initCartQuickPeek(){ + const navCart = qs('[data-testid="nav-cart"]'); + if (!navCart || qs('#cartPeek')) return; + + const peek = document.createElement('div'); + peek.id = 'cartPeek'; + peek.className = 'cart-peek'; + peek.setAttribute('aria-hidden', 'true'); + peek.innerHTML = ` +
Cart shortcut
+
Go to checkout to review totals, VAT, and payment options.
+ Open checkout + `; + document.body.appendChild(peek); + + let timer; + const show = () => { + clearTimeout(timer); + peek.classList.add('show'); + peek.setAttribute('aria-hidden', 'false'); + }; + const hide = () => { + timer = setTimeout(() => { + peek.classList.remove('show'); + peek.setAttribute('aria-hidden', 'true'); + }, 120); + }; + + navCart.addEventListener('mouseenter', show); + navCart.addEventListener('mouseleave', hide); + peek.addEventListener('mouseenter', show); + peek.addEventListener('mouseleave', hide); +} + +const pageHints = { + '/products.html': ['Try filter: stock=out to capture notify requests', 'Tip: click product card body to quick-open details modal.'], + '/warranty.html': ['Choose a duration +24 or +36 months to compare pricing.'], + '/build.html': ['Start with a CPU then match motherboard socket for compatibility.'], + '/checkout.html': ['Card/Tabby can use demo payment link if live keys are unavailable.'], +}; + +export function initContextHints(){ + if (qs('#pageHints') || !pageHints[location.pathname]) return; + + const hints = pageHints[location.pathname]; + const box = document.createElement('aside'); + box.id = 'pageHints'; + box.className = 'page-hints'; + box.innerHTML = ` +
Helpful hints
+ + `; + + const main = qs('main .container') || qs('main'); + if (main) main.prepend(box); +} + +const originalInitGlobalUXEnhancements = initGlobalUXEnhancements; +export function initGlobalUXEnhancementsExtended(){ + originalInitGlobalUXEnhancements(); + initShortcutHelpModal(); + initSmartSectionObserver(); + initCartQuickPeek(); + initContextHints(); +} diff --git a/partials/footer.html b/partials/footer.html index 47a22ce..536c951 100644 --- a/partials/footer.html +++ b/partials/footer.html @@ -3,6 +3,7 @@
Nexus Gaming UAE
Online store • WhatsApp: +971 50 505 8609
+
Designed with premium glass UI, mobile-first checkout, and resilient demo-mode backend flows.
About diff --git a/partials/header.html b/partials/header.html index c4ea0c0..48662a2 100644 --- a/partials/header.html +++ b/partials/header.html @@ -1,4 +1,11 @@