FlorinONE commited on
Commit
39cd1f2
·
verified ·
1 Parent(s): 13249a6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1019 -19
index.html CHANGED
@@ -1,19 +1,1019 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ro">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>VideoGen Pro · Generator 9:16</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;400;500;600;700;800;900&display=swap" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --bg: #02040a;
12
+ --border: rgba(100, 200, 255, 0.14);
13
+ --text: #e8edf5;
14
+ --muted: #9aa3c7;
15
+ --accent: #64c8ff;
16
+ --gold: #f5d742;
17
+ --radius: 22px;
18
+ --shadow: 0 16px 48px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.04);
19
+ --bg-main: #02040a;
20
+ --bg-card: #0b0f19;
21
+ --bg-input: #1a1f2e;
22
+ --border-color: #2a3344;
23
+ --accent-color: #10b981;
24
+ --text-main: #e8edf5;
25
+ --text-secondary: #9aa3c7;
26
+ }
27
+ * { box-sizing: border-box; margin: 0; padding: 0; }
28
+ body {
29
+ background: var(--bg-main);
30
+ color: var(--text-main);
31
+ font-family: 'Inter', sans-serif;
32
+ min-height: 100vh;
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ padding: 20px;
37
+ }
38
+ .bg {
39
+ position: fixed; inset: 0; z-index: -2;
40
+ background:
41
+ radial-gradient(ellipse at 15% 20%, rgba(100,200,255,0.08), transparent 50%),
42
+ radial-gradient(ellipse at 85% 10%, rgba(255,107,203,0.07), transparent 45%),
43
+ radial-gradient(ellipse at 50% 90%, rgba(155,93,229,0.06), transparent 50%),
44
+ #02040a;
45
+ }
46
+ .grid-overlay {
47
+ position: fixed; inset: 0; z-index: -1; pointer-events: none;
48
+ background-image:
49
+ linear-gradient(rgba(100,200,255,0.03) 1px, transparent 1px),
50
+ linear-gradient(90deg, rgba(100,200,255,0.03) 1px, transparent 1px);
51
+ background-size: 48px 48px;
52
+ mask-image: radial-gradient(ellipse at center, black 20%, transparent 75%);
53
+ }
54
+ .header {
55
+ position: sticky; top: 0; z-index: 1000;
56
+ background: rgba(2, 4, 10, 0.85);
57
+ backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
58
+ border-bottom: 1px solid var(--border);
59
+ padding: 0 24px; height: 76px;
60
+ display: flex; align-items: center; justify-content: space-between;
61
+ margin: 0 auto 20px; width: 100%; max-width: 1400px;
62
+ }
63
+ .brand { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.1; }
64
+ .brand-name {
65
+ font-size: 1.6rem; font-weight: 800; letter-spacing: 1.5px;
66
+ color: var(--text); display: flex; align-items: baseline; gap: 6px;
67
+ }
68
+ .brand-name span {
69
+ background: linear-gradient(135deg, #10b981, #64c8ff);
70
+ -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
71
+ }
72
+ .brand-sub {
73
+ font-size: 0.55rem; font-weight: 400; letter-spacing: 0.8px;
74
+ text-transform: uppercase; opacity: 0.85; margin-top: -2px;
75
+ }
76
+ .nav-links a {
77
+ font-size: 0.8rem; font-weight: 500; padding: 8px 18px; border-radius: 40px;
78
+ border: 1px solid var(--border); color: rgba(255,255,255,0.35);
79
+ transition: all 0.3s; background: rgba(255,255,255,0.02); text-decoration: none;
80
+ display: inline-flex; align-items: center; gap: 6px;
81
+ }
82
+ .nav-links a:hover { color: #e8edf5; border-color: var(--accent); background: rgba(100,200,255,0.06); }
83
+ @media (max-width: 768px) {
84
+ .header { padding: 0 14px; height: 64px; }
85
+ .brand-name { font-size: 1.25rem; }
86
+ }
87
+ .footer {
88
+ border-top: 1px solid var(--border);
89
+ text-align: center; padding: 28px 16px 36px;
90
+ color: #6a6f8a; font-size: 0.7rem;
91
+ width: 100%; max-width: 1400px; margin: 40px auto 0;
92
+ }
93
+ .footer strong { color: var(--muted); }
94
+ .studio-container {
95
+ background: var(--bg-card);
96
+ border: 1px solid var(--border-color);
97
+ border-radius: var(--radius);
98
+ width: 100%; max-width: 1140px;
99
+ display: grid;
100
+ grid-template-columns: 1fr 380px;
101
+ gap: 28px; padding: 28px;
102
+ box-shadow: var(--shadow);
103
+ }
104
+ @media (max-width: 860px) {
105
+ .studio-container { grid-template-columns: 1fr; gap: 22px; padding: 18px; }
106
+ }
107
+ .control-panel { display: flex; flex-direction: column; gap: 15px; }
108
+ .panel-header h1 { font-size: 26px; font-weight: 800; letter-spacing: -0.3px; }
109
+ .panel-header h1 span {
110
+ background: linear-gradient(135deg, #10b981, #64c8ff);
111
+ -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
112
+ }
113
+ .panel-header p { font-size: 13.5px; color: var(--text-secondary); margin-top: 3px; }
114
+
115
+ .mode-switch {
116
+ display: flex; background: var(--bg-input); border-radius: 40px;
117
+ padding: 4px; border: 1px solid var(--border-color); gap: 4px;
118
+ }
119
+ .mode-btn {
120
+ flex: 1; padding: 9px 14px; border-radius: 36px; border: none;
121
+ background: transparent; color: var(--text-secondary); font-size: 13px;
122
+ font-weight: 600; cursor: pointer; transition: 0.25s; font-family: Inter, sans-serif;
123
+ display: flex; align-items: center; justify-content: center; gap: 7px;
124
+ }
125
+ .mode-btn.active {
126
+ background: linear-gradient(105deg, #059669, #10b981);
127
+ color: #02040a; box-shadow: 0 2px 12px rgba(16,185,129,0.3);
128
+ }
129
+ .mode-btn:not(.active):hover { color: var(--text-main); background: rgba(255,255,255,0.04); }
130
+
131
+ .info-box {
132
+ background: rgba(100,200,255,0.06);
133
+ border: 1px solid rgba(100,200,255,0.18);
134
+ border-radius: 12px; padding: 11px 13px;
135
+ font-size: 12px; color: var(--muted); line-height: 1.5;
136
+ }
137
+ .info-box strong { color: #64c8ff; }
138
+ .info-box code { font-size: 11px; background: rgba(0,0,0,0.3); padding: 1px 5px; border-radius: 4px; }
139
+
140
+ .input-group { display: flex; flex-direction: column; gap: 7px; }
141
+ label {
142
+ font-size: 11px; font-weight: 600; text-transform: uppercase;
143
+ letter-spacing: 1px; color: var(--accent-color);
144
+ display: flex; align-items: center; gap: 6px;
145
+ }
146
+ textarea {
147
+ background: var(--bg-input); border: 1px solid var(--border-color);
148
+ border-radius: 10px; color: var(--text-main); padding: 12px 14px;
149
+ font-size: 13.5px; line-height: 1.55; resize: vertical;
150
+ min-height: 90px; outline: none; font-family: Inter, sans-serif;
151
+ transition: border-color 0.25s;
152
+ }
153
+ textarea:focus { border-color: var(--accent-color); }
154
+ textarea.sm { min-height: 54px; }
155
+
156
+ .upload-zone {
157
+ border: 2px dashed var(--border-color);
158
+ border-radius: 12px; padding: 22px 16px;
159
+ text-align: center; cursor: pointer; transition: 0.25s;
160
+ background: rgba(255,255,255,0.02); position: relative;
161
+ }
162
+ .upload-zone:hover, .upload-zone.dragover {
163
+ border-color: var(--accent-color);
164
+ background: rgba(16,185,129,0.06);
165
+ }
166
+ .upload-zone.has-image { border-style: solid; padding: 0; overflow: hidden; }
167
+ .upload-zone input[type="file"] { display: none; }
168
+ .upload-placeholder { color: var(--text-secondary); font-size: 13px; line-height: 1.5; }
169
+ .upload-placeholder i { font-size: 28px; display: block; margin-bottom: 8px; color: var(--border-color); }
170
+ .upload-preview {
171
+ width: 100%; max-height: 200px; object-fit: contain; display: none;
172
+ background: #000;
173
+ }
174
+ .upload-zone.has-image .upload-preview { display: block; }
175
+ .upload-zone.has-image .upload-placeholder { display: none; }
176
+ .upload-clear {
177
+ position: absolute; top: 8px; right: 8px;
178
+ background: rgba(0,0,0,0.7); border: none; color: #fff;
179
+ width: 28px; height: 28px; border-radius: 50%; cursor: pointer;
180
+ display: none; align-items: center; justify-content: center; font-size: 12px;
181
+ z-index: 2;
182
+ }
183
+ .upload-zone.has-image .upload-clear { display: flex; }
184
+
185
+ .row {
186
+ display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
187
+ }
188
+ .row label { min-width: 72px; font-size: 11px; }
189
+ .row input, .row select {
190
+ flex: 1; min-width: 130px;
191
+ background: var(--bg-input); border: 1px solid var(--border-color);
192
+ border-radius: 40px; padding: 8px 14px;
193
+ color: var(--text-main); font-size: 13px; outline: none;
194
+ font-family: Inter, sans-serif;
195
+ }
196
+ .row input:focus, .row select:focus { border-color: var(--accent-color); }
197
+ .row select option { background: var(--bg-card); }
198
+
199
+ .examples { display: flex; flex-wrap: wrap; gap: 6px; }
200
+ .example-chip {
201
+ font-size: 11px; padding: 5px 11px; border-radius: 20px;
202
+ background: rgba(255,255,255,0.04); border: 1px solid var(--border-color);
203
+ color: var(--text-secondary); cursor: pointer; transition: 0.2s;
204
+ }
205
+ .example-chip:hover {
206
+ background: rgba(16,185,129,0.12); border-color: var(--accent-color); color: var(--accent-color);
207
+ }
208
+
209
+ .btn {
210
+ display: inline-flex; align-items: center; justify-content: center;
211
+ gap: 7px; padding: 11px 20px; border-radius: 40px;
212
+ font-weight: 600; font-size: 13.5px; border: none;
213
+ cursor: pointer; transition: 0.25s; font-family: Inter, sans-serif;
214
+ }
215
+ .btn-primary {
216
+ background: linear-gradient(105deg, #059669, #10b981);
217
+ color: #02040a; box-shadow: 0 4px 18px rgba(16,185,129,0.25);
218
+ }
219
+ .btn-primary:hover:not(:disabled) {
220
+ transform: translateY(-2px); box-shadow: 0 8px 28px rgba(16,185,129,0.35);
221
+ }
222
+ .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
223
+ .btn-secondary {
224
+ background: rgba(255,255,255,0.06); color: var(--text-secondary);
225
+ border: 1px solid var(--border-color);
226
+ }
227
+ .btn-secondary:hover { background: rgba(255,255,255,0.1); }
228
+ .btn-test {
229
+ background: rgba(245,215,66,0.12); color: #f5d742;
230
+ border: 1px solid rgba(245,215,66,0.2);
231
+ }
232
+ .btn-test:hover { background: rgba(245,215,66,0.2); }
233
+ .btn-proxy {
234
+ background: rgba(100,200,255,0.10); color: #64c8ff;
235
+ border: 1px solid rgba(100,200,255,0.2); padding: 6px 13px; font-size: 12px;
236
+ white-space: nowrap;
237
+ }
238
+ .btn-proxy.active { background: rgba(100,200,255,0.25); border-color: #64c8ff; }
239
+ .btn-proxy.proxy-error { border-color: #ef4444; color: #ef4444; background: rgba(239,68,68,0.1); }
240
+ .action-row { display: flex; flex-wrap: wrap; gap: 9px; }
241
+ .action-row .btn { flex: 1; min-width: 95px; }
242
+
243
+ .status-box { display: flex; flex-direction: column; gap: 6px; }
244
+ .progress-bar-container {
245
+ background: var(--bg-input); border: 1px solid var(--border-color);
246
+ border-radius: 4px; height: 9px; overflow: hidden; display: none;
247
+ }
248
+ .progress-bar-container.show { display: block; }
249
+ .progress-bar-fill {
250
+ background: linear-gradient(90deg, #059669, #10b981);
251
+ height: 100%; width: 0%; transition: width 0.45s ease;
252
+ }
253
+ .status-text {
254
+ font-size: 12.5px; font-weight: 500; color: var(--text-secondary);
255
+ min-height: 20px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
256
+ }
257
+ .status-text .timer { color: var(--accent-color); font-weight: 600; font-variant-numeric: tabular-nums; }
258
+ .status-text.error { color: #ef4444; }
259
+ .status-text.success { color: var(--accent-color); }
260
+ .error-detail {
261
+ font-size: 11.5px; color: #ef4444; word-break: break-word; display: none;
262
+ white-space: pre-wrap; background: rgba(239,68,68,0.08); padding: 9px 11px;
263
+ border-radius: 8px; border: 1px solid rgba(239,68,68,0.2);
264
+ }
265
+ .warning-box {
266
+ background: rgba(245,215,66,0.08); border: 1px solid rgba(245,215,66,0.2);
267
+ border-radius: 10px; padding: 9px 13px; font-size: 12.5px;
268
+ color: #f5d742; display: none;
269
+ }
270
+ .warning-box.show { display: block; }
271
+ .proxy-row {
272
+ display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
273
+ }
274
+ .proxy-row input {
275
+ flex: 1; min-width: 130px;
276
+ background: var(--bg-input); border: 1px solid var(--border-color);
277
+ border-radius: 40px; padding: 6px 13px; color: var(--text-main);
278
+ font-size: 12px; outline: none; font-family: Inter, sans-serif;
279
+ }
280
+ .proxy-row .proxy-select {
281
+ flex: 0 0 auto; min-width: 110px;
282
+ background: var(--bg-input); border: 1px solid var(--border-color);
283
+ border-radius: 40px; padding: 6px 12px; color: var(--text-main);
284
+ font-size: 11px; outline: none; font-family: Inter, sans-serif;
285
+ }
286
+
287
+ .preview-panel { display: flex; flex-direction: column; align-items: center; gap: 11px; }
288
+ .preview-panel > label {
289
+ font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
290
+ color: var(--accent-color); align-self: flex-start;
291
+ }
292
+ .viewport-9-16 {
293
+ background: #02040a; border: 2px dashed var(--border-color);
294
+ border-radius: 12px; width: 100%; aspect-ratio: 9 / 16;
295
+ max-height: 560px; display: flex; justify-content: center;
296
+ align-items: center; position: relative; overflow: hidden;
297
+ transition: border-color 0.35s;
298
+ }
299
+ .viewport-9-16.active { border-style: solid; border-color: var(--accent-color); }
300
+ .placeholder-text {
301
+ color: var(--text-secondary); font-size: 13.5px; text-align: center;
302
+ padding: 18px; pointer-events: none; line-height: 1.55;
303
+ }
304
+ .placeholder-text i { font-size: 30px; display: block; margin-bottom: 9px; color: var(--border-color); }
305
+ video { width: 100%; height: 100%; object-fit: cover; display: none; }
306
+ .download-btn {
307
+ display: none; background: transparent; border: 1px solid var(--accent-color);
308
+ color: var(--accent-color); padding: 8px 18px; border-radius: 40px;
309
+ font-size: 13px; font-weight: 600; cursor: pointer;
310
+ text-decoration: none; transition: 0.25s; font-family: Inter, sans-serif;
311
+ }
312
+ .download-btn:hover { background: var(--accent-color); color: #02040a; }
313
+ .hint { font-size: 11px; color: var(--text-secondary); text-align: center; }
314
+ .hidden { display: none !important; }
315
+
316
+ @media (max-width: 480px) {
317
+ .action-row .btn { min-width: 100%; }
318
+ .row { flex-direction: column; align-items: stretch; }
319
+ .row input, .row select { min-width: unset; }
320
+ .proxy-row .proxy-select { flex: 1; min-width: 100%; }
321
+ }
322
+ </style>
323
+ </head>
324
+ <body>
325
+ <div class="bg"></div>
326
+ <div class="grid-overlay"></div>
327
+
328
+ <header class="header">
329
+ <div class="brand">
330
+ <div class="brand-name">One<span>Studio</span></div>
331
+ <div class="brand-sub">Hub · Creative Tools</div>
332
+ </div>
333
+ <nav class="nav-links">
334
+ <a href="index.html" title="Acasă"><i class="fas fa-home"></i></a>
335
+ </nav>
336
+ </header>
337
+
338
+ <div class="studio-container">
339
+ <div class="control-panel">
340
+ <div class="panel-header">
341
+ <h1>Video<span>Gen</span> Pro</h1>
342
+ <p>Text-to-Video & Image-to-Video · 9:16 · Hugging Face</p>
343
+ </div>
344
+
345
+ <!-- Mode switcher -->
346
+ <div class="mode-switch">
347
+ <button class="mode-btn active" id="modeT2V" data-mode="t2v">
348
+ <i class="fas fa-font"></i> Text → Video
349
+ </button>
350
+ <button class="mode-btn" id="modeI2V" data-mode="i2v">
351
+ <i class="fas fa-image"></i> Image → Video
352
+ </button>
353
+ </div>
354
+
355
+ <div class="info-box" id="infoBox">
356
+ <strong>Modele suportate (serverless):</strong><br>
357
+ <strong>T2V:</strong> damo-vilab/text-to-video-ms-1.7b &nbsp;|&nbsp;
358
+ <strong>I2V:</strong> stabilityai/stable-video-diffusion-img2vid-xt · ali-vilab/i2vgen-xl<br>
359
+ <span style="color:#64c8ff;font-size:11px;">💡 Dacă ai eroare CORS, activează Proxy-ul de mai jos.</span>
360
+ </div>
361
+
362
+ <!-- Image upload (I2V only) -->
363
+ <div class="input-group hidden" id="imageGroup">
364
+ <label><i class="fas fa-image"></i> Imagine de start</label>
365
+ <div class="upload-zone" id="uploadZone">
366
+ <input type="file" id="imageInput" accept="image/png,image/jpeg,image/webp">
367
+ <div class="upload-placeholder">
368
+ <i class="fas fa-cloud-upload-alt"></i>
369
+ Click sau drag & drop o imagine<br>
370
+ <span style="font-size:11px;opacity:0.7">PNG / JPG / WEBP · max ~5 MB</span>
371
+ </div>
372
+ <img class="upload-preview" id="uploadPreview" alt="preview">
373
+ <button class="upload-clear" id="uploadClear" title="Șterge"><i class="fas fa-times"></i></button>
374
+ </div>
375
+ </div>
376
+
377
+ <div class="input-group">
378
+ <label for="prompt"><i class="fas fa-pen"></i> Prompt cinematic (engleză)</label>
379
+ <textarea id="prompt" placeholder="Ex: A majestic dragon flying over a futuristic city...">A majestic dragon flying over a futuristic neon city at night, cinematic lighting, vertical 9:16, highly detailed</textarea>
380
+ <div class="examples" id="examplePrompts">
381
+ <span class="example-chip" data-prompt="A cyberpunk girl walking through rainy Tokyo streets at night, neon reflections, vertical 9:16, cinematic">Cyberpunk Tokyo</span>
382
+ <span class="example-chip" data-prompt="A majestic eagle soaring over snowy mountains at sunrise, dramatic clouds, vertical 9:16, epic">Eagle Mountains</span>
383
+ <span class="example-chip" data-prompt="Astronaut floating near a glowing nebula, slow camera push, vertical 9:16, photorealistic">Space</span>
384
+ <span class="example-chip" data-prompt="A cat wearing sunglasses surfing a giant wave, sunny day, playful, vertical 9:16">Cat Surfing</span>
385
+ </div>
386
+ </div>
387
+
388
+ <div class="input-group">
389
+ <label for="negativePrompt"><i class="fas fa-ban"></i> Negative prompt</label>
390
+ <textarea id="negativePrompt" class="sm">blurry, low quality, distorted faces, watermark, text, logo, oversaturated, ugly</textarea>
391
+ </div>
392
+
393
+ <div class="row">
394
+ <label><i class="fas fa-key"></i> API Key</label>
395
+ <input type="password" id="hfApiKey" placeholder="hf_xxxxxxxx" value="" autocomplete="off" />
396
+ </div>
397
+
398
+ <div class="row">
399
+ <label><i class="fas fa-cube"></i> Model</label>
400
+ <select id="modelSelect">
401
+ <optgroup label="Text → Video" id="t2vModels">
402
+ <option value="damo-vilab/text-to-video-ms-1.7b" selected>damo-vilab/text-to-video-ms-1.7b</option>
403
+ </optgroup>
404
+ <optgroup label="Image → Video" id="i2vModels" disabled>
405
+ <option value="stabilityai/stable-video-diffusion-img2vid-xt" selected>Stable Video Diffusion XT</option>
406
+ <option value="ali-vilab/i2vgen-xl">I2VGen-XL</option>
407
+ </optgroup>
408
+ </select>
409
+ </div>
410
+
411
+ <div class="row">
412
+ <label><i class="fas fa-film"></i> Cadre</label>
413
+ <select id="numFrames">
414
+ <option value="16">16 (~2s)</option>
415
+ <option value="24" selected>24 (~3s)</option>
416
+ <option value="25">25</option>
417
+ <option value="32">32 (~4s)</option>
418
+ <option value="48">48 (~6s)</option>
419
+ </select>
420
+ </div>
421
+
422
+ <div class="row">
423
+ <label><i class="fas fa-dice"></i> Seed</label>
424
+ <input type="number" id="seedInput" placeholder="aleatoriu" min="0" max="999999999" />
425
+ </div>
426
+
427
+ <div class="warning-box" id="corsWarning">
428
+ <i class="fas fa-exclamation-triangle"></i>
429
+ <strong>file:// detectat!</strong> Proxy-ul CORS a fost activat automat.
430
+ </div>
431
+
432
+ <div class="proxy-row">
433
+ <label style="font-size:11px;color:var(--text-secondary);"><i class="fas fa-shield-alt"></i> Proxy</label>
434
+ <select class="proxy-select" id="proxySelect">
435
+ <option value="https://corsproxy.io/?url=">corsproxy.io</option>
436
+ <option value="https://api.allorigins.win/raw?url=" selected>allorigins.win</option>
437
+ <option value="https://cors-anywhere.herokuapp.com/">cors-anywhere</option>
438
+ <option value="https://proxy.cors.sh/">cors.sh</option>
439
+ </select>
440
+ <input type="text" id="proxyInput" value="https://api.allorigins.win/raw?url=" />
441
+ <button class="btn btn-proxy active" id="proxyToggleBtn"><i class="fas fa-toggle-on"></i> Activ</button>
442
+ </div>
443
+
444
+ <div class="action-row">
445
+ <button class="btn btn-primary" id="generateBtn"><i class="fas fa-magic"></i> Generează</button>
446
+ <button class="btn btn-test" id="testApiBtn"><i class="fas fa-vial"></i> Test</button>
447
+ <button class="btn btn-secondary" id="copyBtn"><i class="fas fa-copy"></i> Copiază</button>
448
+ <button class="btn btn-secondary" id="clearBtn"><i class="fas fa-undo-alt"></i> Reset</button>
449
+ </div>
450
+
451
+ <div class="status-box">
452
+ <div class="progress-bar-container" id="progressContainer">
453
+ <div class="progress-bar-fill" id="progressBar"></div>
454
+ </div>
455
+ <div class="status-text" id="statusText">
456
+ <span>Alege modul, scrie promptul și apasă Generează.</span>
457
+ <span class="timer" id="timerDisplay"></span>
458
+ </div>
459
+ <div class="error-detail" id="errorDetail"></div>
460
+ </div>
461
+ </div>
462
+
463
+ <div class="preview-panel">
464
+ <label><i class="fas fa-eye"></i> Previzualizare 9:16</label>
465
+ <div id="viewport" class="viewport-9-16">
466
+ <div id="placeholder" class="placeholder-text">
467
+ <i class="fas fa-film"></i>
468
+ Niciun video randat.<br>Introdu un prompt pentru a începe.
469
+ </div>
470
+ <video id="videoResult" controls loop playsinline></video>
471
+ </div>
472
+ <a id="downloadBtn" class="download-btn" download="videogen_9_16.mp4">
473
+ <i class="fas fa-download"></i> Descarcă MP4
474
+ </a>
475
+ <div class="hint"><i class="fas fa-info-circle"></i> Proxy activat implicit pentru file://.</div>
476
+ </div>
477
+ </div>
478
+
479
+ <footer class="footer">
480
+ <p>&copy; 2026 <strong>OneStudio Hub</strong> · Update 07.08.2026 · T2V + I2V</p>
481
+ </footer>
482
+
483
+ <script>
484
+ (function () {
485
+ 'use strict';
486
+
487
+ // --- DOM ---
488
+ const promptInput = document.getElementById('prompt');
489
+ const negativeInput = document.getElementById('negativePrompt');
490
+ const hfKeyInput = document.getElementById('hfApiKey');
491
+ const modelSelect = document.getElementById('modelSelect');
492
+ const numFramesSelect = document.getElementById('numFrames');
493
+ const seedInput = document.getElementById('seedInput');
494
+ const proxyInput = document.getElementById('proxyInput');
495
+ const proxySelect = document.getElementById('proxySelect');
496
+ const proxyToggleBtn = document.getElementById('proxyToggleBtn');
497
+ const corsWarning = document.getElementById('corsWarning');
498
+ const generateBtn = document.getElementById('generateBtn');
499
+ const testApiBtn = document.getElementById('testApiBtn');
500
+ const copyBtn = document.getElementById('copyBtn');
501
+ const clearBtn = document.getElementById('clearBtn');
502
+ const statusText = document.getElementById('statusText');
503
+ const errorDetail = document.getElementById('errorDetail');
504
+ const progressContainer = document.getElementById('progressContainer');
505
+ const progressBar = document.getElementById('progressBar');
506
+ const viewport = document.getElementById('viewport');
507
+ const placeholder = document.getElementById('placeholder');
508
+ const videoResult = document.getElementById('videoResult');
509
+ const downloadBtn = document.getElementById('downloadBtn');
510
+ const imageGroup = document.getElementById('imageGroup');
511
+ const uploadZone = document.getElementById('uploadZone');
512
+ const imageInput = document.getElementById('imageInput');
513
+ const uploadPreview = document.getElementById('uploadPreview');
514
+ const uploadClear = document.getElementById('uploadClear');
515
+ const modeT2V = document.getElementById('modeT2V');
516
+ const modeI2V = document.getElementById('modeI2V');
517
+ const t2vModels = document.getElementById('t2vModels');
518
+ const i2vModels = document.getElementById('i2vModels');
519
+
520
+ // --- State ---
521
+ let mode = 't2v';
522
+ let isGenerating = false;
523
+ let currentBlobUrl = null;
524
+ let useProxy = true; // activat implicit pentru a evita CORS
525
+ let timerInterval = null;
526
+ let startTime = 0;
527
+ let selectedImageFile = null;
528
+ let selectedImageBase64 = null;
529
+
530
+ // Load saved API key
531
+ const savedKey = localStorage.getItem('hf_api_key');
532
+ if (savedKey) hfKeyInput.value = savedKey;
533
+
534
+ hfKeyInput.addEventListener('change', () => {
535
+ const val = hfKeyInput.value.trim();
536
+ if (val) localStorage.setItem('hf_api_key', val);
537
+ else localStorage.removeItem('hf_api_key');
538
+ });
539
+
540
+ // --- Proxy select sync ---
541
+ proxySelect.addEventListener('change', () => {
542
+ proxyInput.value = proxySelect.value;
543
+ });
544
+ proxyInput.addEventListener('input', () => {
545
+ // Sync back to select if value matches one of the options
546
+ const options = proxySelect.options;
547
+ let matched = false;
548
+ for (let opt of options) {
549
+ if (opt.value === proxyInput.value) {
550
+ proxySelect.value = opt.value;
551
+ matched = true;
552
+ break;
553
+ }
554
+ }
555
+ if (!matched) {
556
+ // add a temporary option
557
+ const opt = document.createElement('option');
558
+ opt.value = proxyInput.value;
559
+ opt.textContent = 'personalizat';
560
+ proxySelect.appendChild(opt);
561
+ proxySelect.value = proxyInput.value;
562
+ }
563
+ });
564
+
565
+ // --- Mode switch ---
566
+ function setMode(newMode) {
567
+ mode = newMode;
568
+ modeT2V.classList.toggle('active', mode === 't2v');
569
+ modeI2V.classList.toggle('active', mode === 'i2v');
570
+ imageGroup.classList.toggle('hidden', mode !== 'i2v');
571
+
572
+ t2vModels.disabled = mode !== 't2v';
573
+ i2vModels.disabled = mode !== 'i2v';
574
+
575
+ if (mode === 't2v') {
576
+ modelSelect.value = 'damo-vilab/text-to-video-ms-1.7b';
577
+ } else {
578
+ modelSelect.value = 'stabilityai/stable-video-diffusion-img2vid-xt';
579
+ }
580
+ setStatus(mode === 't2v' ? 'Mod Text → Video activ.' : 'Mod Image → Video activ. Încarcă o imagine.');
581
+ }
582
+ modeT2V.addEventListener('click', () => setMode('t2v'));
583
+ modeI2V.addEventListener('click', () => setMode('i2v'));
584
+
585
+ // --- Image upload ---
586
+ uploadZone.addEventListener('click', (e) => {
587
+ if (e.target === uploadClear || uploadClear.contains(e.target)) return;
588
+ imageInput.click();
589
+ });
590
+ uploadClear.addEventListener('click', (e) => {
591
+ e.stopPropagation();
592
+ clearImage();
593
+ });
594
+
595
+ imageInput.addEventListener('change', () => {
596
+ if (imageInput.files?.[0]) handleImageFile(imageInput.files[0]);
597
+ });
598
+
599
+ ['dragenter', 'dragover'].forEach(ev => {
600
+ uploadZone.addEventListener(ev, (e) => { e.preventDefault(); uploadZone.classList.add('dragover'); });
601
+ });
602
+ ['dragleave', 'drop'].forEach(ev => {
603
+ uploadZone.addEventListener(ev, (e) => { e.preventDefault(); uploadZone.classList.remove('dragover'); });
604
+ });
605
+ uploadZone.addEventListener('drop', (e) => {
606
+ const file = e.dataTransfer?.files?.[0];
607
+ if (file && file.type.startsWith('image/')) handleImageFile(file);
608
+ });
609
+
610
+ function handleImageFile(file) {
611
+ if (file.size > 6 * 1024 * 1024) {
612
+ setStatus('Imaginea e prea mare (max ~5-6 MB).', 'error');
613
+ return;
614
+ }
615
+ selectedImageFile = file;
616
+ const reader = new FileReader();
617
+ reader.onload = (e) => {
618
+ selectedImageBase64 = e.target.result;
619
+ uploadPreview.src = selectedImageBase64;
620
+ uploadZone.classList.add('has-image');
621
+ setStatus('Imagine încărcată: ' + file.name);
622
+ };
623
+ reader.readAsDataURL(file);
624
+ }
625
+
626
+ function clearImage() {
627
+ selectedImageFile = null;
628
+ selectedImageBase64 = null;
629
+ uploadPreview.src = '';
630
+ imageInput.value = '';
631
+ uploadZone.classList.remove('has-image');
632
+ setStatus('Imagine ștearsă.');
633
+ }
634
+
635
+ // --- file:// detect ---
636
+ if (window.location.protocol === 'file:') {
637
+ corsWarning.classList.add('show');
638
+ useProxy = true;
639
+ proxyToggleBtn.innerHTML = '<i class="fas fa-toggle-on"></i> Activ';
640
+ proxyToggleBtn.classList.add('active');
641
+ }
642
+
643
+ // --- Examples ---
644
+ document.querySelectorAll('.example-chip').forEach(chip => {
645
+ chip.addEventListener('click', () => {
646
+ promptInput.value = chip.dataset.prompt;
647
+ setStatus('Prompt exemplu încărcat.');
648
+ });
649
+ });
650
+
651
+ // --- Proxy toggle ---
652
+ function updateProxyUI() {
653
+ proxyToggleBtn.innerHTML = useProxy ? '<i class="fas fa-toggle-on"></i> Activ' : '<i class="fas fa-toggle-off"></i> Inactiv';
654
+ proxyToggleBtn.classList.toggle('active', useProxy);
655
+ if (useProxy) corsWarning.classList.remove('show');
656
+ setStatus(useProxy ? 'Proxy CORS activat.' : 'Proxy CORS dezactivat.');
657
+ }
658
+
659
+ proxyToggleBtn.addEventListener('click', () => {
660
+ useProxy = !useProxy;
661
+ updateProxyUI();
662
+ });
663
+
664
+ // --- Copy / Clear ---
665
+ copyBtn.addEventListener('click', () => {
666
+ const text = promptInput.value.trim();
667
+ if (!text) { setStatus('Prompt gol!', 'error'); return; }
668
+ copyToClipboard(text, 'Prompt copiat!');
669
+ });
670
+ clearBtn.addEventListener('click', () => {
671
+ promptInput.value = '';
672
+ negativeInput.value = 'blurry, low quality, distorted faces, watermark, text, logo, oversaturated, ugly';
673
+ seedInput.value = '';
674
+ clearImage();
675
+ setStatus('Resetat.');
676
+ });
677
+
678
+ function copyToClipboard(text, msg) {
679
+ if (navigator.clipboard?.writeText) {
680
+ navigator.clipboard.writeText(text).then(() => setStatus(msg)).catch(() => fallbackCopy(text, msg));
681
+ } else fallbackCopy(text, msg);
682
+ }
683
+ function fallbackCopy(text, msg) {
684
+ const ta = document.createElement('textarea');
685
+ ta.value = text; ta.style.cssText = 'position:fixed;opacity:0';
686
+ document.body.appendChild(ta); ta.select();
687
+ try { document.execCommand('copy'); setStatus(msg); } catch (e) { setStatus('Nu s-a putut copia.', 'error'); }
688
+ document.body.removeChild(ta);
689
+ }
690
+
691
+ // --- Status ---
692
+ function setStatus(msg, type = 'info', detail = '') {
693
+ statusText.innerHTML = `<span>${msg}</span><span class="timer" id="timerDisplay"></span>`;
694
+ statusText.className = 'status-text';
695
+ if (type === 'error') statusText.classList.add('error');
696
+ if (type === 'success') statusText.classList.add('success');
697
+ errorDetail.textContent = detail || '';
698
+ errorDetail.style.display = detail ? 'block' : 'none';
699
+ }
700
+ function setProgress(pct) {
701
+ progressContainer.classList.add('show');
702
+ progressBar.style.width = Math.min(100, pct) + '%';
703
+ }
704
+ function hideProgress() {
705
+ progressContainer.classList.remove('show');
706
+ progressBar.style.width = '0%';
707
+ stopTimer();
708
+ }
709
+ function startTimer() {
710
+ startTime = performance.now();
711
+ const td = document.getElementById('timerDisplay');
712
+ if (td) td.textContent = '⏳ 0.0s';
713
+ if (timerInterval) clearInterval(timerInterval);
714
+ timerInterval = setInterval(() => {
715
+ const el = document.getElementById('timerDisplay');
716
+ if (el) el.textContent = `⏳ ${((performance.now() - startTime) / 1000).toFixed(1)}s`;
717
+ }, 100);
718
+ }
719
+ function stopTimer() {
720
+ if (timerInterval) { clearInterval(timerInterval); timerInterval = null; }
721
+ }
722
+
723
+ // --- URL builder ---
724
+ function buildApiUrl(model) {
725
+ const base = `https://api-inference.huggingface.co/models/${model}`;
726
+ if (useProxy) {
727
+ let proxy = (proxyInput.value.trim() || 'https://api.allorigins.win/raw?url=');
728
+ // Ensure proper encoding
729
+ if (!proxy.includes('url=')) {
730
+ proxy = proxy.endsWith('/') ? proxy + '?url=' : proxy + (proxy.includes('?') ? '&url=' : '?url=');
731
+ }
732
+ return proxy + encodeURIComponent(base);
733
+ }
734
+ return base;
735
+ }
736
+
737
+ // --- Convert dataURL to Blob ---
738
+ function dataURLtoBlob(dataurl) {
739
+ const arr = dataurl.split(',');
740
+ const mime = arr[0].match(/:(.*?);/)[1];
741
+ const bstr = atob(arr[1]);
742
+ let n = bstr.length;
743
+ const u8arr = new Uint8Array(n);
744
+ while (n--) u8arr[n] = bstr.charCodeAt(n);
745
+ return new Blob([u8arr], { type: mime });
746
+ }
747
+
748
+ // --- Generate ---
749
+ async function generateVideo() {
750
+ if (isGenerating) return;
751
+
752
+ const prompt = promptInput.value.trim();
753
+ if (!prompt) {
754
+ setStatus('Scrie un prompt!', 'error');
755
+ return;
756
+ }
757
+ if (mode === 'i2v' && !selectedImageBase64) {
758
+ setStatus('Încarcă o imagine pentru Image → Video!', 'error');
759
+ return;
760
+ }
761
+
762
+ const apiKey = hfKeyInput.value.trim();
763
+ if (!apiKey) {
764
+ setStatus('Introdu cheia Hugging Face API! (hf_...)', 'error');
765
+ return;
766
+ }
767
+ if (!apiKey.startsWith('hf_')) {
768
+ setStatus('Cheia trebuie să înceapă cu "hf_".', 'error');
769
+ return;
770
+ }
771
+
772
+ isGenerating = true;
773
+ generateBtn.disabled = true;
774
+ testApiBtn.disabled = true;
775
+ setProgress(5);
776
+ setStatus(mode === 'i2v' ? 'Se trimite imaginea + prompt...' : 'Se trimite cererea...');
777
+ startTimer();
778
+
779
+ videoResult.style.display = 'none';
780
+ placeholder.style.display = 'block';
781
+ downloadBtn.style.display = 'none';
782
+ viewport.classList.remove('active');
783
+ if (currentBlobUrl) { URL.revokeObjectURL(currentBlobUrl); currentBlobUrl = null; }
784
+ errorDetail.style.display = 'none';
785
+
786
+ const model = modelSelect.value;
787
+ const url = buildApiUrl(model);
788
+ const numFrames = parseInt(numFramesSelect.value, 10) || 24;
789
+ const seed = seedInput.value ? parseInt(seedInput.value, 10) : Math.floor(Math.random() * 1e9);
790
+ const negative = negativeInput.value.trim();
791
+ const finalPrompt = `${prompt}, vertical 9:16 aspect ratio, portrait orientation, cinematic`;
792
+
793
+ // Progress simulation
794
+ const progressSteps = [
795
+ { t: 800, p: 15, m: 'Modelul se încarcă...' },
796
+ { t: 2500, p: 32, m: 'Se procesează...' },
797
+ { t: 5000, p: 52, m: 'Se randează cadrele...' },
798
+ { t: 9000, p: 70, m: 'Rendere finală...' },
799
+ { t: 14000, p: 84, m: 'Aproape gata...' }
800
+ ];
801
+ const timeouts = progressSteps.map(s =>
802
+ setTimeout(() => { if (isGenerating) { setProgress(s.p); setStatus(s.m); } }, s.t)
803
+ );
804
+
805
+ try {
806
+ const startReq = performance.now();
807
+ let response;
808
+
809
+ if (mode === 'i2v') {
810
+ const isSVD = model.includes('stable-video-diffusion');
811
+
812
+ if (isSVD) {
813
+ const blobImg = dataURLtoBlob(selectedImageBase64);
814
+ response = await fetch(url, {
815
+ method: 'POST',
816
+ headers: {
817
+ 'Authorization': `Bearer ${apiKey}`,
818
+ 'Content-Type': 'application/octet-stream',
819
+ 'x-wait-for-model': 'true'
820
+ },
821
+ body: blobImg
822
+ });
823
+ } else {
824
+ const body = {
825
+ inputs: selectedImageBase64,
826
+ parameters: {
827
+ prompt: finalPrompt,
828
+ negative_prompt: negative || undefined,
829
+ num_frames: numFrames,
830
+ guidance_scale: 7.5,
831
+ seed: seed
832
+ }
833
+ };
834
+ response = await fetch(url, {
835
+ method: 'POST',
836
+ headers: {
837
+ 'Authorization': `Bearer ${apiKey}`,
838
+ 'Content-Type': 'application/json',
839
+ 'x-wait-for-model': 'true'
840
+ },
841
+ body: JSON.stringify(body)
842
+ });
843
+ }
844
+ } else {
845
+ const body = {
846
+ inputs: finalPrompt,
847
+ parameters: {
848
+ num_frames: numFrames,
849
+ guidance_scale: 7.5,
850
+ negative_prompt: negative || undefined,
851
+ seed: seed
852
+ }
853
+ };
854
+ response = await fetch(url, {
855
+ method: 'POST',
856
+ headers: {
857
+ 'Authorization': `Bearer ${apiKey}`,
858
+ 'Content-Type': 'application/json',
859
+ 'x-wait-for-model': 'true'
860
+ },
861
+ body: JSON.stringify(body)
862
+ });
863
+ }
864
+
865
+ timeouts.forEach(clearTimeout);
866
+ const elapsed = ((performance.now() - startReq) / 1000).toFixed(1);
867
+
868
+ if (!response.ok) {
869
+ let errMsg = '', detail = '';
870
+ try {
871
+ const errData = await response.json();
872
+ errMsg = errData.error || errData.message || JSON.stringify(errData);
873
+ detail = JSON.stringify(errData, null, 2);
874
+ } catch (_) {
875
+ errMsg = response.statusText || 'Eroare necunoscută';
876
+ }
877
+
878
+ if (response.status === 401 || response.status === 403) {
879
+ setStatus('❌ Cheie invalidă sau fără acces.', 'error',
880
+ 'Verifică token-ul pe https://huggingface.co/settings/tokens (trebuie acces Inference API).');
881
+ } else if (response.status === 503) {
882
+ setStatus('⏳ Modelul se încarcă (cold start). Reîncearcă în 20-40s.', 'error',
883
+ 'A doua încercare e de obicei mai rapidă.');
884
+ } else if (response.status === 429) {
885
+ setStatus('⏳ Rate limit. Așteaptă 1-2 minute.', 'error');
886
+ } else if (response.status === 410) {
887
+ setStatus('❌ Endpoint dezafectat. Încearcă alt model.', 'error');
888
+ } else {
889
+ setStatus('❌ Eroare API: ' + errMsg, 'error', detail || 'Status: ' + response.status);
890
+ }
891
+ return;
892
+ }
893
+
894
+ const contentType = response.headers.get('content-type') || '';
895
+ let blob;
896
+
897
+ if (contentType.includes('application/json')) {
898
+ const data = await response.json();
899
+ if (data.error) {
900
+ setStatus('❌ ' + data.error, 'error', JSON.stringify(data, null, 2));
901
+ return;
902
+ }
903
+ const videoUrl = data.video || data.output || data.url || (Array.isArray(data) && data[0]);
904
+ if (typeof videoUrl === 'string' && videoUrl.startsWith('http')) {
905
+ setStatus('Se descarcă video-ul...');
906
+ const vidResp = await fetch(videoUrl);
907
+ blob = await vidResp.blob();
908
+ } else {
909
+ setStatus('❌ Răspuns JSON fără video.', 'error', JSON.stringify(data, null, 2));
910
+ return;
911
+ }
912
+ } else {
913
+ blob = await response.blob();
914
+ }
915
+
916
+ if (!blob || blob.size < 800) {
917
+ setStatus('❌ Răspuns gol sau prea mic.', 'error');
918
+ return;
919
+ }
920
+
921
+ currentBlobUrl = URL.createObjectURL(blob);
922
+ setProgress(100);
923
+ setStatus(`✅ Video generat! (${elapsed}s · seed ${seed})`, 'success');
924
+
925
+ placeholder.style.display = 'none';
926
+ videoResult.src = currentBlobUrl;
927
+ videoResult.style.display = 'block';
928
+ videoResult.load();
929
+ videoResult.play().catch(() => {});
930
+ viewport.classList.add('active');
931
+
932
+ const sizeMB = (blob.size / (1024 * 1024)).toFixed(1);
933
+ downloadBtn.href = currentBlobUrl;
934
+ downloadBtn.download = `videogen_${mode}_${Date.now()}.mp4`;
935
+ downloadBtn.style.display = 'inline-block';
936
+ downloadBtn.innerHTML = `<i class="fas fa-download"></i> Descarcă MP4 (${sizeMB} MB)`;
937
+
938
+ } catch (err) {
939
+ console.error(err);
940
+ const msg = err.message || 'Conexiune eșuată';
941
+ if (/fetch|network|Failed to fetch|CORS/i.test(msg)) {
942
+ setStatus('❌ Eroare de rețea / CORS.', 'error',
943
+ `Proxy-ul este ${useProxy ? 'ACTIV' : 'INACTIV'}.\n` +
944
+ `Proxy URL: ${proxyInput.value}\n\n` +
945
+ `Soluții:\n` +
946
+ `1. Activează Proxy-ul (butonul de mai sus)\n` +
947
+ `2. Încearcă alt proxy din dropdown\n` +
948
+ `3. Rulează cu Live Server (VS Code) sau python -m http.server\n` +
949
+ `4. Dacă ești online, folosește https://corsproxy.io/ sau https://api.allorigins.win/`);
950
+ } else {
951
+ setStatus('❌ Eroare: ' + msg, 'error');
952
+ }
953
+ } finally {
954
+ isGenerating = false;
955
+ generateBtn.disabled = false;
956
+ testApiBtn.disabled = false;
957
+ hideProgress();
958
+ }
959
+ }
960
+
961
+ // --- Test API ---
962
+ async function testApi() {
963
+ const apiKey = hfKeyInput.value.trim();
964
+ if (!apiKey) { setStatus('Introdu cheia API!', 'error'); return; }
965
+ if (!apiKey.startsWith('hf_')) { setStatus('Cheia trebuie să înceapă cu "hf_".', 'error'); return; }
966
+
967
+ setStatus('Testare conexiune...');
968
+ const model = modelSelect.value;
969
+ const url = buildApiUrl(model);
970
+
971
+ try {
972
+ const resp = await fetch(url, {
973
+ method: 'POST',
974
+ headers: {
975
+ 'Authorization': `Bearer ${apiKey}`,
976
+ 'Content-Type': 'application/json'
977
+ },
978
+ body: JSON.stringify({ inputs: 'test connection' })
979
+ });
980
+
981
+ if (resp.status === 200 || resp.status === 503) {
982
+ setStatus('✅ Conexiune OK (cheia e acceptată).', 'success');
983
+ } else if (resp.status === 401 || resp.status === 403) {
984
+ setStatus('❌ Cheie invalidă sau fără acces.', 'error');
985
+ } else if (resp.status === 410) {
986
+ setStatus('❌ Endpoint dezafectat. Schimbă modelul.', 'error');
987
+ } else if (resp.status === 404) {
988
+ setStatus('❌ Modelul nu există sau nu e accesibil.', 'error');
989
+ } else {
990
+ setStatus(`⚠️ Status ${resp.status}. Poate fi model cold.`, 'info');
991
+ }
992
+ } catch (err) {
993
+ const msg = err.message || 'Conexiune eșuată';
994
+ if (/fetch|network|Failed to fetch|CORS/i.test(msg)) {
995
+ setStatus('❌ Eroare de rețea / CORS la test.', 'error',
996
+ `Proxy activ: ${useProxy ? 'DA' : 'NU'}\n` +
997
+ `Proxy URL: ${proxyInput.value}\n\n` +
998
+ `Activează Proxy-ul sau rulează cu Live Server.`);
999
+ } else {
1000
+ setStatus('❌ Eroare: ' + msg, 'error');
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ // --- Events ---
1006
+ generateBtn.addEventListener('click', generateVideo);
1007
+ testApiBtn.addEventListener('click', testApi);
1008
+ document.addEventListener('keydown', (e) => {
1009
+ if (e.target?.tagName === 'INPUT' || e.target?.tagName === 'TEXTAREA') return;
1010
+ if ((e.key === 'g' || e.key === 'G') && !isGenerating) generateBtn.click();
1011
+ });
1012
+
1013
+ // Initial status
1014
+ setStatus('Proxy activat implicit. Dacă ai erori, încearcă alt proxy din dropdown.');
1015
+ console.log('🎬 VideoGen Pro v3.2 · Proxy activat · Modele suportate');
1016
+ })();
1017
+ </script>
1018
+ </body>
1019
+ </html>