|
<!DOCTYPE html> |
|
<html lang="fa" dir="rtl"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>ساخت ویدیو هوشمند آلفا | تبدیل تصویر و متن به انیمیشن</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link href="https://cdn.jsdelivr.net/gh/rastikerdar/[email protected]/Vazirmatn-font-face.css" rel="stylesheet"> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
:root { |
|
--primary-color: rgb(84, 110, 122); |
|
--primary-light: rgba(84, 110, 122, 0.1); |
|
--primary-dark: rgb(69, 90, 100); |
|
--accent-color: #4fc3f7; |
|
--accent-dark: #29b6f6; |
|
} |
|
|
|
@keyframes float { |
|
0% { transform: translateY(0px); } |
|
50% { transform: translateY(-8px); } |
|
100% { transform: translateY(0px); } |
|
} |
|
|
|
@keyframes gradientFlow { |
|
0% { background-position: 0% 50%; } |
|
50% { background-position: 100% 50%; } |
|
100% { background-position: 0% 50%; } |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
@keyframes pulseGlow { |
|
0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4); } |
|
70% { box-shadow: 0 0 0 12px rgba(76, 175, 80, 0); } |
|
100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); } |
|
} |
|
|
|
.animate-float { |
|
animation: float 6s ease-in-out infinite; |
|
} |
|
|
|
.gradient-animate { |
|
background-size: 300% 300%; |
|
animation: gradientFlow 12s ease infinite; |
|
} |
|
|
|
.animate-fade-in { |
|
animation: fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; |
|
} |
|
|
|
.pulse-glow { |
|
animation: pulseGlow 2s infinite; |
|
} |
|
|
|
.glass-morphism { |
|
background: rgba(255, 255, 255, 0.05); |
|
backdrop-filter: blur(16px); |
|
-webkit-backdrop-filter: blur(16px); |
|
border: 1px solid rgba(255, 255, 255, 0.08); |
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15); |
|
} |
|
|
|
.option-card { |
|
transition: all 0.4s cubic-bezier(0.68, -0.6, 0.32, 1.6); |
|
transform-style: preserve-3d; |
|
} |
|
|
|
.option-card:hover { |
|
transform: translateY(-6px) scale(1.02); |
|
} |
|
|
|
.option-card.selected { |
|
transform: translateY(-4px); |
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.file-drop-area { |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.file-drop-area.drag-over { |
|
border-color: var(--accent-color); |
|
background: rgba(79, 195, 247, 0.08); |
|
} |
|
|
|
.progress-track { |
|
background: linear-gradient(90deg, var(--primary-color) 0%, var(--accent-color) 100%); |
|
background-size: 200% 100%; |
|
animation: gradientFlow 4s ease infinite; |
|
} |
|
|
|
.text-gradient { |
|
background-clip: text; |
|
-webkit-background-clip: text; |
|
color: transparent; |
|
} |
|
|
|
.status-message { |
|
animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1); |
|
} |
|
|
|
.video-container { |
|
perspective: 1000px; |
|
} |
|
|
|
.video-wrapper { |
|
transform-style: preserve-3d; |
|
transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1); |
|
} |
|
|
|
.video-wrapper:hover { |
|
transform: rotateX(2deg) rotateY(2deg) scale(1.02); |
|
} |
|
|
|
|
|
.primary-btn { |
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%); |
|
color: white; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.primary-btn:hover { |
|
background: linear-gradient(135deg, var(--primary-dark) 0%, rgb(59, 77, 86) 100%); |
|
transform: translateY(-2px); |
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
|
} |
|
|
|
.secondary-btn { |
|
background: rgba(255, 255, 255, 0.05); |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
color: #e2e8f0; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.secondary-btn:hover { |
|
background: rgba(255, 255, 255, 0.1); |
|
border-color: rgba(255, 255, 255, 0.2); |
|
} |
|
|
|
.accent-btn { |
|
background: linear-gradient(135deg, var(--accent-color) 0%, var(--accent-dark) 100%); |
|
color: white; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.accent-btn:hover { |
|
background: linear-gradient(135deg, var(--accent-dark) 0%, #039be5 100%); |
|
} |
|
|
|
.tab-btn { |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.tab-btn::after { |
|
content: ''; |
|
position: absolute; |
|
bottom: 0; |
|
left: 0; |
|
right: 0; |
|
height: 2px; |
|
background: linear-gradient(90deg, var(--primary-color) 0%, var(--accent-color) 100%); |
|
transform: scaleX(0); |
|
transition: transform 0.3s ease; |
|
transform-origin: left; |
|
} |
|
|
|
.tab-btn.active::after { |
|
transform: scaleX(1); |
|
} |
|
|
|
|
|
::-webkit-scrollbar { |
|
width: 8px; |
|
height: 8px; |
|
} |
|
|
|
::-webkit-scrollbar-track { |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 10px; |
|
} |
|
|
|
::-webkit-scrollbar-thumb { |
|
background: var(--primary-light); |
|
border-radius: 10px; |
|
} |
|
|
|
::-webkit-scrollbar-thumb:hover { |
|
background: var(--primary-color); |
|
} |
|
|
|
|
|
.bg-pattern { |
|
background-color: #0f172a; |
|
background-image: |
|
radial-gradient(at 47% 33%, rgba(84, 110, 122, 0.2) 0, transparent 59%), |
|
radial-gradient(at 82% 65%, rgba(79, 195, 247, 0.1) 0, transparent 55%); |
|
} |
|
|
|
|
|
.floating-element { |
|
position: absolute; |
|
border-radius: 50%; |
|
filter: blur(60px); |
|
opacity: 0.15; |
|
z-index: -1; |
|
} |
|
|
|
|
|
.custom-input:focus { |
|
border-color: var(--accent-color); |
|
box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.2); |
|
} |
|
|
|
|
|
.hover-card { |
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
|
} |
|
|
|
.hover-card:hover { |
|
transform: translateY(-5px); |
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.1); |
|
} |
|
</style> |
|
</head> |
|
<body class="min-h-screen bg-pattern text-gray-100 font-sans overflow-x-hidden"> |
|
|
|
<div class="floating-element bg-primary-color w-64 h-64 top-20 left-20"></div> |
|
<div class="floating-element bg-accent-color w-96 h-96 bottom-20 right-20"></div> |
|
|
|
<div class="container mx-auto px-4 py-12 max-w-4xl relative"> |
|
|
|
<div class="text-center mb-16 animate-fade-in" style="animation-delay: 0.1s;"> |
|
<div class="inline-flex items-center justify-center p-5 rounded-2xl glass-morphism mb-6 shadow-lg hover-card"> |
|
<i class="fas fa-film text-4xl text-transparent bg-clip-text bg-gradient-to-r from-primary-color to-accent-color"></i> |
|
</div> |
|
<h1 class="text-4xl font-bold mb-4 flex items-center justify-center"> |
|
<span class="text-gradient bg-gradient-to-r from-primary-color to-accent-color">ساخت ویدیو هوشمند</span> |
|
<span class="text-gradient bg-gradient-to-r from-accent-color to-cyan-400 mr-3">آلفا</span> |
|
</h1> |
|
<p class="text-lg text-gray-300 max-w-2xl mx-auto leading-relaxed"> |
|
با فناوری هوش مصنوعی آلفا، تصاویر و متون خود را به انیمیشنهای خیرهکننده تبدیل کنید |
|
</p> |
|
</div> |
|
|
|
|
|
<div class="glass-morphism rounded-3xl overflow-hidden shadow-2xl mb-10 animate-fade-in hover-card" style="animation-delay: 0.2s;"> |
|
|
|
<div class="flex border-b border-gray-700/50"> |
|
<button class="mode-button tab-btn flex-1 py-5 font-medium text-sm uppercase tracking-wider transition-all duration-300 relative overflow-hidden group active" data-mode="image-to-video"> |
|
<span class="relative z-10 flex items-center justify-center"> |
|
<i class="fas fa-image ml-2 text-accent-color"></i> |
|
تصویر به ویدیو |
|
</span> |
|
</button> |
|
<button class="mode-button tab-btn flex-1 py-5 font-medium text-sm uppercase tracking-wider transition-all duration-300 relative overflow-hidden group" data-mode="text-to-video"> |
|
<span class="relative z-10 flex items-center justify-center"> |
|
<i class="fas fa-font ml-2 text-gray-400 group-hover:text-gray-300 transition-colors"></i> |
|
متن به ویدیو |
|
</span> |
|
</button> |
|
</div> |
|
|
|
|
|
<div class="p-8"> |
|
|
|
<div id="imageToVideoSection" class="form-mode-section"> |
|
<div class="mb-8"> |
|
<label for="imageFile" class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-image mr-2 text-accent-color"></i> |
|
انتخاب تصویر |
|
</label> |
|
<div class="file-drop-area relative border-2 border-dashed border-gray-700 rounded-2xl p-10 text-center cursor-pointer transition-all duration-300 hover:border-accent-color bg-gray-800/30" id="fileUploadArea"> |
|
<input type="file" id="imageFile" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" accept="image/jpeg, image/png, image/webp" /> |
|
<div class="flex flex-col items-center justify-center"> |
|
<div class="w-16 h-16 rounded-full bg-gradient-to-br from-primary-light to-accent-color/20 flex items-center justify-center mb-4"> |
|
<i class="fas fa-cloud-upload-alt text-2xl text-accent-color"></i> |
|
</div> |
|
<p class="text-sm text-gray-300 mb-1">فایل را اینجا رها کنید یا برای انتخاب کلیک کنید</p> |
|
<p class="text-xs text-gray-500">فرمتهای پشتیبانی شده: JPG, PNG, WebP</p> |
|
</div> |
|
</div> |
|
<img id="imagePreview" src="#" alt="پیشنمایش" class="mt-5 rounded-xl shadow-lg w-full max-h-72 object-contain hidden mx-auto border border-gray-700/50" /> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="textToVideoSection" class="form-mode-section hidden"> |
|
<div class="mb-8 text-center py-5 px-6 rounded-xl bg-gradient-to-r from-primary-light to-accent-color/20 border border-accent-color/20 shadow-inner"> |
|
<div class="flex items-center justify-center"> |
|
<i class="fas fa-info-circle text-accent-color mr-3"></i> |
|
<span class="text-sm text-gray-200">در این حالت، ویدیو فقط بر اساس متن راهنما ساخته میشود.</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mb-8"> |
|
<label for="prompt" class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-magic mr-2 text-accent-color"></i> |
|
شرح انیمیشن (Prompt) |
|
</label> |
|
<div class="relative"> |
|
<textarea id="prompt" rows="3" class="custom-input w-full px-5 py-4 bg-gray-800/40 border border-gray-700 rounded-xl focus:ring-2 focus:ring-accent-color focus:border-accent-color text-gray-200 placeholder-gray-500 transition-all duration-300 shadow-inner" placeholder="مثال: گربهای که به آرامی پلک میزند">یک موجود از داخل تصویر شروع به حرکت میکند</textarea> |
|
<div class="absolute bottom-4 left-4 text-xs text-gray-500 flex items-center"> |
|
<i class="fas fa-lightbulb mr-1 text-yellow-300/70"></i> |
|
<span>توضیحات دقیقتر = نتیجه بهتر</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mb-8"> |
|
<label class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-clock mr-2 text-accent-color"></i> |
|
مدت زمان |
|
</label> |
|
<div class="grid grid-cols-2 gap-4"> |
|
<button class="option-card duration-button p-4 rounded-xl bg-gray-800/50 border border-gray-700 text-gray-200 hover:border-accent-color flex flex-col items-center relative overflow-hidden" data-api-duration="5"> |
|
<div class="w-10 h-10 rounded-full bg-accent-color/10 flex items-center justify-center mb-2"> |
|
<i class="fas fa-hourglass-start text-accent-color"></i> |
|
</div> |
|
<span class="font-medium">کوتاه</span> |
|
<span class="text-xs text-gray-400 mt-1">۵ ثانیه</span> |
|
<div class="absolute inset-0 bg-gradient-to-br from-primary-light to-accent-color/5 opacity-0 hover:opacity-100 transition-opacity duration-300"></div> |
|
</button> |
|
<button class="option-card duration-button p-4 rounded-xl bg-gradient-to-br from-primary-light to-accent-color/20 border border-accent-color/30 text-gray-100 hover:border-accent-color flex flex-col items-center relative selected" data-api-duration="7.8"> |
|
<div class="w-10 h-10 rounded-full bg-accent-color/20 flex items-center justify-center mb-2"> |
|
<i class="fas fa-hourglass-half text-gray-200"></i> |
|
</div> |
|
<span class="font-medium">استاندارد</span> |
|
<span class="text-xs text-accent-color mt-1">۸ ثانیه</span> |
|
<div class="absolute inset-0 bg-gradient-to-br from-primary-light to-accent-color/10 opacity-0 hover:opacity-100 transition-opacity duration-300"></div> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<button id="advancedSettingsToggle" class="w-full py-4 px-5 mb-6 rounded-xl bg-gray-800/50 border border-gray-700 text-gray-300 hover:bg-gray-700/50 hover:text-white transition-all duration-300 flex items-center justify-between group"> |
|
<span class="flex items-center"> |
|
<i class="fas fa-cog mr-3 text-accent-color group-hover:animate-spin"></i> |
|
تنظیمات پیشرفته |
|
</span> |
|
<i class="fas fa-chevron-down text-gray-400 transition-transform duration-300 group-hover:text-accent-color"></i> |
|
</button> |
|
|
|
|
|
<div id="advancedSettings" class="hidden bg-gradient-to-br from-gray-800/30 to-gray-900/30 rounded-2xl p-6 mb-8 border border-gray-700/50 shadow-inner"> |
|
<div class="mb-8"> |
|
<label class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-crop-alt mr-2 text-accent-color"></i> |
|
نسبت تصویر |
|
</label> |
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-3"> |
|
<button class="option-card aspect-ratio-button p-3 rounded-xl bg-gray-800/50 border border-gray-700 text-gray-200 hover:border-accent-color flex flex-col items-center" data-height="768" data-width="768"> |
|
<i class="fas fa-square text-xl mb-1"></i> |
|
<span class="text-sm">مربع</span> |
|
<span class="text-xs text-gray-400 mt-1">۱:۱</span> |
|
</button> |
|
<button class="option-card aspect-ratio-button p-3 rounded-xl bg-gray-800/50 border border-gray-700 text-gray-200 hover:border-accent-color flex flex-col items-center" data-height="768" data-width="432"> |
|
<i class="fas fa-mobile-alt text-xl mb-1"></i> |
|
<span class="text-sm">پرتره</span> |
|
<span class="text-xs text-gray-400 mt-1">۹:۱۶</span> |
|
</button> |
|
<button class="option-card aspect-ratio-button p-3 rounded-xl bg-gray-800/50 border border-gray-700 text-gray-200 hover:border-accent-color flex flex-col items-center" data-height="432" data-width="768"> |
|
<i class="fas fa-desktop text-xl mb-1"></i> |
|
<span class="text-sm">لنداسکیپ</span> |
|
<span class="text-xs text-gray-400 mt-1">۱۶:۹</span> |
|
</button> |
|
<button class="option-card aspect-ratio-button p-3 rounded-xl bg-gradient-to-br from-primary-light to-accent-color/20 border border-accent-color/30 text-gray-100 hover:border-accent-color flex flex-col items-center selected" data-height="576" data-width="1024"> |
|
<i class="fas fa-expand text-xl mb-1"></i> |
|
<span class="text-sm">پیشفرض</span> |
|
<span class="text-xs text-accent-color mt-1">آلفا</span> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="mb-8"> |
|
<label for="negativePrompt" class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-ban mr-2 text-red-300"></i> |
|
متن راهنمای منفی |
|
</label> |
|
<textarea id="negativePrompt" rows="2" class="custom-input w-full px-5 py-4 bg-gray-800/40 border border-gray-700 rounded-xl focus:ring-2 focus:ring-accent-color focus:border-accent-color text-gray-200 placeholder-gray-500 transition-all duration-300 shadow-inner">کیفیت پایین، تار، لرزان</textarea> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> |
|
<div> |
|
<label for="cfgScale" class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-sliders-h mr-2 text-accent-color"></i> |
|
مقیاس CFG |
|
</label> |
|
<div class="relative"> |
|
<input type="number" id="cfgScale" value="1.0" min="1.0" max="10.0" step="0.1" class="custom-input w-full px-5 py-3 bg-gray-800/40 border border-gray-700 rounded-xl focus:ring-2 focus:ring-accent-color focus:border-accent-color text-gray-200 transition-all duration-300 shadow-inner" /> |
|
<div class="absolute left-4 top-3 text-gray-500"> |
|
<i class="fas fa-adjust"></i> |
|
</div> |
|
</div> |
|
</div> |
|
<div> |
|
<label for="seed" class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-dice mr-2 text-yellow-300"></i> |
|
سید تصادفی |
|
</label> |
|
<div class="relative"> |
|
<input type="number" id="seed" value="0" min="0" class="custom-input w-full px-5 py-3 bg-gray-800/40 border border-gray-700 rounded-xl focus:ring-2 focus:ring-accent-color focus:border-accent-color text-gray-200 transition-all duration-300 shadow-inner" /> |
|
<div class="absolute left-4 top-3 text-gray-500"> |
|
<i class="fas fa-random"></i> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
<div> |
|
<label for="outputHeight" class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-arrows-alt-v mr-2 text-accent-color"></i> |
|
ارتفاع (پیکسل) |
|
</label> |
|
<div class="relative"> |
|
<input type="number" id="outputHeight" value="576" step="32" min="256" max="1280" class="custom-input w-full px-5 py-3 bg-gray-800/40 border border-gray-700 rounded-xl focus:ring-2 focus:ring-accent-color focus:border-accent-color text-gray-200 transition-all duration-300 shadow-inner" /> |
|
<div class="absolute left-4 top-3 text-gray-500"> |
|
<i class="fas fa-ruler-vertical"></i> |
|
</div> |
|
</div> |
|
</div> |
|
<div> |
|
<label for="outputWidth" class="block text-sm font-medium mb-3 text-gray-300 flex items-center"> |
|
<i class="fas fa-arrows-alt-h mr-2 text-accent-color"></i> |
|
عرض (پیکسل) |
|
</label> |
|
<div class="relative"> |
|
<input type="number" id="outputWidth" value="1024" step="32" min="256" max="1280" class="custom-input w-full px-5 py-3 bg-gray-800/40 border border-gray-700 rounded-xl focus:ring-2 focus:ring-accent-color focus:border-accent-color text-gray-200 transition-all duration-300 shadow-inner" /> |
|
<div class="absolute left-4 top-3 text-gray-500"> |
|
<i class="fas fa-ruler-horizontal"></i> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="text-center mt-10"> |
|
<button id="generateButton" class="primary-btn w-full py-5 px-8 rounded-2xl text-lg font-bold shadow-lg hover:shadow-xl group relative overflow-hidden"> |
|
<span class="relative z-10 flex items-center justify-center"> |
|
<i class="fas fa-magic mr-3 transform group-hover:rotate-12 transition-transform"></i> |
|
ساخت ویدیو هوشمند |
|
</span> |
|
<span class="absolute inset-0 bg-gradient-to-r from-primary-color/30 to-accent-color/30 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></span> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="statusSection" class="glass-morphism rounded-3xl overflow-hidden shadow-2xl mb-10 hidden animate-fade-in hover-card"> |
|
<div class="p-8"> |
|
<h2 class="text-xl font-bold mb-6 flex items-center"> |
|
<div class="w-10 h-10 rounded-full bg-gradient-to-r from-primary-light to-accent-color/20 flex items-center justify-center mr-3"> |
|
<i class="fas fa-spinner fa-spin text-accent-color"></i> |
|
</div> |
|
<span class="text-gradient bg-gradient-to-r from-primary-color to-accent-color">در حال پردازش درخواست</span> |
|
</h2> |
|
|
|
<div class="mb-6"> |
|
<div class="flex justify-between text-sm mb-2"> |
|
<span class="text-gray-300">پیشرفت عملیات</span> |
|
<span id="progressPercent" class="text-accent-color font-medium">0%</span> |
|
</div> |
|
<div class="w-full h-2.5 bg-gray-800 rounded-full overflow-hidden"> |
|
<div id="progressBar" class="h-full rounded-full progress-track" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
|
|
<div id="statusMessages" class="space-y-3 max-h-60 overflow-y-auto pr-2 custom-scroll"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="outputSection" class="glass-morphism rounded-3xl overflow-hidden shadow-2xl hidden animate-fade-in hover-card"> |
|
<div class="p-8"> |
|
<h2 class="text-xl font-bold mb-6 flex items-center"> |
|
<div class="w-10 h-10 rounded-full bg-gradient-to-r from-green-500/20 to-teal-500/20 flex items-center justify-center mr-3"> |
|
<i class="fas fa-check text-green-300"></i> |
|
</div> |
|
<span class="text-gradient bg-gradient-to-r from-green-200 to-teal-200">ویدیوی شما آماده شد!</span> |
|
</h2> |
|
|
|
<div class="video-container mb-6"> |
|
<div class="video-wrapper bg-gray-900 rounded-xl overflow-hidden shadow-lg"> |
|
<video id="outputVideo" controls preload="metadata" playsinline class="w-full"></video> |
|
</div> |
|
</div> |
|
|
|
<div class="flex flex-col md:flex-row justify-between items-center"> |
|
<p id="finalSeed" class="text-sm text-gray-400 mb-4 md:mb-0"> |
|
|
|
</p> |
|
|
|
<div class="flex space-x-3 rtl:space-x-reverse"> |
|
<button id="downloadButton" class="accent-btn py-2.5 px-5 rounded-xl flex items-center shadow hover:shadow-md"> |
|
<i class="fas fa-download mr-2"></i> |
|
دانلود ویدیو |
|
</button> |
|
<button id="newGenerationButton" class="secondary-btn py-2.5 px-5 rounded-xl flex items-center"> |
|
<i class="fas fa-redo mr-2"></i> |
|
ساخت جدید |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="text-center text-gray-500 text-sm mt-16"> |
|
<p class="flex items-center justify-center"> |
|
ساخته شده با |
|
<i class="fas fa-heart text-accent-color mx-2 animate-pulse"></i> |
|
توسط تیم آلفا |
|
</p> |
|
<p class="mt-2 text-gray-600">نسخه ۲.۰ | فناوری هوش مصنوعی پیشرفته</p> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const imageFileInput = document.getElementById('imageFile'); |
|
const imagePreview = document.getElementById('imagePreview'); |
|
const promptInput = document.getElementById('prompt'); |
|
const durationButtons = document.querySelectorAll('.duration-button'); |
|
const aspectRatioButtons = document.querySelectorAll('.aspect-ratio-button'); |
|
const generateButton = document.getElementById('generateButton'); |
|
const outputVideo = document.getElementById('outputVideo'); |
|
const finalSeedElement = document.getElementById('finalSeed'); |
|
const statusMessagesDiv = document.getElementById('statusMessages'); |
|
const statusSection = document.getElementById('statusSection'); |
|
const outputSection = document.getElementById('outputSection'); |
|
const progressBar = document.getElementById('progressBar'); |
|
const progressPercent = document.getElementById('progressPercent'); |
|
const downloadButton = document.getElementById('downloadButton'); |
|
const newGenerationButton = document.getElementById('newGenerationButton'); |
|
|
|
const advancedSettingsToggle = document.getElementById('advancedSettingsToggle'); |
|
const advancedSettingsDiv = document.getElementById('advancedSettings'); |
|
const negativePromptInput = document.getElementById('negativePrompt'); |
|
const cfgScaleInput = document.getElementById('cfgScale'); |
|
const seedInput = document.getElementById('seed'); |
|
const outputHeightInput = document.getElementById('outputHeight'); |
|
const outputWidthInput = document.getElementById('outputWidth'); |
|
|
|
const modeButtons = document.querySelectorAll('.mode-button'); |
|
const imageToVideoSection = document.getElementById('imageToVideoSection'); |
|
const textToVideoSection = document.getElementById('textToVideoSection'); |
|
const fileUploadArea = document.getElementById('fileUploadArea'); |
|
|
|
|
|
const SPACE_URL_BASE = "https://lightricks-ltx-video-distilled.hf.space"; |
|
const FN_INDEX_GENERATE = 5; |
|
let selectedApiDuration = 7.8; |
|
|
|
|
|
let currentSessionHash = ''; |
|
let uploadedImageInfo = null; |
|
let currentGenerationMode = "image-to-video"; |
|
let currentProgressPhase = 0; |
|
let totalUnitsInPhase = 0; |
|
let completedUnitsInPhase = 0; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
document.querySelector('.duration-button.selected')?.dispatchEvent(new Event('click')); |
|
document.querySelector('.aspect-ratio-button.selected')?.dispatchEvent(new Event('click')); |
|
updateFormForMode(currentGenerationMode); |
|
|
|
|
|
setupDragAndDrop(); |
|
}); |
|
|
|
|
|
function setupDragAndDrop() { |
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
|
fileUploadArea.addEventListener(eventName, preventDefaults, false); |
|
}); |
|
|
|
function preventDefaults(e) { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
} |
|
|
|
['dragenter', 'dragover'].forEach(eventName => { |
|
fileUploadArea.addEventListener(eventName, highlight, false); |
|
}); |
|
|
|
['dragleave', 'drop'].forEach(eventName => { |
|
fileUploadArea.addEventListener(eventName, unhighlight, false); |
|
}); |
|
|
|
function highlight() { |
|
fileUploadArea.classList.add('drag-over'); |
|
playHoverSound(); |
|
} |
|
|
|
function unhighlight() { |
|
fileUploadArea.classList.remove('drag-over'); |
|
} |
|
|
|
fileUploadArea.addEventListener('drop', handleDrop, false); |
|
|
|
function handleDrop(e) { |
|
const dt = e.dataTransfer; |
|
const files = dt.files; |
|
|
|
if (files.length > 0) { |
|
imageFileInput.files = files; |
|
imageFileInput.dispatchEvent(new Event('change')); |
|
playSuccessSound(); |
|
} |
|
} |
|
} |
|
|
|
|
|
modeButtons.forEach(button => { |
|
button.addEventListener('click', () => { |
|
|
|
modeButtons.forEach(btn => { |
|
btn.classList.remove('active'); |
|
btn.querySelector('i').classList.remove('text-accent-color'); |
|
btn.querySelector('i').classList.add('text-gray-400'); |
|
}); |
|
|
|
button.classList.add('active'); |
|
button.querySelector('i').classList.remove('text-gray-400'); |
|
button.querySelector('i').classList.add('text-accent-color'); |
|
|
|
|
|
currentGenerationMode = button.dataset.mode; |
|
updateFormForMode(currentGenerationMode); |
|
|
|
|
|
playClickSound(); |
|
}); |
|
}); |
|
|
|
|
|
advancedSettingsToggle.addEventListener('click', () => { |
|
const isOpen = advancedSettingsDiv.classList.contains('hidden'); |
|
|
|
if (isOpen) { |
|
advancedSettingsDiv.classList.remove('hidden'); |
|
advancedSettingsToggle.querySelector('i').classList.remove('fa-chevron-down'); |
|
advancedSettingsToggle.querySelector('i').classList.add('fa-chevron-up'); |
|
} else { |
|
advancedSettingsDiv.classList.add('hidden'); |
|
advancedSettingsToggle.querySelector('i').classList.remove('fa-chevron-up'); |
|
advancedSettingsToggle.querySelector('i').classList.add('fa-chevron-down'); |
|
} |
|
|
|
|
|
playClickSound(); |
|
}); |
|
|
|
|
|
imageFileInput.addEventListener('change', function(event) { |
|
const file = event.target.files[0]; |
|
if (file) { |
|
|
|
if (!file.type.startsWith('image/')) { |
|
addStatusMessage('لطفاً یک فایل تصویری (JPG, PNG, WebP) انتخاب کنید.', 'error'); |
|
imageFileInput.value = ''; |
|
imagePreview.style.display = 'none'; |
|
playErrorSound(); |
|
return; |
|
} |
|
|
|
|
|
const reader = new FileReader(); |
|
reader.onload = function(e) { |
|
imagePreview.src = e.target.result; |
|
imagePreview.style.display = 'block'; |
|
imagePreview.classList.add('animate-fade-in'); |
|
|
|
|
|
addStatusMessage('تصویر با موفقیت آپلود شد!', 'success'); |
|
playSuccessSound(); |
|
} |
|
reader.readAsDataURL(file); |
|
uploadedImageInfo = null; |
|
} else { |
|
imagePreview.style.display = 'none'; |
|
} |
|
}); |
|
|
|
|
|
durationButtons.forEach(button => { |
|
button.addEventListener('click', () => { |
|
|
|
durationButtons.forEach(btn => { |
|
btn.classList.remove('selected', 'bg-gradient-to-br', 'from-primary-light', 'to-accent-color/20', 'border-accent-color/30', 'text-gray-100'); |
|
btn.classList.add('bg-gray-800/50', 'border-gray-700', 'text-gray-200'); |
|
}); |
|
|
|
button.classList.add('selected', 'bg-gradient-to-br', 'from-primary-light', 'to-accent-color/20', 'border-accent-color/30', 'text-gray-100'); |
|
button.classList.remove('bg-gray-800/50', 'border-gray-700', 'text-gray-200'); |
|
|
|
selectedApiDuration = parseFloat(button.dataset.apiDuration); |
|
|
|
|
|
playClickSound(); |
|
}); |
|
}); |
|
|
|
|
|
aspectRatioButtons.forEach(button => { |
|
button.addEventListener('click', () => { |
|
|
|
aspectRatioButtons.forEach(btn => { |
|
btn.classList.remove('selected', 'bg-gradient-to-br', 'from-primary-light', 'to-accent-color/20', 'border-accent-color/30', 'text-gray-100'); |
|
btn.classList.add('bg-gray-800/50', 'border-gray-700', 'text-gray-200'); |
|
}); |
|
|
|
button.classList.add('selected', 'bg-gradient-to-br', 'from-primary-light', 'to-accent-color/20', 'border-accent-color/30', 'text-gray-100'); |
|
button.classList.remove('bg-gray-800/50', 'border-gray-700', 'text-gray-200'); |
|
|
|
outputHeightInput.value = button.dataset.height; |
|
outputWidthInput.value = button.dataset.width; |
|
|
|
|
|
playClickSound(); |
|
}); |
|
}); |
|
|
|
|
|
newGenerationButton.addEventListener('click', () => { |
|
outputSection.classList.add('hidden'); |
|
window.scrollTo({ |
|
top: 0, |
|
behavior: 'smooth' |
|
}); |
|
playClickSound(); |
|
}); |
|
|
|
|
|
function updateFormForMode(mode) { |
|
if (mode === "image-to-video") { |
|
imageToVideoSection.classList.remove('hidden'); |
|
textToVideoSection.classList.add('hidden'); |
|
promptInput.placeholder = "مثال: گربهای که به آرامی پلک میزند"; |
|
} else if (mode === "text-to-video") { |
|
imageToVideoSection.classList.add('hidden'); |
|
textToVideoSection.classList.remove('hidden'); |
|
imageFileInput.value = ''; |
|
imagePreview.style.display = 'none'; |
|
uploadedImageInfo = null; |
|
promptInput.placeholder = "مثال: یک اژدهای آتشی که بر فراز قلعه پرواز میکند"; |
|
} |
|
} |
|
|
|
|
|
function addStatusMessage(message, type = 'info') { |
|
statusSection.classList.remove('hidden'); |
|
|
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `status-message p-4 rounded-lg text-sm flex items-start ${type === 'error' ? |
|
'bg-gradient-to-r from-red-900/30 to-pink-900/20 text-red-100 border-r-2 border-red-400' : |
|
type === 'success' ? |
|
'bg-gradient-to-r from-green-900/30 to-teal-900/20 text-green-100 border-r-2 border-green-400' : |
|
'bg-gradient-to-r from-blue-900/30 to-indigo-900/20 text-blue-100 border-r-2 border-blue-400'}`; |
|
|
|
const icon = document.createElement('i'); |
|
icon.className = type === 'error' ? 'fas fa-exclamation-circle mt-0.5 mr-3 text-red-300' : |
|
type === 'success' ? 'fas fa-check-circle mt-0.5 mr-3 text-green-300' : |
|
'fas fa-info-circle mt-0.5 mr-3 text-blue-300'; |
|
|
|
const text = document.createElement('span'); |
|
text.textContent = message; |
|
|
|
messageDiv.appendChild(icon); |
|
messageDiv.appendChild(text); |
|
|
|
if (statusMessagesDiv.firstChild) { |
|
statusMessagesDiv.insertBefore(messageDiv, statusMessagesDiv.firstChild); |
|
} else { |
|
statusMessagesDiv.appendChild(messageDiv); |
|
} |
|
|
|
|
|
statusMessagesDiv.scrollTop = 0; |
|
} |
|
|
|
|
|
function clearStatusAndOutput() { |
|
statusMessagesDiv.innerHTML = ''; |
|
statusSection.classList.add('hidden'); |
|
outputSection.classList.add('hidden'); |
|
|
|
if (outputVideo.src) { |
|
URL.revokeObjectURL(outputVideo.src); |
|
} |
|
|
|
outputVideo.src = ''; |
|
finalSeedElement.textContent = ''; |
|
progressBar.style.width = '0%'; |
|
progressPercent.textContent = '0%'; |
|
} |
|
|
|
|
|
function generateRandomHash(length = 11) { |
|
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; |
|
let result = ''; |
|
|
|
for (let i = 0; i < length; i++) { |
|
result += characters.charAt(Math.floor(Math.random() * characters.length)); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
async function uploadImage(file) { |
|
addStatusMessage('۱. آمادهسازی تصویر...'); |
|
|
|
const uploadId = generateRandomHash(12); |
|
const formData = new FormData(); |
|
formData.append('files', file); |
|
|
|
try { |
|
const response = await fetch(`${SPACE_URL_BASE}/gradio_api/upload?upload_id=${uploadId}`, { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error(`خطا در آپلود (${response.status})`); |
|
} |
|
|
|
const result = await response.json(); |
|
|
|
if (result && result.length > 0 && typeof result[0] === 'string') { |
|
return { |
|
path: result[0], |
|
url: `${SPACE_URL_BASE}/gradio_api/file=${result[0]}`, |
|
orig_name: file.name, |
|
size: file.size, |
|
mime_type: file.type || 'application/octet-stream', |
|
meta: { "_type": "gradio.FileData" } |
|
}; |
|
} else { |
|
throw new Error('پاسخ آپلود نامعتبر.'); |
|
} |
|
} catch (error) { |
|
addStatusMessage(`خطا در آپلود: ${error.message}`, 'error'); |
|
console.error('Upload error:', error); |
|
return null; |
|
} |
|
} |
|
|
|
|
|
async function submitToQueue(payload) { |
|
addStatusMessage('۲. ارسال درخواست به سرور...'); |
|
|
|
try { |
|
const response = await fetch(`${SPACE_URL_BASE}/gradio_api/queue/join`, { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify(payload) |
|
}); |
|
|
|
if (!response.ok) { |
|
const errorText = await response.text(); |
|
throw new Error(`خطا در ارسال به صف (${response.status}) - ${errorText.substring(0,100)}`); |
|
} |
|
|
|
return await response.json(); |
|
} catch (error) { |
|
addStatusMessage(`خطا در ارتباط با سرور: ${error.message}`, 'error'); |
|
console.error('Queue join error:', error); |
|
return null; |
|
} |
|
} |
|
|
|
|
|
function updateVisualProgress(progressDataArray) { |
|
if (!progressDataArray || progressDataArray.length === 0) return; |
|
|
|
const pInfo = progressDataArray[0]; |
|
|
|
if (pInfo.desc === "Saving video") { |
|
if (currentProgressPhase !== 1) { |
|
currentProgressPhase = 1; |
|
addStatusMessage("مرحله نهایی: ذخیره ویدیو..."); |
|
} |
|
|
|
const progress = Math.round((pInfo.progress || 0) * 100); |
|
progressBar.style.width = `${progress}%`; |
|
progressPercent.textContent = `${progress}%`; |
|
} else if (pInfo.length && pInfo.index !== null) { |
|
if (currentProgressPhase !== 0 || pInfo.index === 0) { |
|
if (pInfo.index === 0) { |
|
currentProgressPhase = 0; |
|
totalUnitsInPhase = pInfo.length; |
|
completedUnitsInPhase = 0; |
|
addStatusMessage(`شروع پردازش (${totalUnitsInPhase} گام)...`); |
|
} |
|
} |
|
|
|
completedUnitsInPhase = pInfo.index + 1; |
|
const progress = Math.round((completedUnitsInPhase / totalUnitsInPhase) * 100); |
|
progressBar.style.width = `${progress}%`; |
|
progressPercent.textContent = `${progress}%`; |
|
} |
|
} |
|
|
|
|
|
function listenForResults(eventId) { |
|
addStatusMessage('۳. در حال تولید ویدیو...'); |
|
|
|
const eventSource = new EventSource(`${SPACE_URL_BASE}/gradio_api/queue/data?session_hash=${currentSessionHash}`); |
|
currentProgressPhase = -1; |
|
|
|
eventSource.onmessage = function(event) { |
|
const data = JSON.parse(event.data); |
|
|
|
if (data.event_id !== eventId && data.msg !== "queue_full") return; |
|
|
|
switch (data.msg) { |
|
case "process_starts": |
|
addStatusMessage('عملیات در سرور آغاز شد.'); |
|
progressBar.style.width = '0%'; |
|
progressPercent.textContent = '0%'; |
|
break; |
|
|
|
case "progress": |
|
updateVisualProgress(data.progress_data); |
|
break; |
|
|
|
case "process_completed": |
|
eventSource.close(); |
|
generateButton.disabled = false; |
|
generateButton.innerHTML = '<i class="fas fa-magic mr-2"></i> ساخت ویدیو هوشمند'; |
|
progressBar.style.width = '100%'; |
|
progressPercent.textContent = '100%'; |
|
|
|
if (data.success && data.output?.data?.[0]?.video?.url) { |
|
addStatusMessage('ویدیو با موفقیت ساخته شد! 🎉', 'success'); |
|
outputSection.classList.remove('hidden'); |
|
|
|
|
|
setTimeout(() => { |
|
outputSection.scrollIntoView({ behavior: 'smooth' }); |
|
}, 500); |
|
|
|
outputVideo.src = data.output.data[0].video.url; |
|
outputVideo.load(); |
|
|
|
|
|
downloadButton.onclick = function() { |
|
const a = document.createElement('a'); |
|
a.href = data.output.data[0].video.url; |
|
a.download = `alpha-video-${new Date().toISOString().slice(0,10)}.mp4`; |
|
document.body.appendChild(a); |
|
a.click(); |
|
document.body.removeChild(a); |
|
playSuccessSound(); |
|
}; |
|
|
|
outputVideo.play().catch(e => { |
|
addStatusMessage("برای مشاهده، دکمه پخش ویدیو را بزنید.", "info"); |
|
}); |
|
|
|
finalSeedElement.textContent = data.output.data[1] ? `سید نهایی: ${data.output.data[1]}` : ''; |
|
|
|
|
|
playCompletionSound(); |
|
} else { |
|
const errorMsg = data.output?.error || data.error || "خطای نامشخص در تکمیل فرآیند."; |
|
addStatusMessage(`تولید ویدیو ناموفق بود: ${errorMsg}`, 'error'); |
|
console.error('Process completed with error:', data); |
|
playErrorSound(); |
|
} |
|
break; |
|
|
|
case "queue_full": |
|
addStatusMessage('سرور مشغول است، لطفا کمی بعد تلاش کنید.', 'error'); |
|
eventSource.close(); |
|
generateButton.disabled = false; |
|
generateButton.innerHTML = '<i class="fas fa-magic mr-2"></i> ساخت ویدیو هوشمند'; |
|
playErrorSound(); |
|
break; |
|
} |
|
}; |
|
|
|
eventSource.onerror = function(error) { |
|
addStatusMessage('خطا در ارتباط با سرور. اتصال خود را بررسی کنید.', 'error'); |
|
console.error('EventSource error:', error); |
|
eventSource.close(); |
|
generateButton.disabled = false; |
|
generateButton.innerHTML = '<i class="fas fa-magic mr-2"></i> ساخت ویدیو هوشمند'; |
|
playErrorSound(); |
|
}; |
|
} |
|
|
|
|
|
generateButton.addEventListener('click', async () => { |
|
clearStatusAndOutput(); |
|
|
|
const imageFile = imageFileInput.files[0]; |
|
|
|
|
|
if (currentGenerationMode === "image-to-video" && !imageFile) { |
|
addStatusMessage('لطفاً ابتدا یک تصویر انتخاب کنید.', 'error'); |
|
playErrorSound(); |
|
return; |
|
} |
|
|
|
if (!promptInput.value.trim()) { |
|
addStatusMessage('لطفاً متن راهنما (Prompt) را وارد کنید.', 'error'); |
|
playErrorSound(); |
|
return; |
|
} |
|
|
|
|
|
generateButton.disabled = true; |
|
generateButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> در حال پردازش...'; |
|
statusSection.classList.remove('hidden'); |
|
|
|
|
|
currentSessionHash = generateRandomHash(); |
|
|
|
|
|
if (currentGenerationMode === "image-to-video") { |
|
uploadedImageInfo = await uploadImage(imageFile); |
|
|
|
if (!uploadedImageInfo) { |
|
generateButton.disabled = false; |
|
generateButton.innerHTML = '<i class="fas fa-magic mr-2"></i> ساخت ویدیو هوشمند'; |
|
return; |
|
} |
|
} else { |
|
uploadedImageInfo = null; |
|
} |
|
|
|
|
|
const userSeed = parseInt(seedInput.value); |
|
|
|
const generationPayload = { |
|
fn_index: FN_INDEX_GENERATE, |
|
data: [ |
|
promptInput.value, |
|
negativePromptInput.value, |
|
uploadedImageInfo, |
|
(currentGenerationMode === "image-to-video" ? null : ""), |
|
parseInt(outputHeightInput.value), |
|
parseInt(outputWidthInput.value), |
|
currentGenerationMode, |
|
selectedApiDuration, |
|
9, |
|
(userSeed > 0) ? userSeed : Math.floor(Math.random() * (2**32 -1)), |
|
(userSeed <= 0), |
|
parseFloat(cfgScaleInput.value), |
|
true |
|
], |
|
event_data: null, |
|
session_hash: currentSessionHash, |
|
}; |
|
|
|
|
|
const joinResponse = await submitToQueue(generationPayload); |
|
|
|
if (joinResponse && joinResponse.event_id) { |
|
listenForResults(joinResponse.event_id); |
|
} else { |
|
addStatusMessage('ارسال درخواست به سرور ناموفق بود.', 'error'); |
|
generateButton.disabled = false; |
|
generateButton.innerHTML = '<i class="fas fa-magic mr-2"></i> ساخت ویدیو هوشمند'; |
|
playErrorSound(); |
|
} |
|
}); |
|
|
|
|
|
function playClickSound() { |
|
const audio = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-select-click-1109.mp3'); |
|
audio.volume = 0.2; |
|
audio.play().catch(e => console.log('Audio play failed:', e)); |
|
} |
|
|
|
function playHoverSound() { |
|
const audio = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-arcade-game-jump-coin-216.mp3'); |
|
audio.volume = 0.1; |
|
audio.play().catch(e => console.log('Audio play failed:', e)); |
|
} |
|
|
|
function playSuccessSound() { |
|
const audio = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-correct-answer-tone-2870.mp3'); |
|
audio.volume = 0.2; |
|
audio.play().catch(e => console.log('Audio play failed:', e)); |
|
} |
|
|
|
function playErrorSound() { |
|
const audio = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-warning-alarm-688.mp3'); |
|
audio.volume = 0.1; |
|
audio.play().catch(e => console.log('Audio play failed:', e)); |
|
} |
|
|
|
function playCompletionSound() { |
|
const audio = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3'); |
|
audio.volume = 0.2; |
|
audio.play().catch(e => console.log('Audio play failed:', e)); |
|
} |
|
</script> |
|
</body> |
|
</html> |