diff --git a/public/locales/ar.json b/public/locales/ar.json index b178083..2ddfa8c 100644 --- a/public/locales/ar.json +++ b/public/locales/ar.json @@ -780,5 +780,16 @@ "notificationEnabled": "الإشعارات نشطة", "notificationDenied": "الإشعارات محظورة", "notificationHint": "احصل على إشعارات حتى عندما يكون التطبيق مفتوحًا." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/de.json b/public/locales/de.json index 72888a0..aba3243 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -780,5 +780,16 @@ "duplicate": "Duplizieren", "duplicated": "Rezept dupliziert.", "copySuffix": "Kopie" + }, + "onboarding": { + "step1Title": "Willkommen bei Oikos", + "step1Body": "Dein persönlicher Familienplaner. Aufgaben, Kalender, Einkauf und mehr – alles an einem Ort.", + "step2Title": "Alles im Blick", + "step2Body": "Über die Navigation unten erreichst du alle Module. Mit dem +-Button erstellst du schnell neue Einträge.", + "step3Title": "Bereit loszulegen", + "step3Body": "Das Dashboard zeigt dir die wichtigsten Infos auf einen Blick. Du kannst es unter \"Anpassen\" nach deinen Wünschen einrichten.", + "next": "Weiter", + "done": "Loslegen", + "skip": "Überspringen" } } diff --git a/public/locales/el.json b/public/locales/el.json index 8c5924c..f9535b0 100644 --- a/public/locales/el.json +++ b/public/locales/el.json @@ -780,5 +780,16 @@ "notificationEnabled": "Ειδοποιήσεις ενεργές", "notificationDenied": "Ειδοποιήσεις αποκλεισμένες", "notificationHint": "Λάβετε ειδοποιήσεις ακόμα και όταν η εφαρμογή είναι ανοιχτή." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/en.json b/public/locales/en.json index 569a1ad..cbed02d 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -780,5 +780,16 @@ "open": "Open search", "placeholder": "Search…", "noResults": "No results found." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/es.json b/public/locales/es.json index 6db084f..4fdd6da 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -780,5 +780,16 @@ "notificationEnabled": "Notificaciones activas", "notificationDenied": "Notificaciones bloqueadas", "notificationHint": "Recibe notificaciones incluso cuando la aplicación está abierta." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/fr.json b/public/locales/fr.json index 0fc627e..232f203 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -780,5 +780,16 @@ "notificationEnabled": "Notifications actives", "notificationDenied": "Notifications bloquées", "notificationHint": "Recevez des notifications même lorsque l'application est ouverte." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/hi.json b/public/locales/hi.json index 2ab1479..1d7e695 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -780,5 +780,16 @@ "notificationEnabled": "सूचनाएं सक्रिय", "notificationDenied": "सूचनाएं अवरुद्ध", "notificationHint": "ऐप खुली होने पर भी सूचनाएं प्राप्त करें।" + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/it.json b/public/locales/it.json index fe7fa3a..802f018 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -780,5 +780,16 @@ "notificationEnabled": "Notifiche attive", "notificationDenied": "Notifiche bloccate", "notificationHint": "Ricevi notifiche anche quando l'app è aperta." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/ja.json b/public/locales/ja.json index 31a91b1..29d0cbe 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -780,5 +780,16 @@ "notificationEnabled": "通知が有効", "notificationDenied": "通知がブロックされています", "notificationHint": "アプリが開いているときでも通知を受け取ります。" + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/pt.json b/public/locales/pt.json index d2e766e..d8be85c 100644 --- a/public/locales/pt.json +++ b/public/locales/pt.json @@ -780,5 +780,16 @@ "notificationEnabled": "Notificações ativas", "notificationDenied": "Notificações bloqueadas", "notificationHint": "Receba notificações mesmo quando a aplicação está aberta." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/ru.json b/public/locales/ru.json index 6aabd07..44720cd 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -780,5 +780,16 @@ "notificationEnabled": "Уведомления активны", "notificationDenied": "Уведомления заблокированы", "notificationHint": "Получайте уведомления, даже когда приложение открыто." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/sv.json b/public/locales/sv.json index 0ced812..ff89400 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -780,5 +780,16 @@ "notificationEnabled": "Notiser aktiva", "notificationDenied": "Notiser blockerade", "notificationHint": "Få notiser även när appen är öppen." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/tr.json b/public/locales/tr.json index 8aecf1e..dfdebe8 100644 --- a/public/locales/tr.json +++ b/public/locales/tr.json @@ -780,5 +780,16 @@ "notificationEnabled": "Bildirimler etkin", "notificationDenied": "Bildirimler engellendi", "notificationHint": "Uygulama açıkken bile bildirim alın." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/uk.json b/public/locales/uk.json index 3e2efb8..1b20ef2 100644 --- a/public/locales/uk.json +++ b/public/locales/uk.json @@ -780,5 +780,16 @@ "open": "Відкрити пошук", "placeholder": "Пошук…", "noResults": "Результатів не знайдено." + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/locales/zh.json b/public/locales/zh.json index 87e8039..22c3361 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -780,5 +780,16 @@ "notificationEnabled": "通知已启用", "notificationDenied": "通知已被阻止", "notificationHint": "即使应用程序打开时也能收到通知。" + }, + "onboarding": { + "step1Title": "Welcome to Oikos", + "step1Body": "Your personal family planner. Tasks, calendar, shopping and more – all in one place.", + "step2Title": "Everything at a glance", + "step2Body": "Use the navigation below to reach all modules. The + button creates new entries quickly.", + "step3Title": "Ready to go", + "step3Body": "The dashboard shows you the most important information at a glance. Customize it under \"Customize\".", + "next": "Next", + "done": "Get started", + "skip": "Skip" } } diff --git a/public/pages/dashboard.js b/public/pages/dashboard.js index 68c42ff..ebaa8dd 100644 --- a/public/pages/dashboard.js +++ b/public/pages/dashboard.js @@ -12,6 +12,100 @@ import { openModal, closeModal } from '/components/modal.js'; // Hält den AbortController des aktuellen FAB-Listeners - wird bei jedem render() erneuert. let _fabController = null; +// ── Onboarding ────────────────────────────────────────────────────────────── + +const ONBOARDING_KEY = 'oikos-onboarded'; + +function getOnboardingSteps() { + return [ + { icon: 'home', title: t('onboarding.step1Title'), body: t('onboarding.step1Body') }, + { icon: 'grid-2x2', title: t('onboarding.step2Title'), body: t('onboarding.step2Body') }, + { icon: 'circle-check', title: t('onboarding.step3Title'), body: t('onboarding.step3Body') }, + ]; +} + +function showOnboarding(appContainer) { + const steps = getOnboardingSteps(); + let current = 0; + + const overlay = document.createElement('div'); + overlay.className = 'onboarding-overlay'; + overlay.setAttribute('role', 'dialog'); + overlay.setAttribute('aria-modal', 'true'); + + function renderStep() { + const step = steps[current]; + const isLast = current === steps.length - 1; + overlay.replaceChildren(); + + const card = document.createElement('div'); + card.className = 'onboarding-card'; + + const icon = document.createElement('i'); + icon.dataset.lucide = step.icon; + icon.className = 'onboarding-icon'; + icon.setAttribute('aria-hidden', 'true'); + + const title = document.createElement('h2'); + title.className = 'onboarding-title'; + title.textContent = step.title; + + const body = document.createElement('p'); + body.className = 'onboarding-body'; + body.textContent = step.body; + + const dots = document.createElement('div'); + dots.className = 'onboarding-dots'; + steps.forEach((_, i) => { + const dot = document.createElement('span'); + dot.className = `onboarding-dot${i === current ? ' onboarding-dot--active' : ''}`; + dots.appendChild(dot); + }); + + const actions = document.createElement('div'); + actions.className = 'onboarding-actions'; + + const skipBtn = document.createElement('button'); + skipBtn.className = 'btn btn--ghost'; + skipBtn.textContent = t('onboarding.skip'); + skipBtn.addEventListener('click', finish); + + const nextBtn = document.createElement('button'); + nextBtn.className = 'btn btn--primary'; + nextBtn.textContent = isLast ? t('onboarding.done') : t('onboarding.next'); + nextBtn.addEventListener('click', () => { + if (isLast) { finish(); return; } + current++; + renderStep(); + if (window.lucide) window.lucide.createIcons({ el: overlay }); + nextBtn.focus(); + }); + + actions.appendChild(skipBtn); + actions.appendChild(nextBtn); + card.appendChild(icon); + card.appendChild(title); + card.appendChild(body); + card.appendChild(dots); + card.appendChild(actions); + overlay.appendChild(card); + + if (window.lucide) window.lucide.createIcons({ el: overlay }); + setTimeout(() => nextBtn.focus(), 50); + } + + function finish() { + localStorage.setItem(ONBOARDING_KEY, '1'); + overlay.classList.add('onboarding-overlay--out'); + overlay.addEventListener('animationend', () => overlay.remove(), { once: true }); + // Fallback falls animationend nicht feuert (prefers-reduced-motion): + setTimeout(() => overlay.remove(), 300); + } + + renderStep(); + appContainer.appendChild(overlay); +} + // -------------------------------------------------------- // Widget-Definitionen (Reihenfolge = Standard-Layout) // -------------------------------------------------------- @@ -823,6 +917,10 @@ export async function render(container, { user }) { const timerId = setInterval(doAutoRefresh, 30 * 60 * 1000); _fabController.signal.addEventListener('abort', () => clearInterval(timerId)); } + + if (!localStorage.getItem(ONBOARDING_KEY)) { + setTimeout(() => showOnboarding(container), 400); + } } function wireWeatherRefresh(container) { diff --git a/public/styles/dashboard.css b/public/styles/dashboard.css index b6edb8e..086164f 100644 --- a/public/styles/dashboard.css +++ b/public/styles/dashboard.css @@ -1220,3 +1220,98 @@ opacity: 0.3; cursor: not-allowed; } + +/* ── Onboarding Overlay ── */ +.onboarding-overlay { + position: fixed; + inset: 0; + z-index: var(--z-modal); + background: color-mix(in srgb, var(--color-bg) 70%, transparent); + backdrop-filter: var(--blur-lg); + -webkit-backdrop-filter: var(--blur-lg); + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-6); + animation: onboarding-in 0.3s var(--ease-out); +} + +.onboarding-overlay--out { + animation: onboarding-out 0.25s ease forwards; +} + +@keyframes onboarding-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes onboarding-out { + from { opacity: 1; } + to { opacity: 0; } +} + +.onboarding-card { + background: var(--color-surface); + border: 1px solid var(--color-border-subtle); + border-radius: var(--radius-xl); + padding: var(--space-8); + max-width: 360px; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-4); + text-align: center; + box-shadow: var(--shadow-lg); +} + +.onboarding-icon { + width: 48px; + height: 48px; + color: var(--color-accent); +} + +.onboarding-title { + font-size: var(--text-xl); + font-weight: var(--font-weight-semibold); + color: var(--color-text); + margin: 0; +} + +.onboarding-body { + font-size: var(--text-base); + color: var(--color-text-secondary); + line-height: 1.6; + margin: 0; +} + +.onboarding-dots { + display: flex; + gap: var(--space-2); +} + +.onboarding-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--color-border); + transition: background 0.2s; +} + +.onboarding-dot--active { + background: var(--color-accent); +} + +.onboarding-actions { + display: flex; + gap: var(--space-3); + width: 100%; + justify-content: flex-end; + margin-top: var(--space-2); +} + +@media (prefers-reduced-motion: reduce) { + .onboarding-overlay, + .onboarding-overlay--out { animation: none; } + .onboarding-dot { transition: none; } +}