Download index.html from shukdev3/ShukGEN: direct link, hf CLI and curl.
- Browser
- Download file 52 kB
-
https://huggingface.co/spaces/shukdev3/ShukGEN/resolve/main/index.html
- Command line
-
hf download hf://spaces/shukdev3/ShukGEN/index.html
-
curl -L -o index.html https://huggingface.co/spaces/shukdev3/ShukGEN/resolve/main/index.html
52 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>ShukGEN β Face Style Generator</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" /> | |
| <style> | |
| /* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| DESIGN TOKENS (mirrors PyQt6 DARK palette) | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| :root { | |
| --bg-dark: #0D0F18; | |
| --bg-panel: #161927; | |
| --bg-card: #1E2235; | |
| --bg-input: #252A3E; | |
| --accent: #7B5CF0; | |
| --accent-hover: #9B7FFF; | |
| --accent2: #38BDF8; | |
| --success: #34D399; | |
| --warning: #FBBF24; | |
| --danger: #F87171; | |
| --text-primary: #EEF0FF; | |
| --text-secondary: #8B91B8; | |
| --text-muted: #4A5070; | |
| --border: #272C42; | |
| --sep: #1E2235; | |
| --style0: #34D399; | |
| --style1: #C0392B; | |
| --style2: #8E44AD; | |
| --style3: #F59E0B; | |
| --style4: #EF4444; | |
| --style5: #F97316; | |
| --style6: #38BDF8; | |
| --style7: #14B8A6; | |
| --font-ui: 'DM Sans', sans-serif; | |
| --font-mono: 'Space Mono', monospace; | |
| --server: ; | |
| } | |
| /* LIGHT THEME */ | |
| body.light { | |
| --bg-dark: #F0F2FF; | |
| --bg-panel: #E4E6F5; | |
| --bg-card: #D8DAF0; | |
| --bg-input: #C8CAE0; | |
| --accent: #7B5CF0; | |
| --accent-hover: #9B7FFF; | |
| --accent2: #0284C7; | |
| --success: #059669; | |
| --warning: #D97706; | |
| --danger: #DC2626; | |
| --text-primary: #0D0F18; | |
| --text-secondary: #3A3D5C; | |
| --text-muted: #7A7DA0; | |
| --border: #B0B3D0; | |
| --sep: #C8CAE0; | |
| } | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| html, body { | |
| height: 100%; | |
| background: var(--bg-dark); | |
| color: var(--text-primary); | |
| font-family: var(--font-ui); | |
| font-size: 14px; | |
| overflow: hidden; | |
| } | |
| /* ββ Layout ββ */ | |
| #app { display: flex; flex-direction: column; height: 100vh; } | |
| /* ββ Title Bar ββ */ | |
| #titlebar { | |
| display: flex; align-items: center; | |
| height: 58px; padding: 0 16px 0 0; | |
| background: var(--bg-panel); | |
| border-bottom: 1px solid var(--sep); | |
| flex-shrink: 0; | |
| } | |
| .titlebar-strip { | |
| width: 5px; height: 58px; flex-shrink: 0; | |
| background: linear-gradient(to bottom, var(--accent), var(--accent2)); | |
| } | |
| .titlebar-logo { | |
| display: flex; align-items: baseline; gap: 2px; | |
| margin-left: 16px; | |
| } | |
| .titlebar-logo .name { font-size: 20px; font-weight: 800; letter-spacing: -1px; } | |
| .titlebar-logo .ver { font-size: 14px; font-weight: 700; color: var(--accent); padding-top: 2px; } | |
| .titlebar-sub { color: var(--text-secondary); font-size: 13px; margin-left: 8px; } | |
| .spacer { flex: 1; } | |
| .badge { | |
| padding: 3px 10px; border-radius: 10px; | |
| font-size: 11px; font-weight: 700; color: #fff; | |
| background: var(--accent2); | |
| } | |
| .badge.gpu { background: var(--success); } | |
| #theme-btn { | |
| margin-left: 12px; | |
| background: var(--bg-input); color: var(--text-secondary); | |
| border: none; border-radius: 8px; padding: 6px 14px; | |
| font-size: 12px; font-family: var(--font-ui); cursor: pointer; | |
| transition: background .2s, color .2s; | |
| } | |
| #theme-btn:hover { background: var(--border); color: var(--text-primary); } | |
| /* ββ Body (sidebar + content) ββ */ | |
| #body { display: flex; flex: 1; min-height: 0; } | |
| /* ββ Sidebar ββ */ | |
| #sidebar { | |
| width: 300px; flex-shrink: 0; | |
| background: var(--bg-panel); | |
| border-right: 1px solid var(--sep); | |
| overflow-y: auto; | |
| padding: 20px 16px; | |
| display: flex; flex-direction: column; gap: 4px; | |
| } | |
| /* ββ Section Headers ββ */ | |
| .section-header { | |
| display: flex; align-items: center; gap: 10px; | |
| margin-bottom: 6px; | |
| } | |
| .section-header .bar { | |
| width: 3px; height: 18px; border-radius: 2px; | |
| background: var(--accent); flex-shrink: 0; | |
| } | |
| .section-header span { font-size: 13px; font-weight: 700; } | |
| .hs { height: 1px; background: var(--sep); margin: 10px 0; } | |
| /* ββ Buttons ββ */ | |
| .btn { | |
| display: flex; align-items: center; justify-content: center; | |
| gap: 6px; width: 100%; padding: 9px 18px; | |
| font-size: 13px; font-weight: 600; font-family: var(--font-ui); | |
| border: none; border-radius: 8px; cursor: pointer; | |
| transition: background .18s, transform .1s, box-shadow .2s; | |
| letter-spacing: .3px; text-decoration: none; | |
| } | |
| .btn:active { transform: scale(.98); } | |
| .btn-primary { background: var(--accent); color: #fff; } | |
| .btn-primary:hover { background: var(--accent-hover); box-shadow: 0 0 18px rgba(123,92,240,.35); } | |
| .btn-success { background: var(--success); color: #fff; } | |
| .btn-success:hover { background: #52E8A8; } | |
| .btn-secondary { background: var(--bg-input); color: var(--text-primary); } | |
| .btn-secondary:hover { background: var(--border); } | |
| .btn-ghost { background: transparent; color: var(--text-secondary); | |
| border: 1px solid rgba(123,92,240,.27); } | |
| .btn-ghost:hover { background: var(--bg-card); } | |
| .btn-danger { background: var(--danger); color: #fff; } | |
| .label-muted { color: var(--text-muted); font-size: 11px; margin-top: 3px; word-break: break-all; } | |
| .label-status { font-size: 11px; font-weight: 600; } | |
| .label-status.ok { color: var(--success); } | |
| .label-status.err { color: var(--danger); } | |
| /* Input preview box */ | |
| #input-preview-box { | |
| width: 100%; height: 190px; | |
| background: var(--bg-input); border-radius: 8px; | |
| display: flex; align-items: center; justify-content: center; | |
| overflow: hidden; margin-top: 8px; position: relative; | |
| } | |
| #input-preview-box img { | |
| width: 100%; height: 100%; object-fit: contain; | |
| } | |
| .img-placeholder { | |
| color: var(--text-muted); font-size: 12px; text-align: center; | |
| pointer-events: none; | |
| } | |
| /* Progress bar */ | |
| .progress-wrap { height: 6px; background: var(--bg-input); border-radius: 4px; overflow: hidden; margin: 6px 0; } | |
| .progress-fill { | |
| height: 100%; width: 0%; | |
| background: linear-gradient(to right, var(--accent), var(--accent2)); | |
| border-radius: 4px; transition: width .4s ease; | |
| } | |
| /* Quick guide */ | |
| .quick-guide { | |
| background: var(--bg-input); border-radius: 10px; | |
| padding: 12px 14px; margin-top: auto; | |
| } | |
| .quick-guide .title { color: var(--warning); font-size: 13px; font-weight: 700; margin-bottom: 6px; } | |
| .quick-guide p { color: var(--text-secondary); font-size: 11px; margin: 2px 0; } | |
| /* ββ Content Area ββ */ | |
| #content { flex: 1; display: flex; flex-direction: column; min-width: 0; overflow: hidden; } | |
| /* ββ Tabs ββ */ | |
| .tab-bar { | |
| display: flex; background: var(--bg-panel); | |
| border-bottom: 1px solid var(--border); | |
| flex-shrink: 0; | |
| } | |
| .tab { | |
| padding: 10px 20px; font-size: 12px; font-weight: 500; | |
| color: var(--text-secondary); border: none; background: none; | |
| cursor: pointer; font-family: var(--font-ui); | |
| border-bottom: 2px solid transparent; | |
| transition: color .15s, border-color .15s, background .15s; | |
| } | |
| .tab:hover { background: var(--bg-card); color: var(--text-primary); } | |
| .tab.active { color: var(--text-primary); border-bottom-color: var(--accent); background: var(--bg-dark); } | |
| .tab-content { display: none; flex: 1; flex-direction: column; min-height: 0; overflow: hidden; } | |
| .tab-content.active { display: flex; } | |
| /* Tab header strip */ | |
| .tab-header { | |
| display: flex; align-items: center; gap: 12px; | |
| padding: 12px 20px; background: var(--bg-panel); | |
| border-bottom: 1px solid var(--sep); flex-shrink: 0; | |
| } | |
| .tab-header .th-title { font-size: 16px; font-weight: 700; } | |
| .tab-header .th-sub { font-size: 12px; color: var(--text-muted); } | |
| /* ββ Style Gallery ββ */ | |
| #gallery-scroll { flex: 1; overflow-y: auto; padding: 16px; } | |
| #gallery-grid { | |
| display: grid; grid-template-columns: repeat(4, 1fr); | |
| gap: 12px; | |
| } | |
| /* Style cards */ | |
| .style-card { | |
| background: var(--bg-card); border-radius: 12px; | |
| overflow: hidden; display: flex; flex-direction: column; | |
| transition: transform .2s, box-shadow .2s; | |
| } | |
| .style-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,.35); } | |
| .card-accent-bar { height: 4px; flex-shrink: 0; } | |
| .card-img-wrap { | |
| width: 100%; aspect-ratio: 1; | |
| background: var(--bg-card); | |
| display: flex; align-items: center; justify-content: center; | |
| overflow: hidden; cursor: pointer; | |
| } | |
| .card-img-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; } | |
| .card-img-placeholder { | |
| color: var(--text-muted); font-size: 11px; text-align: center; padding: 8px; | |
| } | |
| .card-meta { padding: 6px 10px 0; } | |
| .card-style-num { font-size: 11px; font-weight: 700; } | |
| .card-style-name { font-size: 12px; font-weight: 600; margin-top: 2px; } | |
| .card-btns { | |
| display: flex; gap: 6px; padding: 6px 8px 10px; | |
| } | |
| .card-btn { | |
| flex: 1; padding: 4px 8px; font-size: 11px; font-weight: 600; | |
| border: none; border-radius: 6px; cursor: pointer; font-family: var(--font-ui); | |
| background: var(--bg-input); color: var(--text-secondary); | |
| transition: background .15s, color .15s; | |
| } | |
| .card-btn:hover { background: var(--border); color: var(--text-primary); } | |
| .card-btn.save { color: #fff; } | |
| /* ββ Compare Tab ββ */ | |
| #compare-area { | |
| flex: 1; overflow-y: auto; padding: 20px; | |
| display: flex; gap: 16px; align-items: flex-start; | |
| } | |
| .compare-panel { | |
| background: var(--bg-card); border-radius: 12px; overflow: hidden; | |
| flex: 1; max-width: 320px; display: flex; flex-direction: column; | |
| } | |
| .compare-panel-header { | |
| padding: 10px 14px; font-size: 12px; font-weight: 700; | |
| border-bottom: 3px solid var(--accent); | |
| } | |
| .compare-img-wrap { | |
| width: 100%; aspect-ratio: 1; background: var(--bg-card); | |
| display: flex; align-items: center; justify-content: center; overflow: hidden; | |
| } | |
| .compare-img-wrap img { width: 100%; height: 100%; object-fit: cover; } | |
| .compare-panel-footer { padding: 8px; } | |
| .compare-panel-footer button { | |
| width: 100%; padding: 6px 12px; font-size: 11px; | |
| background: var(--bg-input); color: var(--text-secondary); | |
| border: none; border-radius: 6px; cursor: pointer; font-family: var(--font-ui); | |
| } | |
| .compare-panel-footer button:hover { background: var(--border); color: var(--text-primary); } | |
| .compare-select { | |
| padding: 8px; | |
| display: flex; align-items: center; gap: 8px; | |
| } | |
| .compare-select label { font-size: 12px; color: var(--text-secondary); flex-shrink: 0; } | |
| .compare-select select { | |
| flex: 1; padding: 5px 8px; | |
| background: var(--bg-input); color: var(--text-primary); | |
| border: 1px solid var(--border); border-radius: 6px; | |
| font-size: 12px; font-family: var(--font-ui); | |
| } | |
| /* ββ Strength Tab ββ */ | |
| #strength-area { | |
| flex: 1; overflow-y: auto; padding: 24px; | |
| display: flex; gap: 40px; align-items: flex-start; flex-wrap: wrap; | |
| } | |
| .strength-controls { display: flex; flex-direction: column; gap: 10px; min-width: 220px; } | |
| .strength-controls label { font-size: 13px; font-weight: 700; } | |
| .radio-group { display: flex; flex-direction: column; gap: 6px; } | |
| .radio-item { | |
| display: flex; align-items: center; gap: 8px; | |
| font-size: 12px; color: var(--text-secondary); cursor: pointer; | |
| } | |
| .radio-item input[type=radio] { accent-color: var(--accent); width: 16px; height: 16px; } | |
| .radio-item:has(input:checked) { color: var(--text-primary); } | |
| .alpha-display { | |
| font-size: 28px; font-weight: 800; color: var(--accent); | |
| font-family: var(--font-mono); letter-spacing: -1px; | |
| } | |
| input[type=range] { | |
| width: 280px; accent-color: var(--accent); | |
| cursor: pointer; | |
| } | |
| .preset-row { display: flex; gap: 6px; flex-wrap: wrap; } | |
| .preset-btn { | |
| padding: 4px 10px; border-radius: 6px; font-size: 11px; | |
| background: var(--bg-input); color: var(--text-secondary); | |
| border: none; cursor: pointer; font-family: var(--font-ui); | |
| transition: background .15s; | |
| } | |
| .preset-btn:hover { background: var(--border); color: var(--text-primary); } | |
| .strength-previews { display: flex; gap: 12px; flex-wrap: wrap; } | |
| .strength-preview-card { | |
| background: var(--bg-card); border-radius: 10px; overflow: hidden; | |
| display: flex; flex-direction: column; width: 200px; | |
| } | |
| .strength-preview-card .card-label { | |
| padding: 6px 10px; font-size: 11px; font-weight: 700; | |
| color: var(--text-secondary); background: var(--bg-panel); | |
| } | |
| .strength-preview-card img, | |
| .strength-preview-card .img-ph { | |
| width: 100%; aspect-ratio: 1; object-fit: cover; | |
| background: var(--bg-input); display: flex; | |
| align-items: center; justify-content: center; | |
| color: var(--text-muted); font-size: 11px; text-align: center; | |
| } | |
| /* ββ Latent Walk Tab ββ */ | |
| #walk-controls { | |
| display: flex; align-items: center; gap: 20px; flex-wrap: wrap; | |
| padding: 16px 20px; background: var(--bg-panel); | |
| border-bottom: 1px solid var(--sep); flex-shrink: 0; | |
| } | |
| #walk-controls label { font-size: 12px; color: var(--text-secondary); } | |
| #walk-controls select, #walk-controls input[type=number] { | |
| padding: 5px 10px; | |
| background: var(--bg-input); color: var(--text-primary); | |
| border: 1px solid var(--border); border-radius: 6px; | |
| font-size: 12px; font-family: var(--font-ui); | |
| } | |
| #walk-scroll { flex: 1; overflow: auto; padding: 16px; } | |
| #walk-frames-row { display: flex; gap: 8px; flex-wrap: wrap; } | |
| .walk-frame-card { | |
| background: var(--bg-card); border-radius: 8px; overflow: hidden; | |
| display: flex; flex-direction: column; width: 150px; flex-shrink: 0; | |
| } | |
| .walk-frame-card .wf-bar { height: 3px; } | |
| .walk-frame-card img { width: 150px; height: 150px; object-fit: cover; } | |
| .walk-frame-card .wf-label { | |
| text-align: center; font-size: 10px; color: var(--text-muted); | |
| padding: 4px 0; font-family: var(--font-mono); | |
| } | |
| /* ββ Status Bar ββ */ | |
| #statusbar { | |
| height: 32px; padding: 0 16px; | |
| background: var(--bg-panel); border-top: 1px solid var(--sep); | |
| display: flex; align-items: center; gap: 12px; flex-shrink: 0; | |
| font-size: 11px; color: var(--text-muted); | |
| } | |
| #status-text { flex: 1; } | |
| #server-indicator { font-size: 11px; font-weight: 600; } | |
| #server-indicator.ok { color: var(--success); } | |
| #server-indicator.err { color: var(--danger); } | |
| /* ββ Lightbox ββ */ | |
| #lightbox { | |
| display: none; position: fixed; inset: 0; | |
| background: rgba(0,0,0,.85); z-index: 9999; | |
| align-items: center; justify-content: center; | |
| cursor: zoom-out; | |
| } | |
| #lightbox.show { display: flex; } | |
| #lightbox img { max-width: 90vw; max-height: 90vh; border-radius: 8px; object-fit: contain; } | |
| /* ββ Scrollbars ββ */ | |
| ::-webkit-scrollbar { width: 8px; height: 8px; } | |
| ::-webkit-scrollbar-track { background: var(--bg-panel); } | |
| ::-webkit-scrollbar-thumb { background: var(--bg-input); border-radius: 4px; } | |
| ::-webkit-scrollbar-thumb:hover { background: var(--accent); } | |
| /* ββ Server URL bar ββ */ | |
| .server-row { | |
| display: flex; align-items: center; gap: 6px; margin-bottom: 6px; | |
| } | |
| .server-row input { | |
| flex: 1; padding: 6px 10px; | |
| background: var(--bg-input); color: var(--text-primary); | |
| border: 1px solid var(--border); border-radius: 6px; | |
| font-size: 11px; font-family: var(--font-mono); | |
| } | |
| .server-row button { | |
| padding: 6px 10px; background: var(--accent); color: #fff; | |
| border: none; border-radius: 6px; font-size: 11px; cursor: pointer; | |
| } | |
| /* ββ Model path input ββ */ | |
| .path-row { | |
| display: flex; gap: 6px; align-items: stretch; margin-bottom: 4px; | |
| } | |
| .path-row input { | |
| flex: 1; padding: 7px 10px; | |
| background: var(--bg-input); color: var(--text-primary); | |
| border: 1px solid var(--border); border-radius: 8px; | |
| font-size: 11px; font-family: var(--font-mono); | |
| min-width: 0; | |
| } | |
| .path-row input::placeholder { color: var(--text-muted); } | |
| .path-row button { | |
| padding: 7px 12px; background: var(--accent); color: #fff; | |
| border: none; border-radius: 8px; font-size: 12px; font-weight: 600; | |
| cursor: pointer; white-space: nowrap; | |
| } | |
| .path-row button:hover { background: var(--accent-hover); } | |
| /* drag-and-drop zone */ | |
| #drop-zone { | |
| border: 2px dashed var(--border); border-radius: 8px; | |
| padding: 16px 10px; text-align: center; cursor: pointer; | |
| color: var(--text-muted); font-size: 12px; | |
| transition: border-color .2s, background .2s; | |
| margin-top: 8px; | |
| } | |
| #drop-zone.over { border-color: var(--accent); background: rgba(123,92,240,.08); } | |
| #drop-zone input { display: none; } | |
| @media (max-width: 900px) { | |
| #gallery-grid { grid-template-columns: repeat(2, 1fr); } | |
| #sidebar { width: 260px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Lightbox --> | |
| <div id="lightbox" onclick="closeLightbox()"> | |
| <img id="lightbox-img" src="" alt="Preview" /> | |
| </div> | |
| <div id="app"> | |
| <!-- ββ Title Bar ββ --> | |
| <div id="titlebar"> | |
| <div class="titlebar-strip"></div> | |
| <div class="titlebar-logo"> | |
| <span class="name">ShukGEN</span> | |
| </div> | |
| <span class="titlebar-sub">β Face Style Generator</span> | |
| <div class="spacer"></div> | |
| <span class="badge" id="device-badge">π₯ CPU</span> | |
| <button id="theme-btn" onclick="toggleTheme()">β Light Mode</button> | |
| </div> | |
| <!-- ββ Body ββ --> | |
| <div id="body"> | |
| <!-- ββ SIDEBAR ββ --> | |
| <div id="sidebar"> | |
| <!-- Server --> | |
| <div class="section-header"> | |
| <div class="bar"></div><span>π Server</span> | |
| </div> | |
| <div class="server-row"> | |
| <input id="server-url-input" type="text" value="" /> | |
| <button onclick="pingServer()">Connect</button> | |
| </div> | |
| <div id="server-status" class="label-muted">Not connected β start server.py first</div> | |
| <div class="hs"></div> | |
| <!-- Step 1: Load Model --> | |
| <div class="section-header"> | |
| <div class="bar"></div><span>β Load Model</span> | |
| </div> | |
| <div class="path-row"> | |
| <input id="model-path-input" type="text" placeholder="Paste full path to .pth fileβ¦" /> | |
| <button onclick="loadModel()">Load</button> | |
| </div> | |
| <div id="model-path-label" class="label-muted">No model loaded</div> | |
| <div id="model-badge" class="label-status err">β Not Loaded</div> | |
| <div class="hs"></div> | |
| <!-- Step 2: Upload Image --> | |
| <div class="section-header"> | |
| <div class="bar"></div><span>β‘ Upload Face Image</span> | |
| </div> | |
| <div id="drop-zone" onclick="document.getElementById('file-input').click()" | |
| ondragover="event.preventDefault(); this.classList.add('over')" | |
| ondragleave="this.classList.remove('over')" | |
| ondrop="handleDrop(event)"> | |
| <input type="file" id="file-input" accept="image/*" onchange="handleFileSelect(event)" /> | |
| πΌ Click or drag & drop a face image | |
| </div> | |
| <div id="img-path-label" class="label-muted" style="margin-top:4px;">No image selected</div> | |
| <div id="input-preview-box"> | |
| <span class="img-placeholder">Upload a face image</span> | |
| </div> | |
| <div class="hs"></div> | |
| <!-- Step 3: Generate --> | |
| <div class="section-header"> | |
| <div class="bar"></div><span>β’ Generate Styles</span> | |
| </div> | |
| <button class="btn btn-success" onclick="generateAll()">β¨ Generate All 8 Styles</button> | |
| <div class="progress-wrap" style="margin-top:6px;"> | |
| <div class="progress-fill" id="progress-fill"></div> | |
| </div> | |
| <div id="gen-status" class="label-muted">Ready</div> | |
| <div class="hs"></div> | |
| <!-- Step 4: Export --> | |
| <div class="section-header"> | |
| <div class="bar"></div><span>β£ Export & Tools</span> | |
| </div> | |
| <button class="btn btn-secondary" onclick="saveAll()" style="margin-bottom:4px;">πΎ Save All Outputs</button> | |
| <button class="btn btn-ghost" onclick="showAbout()">βΉοΈ Architecture Info</button> | |
| <!-- Quick Guide --> | |
| <div class="quick-guide" style="margin-top:16px;"> | |
| <div class="title">π‘ Quick Guide</div> | |
| <p>1. Start server.py, then Connect</p> | |
| <p>2. Paste .pth model path, click Load</p> | |
| <p>3. Upload a face photo</p> | |
| <p>4. Click Generate All Styles</p> | |
| <p>5. Preview / Save any result</p> | |
| <p>6. Try Strength & Walk tabs!</p> | |
| </div> | |
| </div><!-- /sidebar --> | |
| <!-- ββ CONTENT ββ --> | |
| <div id="content"> | |
| <!-- Tab bar --> | |
| <div class="tab-bar"> | |
| <button class="tab active" onclick="switchTab('gallery', this)">π¨ Style Gallery</button> | |
| <button class="tab" onclick="switchTab('compare', this)">π Compare</button> | |
| <button class="tab" onclick="switchTab('strength', this)">π Strength</button> | |
| <button class="tab" onclick="switchTab('walk', this)">πΆ Latent Walk</button> | |
| </div> | |
| <!-- ββ Tab: Gallery ββ --> | |
| <div class="tab-content active" id="tab-gallery"> | |
| <div class="tab-header"> | |
| <span class="th-title">8 Visually Distinct Style Variants</span> | |
| <span class="th-sub">Each style applies different pixel transforms β mathematically guaranteed to look distinct</span> | |
| </div> | |
| <div id="gallery-scroll"> | |
| <div id="gallery-grid"> | |
| <!-- Cards built by JS --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ββ Tab: Compare ββ --> | |
| <div class="tab-content" id="tab-compare"> | |
| <div class="tab-header"> | |
| <span class="th-title">Original vs Reconstruction vs Styled</span> | |
| </div> | |
| <div id="compare-area"> | |
| <!-- Original --> | |
| <div class="compare-panel"> | |
| <div class="compare-panel-header" style="border-color: var(--accent2);">Original Input</div> | |
| <div class="compare-img-wrap" id="cmp-orig-wrap"> | |
| <span class="img-placeholder" style="color:var(--text-muted);font-size:11px;">Upload image first</span> | |
| </div> | |
| <div class="compare-panel-footer"> | |
| <button onclick="openLightbox('orig')">π Full Size Preview</button> | |
| </div> | |
| </div> | |
| <!-- Reconstruction --> | |
| <div class="compare-panel"> | |
| <div class="compare-panel-header" style="border-color: var(--success);">VAE Reconstruction</div> | |
| <div class="compare-img-wrap" id="cmp-recon-wrap"> | |
| <span class="img-placeholder" style="color:var(--text-muted);font-size:11px;">Generate first</span> | |
| </div> | |
| <div class="compare-panel-footer"> | |
| <button onclick="openLightbox('recon')">π Full Size Preview</button> | |
| </div> | |
| </div> | |
| <!-- Styled --> | |
| <div class="compare-panel"> | |
| <div class="compare-panel-header" style="border-color: var(--accent);">Styled Version</div> | |
| <div class="compare-img-wrap" id="cmp-styled-wrap"> | |
| <span class="img-placeholder" style="color:var(--text-muted);font-size:11px;">Generate first</span> | |
| </div> | |
| <div class="compare-select"> | |
| <label>Style:</label> | |
| <select id="compare-style-sel" onchange="updateCompareStyled()"></select> | |
| </div> | |
| <div class="compare-panel-footer"> | |
| <button onclick="openLightbox('styled')">π Full Size Preview</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ββ Tab: Strength ββ --> | |
| <div class="tab-content" id="tab-strength"> | |
| <div class="tab-header"> | |
| <span class="th-title">Style Strength Control (Ξ±-Blending)</span> | |
| <span class="th-sub">Ξ±=0 β pure reconstruction | Ξ±=1 β full style effect</span> | |
| </div> | |
| <div id="strength-area"> | |
| <div class="strength-controls"> | |
| <label>Select Style:</label> | |
| <div class="radio-group" id="strength-radio-group"> | |
| <!-- built by JS --> | |
| </div> | |
| <div style="margin-top:16px;"> | |
| <label>Blend Strength (Ξ±):</label> | |
| <div class="alpha-display" id="alpha-display">Ξ± = 1.00</div> | |
| <input type="range" id="alpha-slider" min="0" max="100" value="100" | |
| oninput="onAlphaChange(this.value)" style="margin: 8px 0;" /> | |
| <div class="preset-row"> | |
| <button class="preset-btn" onclick="setAlpha(0)">0%</button> | |
| <button class="preset-btn" onclick="setAlpha(25)">25%</button> | |
| <button class="preset-btn" onclick="setAlpha(50)">50%</button> | |
| <button class="preset-btn" onclick="setAlpha(75)">75%</button> | |
| <button class="preset-btn" onclick="setAlpha(100)">100%</button> | |
| </div> | |
| </div> | |
| <button class="btn btn-primary" onclick="updateStrengthPreview()" style="margin-top:12px;"> | |
| π Update Preview | |
| </button> | |
| </div> | |
| <div class="strength-previews"> | |
| <div class="strength-preview-card"> | |
| <div class="card-label">Reconstruction</div> | |
| <div id="sp-recon" class="img-ph" style="display:flex;align-items:center;justify-content:center;min-height:200px;">Generate first</div> | |
| </div> | |
| <div class="strength-preview-card"> | |
| <div class="card-label">Blended (Ξ±)</div> | |
| <div id="sp-blend" class="img-ph" style="display:flex;align-items:center;justify-content:center;min-height:200px;">Generate first</div> | |
| </div> | |
| <div class="strength-preview-card"> | |
| <div class="card-label">Full Style</div> | |
| <div id="sp-styled" class="img-ph" style="display:flex;align-items:center;justify-content:center;min-height:200px;">Generate first</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ββ Tab: Latent Walk ββ --> | |
| <div class="tab-content" id="tab-walk"> | |
| <div class="tab-header"> | |
| <span class="th-title">Latent Space Walk</span> | |
| <span class="th-sub">Traverse the VAE latent space between two style directions</span> | |
| </div> | |
| <div id="walk-controls"> | |
| <label>Style A: | |
| <select id="walk-style-a"></select> | |
| </label> | |
| <label>Style B: | |
| <select id="walk-style-b"></select> | |
| </label> | |
| <label>Steps: | |
| <input type="number" id="walk-steps" value="7" min="2" max="16" style="width:60px;" /> | |
| </label> | |
| <button class="btn btn-primary" style="width:auto;padding:8px 20px;" onclick="runWalk()"> | |
| πΆ Run Walk | |
| </button> | |
| <span id="walk-status" style="font-size:11px;color:var(--text-muted);">Generate styles first</span> | |
| </div> | |
| <div id="walk-scroll"> | |
| <div id="walk-frames-row"> | |
| <span style="color:var(--text-muted);font-size:12px;">Run a latent walk to see frames here.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div><!-- /content --> | |
| </div><!-- /body --> | |
| <!-- ββ Status Bar ββ --> | |
| <div id="statusbar"> | |
| <span id="status-text">Ready β start server.py and click Connect</span> | |
| <span id="server-indicator" class="err">β Disconnected</span> | |
| </div> | |
| </div><!-- /app --> | |
| <!-- About dialog --> | |
| <div id="about-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:8888;align-items:center;justify-content:center;"> | |
| <div style="background:var(--bg-dark);border-radius:12px;max-width:560px;width:90%;max-height:80vh;overflow-y:auto;padding:24px;"> | |
| <h2 style="font-size:18px;font-weight:800;margin-bottom:8px;">ShukGEN β Architecture Info</h2> | |
| <pre id="about-text" style="font-family:var(--font-mono);font-size:11px;color:var(--text-secondary);white-space:pre-wrap;line-height:1.6;"></pre> | |
| <button class="btn btn-secondary" style="margin-top:16px;" onclick="document.getElementById('about-overlay').style.display='none'">Close</button> | |
| </div> | |
| </div> | |
| <script> | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CONSTANTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const STYLE_NAMES = [ | |
| "Youthful", "Aged / Mature", "Dramatic Light", "Soft Glow", | |
| "Intense / Bold", "Warm Golden Hour", "Cool / Moody", "Sketch / Artistic", | |
| ]; | |
| const STYLE_ICONS = ["πΈ","πΏ","β‘","β¨","π₯","π ","π","π¨"]; | |
| const STYLE_COLORS = ["#34D399","#C0392B","#8E44AD","#F59E0B","#EF4444","#F97316","#38BDF8","#14B8A6"]; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // STATE | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const S = { | |
| serverURL: '', | |
| modelLoaded: false, | |
| imageLoaded: false, | |
| generated: false, | |
| origB64: null, | |
| reconB64: null, | |
| styledB64: new Array(8).fill(null), | |
| uploadedImg: null, // File object | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SERVER HELPERS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function getURL() { | |
| return document.getElementById('server-url-input').value.replace(/\/$/, ''); | |
| } | |
| async function apiFetch(path, opts = {}) { | |
| const url = getURL() + path; | |
| const res = await fetch(url, { | |
| headers: { 'Content-Type': 'application/json' }, | |
| ...opts, | |
| }); | |
| return res.json(); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // PING / CONNECT | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function pingServer() { | |
| setStatus('Connecting to serverβ¦'); | |
| try { | |
| const data = await apiFetch('/status'); | |
| document.getElementById('server-status').textContent = 'β Connected'; | |
| document.getElementById('server-indicator').textContent = 'β Connected'; | |
| document.getElementById('server-indicator').className = 'ok'; | |
| // Device badge | |
| const badge = document.getElementById('device-badge'); | |
| if (data.device && data.device.startsWith('GPU')) { | |
| badge.textContent = 'β‘ ' + data.device; | |
| badge.classList.add('gpu'); | |
| } else { | |
| badge.textContent = 'π₯ ' + (data.device || 'CPU'); | |
| } | |
| setStatus(`Connected β PyTorch ${data.torch_version} | ${data.device}`); | |
| if (data.model_loaded) { | |
| S.modelLoaded = true; | |
| document.getElementById('model-badge').textContent = 'β Loaded'; | |
| document.getElementById('model-badge').className = 'label-status ok'; | |
| const cfg = data.model_config; | |
| document.getElementById('model-path-label').textContent = | |
| `latent_dim=${cfg.latent_dim} | base_filters=${cfg.base_filters} | img_size=${data.image_size || '?'}`; | |
| } | |
| } catch (e) { | |
| document.getElementById('server-status').textContent = 'β Cannot reach server β is server.py running?'; | |
| document.getElementById('server-indicator').textContent = 'β Disconnected'; | |
| document.getElementById('server-indicator').className = 'err'; | |
| setStatus('Cannot reach server. Start server.py first.'); | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // LOAD MODEL | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function loadModel() { | |
| const path = document.getElementById('model-path-input').value.trim(); | |
| if (!path) { alert('Please paste the full path to your .pth file.'); return; } | |
| setStatus('β³ Loading modelβ¦'); | |
| try { | |
| const data = await apiFetch('/load_model', { | |
| method: 'POST', | |
| body: JSON.stringify({ path }), | |
| }); | |
| if (data.error) { | |
| setStatus('β ' + data.error); | |
| document.getElementById('model-badge').textContent = 'β Load Failed'; | |
| document.getElementById('model-badge').className = 'label-status err'; | |
| alert('Model load error:\n' + data.error); | |
| return; | |
| } | |
| S.modelLoaded = true; | |
| document.getElementById('model-badge').textContent = 'β Loaded β'; | |
| document.getElementById('model-badge').className = 'label-status ok'; | |
| document.getElementById('model-path-label').textContent = | |
| `${data.filename} | latent=${data.config.latent_dim} base_f=${data.config.base_filters} img=${data.image_size}`; | |
| setStatus(`β Model loaded: ${data.filename}`); | |
| } catch (e) { | |
| setStatus('β Network error β is server running?'); | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // IMAGE UPLOAD | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function handleDrop(e) { | |
| e.preventDefault(); | |
| document.getElementById('drop-zone').classList.remove('over'); | |
| const file = e.dataTransfer.files[0]; | |
| if (file && file.type.startsWith('image/')) loadImageFile(file); | |
| } | |
| function handleFileSelect(e) { | |
| const file = e.target.files[0]; | |
| if (file) loadImageFile(file); | |
| } | |
| function loadImageFile(file) { | |
| const reader = new FileReader(); | |
| reader.onload = async (ev) => { | |
| const b64 = ev.target.result; | |
| S.origB64 = b64; | |
| S.imageLoaded = true; | |
| document.getElementById('img-path-label').textContent = file.name; | |
| // Show preview in sidebar | |
| const box = document.getElementById('input-preview-box'); | |
| box.innerHTML = `<img src="${b64}" style="width:100%;height:100%;object-fit:contain;" />`; | |
| // Update compare original | |
| updateCompareOrig(b64); | |
| setStatus('πΌ Image loaded: ' + file.name); | |
| // Send to server | |
| try { | |
| const data = await apiFetch('/load_image', { | |
| method: 'POST', | |
| body: JSON.stringify({ image_b64: b64 }), | |
| }); | |
| if (data.error) setStatus('β Image upload error: ' + data.error); | |
| } catch (e) { | |
| setStatus('β Could not send image to server β check connection.'); | |
| } | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // GENERATE | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function generateAll() { | |
| if (!S.modelLoaded) { alert('Load a model first.'); return; } | |
| if (!S.imageLoaded) { alert('Upload an image first.'); return; } | |
| setProgress(0); | |
| setGenStatus('β³ Encoding face with VAEβ¦'); | |
| setStatus('β³ Encoding face with VAEβ¦'); | |
| // Set all cards to loading | |
| for (let i = 0; i < 8; i++) setCardLoading(i); | |
| try { | |
| const data = await apiFetch('/generate', { method: 'POST', body: '{}' }); | |
| if (data.error) { | |
| setGenStatus('β Error'); | |
| setStatus('β Generate error: ' + data.error); | |
| alert('Generation error:\n' + data.error); | |
| return; | |
| } | |
| S.reconB64 = data.recon; | |
| S.styledB64 = data.styles; | |
| S.generated = true; | |
| setProgress(100); | |
| // Update gallery cards | |
| for (let i = 0; i < 8; i++) setCardImage(i, data.styles[i]); | |
| // Update compare tab | |
| updateCompareRecon(data.recon); | |
| updateCompareStyled(); | |
| // Update strength previews (recon panel) | |
| setImgOrPh('sp-recon', data.recon); | |
| setGenStatus('β Done β 8 styles generated!'); | |
| setStatus('β All 8 styles generated successfully.'); | |
| } catch (e) { | |
| setGenStatus('β Network error'); | |
| setStatus('β Generation failed β is server running?'); | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // STRENGTH PREVIEW | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function onAlphaChange(val) { | |
| const v = (val / 100).toFixed(2); | |
| document.getElementById('alpha-display').textContent = `Ξ± = ${v}`; | |
| } | |
| function setAlpha(val) { | |
| document.getElementById('alpha-slider').value = val; | |
| onAlphaChange(val); | |
| } | |
| async function updateStrengthPreview() { | |
| if (!S.generated) { alert('Generate styles first.'); return; } | |
| const styleIdx = getCheckedRadio(); | |
| const alpha = document.getElementById('alpha-slider').value / 100; | |
| // Recon always shown | |
| setImgOrPh('sp-recon', S.reconB64); | |
| // Full styled | |
| setImgOrPh('sp-styled', S.styledB64[styleIdx]); | |
| // Blend from server | |
| try { | |
| const data = await apiFetch('/strength_preview', { | |
| method: 'POST', | |
| body: JSON.stringify({ style_idx: styleIdx, alpha }), | |
| }); | |
| if (data.blend) setImgOrPh('sp-blend', data.blend); | |
| } catch (e) { | |
| setStatus('β Strength preview failed'); | |
| } | |
| } | |
| function getCheckedRadio() { | |
| const radios = document.querySelectorAll('#strength-radio-group input[type=radio]'); | |
| for (let r of radios) if (r.checked) return parseInt(r.value); | |
| return 0; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // LATENT WALK | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function runWalk() { | |
| if (!S.generated) { alert('Generate styles first.'); return; } | |
| const styleA = parseInt(document.getElementById('walk-style-a').value); | |
| const styleB = parseInt(document.getElementById('walk-style-b').value); | |
| const steps = parseInt(document.getElementById('walk-steps').value); | |
| document.getElementById('walk-status').textContent = 'β³ Runningβ¦'; | |
| document.getElementById('walk-frames-row').innerHTML = | |
| '<span style="color:var(--text-muted);font-size:12px;">Generating framesβ¦</span>'; | |
| try { | |
| const data = await apiFetch('/latent_walk', { | |
| method: 'POST', | |
| body: JSON.stringify({ style_a: styleA, style_b: styleB, steps }), | |
| }); | |
| if (data.error) { | |
| document.getElementById('walk-status').textContent = 'β ' + data.error; | |
| return; | |
| } | |
| const row = document.getElementById('walk-frames-row'); | |
| row.innerHTML = ''; | |
| data.frames.forEach((f, i) => { | |
| const color = f.alpha < 0.5 ? STYLE_COLORS[styleA] : STYLE_COLORS[styleB]; | |
| const card = document.createElement('div'); | |
| card.className = 'walk-frame-card'; | |
| card.innerHTML = ` | |
| <div class="wf-bar" style="background:${color};"></div> | |
| <img src="${f.img}" loading="lazy" onclick="openLightboxSrc('${f.img}')" style="cursor:zoom-in;" /> | |
| <div class="wf-label">Ξ±=${f.alpha.toFixed(2)}</div> | |
| `; | |
| row.appendChild(card); | |
| }); | |
| document.getElementById('walk-status').textContent = | |
| `β ${data.frames.length} frames: ${STYLE_NAMES[styleA]} β ${STYLE_NAMES[styleB]}`; | |
| setStatus(`β Latent walk: ${data.frames.length} frames`); | |
| } catch (e) { | |
| document.getElementById('walk-status').textContent = 'β Network error'; | |
| setStatus('β Walk failed β is server running?'); | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // COMPARE TAB | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function updateCompareOrig(b64) { | |
| setImgInWrap('cmp-orig-wrap', b64, 'Original'); | |
| } | |
| function updateCompareRecon(b64) { | |
| setImgInWrap('cmp-recon-wrap', b64, 'Reconstruction'); | |
| } | |
| function updateCompareStyled() { | |
| const idx = parseInt(document.getElementById('compare-style-sel').value); | |
| const b64 = S.styledB64[idx]; | |
| if (b64) setImgInWrap('cmp-styled-wrap', b64, STYLE_NAMES[idx]); | |
| else { | |
| document.getElementById('cmp-styled-wrap').innerHTML = | |
| `<span class="img-placeholder" style="color:var(--text-muted);font-size:11px;">Generate first</span>`; | |
| } | |
| } | |
| function setImgInWrap(id, b64, label) { | |
| const wrap = document.getElementById(id); | |
| wrap.innerHTML = `<img src="${b64}" style="width:100%;height:100%;object-fit:cover;" alt="${label}" onclick="openLightboxSrc('${b64}')" style="cursor:zoom-in;" />`; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SAVE | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function downloadB64(b64, filename) { | |
| const a = document.createElement('a'); | |
| a.href = b64; | |
| a.download = filename; | |
| a.click(); | |
| } | |
| function saveStyle(idx) { | |
| const b64 = S.styledB64[idx]; | |
| if (!b64) { alert('Generate styles first.'); return; } | |
| const name = STYLE_NAMES[idx].replace(/[/ ]+/g, '_').toLowerCase(); | |
| downloadB64(b64, `shukgen_v3_style_${idx}_${name}.png`); | |
| setStatus(`πΎ Saved style ${idx}: ${STYLE_NAMES[idx]}`); | |
| } | |
| async function saveAll() { | |
| if (!S.generated && !S.imageLoaded) { alert('Generate styles first.'); return; } | |
| if (S.origB64) downloadB64(S.origB64, '00_original.png'); | |
| if (S.reconB64) downloadB64(S.reconB64, '01_reconstruction.png'); | |
| S.styledB64.forEach((b64, i) => { | |
| if (b64) { | |
| const name = STYLE_NAMES[i].replace(/[/ ]+/g, '_').toLowerCase(); | |
| setTimeout(() => downloadB64(b64, `style_${String(i).padStart(2,'0')}_${name}.png`), i * 100); | |
| } | |
| }); | |
| setStatus('πΎ Saving all generated imagesβ¦'); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // LIGHTBOX | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const _lightboxSrcs = {}; | |
| function openLightbox(key) { | |
| const srcs = { orig: S.origB64, recon: S.reconB64, styled: S.styledB64[parseInt(document.getElementById('compare-style-sel').value)] }; | |
| const src = srcs[key]; | |
| if (!src) return; | |
| openLightboxSrc(src); | |
| } | |
| function openLightboxSrc(src) { | |
| document.getElementById('lightbox-img').src = src; | |
| document.getElementById('lightbox').classList.add('show'); | |
| } | |
| function closeLightbox() { | |
| document.getElementById('lightbox').classList.remove('show'); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // GALLERY CARDS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildGallery() { | |
| const grid = document.getElementById('gallery-grid'); | |
| grid.innerHTML = ''; | |
| for (let i = 0; i < 8; i++) { | |
| const color = STYLE_COLORS[i]; | |
| const card = document.createElement('div'); | |
| card.className = 'style-card'; | |
| card.id = `card-${i}`; | |
| card.innerHTML = ` | |
| <div class="card-accent-bar" style="background:${color};"></div> | |
| <div class="card-img-wrap" id="card-img-wrap-${i}" onclick="previewStyle(${i})"> | |
| <span class="card-img-placeholder">Pendingβ¦</span> | |
| </div> | |
| <div class="card-meta"> | |
| <div class="card-style-num" style="color:${color};">${STYLE_ICONS[i]} Style ${i}</div> | |
| <div class="card-style-name">${STYLE_NAMES[i]}</div> | |
| </div> | |
| <div class="card-btns"> | |
| <button class="card-btn" onclick="previewStyle(${i})">π Preview</button> | |
| <button class="card-btn save" style="background:${color};" onclick="saveStyle(${i})">πΎ Save</button> | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| } | |
| } | |
| function setCardLoading(i) { | |
| document.getElementById(`card-img-wrap-${i}`).innerHTML = | |
| '<span class="card-img-placeholder">Generatingβ¦</span>'; | |
| } | |
| function setCardImage(i, b64) { | |
| document.getElementById(`card-img-wrap-${i}`).innerHTML = | |
| `<img src="${b64}" alt="${STYLE_NAMES[i]}" />`; | |
| } | |
| function previewStyle(i) { | |
| if (!S.styledB64[i]) { alert('Generate styles first.'); return; } | |
| openLightboxSrc(S.styledB64[i]); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // COMPARE STYLE SELECT | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildCompareSelect() { | |
| const sel = document.getElementById('compare-style-sel'); | |
| sel.innerHTML = ''; | |
| STYLE_NAMES.forEach((n, i) => { | |
| const opt = document.createElement('option'); | |
| opt.value = i; opt.textContent = `${i}: ${n}`; | |
| sel.appendChild(opt); | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // STRENGTH RADIO GROUP | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildStrengthRadios() { | |
| const group = document.getElementById('strength-radio-group'); | |
| group.innerHTML = ''; | |
| STYLE_NAMES.forEach((n, i) => { | |
| const lbl = document.createElement('label'); | |
| lbl.className = 'radio-item'; | |
| lbl.innerHTML = ` | |
| <input type="radio" name="strength-style" value="${i}" ${i===0?'checked':''} /> | |
| ${STYLE_ICONS[i]} ${n} | |
| `; | |
| group.appendChild(lbl); | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // WALK SELECTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildWalkSelects() { | |
| ['walk-style-a','walk-style-b'].forEach((id, j) => { | |
| const sel = document.getElementById(id); | |
| sel.innerHTML = ''; | |
| STYLE_NAMES.forEach((n, i) => { | |
| const opt = document.createElement('option'); | |
| opt.value = i; opt.textContent = `${i}: ${n}`; | |
| if (j === 0 && i === 0) opt.selected = true; | |
| if (j === 1 && i === 1) opt.selected = true; | |
| sel.appendChild(opt); | |
| }); | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // ABOUT | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function showAbout() { | |
| const overlay = document.getElementById('about-overlay'); | |
| overlay.style.display = 'flex'; | |
| fetch(getURL() + '/status').then(r => r.json()).then(data => { | |
| const cfg = data.model_config || {}; | |
| document.getElementById('about-text').textContent = [ | |
| 'ShukGEN β Face Style Generator', | |
| 'ββββββββββββββββββββββββββββββββββββ', | |
| '', | |
| 'MODEL ARCHITECTURE: FaceVAE', | |
| ` Latent Dimension : ${cfg.latent_dim ?? 'β'}`, | |
| ` Base Filters : ${cfg.base_filters ?? 'β'}`, | |
| ` Image Size : ${data.image_size ?? 'β'}`, | |
| ` Device : ${data.device ?? 'β'}`, | |
| ` PyTorch : ${data.torch_version ?? 'β'}`, | |
| '', | |
| 'ENCODER', | |
| ' Inc β DownBlockΓ4 β Bottleneck β Pool β fc_mu / fc_logvar', | |
| ' Bottleneck: ResBlock β AttnBlock β ResBlock β SE', | |
| '', | |
| 'DECODER', | |
| ' fc_dec β Upsample β UpBlockΓ4 β outc (Tanh)', | |
| '', | |
| 'STYLE TRANSFORMS (8 built-in, pixel-space):', | |
| ' 0: Youthful β saturation boost + subtle smoothing', | |
| ' 1: Aged / Mature β desaturation + warm tint + grain + vignette', | |
| ' 2: Dramatic Light β horizontal gradient + high contrast + vignette', | |
| ' 3: Soft Glow β blur additive blend + warm tint + lift', | |
| ' 4: Intense / Bold β heavy saturation + contrast + sharpness', | |
| ' 5: Warm Golden Hr β red/green boost + blue reduction', | |
| ' 6: Cool / Moody β red reduction + blue boost + grain + vignette', | |
| ' 7: Sketch/Artisticβ posterize + edge detection + canvas grain', | |
| '', | |
| 'LATENT WALK', | |
| ' Random direction sampled in latent space.', | |
| ' Walk range: β2.5Ο to +2.5Ο, styles blended by Ξ±.', | |
| ].join('\n'); | |
| }).catch(() => { | |
| document.getElementById('about-text').textContent = 'Connect to server to see architecture details.'; | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // HELPERS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function setStatus(msg) { | |
| document.getElementById('status-text').textContent = msg; | |
| } | |
| function setGenStatus(msg) { | |
| document.getElementById('gen-status').textContent = msg; | |
| } | |
| function setProgress(pct) { | |
| document.getElementById('progress-fill').style.width = pct + '%'; | |
| } | |
| function setImgOrPh(id, b64) { | |
| const el = document.getElementById(id); | |
| if (b64) { | |
| el.outerHTML = `<img id="${id}" src="${b64}" style="width:100%;aspect-ratio:1;object-fit:cover;cursor:zoom-in;" onclick="openLightboxSrc('${b64}')" />`; | |
| } | |
| } | |
| // ββ Tab switching ββ | |
| function switchTab(name, btn) { | |
| document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); | |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); | |
| document.getElementById('tab-' + name).classList.add('active'); | |
| btn.classList.add('active'); | |
| } | |
| // ββ Theme ββ | |
| let isDark = true; | |
| function toggleTheme() { | |
| isDark = !isDark; | |
| document.body.classList.toggle('light', !isDark); | |
| document.getElementById('theme-btn').textContent = isDark ? 'β Light Mode' : 'π Dark Mode'; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // INIT | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function init() { | |
| buildGallery(); | |
| buildCompareSelect(); | |
| buildStrengthRadios(); | |
| buildWalkSelects(); | |
| // Auto-ping server | |
| pingServer(); | |
| } | |
| document.addEventListener('DOMContentLoaded', init); | |
| </script> | |
| </body> | |
| </html> | |