Nymbo commited on
Commit
2369c60
·
verified ·
1 Parent(s): b2559cb

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1320 -19
index.html CHANGED
@@ -1,19 +1,1320 @@
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">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>GLSL Shader Viewer</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ font-family: Arial, sans-serif;
13
+ }
14
+
15
+ body {
16
+ background-color: #121212;
17
+ color: #e0e0e0;
18
+ padding: 20px;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ min-height: 100vh;
23
+ }
24
+
25
+ .container {
26
+ width: 100%;
27
+ max-width: 1200px;
28
+ background-color: #1e1e1e;
29
+ border-radius: 10px;
30
+ padding: 20px;
31
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
32
+ }
33
+
34
+ h1 {
35
+ text-align: center;
36
+ margin-bottom: 20px;
37
+ color: #3498db;
38
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
39
+ }
40
+
41
+ .shader-container {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 20px;
45
+ }
46
+
47
+ @media (min-width: 992px) {
48
+ .shader-container {
49
+ flex-direction: row;
50
+ }
51
+
52
+ .shader-controls {
53
+ width: 30%;
54
+ }
55
+
56
+ .shader-preview {
57
+ width: 70%;
58
+ }
59
+ }
60
+
61
+ .shader-controls {
62
+ background-color: #242424;
63
+ padding: 15px;
64
+ border-radius: 8px;
65
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
66
+ }
67
+
68
+ .shader-preview {
69
+ position: relative;
70
+ overflow: hidden;
71
+ border: 2px solid #3498db;
72
+ border-radius: 8px;
73
+ box-shadow: 0 0 20px rgba(52, 152, 219, 0.2);
74
+ }
75
+
76
+ canvas {
77
+ display: block;
78
+ width: 100%;
79
+ background-color: #000;
80
+ }
81
+
82
+ .form-group {
83
+ margin-bottom: 15px;
84
+ }
85
+
86
+ label {
87
+ display: block;
88
+ margin-bottom: 5px;
89
+ font-weight: bold;
90
+ color: #3498db;
91
+ }
92
+
93
+ input[type="file"], select {
94
+ width: 100%;
95
+ padding: 8px;
96
+ border-radius: 5px;
97
+ border: 1px solid #333;
98
+ background-color: #2a2a2a;
99
+ color: #e0e0e0;
100
+ margin-bottom: 10px;
101
+ }
102
+
103
+ button {
104
+ background-color: #3498db;
105
+ color: #121212;
106
+ border: none;
107
+ padding: 10px 15px;
108
+ border-radius: 5px;
109
+ cursor: pointer;
110
+ font-weight: bold;
111
+ transition: all 0.3s;
112
+ margin-right: 10px;
113
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
114
+ }
115
+
116
+ button:hover {
117
+ background-color: #2980b9;
118
+ transform: translateY(-2px);
119
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
120
+ }
121
+
122
+ .shader-controls-section {
123
+ background-color: #1a1a1a;
124
+ border-radius: 5px;
125
+ padding: 10px;
126
+ margin-bottom: 15px;
127
+ }
128
+
129
+ .shader-controls-section h3 {
130
+ margin-bottom: 10px;
131
+ color: #3498db;
132
+ font-size: 16px;
133
+ border-bottom: 1px solid #333;
134
+ padding-bottom: 5px;
135
+ }
136
+
137
+ .control-row {
138
+ display: flex;
139
+ align-items: center;
140
+ margin-bottom: 8px;
141
+ }
142
+
143
+ .control-row label {
144
+ flex: 1;
145
+ margin-bottom: 0;
146
+ }
147
+
148
+ .control-row input[type="range"] {
149
+ flex: 2;
150
+ }
151
+
152
+ .control-row .value-display {
153
+ flex: 0 0 60px;
154
+ text-align: right;
155
+ font-family: monospace;
156
+ color: #3498db;
157
+ }
158
+
159
+ .shader-code {
160
+ background-color: #1a1a1a;
161
+ border-radius: 5px;
162
+ padding: 10px;
163
+ margin-top: 15px;
164
+ font-family: monospace;
165
+ font-size: 12px;
166
+ max-height: 300px;
167
+ overflow-y: auto;
168
+ white-space: pre;
169
+ color: #ddd;
170
+ border-left: 3px solid #3498db;
171
+ }
172
+
173
+ .shader-code.error {
174
+ border-left-color: #e74c3c;
175
+ color: #e74c3c;
176
+ }
177
+
178
+ .code-container {
179
+ position: relative;
180
+ }
181
+
182
+ .code-header {
183
+ display: flex;
184
+ justify-content: space-between;
185
+ align-items: center;
186
+ margin-bottom: 5px;
187
+ }
188
+
189
+ .code-header h3 {
190
+ margin: 0;
191
+ }
192
+
193
+ .code-toggle {
194
+ background: none;
195
+ border: none;
196
+ color: #3498db;
197
+ cursor: pointer;
198
+ font-size: 12px;
199
+ }
200
+
201
+ .code-toggle:hover {
202
+ text-decoration: underline;
203
+ }
204
+
205
+ .status-indicator {
206
+ display: inline-block;
207
+ width: 10px;
208
+ height: 10px;
209
+ border-radius: 50%;
210
+ margin-right: 5px;
211
+ }
212
+
213
+ .status-indicator.success {
214
+ background-color: #2ecc71;
215
+ }
216
+
217
+ .status-indicator.error {
218
+ background-color: #e74c3c;
219
+ }
220
+
221
+ .status-message {
222
+ margin-top: 10px;
223
+ padding: 8px;
224
+ border-radius: 4px;
225
+ background-color: #242424;
226
+ font-size: 14px;
227
+ }
228
+
229
+ .status-message.success {
230
+ color: #2ecc71;
231
+ border-left: 3px solid #2ecc71;
232
+ }
233
+
234
+ .status-message.error {
235
+ color: #e74c3c;
236
+ border-left: 3px solid #e74c3c;
237
+ }
238
+
239
+ .preset-controls {
240
+ display: flex;
241
+ gap: 10px;
242
+ margin-bottom: 15px;
243
+ }
244
+
245
+ .preset-btn {
246
+ background-color: #2c3e50;
247
+ color: #ecf0f1;
248
+ border: none;
249
+ padding: 6px 12px;
250
+ border-radius: 4px;
251
+ cursor: pointer;
252
+ font-size: 13px;
253
+ transition: all 0.2s;
254
+ }
255
+
256
+ .preset-btn:hover {
257
+ background-color: #34495e;
258
+ }
259
+
260
+ .preset-btn.active {
261
+ background-color: #3498db;
262
+ box-shadow: 0 0 8px rgba(52, 152, 219, 0.5);
263
+ }
264
+
265
+ .resolution-control {
266
+ display: flex;
267
+ gap: 10px;
268
+ align-items: center;
269
+ margin-bottom: 10px;
270
+ }
271
+
272
+ .resolution-control input {
273
+ width: 80px;
274
+ text-align: center;
275
+ }
276
+
277
+ .resolution-control span {
278
+ color: #3498db;
279
+ font-weight: bold;
280
+ }
281
+ </style>
282
+ </head>
283
+ <body>
284
+ <div class="container">
285
+ <h1>GLSL Shader Viewer</h1>
286
+
287
+ <div class="shader-container">
288
+ <div class="shader-controls">
289
+ <div class="form-group">
290
+ <label for="shaderFile">Upload Shader File:</label>
291
+ <input type="file" id="shaderFile" accept=".glsl,.frag,.vert">
292
+ </div>
293
+
294
+ <div class="form-group">
295
+ <label for="shaderPresets">Shader Presets:</label>
296
+ <div class="preset-controls">
297
+ <button id="cloudPreset" class="preset-btn active">Clouds</button>
298
+ <button id="crtPreset" class="preset-btn">CRT Effect</button>
299
+ <button id="noisePreset" class="preset-btn">Noise</button>
300
+ </div>
301
+ </div>
302
+
303
+ <div class="shader-controls-section">
304
+ <h3>Display Settings</h3>
305
+ <div class="form-group">
306
+ <label for="resolutionSelect">Resolution:</label>
307
+ <select id="resolutionSelect">
308
+ <option value="auto">Auto (Canvas Size)</option>
309
+ <option value="custom">Custom</option>
310
+ <option value="720p">720p (1280×720)</option>
311
+ <option value="1080p">1080p (1920×1080)</option>
312
+ </select>
313
+ </div>
314
+
315
+ <div id="customResolution" style="display: none;">
316
+ <div class="resolution-control">
317
+ <input type="number" id="resolutionWidth" value="800" min="100" max="2560">
318
+ <span>×</span>
319
+ <input type="number" id="resolutionHeight" value="600" min="100" max="1440">
320
+ </div>
321
+ </div>
322
+ </div>
323
+
324
+ <div class="shader-controls-section">
325
+ <h3>Uniform Values</h3>
326
+ <!-- Time Control -->
327
+ <div class="control-row">
328
+ <label for="timeSpeed">Time Speed:</label>
329
+ <input type="range" id="timeSpeed" min="0" max="2" step="0.05" value="1">
330
+ <span class="value-display" id="timeSpeedValue">1.00</span>
331
+ </div>
332
+
333
+ <!-- Mouse Position -->
334
+ <div class="control-row">
335
+ <label>Mouse Position:</label>
336
+ <span class="value-display" id="mousePosition">0, 0</span>
337
+ </div>
338
+
339
+ <!-- Cloud Parameters -->
340
+ <div id="cloudParams">
341
+ <h3>Cloud Parameters</h3>
342
+ <div class="control-row">
343
+ <label for="cloudCoverage">Coverage:</label>
344
+ <input type="range" id="cloudCoverage" min="0" max="1" step="0.05" value="0.6">
345
+ <span class="value-display" id="cloudCoverageValue">0.60</span>
346
+ </div>
347
+
348
+ <div class="control-row">
349
+ <label for="cloudSharpness">Sharpness:</label>
350
+ <input type="range" id="cloudSharpness" min="0.01" max="0.5" step="0.01" value="0.3">
351
+ <span class="value-display" id="cloudSharpnessValue">0.30</span>
352
+ </div>
353
+
354
+ <div class="control-row">
355
+ <label for="cloudSpeed">Movement:</label>
356
+ <input type="range" id="cloudSpeed" min="0" max="2" step="0.05" value="0.5">
357
+ <span class="value-display" id="cloudSpeedValue">0.50</span>
358
+ </div>
359
+ </div>
360
+
361
+ <!-- CRT Parameters -->
362
+ <div id="crtParams" style="display: none;">
363
+ <h3>CRT Parameters</h3>
364
+ <div class="control-row">
365
+ <label for="crtCurvature">Curvature:</label>
366
+ <input type="range" id="crtCurvature" min="0" max="2" step="0.05" value="1.1">
367
+ <span class="value-display" id="crtCurvatureValue">1.10</span>
368
+ </div>
369
+
370
+ <div class="control-row">
371
+ <label for="crtScanlines">Scanlines:</label>
372
+ <input type="range" id="crtScanlines" min="0" max="2" step="0.05" value="1.0">
373
+ <span class="value-display" id="crtScanlinesValue">1.00</span>
374
+ </div>
375
+
376
+ <div class="control-row">
377
+ <label for="crtChromatic">Chromatic:</label>
378
+ <input type="range" id="crtChromatic" min="0" max="2" step="0.05" value="1.0">
379
+ <span class="value-display" id="crtChromaticValue">1.00</span>
380
+ </div>
381
+ </div>
382
+ </div>
383
+
384
+ <div class="form-group">
385
+ <button id="playPauseBtn">Pause</button>
386
+ <button id="resetBtn">Reset</button>
387
+ </div>
388
+
389
+ <div id="statusMessage" class="status-message success" style="display: none;"></div>
390
+ </div>
391
+
392
+ <div class="shader-preview">
393
+ <canvas id="shaderCanvas"></canvas>
394
+ </div>
395
+ </div>
396
+
397
+ <div class="code-container">
398
+ <div class="code-header">
399
+ <h3>
400
+ <span class="status-indicator success" id="shaderStatus"></span>
401
+ Shader Code
402
+ </h3>
403
+ <button class="code-toggle" id="toggleCode">Show/Hide Code</button>
404
+ </div>
405
+ <div class="shader-code" id="shaderCode" style="display: none;"></div>
406
+ </div>
407
+ </div>
408
+
409
+ <script>
410
+ // DOM Elements
411
+ const canvas = document.getElementById('shaderCanvas');
412
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
413
+ const shaderFileInput = document.getElementById('shaderFile');
414
+ const cloudPresetBtn = document.getElementById('cloudPreset');
415
+ const crtPresetBtn = document.getElementById('crtPreset');
416
+ const noisePresetBtn = document.getElementById('noisePreset');
417
+ const playPauseBtn = document.getElementById('playPauseBtn');
418
+ const resetBtn = document.getElementById('resetBtn');
419
+ const timeSpeedSlider = document.getElementById('timeSpeed');
420
+ const timeSpeedValue = document.getElementById('timeSpeedValue');
421
+ const cloudCoverageSlider = document.getElementById('cloudCoverage');
422
+ const cloudCoverageValue = document.getElementById('cloudCoverageValue');
423
+ const cloudSharpnessSlider = document.getElementById('cloudSharpness');
424
+ const cloudSharpnessValue = document.getElementById('cloudSharpnessValue');
425
+ const cloudSpeedSlider = document.getElementById('cloudSpeed');
426
+ const cloudSpeedValue = document.getElementById('cloudSpeedValue');
427
+ const crtCurvatureSlider = document.getElementById('crtCurvature');
428
+ const crtCurvatureValue = document.getElementById('crtCurvatureValue');
429
+ const crtScanlinesSlider = document.getElementById('crtScanlines');
430
+ const crtScanlinesValue = document.getElementById('crtScanlinesValue');
431
+ const crtChromaticSlider = document.getElementById('crtChromatic');
432
+ const crtChromaticValue = document.getElementById('crtChromaticValue');
433
+ const shaderCode = document.getElementById('shaderCode');
434
+ const toggleCodeBtn = document.getElementById('toggleCode');
435
+ const shaderStatus = document.getElementById('shaderStatus');
436
+ const statusMessage = document.getElementById('statusMessage');
437
+ const mousePosition = document.getElementById('mousePosition');
438
+ const resolutionSelect = document.getElementById('resolutionSelect');
439
+ const customResolution = document.getElementById('customResolution');
440
+ const resolutionWidth = document.getElementById('resolutionWidth');
441
+ const resolutionHeight = document.getElementById('resolutionHeight');
442
+ const cloudParams = document.getElementById('cloudParams');
443
+ const crtParams = document.getElementById('crtParams');
444
+
445
+ // Check if WebGL is available
446
+ if (!gl) {
447
+ alert('Unable to initialize WebGL. Your browser may not support it.');
448
+ }
449
+
450
+ // Shader state
451
+ let isPlaying = true;
452
+ let startTime = Date.now();
453
+ let timeSpeed = 1.0;
454
+ let currentShaderType = 'cloud';
455
+ let mouseX = 0;
456
+ let mouseY = 0;
457
+ let cameraOffsetX = 0;
458
+ let cameraOffsetY = 0;
459
+
460
+ // Initialize cloud parameters
461
+ let cloudCoverage = 0.6;
462
+ let cloudSharpness = 0.3;
463
+ let cloudSpeed = 0.5;
464
+
465
+ // Initialize CRT parameters
466
+ let crtCurvature = 1.1;
467
+ let crtScanlines = 1.0;
468
+ let crtChromatic = 1.0;
469
+
470
+ // Keep track of shader program
471
+ let shaderProgram = null;
472
+ let uniformLocations = {};
473
+
474
+ // Vertex shader for a quad that fills the canvas
475
+ const vertexShaderSource = `
476
+ attribute vec2 a_position;
477
+ attribute vec2 a_texCoord;
478
+ varying vec2 texture_coords;
479
+ varying vec2 screen_coords;
480
+
481
+ void main() {
482
+ gl_Position = vec4(a_position, 0, 1);
483
+ texture_coords = a_texCoord;
484
+ screen_coords = (a_position + 1.0) * 0.5 * vec2(${canvas.width}.0, ${canvas.height}.0);
485
+ }
486
+ `;
487
+
488
+ // Default fragment shaders
489
+ const cloudShaderSource = `
490
+ // Cloud shader converted for WebGL
491
+ precision mediump float;
492
+
493
+ // Uniform variables
494
+ uniform float millis;
495
+ uniform vec2 resolution;
496
+ uniform vec2 cameraOffset;
497
+
498
+ // Hash function for noise generation
499
+ vec2 hash(vec2 p)
500
+ {
501
+ p = vec2(dot(p, vec2(127.1, 311.7)),
502
+ dot(p, vec2(269.5, 183.3)));
503
+ return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
504
+ }
505
+
506
+ // Improved gradient noise function
507
+ float noise(in vec2 p)
508
+ {
509
+ vec2 i = floor(p);
510
+ vec2 f = fract(p);
511
+
512
+ // Cubic Hermite curve for smoother interpolation
513
+ vec2 u = f * f * (3.0 - 2.0 * f);
514
+
515
+ // Improved gradient interpolation
516
+ return mix(
517
+ mix(dot(hash(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0)),
518
+ dot(hash(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)), u.x),
519
+ mix(dot(hash(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)),
520
+ dot(hash(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)), u.x),
521
+ u.y
522
+ );
523
+ }
524
+
525
+ // Enhanced fBm with more octaves
526
+ float fbm(vec2 p)
527
+ {
528
+ float f = 0.0;
529
+ float amplitude = 0.5;
530
+ float frequency = 1.0;
531
+ float total_amplitude = 0.0;
532
+
533
+ // Rotation matrix for domain warping
534
+ mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
535
+
536
+ // More octaves for richer detail
537
+ for (int i = 0; i < 6; i++) {
538
+ f += amplitude * noise(frequency * p);
539
+ total_amplitude += amplitude;
540
+ amplitude *= 0.5;
541
+ frequency *= 2.0;
542
+ p = m * p;
543
+ }
544
+
545
+ return f / total_amplitude;
546
+ }
547
+
548
+ // Domain warping for more natural cloud shapes
549
+ vec2 warp_domain(vec2 p, float time) {
550
+ // Apply slow warping to the domain
551
+ vec2 offset = vec2(
552
+ fbm(p + vec2(0.0, 0.1*time)),
553
+ fbm(p + vec2(0.1*time, 0.0))
554
+ );
555
+
556
+ // Second level of warping
557
+ return p + 0.4 * offset;
558
+ }
559
+
560
+ // Cloud density function
561
+ float cloud_density(float noise_val, float coverage, float sharpness) {
562
+ // Map noise from [-1, 1] to [0, 1]
563
+ float mapped = (noise_val + 1.0) * 0.5;
564
+
565
+ // Apply coverage control and sharpening
566
+ return smoothstep(1.0 - coverage, 1.0 - coverage + sharpness, mapped);
567
+ }
568
+
569
+ // Additional uniform for cloud parameters
570
+ uniform float cloudCoverage;
571
+ uniform float cloudSharpness;
572
+ uniform float cloudSpeed;
573
+
574
+ void main() {
575
+ // Normalize coordinates
576
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
577
+
578
+ // Time calculation with speed control
579
+ float time = millis * cloudSpeed;
580
+
581
+ // Calculate a base UV that includes camera offset
582
+ float cloud_world_scale = 0.001;
583
+ vec2 uv_world = uv + (cameraOffset * cloud_world_scale);
584
+
585
+ // Movement vectors
586
+ vec2 time_movement1 = vec2(time * 0.01, time * 0.005);
587
+ vec2 time_movement2 = vec2(time * 0.015, -time * 0.007);
588
+ vec2 time_movement3 = vec2(time * 0.02, time * 0.01);
589
+
590
+ // BASE LAYER - large clouds
591
+ vec2 warped_uv1 = warp_domain(uv_world, time * 0.2);
592
+ float base_clouds = fbm(warped_uv1 * 2.0 + time_movement1);
593
+
594
+ // DETAIL LAYER - medium features
595
+ vec2 warped_uv2 = warp_domain(uv_world * 1.5, time * 0.3);
596
+ float detail_clouds = fbm(warped_uv2 * 4.0 + time_movement2);
597
+
598
+ // WISP LAYER - high-frequency details
599
+ float wisp_clouds = fbm(uv_world * 8.0 + time_movement3);
600
+
601
+ // Combine layers with different weights
602
+ float combined = base_clouds * 0.65 + detail_clouds * 0.25 + wisp_clouds * 0.1;
603
+
604
+ // Shape the noise into defined cloud formations using provided parameters
605
+ float cloud_shape = cloud_density(combined, cloudCoverage, cloudSharpness);
606
+
607
+ // Add height variation for 3D effect
608
+ vec3 cloud_color = mix(
609
+ vec3(0.8, 0.8, 0.85), // Bottom color (slightly grayish)
610
+ vec3(1.0, 1.0, 1.0), // Top color (bright white)
611
+ cloud_shape * 0.7 + base_clouds * 0.3
612
+ );
613
+
614
+ // Final cloud color with alpha
615
+ float opacity = cloud_shape * 0.85;
616
+ gl_FragColor = vec4(cloud_color, opacity);
617
+ }
618
+ `;
619
+
620
+ const crtShaderSource = `
621
+ // CRT shader converted for WebGL
622
+ precision mediump float;
623
+
624
+ // Uniform variables
625
+ uniform float millis;
626
+ uniform vec2 resolution;
627
+ uniform sampler2D u_texture;
628
+
629
+ // Additional parameters for CRT effect
630
+ uniform float crtCurvature;
631
+ uniform float crtScanlines;
632
+ uniform float crtChromatic;
633
+
634
+ // Helper function for screen curvature effect
635
+ vec2 curve(vec2 uv)
636
+ {
637
+ uv = (uv - 0.5) * 2.0; // Map uv from [0,1] to [-1,1]
638
+ uv *= crtCurvature; // Apply curvature parameter
639
+
640
+ // Apply barrel distortion based on distance from center
641
+ uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
642
+ uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
643
+
644
+ uv = (uv / 2.0) + 0.5; // Map back to [0,1]
645
+ uv = uv * 0.92 + 0.04; // Scale down and add border margin
646
+ return uv;
647
+ }
648
+
649
+ // Helper to sample texture with bounds checking
650
+ vec4 texSample(sampler2D tex, vec2 uv) {
651
+ if(uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
652
+ return vec4(0.0, 0.0, 0.0, 1.0);
653
+ }
654
+ return texture2D(tex, uv);
655
+ }
656
+
657
+ void main() {
658
+ // Normalize coordinates
659
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
660
+
661
+ // Apply screen curvature
662
+ vec2 curved_uv = curve(uv);
663
+
664
+ // Base color sample (replace with a generated pattern for this example)
665
+ // Generate a sample pattern as we don't have an actual texture
666
+ vec2 patternUV = curved_uv * 10.0; // Scale for visibility
667
+ vec3 baseColor = vec3(
668
+ mod(floor(patternUV.x) + floor(patternUV.y), 2.0) * 0.5 + 0.25,
669
+ mod(floor(patternUV.x * 0.7) + floor(patternUV.y * 0.7), 2.0) * 0.4 + 0.2,
670
+ mod(floor(patternUV.x * 0.5) + floor(patternUV.y * 0.9), 2.0) * 0.5 + 0.15
671
+ );
672
+
673
+ vec3 col = vec3(0.0);
674
+
675
+ // Chromatic aberration effect based on time
676
+ float x = sin(0.3 * millis + curved_uv.y * 21.0) *
677
+ sin(0.7 * millis + curved_uv.y * 29.0) *
678
+ sin(0.3 + 0.33 * millis + curved_uv.y * 31.0) *
679
+ 0.0017 * crtChromatic;
680
+
681
+ // Sample R, G, B channels with chromatic aberration
682
+ vec2 rUV = vec2(x + curved_uv.x + 0.001 * crtChromatic, curved_uv.y + 0.001 * crtChromatic);
683
+ vec2 gUV = vec2(x + curved_uv.x, curved_uv.y - 0.002 * crtChromatic);
684
+ vec2 bUV = vec2(x + curved_uv.x - 0.002 * crtChromatic, curved_uv.y);
685
+
686
+ // Get RGB values with a patterned background to simulate content
687
+ col.r = baseColor.r;
688
+ if(rUV.x >= 0.0 && rUV.x <= 1.0 && rUV.y >= 0.0 && rUV.y <= 1.0) {
689
+ vec2 patternR = rUV * 10.0;
690
+ col.r = mod(floor(patternR.x) + floor(patternR.y), 2.0) * 0.5 + 0.25 + 0.05;
691
+ }
692
+
693
+ col.g = baseColor.g;
694
+ if(gUV.x >= 0.0 && gUV.x <= 1.0 && gUV.y >= 0.0 && gUV.y <= 1.0) {
695
+ vec2 patternG = gUV * 10.0;
696
+ col.g = mod(floor(patternG.x * 0.7) + floor(patternG.y * 0.7), 2.0) * 0.4 + 0.2 + 0.05;
697
+ }
698
+
699
+ col.b = baseColor.b;
700
+ if(bUV.x >= 0.0 && bUV.x <= 1.0 && bUV.y >= 0.0 && bUV.y <= 1.0) {
701
+ vec2 patternB = bUV * 10.0;
702
+ col.b = mod(floor(patternB.x * 0.5) + floor(patternB.y * 0.9), 2.0) * 0.5 + 0.15 + 0.05;
703
+ }
704
+
705
+ // Apply contrast curve and clamp
706
+ col = clamp(col * 0.6 + 0.4 * col * col * 1.0, 0.0, 1.0);
707
+
708
+ // Apply vignetting effect (darken corners)
709
+ float vig = (0.0 + 1.0 * 16.0 * curved_uv.x * curved_uv.y * (1.0 - curved_uv.x) * (1.0 - curved_uv.y));
710
+ col *= vec3(pow(vig, 0.3));
711
+
712
+ // Adjust color balance and intensity
713
+ col *= vec3(0.95, 1.05, 0.95); // Slightly tint green
714
+ col *= 2.8; // Increase brightness
715
+
716
+ // Simulate scanlines based on time and position
717
+ float scans = clamp(0.35 + 0.35 * sin(3.5 * millis + curved_uv.y * resolution.y * 1.5 * crtScanlines), 0.0, 1.0);
718
+ float s = pow(scans, 5.7); // Sharpen the scanline effect
719
+ col = col * vec3(0.4 + 0.7 * s); // Darken based on scanlines
720
+
721
+ // Add slight flicker effect
722
+ col *= 1.0 + 0.01 * sin(10.0 * millis);
723
+
724
+ // Black out pixels outside the curved screen area
725
+ if (curved_uv.x < 0.0 || curved_uv.x > 1.0 || curved_uv.y < 0.0 || curved_uv.y > 1.0) {
726
+ col = vec3(0.0);
727
+ }
728
+
729
+ // Simulate RGB pixel grid (simple version)
730
+ col *= 1.0 - 0.65 * vec3(clamp((mod(gl_FragCoord.x, 2.0) - 1.0) * 2.0, 0.0, 1.0));
731
+
732
+ gl_FragColor = vec4(col, 1.0);
733
+ }
734
+ `;
735
+
736
+ const noiseShaderSource = `
737
+ // Simple perlin noise shader
738
+ precision mediump float;
739
+
740
+ uniform vec2 resolution;
741
+ uniform float millis;
742
+ uniform vec2 cameraOffset;
743
+
744
+ // Simplex noise helper functions
745
+ vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
746
+
747
+ float snoise(vec2 v) {
748
+ const vec4 C = vec4(0.211324865405187, 0.366025403784439,
749
+ -0.577350269189626, 0.024390243902439);
750
+ vec2 i = floor(v + dot(v, C.yy));
751
+ vec2 x0 = v - i + dot(i, C.xx);
752
+ vec2 i1;
753
+ i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
754
+ vec4 x12 = x0.xyxy + C.xxzz;
755
+ x12.xy -= i1;
756
+ i = mod(i, 289.0);
757
+ vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
758
+ vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
759
+ m = m*m;
760
+ m = m*m;
761
+ vec3 x = 2.0 * fract(p * C.www) - 1.0;
762
+ vec3 h = abs(x) - 0.5;
763
+ vec3 ox = floor(x + 0.5);
764
+ vec3 a0 = x - ox;
765
+ m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
766
+ vec3 g;
767
+ g.x = a0.x * x0.x + h.x * x0.y;
768
+ g.yz = a0.yz * x12.xz + h.yz * x12.yw;
769
+ return 130.0 * dot(m, g);
770
+ }
771
+
772
+ void main() {
773
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
774
+
775
+ // Apply camera offset for panning
776
+ vec2 p = uv + cameraOffset * 0.001;
777
+
778
+ // Create animated noise pattern
779
+ float n1 = snoise(p * 3.0 + millis * 0.1);
780
+ float n2 = snoise(p * 6.0 - millis * 0.15);
781
+ float n3 = snoise(p * 12.0 + millis * 0.2);
782
+
783
+ // Combine noise at different frequencies
784
+ float combinedNoise =
785
+ 0.5 * n1 +
786
+ 0.3 * n2 +
787
+ 0.2 * n3;
788
+
789
+ // Map from [-1,1] to [0,1] range
790
+ combinedNoise = (combinedNoise + 1.0) * 0.5;
791
+
792
+ // Create color gradient based on noise
793
+ vec3 color = mix(
794
+ vec3(0.2, 0.1, 0.4), // Dark purple for low values
795
+ vec3(1.0, 0.8, 0.2), // Yellow for high values
796
+ combinedNoise
797
+ );
798
+
799
+ // Add some glow effects
800
+ color += 0.05 * vec3(1.0, 0.6, 0.3) * pow(combinedNoise, 3.0);
801
+
802
+ gl_FragColor = vec4(color, 1.0);
803
+ }
804
+ `;
805
+
806
+ // Initialize WebGL
807
+ function initWebGL() {
808
+ // Set canvas size
809
+ resizeCanvas();
810
+
811
+ // Create a vertex buffer for a quad that fills the viewport
812
+ const vertices = new Float32Array([
813
+ -1.0, -1.0, // bottom left
814
+ 1.0, -1.0, // bottom right
815
+ -1.0, 1.0, // top left
816
+ 1.0, 1.0 // top right
817
+ ]);
818
+
819
+ const vertexBuffer = gl.createBuffer();
820
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
821
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
822
+
823
+ // Set up texcoords for the quad
824
+ const texCoords = new Float32Array([
825
+ 0.0, 0.0, // bottom left
826
+ 1.0, 0.0, // bottom right
827
+ 0.0, 1.0, // top left
828
+ 1.0, 1.0 // top right
829
+ ]);
830
+
831
+ const texCoordBuffer = gl.createBuffer();
832
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
833
+ gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
834
+
835
+ // Use the cloud shader by default
836
+ loadShader(cloudShaderSource);
837
+
838
+ // Start the rendering loop
839
+ requestAnimationFrame(render);
840
+ }
841
+
842
+ // Resize canvas based on resolution settings
843
+ function resizeCanvas() {
844
+ const parent = canvas.parentElement;
845
+ const resolution = resolutionSelect.value;
846
+
847
+ if (resolution === 'auto') {
848
+ // Use the container size
849
+ canvas.width = parent.clientWidth;
850
+ canvas.height = Math.floor(parent.clientWidth * 0.5625); // 16:9 aspect ratio
851
+ } else if (resolution === 'custom') {
852
+ // Use custom resolution
853
+ canvas.width = parseInt(resolutionWidth.value);
854
+ canvas.height = parseInt(resolutionHeight.value);
855
+ } else if (resolution === '720p') {
856
+ canvas.width = 1280;
857
+ canvas.height = 720;
858
+ } else if (resolution === '1080p') {
859
+ canvas.width = 1920;
860
+ canvas.height = 1080;
861
+ }
862
+
863
+ // Update the vertex shader with new dimensions
864
+ if (shaderProgram) {
865
+ loadShader(currentShaderType === 'cloud' ? cloudShaderSource :
866
+ (currentShaderType === 'crt' ? crtShaderSource : noiseShaderSource));
867
+ }
868
+
869
+ // Update WebGL viewport
870
+ gl.viewport(0, 0, canvas.width, canvas.height);
871
+ }
872
+
873
+ // Compile and link shaders
874
+ function loadShader(fragmentShaderSource, isUpload = false) {
875
+ try {
876
+ // Create and compile vertex shader
877
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
878
+ gl.shaderSource(vertexShader, vertexShaderSource);
879
+ gl.compileShader(vertexShader);
880
+
881
+ if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
882
+ throw new Error("Vertex shader compilation failed: " + gl.getShaderInfoLog(vertexShader));
883
+ }
884
+
885
+ // Create and compile fragment shader
886
+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
887
+ gl.shaderSource(fragmentShader, fragmentShaderSource);
888
+ gl.compileShader(fragmentShader);
889
+
890
+ if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
891
+ throw new Error("Fragment shader compilation failed: " + gl.getShaderInfoLog(fragmentShader));
892
+ }
893
+
894
+ // Create shader program
895
+ const program = gl.createProgram();
896
+ gl.attachShader(program, vertexShader);
897
+ gl.attachShader(program, fragmentShader);
898
+ gl.linkProgram(program);
899
+
900
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
901
+ throw new Error("Shader program linking failed: " + gl.getProgramInfoLog(program));
902
+ }
903
+
904
+ // Clean up old program if exists
905
+ if (shaderProgram) {
906
+ gl.deleteProgram(shaderProgram);
907
+ }
908
+
909
+ // Use the new program
910
+ shaderProgram = program;
911
+ gl.useProgram(shaderProgram);
912
+
913
+ // Set up attribute locations
914
+ const positionAttribLocation = gl.getAttribLocation(shaderProgram, "a_position");
915
+ gl.enableVertexAttribArray(positionAttribLocation);
916
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
917
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
918
+ -1.0, -1.0, // bottom left
919
+ 1.0, -1.0, // bottom right
920
+ -1.0, 1.0, // top left
921
+ 1.0, 1.0 // top right
922
+ ]), gl.STATIC_DRAW);
923
+ gl.vertexAttribPointer(positionAttribLocation, 2, gl.FLOAT, false, 0, 0);
924
+
925
+ const texCoordAttribLocation = gl.getAttribLocation(shaderProgram, "a_texCoord");
926
+ if (texCoordAttribLocation !== -1) {
927
+ gl.enableVertexAttribArray(texCoordAttribLocation);
928
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
929
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
930
+ 0.0, 0.0, // bottom left
931
+ 1.0, 0.0, // bottom right
932
+ 0.0, 1.0, // top left
933
+ 1.0, 1.0 // top right
934
+ ]), gl.STATIC_DRAW);
935
+ gl.vertexAttribPointer(texCoordAttribLocation, 2, gl.FLOAT, false, 0, 0);
936
+ }
937
+
938
+ // Get uniform locations
939
+ uniformLocations = {
940
+ millis: gl.getUniformLocation(shaderProgram, "millis"),
941
+ resolution: gl.getUniformLocation(shaderProgram, "resolution"),
942
+ cameraOffset: gl.getUniformLocation(shaderProgram, "cameraOffset"),
943
+ cloudCoverage: gl.getUniformLocation(shaderProgram, "cloudCoverage"),
944
+ cloudSharpness: gl.getUniformLocation(shaderProgram, "cloudSharpness"),
945
+ cloudSpeed: gl.getUniformLocation(shaderProgram, "cloudSpeed"),
946
+ crtCurvature: gl.getUniformLocation(shaderProgram, "crtCurvature"),
947
+ crtScanlines: gl.getUniformLocation(shaderProgram, "crtScanlines"),
948
+ crtChromatic: gl.getUniformLocation(shaderProgram, "crtChromatic")
949
+ };
950
+
951
+ // Update shader code display
952
+ shaderCode.textContent = fragmentShaderSource;
953
+
954
+ // Update status
955
+ shaderStatus.className = 'status-indicator success';
956
+ if (isUpload) {
957
+ statusMessage.textContent = 'Shader compiled successfully!';
958
+ statusMessage.className = 'status-message success';
959
+ statusMessage.style.display = 'block';
960
+ setTimeout(() => { statusMessage.style.display = 'none'; }, 3000);
961
+ }
962
+
963
+ return true;
964
+ } catch (error) {
965
+ console.error('Shader compilation error:', error);
966
+
967
+ // Update status
968
+ shaderStatus.className = 'status-indicator error';
969
+ statusMessage.textContent = 'Shader Error: ' + error.message;
970
+ statusMessage.className = 'status-message error';
971
+ statusMessage.style.display = 'block';
972
+
973
+ // Show error in shader code display
974
+ shaderCode.className = 'shader-code error';
975
+ shaderCode.textContent = error.message + '\n\nShader Source:\n' + fragmentShaderSource;
976
+
977
+ // Make sure code is visible
978
+ shaderCode.style.display = 'block';
979
+
980
+ return false;
981
+ }
982
+ }
983
+
984
+ // Main render loop
985
+ function render() {
986
+ if (!gl || !shaderProgram) {
987
+ requestAnimationFrame(render);
988
+ return;
989
+ }
990
+
991
+ // Clear canvas
992
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
993
+ gl.clear(gl.COLOR_BUFFER_BIT);
994
+
995
+ // Set uniforms
996
+ const currentTime = isPlaying ? (Date.now() - startTime) / 1000.0 * timeSpeed : 0;
997
+
998
+ if (uniformLocations.millis !== -1) {
999
+ gl.uniform1f(uniformLocations.millis, currentTime);
1000
+ }
1001
+
1002
+ if (uniformLocations.resolution !== -1) {
1003
+ gl.uniform2f(uniformLocations.resolution, canvas.width, canvas.height);
1004
+ }
1005
+
1006
+ if (uniformLocations.cameraOffset !== -1) {
1007
+ gl.uniform2f(uniformLocations.cameraOffset, cameraOffsetX, cameraOffsetY);
1008
+ }
1009
+
1010
+ // Set shader-specific uniforms
1011
+ if (currentShaderType === 'cloud') {
1012
+ if (uniformLocations.cloudCoverage !== -1) {
1013
+ gl.uniform1f(uniformLocations.cloudCoverage, cloudCoverage);
1014
+ }
1015
+
1016
+ if (uniformLocations.cloudSharpness !== -1) {
1017
+ gl.uniform1f(uniformLocations.cloudSharpness, cloudSharpness);
1018
+ }
1019
+
1020
+ if (uniformLocations.cloudSpeed !== -1) {
1021
+ gl.uniform1f(uniformLocations.cloudSpeed, cloudSpeed);
1022
+ }
1023
+ } else if (currentShaderType === 'crt') {
1024
+ if (uniformLocations.crtCurvature !== -1) {
1025
+ gl.uniform1f(uniformLocations.crtCurvature, crtCurvature);
1026
+ }
1027
+
1028
+ if (uniformLocations.crtScanlines !== -1) {
1029
+ gl.uniform1f(uniformLocations.crtScanlines, crtScanlines);
1030
+ }
1031
+
1032
+ if (uniformLocations.crtChromatic !== -1) {
1033
+ gl.uniform1f(uniformLocations.crtChromatic, crtChromatic);
1034
+ }
1035
+ }
1036
+
1037
+ // Draw the quad
1038
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
1039
+
1040
+ // Continue animation
1041
+ requestAnimationFrame(render);
1042
+ }
1043
+
1044
+ // Handle shader file upload
1045
+ shaderFileInput.addEventListener('change', (event) => {
1046
+ const file = event.target.files[0];
1047
+ if (file) {
1048
+ const reader = new FileReader();
1049
+ reader.onload = (e) => {
1050
+ const shaderSource = e.target.result;
1051
+
1052
+ // Try to determine shader type from content
1053
+ if (shaderSource.includes('cloud_density') || shaderSource.includes('fbm')) {
1054
+ currentShaderType = 'cloud';
1055
+ cloudParams.style.display = 'block';
1056
+ crtParams.style.display = 'none';
1057
+
1058
+ // Update preset buttons
1059
+ cloudPresetBtn.classList.add('active');
1060
+ crtPresetBtn.classList.remove('active');
1061
+ noisePresetBtn.classList.remove('active');
1062
+ } else if (shaderSource.includes('curve') || shaderSource.includes('scanline')) {
1063
+ currentShaderType = 'crt';
1064
+ cloudParams.style.display = 'none';
1065
+ crtParams.style.display = 'block';
1066
+
1067
+ // Update preset buttons
1068
+ cloudPresetBtn.classList.remove('active');
1069
+ crtPresetBtn.classList.add('active');
1070
+ noisePresetBtn.classList.remove('active');
1071
+ } else {
1072
+ // Default to cloud shader if can't determine
1073
+ currentShaderType = 'cloud';
1074
+ cloudParams.style.display = 'block';
1075
+ crtParams.style.display = 'none';
1076
+
1077
+ // Update preset buttons
1078
+ cloudPresetBtn.classList.add('active');
1079
+ crtPresetBtn.classList.remove('active');
1080
+ noisePresetBtn.classList.remove('active');
1081
+ }
1082
+
1083
+ // Adapt LÖVE shader to WebGL
1084
+ let adaptedSource = adaptLoveShaderToWebGL(shaderSource);
1085
+
1086
+ // Load the shader
1087
+ loadShader(adaptedSource, true);
1088
+
1089
+ // Show the shader code
1090
+ shaderCode.style.display = 'block';
1091
+ };
1092
+ reader.readAsText(file);
1093
+ }
1094
+ });
1095
+
1096
+ // Adapt LÖVE shader to WebGL
1097
+ function adaptLoveShaderToWebGL(source) {
1098
+ // Add precision specifier if missing
1099
+ if (!source.includes('precision ')) {
1100
+ source = 'precision mediump float;\n\n' + source;
1101
+ }
1102
+
1103
+ // Replace LÖVE's effect function with main
1104
+ source = source.replace(/vec4\s+effect\s*\(\s*vec4\s+color\s*,\s*Image\s+tex\s*,\s*vec2\s+texture_coords\s*,\s*vec2\s+screen_coords\s*\)/g,
1105
+ 'void main()');
1106
+
1107
+ // Replace Texel with texture2D
1108
+ source = source.replace(/Texel\s*\(\s*tex\s*,/g, 'texture2D(u_texture,');
1109
+
1110
+ // Add necessary uniforms if they don't exist
1111
+ if (!source.includes('uniform vec2 resolution')) {
1112
+ source = source.replace('void main()',
1113
+ 'uniform vec2 resolution;\n\nvoid main()');
1114
+ }
1115
+
1116
+ // Add cloud-specific uniforms if needed
1117
+ if (source.includes('cloud_density')) {
1118
+ if (!source.includes('uniform float cloudCoverage')) {
1119
+ source = source.replace('void main()',
1120
+ 'uniform float cloudCoverage;\nuniform float cloudSharpness;\nuniform float cloudSpeed;\n\nvoid main()');
1121
+ }
1122
+ }
1123
+
1124
+ // Add CRT-specific uniforms if needed
1125
+ if (source.includes('curve(') || source.includes('scanline')) {
1126
+ if (!source.includes('uniform float crtCurvature')) {
1127
+ source = source.replace('void main()',
1128
+ 'uniform float crtCurvature;\nuniform float crtScanlines;\nuniform float crtChromatic;\n\nvoid main()');
1129
+ }
1130
+ }
1131
+
1132
+ // Replace texture_coords and screen_coords with gl_FragCoord
1133
+ source = source.replace(/texture_coords/g, 'gl_FragCoord.xy / resolution.xy');
1134
+ source = source.replace(/screen_coords/g, 'gl_FragCoord.xy');
1135
+
1136
+ // Replace return statements with gl_FragColor assignment
1137
+ source = source.replace(/return\s+(.*?);/g, 'gl_FragColor = $1;');
1138
+
1139
+ return source;
1140
+ }
1141
+
1142
+ // Initialize event listeners for controls
1143
+ function initControlListeners() {
1144
+ // Play/Pause button
1145
+ playPauseBtn.addEventListener('click', () => {
1146
+ isPlaying = !isPlaying;
1147
+ playPauseBtn.textContent = isPlaying ? 'Pause' : 'Play';
1148
+
1149
+ if (isPlaying) {
1150
+ startTime = Date.now() - (startTime - Date.now()); // Adjust for pause time
1151
+ }
1152
+ });
1153
+
1154
+ // Reset button
1155
+ resetBtn.addEventListener('click', () => {
1156
+ startTime = Date.now();
1157
+ cameraOffsetX = 0;
1158
+ cameraOffsetY = 0;
1159
+ });
1160
+
1161
+ // Time speed control
1162
+ timeSpeedSlider.addEventListener('input', (e) => {
1163
+ timeSpeed = parseFloat(e.target.value);
1164
+ timeSpeedValue.textContent = timeSpeed.toFixed(2);
1165
+ });
1166
+
1167
+ // Cloud parameters
1168
+ cloudCoverageSlider.addEventListener('input', (e) => {
1169
+ cloudCoverage = parseFloat(e.target.value);
1170
+ cloudCoverageValue.textContent = cloudCoverage.toFixed(2);
1171
+ });
1172
+
1173
+ cloudSharpnessSlider.addEventListener('input', (e) => {
1174
+ cloudSharpness = parseFloat(e.target.value);
1175
+ cloudSharpnessValue.textContent = cloudSharpness.toFixed(2);
1176
+ });
1177
+
1178
+ cloudSpeedSlider.addEventListener('input', (e) => {
1179
+ cloudSpeed = parseFloat(e.target.value);
1180
+ cloudSpeedValue.textContent = cloudSpeed.toFixed(2);
1181
+ });
1182
+
1183
+ // CRT parameters
1184
+ crtCurvatureSlider.addEventListener('input', (e) => {
1185
+ crtCurvature = parseFloat(e.target.value);
1186
+ crtCurvatureValue.textContent = crtCurvature.toFixed(2);
1187
+ });
1188
+
1189
+ crtScanlinesSlider.addEventListener('input', (e) => {
1190
+ crtScanlines = parseFloat(e.target.value);
1191
+ crtScanlinesValue.textContent = crtScanlines.toFixed(2);
1192
+ });
1193
+
1194
+ crtChromaticSlider.addEventListener('input', (e) => {
1195
+ crtChromatic = parseFloat(e.target.value);
1196
+ crtChromaticValue.textContent = crtChromatic.toFixed(2);
1197
+ });
1198
+
1199
+ // Show/Hide shader code
1200
+ toggleCodeBtn.addEventListener('click', () => {
1201
+ if (shaderCode.style.display === 'none') {
1202
+ shaderCode.style.display = 'block';
1203
+ } else {
1204
+ shaderCode.style.display = 'none';
1205
+ }
1206
+ });
1207
+
1208
+ // Preset buttons
1209
+ cloudPresetBtn.addEventListener('click', () => {
1210
+ currentShaderType = 'cloud';
1211
+ loadShader(cloudShaderSource);
1212
+
1213
+ // Update UI
1214
+ cloudParams.style.display = 'block';
1215
+ crtParams.style.display = 'none';
1216
+
1217
+ // Update buttons
1218
+ cloudPresetBtn.classList.add('active');
1219
+ crtPresetBtn.classList.remove('active');
1220
+ noisePresetBtn.classList.remove('active');
1221
+ });
1222
+
1223
+ crtPresetBtn.addEventListener('click', () => {
1224
+ currentShaderType = 'crt';
1225
+ loadShader(crtShaderSource);
1226
+
1227
+ // Update UI
1228
+ cloudParams.style.display = 'none';
1229
+ crtParams.style.display = 'block';
1230
+
1231
+ // Update buttons
1232
+ cloudPresetBtn.classList.remove('active');
1233
+ crtPresetBtn.classList.add('active');
1234
+ noisePresetBtn.classList.remove('active');
1235
+ });
1236
+
1237
+ noisePresetBtn.addEventListener('click', () => {
1238
+ currentShaderType = 'noise';
1239
+ loadShader(noiseShaderSource);
1240
+
1241
+ // Update UI
1242
+ cloudParams.style.display = 'none';
1243
+ crtParams.style.display = 'none';
1244
+
1245
+ // Update buttons
1246
+ cloudPresetBtn.classList.remove('active');
1247
+ crtPresetBtn.classList.remove('active');
1248
+ noisePresetBtn.classList.add('active');
1249
+ });
1250
+
1251
+ // Mouse interaction for camera offset
1252
+ canvas.addEventListener('mousemove', (e) => {
1253
+ const rect = canvas.getBoundingClientRect();
1254
+ const x = e.clientX - rect.left;
1255
+ const y = e.clientY - rect.top;
1256
+
1257
+ // Normalize coordinates
1258
+ mouseX = (x / canvas.width) * 2 - 1;
1259
+ mouseY = (y / canvas.height) * 2 - 1;
1260
+
1261
+ // Update display
1262
+ mousePosition.textContent = `${mouseX.toFixed(2)}, ${mouseY.toFixed(2)}`;
1263
+ });
1264
+
1265
+ // Mouse drag for camera panning
1266
+ let isDragging = false;
1267
+ let lastMouseX, lastMouseY;
1268
+
1269
+ canvas.addEventListener('mousedown', (e) => {
1270
+ isDragging = true;
1271
+ lastMouseX = e.clientX;
1272
+ lastMouseY = e.clientY;
1273
+ });
1274
+
1275
+ window.addEventListener('mouseup', () => {
1276
+ isDragging = false;
1277
+ });
1278
+
1279
+ window.addEventListener('mousemove', (e) => {
1280
+ if (isDragging) {
1281
+ const deltaX = e.clientX - lastMouseX;
1282
+ const deltaY = e.clientY - lastMouseY;
1283
+
1284
+ cameraOffsetX += deltaX;
1285
+ cameraOffsetY -= deltaY; // Invert Y for intuitive panning
1286
+
1287
+ lastMouseX = e.clientX;
1288
+ lastMouseY = e.clientY;
1289
+ }
1290
+ });
1291
+
1292
+ // Resolution controls
1293
+ resolutionSelect.addEventListener('change', () => {
1294
+ if (resolutionSelect.value === 'custom') {
1295
+ customResolution.style.display = 'block';
1296
+ } else {
1297
+ customResolution.style.display = 'none';
1298
+ }
1299
+
1300
+ resizeCanvas();
1301
+ });
1302
+
1303
+ // Custom resolution inputs
1304
+ resolutionWidth.addEventListener('change', resizeCanvas);
1305
+ resolutionHeight.addEventListener('change', resizeCanvas);
1306
+
1307
+ // Window resize handler
1308
+ window.addEventListener('resize', () => {
1309
+ if (resolutionSelect.value === 'auto') {
1310
+ resizeCanvas();
1311
+ }
1312
+ });
1313
+ }
1314
+
1315
+ // Initialize the application
1316
+ initWebGL();
1317
+ initControlListeners();
1318
+ </script>
1319
+ </body>
1320
+ </html>