Spaces:
Running on Zero
Running on Zero
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Hy3D Lab · AI Image Editor</title> | |
| <style> | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| :root{ | |
| --bg:#0a0a0c;--panel:#131316;--panel2:#0e0e11;--canvas:#08080a; | |
| --border:#232328;--border2:#33333a; | |
| --text:#e4e4e7;--muted:#a1a1aa;--dim:#6b6b75;--faint:#4a4a52; | |
| --accent:#1E90FF;--accent2:#47A3FF;--accent3:#7CB8FF; | |
| --menubar-h:48px;--statusbar-h:30px;--rail-w:52px;--inspector-w:360px;--filmstrip-h:132px; | |
| } | |
| html,body{height:100%} | |
| body{ | |
| background:var(--bg);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; | |
| font-size:13px;color:var(--text);overflow:hidden; | |
| } | |
| /* ══ Layout grid ══ */ | |
| .studio{ | |
| display:grid;height:100vh; | |
| grid-template-rows:var(--menubar-h) 1fr var(--statusbar-h); | |
| grid-template-columns:var(--rail-w) 1fr var(--inspector-w); | |
| grid-template-areas: | |
| "menubar menubar menubar" | |
| "rail workspace inspector" | |
| "statusbar statusbar statusbar"; | |
| } | |
| /* ══ Menu bar ══ */ | |
| .menubar{ | |
| grid-area:menubar;display:flex;align-items:center;gap:14px; | |
| background:var(--panel);border-bottom:1px solid var(--border); | |
| padding:0 16px;z-index:50; | |
| } | |
| .menubar-logo{ | |
| width:28px;height:28px;background:linear-gradient(135deg,#1E90FF,#47A3FF,#7CB8FF); | |
| border-radius:8px;display:flex;align-items:center;justify-content:center; | |
| box-shadow:0 2px 8px rgba(30,144,255,.35);flex-shrink:0; | |
| } | |
| .menubar-logo svg{width:15px;height:15px;fill:#fff} | |
| .menubar-title{font-size:14px;font-weight:700;letter-spacing:-.2px;white-space:nowrap} | |
| .menubar-title span{color:var(--dim);font-weight:500} | |
| .menubar-badge{ | |
| font-size:10px;font-weight:700;padding:2px 8px;border-radius:12px; | |
| background:rgba(30,144,255,.14);color:var(--accent2);border:1px solid rgba(30,144,255,.25); | |
| letter-spacing:.4px;white-space:nowrap; | |
| } | |
| .menubar-badge.fast{background:rgba(34,197,94,.12);color:#4ade80;border-color:rgba(34,197,94,.25)} | |
| .menubar-spacer{flex:1} | |
| .menubar-status{ | |
| display:flex;align-items:center;gap:7px;font-family:'JetBrains Mono',monospace; | |
| font-size:11px;color:var(--dim); | |
| } | |
| .menubar-status .dot{width:7px;height:7px;border-radius:50%;background:#4ade80;box-shadow:0 0 6px rgba(74,222,128,.6)} | |
| .menubar-status .dot.pending{background:#facc15;box-shadow:0 0 6px rgba(250,204,21,.45)} | |
| .menubar-status .dot.off{background:#f87171;box-shadow:0 0 6px rgba(248,113,113,.5)} | |
| .site-btn{ | |
| display:inline-flex;align-items:center;gap:6px; | |
| padding:5px 13px;border-radius:7px;text-decoration:none; | |
| font-size:12px;font-weight:600;background:var(--accent);color:#fff; | |
| border:1px solid rgba(255,255,255,.15); | |
| transition:all .15s ease;flex-shrink:0; | |
| } | |
| .site-btn:hover{background:var(--accent2)} | |
| .site-btn svg{fill:none;stroke:currentColor;stroke-width:2;width:13px;height:13px} | |
| /* ══ Left icon rail ══ */ | |
| .rail{ | |
| grid-area:rail;background:var(--panel);border-right:1px solid var(--border); | |
| display:flex;flex-direction:column;align-items:center;padding:10px 0;gap:4px; | |
| overflow-y:auto;z-index:40; | |
| } | |
| .rail-btn{ | |
| width:38px;height:38px;border-radius:9px;border:1px solid transparent; | |
| background:transparent;color:var(--dim);cursor:pointer; | |
| display:flex;align-items:center;justify-content:center; | |
| transition:all .15s ease;position:relative; | |
| } | |
| .rail-btn:hover:not(:disabled){background:rgba(30,144,255,.12);color:var(--accent3);border-color:rgba(30,144,255,.25)} | |
| .rail-btn.active{background:rgba(30,144,255,.2);color:var(--accent2);border-color:rgba(30,144,255,.4)} | |
| .rail-btn:disabled{opacity:.3;cursor:not-allowed} | |
| .rail-btn svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round} | |
| .rail-btn svg.fill{fill:currentColor;stroke:none} | |
| .rail-sep{width:24px;height:1px;background:var(--border);margin:6px 0} | |
| .rail-label{ | |
| font-size:8px;font-weight:700;color:var(--faint);letter-spacing:1px; | |
| text-transform:uppercase;margin:8px 0 2px;user-select:none; | |
| } | |
| /* ══ Workspace (canvas + filmstrip) ══ */ | |
| .workspace{ | |
| grid-area:workspace;display:flex;flex-direction:column;min-width:0;min-height:0; | |
| background:var(--canvas); | |
| } | |
| .canvas-head{ | |
| display:flex;align-items:center;gap:10px;padding:8px 16px; | |
| border-bottom:1px solid var(--border);background:var(--panel2);flex-shrink:0; | |
| } | |
| .seg-control{ | |
| display:inline-flex;background:var(--panel);border:1px solid var(--border); | |
| border-radius:8px;padding:2px;gap:2px; | |
| } | |
| .seg-btn{ | |
| padding:4px 14px;font-size:11px;font-weight:600;color:var(--dim); | |
| background:transparent;border:none;border-radius:6px;cursor:pointer; | |
| font-family:'Inter',sans-serif;transition:all .15s;letter-spacing:.2px; | |
| } | |
| .seg-btn:hover:not(.active){color:var(--muted)} | |
| .seg-btn.active{background:rgba(30,144,255,.18);color:var(--accent2)} | |
| .canvas-meta{ | |
| font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--faint); | |
| overflow:hidden;white-space:nowrap;text-overflow:ellipsis;flex:1; | |
| } | |
| .canvas-actions{display:flex;align-items:center;gap:6px} | |
| .canvas-action-btn{ | |
| display:none;align-items:center;gap:5px;background:rgba(30,144,255,.1); | |
| border:1px solid rgba(30,144,255,.2);border-radius:6px;cursor:pointer;padding:4px 11px; | |
| font-size:11px;font-weight:500;color:var(--accent3);height:26px;transition:all .15s; | |
| font-family:'Inter',sans-serif;white-space:nowrap; | |
| } | |
| .canvas-action-btn:hover{background:rgba(30,144,255,.2);border-color:rgba(30,144,255,.35);color:#fff} | |
| .canvas-action-btn.visible{display:inline-flex} | |
| .canvas-action-btn.active{background:rgba(30,144,255,.3);border-color:var(--accent);color:#fff} | |
| .canvas-action-btn svg{width:12px;height:12px;fill:currentColor} | |
| .out-seed-chip{ | |
| display:none;font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:500; | |
| color:var(--dim);background:var(--panel);border:1px solid var(--border); | |
| border-radius:6px;padding:4px 9px;cursor:pointer;transition:all .15s; | |
| } | |
| .out-seed-chip.visible{display:inline-flex} | |
| .out-seed-chip:hover{color:var(--accent3);border-color:rgba(30,144,255,.35)} | |
| .canvas-stage{ | |
| flex:1;position:relative;overflow:hidden;min-height:0; | |
| display:flex;align-items:center;justify-content:center; | |
| background: | |
| repeating-conic-gradient(#0c0c0f 0% 25%, #08080a 0% 50%) 0 0/24px 24px; | |
| } | |
| .canvas-stage img.modern-out-img{ | |
| max-width:calc(100% - 48px);max-height:calc(100% - 48px); | |
| box-shadow:0 12px 48px rgba(0,0,0,.7),0 0 0 1px var(--border); | |
| border-radius:4px;cursor:zoom-in;image-rendering:auto; | |
| animation:canvasIn .3s ease; | |
| } | |
| @keyframes canvasIn{from{opacity:0;transform:scale(.985)}to{opacity:1;transform:scale(1)}} | |
| .canvas-placeholder{ | |
| text-align:center;color:var(--faint);user-select:none; | |
| display:flex;flex-direction:column;align-items:center;gap:14px;padding:24px; | |
| } | |
| .canvas-placeholder svg{width:72px;height:72px;opacity:.5} | |
| .canvas-placeholder .cp-title{font-size:15px;font-weight:600;color:var(--dim)} | |
| .canvas-placeholder .cp-sub{font-size:12px;line-height:1.7;max-width:340px} | |
| .canvas-placeholder kbd{ | |
| padding:1px 6px;background:var(--panel);border:1px solid var(--border2); | |
| border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--muted); | |
| } | |
| /* Compare slider */ | |
| .compare-wrap{ | |
| position:relative;max-width:calc(100% - 48px);max-height:calc(100% - 48px);overflow:hidden; | |
| display:none;user-select:none;touch-action:none;cursor:ew-resize; | |
| box-shadow:0 12px 48px rgba(0,0,0,.7),0 0 0 1px var(--border);border-radius:4px; | |
| } | |
| .compare-wrap.visible{display:block} | |
| .compare-wrap:focus-visible{outline:2px solid var(--accent);outline-offset:3px} | |
| .compare-wrap img{display:block;max-width:100%;max-height:100%;pointer-events:none} | |
| .compare-top{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden} | |
| .compare-top img{position:absolute;top:0;left:0;height:100%;width:auto;max-width:none!important;max-height:none!important} | |
| .compare-divider{position:absolute;top:0;bottom:0;width:2px;background:#fff;box-shadow:0 0 8px rgba(0,0,0,.6);pointer-events:none} | |
| .compare-handle{ | |
| position:absolute;top:50%;transform:translate(-50%,-50%); | |
| width:32px;height:32px;border-radius:50%;background:#fff; | |
| display:flex;align-items:center;justify-content:center; | |
| box-shadow:0 2px 8px rgba(0,0,0,.5);color:#111;font-size:13px;font-weight:800; | |
| } | |
| .compare-label{ | |
| position:absolute;top:8px;padding:2px 8px;border-radius:4px; | |
| background:rgba(0,0,0,.65);color:#fff;font-size:10px;font-weight:600; | |
| font-family:'JetBrains Mono',monospace;pointer-events:none; | |
| } | |
| .compare-label.before{left:8px} | |
| .compare-label.after{right:8px} | |
| /* Loader */ | |
| .modern-loader{ | |
| display:none;position:absolute;inset:0;background:rgba(8,8,10,.9); | |
| z-index:15;flex-direction:column;align-items:center;justify-content:center;gap:16px;backdrop-filter:blur(5px); | |
| } | |
| .modern-loader.active{display:flex} | |
| .loader-spinner{ | |
| width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent); | |
| border-radius:50%;animation:spin .8s linear infinite; | |
| } | |
| @keyframes spin{to{transform:rotate(360deg)}} | |
| .loader-text{font-size:13px;color:var(--muted);font-weight:500;text-align:center;padding:0 16px} | |
| .loader-bar-track{width:220px;height:4px;background:var(--border);border-radius:2px;overflow:hidden} | |
| .loader-bar-fill{ | |
| height:100%;width:100%;background:linear-gradient(90deg,#1E90FF,#47A3FF,#1E90FF); | |
| background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:2px; | |
| transition:width .3s ease; | |
| } | |
| .loader-bar-fill.determinate{animation:none;background:var(--accent)} | |
| @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}} | |
| /* ══ Filmstrip ══ */ | |
| .filmstrip{ | |
| height:var(--filmstrip-h);flex-shrink:0;background:var(--panel); | |
| border-top:1px solid var(--border);display:flex;min-height:0;overflow:hidden; | |
| } | |
| .filmstrip-section{ | |
| display:flex;flex-direction:column;min-width:0;padding:8px 0 8px 14px; | |
| } | |
| .filmstrip-section.inputs{flex:1.2;border-right:1px solid var(--border)} | |
| .filmstrip-section.history{flex:1} | |
| .filmstrip-label{ | |
| font-size:9px;font-weight:700;color:var(--faint);letter-spacing:1.2px; | |
| text-transform:uppercase;margin-bottom:6px;display:flex;align-items:center;gap:8px; | |
| user-select:none;flex-shrink:0; | |
| } | |
| .filmstrip-label .count{ | |
| font-family:'JetBrains Mono',monospace;font-weight:500;color:var(--dim); | |
| background:var(--panel2);border:1px solid var(--border);border-radius:4px;padding:0 5px;font-size:9px; | |
| } | |
| .filmstrip-row{ | |
| display:flex;gap:8px;overflow-x:auto;overflow-y:hidden;flex:1; | |
| align-items:flex-start;padding-bottom:4px;padding-right:14px; | |
| } | |
| .filmstrip-row::-webkit-scrollbar{height:5px} | |
| .filmstrip-row::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px} | |
| .fs-thumb{ | |
| position:relative;flex-shrink:0;width:76px;height:76px;border-radius:8px;overflow:hidden; | |
| border:2px solid var(--border);cursor:pointer;transition:all .15s;background:var(--panel2); | |
| animation:thumbIn .25s ease; | |
| } | |
| @keyframes thumbIn{from{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}} | |
| .fs-thumb:hover{border-color:var(--border2);transform:translateY(-2px)} | |
| .fs-thumb.selected{border-color:var(--accent);box-shadow:0 0 0 2px rgba(30,144,255,.25)} | |
| .fs-thumb-select{ | |
| display:block;width:100%;height:100%;padding:0;border:0;border-radius:inherit; | |
| background:transparent;color:inherit;cursor:pointer;overflow:hidden; | |
| } | |
| .fs-thumb-select:focus-visible{outline:2px solid var(--accent2);outline-offset:-3px} | |
| .fs-thumb img{width:100%;height:100%;object-fit:cover} | |
| .fs-badge{ | |
| position:absolute;bottom:4px;left:4px;background:rgba(0,0,0,.75);color:#fff; | |
| padding:1px 6px;border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:9px;font-weight:600; | |
| } | |
| .fs-remove{ | |
| position:absolute;top:4px;right:4px;width:20px;height:20px;background:rgba(0,0,0,.8); | |
| color:#fff;border:1px solid rgba(255,255,255,.2);border-radius:50%;cursor:pointer; | |
| display:none;align-items:center;justify-content:center;font-size:10px;transition:all .15s;line-height:1; | |
| } | |
| .fs-thumb:hover .fs-remove{display:flex} | |
| .fs-remove:hover{background:#ef4444;border-color:#ef4444} | |
| .fs-add{ | |
| flex-shrink:0;width:76px;height:76px;border-radius:8px;border:2px dashed var(--border2); | |
| display:flex;flex-direction:column;align-items:center;justify-content:center; | |
| cursor:pointer;transition:all .2s;background:rgba(30,144,255,.03);gap:3px; | |
| } | |
| .fs-add:hover{border-color:var(--accent);background:rgba(30,144,255,.08)} | |
| .fs-add .add-icon{font-size:22px;color:var(--dim);font-weight:300} | |
| .fs-add .add-text{font-size:9px;color:var(--dim);font-weight:600;text-transform:uppercase;letter-spacing:.5px} | |
| .fs-empty{ | |
| flex-shrink:0;display:flex;align-items:center;height:76px; | |
| color:var(--faint);font-size:11px;font-style:italic;padding-right:12px; | |
| } | |
| /* ══ Inspector (right panel) ══ */ | |
| .inspector{ | |
| grid-area:inspector;background:var(--panel);border-left:1px solid var(--border); | |
| display:flex;flex-direction:column;min-height:0;z-index:40; | |
| } | |
| .inspector-tabs{ | |
| display:flex;border-bottom:1px solid var(--border);flex-shrink:0; | |
| } | |
| .insp-tab{ | |
| flex:1;padding:11px 8px;font-size:11px;font-weight:700;color:var(--dim); | |
| background:transparent;border:none;border-bottom:2px solid transparent; | |
| cursor:pointer;font-family:'Inter',sans-serif;text-transform:uppercase;letter-spacing:.8px; | |
| transition:all .15s; | |
| } | |
| .insp-tab:hover:not(.active){color:var(--muted)} | |
| .insp-tab.active{color:var(--accent2);border-bottom-color:var(--accent)} | |
| .insp-page{display:none;flex:1;overflow-y:auto;min-height:0;flex-direction:column} | |
| .insp-page.active{display:flex} | |
| .insp-section{border-bottom:1px solid var(--border);padding:14px 18px} | |
| .insp-section-title{ | |
| font-size:10px;font-weight:700;color:var(--dim);text-transform:uppercase; | |
| letter-spacing:1px;margin-bottom:10px;display:flex;align-items:center;justify-content:space-between; | |
| } | |
| .char-count{font-size:9px;font-weight:500;color:var(--faint);font-family:'JetBrains Mono',monospace;text-transform:none;letter-spacing:0} | |
| .modern-textarea{ | |
| width:100%;background:var(--panel2);border:1px solid var(--border);border-radius:8px; | |
| padding:10px 13px;font-family:'Inter',sans-serif;font-size:13px;color:var(--text); | |
| resize:vertical;outline:none;min-height:64px;transition:border-color .2s;line-height:1.55; | |
| } | |
| .modern-textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(30,144,255,.15)} | |
| .modern-textarea::placeholder{color:var(--faint)} | |
| .modern-textarea.error-flash{ | |
| border-color:#ef4444!important;box-shadow:0 0 0 3px rgba(239,68,68,.2)!important;animation:shake .4s ease; | |
| } | |
| @keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-4px)}40%,80%{transform:translateX(4px)}} | |
| .suggestions-wrap{display:flex;flex-wrap:wrap;gap:5px} | |
| .suggestion-chip{ | |
| display:inline-flex;align-items:center;padding:4px 11px; | |
| background:rgba(30,144,255,.07);border:1px solid rgba(30,144,255,.18);border-radius:14px; | |
| color:var(--accent3);font-size:11px;font-weight:500;font-family:'Inter',sans-serif; | |
| cursor:pointer;transition:all .15s;white-space:nowrap; | |
| } | |
| .suggestion-chip:hover{background:rgba(30,144,255,.15);border-color:rgba(30,144,255,.35);color:var(--accent2)} | |
| .suggestion-chip.active{background:rgba(30,144,255,.25);border-color:var(--accent);color:#fff} | |
| .lora-native-select{ | |
| width:100%;background:var(--panel2); | |
| border:1px solid var(--border2);border-radius:8px; | |
| padding:9px 34px 9px 13px; | |
| font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:var(--text); | |
| outline:none;appearance:none;-webkit-appearance:none; | |
| background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%231E90FF' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); | |
| background-repeat:no-repeat;background-position:right 11px center; | |
| cursor:pointer;transition:border-color .2s;color-scheme:dark; | |
| } | |
| .lora-native-select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(30,144,255,.15)} | |
| .lora-native-select:disabled{opacity:.55;cursor:not-allowed} | |
| .lora-native-select option{background:var(--panel);color:var(--text)} | |
| /* Examples page */ | |
| .examples-list{padding:12px 14px;display:flex;flex-direction:column;gap:10px} | |
| .example-card{ | |
| width:100%;text-align:left;background:var(--panel2);border:1px solid var(--border); | |
| border-radius:10px;overflow:hidden;cursor:pointer;transition:all .2s ease;flex-shrink:0; | |
| color:var(--text);font-family:'Inter',sans-serif;padding:0; | |
| } | |
| .example-card:hover:not(:disabled){border-color:var(--accent);box-shadow:0 4px 14px rgba(30,144,255,.15)} | |
| .example-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px} | |
| .example-card.loading,.example-card:disabled{opacity:.5;pointer-events:none} | |
| .example-thumbs{display:flex;height:96px;overflow:hidden;background:var(--panel)} | |
| .example-thumbs img{flex:1;object-fit:cover;min-width:0;border-bottom:1px solid var(--border)} | |
| .example-meta{padding:6px 10px 2px;display:flex;align-items:center;gap:5px;flex-wrap:wrap} | |
| .example-badge{ | |
| display:inline-flex;padding:2px 7px;background:rgba(30,144,255,.1);border-radius:4px; | |
| font-size:9px;font-weight:600;color:var(--accent2);font-family:'JetBrains Mono',monospace;white-space:nowrap; | |
| } | |
| .example-lora-badge{ | |
| display:inline-flex;padding:2px 7px;background:rgba(255,255,255,.05);border-radius:4px; | |
| font-size:9px;font-weight:600;color:var(--muted);font-family:'JetBrains Mono',monospace; | |
| white-space:nowrap;border:1px solid var(--border);max-width:190px;overflow:hidden;text-overflow:ellipsis; | |
| } | |
| .example-prompt-text{ | |
| padding:4px 10px 9px;font-size:11px;color:var(--muted);line-height:1.45; | |
| display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden; | |
| } | |
| .btn-run{ | |
| display:flex;align-items:center;justify-content:center;gap:8px;width:100%; | |
| background:linear-gradient(135deg,#1E90FF,#1873CC);border:none;border-radius:9px; | |
| padding:12px 24px;cursor:pointer;font-size:14px;font-weight:700;font-family:'Inter',sans-serif; | |
| color:#fff;transition:all .2s ease;letter-spacing:-.2px; | |
| box-shadow:0 4px 16px rgba(30,144,255,.3),inset 0 1px 0 rgba(255,255,255,.1); | |
| } | |
| .btn-run:hover:not(:disabled){ | |
| background:linear-gradient(135deg,#47A3FF,#1E90FF);transform:translateY(-1px); | |
| box-shadow:0 6px 24px rgba(30,144,255,.45),inset 0 1px 0 rgba(255,255,255,.15); | |
| } | |
| .btn-run:active:not(:disabled){transform:translateY(0)} | |
| .btn-run:disabled{opacity:.55;cursor:not-allowed} | |
| .btn-run svg{width:16px;height:16px;fill:#fff} | |
| .btn-cancel{ | |
| display:flex;align-items:center;justify-content:center;gap:6px;width:100%; | |
| margin-top:8px;background:transparent;border:1px solid var(--border2);border-radius:9px; | |
| padding:8px 24px;cursor:pointer;font-size:12px;font-weight:600;font-family:'Inter',sans-serif; | |
| color:var(--muted);transition:all .15s; | |
| } | |
| .btn-cancel:hover{border-color:#ef4444;color:#ef4444;background:rgba(239,68,68,.08)} | |
| .run-hint{ | |
| text-align:center;font-size:10px;color:var(--faint);margin-top:8px; | |
| font-family:'JetBrains Mono',monospace; | |
| } | |
| .slider-row{display:flex;align-items:center;gap:9px;min-height:26px;margin-bottom:10px} | |
| .slider-row:last-child{margin-bottom:0} | |
| .slider-row label{font-size:12px;font-weight:500;color:var(--muted);min-width:64px;flex-shrink:0} | |
| .slider-row input[type="range"]{ | |
| flex:1;-webkit-appearance:none;appearance:none;height:5px;background:var(--border); | |
| border-radius:3px;outline:none;min-width:0; | |
| } | |
| .slider-row input[type="range"]::-webkit-slider-thumb{ | |
| -webkit-appearance:none;width:14px;height:14px;background:linear-gradient(135deg,#1E90FF,#1873CC); | |
| border-radius:50%;cursor:pointer;box-shadow:0 2px 6px rgba(30,144,255,.4);transition:transform .15s; | |
| } | |
| .slider-row input[type="range"]::-webkit-slider-thumb:hover{transform:scale(1.2)} | |
| .slider-row input[type="range"]::-moz-range-thumb{ | |
| width:14px;height:14px;background:linear-gradient(135deg,#1E90FF,#1873CC); | |
| border-radius:50%;cursor:pointer;border:none;box-shadow:0 2px 6px rgba(30,144,255,.4); | |
| } | |
| .slider-val{ | |
| min-width:48px;text-align:right;font-family:'JetBrains Mono',monospace;font-size:11px; | |
| font-weight:500;padding:2px 7px;background:var(--panel2);border:1px solid var(--border); | |
| border-radius:5px;color:var(--muted);flex-shrink:0; | |
| } | |
| .dice-btn{ | |
| display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px; | |
| background:var(--panel2);border:1px solid var(--border);border-radius:5px;cursor:pointer; | |
| color:var(--dim);transition:all .15s;flex-shrink:0;padding:0; | |
| } | |
| .dice-btn:hover{border-color:var(--accent);color:var(--accent3)} | |
| .dice-btn:disabled{opacity:.35;cursor:not-allowed;border-color:var(--border);color:var(--dim)} | |
| .dice-btn svg{width:13px;height:13px;fill:currentColor} | |
| .checkbox-row{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--muted);margin-bottom:10px} | |
| .checkbox-row input[type="checkbox"]{accent-color:var(--accent);width:15px;height:15px;cursor:pointer} | |
| .checkbox-row label{color:var(--muted);font-size:12px;cursor:pointer} | |
| .advanced-section{border-bottom:1px solid var(--border)} | |
| .advanced-section summary{ | |
| padding:14px 18px;cursor:pointer;list-style:none;font-size:10px;font-weight:700; | |
| color:var(--dim);text-transform:uppercase;letter-spacing:1px;user-select:none; | |
| display:flex;align-items:center;justify-content:space-between; | |
| } | |
| .advanced-section summary::-webkit-details-marker{display:none} | |
| .advanced-section summary::after{content:'›';font-size:18px;line-height:1;transition:transform .15s} | |
| .advanced-section[open] summary::after{transform:rotate(90deg)} | |
| .advanced-content{padding:0 18px 14px} | |
| /* ══ Status bar ══ */ | |
| .statusbar{ | |
| grid-area:statusbar;background:var(--panel);border-top:1px solid var(--border); | |
| display:flex;align-items:center;gap:4px;padding:0 14px;font-size:11px;z-index:50; | |
| } | |
| .statusbar .sb-section{ | |
| padding:0 10px;display:flex;align-items:center;font-family:'JetBrains Mono',monospace; | |
| font-size:11px;color:var(--faint);overflow:hidden;white-space:nowrap;gap:6px; | |
| } | |
| .statusbar .sb-right{margin-left:auto;display:flex;align-items:center;gap:10px} | |
| .sb-pill{ | |
| padding:2px 12px;border-radius:5px;font-weight:600;font-size:10px; | |
| background:rgba(30,144,255,.1);color:var(--accent2); | |
| } | |
| .sb-pill.error{background:rgba(239,68,68,.12);color:#f87171} | |
| .sb-pill.done{background:rgba(34,197,94,.12);color:#4ade80} | |
| .sb-link{color:var(--faint);text-decoration:none;font-family:'JetBrains Mono',monospace;font-size:10px} | |
| .sb-link:hover{color:var(--accent3)} | |
| /* ══ Toast ══ */ | |
| .toast-notification{ | |
| position:fixed;top:60px;left:50%;transform:translateX(-50%) translateY(-140%); | |
| z-index:9999;padding:10px 22px;border-radius:9px;font-family:'Inter',sans-serif; | |
| font-size:13px;font-weight:600;display:flex;align-items:center;gap:8px; | |
| box-shadow:0 8px 24px rgba(0,0,0,.5); | |
| transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .35s ease;opacity:0;pointer-events:none; | |
| } | |
| .toast-notification.visible{transform:translateX(-50%) translateY(0);opacity:1;pointer-events:auto} | |
| .toast-notification.error{background:linear-gradient(135deg,#dc2626,#b91c1c);color:#fff;border:1px solid rgba(255,255,255,.15)} | |
| .toast-notification.warning{background:linear-gradient(135deg,#d97706,#b45309);color:#fff;border:1px solid rgba(255,255,255,.15)} | |
| .toast-notification.info{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border:1px solid rgba(255,255,255,.15)} | |
| .toast-notification .toast-icon{font-size:15px;line-height:1} | |
| /* ══ Lightbox ══ */ | |
| .lightbox{ | |
| position:fixed;inset:0;background:rgba(0,0,0,.92);z-index:10000; | |
| display:none;align-items:center;justify-content:center;cursor:zoom-out; | |
| backdrop-filter:blur(6px);animation:fadeIn .2s ease; | |
| } | |
| .lightbox.visible{display:flex} | |
| .lightbox img{max-width:94vw;max-height:94vh;border-radius:6px;box-shadow:0 20px 60px rgba(0,0,0,.8)} | |
| .lightbox-close{ | |
| position:fixed;top:18px;right:18px;width:42px;height:42px;border-radius:50%; | |
| border:1px solid var(--border2);background:rgba(19,19,22,.92);color:var(--text); | |
| font-size:24px;line-height:1;cursor:pointer; | |
| } | |
| .lightbox-close:hover{border-color:var(--accent);color:var(--accent3)} | |
| @keyframes fadeIn{from{opacity:0}to{opacity:1}} | |
| /* ══ Drop overlay ══ */ | |
| .drop-overlay{ | |
| position:fixed;inset:0;z-index:9000;background:rgba(8,8,10,.85); | |
| display:none;align-items:center;justify-content:center;pointer-events:none; | |
| backdrop-filter:blur(3px); | |
| } | |
| .drop-overlay.visible{display:flex} | |
| .drop-overlay-inner{ | |
| border:3px dashed var(--accent);border-radius:22px;padding:56px 76px; | |
| font-size:19px;font-weight:700;color:var(--accent2);background:rgba(30,144,255,.06); | |
| } | |
| /* ══ Scrollbars ══ */ | |
| ::-webkit-scrollbar{width:8px;height:8px} | |
| ::-webkit-scrollbar-track{background:transparent} | |
| ::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px} | |
| ::-webkit-scrollbar-thumb:hover{background:var(--border2)} | |
| /* ══ Touch devices: hover-only controls must always be visible ══ */ | |
| @media (pointer:coarse){ | |
| .fs-remove{display:flex} | |
| .rail-btn{width:44px;height:44px} | |
| .rail-btn svg{width:19px;height:19px} | |
| } | |
| /* ══ Responsive: tablet & phone ══ */ | |
| @media(max-width:960px){ | |
| html,body{height:auto} | |
| body{overflow:auto!important} | |
| .studio{ | |
| display:flex;flex-direction:column;height:auto;min-height:100vh; | |
| } | |
| /* Menu bar */ | |
| .menubar{ | |
| flex-wrap:wrap;gap:8px;padding:10px 12px;min-height:var(--menubar-h); | |
| position:sticky;top:0;order:1; | |
| } | |
| .menubar-title{font-size:13px} | |
| .menubar-status{display:flex;order:6;flex-basis:100%;font-size:10px} | |
| .menubar-spacer{flex:1} | |
| .site-btn{padding:6px 10px} | |
| .site-btn span{display:none} | |
| /* Rail → horizontal tool strip */ | |
| .rail{ | |
| flex-direction:row;justify-content:flex-start;flex-wrap:nowrap; | |
| overflow-x:auto;overflow-y:hidden;border-right:none; | |
| border-bottom:1px solid var(--border);padding:6px 10px;gap:6px;order:2; | |
| } | |
| .rail-sep{width:1px;height:26px;margin:0 4px;flex-shrink:0} | |
| .rail-label{display:none} | |
| .rail-btn{flex-shrink:0} | |
| /* Workspace */ | |
| .workspace{min-height:0;order:4} | |
| .canvas-head{flex-wrap:wrap;gap:8px;padding:8px 12px} | |
| .canvas-meta{flex-basis:100%;order:3;font-size:10px} | |
| .canvas-stage{min-height:42vh} | |
| .canvas-stage img.modern-out-img{max-width:calc(100% - 20px);max-height:52vh} | |
| .compare-wrap{max-width:calc(100% - 20px);max-height:52vh} | |
| .canvas-placeholder .cp-sub{font-size:11px;max-width:280px} | |
| /* Filmstrip → stacked rows, natural height */ | |
| .filmstrip{flex-direction:column;height:auto;max-height:none} | |
| .filmstrip-section.inputs{border-right:none;border-bottom:1px solid var(--border)} | |
| .filmstrip-section{padding:8px 0 8px 12px} | |
| .fs-thumb{width:68px;height:68px} | |
| .fs-add{width:68px;height:68px} | |
| .fs-empty{height:68px} | |
| /* Inspector → full width, natural page scroll */ | |
| .inspector{border-left:none;border-top:1px solid var(--border);order:3} | |
| .inspector-tabs{position:static;background:var(--panel)} | |
| .insp-tab{padding:13px 8px;font-size:12px} | |
| .insp-page{overflow-y:visible} | |
| .insp-section{padding:14px 16px} | |
| .modern-textarea{font-size:16px} /* prevent iOS zoom-on-focus */ | |
| .btn-run{padding:14px 24px;font-size:15px} | |
| .suggestion-chip{padding:6px 13px;font-size:12px} | |
| /* Status bar */ | |
| .statusbar{flex-wrap:wrap;height:auto;min-height:var(--statusbar-h);padding:4px 10px;gap:2px;order:5} | |
| .sb-link{display:none} | |
| /* Toasts span the phone width */ | |
| .toast-notification{max-width:calc(100vw - 32px);font-size:12px;padding:10px 16px} | |
| /* Drop overlay fits small screens */ | |
| .drop-overlay-inner{padding:32px 28px;font-size:15px;border-radius:16px;margin:0 16px} | |
| } | |
| /* ══ Very small phones ══ */ | |
| @media(max-width:400px){ | |
| .menubar-badge.fast{display:none} | |
| .seg-btn{padding:4px 12px} | |
| .canvas-stage{min-height:36vh} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="studio"> | |
| <!-- ══ Menu bar ══ --> | |
| <div class="menubar"> | |
| <div class="menubar-logo"> | |
| <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 23c-3.6 0-8-2.69-8-7.5 0-3.5 3-6.5 4.5-8 .27-.27.75-.08.75.28v2.44c0 .42.5.63.72.28C12.28 7.5 13 3 13 1c0-.42.48-.64.8-.35C18 4.5 20 9 20 12c0 5.5-3.5 11-8 11z"/></svg> | |
| </div> | |
| <div class="menubar-title">Hy3D Studio <span>/ Image Editor</span></div> | |
| <span class="menubar-badge">GENERAL + LORA</span> | |
| <span class="menubar-badge fast">4 STEPS DEFAULT</span> | |
| <div class="menubar-spacer"></div> | |
| <div class="menubar-status"><span class="dot pending" id="conn-dot"></span><span id="conn-text" role="status" aria-live="polite">connecting…</span></div> | |
| <a href="https://hy3d.dev" target="_blank" rel="noopener" class="site-btn"> | |
| <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14 3h7v7"/><path d="M10 14L21 3"/><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"/></svg> | |
| <span>Hy3D</span> | |
| </a> | |
| </div> | |
| <!-- ══ Left icon rail ══ --> | |
| <div class="rail"> | |
| <div class="rail-label">Input</div> | |
| <button class="rail-btn" id="tb-upload" title="Upload image" aria-label="Upload image"> | |
| <svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg> | |
| </button> | |
| <button class="rail-btn" id="tb-remove" title="Remove selected image" aria-label="Remove selected image"> | |
| <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg> | |
| </button> | |
| <button class="rail-btn" id="tb-clear" title="Clear image" aria-label="Clear image"> | |
| <svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg> | |
| </button> | |
| <div class="rail-sep"></div> | |
| <div class="rail-label">Result</div> | |
| <button class="rail-btn" id="compare-btn" title="Compare before / after" aria-label="Compare before and after" aria-pressed="false" disabled> | |
| <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="12" y1="3" x2="12" y2="21"/></svg> | |
| </button> | |
| <button class="rail-btn" id="use-as-input-btn" title="Use result as input (chain edits)" aria-label="Use result as input" disabled> | |
| <svg viewBox="0 0 24 24"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 014-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 01-4 4H3"/></svg> | |
| </button> | |
| <button class="rail-btn" id="dl-btn-output" title="Download result" aria-label="Download result" disabled> | |
| <svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> | |
| </button> | |
| <button class="rail-btn" id="zoom-btn" title="Zoom result" aria-label="Zoom image" disabled> | |
| <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg> | |
| </button> | |
| </div> | |
| <!-- ══ Inspector ══ --> | |
| <div class="inspector"> | |
| <div class="inspector-tabs" role="tablist" aria-label="Image editor panels"> | |
| <button class="insp-tab active" id="tab-edit" type="button" role="tab" aria-selected="true" aria-controls="page-edit">Edit</button> | |
| <button class="insp-tab" id="tab-examples" type="button" role="tab" aria-selected="false" aria-controls="page-examples">Examples</button> | |
| </div> | |
| <!-- Edit page --> | |
| <div class="insp-page active" id="page-edit" role="tabpanel" aria-labelledby="tab-edit"> | |
| <div class="insp-section"> | |
| <div class="insp-section-title"><span>Instruction</span><span class="char-count" id="char-count">0</span></div> | |
| <textarea id="custom-prompt-input" class="modern-textarea" rows="3" maxlength="1000" | |
| placeholder="e.g., replace the background, adjust lighting, remove an object…"></textarea> | |
| </div> | |
| <div class="insp-section"> | |
| <div class="insp-section-title"><span>Quick Prompts</span></div> | |
| <div class="suggestions-wrap" id="suggestions-wrap"></div> | |
| </div> | |
| <div class="insp-section"> | |
| <div class="insp-section-title"><label for="custom-lora-select">Style / LoRA</label></div> | |
| <select id="custom-lora-select" class="lora-native-select" disabled> | |
| <option value="">Connecting…</option> | |
| </select> | |
| </div> | |
| <div class="insp-section"> | |
| <button id="custom-run-btn" class="btn-run" disabled> | |
| <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 23c-3.6 0-8-2.69-8-7.5 0-3.5 3-6.5 4.5-8 .27-.27.75-.08.75.28v2.44c0 .42.5.63.72.28C12.28 7.5 13 3 13 1c0-.42.48-.64.8-.35C18 4.5 20 9 20 12c0 5.5-3.5 11-8 11z"/></svg> | |
| <span id="run-btn-label">Edit Image</span> | |
| </button> | |
| <button id="custom-cancel-btn" class="btn-cancel" style="display:none;">Request queue cancellation</button> | |
| <div class="run-hint">⌘/Ctrl + Enter</div> | |
| </div> | |
| <details class="advanced-section"> | |
| <summary>Advanced</summary> | |
| <div class="advanced-content"> | |
| <div class="slider-row"> | |
| <label for="custom-seed">Seed</label> | |
| <input type="range" id="custom-seed" min="0" max="2147483647" step="1" value="0"> | |
| <span class="slider-val" id="custom-seed-val">0</span> | |
| <button class="dice-btn" id="dice-btn" title="Random seed" aria-label="Choose a random seed"> | |
| <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM7.5 18c-.83 0-1.5-.67-1.5-1.5S6.67 15 7.5 15s1.5.67 1.5 1.5S8.33 18 7.5 18zm0-9C6.67 9 6 8.33 6 7.5S6.67 6 7.5 6 9 6.67 9 7.5 8.33 9 7.5 9zm4.5 4.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5 4.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm0-9c-.83 0-1.5-.67-1.5-1.5S15.67 6 16.5 6s1.5.67 1.5 1.5S17.33 9 16.5 9z"/></svg> | |
| </button> | |
| </div> | |
| <div class="checkbox-row"> | |
| <input type="checkbox" id="custom-randomize" checked> | |
| <label for="custom-randomize">Randomize seed</label> | |
| </div> | |
| <div class="slider-row"> | |
| <label for="custom-guidance">Guidance</label> | |
| <input type="range" id="custom-guidance" min="1" max="10" step="0.1" value="1.0"> | |
| <span class="slider-val" id="custom-guidance-val">1.0</span> | |
| </div> | |
| <div class="slider-row"> | |
| <label for="custom-steps">Steps</label> | |
| <input type="range" id="custom-steps" min="1" max="50" step="1" value="4"> | |
| <span class="slider-val" id="custom-steps-val">4</span> | |
| </div> | |
| </div> | |
| </details> | |
| </div> | |
| <!-- Examples page --> | |
| <div class="insp-page" id="page-examples" role="tabpanel" aria-labelledby="tab-examples"> | |
| <div class="examples-list" id="examples-scroll"></div> | |
| </div> | |
| </div> | |
| <!-- ══ Workspace ══ --> | |
| <div class="workspace"> | |
| <div class="canvas-head"> | |
| <div class="seg-control"> | |
| <button class="seg-btn active" id="seg-result">Result</button> | |
| <button class="seg-btn" id="seg-input">Input</button> | |
| </div> | |
| <div class="canvas-meta" id="canvas-meta">No image loaded</div> | |
| <div class="canvas-actions"> | |
| <span id="out-seed-chip" class="out-seed-chip" title="Click to copy seed"></span> | |
| </div> | |
| </div> | |
| <div class="canvas-stage" id="output-image-container"> | |
| <div class="modern-loader" id="output-loader"> | |
| <div class="loader-spinner"></div> | |
| <div class="loader-text" id="loader-text">Processing image…</div> | |
| <div class="loader-bar-track"><div class="loader-bar-fill" id="loader-bar-fill"></div></div> | |
| </div> | |
| <div class="canvas-placeholder" id="output-placeholder"> | |
| <svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <rect x="8" y="14" width="64" height="52" rx="6" fill="none" stroke="#33333a" stroke-width="2" stroke-dasharray="4 3"/> | |
| <polygon points="12,62 30,40 42,50 54,34 68,62" fill="rgba(30,144,255,0.1)" stroke="#33333a" stroke-width="1.5"/> | |
| <circle cx="28" cy="30" r="6" fill="rgba(30,144,255,0.12)" stroke="#33333a" stroke-width="1.5"/> | |
| </svg> | |
| <div class="cp-title">Canvas is empty</div> | |
| <div class="cp-sub"> | |
| Drop up to two images anywhere, paste with <kbd>⌘/Ctrl+V</kbd>, or use the rail to upload. | |
| Then write an instruction and press <kbd>⌘/Ctrl</kbd>+<kbd>↵</kbd>. | |
| </div> | |
| </div> | |
| <div class="compare-wrap" id="compare-wrap" role="slider" tabindex="0" aria-label="Before and after comparison" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"> | |
| <img id="compare-after-img" alt="after"> | |
| <div class="compare-top" id="compare-top"><img id="compare-before-img" alt="before"></div> | |
| <div class="compare-divider" id="compare-divider"><div class="compare-handle">⇄</div></div> | |
| <span class="compare-label before">Before</span> | |
| <span class="compare-label after">After</span> | |
| </div> | |
| </div> | |
| <!-- Filmstrip --> | |
| <div class="filmstrip"> | |
| <div class="filmstrip-section inputs"> | |
| <div class="filmstrip-label">Inputs <span class="count" id="tb-image-count">0</span></div> | |
| <div class="filmstrip-row" id="image-gallery-grid"></div> | |
| </div> | |
| <div class="filmstrip-section history"> | |
| <div class="filmstrip-label">History <span class="count" id="history-count">0</span></div> | |
| <div class="filmstrip-row" id="history-strip"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ══ Status bar ══ --> | |
| <div class="statusbar"> | |
| <div class="sb-section" id="sb-image-count">No inputs</div> | |
| <div class="sb-right"> | |
| <span class="sb-link" id="sb-edit-mode">General / No LoRA</span> | |
| <span class="sb-pill" id="sb-status" role="status" aria-live="polite" aria-atomic="true">Connecting…</span> | |
| </div> | |
| </div> | |
| </div> | |
| <input id="custom-file-input" type="file" accept="image/png,image/jpeg,image/webp" multiple style="display:none;" /> | |
| <!-- Lightbox --> | |
| <div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Image preview" aria-hidden="true" tabindex="-1"> | |
| <button type="button" class="lightbox-close" id="lightbox-close" aria-label="Close image preview">×</button> | |
| <img id="lightbox-img" alt="Expanded image preview"> | |
| </div> | |
| <!-- Global drop overlay --> | |
| <div class="drop-overlay" id="drop-overlay"><div class="drop-overlay-inner">Drop up to two images to upload</div></div> | |
| <script type="module" src="/hy3d-assets/app-ui.js"></script> | |
| </body> | |
| </html> | |