diff --git a/public/ux-foundation.js b/public/ux-foundation.js new file mode 100644 index 0000000..35c9072 --- /dev/null +++ b/public/ux-foundation.js @@ -0,0 +1,246 @@ +(() => { + 'use strict'; + + const NAV_SECTIONS = [ + { + id: 'start', + label: 'Start', + tabs: ['dashboard', 'today', 'generator'], + }, + { + id: 'commerce', + label: 'Verkaufen', + tabs: ['history', 'quality-center', 'publish', 'inventory'], + }, + { + id: 'content', + label: 'Inhalte & AI', + tabs: ['media-library', 'image-factory', 'flux-studio', 'templates'], + }, + { + id: 'system', + label: 'Werkzeuge & System', + collapsible: true, + tabs: ['extensions', 'settings', 'admin', 'fees', 'trash'], + }, + ]; + + const VIEW_META = { + dashboard: { title: 'Übersicht', kicker: 'Start' }, + today: { title: 'Aufgaben', kicker: 'Arbeitsbereich' }, + generator: { title: 'Neuer Artikel', kicker: 'Erstellen' }, + history: { title: 'Artikel', kicker: 'Verkaufen' }, + 'quality-center': { title: 'Qualität', kicker: 'Optimieren' }, + publish: { title: 'Veröffentlichen', kicker: 'Marktplätze' }, + inventory: { title: 'Lager', kicker: 'Bestand' }, + templates: { title: 'Vorlagen', kicker: 'Inhalte' }, + 'flux-studio': { title: 'FLUX Studio', kicker: 'AI-Bilder' }, + 'media-library': { title: 'Medien', kicker: 'Bilder' }, + 'image-factory': { title: 'Bildwerkstatt', kicker: 'Produktion' }, + extensions: { title: 'Erweiterungen', kicker: 'Browser' }, + settings: { title: 'Einstellungen', kicker: 'System' }, + admin: { title: 'Verwaltung', kicker: 'Benutzer & Betrieb' }, + fees: { title: 'Gebühren', kicker: 'Kalkulation' }, + trash: { title: 'Papierkorb', kicker: 'Wiederherstellen' }, + }; + + const NAV_LABELS = { + dashboard: 'Übersicht', + today: 'Aufgaben', + generator: 'Neuer Artikel', + history: 'Artikel', + 'quality-center': 'Qualität', + publish: 'Veröffentlichen', + inventory: 'Lager', + templates: 'Vorlagen', + 'flux-studio': 'FLUX Studio', + 'media-library': 'Medien', + 'image-factory': 'Bildwerkstatt', + extensions: 'Erweiterungen', + settings: 'Einstellungen', + admin: 'Verwaltung', + fees: 'Gebühren', + trash: 'Papierkorb', + }; + + function getNavItem(tab) { + return document.querySelector(`.sidebar-nav .nav-item[data-tab="${tab}"]`); + } + + function createSection(section) { + const element = document.createElement(section.collapsible ? 'details' : 'section'); + element.className = `ux-nav-section ux-nav-section-${section.id}`; + element.dataset.uxNavSection = section.id; + + if (section.collapsible) { + const stored = localStorage.getItem('vendoo_ux_system_nav_open'); + element.open = stored === 'true' || section.tabs.some(tab => getNavItem(tab)?.classList.contains('active')); + const summary = document.createElement('summary'); + summary.textContent = section.label; + element.append(summary); + element.addEventListener('toggle', () => { + localStorage.setItem('vendoo_ux_system_nav_open', String(element.open)); + }); + } else { + const label = document.createElement('div'); + label.className = 'ux-nav-section-label'; + label.textContent = section.label; + element.append(label); + } + + const items = document.createElement('div'); + items.className = 'ux-nav-section-items'; + items.dataset.uxNavItems = section.id; + element.append(items); + return element; + } + + function reorganizeNavigation() { + const nav = document.querySelector('.sidebar-nav'); + if (!nav || nav.querySelector('.ux-nav-sections')) return; + + const itemsByTab = new Map(); + nav.querySelectorAll('.nav-item[data-tab]').forEach(item => { + itemsByTab.set(item.dataset.tab, item); + const label = item.querySelector('span'); + if (label && NAV_LABELS[item.dataset.tab]) label.textContent = NAV_LABELS[item.dataset.tab]; + item.setAttribute('aria-current', item.classList.contains('active') ? 'page' : 'false'); + }); + + const shell = document.createElement('div'); + shell.className = 'ux-nav-sections'; + + for (const section of NAV_SECTIONS) { + const sectionElement = createSection(section); + const container = sectionElement.querySelector('.ux-nav-section-items'); + for (const tab of section.tabs) { + const item = itemsByTab.get(tab); + if (item) container.append(item); + } + shell.append(sectionElement); + } + + const assigned = new Set(NAV_SECTIONS.flatMap(section => section.tabs)); + const remaining = [...itemsByTab.entries()].filter(([tab]) => !assigned.has(tab)); + if (remaining.length) { + const other = createSection({ id: 'other', label: 'Weitere', collapsible: true, tabs: [] }); + const container = other.querySelector('.ux-nav-section-items'); + remaining.forEach(([, item]) => container.append(item)); + shell.append(other); + } + + nav.querySelectorAll(':scope > .nav-group').forEach(group => group.remove()); + nav.append(shell); + } + + function activateView(tab) { + const item = getNavItem(tab); + if (!item || item.classList.contains('hidden')) return; + const systemSection = item.closest('details.ux-nav-section'); + if (systemSection) systemSection.open = true; + item.click(); + } + + function createDashboardCommandCenter() { + const dashboard = document.getElementById('dashboard'); + const layout = dashboard?.querySelector('.dashboard-layout'); + if (!dashboard || !layout || dashboard.querySelector('.ux-command-center')) return; + + const panel = document.createElement('section'); + panel.className = 'ux-command-center'; + panel.setAttribute('aria-label', 'Schnellstart'); + panel.innerHTML = ` +
+ Schnellstart +

Was möchtest du als Nächstes erledigen?

+

Die wichtigsten Wege sind direkt erreichbar. Detailauswertungen bleiben darunter verfügbar, ohne den Einstieg zu überladen.

+
+
+ + + + +
`; + layout.before(panel); + } + + function simplifyDashboardRail() { + const rail = document.querySelector('#dashboard .dashboard-rail'); + if (!rail || rail.querySelector('.ux-dashboard-more')) return; + const cards = [...rail.children]; + const secondaryCards = cards.filter((_, index) => index > 0); + if (!secondaryCards.length) return; + + const details = document.createElement('details'); + details.className = 'ux-dashboard-more'; + const summary = document.createElement('summary'); + summary.textContent = 'Aktivität, Hinweise und Systemstatus'; + const content = document.createElement('div'); + content.className = 'ux-dashboard-more-content'; + secondaryCards.forEach(card => content.append(card)); + details.append(summary, content); + rail.append(details); + } + + function updateContext() { + const active = document.querySelector('.sidebar-nav .nav-item.active[data-tab]'); + const tab = active?.dataset.tab || document.querySelector('.tab-content.active')?.id || 'dashboard'; + const meta = VIEW_META[tab] || { title: active?.textContent?.trim() || 'Vendoo', kicker: 'Arbeitsbereich' }; + const title = document.getElementById('topbar-title'); + const kicker = document.getElementById('topbar-kicker'); + if (title) title.textContent = meta.title; + if (kicker) kicker.textContent = meta.kicker; + + document.querySelectorAll('.sidebar-nav .nav-item[data-tab]').forEach(item => { + item.setAttribute('aria-current', item === active ? 'page' : 'false'); + }); + + const system = active?.closest('details.ux-nav-section'); + if (system) system.open = true; + } + + function improveSearchPresentation() { + const search = document.getElementById('topbar-search-btn'); + const label = search?.querySelector('span'); + const shortcut = search?.querySelector('kbd'); + if (label) label.textContent = 'Suchen, Seite öffnen oder Aktion starten …'; + if (shortcut) shortcut.textContent = /Mac|iPhone|iPad/.test(navigator.platform) ? '⌘ K' : 'Strg K'; + } + + function bindUxActions() { + document.addEventListener('click', event => { + const target = event.target.closest?.('[data-ux-target]'); + if (!target) return; + activateView(target.dataset.uxTarget); + }); + } + + function observeNavigation() { + const nav = document.querySelector('.sidebar-nav'); + if (!nav) return; + const observer = new MutationObserver(mutations => { + if (mutations.some(mutation => mutation.type === 'attributes' && mutation.attributeName === 'class')) { + updateContext(); + } + }); + nav.querySelectorAll('.nav-item[data-tab]').forEach(item => observer.observe(item, { attributes: true, attributeFilter: ['class'] })); + window.addEventListener('beforeunload', () => observer.disconnect(), { once: true }); + } + + function initializeUxFoundation() { + reorganizeNavigation(); + createDashboardCommandCenter(); + simplifyDashboardRail(); + improveSearchPresentation(); + bindUxActions(); + observeNavigation(); + updateContext(); + document.body.classList.add('ux-shell-ready'); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeUxFoundation, { once: true }); + } else { + initializeUxFoundation(); + } +})();