aiqtech commited on
Commit
5217429
·
verified ·
1 Parent(s): 1b88646

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +200 -805
index.html CHANGED
@@ -6,821 +6,216 @@
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>
 
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{--bg:#f8f9fc;--surface:#fff;--surface-alt:#f5f6fa;--border:#e2e5f0;--border-hover:#c7cce0;
11
+ --shadow-sm:0 1px 3px rgba(15,23,42,.04),0 1px 2px rgba(15,23,42,.06);--shadow:0 4px 16px rgba(15,23,42,.06),0 1px 3px rgba(15,23,42,.08);
12
+ --text:#0f172a;--text-sec:#475569;--text-muted:#94a3b8;--accent:#6366f1;--teal:#0d9488;--rose:#e11d48;--pink:#ec4899;--amber:#d97706;
13
+ --radius:16px;--radius-sm:10px;--radius-xs:6px;
14
+ --fd:'Sora','Noto Sans KR',sans-serif;--fb:'Noto Sans KR','Sora',sans-serif;--fm:'JetBrains Mono',monospace;--tr:.25s cubic-bezier(.4,0,.2,1)}
15
+ html{scroll-behavior:smooth}body{font-family:var(--fb);background:var(--bg);color:var(--text);min-height:100vh;overflow-x:hidden;-webkit-font-smoothing:antialiased}
16
+ ::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:rgba(99,102,241,.2);border-radius:10px}::selection{background:rgba(99,102,241,.15)}
17
+ .bg-p{position:fixed;inset:0;z-index:0;pointer-events:none;background:radial-gradient(ellipse 80% 50% at 20% 10%,rgba(99,102,241,.04),transparent 50%),radial-gradient(ellipse 60% 40% at 80% 90%,rgba(13,148,136,.03),transparent 50%)}
18
+ .wrap{position:relative;z-index:1;max-width:960px;margin:0 auto;padding:24px 24px 48px}
19
+ .hdr{text-align:center;padding:28px 0 16px;animation:fi .8s ease-out}
20
+ @keyframes fi{from{opacity:0;transform:translateY(-16px)}to{opacity:1;transform:translateY(0)}}
21
+ .hdr-eye{font-family:var(--fm);font-size:10px;font-weight:600;letter-spacing:5px;text-transform:uppercase;color:var(--accent);margin-bottom:6px}
22
+ .hdr-t{font-family:var(--fd);font-size:40px;font-weight:800;letter-spacing:-1.5px;margin-bottom:8px}
23
+ .gt{background:linear-gradient(135deg,#6366f1,#e11d48 40%,#0d9488 70%,#6366f1);background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:sh 6s ease-in-out infinite}
24
+ @keyframes sh{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
25
+ .pills{display:flex;gap:5px;justify-content:center;flex-wrap:wrap;animation:fu .8s .2s ease-out both}
26
+ @keyframes fu{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
27
+ .pill{padding:3px 10px;background:var(--surface);border:1px solid var(--border);border-radius:20px;font-family:var(--fm);font-size:9px;font-weight:500;color:var(--text-muted);box-shadow:var(--shadow-sm)}
28
+ .nav{display:flex;gap:3px;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:4px;margin-bottom:18px;position:sticky;top:12px;z-index:100;box-shadow:var(--shadow);animation:fu .8s .3s ease-out both}
29
+ .ni{flex:1;display:flex;align-items:center;justify-content:center;gap:4px;padding:10px 4px;border-radius:var(--radius-sm);cursor:pointer;font-size:11.5px;font-weight:600;color:var(--text-muted);transition:var(--tr);user-select:none}
30
+ .ni:hover{color:var(--text-sec);background:var(--surface-alt)}
31
+ .ni.a{color:#fff;box-shadow:0 4px 16px rgba(99,102,241,.3)}
32
+ .ni.a[data-t="face"]{background:linear-gradient(135deg,#3b82f6,#2563eb)}.ni.a[data-t="physio"]{background:linear-gradient(135deg,#8b5cf6,#7c3aed)}.ni.a[data-t="compat"]{background:linear-gradient(135deg,#ec4899,#db2777)}.ni.a[data-t="sim"]{background:linear-gradient(135deg,#e11d48,#be123c)}
33
+ .pnl{display:none;animation:pi .4s ease-out}.pnl.a{display:block}@keyframes pi{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
34
+ .card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px;margin-bottom:14px;box-shadow:var(--shadow-sm);transition:all .3s}
35
+ .card:hover{box-shadow:var(--shadow);border-color:var(--border-hover)}
36
+ .ch{display:flex;align-items:center;gap:10px;margin-bottom:12px}.ci{width:34px;height:34px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
37
+ .ci.bl{background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.15)}.ci.pu{background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.15)}.ci.pk{background:rgba(236,72,153,.06);border:1px solid rgba(236,72,153,.15)}.ci.ro{background:rgba(225,29,72,.06);border:1px solid rgba(225,29,72,.15)}
38
+ .ct{font-family:var(--fd);font-size:15px;font-weight:700}
39
+ .fl{display:block;font-size:10.5px;font-weight:600;color:var(--text-sec);margin-bottom:4px}.fr{display:flex;gap:10px;margin-bottom:10px;flex-wrap:wrap}.fg{flex:1;min-width:140px}
40
+ select,input[type=password]{width:100%;padding:8px 11px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:var(--radius-xs);color:var(--text);font-family:var(--fb);font-size:12px;outline:none;transition:border-color .3s}
41
+ select:focus,input:focus{border-color:var(--accent)}
42
+ textarea{width:100%;min-height:60px;padding:8px 11px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:var(--radius-xs);color:var(--text);font-family:var(--fb);font-size:12px;line-height:1.6;resize:vertical;outline:none}textarea:focus{border-color:var(--accent)}textarea::placeholder{color:var(--text-muted);font-size:11px}
43
+ .uz{position:relative;border:2px dashed var(--border);border-radius:var(--radius);padding:28px 14px;text-align:center;cursor:pointer;transition:all .3s;background:var(--surface-alt)}
44
+ .uz:hover{border-color:var(--accent);background:rgba(99,102,241,.03)}.uz.hi{border-style:solid;border-color:var(--teal);background:#fff;padding:6px}
45
+ .uz input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer}.uz img{width:100%;max-height:280px;object-fit:contain;border-radius:var(--radius-sm)}
46
+ .uzi{font-size:26px;margin-bottom:4px;opacity:.6}.uzt{font-size:10.5px;color:var(--text-muted)}.uzt b{color:var(--text-sec)}
47
+ .uzs{padding:18px 10px}.uzs .uzi{font-size:20px;margin-bottom:2px}.uzs img{max-height:180px}
48
+ .btn{padding:11px 20px;border:none;border-radius:var(--radius-sm);cursor:pointer;font-family:var(--fb);font-weight:600;font-size:12.5px;transition:all .25s;width:100%;margin-top:10px}
49
+ .btn:disabled{opacity:.4;cursor:not-allowed;transform:none!important}.btn:hover{transform:translateY(-2px)}
50
+ .bb{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 4px 16px rgba(59,130,246,.25)}
51
+ .bp{background:linear-gradient(135deg,#8b5cf6,#7c3aed);color:#fff;box-shadow:0 4px 16px rgba(139,92,246,.25)}
52
+ .bk{background:linear-gradient(135deg,#ec4899,#db2777);color:#fff;box-shadow:0 4px 16px rgba(236,72,153,.25)}
53
+ .br{background:linear-gradient(135deg,#e11d48,#be123c);color:#fff;box-shadow:0 4px 16px rgba(225,29,72,.25)}
54
+ .rg{display:flex;gap:4px;flex-wrap:wrap}.rg label{display:flex;align-items:center;gap:3px;padding:5px 10px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:20px;cursor:pointer;font-size:10.5px;font-weight:500;color:var(--text-sec);transition:all .2s}
55
+ .rg input{display:none}.rg input:checked+label{background:rgba(99,102,241,.06);border-color:var(--accent);color:var(--accent);font-weight:700}
56
+ .kt{font-size:10px;color:var(--accent);cursor:pointer;font-weight:600;margin-bottom:8px;display:inline-flex;align-items:center;gap:3px}.kt:hover{text-decoration:underline}
57
+ .ks{display:none;margin-bottom:10px;padding:12px;background:var(--surface-alt);border-radius:var(--radius-sm);border:1px solid var(--border)}.ks.o{display:block}
58
+ /* Loader */
59
+ .ld{display:none;text-align:center;padding:36px 16px}.ld.a{display:block}
60
+ .lr{width:38px;height:38px;border:3px solid rgba(99,102,241,.12);border-top-color:var(--accent);border-radius:50%;animation:sp .9s linear infinite;margin:0 auto 10px;position:relative}
61
+ .lr::after{content:'';position:absolute;inset:4px;border:2px solid transparent;border-top-color:var(--teal);border-radius:50%;animation:sp 1.4s linear infinite reverse}
62
+ @keyframes sp{to{transform:rotate(360deg)}}.lt{font-size:12px;color:var(--text-sec);animation:br 2s ease-in-out infinite}@keyframes br{0%,100%{opacity:1}50%{opacity:.5}}
63
+ /* Scan */
64
+ .sco{display:none;position:relative;padding:44px 14px;text-align:center;background:linear-gradient(180deg,#0f172a,#1e1b4b 50%,#0f172a);border-radius:var(--radius);overflow:hidden;margin-bottom:14px}.sco.a{display:block;animation:sci .5s ease-out}
65
+ @keyframes sci{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
66
+ .sp{position:absolute;inset:0;overflow:hidden}.sp span{position:absolute;width:2px;height:2px;background:#818cf8;border-radius:50%;opacity:0;animation:pd 4s linear infinite}
67
+ @keyframes pd{0%{opacity:0;transform:translateY(100%) scale(0)}20%{opacity:.5}80%{opacity:.3}100%{opacity:0;transform:translateY(-100%) scale(1.5)}}
68
+ .fc{position:relative;width:160px;height:210px;margin:0 auto 16px}
69
+ .fm{width:100%;height:100%;filter:drop-shadow(0 0 14px rgba(99,102,241,.3))}
70
+ .fo{fill:none;stroke:#818cf8;stroke-width:1.5;stroke-linecap:round;opacity:0;animation:df 3s ease-out forwards}
71
+ @keyframes df{0%{stroke-dasharray:1200;stroke-dashoffset:1200;opacity:0}10%{opacity:1}100%{stroke-dashoffset:0;opacity:1}}
72
+ .ll{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,.4);border-radius:2px;animation:ls 2.8s ease-in-out infinite;z-index:5}
73
+ @keyframes ls{0%{top:8%;opacity:0}5%{opacity:1}50%{top:88%;opacity:1}55%{opacity:0}100%{top:8%;opacity:0}}
74
+ .sg{position:absolute;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 19px,rgba(99,102,241,.05) 19px,rgba(99,102,241,.05) 20px),repeating-linear-gradient(90deg,transparent,transparent 19px,rgba(99,102,241,.05) 19px,rgba(99,102,241,.05) 20px);animation:gp 3s ease-in-out infinite}@keyframes gp{0%,100%{opacity:.3}50%{opacity:.7}}
75
+ .sc{position:absolute;width:18px;height:18px;z-index:6}.sc::before,.sc::after{content:'';position:absolute;background:#0d9488;border-radius:1px}
76
+ .sc.tl{top:0;left:0}.sc.tr{top:0;right:0}.sc.bl{bottom:0;left:0}.sc.br{bottom:0;right:0}
77
+ .sc.tl::before,.sc.tr::before{top:0;height:2px;width:12px}.sc.tl::after,.sc.bl::after{left:0;width:2px;height:12px}
78
+ .sc.tr::before{right:0;left:auto}.sc.tr::after{right:0;width:2px;height:12px}.sc.bl::before{bottom:0;top:auto;height:2px;width:12px}.sc.bl::after{bottom:0;top:auto}
79
+ .sc.br::before{bottom:0;right:0;top:auto;left:auto;height:2px;width:12px}.sc.br::after{right:0;bottom:0;top:auto;width:2px;height:12px}
80
+ .pr{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border:1px solid rgba(99,102,241,.2);border-radius:50%;animation:pe 3s ease-out infinite}
81
+ @keyframes pe{0%{width:70px;height:95px;opacity:.5}100%{width:220px;height:290px;opacity:0}}
82
+ .sd{position:absolute;font-family:var(--fm);font-size:7.5px;color:#818cf8;opacity:0;animation:dfl 4s ease-in-out infinite;white-space:nowrap}
83
+ @keyframes dfl{0%,100%{opacity:0}20%{opacity:.7}60%{opacity:.8}80%{opacity:0}}
84
+ .ssm{font-family:var(--fd);font-size:13px;font-weight:700;color:#e2e8f0;margin-bottom:3px;animation:br 2s ease-in-out infinite}
85
+ .sss{font-family:var(--fm);font-size:9px;color:#818cf8;letter-spacing:1px}
86
+ .spb{width:50%;margin:8px auto 0;height:3px;background:rgba(99,102,241,.15);border-radius:3px;overflow:hidden}
87
+ .spf{height:100%;background:linear-gradient(90deg,#6366f1,#e11d48,#0d9488);background-size:200% 100%;border-radius:3px;animation:ps 2s linear infinite;transition:width .5s ease}
88
+ @keyframes ps{0%{background-position:100% 0}100%{background-position:-100% 0}}
89
+ /* Results */
90
+ .ra{margin-top:14px;animation:ri .5s ease-out}@keyframes ri{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
91
+ .rh{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px;box-shadow:var(--shadow-sm);font-size:12.5px;line-height:1.9;color:var(--text-sec);margin-top:10px}
92
+ .rh:empty{display:none}.rh h1,.rh h2,.rh h3{font-family:var(--fd);color:var(--text);margin:12px 0 5px}.rh h1{font-size:16px}.rh h2{font-size:13.5px}.rh h3{font-size:12px}
93
+ .rh blockquote{border-left:3px solid var(--accent);padding:5px 10px;background:rgba(99,102,241,.06);border-radius:0 var(--radius-xs) var(--radius-xs) 0;margin:8px 0;font-size:11px;color:var(--accent)}
94
+ .rh hr{border:none;border-top:1px solid var(--border);margin:12px 0}.rh li{margin-left:14px}
95
+ .riw{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);margin-bottom:12px}.riw img{width:100%;display:block}
96
+ .ril{padding:8px 12px;font-size:10.5px;font-weight:600;color:var(--text-sec);border-top:1px solid var(--border)}
97
+ .rgg{display:grid;grid-template-columns:1fr 1fr;gap:10px}.em{padding:12px;text-align:center;color:var(--rose);font-size:11.5px;background:var(--surface);border:1px solid rgba(225,29,72,.2);border-radius:var(--radius);margin-bottom:10px}.em:empty{display:none}
98
+ .rv:empty{display:none}
99
+ .ft{text-align:center;margin-top:36px;padding:18px 0;border-top:1px solid var(--border)}.ftb{font-family:var(--fd);font-size:11px;font-weight:600;color:var(--text-muted);margin-bottom:3px}
100
+ .ftd{font-size:10px;color:var(--text-muted)}.ftd a{color:var(--accent);text-decoration:none;font-weight:600}
101
+ .ftl{width:40px;height:2px;background:linear-gradient(90deg,transparent,var(--accent),var(--rose),transparent);margin:6px auto;border-radius:2px;opacity:.4}
102
+ .ftt{font-family:var(--fm);font-size:8px;color:var(--text-muted);opacity:.5;letter-spacing:1px}
103
+ @media(max-width:640px){.wrap{padding:12px 12px 32px}.hdr{padding:16px 0 10px}.hdr-t{font-size:28px}.nav{flex-wrap:wrap;position:static}.ni{font-size:10px;padding:8px 3px}.card{padding:16px 12px}.fr{flex-direction:column}.rgg{grid-template-columns:1fr}.fc{width:120px;height:160px}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  </style>
105
  </head>
106
  <body>
107
+ <div class="bg-p"></div>
108
+ <div class="wrap">
109
+ <header class="hdr">
110
+ <div class="hdr-eye">SOMA Multi-Agent System</div>
111
+ <h1 class="hdr-t"><span class="gt">Proto-AGI FACE</span></h1>
112
+ <div class="pills">
113
+ <span class="pill" style="background:linear-gradient(135deg,rgba(225,29,72,.08),rgba(249,115,22,.08));border-color:rgba(225,29,72,.25);color:#e11d48;font-weight:700">🏥 VIDRAFT</span>
114
+ <span class="pill">🩺 의사</span><span class="pill">🔮 관상</span><span class="pill">💡 창발</span><span class="pill">⚖️ 비평</span><span class="pill">🎯 감독</span>
 
 
 
 
115
  </div>
116
  </header>
 
 
117
  <nav class="nav">
118
+ <div class="ni a" data-t="face" onclick="st('face')">🔍 얼굴분석</div>
119
+ <div class="ni" data-t="physio" onclick="st('physio')">🔮 관상</div>
120
+ <div class="ni" data-t="compat" onclick="st('compat')">💑 궁합</div>
121
+ <div class="ni" data-t="sim" onclick="st('sim')">✨ 시뮬레이션</div>
122
  </nav>
123
+ <div style="margin-bottom:14px"><span class="kt" onclick="document.getElementById('ks').classList.toggle('o')">🔑 API Keys ▾</span>
124
+ <div class="ks" id="ks"><div class="fr"><div class="fg"><div class="fl">FAL Key</div><input type="password" id="fk" placeholder="fal_..."></div><div class="fg"><div class="fl">Fireworks Key</div><input type="password" id="wk" placeholder="fw_..."></div></div></div></div>
125
+
126
+ <!-- TAB 1: 얼굴 분석 -->
127
+ <div class="pnl a" id="p-face"><div class="card"><div class="ch"><div class="ci bl">🔍</div><div class="ct">의학적 얼굴 분석</div></div>
128
+ <div class="uz" id="uz-face" onclick="this.querySelector('input').click()"><div class="ph"><div class="uzi">🖼️</div><div class="uzt"><b>얼굴 사진 업로드</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'face')"></div>
129
+ <button class="btn bb" id="bf" onclick="runFace()">🔍 의학적 얼굴 분석 </button></div>
130
+ <div class="ld" id="ld-face"><div class="lr"></div><div class="lt">🩺 얼굴 구조 분석 중...</div></div>
131
+ <div class="ra"><div class="rh" id="rf"></div></div></div>
132
+
133
+ <!-- TAB 2: 관상 -->
134
+ <div class="pnl" id="p-physio"><div class="card"><div class="ch"><div class="ci pu">🔮</div><div class="ct">관상 분석</div></div>
135
+ <div class="uz" id="uz-physio" onclick="this.querySelector('input').click()"><div class="ph"><div class="uzi">🖼️</div><div class="uzt"><b>얼굴 사진 업로드</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'physio')"></div>
136
+ <button class="btn bp" id="bp" onclick="runPhysio()">🔮 관상 분석 시작</button></div>
137
+ <div class="ld" id="ld-physio"><div class="lr"></div><div class="lt">🔮 관상 분석 중...</div></div>
138
+ <div class="ra"><div class="rv" id="rpv"></div><div class="rh" id="rp"></div></div></div>
139
+
140
+ <!-- TAB 3: 궁-->
141
+ <div class="pnl" id="p-compat"><div class="card"><div class="ch"><div class="ci pk">💑</div><div class="ct">관상 궁합 분석</div></div>
142
+ <div class="fr"><div class="fg"><div class="fl">📸 나의 얼굴</div><div class="uz uzs" id="uz-c1" onclick="this.querySelector('input').click()"><div class="ph"><div class="uzi">🧑</div><div class="uzt"><b>사진 1</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'c1')"></div></div>
143
+ <div class="fg"><div class="fl">📸 상대방 얼굴</div><div class="uz uzs" id="uz-c2" onclick="this.querySelector('input').click()"><div class="ph"><div class="uzi">🧑</div><div class="uzt"><b>사진 2</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'c2')"></div></div></div>
144
+ <div class="fg"><div class="fl">🔗 관계 유형</div><div class="rg">
145
+ <input type="radio" name="rel" id="r1" value="💼 비즈니스 파트너"><label for="r1">💼 비즈니스</label>
146
+ <input type="radio" name="rel" id="r2" value="❤️ 연인 / 배우자" checked><label for="r2">❤️ 연인</label>
147
+ <input type="radio" name="rel" id="r3" value="👨‍👩‍👧 가족"><label for="r3">👨‍👩‍👧 가족</label>
148
+ <input type="radio" name="rel" id="r4" value="🤝 친구 / 동료"><label for="r4">🤝 친구</label>
149
+ </div></div>
150
+ <button class="btn bk" id="bc" onclick="runCompat()">💑 궁합 분석 시작</button></div>
151
+ <div class="ld" id="ld-compat"><div class="lr"></div><div class="lt">💑 궁합 분석 중...</div></div>
152
+ <div class="ra"><div class="rv" id="rcv"></div><div class="rh" id="rc"></div></div></div>
153
+
154
+ <!-- TAB 4: 시뮬레이션 -->
155
+ <div class="pnl" id="p-sim"><div class="card"><div class="ch"><div class="ci ro">✨</div><div class="ct">성형 시뮬레이션</div></div>
156
+ <div class="uz" id="uz-sim" onclick="this.querySelector('input').click()"><div class="ph"><div class="uzi">🖼️</div><div class="uzt"><b>얼굴 사진 업로드</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'sim')"></div>
157
+ <div class="fr" style="margin-top:10px"><div class="fg" style="flex:2"><div class="fl">🏥 시술 프리셋</div><select id="ps">
158
+ <option>없음 (직접 입력)</option><option>👁️ 자연유착 쌍꺼풀</option><option>👁️ 절개 쌍꺼풀</option><option>👃 코끝 성형</option><option>👃 콧대 성형</option>
159
+ <option>🦷 사각턱 축소</option><option>💉 팔자주름 필러</option><option>💉 이마 보톡스</option><option>💋 입술 필러</option><option>🏥 눈+코 동시</option><option>🏥 풀페이스 리프팅</option><option>✨ 동안 스타일</option>
160
+ </select></div><div class="fg" style="flex:1"><div class="fl">📐 비율</div><select id="ar"><option>auto</option><option>1:1</option><option>3:4</option><option>4:3</option></select></div></div>
161
+ <div class="fg"><div class="fl">✏️ 추가 지시</div><textarea id="cp" placeholder="프리셋 외 추가 요청"></textarea></div>
162
+ <div class="fg" style="margin-top:4px"><div class="fl">💪 강도</div><div class="rg">
163
+ <input type="radio" name="int" id="i1" value="자연스럽게 (Subtle)"><label for="i1">🌿 자연</label>
164
+ <input type="radio" name="int" id="i2" value="보통 (Moderate)" checked><label for="i2">⚡ 보통</label>
165
+ <input type="radio" name="int" id="i3" value="확실하게 (Strong)"><label for="i3">🔥 확실</label>
166
+ </div></div>
167
+ <button class="btn br" id="bs" onclick="runSim()">🚀 시뮬레이션 시작</button></div>
168
+ <!-- Scan Animation -->
169
+ <div class="sco" id="sco"><div class="sp" id="spp"></div><div class="sg"></div>
170
+ <div class="fc"><div class="pr"></div><div class="pr" style="animation-delay:1s"></div><div class="pr" style="animation-delay:2s"></div>
171
+ <div class="sc tl"></div><div class="sc tr"></div><div class="sc bl"></div><div class="sc br"></div><div class="ll"></div>
172
+ <svg class="fm" viewBox="0 0 220 280"><ellipse class="fo" cx="110" cy="145" rx="78" ry="105" style="stroke-dasharray:600"/>
173
+ <ellipse class="fo" cx="78" cy="118" rx="22" ry="10" style="animation-delay:.4s;stroke-dasharray:120;stroke:#e11d48;stroke-width:1.2"/>
174
+ <ellipse class="fo" cx="142" cy="118" rx="22" ry="10" style="animation-delay:.5s;stroke-dasharray:120;stroke:#e11d48;stroke-width:1.2"/>
175
+ <path class="fo" d="M52,98 Q78,84 102,98" style="animation-delay:.7s;stroke-dasharray:80;stroke-width:1"/>
176
+ <path class="fo" d="M118,98 Q142,84 168,98" style="animation-delay:.8s;stroke-dasharray:80;stroke-width:1"/>
177
+ <path class="fo" d="M110,128 L110,160 Q102,172 94,166 M110,160 Q118,172 126,166" style="animation-delay:1s;stroke-dasharray:100;stroke:#0d9488;stroke-width:1.2"/>
178
+ <path class="fo" d="M82,195 Q96,206 110,207 Q124,206 138,195" style="animation-delay:1.3s;stroke-dasharray:80;stroke:#f59e0b;stroke-width:1.2"/>
179
+ </svg>
180
+ <div class="sd" style="top:10%;left:-22%;animation-delay:.5s">SYMMETRY: ANALYZING</div>
181
+ <div class="sd" style="top:38%;right:-25%;animation-delay:1.2s">RATIO: 1.618</div>
182
+ <div class="sd" style="top:62%;left:-20%;animation-delay:2s">BONE: MAPPING</div>
183
+ <div class="sd" style="top:82%;right:-22%;animation-delay:2.8s">TISSUE: READY</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  </div>
185
+ <div style="position:relative;z-index:10"><div class="ssm" id="stx">🩺 분석 중...</div><div class="sss" id="ssu">SOMA Processing</div><div class="spb"><div class="spf" id="spg" style="width:5%"></div></div></div></div>
186
+ <!-- Results -->
187
+ <div id="sr" style="display:none"><div class="em" id="se"></div>
188
+ <div class="riw" id="sg" style="display:none"><img id="si"><div class="ril">🎨 시뮬레이션 결과</div></div>
189
+ <div id="sb" style="display:none;margin-top:10px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px"></div>
190
+ <div class="rgg" id="sa" style="display:none;margin-top:10px"><div class="riw"><img id="s45"><div class="ril">📐 45도</div></div><div class="riw"><img id="ssd"><div class="ril">📐 측면</div></div></div>
191
+ <div class="rh" id="srp" style="margin-top:10px"></div></div></div>
192
+
193
+ <footer class="ft"><div class="ftb">Proto-AGI FACE</div><div class="ftd"><a href="https://vidraft.net" target="_blank">VIDRAFT</a> · <a href="/gradio" style="color:var(--teal);font-weight:600">Gradio UI →</a></div><div class="ftl"></div><div class="ftt">SOMA × NANO-BANANA-2 × KIMI-K2P5 · 5-Agent</div></footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  </div>
 
195
  <script>
196
+ const B=window.location.origin,U={face:null,physio:null,c1:null,c2:null,sim:null};
197
+ function st(t){document.querySelectorAll('.ni').forEach(e=>e.classList.remove('a'));document.querySelectorAll('.pnl').forEach(e=>e.classList.remove('a'));document.querySelector(`[data-t="${t}"]`).classList.add('a');document.getElementById(`p-${t}`).classList.add('a')}
198
+ function hu(inp,k){const f=inp.files&&inp.files[0];if(!f)return;const z=inp.closest('.uz'),ph=z.querySelector('.ph'),im=z.querySelector('.ui');const r=new FileReader();r.onload=e=>{im.src=e.target.result;im.style.display='block';if(ph)ph.style.display='none';z.classList.add('hi')};r.readAsDataURL(f);ul(f,k)}
199
+ async function ul(f,k){const fd=new FormData();fd.append('files',f);for(const u of[`${B}/gradio/upload`,`${B}/gradio/gradio_api/upload`,`${B}/gradio_api/upload`,`${B}/upload`]){try{const r=await fetch(u,{method:'POST',body:fd});if(r.ok){const j=await r.json();U[k]=Array.isArray(j)?j[0]:j;return}}catch{}}}
200
+ function mf(p){return{path:p,meta:{_type:'gradio.FileData'},orig_name:'face.png',mime_type:'image/png'}}
201
+ async function gc(api,data){for(const u of[`${B}/gradio/call${api}`,`${B}/gradio/api${api}`,`${B}/gradio/gradio_api/call${api}`,`${B}/api${api}`,`${B}/gradio_api/call${api}`]){try{const r=await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({data})});if(!r.ok)continue;const j=await r.json();if(!j.event_id)continue;return new Promise((ok,no)=>{const es=new EventSource(`${u}/${j.event_id}`);let d=false;const h=e=>{if(d)return;try{const raw=JSON.parse(e.data),p=Array.isArray(raw)?raw:(raw&&raw.data?raw.data:null);if(p){d=true;es.close();ok(p)}}catch{}};es.onmessage=h;es.addEventListener('complete',h);es.addEventListener('process_completed',e=>{if(d)return;try{const raw=JSON.parse(e.data),out=raw&&raw.output&&raw.output.data?raw.output.data:null;if(out){d=true;es.close();ok(out)}}catch{}});es.onerror=()=>{if(!d){d=true;es.close();no(new Error('연결 오류'))}};setTimeout(()=>{if(!d){d=true;es.close();no(new Error('시간 초과'))}},180000)})}catch{}}throw new Error('API 연결 실패')}
202
+ function iu(d){if(!d)return null;if(typeof d==='string'){if(d.startsWith('/file='))return B+'/gradio'+d;return d}const u=d.url||d.path||(d.value&&(d.value.url||d.value.path));if(!u)return null;if(u.startsWith('/file='))return B+'/gradio'+u;if(!u.startsWith('http'))return B+'/gradio/file='+u;return u}
203
+ function md(s){return s.replace(/^### (.*$)/gm,'<h3>$1</h3>').replace(/^## (.*$)/gm,'<h2>$1</h2>').replace(/^# (.*$)/gm,'<h1>$1</h1>').replace(/\*\*(.*?)\*\*/g,'<b>$1</b>').replace(/\*(.*?)\*/g,'<em>$1</em>').replace(/^> (.*$)/gm,'<blockquote>$1</blockquote>').replace(/^---$/gm,'<hr>').replace(/^- (.*$)/gm,'<li>$1</li>').replace(/\n\n/g,'<br><br>').replace(/\n/g,'<br>')}
204
+ function sl(id){document.getElementById(id).classList.add('a')}function hl(id){document.getElementById(id).classList.remove('a')}
205
+ /* 1. Face */
206
+ async function runFace(){if(!U.face){alert('사진을 업로드해주세요.');return}const b=document.getElementById('bf');b.disabled=true;document.getElementById('rf').innerHTML='';sl('ld-face');try{const r=await gc('/face_analysis',[mf(U.face),document.getElementById('wk').value]);hl('ld-face');document.getElementById('rf').innerHTML=md(r[0]||'')}catch(e){hl('ld-face');document.getElementById('rf').innerHTML='<div class="em">⚠️ '+e.message+'</div>'}b.disabled=false}
207
+ /* 2. Physio */
208
+ async function runPhysio(){if(!U.physio){alert('사진을 업로드해주세요.');return}const b=document.getElementById('bp');b.disabled=true;document.getElementById('rpv').innerHTML='';document.getElementById('rp').innerHTML='';sl('ld-physio');try{const r=await gc('/physiognomy_analysis',[mf(U.physio),document.getElementById('wk').value]);hl('ld-physio');document.getElementById('rpv').innerHTML=r[0]||'';document.getElementById('rp').innerHTML=md(r[1]||'')}catch(e){hl('ld-physio');document.getElementById('rp').innerHTML='<div class="em">⚠️ '+e.message+'</div>'}b.disabled=false}
209
+ /* 3. Compat */
210
+ async function runCompat(){if(!U.c1||!U.c2){alert('두 사람의 사진을 모두 업로드해주세요.');return}const b=document.getElementById('bc');b.disabled=true;document.getElementById('rcv').innerHTML='';document.getElementById('rc').innerHTML='';sl('ld-compat');const rel=document.querySelector('input[name="rel"]:checked').value;try{const r=await gc('/compatibility_analysis',[mf(U.c1),mf(U.c2),rel,document.getElementById('wk').value]);hl('ld-compat');document.getElementById('rcv').innerHTML=r[0]||'';document.getElementById('rc').innerHTML=md(r[1]||'')}catch(e){hl('ld-compat');document.getElementById('rc').innerHTML='<div class="em">⚠️ '+e.message+'</div>'}b.disabled=false}
211
+ /* 4. Sim — scan animation */
212
+ const SS=[{t:'🩺 얼굴 분석 중...',s:'Doctor Agent',p:12},{t:'💡 프롬프트 최적화...',s:'Creator Agent',p:25},{t:'🎨 이미지 생성 중...',s:'Nano Banana 2',p:45},{t:'⚖️ 품질 검증...',s:'Critic Agent',p:65},{t:'📐 다각도 생성...',s:'Angle Gen',p:78},{t:'🎯 리포트 작성...',s:'Director Agent',p:90}];
213
+ let si=0,stm=null;
214
+ function startSc(){const o=document.getElementById('sco');o.style.display='block';o.classList.add('a');const c=document.getElementById('spp');c.innerHTML='';for(let i=0;i<20;i++){const s=document.createElement('span');s.style.left=Math.random()*100+'%';s.style.animationDelay=Math.random()*4+'s';s.style.animationDuration=(3+Math.random()*3)+'s';c.appendChild(s)}si=0;uSc();stm=setInterval(()=>{si++;if(si<SS.length)uSc()},5000)}
215
+ function uSc(){const s=SS[si];document.getElementById('stx').textContent=s.t;document.getElementById('ssu').textContent=s.s;document.getElementById('spg').style.width=s.p+'%'}
216
+ function stopSc(){if(stm)clearInterval(stm);const o=document.getElementById('sco');o.classList.remove('a');o.style.display='none'}
217
+ async function runSim(){if(!U.sim){alert('사진을 업로드해주세요.');return}const b=document.getElementById('bs');b.disabled=true;document.getElementById('sr').style.display='none';['sg','sb','sa'].forEach(id=>{document.getElementById(id).style.display='none'});document.getElementById('se').textContent='';document.getElementById('srp').innerHTML='';startSc();try{const r=await gc('/run_simulation',[mf(U.sim),document.getElementById('cp').value,document.getElementById('ps').value,document.querySelector('input[name="int"]:checked').value,document.getElementById('ar').value,document.getElementById('fk').value,document.getElementById('wk').value]);stopSc();document.getElementById('sr').style.display='block';const gi=iu(r[0]),ba=r[1]||'',rp=r[2]||'',a45=iu(r[3]),as=iu(r[4]),er=r[5]||'';if(er)document.getElementById('se').textContent='⚠️ '+er;if(gi){document.getElementById('si').src=gi;document.getElementById('sg').style.display='block'}if(ba){document.getElementById('sb').innerHTML=ba;document.getElementById('sb').style.display='block'}if(a45||as){if(a45)document.getElementById('s45').src=a45;if(as)document.getElementById('ssd').src=as;document.getElementById('sa').style.display='grid'}if(rp)document.getElementById('srp').innerHTML=md(rp);document.getElementById('sr').scrollIntoView({behavior:'smooth'})}catch(e){stopSc();document.getElementById('sr').style.display='block';document.getElementById('se').textContent='⚠️ '+e.message}b.disabled=false}
218
+ document.querySelectorAll('.uz').forEach(z=>{z.addEventListener('dragover',e=>{e.preventDefault();z.style.borderColor='var(--accent)'});z.addEventListener('dragleave',()=>{z.style.borderColor=''});z.addEventListener('drop',e=>{e.preventDefault();z.style.borderColor='';const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f&&f.type.startsWith('image/')){const inp=z.querySelector('input[type=file]');const dt=new DataTransfer();dt.items.add(f);inp.files=dt.files;inp.dispatchEvent(new Event('change'))}})});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  </script>
220
  </body>
221
  </html>