matthewspring commited on
Commit
8c2bc39
·
verified ·
1 Parent(s): fee58ec

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +524 -19
index.html CHANGED
@@ -1,19 +1,524 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cosmic Canvas | Interactive Particle Art</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
9
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;500;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --primary: #00f3ff;
13
+ --secondary: #ff00aa;
14
+ --dark: #0a0a12;
15
+ --glass: rgba(255, 255, 255, 0.05);
16
+ }
17
+
18
+ body {
19
+ margin: 0;
20
+ overflow: hidden;
21
+ background-color: var(--dark);
22
+ font-family: 'Space Grotesk', sans-serif;
23
+ color: white;
24
+ }
25
+
26
+ canvas {
27
+ display: block;
28
+ position: absolute;
29
+ top: 0;
30
+ left: 0;
31
+ z-index: 0;
32
+ }
33
+
34
+ /* Custom Scrollbar for panels */
35
+ ::-webkit-scrollbar {
36
+ width: 6px;
37
+ }
38
+ ::-webkit-scrollbar-track {
39
+ background: rgba(0,0,0,0.3);
40
+ }
41
+ ::-webkit-scrollbar-thumb {
42
+ background: var(--primary);
43
+ border-radius: 3px;
44
+ }
45
+
46
+ /* Glassmorphism Utilities */
47
+ .glass-panel {
48
+ background: var(--glass);
49
+ backdrop-filter: blur(12px);
50
+ -webkit-backdrop-filter: blur(12px);
51
+ border: 1px solid rgba(255, 255, 255, 0.1);
52
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
53
+ }
54
+
55
+ .neon-text {
56
+ text-shadow: 0 0 5px var(--primary), 0 0 10px var(--primary);
57
+ }
58
+
59
+ .neon-text-pink {
60
+ text-shadow: 0 0 5px var(--secondary), 0 0 10px var(--secondary);
61
+ }
62
+
63
+ /* Range Slider Styling */
64
+ input[type=range] {
65
+ -webkit-appearance: none;
66
+ width: 100%;
67
+ background: transparent;
68
+ }
69
+ input[type=range]::-webkit-slider-thumb {
70
+ -webkit-appearance: none;
71
+ height: 16px;
72
+ width: 16px;
73
+ border-radius: 50%;
74
+ background: var(--primary);
75
+ cursor: pointer;
76
+ margin-top: -6px;
77
+ box-shadow: 0 0 10px var(--primary);
78
+ }
79
+ input[type=range]::-webkit-slider-runnable-track {
80
+ width: 100%;
81
+ height: 4px;
82
+ cursor: pointer;
83
+ background: rgba(255,255,255,0.2);
84
+ border-radius: 2px;
85
+ }
86
+
87
+ /* Animations */
88
+ @keyframes float {
89
+ 0% { transform: translateY(0px); }
90
+ 50% { transform: translateY(-10px); }
91
+ 100% { transform: translateY(0px); }
92
+ }
93
+ .animate-float {
94
+ animation: float 6s ease-in-out infinite;
95
+ }
96
+
97
+ @keyframes pulse-glow {
98
+ 0%, 100% { box-shadow: 0 0 10px var(--primary); }
99
+ 50% { box-shadow: 0 0 25px var(--primary), 0 0 10px var(--secondary); }
100
+ }
101
+
102
+ .btn-glow:hover {
103
+ animation: pulse-glow 1.5s infinite;
104
+ }
105
+
106
+ .fade-in {
107
+ animation: fadeIn 0.5s ease-in forwards;
108
+ }
109
+ @keyframes fadeIn {
110
+ from { opacity: 0; transform: translateY(20px); }
111
+ to { opacity: 1; transform: translateY(0); }
112
+ }
113
+ </style>
114
+ </head>
115
+ <body class="antialiased selection:bg-cyan-500 selection:text-black">
116
+
117
+ <!-- Canvas Layer -->
118
+ <canvas id="canvas1"></canvas>
119
+
120
+ <!-- UI Overlay -->
121
+ <div class="relative z-10 w-full h-screen pointer-events-none flex flex-col justify-between p-4 md:p-8">
122
+
123
+ <!-- Header -->
124
+ <header class="flex justify-between items-start pointer-events-auto">
125
+ <div class="glass-panel p-4 rounded-xl border-l-4 border-cyan-400 animate-float">
126
+ <h1 class="text-3xl md:text-5xl font-bold tracking-tighter neon-text">COSMIC<span class="text-pink-500 neon-text-pink">CANVAS</span></h1>
127
+ <p class="text-xs md:text-sm text-gray-300 mt-1 tracking-widest uppercase">Interactive Particle Simulation</p>
128
+ <div class="mt-2 text-[10px] text-gray-400">
129
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="underline hover:text-cyan-400 transition-colors">anycoder</a>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="flex gap-2">
134
+ <button id="audioBtn" class="glass-panel p-3 rounded-full hover:bg-white/10 transition-all btn-glow group" title="Toggle Ambient Audio">
135
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-cyan-400 group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
136
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
137
+ </svg>
138
+ </button>
139
+ <button id="infoBtn" class="glass-panel p-3 rounded-full hover:bg-white/10 transition-all btn-glow group">
140
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-pink-500 group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
141
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
142
+ </svg>
143
+ </button>
144
+ </div>
145
+ </header>
146
+
147
+ <!-- Center Interaction Hint -->
148
+ <div id="hint" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center pointer-events-none transition-opacity duration-500">
149
+ <p class="text-cyan-200 text-lg md:text-2xl font-light tracking-widest opacity-70">CLICK & DRAG TO CREATE</p>
150
+ <p class="text-gray-400 text-sm mt-2">MOVE MOUSE TO INTERACT</p>
151
+ </div>
152
+
153
+ <!-- Controls Footer -->
154
+ <footer class="pointer-events-auto w-full max-w-4xl mx-auto">
155
+ <div class="glass-panel rounded-2xl p-4 md:p-6 grid grid-cols-1 md:grid-cols-4 gap-6 items-end">
156
+
157
+ <!-- Mode Selector -->
158
+ <div class="col-span-1 md:col-span-1">
159
+ <label class="block text-xs font-bold text-cyan-400 uppercase tracking-wider mb-2">Simulation Mode</label>
160
+ <div class="flex gap-2">
161
+ <button class="mode-btn flex-1 py-2 text-xs border border-cyan-500/30 rounded bg-cyan-500/20 text-cyan-300 hover:bg-cyan-500 hover:text-black transition-all active" data-mode="particles">Flow</button>
162
+ <button class="mode-btn flex-1 py-2 text-xs border border-pink-500/30 rounded bg-pink-500/20 text-pink-300 hover:bg-pink-500 hover:text-black transition-all" data-mode="constellation">Web</button>
163
+ </div>
164
+ </div>
165
+
166
+ <!-- Sliders -->
167
+ <div class="col-span-1 md:col-span-2 grid grid-cols-2 gap-4">
168
+ <div>
169
+ <div class="flex justify-between text-xs text-gray-400 mb-1">
170
+ <span>Particle Count</span>
171
+ <span id="countVal">150</span>
172
+ </div>
173
+ <input type="range" id="particleCount" min="50" max="400" value="150">
174
+ </div>
175
+ <div>
176
+ <div class="flex justify-between text-xs text-gray-400 mb-1">
177
+ <span>Speed</span>
178
+ <span id="speedVal">1.0x</span>
179
+ </div>
180
+ <input type="range" id="speedControl" min="0.1" max="3.0" step="0.1" value="1.0">
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Actions -->
185
+ <div class="col-span-1 md:col-span-1 flex gap-2">
186
+ <button id="resetBtn" class="flex-1 bg-gray-700 hover:bg-gray-600 text-white py-2 px-4 rounded text-sm font-medium transition-colors border border-gray-600">
187
+ Reset
188
+ </button>
189
+ <button id="colorBtn" class="flex-1 bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 text-white py-2 px-4 rounded text-sm font-medium transition-all shadow-lg shadow-cyan-500/30">
190
+ Theme
191
+ </button>
192
+ </div>
193
+ </div>
194
+ </footer>
195
+ </div>
196
+
197
+ <!-- Info Modal -->
198
+ <div id="infoModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm opacity-0 pointer-events-none transition-opacity duration-300">
199
+ <div class="glass-panel max-w-md w-full p-8 rounded-2xl transform scale-95 transition-transform duration-300" id="modalContent">
200
+ <h2 class="text-2xl font-bold text-white mb-4">How to use Cosmic Canvas</h2>
201
+ <ul class="space-y-3 text-gray-300 text-sm">
202
+ <li class="flex items-start"><span class="text-cyan-400 mr-2">●</span> <strong>Flow Mode:</strong> Particles follow the mouse and leave a cosmic trail.</li>
203
+ <li class="flex items-start"><span class="text-pink-400 mr-2">●</span> <strong>Web Mode:</strong> Particles connect to each other forming a neural network.</li>
204
+ <li class="flex items-start"><span class="text-yellow-400 mr-2">●</span> <strong>Audio:</strong> Toggle the ambient soundscape for immersion.</li>
205
+ <li class="flex items-start"><span class="text-green-400 mr-2">●</span> <strong>Click & Drag:</strong> Creates a burst of energy and sound.</li>
206
+ </ul>
207
+ <button id="closeModal" class="mt-6 w-full bg-white/10 hover:bg-white/20 text-white py-3 rounded-lg transition-colors border border-white/10">
208
+ Start Creating
209
+ </button>
210
+ </div>
211
+ </div>
212
+
213
+ <script>
214
+ /**
215
+ * COSMIC CANVAS ENGINE
216
+ * Handles Particle System, Audio Synthesis, and UI Logic
217
+ */
218
+
219
+ const canvas = document.getElementById('canvas1');
220
+ const ctx = canvas.getContext('2d');
221
+
222
+ // State
223
+ let particlesArray = [];
224
+ let hue = 0;
225
+ let isMouseDown = false;
226
+ let mode = 'particles'; // 'particles' or 'constellation'
227
+ let animationId;
228
+ let audioStarted = false;
229
+
230
+ // Configuration
231
+ const config = {
232
+ particleCount: 150,
233
+ baseSpeed: 1.0,
234
+ connectionDistance: 100,
235
+ mouseRadius: 150,
236
+ themeHue: 0 // 0 = Cyan/Blue, 180 = Purple/Pink, etc.
237
+ };
238
+
239
+ // Mouse Object
240
+ const mouse = {
241
+ x: undefined,
242
+ y: undefined,
243
+ }
244
+
245
+ // Resize Handling
246
+ canvas.width = window.innerWidth;
247
+ canvas.height = window.innerHeight;
248
+
249
+ window.addEventListener('resize', () => {
250
+ canvas.width = window.innerWidth;
251
+ canvas.height = window.innerHeight;
252
+ init();
253
+ });
254
+
255
+ // Mouse Events
256
+ window.addEventListener('mousemove', (e) => {
257
+ mouse.x = e.x;
258
+ mouse.y = e.y;
259
+
260
+ // Hide hint on first interaction
261
+ const hint = document.getElementById('hint');
262
+ if(hint.style.opacity !== '0') hint.style.opacity = '0';
263
+ });
264
+
265
+ window.addEventListener('mousedown', () => {
266
+ isMouseDown = true;
267
+ if(audioStarted) playBurstSound();
268
+ });
269
+
270
+ window.addEventListener('mouseup', () => {
271
+ isMouseDown = false;
272
+ });
273
+
274
+ window.addEventListener('mouseout', () => {
275
+ mouse.x = undefined;
276
+ mouse.y = undefined;
277
+ });
278
+
279
+ // --- Audio System (Tone.js) ---
280
+ let synth, drone, filter;
281
+
282
+ async function initAudio() {
283
+ await Tone.start();
284
+
285
+ // PolySynth for interaction sounds
286
+ synth = new Tone.PolySynth(Tone.Synth, {
287
+ oscillator: { type: "fatsawtooth" },
288
+ envelope: { attack: 0.01, decay: 0.1, sustain: 0.1, release: 1 }
289
+ }).toDestination();
290
+ synth.volume.value = -10;
291
+
292
+ // Drone for background ambience
293
+ filter = new Tone.AutoFilter({
294
+ frequency: 0.1,
295
+ baseFrequency: 200,
296
+ octaves: 2.6
297
+ }).toDestination().start();
298
+
299
+ drone = new Tone.Oscillator(110, "sine").connect(filter).start();
300
+ drone.volume.value = -20;
301
+
302
+ // Reverb for spacey feel
303
+ const reverb = new Tone.Reverb({ decay: 5, wet: 0.5 }).toDestination();
304
+ synth.connect(reverb);
305
+ drone.connect(reverb);
306
+
307
+ audioStarted = true;
308
+ }
309
+
310
+ function playBurstSound() {
311
+ if(!synth) return;
312
+ const notes = ["C4", "E4", "G4", "A4", "C5"];
313
+ const note = notes[Math.floor(Math.random() * notes.length)];
314
+ synth.triggerAttackRelease(note, "8n");
315
+ }
316
+
317
+ function toggleAudio() {
318
+ if (!audioStarted) {
319
+ initAudio();
320
+ document.getElementById('audioBtn').classList.add('bg-cyan-500/40');
321
+ } else {
322
+ Tone.Destination.mute = !Tone.Destination.mute;
323
+ document.getElementById('audioBtn').classList.toggle('bg-cyan-500/40');
324
+ }
325
+ }
326
+
327
+ // --- Particle Class ---
328
+ class Particle {
329
+ constructor() {
330
+ this.x = Math.random() * canvas.width;
331
+ this.y = Math.random() * canvas.height;
332
+ this.size = Math.random() * 3 + 1;
333
+ // Random velocity vector
334
+ this.speedX = (Math.random() * 3 - 1.5) * config.baseSpeed;
335
+ this.speedY = (Math.random() * 3 - 1.5) * config.baseSpeed;
336
+ this.color = `hsl(${config.themeHue + Math.random() * 60}, 100%, 50%)`;
337
+ }
338
+
339
+ update() {
340
+ // Movement
341
+ this.x += this.speedX;
342
+ this.y += this.speedY;
343
+
344
+ // Bounce off edges
345
+ if (this.x > canvas.width || this.x < 0) this.speedX = -this.speedX;
346
+ if (this.y > canvas.height || this.y < 0) this.speedY = -this.speedY;
347
+
348
+ // Mouse Interaction
349
+ if (mouse.x != undefined) {
350
+ let dx = mouse.x - this.x;
351
+ let dy = mouse.y - this.y;
352
+ let distance = Math.sqrt(dx * dx + dy * dy);
353
+
354
+ if (distance < config.mouseRadius) {
355
+ const forceDirectionX = dx / distance;
356
+ const forceDirectionY = dy / distance;
357
+ const force = (config.mouseRadius - distance) / config.mouseRadius;
358
+
359
+ // Push away or attract based on click
360
+ const directionMultiplier = isMouseDown ? 1 : -1;
361
+
362
+ const directionX = forceDirectionX * force * directionMultiplier * 5;
363
+ const directionY = forceDirectionY * force * directionMultiplier * 5;
364
+
365
+ this.x += directionX;
366
+ this.y += directionY;
367
+ }
368
+ }
369
+ }
370
+
371
+ draw() {
372
+ ctx.fillStyle = this.color;
373
+ ctx.beginPath();
374
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
375
+ ctx.fill();
376
+ }
377
+ }
378
+
379
+ // --- System Logic ---
380
+
381
+ function init() {
382
+ particlesArray = [];
383
+ for (let i = 0; i < config.particleCount; i++) {
384
+ particlesArray.push(new Particle());
385
+ }
386
+ }
387
+
388
+ function handleParticles() {
389
+ for (let i = 0; i < particlesArray.length; i++) {
390
+ particlesArray[i].update();
391
+ particlesArray[i].draw();
392
+
393
+ // Constellation Mode Logic
394
+ if (mode === 'constellation') {
395
+ for (let j = i; j < particlesArray.length; j++) {
396
+ let dx = particlesArray[i].x - particlesArray[j].x;
397
+ let dy = particlesArray[i].y - particlesArray[j].y;
398
+ let distance = Math.sqrt(dx * dx + dy * dy);
399
+
400
+ if (distance < config.connectionDistance) {
401
+ ctx.beginPath();
402
+ // Dynamic opacity based on distance
403
+ let opacity = 1 - (distance / config.connectionDistance);
404
+ ctx.strokeStyle = `hsla(${config.themeHue}, 100%, 50%, ${opacity})`;
405
+ ctx.lineWidth = 1;
406
+ ctx.moveTo(particlesArray[i].x, particlesArray[i].y);
407
+ ctx.lineTo(particlesArray[j].x, particlesArray[j].y);
408
+ ctx.stroke();
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ function animate() {
416
+ // Clear canvas with trail effect
417
+ ctx.fillStyle = 'rgba(10, 10, 18, 0.15)'; // Low opacity for trails
418
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
419
+
420
+ handleParticles();
421
+
422
+ // Cycle global hue slowly for dynamic color shifting if not using fixed theme
423
+ // hue += 0.5;
424
+
425
+ animationId = requestAnimationFrame(animate);
426
+ }
427
+
428
+ // --- UI Logic ---
429
+
430
+ // Mode Switching
431
+ const modeBtns = document.querySelectorAll('.mode-btn');
432
+ modeBtns.forEach(btn => {
433
+ btn.addEventListener('click', (e) => {
434
+ // Update UI
435
+ modeBtns.forEach(b => {
436
+ b.classList.remove('active', 'bg-cyan-500', 'text-black', 'bg-pink-500');
437
+ // Reset to default styles based on type
438
+ if(b.dataset.mode === 'particles') {
439
+ b.className = 'mode-btn flex-1 py-2 text-xs border border-cyan-500/30 rounded bg-cyan-500/20 text-cyan-300 hover:bg-cyan-500 hover:text-black transition-all';
440
+ } else {
441
+ b.className = 'mode-btn flex-1 py-2 text-xs border border-pink-500/30 rounded bg-pink-500/20 text-pink-300 hover:bg-pink-500 hover:text-black transition-all';
442
+ }
443
+ });
444
+
445
+ mode = e.target.dataset.mode;
446
+
447
+ // Set Active Style
448
+ if(mode === 'particles') {
449
+ e.target.classList.add('bg-cyan-500', 'text-black');
450
+ } else {
451
+ e.target.classList.add('bg-pink-500', 'text-black');
452
+ }
453
+ });
454
+ });
455
+
456
+ // Controls
457
+ document.getElementById('particleCount').addEventListener('input', (e) => {
458
+ config.particleCount = parseInt(e.target.value);
459
+ document.getElementById('countVal').innerText = config.particleCount;
460
+ init();
461
+ });
462
+
463
+ document.getElementById('speedControl').addEventListener('input', (e) => {
464
+ config.baseSpeed = parseFloat(e.target.value);
465
+ document.getElementById('speedVal').innerText = config.baseSpeed + 'x';
466
+ // Update existing particles speed
467
+ particlesArray.forEach(p => {
468
+ p.speedX = (Math.random() * 3 - 1.5) * config.baseSpeed;
469
+ p.speedY = (Math.random() * 3 - 1.5) * config.baseSpeed;
470
+ });
471
+ });
472
+
473
+ document.getElementById('resetBtn').addEventListener('click', () => {
474
+ init();
475
+ });
476
+
477
+ document.getElementById('colorBtn').addEventListener('click', () => {
478
+ // Shift hue theme
479
+ config.themeHue = (config.themeHue + 60) % 360;
480
+ // Update particle colors
481
+ particlesArray.forEach(p => {
482
+ p.color = `hsl(${config.themeHue + Math.random() * 60}, 100%, 50%)`;
483
+ });
484
+ });
485
+
486
+ // Audio Toggle
487
+ document.getElementById('audioBtn').addEventListener('click', toggleAudio);
488
+
489
+ // Modal Logic
490
+ const modal = document.getElementById('infoModal');
491
+ const modalContent = document.getElementById('modalContent');
492
+ const infoBtn = document.getElementById('infoBtn');
493
+ const closeModal = document.getElementById('closeModal');
494
+
495
+ function openModal() {
496
+ modal.classList.remove('pointer-events-none', 'opacity-0');
497
+ modalContent.classList.remove('scale-95');
498
+ modalContent.classList.add('scale-100');
499
+ }
500
+
501
+ function hideModal() {
502
+ modal.classList.add('pointer-events-none', 'opacity-0');
503
+ modalContent.classList.remove('scale-100');
504
+ modalContent.classList.add('scale-95');
505
+ }
506
+
507
+ infoBtn.addEventListener('click', openModal);
508
+ closeModal.addEventListener('click', hideModal);
509
+ modal.addEventListener('click', (e) => {
510
+ if(e.target === modal) hideModal();
511
+ });
512
+
513
+ // Initialization
514
+ init();
515
+ animate();
516
+
517
+ // Show modal on first load after a short delay
518
+ setTimeout(() => {
519
+ // Check if user has interacted, if not, maybe show hint
520
+ }, 2000);
521
+
522
+ </script>
523
+ </body>
524
+ </html>