HI7RAI commited on
Commit
bb144fa
·
verified ·
1 Parent(s): 4128554

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +647 -19
index.html CHANGED
@@ -1,19 +1,647 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HYPER-FX STUDIO | Ultimate Morph & Shader Engine</title>
7
+
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+
11
+ <!-- Icons -->
12
+ <script src="https://unpkg.com/lucide@latest"></script>
13
+
14
+ <!-- GIF.js -->
15
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js"></script>
16
+
17
+ <!-- Import Map für sauberes Three.js Module Loading -->
18
+ <script type="importmap">
19
+ {
20
+ "imports": {
21
+ "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
22
+ "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
23
+ }
24
+ }
25
+ </script>
26
+
27
+ <script>
28
+ tailwind.config = {
29
+ darkMode: 'class',
30
+ theme: {
31
+ extend: {
32
+ colors: {
33
+ cyber: {
34
+ bg: '#050505',
35
+ panel: '#111111',
36
+ border: '#333333',
37
+ accent: '#00ff9d', // Neon Green
38
+ secondary: '#bd00ff', // Neon Purple
39
+ alert: '#ff0055', // Neon Red
40
+ text: '#eeeeee'
41
+ }
42
+ },
43
+ fontFamily: { mono: ['monospace'] }
44
+ }
45
+ }
46
+ }
47
+ </script>
48
+
49
+ <style>
50
+ /* UI Tweaks */
51
+ body { overflow: hidden; background: #000; }
52
+ ::-webkit-scrollbar { width: 4px; }
53
+ ::-webkit-scrollbar-thumb { background: #333; border-radius: 2px; }
54
+ ::-webkit-scrollbar-thumb:hover { background: #00ff9d; }
55
+
56
+ .shader-card {
57
+ border-left: 2px solid transparent;
58
+ transition: all 0.2s;
59
+ }
60
+ .shader-card:hover { border-left-color: #00ff9d; background: #1a1a1a; }
61
+
62
+ canvas { display: block; width: 100%; height: 100%; }
63
+
64
+ #loader {
65
+ position: fixed; inset: 0; background: #000; z-index: 50;
66
+ display: flex; justify-content: center; align-items: center;
67
+ color: #00ff9d; font-family: monospace;
68
+ }
69
+
70
+ /* Range Slider Styling */
71
+ input[type=range] {
72
+ -webkit-appearance: none;
73
+ background: transparent;
74
+ }
75
+ input[type=range]::-webkit-slider-thumb {
76
+ -webkit-appearance: none;
77
+ height: 14px;
78
+ width: 14px;
79
+ border-radius: 50%;
80
+ background: #00ff9d;
81
+ cursor: pointer;
82
+ margin-top: -5px;
83
+ }
84
+ input[type=range]::-webkit-slider-runnable-track {
85
+ width: 100%;
86
+ height: 4px;
87
+ cursor: pointer;
88
+ background: #333;
89
+ border-radius: 2px;
90
+ }
91
+ </style>
92
+ </head>
93
+ <body class="bg-cyber-bg text-cyber-text h-screen flex flex-col font-sans">
94
+
95
+ <!-- Loading Screen -->
96
+ <div id="loader">
97
+ <div class="text-center">
98
+ <div class="text-2xl font-bold mb-2">INITIALIZING HYPER CORE</div>
99
+ <div class="text-xs text-gray-500">Loading WebGL & Canvas Modules...</div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- Header -->
104
+ <header class="h-14 bg-cyber-panel border-b border-cyber-border flex items-center justify-between px-4 z-20 shrink-0">
105
+ <div class="flex items-center gap-3">
106
+ <i data-lucide="cpu" class="text-cyber-accent"></i>
107
+ <div>
108
+ <h1 class="font-bold text-sm tracking-widest uppercase">Hyper-FX <span class="text-cyber-secondary">Studio</span></h1>
109
+ </div>
110
+ </div>
111
+
112
+ <div class="flex items-center gap-4">
113
+ <!-- Mode Switcher -->
114
+ <div class="flex bg-black rounded p-1 border border-cyber-border">
115
+ <button id="modeVideo" class="px-3 py-1 text-xs rounded bg-cyber-accent text-black font-bold transition">VIDEO MODE</button>
116
+ <button id="modeImage" class="px-3 py-1 text-xs rounded text-gray-400 hover:text-white transition">GIF MODE</button>
117
+ </div>
118
+
119
+ <div class="h-6 w-px bg-cyber-border"></div>
120
+
121
+ <button id="btnRandom" class="px-3 py-1 bg-cyber-secondary/20 border border-cyber-secondary text-cyber-secondary text-xs rounded hover:bg-cyber-secondary hover:text-white transition uppercase font-bold hidden" title="Randomize Shaders">
122
+ <i data-lucide="shuffle" class="inline w-3 h-3 mr-1"></i> Random FX
123
+ </button>
124
+ <button id="btnRenderGif" class="px-3 py-1 bg-cyber-alert/20 border border-cyber-alert text-cyber-alert text-xs rounded hover:bg-cyber-alert hover:text-white transition uppercase font-bold hidden" title="Generate GIF">
125
+ <i data-lucide="film" class="inline w-3 h-3 mr-1"></i> Render GIF
126
+ </button>
127
+ </div>
128
+ </header>
129
+
130
+ <div class="flex flex-1 overflow-hidden">
131
+
132
+ <!-- LEFT: Resource Library -->
133
+ <aside class="w-72 bg-cyber-panel border-r border-cyber-border flex flex-col z-10 shrink-0">
134
+ <!-- Tabs -->
135
+ <div class="flex border-b border-cyber-border">
136
+ <button id="tabShaders" class="flex-1 py-2 text-xs font-bold text-cyber-accent border-b-2 border-cyber-accent bg-cyber-border/30">SHADERS</button>
137
+ <button id="tabImages" class="flex-1 py-2 text-xs font-bold text-gray-500 hover:text-white">IMAGES</button>
138
+ </div>
139
+
140
+ <!-- Content: Shader Library (Video Mode) -->
141
+ <div id="panelShaders" class="flex-1 overflow-y-auto flex flex-col">
142
+ <div class="p-3 border-b border-cyber-border">
143
+ <input type="text" id="searchFx" placeholder="Search Shaders..." class="w-full bg-black border border-cyber-border rounded px-3 py-2 text-xs text-white focus:border-cyber-accent outline-none">
144
+ </div>
145
+ <div id="libraryList" class="flex-1 overflow-y-auto p-2 space-y-1">
146
+ <!-- Injected via JS -->
147
+ </div>
148
+
149
+ <!-- Media Input (Video) -->
150
+ <div class="p-3 border-t border-cyber-border bg-black/40">
151
+ <div class="grid grid-cols-2 gap-2">
152
+ <label class="cursor-pointer bg-cyber-border hover:bg-cyber-accent hover:text-black text-center py-2 rounded text-xs transition font-bold">
153
+ LOAD VIDEO
154
+ <input type="file" id="inpVideo" accept="video/*" class="hidden">
155
+ </label>
156
+ <label class="cursor-pointer bg-cyber-border hover:bg-cyber-secondary hover:text-white text-center py-2 rounded text-xs transition font-bold">
157
+ LOAD AUDIO
158
+ <input type="file" id="inpAudio" accept="audio/*" class="hidden">
159
+ </label>
160
+ </div>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Content: Image Sequence (GIF Mode) -->
165
+ <div id="panelImages" class="flex-1 overflow-y-auto flex flex-col hidden">
166
+ <div class="p-3 border-b border-cyber-border">
167
+ <label class="text-[10px] text-gray-500 uppercase font-bold mb-1 block">Source URLs (Comma separated)</label>
168
+ <textarea id="imageSource" rows="3" class="w-full bg-black border border-cyber-border rounded px-2 py-1 text-xs text-white focus:border-cyber-accent outline-none mb-2">https://picsum.photos/id/237/400/400,https://picsum.photos/id/238/400/400,https://picsum.photos/id/239/400/400,https://picsum.photos/id/240/400/400</textarea>
169
+ <button id="loadImagesBtn" class="w-full bg-cyber-secondary/20 border border-cyber-secondary text-cyber-secondary text-xs rounded py-2 hover:bg-cyber-secondary hover:text-white transition font-bold">LOAD & ANALYZE</button>
170
+ </div>
171
+ <div id="mediaList" class="flex-1 overflow-y-auto p-2 grid grid-cols-3 gap-2 content-start">
172
+ <div class="col-span-3 text-center text-xs text-gray-600 mt-4">No images loaded.</div>
173
+ </div>
174
+ </div>
175
+ </aside>
176
+
177
+ <!-- CENTER: Viewport -->
178
+ <main class="flex-1 bg-black relative flex items-center justify-center overflow-hidden">
179
+
180
+ <!-- Video Container (Three.js) -->
181
+ <div id="video-container" class="absolute inset-0 w-full h-full z-10">
182
+ <div id="canvas-wrapper" class="w-full h-full"></div>
183
+
184
+ <!-- Video Overlay Controls -->
185
+ <div class="absolute bottom-6 left-1/2 -translate-x-1/2 bg-cyber-panel/90 backdrop-blur border border-cyber-border rounded-full px-6 py-2 flex items-center gap-4 shadow-2xl">
186
+ <button id="btnPlay" class="text-white hover:text-cyber-accent"><i data-lucide="play"></i></button>
187
+ <div class="text-xs font-mono text-cyber-accent" id="timeDisplay">00:00</div>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- Image Container (2D Canvas) -->
192
+ <div id="image-container" class="absolute inset-0 flex items-center justify-center bg-gray-900 z-0 hidden">
193
+ <canvas id="artCanvas" width="600" height="600" class="max-w-full max-h-full shadow-2xl border border-cyber-border"></canvas>
194
+ </div>
195
+
196
+ </main>
197
+
198
+ <!-- RIGHT: Stack & Settings -->
199
+ <aside class="w-80 bg-cyber-panel border-l border-cyber-border flex flex-col z-10 shrink-0">
200
+
201
+ <!-- Video Mode Stack -->
202
+ <div id="rightStack" class="flex flex-col h-full">
203
+ <div class="p-3 border-b border-cyber-border flex justify-between items-center">
204
+ <span class="text-xs font-bold text-gray-400">ACTIVE SHADERS</span>
205
+ <span id="stackCount" class="text-[10px] bg-cyber-border px-1 rounded">0/8</span>
206
+ </div>
207
+ <div id="stackList" class="flex-1 overflow-y-auto p-2 space-y-2">
208
+ <div class="text-center mt-10 text-gray-600 text-xs">Stack is empty.</div>
209
+ </div>
210
+
211
+ <!-- Snippet Info -->
212
+ <div class="p-3 border-t border-cyber-border bg-black/40 text-[10px] text-gray-500 font-mono">
213
+ <div class="flex justify-between mb-1">
214
+ <span>WebGL Renderer</span>
215
+ <span class="text-cyber-accent">Active</span>
216
+ </div>
217
+ <div class="flex justify-between">
218
+ <span>Audio Sync</span>
219
+ <span class="text-cyber-secondary" id="audioStatus">Waiting...</span>
220
+ </div>
221
+ </div>
222
+ </div>
223
+
224
+ <!-- GIF Mode Settings -->
225
+ <div id="rightSettings" class="flex flex-col h-full hidden overflow-y-auto">
226
+ <div class="p-3 border-b border-cyber-border flex justify-between items-center">
227
+ <span class="text-xs font-bold text-cyber-alert">GIF SETTINGS</span>
228
+ </div>
229
+
230
+ <div class="p-4 space-y-4">
231
+ <!-- Animation -->
232
+ <div>
233
+ <label class="text-[10px] uppercase text-gray-500 font-bold">Transition Speed</label>
234
+ <input type="range" id="transitionSpeed" min="50" max="2000" value="300" class="w-full mt-1">
235
+ <div class="flex justify-between text-[10px] text-gray-600"><span>Fast</span><span id="speedVal">300ms</span><span>Slow</span></div>
236
+ </div>
237
+
238
+ <div>
239
+ <label class="text-[10px] uppercase text-gray-500 font-bold">Hold Duration</label>
240
+ <input type="range" id="holdDuration" min="0" max="2000" value="100" class="w-full mt-1">
241
+ <div class="flex justify-between text-[10px] text-gray-600"><span>Instant</span><span id="holdVal">100ms</span><span>Stare</span></div>
242
+ </div>
243
+
244
+ <div>
245
+ <label class="text-[10px] uppercase text-gray-500 font-bold">Loops</label>
246
+ <input type="number" id="loopCount" value="5" min="1" max="50" class="w-full bg-black border border-cyber-border rounded px-2 py-1 text-xs text-white mt-1">
247
+ </div>
248
+
249
+ <div class="border-t border-cyber-border my-2"></div>
250
+
251
+ <!-- Filters -->
252
+ <div>
253
+ <label class="text-[10px] uppercase text-gray-500 font-bold">Contrast</label>
254
+ <input type="range" id="contrast" min="0" max="300" value="100" class="w-full mt-1">
255
+ </div>
256
+
257
+ <div>
258
+ <label class="text-[10px] uppercase text-gray-500 font-bold">Brightness</label>
259
+ <input type="range" id="brightness" min="0" max="200" value="100" class="w-full mt-1">
260
+ </div>
261
+
262
+ <div>
263
+ <label class="text-[10px] uppercase text-gray-500 font-bold block mb-2">Spot Color</label>
264
+ <div class="flex gap-2 flex-wrap" id="spotColorPreview">
265
+ <!-- Swatches injected via JS -->
266
+ </div>
267
+ </div>
268
+
269
+ <div class="space-y-2 pt-2">
270
+ <label class="flex items-center gap-2 cursor-pointer">
271
+ <input type="checkbox" id="grayscaleToggle" class="accent-cyber-accent">
272
+ <span class="text-xs">Grayscale</span>
273
+ </label>
274
+ <label class="flex items-center gap-2 cursor-pointer">
275
+ <input type="checkbox" id="glitchToggle" class="accent-cyber-alert">
276
+ <span class="text-xs">Glitch Noise</span>
277
+ </label>
278
+ <label class="flex items-center gap-2 cursor-pointer">
279
+ <input type="checkbox" id="rotateToggle" class="accent-cyber-secondary">
280
+ <span class="text-xs">Film Jitter</span>
281
+ </label>
282
+ </div>
283
+
284
+ <div class="border-t border-cyber-border my-4"></div>
285
+
286
+ <!-- Status & Download -->
287
+ <div class="bg-black p-2 rounded border border-cyber-border">
288
+ <div class="flex justify-between text-[10px] mb-1">
289
+ <span id="renderStatus">Ready</span>
290
+ <span id="frameCount">0/0</span>
291
+ </div>
292
+ <div class="w-full bg-gray-800 h-1 rounded overflow-hidden mb-2">
293
+ <div id="renderProgress" class="h-full bg-cyber-alert w-0"></div>
294
+ </div>
295
+ </div>
296
+
297
+ <a id="downloadLink" href="#" class="hidden block text-center text-xs text-cyber-accent underline py-2">Download GIF</a>
298
+ </div>
299
+ </div>
300
+
301
+ </aside>
302
+ </div>
303
+
304
+ <!-- Hidden Sources -->
305
+ <video id="videoSrc" playsinline loop crossorigin="anonymous" style="display:none;"></video>
306
+ <audio id="audioSrc" crossorigin="anonymous" style="display:none;"></audio>
307
+
308
+ <!-- MODULE LOGIC -->
309
+ <script type="module">
310
+ import * as THREE from 'three';
311
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
312
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
313
+ import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
314
+
315
+ // --- GLOBAL STATE ---
316
+ const State = {
317
+ mode: 'VIDEO', // 'VIDEO' or 'IMAGE'
318
+ };
319
+
320
+ // --- 1. SHADER LIBRARY (GLSL SNIPPETS) ---
321
+ const SHADER_LIB = [
322
+ { id: 'bw', name: 'Grayscale', cat: 'Color', glsl: `uniform float amount; void main() { vec4 color = texture2D(tDiffuse, vUv); float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); gl_FragColor = vec4(mix(color.rgb, vec3(gray), amount), color.a); }` },
323
+ { id: 'sepia', name: 'Sepia Tone', cat: 'Color', glsl: `uniform float amount; void main() { vec4 color = texture2D(tDiffuse, vUv); vec3 sepia = vec3(dot(color.rgb, vec3(0.393, 0.769, 0.189)), dot(color.rgb, vec3(0.349, 0.686, 0.168)), dot(color.rgb, vec3(0.272, 0.534, 0.131))); gl_FragColor = vec4(mix(color.rgb, sepia, amount), color.a); }` },
324
+ { id: 'invert', name: 'Invert Color', cat: 'Color', glsl: `uniform float amount; void main() { vec4 color = texture2D(tDiffuse, vUv); gl_FragColor = vec4(mix(color.rgb, 1.0 - color.rgb, amount), color.a); }` },
325
+ { id: 'rgb', name: 'RGB Shift', cat: 'Glitch', glsl: `uniform float amount; uniform float uAudio; void main() { float offset = amount * 0.05 * (1.0 + uAudio); vec2 rUv = vUv + vec2(offset, 0.0); vec2 gUv = vUv; vec2 bUv = vUv - vec2(offset, 0.0); vec4 r = texture2D(tDiffuse, rUv); vec4 g = texture2D(tDiffuse, gUv); vec4 b = texture2D(tDiffuse, bUv); gl_FragColor = vec4(r.r, g.g, b.b, 1.0); }` },
326
+ { id: 'pixel', name: 'Pixelate', cat: 'Glitch', glsl: `uniform float amount; uniform vec2 resolution; void main() { float d = 1.0 / (amount * 100.0 + 10.0); vec2 coord = d * floor(vUv / d); gl_FragColor = texture2D(tDiffuse, coord); }` },
327
+ { id: 'noise', name: 'Static Noise', cat: 'Glitch', glsl: `uniform float amount; uniform float time; float rand(vec2 co) { return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } void main() { vec4 color = texture2D(tDiffuse, vUv); float diff = (rand(vUv + time) - 0.5) * amount; gl_FragColor = vec4(color.rgb + diff, color.a); }` },
328
+ { id: 'scanline', name: 'CRT Scanlines', cat: 'Glitch', glsl: `uniform float amount; uniform vec2 resolution; void main() { vec4 color = texture2D(tDiffuse, vUv); float scanline = sin(vUv.y * resolution.y * 0.5) * 0.1 * amount; gl_FragColor = vec4(color.rgb - scanline, color.a); }` },
329
+ { id: 'vignette', name: 'Dark Vignette', cat: 'Art', glsl: `uniform float amount; void main() { vec4 color = texture2D(tDiffuse, vUv); float dist = distance(vUv, vec2(0.5)); color.rgb *= smoothstep(0.8, 0.8 - amount, dist); gl_FragColor = color; }` },
330
+ { id: 'kaleido', name: 'Kaleidoscope', cat: 'Art', glsl: `uniform float amount; void main() { vec2 uv = vUv - 0.5; float angle = atan(uv.y, uv.x); float radius = length(uv); float segments = 6.0 + floor(amount * 10.0); angle = mod(angle, 6.28318 / segments); angle = abs(angle - 3.14159 / segments); vec2 newUv = vec2(cos(angle), sin(angle)) * radius + 0.5; gl_FragColor = texture2D(tDiffuse, newUv); }` },
331
+ { id: 'contrast', name: 'High Contrast', cat: 'Color', glsl: `uniform float amount; void main() { vec4 c = texture2D(tDiffuse, vUv); gl_FragColor = vec4((c.rgb - 0.5) * (1.0 + amount) + 0.5, c.a); }` },
332
+ { id: 'shake', name: 'Bass Shake', cat: 'Motion', glsl: `uniform float amount; uniform float uAudio; uniform float time; void main() { vec2 uv = vUv; uv.x += sin(time * 50.0) * amount * 0.1 * uAudio; gl_FragColor = texture2D(tDiffuse, uv); }` }
333
+ ];
334
+
335
+ // --- 2. VIDEO ENGINE (Three.js) ---
336
+ const VideoEngine = {
337
+ scene: null,
338
+ camera: null,
339
+ renderer: null,
340
+ composer: null,
341
+ video: null,
342
+ videoTex: null,
343
+ audioCtx: null,
344
+ analyser: null,
345
+ dataArray: null,
346
+ stack: [],
347
+ maxEffects: 8,
348
+ isPlaying: false,
349
+
350
+ init: () => {
351
+ const container = document.getElementById('canvas-wrapper');
352
+ const width = container.clientWidth;
353
+ const height = container.clientHeight;
354
+
355
+ VideoEngine.scene = new THREE.Scene();
356
+ VideoEngine.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
357
+
358
+ VideoEngine.renderer = new THREE.WebGLRenderer({ antialias: false, preserveDrawingBuffer: true });
359
+ VideoEngine.renderer.setSize(width, height);
360
+ container.appendChild(VideoEngine.renderer.domElement);
361
+
362
+ VideoEngine.video = document.getElementById('videoSrc');
363
+ VideoEngine.videoTex = new THREE.VideoTexture(VideoEngine.video);
364
+ VideoEngine.videoTex.minFilter = THREE.LinearFilter;
365
+ VideoEngine.videoTex.magFilter = THREE.LinearFilter;
366
+
367
+ VideoEngine.composer = new EffectComposer(VideoEngine.renderer);
368
+ const renderPass = new RenderPass(VideoEngine.scene, VideoEngine.camera);
369
+
370
+ const geometry = new THREE.PlaneGeometry(2, 2);
371
+ const material = new THREE.MeshBasicMaterial({ map: VideoEngine.videoTex });
372
+ const quad = new THREE.Mesh(geometry, material);
373
+ VideoEngine.scene.add(quad);
374
+
375
+ VideoEngine.composer.addPass(renderPass);
376
+ VideoEngine.buildLibrary();
377
+ VideoEngine.animate();
378
+ },
379
+
380
+ buildLibrary: () => {
381
+ const list = document.getElementById('libraryList');
382
+ list.innerHTML = '';
383
+ const categories = [...new Set(SHADER_LIB.map(s => s.cat))];
384
+
385
+ categories.forEach(cat => {
386
+ const header = document.createElement('div');
387
+ header.className = "px-2 py-1 bg-cyber-border/30 text-[10px] font-bold text-cyber-accent uppercase mt-2 mb-1 sticky top-0 backdrop-blur-sm";
388
+ header.innerText = cat;
389
+ list.appendChild(header);
390
+
391
+ SHADER_LIB.filter(s => s.cat === cat).forEach(shader => {
392
+ const el = document.createElement('div');
393
+ el.className = "shader-card p-2 text-xs text-gray-300 cursor-pointer flex justify-between items-center rounded";
394
+ el.innerHTML = `<span>${shader.name}</span> <i data-lucide="plus" class="w-3 h-3 opacity-50"></i>`;
395
+ el.onclick = () => VideoEngine.addEffect(shader.id);
396
+ list.appendChild(el);
397
+ });
398
+ });
399
+ lucide.createIcons();
400
+ },
401
+
402
+ addEffect: (id) => {
403
+ if (VideoEngine.stack.length >= VideoEngine.maxEffects) return;
404
+ const def = SHADER_LIB.find(s => s.id === id);
405
+ if (!def) return;
406
+
407
+ const myUniforms = {
408
+ "tDiffuse": { value: null },
409
+ "amount": { value: 0.5 },
410
+ "time": { value: 0.0 },
411
+ "resolution": { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
412
+ "uAudio": { value: 0.0 }
413
+ };
414
+
415
+ const myShader = {
416
+ uniforms: myUniforms,
417
+ vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }`,
418
+ fragmentShader: def.glsl
419
+ };
420
+
421
+ const pass = new ShaderPass(myShader);
422
+ pass.uid = Date.now() + Math.random();
423
+ pass.def = def;
424
+
425
+ VideoEngine.composer.addPass(pass);
426
+ VideoEngine.stack.push(pass);
427
+ VideoEngine.renderStackUI();
428
+ },
429
+
430
+ removeEffect: (uid) => {
431
+ const idx = VideoEngine.stack.findIndex(p => p.uid === uid);
432
+ if (idx > -1) {
433
+ VideoEngine.composer.removePass(VideoEngine.stack[idx]);
434
+ VideoEngine.stack.splice(idx, 1);
435
+ VideoEngine.renderStackUI();
436
+ }
437
+ },
438
+
439
+ renderStackUI: () => {
440
+ const container = document.getElementById('stackList');
441
+ document.getElementById('stackCount').innerText = `${VideoEngine.stack.length}/${VideoEngine.maxEffects}`;
442
+
443
+ if (VideoEngine.stack.length === 0) {
444
+ container.innerHTML = '<div class="text-center mt-10 text-gray-600 text-xs">Stack is empty.</div>';
445
+ return;
446
+ }
447
+
448
+ container.innerHTML = '';
449
+ VideoEngine.stack.forEach((pass, i) => {
450
+ const el = document.createElement('div');
451
+ el.className = "bg-black border border-cyber-border rounded p-2 mb-2";
452
+ el.innerHTML = `
453
+ <div class="flex justify-between items-center mb-1">
454
+ <span class="font-bold text-cyber-secondary text-xs">${i+1}. ${pass.def.name}</span>
455
+ <button class="text-red-500 hover:text-white" onclick="window.removeVideoFx(${pass.uid})"><i data-lucide="x" class="w-3 h-3"></i></button>
456
+ </div>
457
+ <input type="range" class="w-full h-1 bg-gray-800 rounded appearance-none cursor-pointer"
458
+ min="0" max="1" step="0.01" value="${pass.uniforms.amount.value}"
459
+ oninput="window.updateVideoFx(${pass.uid}, this.value)">
460
+ `;
461
+ container.appendChild(el);
462
+ });
463
+ lucide.createIcons();
464
+ },
465
+
466
+ animate: () => {
467
+ if (State.mode !== 'VIDEO') return;
468
+ requestAnimationFrame(VideoEngine.animate);
469
+
470
+ let audioLevel = 0;
471
+ if (VideoEngine.analyser) {
472
+ VideoEngine.analyser.getByteFrequencyData(VideoEngine.dataArray);
473
+ let sum = 0;
474
+ for(let i=0; i<10; i++) sum += VideoEngine.dataArray[i];
475
+ audioLevel = sum / 10 / 255;
476
+ }
477
+
478
+ const time = performance.now() * 0.001;
479
+ VideoEngine.stack.forEach(pass => {
480
+ if (pass.uniforms.time) pass.uniforms.time.value = time;
481
+ if (pass.uniforms.uAudio) pass.uniforms.uAudio.value = audioLevel;
482
+ });
483
+
484
+ const audioEl = document.getElementById('audioSrc');
485
+ if (Math.abs(VideoEngine.video.currentTime - audioEl.currentTime) > 0.5 && !VideoEngine.video.paused) {
486
+ audioEl.currentTime = VideoEngine.video.currentTime;
487
+ }
488
+
489
+ const m = Math.floor(VideoEngine.video.currentTime / 60);
490
+ const s = Math.floor(VideoEngine.video.currentTime % 60);
491
+ document.getElementById('timeDisplay').innerText = `${m}:${s.toString().padStart(2, '0')}`;
492
+
493
+ VideoEngine.composer.render();
494
+ }
495
+ };
496
+
497
+ // --- 3. GIF ENGINE (Canvas 2D) ---
498
+ const GifEngine = {
499
+ canvas: null,
500
+ ctx: null,
501
+ images: [],
502
+ currentIndex: 0,
503
+ isRendering: false,
504
+ animationId: null,
505
+
506
+ filters: {
507
+ contrast: 100,
508
+ brightness: 100,
509
+ grayscale: true,
510
+ spotColor: { r: 255, g: 0, b: 85, active: true },
511
+ glitch: false,
512
+ rotation: false
513
+ },
514
+ animation: {
515
+ speed: 300,
516
+ hold: 100,
517
+ loops: 5
518
+ },
519
+
520
+ init: () => {
521
+ GifEngine.canvas = document.getElementById('artCanvas');
522
+ GifEngine.ctx = GifEngine.canvas.getContext('2d');
523
+ GifEngine.initSpotColors();
524
+
525
+ // Load default images
526
+ document.getElementById('loadImagesBtn').click();
527
+ },
528
+
529
+ initSpotColors: () => {
530
+ const colors = [
531
+ { c: '255,255,255', bg: '#ffffff', name: 'None' },
532
+ { c: '255,0,85', bg: '#ff0055', name: 'Red' },
533
+ { c: '0,204,255', bg: '#00ccff', name: 'Blue' },
534
+ { c: '255,204,0', bg: '#ffcc00', name: 'Gold' },
535
+ { c: '51,255,51', bg: '#33ff33', name: 'Green' },
536
+ { c: '170,0,255', bg: '#aa00ff', name: 'Purple' }
537
+ ];
538
+
539
+ const container = document.getElementById('spotColorPreview');
540
+ container.innerHTML = '';
541
+ colors.forEach((col, idx) => {
542
+ const div = document.createElement('div');
543
+ div.className = `w-6 h-6 rounded-full cursor-pointer border border-gray-600 hover:scale-110 transition ${idx===1?'ring-2 ring-cyber-alert':''}`;
544
+ div.style.backgroundColor = col.bg;
545
+ div.onclick = () => {
546
+ GifEngine.filters.spotColor = {
547
+ r: parseInt(col.c.split(',')[0]),
548
+ g: parseInt(col.c.split(',')[1]),
549
+ b: parseInt(col.c.split(',')[2]),
550
+ active: true
551
+ };
552
+ // Visual update
553
+ Array.from(container.children).forEach(c => c.classList.remove('ring-2', 'ring-cyber-alert'));
554
+ div.classList.add('ring-2', 'ring-cyber-alert');
555
+ if (!GifEngine.isRendering) GifEngine.drawFrame();
556
+ };
557
+ container.appendChild(div);
558
+ });
559
+ },
560
+
561
+ getLuminance: (img) => {
562
+ const tempC = document.createElement('canvas');
563
+ tempC.width = 50; tempC.height = 50;
564
+ const tCtx = tempC.getContext('2d');
565
+ tCtx.drawImage(img, 0, 0, 50, 50);
566
+ const data = tCtx.getImageData(0,0,50,50).data;
567
+ let sum = 0;
568
+ for(let i=0; i<data.length; i+=4) sum += (0.2126*data[i] + 0.7152*data[i+1] + 0.0722*data[i+2]);
569
+ return sum / 2500;
570
+ },
571
+
572
+ sortImages: () => {
573
+ GifEngine.images.sort((a, b) => GifEngine.getLuminance(a) - GifEngine.getLuminance(b));
574
+ },
575
+
576
+ drawFrame: () => {
577
+ if (GifEngine.images.length === 0) return;
578
+ const ctx = GifEngine.ctx;
579
+ const canvas = GifEngine.canvas;
580
+
581
+ ctx.fillStyle = '#000';
582
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
583
+
584
+ const img = GifEngine.images[GifEngine.currentIndex];
585
+
586
+ ctx.save();
587
+
588
+ // CSS Filters string
589
+ let f = `contrast(${GifEngine.filters.contrast}%) brightness(${GifEngine.filters.brightness}%)`;
590
+ if (GifEngine.filters.grayscale) f += ' grayscale(100%)';
591
+ ctx.filter = f;
592
+
593
+ // Transforms
594
+ if (GifEngine.filters.rotation) {
595
+ const ang = (Math.random() - 0.5) * 0.05;
596
+ ctx.translate(canvas.width/2, canvas.height/2);
597
+ ctx.rotate(ang);
598
+ ctx.translate(-canvas.width/2, -canvas.height/2);
599
+ }
600
+
601
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
602
+
603
+ // Spot Color
604
+ if (GifEngine.filters.spotColor.active) {
605
+ ctx.globalCompositeOperation = 'overlay';
606
+ ctx.fillStyle = `rgba(${GifEngine.filters.spotColor.r},${GifEngine.filters.spotColor.g},${GifEngine.filters.spotColor.b}, 0.4)`;
607
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
608
+ }
609
+
610
+ // Glitch
611
+ if (GifEngine.filters.glitch) {
612
+ const h = Math.random() * 30;
613
+ const y = Math.random() * canvas.height;
614
+ const off = (Math.random() - 0.5) * 60;
615
+ try {
616
+ const d = ctx.getImageData(0, y, canvas.width, h);
617
+ ctx.putImageData(d, off, y);
618
+ } catch(e){}
619
+ }
620
+
621
+ ctx.restore();
622
+ },
623
+
624
+ loop: () => {
625
+ if (State.mode !== 'IMAGE' || GifEngine.isRendering) return;
626
+
627
+ GifEngine.drawFrame();
628
+
629
+ setTimeout(() => {
630
+ GifEngine.currentIndex = (GifEngine.currentIndex + 1) % GifEngine.images.length;
631
+ GifEngine.animationId = requestAnimationFrame(GifEngine.loop);
632
+ }, GifEngine.animation.speed + GifEngine.animation.hold);
633
+ },
634
+
635
+ loadImages: () => {
636
+ const urls = document.getElementById('imageSource').value.split(',').map(s=>s.trim()).filter(s=>s);
637
+ const list = document.getElementById('mediaList');
638
+ list.innerHTML = '<div class="col-span-3 text-center text-xs animate-pulse">Analyzing...</div>';
639
+
640
+ let loaded = 0;
641
+ GifEngine.images = [];
642
+
643
+ urls.forEach(url => {
644
+ const img = new Image();
645
+ img.crossOrigin = "Anonymous";
646
+ img.onload = () => {
647
+ GifEngine.images.push