Spaces:
Running
Running
| <html lang="fr" data-theme="light"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>GENERATOR 3000</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> | |
| <style> | |
| *, *::before, *::after { margin:0; padding:0; box-sizing:border-box; } | |
| /* ββ THEMES ββ */ | |
| [data-theme="dark"] { | |
| --bg:#0d0d0d; --bg2:#000; --fg:#fff; --fg2:#aaa; | |
| --dim:#777; --muted:#555; --border:#fff; --border2:#444; --border3:#333; | |
| --btn-primary-bg:#fff; --btn-primary-fg:#000; | |
| --btn-secondary-bg:transparent; --btn-secondary-fg:#fff; --btn-secondary-border:#fff; | |
| --input-bg:#000; --input-color:#ccc; | |
| --scanlines:1; --card-border:#333; | |
| } | |
| [data-theme="light"] { | |
| --bg:#fff; --bg2:#fff; --fg:#000; --fg2:#333; | |
| --dim:#555; --muted:#777; --border:#000; --border2:#000; --border3:#ccc; | |
| --btn-primary-bg:#000; --btn-primary-fg:#fff; | |
| --btn-secondary-bg:transparent; --btn-secondary-fg:#000; --btn-secondary-border:#000; | |
| --input-bg:#fff; --input-color:#000; | |
| --scanlines:0; --card-border:#000; | |
| } | |
| body { | |
| background:var(--bg); | |
| color:var(--fg); | |
| font-family:'Press Start 2P', monospace; | |
| min-height:100vh; | |
| overflow-x:hidden; | |
| transition:background .2s, color .2s; | |
| } | |
| /* ββ SCANLINES (dark only) ββ */ | |
| body::before { | |
| content:''; | |
| position:fixed; inset:0; | |
| background:repeating-linear-gradient( | |
| to bottom, | |
| transparent 0px, transparent 2px, | |
| rgba(0,0,0,0.25) 2px, rgba(0,0,0,0.25) 3px | |
| ); | |
| pointer-events:none; z-index:9998; | |
| opacity:var(--scanlines); | |
| transition:opacity .2s; | |
| } | |
| /* ββ THEME TOGGLE ββ */ | |
| .theme-toggle { | |
| position:fixed; top:14px; right:16px; z-index:500; | |
| background:var(--btn-secondary-bg); | |
| border:1px solid var(--border2); | |
| color:var(--dim); | |
| font-family:'Press Start 2P', monospace; | |
| font-size:7px; letter-spacing:1px; | |
| padding:6px 10px; cursor:pointer; transition:all .15s; | |
| } | |
| .theme-toggle:hover { border-color:var(--border); color:var(--fg); } | |
| /* ββ HERO ββ */ | |
| .hero { | |
| text-align:center; | |
| padding:52px 24px 28px; | |
| } | |
| .hero-title { | |
| font-size:clamp(22px, 5.5vw, 60px); | |
| color:var(--fg); | |
| letter-spacing:4px; | |
| line-height:1.2; | |
| margin-bottom:18px; | |
| } | |
| [data-theme="dark"] .hero-title { text-shadow:3px 3px 0 #444; } | |
| [data-theme="light"] .hero-title { text-shadow:3px 3px 0 #ccc; } | |
| .hero-sub { | |
| font-size:clamp(6px, 1vw, 10px); | |
| letter-spacing:5px; | |
| color:var(--dim); | |
| } | |
| .divider { | |
| max-width:900px; margin:24px auto; | |
| height:2px; background:var(--border); opacity:0.2; | |
| } | |
| /* ββ MAIN AREA ββ */ | |
| .main-area { | |
| max-width:900px; margin:0 auto; padding:0 24px; | |
| } | |
| /* Token */ | |
| .token-row { | |
| display:flex; align-items:center; gap:10px; margin-bottom:14px; | |
| } | |
| .token-label { font-size:7px; letter-spacing:2px; color:var(--muted); white-space:nowrap; } | |
| #hf-token { | |
| flex:1; background:var(--input-bg); border:1px solid var(--border2); | |
| color:var(--input-color); font-family:'Press Start 2P', monospace; | |
| font-size:7px; padding:8px 10px; outline:none; letter-spacing:2px; | |
| transition:border-color .15s; | |
| } | |
| #hf-token:focus { border-color:var(--border); } | |
| #hf-token::placeholder { color:var(--border3); } | |
| /* ββ PROMPT CONTAINER (unified box like light ref) ββ */ | |
| .prompt-container { | |
| border:2px solid var(--border); | |
| background:var(--bg2); | |
| margin-bottom:20px; | |
| transition:border-color .2s, background .2s; | |
| } | |
| .prompt-top { | |
| padding:18px 20px 14px; | |
| position:relative; | |
| min-height:100px; | |
| } | |
| #prompt { | |
| width:100%; background:transparent; border:none; | |
| color:var(--fg); font-family:'Press Start 2P', monospace; | |
| font-size:clamp(9px, 1.4vw, 12px); line-height:2.2; | |
| padding:0; outline:none; resize:none; caret-color:transparent; | |
| } | |
| #prompt::placeholder { color:var(--border3); } | |
| .pixel-cursor { | |
| display:inline-block; | |
| width:10px; height:13px; | |
| background:var(--fg); | |
| margin-left:2px; | |
| vertical-align:middle; | |
| animation:blink .8s step-end infinite; | |
| } | |
| @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} } | |
| .prompt-divider { | |
| height:1px; background:var(--border); opacity:0.3; margin:0 0; | |
| } | |
| .prompt-bottom { | |
| display:flex; align-items:center; justify-content:space-between; | |
| gap:10px; padding:12px 16px; | |
| } | |
| .prompt-bottom-left { display:flex; gap:10px; } | |
| /* ββ PIXEL BUTTONS ββ */ | |
| .pixel-btn { | |
| font-family:'Press Start 2P', monospace; | |
| font-size:clamp(7px, 1vw, 10px); | |
| letter-spacing:2px; text-transform:uppercase; | |
| padding:12px 16px; cursor:pointer; | |
| display:flex; align-items:center; justify-content:center; gap:8px; | |
| transition:all .1s; border:2px solid var(--border); | |
| } | |
| .pixel-btn:active { transform:translate(2px,2px); } | |
| .pixel-btn svg { width:13px; height:13px; flex-shrink:0; } | |
| .pixel-btn.primary { | |
| background:var(--btn-primary-bg); color:var(--btn-primary-fg); | |
| } | |
| .pixel-btn.primary:hover { opacity:.85; } | |
| .pixel-btn.primary:disabled { opacity:.3; cursor:not-allowed; } | |
| .pixel-btn.secondary { | |
| background:var(--btn-secondary-bg); color:var(--btn-secondary-fg); | |
| } | |
| .pixel-btn.secondary:hover { | |
| background:var(--btn-primary-bg); color:var(--btn-primary-fg); | |
| } | |
| /* ββ OUTPUT SECTION ββ */ | |
| .output-header { | |
| display:flex; align-items:center; gap:14px; margin-bottom:12px; | |
| } | |
| .output-title { | |
| font-size:clamp(9px, 1.5vw, 13px); color:var(--fg); letter-spacing:3px; white-space:nowrap; | |
| } | |
| .output-line { flex:1; height:2px; background:var(--border); opacity:0.25; } | |
| /* ββ ACCORDIONS ββ */ | |
| .accordion { border:1px solid var(--border3); margin-bottom:8px; } | |
| .accordion-header { | |
| display:flex; align-items:center; justify-content:space-between; | |
| padding:9px 12px; cursor:pointer; user-select:none; | |
| transition:background .1s; | |
| } | |
| .accordion-header:hover { background:rgba(128,128,128,0.05); } | |
| .accordion-title { font-size:7px; letter-spacing:2px; color:var(--dim); text-transform:uppercase; } | |
| .accordion-val { font-size:6px; letter-spacing:1px; color:var(--muted); } | |
| .accordion-arrow { font-size:7px; color:var(--muted); transition:transform .2s; } | |
| .accordion-arrow.open { transform:rotate(180deg); } | |
| .accordion-body { overflow:hidden; max-height:0; transition:max-height .3s ease; border-top:1px solid transparent; } | |
| .accordion-body.open { max-height:500px; border-top-color:var(--border3); } | |
| .accordion-inner { padding:12px; } | |
| /* Style pills */ | |
| .style-grid { display:flex; flex-wrap:wrap; gap:6px; } | |
| .style-pill { | |
| background:transparent; border:1px solid var(--border3); | |
| color:var(--dim); padding:5px 9px; cursor:pointer; | |
| font-family:'Press Start 2P', monospace; font-size:6px; | |
| letter-spacing:1px; text-transform:uppercase; transition:all .1s; | |
| } | |
| .style-pill:hover { border-color:var(--border); color:var(--fg); } | |
| .style-pill.active { border-color:var(--border); color:var(--fg); background:rgba(128,128,128,0.1); } | |
| /* Params */ | |
| .params-grid { display:flex; gap:14px; flex-wrap:wrap; align-items:flex-start; } | |
| .param-group { display:flex; flex-direction:column; gap:5px; } | |
| .param-label { font-size:6px; letter-spacing:2px; color:var(--muted); text-transform:uppercase; } | |
| .param-group select, .param-group input[type="number"] { | |
| background:var(--input-bg); border:1px solid var(--border3); color:var(--input-color); | |
| font-family:'Press Start 2P', monospace; font-size:7px; padding:5px 8px; outline:none; | |
| } | |
| .param-group select:focus, .param-group input:focus { border-color:var(--border); } | |
| .param-group select option { background:var(--bg); } | |
| .steps-row { display:flex; align-items:center; gap:8px; } | |
| .steps-row input[type="range"] { width:80px; accent-color:var(--fg); } | |
| .steps-val { font-size:7px; color:var(--dim); min-width:14px; } | |
| .seed-row { display:flex; align-items:center; gap:5px; } | |
| .rand-seed-btn { | |
| background:none; border:1px solid var(--border3); color:var(--muted); | |
| cursor:pointer; padding:3px 7px; font-size:11px; transition:.1s; | |
| } | |
| .rand-seed-btn:hover { border-color:var(--border); color:var(--fg); } | |
| .neg-textarea { | |
| background:var(--input-bg); border:1px solid var(--border3); color:var(--input-color); | |
| font-family:'Press Start 2P', monospace; font-size:7px; padding:5px 8px; | |
| outline:none; resize:none; height:46px; letter-spacing:1px; line-height:1.8; | |
| } | |
| .neg-textarea:focus { border-color:var(--border); } | |
| .neg-textarea::placeholder { color:var(--border3); } | |
| /* Error */ | |
| #error-box { | |
| display:none; border:1px solid var(--dim); padding:8px 12px; | |
| margin-bottom:12px; font-size:7px; color:var(--fg2); line-height:2; | |
| background:rgba(128,128,128,0.05); align-items:center; gap:10px; | |
| } | |
| #retry-btn { | |
| background:none; border:1px solid var(--border2); color:var(--fg2); | |
| cursor:pointer; padding:4px 10px; font-family:'Press Start 2P', monospace; | |
| font-size:6px; letter-spacing:2px; flex-shrink:0; white-space:nowrap; | |
| } | |
| #retry-btn:hover { border-color:var(--border); color:var(--fg); } | |
| /* ββ STATUS BAR ββ */ | |
| .status-bar { | |
| font-size:7px; letter-spacing:2px; color:var(--muted); | |
| padding:12px 0 0; | |
| line-height:2; | |
| } | |
| /* ββ GALLERY ββ */ | |
| .gallery-toolbar { | |
| display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; | |
| } | |
| #dl-all { | |
| background:none; border:1px solid var(--border3); color:var(--dim); | |
| font-family:'Press Start 2P', monospace; font-size:7px; letter-spacing:1px; | |
| cursor:pointer; padding:5px 10px; display:none; transition:.1s; | |
| } | |
| #dl-all:hover { border-color:var(--border); color:var(--fg); } | |
| .empty-state { | |
| border:2px solid var(--border); min-height:200px; | |
| display:flex; align-items:center; justify-content:center; | |
| font-size:8px; letter-spacing:3px; color:var(--border3); text-transform:uppercase; | |
| line-height:2.5; text-align:center; padding:24px; | |
| } | |
| .gallery-grid { | |
| display:grid; | |
| grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); | |
| gap:10px; | |
| } | |
| .img-card { | |
| background:var(--bg2); border:2px solid var(--card-border); | |
| position:relative; overflow:hidden; transition:border-color .15s; | |
| } | |
| .img-card:hover { border-color:var(--border); } | |
| .img-card img { width:100%; display:block; cursor:zoom-in; transition:opacity .15s; } | |
| .img-card img:hover { opacity:0.85; } | |
| .img-card-footer { | |
| padding:6px 8px; border-top:1px solid var(--border3); | |
| display:flex; align-items:center; justify-content:space-between; gap:6px; | |
| } | |
| .img-prompt { | |
| font-size:6px; color:var(--muted); overflow:hidden; | |
| text-overflow:ellipsis; white-space:nowrap; flex:1; | |
| } | |
| .img-actions { display:flex; gap:4px; flex-shrink:0; } | |
| .img-btn { | |
| font-size:7px; color:var(--muted); background:none; text-decoration:none; | |
| padding:3px 7px; border:1px solid var(--border3); transition:.1s; | |
| cursor:pointer; font-family:'Press Start 2P', monospace; | |
| } | |
| .img-btn:hover { color:var(--fg); border-color:var(--border); } | |
| /* ββ LOADING ββ */ | |
| #loading { | |
| position:fixed; inset:0; display:none; | |
| align-items:center; justify-content:center; flex-direction:column; gap:20px; | |
| background:rgba(0,0,0,0.96); z-index:200; | |
| } | |
| [data-theme="light"] #loading { background:rgba(255,255,255,0.96); } | |
| .loading-inner { border:2px solid var(--border); padding:28px 48px; text-align:center; background:var(--bg); } | |
| #loading-text { font-size:9px; letter-spacing:4px; color:var(--dim); margin-bottom:16px; } | |
| .dot-row { display:flex; justify-content:center; gap:10px; } | |
| .dot { width:10px; height:10px; background:var(--fg); animation:dotpulse 1.2s ease-in-out infinite; } | |
| .dot:nth-child(2) { animation-delay:.2s; } | |
| .dot:nth-child(3) { animation-delay:.4s; } | |
| @keyframes dotpulse { 0%,80%,100%{opacity:0.2} 40%{opacity:1} } | |
| /* ββ LIGHTBOX ββ */ | |
| #lightbox { | |
| position:fixed; inset:0; display:none; | |
| flex-direction:column; align-items:center; justify-content:center; | |
| z-index:1000; | |
| } | |
| [data-theme="dark"] #lightbox { background:rgba(0,0,0,0.98); } | |
| [data-theme="light"] #lightbox { background:rgba(255,255,255,0.98); } | |
| #lightbox.open { display:flex; } | |
| #lb-img { max-width:90vw; max-height:82vh; object-fit:contain; display:block; border:2px solid var(--border); } | |
| #lb-nav-prev, #lb-nav-next { | |
| position:fixed; top:50%; transform:translateY(-50%); | |
| background:none; border:1px solid var(--border3); color:var(--dim); | |
| font-family:'Press Start 2P', monospace; font-size:12px; | |
| padding:12px 16px; cursor:pointer; transition:.1s; z-index:1001; | |
| } | |
| #lb-nav-prev { left:20px; } | |
| #lb-nav-next { right:20px; } | |
| #lb-nav-prev:hover, #lb-nav-next:hover { border-color:var(--border); color:var(--fg); } | |
| #lb-nav-prev:disabled, #lb-nav-next:disabled { opacity:.15; cursor:default; } | |
| #lb-bar { | |
| position:fixed; bottom:0; left:0; right:0; padding:10px 24px; | |
| background:var(--bg); border-top:1px solid var(--border3); | |
| display:flex; align-items:center; justify-content:space-between; gap:16px; | |
| } | |
| #lb-prompt { font-size:7px; color:var(--muted); flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } | |
| #lb-counter { font-size:7px; color:var(--muted); letter-spacing:2px; flex-shrink:0; } | |
| #lb-close { | |
| background:none; border:1px solid var(--border3); color:var(--dim); | |
| font-family:'Press Start 2P', monospace; font-size:7px; letter-spacing:2px; | |
| padding:5px 12px; cursor:pointer; transition:.1s; flex-shrink:0; | |
| } | |
| #lb-close:hover { border-color:var(--border); color:var(--fg); } | |
| #lb-dl { | |
| background:none; border:1px solid var(--border3); color:var(--dim); | |
| font-family:'Press Start 2P', monospace; font-size:7px; letter-spacing:2px; | |
| padding:5px 12px; text-decoration:none; transition:.1s; flex-shrink:0; | |
| } | |
| #lb-dl:hover { border-color:var(--border); color:var(--fg); } | |
| /* ββ FOOTER ββ */ | |
| .footer-divider { max-width:900px; margin:28px auto 0; padding:0 24px 48px; } | |
| .footer-label { | |
| text-align:center; font-size:7px; letter-spacing:5px; | |
| color:var(--muted); padding:20px 0 28px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <button class="theme-toggle" id="theme-toggle">β THEME</button> | |
| <!-- HERO --> | |
| <div class="hero"> | |
| <div class="hero-title">GENERATOR 3000</div> | |
| <div class="hero-sub">8-BIT IMAGE GENERATOR</div> | |
| </div> | |
| <div class="divider"></div> | |
| <!-- MAIN --> | |
| <div class="main-area"> | |
| <!-- TOKEN --> | |
| <div class="token-row"> | |
| <span class="token-label">HF TOKEN βΊ</span> | |
| <input type="password" id="hf-token" placeholder="hf_..."> | |
| </div> | |
| <!-- PROMPT CONTAINER --> | |
| <div class="prompt-container"> | |
| <div class="prompt-top"> | |
| <textarea id="prompt" placeholder="> ENTER PROMPT... " rows="3"></textarea> | |
| </div> | |
| <div class="prompt-divider"></div> | |
| <div class="prompt-bottom"> | |
| <div class="prompt-bottom-left"> | |
| <button class="pixel-btn secondary" id="btn-auto"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5z"/><path d="M19 15l.75 2.25L22 18l-2.25.75L19 21l-.75-2.25L16 18l2.25-.75z"/></svg> | |
| AUTO | |
| </button> | |
| <button class="pixel-btn secondary" id="btn-random"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="2"/><circle cx="8" cy="8" r="1.5" fill="currentColor"/><circle cx="16" cy="8" r="1.5" fill="currentColor"/><circle cx="8" cy="16" r="1.5" fill="currentColor"/><circle cx="16" cy="16" r="1.5" fill="currentColor"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></svg> | |
| RANDOM | |
| </button> | |
| </div> | |
| <button class="pixel-btn primary" id="gen-btn"> | |
| <svg viewBox="0 0 24 24" fill="currentColor"><polygon points="5,3 19,12 5,21"/></svg> | |
| GENERATE | |
| </button> | |
| </div> | |
| </div> | |
| <!-- ERROR --> | |
| <div id="error-box"> | |
| <span id="error-text"></span> | |
| <button id="retry-btn">RETRY</button> | |
| </div> | |
| <!-- STYLE ACCORDION --> | |
| <div class="accordion"> | |
| <div class="accordion-header" id="style-toggle"> | |
| <span class="accordion-title">β¦ Style Preset</span> | |
| <span class="accordion-val" id="style-val">NONE</span> | |
| <span class="accordion-arrow" id="style-arrow">βΌ</span> | |
| </div> | |
| <div class="accordion-body" id="style-body"> | |
| <div class="accordion-inner"> | |
| <div class="style-grid"> | |
| <button class="style-pill active" data-suffix="" data-label="NONE">None</button> | |
| <button class="style-pill" data-suffix=", photorealistic, 8k ultra HD, sharp focus, hyperdetailed, professional photography" data-label="REALISTIC">Realistic</button> | |
| <button class="style-pill" data-suffix=", anime style, vibrant colors, studio ghibli, detailed illustration, soft lighting" data-label="ANIME">Anime</button> | |
| <button class="style-pill" data-suffix=", oil painting, visible brushstrokes, rich colors, impressionist, museum quality" data-label="PAINTING">Painting</button> | |
| <button class="style-pill" data-suffix=", cinematic lighting, film grain, movie still, anamorphic bokeh, color graded" data-label="CINEMA">Cinema</button> | |
| <button class="style-pill" data-suffix=", pencil sketch, graphite drawing, crosshatching, black and white, fine linework" data-label="SKETCH">Sketch</button> | |
| <button class="style-pill" data-suffix=", retro 80s synthwave, vaporwave neon glow, purple and cyan, grid, retrofuturism" data-label="SYNTHWAVE">Synthwave</button> | |
| <button class="style-pill" data-suffix=", comic book art, halftone dots, bold ink outlines, flat colors, graphic novel" data-label="COMICS">Comics</button> | |
| <button class="style-pill" data-suffix=", watercolor painting, soft washes, paper texture, delicate, translucent layers" data-label="WATERCOLOR">Watercolor</button> | |
| <button class="style-pill" data-suffix=", isometric 3D render, clean geometry, soft shadows, pastel colors, game asset" data-label="ISOMETRIC">Isometric</button> | |
| <button class="style-pill" data-suffix=", dark fantasy concept art, dramatic lighting, intricate details, epic scene" data-label="DARK FANTASY">Dark Fantasy</button> | |
| <button class="style-pill" data-suffix=", minimal flat design, solid shapes, limited palette, bauhaus, geometric" data-label="MINIMAL">Minimal</button> | |
| <button class="style-pill" data-suffix=", pixel art, 16-bit retro video game, sharp pixels, no anti-aliasing, limited color palette, NES SNES style" data-label="PIXEL ART">Pixel Art</button> | |
| <button class="style-pill" data-suffix=", 3D render, octane render, physically based rendering, subsurface scattering, volumetric lighting, hyperrealistic CGI, 8k" data-label="3D RENDER">3D Render</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- PARAMS ACCORDION --> | |
| <div class="accordion"> | |
| <div class="accordion-header" id="params-toggle"> | |
| <span class="accordion-title">β ParamΓ¨tres</span> | |
| <span class="accordion-val" id="params-val">1024Γ1024 Β· 4 STEPS</span> | |
| <span class="accordion-arrow" id="params-arrow">βΌ</span> | |
| </div> | |
| <div class="accordion-body" id="params-body"> | |
| <div class="accordion-inner"> | |
| <div class="params-grid"> | |
| <div class="param-group"> | |
| <span class="param-label">Taille</span> | |
| <select id="dimensions"> | |
| <option value="512x512">512 Γ 512</option> | |
| <option value="768x768">768 Γ 768</option> | |
| <option value="1024x1024" selected>1024 Γ 1024</option> | |
| <option value="512x768">512 Γ 768 Portrait</option> | |
| <option value="768x512">768 Γ 512 Paysage</option> | |
| <option value="1024x576">1024 Γ 576 Wide</option> | |
| </select> | |
| </div> | |
| <div class="param-group"> | |
| <span class="param-label">Steps</span> | |
| <div class="steps-row"> | |
| <input type="range" id="steps" min="1" max="50" value="4"> | |
| <span class="steps-val" id="steps-val">4</span> | |
| </div> | |
| </div> | |
| <div class="param-group"> | |
| <span class="param-label">Seed</span> | |
| <div class="seed-row"> | |
| <input type="number" id="seed" value="-1" min="-1" max="9999999" style="width:90px"> | |
| <button class="rand-seed-btn" id="rand-seed">β³</button> | |
| </div> | |
| </div> | |
| <div class="param-group" style="flex:1;min-width:160px;"> | |
| <span class="param-label">Prompt nΓ©gatif</span> | |
| <textarea class="neg-textarea" id="neg-prompt" placeholder="blurry, low quality..."></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- MODEL SELECTOR --> | |
| <div style="margin-bottom:8px;"> | |
| <div class="accordion"> | |
| <div class="accordion-header" id="model-toggle"> | |
| <span class="accordion-title">β ModΓ¨le</span> | |
| <span class="accordion-val" id="model-val">FLUX.1 [FAST]</span> | |
| <span class="accordion-arrow" id="model-arrow">βΌ</span> | |
| </div> | |
| <div class="accordion-body" id="model-body"> | |
| <div class="accordion-inner"> | |
| <div class="style-grid" id="model-grid"> | |
| <button class="style-pill active" data-model="flux-schnell">FLUX.1 SCHNELL</button> | |
| <button class="style-pill" data-model="sdxl">SDXL BASE</button> | |
| <button class="style-pill" data-model="sd21">DREAMSHAPER β FREE</button> | |
| <button class="style-pill" data-model="sd15">SD 1.5 β FREE</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- STATUS BAR --> | |
| <div class="status-bar" id="status-bar"> | |
| SIZE: 1024Γ1024 Β· STEPS: 4 Β· MODEL: FLUX.1 [FAST] | |
| </div> | |
| </div> | |
| <div class="divider" style="margin-top:24px;"></div> | |
| <!-- FOOTER / GALLERY --> | |
| <div class="footer-divider"> | |
| <div class="footer-label">FREE + FAST + 8BIT</div> | |
| <!-- OUTPUT --> | |
| <div class="output-header"> | |
| <span class="output-title">β¦ OUTPUT</span> | |
| <div class="output-line"></div> | |
| <div style="display:flex;align-items:center;gap:10px;"> | |
| <span id="img-count" style="font-size:7px;color:var(--muted);letter-spacing:2px;"></span> | |
| <button id="dl-all">EXPORT ZIP</button> | |
| </div> | |
| </div> | |
| <div id="empty-state" class="empty-state"> | |
| β NO OUTPUT YET β<br><br>PRESS GENERATE TO START | |
| </div> | |
| <div class="gallery-grid" id="gallery-grid" style="display:none"></div> | |
| </div> | |
| <!-- LOADING --> | |
| <div id="loading"> | |
| <div class="loading-inner"> | |
| <div id="loading-text">GENERATING...</div> | |
| <div class="dot-row"> | |
| <div class="dot"></div><div class="dot"></div><div class="dot"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- LIGHTBOX --> | |
| <div id="lightbox"> | |
| <button id="lb-nav-prev">←</button> | |
| <img id="lb-img" src="" alt=""> | |
| <button id="lb-nav-next">→</button> | |
| <div id="lb-bar"> | |
| <span id="lb-prompt"></span> | |
| <span id="lb-counter"></span> | |
| <a id="lb-dl" download="">β DL</a> | |
| <button id="lb-close">β CLOSE</button> | |
| </div> | |
| </div> | |
| <script> | |
| const state = { | |
| prompt:'', negPrompt:'', stylePreset:'', | |
| dimensions:'1024x1024', steps:4, seed:-1, isLoading:false, | |
| }; | |
| const MODELS = { | |
| 'flux-schnell': { endpoint:'https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-schnell', label:'FLUX.1 [FAST]' }, | |
| 'sdxl': { endpoint:'https://router.huggingface.co/hf-inference/models/stabilityai/stable-diffusion-xl-base-1.0', label:'SDXL [FREE]' }, | |
| 'sd21': { endpoint:'https://router.huggingface.co/hf-inference/models/Lykon/dreamshaper-8', label:'DREAMSHAPER [FREE]' }, | |
| 'sd15': { endpoint:'https://router.huggingface.co/hf-inference/models/stable-diffusion-v1-5/stable-diffusion-v1-5', label:'SD 1.5 [FREE]' }, | |
| }; | |
| let currentModel = 'flux-schnell'; | |
| const gallery = []; | |
| let lbIndex = 0; | |
| const RANDOM_PROMPTS = [ | |
| 'a cyberpunk city at night, rain-soaked streets reflecting neon signs', | |
| 'an ancient library filled with floating glowing books', | |
| 'a lone astronaut standing on an alien purple planet, distant stars', | |
| 'a medieval castle on a cliff above the clouds at golden hour', | |
| 'a futuristic Tokyo street market, holographic advertisements', | |
| 'an underwater city with bioluminescent coral towers', | |
| 'a giant robot walking through a misty forest', | |
| 'a vintage space shuttle launch at dusk, dramatic smoke clouds', | |
| 'a cozy cabin in a snowy pine forest at night, warm light inside', | |
| 'a surreal floating island with a waterfall into the clouds', | |
| ]; | |
| const $ = id => { | |
| const el = document.getElementById(id); | |
| if (!el) throw new Error(`#${id} not found`); | |
| return el; | |
| }; | |
| // Theme toggle | |
| $('theme-toggle').addEventListener('click', () => { | |
| const html = document.documentElement; | |
| html.dataset.theme = html.dataset.theme === 'dark' ? 'light' : 'dark'; | |
| }); | |
| // Status bar | |
| function updateStatus() { | |
| $('status-bar').textContent = | |
| `SIZE: ${state.dimensions.replace('x','Γ')} Β· STEPS: ${state.steps} Β· MODEL: ${MODELS[currentModel].label}`; | |
| } | |
| // Wiring | |
| $('prompt').addEventListener('input', e => { state.prompt = e.target.value; }); | |
| $('neg-prompt').addEventListener('input', e => { state.negPrompt = e.target.value; }); | |
| $('dimensions').addEventListener('change', e => { state.dimensions = e.target.value; updateStatus(); }); | |
| $('btn-random').addEventListener('click', () => { | |
| const p = RANDOM_PROMPTS[Math.floor(Math.random() * RANDOM_PROMPTS.length)]; | |
| $('prompt').value = p; state.prompt = p; | |
| }); | |
| $('btn-auto').addEventListener('click', () => { | |
| const p = state.prompt.toLowerCase(); | |
| let dim = '1024x1024'; | |
| if (/portrait|person|face|woman|man|model|people/.test(p)) dim = '512x768'; | |
| else if (/landscape|panorama|wide|city|sky|ocean|mountain/.test(p)) dim = '768x512'; | |
| $('dimensions').value = dim; state.dimensions = dim; updateStatus(); | |
| }); | |
| document.querySelectorAll('.style-pill').forEach(pill => { | |
| pill.addEventListener('click', () => { | |
| document.querySelectorAll('.style-pill').forEach(p => p.classList.remove('active')); | |
| pill.classList.add('active'); | |
| state.stylePreset = pill.dataset.suffix; | |
| $('style-val').textContent = pill.dataset.label; | |
| }); | |
| }); | |
| $('steps').addEventListener('input', e => { | |
| state.steps = parseInt(e.target.value, 10); | |
| $('steps-val').textContent = state.steps; | |
| $('params-val').textContent = `${state.dimensions.replace('x','Γ')} Β· ${state.steps} STEPS`; | |
| updateStatus(); | |
| }); | |
| $('seed').addEventListener('change', e => { | |
| const v = parseInt(e.target.value, 10); | |
| state.seed = isNaN(v) ? -1 : v; | |
| }); | |
| $('rand-seed').addEventListener('click', () => { | |
| const s = Math.floor(Math.random() * 9999999); | |
| state.seed = s; $('seed').value = s; | |
| }); | |
| document.querySelectorAll('#model-grid .style-pill').forEach(pill => { | |
| pill.addEventListener('click', () => { | |
| document.querySelectorAll('#model-grid .style-pill').forEach(p => p.classList.remove('active')); | |
| pill.classList.add('active'); | |
| currentModel = pill.dataset.model; | |
| $('model-val').textContent = MODELS[currentModel].label; | |
| updateStatus(); | |
| }); | |
| }); | |
| $('model-toggle').addEventListener('click', () => { | |
| $('model-body').classList.toggle('open'); | |
| $('model-arrow').classList.toggle('open'); | |
| }); | |
| $('style-toggle').addEventListener('click', () => { | |
| $('style-body').classList.toggle('open'); | |
| $('style-arrow').classList.toggle('open'); | |
| }); | |
| $('params-toggle').addEventListener('click', () => { | |
| $('params-body').classList.toggle('open'); | |
| $('params-arrow').classList.toggle('open'); | |
| }); | |
| $('gen-btn').addEventListener('click', generateImage); | |
| $('retry-btn').addEventListener('click', () => { hideError(); generateImage(); }); | |
| document.addEventListener('keydown', e => { | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') generateImage(); | |
| if (e.key === 'Escape') closeLightbox(); | |
| if (e.key === 'ArrowLeft') lbNav(-1); | |
| if (e.key === 'ArrowRight') lbNav(1); | |
| }); | |
| $('dl-all').addEventListener('click', downloadAll); | |
| // Helpers | |
| function showLoading() { $('loading').style.display = 'flex'; $('gen-btn').disabled = true; } | |
| function hideLoading() { $('loading').style.display = 'none'; $('gen-btn').disabled = false; } | |
| function showError(msg, showRetry = false) { | |
| $('error-text').textContent = msg; | |
| $('error-box').style.display = 'flex'; | |
| $('retry-btn').style.display = showRetry ? 'inline-block' : 'none'; | |
| } | |
| function hideError() { $('error-box').style.display = 'none'; } | |
| // Generate | |
| async function generateImage() { | |
| if (state.isLoading) return; | |
| const token = $('hf-token').value.trim(); | |
| if (!token) { showError('ENTER HF TOKEN'); return; } | |
| const promptText = state.prompt.trim(); | |
| if (!promptText) { showError('TYPE A PROMPT'); return; } | |
| state.isLoading = true; showLoading(); hideError(); | |
| const [w, h] = state.dimensions.split('x').map(Number); | |
| const seed = state.seed === -1 ? Math.floor(Math.random() * 9999999) : state.seed; | |
| const fullPrompt = promptText + state.stylePreset; | |
| try { | |
| const body = { inputs: fullPrompt, parameters: { | |
| negative_prompt: state.negPrompt || undefined, | |
| width: w, height: h, num_inference_steps: state.steps, seed, | |
| }}; | |
| Object.keys(body.parameters).forEach(k => { if (body.parameters[k] === undefined) delete body.parameters[k]; }); | |
| const res = await fetch(MODELS[currentModel].endpoint, { | |
| method:'POST', | |
| headers:{ 'Content-Type':'application/json', 'Authorization':`Bearer ${$('hf-token').value.trim()}` }, | |
| body:JSON.stringify(body), | |
| }); | |
| if (res.status === 503) { | |
| const json = await res.json().catch(() => ({})); | |
| showError(`MODEL LOADING. RETRY IN ~${json.estimated_time ? Math.ceil(json.estimated_time) : 20}S`, true); return; | |
| } | |
| if (res.status === 429) { showError('RATE LIMIT. WAIT 30S', true); return; } | |
| if (res.status === 402) { showError('402: MODEL REQUIRES PAYMENT. SWITCH TO SD 2.1 OR SD 1.5 (FREE) IN THE MODEL SELECTOR.'); return; } | |
| if (!res.ok) throw new Error(`API ERROR ${res.status}`); | |
| addImageToGallery({ url: URL.createObjectURL(await res.blob()), prompt: fullPrompt, seed }); | |
| } catch (err) { | |
| showError(err.message); | |
| } finally { | |
| state.isLoading = false; hideLoading(); | |
| } | |
| } | |
| // Gallery | |
| function addImageToGallery({ url, prompt, seed }) { | |
| const id = Date.now(); | |
| gallery.unshift({ id, url, prompt, seed }); | |
| $('empty-state').style.display = 'none'; | |
| $('gallery-grid').style.display = 'grid'; | |
| $('dl-all').style.display = 'block'; | |
| $('img-count').textContent = `${gallery.length} IMG`; | |
| const card = document.createElement('div'); card.className = 'img-card'; | |
| const img = document.createElement('img'); img.src = url; img.alt = prompt; img.loading = 'lazy'; | |
| const footer = document.createElement('div'); footer.className = 'img-card-footer'; | |
| const pText = document.createElement('div'); pText.className = 'img-prompt'; pText.textContent = prompt; pText.title = prompt; | |
| const actions = document.createElement('div'); actions.className = 'img-actions'; | |
| const expand = document.createElement('button'); expand.className = 'img-btn'; expand.textContent = 'β€’'; | |
| const dl = document.createElement('a'); dl.className = 'img-btn'; dl.href = url; dl.download = `gen3000-${id}.jpg`; dl.textContent = 'β'; | |
| dl.addEventListener('click', e => e.stopPropagation()); | |
| actions.appendChild(expand); actions.appendChild(dl); | |
| footer.appendChild(pText); footer.appendChild(actions); | |
| card.appendChild(img); card.appendChild(footer); | |
| $('gallery-grid').prepend(card); | |
| updateCardIndices(); | |
| } | |
| function updateCardIndices() { | |
| $('gallery-grid').querySelectorAll('.img-card').forEach((card, i) => { | |
| const btn = card.querySelector('.img-btn'); if (btn) btn.onclick = () => openLightbox(i); | |
| const img = card.querySelector('img'); if (img) img.onclick = () => openLightbox(i); | |
| }); | |
| } | |
| // Lightbox | |
| function openLightbox(i) { lbIndex = i; renderLightbox(); $('lightbox').classList.add('open'); document.body.style.overflow = 'hidden'; } | |
| function closeLightbox() { $('lightbox').classList.remove('open'); document.body.style.overflow = ''; } | |
| function lbNav(dir) { const n = lbIndex + dir; if (n < 0 || n >= gallery.length) return; lbIndex = n; renderLightbox(); } | |
| function renderLightbox() { | |
| const item = gallery[lbIndex]; if (!item) return; | |
| $('lb-img').src = item.url; $('lb-img').alt = item.prompt; | |
| $('lb-prompt').textContent = item.prompt; | |
| $('lb-counter').textContent = `${lbIndex + 1} / ${gallery.length}`; | |
| $('lb-dl').href = item.url; $('lb-dl').download = `gen3000-${item.id}.jpg`; | |
| $('lb-nav-prev').disabled = lbIndex === 0; | |
| $('lb-nav-next').disabled = lbIndex === gallery.length - 1; | |
| } | |
| $('lb-close').addEventListener('click', closeLightbox); | |
| $('lb-nav-prev').addEventListener('click', () => lbNav(-1)); | |
| $('lb-nav-next').addEventListener('click', () => lbNav(1)); | |
| $('lightbox').addEventListener('click', e => { if (e.target === $('lightbox')) closeLightbox(); }); | |
| // ZIP | |
| async function downloadAll() { | |
| if (!gallery.length || typeof JSZip === 'undefined') return; | |
| $('dl-all').textContent = 'ZIP...'; $('dl-all').disabled = true; | |
| try { | |
| const zip = new JSZip(); | |
| for (const item of gallery) { zip.file(`gen3000-${item.id}.jpg`, await (await fetch(item.url)).blob()); } | |
| const zipUrl = URL.createObjectURL(await zip.generateAsync({ type:'blob' })); | |
| const a = document.createElement('a'); a.href = zipUrl; a.download = 'generator-3000-gallery.zip'; | |
| document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(zipUrl); | |
| } finally { $('dl-all').textContent = 'EXPORT ZIP'; $('dl-all').disabled = false; } | |
| } | |
| </script> | |
| </body> | |
| </html> | |