HI7RAI commited on
Commit
85f4eda
·
verified ·
1 Parent(s): 6cc5215

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1102 -19
index.html CHANGED
@@ -1,19 +1,1102 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
6
+ <title>Advanced Image-to-Video Morphing Studio</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary-bg: #0a0a0a;
11
+ --secondary-bg: #1a1a1a;
12
+ --accent-color: #00ff88;
13
+ --spot-color-1: #ff006e;
14
+ --spot-color-2: #00f5ff;
15
+ --text-primary: #ffffff;
16
+ --text-secondary: #888888;
17
+ --glass-bg: rgba(30, 30, 40, 0.4);
18
+ --glass-border: rgba(255, 255, 255, 0.1);
19
+ --control-height: 60px;
20
+ --panel-width: 320px;
21
+ }
22
+
23
+ * {
24
+ margin: 0;
25
+ padding: 0;
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ body {
30
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
31
+ background: var(--primary-bg);
32
+ color: var(--text-primary);
33
+ overflow: hidden;
34
+ height: 100vh;
35
+ display: grid;
36
+ grid-template-rows: auto 1fr auto;
37
+ grid-template-columns: 1fr var(--panel-width);
38
+ grid-template-areas:
39
+ "header header"
40
+ "main controls"
41
+ "footer footer";
42
+ }
43
+
44
+ header {
45
+ grid-area: header;
46
+ background: var(--glass-bg);
47
+ backdrop-filter: blur(20px);
48
+ border-bottom: 1px solid var(--glass-border);
49
+ padding: 1rem 2rem;
50
+ display: flex;
51
+ justify-content: space-between;
52
+ align-items: center;
53
+ z-index: 100;
54
+ }
55
+
56
+ header h1 {
57
+ font-size: clamp(1.2rem, 2vw, 1.8rem);
58
+ font-weight: 300;
59
+ letter-spacing: 2px;
60
+ background: linear-gradient(45deg, var(--accent-color), var(--spot-color-1));
61
+ -webkit-background-clip: text;
62
+ -webkit-text-fill-color: transparent;
63
+ }
64
+
65
+ header a {
66
+ color: var(--accent-color);
67
+ text-decoration: none;
68
+ font-weight: 500;
69
+ transition: all 0.3s ease;
70
+ }
71
+
72
+ header a:hover {
73
+ text-shadow: 0 0 10px var(--accent-color);
74
+ }
75
+
76
+ main {
77
+ grid-area: main;
78
+ perspective: 1200px;
79
+ position: relative;
80
+ overflow: hidden;
81
+ background: radial-gradient(circle at center, #111 0%, #000 100%);
82
+ }
83
+
84
+ .showroom-container {
85
+ position: absolute;
86
+ inset: 0;
87
+ display: grid;
88
+ place-items: center;
89
+ transform-style: preserve-3d;
90
+ transition: transform 0.1s ease-out;
91
+ }
92
+
93
+ #mainCanvas {
94
+ width: min(80vw, 80vh);
95
+ height: min(80vw, 80vh);
96
+ max-width: 600px;
97
+ max-height: 600px;
98
+ border: 1px solid var(--glass-border);
99
+ background: #000;
100
+ transform: rotateX(15deg) rotateY(-15deg);
101
+ box-shadow:
102
+ 0 0 50px rgba(0, 255, 136, 0.2),
103
+ 0 0 100px rgba(255, 0, 110, 0.1);
104
+ transition: transform 0.3s ease;
105
+ }
106
+
107
+ .canvas-locked-x {
108
+ transform: rotateX(15deg) !important;
109
+ }
110
+
111
+ .canvas-locked-y {
112
+ transform: rotateY(-15deg) !important;
113
+ }
114
+
115
+ .overlay-effects {
116
+ position: absolute;
117
+ inset: 0;
118
+ pointer-events: none;
119
+ mix-blend-mode: screen;
120
+ }
121
+
122
+ .pulse-effect {
123
+ position: absolute;
124
+ top: 50%;
125
+ left: 50%;
126
+ width: 0;
127
+ height: 0;
128
+ border-radius: 50%;
129
+ background: radial-gradient(circle, var(--accent-color) 0%, transparent 70%);
130
+ transform: translate(-50%, -50%);
131
+ animation: pulse 0.5s ease-out;
132
+ }
133
+
134
+ @keyframes pulse {
135
+ to {
136
+ width: 200%;
137
+ height: 200%;
138
+ opacity: 0;
139
+ }
140
+ }
141
+
142
+ .controls-panel {
143
+ grid-area: controls;
144
+ background: var(--glass-bg);
145
+ backdrop-filter: blur(20px);
146
+ border-left: 1px solid var(--glass-border);
147
+ padding: 1rem;
148
+ overflow-y: auto;
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 1rem;
152
+ }
153
+
154
+ .control-section {
155
+ background: rgba(20, 20, 30, 0.6);
156
+ border-radius: 12px;
157
+ padding: 1rem;
158
+ border: 1px solid var(--glass-border);
159
+ }
160
+
161
+ .control-section h3 {
162
+ font-size: 0.9rem;
163
+ text-transform: uppercase;
164
+ letter-spacing: 1px;
165
+ color: var(--accent-color);
166
+ margin-bottom: 1rem;
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 0.5rem;
170
+ }
171
+
172
+ .file-input-wrapper {
173
+ position: relative;
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 0.5rem;
177
+ }
178
+
179
+ input[type="file"] {
180
+ position: absolute;
181
+ opacity: 0;
182
+ width: 100%;
183
+ height: 100%;
184
+ cursor: pointer;
185
+ }
186
+
187
+ .file-button {
188
+ background: linear-gradient(45deg, var(--accent-color), var(--spot-color-2));
189
+ border: none;
190
+ padding: 0.75rem 1rem;
191
+ border-radius: 8px;
192
+ color: var(--primary-bg);
193
+ font-weight: 600;
194
+ cursor: pointer;
195
+ transition: all 0.3s ease;
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ gap: 0.5rem;
200
+ }
201
+
202
+ .file-button:hover {
203
+ transform: translateY(-2px);
204
+ box-shadow: 0 5px 20px rgba(0, 255, 136, 0.4);
205
+ }
206
+
207
+ .slider-group {
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 0.5rem;
211
+ margin-bottom: 0.75rem;
212
+ }
213
+
214
+ .slider-label {
215
+ display: flex;
216
+ justify-content: space-between;
217
+ font-size: 0.8rem;
218
+ color: var(--text-secondary);
219
+ }
220
+
221
+ input[type="range"] {
222
+ width: 100%;
223
+ height: 4px;
224
+ background: var(--secondary-bg);
225
+ border-radius: 2px;
226
+ outline: none;
227
+ -webkit-appearance: none;
228
+ }
229
+
230
+ input[type="range"]::-webkit-slider-thumb {
231
+ -webkit-appearance: none;
232
+ width: 16px;
233
+ height: 16px;
234
+ background: var(--accent-color);
235
+ border-radius: 50%;
236
+ cursor: pointer;
237
+ transition: all 0.2s ease;
238
+ }
239
+
240
+ input[type="range"]::-webkit-slider-thumb:hover {
241
+ transform: scale(1.2);
242
+ box-shadow: 0 0 10px var(--accent-color);
243
+ }
244
+
245
+ .toggle-group {
246
+ display: grid;
247
+ grid-template-columns: 1fr 1fr;
248
+ gap: 0.5rem;
249
+ }
250
+
251
+ .toggle-button {
252
+ background: var(--secondary-bg);
253
+ border: 1px solid var(--glass-border);
254
+ padding: 0.5rem;
255
+ border-radius: 6px;
256
+ color: var(--text-secondary);
257
+ cursor: pointer;
258
+ transition: all 0.3s ease;
259
+ font-size: 0.8rem;
260
+ }
261
+
262
+ .toggle-button.active {
263
+ background: var(--accent-color);
264
+ color: var(--primary-bg);
265
+ border-color: var(--accent-color);
266
+ }
267
+
268
+ .action-button {
269
+ background: linear-gradient(45deg, var(--spot-color-1), var(--spot-color-2));
270
+ border: none;
271
+ padding: 1rem;
272
+ border-radius: 8px;
273
+ color: white;
274
+ font-weight: 600;
275
+ cursor: pointer;
276
+ transition: all 0.3s ease;
277
+ text-transform: uppercase;
278
+ letter-spacing: 1px;
279
+ width: 100%;
280
+ }
281
+
282
+ .action-button:hover {
283
+ transform: translateY(-2px);
284
+ box-shadow: 0 5px 25px rgba(255, 0, 110, 0.5);
285
+ }
286
+
287
+ .status-bar {
288
+ grid-area: footer;
289
+ background: var(--glass-bg);
290
+ backdrop-filter: blur(20px);
291
+ border-top: 1px solid var(--glass-border);
292
+ padding: 0.5rem 2rem;
293
+ display: flex;
294
+ justify-content: space-between;
295
+ align-items: center;
296
+ font-size: 0.8rem;
297
+ color: var(--text-secondary);
298
+ }
299
+
300
+ .image-thumbnails {
301
+ display: grid;
302
+ grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
303
+ gap: 0.5rem;
304
+ max-height: 150px;
305
+ overflow-y: auto;
306
+ }
307
+
308
+ .thumbnail {
309
+ aspect-ratio: 1;
310
+ background: var(--secondary-bg);
311
+ border-radius: 4px;
312
+ cursor: pointer;
313
+ transition: all 0.2s ease;
314
+ border: 2px solid transparent;
315
+ }
316
+
317
+ .thumbnail:hover {
318
+ border-color: var(--accent-color);
319
+ transform: scale(1.05);
320
+ }
321
+
322
+ .thumbnail.active {
323
+ border-color: var(--spot-color-1);
324
+ }
325
+
326
+ @media (max-width: 768px) {
327
+ body {
328
+ grid-template-columns: 1fr;
329
+ grid-template-rows: auto 1fr auto auto;
330
+ grid-template-areas:
331
+ "header"
332
+ "main"
333
+ "controls"
334
+ "footer";
335
+ }
336
+
337
+ .controls-panel {
338
+ max-height: 40vh;
339
+ }
340
+
341
+ #mainCanvas {
342
+ width: 90vw;
343
+ height: 90vw;
344
+ }
345
+ }
346
+
347
+ .loading-overlay {
348
+ position: fixed;
349
+ inset: 0;
350
+ background: rgba(0, 0, 0, 0.9);
351
+ display: none;
352
+ place-items: center;
353
+ z-index: 1000;
354
+ }
355
+
356
+ .loading-spinner {
357
+ width: 60px;
358
+ height: 60px;
359
+ border: 3px solid var(--glass-border);
360
+ border-top-color: var(--accent-color);
361
+ border-radius: 50%;
362
+ animation: spin 1s linear infinite;
363
+ }
364
+
365
+ @keyframes spin {
366
+ to { transform: rotate(360deg); }
367
+ }
368
+
369
+ .audio-visualizer {
370
+ height: 40px;
371
+ background: var(--secondary-bg);
372
+ border-radius: 6px;
373
+ display: flex;
374
+ align-items: flex-end;
375
+ gap: 2px;
376
+ padding: 4px;
377
+ overflow: hidden;
378
+ }
379
+
380
+ .audio-bar {
381
+ flex: 1;
382
+ background: linear-gradient(to top, var(--accent-color), var(--spot-color-1));
383
+ border-radius: 2px;
384
+ transition: height 0.1s ease;
385
+ min-height: 2px;
386
+ }
387
+ </style>
388
+ </head>
389
+ <body>
390
+ <header>
391
+ <h1><i class="fas fa-magic"></i> Advanced Morphing Studio</h1>
392
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
393
+ </header>
394
+
395
+ <main>
396
+ <div class="showroom-container" id="showroom">
397
+ <canvas id="mainCanvas"></canvas>
398
+ <div class="overlay-effects" id="overlayEffects"></div>
399
+ </div>
400
+ </main>
401
+
402
+ <aside class="controls-panel">
403
+ <div class="control-section">
404
+ <h3><i class="fas fa-images"></i> Bilder</h3>
405
+ <div class="file-input-wrapper">
406
+ <button class="file-button" onclick="document.getElementById('folderInput').click()">
407
+ <i class="fas fa-folder-open"></i> Ordner laden
408
+ </button>
409
+ <input type="file" id="folderInput" webkitdirectory directory multiple accept="image/*">
410
+ <button class="file-button" onclick="document.getElementById('fileInput').click()">
411
+ <i class="fas fa-file-image"></i> Einzelbilder
412
+ </button>
413
+ <input type="file" id="fileInput" multiple accept="image/*">
414
+ </div>
415
+ <div class="image-thumbnails" id="thumbnails"></div>
416
+ </div>
417
+
418
+ <div class="control-section">
419
+ <h3><i class="fas fa-sliders-h"></i> Farbanalyse</h3>
420
+ <div class="slider-group">
421
+ <div class="slider-label">
422
+ <span>Kontrast Winkel</span>
423
+ <span id="contrastValue">50</span>
424
+ </div>
425
+ <input type="range" id="contrastAngle" min="0" max="100" value="50">
426
+ </div>
427
+ <div class="slider-group">
428
+ <div class="slider-label">
429
+ <span>Lichtverschiebung</span>
430
+ <span id="lightShiftValue">0</span>
431
+ </div>
432
+ <input type="range" id="lightShift" min="-100" max="100" value="0">
433
+ </div>
434
+ <div class="slider-group">
435
+ <div class="slider-label">
436
+ <span>Spot Farbe 1</span>
437
+ <span id="spotColor1Value">#ff006e</span>
438
+ </div>
439
+ <input type="color" id="spotColor1" value="#ff006e" style="width: 100%; height: 30px; border: none; border-radius: 6px;">
440
+ </div>
441
+ <div class="slider-group">
442
+ <div class="slider-label">
443
+ <span>Spot Farbe 2</span>
444
+ <span id="spotColor2Value">#00f5ff</span>
445
+ </div>
446
+ <input type="color" id="spotColor2" value="#00f5ff" style="width: 100%; height: 30px; border: none; border-radius: 6px;">
447
+ </div>
448
+ </div>
449
+
450
+ <div class="control-section">
451
+ <h3><i class="fas fa-cube"></i> 3D Effekte</h3>
452
+ <div class="toggle-group">
453
+ <button class="toggle-button active" id="toggle3D">3D Raum</button>
454
+ <button class="toggle-button" id="toggleDoubleExp">Double Exposure</button>
455
+ <button class="toggle-button" id="toggleMorph">Morphing</button>
456
+ <button class="toggle-button" id="togglePulse">Pulse</button>
457
+ </div>
458
+ <div class="slider-group">
459
+ <div class="slider-label">
460
+ <span>3D Tiefe</span>
461
+ <span id="depth3DValue">50</span>
462
+ </div>
463
+ <input type="range" id="depth3D" min="0" max="100" value="50">
464
+ </div>
465
+ <div class="slider-group">
466
+ <div class="slider-label">
467
+ <span>Morph Stärke</span>
468
+ <span id="morphStrengthValue">30</span>
469
+ </div>
470
+ <input type="range" id="morphStrength" min="0" max="100" value="30">
471
+ </div>
472
+ </div>
473
+
474
+ <div class="control-section">
475
+ <h3><i class="fas fa-music"></i> Audio Engine</h3>
476
+ <div class="file-input-wrapper">
477
+ <button class="file-button" onclick="document.getElementById('audioInput').click()">
478
+ <i class="fas fa-file-audio"></i> Audio importieren
479
+ </button>
480
+ <input type="file" id="audioInput" accept="audio/*">
481
+ </div>
482
+ <div class="audio-visualizer" id="audioVisualizer"></div>
483
+ <div class="slider-group">
484
+ <div class="slider-label">
485
+ <span>Takt (BPM)</span>
486
+ <span id="bpmValue">120</span>
487
+ </div>
488
+ <input type="range" id="bpm" min="60" max="180" value="120">
489
+ </div>
490
+ <div class="slider-group">
491
+ <div class="slider-label">
492
+ <span>Fazer Tiefe</span>
493
+ <span id="fazerValue">20</span>
494
+ </div>
495
+ <input type="range" id="fazerDepth" min="0" max="100" value="20">
496
+ </div>
497
+ <div class="slider-group">
498
+ <div class="slider-label">
499
+ <span>Flanger Rate</span>
500
+ <span id="flangerValue">0.5</span>
501
+ </div>
502
+ <input type="range" id="flangerRate" min="0" max="5" step="0.1" value="0.5">
503
+ </div>
504
+ <button class="action-button" id="generateAudioBtn">
505
+ <i class="fas fa-play"></i> Generiere Space Audio
506
+ </button>
507
+ </div>
508
+
509
+ <div class="control-section">
510
+ <h3><i class="fas fa-video"></i> Video Output</h3>
511
+ <div class="slider-group">
512
+ <div class="slider-label">
513
+ <span>Frame Rate</span>
514
+ <span id="fpsValue">30</span>
515
+ </div>
516
+ <input type="range" id="fps" min="15" max="60" value="30">
517
+ </div>
518
+ <div class="slider-group">
519
+ <div class="slider-label">
520
+ <span>Übergangszeit (s)</span>
521
+ <span id="transitionValue">2</span>
522
+ </div>
523
+ <input type="range" id="transitionTime" min="1" max="10" value="2">
524
+ </div>
525
+ <button class="action-button" id="renderBtn">
526
+ <i class="fas fa-film"></i> Rendere Video
527
+ </button>
528
+ <video id="outputVideo" style="width: 100%; margin-top: 1rem; border-radius: 8px; display: none;"></video>
529
+ </div>
530
+ </aside>
531
+
532
+ <footer class="status-bar">
533
+ <div id="statusText">Bereit - Lade Bilder um zu starten</div>
534
+ <div id="sensorStatus">Sensor: Inaktiv</div>
535
+ </footer>
536
+
537
+ <div class="loading-overlay" id="loadingOverlay">
538
+ <div class="loading-spinner"></div>
539
+ </div>
540
+
541
+ <script>
542
+ // Global State
543
+ const state = {
544
+ images: [],
545
+ currentImageIndex: 0,
546
+ canvas: null,
547
+ ctx: null,
548
+ audioContext: null,
549
+ audioNodes: {},
550
+ isRendering: false,
551
+ lockedAxes: { x: false, y: false },
552
+ effects: {
553
+ contrastAngle: 50,
554
+ lightShift: 0,
555
+ spotColor1: '#ff006e',
556
+ spotColor2: '#00f5ff',
557
+ depth3D: 50,
558
+ morphStrength: 30,
559
+ doubleExposure: false,
560
+ pulse: false,
561
+ bpm: 120,
562
+ fazerDepth: 20,
563
+ flangerRate: 0.5,
564
+ fps: 30,
565
+ transitionTime: 2
566
+ }
567
+ };
568
+
569
+ // Initialize
570
+ document.addEventListener('DOMContentLoaded', () => {
571
+ initializeCanvas();
572
+ initializeControls();
573
+ initializeAudio();
574
+ initializeSensors();
575
+ createAudioVisualizer();
576
+ });
577
+
578
+ function initializeCanvas() {
579
+ state.canvas = document.getElementById('mainCanvas');
580
+ state.ctx = state.canvas.getContext('2d');
581
+ state.canvas.width = 800;
582
+ state.canvas.height = 800;
583
+ }
584
+
585
+ function initializeControls() {
586
+ // File inputs
587
+ document.getElementById('folderInput').addEventListener('change', handleFolderSelect);
588
+ document.getElementById('fileInput').addEventListener('change', handleFileSelect);
589
+ document.getElementById('audioInput').addEventListener('change', handleAudioImport);
590
+
591
+ // Sliders
592
+ const sliders = [
593
+ 'contrastAngle', 'lightShift', 'depth3D', 'morphStrength',
594
+ 'bpm', 'fazerDepth', 'flangerRate', 'fps', 'transitionTime'
595
+ ];
596
+ sliders.forEach(id => {
597
+ const slider = document.getElementById(id);
598
+ const valueSpan = document.getElementById(id + 'Value');
599
+ slider.addEventListener('input', (e) => {
600
+ const value = e.target.value;
601
+ valueSpan.textContent = value;
602
+ state.effects[id] = parseFloat(value);
603
+ updateProcessing();
604
+ });
605
+ });
606
+
607
+ // Color pickers
608
+ document.getElementById('spotColor1').addEventListener('input', (e) => {
609
+ state.effects.spotColor1 = e.target.value;
610
+ document.getElementById('spotColor1Value').textContent = e.target.value;
611
+ });
612
+ document.getElementById('spotColor2').addEventListener('input', (e) => {
613
+ state.effects.spotColor2 = e.target.value;
614
+ document.getElementById('spotColor2Value').textContent = e.target.value;
615
+ });
616
+
617
+ // Toggle buttons
618
+ document.getElementById('toggle3D').addEventListener('click', toggleEffect);
619
+ document.getElementById('toggleDoubleExp').addEventListener('click', toggleEffect);
620
+ document.getElementById('toggleMorph').addEventListener('click', toggleEffect);
621
+ document.getElementById('togglePulse').addEventListener('click', toggleEffect);
622
+
623
+ // Action buttons
624
+ document.getElementById('generateAudioBtn').addEventListener('click', generateSpaceAudio);
625
+ document.getElementById('renderBtn').addEventListener('click', renderVideo);
626
+ }
627
+
628
+ function toggleEffect(e) {
629
+ e.target.classList.toggle('active');
630
+ const effectMap = {
631
+ 'toggle3D': 'depth3D',
632
+ 'toggleDoubleExp': 'doubleExposure',
633
+ 'toggleMorph': 'morphing',
634
+ 'togglePulse': 'pulse'
635
+ };
636
+ const effect = effectMap[e.target.id];
637
+ if (effect) state.effects[effect] = e.target.classList.contains('active');
638
+ updateProcessing();
639
+ }
640
+
641
+ async function handleFolderSelect(e) {
642
+ const files = Array.from(e.target.files).filter(f => f.type.startsWith('image/'));
643
+ await loadImages(files);
644
+ }
645
+
646
+ async function handleFileSelect(e) {
647
+ await loadImages(Array.from(e.target.files));
648
+ }
649
+
650
+ async function loadImages(files) {
651
+ showLoading(true);
652
+ state.images = [];
653
+ const thumbnails = document.getElementById('thumbnails');
654
+ thumbnails.innerHTML = '';
655
+
656
+ for (const file of files) {
657
+ const img = await createImageBitmap(file);
658
+ state.images.push(img);
659
+
660
+ const thumb = document.createElement('div');
661
+ thumb.className = 'thumbnail';
662
+ thumb.style.backgroundImage = `url(${URL.createObjectURL(file)})`;
663
+ thumb.style.backgroundSize = 'cover';
664
+ thumb.onclick = () => setActiveImage(state.images.length - 1);
665
+ thumbnails.appendChild(thumb);
666
+ }
667
+
668
+ if (state.images.length > 0) {
669
+ setActiveImage(0);
670
+ updateStatus(`${state.images.length} Bilder geladen`);
671
+ }
672
+ showLoading(false);
673
+ }
674
+
675
+ function setActiveImage(index) {
676
+ state.currentImageIndex = index;
677
+ document.querySelectorAll('.thumbnail').forEach((thumb, i) => {
678
+ thumb.classList.toggle('active', i === index);
679
+ });
680
+ updateProcessing();
681
+ }
682
+
683
+ function updateProcessing() {
684
+ if (state.images.length === 0) return;
685
+
686
+ const img = state.images[state.currentImageIndex];
687
+ const canvas = state.canvas;
688
+ const ctx = state.ctx;
689
+
690
+ // Clear canvas
691
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
692
+
693
+ // Apply long exposure effect with previous image
694
+ if (state.currentImageIndex > 0 && state.effects.morphing) {
695
+ const prevImg = state.images[state.currentImageIndex - 1];
696
+ applyLongExposureMorph(ctx, prevImg, img, state.effects.morphStrength / 100);
697
+ } else {
698
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
699
+ }
700
+
701
+ // Analyze and apply color grading
702
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
703
+ applyColorAnalysis(imageData);
704
+ ctx.putImageData(imageData, 0, 0);
705
+
706
+ // Apply double exposure if enabled
707
+ if (state.effects.doubleExposure && state.currentImageIndex < state.images.length - 1) {
708
+ const nextImg = state.images[state.currentImageIndex + 1];
709
+ applyDoubleExposure(ctx, nextImg, state.effects.depth3D / 100);
710
+ }
711
+
712
+ // Apply 3D displacement
713
+ if (state.effects.depth3D) {
714
+ apply3DDisplacement(ctx, state.effects.depth3D);
715
+ }
716
+ }
717
+
718
+ function applyLongExposureMorph(ctx, img1, img2, strength) {
719
+ const tempCanvas = document.createElement('canvas');
720
+ const tempCtx = tempCanvas.getContext('2d');
721
+ tempCanvas.width = ctx.canvas.width;
722
+ tempCanvas.height = ctx.canvas.height;
723
+
724
+ // Draw first image with fade
725
+ tempCtx.globalAlpha = 1 - strength * 0.5;
726
+ tempCtx.drawImage(img1, 0, 0);
727
+
728
+ // Draw second image with morph
729
+ tempCtx.globalAlpha = strength;
730
+ tempCtx.filter = `blur(${strength * 2}px)`;
731
+ tempCtx.drawImage(img2, 0, 0);
732
+
733
+ ctx.globalAlpha = 1;
734
+ ctx.filter = 'none';
735
+ ctx.drawImage(tempCanvas, 0, 0);
736
+ }
737
+
738
+ function applyColorAnalysis(imageData) {
739
+ const data = imageData.data;
740
+ const contrast = state.effects.contrastAngle / 50;
741
+ const lightShift = state.effects.lightShift;
742
+
743
+ // Extract spot colors
744
+ const spot1 = hexToRgb(state.effects.spotColor1);
745
+ const spot2 = hexToRgb(state.effects.spotColor2);
746
+
747
+ for (let i = 0; i < data.length; i += 4) {
748
+ // Convert to grayscale with custom weights
749
+ let gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
750
+
751
+ // Apply contrast curve
752
+ gray = 128 + (gray - 128) * contrast;
753
+ gray = Math.max(0, Math.min(255, gray + lightShift));
754
+
755
+ // Determine which spot color to apply based on pixel position
756
+ const pixelIndex = i / 4;
757
+ const x = (pixelIndex % imageData.width) / imageData.width;
758
+ const y = Math.floor(pixelIndex / imageData.width) / imageData.height;
759
+
760
+ const spotMix = (x + y) / 2;
761
+ const spotColor = spotMix > 0.5 ? spot1 : spot2;
762
+ const mixFactor = Math.abs(spotMix - 0.5) * 2;
763
+
764
+ // Mix grayscale with spot color
765
+ data[i] = gray * (1 - mixFactor * 0.3) + spotColor.r * mixFactor * 0.3;
766
+ data[i + 1] = gray * (1 - mixFactor * 0.3) + spotColor.g * mixFactor * 0.3;
767
+ data[i + 2] = gray * (1 - mixFactor * 0.3) + spotColor.b * mixFactor * 0.3;
768
+ }
769
+ }
770
+
771
+ function applyDoubleExposure(ctx, overlayImg, opacity) {
772
+ ctx.globalAlpha = opacity * 0.5;
773
+ ctx.globalCompositeOperation = 'screen';
774
+ ctx.drawImage(overlayImg, 0, 0, ctx.canvas.width, ctx.canvas.height);
775
+ ctx.globalAlpha = 1;
776
+ ctx.globalCompositeOperation = 'source-over';
777
+ }
778
+
779
+ function apply3DDisplacement(ctx, depth) {
780
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
781
+ const newData = ctx.createImageData(ctx.canvas.width, ctx.canvas.height);
782
+
783
+ const displacement = depth / 10;
784
+
785
+ for (let y = 0; y < ctx.canvas.height; y++) {
786
+ for (let x = 0; x < ctx.canvas.width; x++) {
787
+ const offsetX = Math.sin(y * 0.01) * displacement;
788
+ const offsetY = Math.cos(x * 0.01) * displacement;
789
+
790
+ const srcX = Math.max(0, Math.min(ctx.canvas.width - 1, x + offsetX));
791
+ const srcY = Math.max(0, Math.min(ctx.canvas.height - 1, y + offsetY));
792
+
793
+ const srcIndex = (Math.floor(srcY) * ctx.canvas.width + Math.floor(srcX)) * 4;
794
+ const destIndex = (y * ctx.canvas.width + x) * 4;
795
+
796
+ newData.data[destIndex] = imageData.data[srcIndex];
797
+ newData.data[destIndex + 1] = imageData.data[srcIndex + 1];
798
+ newData.data[destIndex + 2] = imageData.data[srcIndex + 2];
799
+ newData.data[destIndex + 3] = imageData.data[srcIndex + 3];
800
+ }
801
+ }
802
+
803
+ ctx.putImageData(newData, 0, 0);
804
+ }
805
+
806
+ function hexToRgb(hex) {
807
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
808
+ return result ? {
809
+ r: parseInt(result[1], 16),
810
+ g: parseInt(result[2], 16),
811
+ b: parseInt(result[3], 16)
812
+ } : null;
813
+ }
814
+
815
+ function initializeAudio() {
816
+ state.audioContext = new (window.AudioContext || window.webkitAudioContext)();
817
+ }
818
+
819
+ function createAudioVisualizer() {
820
+ const visualizer = document.getElementById('audioVisualizer');
821
+ for (let i = 0; i < 32; i++) {
822
+ const bar = document.createElement('div');
823
+ bar.className = 'audio-bar';
824
+ bar.style.height = '2px';
825
+ visualizer.appendChild(bar);
826
+ }
827
+ }
828
+
829
+ function generateSpaceAudio() {
830
+ if (!state.audioContext) return;
831
+
832
+ updateStatus('Generiere Space Audio...');
833
+
834
+ // Create drone
835
+ const oscillator = state.audioContext.createOscillator();
836
+ const gainNode = state.audioContext.createGain();
837
+ const filter = state.audioContext.createBiquadFilter();
838
+
839
+ oscillator.type = 'sawtooth';
840
+ oscillator.frequency.setValueAtTime(55, state.audioContext.currentTime); // A1
841
+
842
+ filter.type = 'lowpass';
843
+ filter.frequency.setValueAtTime(200, state.audioContext.currentTime);
844
+ filter.Q.setValueAtTime(10, state.audioContext.currentTime);
845
+
846
+ gainNode.gain.setValueAtTime(0.1, state.audioContext.currentTime);
847
+
848
+ // Create LFO for pulsating
849
+ const lfo = state.audioContext.createOscillator();
850
+ const lfoGain = state.audioContext.createGain();
851
+ lfo.frequency.setValueAtTime(state.effects.bpm / 60, state.audioContext.currentTime);
852
+ lfoGain.gain.setValueAtTime(0.05, state.audioContext.currentTime);
853
+
854
+ lfo.connect(lfoGain);
855
+ lfoGain.connect(gainNode.gain);
856
+
857
+ // Create stereo panner for binaural effect
858
+ const panner = state.audioContext.createStereoPanner();
859
+ panner.pan.setValueAtTime(-0.5, state.audioContext.currentTime);
860
+
861
+ // Create delay for space echo
862
+ const delay = state.audioContext.createDelay(1);
863
+ delay.delayTime.setValueAtTime(0.5, state.audioContext.currentTime);
864
+
865
+ const delayGain = state.audioContext.createGain();
866
+ delayGain.gain.setValueAtTime(0.3, state.audioContext.currentTime);
867
+
868
+ // Create flanger
869
+ const flanger = state.audioContext.createDelay(0.05);
870
+ flanger.delayTime.setValueAtTime(0.01, state.audioContext.currentTime);
871
+
872
+ const flangerLfo = state.audioContext.createOscillator();
873
+ flangerLfo.frequency.setValueAtTime(state.effects.flangerRate, state.audioContext.currentTime);
874
+
875
+ const flangerGain = state.audioContext.createGain();
876
+ flangerGain.gain.setValueAtTime(0.01, state.audioContext.currentTime);
877
+
878
+ flangerLfo.connect(flangerGain);
879
+ flangerGain.connect(flanger.delayTime);
880
+
881
+ // Connect nodes
882
+ oscillator.connect(filter);
883
+ filter.connect(gainNode);
884
+ gainNode.connect(delay);
885
+ gainNode.connect(panner);
886
+ delay.connect(delayGain);
887
+ delayGain.connect(panner);
888
+ panner.connect(state.audioContext.destination);
889
+
890
+ flangerLfo.start();
891
+ lfo.start();
892
+ oscillator.start();
893
+
894
+ // Store nodes for later manipulation
895
+ state.audioNodes = { oscillator, gainNode, filter, lfo, panner, flangerLfo };
896
+
897
+ // Generate random thunder/explosions
898
+ setInterval(() => {
899
+ if (Math.random() > 0.7) {
900
+ createThunderEffect();
901
+ }
902
+ }, 3000);
903
+
904
+ updateStatus('Space Audio aktiv');
905
+ }
906
+
907
+ function createThunderEffect() {
908
+ if (!state.audioContext) return;
909
+
910
+ const noise = state.audioContext.createBufferSource();
911
+ const buffer = state.audioContext.createBuffer(1, state.audioContext.sampleRate * 2, state.audioContext.sampleRate);
912
+ const data = buffer.getChannelData(0);
913
+
914
+ for (let i = 0; i < buffer.length; i++) {
915
+ data[i] = Math.random() * 2 - 1;
916
+ }
917
+
918
+ noise.buffer = buffer;
919
+
920
+ const filter = state.audioContext.createBiquadFilter();
921
+ filter.type = 'lowpass';
922
+ filter.frequency.setValueAtTime(100, state.audioContext.currentTime);
923
+ filter.frequency.exponentialRampToValueAtTime(20, state.audioContext.currentTime + 2);
924
+
925
+ const gainNode = state.audioContext.createGain();
926
+ gainNode.gain.setValueAtTime(0.5, state.audioContext.currentTime);
927
+ gainNode.gain.exponentialRampToValueAtTime(0.001, state.audioContext.currentTime + 2);
928
+
929
+ noise.connect(filter);
930
+ filter.connect(gainNode);
931
+ gainNode.connect(state.audioContext.destination);
932
+
933
+ noise.start();
934
+ noise.stop(state.audioContext.currentTime + 2);
935
+
936
+ // Trigger visual pulse
937
+ if (state.effects.pulse) {
938
+ createPulseEffect();
939
+ }
940
+ }
941
+
942
+ function createPulseEffect() {
943
+ const overlay = document.getElementById('overlayEffects');
944
+ const pulse = document.createElement('div');
945
+ pulse.className = 'pulse-effect';
946
+ overlay.appendChild(pulse);
947
+ setTimeout(() => pulse.remove(), 500);
948
+ }
949
+
950
+ function handleAudioImport(e) {
951
+ const file = e.target.files[0];
952
+ if (!file) return;
953
+
954
+ const reader = new FileReader();
955
+ reader.onload = async (event) => {
956
+ const audioBuffer = await state.audioContext.decodeAudioData(event.target.result);
957
+ analyzeAudioBeat(audioBuffer);
958
+ };
959
+ reader.readAsArrayBuffer(file);
960
+ }
961
+
962
+ function analyzeAudioBeat(buffer) {
963
+ // Simple beat detection
964
+ const channelData = buffer.getChannelData(0);
965
+ const sampleRate = buffer.sampleRate;
966
+ const bpm = detectBPM(channelData, sampleRate);
967
+ state.effects.bpm = bpm;
968
+ document.getElementById('bpm').value = bpm;
969
+ document.getElementById('bpmValue').textContent = bpm;
970
+ updateStatus(`BPM erkannt: ${bpm}`);
971
+ }
972
+
973
+ function detectBPM(data, sampleRate) {
974
+ // Simplified beat detection - returns average energy peaks
975
+ const frameSize = Math.floor(sampleRate * 0.1);
976
+ let sum = 0;
977
+ let peaks = 0;
978
+
979
+ for (let i = 0; i < data.length; i += frameSize) {
980
+ let energy = 0;
981
+ for (let j = i; j < Math.min(i + frameSize, data.length); j++) {
982
+ energy += Math.abs(data[j]);
983
+ }
984
+ if (energy > 0.1) peaks++;
985
+ sum += energy;
986
+ }
987
+
988
+ return Math.max(60, Math.min(180, Math.floor(60 / (peaks / (data.length / sampleRate)))));
989
+ }
990
+
991
+ function initializeSensors() {
992
+ if (window.DeviceOrientationEvent) {
993
+ window.addEventListener('deviceorientation', handleOrientation);
994
+ document.getElementById('sensorStatus').textContent = 'Sensor: Aktiv';
995
+ } else {
996
+ // Fallback to mouse interaction
997
+ let mouseX = 0, mouseY = 0;
998
+ document.addEventListener('mousemove', (e) => {
999
+ mouseX = (e.clientX / window.innerWidth - 0.5) * 2;
1000
+ mouseY = (e.clientY / window.innerHeight - 0.5) * 2;
1001
+ update3DView(mouseY * 30, mouseX * 30);
1002
+ });
1003
+ document.getElementById('sensorStatus').textContent = 'Sensor: Maus-Modus';
1004
+ }
1005
+
1006
+ // Click to lock axes
1007
+ document.getElementById('showroom').addEventListener('click', (e) => {
1008
+ if (e.shiftKey) {
1009
+ state.lockedAxes.x = !state.lockedAxes.x;
1010
+ state.canvas.classList.toggle('canvas-locked-x', state.lockedAxes.x);
1011
+ } else if (e.ctrlKey) {
1012
+ state.lockedAxes.y = !state.lockedAxes.y;
1013
+ state.canvas.classList.toggle('canvas-locked-y', state.lockedAxes.y);
1014
+ }
1015
+ });
1016
+ }
1017
+
1018
+ function handleOrientation(event) {
1019
+ const alpha = event.alpha || 0;
1020
+ const beta = event.beta || 0;
1021
+ const gamma = event.gamma || 0;
1022
+
1023
+ const rotX = state.lockedAxes.x ? 15 : beta * 0.5;
1024
+ const rotY = state.lockedAxes.y ? -15 : gamma * 0.5;
1025
+
1026
+ update3DView(rotX, rotY);
1027
+
1028
+ // Trigger effects based on movement
1029
+ if (Math.abs(beta) > 30 || Math.abs(gamma) > 30) {
1030
+ createThunderEffect();
1031
+ }
1032
+ }
1033
+
1034
+ function update3DView(rotX, rotY) {
1035
+ const showroom = document.getElementById('showroom');
1036
+ if (!state.lockedAxes.x && !state.lockedAxes.y) {
1037
+ showroom.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg)`;
1038
+ } else if (state.lockedAxes.x) {
1039
+ showroom.style.transform = `rotateY(${rotY}deg)`;
1040
+ } else if (state.lockedAxes.y) {
1041
+ showroom.style.transform = `rotateX(${rotX}deg)`;
1042
+ }
1043
+ }
1044
+
1045
+ async function renderVideo() {
1046
+ if (state.images.length < 2) {
1047
+ updateStatus('Mindestens 2 Bilder für Video benötigt');
1048
+ return;
1049
+ }
1050
+
1051
+ showLoading(true);
1052
+ updateStatus('Rendere Video...');
1053
+
1054
+ const fps = state.effects.fps;
1055
+ const duration = state.effects.transitionTime;
1056
+ const totalFrames = fps * duration * (state.images.length - 1);
1057
+
1058
+ const stream = state.canvas.captureStream(fps);
1059
+ const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
1060
+ const chunks = [];
1061
+
1062
+ recorder.ondataavailable = (e) => chunks.push(e.data);
1063
+ recorder.onstop = () => {
1064
+ const blob = new Blob(chunks, { type: 'video/webm' });
1065
+ const video = document.getElementById('outputVideo');
1066
+ video.src = URL.createObjectURL(blob);
1067
+ video.style.display = 'block';
1068
+ showLoading(false);
1069
+ updateStatus('Video fertig!');
1070
+ };
1071
+
1072
+ recorder.start();
1073
+
1074
+ // Render frames
1075
+ for (let i = 0; i < state.images.length - 1; i++) {
1076
+ for (let frame = 0; frame < fps * duration; frame++) {
1077
+ const progress = frame / (fps * duration);
1078
+
1079
+ // Interpolate between images
1080
+ await interpolateFrames(state.images[i], state.images[i + 1], progress);
1081
+
1082
+ // Update audio visualizer
1083
+ updateVisualizer();
1084
+
1085
+ await new Promise(r => setTimeout(r, 1000 / fps));
1086
+ }
1087
+ }
1088
+
1089
+ recorder.stop();
1090
+ }
1091
+
1092
+ async function interpolateFrames(img1, img2, progress) {
1093
+ const ctx = state.ctx;
1094
+ const canvas = state.canvas;
1095
+
1096
+ // Clear with fade
1097
+ ctx.globalAlpha = 0.1;
1098
+ ctx.fillStyle = '#000';
1099
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
1100
+ ctx.globalAlpha = 1;
1101
+
1102
+ // Draw morphed image