@@ -0,0 +1,629 @@
<!DOCTYPE html>
< html lang = "de" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Oikos — Lösungsvorschläge< / 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 : 100 vh ; padding : 32 px 24 px 100 px ;
}
code { font-family : 'SF Mono' , 'Fira Code' , monospace ; font-size : .9 em ; background : rgba ( 255 , 255 , 255 , .08 ) ; padding : 1 px 5 px ; border-radius : 4 px ; }
a { color : #818CF8 ; text-decoration : none ; }
. page-title { max-width : 960 px ; margin : 0 auto 48 px ; }
. eyebrow { font-size : 11 px ; font-weight : 600 ; letter-spacing : .1 em ; text-transform : uppercase ; color : #818CF8 ; margin-bottom : 8 px ; }
h1 { font-size : clamp ( 24 px , 4 vw , 38 px ) ; font-weight : 700 ; letter-spacing : -.03 em ; line-height : 1.1 ; margin-bottom : 10 px ; }
h1 span { color : #818CF8 ; }
. sub { font-size : 14 px ; color : #8E8D89 ; line-height : 1.5 ; }
/* Fix block */
. fix { max-width : 960 px ; margin : 0 auto 56 px ; }
. fix__header { display : flex ; align-items : flex-start ; gap : 16 px ; margin-bottom : 24 px ; }
. fix__num { width : 40 px ; height : 40 px ; border-radius : 50 % ; display : flex ; align-items : center ; justify-content : center ; font-size : 16 px ; font-weight : 700 ; flex-shrink : 0 ; margin-top : 2 px ; }
. num-c { background : rgba ( 252 , 165 , 165 , .15 ) ; color : #FCA5A5 ; }
. num-h { background : rgba ( 252 , 211 , 77 , .12 ) ; color : #FCD34D ; }
. num-m { background : rgba ( 110 , 231 , 183 , .1 ) ; color : #6EE7B7 ; }
. fix__meta { flex : 1 ; }
. fix__tag { font-size : 10 px ; font-weight : 700 ; letter-spacing : .08 em ; text-transform : uppercase ; margin-bottom : 4 px ; }
. fix__title { font-size : 20 px ; font-weight : 700 ; letter-spacing : -.02 em ; line-height : 1.2 ; }
. fix__why { font-size : 13 px ; color : #8E8D89 ; line-height : 1.6 ; margin-top : 6 px ; }
/* Split layout */
. split { display : grid ; grid-template-columns : 1 fr 1 fr ; gap : 16 px ; }
@ media ( max-width : 640px ) { . split { grid-template-columns : 1 fr ; } }
. panel { background : #1a1a18 ; border-radius : 14 px ; border : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; overflow : hidden ; }
. panel__head { padding : 10 px 16 px ; font-size : 11 px ; font-weight : 600 ; letter-spacing : .06 em ; text-transform : uppercase ; border-bottom : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; display : flex ; align-items : center ; gap : 8 px ; }
. dot { width : 8 px ; height : 8 px ; border-radius : 50 % ; flex-shrink : 0 ; }
. dot-r { background : #FCA5A5 ; } . dot-g { background : #6EE7B7 ; }
. panel__body { padding : 20 px ; }
/* Code panel */
. code-panel { background : #111110 ; border-radius : 14 px ; border : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; overflow : hidden ; }
. code-panel__head { padding : 10 px 16 px ; font-size : 11 px ; font-weight : 600 ; color : #8E8D89 ; border-bottom : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; display : flex ; align-items : center ; justify-content : space-between ; }
. code-panel__body { padding : 16 px 20 px ; font-family : 'SF Mono' , 'Fira Code' , monospace ; font-size : 12 px ; line-height : 1.7 ; overflow-x : auto ; }
. del { color : #FCA5A5 ; } . add { color : #6EE7B7 ; } . cmt { color : #636360 ; }
/* Sidebar mock */
. mock-sidebar { display : flex ; flex-direction : column ; gap : 4 px ; width : 60 px ; background : #222220 ; border-radius : 10 px ; padding : 10 px 6 px ; }
. mock-sidebar-wide { width : 190 px ; }
. mock-si { display : flex ; align-items : center ; gap : 10 px ; padding : 8 px ; border-radius : 8 px ; color : #8E8D89 ; font-size : 12 px ; position : relative ; }
. mock-si--active { background : rgba ( 129 , 140 , 248 , .15 ) ; color : #818CF8 ; }
. mock-si__ico { width : 18 px ; height : 18 px ; background : currentColor ; border-radius : 4 px ; opacity : .5 ; flex-shrink : 0 ; }
. mock-si--active . mock-si__ico { opacity : 1 ; }
. mock-tooltip { position : absolute ; left : calc ( 100 % + 10 px ) ; top : 50 % ; transform : translateY ( -50 % ) ; background : #2A2A28 ; border : 1 px solid rgba ( 255 , 255 , 255 , .12 ) ; color : #f0ede8 ; font-size : 11 px ; padding : 4 px 10 px ; border-radius : 6 px ; white-space : nowrap ; z-index : 10 ; pointer-events : none ; }
. mock-tooltip :: before { content : '' ; position : absolute ; right : 100 % ; top : 50 % ; transform : translateY ( -50 % ) ; border : 5 px solid transparent ; border-right-color : #2A2A28 ; }
/* Touch mock */
. touch-compare { display : flex ; gap : 20 px ; align-items : flex-start ; flex-wrap : wrap ; }
. touch-item { display : flex ; flex-direction : column ; align-items : center ; gap : 8 px ; }
. touch-box { border-radius : 10 px ; border : 1.5 px solid ; display : flex ; align-items : center ; justify-content : center ; font-weight : 600 ; font-size : 13 px ; }
. tb-bad { width : 40 px ; height : 40 px ; border-color : rgba ( 252 , 165 , 165 , .4 ) ; background : rgba ( 252 , 165 , 165 , .06 ) ; color : #FCA5A5 ; }
. tb-good { width : 44 px ; height : 44 px ; border-color : rgba ( 110 , 231 , 183 , .4 ) ; background : rgba ( 110 , 231 , 183 , .06 ) ; color : #6EE7B7 ; }
. touch-lbl { font-size : 11 px ; color : #8E8D89 ; text-align : center ; }
. touch-lbl strong { color : #f0ede8 ; display : block ; }
/* Widget link mock */
. widget-mock { background : #222220 ; border-radius : 10 px ; padding : 12 px 14 px ; }
. wm-row { display : flex ; align-items : center ; justify-content : space-between ; margin-bottom : 8 px ; }
. wm-title { font-size : 13 px ; font-weight : 600 ; }
. wm-link { font-size : 11 px ; }
. wm-link--bad { color : #818CF8 ; padding : 2 px 0 ; } /* no min-height */
. wm-link--good { color : #818CF8 ; padding : 8 px 10 px ; background : rgba ( 129 , 140 , 248 , .1 ) ; border-radius : 6 px ; min-height : 32 px ; display : flex ; align-items : center ; }
. wm-body { height : 40 px ; background : rgba ( 255 , 255 , 255 , .04 ) ; border-radius : 6 px ; }
/* FAB comparison */
. fab-compare { display : flex ; flex-direction : column ; gap : 12 px ; }
. fab-row { display : flex ; align-items : center ; gap : 12 px ; padding : 10 px 12 px ; background : rgba ( 255 , 255 , 255 , .04 ) ; border-radius : 8 px ; border : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; }
. fab-circle { width : 44 px ; height : 44 px ; border-radius : 50 % ; display : flex ; align-items : center ; justify-content : center ; font-size : 20 px ; flex-shrink : 0 ; }
. fab-info { flex : 1 ; }
. fab-name { font-size : 12 px ; font-weight : 600 ; font-family : monospace ; }
. fab-desc { font-size : 11 px ; color : #8E8D89 ; margin-top : 2 px ; }
. fab-bad { background : rgba ( 252 , 165 , 165 , .12 ) ; border : 1.5 px solid rgba ( 252 , 165 , 165 , .3 ) ; }
. fab-good { background : rgba ( 110 , 231 , 183 , .1 ) ; border : 1.5 px solid rgba ( 110 , 231 , 183 , .25 ) ; }
/* Onboarding mock */
. ob-flow { display : flex ; gap : 12 px ; overflow-x : auto ; padding-bottom : 4 px ; }
. ob-card { background : #222220 ; border-radius : 12 px ; padding : 16 px ; width : 140 px ; flex-shrink : 0 ; text-align : center ; border : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; }
. ob-card--highlight { border-color : rgba ( 129 , 140 , 248 , .3 ) ; background : rgba ( 129 , 140 , 248 , .06 ) ; }
. ob-ico { width : 36 px ; height : 36 px ; border-radius : 10 px ; background : rgba ( 129 , 140 , 248 , .15 ) ; margin : 0 auto 8 px ; display : flex ; align-items : center ; justify-content : center ; font-size : 18 px ; }
. ob-title { font-size : 11 px ; font-weight : 600 ; margin-bottom : 4 px ; }
. ob-desc { font-size : 10 px ; color : #8E8D89 ; line-height : 1.4 ; }
. ob-dots { display : flex ; justify-content : center ; gap : 4 px ; margin-top : 8 px ; }
. ob-dot { width : 6 px ; height : 6 px ; border-radius : 50 % ; background : rgba ( 255 , 255 , 255 , .15 ) ; }
. ob-dot--on { background : #818CF8 ; width : 16 px ; border-radius : 3 px ; }
/* Widget order mock */
. widget-order { display : flex ; flex-direction : column ; gap : 8 px ; }
. wo-item { display : flex ; align-items : center ; gap : 10 px ; padding : 10 px 12 px ; background : #222220 ; border-radius : 8 px ; border : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; cursor : grab ; }
. wo-handle { color : #636360 ; font-size : 14 px ; }
. wo-dot { width : 10 px ; height : 10 px ; border-radius : 3 px ; flex-shrink : 0 ; }
. wo-label { font-size : 13 px ; font-weight : 500 ; flex : 1 ; }
. wo-vis { font-size : 10 px ; color : #8E8D89 ; }
/* Offline banner mock */
. offline-banner { padding : 10 px 16 px ; background : rgba ( 161 , 98 , 7 , .2 ) ; border : 1 px solid rgba ( 161 , 98 , 7 , .3 ) ; border-radius : 10 px ; display : flex ; align-items : center ; gap : 10 px ; margin-bottom : 10 px ; }
. ob-icon { font-size : 16 px ; }
. ob-text { font-size : 12 px ; color : #FCD34D ; flex : 1 ; }
. ob-pill { font-size : 10 px ; font-weight : 600 ; padding : 2 px 8 px ; background : rgba ( 161 , 98 , 7 , .25 ) ; color : #FCD34D ; border-radius : 99 px ; }
/* Keyboard shortcut mock */
. kbd-grid { display : grid ; grid-template-columns : repeat ( auto - fill , minmax ( 180 px , 1 fr ) ) ; gap : 8 px ; }
. kbd-row { display : flex ; align-items : center ; gap : 10 px ; padding : 8 px 12 px ; background : #1a1a18 ; border-radius : 8 px ; border : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; }
. kbd { padding : 2 px 7 px ; background : #2A2A28 ; border : 1 px solid rgba ( 255 , 255 , 255 , .15 ) ; border-bottom : 2 px solid rgba ( 255 , 255 , 255 , .08 ) ; border-radius : 5 px ; font-size : 11 px ; font-family : monospace ; color : #f0ede8 ; }
. kbd-label { font-size : 12 px ; color : #8E8D89 ; }
/* Divider */
hr { max-width : 960 px ; margin : 0 auto 48 px ; border : none ; border-top : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; }
/* Undo */
. undo-arch { display : flex ; flex-direction : column ; gap : 8 px ; }
. undo-row { display : flex ; align-items : center ; gap : 10 px ; padding : 10 px 14 px ; background : #1a1a18 ; border-radius : 8 px ; border : 1 px solid rgba ( 255 , 255 , 255 , .06 ) ; }
. undo-action { font-size : 12 px ; font-weight : 600 ; flex : 1 ; }
. undo-badge { font-size : 10 px ; padding : 2 px 8 px ; border-radius : 99 px ; }
. ub-yes { color : #6EE7B7 ; background : rgba ( 110 , 231 , 183 , .1 ) ; }
. ub-no { color : #FCA5A5 ; background : rgba ( 252 , 165 , 165 , .1 ) ; }
. ub-new { color : #FCD34D ; background : rgba ( 252 , 211 , 77 , .1 ) ; }
< / style >
< / head >
< body >
< div class = "page-title" >
< div class = "eyebrow" > Lösungsvorschläge · Oikos · April 2026< / div >
< h1 > Konkrete < span > Optimierungen< / span > — sortiert nach Impact< / h1 >
< p class = "sub" > 13 Punkte · 4 kritisch · 5 hoch · 4 mittel< / p >
< / div >
<!-- ════════════════════════════════ KRITISCH ════════════════════════════════ -->
<!-- FIX 1: Sidebar Tooltips -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-c" > 1< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCA5A5" > Kritisch · 1– 2 h< / div >
< div class = "fix__title" > Sidebar: title-Tooltips für icon-only Modus< / div >
< div class = "fix__why" > Nutzer bei 1024– 1279 px sehen 11 Icons ohne jede Beschriftung. Ein einfaches < code > title< / code > -Attribut auf jedem Nav-Item genügt als sofortiger Fix. Langfristig: expanded sidebar ab 1280 px früher aktivieren.< / div >
< / div >
< / div >
< div class = "split" >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-r" > < / span > Ist-Zustand< / div >
< div class = "panel__body" style = "display:flex;justify-content:center" >
< div class = "mock-sidebar" >
< div class = "mock-si mock-si--active" > < div class = "mock-si__ico" > < / div > < / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< / div >
< p style = "font-size:11px;color:#FCA5A5;margin-left:16px;align-self:center" > Kein Hinweis< br > was die Icons bedeuten< / p >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-g" > < / span > Soll-Zustand< / div >
< div class = "panel__body" style = "display:flex;justify-content:center" >
< div class = "mock-sidebar" style = "position:relative;overflow:visible" >
< div class = "mock-si mock-si--active" style = "position:relative" >
< div class = "mock-si__ico" > < / div >
< div class = "mock-tooltip" > Dashboard< / div >
< / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< div class = "mock-si" > < div class = "mock-si__ico" > < / div > < / div >
< / div >
< p style = "font-size:11px;color:#6EE7B7;margin-left:24px;align-self:center" > Tooltip bei< br > Hover/Focus sichtbar< / p >
< / div >
< / div >
< / div >
< div class = "code-panel" style = "margin-top:14px" >
< div class = "code-panel__head" > < span > router.js — Nav-Item Rendering< / span > < span style = "color:#6EE7B7" > +1 Zeile< / span > < / div >
< div class = "code-panel__body" >
< span class = "cmt" > // In renderAppShell() oder wo nav-items erzeugt werden:< / span >
< span class = "del" > - a.setAttribute('aria-label', label);< / span >
< span class = "add" > + a.setAttribute('aria-label', label);< / span >
< span class = "add" > + a.setAttribute('title', label); < span class = "cmt" > // Tooltip für collapsed sidebar< / span > < / span >
< br >
< span class = "cmt" > /* Optional: CSS-Tooltip statt native title (für Styling) */< / span >
< span class = "add" > + .nav-sidebar .nav-item[title]:hover::after {< / span >
< span class = "add" > + content: attr(title);< / span >
< span class = "add" > + position: absolute;< / span >
< span class = "add" > + left: calc(100% + 10px);< / span >
< span class = "add" > + /* ... Tooltip-Styles */< / span >
< span class = "add" > + }< / span >
< / div >
< / div >
< / div >
< hr >
<!-- FIX 2: Modal Close Button -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-c" > 2< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCA5A5" > Kritisch · 5 min< / div >
< div class = "fix__title" > Modal-Close: von 40 px auf 44 px< / div >
< div class = "fix__why" > Ein Einzeiler in < code > layout.css< / code > . Der Fehler liegt darin, dass < code > --target-md< / code > (40 px) statt < code > --target-base< / code > (44 px) verwendet wird. Das Token existiert bereits — es muss nur gewechselt werden.< / div >
< / div >
< / div >
< div class = "split" >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-r" > < / span > Problem: 40 × 40 px< / div >
< div class = "panel__body" >
< div class = "touch-compare" >
< div class = "touch-item" >
< div class = "touch-box tb-bad" > ✕< / div >
< div class = "touch-lbl" > < strong > 40 × 40 px< / strong > – target-md< br > 4 px unter Minimum< / div >
< / div >
< div style = "flex:1;font-size:12px;color:#8E8D89;line-height:1.6;align-self:center" >
Ein durchschnittlicher Fingertipp belegt 44– 50 px. Beim Schließen unter Stress (mit einer Hand, unterwegs) erhöht sich die Fehlklickrate spürbar.
< / div >
< / div >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-g" > < / span > Fix: 44 × 44 px< / div >
< div class = "panel__body" >
< div class = "touch-compare" >
< div class = "touch-item" >
< div class = "touch-box tb-good" > ✕< / div >
< div class = "touch-lbl" > < strong > 44 × 44 px< / strong > --target-base< br > Apple HIG ✓< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "code-panel" style = "margin-top:14px" >
< div class = "code-panel__head" > < span > layout.css · .modal-panel__close< / span > < span style = "color:#6EE7B7" > 1 Zeile< / span > < / div >
< div class = "code-panel__body" >
< span class = "del" > - width: var(--target-md); /* 40px */< / span >
< span class = "del" > - height: var(--target-md); /* 40px */< / span >
< span class = "add" > + width: var(--target-base); /* 44px — Apple HIG Minimum */< / span >
< span class = "add" > + height: var(--target-base); /* 44px */< / span >
< / div >
< / div >
< / div >
< hr >
<!-- FIX 3: Widget Links -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-c" > 3< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCA5A5" > Kritisch · 15 min< / div >
< div class = "fix__title" > Widget-Links: Tap-Target auf 44 px bringen< / div >
< div class = "fix__why" > Der „Alle anzeigen →"-Link in jedem Dashboard-Widget hat keinen definierten < code > min-height< / code > . Bei 12 px Text liegt der tatsächliche Klickbereich bei etwa 16– 18 px — weit unter iOS-Minimum. Fix: padding + min-height in < code > dashboard.css< / code > .< / div >
< / div >
< / div >
< div class = "split" >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-r" > < / span > Ist-Zustand< / div >
< div class = "panel__body" >
< div class = "widget-mock" >
< div class = "wm-row" >
< div class = "wm-title" > Aufgaben< / div >
< a class = "wm-link wm-link--bad" > Alle →< / a >
< / div >
< div class = "wm-body" > < / div >
< / div >
< p style = "font-size:11px;color:#FCA5A5;margin-top:8px" > Tap-Target ≈ 16 px Höhe< / p >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-g" > < / span > Soll-Zustand< / div >
< div class = "panel__body" >
< div class = "widget-mock" >
< div class = "wm-row" >
< div class = "wm-title" > Aufgaben< / div >
< a class = "wm-link wm-link--good" > Alle →< / a >
< / div >
< div class = "wm-body" > < / div >
< / div >
< p style = "font-size:11px;color:#6EE7B7;margin-top:8px" > Tap-Target ≥ 32 px mit Padding< / p >
< / div >
< / div >
< / div >
< div class = "code-panel" style = "margin-top:14px" >
< div class = "code-panel__head" > < span > dashboard.css · .widget__link< / span > < / div >
< div class = "code-panel__body" >
< span class = "add" > + .widget__link {< / span >
< span class = "add" > + min-height: var(--target-base); < span class = "cmt" > /* 44px */< / span > < / span >
< span class = "add" > + display: inline-flex;< / span >
< span class = "add" > + align-items: center;< / span >
< span class = "add" > + padding: 0 var(--space-2);< / span >
< span class = "add" > + border-radius: var(--radius-sm);< / span >
< span class = "add" > + }< / span >
< span class = "add" > + .widget__link:hover {< / span >
< span class = "add" > + background: color-mix(in srgb, var(--widget-accent) 10%, transparent);< / span >
< span class = "add" > + }< / span >
< / div >
< / div >
< / div >
< hr >
<!-- FIX 4: FAB -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-c" > 4< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCA5A5" > Kritisch · 30 min< / div >
< div class = "fix__title" > FAB konsolidieren: .fab + .page-fab → eine Klasse< / div >
< div class = "fix__why" > Beide Klassen definieren fast denselben Button mit leicht abweichender < code > bottom< / code > -Berechnung. Das führt zu inkonsistenter Positionierung. Die Lösung: eine Klasse, ein Token, alle Seiten konsistent.< / div >
< / div >
< / div >
< div class = "split" >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-r" > < / span > Ist-Zustand: 2 Klassen< / div >
< div class = "panel__body" >
< div class = "fab-compare" >
< div class = "fab-row" >
< div class = "fab-circle fab-bad" style = "background:rgba(252,165,165,.12)" > + < / div >
< div class = "fab-info" >
< div class = "fab-name" > .fab< / div >
< div class = "fab-desc" > bottom: nav-height + safe-area + space-4< / div >
< / div >
< / div >
< div class = "fab-row" >
< div class = "fab-circle fab-bad" style = "background:rgba(252,165,165,.12)" > + < / div >
< div class = "fab-info" >
< div class = "fab-name" > .page-fab< / div >
< div class = "fab-desc" > bottom: nav-bottom-height + 24px + safe-area< / div >
< / div >
< / div >
< / div >
< p style = "font-size:11px;color:#FCA5A5;margin-top:10px" > Verschiedene bottom-Werte → visuell inkonsistent je nach Seite< / p >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-g" > < / span > Soll-Zustand: 1 Klasse< / div >
< div class = "panel__body" >
< div class = "fab-compare" >
< div class = "fab-row" >
< div class = "fab-circle fab-good" > + < / div >
< div class = "fab-info" >
< div class = "fab-name" > .page-fab (Canonical)< / div >
< div class = "fab-desc" > Einheitliche bottom-Formel, alle Seiten< / div >
< / div >
< / div >
< / div >
< p style = "font-size:11px;color:#6EE7B7;margin-top:10px" > Alle Seiten nutzen .page-fab, .fab wird entfernt oder als Alias gesetzt< / p >
< / div >
< / div >
< / div >
< / div >
< hr >
<!-- ════════════════════════════════ HOCH ════════════════════════════════ -->
<!-- FIX 5: Widget Reorder -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-h" > 5< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCD34D" > Hoch · 2– 4 h< / div >
< div class = "fix__title" > Dashboard: Widget-Reihenfolge anpassbar machen< / div >
< div class = "fix__why" > Die Widget-Config speichert aktuell nur < code > { id, visible }< / code > . Ein < code > order< / code > -Feld hinzufügen und im Customization-Modal drag-sortierbar machen (per < code > touch-action: none< / code > + pointer events, kein externes Drag-Library nötig).< / div >
< / div >
< / div >
< div class = "split" >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-r" > < / span > Ist-Zustand: Feste Reihenfolge< / div >
< div class = "panel__body" >
< div class = "widget-order" >
< div class = "wo-item" style = "opacity:.5;cursor:default" >
< div class = "wo-dot" style = "background:#818CF8" > < / div >
< div class = "wo-label" > Dashboard< / div >
< div class = "wo-vis" > ⊙ sichtbar< / div >
< / div >
< div class = "wo-item" style = "opacity:.5;cursor:default" >
< div class = "wo-dot" style = "background:#15803D" > < / div >
< div class = "wo-label" > Aufgaben< / div >
< div class = "wo-vis" > ⊙ sichtbar< / div >
< / div >
< div class = "wo-item" style = "opacity:.5;cursor:default" >
< div class = "wo-dot" style = "background:#8250DF" > < / div >
< div class = "wo-label" > Kalender< / div >
< div class = "wo-vis" > ⊙ sichtbar< / div >
< / div >
< / div >
< p style = "font-size:11px;color:#FCA5A5;margin-top:8px" > Reihenfolge fest codiert — nicht änderbar< / p >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-g" > < / span > Soll-Zustand: Drag-to-reorder< / div >
< div class = "panel__body" >
< div class = "widget-order" >
< div class = "wo-item" >
< div class = "wo-handle" > ⠿< / div >
< div class = "wo-dot" style = "background:#818CF8" > < / div >
< div class = "wo-label" > Dashboard< / div >
< div class = "wo-vis" > ⊙< / div >
< / div >
< div class = "wo-item" style = "background:rgba(129,140,248,.08);border-color:rgba(129,140,248,.2)" >
< div class = "wo-handle" > ⠿< / div >
< div class = "wo-dot" style = "background:#8250DF" > < / div >
< div class = "wo-label" > Kalender< / div >
< div class = "wo-vis" > ⊙< / div >
< / div >
< div class = "wo-item" >
< div class = "wo-handle" > ⠿< / div >
< div class = "wo-dot" style = "background:#15803D" > < / div >
< div class = "wo-label" > Aufgaben< / div >
< div class = "wo-vis" > ⊙< / div >
< / div >
< / div >
< p style = "font-size:11px;color:#6EE7B7;margin-top:8px" > ⠿ Handle zum Sortieren — gespeichert in localStorage< / p >
< / div >
< / div >
< / div >
< div class = "code-panel" style = "margin-top:14px" >
< div class = "code-panel__head" > < span > dashboard.js — Config-Schema< / span > < / div >
< div class = "code-panel__body" >
< span class = "del" > - const DEFAULT_WIDGET_CONFIG = WIDGET_IDS.map((id) => ({ id, visible: true }));< / span >
< span class = "add" > + const DEFAULT_WIDGET_CONFIG = WIDGET_IDS.map((id, i) => ({ id, visible: true, order: i }));< / span >
< br >
< span class = "cmt" > // Beim Laden: nach order sortieren< / span >
< span class = "add" > + config.sort((a, b) => a.order - b.order);< / span >
< / div >
< / div >
< / div >
< hr >
<!-- FIX 6: Offline Indicator -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-h" > 6< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCD34D" > Hoch · 1 h< / div >
< div class = "fix__title" > Offline-Banner in App-Shell< / div >
< div class = "fix__why" > Der Service Worker ist vorhanden, aber die App gibt kein visuelles Feedback zum Offline-Zustand. Ein kleines Banner direkt unter der Navigation (wenn < code > navigator.onLine< / code > false ist) ist die robusteste Lösung — kein Flickering, immer sichtbar.< / div >
< / div >
< / div >
< div class = "panel" style = "margin-bottom:14px" >
< div class = "panel__head" > < span class = "dot dot-g" > < / span > Soll-Zustand: Shell-Level Banner< / div >
< div class = "panel__body" >
< div class = "offline-banner" >
< span class = "ob-icon" > 📡< / span >
< span class = "ob-text" > Offline — Änderungen werden gespeichert und beim nächsten Verbindungsaufbau synchronisiert.< / span >
< span class = "ob-pill" > Offline< / span >
< / div >
< p style = "font-size:11px;color:#8E8D89" > Erscheint unter Nav-Bar, verschwindet automatisch wenn online< / p >
< / div >
< / div >
< div class = "code-panel" >
< div class = "code-panel__head" > < span > router.js — App-Shell Setup< / span > < / div >
< div class = "code-panel__body" >
< span class = "add" > + function initOfflineBanner() {< / span >
< span class = "add" > + const banner = document.getElementById('offline-banner');< / span >
< span class = "add" > + const update = () => banner.hidden = navigator.onLine;< / span >
< span class = "add" > + window.addEventListener('online', update);< / span >
< span class = "add" > + window.addEventListener('offline', update);< / span >
< span class = "add" > + update();< / span >
< span class = "add" > + }< / span >
< / div >
< / div >
< / div >
< hr >
<!-- FIX 7: Keyboard Shortcuts -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-h" > 7< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCD34D" > Hoch · 2– 3 h< / div >
< div class = "fix__title" > Globale Keyboard Shortcuts (Desktop)< / div >
< div class = "fix__why" > 30 % der Nutzer verwenden Desktop. Ein zentrales Keyboard-Shortcut-System im Router beschleunigt häufige Aktionen erheblich. Alle Shortcuts per < code > ?< / code > einsehbar.< / div >
< / div >
< / div >
< div class = "kbd-grid" >
< div class = "kbd-row" > < kbd class = "kbd" > /< / kbd > < span class = "kbd-label" > Suche öffnen< / span > < / div >
< div class = "kbd-row" > < kbd class = "kbd" > N< / kbd > < span class = "kbd-label" > Neu (kontextabhängig)< / span > < / div >
< div class = "kbd-row" > < kbd class = "kbd" > G D< / kbd > < span class = "kbd-label" > → Dashboard< / span > < / div >
< div class = "kbd-row" > < kbd class = "kbd" > G T< / kbd > < span class = "kbd-label" > → Tasks< / span > < / div >
< div class = "kbd-row" > < kbd class = "kbd" > G C< / kbd > < span class = "kbd-label" > → Kalender< / span > < / div >
< div class = "kbd-row" > < kbd class = "kbd" > Esc< / kbd > < span class = "kbd-label" > Modal / Sheet schließen< / span > < / div >
< div class = "kbd-row" > < kbd class = "kbd" > ?< / kbd > < span class = "kbd-label" > Shortcut-Übersicht< / span > < / div >
< div class = "kbd-row" > < kbd class = "kbd" > ⌘K< / kbd > < span class = "kbd-label" > Command Palette< / span > < / div >
< / div >
< / div >
< hr >
<!-- FIX 8: Undo System -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-h" > 8< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCD34D" > Hoch · 3– 4 h< / div >
< div class = "fix__title" > Zentrales Undo-System für destruktive Aktionen< / div >
< div class = "fix__why" > Aktuell haben manche Aktionen Undo-Toasts, andere nicht. Eine zentrale < code > undoStack< / code > -Utility mit standardisiertem Toast-Muster schafft konsistente Sicherheit über alle Module hinweg.< / div >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > < span class = "dot dot-g" > < / span > Soll-Zustand: konsistente Undo-Abdeckung< / div >
< div class = "panel__body" >
< div class = "undo-arch" >
< div class = "undo-row" > < div class = "undo-action" > Aufgabe löschen< / div > < span class = "undo-badge ub-yes" > ✓ Undo vorhanden< / span > < / div >
< div class = "undo-row" > < div class = "undo-action" > Eintrag Einkaufsliste löschen< / div > < span class = "undo-badge ub-yes" > ✓ Undo vorhanden< / span > < / div >
< div class = "undo-row" > < div class = "undo-action" > Kontakt löschen< / div > < span class = "undo-badge ub-no" > ✗ fehlt< / span > < / div >
< div class = "undo-row" > < div class = "undo-action" > Notiz löschen< / div > < span class = "undo-badge ub-no" > ✗ fehlt< / span > < / div >
< div class = "undo-row" > < div class = "undo-action" > Geburtstag löschen< / div > < span class = "undo-badge ub-no" > ✗ fehlt< / span > < / div >
< div class = "undo-row" > < div class = "undo-action" > Mahlzeit löschen< / div > < span class = "undo-badge ub-no" > ✗ fehlt< / span > < / div >
< / div >
< p style = "font-size:11px;color:#8E8D89;margin-top:10px" > → Alle DELETE-Aktionen über eine zentrale < code > deleteWithUndo(url, label)< / code > Funktion routen< / p >
< / div >
< / div >
< / div >
< hr >
<!-- FIX 9: Onboarding -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-h" > 9< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#FCD34D" > Hoch · 3 h< / div >
< div class = "fix__title" > Onboarding: Modul-spezifische Erst-Nutzung< / div >
< div class = "fix__why" > Statt 3 generischen Screens beim ersten Start: leere Zustände auf jeder Seite mit einem konkreten Tipp für genau dieses Modul. Zusätzlich: ein permanentes „?" / Hilfe-Icon in der Toolbar für den Onboarding-Replay.< / div >
< / div >
< / div >
< div class = "ob-flow" >
< div class = "ob-card ob-card--highlight" >
< div class = "ob-ico" > 📋< / div >
< div class = "ob-title" > Aufgaben< / div >
< div class = "ob-desc" > Tippe + um deine erste Aufgabe zu erstellen. Wische links zum Löschen.< / div >
< div class = "ob-dots" > < div class = "ob-dot ob-dot--on" > < / div > < div class = "ob-dot" > < / div > < div class = "ob-dot" > < / div > < / div >
< / div >
< div class = "ob-card" >
< div class = "ob-ico" > 📅< / div >
< div class = "ob-title" > Kalender< / div >
< div class = "ob-desc" > Verbinde deinen Google Kalender unter Einstellungen → Kalender-Sync.< / div >
< div class = "ob-dots" > < div class = "ob-dot" > < / div > < div class = "ob-dot ob-dot--on" > < / div > < div class = "ob-dot" > < / div > < / div >
< / div >
< div class = "ob-card" >
< div class = "ob-ico" > 💰< / div >
< div class = "ob-title" > Budget< / div >
< div class = "ob-desc" > Lege Kategorien und ein Monatsbudget fest. Einnahmen und Ausgaben werden automatisch summiert.< / div >
< div class = "ob-dots" > < div class = "ob-dot" > < / div > < div class = "ob-dot" > < / div > < div class = "ob-dot ob-dot--on" > < / div > < / div >
< / div >
< div class = "ob-card" >
< div class = "ob-ico" > 🔔< / div >
< div class = "ob-title" > Erinnerungen< / div >
< div class = "ob-desc" > RRule ermöglicht Wiederholungen: täglich, wöchentlich, oder komplex wie „jeden 2. Montag".< / div >
< / div >
< div class = "ob-card" style = "opacity:.5" >
< div class = "ob-ico" > …< / div >
< div class = "ob-title" > +7 weitere< / div >
< div class = "ob-desc" > Jedes Modul erklärt sich beim ersten Besuch selbst.< / div >
< / div >
< / div >
< / div >
< hr >
<!-- ════════════════════════════════ MITTEL ════════════════════════════════ -->
< div class = "fix" >
< div class = "fix__header" >
< div class = "fix__num num-m" > 10– 13< / div >
< div class = "fix__meta" >
< div class = "fix__tag" style = "color:#6EE7B7" > Mittel · Backlog< / div >
< div class = "fix__title" > Weitere Optimierungen< / div >
< / div >
< / div >
< div class = "split" >
< div class = "panel" >
< div class = "panel__head" > 🟢 Toast Swipe-to-Dismiss< / div >
< div class = "panel__body" >
< p style = "font-size:12px;color:#8E8D89;line-height:1.6" >
< code > pointerdown< / code > + < code > pointermove< / code > auf < code > .toast< / code > → bei
> 40 px horizontaler Bewegung < code > toast--out< / code > Klasse setzen und entfernen.
Entspricht iOS/Android-Konvention. Ca. 30 Zeilen JS.
< / p >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > 🟢 Swipe-Reveal auf alle Listen< / div >
< div class = "panel__body" >
< p style = "font-size:12px;color:#8E8D89;line-height:1.6" >
Das bestehende < code > .swipe-row< / code > Pattern auf Kontakte, Notizen und Geburtstage
ausweiten. Die Basis-CSS existiert bereits in < code > layout.css< / code > —
nur modul-spezifische Reveal-Farben und JS-Handler ergänzen.
< / p >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > 🟢 Dashboard: weniger Farbrauschen< / div >
< div class = "panel__body" >
< p style = "font-size:12px;color:#8E8D89;line-height:1.6" >
Widget-Icons auf neutrale < code > --color-text-secondary< / code > setzen, Widget-Akzentfarbe
nur für Badge und Link reservieren. Reduziert visuelle Überforderung wenn alle
11 Widgets gleichzeitig sichtbar sind.
< / p >
< / div >
< / div >
< div class = "panel" >
< div class = "panel__head" > 🟢 reminders.css lazy laden< / div >
< div class = "panel__body" >
< p style = "font-size:12px;color:#8E8D89;line-height:1.6" >
< code > reminders.css< / code > dynamisch per < code > < link rel="stylesheet"> < / code >
nur in den Seiten laden, die Reminders anzeigen — analog zum bestehenden
Lazy-Loading-Pattern für Page-Module.
< / p >
< / div >
< / div >
< / div >
< / div >
< / body >
< / html >