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)} + + `; + 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 = ` +