Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>MATRIX MUSIC HACKER v2.1</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/strudel/1.0.0/strudel.umd.js"></script> | |
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Courier+Prime:wght@400;700&display=swap" rel="stylesheet"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Courier+Prime:wght@400;700&display=swap'); | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: 'Orbitron', monospace; | |
background: #000; | |
color: #00ff00; | |
min-height: 100vh; | |
overflow-x: hidden; | |
position: relative; | |
} | |
/* Matrix rain background */ | |
.matrix-bg { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
z-index: 1; | |
opacity: 0.1; | |
} | |
.matrix-bg canvas { | |
width: 100%; | |
height: 100%; | |
} | |
.container { | |
position: relative; | |
z-index: 10; | |
max-width: 1400px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
.header { | |
text-align: center; | |
margin-bottom: 40px; | |
padding: 20px; | |
border: 2px solid #00ff00; | |
background: rgba(0, 255, 0, 0.03); | |
border-radius: 0; | |
position: relative; | |
overflow: hidden; | |
} | |
.header::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: -100%; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(90deg, transparent, rgba(0, 255, 0, 0.1), transparent); | |
animation: scan 3s infinite; | |
} | |
.header h1 { | |
font-size: 2.8rem; | |
font-weight: 900; | |
margin-bottom: 10px; | |
text-shadow: 0 0 20px #00ff00, 0 0 40px #00ff00; | |
letter-spacing: 3px; | |
animation: flicker 2s infinite alternate; | |
} | |
.header .subtitle { | |
font-size: 1.2rem; | |
color: #00cccc; | |
font-family: 'Courier Prime', monospace; | |
text-transform: uppercase; | |
letter-spacing: 2px; | |
} | |
.system-info { | |
display: flex; | |
justify-content: space-between; | |
margin-top: 15px; | |
font-size: 0.9rem; | |
color: #00ff00; | |
font-family: 'Courier Prime', monospace; | |
} | |
.controls { | |
display: grid; | |
grid-template-columns: 1fr 1fr; | |
gap: 30px; | |
margin-bottom: 30px; | |
} | |
.terminal { | |
background: rgba(0, 0, 0, 0.8); | |
border: 2px solid #00ff00; | |
border-radius: 0; | |
padding: 20px; | |
position: relative; | |
box-shadow: | |
0 0 20px rgba(0, 255, 0, 0.3), | |
inset 0 0 20px rgba(0, 255, 0, 0.1); | |
} | |
.terminal::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
height: 2px; | |
background: linear-gradient(90deg, transparent, #00ff00, transparent); | |
animation: sweep 2s infinite; | |
} | |
.terminal-header { | |
display: flex; | |
justify-content: between; | |
align-items: center; | |
margin-bottom: 20px; | |
padding-bottom: 10px; | |
border-bottom: 1px solid #00ff00; | |
} | |
.terminal-title { | |
font-size: 1.4rem; | |
font-weight: 700; | |
color: #00ff00; | |
text-transform: uppercase; | |
letter-spacing: 1px; | |
} | |
.terminal-status { | |
font-size: 0.8rem; | |
color: #00cccc; | |
font-family: 'Courier Prime', monospace; | |
} | |
.voice-controls { | |
display: flex; | |
flex-direction: column; | |
gap: 15px; | |
} | |
.hack-btn { | |
padding: 15px 30px; | |
border: 2px solid #00ff00; | |
background: rgba(0, 255, 0, 0.1); | |
color: #00ff00; | |
font-family: 'Orbitron', monospace; | |
font-size: 1rem; | |
font-weight: 700; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
text-transform: uppercase; | |
letter-spacing: 2px; | |
position: relative; | |
overflow: hidden; | |
} | |
.hack-btn::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: -100%; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(90deg, transparent, rgba(0, 255, 0, 0.3), transparent); | |
transition: left 0.5s; | |
} | |
.hack-btn:hover::before { | |
left: 100%; | |
} | |
.hack-btn:hover { | |
background: rgba(0, 255, 0, 0.2); | |
box-shadow: 0 0 20px rgba(0, 255, 0, 0.5); | |
text-shadow: 0 0 10px #00ff00; | |
} | |
.hack-btn:disabled { | |
opacity: 0.5; | |
cursor: not-allowed; | |
} | |
.hack-btn.active { | |
background: rgba(255, 0, 0, 0.2); | |
border-color: #ff0000; | |
color: #ff0000; | |
animation: pulse-red 1s infinite; | |
} | |
.status-display { | |
padding: 15px; | |
border: 1px solid #00ff00; | |
background: rgba(0, 0, 0, 0.8); | |
margin: 15px 0; | |
font-family: 'Courier Prime', monospace; | |
font-size: 1rem; | |
position: relative; | |
} | |
.status-display::before { | |
content: '> '; | |
color: #00cccc; | |
} | |
.status-display.listening { | |
border-color: #ff0000; | |
color: #ff0000; | |
animation: pulse-red 1s infinite; | |
} | |
.status-display.processing { | |
border-color: #ffff00; | |
color: #ffff00; | |
animation: pulse-yellow 1s infinite; | |
} | |
.status-display.success { | |
border-color: #00ff00; | |
color: #00ff00; | |
animation: pulse-green 1s infinite; | |
} | |
.transcript-display { | |
margin-top: 15px; | |
padding: 10px; | |
background: rgba(0, 255, 0, 0.05); | |
border-left: 4px solid #00ff00; | |
font-family: 'Courier Prime', monospace; | |
font-style: italic; | |
min-height: 40px; | |
color: #00cccc; | |
} | |
.code-matrix { | |
width: 100%; | |
height: 200px; | |
background: rgba(0, 0, 0, 0.9); | |
border: 2px solid #00ff00; | |
padding: 15px; | |
color: #00ff00; | |
font-family: 'Courier Prime', monospace; | |
font-size: 14px; | |
resize: vertical; | |
box-shadow: inset 0 0 20px rgba(0, 255, 0, 0.1); | |
} | |
.code-matrix:focus { | |
outline: none; | |
box-shadow: | |
inset 0 0 20px rgba(0, 255, 0, 0.2), | |
0 0 20px rgba(0, 255, 0, 0.3); | |
} | |
.hack-commands { | |
display: flex; | |
gap: 15px; | |
margin-top: 15px; | |
} | |
.visualization-matrix { | |
grid-column: 1 / -1; | |
height: 400px; | |
border: 2px solid #00ff00; | |
overflow: hidden; | |
position: relative; | |
background: rgba(0, 0, 0, 0.9); | |
box-shadow: | |
0 0 30px rgba(0, 255, 0, 0.3), | |
inset 0 0 30px rgba(0, 255, 0, 0.1); | |
} | |
.viz-header { | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
padding: 10px 20px; | |
background: rgba(0, 0, 0, 0.8); | |
border-bottom: 1px solid #00ff00; | |
font-family: 'Courier Prime', monospace; | |
font-size: 0.9rem; | |
z-index: 5; | |
} | |
.canvas-matrix { | |
width: 100%; | |
height: 100%; | |
background: radial-gradient(circle at center, #001100 0%, #000000 100%); | |
} | |
.examples-matrix { | |
margin-top: 20px; | |
padding: 15px; | |
background: rgba(0, 255, 0, 0.05); | |
border: 1px solid #00ff00; | |
} | |
.examples-matrix h4 { | |
margin-bottom: 15px; | |
color: #00cccc; | |
font-family: 'Courier Prime', monospace; | |
text-transform: uppercase; | |
letter-spacing: 1px; | |
} | |
.command-list { | |
list-style: none; | |
padding: 0; | |
} | |
.command-list li { | |
padding: 8px 0; | |
color: #00ff00; | |
cursor: pointer; | |
font-family: 'Courier Prime', monospace; | |
border-left: 4px solid transparent; | |
padding-left: 10px; | |
transition: all 0.3s ease; | |
} | |
.command-list li::before { | |
content: '$ '; | |
color: #00cccc; | |
} | |
.command-list li:hover { | |
border-left-color: #00ff00; | |
background: rgba(0, 255, 0, 0.1); | |
text-shadow: 0 0 5px #00ff00; | |
} | |
/* Animations */ | |
@keyframes flicker { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.8; } | |
} | |
@keyframes scan { | |
0% { left: -100%; } | |
100% { left: 100%; } | |
} | |
@keyframes sweep { | |
0% { transform: translateX(-100%); } | |
100% { transform: translateX(100%); } | |
} | |
@keyframes pulse-red { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.7; } | |
} | |
@keyframes pulse-yellow { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.7; } | |
} | |
@keyframes pulse-green { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.7; } | |
} | |
@keyframes matrix-fall { | |
0% { transform: translateY(-100vh); } | |
100% { transform: translateY(100vh); } | |
} | |
/* Responsive design */ | |
@media (max-width: 768px) { | |
.controls { | |
grid-template-columns: 1fr; | |
} | |
.header h1 { | |
font-size: 2rem; | |
} | |
.hack-commands { | |
flex-direction: column; | |
} | |
} | |
/* Glitch effect for active states */ | |
.glitch { | |
position: relative; | |
} | |
.glitch::before, | |
.glitch::after { | |
content: attr(data-text); | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
} | |
.glitch::before { | |
animation: glitch-anim-1 0.3s infinite linear alternate-reverse; | |
color: #ff0000; | |
z-index: -1; | |
} | |
.glitch::after { | |
animation: glitch-anim-2 0.3s infinite linear alternate-reverse; | |
color: #00ffff; | |
z-index: -2; | |
} | |
@keyframes glitch-anim-1 { | |
0% { clip: rect(42px, 9999px, 44px, 0); } | |
25% { clip: rect(12px, 9999px, 59px, 0); } | |
50% { clip: rect(25px, 9999px, 33px, 0); } | |
75% { clip: rect(91px, 9999px, 94px, 0); } | |
100% { clip: rect(65px, 9999px, 75px, 0); } | |
} | |
@keyframes glitch-anim-2 { | |
0% { clip: rect(65px, 9999px, 119px, 0); } | |
25% { clip: rect(52px, 9999px, 74px, 0); } | |
50% { clip: rect(79px, 9999px, 85px, 0); } | |
75% { clip: rect(29px, 9999px, 95px, 0); } | |
100% { clip: rect(55px, 9999px, 87px, 0); } | |
} | |
</style> | |
</head> | |
<body> | |
<div class="matrix-bg"> | |
<canvas id="matrixCanvas"></canvas> | |
</div> | |
<div class="container"> | |
<div class="header"> | |
<h1 class="glitch" data-text="MATRIX MUSIC HACKER">MATRIX MUSIC HACKER</h1> | |
<div class="subtitle">Voice-Controlled Beat Synthesis Protocol v2.1</div> | |
<div class="system-info"> | |
<span>CONNECTION: SECURE</span> | |
<span>STATUS: ONLINE</span> | |
<span>PROTOCOL: STRUDEL.CC</span> | |
</div> | |
</div> | |
<div class="controls"> | |
<div class="terminal"> | |
<div class="terminal-header"> | |
<div class="terminal-title">🎤 VOICE INPUT MODULE</div> | |
<div class="terminal-status">READY</div> | |
</div> | |
<div class="voice-controls"> | |
<button id="startBtn" class="hack-btn">INITIATE VOICE HACK</button> | |
<button id="stopBtn" class="hack-btn" disabled>TERMINATE CONNECTION</button> | |
<div id="status" class="status-display">AWAITING VOICE COMMAND...</div> | |
<div id="transcript" class="transcript-display"></div> | |
</div> | |
<div class="examples-matrix"> | |
<h4>AVAILABLE HACK COMMANDS:</h4> | |
<ul class="command-list"> | |
<li onclick="useExample(this)">execute drum_sequence --deep-house</li> | |
<li onclick="useExample(this)">generate bass_matrix --underground</li> | |
<li onclick="useExample(this)">deploy synth_wave --cyberpunk</li> | |
<li onclick="useExample(this)">create ambient_hack --matrix</li> | |
<li onclick="useExample(this)">launch breakbeat --rave</li> | |
</ul> | |
</div> | |
</div> | |
<div class="terminal"> | |
<div class="terminal-header"> | |
<div class="terminal-title">💻 CODE MATRIX</div> | |
<div class="terminal-status">COMPILING</div> | |
</div> | |
<textarea id="codeArea" class="code-matrix" placeholder="// STRUDEL CODE WILL MATERIALIZE HERE... | |
// READY TO HACK THE MATRIX OF SOUND..."></textarea> | |
<div class="hack-commands"> | |
<button id="playBtn" class="hack-btn">EXECUTE HACK</button> | |
<button id="stopMusicBtn" class="hack-btn">KILL PROCESS</button> | |
</div> | |
</div> | |
</div> | |
<div class="terminal visualization-matrix"> | |
<div class="viz-header"> | |
<span>🌊 AUDIO VISUALIZATION MATRIX - REAL-TIME FREQUENCY ANALYSIS</span> | |
<span style="float: right;">FPS: 60 | SAMPLES: 1024</span> | |
</div> | |
<canvas id="visualCanvas" class="canvas-matrix"></canvas> | |
</div> | |
</div> | |
<script> | |
// Global variables | |
let recognition; | |
let isListening = false; | |
let audioContext; | |
let analyzer; | |
let canvas; | |
let ctx; | |
let animationId; | |
let currentPattern; | |
let matrixCanvas; | |
let matrixCtx; | |
let matrixColumns = []; | |
// Initialize everything when page loads | |
window.addEventListener('load', function() { | |
initSpeechRecognition(); | |
initVisualization(); | |
initMatrixBackground(); | |
}); | |
// Matrix rain background | |
function initMatrixBackground() { | |
matrixCanvas = document.getElementById('matrixCanvas'); | |
matrixCtx = matrixCanvas.getContext('2d'); | |
function resizeMatrix() { | |
matrixCanvas.width = window.innerWidth; | |
matrixCanvas.height = window.innerHeight; | |
const columnCount = Math.floor(matrixCanvas.width / 20); | |
matrixColumns = []; | |
for (let i = 0; i < columnCount; i++) { | |
matrixColumns[i] = Math.random() * matrixCanvas.height; | |
} | |
} | |
resizeMatrix(); | |
window.addEventListener('resize', resizeMatrix); | |
function drawMatrix() { | |
matrixCtx.fillStyle = 'rgba(0, 0, 0, 0.05)'; | |
matrixCtx.fillRect(0, 0, matrixCanvas.width, matrixCanvas.height); | |
matrixCtx.fillStyle = '#00ff00'; | |
matrixCtx.font = '15px monospace'; | |
for (let i = 0; i < matrixColumns.length; i++) { | |
const text = String.fromCharCode(Math.random() * 128); | |
const x = i * 20; | |
const y = matrixColumns[i] * 20; | |
matrixCtx.fillText(text, x, y); | |
if (y > matrixCanvas.height && Math.random() > 0.975) { | |
matrixColumns[i] = 0; | |
} | |
matrixColumns[i]++; | |
} | |
requestAnimationFrame(drawMatrix); | |
} | |
drawMatrix(); | |
} | |
// Speech Recognition Setup | |
function initSpeechRecognition() { | |
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { | |
document.getElementById('status').textContent = 'ERROR: VOICE RECOGNITION MODULE NOT FOUND'; | |
return; | |
} | |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
recognition = new SpeechRecognition(); | |
recognition.continuous = true; | |
recognition.interimResults = true; | |
recognition.lang = 'en-US'; | |
recognition.onstart = function() { | |
isListening = true; | |
updateUI(); | |
document.getElementById('status').textContent = 'VOICE HACK INITIATED... LISTENING FOR COMMANDS'; | |
document.getElementById('status').className = 'status-display listening'; | |
}; | |
recognition.onresult = function(event) { | |
let transcript = ''; | |
for (let i = event.resultIndex; i < event.results.length; i++) { | |
transcript += event.results[i][0].transcript; | |
} | |
document.getElementById('transcript').textContent = '> ' + transcript; | |
if (event.results[event.results.length - 1].isFinal) { | |
processVoiceHack(transcript); | |
} | |
}; | |
recognition.onerror = function(event) { | |
document.getElementById('status').textContent = 'HACK FAILED: ' + event.error.toUpperCase(); | |
document.getElementById('status').className = 'status-display'; | |
isListening = false; | |
updateUI(); | |
}; | |
recognition.onend = function() { | |
isListening = false; | |
updateUI(); | |
if (document.getElementById('status').textContent.includes('LISTENING')) { | |
document.getElementById('status').textContent = 'VOICE HACK TERMINATED... READY FOR NEW COMMAND'; | |
document.getElementById('status').className = 'status-display'; | |
} | |
}; | |
} | |
// UI Event Handlers | |
document.getElementById('startBtn').addEventListener('click', function() { | |
if (recognition) { | |
recognition.start(); | |
this.classList.add('active'); | |
} | |
}); | |
document.getElementById('stopBtn').addEventListener('click', function() { | |
if (recognition) { | |
recognition.stop(); | |
document.getElementById('startBtn').classList.remove('active'); | |
} | |
}); | |
document.getElementById('playBtn').addEventListener('click', function() { | |
const code = document.getElementById('codeArea').value; | |
if (code.trim()) { | |
executeHack(code); | |
} | |
}); | |
document.getElementById('stopMusicBtn').addEventListener('click', function() { | |
killProcess(); | |
}); | |
function updateUI() { | |
document.getElementById('startBtn').disabled = isListening; | |
document.getElementById('stopBtn').disabled = !isListening; | |
if (isListening) { | |
document.getElementById('startBtn').textContent = 'HACKING IN PROGRESS...'; | |
} else { | |
document.getElementById('startBtn').textContent = 'INITIATE VOICE HACK'; | |
document.getElementById('startBtn').classList.remove('active'); | |
} | |
} | |
// Process voice input and convert to Strudel code | |
function processVoiceHack(transcript) { | |
document.getElementById('status').textContent = 'PROCESSING VOICE HACK... COMPILING AUDIO MATRIX'; | |
document.getElementById('status').className = 'status-display processing'; | |
const strudelCode = hackTheMatrix(transcript.toLowerCase()); | |
document.getElementById('codeArea').value = strudelCode; | |
document.getElementById('status').textContent = 'HACK COMPILED SUCCESSFULLY... READY TO EXECUTE'; | |
document.getElementById('status').className = 'status-display success'; | |
// Auto-execute the hack | |
setTimeout(() => { | |
executeHack(strudelCode); | |
}, 1000); | |
} | |
// Enhanced cyberpunk conversion to Strudel code | |
function hackTheMatrix(input) { | |
const hackPatterns = [ | |
{ | |
keywords: ['drum', 'beat', 'kick', 'deep house', 'house'], | |
code: `// DEEP HOUSE MATRIX HACK | |
stack( | |
s("bd*4").gain(0.8).lpf(1000), | |
s("~ hh ~ hh").gain(0.6).hpf(8000), | |
s("~ ~ sd ~").gain(0.7).room(0.3) | |
).slow(2)` | |
}, | |
{ | |
keywords: ['bass', 'underground', 'sub'], | |
code: `// UNDERGROUND BASS MATRIX | |
stack( | |
note("c1 ~ c1 ~").s('sawtooth').lpf(200).gain(0.9), | |
note("c2 eb2 f2 g2").s('sawtooth').lpf(400).delay(0.125), | |
s("bd ~ ~ ~").gain(0.8) | |
).slow(2)` | |
}, | |
{ | |
keywords: ['synth', 'cyberpunk', 'wave', 'cyber'], | |
code: `// CYBERPUNK SYNTH WAVE | |
stack( | |
note("c4 eb4 g4 bb4").s('sawtooth').lpf(800).delay(0.25).room(0.5), | |
note("c3 ~ eb3 ~").s('square').lpf(400).gain(0.7), | |
s("bd hh sd hh").gain(0.6).room(0.2) | |
).slow(1.5)` | |
}, | |
{ | |
keywords: ['ambient', 'matrix', 'atmospheric'], | |
code: `// MATRIX AMBIENT HACK | |
stack( | |
note("c3 eb3 g3 bb3").s('sawtooth').lpf(300).delay(0.5).room(0.9).slow(4), | |
note("c4 ~ ~ ~").s('sine').lpf(200).gain(0.5).slow(8), | |
s("~ ~ ~ bd").gain(0.3).room(0.8) | |
).slow(2)` | |
}, | |
{ | |
keywords: ['breakbeat', 'rave', 'break', 'jungle'], | |
code: `// UNDERGROUND RAVE BREAKBEAT | |
stack( | |
s("bd*2 ~ sd bd").gain(0.8).speed(1.2), | |
s("~ hh*4 ~ hh*2").gain(0.4).hpf(10000), | |
note("c2 ~ f2 g2").s('sawtooth').lpf(600).gain(0.7) | |
).fast(2)` | |
}, | |
{ | |
keywords: ['techno', 'industrial', 'machine'], | |
code: `// INDUSTRIAL TECHNO MATRIX | |
stack( | |
s("bd*4").gain(0.9).distort(0.3), | |
s("~ ~ sd ~").gain(0.7).room(0.1), | |
s("hh*8").gain(0.3).hpf(12000), | |
note("c2 ~ ~ c2").s('sawtooth').lpf(300).gain(0.6) | |
)` | |
} | |
]; | |
// Find the best matching hack pattern | |
for (const pattern of hackPatterns) { | |
if (pattern.keywords.some(keyword => input.includes(keyword))) { | |
return pattern.code; | |
} | |
} | |
// Default matrix hack if no match | |
return `// DEFAULT MATRIX HACK | |
stack( | |
s("bd ~ sd ~").gain(0.8).room(0.2), | |
s("~ hh ~ hh").gain(0.5).hpf(8000), | |
note("c2 ~ e2 ~").s('sawtooth').lpf(500) | |
)`; | |
} | |
// Execute the hack (play music) | |
function executeHack(code) { | |
try { | |
document.getElementById('status').textContent = 'HACK EXECUTED... INJECTING AUDIO INTO MATRIX'; | |
document.getElementById('status').className = 'status-display success'; | |
// Start visualization | |
startMatrixVisualization(); | |
currentPattern = { | |
code: code, | |
playing: true, | |
startTime: Date.now() | |
}; | |
} catch (error) { | |
console.error('Hack execution failed:', error); | |
document.getElementById('status').textContent = 'HACK EXECUTION FAILED... SYSTEM COMPROMISED'; | |
} | |
} | |
function killProcess() { | |
if (currentPattern) { | |
currentPattern.playing = false; | |
currentPattern = null; | |
} | |
stopMatrixVisualization(); | |
document.getElementById('status').textContent = 'PROCESS TERMINATED... MATRIX RESTORED'; | |
document.getElementById('status').className = 'status-display'; | |
} | |
// Visualization | |
function initVisualization() { | |
canvas = document.getElementById('visualCanvas'); | |
ctx = canvas.getContext('2d'); | |
resizeCanvas(); | |
window.addEventListener('resize', resizeCanvas); | |
} | |
function resizeCanvas() { | |
const rect = canvas.getBoundingClientRect(); | |
canvas.width = rect.width; | |
canvas.height = rect.height; | |
} | |
function startMatrixVisualization() { | |
if (animationId) return; | |
function animate() { | |
if (!currentPattern || !currentPattern.playing) { | |
stopMatrixVisualization(); | |
return; | |
} | |
const width = canvas.width; | |
const height = canvas.height; | |
const time = (Date.now() - currentPattern.startTime) * 0.001; | |
// Clear with matrix-style fade | |
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; | |
ctx.fillRect(0, 0, width, height); | |
// Draw central matrix core | |
const centerX = width / 2; | |
const centerY = height / 2; | |
// Pulsing core | |
const coreRadius = 50 + Math.sin(time * 4) * 20; | |
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, coreRadius); | |
gradient.addColorStop(0, 'rgba(0, 255, 0, 0.8)'); | |
gradient.addColorStop(0.5, 'rgba(0, 255, 0, 0.3)'); | |
gradient.addColorStop(1, 'rgba(0, 255, 0, 0)'); | |
ctx.fillStyle = gradient; |