Spaces:
Running
Running
| // ============================================================ | |
| // RushPilot AI — Application Logic | |
| // ============================================================ | |
| // ---- Initialize Lucide icons ---- | |
| function refreshIcons() { | |
| if (window.lucide) lucide.createIcons(); | |
| } | |
| refreshIcons(); | |
| // ============================================================ | |
| // DATA | |
| // ============================================================ | |
| const mediaAssets = [ | |
| { name: 'interview_take_04', dur: '00:18', badge: '4K', cat: 'people', seed: 22 }, | |
| { name: 'broll_citywide_4k', dur: '00:42', badge: 'AI', cat: 'cityscape', seed: 11 }, | |
| { name: 'product_hero_shot', dur: '00:24', cat: 'technology', seed: 42 }, | |
| { name: 'demo_sequence_v2', dur: '01:12', badge: 'AI', cat: 'office', seed: 9 }, | |
| { name: 'team_working_01', dur: '00:36', cat: 'workspace', seed: 3 }, | |
| { name: 'outro_logo_anim', dur: '00:08', cat: 'abstract', seed: 5 }, | |
| { name: 'interview_take_07', dur: '00:22', cat: 'people', seed: 15 }, | |
| { name: 'broll_product_macro', dur: '00:30', badge: 'AI', cat: 'technology', seed: 7 }, | |
| ]; | |
| const stylePresets = [ | |
| { name: 'Cinematic', cat: 'technology', seed: 42, selected: true }, | |
| { name: 'Documentary', cat: 'people', seed: 22 }, | |
| { name: 'Vlog / Social', cat: 'office', seed: 9 }, | |
| { name: 'Commercial', cat: 'minimal', seed: 8 }, | |
| { name: 'Podcast', cat: 'workspace', seed: 3 }, | |
| { name: 'Product Reveal', cat: 'abstract', seed: 5 }, | |
| { name: 'Drone Aerial', cat: 'aerial', seed: 12 }, | |
| { name: 'Anime / Stylized', cat: 'gradient', seed: 33 }, | |
| ]; | |
| const colorPresets = [ | |
| { name: 'Teal/Orange', grad: 'linear-gradient(135deg,#0f766e,#f97316)' }, | |
| { name: 'Noir B&W', grad: 'linear-gradient(135deg,#27272a,#71717a)' }, | |
| { name: 'Golden Hour', grad: 'linear-gradient(135deg,#f59e0b,#dc2626)' }, | |
| { name: 'Cyberpunk', grad: 'linear-gradient(135deg,#6366f1,#ec4899)' }, | |
| { name: 'Vintage Film', grad: 'linear-gradient(135deg,#92400e,#fcd34d)' }, | |
| { name: 'Cool Blue', grad: 'linear-gradient(135deg,#0ea5e9,#1e3a8a)' }, | |
| ]; | |
| const transitions = [ | |
| { name: 'Cross Dissolve', dur: '0.5s' }, | |
| { name: 'Dip to Black', dur: '0.8s' }, | |
| { name: 'Whip Pan', dur: '0.3s' }, | |
| { name: 'Zoom Blur', dur: '0.4s' }, | |
| { name: 'Glitch Cut', dur: '0.2s' }, | |
| { name: 'Slide Push', dur: '0.5s' }, | |
| ]; | |
| const historyItems = [ | |
| { prompt: 'Cinematic product reveal', cat: 'technology', seed: 42, time: '2m ago', badge: '4K' }, | |
| { prompt: 'Aerial city flythrough', cat: 'aerial', seed: 12, time: '8m ago' }, | |
| { prompt: 'Podcast intro animation', cat: 'workspace', seed: 3, time: '14m ago' }, | |
| { prompt: 'Abstract logo motion', cat: 'abstract', seed: 5, time: '32m ago' }, | |
| { prompt: 'Macro product detail', cat: 'technology', seed: 7, time: '1h ago', badge: 'AI' }, | |
| { prompt: 'Team b-roll sequence', cat: 'workspace', seed: 3, time: '2h ago' }, | |
| { prompt: 'Documentary interview', cat: 'people', seed: 22, time: '3h ago' }, | |
| { prompt: 'Social vertical cut', cat: 'office', seed: 9, time: '5h ago' }, | |
| ]; | |
| // ============================================================ | |
| // RENDER MEDIA GRID | |
| // ============================================================ | |
| function renderMediaGrid() { | |
| const grid = document.getElementById('mediaGrid'); | |
| grid.innerHTML = mediaAssets.map(a => ` | |
| <div class="media-item" data-name="${a.name}"> | |
| <img src="http://static.photos/${a.cat}/320x240/${a.seed}" loading="lazy" alt="${a.name}"/> | |
| ${a.badge ? `<div class="mi-badge ${a.badge==='AI'?'ai':''}">${a.badge}</div>` : ''} | |
| <div class="mi-duration">${a.dur}</div> | |
| <div class="mi-overlay"> | |
| <div class="mi-name">${a.name}</div> | |
| <div class="mi-meta">MOV · 4K · 23.976</div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| grid.querySelectorAll('.media-item').forEach(el => { | |
| el.addEventListener('click', () => { | |
| grid.querySelectorAll('.media-item').forEach(m => m.style.borderColor = ''); | |
| el.style.borderColor = '#818cf8'; | |
| showToast(`Added "${el.dataset.name}" to timeline`, 'info'); | |
| }); | |
| }); | |
| } | |
| // ============================================================ | |
| // RENDER STYLE PRESETS | |
| // ============================================================ | |
| function renderStylePresets() { | |
| const container = document.getElementById('stylePresets'); | |
| container.innerHTML = stylePresets.map((s, i) => ` | |
| <div class="style-card ${s.selected ? 'selected' : ''}" data-idx="${i}"> | |
| <div class="sc-bg" style="background-image:url('http://static.photos/${s.cat}/320x240/${s.seed}')"></div> | |
| <div class="sc-overlay"></div> | |
| <div class="sc-check"><i data-lucide="check" class="w-2.5 h-2.5 text-white"></i></div> | |
| <div class="sc-label"><span>${s.name}</span></div> | |
| </div> | |
| `).join(''); | |
| container.querySelectorAll('.style-card').forEach(el => { | |
| el.addEventListener('click', () => { | |
| container.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected')); | |
| el.classList.add('selected'); | |
| }); | |
| }); | |
| refreshIcons(); | |
| } | |
| // ============================================================ | |
| // RENDER COLOR PRESETS | |
| // ============================================================ | |
| function renderColorPresets() { | |
| const container = document.getElementById('effectsColor'); | |
| container.innerHTML = colorPresets.map((p, i) => ` | |
| <div class="effect-preset ${i===0?'selected':''}" style="background:${p.grad}"> | |
| <div class="ep-label">${p.name}</div> | |
| </div> | |
| `).join(''); | |
| container.querySelectorAll('.effect-preset').forEach(el => { | |
| el.addEventListener('click', () => { | |
| container.querySelectorAll('.effect-preset').forEach(e => e.classList.remove('selected')); | |
| el.classList.add('selected'); | |
| }); | |
| }); | |
| } | |
| // ============================================================ | |
| // RENDER TRANSITIONS | |
| // ============================================================ | |
| function renderTransitions() { | |
| const container = document.getElementById('effectsTrans'); | |
| container.innerHTML = transitions.map(t => ` | |
| <div class="trans-item"> | |
| <div class="trans-icon"></div> | |
| <span class="trans-name">${t.name}</span> | |
| <span class="trans-dur">${t.dur}</span> | |
| </div> | |
| `).join(''); | |
| } | |
| // ============================================================ | |
| // RENDER STOCK GRID | |
| // ============================================================ | |
| function renderStockGrid() { | |
| const grid = document.getElementById('stockGrid'); | |
| const cats = ['nature','cityscape','technology','people','workspace','abstract','office','minimal']; | |
| grid.innerHTML = cats.map((c, i) => ` | |
| <div class="media-item"> | |
| <img src="http://static.photos/${c}/320x240/${i+1}" loading="lazy" alt="stock"/> | |
| <div class="mi-badge">4K</div> | |
| <div class="mi-overlay"> | |
| <div class="mi-name">${c.charAt(0).toUpperCase()+c.slice(1)} clip</div> | |
| <div class="mi-meta">Royalty-free</div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| // ============================================================ | |
| // RENDER HISTORY STRIP | |
| // ============================================================ | |
| function renderHistory() { | |
| const strip = document.getElementById('historyStrip'); | |
| strip.innerHTML = historyItems.map(h => ` | |
| <div class="history-item"> | |
| <img src="http://static.photos/${h.cat}/320x240/${h.seed}" loading="lazy" alt="${h.prompt}"/> | |
| ${h.badge ? `<div class="hi-badge">${h.badge}</div>` : ''} | |
| <div class="hi-overlay"> | |
| <div class="hi-prompt">${h.prompt}</div> | |
| <div class="hi-time">${h.time}</div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| // ============================================================ | |
| // TIMELINE RULER | |
| // ============================================================ | |
| function renderTimelineRuler() { | |
| const ruler = document.getElementById('timelineRuler'); | |
| const rulerWidth = 1040; // matches track content | |
| let html = ''; | |
| const totalSec = 32; // 32 seconds shown | |
| for (let s = 0; s <= totalSec; s++) { | |
| const x = (s / totalSec) * rulerWidth; | |
| const major = s % 5 === 0; | |
| html += `<div class="ruler-tick ${major?'major':''}" style="left:${x}px; height:${major?6:4}px"></div>`; | |
| if (major) { | |
| const mm = String(Math.floor(s/60)).padStart(2,'0'); | |
| const ss = String(s%60).padStart(2,'0'); | |
| html += `<div class="ruler-label" style="left:${x+3}px">${mm}:${ss}:00</div>`; | |
| } | |
| } | |
| ruler.innerHTML = html; | |
| } | |
| // ============================================================ | |
| // WAVEFORMS | |
| // ============================================================ | |
| function renderWaveforms() { | |
| document.querySelectorAll('.waveform').forEach(wf => { | |
| const seed = parseInt(wf.dataset.seed) || 1; | |
| const bars = wf.classList.contains('dense') ? 120 : 60; | |
| let rng = seed; | |
| const rand = () => { rng = (rng * 9301 + 49297) % 233280; return rng / 233280; }; | |
| let html = ''; | |
| for (let i = 0; i < bars; i++) { | |
| const base = 0.3 + 0.7 * Math.abs(Math.sin(i * 0.3 + seed)); | |
| const noise = rand() * 0.4; | |
| const h = Math.max(8, Math.min(100, (base + noise) * 100)); | |
| html += `<div class="wf-bar" style="height:${h}%"></div>`; | |
| } | |
| wf.innerHTML = html; | |
| }); | |
| } | |
| // ============================================================ | |
| // MODE SWITCHING | |
| // ============================================================ | |
| const modeBtns = document.querySelectorAll('.mode-btn'); | |
| const modeIndicator = document.getElementById('modeIndicator'); | |
| const editorView = document.getElementById('editorView'); | |
| const generatorView = document.getElementById('generatorView'); | |
| const statusMode = document.getElementById('statusMode'); | |
| modeBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const mode = btn.dataset.mode; | |
| modeBtns.forEach(b => { | |
| b.classList.remove('text-white'); | |
| b.classList.add('text-zinc-400'); | |
| }); | |
| btn.classList.remove('text-zinc-400'); | |
| btn.classList.add('text-white'); | |
| modeIndicator.style.transform = mode === 'generator' ? 'translateX(100%)' : 'translateX(0)'; | |
| if (mode === 'editor') { | |
| editorView.classList.remove('hidden'); | |
| editorView.classList.add('flex'); | |
| generatorView.classList.add('hidden'); | |
| generatorView.classList.remove('flex'); | |
| statusMode.textContent = 'Editor'; | |
| } else { | |
| generatorView.classList.remove('hidden'); | |
| generatorView.classList.add('flex'); | |
| editorView.classList.add('hidden'); | |
| editorView.classList.remove('flex'); | |
| statusMode.textContent = 'Generator'; | |
| } | |
| refreshIcons(); | |
| }); | |
| }); | |
| // ============================================================ | |
| // LEFT PANEL TABS | |
| // ============================================================ | |
| document.querySelectorAll('.left-tab').forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| document.querySelectorAll('.left-tab').forEach(t => t.classList.remove('active')); | |
| tab.classList.add('active'); | |
| const target = tab.dataset.leftTab; | |
| document.querySelectorAll('.left-panel').forEach(p => { | |
| p.classList.toggle('hidden', p.dataset.leftPanel !== target); | |
| }); | |
| }); | |
| }); | |
| // ============================================================ | |
| // INSPECTOR COLLAPSE | |
| // ============================================================ | |
| document.querySelectorAll('.inspector-header').forEach(h => { | |
| h.addEventListener('click', () => { | |
| h.parentElement.classList.toggle('collapsed'); | |
| }); | |
| }); | |
| // ============================================================ | |
| // PLAYBACK SIMULATION | |
| // ============================================================ | |
| let isPlaying = false; | |
| let playInterval = null; | |
| let currentTime = 0; | |
| const playBtn = document.getElementById('playBtn'); | |
| const playIcon = document.getElementById('playIcon'); | |
| const tcDisplay = document.getElementById('timecodeDisplay'); | |
| const playhead = document.getElementById('playhead'); | |
| function formatTC(frames) { | |
| const fps = 24; | |
| const totalFrames = Math.floor(frames); | |
| const f = totalFrames % fps; | |
| const s = Math.floor(totalFrames / fps) % 60; | |
| const m = Math.floor(totalFrames / (fps*60)) % 60; | |
| const h = Math.floor(totalFrames / (fps*3600)); | |
| return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}:${String(f).padStart(2,'0')}`; | |
| } | |
| playBtn.addEventListener('click', () => { | |
| isPlaying = !isPlaying; | |
| if (isPlaying) { | |
| playIcon.setAttribute('data-lucide', 'square'); | |
| playBtn.style.background = '#ef4444'; | |
| playBtn.style.color = '#fff'; | |
| playInterval = setInterval(() => { | |
| currentTime += 1; | |
| tcDisplay.textContent = formatTC(84*24 + currentTime); | |
| const px = 320 + (currentTime / 24) * 40; | |
| if (px < 1040) playhead.style.left = px + 'px'; | |
| else { currentTime = 0; playhead.style.left = '320px'; } | |
| }, 42); | |
| } else { | |
| playIcon.setAttribute('data-lucide', 'play'); | |
| playBtn.style.background = '#fff'; | |
| playBtn.style.color = '#000'; | |
| clearInterval(playInterval); | |
| } | |
| refreshIcons(); | |
| }); | |
| // Spacebar shortcut | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space' && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA') { | |
| e.preventDefault(); | |
| playBtn.click(); | |
| } | |
| }); | |
| // ============================================================ | |
| // CLIP SELECTION | |
| // ============================================================ | |
| document.querySelectorAll('.clip').forEach(clip => { | |
| clip.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| document.querySelectorAll('.clip').forEach(c => c.classList.remove('selected')); | |
| clip.classList.add('selected'); | |
| const label = clip.querySelector('.clip-label')?.textContent || 'clip'; | |
| showToast(`Selected: ${label}`, 'info'); | |
| }); | |
| }); | |
| // ============================================================ | |
| // TIMELINE ZOOM | |
| // ============================================================ | |
| const timelineZoom = document.getElementById('timelineZoom'); | |
| const timelineTracks = document.getElementById('timelineTracks'); | |
| const timelineRuler = document.getElementById('timelineRuler'); | |
| timelineZoom.addEventListener('input', () => { | |
| const scale = timelineZoom.value / 100; | |
| timelineTracks.style.transform = `scaleX(${scale})`; | |
| timelineTracks.style.transformOrigin = 'left center'; | |
| timelineRuler.style.transform = `scaleX(${scale})`; | |
| timelineRuler.style.transformOrigin = 'left center'; | |
| }); | |
| // Timeline scroll sync | |
| const timelineScroll = document.getElementById('timelineScroll'); | |
| timelineScroll.addEventListener('scroll', () => { | |
| timelineRuler.style.transform = `translateX(${-timelineScroll.scrollLeft}px)`; | |
| }); | |
| // ============================================================ | |
| // FILTER CHIPS | |
| // ============================================================ | |
| document.querySelectorAll('.filter-chip').forEach(chip => { | |
| chip.addEventListener('click', () => { | |
| document.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active')); | |
| chip.classList.add('active'); | |
| }); | |
| }); | |
| // ============================================================ | |
| // GENERATOR: PARAMETERS | |
| // ============================================================ | |
| // Model select | |
| document.querySelectorAll('.model-opt').forEach(opt => { | |
| opt.addEventListener('click', () => { | |
| document.querySelectorAll('.model-opt').forEach(o => o.classList.remove('active')); | |
| opt.classList.add('active'); | |
| }); | |
| }); | |
| // Ratio select | |
| const genResBadge = document.getElementById('genResBadge'); | |
| document.querySelectorAll('.ratio-opt').forEach(opt => { | |
| opt.addEventListener('click', () => { | |
| document.querySelectorAll('.ratio-opt').forEach(o => o.classList.remove('active')); | |
| opt.classList.add('active'); | |
| const fps = document.querySelector('#fpsSelect .active')?.dataset.fps || 30; | |
| const dur = document.getElementById('durationSlider').value; | |
| genResBadge.textContent = `${opt.dataset.w}×${opt.dataset.h} · ${fps}fps · ${dur}s`; | |
| }); | |
| }); | |
| // FPS | |
| document.querySelectorAll('#fpsSelect .seg-opt').forEach(opt => { | |
| opt.addEventListener('click', () => { | |
| document.querySelectorAll('#fpsSelect .seg-opt').forEach(o => o.classList.remove('active')); | |
| opt.classList.add('active'); | |
| const ratio = document.querySelector('#ratioSelect .active'); | |
| const dur = document.getElementById('durationSlider').value; | |
| genResBadge.textContent = `${ratio.dataset.w}×${ratio.dataset.h} · ${opt.dataset.fps}fps · ${dur}s`; | |
| }); | |
| }); | |
| // Resolution | |
| document.querySelectorAll('#resSelect .seg-opt').forEach(opt => { | |
| opt.addEventListener('click', () => { | |
| document.querySelectorAll('#resSelect .seg-opt').forEach(o => o.classList.remove('active')); | |
| opt.classList.add('active'); | |
| }); | |
| }); | |
| // Duration | |
| const durationSlider = document.getElementById('durationSlider'); | |
| const durationValue = document.getElementById('durationValue'); | |
| durationSlider.addEventListener('input', () => { | |
| durationValue.textContent = durationSlider.value + 's'; | |
| const ratio = document.querySelector('#ratioSelect .active'); | |
| const fps = document.querySelector('#fpsSelect .active')?.dataset.fps || 30; | |
| genResBadge.textContent = `${ratio.dataset.w}×${ratio.dataset.h} · ${fps}fps · ${durationSlider.value}s`; | |
| }); | |
| // Motion | |
| const motionSlider = document.getElementById('motionSlider'); | |
| const motionValue = document.getElementById('motionValue'); | |
| const motionLabels = ['Static','Subtle','Low','Medium','High','Intense']; | |
| motionSlider.addEventListener('input', () => { | |
| const idx = Math.floor(motionSlider.value / 100 * (motionLabels.length-1)); | |
| motionValue.textContent = motionLabels[idx]; | |
| }); | |
| // Prompt chips | |
| document.querySelectorAll('.prompt-chip').forEach(chip => { | |
| chip.addEventListener('click', () => { | |
| const input = document.getElementById('promptInput'); | |
| const insert = chip.dataset.insert; | |
| input.value = input.value.trim() + ' ' + insert; | |
| }); | |
| }); | |
| // Enhance prompt | |
| document.getElementById('enhancePrompt').addEventListener('click', () => { | |
| const input = document.getElementById('promptInput'); | |
| const additions = ' Shot on ARRI Alexa, 35mm anamorphic lens, color graded teal-and-orange, volumetric haze, film grain, high dynamic range, 8k detail.'; | |
| if (!input.value.includes('ARRI')) { | |
| input.value = input.value.replace(/\.$/, '') + additions; | |
| showToast('Prompt enhanced with cinematic tokens', 'success'); | |
| } else { | |
| showToast('Prompt already enhanced', 'info'); | |
| } | |
| }); | |
| // ============================================================ | |
| // GENERATION SIMULATION | |
| // ============================================================ | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const genOverlay = document.getElementById('genOverlay'); | |
| const genResultBar = document.getElementById('genResultBar'); | |
| const progressRing = document.getElementById('progressRing'); | |
| const progressPct = document.getElementById('progressPct'); | |
| const genStageText = document.getElementById('genStageText'); | |
| const genStageSub = document.getElementById('genStageSub'); | |
| const stageDots = document.querySelectorAll('#genStages .stage-dot'); | |
| const stages = [ | |
| { text: 'Initializing engine…', sub: 'Allocating GPU resources' }, | |
| { text: 'Analyzing prompt semantics…', sub: 'Parsing scene graph & intent' }, | |
| { text: 'Generating keyframes…', sub: 'Diffusion sampling · 40 steps' }, | |
| { text: 'Synthesizing motion…', sub: 'Temporal coherence & flow' }, | |
| { text: 'Upscaling & color grading…', sub: 'AI super-resolution · 4K' }, | |
| ]; | |
| generateBtn.addEventListener('click', () => { | |
| genOverlay.classList.remove('hidden'); | |
| genOverlay.classList.add('flex'); | |
| genResultBar.style.opacity = '0'; | |
| generateBtn.disabled = true; | |
| generateBtn.style.opacity = '0.5'; | |
| let progress = 0; | |
| let stageIdx = 0; | |
| stageDots.forEach(d => d.classList.remove('active','done')); | |
| stageDots[0].classList.add(' |