memoscan
← all memos

Memo 0xac045f12…3a6adf on Ethereum

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Binary Partitions - Impulse & Interactive</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; cursor: crosshair; } html, body { width: 100%; height: 100%; background: #000; overflow: hidden; } canvas { display: block; width: 100%; height: 100%; } /* Simple instruction overlay */ #hint { position: absolute; bottom: 20px; left: 20px; color: #00ff00; font-family: monospace; font-size: 12px; opacity: 0.5; pointer-events: none; z-index: 10; } </style> </head> <body> <div id="hint">SYSTEM: SHAKE MOUSE TO DESTROY // RANDOM GLITCH: ACTIVE</div> <canvas id="artCanvas"></canvas> <script> // --- Partition Data (Original) --- const originalPartitions = [{"x":0,"y":0,"width":731.7712962962963,"height":177.64635903206445,"gradientConfig":{"type":"text","colors":["#ff00ea"],"angle":135.5864197530864,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#47c8ff"],"angle":180,"rotationDirection":-1,"blendMode":"exclusion"}},{"x":731.7712962962963,"y":0,"width":348.2287037037037,"height":310.9861111111111,"gradientConfig":{"type":"text","colors":["#000000","#4dff00"],"angle":26.933641975308642,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#70ade6"],"angle":270,"rotationDirection":1,"blendMode":"exclusion"}},{"x":0,"y":393.83503017427313,"width":129.8141199798049,"height":401.7399698257268,"gradientConfig":{"type":"text","colors":["#000000","#4dff00"],"angle":0.7808641975308641,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#a4afda"],"angle":0,"rotationDirection":1,"blendMode":"exclusion"}},{"x":731.7712962962963,"y":883.2546796767833,"width":348.2287037037037,"height":196.7453203232167,"gradientConfig":{"type":"text","colors":["#ffffff"],"angle":31.978395061728392,"rotationDirection":-1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#ffffff"],"angle":90,"rotationDirection":1,"blendMode":"exclusion"}},{"x":129.8141199798049,"y":393.83503017427313,"width":218.40661945429682,"height":401.7399698257268,"gradientConfig":{"type":"text","colors":["#ff00ea"],"angle":70.58333333333333,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#ff0000"],"angle":90,"rotationDirection":-1,"blendMode":"exclusion"}},{"x":0,"y":795.5749999999999,"width":177.79144023157676,"height":284.425,"gradientConfig":{"type":"text","colors":["#4dff00"],"angle":214.37345679012344,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#989898"],"angle":180,"rotationDirection":-1,"blendMode":"exclusion"}},{"x":731.7712962962963,"y":310.9861111111111,"width":348.2287037037037,"height":195.52509425993802,"gradientConfig":{"type":"text","colors":["#ff0a0a","#ff00ea","#0084ff"],"angle":39.111111111111114,"rotationDirection":-1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#c80e0e"],"angle":90,"rotationDirection":1,"blendMode":"exclusion"}},{"x":0,"y":177.64635903206445,"width":110.51289918886222,"height":216.18867114220868,"gradientConfig":{"type":"text","colors":["#ffffff","#ffffff","#949494"],"angle":125.70061728395062,"rotationDirection":-1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#70ade6"],"angle":180,"rotationDirection":1,"blendMode":"exclusion"}},{"x":177.79144023157676,"y":795.5749999999999,"width":362.50059439518924,"height":284.425,"gradientConfig":{"type":"text","colors":["#ffffff"],"angle":337.63888888888886,"rotationDirection":-1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#000000"],"angle":90,"rotationDirection":1,"blendMode":"exclusion"}},{"x":540.292034626766,"y":795.5749999999999,"width":191.47926166953033,"height":284.425,"gradientConfig":{"type":"text","colors":["#989898"],"angle":134.76234567901236,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#000000"],"angle":270,"rotationDirection":1,"blendMode":"exclusion"}},{"x":348.2207394341017,"y":393.83503017427313,"width":90.13734035765324,"height":401.7399698257268,"gradientConfig":{"type":"text","colors":["#ff00ea","#949494","#989898"],"angle":246.16666666666669,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#a4afda"],"angle":0,"rotationDirection":1,"blendMode":"exclusion"}},{"x":438.3580797917549,"y":393.83503017427313,"width":293.4132165045413,"height":401.7399698257268,"gradientConfig":{"type":"text","colors":["#989898","#1c1c1c"],"angle":144.18055555555554,"rotationDirection":-1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#44ff00"],"angle":0,"rotationDirection":1,"blendMode":"exclusion"}},{"x":110.51289918886222,"y":177.64635903206445,"width":105.49302073859354,"height":216.18867114220868,"gradientConfig":{"type":"text","colors":["#1c1c1c","#989898","#ff0a0a"],"angle":299.1033950617284,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#70ade6"],"angle":0,"rotationDirection":1,"blendMode":"exclusion"}},{"x":216.00591992745575,"y":177.64635903206445,"width":515.7653763688405,"height":216.18867114220868,"gradientConfig":{"type":"text","colors":["#4dff00"],"angle":277.70833333333337,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#70ade6"],"angle":270,"rotationDirection":-1,"blendMode":"exclusion"}},{"x":731.7712962962963,"y":506.5112053710491,"width":230.64538880633032,"height":376.7434743057342,"gradientConfig":{"type":"text","colors":["#4dff00","#ffffff","#0084ff"],"angle":135.9645061728395,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#ff0000"],"angle":180,"rotationDirection":-1,"blendMode":"exclusion"}},{"x":962.4166851026266,"y":506.5112053710491,"width":117.58331489737336,"height":376.7434743057342,"gradientConfig":{"type":"text","colors":["#0084ff","#ffffff","#4dff00"],"angle":238.29475308641975,"rotationDirection":1,"textContent":"GURU"},"secondLayer":{"enabled":true,"type":"linear","colors":["#a4afda"],"angle":0,"rotationDirection":1,"blendMode":"exclusion"}}]; const config = {"width":1080,"height":1080,"animationSpeed":47,"loopDuration":10,"shrinkPercentage":100}; const canvas = document.getElementById('artCanvas'); const ctx = canvas.getContext('2d'); let rotation = 0; let lastTime = 0; let offscreenCache = null; let scaleX = 1; let scaleY = 1; // --- Glitch State Variables --- let currentChaos = 0; // Current intensity of glitch (0.0 to 1.0) let nextBurstTime = 0; // Timestamp for next random glitch let mouseVelocity = 0; // How fast mouse is moving let lastMousePos = { x: 0, y: 0 }; let isGlitching = false; // Is a burst currently active? function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; scaleX = window.innerWidth / config.width; scaleY = window.innerHeight / config.height; offscreenCache = null; } function scalePartition(p) { return { x: p.x * scaleX, y: p.y * scaleY, width: p.width * scaleX, height: p.height * scaleY, gradientConfig: p.gradientConfig, secondLayer: p.secondLayer }; } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // --- Interaction: Mouse Velocity & Click --- window.addEventListener('mousemove', (e) => { const dx = e.clientX - lastMousePos.x; const dy = e.clientY - lastMousePos.y; const dist = Math.sqrt(dx*dx + dy*dy); // Add speed to velocity (sensitivity adjusted by factor) mouseVelocity += dist * 0.005; // Cap max velocity if (mouseVelocity > 2.0) mouseVelocity = 2.0; lastMousePos = { x: e.clientX, y: e.clientY }; }); window.addEventListener('mousedown', () => { // Click triggers an immediate massive burst mouseVelocity = 3.0; currentChaos = 1.0; }); // --- Helper Functions --- function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [0, 0, 0]; } function rgbToHex(r, g, b) { return '#' + [r, g, b].map(x => { const hex = Math.round(x).toString(16); return hex.length === 1 ? '0' + hex : hex; }).join(''); } function interpolateColor(color1, color2, t) { const rgb1 = hexToRgb(color1); const rgb2 = hexToRgb(color2); return rgbToHex( rgb1[0] + (rgb2[0] - rgb1[0]) * t, rgb1[1] + (rgb2[1] - rgb1[1]) * t, rgb1[2] + (rgb2[2] - rgb1[2]) * t ); } // --- Gradient & Pattern Drawing --- function createLinearGradientPattern(ctx, colors, partitionLength) { const offscreenCanvas = document.createElement('canvas'); const totalSegments = colors.length * 2; const tileWidth = Math.ceil(partitionLength); offscreenCanvas.width = tileWidth; offscreenCanvas.height = 100; const offscreenCtx = offscreenCanvas.getContext('2d'); if (!offscreenCtx) return null; const gradient = offscreenCtx.createLinearGradient(0, 0, tileWidth, 0); const segmentSize = 1 / totalSegments; for (let i = 0; i < colors.length; i++) { gradient.addColorStop(i * 2 * segmentSize, '#000000'); gradient.addColorStop((i * 2 + 1) * segmentSize, colors[i]); } gradient.addColorStop(1, '#000000'); offscreenCtx.fillStyle = gradient; offscreenCtx.fillRect(0, 0, tileWidth, 100); const pattern = ctx.createPattern(offscreenCanvas, 'repeat'); if (!pattern) return null; return { pattern, tileWidth }; } function createGradientForLayer(ctx, partition, layerConfig, rotation) { const { type, colors, angle } = layerConfig; const centerX = partition.x + partition.width / 2; const centerY = partition.y + partition.height / 2; let gradient; if (type === 'linear') { const angleRad = (angle * Math.PI) / 180; const partitionLength = Math.abs(Math.cos(angleRad)) * partition.width + Math.abs(Math.sin(angleRad)) * partition.height; const halfLength = partitionLength / 2; const startX = centerX - halfLength * Math.cos(angleRad); const startY = centerY - halfLength * Math.sin(angleRad); const endX = centerX + halfLength * Math.cos(angleRad); const endY = centerY + halfLength * Math.sin(angleRad); gradient = ctx.createLinearGradient(startX, startY, endX, endY); const totalSegments = colors.length * 2; const rotationFraction = ((rotation % 360) / 360); const allStops = []; for (let i = 0; i < colors.length; i++) { const blackPos = ((i * 2 / totalSegments - rotationFraction + 1) % 1); const colorPos = (((i * 2 + 1) / totalSegments - rotationFraction + 1) % 1); allStops.push({ position: blackPos, color: '#000000' }); allStops.push({ position: colorPos, color: colors[i] }); } allStops.sort((a, b) => a.position - b.position); const firstStop = allStops[0]; const lastStop = allStops[allStops.length - 1]; const totalGap = firstStop.position + (1 - lastStop.position); if (firstStop.position > 0) { const t = (1 - lastStop.position) / totalGap; gradient.addColorStop(0, interpolateColor(lastStop.color, firstStop.color, t)); } allStops.forEach(stop => gradient.addColorStop(stop.position, stop.color)); if (lastStop.position < 1) { const t = (1 - lastStop.position) / totalGap; gradient.addColorStop(1, interpolateColor(lastStop.color, firstStop.color, t)); } return gradient; } else if (type === 'conic') { const normalizedRotation = rotation % 360; const rotationDirection = layerConfig.rotationDirection || 1; gradient = ctx.createConicGradient(((angle + normalizedRotation * rotationDirection) * Math.PI) / 180, centerX, centerY); } else { const maxSize = Math.max(partition.width, partition.height); gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxSize); } const segmentSize = 1 / (colors.length * 2); colors.forEach((color, i) => { gradient.addColorStop(i * segmentSize * 2, color); gradient.addColorStop(i * segmentSize * 2 + segmentSize, '#000000'); }); gradient.addColorStop(1, colors[0]); return gradient; } function calculateTextHeight(ctx, text, targetHeight) { let minSize = 1; let maxSize = targetHeight * 2; let fontSize = targetHeight; for (let i = 0; i < 20; i++) { ctx.font = `bold ${fontSize}px Helvetica`; const metrics = ctx.measureText(text); const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; if (Math.abs(actualHeight - targetHeight) < 1) break; if (actualHeight < targetHeight) { minSize = fontSize; fontSize = (fontSize + maxSize) / 2; } else { maxSize = fontSize; fontSize = (minSize + fontSize) / 2; } } return fontSize; } function getOffscreenCanvas(width, height) { if (!offscreenCache) { offscreenCache = document.createElement('canvas'); } if (offscreenCache.width < width) offscreenCache.width = width; if (offscreenCache.height < height) offscreenCache.height = height; const octx = offscreenCache.getContext('2d'); if (octx) octx.clearRect(0, 0, width, height); return offscreenCache; } function drawGradientLayer(ctx, partition, layerConfig, rotation, partitionIndex, blendMode) { ctx.globalCompositeOperation = blendMode; const centerX = partition.x + partition.width / 2; const centerY = partition.y + partition.height / 2; const { type, colors, angle } = layerConfig; if (type === 'linear') { const angleRad = (angle * Math.PI) / 180; const partitionLength = Math.abs(Math.cos(angleRad)) * partition.width + Math.abs(Math.sin(angleRad)) * partition.height; const result = createLinearGradientPattern(ctx, colors, partitionLength); if (result) { const { pattern, tileWidth } = result; const maxSize = Math.max(partition.width, partition.height) * 2; if (pattern.setTransform) { const offsetDistance = ((rotation / 360) * tileWidth) % tileWidth; const offsetX = offsetDistance * Math.cos(angleRad); const offsetY = offsetDistance * Math.sin(angleRad); const matrix = new DOMMatrix(); matrix.translateSelf(offsetX, offsetY); matrix.rotateSelf(angle); pattern.setTransform(matrix); ctx.fillStyle = pattern; ctx.fillRect(centerX - maxSize, centerY - maxSize, maxSize * 2, maxSize * 2); } else { const gradient = createGradientForLayer(ctx, partition, layerConfig, rotation); ctx.fillStyle = gradient; ctx.fillRect(partition.x, partition.y, partition.width, partition.height); } } } else if (type === 'radial') { const maxSize = Math.max(partition.width, partition.height); const selectedColor = colors[partitionIndex % colors.length]; const currentCycle = Math.floor(rotation / 360); const maxIterations = 4; const startCycle = Math.max(0, currentCycle - maxIterations + 1); const offscreen = getOffscreenCanvas(partition.width, partition.height); const offscreenCtx = offscreen.getContext('2d'); if (!offscreenCtx) return; offscreenCtx.globalCompositeOperation = 'lighten'; const offscreenCenterX = partition.width / 2; const offscreenCenterY = partition.height / 2; for (let cycle = startCycle; cycle <= currentCycle; cycle++) { const cycleAge = rotation / 360 - cycle; const iterationSize = maxSize * cycleAge; if (iterationSize > 0 && iterationSize <= maxSize * maxIterations) { const gradient = offscreenCtx.createRadialGradient(offscreenCenterX, offscreenCenterY, 0, offscreenCenterX, offscreenCenterY, iterationSize); gradient.addColorStop(0, '#000000'); gradient.addColorStop(0.5, selectedColor); gradient.addColorStop(1, '#000000'); offscreenCtx.fillStyle = gradient; offscreenCtx.fillRect(offscreenCenterX - iterationSize, offscreenCenterY - iterationSize, iterationSize * 2, iterationSize * 2); } } ctx.drawImage(offscreen, partition.x, partition.y); } else if (type === 'text') { const textContent = layerConfig.textContent || '$$$'; const xCoord = Math.round(partition.x * 10000); const yCoord = Math.round(partition.y * 10000); const hash = Math.abs((xCoord * 73856093) ^ (yCoord * 19349663)); const selectedColor = colors[hash % colors.length]; const hash2 = Math.abs((xCoord * 19349663) ^ (yCoord * 73856093)); const randomStartOffset = (hash2 % 10000) / 10000; const hash3 = Math.abs((xCoord * 50331653) ^ (yCoord * 25165843)); const direction = hash3 % 3; ctx.save(); const partCenterX = partition.x + partition.width / 2; const partCenterY = partition.y + partition.height / 2; if (direction === 1) { ctx.translate(partCenterX, partCenterY); ctx.rotate(-Math.PI / 2); ctx.translate(-partCenterX, -partCenterY); } else if (direction === 2) { ctx.translate(partCenterX, partCenterY); ctx.rotate(Math.PI / 2); ctx.translate(-partCenterX, -partCenterY); } const fillDimension = direction === 0 ? partition.height : partition.width; const targetHeight = fillDimension * 0.95; const fontSize = calculateTextHeight(ctx, textContent, targetHeight); ctx.font = `bold ${fontSize}px Helvetica`; ctx.fillStyle = selectedColor; ctx.textBaseline = 'middle'; const wordWidth = ctx.measureText(textContent).width; const normalizedRotation = rotation % 360; const scrollProgress = (normalizedRotation / 360 + randomStartOffset) % 1; const scrollOffset = -(scrollProgress * wordWidth) % wordWidth; const scrollDimensionSize = direction === 0 ? partition.width : partition.height; const textY = partition.y + partition.height * 0.55; const textX = partition.x + scrollOffset; const numCopies = Math.ceil(scrollDimensionSize / wordWidth) + 3; for (let i = -1; i < numCopies; i++) { ctx.fillText(textContent, textX + (i * wordWidth), textY); } ctx.restore(); } else { ctx.save(); const normalizedRotation = rotation % 360; const rotationDirection = layerConfig.rotationDirection || 1; ctx.translate(centerX, centerY); ctx.rotate((normalizedRotation * rotationDirection * Math.PI) / 180); ctx.translate(-centerX, -centerY); const gradient = createGradientForLayer(ctx, partition, layerConfig, rotation); ctx.fillStyle = gradient; const maxSize = Math.max(partition.width, partition.height) * 2; ctx.fillRect(centerX - maxSize, centerY - maxSize, maxSize * 2, maxSize * 2); ctx.restore(); } } function renderPartitions(rotation) { // 1. Trails / Feedback (always slightly visible) // When chaos is high, trails are stronger (less erasure) const baseAlpha = 0.2; // If chaos is high, we clear less, creating smear const clearAlpha = baseAlpha - (currentChaos * 0.15); ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle = `rgba(0, 0, 0, ${Math.max(0.02, clearAlpha)})`; ctx.fillRect(0, 0, canvas.width, canvas.height); const scale = config.shrinkPercentage / 100; // Render partitions originalPartitions.forEach((originalPartition, partitionIndex) => { // --- Jitter partitions during High Chaos --- let jitterX = 0; let jitterY = 0; if (currentChaos > 0.3) { jitterX = (Math.random() - 0.5) * 50 * currentChaos; jitterY = (Math.random() - 0.5) * 50 * currentChaos; } const partition = scalePartition(originalPartition); ctx.save(); const centerX = partition.x + partition.width / 2; const centerY = partition.y + partition.height / 2; ctx.translate(centerX + jitterX, centerY + jitterY); ctx.scale(scale, scale); ctx.translate(-centerX, -centerY); ctx.beginPath(); ctx.rect(partition.x, partition.y, partition.width, partition.height); ctx.clip(); const primaryLayerConfig = { type: partition.gradientConfig.type, colors: partition.gradientConfig.colors, angle: partition.gradientConfig.angle, rotationDirection: partition.gradientConfig.rotationDirection, textContent: partition.gradientConfig.textContent, }; drawGradientLayer(ctx, partition, primaryLayerConfig, rotation, partitionIndex, 'source-over'); if (partition.secondLayer.enabled) { const secondLayerConfig = { type: partition.secondLayer.type, colors: partition.secondLayer.colors, angle: partition.secondLayer.angle, rotationDirection: partition.secondLayer.rotationDirection, textContent: partition.secondLayer.textContent, }; ctx.save(); drawGradientLayer(ctx, partition, secondLayerConfig, rotation, partitionIndex, partition.secondLayer.blendMode); ctx.restore(); } ctx.restore(); }); // --- 2. THE GLITCH / DATAMOSH LAYER --- // If chaos is active (either from Burst or Mouse) if (currentChaos > 0.05) { // A. Horizontal Tearing (The "Datamosh" look) // Number of tears depends on intensity const numTears = Math.floor(currentChaos * 20); for(let i = 0; i < numTears; i++) { if (Math.random() < 0.8) { const sliceY = Math.random() * canvas.height; const sliceH = Math.random() * 50 + 5; // Extreme horizontal stretching const stretch = (Math.random() - 0.5) * 400 * currentChaos; ctx.save(); ctx.globalCompositeOperation = 'source-over'; ctx.drawImage( canvas, 0, sliceY, canvas.width, sliceH, stretch, sliceY, canvas.width + Math.abs(stretch), sliceH ); // Invert color occasionally (Digital artifact) if (Math.random() < 0.2 * currentChaos) { ctx.globalCompositeOperation = 'difference'; ctx.fillStyle = '#fff'; ctx.fillRect(stretch, sliceY, canvas.width, sliceH); } ctx.restore(); } } // B. RGB Shift (Chromatic Aberration) // Scales with Chaos ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.globalAlpha = 0.5 * currentChaos; const shiftX = (Math.random() - 0.5) * 30 * currentChaos; const shiftY = (Math.random() - 0.5) * 10 * currentChaos; // Draw Red Channel offset ctx.translate(shiftX, shiftY); // Just drawing the canvas over itself with screen blend creates the ghosting // If we had separate channels we'd color it red, but this is a cheap fast approximation ctx.drawImage(canvas, 0, 0); ctx.restore(); } } function animate(timestamp) { if (!lastTime) lastTime = timestamp; const deltaTime = timestamp - lastTime; lastTime = timestamp; const currentTime = timestamp / 1000; // --- LOGIC: Update Chaos Level --- // 1. Decay the mouse velocity mouseVelocity *= 0.92; // Friction if (mouseVelocity < 0.01) mouseVelocity = 0; // 2. Random Bursts (The "Every few seconds" logic) if (currentTime > nextBurstTime) { // Trigger a burst isGlitching = true; // Duration of burst is short (0.1s to 0.4s) const burstDuration = Math.random() * 0.3 + 0.1; // Schedule end of burst setTimeout(() => { isGlitching = false; }, burstDuration * 1000); // Schedule next burst (Randomly between 2 and 6 seconds) nextBurstTime = currentTime + (Math.random() * 4 + 2); } // 3. Combine Sources into currentChaos let targetChaos = 0; if (isGlitching) targetChaos = 1.0; // Max chaos during random burst targetChaos += mouseVelocity; // Add mouse movement chaos // Smoothly interpolate currentChaos towards target (Easing) // This makes the onset fast but the falloff smooth currentChaos += (targetChaos - currentChaos) * 0.2; // Clamp if (currentChaos > 1.0) currentChaos = 1.0; if (currentChaos < 0.0) currentChaos = 0.0; // --- Rotation Logic --- const actualLoopDuration = config.loopDuration / (config.animationSpeed / 50); const degreesPerSecond = 360 / actualLoopDuration; // Chaos can also speed up or reverse rotation momentarily! const chaosSpeedMod = 1 + (Math.random() - 0.5) * 10 * currentChaos; rotation += (deltaTime / 1000) * degreesPerSecond * chaosSpeedMod; renderPartitions(rotation); requestAnimationFrame(animate); } requestAnimationFrame(animate); </script> </body> </html>