aiqtech commited on
Commit
e927f90
·
verified ·
1 Parent(s): 555dbdf

Upload index (39).html

Browse files
Files changed (1) hide show
  1. 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>