feat(ux): first-time onboarding overlay for new users
Shows a 3-screen modal overlay on first dashboard visit explaining key features; dismissed state is persisted to localStorage so it never reappears. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user