HI7RAI commited on
Commit
49f5034
·
verified ·
1 Parent(s): 2fbb230

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +743 -19
index.html CHANGED
@@ -1,19 +1,743 @@
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">
6
+ <title>Pro Video & Frame Werkzeug</title>
7
+ <!-- Icons importieren -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+
10
+ <style>
11
+ :root {
12
+ --bg-color: #121212;
13
+ --panel-color: #1e1e1e;
14
+ --accent-color: #00e5ff;
15
+ --accent-secondary: #ff0055;
16
+ --text-color: #e0e0e0;
17
+ --input-bg: #2c2c2c;
18
+ --border-radius: 12px;
19
+ --font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
20
+ }
21
+
22
+ * {
23
+ box-sizing: border-box;
24
+ margin: 0;
25
+ padding: 0;
26
+ outline: none;
27
+ }
28
+
29
+ body {
30
+ background-color: var(--bg-color);
31
+ color: var(--text-color);
32
+ font-family: var(--font-family);
33
+ display: flex;
34
+ flex-direction: column;
35
+ min-height: 100vh;
36
+ overflow-x: hidden;
37
+ }
38
+
39
+ /* HEADER */
40
+ header {
41
+ background: linear-gradient(90deg, #0f0f0f, #1a1a1a);
42
+ padding: 1rem 2rem;
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ border-bottom: 1px solid #333;
47
+ box-shadow: 0 4px 6px rgba(0,0,0,0.3);
48
+ }
49
+
50
+ h1 {
51
+ font-size: 1.2rem;
52
+ text-transform: uppercase;
53
+ letter-spacing: 2px;
54
+ color: var(--accent-color);
55
+ }
56
+
57
+ .credits a {
58
+ color: var(--text-color);
59
+ text-decoration: none;
60
+ font-size: 0.9rem;
61
+ opacity: 0.7;
62
+ transition: opacity 0.3s;
63
+ }
64
+ .credits a:hover {
65
+ opacity: 1;
66
+ color: var(--accent-color);
67
+ }
68
+
69
+ /* MAIN LAYOUT */
70
+ main {
71
+ flex: 1;
72
+ display: grid;
73
+ grid-template-columns: 1fr 350px;
74
+ gap: 20px;
75
+ padding: 20px;
76
+ height: calc(100vh - 60px);
77
+ }
78
+
79
+ @media (max-width: 900px) {
80
+ main {
81
+ grid-template-columns: 1fr;
82
+ height: auto;
83
+ }
84
+ }
85
+
86
+ /* PREVIEW AREA (THE WORLD) */
87
+ .preview-container {
88
+ background-color: #000;
89
+ border-radius: var(--border-radius);
90
+ position: relative;
91
+ overflow: hidden;
92
+ display: flex;
93
+ justify-content: center;
94
+ align-items: center;
95
+ border: 1px solid #333;
96
+ perspective: 1000px; /* Für 3D Effekte */
97
+ }
98
+
99
+ #world {
100
+ width: 100%;
101
+ height: 100%;
102
+ display: flex;
103
+ justify-content: center;
104
+ align-items: center;
105
+ transform-style: preserve-3d;
106
+ transition: transform 0.1s ease-out; /* Smooth Gyro */
107
+ position: relative;
108
+ }
109
+
110
+ /* Das Grid der Bilder */
111
+ .image-grid {
112
+ display: grid;
113
+ gap: 10px;
114
+ padding: 20px;
115
+ background: rgba(255, 255, 255, 0.05);
116
+ border-radius: 8px;
117
+ transform-style: preserve-3d;
118
+ transition: filter 0.2s;
119
+ }
120
+
121
+ .grid-item {
122
+ width: 80px;
123
+ height: 80px;
124
+ background-color: #333;
125
+ background-image: url('https://picsum.photos/seed/tech/100/100.jpg');
126
+ background-size: cover;
127
+ border-radius: 4px;
128
+ box-shadow: 0 4px 8px rgba(0,0,0,0.5);
129
+ transition: transform 0.3s;
130
+ }
131
+
132
+ .grid-item:hover {
133
+ transform: scale(1.1) translateZ(20px);
134
+ border: 2px solid var(--accent-color);
135
+ }
136
+
137
+ /* Video Overlay */
138
+ .video-overlay {
139
+ position: absolute;
140
+ top: 20px;
141
+ left: 20px;
142
+ z-index: 10;
143
+ display: flex;
144
+ gap: 10px;
145
+ }
146
+
147
+ .video-element {
148
+ width: 200px;
149
+ height: 112px;
150
+ background: #000;
151
+ border: 1px solid var(--accent-color);
152
+ display: none; /* Standardmäßig ausgeblendet, wird per JS gesteuert */
153
+ object-fit: cover;
154
+ }
155
+ .video-element.active {
156
+ display: block;
157
+ }
158
+
159
+ /* CONTROL PANEL */
160
+ .controls {
161
+ background-color: var(--panel-color);
162
+ border-radius: var(--border-radius);
163
+ padding: 20px;
164
+ overflow-y: auto;
165
+ display: flex;
166
+ flex-direction: column;
167
+ gap: 20px;
168
+ border: 1px solid #333;
169
+ }
170
+
171
+ .control-group {
172
+ background: rgba(0,0,0,0.2);
173
+ padding: 15px;
174
+ border-radius: 8px;
175
+ border-left: 3px solid var(--accent-color);
176
+ }
177
+
178
+ .control-group h3 {
179
+ font-size: 0.9rem;
180
+ margin-bottom: 15px;
181
+ color: var(--accent-color);
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 8px;
185
+ }
186
+
187
+ label {
188
+ display: block;
189
+ margin-bottom: 5px;
190
+ font-size: 0.8rem;
191
+ color: #aaa;
192
+ }
193
+
194
+ /* Inputs & Sliders */
195
+ input[type="range"] {
196
+ width: 100%;
197
+ margin-bottom: 10px;
198
+ accent-color: var(--accent-color);
199
+ }
200
+
201
+ input[type="number"], input[type="text"] {
202
+ width: 100%;
203
+ background: var(--input-bg);
204
+ border: 1px solid #444;
205
+ color: white;
206
+ padding: 8px;
207
+ border-radius: 4px;
208
+ margin-bottom: 10px;
209
+ }
210
+
211
+ input[type="file"] {
212
+ font-size: 0.8rem;
213
+ width: 100%;
214
+ margin-bottom: 10px;
215
+ }
216
+
217
+ /* Buttons */
218
+ .btn {
219
+ background: #333;
220
+ color: white;
221
+ border: none;
222
+ padding: 8px 12px;
223
+ border-radius: 4px;
224
+ cursor: pointer;
225
+ font-size: 0.85rem;
226
+ transition: all 0.2s;
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ gap: 6px;
231
+ width: 100%;
232
+ margin-bottom: 5px;
233
+ }
234
+
235
+ .btn:hover {
236
+ background: #444;
237
+ }
238
+
239
+ .btn-primary {
240
+ background: var(--accent-color);
241
+ color: #000;
242
+ font-weight: bold;
243
+ }
244
+ .btn-primary:hover {
245
+ background: #00b8cc;
246
+ }
247
+
248
+ .btn-danger {
249
+ background: var(--accent-secondary);
250
+ color: white;
251
+ }
252
+
253
+ .btn.locked {
254
+ background: var(--accent-secondary);
255
+ box-shadow: inset 0 0 5px rgba(0,0,0,0.5);
256
+ }
257
+
258
+ /* Toggle Switch für Szenen */
259
+ .scene-toggle {
260
+ display: flex;
261
+ background: #000;
262
+ border-radius: 4px;
263
+ padding: 2px;
264
+ margin-bottom: 10px;
265
+ }
266
+ .scene-option {
267
+ flex: 1;
268
+ text-align: center;
269
+ padding: 8px;
270
+ cursor: pointer;
271
+ font-size: 0.8rem;
272
+ border-radius: 4px;
273
+ }
274
+ .scene-option.active {
275
+ background: var(--accent-color);
276
+ color: #000;
277
+ font-weight: bold;
278
+ }
279
+
280
+ /* HUD Overlay */
281
+ .hud {
282
+ position: absolute;
283
+ bottom: 20px;
284
+ right: 20px;
285
+ background: rgba(0, 0, 0, 0.7);
286
+ padding: 10px;
287
+ border-radius: 8px;
288
+ font-family: monospace;
289
+ font-size: 0.9rem;
290
+ pointer-events: none;
291
+ z-index: 100;
292
+ border: 1px solid #555;
293
+ }
294
+ .hud-row {
295
+ display: flex;
296
+ justify-content: space-between;
297
+ gap: 15px;
298
+ margin-bottom: 2px;
299
+ }
300
+
301
+ /* Audio Beat Visualizer Placeholder */
302
+ .beat-indicator {
303
+ width: 100%;
304
+ height: 10px;
305
+ background: #333;
306
+ border-radius: 5px;
307
+ overflow: hidden;
308
+ margin-top: 5px;
309
+ }
310
+ .beat-bar {
311
+ width: 0%;
312
+ height: 100%;
313
+ background: var(--accent-secondary);
314
+ transition: width 0.1s linear;
315
+ }
316
+
317
+ </style>
318
+ </head>
319
+ <body>
320
+
321
+ <header>
322
+ <h1><i class="fas fa-cut"></i> VideoCutter & AlignTool</h1>
323
+ <div class="credits">
324
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
325
+ </div>
326
+ </header>
327
+
328
+ <main>
329
+ <!-- LINKER BEREICH: VORSCHAU -->
330
+ <div class="preview-container">
331
+ <div id="world">
332
+ <!-- Video Overlay (Szene 1 & 2) -->
333
+ <div class="video-overlay">
334
+ <video id="videoScene1" class="video-element active" muted loop playsinline></video>
335
+ <video id="videoScene2" class="video-element" muted loop playsinline></video>
336
+ </div>
337
+
338
+ <!-- Grid mit Einzelbildern -->
339
+ <div class="image-grid" id="imageGrid">
340
+ <!-- Dynamisch generierte Bilder -->
341
+ </div>
342
+ </div>
343
+
344
+ <!-- Gyro HUD -->
345
+ <div class="hud">
346
+ <div class="hud-row"><span>Gyro X:</span> <span id="hudX">0°</span></div>
347
+ <div class="hud-row"><span>Gyro Y:</span> <span id="hudY">0°</span></div>
348
+ <div class="hud-row"><span>Status:</span> <span id="hudStatus">Bereit</span></div>
349
+ </div>
350
+ </div>
351
+
352
+ <!-- RECHTER BEREICH: STEUERUNG -->
353
+ <div class="controls">
354
+
355
+ <!-- 1. VIDEO CUTTER -->
356
+ <div class="control-group">
357
+ <h3><i class="fas fa-film"></i> Video Cutter (2 Szenen)</h3>
358
+
359
+ <div class="scene-toggle">
360
+ <div class="scene-option active" onclick="switchScene(1)">Szene 1</div>
361
+ <div class="scene-option" onclick="switchScene(2)">Szene 2</div>
362
+ </div>
363
+
364
+ <label>Szene 1 Datei</label>
365
+ <input type="file" accept="video/*" onchange="loadVideo(this, 1)">
366
+
367
+ <label>Szene 2 Datei</label>
368
+ <input type="file" accept="video/*" onchange="loadVideo(this, 2)">
369
+
370
+ <button class="btn btn-primary" onclick="togglePlay()">
371
+ <i class="fas fa-play"></i> / <i class="fas fa-pause"></i> Wiedergabe
372
+ </button>
373
+ </div>
374
+
375
+ <!-- 2. RASTER & MATHE -->
376
+ <div class="control-group">
377
+ <h3><i class="fas fa-th"></i> Raster & Math (JS)</h3>
378
+ <label>Raster Wert (Gap): <span id="rasterValDisplay">0.00</span></label>
379
+ <input type="range" id="rasterInput" min="0" max="100" step="0.1">
380
+
381
+ <div style="font-size: 0.75rem; color: #888; margin-bottom: 10px;">
382
+ Mathematische Basis: <code>Math.pow(Math.PI, 3)</code> ≈ 31.006
383
+ </div>
384
+
385
+ <label>Gesamt Bilder (Anzahl)</label>
386
+ <input type="range" id="imgCountInput" min="1" max="50" value="12" oninput="updateGridCount()">
387
+ </div>
388
+
389
+ <!-- 3. AUDIO & BPM -->
390
+ <div class="control-group">
391
+ <h3><i class="fas fa-music"></i> Audio & BPM</h3>
392
+ <label>Audio Datei</label>
393
+ <input type="file" accept="audio/*" id="audioInput">
394
+
395
+ <label>BPM (Beats Per Minute)</label>
396
+ <input type="number" id="bpmInput" value="120" min="60" max="200">
397
+
398
+ <div class="beat-indicator">
399
+ <div class="beat-bar" id="beatBar"></div>
400
+ </div>
401
+ </div>
402
+
403
+ <!-- 4. LICHT & FARBE -->
404
+ <div class="control-group">
405
+ <h3><i class="fas fa-adjust"></i> Licht Kontrast / Farbe</h3>
406
+
407
+ <label>Helligkeit (Light)</label>
408
+ <input type="range" id="brightnessInput" min="0" max="200" value="100" oninput="updateFilters()">
409
+
410
+ <label>Kontrast</label>
411
+ <input type="range" id="contrastInput" min="0" max="200" value="100" oninput="updateFilters()">
412
+
413
+ <label>Sättigung (Color)</label>
414
+ <input type="range" id="saturateInput" min="0" max="200" value="100" oninput="updateFilters()">
415
+ </div>
416
+
417
+ <!-- 5. WINKEL / GYRO CONTROL -->
418
+ <div class="control-group">
419
+ <h3><i class="fas fa-sync-alt"></i> Winkel Ausrichtung</h3>
420
+
421
+ <button id="reqPermission" class="btn btn-primary">
422
+ <i class="fas fa-mobile-alt"></i> Gyro Berechtigung
423
+ </button>
424
+
425
+ <label>Geschwindigkeits-Multiplikator: <span id="multVal">1.0</span></label>
426
+ <input type="range" id="speedMult" min="0.1" max="5.0" step="0.1" value="1.0">
427
+
428
+ <label>Manueller Offset (Touchpoint)</label>
429
+ <input type="number" id="manualOffset" value="0">
430
+
431
+ <div style="display: flex; gap: 10px; margin-top: 10px;">
432
+ <button id="lockXBtn" class="btn" onclick="toggleLock('x')">
433
+ <i class="fas fa-unlock"></i> X-Achse
434
+ </button>
435
+ <button id="lockYBtn" class="btn" onclick="toggleLock('y')">
436
+ <i class="fas fa-unlock"></i> Y-Achse
437
+ </button>
438
+ </div>
439
+ </div>
440
+
441
+ </div>
442
+ </main>
443
+
444
+ <script>
445
+ // --- 1. INITIALISIERUNG & HELPER ---
446
+ const world = document.getElementById('world');
447
+ const imageGrid = document.getElementById('imageGrid');
448
+ const rasterInput = document.getElementById('rasterInput');
449
+ const rasterValDisplay = document.getElementById('rasterValDisplay');
450
+
451
+ // Default Wert: Pi^3
452
+ const PI_CUBED = Math.pow(Math.PI, 3); // ~31.006
453
+ rasterInput.value = PI_CUBED;
454
+ rasterValDisplay.innerText = PI_CUBED.toFixed(3);
455
+
456
+ // Grid beim Start generieren
457
+ updateGridCount();
458
+
459
+ // --- 2. VIDEO CUTTER LOGIK ---
460
+ let currentScene = 1;
461
+ const vid1 = document.getElementById('videoScene1');
462
+ const vid2 = document.getElementById('videoScene2');
463
+ let isPlaying = false;
464
+
465
+ function loadVideo(input, sceneNum) {
466
+ const file = input.files[0];
467
+ if (file) {
468
+ const url = URL.createObjectURL(file);
469
+ const vid = sceneNum === 1 ? vid1 : vid2;
470
+ vid.src = url;
471
+ vid.load();
472
+ // Automatisch abspielen wenn Szene aktiv
473
+ if (currentScene === sceneNum && isPlaying) vid.play();
474
+ }
475
+ }
476
+
477
+ function switchScene(num) {
478
+ // UI Update
479
+ document.querySelectorAll('.scene-option').forEach((el, idx) => {
480
+ el.classList.toggle('active', idx + 1 === num);
481
+ });
482
+
483
+ // Video Update
484
+ if (currentScene === num) return; // Nichts tun wenn schon aktiv
485
+
486
+ // Altes pausieren
487
+ const oldVid = currentScene === 1 ? vid1 : vid2;
488
+ oldVid.classList.remove('active');
489
+ if(isPlaying) oldVid.pause();
490
+
491
+ currentScene = num;
492
+ const newVid = currentScene === 1 ? vid1 : vid2;
493
+ newVid.classList.add('active');
494
+ if(isPlaying) newVid.play();
495
+ }
496
+
497
+ function togglePlay() {
498
+ isPlaying = !isPlaying;
499
+ const activeVid = currentScene === 1 ? vid1 : vid2;
500
+ if (isPlaying) {
501
+ activeVid.play();
502
+ } else {
503
+ vid1.pause();
504
+ vid2.pause();
505
+ }
506
+ }
507
+
508
+ // --- 3. RASTER & GRID LOGIK (MATH) ---
509
+ rasterInput.addEventListener('input', (e) => {
510
+ const val = parseFloat(e.target.value);
511
+ rasterValDisplay.innerText = val.toFixed(3);
512
+ updateGridGap(val);
513
+ });
514
+
515
+ function updateGridGap(val) {
516
+ // Wir teilen den Wert durch 10, damit es gut in px passt
517
+ imageGrid.style.gap = `${val / 10}px`;
518
+ }
519
+ // Initiale Anwendeung
520
+ updateGridGap(PI_CUBED);
521
+
522
+ function updateGridCount() {
523
+ const count = document.getElementById('imgCountInput').value;
524
+ imageGrid.innerHTML = '';
525
+
526
+ // Grid Spalten anpassen basierend auf Anzahl (quadratisch näherungsweise)
527
+ const cols = Math.ceil(Math.sqrt(count));
528
+ imageGrid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
529
+
530
+ for(let i=0; i<count; i++) {
531
+ const div = document.createElement('div');
532
+ div.className = 'grid-item';
533
+ // Zufälliger Seed für Bilder
534
+ div.style.backgroundImage = `url('https://picsum.photos/seed/${i + 50}/100/100.jpg')`;
535
+ imageGrid.appendChild(div);
536
+ }
537
+ }
538
+
539
+ // --- 4. AUDIO & BPM LOGIK ---
540
+ const audioInput = document.getElementById('audioInput');
541
+ const bpmInput = document.getElementById('bpmInput');
542
+ const beatBar = document.getElementById('beatBar');
543
+ let audioContext, audioSource, analyser;
544
+ let beatInterval;
545
+
546
+ audioInput.addEventListener('change', function() {
547
+ const file = this.files[0];
548
+ if (file) {
549
+ const url = URL.createObjectURL(file);
550
+ const audio = new Audio(url);
551
+ audio.loop = true;
552
+
553
+ // Simpler Metronom Visualisierung basierend auf BPM
554
+ startMetronome();
555
+
556
+ // Audio abspielen (optional, hier nur Logik-Setup)
557
+ audio.play().catch(e => console.log("Auto-play blocked"));
558
+ }
559
+ });
560
+
561
+ bpmInput.addEventListener('input', () => {
562
+ startMetronome();
563
+ });
564
+
565
+ function startMetronome() {
566
+ if (beatInterval) clearInterval(beatInterval);
567
+ const bpm = parseInt(bpmInput.value) || 120;
568
+ const intervalMs = (60 / bpm) * 1000;
569
+
570
+ beatInterval = setInterval(() => {
571
+ // Beat Animation
572
+ beatBar.style.width = '100%';
573
+ beatBar.style.opacity = '1';
574
+
575
+ setTimeout(() => {
576
+ beatBar.style.width = '0%';
577
+ beatBar.style.opacity = '0.5';
578
+ }, 100);
579
+ }, intervalMs);
580
+ }
581
+
582
+ // --- 5. LICHT & FARBE ---
583
+ function updateFilters() {
584
+ const b = document.getElementById('brightnessInput').value;
585
+ const c = document.getElementById('contrastInput').value;
586
+ const s = document.getElementById('saturateInput').value;
587
+
588
+ // Anwenden auf das Grid (Bilder)
589
+ imageGrid.style.filter = `brightness(${b}%) contrast(${c}%) saturate(${s}%)`;
590
+ }
591
+
592
+ // --- 6. GYROSKOP & WINKEL (USER CODE INTEGRATION) ---
593
+ // Elemente referenzieren
594
+ const hudX = document.getElementById('hudX');
595
+ const hudY = document.getElementById('hudY');
596
+ const speedInput = document.getElementById('speedMult');
597
+ const multValDisplay = document.getElementById('multVal');
598
+ const btnPerm = document.getElementById('reqPermission');
599
+ const offsetInput = document.getElementById('manualOffset');
600
+ const hudStatus = document.getElementById('hudStatus');
601
+
602
+ // Status Variablen
603
+ let state = {
604
+ currentX: 0, // Aktuelle Rotation X (finaler Wert für CSS)
605
+ currentY: 0, // Aktuelle Rotation Y (finaler Wert für CSS)
606
+ baseX: null, // Startwert beim Gyro-Start (Nullpunkt)
607
+ baseY: null,
608
+ lockedX: false, // Ist X fixiert?
609
+ lockedY: false, // Ist Y fixiert?
610
+ fixedValX: 0, // Der Wert, auf dem X fixiert wurde
611
+ fixedValY: 0, // Der Wert, auf dem Y fixiert wurde
612
+ multiplier: 1, // Geschwindigkeitsfaktor
613
+ manualOffset: 0 // Touchpoint Offset (manuelle Korrektur)
614
+ };
615
+
616
+ // --- KONFIGURATION UPDATES ---
617
+ speedInput.addEventListener('input', (e) => {
618
+ state.multiplier = parseFloat(e.target.value);
619
+ multValDisplay.innerText = state.multiplier;
620
+ });
621
+
622
+ offsetInput.addEventListener('input', (e) => {
623
+ state.manualOffset = parseInt(e.target.value);
624
+ updateView();
625
+ });
626
+
627
+ // --- LOCKING LOGIK (FIXIEREN) ---
628
+ window.toggleLock = function(axis) {
629
+ if (axis === 'x') {
630
+ state.lockedX = !state.lockedX;
631
+ const btn = document.getElementById('lockXBtn');
632
+ if (state.lockedX) {
633
+ state.fixedValX = state.currentX; // Speichere aktuellen IST-Wert als Fixpunkt
634
+ btn.classList.add('locked');
635
+ btn.innerHTML = `<i class="fas fa-lock"></i> X LOCKED`;
636
+ } else {
637
+ btn.classList.remove('locked');
638
+ btn.innerHTML = `<i class="fas fa-unlock"></i> X-Achse`;
639
+ }
640
+ }
641
+ else if (axis === 'y') {
642
+ state.lockedY = !state.lockedY;
643
+ const btn = document.getElementById('lockYBtn');
644
+ if (state.lockedY) {
645
+ state.fixedValY = state.currentY; // Speichere aktuellen IST-Wert
646
+ btn.classList.add('locked');
647
+ btn.innerHTML = `<i class="fas fa-lock"></i> Y LOCKED`;
648
+ } else {
649
+ btn.classList.remove('locked');
650
+ btn.innerHTML = `<i class="fas fa-unlock"></i> Y-Achse`;
651
+ }
652
+ }
653
+ }
654
+
655
+ // --- GYROSKOP LOGIK ---
656
+ function handleOrientation(event) {
657
+ // Beta = X-Achse (vor/zurück neigen) [-180, 180]
658
+ // Gamma = Y-Achse (links/rechts neigen) [-90, 90]
659
+
660
+ const xRaw = event.beta;
661
+ const yRaw = event.gamma;
662
+
663
+ // Wenn noch kein Basiswert (Kalibrierung beim Start), setze ihn
664
+ if (state.baseX === null) {
665
+ state.baseX = xRaw;
666
+ state.baseY = yRaw;
667
+ hudStatus.innerText = "Aktiv";
668
+ hudStatus.style.color = "#00e5ff";
669
+ }
670
+
671
+ // Differenz berechnen * Multiplier
672
+ const deltaX = (xRaw - state.baseX) * state.multiplier;
673
+ const deltaY = (yRaw - state.baseY) * state.multiplier;
674
+
675
+ // Berechne potenzielle neue Zielwerte
676
+ let newX = deltaX + state.manualOffset;
677
+ let newY = deltaY;
678
+
679
+ // --- ACHSEN FIXIERUNG PRÜFEN ---
680
+
681
+ // X-Achse Logic
682
+ if (!state.lockedX) {
683
+ state.currentX = newX;
684
+ } else {
685
+ state.currentX = state.fixedValX;
686
+ }
687
+
688
+ // Y-Achse Logic
689
+ if (!state.lockedY) {
690
+ state.currentY = newY;
691
+ } else {
692
+ state.currentY = state.fixedValY;
693
+ }
694
+
695
+ updateView();
696
+ }
697
+
698
+ // --- VISUALISIERUNG UPDATEN ---
699
+ function updateView() {
700
+ // Begrenzung der Winkel optional, um "Flip" zu vermeiden (z.B. -90 bis 90)
701
+ // Hier lassen wir es frei für 360 Grad Feeling
702
+
703
+ // CSS Transform anwenden
704
+ // rotateX für Beta, rotateY für Gamma
705
+ world.style.transform = `rotateX(${-state.currentX}deg) rotateY(${state.currentY}deg)`;
706
+
707
+ // HUD Updaten
708
+ hudX.innerText = Math.round(state.currentX) + "°";
709
+ hudY.innerText = Math.round(state.currentY) + "°";
710
+ }
711
+
712
+ // --- PERMISSION REQUEST (iOS 13+ support) ---
713
+ btnPerm.addEventListener('click', requestGyro);
714
+
715
+ function requestGyro() {
716
+ if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
717
+ // iOS 13+
718
+ DeviceOrientationEvent.requestPermission()
719
+ .then(response => {
720
+ if (response === 'granted') {
721
+ window.addEventListener('deviceorientation', handleOrientation);
722
+ btnPerm.style.display = 'none'; // Button ausblenden nach Erfolg
723
+ hudStatus.innerText = "Berechtigung erteilt";
724
+ } else {
725
+ alert('Gyro permission denied');
726
+ hudStatus.innerText = "Verweigert";
727
+ hudStatus.style.color = "red";
728
+ }
729
+ })
730
+ .catch(console.error);
731
+ } else {
732
+ // Android / ältere iOS / PC (DevTools Sensors)
733
+ window.addEventListener('deviceorientation', handleOrientation);
734
+ btnPerm.style.display = 'none';
735
+ hudStatus.innerText = "Lausche...";
736
+ // Für Debugging auf Desktop ohne Sensor:
737
+ console.log("Gyro Event Listener added (Sensors required for movement)");
738
+ }
739
+ }
740
+
741
+ </script>
742
+ </body>
743
+ </html>