Spaces:
Running
Running
| <html lang="en" data-theme="dark"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>FLUX.2 Static WebGPU</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,500,0,0&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| color-scheme: dark; | |
| --bg: #101114; | |
| --panel: #17191f; | |
| --panel-2: #1f232b; | |
| --panel-3: #262b35; | |
| --stage: #0b0c0f; | |
| --text: #f2f4f8; | |
| --muted: #aab2c0; | |
| --line: #333a48; | |
| --line-strong: #465063; | |
| --accent: #18b7a7; | |
| --accent-strong: #42d8c8; | |
| --accent-soft: rgba(24, 183, 167, 0.18); | |
| --danger: #ff766d; | |
| --shadow: 0 18px 50px rgba(0, 0, 0, 0.34); | |
| --radius: 8px; | |
| } | |
| :root[data-theme="light"] { | |
| color-scheme: light; | |
| --bg: #eef1f5; | |
| --panel: #ffffff; | |
| --panel-2: #f7f9fc; | |
| --panel-3: #edf1f6; | |
| --stage: #f8fafc; | |
| --text: #172033; | |
| --muted: #566274; | |
| --line: #d8dee8; | |
| --line-strong: #c4cedc; | |
| --accent: #087f7a; | |
| --accent-strong: #066c67; | |
| --accent-soft: rgba(8, 127, 122, 0.13); | |
| --danger: #b42318; | |
| --shadow: 0 18px 40px rgba(27, 38, 55, 0.12); | |
| } | |
| * { box-sizing: border-box; } | |
| [hidden] { display: none ; } | |
| body { | |
| margin: 0; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| font-size: 14px; | |
| letter-spacing: 0; | |
| } | |
| main { | |
| display: grid; | |
| grid-template-columns: minmax(340px, 456px) minmax(0, 1fr); | |
| min-height: 100vh; | |
| } | |
| aside { | |
| position: relative; | |
| border-right: 1px solid var(--line); | |
| background: var(--panel); | |
| padding: 18px; | |
| box-shadow: var(--shadow); | |
| z-index: 1; | |
| } | |
| section { | |
| padding: 18px; | |
| min-width: 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 18px; | |
| background: | |
| linear-gradient(90deg, color-mix(in srgb, var(--line) 28%, transparent) 1px, transparent 1px), | |
| linear-gradient(0deg, color-mix(in srgb, var(--line) 28%, transparent) 1px, transparent 1px), | |
| var(--bg); | |
| background-size: 28px 28px; | |
| } | |
| .app-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| margin-bottom: 16px; | |
| } | |
| .header-actions { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .settings-popover { | |
| position: absolute; | |
| top: 68px; | |
| left: 18px; | |
| right: 18px; | |
| z-index: 20; | |
| padding: 12px; | |
| border: 1px solid var(--line-strong); | |
| border-radius: var(--radius); | |
| background: var(--panel); | |
| box-shadow: var(--shadow); | |
| } | |
| .settings-popover[hidden] { | |
| display: none; | |
| } | |
| .settings-popover::before { | |
| content: ""; | |
| position: absolute; | |
| top: -7px; | |
| right: 58px; | |
| width: 12px; | |
| height: 12px; | |
| border-left: 1px solid var(--line-strong); | |
| border-top: 1px solid var(--line-strong); | |
| background: var(--panel); | |
| transform: rotate(45deg); | |
| } | |
| .settings-popover h2 { | |
| margin: 0 0 8px; | |
| font-size: 13px; | |
| line-height: 1.2; | |
| font-weight: 800; | |
| } | |
| h1 { | |
| margin: 0; | |
| font-size: 19px; | |
| line-height: 1.2; | |
| letter-spacing: 0; | |
| font-weight: 800; | |
| } | |
| label { | |
| display: block; | |
| margin: 10px 0 6px; | |
| color: var(--muted); | |
| font-weight: 650; | |
| font-size: 12px; | |
| } | |
| textarea, | |
| input, | |
| select { | |
| width: 100%; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| color: var(--text); | |
| font: inherit; | |
| outline: none; | |
| transition: border-color 140ms ease, box-shadow 140ms ease, background-color 140ms ease; | |
| } | |
| textarea:hover, | |
| input:hover, | |
| select:hover { | |
| border-color: var(--line-strong); | |
| } | |
| textarea:focus, | |
| input:focus, | |
| select:focus { | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-soft); | |
| } | |
| textarea { | |
| min-height: 124px; | |
| resize: vertical; | |
| padding: 12px; | |
| line-height: 1.4; | |
| } | |
| input, | |
| select { | |
| height: 42px; | |
| padding: 0 12px; | |
| } | |
| select { | |
| appearance: none; | |
| background-image: | |
| linear-gradient(45deg, transparent 50%, var(--muted) 50%), | |
| linear-gradient(135deg, var(--muted) 50%, transparent 50%); | |
| background-position: | |
| calc(100% - 17px) 18px, | |
| calc(100% - 12px) 18px; | |
| background-size: 5px 5px, 5px 5px; | |
| background-repeat: no-repeat; | |
| padding-right: 34px; | |
| } | |
| input[type="file"] { | |
| height: auto; | |
| padding: 10px; | |
| } | |
| input[type="range"] { | |
| padding: 0; | |
| accent-color: var(--accent); | |
| } | |
| .range-row { | |
| display: grid; | |
| grid-template-columns: 1fr 96px; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .resolution-row { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr); | |
| align-items: end; | |
| gap: 8px; | |
| } | |
| .icon { | |
| font-family: "Material Symbols Rounded"; | |
| font-weight: 500; | |
| font-style: normal; | |
| font-size: 21px; | |
| line-height: 1; | |
| display: inline-block; | |
| text-transform: none; | |
| letter-spacing: 0; | |
| white-space: nowrap; | |
| font-feature-settings: "liga"; | |
| -webkit-font-feature-settings: "liga"; | |
| -webkit-font-smoothing: antialiased; | |
| } | |
| button.icon-button { | |
| width: 42px; | |
| min-height: 42px; | |
| padding: 0; | |
| display: grid; | |
| place-items: center; | |
| } | |
| .resolution-advanced { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr) 42px minmax(0, 1fr); | |
| gap: 10px; | |
| align-items: end; | |
| } | |
| .aspect-lock { | |
| align-self: end; | |
| } | |
| .aspect-lock[aria-pressed="true"] { | |
| border-color: var(--accent); | |
| color: var(--accent-strong); | |
| background: var(--accent-soft); | |
| } | |
| .aspect-row { | |
| grid-column: 1 / -1; | |
| } | |
| .visually-hidden { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip: rect(0, 0, 0, 0); | |
| white-space: nowrap; | |
| border: 0; | |
| } | |
| .img2img-panel { | |
| margin-top: 2px; | |
| padding: 12px; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: color-mix(in srgb, var(--panel-2) 72%, transparent); | |
| } | |
| .field-heading { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .field-title { | |
| display: block; | |
| font-size: 13px; | |
| font-weight: 800; | |
| color: var(--text); | |
| } | |
| .field-subtitle { | |
| display: block; | |
| margin-top: 2px; | |
| font-size: 12px; | |
| color: var(--muted); | |
| } | |
| .file-picker { | |
| min-height: 42px; | |
| margin: 0; | |
| padding: 0 12px; | |
| border: 1px dashed var(--line-strong); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| color: var(--text); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| cursor: pointer; | |
| transition: border-color 140ms ease, box-shadow 140ms ease, background-color 140ms ease; | |
| } | |
| .file-picker:hover { | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-soft); | |
| } | |
| .source-preview { | |
| display: grid; | |
| grid-template-columns: 64px 1fr; | |
| align-items: center; | |
| gap: 10px; | |
| margin-top: 10px; | |
| padding: 8px; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| .source-preview[hidden] { | |
| display: none; | |
| } | |
| .source-preview img { | |
| width: 64px; | |
| height: 64px; | |
| max-height: none; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| object-fit: cover; | |
| background: var(--panel-3); | |
| } | |
| .img2img-controls { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr); | |
| gap: 10px; | |
| margin-top: 10px; | |
| } | |
| .img2img-controls .range-row { | |
| grid-template-columns: 1fr 88px; | |
| } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| gap: 10px; | |
| } | |
| .wide { grid-column: 1 / -1; } | |
| .actions { | |
| display: flex; | |
| gap: 10px; | |
| margin-top: 18px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| .actions[hidden] { | |
| display: none; | |
| } | |
| .download-gate { | |
| margin-top: 18px; | |
| padding: 14px; | |
| border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line)); | |
| border-radius: var(--radius); | |
| background: color-mix(in srgb, var(--accent-soft) 74%, var(--panel-2)); | |
| } | |
| .download-gate[hidden] { | |
| display: none; | |
| } | |
| .download-gate-title { | |
| font-size: 13px; | |
| font-weight: 850; | |
| color: var(--text); | |
| } | |
| .download-gate button { | |
| margin-top: 12px; | |
| width: 100%; | |
| } | |
| button { | |
| min-height: 42px; | |
| border: 0; | |
| border-radius: var(--radius); | |
| padding: 0 14px; | |
| background: var(--accent); | |
| color: #061715; | |
| font: inherit; | |
| font-weight: 750; | |
| cursor: pointer; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| transition: transform 120ms ease, filter 120ms ease, background-color 140ms ease, color 140ms ease; | |
| } | |
| button:hover:not(:disabled) { | |
| filter: brightness(1.06); | |
| transform: translateY(-1px); | |
| } | |
| button.secondary { | |
| background: var(--panel-3); | |
| color: var(--text); | |
| border: 1px solid var(--line); | |
| } | |
| button:disabled { | |
| cursor: not-allowed; | |
| opacity: 0.55; | |
| transform: none; | |
| } | |
| .elapsed-meter { | |
| min-height: 42px; | |
| padding: 0 12px; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| color: var(--muted); | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: 750; | |
| min-width: 96px; | |
| } | |
| .elapsed-meter.active { | |
| border-color: var(--accent); | |
| color: var(--accent-strong); | |
| background: var(--accent-soft); | |
| } | |
| .load-progress { | |
| margin: 14px 0 0; | |
| padding: 12px; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| } | |
| .load-progress[hidden] { | |
| display: none; | |
| } | |
| .load-progress-head, | |
| .load-progress-meta { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| } | |
| .load-progress-title { | |
| min-width: 0; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| font-size: 12px; | |
| font-weight: 800; | |
| color: var(--text); | |
| } | |
| .load-progress-summary { | |
| flex: 0 0 auto; | |
| color: var(--accent-strong); | |
| font-size: 12px; | |
| font-weight: 800; | |
| } | |
| .load-progress-track { | |
| position: relative; | |
| height: 9px; | |
| margin: 10px 0 8px; | |
| overflow: hidden; | |
| border-radius: 999px; | |
| background: color-mix(in srgb, var(--line) 55%, transparent); | |
| } | |
| .load-progress-fill { | |
| width: 0%; | |
| height: 100%; | |
| border-radius: inherit; | |
| background: var(--accent); | |
| transition: width 160ms ease; | |
| } | |
| .load-progress-track.indeterminate .load-progress-fill { | |
| width: 42%; | |
| animation: load-progress-sweep 1.15s ease-in-out infinite; | |
| } | |
| @keyframes load-progress-sweep { | |
| 0% { transform: translateX(-110%); } | |
| 100% { transform: translateX(245%); } | |
| } | |
| .load-progress-meta { | |
| color: var(--muted); | |
| font: 11px/1.35 ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace; | |
| } | |
| .load-progress-detail { | |
| min-width: 0; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .load-progress-rate { | |
| flex: 0 0 auto; | |
| color: var(--muted); | |
| white-space: nowrap; | |
| } | |
| pre { | |
| margin: 16px 0 0; | |
| padding: 12px; | |
| min-height: 132px; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| color: var(--muted); | |
| white-space: pre-wrap; | |
| overflow-wrap: anywhere; | |
| font: 12px/1.45 ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace; | |
| } | |
| .stage { | |
| min-height: calc(100vh - 36px); | |
| display: grid; | |
| place-items: center; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--stage); | |
| box-shadow: var(--shadow); | |
| overflow: hidden; | |
| } | |
| img, | |
| canvas { | |
| display: block; | |
| max-width: 100%; | |
| max-height: calc(100vh - 64px); | |
| object-fit: contain; | |
| } | |
| .placeholder { | |
| color: var(--muted); | |
| font-size: 13px; | |
| padding: 10px 14px; | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| } | |
| .gallery-panel { | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel); | |
| box-shadow: var(--shadow); | |
| padding: 14px; | |
| } | |
| .gallery-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| margin-bottom: 12px; | |
| } | |
| .gallery-header h2 { | |
| margin: 0; | |
| font-size: 14px; | |
| line-height: 1.2; | |
| font-weight: 800; | |
| } | |
| .gallery-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); | |
| gap: 12px; | |
| } | |
| .gallery-card { | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius); | |
| background: var(--panel-2); | |
| overflow: hidden; | |
| } | |
| .gallery-card img { | |
| width: 100%; | |
| max-height: none; | |
| aspect-ratio: 1 / 1; | |
| object-fit: cover; | |
| border-bottom: 1px solid var(--line); | |
| background: var(--stage); | |
| } | |
| .gallery-body { | |
| padding: 10px; | |
| } | |
| .gallery-meta { | |
| display: grid; | |
| gap: 3px; | |
| margin-bottom: 8px; | |
| color: var(--muted); | |
| font-size: 12px; | |
| line-height: 1.35; | |
| } | |
| .gallery-prompt { | |
| color: var(--text); | |
| display: -webkit-box; | |
| -webkit-line-clamp: 2; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .gallery-actions { | |
| display: grid; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| gap: 6px; | |
| } | |
| .gallery-actions button { | |
| min-height: 32px; | |
| padding: 0 8px; | |
| font-size: 12px; | |
| } | |
| .error { color: var(--danger); } | |
| @media (max-width: 860px) { | |
| main { grid-template-columns: 1fr; } | |
| aside { border-right: 0; border-bottom: 1px solid var(--line); } | |
| .stage { min-height: 55vh; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <aside> | |
| <div class="app-header"> | |
| <h1>FLUX.2 Static WebGPU</h1> | |
| <div class="header-actions"> | |
| <button id="settings-toggle" class="secondary icon-button" type="button" aria-label="Open generation settings" aria-expanded="false" aria-controls="settings-popover"> | |
| <span class="icon">tune</span> | |
| </button> | |
| <button id="theme-toggle" class="secondary icon-button" type="button" aria-label="Switch to light mode"> | |
| <span id="theme-icon" class="icon">light_mode</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="settings-popover" class="settings-popover" hidden> | |
| <h2>Generation settings</h2> | |
| <label for="backend">Transformer</label> | |
| <select id="backend"> | |
| <option value="custom-lowbit-webgpu" selected>Custom low-bit WebGPU (3.5 GB)</option> | |
| <option value="onnx-webgpu">ONNX WebGPU (5.2 GB)</option> | |
| </select> | |
| <label for="speed-profile">Speed Profile</label> | |
| <select id="speed-profile"> | |
| <option value="progressive-fast" selected>Fast default (exact)</option> | |
| <option value="subsecond-final">Subsecond final</option> | |
| <option value="subsecond-ultra">Subsecond ultra preview</option> | |
| <option value="subsecond-instant">Subsecond instant sketch</option> | |
| <option value="progressive">Progressive preview + refine</option> | |
| <option value="auto">Auto coherent fast</option> | |
| <option value="quality">Quality q4</option> | |
| <option value="mlp-dp4a">Experimental MLP DP4A</option> | |
| <option value="all-dp4a">Experimental fastest DP4A</option> | |
| <option value="turbo-cache">Turbo cache 2x approx</option> | |
| <option value="ab-cache">AB cache 2x approx</option> | |
| <option value="tail-cache">Block tail cache approx</option> | |
| <option value="tail-cache-lite">Block tail cache lite approx</option> | |
| <option value="tail-cache-mid">Block tail cache mid approx</option> | |
| <option value="tail-cache-fast">Block tail cache fast approx</option> | |
| <option value="subsecond-cache">Subsecond cache 4x approx</option> | |
| <option value="subsecond-preview">Subsecond preview auto + text trim</option> | |
| </select> | |
| </div> | |
| <label for="prompt">Prompt</label> | |
| <textarea id="prompt">A small ceramic robot on a workbench, product photo lighting</textarea> | |
| <div class="grid"> | |
| <div class="wide"> | |
| <label for="resolution-preset">Resolution</label> | |
| <div class="resolution-row"> | |
| <select id="resolution-preset"> | |
| <option value="256" selected>256 x 256</option> | |
| <option value="512">512 x 512</option> | |
| <option value="768">768 x 768</option> | |
| <option value="1024">1024 x 1024</option> | |
| <option value="custom">Custom</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div id="resolution-advanced" class="wide resolution-advanced" hidden> | |
| <div><label for="width">Width</label><input id="width" type="number" min="16" step="16" value="256"></div> | |
| <button id="aspect-lock" class="secondary icon-button aspect-lock" type="button" aria-label="Unlock aspect ratio" aria-pressed="true"><span id="aspect-lock-icon" class="icon">link</span></button> | |
| <div><label for="height">Height</label><input id="height" type="number" min="16" step="16" value="256"></div> | |
| <div class="aspect-row"> | |
| <label for="aspect-ratio">Aspect</label> | |
| <select id="aspect-ratio"> | |
| <option value="custom">Current aspect</option> | |
| <option value="1:1" selected>1:1 square</option> | |
| <option value="4:3">4:3 landscape</option> | |
| <option value="3:4">3:4 portrait</option> | |
| <option value="16:9">16:9 wide</option> | |
| <option value="9:16">9:16 vertical</option> | |
| <option value="free">Free / unlocked</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div><label for="steps">Steps</label><input id="steps" type="number" min="1" max="4" value="4"></div> | |
| <div><label for="seed">Seed</label><input id="seed" type="number" value="-1"></div> | |
| <div class="wide img2img-panel"> | |
| <div class="field-heading"> | |
| <div> | |
| <span class="field-title">Image to image</span> | |
| <span class="field-subtitle">Optional source image; leave empty for text to image.</span> | |
| </div> | |
| <button id="clear-init-image" class="secondary icon-button" type="button" aria-label="Remove input image" hidden><span class="icon">close</span></button> | |
| </div> | |
| <label class="file-picker" for="init-image"> | |
| <span class="icon">add_photo_alternate</span> | |
| <span id="init-image-label">Choose optional image</span> | |
| </label> | |
| <input id="init-image" class="visually-hidden" type="file" accept="image/*"> | |
| <div id="source-preview-wrap" class="source-preview" hidden> | |
| <img id="source-preview" alt="Input image preview"> | |
| <div id="source-meta"></div> | |
| </div> | |
| <div id="img2img-controls" class="img2img-controls" hidden> | |
| <div> | |
| <label for="strength">Strength</label> | |
| <div class="range-row"> | |
| <input id="strength" type="range" min="0" max="1" step="0.01" value="0.65"> | |
| <span id="strength-value">0.65 -> 0.93</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label for="strength-curve">Edit behavior</label> | |
| <select id="strength-curve"> | |
| <option value="edit" selected>Prompt edit</option> | |
| <option value="strong">Strong edit boost</option> | |
| <option value="balanced">Balanced</option> | |
| <option value="linear">Preserve source</option> | |
| <option value="rewrite">Prompt dominant</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="fit-mode">Source fit</label> | |
| <select id="fit-mode"> | |
| <option value="cover" selected>Cover canvas</option> | |
| <option value="contain">Contain image</option> | |
| <option value="stretch">Stretch to canvas</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <input id="decode-max" type="hidden" value="0"> | |
| <input id="render-max" type="hidden" value="0"> | |
| </div> | |
| <div id="download-gate" class="download-gate"> | |
| <div id="download-gate-title" class="download-gate-title">Download model assets (3.5 GB)</div> | |
| <button id="start-download" type="button"><span class="icon">download</span>Start download</button> | |
| </div> | |
| <div id="actions" class="actions" hidden> | |
| <button id="run" type="button" disabled><span class="icon">auto_awesome</span>Generate</button> | |
| <button id="abort" class="secondary" type="button" disabled><span class="icon">stop</span>Stop</button> | |
| <div id="elapsed" class="elapsed-meter" aria-live="polite">0.0s</div> | |
| </div> | |
| <div id="load-progress" class="load-progress" aria-live="polite" hidden> | |
| <div class="load-progress-head"> | |
| <div id="load-progress-title" class="load-progress-title">Loading</div> | |
| <div id="load-progress-summary" class="load-progress-summary">0%</div> | |
| </div> | |
| <div id="load-progress-track" class="load-progress-track"> | |
| <div id="load-progress-fill" class="load-progress-fill"></div> | |
| </div> | |
| <div class="load-progress-meta"> | |
| <div id="load-progress-detail" class="load-progress-detail">Starting</div> | |
| <div id="load-progress-rate" class="load-progress-rate">0 MiB/s</div> | |
| </div> | |
| </div> | |
| <pre id="status">Loading</pre> | |
| </aside> | |
| <section> | |
| <div class="stage"> | |
| <img id="image" alt="Generated image" hidden> | |
| <canvas id="image-canvas" hidden></canvas> | |
| <div id="placeholder" class="placeholder">Ready</div> | |
| </div> | |
| <div id="gallery-panel" class="gallery-panel" hidden> | |
| <div class="gallery-header"> | |
| <h2>Generation history</h2> | |
| <button id="clear-gallery" class="secondary" type="button"><span class="icon">delete</span>Clear</button> | |
| </div> | |
| <div id="gallery-grid" class="gallery-grid"></div> | |
| </div> | |
| </section> | |
| </main> | |
| <script src="space-config.js"></script> | |
| <script type="module"> | |
| const appVersion = new URLSearchParams(window.location.search).get("cacheBust") || "20260606-backend-download-gate"; | |
| await import(`./static-app.js?v=${encodeURIComponent(appVersion)}`); | |
| </script> | |
| </body> | |
| </html> | |