Upload index (39).html
Browse files- index (39).html +826 -0
index (39).html
ADDED
|
@@ -0,0 +1,826 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ko">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Proto-AGI FACE — AI 성형 시뮬레이터</title>
|
| 7 |
+
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
| 8 |
+
<style>
|
| 9 |
+
*{margin:0;padding:0;box-sizing:border-box;}
|
| 10 |
+
:root{
|
| 11 |
+
--bg:#f8f9fc;--bg-deep:#f0f2f8;--surface:#ffffff;--surface-alt:#f5f6fa;
|
| 12 |
+
--border:#e2e5f0;--border-hover:#c7cce0;
|
| 13 |
+
--shadow-sm:0 1px 3px rgba(15,23,42,0.04),0 1px 2px rgba(15,23,42,0.06);
|
| 14 |
+
--shadow:0 4px 16px rgba(15,23,42,0.06),0 1px 3px rgba(15,23,42,0.08);
|
| 15 |
+
--shadow-lg:0 12px 40px rgba(15,23,42,0.08),0 4px 12px rgba(15,23,42,0.06);
|
| 16 |
+
--text:#0f172a;--text-sec:#475569;--text-muted:#94a3b8;
|
| 17 |
+
--accent:#6366f1;--accent-light:#818cf8;--accent-bg:rgba(99,102,241,0.06);
|
| 18 |
+
--teal:#0d9488;--teal-bg:rgba(13,148,136,0.06);
|
| 19 |
+
--rose:#e11d48;--rose-bg:rgba(225,29,72,0.06);
|
| 20 |
+
--amber:#d97706;--amber-bg:rgba(217,119,6,0.06);
|
| 21 |
+
--green:#16a34a;
|
| 22 |
+
--radius:16px;--radius-sm:10px;--radius-xs:6px;
|
| 23 |
+
--font-display:'Sora','Noto Sans KR',sans-serif;
|
| 24 |
+
--font-body:'Noto Sans KR','Sora',sans-serif;
|
| 25 |
+
--font-mono:'JetBrains Mono',monospace;
|
| 26 |
+
--tr:0.25s cubic-bezier(0.4,0,0.2,1);
|
| 27 |
+
}
|
| 28 |
+
html{scroll-behavior:smooth;}
|
| 29 |
+
body{font-family:var(--font-body);background:var(--bg);color:var(--text);min-height:100vh;overflow-x:hidden;-webkit-font-smoothing:antialiased;}
|
| 30 |
+
::-webkit-scrollbar{width:6px;}::-webkit-scrollbar-track{background:transparent;}
|
| 31 |
+
::-webkit-scrollbar-thumb{background:rgba(99,102,241,0.2);border-radius:10px;}
|
| 32 |
+
::selection{background:rgba(99,102,241,0.15);}
|
| 33 |
+
|
| 34 |
+
.bg-pattern{position:fixed;inset:0;z-index:0;pointer-events:none;
|
| 35 |
+
background:radial-gradient(ellipse 80% 50% at 20% 10%,rgba(99,102,241,0.04),transparent 50%),
|
| 36 |
+
radial-gradient(ellipse 60% 40% at 80% 90%,rgba(13,148,136,0.03),transparent 50%);}
|
| 37 |
+
|
| 38 |
+
.app-wrapper{position:relative;z-index:1;display:flex;flex-direction:column;min-height:100vh;max-width:960px;margin:0 auto;padding:24px 24px 48px;}
|
| 39 |
+
|
| 40 |
+
/* HEADER */
|
| 41 |
+
.header{text-align:center;padding:40px 0 28px;animation:fadeIn .8s ease-out;}
|
| 42 |
+
@keyframes fadeIn{from{opacity:0;transform:translateY(-16px)}to{opacity:1;transform:translateY(0)}}
|
| 43 |
+
.header-eyebrow{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:5px;text-transform:uppercase;color:var(--accent);margin-bottom:10px;}
|
| 44 |
+
.header-title{font-family:var(--font-display);font-size:44px;font-weight:800;line-height:1.15;letter-spacing:-1.5px;margin-bottom:12px;}
|
| 45 |
+
.gradient-text{background:linear-gradient(135deg,#6366f1,#e11d48 40%,#0d9488 70%,#6366f1);background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:shimmer 6s ease-in-out infinite;}
|
| 46 |
+
@keyframes shimmer{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
| 47 |
+
.header-sub{font-size:13px;color:var(--text-sec);line-height:1.7;max-width:600px;margin:0 auto 16px;}
|
| 48 |
+
.header-sub b{color:var(--text);font-weight:600;}
|
| 49 |
+
.pill-row{display:flex;gap:6px;justify-content:center;flex-wrap:wrap;animation:fadeUp .8s .2s ease-out both;}
|
| 50 |
+
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
| 51 |
+
.pill{padding:4px 12px;background:var(--surface);border:1px solid var(--border);border-radius:20px;font-family:var(--font-mono);font-size:10px;font-weight:500;color:var(--text-muted);transition:var(--tr);box-shadow:var(--shadow-sm);}
|
| 52 |
+
.pill:hover{border-color:var(--accent);color:var(--accent);}
|
| 53 |
+
|
| 54 |
+
/* NAV */
|
| 55 |
+
.nav{display:flex;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:4px;margin-bottom:24px;position:sticky;top:12px;z-index:100;box-shadow:var(--shadow);animation:fadeUp .8s .3s ease-out both;}
|
| 56 |
+
.nav-item{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:12px 8px;border-radius:var(--radius-sm);cursor:pointer;font-size:12.5px;font-weight:600;color:var(--text-muted);transition:var(--tr);user-select:none;}
|
| 57 |
+
.nav-item:hover{color:var(--text-sec);background:var(--surface-alt);}
|
| 58 |
+
.nav-item.active{color:#fff;background:linear-gradient(135deg,#6366f1,#4f46e5);box-shadow:0 4px 16px rgba(99,102,241,0.3),inset 0 1px 0 rgba(255,255,255,0.15);}
|
| 59 |
+
.nav-icon{font-size:14px;}
|
| 60 |
+
|
| 61 |
+
/* PANELS */
|
| 62 |
+
.panel{display:none;animation:panelIn .4s ease-out;}.panel.active{display:block;}
|
| 63 |
+
@keyframes panelIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
| 64 |
+
|
| 65 |
+
/* CARD */
|
| 66 |
+
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px;margin-bottom:20px;box-shadow:var(--shadow-sm);transition:all .3s;}
|
| 67 |
+
.card:hover{box-shadow:var(--shadow);border-color:var(--border-hover);}
|
| 68 |
+
.card-header{display:flex;align-items:center;gap:12px;margin-bottom:6px;}
|
| 69 |
+
.card-icon{width:38px;height:38px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}
|
| 70 |
+
.card-icon.purple{background:var(--accent-bg);border:1px solid rgba(99,102,241,0.15);}
|
| 71 |
+
.card-icon.teal{background:var(--teal-bg);border:1px solid rgba(13,148,136,0.15);}
|
| 72 |
+
.card-icon.rose{background:var(--rose-bg);border:1px solid rgba(225,29,72,0.15);}
|
| 73 |
+
.card-icon.amber{background:var(--amber-bg);border:1px solid rgba(217,119,6,0.15);}
|
| 74 |
+
.card-title{font-family:var(--font-display);font-size:17px;font-weight:700;}
|
| 75 |
+
.card-desc{font-size:12.5px;color:var(--text-muted);line-height:1.65;margin-bottom:18px;padding-left:50px;}
|
| 76 |
+
|
| 77 |
+
/* FORM */
|
| 78 |
+
.form-row{display:flex;gap:12px;margin-bottom:14px;flex-wrap:wrap;}
|
| 79 |
+
.form-group{flex:1;min-width:180px;}
|
| 80 |
+
.form-label{display:block;font-size:11px;font-weight:600;color:var(--text-sec);margin-bottom:6px;letter-spacing:0.3px;}
|
| 81 |
+
select,input[type=password]{width:100%;padding:10px 14px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:var(--radius-xs);color:var(--text);font-family:var(--font-body);font-size:13px;outline:none;transition:border-color .3s,box-shadow .3s;-webkit-appearance:none;}
|
| 82 |
+
select:focus,input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,102,241,0.1);}
|
| 83 |
+
textarea{width:100%;min-height:80px;padding:12px 14px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:var(--radius-xs);color:var(--text);font-family:var(--font-body);font-size:13px;line-height:1.7;resize:vertical;outline:none;transition:border-color .3s,box-shadow .3s;}
|
| 84 |
+
textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,102,241,0.1);}
|
| 85 |
+
textarea::placeholder{color:var(--text-muted);font-size:12px;}
|
| 86 |
+
|
| 87 |
+
/* UPLOAD */
|
| 88 |
+
.upload-zone{position:relative;border:2px dashed var(--border);border-radius:var(--radius);padding:40px 20px;text-align:center;cursor:pointer;transition:all .3s;background:var(--surface-alt);overflow:hidden;}
|
| 89 |
+
.upload-zone:hover{border-color:var(--accent);background:rgba(99,102,241,0.03);}
|
| 90 |
+
.upload-zone.has-image{border-style:solid;border-color:var(--teal);background:#fff;padding:8px;}
|
| 91 |
+
.upload-icon{font-size:36px;margin-bottom:8px;opacity:.6;}
|
| 92 |
+
.upload-text{font-size:12px;color:var(--text-muted);}
|
| 93 |
+
.upload-text b{color:var(--text-sec);}
|
| 94 |
+
.upload-zone input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer;}
|
| 95 |
+
.upload-zone img{width:100%;max-height:350px;object-fit:contain;border-radius:var(--radius-sm);}
|
| 96 |
+
|
| 97 |
+
/* BUTTONS */
|
| 98 |
+
.btn-group{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:18px;}
|
| 99 |
+
.btn{padding:12px 24px;border:none;border-radius:var(--radius-sm);cursor:pointer;font-family:var(--font-body);font-weight:600;font-size:13px;transition:all .25s;letter-spacing:0.2px;}
|
| 100 |
+
.btn-primary{background:linear-gradient(135deg,#e11d48,#be123c);color:#fff;box-shadow:0 4px 16px rgba(225,29,72,0.25);width:100%;padding:14px;}
|
| 101 |
+
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(225,29,72,0.35);}
|
| 102 |
+
.btn-primary:active{transform:translateY(0);}.btn-primary:disabled{opacity:0.45;cursor:not-allowed;transform:none!important;}
|
| 103 |
+
|
| 104 |
+
/* RADIO GROUP */
|
| 105 |
+
.radio-group{display:flex;gap:6px;flex-wrap:wrap;}
|
| 106 |
+
.radio-group label{display:flex;align-items:center;gap:5px;padding:7px 14px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:20px;cursor:pointer;font-size:11.5px;font-weight:500;color:var(--text-sec);transition:all .2s;}
|
| 107 |
+
.radio-group input{display:none;}
|
| 108 |
+
.radio-group input:checked+label{background:var(--accent-bg);border-color:var(--accent);color:var(--accent);font-weight:700;box-shadow:0 2px 8px rgba(99,102,241,0.15);}
|
| 109 |
+
|
| 110 |
+
/* ═══════════════════════════════════════
|
| 111 |
+
FACE SCAN ANIMATION — 얼굴 마스크 레이저
|
| 112 |
+
═══════════════════════════════════════ */
|
| 113 |
+
.scan-overlay{display:none;position:relative;padding:60px 20px;text-align:center;background:linear-gradient(180deg,#0f172a 0%,#1e1b4b 50%,#0f172a 100%);border-radius:var(--radius);overflow:hidden;margin-bottom:20px;}
|
| 114 |
+
.scan-overlay.active{display:block;animation:scanFadeIn .6s ease-out;}
|
| 115 |
+
@keyframes scanFadeIn{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}
|
| 116 |
+
|
| 117 |
+
/* Particle field background */
|
| 118 |
+
.scan-particles{position:absolute;inset:0;overflow:hidden;}
|
| 119 |
+
.scan-particles span{position:absolute;width:2px;height:2px;background:#818cf8;border-radius:50%;opacity:0;animation:particleDrift 4s linear infinite;}
|
| 120 |
+
@keyframes particleDrift{
|
| 121 |
+
0%{opacity:0;transform:translateY(100%) scale(0);}
|
| 122 |
+
20%{opacity:.6;}
|
| 123 |
+
80%{opacity:.4;}
|
| 124 |
+
100%{opacity:0;transform:translateY(-100%) scale(1.5);}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
/* SVG Face mask container */
|
| 128 |
+
.face-container{position:relative;width:220px;height:280px;margin:0 auto 24px;}
|
| 129 |
+
|
| 130 |
+
/* The face mask SVG */
|
| 131 |
+
.face-mask{width:100%;height:100%;filter:drop-shadow(0 0 20px rgba(99,102,241,0.3));}
|
| 132 |
+
.face-outline{fill:none;stroke:#818cf8;stroke-width:1.5;stroke-linecap:round;opacity:0;animation:drawFace 3s ease-out forwards;}
|
| 133 |
+
@keyframes drawFace{
|
| 134 |
+
0%{stroke-dasharray:1200;stroke-dashoffset:1200;opacity:0;}
|
| 135 |
+
10%{opacity:1;}
|
| 136 |
+
100%{stroke-dashoffset:0;opacity:1;}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
/* Horizontal laser scan line */
|
| 140 |
+
.laser-line{position:absolute;left:10%;right:10%;height:2px;background:linear-gradient(90deg,transparent,#e11d48,#f97316,#e11d48,transparent);box-shadow:0 0 15px 3px rgba(225,29,72,0.4),0 0 40px 8px rgba(225,29,72,0.15);border-radius:2px;animation:laserScan 2.8s ease-in-out infinite;z-index:5;}
|
| 141 |
+
@keyframes laserScan{
|
| 142 |
+
0%{top:8%;opacity:0;}
|
| 143 |
+
5%{opacity:1;}
|
| 144 |
+
50%{top:88%;opacity:1;}
|
| 145 |
+
55%{opacity:0;}
|
| 146 |
+
100%{top:8%;opacity:0;}
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
/* Grid overlay */
|
| 150 |
+
.scan-grid{position:absolute;inset:0;background:
|
| 151 |
+
repeating-linear-gradient(0deg,transparent,transparent 19px,rgba(99,102,241,0.06) 19px,rgba(99,102,241,0.06) 20px),
|
| 152 |
+
repeating-linear-gradient(90deg,transparent,transparent 19px,rgba(99,102,241,0.06) 19px,rgba(99,102,241,0.06) 20px);
|
| 153 |
+
animation:gridPulse 3s ease-in-out infinite;}
|
| 154 |
+
@keyframes gridPulse{0%,100%{opacity:.3}50%{opacity:.7}}
|
| 155 |
+
|
| 156 |
+
/* Corner brackets */
|
| 157 |
+
.scan-corner{position:absolute;width:24px;height:24px;z-index:6;}
|
| 158 |
+
.scan-corner::before,.scan-corner::after{content:'';position:absolute;background:#0d9488;border-radius:1px;}
|
| 159 |
+
.scan-corner.tl{top:0;left:0;}.scan-corner.tr{top:0;right:0;}.scan-corner.bl{bottom:0;left:0;}.scan-corner.br{bottom:0;right:0;}
|
| 160 |
+
.scan-corner.tl::before,.scan-corner.tr::before{top:0;height:2px;width:16px;}
|
| 161 |
+
.scan-corner.tl::after,.scan-corner.bl::after{left:0;width:2px;height:16px;}
|
| 162 |
+
.scan-corner.tr::before{right:0;left:auto;}.scan-corner.tr::after{right:0;width:2px;height:16px;}
|
| 163 |
+
.scan-corner.bl::before{bottom:0;top:auto;height:2px;width:16px;}.scan-corner.bl::after{bottom:0;top:auto;}
|
| 164 |
+
.scan-corner.br::before{bottom:0;right:0;top:auto;left:auto;height:2px;width:16px;}.scan-corner.br::after{right:0;bottom:0;top:auto;width:2px;height:16px;}
|
| 165 |
+
|
| 166 |
+
/* Data readout */
|
| 167 |
+
.scan-data{position:absolute;font-family:var(--font-mono);font-size:9px;color:#818cf8;opacity:0;animation:dataFlash 4s ease-in-out infinite;white-space:nowrap;}
|
| 168 |
+
@keyframes dataFlash{0%,100%{opacity:0;}20%{opacity:.8;}40%{opacity:.3;}60%{opacity:.9;}80%{opacity:0;}}
|
| 169 |
+
|
| 170 |
+
/* Status text */
|
| 171 |
+
.scan-status{position:relative;z-index:10;}
|
| 172 |
+
.scan-status-main{font-family:var(--font-display);font-size:16px;font-weight:700;color:#e2e8f0;margin-bottom:6px;animation:breathe 2s ease-in-out infinite;}
|
| 173 |
+
@keyframes breathe{0%,100%{opacity:1}50%{opacity:.5}}
|
| 174 |
+
.scan-status-sub{font-family:var(--font-mono);font-size:11px;color:#818cf8;letter-spacing:1px;}
|
| 175 |
+
.scan-progress-bar{width:60%;margin:14px auto 0;height:3px;background:rgba(99,102,241,0.15);border-radius:3px;overflow:hidden;}
|
| 176 |
+
.scan-progress-fill{height:100%;background:linear-gradient(90deg,#6366f1,#e11d48,#0d9488);background-size:200% 100%;border-radius:3px;animation:progressShimmer 2s linear infinite;transition:width 0.5s ease;}
|
| 177 |
+
@keyframes progressShimmer{0%{background-position:100% 0}100%{background-position:-100% 0}}
|
| 178 |
+
|
| 179 |
+
/* Pulsing rings around face */
|
| 180 |
+
.pulse-ring{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border:1px solid rgba(99,102,241,0.3);border-radius:50%;animation:pulseExpand 3s ease-out infinite;}
|
| 181 |
+
@keyframes pulseExpand{0%{width:100px;height:130px;opacity:.6;}100%{width:280px;height:360px;opacity:0;}}
|
| 182 |
+
|
| 183 |
+
/* ═══ RESULTS ═══ */
|
| 184 |
+
.result-section{margin-bottom:20px;animation:rIn .5s ease-out;}
|
| 185 |
+
@keyframes rIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
| 186 |
+
.result-image-wrap{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);}
|
| 187 |
+
.result-image-wrap img{width:100%;display:block;}
|
| 188 |
+
.result-label{padding:12px 16px;font-size:12px;font-weight:600;color:var(--text-sec);border-top:1px solid var(--border);display:flex;align-items:center;gap:6px;}
|
| 189 |
+
.result-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:20px;}
|
| 190 |
+
.result-grid .result-image-wrap img{max-height:300px;object-fit:contain;}
|
| 191 |
+
|
| 192 |
+
/* Before/After slider container */
|
| 193 |
+
.ba-container{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:20px;box-shadow:var(--shadow-sm);}
|
| 194 |
+
.ba-container .ba-label{font-size:13px;font-weight:700;color:var(--text);margin-bottom:10px;display:flex;align-items:center;gap:8px;}
|
| 195 |
+
|
| 196 |
+
/* Report */
|
| 197 |
+
.report-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;box-shadow:var(--shadow-sm);margin-bottom:20px;}
|
| 198 |
+
.report-card h1,.report-card h2,.report-card h3{font-family:var(--font-display);margin-top:16px;margin-bottom:8px;color:var(--text);}
|
| 199 |
+
.report-card h1{font-size:18px;}.report-card h2{font-size:15px;}.report-card h3{font-size:13px;}
|
| 200 |
+
.report-card p,.report-card li{font-size:13px;line-height:1.8;color:var(--text-sec);}
|
| 201 |
+
.report-card blockquote{border-left:3px solid var(--accent);padding:8px 14px;background:var(--accent-bg);border-radius:0 var(--radius-xs) var(--radius-xs) 0;margin:12px 0;font-size:12px;color:var(--accent);}
|
| 202 |
+
.report-card hr{border:none;border-top:1px solid var(--border);margin:16px 0;}
|
| 203 |
+
|
| 204 |
+
/* Error */
|
| 205 |
+
.error-msg{padding:16px 20px;text-align:center;color:var(--rose);font-size:13px;background:var(--surface);border:1px solid rgba(225,29,72,0.2);border-radius:var(--radius);margin-bottom:16px;}
|
| 206 |
+
.error-msg:empty{display:none;}
|
| 207 |
+
|
| 208 |
+
/* HERO */
|
| 209 |
+
.hero-card{background:linear-gradient(135deg,#4f46e5,#6366f1 30%,#e11d48 70%,#be123c);border-radius:var(--radius);padding:32px 28px;margin-bottom:20px;position:relative;overflow:hidden;animation:fadeUp .6s ease-out;}
|
| 210 |
+
.hero-card::before{content:'';position:absolute;top:-60%;right:-20%;width:50%;height:200%;background:radial-gradient(circle,rgba(255,255,255,0.08),transparent 60%);transform:rotate(20deg);}
|
| 211 |
+
.hero-title{font-family:var(--font-display);font-size:22px;font-weight:900;color:#fff;position:relative;}
|
| 212 |
+
.hero-sub{font-size:11.5px;color:rgba(255,255,255,0.8);line-height:1.7;margin-top:6px;position:relative;}
|
| 213 |
+
.hero-badge{display:inline-block;background:rgba(255,255,255,0.2);backdrop-filter:blur(8px);color:#fff;font-size:10px;font-weight:800;padding:5px 14px;border-radius:20px;margin-top:10px;animation:cmpPulse 2.5s ease-in-out infinite;position:relative;border:1px solid rgba(255,255,255,0.25);}
|
| 214 |
+
@keyframes cmpPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.03)}}
|
| 215 |
+
|
| 216 |
+
/* SOMA stats */
|
| 217 |
+
.soma-stats{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin-bottom:20px;animation:fadeUp .8s .1s ease-out both;}
|
| 218 |
+
.soma-stat{background:var(--surface);border:1.5px solid var(--border);border-radius:var(--radius-sm);padding:14px 6px;text-align:center;transition:all .3s;box-shadow:var(--shadow-sm);}
|
| 219 |
+
.soma-stat:hover{transform:translateY(-3px);border-color:var(--accent);box-shadow:var(--shadow);}
|
| 220 |
+
.soma-stat-icon{font-size:20px;margin-bottom:4px;}
|
| 221 |
+
.soma-stat-label{font-size:8.5px;color:var(--text-muted);font-weight:600;line-height:1.3;}
|
| 222 |
+
|
| 223 |
+
/* FOOTER */
|
| 224 |
+
.footer{text-align:center;margin-top:48px;padding:24px 0;border-top:1px solid var(--border);}
|
| 225 |
+
.footer-brand{font-family:var(--font-display);font-size:13px;font-weight:600;color:var(--text-muted);margin-bottom:4px;}
|
| 226 |
+
.footer-dev{font-size:11px;color:var(--text-muted);margin-bottom:6px;}
|
| 227 |
+
.footer-dev a{color:var(--accent);text-decoration:none;font-weight:600;}
|
| 228 |
+
.footer-line{width:40px;height:2px;background:linear-gradient(90deg,transparent,var(--accent),var(--rose),transparent);margin:10px auto;border-radius:2px;opacity:0.4;}
|
| 229 |
+
.footer-tech{font-family:var(--font-mono);font-size:9px;color:var(--text-muted);opacity:0.6;letter-spacing:1px;}
|
| 230 |
+
|
| 231 |
+
/* API Key toggle */
|
| 232 |
+
.key-toggle{font-size:11px;color:var(--accent);cursor:pointer;font-weight:600;margin-bottom:12px;display:inline-flex;align-items:center;gap:4px;}
|
| 233 |
+
.key-toggle:hover{text-decoration:underline;}
|
| 234 |
+
.key-section{display:none;margin-bottom:14px;padding:16px;background:var(--surface-alt);border-radius:var(--radius-sm);border:1px solid var(--border);}
|
| 235 |
+
.key-section.open{display:block;}
|
| 236 |
+
|
| 237 |
+
/* Pipeline indicator */
|
| 238 |
+
.pipeline{display:flex;align-items:center;gap:0;margin:14px 0;flex-wrap:wrap;justify-content:center;}
|
| 239 |
+
.pipeline-step{display:flex;align-items:center;gap:4px;padding:5px 10px;background:var(--surface-alt);border:1px solid var(--border);border-radius:16px;font-size:9.5px;font-weight:600;color:var(--text-muted);transition:all .3s;}
|
| 240 |
+
.pipeline-step.active{background:var(--accent-bg);border-color:var(--accent);color:var(--accent);}
|
| 241 |
+
.pipeline-step.done{background:rgba(13,148,136,0.08);border-color:var(--teal);color:var(--teal);}
|
| 242 |
+
.pipeline-arrow{color:var(--border);font-size:12px;margin:0 2px;}
|
| 243 |
+
|
| 244 |
+
/* Responsive */
|
| 245 |
+
@media(max-width:640px){
|
| 246 |
+
.app-wrapper{padding:12px 12px 32px;}.header{padding:24px 0 16px;}.header-title{font-size:32px;}
|
| 247 |
+
.nav{flex-wrap:wrap;position:static;}.nav-item{font-size:10.5px;padding:10px 4px;}
|
| 248 |
+
.card{padding:20px 16px;}.card-desc{padding-left:0;margin-top:8px;}
|
| 249 |
+
.form-row{flex-direction:column;gap:10px;}.form-group{min-width:100%;}
|
| 250 |
+
.soma-stats{grid-template-columns:repeat(3,1fr);}.hero-title{font-size:18px;}
|
| 251 |
+
.result-grid{grid-template-columns:1fr;}
|
| 252 |
+
.face-container{width:160px;height:210px;}
|
| 253 |
+
}
|
| 254 |
+
</style>
|
| 255 |
+
</head>
|
| 256 |
+
<body>
|
| 257 |
+
<div class="bg-pattern"></div>
|
| 258 |
+
<div class="app-wrapper">
|
| 259 |
+
|
| 260 |
+
<!-- HEADER -->
|
| 261 |
+
<header class="header">
|
| 262 |
+
<div class="header-eyebrow">SOMA Multi-Agent System</div>
|
| 263 |
+
<h1 class="header-title"><span class="gradient-text">Proto-AGI FACE</span></h1>
|
| 264 |
+
<p class="header-sub"><b>5 AI 에이전트 협업</b> · <b>성형 시뮬레이션</b> · <b>관상 분석</b> · <b>궁합 분석</b> · <b>Before/After</b></p>
|
| 265 |
+
<div class="pill-row">
|
| 266 |
+
<span class="pill" style="background:linear-gradient(135deg,rgba(225,29,72,0.08),rgba(249,115,22,0.08));border-color:rgba(225,29,72,0.25);color:#e11d48;font-weight:700;">🏥 VIDRAFT</span>
|
| 267 |
+
<span class="pill">🩺 의사 AI</span><span class="pill">🔮 관상 AI</span>
|
| 268 |
+
<span class="pill">💡 창발 AI</span><span class="pill">⚖️ 비평 AI</span><span class="pill">🎯 감독 AI</span>
|
| 269 |
+
</div>
|
| 270 |
+
</header>
|
| 271 |
+
|
| 272 |
+
<!-- NAV -->
|
| 273 |
+
<nav class="nav">
|
| 274 |
+
<div class="nav-item active" data-tab="simulate" onclick="switchTab('simulate')"><span class="nav-icon">✨</span><span>시뮬레이션</span></div>
|
| 275 |
+
<div class="nav-item" data-tab="soma" onclick="switchTab('soma')"><span class="nav-icon">🧠</span><span>SOMA 시스템</span></div>
|
| 276 |
+
<div class="nav-item" data-tab="guide" onclick="switchTab('guide')"><span class="nav-icon">📖</span><span>사용법</span></div>
|
| 277 |
+
</nav>
|
| 278 |
+
|
| 279 |
+
<!-- ═══ 시뮬레이션 탭 ═══ -->
|
| 280 |
+
<div class="panel active" id="panel-simulate">
|
| 281 |
+
|
| 282 |
+
<!-- Hero -->
|
| 283 |
+
<div class="hero-card">
|
| 284 |
+
<div class="hero-title">🏥 AI 성형 시뮬레이션</div>
|
| 285 |
+
<div class="hero-sub">SOMA 파이프라인: 5개 AI 에이전트가 협업하여<br>의학적 분석 → 프롬프트 최적화 → 이미지 생성 → 품질 평가 → 리포트 작성</div>
|
| 286 |
+
<div class="hero-badge">🔬 Nano Banana 2 Edit × SOMA 5-Agent</div>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<!-- SOMA Agent Stats -->
|
| 290 |
+
<div class="soma-stats">
|
| 291 |
+
<div class="soma-stat"><div class="soma-stat-icon">🩺</div><div class="soma-stat-label">의사 AI<br>얼굴 분석</div></div>
|
| 292 |
+
<div class="soma-stat"><div class="soma-stat-icon">💡</div><div class="soma-stat-label">창발 AI<br>프롬프트</div></div>
|
| 293 |
+
<div class="soma-stat"><div class="soma-stat-icon">🎨</div><div class="soma-stat-label">이미지<br>생성 (1K)</div></div>
|
| 294 |
+
<div class="soma-stat"><div class="soma-stat-icon">⚖️</div><div class="soma-stat-label">비평 AI<br>품질 평가</div></div>
|
| 295 |
+
<div class="soma-stat"><div class="soma-stat-icon">🎯</div><div class="soma-stat-label">감독 AI<br>종합 리포트</div></div>
|
| 296 |
+
</div>
|
| 297 |
+
|
| 298 |
+
<!-- Input Card -->
|
| 299 |
+
<div class="card">
|
| 300 |
+
<div class="card-header"><div class="card-icon rose">🏥</div><div class="card-title">시술 시뮬레이션</div></div>
|
| 301 |
+
<div class="card-desc">얼굴 사진을 업로드하고, 원하는 시술을 선택하세요. SOMA 5-Agent가 분석부터 리포트까지 자동 수행합니다.</div>
|
| 302 |
+
|
| 303 |
+
<!-- API Keys -->
|
| 304 |
+
<div class="key-toggle" onclick="toggleKeys()">🔑 API Keys 설정 ▾</div>
|
| 305 |
+
<div class="key-section" id="key-section">
|
| 306 |
+
<div class="form-row">
|
| 307 |
+
<div class="form-group">
|
| 308 |
+
<div class="form-label">FAL API Key (이미지 생성)</div>
|
| 309 |
+
<input type="password" id="fal-key" placeholder="fal_... (환경변수 FAL_KEY 우선)">
|
| 310 |
+
</div>
|
| 311 |
+
<div class="form-group">
|
| 312 |
+
<div class="form-label">Fireworks API Key (VLM 분석)</div>
|
| 313 |
+
<input type="password" id="fw-key" placeholder="fw_... (환경변수 FIREWORKS_API_KEY 우선)">
|
| 314 |
+
</div>
|
| 315 |
+
</div>
|
| 316 |
+
</div>
|
| 317 |
+
|
| 318 |
+
<!-- Upload -->
|
| 319 |
+
<div class="upload-zone" id="upload-zone" onclick="document.getElementById('file-input').click()">
|
| 320 |
+
<div id="upload-placeholder">
|
| 321 |
+
<div class="upload-icon">🖼️</div>
|
| 322 |
+
<div class="upload-text"><b>얼굴 사진 업로드</b><br>클릭 또는 드래그 · 정면, 균일 조명 권장</div>
|
| 323 |
+
</div>
|
| 324 |
+
<img id="preview-img" style="display:none">
|
| 325 |
+
<input type="file" id="file-input" accept="image/*" style="display:none" onchange="handleFile(this)">
|
| 326 |
+
</div>
|
| 327 |
+
|
| 328 |
+
<!-- Procedure selection -->
|
| 329 |
+
<div class="form-row" style="margin-top:16px;">
|
| 330 |
+
<div class="form-group" style="flex:2;">
|
| 331 |
+
<div class="form-label">🏥 시술 프리셋</div>
|
| 332 |
+
<select id="preset-select">
|
| 333 |
+
<option value="없음 (직접 입력)">없음 (직접 입력)</option>
|
| 334 |
+
<option value="👁️ 자연유착 쌍꺼풀">👁️ 자연유착 쌍꺼풀</option>
|
| 335 |
+
<option value="👁️ 절개 쌍꺼풀">👁️ 절개 쌍꺼풀</option>
|
| 336 |
+
<option value="👃 코끝 성형">👃 코끝 성형</option>
|
| 337 |
+
<option value="👃 콧대 성형">👃 콧대 성형</option>
|
| 338 |
+
<option value="🦷 사각턱 축소">🦷 사각턱 축소</option>
|
| 339 |
+
<option value="💉 팔자주름 필러">💉 팔자주름 필러</option>
|
| 340 |
+
<option value="💉 이마 보톡스">💉 이마 보톡스</option>
|
| 341 |
+
<option value="💋 입술 필러">💋 입술 필러</option>
|
| 342 |
+
<option value="🏥 눈+코 동시">🏥 눈+코 동시</option>
|
| 343 |
+
<option value="🏥 풀페이스 리프팅">🏥 풀페이스 리프팅</option>
|
| 344 |
+
<option value="✨ 동안 스타일">✨ 동안 스타일</option>
|
| 345 |
+
</select>
|
| 346 |
+
</div>
|
| 347 |
+
<div class="form-group" style="flex:1;">
|
| 348 |
+
<div class="form-label">📐 비율</div>
|
| 349 |
+
<select id="aspect-ratio">
|
| 350 |
+
<option value="auto">Auto</option>
|
| 351 |
+
<option value="1:1">1:1</option>
|
| 352 |
+
<option value="3:4">3:4</option>
|
| 353 |
+
<option value="4:3">4:3</option>
|
| 354 |
+
<option value="9:16">9:16</option>
|
| 355 |
+
<option value="16:9">16:9</option>
|
| 356 |
+
</select>
|
| 357 |
+
</div>
|
| 358 |
+
</div>
|
| 359 |
+
|
| 360 |
+
<!-- Custom prompt -->
|
| 361 |
+
<div class="form-group">
|
| 362 |
+
<div class="form-label">✏️ 추가 지시 (선택)</div>
|
| 363 |
+
<textarea id="custom-prompt" placeholder="프리셋 외 추가 요청을 자유롭게 입력하세요"></textarea>
|
| 364 |
+
</div>
|
| 365 |
+
|
| 366 |
+
<!-- Intensity -->
|
| 367 |
+
<div class="form-group" style="margin-top:4px;">
|
| 368 |
+
<div class="form-label">💪 시술 강도</div>
|
| 369 |
+
<div class="radio-group">
|
| 370 |
+
<input type="radio" name="intensity" id="int-subtle" value="자연스럽게 (Subtle)">
|
| 371 |
+
<label for="int-subtle">🌿 자연스럽게</label>
|
| 372 |
+
<input type="radio" name="intensity" id="int-moderate" value="보통 (Moderate)" checked>
|
| 373 |
+
<label for="int-moderate">⚡ 보통</label>
|
| 374 |
+
<input type="radio" name="intensity" id="int-strong" value="확실하게 (Strong)">
|
| 375 |
+
<label for="int-strong">🔥 확실하게</label>
|
| 376 |
+
</div>
|
| 377 |
+
</div>
|
| 378 |
+
|
| 379 |
+
<!-- Pipeline indicator -->
|
| 380 |
+
<div class="pipeline" id="pipeline">
|
| 381 |
+
<span class="pipeline-step" id="ps-doctor">🩺 분석</span><span class="pipeline-arrow">→</span>
|
| 382 |
+
<span class="pipeline-step" id="ps-creator">💡 프롬프트</span><span class="pipeline-arrow">→</span>
|
| 383 |
+
<span class="pipeline-step" id="ps-gen">🎨 생성</span><span class="pipeline-arrow">→</span>
|
| 384 |
+
<span class="pipeline-step" id="ps-critic">⚖️ 평가</span><span class="pipeline-arrow">→</span>
|
| 385 |
+
<span class="pipeline-step" id="ps-report">🎯 리포트</span>
|
| 386 |
+
</div>
|
| 387 |
+
|
| 388 |
+
<div class="btn-group">
|
| 389 |
+
<button class="btn btn-primary" id="btn-run" onclick="runSimulation()">🚀 시뮬레이션 시작</button>
|
| 390 |
+
</div>
|
| 391 |
+
</div>
|
| 392 |
+
|
| 393 |
+
<!-- ═══ FACE SCAN ANIMATION ═══ -->
|
| 394 |
+
<div class="scan-overlay" id="scan-overlay">
|
| 395 |
+
<!-- Particles -->
|
| 396 |
+
<div class="scan-particles" id="scan-particles"></div>
|
| 397 |
+
<!-- Grid -->
|
| 398 |
+
<div class="scan-grid"></div>
|
| 399 |
+
|
| 400 |
+
<!-- Face container -->
|
| 401 |
+
<div class="face-container">
|
| 402 |
+
<!-- Pulse rings -->
|
| 403 |
+
<div class="pulse-ring" style="animation-delay:0s;"></div>
|
| 404 |
+
<div class="pulse-ring" style="animation-delay:1s;"></div>
|
| 405 |
+
<div class="pulse-ring" style="animation-delay:2s;"></div>
|
| 406 |
+
|
| 407 |
+
<!-- Corner brackets -->
|
| 408 |
+
<div class="scan-corner tl"></div>
|
| 409 |
+
<div class="scan-corner tr"></div>
|
| 410 |
+
<div class="scan-corner bl"></div>
|
| 411 |
+
<div class="scan-corner br"></div>
|
| 412 |
+
|
| 413 |
+
<!-- Laser scan line -->
|
| 414 |
+
<div class="laser-line"></div>
|
| 415 |
+
|
| 416 |
+
<!-- Face mask SVG -->
|
| 417 |
+
<svg class="face-mask" viewBox="0 0 220 280" xmlns="http://www.w3.org/2000/svg">
|
| 418 |
+
<!-- Face oval -->
|
| 419 |
+
<ellipse class="face-outline" cx="110" cy="145" rx="78" ry="105"
|
| 420 |
+
style="animation-delay:0s;stroke-dasharray:600;"/>
|
| 421 |
+
<!-- Left eye -->
|
| 422 |
+
<ellipse class="face-outline" cx="78" cy="118" rx="22" ry="10"
|
| 423 |
+
style="animation-delay:.4s;stroke-dasharray:120;stroke:#e11d48;stroke-width:1.2;"/>
|
| 424 |
+
<!-- Right eye -->
|
| 425 |
+
<ellipse class="face-outline" cx="142" cy="118" rx="22" ry="10"
|
| 426 |
+
style="animation-delay:.5s;stroke-dasharray:120;stroke:#e11d48;stroke-width:1.2;"/>
|
| 427 |
+
<!-- Left eyebrow -->
|
| 428 |
+
<path class="face-outline" d="M52,98 Q78,84 102,98"
|
| 429 |
+
style="animation-delay:.7s;stroke-dasharray:80;stroke:#818cf8;stroke-width:1;"/>
|
| 430 |
+
<!-- Right eyebrow -->
|
| 431 |
+
<path class="face-outline" d="M118,98 Q142,84 168,98"
|
| 432 |
+
style="animation-delay:.8s;stroke-dasharray:80;stroke:#818cf8;stroke-width:1;"/>
|
| 433 |
+
<!-- Nose -->
|
| 434 |
+
<path class="face-outline" d="M110,128 L110,160 Q102,172 94,166 M110,160 Q118,172 126,166"
|
| 435 |
+
style="animation-delay:1s;stroke-dasharray:100;stroke:#0d9488;stroke-width:1.2;"/>
|
| 436 |
+
<!-- Mouth -->
|
| 437 |
+
<path class="face-outline" d="M82,195 Q96,206 110,207 Q124,206 138,195"
|
| 438 |
+
style="animation-delay:1.3s;stroke-dasharray:80;stroke:#f59e0b;stroke-width:1.2;"/>
|
| 439 |
+
<!-- Jaw line -->
|
| 440 |
+
<path class="face-outline" d="M36,140 Q40,210 110,248 Q180,210 184,140"
|
| 441 |
+
style="animation-delay:1.6s;stroke-dasharray:400;stroke:#6366f1;stroke-width:0.8;opacity:0.5;"/>
|
| 442 |
+
<!-- Cross guides -->
|
| 443 |
+
<line class="face-outline" x1="110" y1="60" x2="110" y2="240"
|
| 444 |
+
style="animation-delay:2s;stroke-dasharray:200;stroke:rgba(99,102,241,0.15);stroke-width:0.6;"/>
|
| 445 |
+
<line class="face-outline" x1="40" y1="145" x2="180" y2="145"
|
| 446 |
+
style="animation-delay:2.1s;stroke-dasharray:160;stroke:rgba(99,102,241,0.15);stroke-width:0.6;"/>
|
| 447 |
+
</svg>
|
| 448 |
+
|
| 449 |
+
<!-- Data readouts -->
|
| 450 |
+
<div class="scan-data" style="top:12%;left:-30%;animation-delay:0.5s;">SYMMETRY: ANALYZING...</div>
|
| 451 |
+
<div class="scan-data" style="top:35%;right:-35%;animation-delay:1.2s;">RATIO: 1.618:1</div>
|
| 452 |
+
<div class="scan-data" style="top:60%;left:-28%;animation-delay:2s;">BONE: MAPPING...</div>
|
| 453 |
+
<div class="scan-data" style="top:82%;right:-30%;animation-delay:2.8s;">TISSUE: READY</div>
|
| 454 |
+
</div>
|
| 455 |
+
|
| 456 |
+
<!-- Status -->
|
| 457 |
+
<div class="scan-status">
|
| 458 |
+
<div class="scan-status-main" id="scan-text">🩺 얼굴 분석 중...</div>
|
| 459 |
+
<div class="scan-status-sub" id="scan-sub">SOMA Multi-Agent Processing</div>
|
| 460 |
+
<div class="scan-progress-bar"><div class="scan-progress-fill" id="scan-progress" style="width:5%;"></div></div>
|
| 461 |
+
</div>
|
| 462 |
+
</div>
|
| 463 |
+
|
| 464 |
+
<!-- RESULTS -->
|
| 465 |
+
<div id="results-area" style="display:none;">
|
| 466 |
+
<div class="error-msg" id="error-msg"></div>
|
| 467 |
+
|
| 468 |
+
<!-- Generated image -->
|
| 469 |
+
<div class="result-section" id="result-gen" style="display:none;">
|
| 470 |
+
<div class="result-image-wrap">
|
| 471 |
+
<img id="result-img" alt="시뮬레이션 결과">
|
| 472 |
+
<div class="result-label">🎨 시뮬레이션 결과 (1K)</div>
|
| 473 |
+
</div>
|
| 474 |
+
</div>
|
| 475 |
+
|
| 476 |
+
<!-- Before/After -->
|
| 477 |
+
<div class="ba-container" id="result-ba" style="display:none;">
|
| 478 |
+
<div class="ba-label">🔄 Before / After 비교</div>
|
| 479 |
+
<div id="ba-html"></div>
|
| 480 |
+
</div>
|
| 481 |
+
|
| 482 |
+
<!-- Angles -->
|
| 483 |
+
<div id="result-angles" style="display:none;">
|
| 484 |
+
<div class="result-grid">
|
| 485 |
+
<div class="result-image-wrap">
|
| 486 |
+
<img id="angle-45" alt="45도 앵글">
|
| 487 |
+
<div class="result-label">📐 45도 앵글</div>
|
| 488 |
+
</div>
|
| 489 |
+
<div class="result-image-wrap">
|
| 490 |
+
<img id="angle-side" alt="측면 프로필">
|
| 491 |
+
<div class="result-label">📐 측면 프로필</div>
|
| 492 |
+
</div>
|
| 493 |
+
</div>
|
| 494 |
+
</div>
|
| 495 |
+
|
| 496 |
+
<!-- Report -->
|
| 497 |
+
<div class="report-card" id="result-report" style="display:none;">
|
| 498 |
+
<div id="report-html"></div>
|
| 499 |
+
</div>
|
| 500 |
+
</div>
|
| 501 |
+
</div>
|
| 502 |
+
|
| 503 |
+
<!-- ═══ SOMA 시스템 탭 ═══ -->
|
| 504 |
+
<div class="panel" id="panel-soma">
|
| 505 |
+
<div class="hero-card" style="background:linear-gradient(135deg,#1e1b4b,#312e81 40%,#4f46e5);">
|
| 506 |
+
<div class="hero-title">🧠 SOMA (Self-Orchestrating Modular Architect)</div>
|
| 507 |
+
<div class="hero-sub">VIDRAFT이 개발한 다중 에이전트 협업 프레임워크<br>"AI 전문가 5명이 회의하는 시스템"</div>
|
| 508 |
+
<div class="hero-badge">상생(相生) × 상극(相克) — 환각 최소화</div>
|
| 509 |
+
</div>
|
| 510 |
+
<div class="card">
|
| 511 |
+
<div class="card-header"><div class="card-icon purple">🔮</div><div class="card-title">작동 원리</div></div>
|
| 512 |
+
<div style="padding:8px 0;font-size:12.5px;color:var(--text-sec);line-height:2;">
|
| 513 |
+
<b style="color:var(--text);">① 🩺 의사 AI</b> → 얼굴 구조 분석, 시술 적합성 판단, 리스크 평가<br>
|
| 514 |
+
<b style="color:var(--text);">② 💡 창발 AI</b> → 의사 소견 반영 최적 프롬프트 생성<br>
|
| 515 |
+
<b style="color:var(--text);">③ 🎨 이미지 생성</b> → Nano Banana 2 Edit (1K)<br>
|
| 516 |
+
<b style="color:var(--text);">④ ⚖️ 비평 AI</b> → 6개 기준 엄격 평가 + 교차 검증<br>
|
| 517 |
+
<b style="color:var(--text);">⑤ 🎯 감독 AI</b> → 모든 결과 종합, 최종 리포트만 출력
|
| 518 |
+
</div>
|
| 519 |
+
</div>
|
| 520 |
+
<div class="card">
|
| 521 |
+
<div class="card-header"><div class="card-icon teal">⚖️</div><div class="card-title">상생과 상극</div></div>
|
| 522 |
+
<div style="padding:8px 0;font-size:12px;color:var(--text-sec);line-height:2;">
|
| 523 |
+
<b style="color:var(--teal);">상생 (시너지):</b> 🩺→💡 의학적 정확성이 창의적 프롬프트로 전환 · 🔮→🩺 관상학 인사이트가 시술 추천 보완<br>
|
| 524 |
+
<b style="color:var(--rose);">상극 (견제):</b> ⚖️이 💡의 결과를 비판적 검증 · 🩺가 비현실적 시술을 의학적으로 걸러냄 · 🎯이 갈등 조율
|
| 525 |
+
</div>
|
| 526 |
+
</div>
|
| 527 |
+
<div class="card">
|
| 528 |
+
<div class="card-header"><div class="card-icon amber">📊</div><div class="card-title">비평 AI 평가 기준 (60점 만점)</div></div>
|
| 529 |
+
<div style="padding:8px 0;font-size:11.5px;color:var(--text-sec);line-height:2;">
|
| 530 |
+
프롬프트 충실도(10) · 자연스러움(10) · 동일인 유지도(10) · 의학적 현실성(10) · 전체 조화(10) · 아티팩트 검출(10)<br>
|
| 531 |
+
<span style="color:var(--teal);font-weight:600;">🟢 48+ 고품질</span> · <span style="color:var(--amber);font-weight:600;">🟡 36~47 수용 가능</span> · <span style="color:var(--rose);font-weight:600;">🔴 36 미만 재생성 필요</span>
|
| 532 |
+
</div>
|
| 533 |
+
</div>
|
| 534 |
+
</div>
|
| 535 |
+
|
| 536 |
+
<!-- ═══ 사용법 탭 ═══ -->
|
| 537 |
+
<div class="panel" id="panel-guide">
|
| 538 |
+
<div class="card">
|
| 539 |
+
<div class="card-header"><div class="card-icon purple">📖</div><div class="card-title">사용 가이드</div></div>
|
| 540 |
+
<div style="padding:8px 0;font-size:12.5px;color:var(--text-sec);line-height:2.2;">
|
| 541 |
+
<b style="color:var(--text);">0. API Key 설정</b> — 환경변수 또는 🔑 섹션에서 입력<br>
|
| 542 |
+
<b style="color:var(--text);">1. 얼굴 사진 업로드</b> — 정면, 균일 조명, 고해상도 권장<br>
|
| 543 |
+
<b style="color:var(--text);">2. 시술 선택</b> — 프리셋 11종 또는 직접 입력, 강도 조절<br>
|
| 544 |
+
<b style="color:var(--text);">3. 시뮬레이션 시작</b> — SOMA 파이프라인 자동 실행<br>
|
| 545 |
+
<b style="color:var(--text);">4. 결과 확인</b> — 시뮬레이션 이미지, Before/After, 다각도, 리포트<br><br>
|
| 546 |
+
<span style="font-size:11px;color:var(--text-muted);">💡 프리셋 + 추가 지시를 함께 사용하면 더 정교한 결과<br>⚠️ AI 시뮬레이션이며 실제 수술 결과와 다를 수 있습니다</span>
|
| 547 |
+
</div>
|
| 548 |
+
</div>
|
| 549 |
+
</div>
|
| 550 |
+
|
| 551 |
+
<!-- FOOTER -->
|
| 552 |
+
<footer class="footer">
|
| 553 |
+
<div class="footer-brand">Proto-AGI FACE</div>
|
| 554 |
+
<div class="footer-dev">개발: <a href="https://vidraft.net" target="_blank">VIDRAFT</a> · <a href="/gradio" style="color:var(--teal);text-decoration:none;font-weight:600;">전체 기능 (Gradio UI) →</a></div>
|
| 555 |
+
<div class="footer-line"></div>
|
| 556 |
+
<div class="footer-tech">SOMA × NANO-BANANA-2 × KIMI-K2P5 · 🩺🔮💡⚖️🎯 5-Agent</div>
|
| 557 |
+
</footer>
|
| 558 |
+
|
| 559 |
+
</div>
|
| 560 |
+
|
| 561 |
+
<script>
|
| 562 |
+
const B = window.location.origin;
|
| 563 |
+
let uploadedFilePath = null;
|
| 564 |
+
|
| 565 |
+
/* ═══ TAB SWITCHING ═══ */
|
| 566 |
+
function switchTab(t){
|
| 567 |
+
document.querySelectorAll('.nav-item').forEach(e=>e.classList.remove('active'));
|
| 568 |
+
document.querySelectorAll('.panel').forEach(e=>e.classList.remove('active'));
|
| 569 |
+
document.querySelector(`[data-tab="${t}"]`).classList.add('active');
|
| 570 |
+
document.getElementById(`panel-${t}`).classList.add('active');
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
/* ═══ API KEY TOGGLE ═══ */
|
| 574 |
+
function toggleKeys(){
|
| 575 |
+
const s=document.getElementById('key-section');
|
| 576 |
+
s.classList.toggle('open');
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
/* ═══ FILE UPLOAD ═══ */
|
| 580 |
+
function handleFile(input){
|
| 581 |
+
const file=input.files&&input.files[0];
|
| 582 |
+
if(!file)return;
|
| 583 |
+
const zone=document.getElementById('upload-zone');
|
| 584 |
+
const ph=document.getElementById('upload-placeholder');
|
| 585 |
+
const img=document.getElementById('preview-img');
|
| 586 |
+
const reader=new FileReader();
|
| 587 |
+
reader.onload=function(e){
|
| 588 |
+
img.src=e.target.result;
|
| 589 |
+
img.style.display='block';
|
| 590 |
+
ph.style.display='none';
|
| 591 |
+
zone.classList.add('has-image');
|
| 592 |
+
};
|
| 593 |
+
reader.readAsDataURL(file);
|
| 594 |
+
uploadToGradio(file);
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
async function uploadToGradio(file){
|
| 598 |
+
const fd=new FormData();fd.append('files',file);
|
| 599 |
+
const urls=[
|
| 600 |
+
`${B}/gradio/upload`,
|
| 601 |
+
`${B}/gradio/gradio_api/upload`,
|
| 602 |
+
`${B}/gradio_api/upload`,
|
| 603 |
+
`${B}/upload`,
|
| 604 |
+
];
|
| 605 |
+
for(const u of urls){
|
| 606 |
+
try{const r=await fetch(u,{method:'POST',body:fd});if(r.ok){const j=await r.json();uploadedFilePath=Array.isArray(j)?j[0]:j;return;}}catch{}
|
| 607 |
+
}
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
/* Drag & drop */
|
| 611 |
+
const zone=document.getElementById('upload-zone');
|
| 612 |
+
zone.addEventListener('dragover',e=>{e.preventDefault();zone.style.borderColor='var(--accent)';zone.style.background='rgba(99,102,241,0.05)';});
|
| 613 |
+
zone.addEventListener('dragleave',()=>{zone.style.borderColor='';zone.style.background='';});
|
| 614 |
+
zone.addEventListener('drop',e=>{e.preventDefault();zone.style.borderColor='';zone.style.background='';const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f&&f.type.startsWith('image/')){document.getElementById('file-input').files=e.dataTransfer.files;handleFile(document.getElementById('file-input'));}});
|
| 615 |
+
|
| 616 |
+
/* ═══ PARTICLES ═══ */
|
| 617 |
+
function initParticles(){
|
| 618 |
+
const c=document.getElementById('scan-particles');
|
| 619 |
+
c.innerHTML='';
|
| 620 |
+
for(let i=0;i<30;i++){
|
| 621 |
+
const s=document.createElement('span');
|
| 622 |
+
s.style.left=Math.random()*100+'%';
|
| 623 |
+
s.style.animationDelay=Math.random()*4+'s';
|
| 624 |
+
s.style.animationDuration=(3+Math.random()*3)+'s';
|
| 625 |
+
c.appendChild(s);
|
| 626 |
+
}
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
/* ═══ SCAN ANIMATION ═══ */
|
| 630 |
+
const scanSteps=[
|
| 631 |
+
{text:'🩺 얼굴 구조 분석 중...',sub:'Doctor Agent — Facial Anatomy Analysis',pct:10,step:'ps-doctor'},
|
| 632 |
+
{text:'🩺 시술 적합성 판단 중...',sub:'Doctor Agent — Procedure Evaluation',pct:15,step:'ps-doctor'},
|
| 633 |
+
{text:'💡 프롬프트 최적화 중...',sub:'Creator Agent — Prompt Engineering',pct:25,step:'ps-creator'},
|
| 634 |
+
{text:'🎨 이미지 생성 중...',sub:'Nano Banana 2 Edit — Generating 1K',pct:40,step:'ps-gen'},
|
| 635 |
+
{text:'🎨 고품질 렌더링 중...',sub:'Image Processing — Please Wait',pct:55,step:'ps-gen'},
|
| 636 |
+
{text:'⚖️ 품질 검증 중...',sub:'Critic Agent — 6-Axis Quality Check',pct:65,step:'ps-critic'},
|
| 637 |
+
{text:'📐 다각도 생성 중...',sub:'45° + Profile Angle Generation',pct:78,step:'ps-critic'},
|
| 638 |
+
{text:'🎯 리포트 작성 중...',sub:'Director Agent — Synthesizing Report',pct:88,step:'ps-report'},
|
| 639 |
+
{text:'✨ 거의 완료...',sub:'Finalizing Results',pct:95,step:'ps-report'},
|
| 640 |
+
];
|
| 641 |
+
let scanInterval=null;
|
| 642 |
+
|
| 643 |
+
function startScan(){
|
| 644 |
+
const ov=document.getElementById('scan-overlay');
|
| 645 |
+
ov.classList.add('active');
|
| 646 |
+
ov.style.display='block';
|
| 647 |
+
initParticles();
|
| 648 |
+
document.querySelectorAll('.pipeline-step').forEach(s=>{s.classList.remove('active','done');});
|
| 649 |
+
let idx=0;
|
| 650 |
+
updateScanStep(scanSteps[0]);
|
| 651 |
+
scanInterval=setInterval(()=>{
|
| 652 |
+
idx++;
|
| 653 |
+
if(idx<scanSteps.length){
|
| 654 |
+
updateScanStep(scanSteps[idx]);
|
| 655 |
+
}
|
| 656 |
+
},4000);
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
function updateScanStep(step){
|
| 660 |
+
document.getElementById('scan-text').textContent=step.text;
|
| 661 |
+
document.getElementById('scan-sub').textContent=step.sub;
|
| 662 |
+
document.getElementById('scan-progress').style.width=step.pct+'%';
|
| 663 |
+
const steps=['ps-doctor','ps-creator','ps-gen','ps-critic','ps-report'];
|
| 664 |
+
const ci=steps.indexOf(step.step);
|
| 665 |
+
steps.forEach((s,i)=>{
|
| 666 |
+
const el=document.getElementById(s);
|
| 667 |
+
if(i<ci)el.classList.add('done'),el.classList.remove('active');
|
| 668 |
+
else if(i===ci)el.classList.add('active'),el.classList.remove('done');
|
| 669 |
+
else el.classList.remove('active','done');
|
| 670 |
+
});
|
| 671 |
+
}
|
| 672 |
+
|
| 673 |
+
function stopScan(){
|
| 674 |
+
if(scanInterval)clearInterval(scanInterval);
|
| 675 |
+
const ov=document.getElementById('scan-overlay');
|
| 676 |
+
ov.classList.remove('active');
|
| 677 |
+
ov.style.display='none';
|
| 678 |
+
document.querySelectorAll('.pipeline-step').forEach(s=>{s.classList.remove('active');s.classList.add('done');});
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
/* ═══ GRADIO API CALL ═══ */
|
| 682 |
+
async function gc(api,data){
|
| 683 |
+
const urls=[
|
| 684 |
+
`${B}/gradio/call${api}`,
|
| 685 |
+
`${B}/gradio/api${api}`,
|
| 686 |
+
`${B}/gradio/gradio_api/call${api}`,
|
| 687 |
+
`${B}/api${api}`,
|
| 688 |
+
`${B}/gradio_api/call${api}`,
|
| 689 |
+
];
|
| 690 |
+
for(const u of urls){
|
| 691 |
+
try{
|
| 692 |
+
const r=await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({data})});
|
| 693 |
+
if(!r.ok)continue;
|
| 694 |
+
const j=await r.json();if(!j.event_id)continue;
|
| 695 |
+
return new Promise((ok,no)=>{
|
| 696 |
+
const es=new EventSource(`${u}/${j.event_id}`);let d=false;
|
| 697 |
+
const h=e=>{if(d)return;try{const raw=JSON.parse(e.data);const p=Array.isArray(raw)?raw:(raw&&raw.data?raw.data:null);if(p){d=true;es.close();ok(p);}}catch{}};
|
| 698 |
+
es.onmessage=h;es.addEventListener('complete',h);
|
| 699 |
+
es.addEventListener('process_completed',e=>{if(d)return;try{const raw=JSON.parse(e.data);const out=raw&&raw.output&&raw.output.data?raw.output.data:null;if(out){d=true;es.close();ok(out);}}catch{}});
|
| 700 |
+
es.onerror=()=>{if(!d){d=true;es.close();no(new Error('연결 오류'));}};
|
| 701 |
+
setTimeout(()=>{if(!d){d=true;es.close();no(new Error('시간 초과 (3분)'));}},180000);
|
| 702 |
+
});
|
| 703 |
+
}catch(e){continue;}
|
| 704 |
+
}
|
| 705 |
+
throw new Error('API 연결 실패 — Gradio 서버를 확인하세요');
|
| 706 |
+
}
|
| 707 |
+
|
| 708 |
+
function mkFile(p){return{path:p,meta:{_type:'gradio.FileData'},orig_name:'face.png',mime_type:'image/png'};}
|
| 709 |
+
|
| 710 |
+
function extractImageUrl(d){
|
| 711 |
+
if(!d)return null;
|
| 712 |
+
if(typeof d==='string'){
|
| 713 |
+
// 상대 경로면 /gradio 접두사 추가
|
| 714 |
+
if(d.startsWith('/file='))return B+'/gradio'+d;
|
| 715 |
+
if(d.startsWith('http'))return d;
|
| 716 |
+
return d;
|
| 717 |
+
}
|
| 718 |
+
if(d.url){
|
| 719 |
+
let u=d.url;
|
| 720 |
+
if(u.startsWith('/file='))return B+'/gradio'+u;
|
| 721 |
+
return u;
|
| 722 |
+
}
|
| 723 |
+
if(d.path){
|
| 724 |
+
let p=d.path;
|
| 725 |
+
if(p.startsWith('/file='))return B+'/gradio'+p;
|
| 726 |
+
if(!p.startsWith('http'))return B+'/gradio/file='+p;
|
| 727 |
+
return p;
|
| 728 |
+
}
|
| 729 |
+
if(d.value){
|
| 730 |
+
return extractImageUrl(d.value);
|
| 731 |
+
}
|
| 732 |
+
return null;
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
+
/* ═══ RUN SIMULATION ═══ */
|
| 736 |
+
async function runSimulation(){
|
| 737 |
+
if(!uploadedFilePath){alert('얼굴 사진을 업로드해주세요.');return;}
|
| 738 |
+
const btn=document.getElementById('btn-run');
|
| 739 |
+
btn.disabled=true;
|
| 740 |
+
|
| 741 |
+
// Hide previous results
|
| 742 |
+
document.getElementById('results-area').style.display='none';
|
| 743 |
+
['result-gen','result-ba','result-angles','result-report'].forEach(id=>{document.getElementById(id).style.display='none';});
|
| 744 |
+
document.getElementById('error-msg').textContent='';
|
| 745 |
+
|
| 746 |
+
// Get form values
|
| 747 |
+
const prompt=document.getElementById('custom-prompt').value;
|
| 748 |
+
const preset=document.getElementById('preset-select').value;
|
| 749 |
+
const intensity=document.querySelector('input[name="intensity"]:checked').value;
|
| 750 |
+
const aspect=document.getElementById('aspect-ratio').value;
|
| 751 |
+
const falKey=document.getElementById('fal-key').value;
|
| 752 |
+
const fwKey=document.getElementById('fw-key').value;
|
| 753 |
+
|
| 754 |
+
startScan();
|
| 755 |
+
|
| 756 |
+
try{
|
| 757 |
+
const fileData=mkFile(uploadedFilePath);
|
| 758 |
+
const result=await gc('/run_simulation',[fileData,prompt,preset,intensity,aspect,falKey,fwKey]);
|
| 759 |
+
stopScan();
|
| 760 |
+
|
| 761 |
+
document.getElementById('results-area').style.display='block';
|
| 762 |
+
|
| 763 |
+
// result: [gen_img, slider_html, report, angle45, angle_side, error]
|
| 764 |
+
const genImg=extractImageUrl(result[0]);
|
| 765 |
+
const sliderHtml=result[1]||'';
|
| 766 |
+
const report=result[2]||'';
|
| 767 |
+
const angle45=extractImageUrl(result[3]);
|
| 768 |
+
const angleSide=extractImageUrl(result[4]);
|
| 769 |
+
const error=result[5]||'';
|
| 770 |
+
|
| 771 |
+
if(error){
|
| 772 |
+
document.getElementById('error-msg').textContent='⚠️ '+error;
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
if(genImg){
|
| 776 |
+
document.getElementById('result-img').src=genImg;
|
| 777 |
+
document.getElementById('result-gen').style.display='block';
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
if(sliderHtml){
|
| 781 |
+
document.getElementById('ba-html').innerHTML=sliderHtml;
|
| 782 |
+
document.getElementById('result-ba').style.display='block';
|
| 783 |
+
}
|
| 784 |
+
|
| 785 |
+
if(angle45||angleSide){
|
| 786 |
+
if(angle45)document.getElementById('angle-45').src=angle45;
|
| 787 |
+
if(angleSide)document.getElementById('angle-side').src=angleSide;
|
| 788 |
+
document.getElementById('result-angles').style.display=((angle45||angleSide)?'block':'none');
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
+
if(report){
|
| 792 |
+
document.getElementById('report-html').innerHTML=marked?marked.parse(report):report.replace(/\n/g,'<br>');
|
| 793 |
+
document.getElementById('result-report').style.display='block';
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
// Scroll to results
|
| 797 |
+
document.getElementById('results-area').scrollIntoView({behavior:'smooth',block:'start'});
|
| 798 |
+
|
| 799 |
+
}catch(e){
|
| 800 |
+
stopScan();
|
| 801 |
+
document.getElementById('results-area').style.display='block';
|
| 802 |
+
document.getElementById('error-msg').textContent='⚠️ '+e.message;
|
| 803 |
+
}
|
| 804 |
+
|
| 805 |
+
btn.disabled=false;
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
/* ═══ SIMPLE MARKDOWN PARSER ═══ */
|
| 809 |
+
const marked={
|
| 810 |
+
parse(md){
|
| 811 |
+
return md
|
| 812 |
+
.replace(/^### (.*$)/gm,'<h3>$1</h3>')
|
| 813 |
+
.replace(/^## (.*$)/gm,'<h2>$1</h2>')
|
| 814 |
+
.replace(/^# (.*$)/gm,'<h1>$1</h1>')
|
| 815 |
+
.replace(/\*\*(.*?)\*\*/g,'<b>$1</b>')
|
| 816 |
+
.replace(/\*(.*?)\*/g,'<em>$1</em>')
|
| 817 |
+
.replace(/^> (.*$)/gm,'<blockquote>$1</blockquote>')
|
| 818 |
+
.replace(/^---$/gm,'<hr>')
|
| 819 |
+
.replace(/^- (.*$)/gm,'<li>$1</li>')
|
| 820 |
+
.replace(/\n\n/g,'<br><br>')
|
| 821 |
+
.replace(/\n/g,'<br>');
|
| 822 |
+
}
|
| 823 |
+
};
|
| 824 |
+
</script>
|
| 825 |
+
</body>
|
| 826 |
+
</html>
|