flausch commited on
Commit
87687a9
·
verified ·
1 Parent(s): b1d1d19

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1315 -568
index.html CHANGED
@@ -1,581 +1,1328 @@
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>CyberFluff - Where Tech Meets Cuteness</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
- <style>
10
- @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Rubik+Mono+One&family=Comfortaa:wght@400;700&display=swap');
11
-
12
- :root {
13
- --neon-pink: #ff6ec7;
14
- --neon-blue: #6ec6ff;
15
- --neon-purple: #b36eff;
16
- --fluffy-white: #fff9f9;
17
- --cyber-black: #0a0a14;
18
- }
19
-
20
- body {
21
- font-family: 'Comfortaa', cursive;
22
- background-color: var(--cyber-black);
23
- color: var(--fluffy-white);
24
- overflow-x: hidden;
25
- }
26
-
27
- .cyber-title {
28
- font-family: 'Rubik Mono One', sans-serif;
29
- text-shadow: 0 0 10px var(--neon-pink),
30
- 0 0 20px var(--neon-blue);
31
- }
32
-
33
- .fluffy-card {
34
- background: rgba(255, 255, 255, 0.08);
35
- backdrop-filter: blur(12px);
36
- border-radius: 25px;
37
- border: 1px solid rgba(255, 255, 255, 0.3);
38
- transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
39
- box-shadow: 0 8px 32px rgba(110, 198, 255, 0.1),
40
- inset 0 0 20px rgba(255, 255, 255, 0.05);
41
- }
42
-
43
- .fluffy-card:hover {
44
- transform: translateY(-10px);
45
- box-shadow: 0 10px 30px rgba(110, 198, 255, 0.3);
46
- }
47
-
48
- .neon-text {
49
- text-shadow: 0 0 5px currentColor;
50
- }
51
-
52
- .pink-neon {
53
- color: var(--neon-pink);
54
- }
55
-
56
- .blue-neon {
57
- color: var(--neon-blue);
58
- }
59
-
60
- .purple-neon {
61
- color: var(--neon-purple);
62
- }
63
-
64
- .fluffy-btn {
65
- background: linear-gradient(45deg, var(--neon-pink), var(--neon-purple));
66
- border: none;
67
- border-radius: 50px;
68
- padding: 12px 30px;
69
- font-weight: bold;
70
- text-transform: uppercase;
71
- letter-spacing: 2px;
72
- position: relative;
73
- overflow: hidden;
74
- z-index: 1;
75
- }
76
-
77
- .fluffy-btn::before {
78
- content: '';
79
- position: absolute;
80
- top: 0;
81
- left: -100%;
82
- width: 100%;
83
- height: 100%;
84
- background: linear-gradient(45deg, var(--neon-blue), var(--neon-pink));
85
- transition: all 0.4s ease;
86
- z-index: -1;
87
- }
88
-
89
- .fluffy-btn:hover::before {
90
- left: 0;
91
- }
92
-
93
- .floating-clouds {
94
- position: absolute;
95
- width: 100%;
96
- height: 100%;
97
- top: 0;
98
- left: 0;
99
- pointer-events: none;
100
- z-index: -1;
101
- }
102
-
103
- .cloud {
104
- position: absolute;
105
- background: rgba(255, 255, 255, 0.15);
106
- border-radius: 50%;
107
- filter: blur(40px);
108
- animation: float 15s infinite ease-in-out;
109
- box-shadow: 0 0 60px rgba(255, 255, 255, 0.2);
110
- }
111
- .cloud::after {
112
- content: '';
113
- position: absolute;
114
- width: 50%;
115
- height: 50%;
116
- background: rgba(255, 255, 255, 0.1);
117
- border-radius: 50%;
118
- top: 20%;
119
- left: 20%;
120
- filter: blur(15px);
121
- }
122
-
123
- @keyframes float {
124
- 0% { transform: translateY(0) translateX(0); }
125
- 50% { transform: translateY(-50px) translateX(50px); }
126
- 100% { transform: translateY(0) translateX(0); }
127
- }
128
-
129
- .cyber-grid {
130
- background-image:
131
- linear-gradient(rgba(110, 198, 255, 0.1) 1px, transparent 1px),
132
- linear-gradient(90deg, rgba(110, 198, 255, 0.1) 1px, transparent 1px);
133
- background-size: 30px 30px;
134
- }
135
-
136
- .pixel-corners {
137
- clip-path: polygon(
138
- 0% 10px, 10px 10px, 10px 0%, calc(100% - 10px) 0%,
139
- calc(100% - 10px) 10px, 100% 10px, 100% calc(100% - 10px),
140
- calc(100% - 10px) calc(100% - 10px), calc(100% - 10px) 100%,
141
- 10px 100%, 10px calc(100% - 10px), 0% calc(100% - 10px)
142
- );
143
- }
144
-
145
- .fluffy-mascot {
146
- animation: bounce 2s infinite cubic-bezier(0.28, 0.84, 0.42, 1.1);
147
- filter: drop-shadow(0 10px 15px rgba(255, 110, 199, 0.4));
148
- }
149
-
150
- @keyframes bounce {
151
- 0%, 100% { transform: translateY(0) rotate(-2deg); }
152
- 25% { transform: translateY(-30px) rotate(2deg); }
153
- 50% { transform: translateY(-10px) rotate(-2deg); }
154
- 75% { transform: translateY(-20px) rotate(2deg); }
155
- }
156
-
157
- .glow {
158
- animation: glow 2s infinite alternate;
159
- }
160
-
161
- @keyframes glow {
162
- from { filter: drop-shadow(0 0 5px currentColor); }
163
- to { filter: drop-shadow(0 0 20px currentColor); }
164
- }
165
- .floating-hearts {
166
- position: absolute;
167
- width: 100%;
168
- height: 100%;
169
- top: 0;
170
- left: 0;
171
- pointer-events: none;
172
- z-index: -1;
173
- }
174
- .heart {
175
- position: absolute;
176
- color: rgba(255, 110, 199, 0.6);
177
- animation: float-up 15s linear infinite;
178
- filter: drop-shadow(0 0 5px rgba(255, 110, 199, 0.4));
179
- }
180
- @keyframes float-up {
181
- 0% { transform: translateY(100vh) translateX(0) scale(0.5); opacity: 0; }
182
- 10% { opacity: 0.8; }
183
- 90% { opacity: 0.8; }
184
- 100% { transform: translateY(-100px) translateX(50px) scale(1.2); opacity: 0; }
185
- }
186
- </style>
187
  </head>
188
- <body class="cyber-grid min-h-screen">
189
- <!-- Floating Clouds Background -->
190
- <div class="floating-clouds">
191
- <div class="cloud w-64 h-64 bg-pink-200 top-20 left-10" style="animation-delay: 0s"></div>
192
- <div class="cloud w-80 h-80 bg-blue-200 top-40 right-20" style="animation-delay: 1s"></div>
193
- <div class="cloud w-96 h-96 bg-purple-200 bottom-20 left-1/4" style="animation-delay: 2s"></div>
194
- <div class="cloud w-72 h-72 bg-pink-200 bottom-40 right-1/3" style="animation-delay: 3s"></div>
195
- <div class="cloud w-60 h-60 bg-blue-200 top-1/4 right-1/3" style="animation-delay: 4s"></div>
196
- <div class="cloud w-88 h-88 bg-purple-200 bottom-1/3 left-1/5" style="animation-delay: 5s"></div>
197
- <div class="cloud w-100 h-100 bg-pink-200 top-1/3 left-1/2" style="animation-delay: 6s"></div>
 
 
 
 
 
 
 
 
 
 
 
 
198
  </div>
199
-
200
- <!-- Floating Hearts -->
201
- <div class="floating-hearts" id="hearts-container"></div>
202
-
203
- <!-- Navigation -->
204
- <nav class="container mx-auto px-6 py-4 flex justify-between items-center">
205
- <div class="flex items-center space-x-2">
206
- <i class="fas fa-cloud text-3xl pink-neon glow"></i>
207
- <span class="cyber-title text-2xl">CyberFluff</span>
208
- </div>
209
- <div class="hidden md:flex space-x-8">
210
- <a href="#" class="text-white hover:text-pink-400 transition">Home</a>
211
- <a href="#" class="text-white hover:text-blue-400 transition">Features</a>
212
- <a href="#" class="text-white hover:text-purple-400 transition">Products</a>
213
- <a href="#" class="text-white hover:text-pink-400 transition">About</a>
214
- <a href="#" class="text-white hover:text-blue-400 transition">Contact</a>
215
- </div>
216
- <button class="md:hidden text-white">
217
- <i class="fas fa-bars text-2xl"></i>
218
- </button>
219
- </nav>
220
-
221
- <!-- Hero Section -->
222
- <section class="container mx-auto px-6 py-20 md:py-32 flex flex-col md:flex-row items-center">
223
- <div class="md:w-1/2 mb-12 md:mb-0">
224
- <h1 class="cyber-title text-4xl md:text-6xl mb-6 leading-tight">
225
- <span class="pink-neon">Super</span> <span class="blue-neon">Fluffy</span>
226
- <span class="purple-neon">Cyber</span> <span class="text-white">Experience</span>
227
- </h1>
228
- <p class="text-xl mb-8 text-gray-300">
229
- Where cutting-edge technology meets adorable fluffiness.
230
- Our cyber-fluff fusion will melt your heart while blowing your mind!
231
- </p>
232
- <div class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
233
- <button class="fluffy-btn text-white">
234
- <i class="fas fa-rocket mr-2"></i> Launch Now
235
- </button>
236
- <button class="px-8 py-3 border-2 border-pink-500 text-pink-400 rounded-full hover:bg-pink-500 hover:text-white transition">
237
- <i class="fas fa-info-circle mr-2"></i> Learn More
238
- </button>
239
- </div>
240
- </div>
241
- <div class="md:w-1/2 flex justify-center">
242
- <div class="relative">
243
- <div class="w-64 h-64 md:w-80 md:h-80 bg-gradient-to-br from-pink-500 to-blue-500 rounded-full opacity-20 blur-3xl absolute -z-10 top-10 left-10"></div>
244
- <img src="https://i.imgur.com/JQ9wXWn.png" alt="Cyber Fluffy Mascot" class="fluffy-mascot w-64 h-64 md:w-80 md:h-80">
245
- <div class="absolute -bottom-10 -right-10 w-32 h-32 bg-purple-500 rounded-full opacity-20 blur-2xl -z-10"></div>
246
- </div>
247
- </div>
248
- </section>
249
-
250
- <!-- Features Section -->
251
- <section class="container mx-auto px-6 py-20">
252
- <div class="text-center mb-16">
253
- <h2 class="cyber-title text-3xl md:text-4xl mb-4">
254
- <span class="pink-neon">Fluffy</span> <span class="blue-neon">Cyber</span> <span class="text-white">Features</span>
255
- </h2>
256
- <p class="text-xl text-gray-400 max-w-3xl mx-auto">
257
- Our unique blend of technology and cuteness provides an experience like no other
258
- </p>
259
- </div>
260
-
261
- <div class="grid grid-cols-1 md:grid-cols-3 gap-8">
262
- <!-- Feature 1 -->
263
- <div class="fluffy-card p-8">
264
- <div class="w-16 h-16 bg-pink-500 bg-opacity-20 rounded-full flex items-center justify-center mb-6">
265
- <i class="fas fa-cloud text-3xl pink-neon glow"></i>
266
- </div>
267
- <h3 class="text-2xl font-bold mb-4 neon-text pink-neon">Cloud Fluff</h3>
268
- <p class="text-gray-400">
269
- Ultra-soft cloud storage that feels like hugging a plushie. Secure, fast, and adorable.
270
- </p>
271
- </div>
272
-
273
- <!-- Feature 2 -->
274
- <div class="fluffy-card p-8">
275
- <div class="w-16 h-16 bg-blue-500 bg-opacity-20 rounded-full flex items-center justify-center mb-6">
276
- <i class="fas fa-shield-alt text-3xl blue-neon glow"></i>
277
- </div>
278
- <h3 class="text-2xl font-bold mb-4 neon-text blue-neon">Cuddly Security</h3>
279
- <p class="text-gray-400">
280
- Protection so warm and fuzzy, hackers won't want to break in. They'll just ask nicely.
281
- </p>
282
- </div>
283
-
284
- <!-- Feature 3 -->
285
- <div class="fluffy-card p-8">
286
- <div class="w-16 h-16 bg-purple-500 bg-opacity-20 rounded-full flex items-center justify-center mb-6">
287
- <i class="fas fa-bolt text-3xl purple-neon glow"></i>
288
- </div>
289
- <h3 class="text-2xl font-bold mb-4 neon-text purple-neon">Lightning Fluff</h3>
290
- <p class="text-gray-400">
291
- Blazing fast speeds with the softest landing. Like a bunny on a rocket.
292
- </p>
293
- </div>
294
  </div>
295
- </section>
296
-
297
- <!-- Products Section -->
298
- <section class="container mx-auto px-6 py-20 bg-gradient-to-r from-purple-900 to-blue-900 bg-opacity-30 rounded-3xl my-12">
299
- <div class="text-center mb-16">
300
- <h2 class="cyber-title text-3xl md:text-4xl mb-4">
301
- <span class="pink-neon">Our</span> <span class="blue-neon">Fluffy</span> <span class="text-white">Products</span>
302
- </h2>
303
- <p class="text-xl text-gray-300 max-w-3xl mx-auto">
304
- Choose your perfect blend of tech and tenderness
305
- </p>
306
  </div>
307
-
308
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
309
- <!-- Product 1 -->
310
- <div class="fluffy-card p-8 pixel-corners">
311
- <div class="text-center mb-6">
312
- <div class="w-24 h-24 bg-pink-500 bg-opacity-20 rounded-full flex items-center justify-center mx-auto mb-4">
313
- <i class="fas fa-baby text-4xl pink-neon"></i>
314
- </div>
315
- <h3 class="text-2xl font-bold mb-2 neon-text pink-neon">Fluff Lite</h3>
316
- <p class="text-gray-400">For beginners</p>
317
- </div>
318
- <ul class="space-y-3 mb-8">
319
- <li class="flex items-center">
320
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
321
- <span>Basic cloud storage</span>
322
- </li>
323
- <li class="flex items-center">
324
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
325
- <span>1 Fluffy mascot</span>
326
- </li>
327
- <li class="flex items-center">
328
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
329
- <span>Pastel security</span>
330
- </li>
331
- </ul>
332
- <div class="text-center">
333
- <p class="text-3xl font-bold mb-4">$9.99<span class="text-sm text-gray-400">/month</span></p>
334
- <button class="fluffy-btn w-full">Get Fluffy</button>
335
- </div>
336
- </div>
337
-
338
- <!-- Product 2 -->
339
- <div class="fluffy-card p-8 pixel-corners transform scale-105 relative">
340
- <div class="absolute top-0 right-0 bg-pink-500 text-white px-4 py-1 text-sm font-bold rounded-bl-lg">
341
- POPULAR
342
- </div>
343
- <div class="text-center mb-6">
344
- <div class="w-24 h-24 bg-blue-500 bg-opacity-20 rounded-full flex items-center justify-center mx-auto mb-4">
345
- <i class="fas fa-cat text-4xl blue-neon"></i>
346
- </div>
347
- <h3 class="text-2xl font-bold mb-2 neon-text blue-neon">Fluff Pro</h3>
348
- <p class="text-gray-400">For enthusiasts</p>
349
- </div>
350
- <ul class="space-y-3 mb-8">
351
- <li class="flex items-center">
352
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
353
- <span>Advanced cloud storage</span>
354
- </li>
355
- <li class="flex items-center">
356
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
357
- <span>3 Fluffy mascots</span>
358
- </li>
359
- <li class="flex items-center">
360
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
361
- <span>Neon security</span>
362
- </li>
363
- <li class="flex items-center">
364
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
365
- <span>Priority support</span>
366
- </li>
367
- </ul>
368
- <div class="text-center">
369
- <p class="text-3xl font-bold mb-4">$19.99<span class="text-sm text-gray-400">/month</span></p>
370
- <button class="fluffy-btn w-full">Get Fluffier</button>
371
- </div>
372
- </div>
373
-
374
- <!-- Product 3 -->
375
- <div class="fluffy-card p-8 pixel-corners">
376
- <div class="text-center mb-6">
377
- <div class="w-24 h-24 bg-purple-500 bg-opacity-20 rounded-full flex items-center justify-center mx-auto mb-4">
378
- <i class="fas fa-crown text-4xl purple-neon"></i>
379
- </div>
380
- <h3 class="text-2xl font-bold mb-2 neon-text purple-neon">Fluff Supreme</h3>
381
- <p class="text-gray-400">For connoisseurs</p>
382
- </div>
383
- <ul class="space-y-3 mb-8">
384
- <li class="flex items-center">
385
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
386
- <span>Unlimited cloud storage</span>
387
- </li>
388
- <li class="flex items-center">
389
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
390
- <span>10 Fluffy mascots</span>
391
- </li>
392
- <li class="flex items-center">
393
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
394
- <span>Holographic security</span>
395
- </li>
396
- <li class="flex items-center">
397
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
398
- <span>VIP support</span>
399
- </li>
400
- <li class="flex items-center">
401
- <i class="fas fa-check-circle text-green-400 mr-2"></i>
402
- <span>Fluffy merch</span>
403
- </li>
404
- </ul>
405
- <div class="text-center">
406
- <p class="text-3xl font-bold mb-4">$49.99<span class="text-sm text-gray-400">/month</span></p>
407
- <button class="fluffy-btn w-full">Get Fluffiest</button>
408
- </div>
409
- </div>
410
  </div>
411
- </section>
412
-
413
- <!-- Testimonials -->
414
- <section class="container mx-auto px-6 py-20">
415
- <div class="text-center mb-16">
416
- <h2 class="cyber-title text-3xl md:text-4xl mb-4">
417
- <span class="pink-neon">What</span> <span class="blue-neon">People</span> <span class="text-white">Say</span>
418
- </h2>
419
- <p class="text-xl text-gray-400 max-w-3xl mx-auto">
420
- Don't just take our word for it - hear from our fluffy family
421
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  </div>
423
-
424
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
425
- <!-- Testimonial 1 -->
426
- <div class="fluffy-card p-8">
427
- <div class="flex items-center mb-6">
428
- <img src="https://i.imgur.com/JQ9wXWn.png" alt="User" class="w-16 h-16 rounded-full border-2 border-pink-500">
429
- <div class="ml-4">
430
- <h4 class="text-xl font-bold">Sarah K.</h4>
431
- <p class="text-pink-400">Digital Artist</p>
432
- </div>
433
- </div>
434
- <p class="text-gray-300 italic">
435
- "I've never felt so secure and comforted by technology before. My files are safe, and my heart is full!"
436
- </p>
437
- <div class="mt-4 text-yellow-400">
438
- <i class="fas fa-star"></i>
439
- <i class="fas fa-star"></i>
440
- <i class="fas fa-star"></i>
441
- <i class="fas fa-star"></i>
442
- <i class="fas fa-star"></i>
443
- </div>
444
- </div>
445
-
446
- <!-- Testimonial 2 -->
447
- <div class="fluffy-card p-8">
448
- <div class="flex items-center mb-6">
449
- <img src="https://i.imgur.com/JQ9wXWn.png" alt="User" class="w-16 h-16 rounded-full border-2 border-blue-500">
450
- <div class="ml-4">
451
- <h4 class="text-xl font-bold">Mark T.</h4>
452
- <p class="text-blue-400">Game Developer</p>
453
- </div>
454
- </div>
455
- <p class="text-gray-300 italic">
456
- "The perfect balance of power and playfulness. My productivity has never been so enjoyable!"
457
- </p>
458
- <div class="mt-4 text-yellow-400">
459
- <i class="fas fa-star"></i>
460
- <i class="fas fa-star"></i>
461
- <i class="fas fa-star"></i>
462
- <i class="fas fa-star"></i>
463
- <i class="fas fa-star-half-alt"></i>
464
- </div>
465
- </div>
466
-
467
- <!-- Testimonial 3 -->
468
- <div class="fluffy-card p-8">
469
- <div class="flex items-center mb-6">
470
- <img src="https://i.imgur.com/JQ9wXWn.png" alt="User" class="w-16 h-16 rounded-full border-2 border-purple-500">
471
- <div class="ml-4">
472
- <h4 class="text-xl font-bold">Lisa M.</h4>
473
- <p class="text-purple-400">Streamer</p>
474
- </div>
475
- </div>
476
- <p class="text-gray-300 italic">
477
- "My viewers love the fluffy mascots that pop up during streams. It's like having digital pets that protect my content!"
478
- </p>
479
- <div class="mt-4 text-yellow-400">
480
- <i class="fas fa-star"></i>
481
- <i class="fas fa-star"></i>
482
- <i class="fas fa-star"></i>
483
- <i class="fas fa-star"></i>
484
- <i class="fas fa-star"></i>
485
- </div>
486
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  </div>
488
- </section>
489
-
490
- <!-- CTA Section -->
491
- <section class="container mx-auto px-6 py-20 text-center">
492
- <div class="fluffy-card p-12 bg-gradient-to-r from-pink-900 to-purple-900 bg-opacity-50 rounded-3xl">
493
- <h2 class="cyber-title text-3xl md:text-5xl mb-6">
494
- <span class="pink-neon">Ready</span> <span class="blue-neon">to</span> <span class="text-white">Embrace</span> <span class="purple-neon">the</span> <span class="pink-neon">Fluff?</span>
495
- </h2>
496
- <p class="text-xl text-gray-300 max-w-3xl mx-auto mb-8">
497
- Join thousands of happy users who've discovered the joy of cyber-fluff fusion
498
- </p>
499
- <button class="fluffy-btn text-white px-12 py-4 text-lg">
500
- <i class="fas fa-paw mr-2"></i> Start Your Fluffy Journey
501
- </button>
502
  </div>
503
- </section>
504
-
505
- <!-- Footer -->
506
- <footer class="bg-black bg-opacity-50 py-12">
507
- <div class="container mx-auto px-6">
508
- <div class="flex flex-col md:flex-row justify-between items-center">
509
- <div class="flex items-center space-x-2 mb-6 md:mb-0">
510
- <i class="fas fa-cloud text-3xl pink-neon glow"></i>
511
- <span class="cyber-title text-2xl">CyberFluff</span>
512
- </div>
513
-
514
- <div class="flex space-x-6 mb-6 md:mb-0">
515
- <a href="#" class="text-gray-400 hover:text-pink-400 transition">
516
- <i class="fab fa-twitter text-2xl"></i>
517
- </a>
518
- <a href="#" class="text-gray-400 hover:text-blue-400 transition">
519
- <i class="fab fa-discord text-2xl"></i>
520
- </a>
521
- <a href="#" class="text-gray-400 hover:text-purple-400 transition">
522
- <i class="fab fa-instagram text-2xl"></i>
523
- </a>
524
- <a href="#" class="text-gray-400 hover:text-pink-400 transition">
525
- <i class="fab fa-tiktok text-2xl"></i>
526
- </a>
527
- </div>
528
-
529
- <div class="text-gray-500 text-sm">
530
- &copy; 2023 CyberFluff. All rights reserved. Made with <i class="fas fa-heart text-pink-500"></i> and fluff.
531
- </div>
532
- </div>
533
  </div>
534
- </footer>
535
-
536
- <!-- Floating Mascot Button -->
537
- <button class="fixed bottom-8 right-8 w-16 h-16 bg-pink-500 rounded-full flex items-center justify-center shadow-lg hover:bg-purple-500 transition z-50">
538
- <i class="fas fa-paw text-2xl text-white"></i>
539
- </button>
540
-
541
- <script>
542
- // Create floating hearts
543
- const heartsContainer = document.getElementById('hearts-container');
544
- for (let i = 0; i < 15; i++) {
545
- const heart = document.createElement('div');
546
- heart.className = 'heart';
547
- heart.innerHTML = '<i class="fas fa-heart"></i>';
548
- heart.style.left = Math.random() * 100 + 'vw';
549
- heart.style.fontSize = (Math.random() * 20 + 10) + 'px';
550
- heart.style.animationDuration = (Math.random() * 10 + 10) + 's';
551
- heart.style.animationDelay = Math.random() * 15 + 's';
552
- heartsContainer.appendChild(heart);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554
 
555
- // Simple animation for the mascot
556
- document.addEventListener('DOMContentLoaded', function() {
557
- const mascot = document.querySelector('.fluffy-mascot');
558
-
559
- function animateMascot() {
560
- mascot.style.transform = 'translateY(-20px)';
561
- setTimeout(() => {
562
- mascot.style.transform = 'translateY(0)';
563
- }, 1000);
564
- }
565
-
566
- setInterval(animateMascot, 3000);
567
-
568
- // Button hover effect
569
- const buttons = document.querySelectorAll('.fluffy-btn');
570
- buttons.forEach(button => {
571
- button.addEventListener('mouseenter', () => {
572
- button.querySelector('i').classList.add('fa-bounce');
573
- });
574
- button.addEventListener('mouseleave', () => {
575
- button.querySelector('i').classList.remove('fa-bounce');
576
- });
577
- });
578
- });
579
- </script>
580
- <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=flausch/fluffy-landing" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
581
- </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="de">
3
  <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
6
+ <title>Visual Synth Psychedelic One-File App</title>
7
+ <style>
8
+ :root{
9
+ --bg:#0a0d12; --panel:#0f1420; --acc:#69f; --acc2:#f0f; --text:#dfe7ff;
10
+ --muted:#7d8aad; --glow:0 0 12px rgba(102,153,255,.5);
11
+ }
12
+ html,body{margin:0;padding:0;background:var(--bg);color:var(--text);font-family:Inter,system-ui,Segoe UI,Arial,sans-serif;height:100%;}
13
+ #wrap{position:fixed;inset:0;display:flex;overflow:hidden;}
14
+ canvas{position:absolute;inset:0;width:100%;height:100%;display:block;background:#000;}
15
+ #ui{
16
+ position:relative;z-index:10;width:360px;max-width:80vw;height:100%;
17
+ background:linear-gradient(180deg,rgba(10,13,18,.96),rgba(10,13,18,.92));
18
+ border-right:1px solid #1c2333; box-shadow:var(--glow); overflow:auto; padding:14px 14px 100px 14px;
19
+ }
20
+ #title{display:flex;align-items:center;gap:10px;margin-bottom:8px;}
21
+ #title h1{font-size:16px;margin:0;letter-spacing:.4px;font-weight:700;}
22
+ #status{font-size:11px;color:var(--muted); margin-left:auto}
23
+ .btnrow{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0 14px}
24
+ button,select{
25
+ background:#141a2a;color:#dfe7ff;border:1px solid #25304a;border-radius:8px;padding:8px 10px;cursor:pointer;
26
+ transition:.2s; box-shadow:0 0 0 0 rgba(0,0,0,0);
27
+ }
28
+ button:hover{border-color:#3b4f7a;box-shadow:0 0 0 2px rgba(59,79,122,.15)}
29
+ button.primary{background:linear-gradient(180deg,#1a2442,#12192c);border-color:#2a3960}
30
+ .section{background:#0f1420;border:1px solid #1b2233;border-radius:12px;margin:8px 0;padding:10px}
31
+ .section h3{margin:0 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.12em;color:#9bb1ff}
32
+ .grid{display:grid;grid-template-columns:1fr 64px;gap:6px 10px;align-items:center}
33
+ .row{display:flex;gap:8px;align-items:center}
34
+ label{font-size:12px;color:#b9c6ff}
35
+ .val{font-size:11px;color:#9fb2ff;text-align:right}
36
+ input[type=range]{
37
+ width:100%; height:26px; background:transparent; -webkit-appearance:none; appearance:none;
38
+ }
39
+ input[type=range]::-webkit-slider-runnable-track{
40
+ height:6px;border-radius:99px;background:linear-gradient(90deg,#263357,#1d2745);border:1px solid #2b3b61;
41
+ }
42
+ input[type=range]::-webkit-slider-thumb{
43
+ -webkit-appearance:none; width:16px;height:16px;border-radius:50%;
44
+ background:radial-gradient(circle at 30% 30%,#9cf,#58f);border:1px solid #2b3b61;margin-top:-5px;box-shadow:0 0 0 2px rgba(102,153,255,.15);
45
+ }
46
+ .small{font-size:11px;color:#8ea0cf}
47
+ .split{display:grid;grid-template-columns:1fr 1fr;gap:8px}
48
+ .muted{color:#8ea0cf}
49
+ .sticky{position:sticky;top:0;background:linear-gradient(180deg,rgba(10,13,18,1),rgba(10,13,18,.9));padding-bottom:8px;margin-bottom:8px;z-index:5}
50
+ #footer{position:fixed;left:0;right:0;bottom:0;padding:6px 12px;background:linear-gradient(180deg,rgba(10,13,18,.1),rgba(10,13,18,.7));display:flex;gap:10px;align-items:center;justify-content:space-between;z-index:9;border-top:1px solid #1b2233}
51
+ #footer .hint{font-size:11px;color:#8ea0cf}
52
+ .kbd{background:#111728;border:1px solid #2b3b61;border-radius:6px;padding:2px 6px;color:#9fb2ff;margin:0 2px}
53
+ .badge{background:rgba(108,80,255,.12);border:1px solid rgba(108,80,255,.45);padding:2px 6px;border-radius:6px;color:#c7b9ff;font-size:11px}
54
+ .two-col{display:grid;grid-template-columns:1fr 1fr;gap:10px}
55
+ .hr{height:1px;background:#1b2233;margin:8px 0}
56
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </head>
58
+ <body>
59
+ <div id="wrap">
60
+ <canvas id="c"></canvas>
61
+ <div id="ui">
62
+ <div class="sticky">
63
+ <div id="title">
64
+ <h1>Visual Synth</h1>
65
+ <span class="badge">psychedelic</span>
66
+ <div id="status">FPS: <span id="fps">–</span></div>
67
+ </div>
68
+ <div class="btnrow">
69
+ <button id="btnFull" title="Fullscreen (F)">Fullscreen</button>
70
+ <button id="btnRandom" class="primary" title="Randomize (R)">Randomize</button>
71
+ <select id="presetSel" title="Presets (1–6)"></select>
72
+ <button id="btnSave">Preset speichern</button>
73
+ <button id="btnLoad">Preset laden</button>
74
+ <button id="btnRecord" title="Canvas aufnehmen">Aufnahme</button>
75
+ </div>
76
+ <div class="row">
77
+ <button id="btnMic" title="Mikrofon (M)">Mic: Aus</button>
78
+ <div class="small muted" id="audioInfo">Audio: –</div>
79
+ </div>
80
  </div>
81
+
82
+ <div class="section">
83
+ <h3>Global & Timing</h3>
84
+ <div class="grid">
85
+ <label for="bpm">BPM</label><div><input id="bpm" type="range" min="40" max="200" step="1"><div class="val" id="bpmVal"></div></div>
86
+ <label for="timeScale">Zeit-Skala</label><div><input id="timeScale" type="range" min="0.1" max="3" step="0.01"><div class="val" id="timeScaleVal"></div></div>
87
+ <label for="seed">Seed</label><div><input id="seed" type="range" min="0" max="999" step="1"><div class="val" id="seedVal"></div></div>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="section">
92
+ <h3>Pattern & Warp</h3>
93
+ <div class="two-col">
94
+ <div>
95
+ <label>Pattern A</label>
96
+ <select id="p1Type">
97
+ <option value="0">Rings</option>
98
+ <option value="1">Stripes</option>
99
+ <option value="2">Checker</option>
100
+ <option value="3">Swirl</option>
101
+ <option value="4">FBM</option>
102
+ </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  </div>
104
+ <div>
105
+ <label>Pattern B</label>
106
+ <select id="p2Type">
107
+ <option value="0">Rings</option>
108
+ <option value="1">Stripes</option>
109
+ <option value="2">Checker</option>
110
+ <option value="3">Swirl</option>
111
+ <option value="4">FBM</option>
112
+ </select>
 
 
113
  </div>
114
+ </div>
115
+ <div class="grid">
116
+ <label for="p1Scale">A Scale</label><div><input id="p1Scale" type="range" min="0.2" max="8" step="0.01"><div class="val" id="p1ScaleVal"></div></div>
117
+ <label for="p2Scale">B Scale</label><div><input id="p2Scale" type="range" min="0.2" max="8" step="0.01"><div class="val" id="p2ScaleVal"></div></div>
118
+
119
+ <label for="p1Speed">A Speed</label><div><input id="p1Speed" type="range" min="-4" max="4" step="0.01"><div class="val" id="p1SpeedVal"></div></div>
120
+ <label for="p2Speed">B Speed</label><div><input id="p2Speed" type="range" min="-4" max="4" step="0.01"><div class="val" id="p2SpeedVal"></div></div>
121
+
122
+ <label for="mix">Mix</label><div><input id="mix" type="range" min="0" max="1" step="0.001"><div class="val" id="mixVal"></div></div>
123
+ <label for="blend">Blend Mode</label>
124
+ <div>
125
+ <select id="blend">
126
+ <option value="0">Linear</option>
127
+ <option value="1">Add</option>
128
+ <option value="2">Multiply</option>
129
+ <option value="3">Screen</option>
130
+ <option value="4">Difference</option>
131
+ </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  </div>
133
+
134
+ <label for="noiseScale">Noise Scale</label><div><input id="noiseScale" type="range" min="0.1" max="8" step="0.01"><div class="val" id="noiseScaleVal"></div></div>
135
+ <label for="warpAmount">Warp Amount</label><div><input id="warpAmount" type="range" min="0" max="2" step="0.001"><div class="val" id="warpAmountVal"></div></div>
136
+ <label for="warpSpeed">Warp Speed</label><div><input id="warpSpeed" type="range" min="-4" max="4" step="0.01"><div class="val" id="warpSpeedVal"></div></div>
137
+ </div>
138
+ </div>
139
+
140
+ <div class="section">
141
+ <h3>Kaleidoskop & Transform</h3>
142
+ <div class="grid">
143
+ <label for="slices">Slices</label><div><input id="slices" type="range" min="1" max="16" step="1"><div class="val" id="slicesVal"></div></div>
144
+ <label for="kAngle">Angle</label><div><input id="kAngle" type="range" min="-3.1416" max="3.1416" step="0.001"><div class="val" id="kAngleVal"></div></div>
145
+ <label for="mirror">Mirror</label><div><input id="mirror" type="range" min="0" max="1" step="1"><div class="val" id="mirrorVal"></div></div>
146
+
147
+ <label for="zoom">Zoom</label><div><input id="zoom" type="range" min="0.2" max="3" step="0.001"><div class="val" id="zoomVal"></div></div>
148
+ <label for="rotSpeed">Rotate Speed</label><div><input id="rotSpeed" type="range" min="-4" max="4" step="0.01"><div class="val" id="rotSpeedVal"></div></div>
149
+ <label for="panX">Pan X</label><div><input id="panX" type="range" min="-1" max="1" step="0.001"><div class="val" id="panXVal"></div></div>
150
+ <label for="panY">Pan Y</label><div><input id="panY" type="range" min="-1" max="1" step="0.001"><div class="val" id="panYVal"></div></div>
151
+ </div>
152
+ </div>
153
+
154
+ <div class="section">
155
+ <h3>Feedback & Post FX</h3>
156
+ <div class="grid">
157
+ <label for="fbAmt">Feedback Amount</label><div><input id="fbAmt" type="range" min="0" max="1" step="0.001"><div class="val" id="fbAmtVal"></div></div>
158
+ <label for="fbZoom">Feedback Zoom</label><div><input id="fbZoom" type="range" min="-0.2" max="0.2" step="0.001"><div class="val" id="fbZoomVal"></div></div>
159
+ <label for="fbRot">Feedback Rotate</label><div><input id="fbRot" type="range" min="-2" max="2" step="0.001"><div class="val" id="fbRotVal"></div></div>
160
+ <label for="fbBlend">Feedback Blend</label>
161
+ <div>
162
+ <select id="fbBlend">
163
+ <option value="0">Linear</option>
164
+ <option value="1">Add</option>
165
+ <option value="2">Multiply</option>
166
+ <option value="3">Screen</option>
167
+ <option value="4">Difference</option>
168
+ </select>
169
  </div>
170
+ <label for="blur">Blur</label><div><input id="blur" type="range" min="0" max="1" step="0.001"><div class="val" id="blurVal"></div></div>
171
+ <label for="chroma">Chromatic Aberration</label><div><input id="chroma" type="range" min="0" max="1" step="0.001"><div class="val" id="chromaVal"></div></div>
172
+ <label for="pixel">Pixelation</label><div><input id="pixel" type="range" min="0" max="1" step="0.001"><div class="val" id="pixelVal"></div></div>
173
+ <label for="vign">Vignette</label><div><input id="vign" type="range" min="0" max="1" step="0.001"><div class="val" id="vignVal"></div></div>
174
+ <label for="strobe">Strobe Speed</label><div><input id="strobe" type="range" min="0" max="20" step="0.01"><div class="val" id="strobeVal"></div></div>
175
+ <label for="strobeDuty">Strobe Duty</label><div><input id="strobeDuty" type="range" min="0.02" max="0.98" step="0.01"><div class="val" id="strobeDutyVal"></div></div>
176
+ </div>
177
+ </div>
178
+
179
+ <div class="section">
180
+ <h3>Farbe</h3>
181
+ <div class="grid">
182
+ <label>Palette</label>
183
+ <div><select id="palette">
184
+ <option value="0">Rainbow</option>
185
+ <option value="1">Neon</option>
186
+ <option value="2">Cyberpunk</option>
187
+ <option value="3">Heat</option>
188
+ <option value="4">Cool</option>
189
+ </select></div>
190
+
191
+ <label for="hueShift">Hue Shift</label><div><input id="hueShift" type="range" min="0" max="1" step="0.001"><div class="val" id="hueShiftVal"></div></div>
192
+ <label for="hueSpeed">Hue Speed</label><div><input id="hueSpeed" type="range" min="-1" max="1" step="0.001"><div class="val" id="hueSpeedVal"></div></div>
193
+ <label for="saturation">Saturation</label><div><input id="saturation" type="range" min="0" max="2" step="0.001"><div class="val" id="saturationVal"></div></div>
194
+ <label for="contrast">Contrast</label><div><input id="contrast" type="range" min="0.2" max="3" step="0.001"><div class="val" id="contrastVal"></div></div>
195
+ <label for="brightness">Brightness</label><div><input id="brightness" type="range" min="0" max="3" step="0.001"><div class="val" id="brightnessVal"></div></div>
196
+ <label for="gamma">Gamma</label><div><input id="gamma" type="range" min="0.2" max="3" step="0.001"><div class="val" id="gammaVal"></div></div>
197
+ </div>
198
+ </div>
199
+
200
+ <div class="section">
201
+ <h3>LFOs (Modulation)</h3>
202
+ <div class="small muted">Jeder LFO kann 2 Ziele modulieren. Depth ist bipolar (−/+).</div>
203
+ <div id="lfos"></div>
204
+ </div>
205
+
206
+ <div class="section">
207
+ <h3>Audio-Reaktiv</h3>
208
+ <div class="grid">
209
+ <label for="audioAmt">Global Amount</label><div><input id="audioAmt" type="range" min="0" max="2" step="0.001"><div class="val" id="audioAmtVal"></div></div>
210
+ <label for="audioHue">→ Hue</label><div><input id="audioHue" type="range" min="0" max="1" step="0.001"><div class="val" id="audioHueVal"></div></div>
211
+ <label for="audioZoom">→ Zoom</label><div><input id="audioZoom" type="range" min="0" max="1" step="0.001"><div class="val" id="audioZoomVal"></div></div>
212
+ <label for="audioWarp">→ Warp</label><div><input id="audioWarp" type="range" min="0" max="1" step="0.001"><div class="val" id="audioWarpVal"></div></div>
213
+ <label for="audioSlices">→ Slices</label><div><input id="audioSlices" type="range" min="0" max="1" step="0.001"><div class="val" id="audioSlicesVal"></div></div>
214
+ <label for="audioSmooth">Smoothing</label><div><input id="audioSmooth" type="range" min="0" max="0.99" step="0.001"><div class="val" id="audioSmoothVal"></div></div>
215
+ </div>
216
+ </div>
217
+
218
+ <div class="section">
219
+ <h3>Presets & JSON</h3>
220
+ <div class="small muted">Du kannst die aktuellen Einstellungen als JSON speichern/laden.</div>
221
+ <textarea id="json" rows="6" style="width:100%;background:#0b1020;color:#dfe7ff;border:1px solid #1b2233;border-radius:8px;padding:8px"></textarea>
222
+ <div class="btnrow">
223
+ <button id="btnToJSON">→ JSON</button>
224
+ <button id="btnFromJSON">← Aus JSON</button>
225
+ </div>
226
+ </div>
227
+
228
+ </div>
229
+ </div>
230
+
231
+ <div id="footer">
232
+ <div class="hint">Shortcuts: <span class="kbd">F</span> Fullscreen • <span class="kbd">R</span> Random • <span class="kbd">1–6</span> Presets • <span class="kbd">M</span> Mic</div>
233
+ <div class="hint">Made for trippy visuals 🎨🔮</div>
234
+ </div>
235
+
236
+ <script>
237
+ ;(() => {
238
+ "use strict";
239
+
240
+ const canvas = document.getElementById('c');
241
+ const gl = canvas.getContext('webgl', {antialias:false, depth:false, stencil:false, premultipliedAlpha:false, preserveDrawingBuffer:true});
242
+ if(!gl){ alert('WebGL wird benötigt.'); return; }
243
+
244
+ // Shaders
245
+ const vertSrc = `
246
+ attribute vec2 a_pos;
247
+ varying vec2 v_uv;
248
+ void main(){
249
+ v_uv = a_pos*0.5 + 0.5;
250
+ gl_Position = vec4(a_pos,0.0,1.0);
251
+ }`;
252
+
253
+ const fragSrc = `
254
+ precision mediump float;
255
+ varying vec2 v_uv;
256
+ uniform vec2 u_res;
257
+ uniform float u_time;
258
+ uniform float u_bpm;
259
+ uniform float u_seed;
260
+
261
+ uniform sampler2D u_prev;
262
+
263
+ // Pattern uniforms
264
+ uniform float u_p1Type;
265
+ uniform float u_p2Type;
266
+ uniform float u_p1Scale;
267
+ uniform float u_p2Scale;
268
+ uniform float u_p1Speed;
269
+ uniform float u_p2Speed;
270
+ uniform float u_mix;
271
+ uniform float u_blend;
272
+
273
+ // Warp
274
+ uniform float u_noiseScale;
275
+ uniform float u_warpAmt;
276
+ uniform float u_warpSpeed;
277
+
278
+ // Kaleidoscope & Transform
279
+ uniform float u_slices;
280
+ uniform float u_kAngle;
281
+ uniform float u_mirror;
282
+ uniform float u_zoom;
283
+ uniform float u_rotSpeed;
284
+ uniform float u_panX;
285
+ uniform float u_panY;
286
+
287
+ // Feedback & FX
288
+ uniform float u_fbAmt;
289
+ uniform float u_fbZoom;
290
+ uniform float u_fbRot;
291
+ uniform float u_fbBlend;
292
+ uniform float u_blur;
293
+ uniform float u_chroma;
294
+ uniform float u_pixel; // pixel size in px
295
+ uniform float u_vign;
296
+ uniform float u_strobe;
297
+ uniform float u_strobeDuty;
298
+
299
+ // Color
300
+ uniform float u_palette;
301
+ uniform float u_hueShift;
302
+ uniform float u_hueSpeed;
303
+ uniform float u_saturation;
304
+ uniform float u_contrast;
305
+ uniform float u_brightness;
306
+ uniform float u_gamma;
307
+
308
+ // Audio (preprocessed)
309
+ uniform float u_audioLevel; // 0..1
310
+ uniform float u_audioBass;
311
+ uniform float u_audioMid;
312
+ uniform float u_audioHigh;
313
+
314
+ // Helpers
315
+ float hash21(vec2 p){
316
+ p = fract(p*vec2(123.34, 456.21));
317
+ p += dot(p, p+45.32);
318
+ return fract(p.x * p.y);
319
+ }
320
+ vec2 hash22(vec2 p){
321
+ float n = sin(dot(p, vec2(41.0,289.0)));
322
+ return fract(vec2(262144.0,32768.0)*n);
323
+ }
324
+ float noise(vec2 p){
325
+ vec2 i = floor(p);
326
+ vec2 f = fract(p);
327
+ vec2 u = f*f*(3.0-2.0*f);
328
+ float a = hash21(i+vec2(0,0));
329
+ float b = hash21(i+vec2(1,0));
330
+ float c = hash21(i+vec2(0,1));
331
+ float d = hash21(i+vec2(1,1));
332
+ return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
333
+ }
334
+ float fbm(vec2 p){
335
+ float a = 0.0;
336
+ float amp = 0.5;
337
+ float f = 1.0;
338
+ for(int i=0;i<5;i++){
339
+ a += amp * noise(p*f);
340
+ f *= 2.0;
341
+ amp *= 0.5;
342
+ }
343
+ return a;
344
+ }
345
+ mat2 rot(float a){ float c=cos(a), s=sin(a); return mat2(c,-s,s,c); }
346
+
347
+ // Patterns return 0..1
348
+ float patRings(vec2 p, float t){
349
+ float r = length(p);
350
+ return 0.5 + 0.5*sin(10.0*r + t);
351
+ }
352
+ float patStripes(vec2 p, float t){
353
+ return 0.5 + 0.5*sin(10.0*p.x + t);
354
+ }
355
+ float patChecker(vec2 p, float t){
356
+ float s = sin(6.0*p.x + t)*sin(6.0*p.y - t);
357
+ return 0.5 + 0.5*sign(s);
358
+ }
359
+ float patSwirl(vec2 p, float t){
360
+ float a = atan(p.y, p.x);
361
+ float r = length(p);
362
+ return 0.5 + 0.5*sin(8.0*a + 12.0*r + t*0.7);
363
+ }
364
+ float patFBM(vec2 p, float t){
365
+ return fbm(p*1.2 + vec2(0.0, t*0.1));
366
+ }
367
+ float getPattern(float typeId, vec2 p, float scale, float speed, float t){
368
+ p *= scale;
369
+ float tt = t * speed;
370
+ if(typeId < 0.5) return patRings(p, tt);
371
+ if(typeId < 1.5) return patStripes(p, tt);
372
+ if(typeId < 2.5) return patChecker(p, tt);
373
+ if(typeId < 3.5) return patSwirl(p, tt);
374
+ return patFBM(p, tt);
375
+ }
376
+
377
+ vec3 hsv2rgb(vec3 c){
378
+ vec3 p = abs(fract(c.xxx + vec3(0.0, 2.0/3.0, 1.0/3.0))*6.0-3.0);
379
+ return c.z * mix(vec3(1.0), clamp(p-1.0,0.0,1.0), c.y);
380
+ }
381
+
382
+ vec3 paletteCosine(float t, int pal){
383
+ // a + b*cos(2pi*(c*t + d))
384
+ vec3 a,b,c,d;
385
+ if(pal==0){ // Rainbow
386
+ a=vec3(0.5,0.5,0.5); b=vec3(0.5,0.5,0.5); c=vec3(1.0,1.0,1.0); d=vec3(0.0,0.33,0.67);
387
+ } else if(pal==1){ // Neon
388
+ a=vec3(0.2,0.1,0.3); b=vec3(0.8,0.3,0.9); c=vec3(1.0,1.0,1.0); d=vec3(0.0,0.2,0.4);
389
+ } else if(pal==2){ // Cyberpunk
390
+ a=vec3(0.08,0.05,0.12); b=vec3(0.9,0.2,0.6); c=vec3(1.0,1.0,1.0); d=vec3(0.0,0.5,0.25);
391
+ } else if(pal==3){ // Heat
392
+ a=vec3(0.1,0.0,0.0); b=vec3(1.0,0.5,0.0); c=vec3(1.0,1.0,1.0); d=vec3(0.0,0.15,0.25);
393
+ } else { // Cool
394
+ a=vec3(0.05,0.08,0.15); b=vec3(0.4,0.7,1.0); c=vec3(1.0,1.0,1.0); d=vec3(0.1,0.3,0.5);
395
+ }
396
+ return a + b*cos(6.28318*(c*t + d));
397
+ }
398
+
399
+ vec3 applyPalette(float v, float hueShift, float hueSpeed, float t, int pal){
400
+ // Mix between cosine palette and HSV cycle
401
+ float h = fract(hueShift + v + t * hueSpeed);
402
+ vec3 rainbow = hsv2rgb(vec3(h, 1.0, 1.0));
403
+ vec3 cosPal = paletteCosine(fract(v + t*0.05), pal);
404
+ return mix(cosPal, rainbow, 0.35);
405
+ }
406
+
407
+ vec3 blendOp(vec3 a, vec3 b, float mode){
408
+ if(mode<0.5) return mix(a,b,0.5);
409
+ if(mode<1.5) return a + b;
410
+ if(mode<2.5) return a * b;
411
+ if(mode<3.5) return 1.0 - (1.0-a)*(1.0-b); // screen
412
+ return abs(a-b);
413
+ }
414
+
415
+ void main(){
416
+ vec2 uv = v_uv;
417
+ vec2 res = u_res;
418
+ vec2 p = (uv*2.0-1.0);
419
+ p.x *= res.x/res.y;
420
+
421
+ // Pixelation
422
+ if(u_pixel > 1.0){
423
+ vec2 pix = vec2(u_pixel);
424
+ vec2 q = (floor(gl_FragCoord.xy / pix) * pix + 0.5*pix)/res;
425
+ p = (q*2.0-1.0);
426
+ p.x *= res.x/res.y;
427
+ uv = q;
428
+ }
429
+
430
+ float t = u_time;
431
+
432
+ // Transform: pan, rotate, zoom
433
+ p -= vec2(u_panX, u_panY);
434
+ float rotA = t*u_rotSpeed;
435
+ p = rot(rotA) * p;
436
+ p /= u_zoom;
437
+
438
+ // Kaleidoscope
439
+ float slices = max(1.0, floor(u_slices + 0.5));
440
+ float seg = 6.2831853 / slices;
441
+ float ang = atan(p.y,p.x) + u_kAngle;
442
+ float rad = length(p);
443
+ ang = mod(ang, seg);
444
+ if(u_mirror > 0.5){
445
+ ang = abs(ang - seg*0.5);
446
+ }
447
+ p = vec2(cos(ang), sin(ang)) * rad;
448
+
449
+ // Domain warp
450
+ vec2 wp = p * u_noiseScale + vec2(u_seed*0.123, u_seed*0.917);
451
+ float n1 = fbm(wp + vec2(0.0, t*u_warpSpeed*0.3));
452
+ float n2 = fbm(wp.yx + vec2(t*u_warpSpeed*0.25, 0.0));
453
+ vec2 wv = vec2(n1, n2) - 0.5;
454
+ p += wv * u_warpAmt;
455
+
456
+ // Patterns
457
+ float a = getPattern(u_p1Type, p, u_p1Scale, u_p1Speed, t);
458
+ float b = getPattern(u_p2Type, p, u_p2Scale, u_p2Speed, t);
459
+ float mixLin = mix(a,b,u_mix);
460
+ float mixBlend;
461
+ {
462
+ vec3 ca = vec3(a);
463
+ vec3 cb = vec3(b);
464
+ vec3 cm = blendOp(ca, cb, u_blend);
465
+ mixBlend = cm.r; // use r channel equivalently
466
+ }
467
+ float v = mix(mixLin, mixBlend, 0.5);
468
+
469
+ // Color
470
+ int pal = int(floor(u_palette+0.5));
471
+ vec3 col = applyPalette(v, u_hueShift, u_hueSpeed, t, pal);
472
+
473
+ // Basic tonemapping-like shaping by v
474
+ col *= 0.7 + 0.6*v;
475
+
476
+ // Feedback sample
477
+ vec2 uvf = uv;
478
+ // Feedback zoom
479
+ uvf -= 0.5;
480
+ float fbz = 1.0 + u_fbZoom;
481
+ uvf = rot(u_fbRot) * (uvf / fbz);
482
+ uvf += 0.5;
483
+
484
+ // Blur sample from previous
485
+ vec2 px = 1.0 / res;
486
+ vec3 prev = texture2D(u_prev, uvf).rgb;
487
+ if(u_blur > 0.001){
488
+ float r = u_blur*3.0;
489
+ vec3 acc = vec3(0.0);
490
+ acc += texture2D(u_prev, uvf + vec2(-r, -r)*px).rgb;
491
+ acc += texture2D(u_prev, uvf + vec2( 0., -r)*px).rgb;
492
+ acc += texture2D(u_prev, uvf + vec2( r, -r)*px).rgb;
493
+ acc += texture2D(u_prev, uvf + vec2(-r, 0.)*px).rgb;
494
+ acc += texture2D(u_prev, uvf).rgb;
495
+ acc += texture2D(u_prev, uvf + vec2( r, 0.)*px).rgb;
496
+ acc += texture2D(u_prev, uvf + vec2(-r, r)*px).rgb;
497
+ acc += texture2D(u_prev, uvf + vec2( 0., r)*px).rgb;
498
+ acc += texture2D(u_prev, uvf + vec2( r, r)*px).rgb;
499
+ prev = mix(prev, acc/9.0, clamp(u_blur,0.0,1.0));
500
+ }
501
+
502
+ // Chromatic aberration
503
+ if(u_chroma > 0.001){
504
+ vec2 off = px * (1.0 + 20.0*u_chroma);
505
+ float rr = texture2D(u_prev, uvf + off).r;
506
+ float gg = texture2D(u_prev, uvf - off).g;
507
+ float bb = texture2D(u_prev, uvf + off.yx).b;
508
+ prev = mix(prev, vec3(rr,gg,bb), u_chroma);
509
+ }
510
+
511
+ // Combine new color and feedback
512
+ vec3 comb = blendOp(col, prev, u_fbBlend);
513
+ vec3 outc = mix(col, comb, u_fbAmt);
514
+
515
+ // Post: strobe
516
+ if(u_strobe > 0.001){
517
+ float ph = fract(t * u_strobe);
518
+ float gate = step(ph, u_strobeDuty);
519
+ outc = mix(outc, vec3(1.0), gate*0.9);
520
+ }
521
+
522
+ // Vignette
523
+ if(u_vign > 0.001){
524
+ float d = length((uv-0.5)*vec2(res.x/res.y,1.0));
525
+ float vig = smoothstep(0.9, 0.2, d);
526
+ outc *= mix(1.0, vig, u_vign);
527
+ }
528
+
529
+ // Color correction
530
+ // Saturation
531
+ float luma = dot(outc, vec3(0.299,0.587,0.114));
532
+ outc = mix(vec3(luma), outc, u_saturation);
533
+ // Contrast
534
+ outc = (outc - 0.5)*u_contrast + 0.5;
535
+ // Brightness
536
+ outc *= u_brightness;
537
+ // Gamma
538
+ outc = pow(max(outc,0.0), vec3(max(u_gamma, 0.0001)));
539
+
540
+ gl_FragColor = vec4(outc, 1.0);
541
+ }`;
542
+
543
+ const blitSrc = `
544
+ precision mediump float;
545
+ varying vec2 v_uv;
546
+ uniform sampler2D u_tex;
547
+ void main(){
548
+ gl_FragColor = texture2D(u_tex, v_uv);
549
+ }`;
550
+
551
+ function compile(type, src){
552
+ const s = gl.createShader(type);
553
+ gl.shaderSource(s, src);
554
+ gl.compileShader(s);
555
+ if(!gl.getShaderParameter(s, gl.COMPILE_STATUS)){
556
+ console.error(gl.getShaderInfoLog(s));
557
+ throw new Error('Shader compile error');
558
+ }
559
+ return s;
560
+ }
561
+ function program(vs, fs){
562
+ const p = gl.createProgram();
563
+ gl.attachShader(p, compile(gl.VERTEX_SHADER, vs));
564
+ gl.attachShader(p, compile(gl.FRAGMENT_SHADER, fs));
565
+ gl.bindAttribLocation(p, 0, 'a_pos');
566
+ gl.linkProgram(p);
567
+ if(!gl.getProgramParameter(p, gl.LINK_STATUS)){
568
+ console.error(gl.getProgramInfoLog(p));
569
+ throw new Error('Program link error');
570
+ }
571
+ return p;
572
+ }
573
+
574
+ const prog = program(vertSrc, fragSrc);
575
+ const blit = program(vertSrc, blitSrc);
576
+
577
+ const quad = gl.createBuffer();
578
+ gl.bindBuffer(gl.ARRAY_BUFFER, quad);
579
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
580
+ -1,-1, 1,-1, -1,1,
581
+ 1,-1, 1, 1, -1,1,
582
+ ]), gl.STATIC_DRAW);
583
+ gl.enableVertexAttribArray(0);
584
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
585
+
586
+ function makeTex(w,h){
587
+ const tex = gl.createTexture();
588
+ gl.bindTexture(gl.TEXTURE_2D, tex);
589
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
590
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
591
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
592
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
593
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
594
+ return tex;
595
+ }
596
+ function makeFBO(tex){
597
+ const fb = gl.createFramebuffer();
598
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
599
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
600
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
601
+ return fb;
602
+ }
603
+
604
+ let W=0, H=0, DPR=1;
605
+ let tex = [null,null], fbo=[null,null], src=0, dst=1;
606
+
607
+ function resize(){
608
+ const dpr = Math.min(2, window.devicePixelRatio || 1);
609
+ const w = Math.floor(gl.canvas.clientWidth * dpr);
610
+ const h = Math.floor(gl.canvas.clientHeight * dpr);
611
+ if(w===W && h===H && dpr===DPR) return;
612
+ DPR=dpr; W=w; H=h;
613
+ gl.canvas.width = W; gl.canvas.height = H;
614
+ for(let i=0;i<2;i++){
615
+ if(tex[i]) gl.deleteTexture(tex[i]);
616
+ if(fbo[i]) gl.deleteFramebuffer(fbo[i]);
617
+ tex[i] = makeTex(W,H);
618
+ fbo[i] = makeFBO(tex[i]);
619
+ }
620
+ // Clear initial
621
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[0]);
622
+ gl.viewport(0,0,W,H);
623
+ gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT);
624
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[1]);
625
+ gl.clear(gl.COLOR_BUFFER_BIT);
626
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
627
+ src=0; dst=1;
628
+ }
629
+
630
+ // Params
631
+ const params = {
632
+ bpm:120, timeScale:1.0, seed:133,
633
+
634
+ p1Type:0, p2Type:4, p1Scale:3.0, p2Scale:2.2, p1Speed:0.3, p2Speed:-0.2, mix:0.5, blend:3,
635
+ noiseScale:2.0, warpAmount:0.35, warpSpeed:0.6,
636
+
637
+ slices:6, kAngle:0.0, mirror:1, zoom:1.0, rotSpeed:0.25, panX:0, panY:0,
638
+
639
+ fbAmt:0.22, fbZoom:-0.02, fbRot:0.02, fbBlend:4, blur:0.0, chroma:0.2, pixel:0.0, vign:0.2, strobe:0.0, strobeDuty:0.5,
640
+
641
+ palette:0, hueShift:0.0, hueSpeed:0.05, saturation:1.0, contrast:1.15, brightness:1.05, gamma:1.0,
642
+
643
+ audioAmt:0.5, audioHue:0.3, audioZoom:0.1, audioWarp:0.2, audioSlices:0.2, audioSmooth:0.5,
644
+ };
645
+
646
+ const ranges = [
647
+ ['bpm','BPM',40,200,1,''],
648
+ ['timeScale','Zeit-Skala',0.1,3,0.01,'x'],
649
+ ['seed','Seed',0,999,1,''],
650
+
651
+ ['p1Scale','A Scale',0.2,8,0.01,'x'],
652
+ ['p2Scale','B Scale',0.2,8,0.01,'x'],
653
+ ['p1Speed','A Speed',-4,4,0.01,''],
654
+ ['p2Speed','B Speed',-4,4,0.01,''],
655
+ ['mix','Mix',0,1,0.001,''],
656
+ ['noiseScale','Noise Scale',0.1,8,0.01,'x'],
657
+ ['warpAmount','Warp',0,2,0.001,''],
658
+ ['warpSpeed','Warp Speed',-4,4,0.01,''],
659
+
660
+ ['slices','Slices',1,16,1,''],
661
+ ['kAngle','Angle',-Math.PI,Math.PI,0.001,'rad'],
662
+ ['mirror','Mirror',0,1,1,''],
663
+ ['zoom','Zoom',0.2,3,0.001,'x'],
664
+ ['rotSpeed','Rotate Speed',-4,4,0.01,''],
665
+ ['panX','Pan X',-1,1,0.001,''],
666
+ ['panY','Pan Y',-1,1,0.001,''],
667
+
668
+ ['fbAmt','FB Amount',0,1,0.001,''],
669
+ ['fbZoom','FB Zoom',-0.2,0.2,0.001,''],
670
+ ['fbRot','FB Rotate',-2,2,0.001,''],
671
+ ['blur','Blur',0,1,0.001,''],
672
+ ['chroma','Chroma',0,1,0.001,''],
673
+ ['pixel','Pixelation',0,1,0.001,''],
674
+ ['vign','Vignette',0,1,0.001,''],
675
+ ['strobe','Strobe',0,20,0.01,'Hz'],
676
+ ['strobeDuty','Duty',0.02,0.98,0.01,''],
677
+
678
+ ['hueShift','Hue Shift',0,1,0.001,''],
679
+ ['hueSpeed','Hue Speed',-1,1,0.001,''],
680
+ ['saturation','Saturation',0,2,0.001,''],
681
+ ['contrast','Contrast',0.2,3,0.001,''],
682
+ ['brightness','Brightness',0,3,0.001,''],
683
+ ['gamma','Gamma',0.2,3,0.001,''],
684
+
685
+ ['audioAmt','Audio Amount',0,2,0.001,''],
686
+ ['audioHue','Audio→Hue',0,1,0.001,''],
687
+ ['audioZoom','Audio→Zoom',0,1,0.001,''],
688
+ ['audioWarp','Audio→Warp',0,1,0.001,''],
689
+ ['audioSlices','Audio→Slices',0,1,0.001,''],
690
+ ['audioSmooth','Audio Smooth',0,0.99,0.001,''],
691
+ ];
692
+
693
+ // LFOs
694
+ const lfoTargets = [
695
+ ['none','–'],
696
+ ['hueShift','Hue'],
697
+ ['zoom','Zoom'],
698
+ ['rotSpeed','Rotate'],
699
+ ['warpAmount','Warp'],
700
+ ['slices','Slices'],
701
+ ['fbAmt','FB Amount'],
702
+ ['blur','Blur'],
703
+ ['chroma','Chroma'],
704
+ ['pixel','Pixelation'],
705
+ ['brightness','Brightness'],
706
+ ['contrast','Contrast'],
707
+ ['saturation','Saturation'],
708
+ ['mix','Mix'],
709
+ ];
710
+ const lfos = [
711
+ {type:'sine', rate:0.30, depth:0.40, phase:0, tgt1:'hueShift', amt1:0.35, tgt2:'warpAmount', amt2:0.25},
712
+ {type:'sine', rate:0.12, depth:0.30, phase:0.2, tgt1:'zoom', amt1:0.15, tgt2:'rotSpeed', amt2:0.5},
713
+ {type:'triangle', rate:0.07, depth:0.5, phase:0.5, tgt1:'slices', amt1:2.5, tgt2:'saturation', amt2:0.3},
714
+ ];
715
+
716
+ // UI hookup
717
+ function byId(id){ return document.getElementById(id); }
718
+ function setRange(id, value){
719
+ const el = byId(id);
720
+ if(!el) return;
721
+ el.value = value;
722
+ const valEl = byId(id+'Val');
723
+ if(valEl) valEl.textContent = Number(value).toFixed(el.step && Number(el.step)<1 ? (String(el.step).split('.')[1]?.length||0) : 0);
724
+ }
725
+ function linkRange(id, key){
726
+ const el = byId(id); const valEl = byId(id+'Val');
727
+ if(!el) return;
728
+ el.addEventListener('input', () => {
729
+ params[key] = parseFloat(el.value);
730
+ if(valEl) valEl.textContent = el.value;
731
+ saveLocal();
732
+ });
733
+ setRange(id, params[key]);
734
+ }
735
+ // Attach ranges
736
+ linkRange('bpm','bpm'); linkRange('timeScale','timeScale'); linkRange('seed','seed');
737
+ // Pattern selects
738
+ const p1TypeSel = byId('p1Type');
739
+ const p2TypeSel = byId('p2Type');
740
+ p1TypeSel.value = params.p1Type; p2TypeSel.value = params.p2Type;
741
+ p1TypeSel.onchange = () => { params.p1Type = parseInt(p1TypeSel.value); saveLocal(); };
742
+ p2TypeSel.onchange = () => { params.p2Type = parseInt(p2TypeSel.value); saveLocal(); };
743
+ // Pattern ranges
744
+ linkRange('p1Scale','p1Scale'); linkRange('p2Scale','p2Scale');
745
+ linkRange('p1Speed','p1Speed'); linkRange('p2Speed','p2Speed');
746
+ linkRange('mix','mix');
747
+ const blendSel = byId('blend'); blendSel.value = params.blend; blendSel.onchange = () => { params.blend = parseInt(blendSel.value); saveLocal(); };
748
+ linkRange('noiseScale','noiseScale'); linkRange('warpAmount','warpAmount'); linkRange('warpSpeed','warpSpeed');
749
+
750
+ // Kalei & Transform
751
+ linkRange('slices','slices'); linkRange('kAngle','kAngle'); linkRange('mirror','mirror');
752
+ linkRange('zoom','zoom'); linkRange('rotSpeed','rotSpeed'); linkRange('panX','panX'); linkRange('panY','panY');
753
+
754
+ // Feedback
755
+ linkRange('fbAmt','fbAmt'); linkRange('fbZoom','fbZoom'); linkRange('fbRot','fbRot');
756
+ const fbBlendSel = byId('fbBlend'); fbBlendSel.value = params.fbBlend; fbBlendSel.onchange = () => { params.fbBlend = parseInt(fbBlendSel.value); saveLocal(); };
757
+ linkRange('blur','blur'); linkRange('chroma','chroma'); linkRange('pixel','pixel'); linkRange('vign','vign'); linkRange('strobe','strobe'); linkRange('strobeDuty','strobeDuty');
758
+
759
+ // Color
760
+ const paletteSel = byId('palette'); paletteSel.value = params.palette; paletteSel.onchange = () => { params.palette = parseInt(paletteSel.value); saveLocal(); };
761
+ linkRange('hueShift','hueShift'); linkRange('hueSpeed','hueSpeed'); linkRange('saturation','saturation'); linkRange('contrast','contrast'); linkRange('brightness','brightness'); linkRange('gamma','gamma');
762
+
763
+ // Audio
764
+ linkRange('audioAmt','audioAmt'); linkRange('audioHue','audioHue'); linkRange('audioZoom','audioZoom'); linkRange('audioWarp','audioWarp'); linkRange('audioSlices','audioSlices'); linkRange('audioSmooth','audioSmooth');
765
+
766
+ // LFO UI
767
+ const lfoWrap = byId('lfos');
768
+ function lfoRow(i){
769
+ const l = lfos[i];
770
+ const wrap = document.createElement('div'); wrap.className='section'; wrap.style.margin='8px 0'; wrap.style.padding='8px';
771
+ wrap.innerHTML = `
772
+ <div class="grid">
773
+ <label>Type</label>
774
+ <div>
775
+ <select id="l${i}type">
776
+ <option value="sine">Sine</option>
777
+ <option value="triangle">Triangle</option>
778
+ <option value="saw">Saw</option>
779
+ <option value="square">Square</option>
780
+ </select>
781
  </div>
782
+ <label>Rate (Hz)</label><div><input id="l${i}rate" type="range" min="0.01" max="4" step="0.01"><div class="val" id="l${i}rateVal"></div></div>
783
+ <label>Depth</label><div><input id="l${i}depth" type="range" min="-1" max="1" step="0.001"><div class="val" id="l${i}depthVal"></div></div>
784
+ <label>Phase</label><div><input id="l${i}phase" type="range" min="0" max="1" step="0.001"><div class="val" id="l${i}phaseVal"></div></div>
785
+
786
+ <label>Target A</label>
787
+ <div class="row">
788
+ <select id="l${i}t1"></select>
789
+ <input id="l${i}a1" type="range" min="-2" max="2" step="0.001" style="width:110px">
 
 
 
 
 
 
790
  </div>
791
+ <label>Target B</label>
792
+ <div class="row">
793
+ <select id="l${i}t2"></select>
794
+ <input id="l${i}a2" type="range" min="-2" max="2" step="0.001" style="width:110px">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  </div>
796
+ </div>
797
+ `;
798
+ lfoWrap.appendChild(wrap);
799
+ const tsel1 = wrap.querySelector(`#l${i}t1`);
800
+ const tsel2 = wrap.querySelector(`#l${i}t2`);
801
+ lfoTargets.forEach(([v,lab])=>{
802
+ const o1=document.createElement('option');o1.value=v;o1.textContent=lab; tsel1.appendChild(o1);
803
+ const o2=document.createElement('option');o2.value=v;o2.textContent=lab; tsel2.appendChild(o2.cloneNode(true));
804
+ });
805
+ wrap.querySelector(`#l${i}type`).value=l.type;
806
+ wrap.querySelector(`#l${i}rate`).value=l.rate; wrap.querySelector(`#l${i}rateVal`).textContent=l.rate;
807
+ wrap.querySelector(`#l${i}depth`).value=l.depth; wrap.querySelector(`#l${i}depthVal`).textContent=l.depth;
808
+ wrap.querySelector(`#l${i}phase`).value=l.phase; wrap.querySelector(`#l${i}phaseVal`).textContent=l.phase;
809
+ tsel1.value = l.tgt1; tsel2.value = l.tgt2;
810
+ wrap.querySelector(`#l${i}a1`).value = l.amt1;
811
+ wrap.querySelector(`#l${i}a2`).value = l.amt2;
812
+
813
+ wrap.querySelector(`#l${i}type`).onchange = e=>{ l.type = e.target.value; saveLocal(); };
814
+ wrap.querySelector(`#l${i}rate`).oninput = e=>{ l.rate = parseFloat(e.target.value); wrap.querySelector(`#l${i}rateVal`).textContent=e.target.value; saveLocal(); };
815
+ wrap.querySelector(`#l${i}depth`).oninput = e=>{ l.depth = parseFloat(e.target.value); wrap.querySelector(`#l${i}depthVal`).textContent=e.target.value; saveLocal(); };
816
+ wrap.querySelector(`#l${i}phase`).oninput = e=>{ l.phase = parseFloat(e.target.value); wrap.querySelector(`#l${i}phaseVal`).textContent=e.target.value; saveLocal(); };
817
+ tsel1.onchange = e=>{ l.tgt1=e.target.value; saveLocal(); };
818
+ tsel2.onchange = e=>{ l.tgt2=e.target.value; saveLocal(); };
819
+ wrap.querySelector(`#l${i}a1`).oninput = e=>{ l.amt1 = parseFloat(e.target.value); saveLocal(); };
820
+ wrap.querySelector(`#l${i}a2`).oninput = e=>{ l.amt2 = parseFloat(e.target.value); saveLocal(); };
821
+ }
822
+ for(let i=0;i<lfos.length;i++) lfoRow(i);
823
+
824
+ // Presets
825
+ const presets = {
826
+ "Kaleido Trip": {
827
+ bpm:120,timeScale:1.0,seed:133,
828
+ p1Type:0,p2Type:4,p1Scale:3.2,p2Scale:2.0,p1Speed:0.42,p2Speed:-0.18,mix:0.52,blend:3,
829
+ noiseScale:2.0,warpAmount:0.45,warpSpeed:0.7,
830
+ slices:8,kAngle:0.0,mirror:1,zoom:1.05,rotSpeed:0.35,panX:0,panY:0,
831
+ fbAmt:0.28,fbZoom:-0.015,fbRot:0.015,fbBlend:4,blur:0.06,chroma:0.22,pixel:0.0,vign:0.25,strobe:0.0,strobeDuty:0.5,
832
+ palette:0,hueShift:0.0,hueSpeed:0.07,saturation:1.2,contrast:1.1,brightness:1.05,gamma:1.0,
833
+ audioAmt:0.6,audioHue:0.35,audioZoom:0.12,audioWarp:0.22,audioSlices:0.25,audioSmooth:0.6,
834
+ lfos:[
835
+ {type:'sine',rate:0.32,depth:0.45,phase:0.0,tgt1:'hueShift',amt1:0.35,tgt2:'warpAmount',amt2:0.25},
836
+ {type:'sine',rate:0.11,depth:0.34,phase:0.4,tgt1:'zoom',amt1:0.18,tgt2:'rotSpeed',amt2:0.6},
837
+ {type:'triangle',rate:0.09,depth:0.55,phase:0.2,tgt1:'slices',amt1:2.6,tgt2:'saturation',amt2:0.35},
838
+ ],
839
+ },
840
+ "Neon Rings": {
841
+ bpm:128,timeScale:1.0,seed:77,
842
+ p1Type:0,p2Type:1,p1Scale:4.0,p2Scale:3.0,p1Speed:0.9,p2Speed:0.2,mix:0.4,blend:1,
843
+ noiseScale:1.2,warpAmount:0.2,warpSpeed:0.3,
844
+ slices:6,kAngle:0.2,mirror:1,zoom:1.15,rotSpeed:0.2,panX:0,panY:0,
845
+ fbAmt:0.2,fbZoom:-0.01,fbRot:0.01,fbBlend:3,blur:0.03,chroma:0.35,pixel:0.0,vign:0.2,strobe:0,strobeDuty:0.5,
846
+ palette:1,hueShift:0.1,hueSpeed:0.03,saturation:1.6,contrast:1.2,brightness:1.1,gamma:1.0,
847
+ audioAmt:0.5,audioHue:0.2,audioZoom:0.08,audioWarp:0.1,audioSlices:0.1,audioSmooth:0.5,
848
+ lfos:[
849
+ {type:'sine',rate:0.5,depth:0.5,phase:0.0,tgt1:'hueShift',amt1:0.25,tgt2:'zoom',amt2:0.12},
850
+ {type:'triangle',rate:0.22,depth:0.4,phase:0.2,tgt1:'rotSpeed',amt1:0.6,tgt2:'warpAmount',amt2:0.2},
851
+ {type:'sine',rate:0.08,depth:0.3,phase:0.8,tgt1:'saturation',amt1:0.4,tgt2:'contrast',amt2:0.2},
852
+ ],
853
+ },
854
+ "Liquid Warp": {
855
+ bpm:110,timeScale:1.0,seed:311,
856
+ p1Type:4,p2Type:4,p1Scale:2.0,p2Scale:1.5,p1Speed:0.3,p2Speed:-0.25,mix:0.5,blend:0,
857
+ noiseScale:2.8,warpAmount:0.85,warpSpeed:0.9,
858
+ slices:4,kAngle:0.0,mirror:0,zoom:1.0,rotSpeed:0.05,panX:0,panY:0,
859
+ fbAmt:0.35,fbZoom:-0.03,fbRot:0.02,fbBlend:3,blur:0.15,chroma:0.25,pixel:0.05,vign:0.3,strobe:0,strobeDuty:0.5,
860
+ palette:4,hueShift:0.0,hueSpeed:0.02,saturation:1.1,contrast:1.05,brightness:1.1,gamma:1.1,
861
+ audioAmt:0.8,audioHue:0.25,audioZoom:0.15,audioWarp:0.3,audioSlices:0.1,audioSmooth:0.7,
862
+ lfos:[
863
+ {type:'sine',rate:0.18,depth:0.5,phase:0.3,tgt1:'warpAmount',amt1:0.5,tgt2:'zoom',amt2:-0.12},
864
+ {type:'triangle',rate:0.09,depth:0.4,phase:0.5,tgt1:'slices',amt1:1.5,tgt2:'saturation',amt2:0.2},
865
+ {type:'sine',rate:0.06,depth:0.3,phase:0.1,tgt1:'hueShift',amt1:0.2,tgt2:'contrast',amt2:-0.1},
866
+ ],
867
+ },
868
+ "Data Checker": {
869
+ bpm:140,timeScale:1.0,seed:500,
870
+ p1Type:2,p2Type:1,p1Scale:3.5,p2Scale:4.0,p1Speed:0.5,p2Speed:-0.5,mix:0.5,blend:4,
871
+ noiseScale:1.5,warpAmount:0.25,warpSpeed:0.4,
872
+ slices:12,kAngle:0.0,mirror:1,zoom:1.1,rotSpeed:0.4,panX:0,panY:0,
873
+ fbAmt:0.18,fbZoom:-0.005,fbRot:0.02,fbBlend:4,blur:0.0,chroma:0.4,pixel:0.0,vign:0.1,strobe:6,strobeDuty:0.25,
874
+ palette:0,hueShift:0.2,hueSpeed:0.1,saturation:1.3,contrast:1.4,brightness:1.0,gamma:1.0,
875
+ audioAmt:0.9,audioHue:0.35,audioZoom:0.2,audioWarp:0.2,audioSlices:0.4,audioSmooth:0.5,
876
+ lfos:[
877
+ {type:'square',rate:0.5,depth:1,phase:0,tgt1:'mix',amt1:0.6,tgt2:'contrast',amt2:0.25},
878
+ {type:'saw',rate:0.25,depth:0.6,phase:0.3,tgt1:'slices',amt1:-3.0,tgt2:'rotSpeed',amt2:0.8},
879
+ {type:'sine',rate:0.12,depth:0.3,phase:0.5,tgt1:'hueShift',amt1:0.4,tgt2:'brightness',amt2:-0.2},
880
+ ],
881
+ },
882
+ "Cosmic Swirl": {
883
+ bpm:100,timeScale:1.0,seed:42,
884
+ p1Type:3,p2Type:4,p1Scale:2.5,p2Scale:2.0,p1Speed:0.2,p2Speed:0.1,mix:0.6,blend:3,
885
+ noiseScale:2.0,warpAmount:0.5,warpSpeed:0.5,
886
+ slices:7,kAngle:0.3,mirror:1,zoom:0.9,rotSpeed:0.18,panX:0,panY:0,
887
+ fbAmt:0.26,fbZoom:-0.02,fbRot:0.03,fbBlend:3,blur:0.05,chroma:0.2,pixel:0.0,vign:0.25,strobe:0,strobeDuty:0.5,
888
+ palette:2,hueShift:0.05,hueSpeed:0.04,saturation:1.4,contrast:1.15,brightness:1.05,gamma:1.0,
889
+ audioAmt:0.5,audioHue:0.25,audioZoom:0.1,audioWarp:0.2,audioSlices:0.15,audioSmooth:0.6,
890
+ lfos:[
891
+ {type:'sine',rate:0.2,depth:0.5,phase:0.1,tgt1:'hueShift',amt1:0.3,tgt2:'warpAmount',amt2:0.3},
892
+ {type:'triangle',rate:0.1,depth:0.4,phase:0.6,tgt1:'zoom',amt1:0.1,tgt2:'rotSpeed',amt2:0.45},
893
+ {type:'sine',rate:0.08,depth:0.4,phase:0.8,tgt1:'slices',amt1:2.0,tgt2:'saturation',amt2:0.25},
894
+ ],
895
+ },
896
+ "Heat Bloom": {
897
+ bpm:90,timeScale:1.0,seed:888,
898
+ p1Type:4,p2Type:0,p1Scale:1.6,p2Scale:2.8,p1Speed:-0.15,p2Speed:0.6,mix:0.55,blend:1,
899
+ noiseScale:2.6,warpAmount:0.75,warpSpeed:0.6,
900
+ slices:5,kAngle:0.0,mirror:0,zoom:1.0,rotSpeed:0.08,panX:0,panY:0,
901
+ fbAmt:0.32,fbZoom:-0.025,fbRot:0.015,fbBlend:3,blur:0.08,chroma:0.18,pixel:0.03,vign:0.3,strobe:0,strobeDuty:0.5,
902
+ palette:3,hueShift:0.0,hueSpeed:0.03,saturation:1.2,contrast:1.2,brightness:1.1,gamma:1.0,
903
+ audioAmt:0.7,audioHue:0.2,audioZoom:0.15,audioWarp:0.3,audioSlices:0.2,audioSmooth:0.7,
904
+ lfos:[
905
+ {type:'saw',rate:0.15,depth:0.5,phase:0.0,tgt1:'warpAmount',amt1:0.4,tgt2:'brightness',amt2:0.2},
906
+ {type:'sine',rate:0.11,depth:0.35,phase:0.2,tgt1:'hueShift',amt1:0.25,tgt2:'contrast',amt2:0.15},
907
+ {type:'triangle',rate:0.07,depth:0.4,phase:0.6,tgt1:'slices',amt1:1.8,tgt2:'zoom',amt2:-0.08},
908
+ ],
909
+ },
910
+ };
911
+ const presetSel = byId('presetSel');
912
+ Object.keys(presets).forEach((name,i)=>{
913
+ const opt = document.createElement('option');
914
+ opt.value=name; opt.textContent = `${i+1}. ${name}`;
915
+ presetSel.appendChild(opt);
916
+ });
917
+ presetSel.onchange = ()=>{ loadPreset(presets[presetSel.value]); };
918
+
919
+ function applyObj(obj, dst){
920
+ for(const k in obj){
921
+ if(k==='lfos' && Array.isArray(obj.lfos)){
922
+ for(let i=0;i<lfos.length;i++){
923
+ Object.assign(lfos[i], obj.lfos[i] || {});
924
  }
925
+ } else {
926
+ dst[k] = obj[k];
927
+ }
928
+ }
929
+ }
930
+ function loadPreset(p){
931
+ applyObj(p, params);
932
+ // Update UI
933
+ p1TypeSel.value = params.p1Type; p2TypeSel.value = params.p2Type;
934
+ blendSel.value = params.blend; fbBlendSel.value = params.fbBlend; paletteSel.value = params.palette;
935
+ ranges.forEach(([keyLabel])=>{
936
+ const id = keyLabel; if(byId(id)) setRange(id, params[id]);
937
+ });
938
+ // Update LFO UI
939
+ const selects = lfoWrap.querySelectorAll('select, input[type=range]');
940
+ // Re-render lfo rows by resetting values
941
+ lfoWrap.innerHTML=''; for(let i=0;i<lfos.length;i++) lfoRow(i);
942
+ saveLocal();
943
+ }
944
+ // Set default preset 1
945
+ presetSel.selectedIndex = 0; loadPreset(presets[presetSel.value]);
946
+
947
+ // JSON
948
+ byId('btnToJSON').onclick = ()=>{
949
+ const obj = {...params, lfos: lfos.map(x=>({...x}))};
950
+ byId('json').value = JSON.stringify(obj, null, 2);
951
+ };
952
+ byId('btnFromJSON').onclick = ()=>{
953
+ try{
954
+ const obj = JSON.parse(byId('json').value);
955
+ loadPreset(obj);
956
+ }catch(e){ alert('Ungültiges JSON'); }
957
+ };
958
+ byId('btnSave').onclick = ()=>{
959
+ const obj = {...params, lfos: lfos.map(x=>({...x}))};
960
+ const blob = new Blob([JSON.stringify(obj, null, 2)], {type:'application/json'});
961
+ const a = document.createElement('a');
962
+ a.href = URL.createObjectURL(blob); a.download = 'visual-synth-preset.json'; a.click();
963
+ URL.revokeObjectURL(a.href);
964
+ };
965
+ byId('btnLoad').onclick = ()=>{
966
+ const inp = document.createElement('input');
967
+ inp.type = 'file'; inp.accept = 'application/json';
968
+ inp.onchange = ()=>{
969
+ const f = inp.files[0]; if(!f) return;
970
+ const r = new FileReader();
971
+ r.onload = ()=>{ try{ const obj = JSON.parse(r.result); loadPreset(obj);}catch(e){ alert('Fehler beim Laden'); } };
972
+ r.readAsText(f);
973
+ };
974
+ inp.click();
975
+ };
976
+
977
+ // Randomize
978
+ function rand(min,max){ return Math.random()*(max-min)+min; }
979
+ function randInt(min,max){ return Math.floor(rand(min,max+1)); }
980
+ function randomize(){
981
+ params.seed = randInt(0,999);
982
+ params.p1Type = randInt(0,4);
983
+ params.p2Type = randInt(0,4);
984
+ params.p1Scale = rand(0.5,5);
985
+ params.p2Scale = rand(0.5,5);
986
+ params.p1Speed = rand(-1.5, 1.5);
987
+ params.p2Speed = rand(-1.5, 1.5);
988
+ params.mix = Math.random();
989
+ params.blend = randInt(0,4);
990
+ params.noiseScale = rand(0.8,3.5);
991
+ params.warpAmount = rand(0.1,1.2);
992
+ params.warpSpeed = rand(-1,1);
993
+ params.slices = randInt(3,14);
994
+ params.kAngle = rand(-Math.PI, Math.PI);
995
+ params.mirror = randInt(0,1);
996
+ params.zoom = rand(0.7,1.5);
997
+ params.rotSpeed = rand(-0.6,0.6);
998
+ params.panX = rand(-0.2,0.2);
999
+ params.panY = rand(-0.2,0.2);
1000
+ params.fbAmt = rand(0.05,0.4);
1001
+ params.fbZoom = rand(-0.03,0.03);
1002
+ params.fbRot = rand(-0.05,0.05);
1003
+ params.fbBlend = randInt(0,4);
1004
+ params.blur = Math.pow(Math.random(),2)*0.2;
1005
+ params.chroma = Math.random()*0.4;
1006
+ params.pixel = Math.random()<0.3 ? Math.random()*0.15 : 0.0;
1007
+ params.vign = rand(0.05,0.35);
1008
+ params.strobe = Math.random()<0.2 ? rand(2,10) : 0;
1009
+ params.strobeDuty = rand(0.1,0.6);
1010
+ params.palette = randInt(0,4);
1011
+ params.hueShift = Math.random();
1012
+ params.hueSpeed = rand(-0.1,0.1);
1013
+ params.saturation = rand(0.8,1.8);
1014
+ params.contrast = rand(0.9,1.6);
1015
+ params.brightness = rand(0.9,1.2);
1016
+ params.gamma = rand(0.8,1.4);
1017
+ // LFOs
1018
+ const types = ['sine','triangle','saw','square'];
1019
+ lfos.forEach(l=>{
1020
+ l.type = types[randInt(0,types.length-1)];
1021
+ l.rate = rand(0.05, 0.8);
1022
+ l.depth = rand(-0.8, 0.8);
1023
+ l.phase = Math.random();
1024
+ const tkeys = lfoTargets.map(t=>t[0]).filter(x=>x!=='none');
1025
+ l.tgt1 = tkeys[randInt(0,tkeys.length-1)];
1026
+ l.tgt2 = tkeys[randInt(0,tkeys.length-1)];
1027
+ l.amt1 = rand(-0.6, 0.6) * (l.tgt1==='slices'?4:1);
1028
+ l.amt2 = rand(-0.6, 0.6) * (l.tgt2==='slices'?4:1);
1029
+ });
1030
+ // Reflect UI
1031
+ p1TypeSel.value=params.p1Type; p2TypeSel.value=params.p2Type; blendSel.value=params.blend; fbBlendSel.value=params.fbBlend; paletteSel.value=params.palette;
1032
+ ranges.forEach(([id])=>{ if(byId(id)) setRange(id, params[id]); });
1033
+ lfoWrap.innerHTML=''; for(let i=0;i<lfos.length;i++) lfoRow(i);
1034
+ saveLocal();
1035
+ }
1036
+ byId('btnRandom').onclick = randomize;
1037
+
1038
+ // Local storage
1039
+ const LSKEY = 'visual-synth-state-v1';
1040
+ function saveLocal(){
1041
+ try{
1042
+ localStorage.setItem(LSKEY, JSON.stringify({params, lfos}));
1043
+ }catch(e){}
1044
+ }
1045
+ function loadLocal(){
1046
+ try{
1047
+ const s = localStorage.getItem(LSKEY);
1048
+ if(!s) return;
1049
+ const obj = JSON.parse(s);
1050
+ if(obj.params) applyObj(obj.params, params);
1051
+ if(obj.lfos) for(let i=0;i<Math.min(lfos.length, obj.lfos.length);i++) Object.assign(lfos[i], obj.lfos[i]);
1052
+ // reflect UI quickly:
1053
+ p1TypeSel.value=params.p1Type; p2TypeSel.value=params.p2Type; blendSel.value=params.blend; fbBlendSel.value=params.fbBlend; paletteSel.value=params.palette;
1054
+ ranges.forEach(([id])=>{ if(byId(id)) setRange(id, params[id]); });
1055
+ lfoWrap.innerHTML=''; for(let i=0;i<lfos.length;i++) lfoRow(i);
1056
+ }catch(e){}
1057
+ }
1058
+ loadLocal();
1059
+
1060
+ // Keyboard
1061
+ window.addEventListener('keydown', (e)=>{
1062
+ if(e.repeat) return;
1063
+ if(e.key==='f' || e.key==='F'){ toggleFullscreen(); }
1064
+ if(e.key==='r' || e.key==='R'){ randomize(); }
1065
+ if(e.key==='m' || e.key==='M'){ toggleMic(); }
1066
+ if('123456'.includes(e.key)){
1067
+ const idx = Number(e.key)-1;
1068
+ const name = Object.keys(presets)[idx];
1069
+ if(name){ presetSel.value = name; loadPreset(presets[name]); }
1070
+ }
1071
+ });
1072
+
1073
+ // Fullscreen
1074
+ function toggleFullscreen(){
1075
+ if(!document.fullscreenElement) document.documentElement.requestFullscreen().catch(()=>{});
1076
+ else document.exitFullscreen();
1077
+ }
1078
+ byId('btnFull').onclick = toggleFullscreen;
1079
+
1080
+ // Recording
1081
+ let recorder=null, chunks=[];
1082
+ byId('btnRecord').onclick = ()=>{
1083
+ if(!recorder){
1084
+ const stream = canvas.captureStream(60);
1085
+ recorder = new MediaRecorder(stream, {mimeType:'video/webm;codecs=vp9'});
1086
+ recorder.ondataavailable = e=>{ if(e.data.size>0) chunks.push(e.data); };
1087
+ recorder.onstop = ()=>{
1088
+ const blob = new Blob(chunks, {type:'video/webm'});
1089
+ chunks = [];
1090
+ const url = URL.createObjectURL(blob);
1091
+ const a = document.createElement('a');
1092
+ a.href = url; a.download = 'visual-synth.webm'; a.click();
1093
+ URL.revokeObjectURL(url);
1094
+ recorder = null;
1095
+ byId('btnRecord').textContent = 'Aufnahme';
1096
+ };
1097
+ recorder.start();
1098
+ byId('btnRecord').textContent = 'Aufnahme läuft...';
1099
+ } else {
1100
+ recorder.stop();
1101
+ }
1102
+ };
1103
+
1104
+ // Audio input
1105
+ let audioCtx=null, analyser=null, micSource=null;
1106
+ let audioOn=false;
1107
+ const audioInfo = byId('audioInfo');
1108
+ function setupAudio(){
1109
+ if(audioCtx) return;
1110
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
1111
+ analyser = audioCtx.createAnalyser();
1112
+ analyser.fftSize = 1024;
1113
+ analyser.smoothingTimeConstant = 0.5;
1114
+ }
1115
+ async function toggleMic(){
1116
+ try{
1117
+ if(!audioOn){
1118
+ setupAudio();
1119
+ const stream = await navigator.mediaDevices.getUserMedia({audio:true});
1120
+ micSource = audioCtx.createMediaStreamSource(stream);
1121
+ micSource.connect(analyser);
1122
+ audioOn = true; byId('btnMic').textContent = 'Mic: An';
1123
+ }else{
1124
+ if(micSource) micSource.disconnect();
1125
+ audioOn=false; byId('btnMic').textContent = 'Mic: Aus';
1126
+ }
1127
+ }catch(e){
1128
+ alert('Mikrofonzugriff fehlgeschlagen.');
1129
+ }
1130
+ }
1131
+ byId('btnMic').onclick = toggleMic;
1132
+
1133
+ // FPS
1134
+ const fpsEl = byId('fps'); let lastFpsT=performance.now(), frames=0;
1135
+ function updateFPS(){
1136
+ frames++;
1137
+ const now=performance.now();
1138
+ if(now-lastFpsT>500){
1139
+ const fps = Math.round(frames*1000/(now-lastFpsT));
1140
+ fpsEl.textContent = String(fps);
1141
+ frames=0; lastFpsT=now;
1142
+ }
1143
+ }
1144
+
1145
+ // Uniform locations
1146
+ const U = {};
1147
+ function getUniforms(p){
1148
+ const names = [
1149
+ 'u_res','u_time','u_bpm','u_seed','u_prev',
1150
+ 'u_p1Type','u_p2Type','u_p1Scale','u_p2Scale','u_p1Speed','u_p2Speed','u_mix','u_blend',
1151
+ 'u_noiseScale','u_warpAmt','u_warpSpeed',
1152
+ 'u_slices','u_kAngle','u_mirror','u_zoom','u_rotSpeed','u_panX','u_panY',
1153
+ 'u_fbAmt','u_fbZoom','u_fbRot','u_fbBlend','u_blur','u_chroma','u_pixel','u_vign','u_strobe','u_strobeDuty',
1154
+ 'u_palette','u_hueShift','u_hueSpeed','u_saturation','u_contrast','u_brightness','u_gamma',
1155
+ 'u_audioLevel','u_audioBass','u_audioMid','u_audioHigh'
1156
+ ];
1157
+ gl.useProgram(p);
1158
+ for(const n of names) U[n] = gl.getUniformLocation(p, n);
1159
+ }
1160
+ getUniforms(prog);
1161
+
1162
+ // Time
1163
+ let startT = performance.now() / 1000;
1164
+ let lastT = startT;
1165
+
1166
+ // Audio data buffers
1167
+ let fft=null, wave=null; let audioLevel=0, audioBass=0, audioMid=0, audioHigh=0;
1168
+
1169
+ function sampleAudio(dt){
1170
+ if(!analyser || !audioOn) { audioLevel*=0.95; audioBass*=0.95; audioMid*=0.95; audioHigh*=0.95; audioInfo.textContent='Audio: –'; return; }
1171
+ if(!fft){ fft = new Uint8Array(analyser.frequencyBinCount); wave = new Uint8Array(analyser.fftSize); }
1172
+ analyser.getByteFrequencyData(fft);
1173
+ analyser.getByteTimeDomainData(wave);
1174
+ const N = fft.length;
1175
+ // Bands: 0-200Hz, 200-1500Hz, 1.5k-8k (roughly)
1176
+ const sr = audioCtx.sampleRate || 48000;
1177
+ const hzPerBin = sr/2/N;
1178
+ let sum=0, bsum=0, msum=0, hsum=0, bc=0, mc=0, hc=0;
1179
+ for(let i=0;i<N;i++){
1180
+ const hz = i*hzPerBin;
1181
+ const v = fft[i]/255;
1182
+ sum += v;
1183
+ if(hz<200){ bsum+=v; bc++; }
1184
+ else if(hz<1500){ msum+=v; mc++; }
1185
+ else if(hz<8000){ hsum+=v; hc++; }
1186
+ }
1187
+ const smooth = params.audioSmooth;
1188
+ audioLevel = audioLevel*(smooth) + (1-smooth)*(sum/N);
1189
+ audioBass = audioBass*(smooth) + (1-smooth)*(bc?bsum/bc:0);
1190
+ audioMid = audioMid*(smooth) + (1-smooth)*(mc?msum/mc:0);
1191
+ audioHigh = audioHigh*(smooth) + (1-smooth)*(hc?hsum/hc:0);
1192
+ audioInfo.textContent = `Audio: L ${audioLevel.toFixed(2)} B ${audioBass.toFixed(2)} M ${audioMid.toFixed(2)} H ${audioHigh.toFixed(2)}`;
1193
+ }
1194
+
1195
+ function lfoValue(lfo, t){
1196
+ const ph = (t * lfo.rate + lfo.phase) % 1;
1197
+ const x = ph*2-1; // -1..1
1198
+ switch(lfo.type){
1199
+ case 'sine': return Math.sin(ph*2*Math.PI);
1200
+ case 'triangle': return 1-2*Math.abs(ph*2-1);
1201
+ case 'saw': return (ph*2-1);
1202
+ case 'square': return ph<0.5 ? 1 : -1;
1203
+ default: return 0;
1204
+ }
1205
+ }
1206
+
1207
+ function applyMods(base, t){
1208
+ const out = {...base};
1209
+ // LFOs
1210
+ for(const L of lfos){
1211
+ const val = lfoValue(L, t) * L.depth;
1212
+ if(L.tgt1 && L.tgt1!=='none') out[L.tgt1] = (out[L.tgt1] ?? 0) + val * L.amt1;
1213
+ if(L.tgt2 && L.tgt2!=='none') out[L.tgt2] = (out[L.tgt2] ?? 0) + val * L.amt2;
1214
+ }
1215
+ // Audio
1216
+ const A = params.audioAmt;
1217
+ out.hueShift = (out.hueShift ?? base.hueShift) + A * params.audioHue * audioLevel * 0.5;
1218
+ out.zoom = clamp((out.zoom ?? base.zoom) + A * params.audioZoom * (audioBass-0.4) * 0.6, 0.2, 3);
1219
+ out.warpAmount = clamp((out.warpAmount ?? base.warpAmount) + A * params.audioWarp * (audioMid-0.4) * 1.2, 0, 2);
1220
+ out.slices = clamp((out.slices ?? base.slices) + A * params.audioSlices * (audioHigh-0.5) * 6.0, 1, 16);
1221
+ return out;
1222
+ }
1223
+
1224
+ function clamp(x,min,max){ return x<min?min:x>max?max:x; }
1225
+
1226
+ // Render loop
1227
+ function draw(){
1228
+ resize();
1229
+ const now = performance.now()/1000;
1230
+ const dt = Math.max(0.001, now-lastT); lastT=now;
1231
+ sampleAudio(dt);
1232
+
1233
+ const t = (now - startT) * params.timeScale;
1234
+
1235
+ // Prepare final param set including mods
1236
+ const P = applyMods(params, t);
1237
+
1238
+ // Upload uniforms & render to FBO[dst]
1239
+ gl.useProgram(prog);
1240
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[dst]);
1241
+ gl.viewport(0,0,W,H);
1242
+
1243
+ gl.activeTexture(gl.TEXTURE0);
1244
+ gl.bindTexture(gl.TEXTURE_2D, tex[src]);
1245
+ gl.uniform1i(U['u_prev'], 0);
1246
+
1247
+ gl.uniform2f(U['u_res'], W, H);
1248
+ gl.uniform1f(U['u_time'], t);
1249
+ gl.uniform1f(U['u_bpm'], params.bpm);
1250
+ gl.uniform1f(U['u_seed'], P.seed);
1251
+
1252
+ gl.uniform1f(U['u_p1Type'], P.p1Type);
1253
+ gl.uniform1f(U['u_p2Type'], P.p2Type);
1254
+ gl.uniform1f(U['u_p1Scale'], P.p1Scale);
1255
+ gl.uniform1f(U['u_p2Scale'], P.p2Scale);
1256
+ gl.uniform1f(U['u_p1Speed'], P.p1Speed);
1257
+ gl.uniform1f(U['u_p2Speed'], P.p2Speed);
1258
+ gl.uniform1f(U['u_mix'], P.mix);
1259
+ gl.uniform1f(U['u_blend'], P.blend);
1260
+
1261
+ gl.uniform1f(U['u_noiseScale'], P.noiseScale);
1262
+ gl.uniform1f(U['u_warpAmt'], P.warpAmount);
1263
+ gl.uniform1f(U['u_warpSpeed'], P.warpSpeed);
1264
+
1265
+ gl.uniform1f(U['u_slices'], P.slices);
1266
+ gl.uniform1f(U['u_kAngle'], P.kAngle);
1267
+ gl.uniform1f(U['u_mirror'], P.mirror);
1268
+ gl.uniform1f(U['u_zoom'], P.zoom);
1269
+ gl.uniform1f(U['u_rotSpeed'], P.rotSpeed);
1270
+ gl.uniform1f(U['u_panX'], P.panX);
1271
+ gl.uniform1f(U['u_panY'], P.panY);
1272
+
1273
+ gl.uniform1f(U['u_fbAmt'], P.fbAmt);
1274
+ gl.uniform1f(U['u_fbZoom'], P.fbZoom);
1275
+ gl.uniform1f(U['u_fbRot'], P.fbRot);
1276
+ gl.uniform1f(U['u_fbBlend'], P.fbBlend);
1277
+ gl.uniform1f(U['u_blur'], P.blur);
1278
+ gl.uniform1f(U['u_chroma'], P.chroma);
1279
+ const pixPx = P.pixel<=0 ? 0.0 : (1.0 + P.pixel*60.0);
1280
+ gl.uniform1f(U['u_pixel'], pixPx);
1281
+ gl.uniform1f(U['u_vign'], P.vign);
1282
+ gl.uniform1f(U['u_strobe'], P.strobe);
1283
+ gl.uniform1f(U['u_strobeDuty'], P.strobeDuty);
1284
+
1285
+ gl.uniform1f(U['u_palette'], P.palette);
1286
+ gl.uniform1f(U['u_hueShift'], P.hueShift%1);
1287
+ gl.uniform1f(U['u_hueSpeed'], P.hueSpeed);
1288
+ gl.uniform1f(U['u_saturation'], P.saturation);
1289
+ gl.uniform1f(U['u_contrast'], P.contrast);
1290
+ gl.uniform1f(U['u_brightness'], P.brightness);
1291
+ gl.uniform1f(U['u_gamma'], P.gamma);
1292
+
1293
+ gl.uniform1f(U['u_audioLevel'], audioLevel);
1294
+ gl.uniform1f(U['u_audioBass'], audioBass);
1295
+ gl.uniform1f(U['u_audioMid'], audioMid);
1296
+ gl.uniform1f(U['u_audioHigh'], audioHigh);
1297
+
1298
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
1299
+
1300
+ // Blit to screen
1301
+ gl.useProgram(blit);
1302
+ const loc = gl.getUniformLocation(blit,'u_tex');
1303
+ gl.uniform1i(loc, 0);
1304
+ gl.bindTexture(gl.TEXTURE_2D, tex[dst]);
1305
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
1306
+ gl.viewport(0,0,W,H);
1307
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
1308
+
1309
+ // swap
1310
+ const tmp=src; src=dst; dst=tmp;
1311
+
1312
+ updateFPS();
1313
+ requestAnimationFrame(draw);
1314
+ }
1315
+ requestAnimationFrame(draw);
1316
+
1317
+ // Buttons and init
1318
+ window.addEventListener('resize', resize);
1319
+
1320
+ // Init UI values for ranges
1321
+ ranges.forEach(([id, label, min,max,step,unit])=>{
1322
+ setRange(id, params[id]);
1323
+ });
1324
 
1325
+ })();
1326
+ </script>
1327
+ </body>
1328
+ </html>