Spaces:
Running
Running
Update index.html
Browse files- index.html +1315 -568
index.html
CHANGED
@@ -1,581 +1,1328 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
-
<html lang="
|
3 |
<head>
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
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
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
<
|
195 |
-
<
|
196 |
-
<div
|
197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
</div>
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
<div
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
<
|
218 |
-
|
219 |
-
|
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 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
<
|
301 |
-
|
302 |
-
</
|
303 |
-
|
304 |
-
Choose your perfect blend of tech and tenderness
|
305 |
-
</p>
|
306 |
</div>
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
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 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
422 |
</div>
|
423 |
-
|
424 |
-
<div
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
487 |
</div>
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
<
|
493 |
-
|
494 |
-
|
495 |
-
|
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 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
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 |
-
© 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 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
553 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
554 |
|
555 |
-
|
556 |
-
|
557 |
-
|
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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|