BUG-01: Resize-Handle griff nicht
Items hatten `draggable=true` (HTML5-DnD). Der Resize-Handle war Kind des
Items, also fired der Browser `dragstart` am Parent, sobald die Maus
überhaupt auf irgendeinem Kind war. `e.preventDefault()` im
`startResize()` mousedown-Handler kam zu spät — HTML5-DnD hatte den
Move-Drag bereits übernommen.
BUG-02: Resize-Drag war zäh
Pro mousemove rief der alte Code `renderGrid()` auf, das die komplette
Cell- und Item-Struktur neu aufbaute. Bei 60 events/s × 100+ DOM-Ops
pro Render entstand sichtbarer Jank.
Fix:
- HTML5-draggable komplett raus.
- Eigene Pointer-Events (`pointerdown`/`pointermove`/`pointerup`)
für Move (startItemPointer) und Resize (startResizePointer).
- Resize-Handle: `pointer-events:auto` auf Handle,
`pointer-events:none` auf `::before` (Dreieck-Deko), eigene
Listener, `stopPropagation()` damit Item-Handler nicht mitfeuert.
- `touch-action:none` auf Handle und Item-Body → Touch-Devices
scrollen nicht versehentlich beim Drag.
- Beide Aktionen nutzen requestAnimationFrame, und nur die
CSS-Geometrie (grid-column/grid-row) wird aktualisiert — kein
renderGrid() während Drag. Erst beim Drop / Resize-End kommt
der volle re-render + save.
- Resize nutzt `setPointerCapture` damit der Move auch dann
weiterläuft, wenn die Maus den Handle kurz verlässt.
Beweis: tests/test_pointer_events.js (Node-basiert, ohne Browser)
1. Resize stopPropagation wird aufgerufen
2. Item-Body pointerdown erreicht nur Item-Handler (nicht Resize)
3. CSS pointer-events korrekt gesetzt
4. renderGrid() in keiner onMove-Funktion (0/2)
5. requestAnimationFrame wird genutzt (3 calls)
6. draggable=true komplett entfernt (0 assignments)
7. touch-action:none auf Resize-Handle
ALLE 7 grün.
Manuelle Verifikation gegen laufenden Server (?demo=1):
GET /?demo=1 → 200, 91685 bytes
grep draggable=true: 0
grep startItemPointer|startResizePointer: 6
grep pointerdown: 3
grep requestAnimationFrame: 3
grep HTML5-drag handlers: 0 (nur 1 in Kommentar)
grep renderGrid(): 12, davon 0 in onMove-Handlern
Closes#3#4
Admin UI: Dark Minimal / Retro Terminal / Warm Editorial via header buttons
Display: Classic White / Dark / Sepia / Nord / Terminal via Settings tab
Both switchers persist via localStorage / config.json