Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NeuroFrame | Pro Text-to-Video AI</title> | |
| <!-- Importing FontAwesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* --- CSS VARIABLES & RESET --- */ | |
| :root { | |
| --bg-dark: #0f1115; | |
| --bg-panel: #161b22; | |
| --bg-input: #0d1117; | |
| --primary: #6366f1; /* Indigo */ | |
| --primary-hover: #4f46e5; | |
| --accent: #ec4899; /* Pink */ | |
| --text-main: #f0f6fc; | |
| --text-muted: #8b949e; | |
| --border: #30363d; | |
| --radius-sm: 6px; | |
| --radius-md: 12px; | |
| --radius-lg: 20px; | |
| --shadow: 0 4px 20px rgba(0, 0, 0, 0.5); | |
| --font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| outline: none; | |
| } | |
| body { | |
| font-family: var(--font-family); | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* --- SCROLLBAR --- */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg-dark); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--border); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--text-muted); | |
| } | |
| /* --- HEADER --- */ | |
| header { | |
| height: 60px; | |
| background-color: var(--bg-panel); | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 0 20px; | |
| z-index: 10; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| font-size: 1.25rem; | |
| font-weight: 700; | |
| background: linear-gradient(135deg, var(--text-main), var(--primary)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .brand i { | |
| font-size: 1.4rem; | |
| color: var(--primary); | |
| -webkit-text-fill-color: initial; | |
| } | |
| .header-actions { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| } | |
| .api-status { | |
| font-size: 0.85rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 12px; | |
| background: rgba(99, 102, 241, 0.1); | |
| border: 1px solid rgba(99, 102, 241, 0.3); | |
| border-radius: var(--radius-sm); | |
| color: var(--primary); | |
| } | |
| .status-dot { | |
| width: 8px; | |
| height: 8px; | |
| background-color: var(--primary); | |
| border-radius: 50%; | |
| box-shadow: 0 0 8px var(--primary); | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.5; transform: scale(0.8); } | |
| 100% { opacity: 1; transform: scale(1); } | |
| } | |
| .built-with { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| transition: var(--transition); | |
| } | |
| .built-with:hover { | |
| color: var(--text-main); | |
| } | |
| /* --- MAIN LAYOUT --- */ | |
| .app-container { | |
| display: flex; | |
| flex: 1; | |
| overflow: hidden; | |
| } | |
| /* --- SIDEBAR (History) --- */ | |
| aside { | |
| width: 280px; | |
| background-color: var(--bg-panel); | |
| border-right: 1px solid var(--border); | |
| display: flex; | |
| flex-direction: column; | |
| transition: transform 0.3s ease; | |
| } | |
| .sidebar-header { | |
| padding: 20px; | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .history-list { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 10px; | |
| } | |
| .history-item { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| padding: 12px; | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| border: 1px solid transparent; | |
| margin-bottom: 8px; | |
| } | |
| .history-item:hover { | |
| background-color: rgba(255, 255, 255, 0.05); | |
| border-color: rgba(255, 255, 255, 0.1); | |
| } | |
| .history-item.active { | |
| background-color: rgba(99, 102, 241, 0.15); | |
| border-color: var(--primary); | |
| } | |
| .history-prompt { | |
| font-size: 0.85rem; | |
| color: var(--text-main); | |
| display: -webkit-box; | |
| -webkit-line-clamp: 2; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .history-meta { | |
| font-size: 0.75rem; | |
| color: var(--text-muted); | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| /* --- CONTENT AREA --- */ | |
| main { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-y: auto; | |
| position: relative; | |
| } | |
| /* --- VIDEO PLAYER AREA --- */ | |
| .preview-stage { | |
| flex: 1; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 40px; | |
| background-image: radial-gradient(circle at center, #1c2128 0%, #0f1115 100%); | |
| min-height: 400px; | |
| position: relative; | |
| } | |
| .video-container { | |
| width: 100%; | |
| max-width: 900px; | |
| aspect-ratio: 16/9; | |
| background-color: #000; | |
| border-radius: var(--radius-md); | |
| box-shadow: 0 20px 50px rgba(0,0,0,0.6); | |
| border: 1px solid var(--border); | |
| overflow: hidden; | |
| position: relative; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| video { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| display: none; | |
| } | |
| .placeholder-state { | |
| text-align: center; | |
| color: var(--text-muted); | |
| } | |
| .placeholder-icon { | |
| font-size: 4rem; | |
| margin-bottom: 20px; | |
| opacity: 0.3; | |
| } | |
| /* --- CONTROLS AREA --- */ | |
| .controls-panel { | |
| background-color: var(--bg-panel); | |
| border-top: 1px solid var(--border); | |
| padding: 30px; | |
| display: grid; | |
| grid-template-columns: 1fr 350px; | |
| gap: 40px; | |
| } | |
| .input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| } | |
| .text-area-wrapper { | |
| position: relative; | |
| } | |
| textarea { | |
| width: 100%; | |
| height: 120px; | |
| background-color: var(--bg-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-md); | |
| padding: 15px; | |
| color: var(--text-main); | |
| font-family: var(--font-family); | |
| font-size: 1rem; | |
| resize: none; | |
| transition: var(--transition); | |
| line-height: 1.5; | |
| } | |
| textarea:focus { | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); | |
| } | |
| .settings-grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, 1fr); | |
| gap: 20px; | |
| } | |
| .control-item { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| label { | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| } | |
| select, input[type="text"], input[type="number"] { | |
| background-color: var(--bg-input); | |
| border: 1px solid var(--border); | |
| color: var(--text-main); | |
| padding: 10px 12px; | |
| border-radius: var(--radius-sm); | |
| font-size: 0.9rem; | |
| transition: var(--transition); | |
| } | |
| select:focus, input:focus { | |
| border-color: var(--primary); | |
| } | |
| /* Range Slider */ | |
| input[type=range] { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| background: transparent; | |
| } | |
| input[type=range]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| height: 16px; | |
| width: 16px; | |
| border-radius: 50%; | |
| background: var(--primary); | |
| cursor: pointer; | |
| margin-top: -6px; | |
| box-shadow: 0 0 10px rgba(99, 102, 241, 0.5); | |
| } | |
| input[type=range]::-webkit-slider-runnable-track { | |
| width: 100%; | |
| height: 4px; | |
| cursor: pointer; | |
| background: var(--border); | |
| border-radius: 2px; | |
| } | |
| /* Generate Button */ | |
| .btn-generate { | |
| background: linear-gradient(135deg, var(--primary), var(--accent)); | |
| color: white; | |
| border: none; | |
| border-radius: var(--radius-sm); | |
| padding: 16px 32px; | |
| font-size: 1rem; | |
| font-weight: 700; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4); | |
| margin-top: 10px; | |
| } | |
| .btn-generate:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(236, 72, 153, 0.4); | |
| } | |
| .btn-generate:disabled { | |
| opacity: 0.7; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* Progress Section */ | |
| .progress-section { | |
| background: rgba(0,0,0,0.2); | |
| padding: 20px; | |
| border-radius: var(--radius-md); | |
| display: none; /* Hidden by default */ | |
| flex-direction: column; | |
| gap: 15px; | |
| } | |
| .progress-section.active { | |
| display: flex; | |
| } | |
| .progress-bar-container { | |
| height: 6px; | |
| background-color: var(--border); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| width: 0%; | |
| background: linear-gradient(90deg, var(--primary), var(--accent)); | |
| transition: width 0.3s ease; | |
| } | |
| .progress-text { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| /* Toast Notification */ | |
| .toast { | |
| position: fixed; | |
| bottom: 30px; | |
| right: 30px; | |
| background-color: var(--bg-panel); | |
| border: 1px solid var(--border); | |
| border-left: 4px solid var(--primary); | |
| padding: 15px 25px; | |
| border-radius: var(--radius-sm); | |
| box-shadow: var(--shadow); | |
| transform: translateY(100px); | |
| opacity: 0; | |
| transition: var(--transition); | |
| z-index: 100; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .toast.show { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| /* --- RESPONSIVE DESIGN --- */ | |
| @media (max-width: 900px) { | |
| .controls-panel { | |
| grid-template-columns: 1fr; | |
| gap: 20px; | |
| } | |
| aside { | |
| position: absolute; | |
| left: -280px; | |
| height: calc(100vh - 60px); | |
| z-index: 20; | |
| } | |
| aside.open { | |
| transform: translateX(280px); | |
| } | |
| .mobile-menu-btn { | |
| display: block ; | |
| } | |
| } | |
| .mobile-menu-btn { | |
| display: none; | |
| background: none; | |
| border: none; | |
| color: var(--text-main); | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <header> | |
| <div class="brand"> | |
| <button class="mobile-menu-btn" id="menuToggle"><i class="fas fa-bars"></i></button> | |
| <i class="fas fa-film"></i> | |
| NeuroFrame | |
| </div> | |
| <div class="header-actions"> | |
| <div class="api-status"> | |
| <div class="status-dot"></div> | |
| API Connected | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with"> | |
| Built with anycoder | |
| </a> | |
| <div class="user-avatar"> | |
| <i class="fas fa-user-circle fa-lg"></i> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="app-container"> | |
| <!-- Sidebar: History --> | |
| <aside id="sidebar"> | |
| <div class="sidebar-header"> | |
| Recent Generations | |
| </div> | |
| <div class="history-list" id="historyList"> | |
| <!-- History items will be injected here via JS --> | |
| <div class="history-item active"> | |
| <div class="history-prompt">Cyberpunk city street at night, neon rain...</div> | |
| <div class="history-meta"> | |
| <span>16:9</span> | |
| <span>2m ago</span> | |
| </div> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- Main Content --> | |
| <main> | |
| <!-- Preview Stage --> | |
| <div class="preview-stage"> | |
| <div class="video-container" id="videoContainer"> | |
| <div class="placeholder-state" id="placeholderState"> | |
| <i class="fas fa-play-circle placeholder-icon"></i> | |
| <h3>Ready to Create</h3> | |
| <p>Enter a prompt and configure settings to generate video.</p> | |
| </div> | |
| <!-- Video Element --> | |
| <video id="mainVideo" controls loop playsinline> | |
| <source src="" type="video/mp4"> | |
| Your browser does not support the video tag. | |
| </video> | |
| <!-- Loading Overlay --> | |
| <div id="loadingOverlay" style="display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.8); flex-direction: column; justify-content: center; align-items: center; z-index: 5;"> | |
| <i class="fas fa-circle-notch fa-spin fa-3x" style="color: var(--primary); margin-bottom: 15px;"></i> | |
| <h3 style="margin-bottom: 5px;">Generating Frames...</h3> | |
| <p id="loadingText" style="color: var(--text-muted); font-size: 0.9rem;">Initializing diffusion model</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Controls Panel --> | |
| <div class="controls-panel"> | |
| <!-- Left Column: Inputs --> | |
| <div class="input-group"> | |
| <div class="text-area-wrapper"> | |
| <textarea id="promptInput" placeholder="Describe your video in detail (e.g., A cinematic drone shot of a futuristic mountain fortress during golden hour, 8k resolution)..."></textarea> | |
| </div> | |
| <div class="settings-grid"> | |
| <div class="control-item"> | |
| <label>Aspect Ratio</label> | |
| <select id="aspectRatio"> | |
| <option value="16:9">16:9 (Landscape)</option> | |
| <option value="9:16">9:16 (Portrait)</option> | |
| <option value="1:1">1:1 (Square)</option> | |
| <option value="2.35:1">2.35:1 (Cinematic)</option> | |
| </select> | |
| </div> | |
| <div class="control-item"> | |
| <label>Motion Strength</label> | |
| <input type="range" min="1" max="10" value="5" id="motionStrength"> | |
| </div> | |
| <div class="control-item"> | |
| <label>Duration (Seconds)</label> | |
| <select id="duration"> | |
| <option value="4">4s</option> | |
| <option value="8" selected>8s</option> | |
| <option value="16">16s</option> | |
| </select> | |
| </div> | |
| <div class="control-item"> | |
| <label>Style Preset</label> | |
| <select id="stylePreset"> | |
| <option value="realistic">Photorealistic</option> | |
| <option value="anime">Anime / 2D</option> | |
| <option value="3d-render">3D Render</option> | |
| <option value="oil-painting">Oil Painting</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Column: Action & Status --> | |
| <div class="action-group"> | |
| <div class="control-item" style="margin-bottom: 20px;"> | |
| <label>Negative Prompt (Optional)</label> | |
| <input type="text" id="negativePrompt" placeholder="blur, distortion, low quality"> | |
| </div> | |
| <div class="progress-section" id="progressSection"> | |
| <div class="progress-text"> | |
| <span id="progressStatus">Processing</span> | |
| <span id="progressPercent">0%</span> | |
| </div> | |
| <div class="progress-bar-container"> | |
| <div class="progress-bar" id="progressBar"></div> | |
| </div> | |
| </div> | |
| <button class="btn-generate" id="generateBtn"> | |
| <i class="fas fa-wand-magic-sparkles"></i> | |
| Generate Video | |
| </button> | |
| <div style="margin-top: 15px; font-size: 0.8rem; color: var(--text-muted); text-align: center;"> | |
| Est. cost: ~0.05 credits | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div class="toast" id="toast"> | |
| <i class="fas fa-check-circle" style="color: var(--primary);"></i> | |
| <span id="toastMessage">Video generated successfully!</span> | |
| </div> | |
| <script> | |
| // --- DOM Elements --- | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const promptInput = document.getElementById('promptInput'); | |
| const videoContainer = document.getElementById('videoContainer'); | |
| const mainVideo = document.getElementById('mainVideo'); | |
| const placeholderState = document.getElementById('placeholderState'); | |
| const loadingOverlay = document.getElementById('loadingOverlay'); | |
| const loadingText = document.getElementById('loadingText'); | |
| const progressSection = document.getElementById('progressSection'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressPercent = document.getElementById('progressPercent'); | |
| const progressStatus = document.getElementById('progressStatus'); | |
| const historyList = document.getElementById('historyList'); | |
| const toast = document.getElementById('toast'); | |
| const menuToggle = document.getElementById('menuToggle'); | |
| const sidebar = document.getElementById('sidebar'); | |
| // --- State --- | |
| let isGenerating = false; | |
| // --- Sample Videos for Simulation --- | |
| // Using reliable, copyright-free stock video URLs for demonstration | |
| const sampleVideos = [ | |
| "https://videos.pexels.com/video-files/3129671/3129671-uhd_2560_1440_30fps.mp4", // Cyberpunk/Neon | |
| "https://videos.pexels.com/video-files/856973/856973-hd_1920_1080_30fps.mp4", // Nature/Timelapse | |
| "https://videos.pexels.com/video-files/2759484/2759484-hd_1920_1080_25fps.mp4", // Abstract/Tech | |
| "https://videos.pexels.com/video-files/3121459/3121459-hd_1920_1080_25fps.mp4" // City/Night | |
| ]; | |
| // --- Event Listeners --- | |
| menuToggle.addEventListener('click', () => { | |
| sidebar.classList.toggle('open'); | |
| }); | |
| generateBtn.addEventListener('click', startGeneration); | |
| // --- Functions --- | |
| function startGeneration() { | |
| const prompt = promptInput.value.trim(); | |
| if (!prompt) { | |
| showToast("Please enter a text prompt first.", "error"); | |
| promptInput.focus(); | |
| return; | |
| } | |
| if (isGenerating) return; | |
| // Start UI State | |
| isGenerating = true; | |
| generateBtn.disabled = true; | |
| generateBtn.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Generating...'; | |
| // Hide video, show loading | |
| mainVideo.style.display = 'none'; | |
| mainVideo.pause(); | |
| placeholderState.style.display = 'none'; | |
| loadingOverlay.style.display = 'flex'; | |
| progressSection.classList.add('active'); | |
| // Reset Progress | |
| progressBar.style.width = '0%'; | |
| progressPercent.innerText = '0%'; | |
| // Simulation Logic | |
| let progress = 0; | |
| const statusMessages = [ | |
| "Initializing diffusion model...", | |
| "Analyzing text prompt semantics...", | |
| "Generating initial noise latents...", | |
| "Denoising frames (Step 10/50)...", | |
| "Denoising frames (Step 30/50)...", | |
| "Upscaling resolution...", | |
| "Rendering final video stream..." | |
| ]; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 15; // Random increment | |
| if (progress > 100) progress = 100; | |
| // Update UI | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.innerText = `${Math.floor(progress)}%`; | |
| // Update text based on progress | |
| const msgIndex = Math.min(Math.floor((progress / 100) * statusMessages.length), statusMessages.length - 1); | |
| loadingText.innerText = statusMessages[msgIndex]; | |
| progressStatus.innerText = statusMessages[msgIndex]; | |
| if (progress >= 100) { | |
| clearInterval(interval); | |
| finishGeneration(prompt); | |
| } | |
| }, 800); | |
| } | |
| function finishGeneration(prompt) { | |
| // Simulate a brief delay at 100% | |
| setTimeout(() => { | |
| // Select a random video from samples | |
| const randomVideo = sampleVideos[Math.floor(Math.random() * sampleVideos.length)]; | |
| // Update Video Player | |
| mainVideo.src = randomVideo; | |
| mainVideo.style.display = 'block'; | |
| loadingOverlay.style.display = 'none'; | |
| // Auto play | |
| mainVideo.play().catch(e => console.log("Auto-play prevented by browser policy")); | |
| // Reset Button | |
| isGenerating = false; | |
| generateBtn.disabled = false; | |
| generateBtn.innerHTML = '<i class="fas fa-wand-magic-sparkles"></i> Generate Video'; | |
| progressSection.classList.remove('active'); | |
| // Add to History | |
| addToHistory(prompt); | |
| // Show Success Message | |
| showToast("Video generated successfully!"); | |
| }, 1000); | |
| } | |
| function addToHistory(prompt) { | |
| const item = document.createElement('div'); | |
| item.className = 'history-item'; | |
| // Truncate prompt if too long | |
| const displayPrompt = prompt.length > 60 ? prompt.substring(0, 60) + "..." : prompt; | |
| item.innerHTML = ` | |
| <div class="history-prompt">${displayPrompt}</div> | |
| <div class="history-meta"> | |
| <span>${document.getElementById('aspectRatio').value}</span> | |
| <span>Just now</span> | |
| </div> | |
| `; | |
| // Add click event to load this "history" (simulation) | |
| item.addEventListener('click', () => { | |
| document.querySelectorAll('.history-item').forEach(i => i.classList.remove('active')); | |
| item.classList.add('active'); | |
| promptInput.value = prompt; | |
| // In a real app, this would reload the specific video blob | |
| mainVideo.currentTime = 0; | |
| mainVideo.play(); | |
| }); | |
| // Insert at top | |
| historyList.insertBefore(item, historyList.firstChild); | |
| // Remove 'active' from others | |
| document.querySelectorAll('.history-item').forEach(i => i.classList.remove('active')); | |
| item.classList.add('active'); | |
| } | |
| function showToast(message, type = "success") { | |
| const toastMsg = document.getElementById('toastMessage'); | |
| toastMsg.innerText = message; | |
| // Simple styling change for error vs success | |
| if(type === 'error') { | |
| toast.style.borderLeftColor = '#ef4444'; | |
| toast.querySelector('i').className = 'fas fa-exclamation-circle'; | |
| toast.querySelector('i').style.color = '#ef4444'; | |
| } else { | |
| toast.style.borderLeftColor = 'var(--primary)'; | |
| toast.querySelector('i').className = 'fas fa-check-circle'; | |
| toast.querySelector('i').style.color = 'var(--primary)'; | |
| } | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Initialize with a prompt placeholder animation logic | |
| promptInput.addEventListener('focus', () => { | |
| promptInput.parentElement.style.boxShadow = "0 0 0 3px rgba(99, 102, 241, 0.1)"; | |
| }); | |
| promptInput.addEventListener('blur', () => { | |
| promptInput.parentElement.style.boxShadow = "none"; | |
| }); | |
| </script> | |
| </body> | |
| </html> |