Files changed (1) hide show
  1. index.html +0 -591
index.html DELETED
@@ -1,591 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>CineFlow Studio - Free Image to Video Generator</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://unpkg.com/lucide@latest"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.12.7/dist/umd/ffmpeg.js"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/@ffmpeg/util@0.12.1/dist/umd/index.js"></script>
11
- <style>
12
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
13
-
14
- body {
15
- font-family: 'Inter', sans-serif;
16
- background: #0a0a0a;
17
- }
18
-
19
- .glass-panel {
20
- background: rgba(255, 255, 255, 0.03);
21
- backdrop-filter: blur(10px);
22
- border: 1px solid rgba(255, 255, 255, 0.1);
23
- }
24
-
25
- .gradient-text {
26
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
27
- -webkit-background-clip: text;
28
- -webkit-text-fill-color: transparent;
29
- background-clip: text;
30
- }
31
-
32
- .drop-zone {
33
- background: repeating-linear-gradient(
34
- 45deg,
35
- rgba(99, 102, 241, 0.05),
36
- rgba(99, 102, 241, 0.05) 10px,
37
- rgba(99, 102, 241, 0.1) 10px,
38
- rgba(99, 102, 241, 0.1) 20px
39
- );
40
- border: 2px dashed rgba(99, 102, 241, 0.3);
41
- transition: all 0.3s ease;
42
- }
43
-
44
- .drop-zone.drag-over {
45
- background: repeating-linear-gradient(
46
- 45deg,
47
- rgba(99, 102, 241, 0.15),
48
- rgba(99, 102, 241, 0.15) 10px,
49
- rgba(99, 102, 241, 0.25) 10px,
50
- rgba(99, 102, 241, 0.25) 20px
51
- );
52
- border-color: #6366f1;
53
- transform: scale(1.02);
54
- }
55
-
56
- .timeline-item {
57
- transition: all 0.2s ease;
58
- }
59
-
60
- .timeline-item:hover {
61
- transform: translateY(-2px);
62
- box-shadow: 0 10px 30px rgba(0,0,0,0.5);
63
- }
64
-
65
- .progress-bar {
66
- background: linear-gradient(90deg, #6366f1, #8b5cf6, #a855f7);
67
- background-size: 200% 100%;
68
- animation: gradient-shift 2s ease infinite;
69
- }
70
-
71
- @keyframes gradient-shift {
72
- 0% { background-position: 0% 50%; }
73
- 50% { background-position: 100% 50%; }
74
- 100% { background-position: 0% 50%; }
75
- }
76
-
77
- .preview-container {
78
- aspect-ratio: 16/9;
79
- background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
80
- }
81
-
82
- input[type="range"] {
83
- -webkit-appearance: none;
84
- appearance: none;
85
- background: transparent;
86
- cursor: pointer;
87
- }
88
-
89
- input[type="range"]::-webkit-slider-track {
90
- background: rgba(255,255,255,0.1);
91
- height: 6px;
92
- border-radius: 3px;
93
- }
94
-
95
- input[type="range"]::-webkit-slider-thumb {
96
- -webkit-appearance: none;
97
- appearance: none;
98
- background: #6366f1;
99
- height: 16px;
100
- width: 16px;
101
- border-radius: 50%;
102
- margin-top: -5px;
103
- transition: all 0.2s;
104
- }
105
-
106
- input[type="range"]::-webkit-slider-thumb:hover {
107
- transform: scale(1.2);
108
- box-shadow: 0 0 20px rgba(99, 102, 241, 0.5);
109
- }
110
-
111
- .frame-preview {
112
- animation: fadeIn 0.3s ease;
113
- }
114
-
115
- @keyframes fadeIn {
116
- from { opacity: 0; transform: scale(0.9); }
117
- to { opacity: 1; transform: scale(1); }
118
- }
119
- </style>
120
- </head>
121
- <body class="text-white min-h-screen overflow-x-hidden">
122
- <!-- Header -->
123
- <header class="glass-panel sticky top-0 z-50 border-b border-white/10">
124
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
125
- <div class="flex items-center gap-3">
126
- <div class="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center">
127
- <i data-lucide="film" class="w-5 h-5 text-white"></i>
128
- </div>
129
- <div>
130
- <h1 class="text-xl font-bold tracking-tight">CineFlow <span class="gradient-text">Studio</span></h1>
131
- <p class="text-xs text-gray-400">Client-side Video Generator</p>
132
- </div>
133
- </div>
134
-
135
- <div class="flex items-center gap-4">
136
- <span class="hidden sm:flex items-center gap-2 text-xs text-gray-400 bg-white/5 px-3 py-1.5 rounded-full">
137
- <i data-lucide="cpu" class="w-3 h-3"></i>
138
- <span id="cpu-status">Ready</span>
139
- </span>
140
- <button onclick="resetAll()" class="text-sm text-gray-400 hover:text-white transition-colors">
141
- Reset
142
- </button>
143
- </div>
144
- </div>
145
- </header>
146
-
147
- <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
148
- <div class="grid lg:grid-cols-3 gap-8">
149
- <!-- Left Panel: Upload & Timeline -->
150
- <div class="lg:col-span-2 space-y-6">
151
- <!-- Upload Zone -->
152
- <div class="glass-panel rounded-2xl p-6">
153
- <div id="dropZone" class="drop-zone rounded-xl p-12 text-center cursor-pointer">
154
- <input type="file" id="fileInput" multiple accept="image/*" class="hidden">
155
- <div class="space-y-4">
156
- <div class="w-16 h-16 mx-auto bg-indigo-500/20 rounded-2xl flex items-center justify-center">
157
- <i data-lucide="upload-cloud" class="w-8 h-8 text-indigo-400"></i>
158
- </div>
159
- <div>
160
- <p class="text-lg font-medium text-white">Drop images here or click to browse</p>
161
- <p class="text-sm text-gray-400 mt-1">Supports JPG, PNG, WebP • Unlimited files • Private & Secure</p>
162
- </div>
163
- <button onclick="document.getElementById('fileInput').click()" class="inline-flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2.5 rounded-lg font-medium transition-all hover:scale-105">
164
- <i data-lucide="plus" class="w-4 h-4"></i>
165
- Select Images
166
- </button>
167
- </div>
168
- </div>
169
- </div>
170
-
171
- <!-- Timeline -->
172
- <div class="glass-panel rounded-2xl p-6">
173
- <div class="flex items-center justify-between mb-4">
174
- <h3 class="text-lg font-semibold flex items-center gap-2">
175
- <i data-lucide="layers" class="w-5 h-5 text-indigo-400"></i>
176
- Timeline
177
- <span id="frameCount" class="text-xs bg-white/10 px-2 py-0.5 rounded-full text-gray-400">0 frames</span>
178
- </h3>
179
- <div class="flex gap-2">
180
- <button onclick="clearAll()" class="text-xs text-red-400 hover:text-red-300 flex items-center gap-1">
181
- <i data-lucide="trash-2" class="w-3 h-3"></i>
182
- Clear
183
- </button>
184
- </div>
185
- </div>
186
-
187
- <div id="timeline" class="space-y-2 max-h-96 overflow-y-auto pr-2">
188
- <div class="text-center py-12 text-gray-500">
189
- <i data-lucide="image-off" class="w-12 h-12 mx-auto mb-3 opacity-20"></i>
190
- <p>No images added yet</p>
191
- </div>
192
- </div>
193
- </div>
194
- </div>
195
-
196
- <!-- Right Panel: Settings & Preview -->
197
- <div class="space-y-6">
198
- <!-- Preview Window -->
199
- <div class="glass-panel rounded-2xl p-6">
200
- <h3 class="text-lg font-semibold mb-4 flex items-center gap-2">
201
- <i data-lucide="play-circle" class="w-5 h-5 text-indigo-400"></i>
202
- Preview
203
- </h3>
204
- <div class="preview-container rounded-xl overflow-hidden relative bg-black">
205
- <img id="previewImage" src="" alt="Preview" class="w-full h-full object-contain hidden">
206
- <div id="previewPlaceholder" class="absolute inset-0 flex items-center justify-center text-gray-500">
207
- <div class="text-center">
208
- <i data-lucide="video" class="w-12 h-12 mx-auto mb-2 opacity-20"></i>
209
- <p class="text-sm">Video preview will appear here</p>
210
- </div>
211
- </div>
212
- <div id="previewOverlay" class="absolute inset-0 bg-black/50 hidden items-center justify-center">
213
- <span class="text-2xl font-bold" id="previewCounter">1/10</span>
214
- </div>
215
- </div>
216
- </div>
217
-
218
- <!-- Settings -->
219
- <div class="glass-panel rounded-2xl p-6 space-y-6">
220
- <h3 class="text-lg font-semibold flex items-center gap-2">
221
- <i data-lucide="settings" class="w-5 h-5 text-indigo-400"></i>
222
- Export Settings
223
- </h3>
224
-
225
- <div class="space-y-4">
226
- <div>
227
- <label class="flex justify-between text-sm font-medium mb-2">
228
- <span>Duration per image</span>
229
- <span id="durationValue" class="text-indigo-400">2s</span>
230
- </label>
231
- <input type="range" id="duration" min="0.5" max="10" step="0.5" value="2" class="w-full">
232
- </div>
233
-
234
- <div>
235
- <label class="flex justify-between text-sm font-medium mb-2">
236
- <span>Frame Rate (FPS)</span>
237
- <span id="fpsValue" class="text-indigo-400">30fps</span>
238
- </label>
239
- <input type="range" id="fps" min="15" max="60" step="15" value="30" class="w-full">
240
- </div>
241
-
242
- <div>
243
- <label class="flex justify-between text-sm font-medium mb-2">
244
- <span>Resolution</span>
245
- <span id="resolutionValue" class="text-indigo-400">1920×1080</span>
246
- </label>
247
- <select id="resolution" class="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-indigo-500">
248
- <option value="3840x2160">4K Ultra HD (3840×2160)</option>
249
- <option value="1920x1080" selected>Full HD (1920×1080)</option>
250
- <option value="1280x720">HD (1280×720)</option>
251
- <option value="854x480">480p (854×480)</option>
252
- <option value="640x360">360p (640×360)</option>
253
- </select>
254
- </div>
255
-
256
- <div class="flex items-center justify-between py-2">
257
- <span class="text-sm font-medium">Transition Effect</span>
258
- <select id="transition" class="bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-indigo-500">
259
- <option value="none">Cut (None)</option>
260
- <option value="fade">Fade</option>
261
- <option value="slide">Slide</option>
262
- </select>
263
- </div>
264
- </div>
265
-
266
- <button id="generateBtn" onclick="generateVideo()" class="w-full bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500 text-white font-semibold py-4 rounded-xl transition-all hover:scale-[1.02] active:scale-[0.98] flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
267
- <i data-lucide="render" class="w-5 h-5"></i>
268
- Generate Video
269
- </button>
270
-
271
- <div id="progressContainer" class="hidden space-y-2">
272
- <div class="flex justify-between text-xs text-gray-400">
273
- <span id="progressText">Initializing...</span>
274
- <span id="progressPercent">0%</span>
275
- </div>
276
- <div class="h-2 bg-white/10 rounded-full overflow-hidden">
277
- <div id="progressBar" class="progress-bar h-full w-0 rounded-full transition-all duration-300"></div>
278
- </div>
279
- </div>
280
-
281
- <div id="downloadContainer" class="hidden">
282
- <a id="downloadLink" href="#" download="cineflow-video.mp4" class="flex items-center justify-center gap-2 w-full bg-green-600 hover:bg-green-500 text-white font-semibold py-3 rounded-xl transition-all">
283
- <i data-lucide="download" class="w-5 h-5"></i>
284
- Download Video
285
- </a>
286
- </div>
287
- </div>
288
-
289
- <!-- Info -->
290
- <div class="glass-panel rounded-2xl p-4 text-xs text-gray-400 space-y-2">
291
- <div class="flex items-start gap-2">
292
- <i data-lucide="shield-check" class="w-4 h-4 text-green-400 mt-0.5"></i>
293
- <span>All processing happens locally in your browser. No data is uploaded to any server.</span>
294
- </div>
295
- <div class="flex items-start gap-2">
296
- <i data-lucide="infinity" class="w-4 h-4 text-indigo-400 mt-0.5"></i>
297
- <span>Unlimited generations. Limited only by your device's memory.</span>
298
- </div>
299
- </div>
300
- </div>
301
- </div>
302
- </main>
303
-
304
- <script>
305
- // Initialize Lucide icons
306
- lucide.createIcons();
307
-
308
- // State
309
- let images = [];
310
- let ffmpeg = null;
311
- let isGenerating = false;
312
-
313
- // DOM Elements
314
- const dropZone = document.getElementById('dropZone');
315
- const fileInput = document.getElementById('fileInput');
316
- const timeline = document.getElementById('timeline');
317
- const frameCount = document.getElementById('frameCount');
318
- const generateBtn = document.getElementById('generateBtn');
319
- const progressContainer = document.getElementById('progressContainer');
320
- const progressBar = document.getElementById('progressBar');
321
- const progressText = document.getElementById('progressText');
322
- const progressPercent = document.getElementById('progressPercent');
323
- const downloadContainer = document.getElementById('downloadContainer');
324
- const downloadLink = document.getElementById('downloadLink');
325
-
326
- // Event Listeners
327
- dropZone.addEventListener('click', () => fileInput.click());
328
- dropZone.addEventListener('dragover', (e) => {
329
- e.preventDefault();
330
- dropZone.classList.add('drag-over');
331
- });
332
- dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
333
- dropZone.addEventListener('drop', (e) => {
334
- e.preventDefault();
335
- dropZone.classList.remove('drag-over');
336
- handleFiles(e.dataTransfer.files);
337
- });
338
- fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
339
-
340
- document.getElementById('duration').addEventListener('input', (e) => {
341
- document.getElementById('durationValue').textContent = e.target.value + 's';
342
- });
343
- document.getElementById('fps').addEventListener('input', (e) => {
344
- document.getElementById('fpsValue').textContent = e.target.value + 'fps';
345
- });
346
- document.getElementById('resolution').addEventListener('change', (e) => {
347
- document.getElementById('resolutionValue').textContent = e.target.value.split('x').join('×');
348
- });
349
-
350
- function handleFiles(files) {
351
- const validFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
352
-
353
- validFiles.forEach(file => {
354
- const reader = new FileReader();
355
- reader.onload = (e) => {
356
- images.push({
357
- id: Date.now() + Math.random(),
358
- src: e.target.result,
359
- name: file.name,
360
- type: file.type
361
- });
362
- updateTimeline();
363
- };
364
- reader.readAsDataURL(file);
365
- });
366
- }
367
-
368
- function updateTimeline() {
369
- frameCount.textContent = `${images.length} frame${images.length !== 1 ? 's' : ''}`;
370
-
371
- if (images.length === 0) {
372
- timeline.innerHTML = `
373
- <div class="text-center py-12 text-gray-500">
374
- <i data-lucide="image-off" class="w-12 h-12 mx-auto mb-3 opacity-20"></i>
375
- <p>No images added yet</p>
376
- </div>
377
- `;
378
- lucide.createIcons();
379
- return;
380
- }
381
-
382
- timeline.innerHTML = images.map((img, index) => `
383
- <div class="timeline-item flex items-center gap-3 bg-white/5 rounded-lg p-2 border border-white/5" id="frame-${img.id}">
384
- <div class="relative w-20 h-12 bg-black rounded overflow-hidden flex-shrink-0">
385
- <img src="${img.src}" class="w-full h-full object-cover frame-preview">
386
- <div class="absolute inset-0 bg-black/30 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity cursor-pointer" onclick="previewImage(${index})">
387
- <i data-lucide="eye" class="w-4 h-4 text-white"></i>
388
- </div>
389
- </div>
390
- <div class="flex-1 min-w-0">
391
- <p class="text-sm font-medium truncate">${img.name}</p>
392
- <p class="text-xs text-gray-500">Frame ${index + 1}</p>
393
- </div>
394
- <div class="flex gap-1">
395
- <button onclick="moveImage(${index}, -1)" class="p-1.5 hover:bg-white/10 rounded transition-colors ${index === 0 ? 'opacity-30 cursor-not-allowed' : ''}" ${index === 0 ? 'disabled' : ''}>
396
- <i data-lucide="chevron-up" class="w-4 h-4"></i>
397
- </button>
398
- <button onclick="moveImage(${index}, 1)" class="p-1.5 hover:bg-white/10 rounded transition-colors ${index === images.length - 1 ? 'opacity-30 cursor-not-allowed' : ''}" ${index === images.length - 1 ? 'disabled' : ''}>
399
- <i data-lucide="chevron-down" class="w-4 h-4"></i>
400
- </button>
401
- <button onclick="removeImage(${index})" class="p-1.5 hover:bg-red-500/20 text-red-400 rounded transition-colors">
402
- <i data-lucide="x" class="w-4 h-4"></i>
403
- </button>
404
- </div>
405
- </div>
406
- `).join('');
407
-
408
- lucide.createIcons();
409
- }
410
-
411
- function removeImage(index) {
412
- images.splice(index, 1);
413
- updateTimeline();
414
- }
415
-
416
- function moveImage(index, direction) {
417
- const newIndex = index + direction;
418
- if (newIndex < 0 || newIndex >= images.length) return;
419
-
420
- const temp = images[index];
421
- images[index] = images[newIndex];
422
- images[newIndex] = temp;
423
- updateTimeline();
424
- }
425
-
426
- function previewImage(index) {
427
- const img = document.getElementById('previewImage');
428
- const placeholder = document.getElementById('previewPlaceholder');
429
- img.src = images[index].src;
430
- img.classList.remove('hidden');
431
- placeholder.classList.add('hidden');
432
- }
433
-
434
- function clearAll() {
435
- images = [];
436
- updateTimeline();
437
- document.getElementById('previewImage').classList.add('hidden');
438
- document.getElementById('previewPlaceholder').classList.remove('hidden');
439
- }
440
-
441
- function resetAll() {
442
- clearAll();
443
- document.getElementById('downloadContainer').classList.add('hidden');
444
- progressContainer.classList.add('hidden');
445
- }
446
-
447
- async function loadFFmpeg() {
448
- if (ffmpeg) return;
449
-
450
- const { FFmpeg } = FFmpegWASM;
451
- ffmpeg = new FFmpeg();
452
-
453
- progressText.textContent = 'Loading FFmpeg...';
454
- await ffmpeg.load();
455
-
456
- ffmpeg.on('log', ({ message }) => {
457
- console.log('FFmpeg:', message);
458
- });
459
- }
460
-
461
- async function generateVideo() {
462
- if (images.length === 0) {
463
- alert('Please add at least one image');
464
- return;
465
- }
466
-
467
- if (isGenerating) return;
468
-
469
- try {
470
- isGenerating = true;
471
- generateBtn.disabled = true;
472
- generateBtn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Processing...';
473
- lucide.createIcons();
474
-
475
- progressContainer.classList.remove('hidden');
476
- downloadContainer.classList.add('hidden');
477
-
478
- // Load FFmpeg if not loaded
479
- if (!ffmpeg) {
480
- progressText.textContent = 'Loading video engine...';
481
- await loadFFmpeg();
482
- }
483
-
484
- const duration = parseFloat(document.getElementById('duration').value);
485
- const fps = parseInt(document.getElementById('fps').value);
486
- const resolution = document.getElementById('resolution').value.split('x');
487
- const width = parseInt(resolution[0]);
488
- const height = parseInt(resolution[1]);
489
-
490
- progressText.textContent = 'Preparing images...';
491
-
492
- // Process images
493
- const canvas = document.createElement('canvas');
494
- canvas.width = width;
495
- canvas.height = height;
496
- const ctx = canvas.getContext('2d');
497
-
498
- let frameCount = 0;
499
- const totalFrames = Math.ceil(images.length * duration * fps);
500
-
501
- for (let i = 0; i < images.length; i++) {
502
- const img = new Image();
503
- img.src = images[i].src;
504
- await new Promise(resolve => {
505
- img.onload = resolve;
506
- });
507
-
508
- // Calculate frames for this image
509
- const framesForThisImage = Math.floor(duration * fps);
510
-
511
- for (let f = 0; f < framesForThisImage; f++) {
512
- ctx.fillStyle = '#000000';
513
- ctx.fillRect(0, 0, width, height);
514
-
515
- // Scale image to fit while maintaining aspect ratio
516
- const scale = Math.min(width / img.width, height / img.height);
517
- const x = (width - img.width * scale) / 2;
518
- const y = (height - img.height * scale) / 2;
519
-
520
- ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
521
-
522
- // Convert to blob and write to FFmpeg
523
- const blob = await new Promise(resolve => {
524
- canvas.toBlob(resolve, 'image/jpeg', 0.95);
525
- });
526
-
527
- const buffer = await blob.arrayBuffer();
528
- const frameName = `frame_${String(frameCount).padStart(6, '0')}.jpg`;
529
- await ffmpeg.writeFile(frameName, new Uint8Array(buffer));
530
-
531
- frameCount++;
532
-
533
- // Update progress
534
- const percent = Math.round((frameCount / totalFrames) * 90);
535
- progressBar.style.width = percent + '%';
536
- progressPercent.textContent = percent + '%';
537
- progressText.textContent = `Processing frame ${frameCount}/${totalFrames}...`;
538
- }
539
- }
540
-
541
- progressText.textContent = 'Encoding video...';
542
- progressBar.style.width = '95%';
543
-
544
- // Generate video
545
- await ffmpeg.exec([
546
- '-framerate', fps.toString(),
547
- '-pattern_type', 'glob',
548
- '-i', 'frame_*.jpg',
549
- '-c:v', 'libx264',
550
- '-pix_fmt', 'yuv420p',
551
- '-preset', 'ultrafast',
552
- '-crf', '23',
553
- 'output.mp4'
554
- ]);
555
-
556
- progressText.textContent = 'Finalizing...';
557
- progressBar.style.width = '100%';
558
-
559
- // Read output file
560
- const data = await ffmpeg.readFile('output.mp4');
561
- const videoBlob = new Blob([data.buffer], { type: 'video/mp4' });
562
- const url = URL.createObjectURL(videoBlob);
563
-
564
- // Setup download
565
- downloadLink.href = url;
566
- downloadContainer.classList.remove('hidden');
567
-
568
- // Cleanup
569
- for (let i = 0; i < frameCount; i++) {
570
- try {
571
- await ffmpeg.deleteFile(`frame_${String(i).padStart(6, '0')}.jpg`);
572
- } catch (e) {}
573
- }
574
-
575
- progressText.textContent = 'Complete!';
576
-
577
- } catch (error) {
578
- console.error('Error:', error);
579
- alert('Error generating video: ' + error.message);
580
- progressText.textContent = 'Error occurred';
581
- } finally {
582
- isGenerating = false;
583
- generateBtn.disabled = false;
584
- generateBtn.innerHTML = '<i data-lucide="render" class="w-5 h-5"></i> Generate Video';
585
- lucide.createIcons();
586
- }
587
- }
588
- </script>
589
- <script src="https://deepsite.hf.co/deepsite-badge.js"></script>
590
- </body>
591
- </html>