document.addEventListener('DOMContentLoaded', function(){ const stage = document.getElementById('stage'); const touches = new Map(); // id -> {el,x,y,colorIdx} const MAX_TOUCHES = /iPhone/.test(navigator.userAgent) ? 5 : 20; const COLOR_CLASSES = ['c1','c2','c3','c4','c5','c6','c7','c8']; const usedColors = new Set(); function pickColor(){ for(let i=0; i({id,el:entry.el,x:entry.x,y:entry.y})); const cx = arr.reduce((s,a)=>s+a.x,0)/arr.length; const cy = arr.reduce((s,a)=>s+a.y,0)/arr.length; arr.forEach(a=>{ const ang = Math.atan2(a.y-cy,a.x-cx) * 180/Math.PI; // -180..180 a.angle = (ang+360)%360; // 0..360 a.dist = Math.hypot(a.x-cx,a.y-cy); }); arr.sort((a,b)=>a.angle-b.angle); return {items:arr, center:{x:cx,y:cy}}; } function startSelectionIfNeeded(){ if(selecting || selectionDone) return; if(touches.size < 1) return; selecting = true; const epoch = selectionEpoch; const {items} = computeOrderedItems(); const els = items.map(i=>i.el); const n = els.length; if(n === 1){ if(epoch !== selectionEpoch) return; const el = els[0]; el.classList.add('winner','pulse'); if(typeof umami !== 'undefined') umami.track('winner-selected', { fingers: 1 }); selecting = false; selectionDone = true; return; } const rotations = Math.floor(Math.random()*3)+3; const winnerIndex = Math.floor(Math.random()*n); const totalSteps = rotations * n + winnerIndex; let cumulative = 0; let current = 0; for(let step=0; step<=totalSteps; step++){ const t = step/totalSteps; const delay = 40 + Math.pow(t,2)*520; cumulative += delay; const idx = current % n; setTimeout(()=>{ if(epoch !== selectionEpoch) return; els.forEach((el,i)=>{ if(i===idx) el.classList.add('highlight'); else el.classList.remove('highlight'); }); }, cumulative); current++; } setTimeout(()=>{ if(epoch !== selectionEpoch) return; els.forEach((el,i)=>{ if(i===winnerIndex){ el.classList.remove('highlight'); el.classList.add('winner','pulse'); } else { el.classList.remove('highlight'); el.classList.add('lost'); } }); if(typeof umami !== 'undefined') umami.track('winner-selected', { fingers: n }); selecting = false; selectionDone = true; }, cumulative + 80); } function cancelSelection(){ selectionEpoch++; clearTimeout(selectionTimer); selectionTimer = null; selecting = false; selectionDone = false; for(const entry of touches.values()){ entry.el.classList.remove('highlight','lost','winner','pulse'); entry.el.style.opacity = '1'; } } function resetWhenAllUp(){ if(touches.size===0){ setTimeout(()=>{ if(touches.size > 0) return; selecting=false; selectionDone=false; const nodes = Array.from(stage.querySelectorAll('.finger')); nodes.forEach(node=>node.remove()); touches.clear(); usedColors.clear(); },160); } } // Touch handlers stage.addEventListener('touchstart', function(e){ e.preventDefault(); const changed = Array.from(e.changedTouches || []); const prevSize = touches.size; changed.forEach(t=>{ if(touches.size < MAX_TOUCHES) createFinger(t); }); if(touches.size > prevSize){ cancelSelection(); selectionTimer = setTimeout(startSelectionIfNeeded, 700); } }, {passive:false}); stage.addEventListener('touchmove', function(e){ e.preventDefault(); const changed = Array.from(e.changedTouches || []); changed.forEach(t=>updateFinger(t)); }, {passive:false}); stage.addEventListener('touchend', function(e){ e.preventDefault(); const changed = Array.from(e.changedTouches || []); changed.forEach(t=>removeFingerById(t.identifier)); if(!selectionDone) cancelSelection(); if(touches.size===0) resetWhenAllUp(); }, {passive:false}); stage.addEventListener('touchcancel', function(e){ e.preventDefault(); const changed = Array.from(e.changedTouches || []); changed.forEach(t=>removeFingerById(t.identifier)); cancelSelection(); if(touches.size > 0){ selectionTimer = setTimeout(startSelectionIfNeeded, 700); } // touches.size===0: cancelSelection hat State bereits bereinigt, // DOM-Elemente durch removeFingerById entfernt. // iOS feuert neue touchstart-Events für noch liegende Finger selbst. }, {passive:false}); // Support mouse for desktop testing let mouseDown = false; const mouseId = 'mouse'; stage.addEventListener('mousedown', function(e){ mouseDown = true; createFinger({identifier:mouseId, clientX:e.clientX, clientY:e.clientY}); cancelSelection(); startSelectionIfNeeded(); }); window.addEventListener('mousemove', function(e){ if(!mouseDown) return; updateFinger({identifier:mouseId, clientX:e.clientX, clientY:e.clientY}); }); window.addEventListener('mouseup', function(e){ if(!mouseDown) return; mouseDown=false; removeFingerById(mouseId); if(!selectionDone) cancelSelection(); if(touches.size===0) resetWhenAllUp(); }); });