pornflux-i2v-6e9et / script.js
Cha007's picture
🐳 17/05 - 12:51 - Make it more sexy, darker, more explicit, improve the design to look like a premium porn AI tool, add more advanced controls.
7ab111d verified
// =============================
// PornFlux I2V β€” Main Script
// =============================
document.addEventListener('DOMContentLoaded', () => {
lucide.createIcons();
// β€”β€”β€” State β€”β€”β€”
const state = {
duration: 5,
resolution: '1080',
count: 1,
motion: 50,
camera: 'pan',
seed: '',
negPrompt: '',
strength: 70,
audio: 'auto',
model: 'wan25-pro',
style: 'cinematic',
aspect: '16:9',
lighting: 'natural',
fps: '30',
nsfw: 75,
faceEnhance: true,
detailUpscale: false,
uploadedImage: null,
isGenerating: false,
};
// β€”β€”β€” Surprise Prompts (explicit, bilingual) β€”β€”β€”
const surprisePrompts = [
"Close-up POV of passionate missionary sex, slow deep thrusts, sweaty skin glistening, intimate eye contact, cinematic lighting",
"POV fellation, caméra subjective, lèvres humides, regard séducteur vers la caméra, mouvement lent et sensuel",
"Doggy style from behind, POV rear angle, rhythmic hip slapping, arched back, hands gripping the sheets, moaning",
"Levrette vue de derrière, angled bas, frappes rythmiques, dos cambré, mains agrippant les draps, gémissements",
"Cowgirl riding, close-up hip movement, slow grinding, breasts bouncing, hair swaying, erotic expression",
"Premier plan, chatte sur le visage, chevauchement lent, regards intenses, Γ©clairage bougie, ambiance intime",
"Erotic lap dance, slow striptease reveal, sensual hip grinding, intimate contact, dim bedroom lighting",
"Sperme facial, finission explosive, gros plan, gouttelettes lentes, rΓ©action de plaisir, ralenti cinΓ©matique",
"Threesome on silk sheets, multiple bodies intertwined, passionate kissing, switching positions, warm golden light",
"Trio lesbien, baisers passionnΓ©s, caresses intimes, corps enlacΓ©s sur des draps en soie, Γ©clairage doux",
"Shower sex scene, water cascading, steamy glass, pressed against tiles, wet hair, intense pounding",
"Sous la douche, eau coulant sur la peau, carrelage, cheveux mouillΓ©s, pΓ©nΓ©tration intense, buΓ©e",
"Sensual massage turning explicit, oil on skin, hands exploring every inch, erotic build-up, soft moaning",
"Massage sensuel变成érotique, huile sur la peau, mains explorant chaque centimètre, escalation du désir",
"Bound in silk restraints, submission, blindfolded pleasure, teasing, slow anticipation, fetish lighting",
"Lingerie tease, stockings and garters, slow reveal, high heels, mirror reflection, boudoir aesthetic",
"DΓ©shabillagePin-up, bas et porte-jarretelles, lente rΓ©vΓ©lation, talons hauts, reflets dans le miroir",
"Outdoor sex on the beach at sunset, sand, crashing waves, passionate, wild, golden hour lighting",
"Public restroom quickie, urgency, skirt hiked up, biting lip to stay quiet, fluorescent light flickering",
"Sextape aesthetic, amateur handheld camera feel, real girlfriend experience, genuine pleasure, authentic audio",
];
let promptIndex = 0;
// β€”β€”β€” Prompt Gallery Data β€”β€”β€”
const promptGalleryData = [
{
category: 'Cinematic',
color: 'flux',
prompt: 'Cinematic slow motion, dramatic backlighting, sensual movement, film grain, noir aesthetic',
image: 'http://static.photos/aerial/320x240/10',
},
{
category: 'Sensual',
color: 'violet',
prompt: 'Soft candlelight, silk and skin, intimate boudoir, warm color grading, slow pan',
image: 'http://static.photos/minimal/320x240/22',
},
{
category: 'Artistic',
color: 'flux',
prompt: 'Nu artistique, Γ©clairage de galerie, poses inspirΓ©es de la sculpture classique, rotation lente',
image: 'http://static.photos/abstract/320x240/15',
},
{
category: 'Romantic',
color: 'violet',
prompt: 'Romance au coucher de soleil, lumière dorée, regard tendre, mouvement délicat, ambiance chaude',
image: 'http://static.photos/nature/320x240/33',
},
{
category: 'Dark & Moody',
color: 'flux',
prompt: 'Dark neon-lit room, slow reveal, shadows and highlights, urban night aesthetic, moody atmosphere',
image: 'http://static.photos/technology/320x240/7',
},
{
category: 'Glamour',
color: 'violet',
prompt: 'Glamour photo shoot motion, professional lighting, beauty close-ups, editorial style, slow dolly',
image: 'http://static.photos/workspace/320x240/44',
},
];
// β€”β€”β€” Video Gallery Data β€”β€”β€”
const videoGalleryData = [
{ image: 'http://static.photos/nature/640x360/1', label: 'Doggy POV' },
{ image: 'http://static.photos/abstract/640x360/3', label: 'BJ Close-up' },
{ image: 'http://static.photos/minimal/640x360/5', label: 'Cowgirl' },
{ image: 'http://static.photos/aerial/640x360/7', label: 'Missionary' },
{ image: 'http://static.photos/technology/640x360/9', label: 'Threesome' },
{ image: 'http://static.photos/workspace/640x360/11', label: 'Striptease' },
{ image: 'http://static.photos/nature/640x360/13', label: 'Facial' },
{ image: 'http://static.photos/abstract/640x360/15', label: 'Shower Sex' },
];
// β€”β€”β€” DOM Elements β€”β€”β€”
const uploadArea = document.getElementById('uploadArea');
const uploadPlaceholder = document.getElementById('uploadPlaceholder');
const uploadPreview = document.getElementById('uploadPreview');
const previewImage = document.getElementById('previewImage');
const removeImageBtn = document.getElementById('removeImageBtn');
const imageInfo = document.getElementById('imageInfo');
const fileInput = document.getElementById('fileInput');
const promptInput = document.getElementById('promptInput');
const charCount = document.getElementById('charCount');
const surpriseBtn = document.getElementById('surpriseBtn');
const generateBtn = document.getElementById('generateBtn');
const loadingState = document.getElementById('loadingState');
const progressBar = document.getElementById('progressBar');
const progressPercent = document.getElementById('progressPercent');
const progressETA = document.getElementById('progressETA');
const loadingText = document.getElementById('loadingText');
const loadingSubtext = document.getElementById('loadingSubtext');
const resultsArea = document.getElementById('resultsArea');
const resultsGrid = document.getElementById('resultsGrid');
const clearResultsBtn = document.getElementById('clearResultsBtn');
const advancedToggle = document.getElementById('advancedToggle');
const advancedPanel = document.getElementById('advancedPanel');
const advancedChevron = document.getElementById('advancedChevron');
const motionSlider = document.getElementById('motionSlider');
const motionVal = document.getElementById('motionVal');
const strengthSlider = document.getElementById('strengthSlider');
const strengthVal = document.getElementById('strengthVal');
const seedInput = document.getElementById('seedInput');
const randomSeedBtn = document.getElementById('randomSeedBtn');
const enhanceBtn = document.getElementById('enhanceBtn');
const nsfwSlider = document.getElementById('nsfwSlider');
const nsfwVal = document.getElementById('nsfwVal');
const faceEnhance = document.getElementById('faceEnhance');
const detailUpscale = document.getElementById('detailUpscale');
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
const mobileMenu = document.getElementById('mobileMenu');
const header = document.getElementById('header');
// β€”β€” Enhance Prompt Keywords β€”β€”β€”
const enhanceKeywords = [
'8K ultra HD', 'hyperrealistic', 'detailed skin texture', 'cinematic depth of field',
'soft ambient lighting', 'wet skin glistening', 'realistic body physics',
'natural movement', 'steady camera', 'professional color grading',
'film grain', 'moody shadows', 'realistic anatomy', 'explicit detail',
'photorealistic rendering', 'smooth motion', 'natural body proportions',
];
// β€”β€”β€” Initialize β€”β€”β€”
init();
function init() {
renderPromptGallery();
renderVideoGallery();
setupButtonGroups();
setupSliders();
setupUpload();
setupSurprise();
setupEnhance();
setupPromptTags();
setupAdvanced();
setupGenerate();
setupHeader();
setupMobileMenu();
setupNavLinks();
setupClearResults();
}
// β€”β€”β€” Prompt Gallery β€”β€”β€”
function renderPromptGallery() {
const container = document.getElementById('promptGallery');
container.innerHTML = promptGalleryData.map((item, i) => `
<div class="prompt-card rounded-xl sm:rounded-2xl bg-dark-900/60 border border-dark-700 overflow-hidden group" data-prompt="${encodeURIComponent(item.prompt)}">
<div class="aspect-video relative overflow-hidden">
<img src="${item.image}" alt="${item.category}" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" loading="lazy">
<div class="absolute inset-0 bg-gradient-to-t from-dark-950/80 via-transparent to-transparent"></div>
<div class="absolute top-3 left-3">
<span class="px-2 py-1 rounded-md text-[10px] font-bold uppercase tracking-wider ${item.color === 'flux' ? 'bg-flux-500/20 text-flux-400' : 'bg-violet-500/20 text-violet-400'}">${item.category}</span>
</div>
</div>
<div class="p-4">
<p class="text-sm text-dark-300 line-clamp-2 leading-relaxed">${item.prompt}</p>
<div class="mt-3 flex items-center gap-2 text-xs text-flux-400 opacity-0 group-hover:opacity-100 transition-opacity">
<i data-lucide="arrow-right" class="w-3 h-3"></i>
<span>Use this prompt</span>
</div>
</div>
</div>
`).join('');
// Click to fill prompt
container.querySelectorAll('.prompt-card').forEach(card => {
card.addEventListener('click', () => {
promptInput.value = decodeURIComponent(card.dataset.prompt);
updateCharCount();
document.getElementById('generator').scrollIntoView({ behavior: 'smooth' });
showToast('Prompt loaded!', 'success');
});
});
lucide.createIcons();
}
// β€”β€”β€” Video Gallery β€”β€”β€”
function renderVideoGallery() {
const container = document.getElementById('videoGallery');
container.innerHTML = videoGalleryData.map((item, i) => `
<div class="gallery-card aspect-video">
<img src="${item.image}" alt="${item.label}" class="w-full h-full object-cover" loading="lazy">
<div class="gallery-overlay">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full bg-flux-500/90 flex items-center justify-center">
<i data-lucide="play" class="w-4 h-4 text-white fill-white"></i>
</div>
<span class="text-sm font-semibold text-white">${item.label}</span>
</div>
</div>
</div>
`).join('');
lucide.createIcons();
}
// β€”β€”β€” Button Groups β€”β€”β€”
function setupButtonGroups() {
// Duration
document.querySelectorAll('#durationBtns .duration-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#durationBtns .duration-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.duration = parseInt(btn.dataset.value);
});
});
// Resolution
document.querySelectorAll('#resolutionBtns .resolution-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#resolutionBtns .resolution-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.resolution = btn.dataset.value;
});
});
// Count
document.querySelectorAll('#countBtns .count-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#countBtns .count-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.count = parseInt(btn.dataset.value);
});
});
// Camera
document.querySelectorAll('#cameraBtns .camera-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#cameraBtns .camera-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.camera = btn.dataset.value;
});
});
// Audio
document.querySelectorAll('#audioBtns .audio-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#audioBtns .audio-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.audio = btn.dataset.value;
});
});
// Model
document.querySelectorAll('#modelBtns .model-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#modelBtns .model-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.model = btn.dataset.value;
});
});
// Style
document.querySelectorAll('#styleBtns .style-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#styleBtns .style-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.style = btn.dataset.value;
});
});
// Aspect Ratio
document.querySelectorAll('#aspectBtns .aspect-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#aspectBtns .aspect-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.aspect = btn.dataset.value;
});
});
// Lighting
document.querySelectorAll('#lightingBtns .lighting-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#lightingBtns .lighting-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.lighting = btn.dataset.value;
});
});
// FPS
document.querySelectorAll('#fpsBtns .fps-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#fpsBtns .fps-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.fps = btn.dataset.value;
});
});
}
// β€”β€”β€” Sliders β€”β€”β€”
function setupSliders() {
motionSlider.addEventListener('input', () => {
state.motion = parseInt(motionSlider.value);
motionVal.textContent = state.motion + '%';
});
strengthSlider.addEventListener('input', () => {
state.strength = parseInt(strengthSlider.value);
strengthVal.textContent = state.strength + '%';
});
if (nsfwSlider) {
nsfwSlider.addEventListener('input', () => {
state.nsfw = parseInt(nsfwSlider.value);
nsfwVal.textContent = state.nsfw + '%';
});
}
randomSeedBtn.addEventListener('click', () => {
seedInput.value = Math.floor(Math.random() * 999999999);
state.seed = seedInput.value;
});
if (faceEnhance) {
faceEnhance.addEventListener('change', () => {
state.faceEnhance = faceEnhance.checked;
});
}
if (detailUpscale) {
detailUpscale.addEventListener('change', () => {
state.detailUpscale = detailUpscale.checked;
});
}
}
// β€”β€”β€” Upload β€”β€”β€”
function setupUpload() {
// Click to upload
uploadArea.addEventListener('click', (e) => {
if (e.target.closest('#removeImageBtn')) return;
if (!state.uploadedImage) fileInput.click();
});
// Drag & Drop
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const file = e.dataTransfer.files[0];
if (file) handleFile(file);
});
// File input change
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) handleFile(file);
});
// Remove image
removeImageBtn.addEventListener('click', (e) => {
e.stopPropagation();
state.uploadedImage = null;
uploadPreview.classList.add('hidden');
uploadPlaceholder.classList.remove('hidden');
fileInput.value = '';
});
}
function handleFile(file) {
const validTypes = ['image/png', 'image/jpeg', 'image/webp'];
if (!validTypes.includes(file.type)) {
showToast('Please upload a PNG, JPG, or WebP image.', 'error');
return;
}
const maxSize = 20 * 1024 * 1024; // 20MB
if (file.size > maxSize) {
showToast('Image must be under 20MB.', 'error');
return;
}
state.uploadedImage = file;
const reader = new FileReader();
reader.onload = (e) => {
previewImage.src = e.target.result;
uploadPlaceholder.classList.add('hidden');
uploadPreview.classList.remove('hidden');
// Get image dimensions
const img = new Image();
img.onload = () => {
imageInfo.textContent = `${file.name} Β· ${img.width}Γ—${img.height} Β· ${(file.size / 1024).toFixed(0)}KB`;
lucide.createIcons();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
showToast('Image uploaded successfully!', 'success');
}
// β€”β€” Surprise Me β€”β€”β€”
function setupSurprise() {
surpriseBtn.addEventListener('click', () => {
const prompt = surprisePrompts[promptIndex % surprisePrompts.length];
promptInput.value = prompt;
promptIndex++;
updateCharCount();
showToast('🎲 Random filthy prompt loaded!', 'success');
});
}
// β€”β€” Enhance Prompt β€”β€”β€”
function setupEnhance() {
if (enhanceBtn) {
enhanceBtn.addEventListener('click', () => {
if (!promptInput.value.trim()) {
showToast('Enter a prompt first to enhance it!', 'error');
promptInput.focus();
return;
}
// Add random enhance keywords
const shuffled = enhanceKeywords.sort(() => 0.5 - Math.random());
const numKeywords = 3 + Math.floor(Math.random() * 3);
const keywords = shuffled.slice(0, numKeywords).join(', ');
promptInput.value = promptInput.value.trim() + ', ' + keywords;
updateCharCount();
showToast('πŸ”₯ Prompt enhanced with detail keywords!', 'success');
});
}
}
// β€”β€” Prompt Tags β€”β€”β€”
function setupPromptTags() {
document.querySelectorAll('.prompt-tag').forEach(tag => {
tag.addEventListener('click', () => {
const additionalText = tag.dataset.prompt;
if (promptInput.value.trim()) {
promptInput.value = promptInput.value.trim() + ', ' + additionalText;
} else {
promptInput.value = additionalText;
}
updateCharCount();
showToast('βœ“ Tag added to prompt', 'success');
});
});
}
// β€”β€”β€” Character Count β€”β€”β€”
promptInput.addEventListener('input', updateCharCount);
function updateCharCount() {
const len = promptInput.value.length;
charCount.textContent = `${len} / 2000`;
if (len > 1800) charCount.classList.add('text-flux-400');
else charCount.classList.remove('text-flux-400');
}
// β€”β€”β€” Advanced Panel β€”β€”β€”
function setupAdvanced() {
advancedToggle.addEventListener('click', () => {
const isOpen = !advancedPanel.classList.contains('hidden');
if (isOpen) {
advancedPanel.classList.add('hidden');
advancedChevron.style.transform = 'rotate(0deg)';
} else {
advancedPanel.classList.remove('hidden');
advancedChevron.style.transform = 'rotate(180deg)';
lucide.createIcons();
}
});
}
// β€”β€”β€” Generate β€”β€”β€”
function setupGenerate() {
generateBtn.addEventListener('click', startGeneration);
}
async function startGeneration() {
if (state.isGenerating) return;
if (!state.uploadedImage) {
showToast('Please upload an image first!', 'error');
uploadArea.scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
if (!promptInput.value.trim()) {
showToast('Please enter a prompt!', 'error');
promptInput.focus();
return;
}
state.isGenerating = true;
// Show loading
loadingState.classList.remove('hidden');
resultsArea.classList.add('hidden');
generateBtn.classList.add('generate-loading');
generateBtn.innerHTML = `
<svg class="animate-spin w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
Generating...
`;
const steps = [
{ text: 'Analyzing source image...', sub: 'Extracting visual features', pct: 10 },
{ text: 'Processing prompt...', sub: 'Interpreting motion instructions', pct: 25 },
{ text: 'Initializing Wan 2.5 engine...', sub: 'Loading uncensored model weights', pct: 35 },
{ text: 'Generating video frames...', sub: 'Rendering keyframes', pct: 55 },
{ text: 'Adding motion dynamics...', sub: `Camera: ${state.camera} | Style: ${state.style} | NSFW: ${state.nsfw}%`, pct: 70 },
{ text: 'Rendering audio track...', sub: `Audio mode: ${state.audio} | Lighting: ${state.lighting}`, pct: 82 },
{ text: 'Post-processing...', sub: `Upscaling to ${getResLabel()} | ${state.fps}fps`, pct: 92 },
{ text: 'Finalizing...', sub: 'Almost there!', pct: 98 },
];
let stepIndex = 0;
const loadingInterval = setInterval(() => {
if (stepIndex < steps.length) {
const step = steps[stepIndex];
loadingText.textContent = step.text;
loadingSubtext.textContent = step.sub;
progressBar.style.width = step.pct + '%';
progressPercent.textContent = step.pct + '%';
const etaMin = state.duration > 10 ? '1-2 min' : '~30s';
progressETA.textContent = `ETA: ${etaMin}`;
stepIndex++;
}
}, 1200);
// Final progress
setTimeout(() => {
clearInterval(loadingInterval);
progressBar.style.width = '100%';
progressPercent.textContent = '100%';
loadingText.textContent = 'Complete!';
loadingSubtext.textContent = 'Your video is ready';
setTimeout(() => {
state.isGenerating = false;
loadingState.classList.add('hidden');
generateBtn.classList.remove('generate-loading');
generateBtn.innerHTML = `
<i data-lucide="wand-2" class="w-5 h-5 sm:w-6 sm:h-6 relative z-10"></i>
<span class="relative z-10">GENERATE VIDEO</span>
`;
lucide.createIcons();
showResults();
}, 600);
}, steps.length * 1200 + 500);
}
function getResLabel() {
const map = { '720': '720p', '1080': '1080p', '4k': '4K' };
return map[state.resolution] || '1080p';
}
function showResults() {
resultsArea.classList.remove('hidden');
resultsGrid.innerHTML = '';
const count = state.count;
for (let i = 0; i < count; i++) {
const seed = state.seed || Math.floor(Math.random() * 999999);
const imgSeed = Math.floor(Math.random() * 999);
const card = document.createElement('div');
card.className = 'video-result-card animate-fadeIn';
card.style.animationDelay = `${i * 0.1}s`;
card.innerHTML = `
<div class="aspect-video relative bg-dark-950 overflow-hidden group">
<img src="http://static.photos/nature/640x360/${imgSeed}" alt="Generated Video ${i + 1}" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-dark-950/30 group-hover:bg-dark-950/10 transition-all duration-300 flex items-center justify-center">
<div class="w-14 h-14 rounded-full bg-flux-500/90 flex items-center justify-center shadow-lg shadow-flux-500/30 cursor-pointer hover:scale-110 transition-transform">
<i data-lucide="play" class="w-6 h-6 text-white fill-white ml-1"></i>
</div>
</div>
<div class="absolute top-2 right-2 px-2 py-1 rounded bg-dark-900/80 backdrop-blur text-[10px] font-mono text-dark-300">
${state.duration}s β€’ ${getResLabel()}
</div>
<div class="absolute bottom-2 left-2 flex items-center gap-1">
<div class="w-2 h-2 rounded-full bg-green-400 animate-pulse"></div>
<span class="text-[10px] text-dark-300 font-medium">AI Generated</span>
</div>
</div>
<div class="p-3 flex items-center justify-between">
<div class="text-xs text-dark-500 font-mono">Seed: ${seed}</div>
<div class="flex items-center gap-2">
<button class="p-2 rounded-lg bg-dark-850 border border-dark-700 hover:border-flux-500/40 hover:bg-flux-500/10 transition-all" title="Download">
<i data-lucide="download" class="w-3.5 h-3.5 text-dark-400"></i>
</button>
<button class="p-2 rounded-lg bg-dark-850 border border-dark-700 hover:border-violet-500/40 hover:bg-violet-500/10 transition-all" title="Regenerate with variation">
<i data-lucide="refresh-cw" class="w-3.5 h-3.5 text-dark-400"></i>
</button>
<button class="p-2 rounded-lg bg-dark-850 border border-dark-700 hover:border-flux-500/40 hover:bg-flux-500/10 transition-all" title="Share">
<i data-lucide="share-2" class="w-3.5 h-3.5 text-dark-400"></i>
</button>
</div>
</div>
`;
resultsGrid.appendChild(card);
}
lucide.createIcons();
resultsArea.scrollIntoView({ behavior: 'smooth', block: 'start' });
showToast(`${count} video${count > 1 ? 's' : ''} generated successfully!`, 'success');
}
// β€”β€”β€” Clear Results β€”β€”β€”
function setupClearResults() {
clearResultsBtn.addEventListener('click', () => {
resultsArea.classList.add('hidden');
resultsGrid.innerHTML = '';
});
}
// β€”β€”β€” Header Scroll β€”β€”β€”
function setupHeader() {
let lastScroll = 0;
window.addEventListener('scroll', () => {
const scrollY = window.scrollY;
if (scrollY > 50) {
header.classList.add('header-scrolled');
} else {
header.classList.remove('header-scrolled');
}
lastScroll = scrollY;
});
}
// β€”β€”β€” Mobile Menu β€”β€”β€”
function setupMobileMenu() {
mobileMenuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
// Close on link click
mobileMenu.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
mobileMenu.classList.add('hidden');
});
});
}
// β€”β€”β€” Nav Links β€”β€”β€”
function setupNavLinks() {
const sections = ['home', 'generator', 'examples', 'about'];
const navLinks = document.querySelectorAll('.nav-link');
window.addEventListener('scroll', () => {
let current = '';
sections.forEach(id => {
const section = document.getElementById(id);
if (section) {
const sectionTop = section.offsetTop - 100;
if (window.scrollY >= sectionTop) current = id;
}
});
navLinks.forEach(link => {
const href = link.getAttribute('href').slice(1);
if (href === current) link.classList.add('active');
else link.classList.remove('active');
});
});
}
// β€”β€”β€” Toast Notifications β€”β€”β€”
function showToast(message, type = 'info') {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
const colors = {
success: 'border-green-500/50 bg-green-500/10 text-green-400',
error: 'border-flux-500/50 bg-flux-500/10 text-flux-400',
info: 'border-violet-500/50 bg-violet-500/10 text-violet-400',
};
const icons = {
success: 'check-circle',
error: 'alert-circle',
info: 'info',
};
toast.className = `toast-enter flex items-center gap-2 px-4 py-3 rounded-xl border backdrop-blur-xl text-sm font-medium shadow-lg ${colors[type] || colors.info}`;
toast.innerHTML = `<i data-lucide="${icons[type] || 'info'}" class="w-4 h-4 shrink-0"></i><span>${message}</span>`;
container.appendChild(toast);
lucide.createIcons();
setTimeout(() => {
toast.classList.remove('toast-enter');
toast.classList.add('toast-exit');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
});