0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2zm12 4c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3z"/>', kdp: '', brain: '', sync: '', pin: '', question: '' }; const nodeMap = [ { id: 'web', url: 'index.html' }, { id: 'heart', url: 'mycelium-ethos.html' }, { id: 'people', url: 'mycelium-everyone.html' }, { id: 'thread', url: 'mycelium-rosetta.html' }, { id: 'books', url: 'mycelium-info.html' }, { id: 'microscope', url: 'mycelium-research.html' }, { id: 'handshake', url: 'mycelium-handshake.html' }, { id: 'ledger', url: 'mycelium-ledger.html' }, { id: 'kdp', url: 'mycelium-kdp.html' }, { id: 'brain', url: 'mycelium-brain-mind.html' }, { id: 'sync', url: 'mycelium-others.html' }, { id: 'pin', url: 'mycelium-contacts.html' }, { id: 'question', url: 'mycelium-faqs.html' } ]; let posX = 0, posY = 0, velX = 0, velY = 0, isDragging = false, lastX, lastY; function init() { const grid = document.getElementById('grid'); const total = nodeMap.length; // 13 for (let c = 0; c < 11; c++) { const col = document.createElement('div'); col.className = `column ${c % 2 === 0 ? 'col-down' : 'col-up'}`; col.style.setProperty('--v-speed', `${65 + (c * 2)}s`); for (let r = 0; r < 26; r++) { const rowStrip = document.createElement('div'); rowStrip.className = `row-strip ${r % 2 === 0 ? 'row-rtl' : 'row-ltr'}`; rowStrip.style.setProperty('--h-speed', `${65 + (r * 2)}s`); // STAGGERED ROW LOGIC (n+1 per row) const rowOffset = (r + c) % total; for (let j = 0; j < 2; j++) { for (let k = 0; k < total; k++) { const iconIndex = (k + rowOffset) % total; const node = nodeMap[iconIndex]; const anchor = document.createElement('a'); anchor.href = node.url; anchor.className = `node-item node-${node.id}`; anchor.style.setProperty('--d-wave-speed', `${4 + Math.random() * 4}s`); anchor.innerHTML = sensaIcons[node.id]; rowStrip.appendChild(anchor); } } col.appendChild(rowStrip); } grid.appendChild(col); } } function createRipple(x, y) { const ripple = document.createElement('div'); ripple.className = 'ripple'; ripple.style.left = x + 'px'; ripple.style.top = y + 'px'; document.body.appendChild(ripple); setTimeout(() => ripple.remove(), 600); } const onStart = (e) => { isDragging = true; const ev = e.touches ? e.touches[0] : e; lastX = ev.clientX; lastY = ev.clientY; createRipple(ev.clientX, ev.clientY); }; const onMove = (e) => { if (!isDragging) return; const ev = e.touches ? e.touches[0] : e; const dx = ev.clientX - lastX; const dy = ev.clientY - lastY; posX += dx; posY += dy; velX = dx; velY = dy; lastX = ev.clientX; lastY = ev.clientY; if(e.cancelable) e.preventDefault(); }; function loop() { if (!isDragging) { posX += velX; posY += velY; velX *= 0.95; velY *= 0.95; } // Correct wrap for 13 nodes * 85px = 1105px const wrap = 1105; if (posX < -wrap) posX += wrap; if (posX > 0) posX -= wrap; if (posY < -wrap) posY += wrap; if (posY > 0) posY -= wrap; document.getElementById('world').style.transform = `translate3d(${posX}px, ${posY}px, 0)`; requestAnimationFrame(loop); } window.addEventListener('mousedown', onStart); window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', () => isDragging = false); window.addEventListener('touchstart', onStart, {passive: false}); window.addEventListener('touchmove', onMove, {passive: false}); window.addEventListener('touchend', () => isDragging = false); init(); loop();