Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>RGB Spectrum Generator 500</title> | |
| <!-- Importing FontAwesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --bg-color: #0f172a; | |
| --card-bg: #1e293b; | |
| --text-primary: #f8fafc; | |
| --text-secondary: #94a3b8; | |
| --accent: #38bdf8; | |
| --accent-hover: #0ea5e9; | |
| --border: #334155; | |
| --success: #22c55e; | |
| --font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: var(--font-family); | |
| background-color: var(--bg-color); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* Header Styling */ | |
| header { | |
| background: rgba(15, 23, 42, 0.95); | |
| backdrop-filter: blur(10px); | |
| border-bottom: 1px solid var(--border); | |
| padding: 1rem 2rem; | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| font-size: 1.25rem; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| } | |
| .brand i { | |
| color: var(--accent); | |
| } | |
| .anycoder-link { | |
| font-size: 0.875rem; | |
| color: var(--text-secondary); | |
| text-decoration: none; | |
| transition: color 0.3s ease; | |
| border: 1px solid var(--border); | |
| padding: 0.4rem 0.8rem; | |
| border-radius: 6px; | |
| background: rgba(255,255,255,0.05); | |
| } | |
| .anycoder-link:hover { | |
| color: var(--accent); | |
| border-color: var(--accent); | |
| background: rgba(56, 189, 248, 0.1); | |
| } | |
| /* Main Content */ | |
| main { | |
| flex: 1; | |
| padding: 2rem; | |
| max-width: 1600px; | |
| margin: 0 auto; | |
| width: 100%; | |
| } | |
| /* Controls Section */ | |
| .controls { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| align-items: center; | |
| justify-content: space-between; | |
| background: var(--card-bg); | |
| padding: 1.5rem; | |
| border-radius: 12px; | |
| border: 1px solid var(--border); | |
| } | |
| .control-group { | |
| display: flex; | |
| gap: 1rem; | |
| align-items: center; | |
| } | |
| h1 { | |
| font-size: 1.5rem; | |
| margin-bottom: 0.5rem; | |
| background: linear-gradient(to right, var(--text-primary), var(--accent)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| p.subtitle { | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| } | |
| button { | |
| background-color: var(--accent); | |
| color: #0f172a; | |
| border: none; | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 0.95rem; | |
| } | |
| button:hover { | |
| background-color: var(--accent-hover); | |
| transform: translateY(-1px); | |
| } | |
| button.secondary { | |
| background-color: transparent; | |
| color: var(--text-primary); | |
| border: 1px solid var(--border); | |
| } | |
| button.secondary:hover { | |
| background-color: rgba(255,255,255,0.05); | |
| border-color: var(--text-secondary); | |
| } | |
| button:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* Progress Bar */ | |
| .progress-container { | |
| width: 100%; | |
| height: 6px; | |
| background-color: var(--border); | |
| border-radius: 3px; | |
| margin-top: 1rem; | |
| overflow: hidden; | |
| display: none; /* Hidden by default */ | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| background-color: var(--success); | |
| width: 0%; | |
| transition: width 0.1s linear; | |
| } | |
| /* Grid Layout */ | |
| .grid-container { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| /* Color Card */ | |
| .card { | |
| background-color: var(--card-bg); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| overflow: hidden; | |
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| animation: fadeIn 0.4s ease-out forwards; | |
| opacity: 0; | |
| } | |
| @keyframes fadeIn { | |
| to { opacity: 1; } | |
| } | |
| .card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3); | |
| border-color: var(--accent); | |
| } | |
| .card-preview { | |
| width: 100%; | |
| aspect-ratio: 1 / 1; | |
| position: relative; | |
| background-color: #000; /* Fallback */ | |
| } | |
| .card-preview img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| display: block; | |
| } | |
| .card-info { | |
| padding: 1rem; | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.25rem; | |
| } | |
| .color-name { | |
| font-weight: 700; | |
| font-size: 1rem; | |
| color: var(--text-primary); | |
| margin-bottom: 0.25rem; | |
| } | |
| .color-values { | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| .card-actions { | |
| margin-top: auto; | |
| padding-top: 0.75rem; | |
| } | |
| .btn-download-sm { | |
| width: 100%; | |
| padding: 0.5rem; | |
| font-size: 0.85rem; | |
| justify-content: center; | |
| background: rgba(255,255,255,0.05); | |
| color: var(--text-primary); | |
| border: 1px solid var(--border); | |
| } | |
| .btn-download-sm:hover { | |
| background: var(--accent); | |
| color: #0f172a; | |
| border-color: var(--accent); | |
| } | |
| /* Empty State */ | |
| .empty-state { | |
| grid-column: 1 / -1; | |
| text-align: center; | |
| padding: 4rem 2rem; | |
| color: var(--text-secondary); | |
| border: 2px dashed var(--border); | |
| border-radius: 12px; | |
| } | |
| .empty-state i { | |
| font-size: 3rem; | |
| margin-bottom: 1rem; | |
| opacity: 0.5; | |
| } | |
| /* Toast Notification */ | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background: var(--card-bg); | |
| border: 1px solid var(--accent); | |
| color: var(--text-primary); | |
| padding: 1rem 1.5rem; | |
| border-radius: 8px; | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.5); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| transform: translateY(100px); | |
| opacity: 0; | |
| transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55); | |
| z-index: 1000; | |
| } | |
| .toast.show { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| .toast i { | |
| color: var(--success); | |
| } | |
| /* Footer */ | |
| footer { | |
| text-align: center; | |
| padding: 2rem; | |
| color: var(--text-secondary); | |
| font-size: 0.875rem; | |
| border-top: 1px solid var(--border); | |
| margin-top: auto; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| header { | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| align-items: stretch; | |
| } | |
| .control-group { | |
| flex-direction: column; | |
| } | |
| .grid-container { | |
| grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-palette"></i> | |
| <span>SpectrumGen</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| Built with anycoder <i class="fa-solid fa-arrow-up-right-from-square" style="font-size: 0.7em;"></i> | |
| </a> | |
| </header> | |
| <main> | |
| <section class="controls"> | |
| <div class="info"> | |
| <h1>RGB Color Generator</h1> | |
| <p class="subtitle">Generate 500 unique 256x256 images covering the full spectrum.</p> | |
| </div> | |
| <div class="control-group"> | |
| <button id="generateBtn" onclick="startGeneration()"> | |
| <i class="fa-solid fa-wand-magic-sparkles"></i> Generate Colors | |
| </button> | |
| <button id="downloadAllBtn" class="secondary" onclick="downloadAll()" disabled> | |
| <i class="fa-solid fa-download"></i> Download All (ZIP-like) | |
| </button> | |
| <button id="clearBtn" class="secondary" onclick="clearGrid()"> | |
| <i class="fa-solid fa-trash"></i> Clear | |
| </button> | |
| </div> | |
| <div class="progress-container" id="progressContainer"> | |
| <div class="progress-bar" id="progressBar"></div> | |
| </div> | |
| </section> | |
| <section id="gallery" class="grid-container"> | |
| <div class="empty-state" id="emptyState"> | |
| <i class="fa-solid fa-image"></i> | |
| <h3>No colors generated yet</h3> | |
| <p>Click "Generate Colors" to create the spectrum.</p> | |
| </div> | |
| </section> | |
| </main> | |
| <footer> | |
| <p>© 2023 RGB Spectrum Generator. Pure HTML/CSS/JS.</p> | |
| </footer> | |
| <!-- Toast Notification Element --> | |
| <div id="toast" class="toast"> | |
| <i class="fa-solid fa-circle-check"></i> | |
| <span id="toastMsg">Operation Successful</span> | |
| </div> | |
| <script> | |
| // --- Configuration & State --- | |
| const TOTAL_COLORS = 500; | |
| const IMAGE_SIZE = 256; | |
| let generatedColors = []; // Stores objects {r, g, b, name, dataUrl} | |
| // --- Color Name Database (Condensed Standard Web Colors) --- | |
| const colorNames = [ | |
| {r:0,g:0,b:0,name:"Black"}, {r:255,g:255,b:255,name:"White"}, | |
| {r:128,g:128,b:128,name:"Gray"}, {r:192,g:192,b:192,name:"Silver"}, | |
| {r:255,g:0,b:0,name:"Red"}, {r:128,g:0,b:0,name:"Maroon"}, | |
| {r:0,g:255,b:0,name:"Lime"}, {r:0,g:128,b:0,name:"Green"}, | |
| {r:0,g:0,b:255,name:"Blue"}, {r:0,g:0,b:128,name:"Navy"}, | |
| {r:255,g:255,b:0,name:"Yellow"}, {r:128,g:128,b:0,name:"Olive"}, | |
| {r:0,g:255,b:255,name:"Aqua"}, {r:0,g:128,b:128,name:"Teal"}, | |
| {r:255,g:0,b:255,name:"Fuchsia"}, {r:128,g:0,b:128,name:"Purple"}, | |
| {r:255,g:165,b:0,name:"Orange"}, {r:255,g:192,b:203,name:"Pink"}, | |
| {r:165,g:42,b:42,name:"Brown"}, {r:244,g:164,b:96,name:"SandyBrown"}, | |
| {r:220,g:20,b:60,name:"Crimson"}, {r:75,g:0,b:130,name:"Indigo"}, | |
| {r:238,g:130,b:238,name:"Violet"}, {r:173,g:255,b:47,name:"GreenYellow"}, | |
| {r:255,g:215,b:0,name:"Gold"}, {r:250,g:128,b:114,name:"Salmon"}, | |
| {r:128,g:0,b:0,name:"DarkRed"}, {r:0,g:100,b:0,name:"DarkGreen"}, | |
| {r:0,g:0,b:139,name:"DarkBlue"}, {r:139,g:0,b:139,name:"DarkMagenta"}, | |
| {r:184,g:134,b:11,name:"DarkGoldenRod"}, {r:169,g:169,b:169,name:"DarkGray"}, | |
| {r:0,g:139,b:139,name:"DarkCyan"}, {r:255,g:140,b:0,name:"DarkOrange"}, | |
| {r:32,g:178,b:170,name:"LightSeaGreen"}, {r:135,g:206,b:250,name:"LightSkyBlue"}, | |
| {r:119,g:136,b:153,name:"LightSlateGray"}, {r:176,g:196,b:222,name:"LightSteelBlue"}, | |
| {r:255,g:228,b:225,name:"MistyRose"}, {r:255,g:240,b:245,name:"LavenderBlush"}, | |
| {r:255,g:250,b:250,name:"Snow"}, {r:240,g:255,b:240,name:"Honeydew"}, | |
| {r:245,g:255,b:250,name:"MintCream"}, {r:240,g:248,b:255,name:"AliceBlue"}, | |
| {r:255,g:235,b:205,name:"BlanchedAlmond"}, {r:255,g:228,b:196,name:"Bisque"}, | |
| {r:255,g:255,b:224,name:"LightYellow"}, {r:250,g:250,b:210,name:"LightGoldenRodYellow"}, | |
| {r:255,g:255,b:0,name:"Yellow"}, {r:154,g:205,b:50,name:"YellowGreen"}, | |
| {r:107,g:142,b:35,name:"OliveDrab"}, {r:85,g:107,b:47,name:"DarkOliveGreen"}, | |
| {r:143,g:188,b:143,name:"DarkSeaGreen"}, {r:46,g:139,b:87,name:"SeaGreen"}, | |
| {r:60,g:179,b:113,name:"MediumSeaGreen"}, {r:3,g:168,b:158,name:"MediumTurquoise"}, | |
| {r:64,g:224,b:208,name:"Turquoise"}, {r:72,g:209,b:204,name:"MediumTurquoise"}, | |
| {r:175,g:238,b:238,name:"PaleTurquoise"}, {r:224,g:255,b:255,name:"LightCyan"}, | |
| {r:0,g:255,b:255,name:"Cyan"}, {r:0,g:206,b:209,name:"DarkTurquoise"}, | |
| {r:70,g:130,b:180,name:"SteelBlue"}, {r:100,g:149,b:237,name:"CornflowerBlue"}, | |
| {r:30,g:144,b:255,name:"DodgerBlue"}, {r:0,g:0,b:205,name:"MediumBlue"}, | |
| {r:65,g:105,b:225,name:"RoyalBlue"}, {r:0,g:191,b:255,name:"DeepSkyBlue"}, | |
| {r:135,g:206,b:235,name:"SkyBlue"}, {r:176,g:224,b:230,name:"PowderBlue"}, | |
| {r:173,g:216,b:230,name:"LightBlue"}, {r:230,g:230,b:250,name:"Lavender"}, | |
| {r:216,g:191,b:216,name:"Thistle"}, {r:221,g:160,b:221,name:"Plum"}, | |
| {r:238,g:130,b:238,name:"Violet"}, {r:255,g:0,b:255,name:"Magenta"}, | |
| {r:199,g:21,b:133,name:"MediumVioletRed"}, {r:255,g:20,b:147,name:"DeepPink"}, | |
| {r:255,g:105,b:180,name:"HotPink"}, {r:255,g:182,b:193,name:"LightPink"}, | |
| {r:255,g:192,b:203,name:"Pink"}, {r:250,g:128,b:114,name:"Salmon"}, | |
| {r:233,g:150,b:122,name:"DarkSalmon"}, {r:255,g:160,b:122,name:"LightSalmon"}, | |
| {r:255,g:127,b:80,name:"Coral"}, {r:240,g:128,b:128,name:"LightCoral"}, | |
| {r:255,g:99,b:71,name:"Tomato"}, {r:255,g:69,b:0,name:"OrangeRed"}, | |
| {r:255,g:215,b:0,name:"Gold"}, {r:218,g:165,b:32,name:"GoldenRod"}, | |
| {r:248,g:248,b:255,name:"GhostWhite"}, {r:245,g:245,b:245,name:"WhiteSmoke"}, | |
| {r:47,g:79,b:79,name:"DarkSlateGray"}, {r:112,g:128,b:144,name:"SlateGray"}, | |
| {r:25,g:25,b:112,name:"MidnightBlue"} | |
| ]; | |
| // --- Helper Functions --- | |
| // Calculate Euclidean distance between two colors | |
| function colorDistance(r1, g1, b1, r2, g2, b2) { | |
| return Math.sqrt( | |
| Math.pow(r1 - r2, 2) + | |
| Math.pow(g1 - g2, 2) + | |
| Math.pow(b1 - b2, 2) | |
| ); | |
| } | |
| // Find the closest named color | |
| function getClosestColorName(r, g, b) { | |
| // Check for grayscale first (simple heuristic) | |
| if (Math.abs(r - g) < 5 && Math.abs(g - b) < 5 && Math.abs(r - b) < 5) { | |
| if (r < 10) return "Black"; | |
| if (r > 245) return "White"; | |
| if (r < 50) return "Very Dark Gray"; | |
| if (r > 200) return "Very Light Gray"; | |
| return "Gray"; | |
| } | |
| let minDistance = Infinity; | |
| let closestName = "Unknown Color"; | |
| for (const color of colorNames) { | |
| const dist = colorDistance(r, g, b, color.r, color.g, color.b); | |
| if (dist < minDistance) { | |
| minDistance = dist; | |
| closestName = color.name; | |
| } | |
| } | |
| return closestName.replace(/\s+/g, ''); // Remove spaces for filename | |
| } | |
| // Create a 256x256 canvas and return Data URL | |
| function generateColorImage(r, g, b) { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = IMAGE_SIZE; | |
| canvas.height = IMAGE_SIZE; | |
| const ctx = canvas.getContext('2d'); | |
| // Fill background | |
| ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; | |
| ctx.fillRect(0, 0, IMAGE_SIZE, IMAGE_SIZE); | |
| // Optional: Add a subtle border to ensure visibility if white on white background | |
| // though the request asked for individual colors, usually raw color is preferred. | |
| // We will stick to pure color as requested. | |
| return canvas.toDataURL('image/png'); | |
| } | |
| function showToast(message) { | |
| const toast = document.getElementById('toast'); | |
| const msg = document.getElementById('toastMsg'); | |
| msg.textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| function downloadImage(dataUrl, filename) { | |
| const link = document.createElement('a'); | |
| link.href = dataUrl; | |
| link.download = filename; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| // --- Main Logic --- | |
| function startGeneration() { | |
| const gallery = document.getElementById('gallery'); | |
| const emptyState = document.getElementById('emptyState'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| const btn = document.getElementById('generateBtn'); | |
| // Reset UI | |
| if (emptyState) emptyState.style.display = 'none'; | |
| gallery.innerHTML = ''; | |
| generatedColors = []; | |
| btn.disabled = true; | |
| btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Generating...'; | |
| progressContainer.style.display = 'block'; | |
| progressBar.style.width = '0%'; | |
| // We'll use a stepped approach to ensure full spectrum coverage (0 to 255) | |
| // 500 is approx 8^3 (512). We will step by 32, which gives 8 steps. | |
| // 8 * 8 * 8 = 512 colors. We will take the first 500. | |
| const step = 32; | |
| let count = 0; | |
| // Using a small timeout to allow UI to render the progress bar before heavy calculation | |
| setTimeout(() => { | |
| const fragment = document.createDocumentFragment(); | |
| for (let r = 0; r <= 255; r += step) { | |
| for (let g = 0; g <= 255; g += step) { | |
| for (let b = 0; b <= 255; b += step) { | |
| if (count >= TOTAL_COLORS) break; | |
| const name = getClosestColorName(r, g, b); | |
| const rgbString = `${r}-${g}-${b}`; | |
| const dataUrl = generateColorImage(r, g, b); | |
| const hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase(); | |
| const colorObj = { | |
| r, g, b, name, dataUrl, hex, rgbString | |
| }; | |
| generatedColors.push(colorObj); | |
| // Create Card Element | |
| const card = document.createElement('div'); | |
| card.className = 'card'; | |
| card.innerHTML = ` | |
| <div class="card-preview" style="background-color: rgb(${r},${g},${b})"> | |
| <!-- Using img tag for actual download source, but styling via bg for speed --> | |
| </div> | |
| <div class="card-info"> | |
| <div class="color-name">${name}</div> | |
| <div class="color-values"> | |
| <span>RGB(${r},${g},${b})</span> | |
| <span>${hex}</span> | |
| </div> | |
| <div class="card-actions"> | |
| <button class="btn-download-sm" onclick="triggerDownload(${count})"> | |
| <i class="fa-solid fa-download"></i> Download | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| fragment.appendChild(card); | |
| count++; | |
| } | |
| if (count >= TOTAL_COLORS) break; | |
| } | |
| if (count >= TOTAL_COLORS) break; | |
| } | |
| // Append all cards at once | |
| gallery.appendChild(fragment); | |
| // Update UI State | |
| progressBar.style.width = '100%'; | |
| btn.disabled = false; | |
| btn.innerHTML = '<i class="fa-solid fa-rotate-right"></i> Regenerate'; | |
| document.getElementById('downloadAllBtn').disabled = false; | |
| setTimeout(() => { | |
| progressContainer.style.display = 'none'; | |
| }, 500); | |
| showToast(`Generated ${count} colors successfully!`); | |
| }, 100); | |
| } | |
| // Exposed global function for the inline onclick handler | |
| window.triggerDownload = function(index) { | |
| const color = generatedColors[index]; | |
| if (!color) return; | |
| const filename = `${color.rgbString}_${color.name}.png`; | |
| downloadImage(color.dataUrl, filename); | |
| }; | |
| function clearGrid() { | |
| const gallery = document.getElementById('gallery'); | |
| gallery.innerHTML = ` | |
| <div class="empty-state" id="emptyState"> | |
| <i class="fa-solid fa-image"></i> | |
| <h3>No colors generated yet</h3> | |
| <p>Click "Generate Colors" to create the spectrum.</p> | |
| </div> | |
| `; | |
| generatedColors = []; | |
| document.getElementById('downloadAllBtn').disabled = true; | |
| } | |
| function downloadAll() { | |
| if (generatedColors.length === 0) return; | |
| const btn = document.getElementById('downloadAllBtn'); | |
| btn.disabled = true; | |
| btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Downloading...'; | |
| let i = 0; | |
| const total = generatedColors.length; | |
| function processNext() { | |
| if (i >= total) { | |
| btn.disabled = false; | |
| btn.innerHTML = '<i class="fa-solid fa-download"></i> Download All (ZIP-like)'; | |
| showToast("Batch download complete!"); | |
| return; | |
| } | |
| const color = generatedColors[i]; | |
| const filename = `${color.rgbString}_${color.name}.png`; | |
| downloadImage(color.dataUrl, filename); | |
| i++; | |
| // Update progress bar for download | |
| const pct = Math.round((i / total) * 100); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| progressContainer.style.display = 'block'; | |
| progressBar.style.width = `${pct}%`; | |
| // Delay to prevent browser blocking (download flood protection) | |
| setTimeout(processNext, 200); | |
| } | |
| processNext(); | |
| } | |
| </script> | |
| </body> | |
| </html> |