Keeg42069 commited on
Commit
047cdde
·
verified ·
1 Parent(s): 0e62b97

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +677 -19
index.html CHANGED
@@ -1,19 +1,677 @@
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>RGB Spectrum Generator 500</title>
7
+ <!-- Importing FontAwesome for Icons -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --bg-color: #0f172a;
12
+ --card-bg: #1e293b;
13
+ --text-primary: #f8fafc;
14
+ --text-secondary: #94a3b8;
15
+ --accent: #38bdf8;
16
+ --accent-hover: #0ea5e9;
17
+ --border: #334155;
18
+ --success: #22c55e;
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
+ }
27
+
28
+ body {
29
+ font-family: var(--font-family);
30
+ background-color: var(--bg-color);
31
+ color: var(--text-primary);
32
+ line-height: 1.6;
33
+ min-height: 100vh;
34
+ display: flex;
35
+ flex-direction: column;
36
+ }
37
+
38
+ /* Header Styling */
39
+ header {
40
+ background: rgba(15, 23, 42, 0.95);
41
+ backdrop-filter: blur(10px);
42
+ border-bottom: 1px solid var(--border);
43
+ padding: 1rem 2rem;
44
+ position: sticky;
45
+ top: 0;
46
+ z-index: 100;
47
+ display: flex;
48
+ justify-content: space-between;
49
+ align-items: center;
50
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
51
+ }
52
+
53
+ .brand {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 0.75rem;
57
+ font-size: 1.25rem;
58
+ font-weight: 700;
59
+ color: var(--text-primary);
60
+ }
61
+
62
+ .brand i {
63
+ color: var(--accent);
64
+ }
65
+
66
+ .anycoder-link {
67
+ font-size: 0.875rem;
68
+ color: var(--text-secondary);
69
+ text-decoration: none;
70
+ transition: color 0.3s ease;
71
+ border: 1px solid var(--border);
72
+ padding: 0.4rem 0.8rem;
73
+ border-radius: 6px;
74
+ background: rgba(255,255,255,0.05);
75
+ }
76
+
77
+ .anycoder-link:hover {
78
+ color: var(--accent);
79
+ border-color: var(--accent);
80
+ background: rgba(56, 189, 248, 0.1);
81
+ }
82
+
83
+ /* Main Content */
84
+ main {
85
+ flex: 1;
86
+ padding: 2rem;
87
+ max-width: 1600px;
88
+ margin: 0 auto;
89
+ width: 100%;
90
+ }
91
+
92
+ /* Controls Section */
93
+ .controls {
94
+ display: flex;
95
+ flex-wrap: wrap;
96
+ gap: 1rem;
97
+ margin-bottom: 2rem;
98
+ align-items: center;
99
+ justify-content: space-between;
100
+ background: var(--card-bg);
101
+ padding: 1.5rem;
102
+ border-radius: 12px;
103
+ border: 1px solid var(--border);
104
+ }
105
+
106
+ .control-group {
107
+ display: flex;
108
+ gap: 1rem;
109
+ align-items: center;
110
+ }
111
+
112
+ h1 {
113
+ font-size: 1.5rem;
114
+ margin-bottom: 0.5rem;
115
+ background: linear-gradient(to right, var(--text-primary), var(--accent));
116
+ -webkit-background-clip: text;
117
+ -webkit-text-fill-color: transparent;
118
+ }
119
+
120
+ p.subtitle {
121
+ color: var(--text-secondary);
122
+ font-size: 0.9rem;
123
+ }
124
+
125
+ button {
126
+ background-color: var(--accent);
127
+ color: #0f172a;
128
+ border: none;
129
+ padding: 0.75rem 1.5rem;
130
+ border-radius: 8px;
131
+ font-weight: 600;
132
+ cursor: pointer;
133
+ transition: all 0.2s ease;
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 0.5rem;
137
+ font-size: 0.95rem;
138
+ }
139
+
140
+ button:hover {
141
+ background-color: var(--accent-hover);
142
+ transform: translateY(-1px);
143
+ }
144
+
145
+ button.secondary {
146
+ background-color: transparent;
147
+ color: var(--text-primary);
148
+ border: 1px solid var(--border);
149
+ }
150
+
151
+ button.secondary:hover {
152
+ background-color: rgba(255,255,255,0.05);
153
+ border-color: var(--text-secondary);
154
+ }
155
+
156
+ button:disabled {
157
+ opacity: 0.5;
158
+ cursor: not-allowed;
159
+ transform: none;
160
+ }
161
+
162
+ /* Progress Bar */
163
+ .progress-container {
164
+ width: 100%;
165
+ height: 6px;
166
+ background-color: var(--border);
167
+ border-radius: 3px;
168
+ margin-top: 1rem;
169
+ overflow: hidden;
170
+ display: none; /* Hidden by default */
171
+ }
172
+
173
+ .progress-bar {
174
+ height: 100%;
175
+ background-color: var(--success);
176
+ width: 0%;
177
+ transition: width 0.1s linear;
178
+ }
179
+
180
+ /* Grid Layout */
181
+ .grid-container {
182
+ display: grid;
183
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
184
+ gap: 1.5rem;
185
+ }
186
+
187
+ /* Color Card */
188
+ .card {
189
+ background-color: var(--card-bg);
190
+ border: 1px solid var(--border);
191
+ border-radius: 12px;
192
+ overflow: hidden;
193
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
194
+ position: relative;
195
+ display: flex;
196
+ flex-direction: column;
197
+ animation: fadeIn 0.4s ease-out forwards;
198
+ opacity: 0;
199
+ }
200
+
201
+ @keyframes fadeIn {
202
+ to { opacity: 1; }
203
+ }
204
+
205
+ .card:hover {
206
+ transform: translateY(-4px);
207
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
208
+ border-color: var(--accent);
209
+ }
210
+
211
+ .card-preview {
212
+ width: 100%;
213
+ aspect-ratio: 1 / 1;
214
+ position: relative;
215
+ background-color: #000; /* Fallback */
216
+ }
217
+
218
+ .card-preview img {
219
+ width: 100%;
220
+ height: 100%;
221
+ object-fit: cover;
222
+ display: block;
223
+ }
224
+
225
+ .card-info {
226
+ padding: 1rem;
227
+ flex: 1;
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: 0.25rem;
231
+ }
232
+
233
+ .color-name {
234
+ font-weight: 700;
235
+ font-size: 1rem;
236
+ color: var(--text-primary);
237
+ margin-bottom: 0.25rem;
238
+ }
239
+
240
+ .color-values {
241
+ font-family: 'Courier New', monospace;
242
+ font-size: 0.8rem;
243
+ color: var(--text-secondary);
244
+ display: flex;
245
+ justify-content: space-between;
246
+ }
247
+
248
+ .card-actions {
249
+ margin-top: auto;
250
+ padding-top: 0.75rem;
251
+ }
252
+
253
+ .btn-download-sm {
254
+ width: 100%;
255
+ padding: 0.5rem;
256
+ font-size: 0.85rem;
257
+ justify-content: center;
258
+ background: rgba(255,255,255,0.05);
259
+ color: var(--text-primary);
260
+ border: 1px solid var(--border);
261
+ }
262
+
263
+ .btn-download-sm:hover {
264
+ background: var(--accent);
265
+ color: #0f172a;
266
+ border-color: var(--accent);
267
+ }
268
+
269
+ /* Empty State */
270
+ .empty-state {
271
+ grid-column: 1 / -1;
272
+ text-align: center;
273
+ padding: 4rem 2rem;
274
+ color: var(--text-secondary);
275
+ border: 2px dashed var(--border);
276
+ border-radius: 12px;
277
+ }
278
+
279
+ .empty-state i {
280
+ font-size: 3rem;
281
+ margin-bottom: 1rem;
282
+ opacity: 0.5;
283
+ }
284
+
285
+ /* Toast Notification */
286
+ .toast {
287
+ position: fixed;
288
+ bottom: 2rem;
289
+ right: 2rem;
290
+ background: var(--card-bg);
291
+ border: 1px solid var(--accent);
292
+ color: var(--text-primary);
293
+ padding: 1rem 1.5rem;
294
+ border-radius: 8px;
295
+ box-shadow: 0 10px 25px rgba(0,0,0,0.5);
296
+ display: flex;
297
+ align-items: center;
298
+ gap: 0.75rem;
299
+ transform: translateY(100px);
300
+ opacity: 0;
301
+ transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
302
+ z-index: 1000;
303
+ }
304
+
305
+ .toast.show {
306
+ transform: translateY(0);
307
+ opacity: 1;
308
+ }
309
+
310
+ .toast i {
311
+ color: var(--success);
312
+ }
313
+
314
+ /* Footer */
315
+ footer {
316
+ text-align: center;
317
+ padding: 2rem;
318
+ color: var(--text-secondary);
319
+ font-size: 0.875rem;
320
+ border-top: 1px solid var(--border);
321
+ margin-top: auto;
322
+ }
323
+
324
+ /* Responsive */
325
+ @media (max-width: 768px) {
326
+ header {
327
+ flex-direction: column;
328
+ gap: 1rem;
329
+ }
330
+ .controls {
331
+ flex-direction: column;
332
+ align-items: stretch;
333
+ }
334
+ .control-group {
335
+ flex-direction: column;
336
+ }
337
+ .grid-container {
338
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
339
+ }
340
+ }
341
+ </style>
342
+ </head>
343
+ <body>
344
+
345
+ <header>
346
+ <div class="brand">
347
+ <i class="fa-solid fa-palette"></i>
348
+ <span>SpectrumGen</span>
349
+ </div>
350
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
351
+ Built with anycoder <i class="fa-solid fa-arrow-up-right-from-square" style="font-size: 0.7em;"></i>
352
+ </a>
353
+ </header>
354
+
355
+ <main>
356
+ <section class="controls">
357
+ <div class="info">
358
+ <h1>RGB Color Generator</h1>
359
+ <p class="subtitle">Generate 500 unique 256x256 images covering the full spectrum.</p>
360
+ </div>
361
+ <div class="control-group">
362
+ <button id="generateBtn" onclick="startGeneration()">
363
+ <i class="fa-solid fa-wand-magic-sparkles"></i> Generate Colors
364
+ </button>
365
+ <button id="downloadAllBtn" class="secondary" onclick="downloadAll()" disabled>
366
+ <i class="fa-solid fa-download"></i> Download All (ZIP-like)
367
+ </button>
368
+ <button id="clearBtn" class="secondary" onclick="clearGrid()">
369
+ <i class="fa-solid fa-trash"></i> Clear
370
+ </button>
371
+ </div>
372
+ <div class="progress-container" id="progressContainer">
373
+ <div class="progress-bar" id="progressBar"></div>
374
+ </div>
375
+ </section>
376
+
377
+ <section id="gallery" class="grid-container">
378
+ <div class="empty-state" id="emptyState">
379
+ <i class="fa-solid fa-image"></i>
380
+ <h3>No colors generated yet</h3>
381
+ <p>Click "Generate Colors" to create the spectrum.</p>
382
+ </div>
383
+ </section>
384
+ </main>
385
+
386
+ <footer>
387
+ <p>&copy; 2023 RGB Spectrum Generator. Pure HTML/CSS/JS.</p>
388
+ </footer>
389
+
390
+ <!-- Toast Notification Element -->
391
+ <div id="toast" class="toast">
392
+ <i class="fa-solid fa-circle-check"></i>
393
+ <span id="toastMsg">Operation Successful</span>
394
+ </div>
395
+
396
+ <script>
397
+ // --- Configuration & State ---
398
+ const TOTAL_COLORS = 500;
399
+ const IMAGE_SIZE = 256;
400
+ let generatedColors = []; // Stores objects {r, g, b, name, dataUrl}
401
+
402
+ // --- Color Name Database (Condensed Standard Web Colors) ---
403
+ const colorNames = [
404
+ {r:0,g:0,b:0,name:"Black"}, {r:255,g:255,b:255,name:"White"},
405
+ {r:128,g:128,b:128,name:"Gray"}, {r:192,g:192,b:192,name:"Silver"},
406
+ {r:255,g:0,b:0,name:"Red"}, {r:128,g:0,b:0,name:"Maroon"},
407
+ {r:0,g:255,b:0,name:"Lime"}, {r:0,g:128,b:0,name:"Green"},
408
+ {r:0,g:0,b:255,name:"Blue"}, {r:0,g:0,b:128,name:"Navy"},
409
+ {r:255,g:255,b:0,name:"Yellow"}, {r:128,g:128,b:0,name:"Olive"},
410
+ {r:0,g:255,b:255,name:"Aqua"}, {r:0,g:128,b:128,name:"Teal"},
411
+ {r:255,g:0,b:255,name:"Fuchsia"}, {r:128,g:0,b:128,name:"Purple"},
412
+ {r:255,g:165,b:0,name:"Orange"}, {r:255,g:192,b:203,name:"Pink"},
413
+ {r:165,g:42,b:42,name:"Brown"}, {r:244,g:164,b:96,name:"SandyBrown"},
414
+ {r:220,g:20,b:60,name:"Crimson"}, {r:75,g:0,b:130,name:"Indigo"},
415
+ {r:238,g:130,b:238,name:"Violet"}, {r:173,g:255,b:47,name:"GreenYellow"},
416
+ {r:255,g:215,b:0,name:"Gold"}, {r:250,g:128,b:114,name:"Salmon"},
417
+ {r:128,g:0,b:0,name:"DarkRed"}, {r:0,g:100,b:0,name:"DarkGreen"},
418
+ {r:0,g:0,b:139,name:"DarkBlue"}, {r:139,g:0,b:139,name:"DarkMagenta"},
419
+ {r:184,g:134,b:11,name:"DarkGoldenRod"}, {r:169,g:169,b:169,name:"DarkGray"},
420
+ {r:0,g:139,b:139,name:"DarkCyan"}, {r:255,g:140,b:0,name:"DarkOrange"},
421
+ {r:32,g:178,b:170,name:"LightSeaGreen"}, {r:135,g:206,b:250,name:"LightSkyBlue"},
422
+ {r:119,g:136,b:153,name:"LightSlateGray"}, {r:176,g:196,b:222,name:"LightSteelBlue"},
423
+ {r:255,g:228,b:225,name:"MistyRose"}, {r:255,g:240,b:245,name:"LavenderBlush"},
424
+ {r:255,g:250,b:250,name:"Snow"}, {r:240,g:255,b:240,name:"Honeydew"},
425
+ {r:245,g:255,b:250,name:"MintCream"}, {r:240,g:248,b:255,name:"AliceBlue"},
426
+ {r:255,g:235,b:205,name:"BlanchedAlmond"}, {r:255,g:228,b:196,name:"Bisque"},
427
+ {r:255,g:255,b:224,name:"LightYellow"}, {r:250,g:250,b:210,name:"LightGoldenRodYellow"},
428
+ {r:255,g:255,b:0,name:"Yellow"}, {r:154,g:205,b:50,name:"YellowGreen"},
429
+ {r:107,g:142,b:35,name:"OliveDrab"}, {r:85,g:107,b:47,name:"DarkOliveGreen"},
430
+ {r:143,g:188,b:143,name:"DarkSeaGreen"}, {r:46,g:139,b:87,name:"SeaGreen"},
431
+ {r:60,g:179,b:113,name:"MediumSeaGreen"}, {r:3,g:168,b:158,name:"MediumTurquoise"},
432
+ {r:64,g:224,b:208,name:"Turquoise"}, {r:72,g:209,b:204,name:"MediumTurquoise"},
433
+ {r:175,g:238,b:238,name:"PaleTurquoise"}, {r:224,g:255,b:255,name:"LightCyan"},
434
+ {r:0,g:255,b:255,name:"Cyan"}, {r:0,g:206,b:209,name:"DarkTurquoise"},
435
+ {r:70,g:130,b:180,name:"SteelBlue"}, {r:100,g:149,b:237,name:"CornflowerBlue"},
436
+ {r:30,g:144,b:255,name:"DodgerBlue"}, {r:0,g:0,b:205,name:"MediumBlue"},
437
+ {r:65,g:105,b:225,name:"RoyalBlue"}, {r:0,g:191,b:255,name:"DeepSkyBlue"},
438
+ {r:135,g:206,b:235,name:"SkyBlue"}, {r:176,g:224,b:230,name:"PowderBlue"},
439
+ {r:173,g:216,b:230,name:"LightBlue"}, {r:230,g:230,b:250,name:"Lavender"},
440
+ {r:216,g:191,b:216,name:"Thistle"}, {r:221,g:160,b:221,name:"Plum"},
441
+ {r:238,g:130,b:238,name:"Violet"}, {r:255,g:0,b:255,name:"Magenta"},
442
+ {r:199,g:21,b:133,name:"MediumVioletRed"}, {r:255,g:20,b:147,name:"DeepPink"},
443
+ {r:255,g:105,b:180,name:"HotPink"}, {r:255,g:182,b:193,name:"LightPink"},
444
+ {r:255,g:192,b:203,name:"Pink"}, {r:250,g:128,b:114,name:"Salmon"},
445
+ {r:233,g:150,b:122,name:"DarkSalmon"}, {r:255,g:160,b:122,name:"LightSalmon"},
446
+ {r:255,g:127,b:80,name:"Coral"}, {r:240,g:128,b:128,name:"LightCoral"},
447
+ {r:255,g:99,b:71,name:"Tomato"}, {r:255,g:69,b:0,name:"OrangeRed"},
448
+ {r:255,g:215,b:0,name:"Gold"}, {r:218,g:165,b:32,name:"GoldenRod"},
449
+ {r:248,g:248,b:255,name:"GhostWhite"}, {r:245,g:245,b:245,name:"WhiteSmoke"},
450
+ {r:47,g:79,b:79,name:"DarkSlateGray"}, {r:112,g:128,b:144,name:"SlateGray"},
451
+ {r:25,g:25,b:112,name:"MidnightBlue"}
452
+ ];
453
+
454
+ // --- Helper Functions ---
455
+
456
+ // Calculate Euclidean distance between two colors
457
+ function colorDistance(r1, g1, b1, r2, g2, b2) {
458
+ return Math.sqrt(
459
+ Math.pow(r1 - r2, 2) +
460
+ Math.pow(g1 - g2, 2) +
461
+ Math.pow(b1 - b2, 2)
462
+ );
463
+ }
464
+
465
+ // Find the closest named color
466
+ function getClosestColorName(r, g, b) {
467
+ // Check for grayscale first (simple heuristic)
468
+ if (Math.abs(r - g) < 5 && Math.abs(g - b) < 5 && Math.abs(r - b) < 5) {
469
+ if (r < 10) return "Black";
470
+ if (r > 245) return "White";
471
+ if (r < 50) return "Very Dark Gray";
472
+ if (r > 200) return "Very Light Gray";
473
+ return "Gray";
474
+ }
475
+
476
+ let minDistance = Infinity;
477
+ let closestName = "Unknown Color";
478
+
479
+ for (const color of colorNames) {
480
+ const dist = colorDistance(r, g, b, color.r, color.g, color.b);
481
+ if (dist < minDistance) {
482
+ minDistance = dist;
483
+ closestName = color.name;
484
+ }
485
+ }
486
+ return closestName.replace(/\s+/g, ''); // Remove spaces for filename
487
+ }
488
+
489
+ // Create a 256x256 canvas and return Data URL
490
+ function generateColorImage(r, g, b) {
491
+ const canvas = document.createElement('canvas');
492
+ canvas.width = IMAGE_SIZE;
493
+ canvas.height = IMAGE_SIZE;
494
+ const ctx = canvas.getContext('2d');
495
+
496
+ // Fill background
497
+ ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
498
+ ctx.fillRect(0, 0, IMAGE_SIZE, IMAGE_SIZE);
499
+
500
+ // Optional: Add a subtle border to ensure visibility if white on white background
501
+ // though the request asked for individual colors, usually raw color is preferred.
502
+ // We will stick to pure color as requested.
503
+
504
+ return canvas.toDataURL('image/png');
505
+ }
506
+
507
+ function showToast(message) {
508
+ const toast = document.getElementById('toast');
509
+ const msg = document.getElementById('toastMsg');
510
+ msg.textContent = message;
511
+ toast.classList.add('show');
512
+ setTimeout(() => {
513
+ toast.classList.remove('show');
514
+ }, 3000);
515
+ }
516
+
517
+ function downloadImage(dataUrl, filename) {
518
+ const link = document.createElement('a');
519
+ link.href = dataUrl;
520
+ link.download = filename;
521
+ document.body.appendChild(link);
522
+ link.click();
523
+ document.body.removeChild(link);
524
+ }
525
+
526
+ // --- Main Logic ---
527
+
528
+ function startGeneration() {
529
+ const gallery = document.getElementById('gallery');
530
+ const emptyState = document.getElementById('emptyState');
531
+ const progressBar = document.getElementById('progressBar');
532
+ const progressContainer = document.getElementById('progressContainer');
533
+ const btn = document.getElementById('generateBtn');
534
+
535
+ // Reset UI
536
+ if (emptyState) emptyState.style.display = 'none';
537
+ gallery.innerHTML = '';
538
+ generatedColors = [];
539
+ btn.disabled = true;
540
+ btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Generating...';
541
+ progressContainer.style.display = 'block';
542
+ progressBar.style.width = '0%';
543
+
544
+ // We'll use a stepped approach to ensure full spectrum coverage (0 to 255)
545
+ // 500 is approx 8^3 (512). We will step by 32, which gives 8 steps.
546
+ // 8 * 8 * 8 = 512 colors. We will take the first 500.
547
+
548
+ const step = 32;
549
+ let count = 0;
550
+
551
+ // Using a small timeout to allow UI to render the progress bar before heavy calculation
552
+ setTimeout(() => {
553
+ const fragment = document.createDocumentFragment();
554
+
555
+ for (let r = 0; r <= 255; r += step) {
556
+ for (let g = 0; g <= 255; g += step) {
557
+ for (let b = 0; b <= 255; b += step) {
558
+ if (count >= TOTAL_COLORS) break;
559
+
560
+ const name = getClosestColorName(r, g, b);
561
+ const rgbString = `${r}-${g}-${b}`;
562
+ const dataUrl = generateColorImage(r, g, b);
563
+ const hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
564
+
565
+ const colorObj = {
566
+ r, g, b, name, dataUrl, hex, rgbString
567
+ };
568
+ generatedColors.push(colorObj);
569
+
570
+ // Create Card Element
571
+ const card = document.createElement('div');
572
+ card.className = 'card';
573
+ card.innerHTML = `
574
+ <div class="card-preview" style="background-color: rgb(${r},${g},${b})">
575
+ <!-- Using img tag for actual download source, but styling via bg for speed -->
576
+ </div>
577
+ <div class="card-info">
578
+ <div class="color-name">${name}</div>
579
+ <div class="color-values">
580
+ <span>RGB(${r},${g},${b})</span>
581
+ <span>${hex}</span>
582
+ </div>
583
+ <div class="card-actions">
584
+ <button class="btn-download-sm" onclick="triggerDownload(${count})">
585
+ <i class="fa-solid fa-download"></i> Download
586
+ </button>
587
+ </div>
588
+ </div>
589
+ `;
590
+ fragment.appendChild(card);
591
+
592
+ count++;
593
+ }
594
+ if (count >= TOTAL_COLORS) break;
595
+ }
596
+ if (count >= TOTAL_COLORS) break;
597
+ }
598
+
599
+ // Append all cards at once
600
+ gallery.appendChild(fragment);
601
+
602
+ // Update UI State
603
+ progressBar.style.width = '100%';
604
+ btn.disabled = false;
605
+ btn.innerHTML = '<i class="fa-solid fa-rotate-right"></i> Regenerate';
606
+ document.getElementById('downloadAllBtn').disabled = false;
607
+
608
+ setTimeout(() => {
609
+ progressContainer.style.display = 'none';
610
+ }, 500);
611
+
612
+ showToast(`Generated ${count} colors successfully!`);
613
+
614
+ }, 100);
615
+ }
616
+
617
+ // Exposed global function for the inline onclick handler
618
+ window.triggerDownload = function(index) {
619
+ const color = generatedColors[index];
620
+ if (!color) return;
621
+
622
+ const filename = `${color.rgbString}_${color.name}.png`;
623
+ downloadImage(color.dataUrl, filename);
624
+ };
625
+
626
+ function clearGrid() {
627
+ const gallery = document.getElementById('gallery');
628
+ gallery.innerHTML = `
629
+ <div class="empty-state" id="emptyState">
630
+ <i class="fa-solid fa-image"></i>
631
+ <h3>No colors generated yet</h3>
632
+ <p>Click "Generate Colors" to create the spectrum.</p>
633
+ </div>
634
+ `;
635
+ generatedColors = [];
636
+ document.getElementById('downloadAllBtn').disabled = true;
637
+ }
638
+
639
+ function downloadAll() {
640
+ if (generatedColors.length === 0) return;
641
+
642
+ const btn = document.getElementById('downloadAllBtn');
643
+ btn.disabled = true;
644
+ btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Downloading...';
645
+
646
+ let i = 0;
647
+ const total = generatedColors.length;
648
+
649
+ function processNext() {
650
+ if (i >= total) {
651
+ btn.disabled = false;
652
+ btn.innerHTML = '<i class="fa-solid fa-download"></i> Download All (ZIP-like)';
653
+ showToast("Batch download complete!");
654
+ return;
655
+ }
656
+
657
+ const color = generatedColors[i];
658
+ const filename = `${color.rgbString}_${color.name}.png`;
659
+ downloadImage(color.dataUrl, filename);
660
+
661
+ i++;
662
+ // Update progress bar for download
663
+ const pct = Math.round((i / total) * 100);
664
+ const progressBar = document.getElementById('progressBar');
665
+ const progressContainer = document.getElementById('progressContainer');
666
+ progressContainer.style.display = 'block';
667
+ progressBar.style.width = `${pct}%`;
668
+
669
+ // Delay to prevent browser blocking (download flood protection)
670
+ setTimeout(processNext, 200);
671
+ }
672
+
673
+ processNext();
674
+ }
675
+ </script>
676
+ </body>
677
+ </html>