82a1f2c239
Add support for customizable birthday reminders with preset offsets (none, at time, 15min, 1h, 1d, 2d, 1w, 2w) and custom intervals. Users can now configure when to be reminded of upcoming birthdays. - Add migration 31: reminder_offset, reminder_custom_amount, reminder_custom_unit to birthdays table - Update POST/PUT /birthdays routes to accept reminder fields - Add getOffsetMinutes() helper in birthday service - Update birthdayReminderAt() to calculate reminder time with offset - Modify syncBirthdayReminder() to handle empty offset (no reminder) - Add renderBirthdayReminderSection() UI component - Move reminder-custom CSS from calendar.css to reminders.css - Add protocol check to service worker (non-http protocol guard) All translations already present in de.json. Tests: 109 passing, 0 failing. Co-Authored-By: Rafael Foster <rafaelfoster@users.noreply.github.com> Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
778 lines
23 KiB
HTML
778 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Oikos UX/UI Analyse</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
background: #0f0f0e;
|
||
color: #f0ede8;
|
||
min-height: 100vh;
|
||
padding: 32px 24px 64px;
|
||
}
|
||
|
||
.header {
|
||
max-width: 900px;
|
||
margin: 0 auto 40px;
|
||
}
|
||
|
||
.header__eyebrow {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: #818CF8;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
h1 {
|
||
font-size: clamp(28px, 5vw, 42px);
|
||
font-weight: 700;
|
||
letter-spacing: -0.03em;
|
||
line-height: 1.1;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
h1 span { color: #818CF8; }
|
||
|
||
.header__sub {
|
||
font-size: 15px;
|
||
color: #8E8D89;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* ---- Section ---- */
|
||
.section {
|
||
max-width: 900px;
|
||
margin: 0 auto 48px;
|
||
}
|
||
|
||
.section__label {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.section__label::after {
|
||
content: '';
|
||
flex: 1;
|
||
height: 1px;
|
||
background: rgba(255,255,255,0.08);
|
||
}
|
||
|
||
.label--critical { color: #FCA5A5; }
|
||
.label--high { color: #FCD34D; }
|
||
.label--medium { color: #6EE7B7; }
|
||
|
||
/* ---- Issue Cards ---- */
|
||
.issues {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.issue {
|
||
background: #1a1a18;
|
||
border-radius: 16px;
|
||
border: 1px solid rgba(255,255,255,0.06);
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
transition: border-color 0.15s, transform 0.15s;
|
||
cursor: default;
|
||
}
|
||
|
||
.issue:hover {
|
||
border-color: rgba(255,255,255,0.12);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.issue__header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
}
|
||
|
||
.issue__icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.icon--critical { background: rgba(252,165,165,0.15); }
|
||
.icon--high { background: rgba(252,211,77,0.12); }
|
||
.icon--medium { background: rgba(110,231,183,0.12); }
|
||
|
||
.issue__title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #f0ede8;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.issue__body {
|
||
font-size: 13px;
|
||
color: #8E8D89;
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.issue__badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 2px 8px;
|
||
border-radius: 99px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
align-self: flex-start;
|
||
margin-top: auto;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
.badge--critical { color: #FCA5A5; background: rgba(252,165,165,0.12); }
|
||
.badge--high { color: #FCD34D; background: rgba(252,211,77,0.10); }
|
||
.badge--medium { color: #6EE7B7; background: rgba(110,231,183,0.10); }
|
||
|
||
/* ---- Visual Demo: Navigation ---- */
|
||
.demo-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
margin-top: 32px;
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.demo-grid { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.demo-box {
|
||
background: #1a1a18;
|
||
border-radius: 16px;
|
||
border: 1px solid rgba(255,255,255,0.06);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.demo-box__label {
|
||
padding: 12px 16px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
border-bottom: 1px solid rgba(255,255,255,0.06);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.demo-box__label .dot {
|
||
width: 8px; height: 8px; border-radius: 50%;
|
||
}
|
||
|
||
.dot--bad { background: #FCA5A5; }
|
||
.dot--good { background: #6EE7B7; }
|
||
|
||
.demo-box__body { padding: 16px; }
|
||
|
||
/* Sidebar simulation */
|
||
.sim-sidebar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
width: 64px;
|
||
background: #222220;
|
||
border-radius: 12px;
|
||
padding: 12px 8px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.sim-sidebar-wide {
|
||
width: 180px;
|
||
}
|
||
|
||
.sim-nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
color: #8E8D89;
|
||
}
|
||
|
||
.sim-nav-item--active {
|
||
background: rgba(129,140,248,0.15);
|
||
color: #818CF8;
|
||
}
|
||
|
||
.sim-nav-icon {
|
||
width: 20px; height: 20px;
|
||
background: currentColor;
|
||
border-radius: 4px;
|
||
opacity: 0.5;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sim-nav-item--active .sim-nav-icon { opacity: 1; }
|
||
|
||
/* Touch target demo */
|
||
.touch-demo {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.touch-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.touch-btn {
|
||
border-radius: 8px;
|
||
background: rgba(255,255,255,0.08);
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
color: #f0ede8;
|
||
font-weight: 500;
|
||
position: relative;
|
||
}
|
||
|
||
.touch-btn--bad {
|
||
width: 40px; height: 40px;
|
||
border-color: rgba(252,165,165,0.4);
|
||
background: rgba(252,165,165,0.08);
|
||
color: #FCA5A5;
|
||
}
|
||
|
||
.touch-btn--good {
|
||
width: 44px; height: 44px;
|
||
border-color: rgba(110,231,183,0.4);
|
||
background: rgba(110,231,183,0.08);
|
||
color: #6EE7B7;
|
||
}
|
||
|
||
.touch-label {
|
||
font-size: 12px;
|
||
color: #8E8D89;
|
||
}
|
||
|
||
.touch-label strong { color: #f0ede8; font-weight: 600; }
|
||
|
||
/* Widget touch demo */
|
||
.widget-link-demo {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px 14px;
|
||
background: #222220;
|
||
border-radius: 10px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.wl-title { font-size: 13px; font-weight: 600; }
|
||
|
||
.wl-link--bad {
|
||
font-size: 11px;
|
||
color: #818CF8;
|
||
padding: 2px 0;
|
||
/* No min-height — very small tap target */
|
||
}
|
||
|
||
.wl-link--good {
|
||
font-size: 11px;
|
||
color: #6EE7B7;
|
||
padding: 8px 10px;
|
||
background: rgba(110,231,183,0.1);
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* Onboarding demo */
|
||
.onboarding-demo {
|
||
background: #222220;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.onboarding-steps {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.o-step {
|
||
width: 8px; height: 8px;
|
||
border-radius: 50%;
|
||
background: rgba(255,255,255,0.15);
|
||
}
|
||
|
||
.o-step--active { background: #818CF8; width: 20px; border-radius: 4px; }
|
||
|
||
.onboarding-icon {
|
||
width: 48px; height: 48px;
|
||
border-radius: 14px;
|
||
background: rgba(129,140,248,0.15);
|
||
margin: 0 auto 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.o-count {
|
||
font-size: 11px;
|
||
color: #8E8D89;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
/* Module grid demo (more-sheet) */
|
||
.module-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 8px;
|
||
padding: 12px;
|
||
background: #222220;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.module-chip {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 10px 6px;
|
||
border-radius: 10px;
|
||
background: #2A2A28;
|
||
font-size: 10px;
|
||
color: #8E8D89;
|
||
text-align: center;
|
||
}
|
||
|
||
.module-icon {
|
||
width: 24px; height: 24px;
|
||
border-radius: 6px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
/* Divider */
|
||
.divider {
|
||
max-width: 900px;
|
||
margin: 0 auto 40px;
|
||
height: 1px;
|
||
background: rgba(255,255,255,0.06);
|
||
}
|
||
|
||
/* Summary bar */
|
||
.summary {
|
||
max-width: 900px;
|
||
margin: 0 auto;
|
||
background: linear-gradient(135deg, rgba(129,140,248,0.15), rgba(167,139,250,0.08));
|
||
border: 1px solid rgba(129,140,248,0.2);
|
||
border-radius: 16px;
|
||
padding: 24px;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
@media (max-width: 500px) {
|
||
.summary { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.summary__num {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.03em;
|
||
line-height: 1;
|
||
}
|
||
|
||
.summary__label {
|
||
font-size: 12px;
|
||
color: #8E8D89;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.num--critical { color: #FCA5A5; }
|
||
.num--high { color: #FCD34D; }
|
||
.num--medium { color: #6EE7B7; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="header">
|
||
<div class="header__eyebrow">UX/UI Analyse · Oikos · April 2026</div>
|
||
<h1>Was funktioniert gut —<br>und was bremst <span>Oikos</span> aus</h1>
|
||
<p class="header__sub">70 % Mobile-PWA · 30 % Desktop · 11 Module · Vanilla JS · Kein Build-Step</p>
|
||
</div>
|
||
|
||
<!-- Summary -->
|
||
<div class="section">
|
||
<div class="summary">
|
||
<div>
|
||
<div class="summary__num num--critical">4</div>
|
||
<div class="summary__label">Kritische Probleme<br>(sofort beheben)</div>
|
||
</div>
|
||
<div>
|
||
<div class="summary__num num--high">5</div>
|
||
<div class="summary__label">Hohe Priorität<br>(nächster Sprint)</div>
|
||
</div>
|
||
<div>
|
||
<div class="summary__num num--medium">4</div>
|
||
<div class="summary__label">Mittlere Priorität<br>(Backlog)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- KRITISCH -->
|
||
<div class="section">
|
||
<div class="section__label label--critical">🔴 Kritisch — direkt sichtbare UX-Brüche</div>
|
||
<div class="issues">
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--critical">🧭</div>
|
||
<div class="issue__title">Sidebar 1024–1279 px: Icons ohne Labels oder Tooltips</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Bei der häufigsten Desktop-Auflösung zeigt die Sidebar nur Icons — kein Tooltip, kein Label.
|
||
Neue Nutzer können die 11 Module nicht erkennen. Das verletzt das Grundprinzip
|
||
«nav-label-icon» (HIG/Material).
|
||
</div>
|
||
<div class="issue__badge badge--critical">Keine Discoverability</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--critical">👆</div>
|
||
<div class="issue__title">Modal-Close: 40 px statt 44 px Minimum</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
<code>.modal-panel__close</code> nutzt <code>--target-md</code> (40 px).
|
||
Das iOS-Minimum liegt bei 44 pt. Auf kleinen Displays — gerade beim
|
||
Schließen tippend — ist das ein spürbares Frustrationspotenzial.
|
||
</div>
|
||
<div class="issue__badge badge--critical">Apple HIG Violation</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--critical">🔗</div>
|
||
<div class="issue__title">Widget-Links: kein Min-Height-Tap-Target</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
<code>.widget__link</code> hat 12 px Text, kein explizites <code>min-height</code>.
|
||
Der effektive Tippbereich ist ~16–18 px — weit unter 44 px.
|
||
Auf dem Dashboard ist dieser Link auf jedem Widget sichtbar.
|
||
</div>
|
||
<div class="issue__badge badge--critical">Tap-Target < 44 px</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--critical">🔁</div>
|
||
<div class="issue__title">Doppelter FAB: .fab vs .page-fab</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
In <code>layout.css</code> existieren zwei nahezu identische FAB-Klassen (<code>.fab</code>
|
||
und <code>.page-fab</code>) mit unterschiedlicher <code>bottom</code>-Berechnung.
|
||
Das erzeugt inkonsistente Positionierung auf verschiedenen Seiten.
|
||
</div>
|
||
<div class="issue__badge badge--critical">Inkonsistente UI</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Visuelle Demo: Navigation -->
|
||
<div class="section">
|
||
<div class="section__label label--critical" style="color:#8E8D89">↳ Visuell: Navigation-Problem & Lösung</div>
|
||
<div class="demo-grid">
|
||
<div class="demo-box">
|
||
<div class="demo-box__label">
|
||
<span class="dot dot--bad"></span>
|
||
Ist-Zustand: 1024–1279 px (nur Icons)
|
||
</div>
|
||
<div class="demo-box__body">
|
||
<div class="sim-sidebar">
|
||
<div class="sim-nav-item sim-nav-item--active">
|
||
<div class="sim-nav-icon"></div>
|
||
</div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
</div>
|
||
<p style="font-size:11px;color:#8E8D89;text-align:center;margin-top:10px">Welcher Icon ist "Geburtstage"?</p>
|
||
</div>
|
||
</div>
|
||
<div class="demo-box">
|
||
<div class="demo-box__label">
|
||
<span class="dot dot--good"></span>
|
||
Soll-Zustand: Tooltip bei Hover (min)
|
||
</div>
|
||
<div class="demo-box__body">
|
||
<div class="sim-sidebar" style="position:relative">
|
||
<div class="sim-nav-item sim-nav-item--active" style="position:relative">
|
||
<div class="sim-nav-icon"></div>
|
||
<div style="position:absolute;left:52px;top:50%;transform:translateY(-50%);background:#333;padding:4px 10px;border-radius:6px;font-size:11px;color:#f0ede8;white-space:nowrap;z-index:10;border:1px solid rgba(255,255,255,0.1)">Dashboard</div>
|
||
</div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
<div class="sim-nav-item"><div class="sim-nav-icon"></div></div>
|
||
</div>
|
||
<p style="font-size:11px;color:#6EE7B7;text-align:center;margin-top:10px">Sofort klar durch title-Tooltip</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Touch Demo -->
|
||
<div class="section" style="margin-top:0">
|
||
<div class="section__label label--critical" style="color:#8E8D89">↳ Visuell: Touch-Target Größen</div>
|
||
<div class="demo-grid">
|
||
<div class="demo-box">
|
||
<div class="demo-box__label">
|
||
<span class="dot dot--bad"></span>
|
||
Ist-Zustand: Modal-Close 40 × 40 px
|
||
</div>
|
||
<div class="demo-box__body">
|
||
<div class="touch-demo">
|
||
<div class="touch-row">
|
||
<div class="touch-btn touch-btn--bad">✕</div>
|
||
<div class="touch-label"><strong>40 × 40 px</strong> — 4 px unter iOS-Minimum</div>
|
||
</div>
|
||
<div class="touch-row" style="margin-top:4px">
|
||
<div style="font-size:11px;color:#FCA5A5;padding:4px 10px;background:rgba(252,165,165,0.08);border-radius:6px">
|
||
Fingertipp ~44–50 px → Fehlklick-Rate steigt
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="demo-box">
|
||
<div class="demo-box__label">
|
||
<span class="dot dot--good"></span>
|
||
Fix: --target-base (44 px)
|
||
</div>
|
||
<div class="demo-box__body">
|
||
<div class="touch-demo">
|
||
<div class="touch-row">
|
||
<div class="touch-btn touch-btn--good">✕</div>
|
||
<div class="touch-label"><strong>44 × 44 px</strong> — Apple HIG Minimum</div>
|
||
</div>
|
||
<div class="touch-row" style="margin-top:4px">
|
||
<div style="font-size:11px;color:#6EE7B7;padding:4px 10px;background:rgba(110,231,183,0.08);border-radius:6px">
|
||
Einzeiler-Fix: <code>--target-base</code> statt <code>--target-md</code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- HOCH -->
|
||
<div class="section">
|
||
<div class="section__label label--high">🟡 Hohe Priorität — spürbare UX-Einschränkungen</div>
|
||
<div class="issues">
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--high">🗂️</div>
|
||
<div class="issue__title">Onboarding: 3 Schritte für 11 Module</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Neue Nutzer sehen einmalig 3 generische Onboarding-Screens.
|
||
Module wie Budget, RRule-Wiederholungen oder Google Calendar-Sync
|
||
werden nie erklärt. Kein Feature-Discovery-Mechanismus danach.
|
||
</div>
|
||
<div class="issue__badge badge--high">Discoverability</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--high">📊</div>
|
||
<div class="issue__title">Dashboard-Widgets: keine Reihenfolge-Anpassung</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Widgets können ein-/ausgeblendet werden, aber nicht umsortiert.
|
||
Die Widget-Config speichert nur <code>{ id, visible }</code> — kein
|
||
Reihenfolge-Feld. Für Familien mit verschiedenen Prioritäten ist das stark limitierend.
|
||
</div>
|
||
<div class="issue__badge badge--high">Personalisierung</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--high">↩️</div>
|
||
<div class="issue__title">Inkonsistentes Undo-Verhalten</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Einige Aktionen zeigen einen Undo-Toast, andere nicht.
|
||
Es gibt kein zentrales Undo-System. Bei destruktiven Aktionen
|
||
(z. B. Kontakt löschen) fehlt der Undo-Pfad komplett.
|
||
</div>
|
||
<div class="issue__badge badge--high">Fehlererholung</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--high">📡</div>
|
||
<div class="issue__title">Kein sichtbarer Offline-Indikator</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Der Service Worker existiert, aber der App-Shell fehlt ein
|
||
Offline-Banner. Nutzer bemerken den Offline-Zustand erst,
|
||
wenn eine API-Anfrage fehlschlägt — zu spät.
|
||
</div>
|
||
<div class="issue__badge badge--high">PWA-Erfahrung</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--high">⌨️</div>
|
||
<div class="issue__title">Desktop: keine Keyboard Shortcuts</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Bei 30 % Desktop-Nutzung fehlen globale Shortcuts.
|
||
Kein „N" für neue Aufgabe, kein „/" für Suche, kein
|
||
Escape-Verhalten im globalem Kontext. Power-User müssen
|
||
alles mit der Maus bedienen.
|
||
</div>
|
||
<div class="issue__badge badge--high">Desktop-Ergonomie</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- MITTEL -->
|
||
<div class="section">
|
||
<div class="section__label label--medium">🟢 Mittlere Priorität — qualitative Verbesserungen</div>
|
||
<div class="issues">
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--medium">💬</div>
|
||
<div class="issue__title">reminders.css global geladen</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
<code>reminders.css</code> wird laut Observations global geladen,
|
||
nicht lazy. Auf Seiten ohne Reminder-UI werden unnötige Styles
|
||
geparst. Kein Blocking-Problem, aber vermeidbare CSS-Last.
|
||
</div>
|
||
<div class="issue__badge badge--medium">Performance</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--medium">🃏</div>
|
||
<div class="issue__title">Swipe-Geste: nur Tasks & Shopping</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Swipe-Reveal ist für Tasks und Shopping implementiert,
|
||
fehlt aber bei Kontakten, Notizen und Geburtstagen — obwohl
|
||
die Interaktion dort genauso wertvoll wäre.
|
||
Inkonsistente Erwartungshaltung.
|
||
</div>
|
||
<div class="issue__badge badge--medium">Interaktions-Konsistenz</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--medium">🎨</div>
|
||
<div class="issue__title">11 Modulfarben gleichzeitig im Dashboard</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Wenn alle Widgets sichtbar sind, treffen 11 verschiedene
|
||
Akzentfarben aufeinander. Das Dashboard wirkt farblich
|
||
überladen. Weniger Kontrast zwischen den Modulen würde
|
||
die Ruhewirkung verbessern.
|
||
</div>
|
||
<div class="issue__badge badge--medium">Visuelle Ruhe</div>
|
||
</div>
|
||
|
||
<div class="issue">
|
||
<div class="issue__header">
|
||
<div class="issue__icon icon--medium">📱</div>
|
||
<div class="issue__title">Toast: kein Swipe-to-Dismiss auf Mobile</div>
|
||
</div>
|
||
<div class="issue__body">
|
||
Toasts können nicht weggewischt werden — nur auto-dismiss
|
||
oder Undo-Button. Auf Mobile ist Swipe-to-Dismiss eine
|
||
etablierte Konvention (iOS, Android), deren Fehlen auffällt.
|
||
</div>
|
||
<div class="issue__badge badge--medium">Mobile-Konvention</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Stärken -->
|
||
<div class="section">
|
||
<div class="section__label" style="color:#818CF8">✨ Was bereits ausgezeichnet ist</div>
|
||
<div class="issues">
|
||
<div class="issue" style="border-color:rgba(129,140,248,0.15)">
|
||
<div class="issue__title" style="margin-bottom:6px">🌙 Dark Mode</div>
|
||
<div class="issue__body">Private-/Public-Token-Architektur ist mustergültig. Alle Kontrastverhältnisse WCAG-geprüft, Toast-Texte passen sich an.</div>
|
||
</div>
|
||
<div class="issue" style="border-color:rgba(129,140,248,0.15)">
|
||
<div class="issue__title" style="margin-bottom:6px">♿ Accessibility-Schichten</div>
|
||
<div class="issue__body">prefers-reduced-motion, prefers-reduced-transparency, prefers-contrast, forced-colors — alle implementiert. Selten in selbstgehosteten Apps.</div>
|
||
</div>
|
||
<div class="issue" style="border-color:rgba(129,140,248,0.15)">
|
||
<div class="issue__title" style="margin-bottom:6px">🍎 iOS-PWA-Bewusstsein</div>
|
||
<div class="issue__body">100dvh + -webkit-fill-available Fallback, safe-area-inset-*, Flex-Kind statt fixed für Bottom-Nav — solide PWA-Grundlage.</div>
|
||
</div>
|
||
<div class="issue" style="border-color:rgba(129,140,248,0.15)">
|
||
<div class="issue__title" style="margin-bottom:6px">💎 Glass Design System</div>
|
||
<div class="issue__body">@supports-basiert mit korrekten webkit-Fallbacks, opake Fallbacks für reduced-transparency. Konsistente Token-Hierarchie.</div>
|
||
</div>
|
||
<div class="issue" style="border-color:rgba(129,140,248,0.15)">
|
||
<div class="issue__title" style="margin-bottom:6px">🎭 Modul-Theming</div>
|
||
<div class="issue__body">--active-module-accent + --module-accent System ist elegant. FAB, Nav-Item und Toggles reflektieren automatisch die aktive Seite.</div>
|
||
</div>
|
||
<div class="issue" style="border-color:rgba(129,140,248,0.15)">
|
||
<div class="issue__title" style="margin-bottom:6px">📐 Design Tokens</div>
|
||
<div class="issue__body">Vollständige Skala für Farben, Radien, Schatten, Spacing, Typografie. Konsistente Anwendung — kaum Hardcoding gefunden.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Inject into brainstorm frame if available
|
||
if (window.parent !== window) {
|
||
// signal ready
|
||
window.parent.postMessage({ type: 'screen-ready' }, '*');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|