Spaces:
Configuration error
Configuration error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta | |
| name="viewport" | |
| content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" | |
| /> | |
| <title>Animetrix AI | AI Animation Engine</title> | |
| <!-- Favicon --> | |
| <link rel="icon" type="image/x-icon" href="/favicon.ico" /> | |
| <!-- Fonts: Inter (Modern, Clean) --> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" | |
| rel="stylesheet" | |
| /> | |
| <!-- Tailwind CSS --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <!-- Three.js --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script> | |
| <!-- Custom Config --> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| fontFamily: { | |
| sans: ["Inter", "sans-serif"], | |
| }, | |
| colors: { | |
| black: "#000000", | |
| obsidian: "#0A0A0A", | |
| orange: { | |
| 50: "#FFF7ED", | |
| 100: "#FFEDD5", | |
| 200: "#FED7AA", | |
| 300: "#FDBA74", | |
| 400: "#FB923C", | |
| 500: "#F97316", | |
| 600: "#EA580C", | |
| 700: "#C2410C", | |
| 800: "#9A3412", | |
| 900: "#7C2D12", | |
| 950: "#431407", | |
| brand: "#FF6B00", // Vibrant Neon Orange | |
| }, | |
| }, | |
| animation: { | |
| glow: "glow 4s ease-in-out infinite", | |
| float: "float 6s ease-in-out infinite", | |
| }, | |
| keyframes: { | |
| glow: { | |
| "0%, 100%": { opacity: 0.3 }, | |
| "50%": { opacity: 0.6 }, | |
| }, | |
| float: { | |
| "0%, 100%": { transform: "translateY(0)" }, | |
| "50%": { transform: "translateY(-10px)" }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }; | |
| </script> | |
| <style> | |
| body { | |
| background-color: #000000; | |
| color: #ffffff; | |
| overflow-x: hidden; | |
| width: 100vw; | |
| min-height: 100vh; | |
| } | |
| #canvas-container { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: 0; | |
| pointer-events: none; | |
| } | |
| .glass { | |
| background: rgba(10, 10, 10, 0.7); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid rgba(255, 107, 0, 0.1); | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8); | |
| } | |
| .glass-hover:hover { | |
| border-color: rgba(255, 107, 0, 0.3); | |
| background: rgba(15, 15, 15, 0.8); | |
| } | |
| .orange-gradient-text { | |
| background: linear-gradient(135deg, #ff6b00 0%, #ffa500 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .btn-orange { | |
| background: linear-gradient(135deg, #ff6b00 0%, #ea580c 100%); | |
| color: white; | |
| font-weight: 600; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn-orange::after { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient( | |
| 90deg, | |
| transparent, | |
| rgba(255, 255, 255, 0.2), | |
| transparent | |
| ); | |
| transition: 0.5s; | |
| } | |
| .btn-orange:hover::after { | |
| left: 100%; | |
| } | |
| .btn-orange:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(255, 107, 0, 0.3); | |
| } | |
| .input-dark { | |
| background: rgba(0, 0, 0, 0.4); | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| transition: all 0.3s ease; | |
| } | |
| .input-dark:focus { | |
| border-color: #ff6b00; | |
| background: rgba(0, 0, 0, 0.6); | |
| outline: none; | |
| box-shadow: 0 0 0 2px rgba(255, 107, 0, 0.1); | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 4px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #000; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #333; | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #ff6b00; | |
| } | |
| .mesh-gradient { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: radial-gradient( | |
| at 0% 0%, | |
| rgba(255, 107, 0, 0.15) 0px, | |
| transparent 50% | |
| ), | |
| radial-gradient( | |
| at 100% 100%, | |
| rgba(255, 107, 0, 0.1) 0px, | |
| transparent 50% | |
| ); | |
| z-index: -1; | |
| } | |
| </style> | |
| </head> | |
| <body class="antialiased font-sans"> | |
| <div class="mesh-gradient"></div> | |
| <div id="canvas-container"></div> | |
| <!-- Header --> | |
| <nav | |
| class="fixed top-0 w-full z-50 border-b border-white/5 bg-black/50 backdrop-blur-xl" | |
| > | |
| <div | |
| class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between" | |
| > | |
| <div class="flex items-center gap-3 group cursor-pointer"> | |
| <div | |
| class="w-8 h-8 rounded bg-orange-brand flex items-center justify-center transition-transform group-hover:rotate-12" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| class="h-5 w-5 text-black" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke="currentColor" | |
| stroke-width="3" | |
| > | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| d="M13 10V3L4 14h7v7l9-11h-7z" | |
| /> | |
| </svg> | |
| </div> | |
| <span class="font-extrabold text-xl tracking-tighter text-white" | |
| >ANIMETRIX AI</span | |
| > | |
| </div> | |
| <div class="flex items-center gap-6"> | |
| <div | |
| class="hidden md:flex items-center gap-2 px-3 py-1 rounded-full bg-orange-950/30 border border-orange-900/50" | |
| > | |
| <span | |
| class="w-2 h-2 rounded-full bg-orange-brand animate-pulse" | |
| ></span> | |
| <span | |
| class="text-[10px] font-bold text-orange-400 uppercase tracking-widest" | |
| >Engine Online</span | |
| > | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <main class="relative z-10 pt-24 pb-20 px-6 max-w-7xl mx-auto"> | |
| <div class="flex flex-col lg:flex-row gap-12"> | |
| <!-- Left: Controls --> | |
| <div class="w-full lg:w-5/12 space-y-8"> | |
| <div class="space-y-4"> | |
| <h1 | |
| class="text-5xl md:text-6xl font-black tracking-tighter leading-[0.9]" | |
| > | |
| ANIMATE <br /> | |
| <span class="orange-gradient-text">IDEAS.</span> | |
| </h1> | |
| <p class="text-zinc-400 text-lg font-medium max-w-sm leading-snug"> | |
| The next generation of educational content. Powered by AI, | |
| rendered with Manim. | |
| </p> | |
| </div> | |
| <div class="glass rounded-3xl p-8 space-y-6"> | |
| <div class="space-y-2"> | |
| <label | |
| class="text-[10px] font-black text-orange-brand uppercase tracking-[0.2em]" | |
| >Prompt Input</label | |
| > | |
| <textarea | |
| id="promptInput" | |
| rows="4" | |
| class="w-full input-dark rounded-2xl p-5 text-white placeholder-zinc-600 text-base font-medium resize-none" | |
| placeholder="Describe the concept you want to visualize..." | |
| ></textarea> | |
| </div> | |
| <div class="space-y-4"> | |
| <div class="flex flex-wrap gap-2"> | |
| <button | |
| onclick="setPrompt('Pythagorean theorem visualization')" | |
| class="px-4 py-2 rounded-xl bg-zinc-900/50 border border-white/5 text-xs font-semibold text-zinc-400 hover:text-orange-400 hover:border-orange-900/50 transition-all" | |
| > | |
| 📐 Geometry | |
| </button> | |
| <button | |
| onclick="setPrompt('Binary search algorithm step by step')" | |
| class="px-4 py-2 rounded-xl bg-zinc-900/50 border border-white/5 text-xs font-semibold text-zinc-400 hover:text-orange-400 hover:border-orange-900/50 transition-all" | |
| > | |
| 💻 Algorithms | |
| </button> | |
| <button | |
| onclick="setPrompt('Structure of an atom with orbiting electrons')" | |
| class="px-4 py-2 rounded-xl bg-zinc-900/50 border border-white/5 text-xs font-semibold text-zinc-400 hover:text-orange-400 hover:border-orange-900/50 transition-all" | |
| > | |
| ⚛️ Physics | |
| </button> | |
| </div> | |
| <button | |
| onclick="generate()" | |
| id="generateBtn" | |
| class="w-full btn-orange py-5 rounded-2xl text-sm uppercase tracking-[0.15em] flex items-center justify-center gap-3" | |
| > | |
| <span>Generate Animation</span> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| class="h-5 w-5" | |
| viewBox="0 0 20 20" | |
| fill="currentColor" | |
| > | |
| <path | |
| fill-rule="evenodd" | |
| d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" | |
| clip-rule="evenodd" | |
| /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-3 gap-4"> | |
| <div class="glass p-4 rounded-2xl text-center"> | |
| <div class="text-xl font-black text-white">4K</div> | |
| <div | |
| class="text-[8px] text-zinc-500 uppercase font-bold tracking-widest" | |
| > | |
| Ready | |
| </div> | |
| </div> | |
| <div class="glass p-4 rounded-2xl text-center"> | |
| <div class="text-xl font-black text-white">AI</div> | |
| <div | |
| class="text-[8px] text-zinc-500 uppercase font-bold tracking-widest" | |
| > | |
| Driven | |
| </div> | |
| </div> | |
| <div class="glass p-4 rounded-2xl text-center"> | |
| <div class="text-xl font-black text-white">FAST</div> | |
| <div | |
| class="text-[8px] text-zinc-500 uppercase font-bold tracking-widest" | |
| > | |
| Render | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right: Preview --> | |
| <div class="w-full lg:w-7/12"> | |
| <div | |
| class="relative aspect-video rounded-[2rem] overflow-hidden glass border-2 border-white/5 group" | |
| > | |
| <!-- Standby --> | |
| <div | |
| id="standbyScreen" | |
| class="absolute inset-0 flex flex-col items-center justify-center bg-black/40" | |
| > | |
| <div class="relative"> | |
| <div | |
| class="absolute inset-0 bg-orange-brand blur-3xl opacity-10 animate-pulse" | |
| ></div> | |
| <div | |
| class="w-20 h-20 rounded-full border border-orange-brand/20 flex items-center justify-center animate-float" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| class="h-10 w-10 text-orange-brand" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke="currentColor" | |
| > | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| stroke-width="1" | |
| d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" | |
| /> | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| stroke-width="1" | |
| d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" | |
| /> | |
| </svg> | |
| </div> | |
| </div> | |
| <p | |
| class="mt-6 text-zinc-500 font-bold text-xs uppercase tracking-[0.3em]" | |
| > | |
| System Idle | |
| </p> | |
| </div> | |
| <!-- Video --> | |
| <video | |
| id="videoPlayer" | |
| class="absolute inset-0 w-full h-full object-cover hidden" | |
| controls | |
| playsinline | |
| > | |
| <source id="videoSource" src="" type="video/mp4" /> | |
| </video> | |
| <!-- Status Overlay --> | |
| <div | |
| id="statusSection" | |
| class="hidden absolute inset-0 bg-black/90 backdrop-blur-2xl flex flex-col items-center justify-center p-12 z-20" | |
| > | |
| <div class="w-full max-w-sm space-y-8"> | |
| <div class="flex justify-center"> | |
| <div class="relative"> | |
| <div | |
| class="absolute inset-0 bg-orange-brand blur-2xl opacity-20 animate-ping" | |
| ></div> | |
| <div | |
| class="w-16 h-16 rounded-2xl bg-orange-brand flex items-center justify-center shadow-2xl shadow-orange-brand/50" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| class="h-8 w-8 text-black animate-spin" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke="currentColor" | |
| > | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| stroke-width="3" | |
| d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" | |
| /> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-center space-y-2"> | |
| <h3 | |
| class="text-2xl font-black text-white tracking-tight" | |
| id="statusTitle" | |
| > | |
| Processing | |
| </h3> | |
| <p | |
| class="text-orange-brand/60 text-xs font-bold uppercase tracking-widest" | |
| id="statusMessage" | |
| > | |
| Initializing... | |
| </p> | |
| </div> | |
| <div class="space-y-4"> | |
| <div | |
| class="h-1 w-full bg-zinc-900 rounded-full overflow-hidden" | |
| > | |
| <div | |
| id="progressFill" | |
| class="h-full bg-orange-brand w-0 transition-all duration-500 shadow-[0_0_15px_rgba(255,107,0,0.5)]" | |
| ></div> | |
| </div> | |
| <div | |
| class="flex justify-between text-[8px] font-black text-zinc-600 uppercase tracking-[0.2em]" | |
| > | |
| <span>Plan</span> | |
| <span>Code</span> | |
| <span>Render</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Error --> | |
| <div | |
| id="errorSection" | |
| class="hidden absolute inset-0 bg-red-950/95 backdrop-blur-xl flex flex-col items-center justify-center p-12 text-center z-30" | |
| > | |
| <div | |
| class="w-16 h-16 rounded-full bg-red-500/10 border border-red-500/20 flex items-center justify-center mb-6" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| class="h-8 w-8 text-red-500" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke="currentColor" | |
| > | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| stroke-width="2" | |
| d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" | |
| /> | |
| </svg> | |
| </div> | |
| <h3 class="text-xl font-black text-white mb-2">System Failure</h3> | |
| <p | |
| id="errorMessage" | |
| class="text-red-400/60 text-xs font-mono bg-black/40 p-4 rounded-xl border border-red-500/10 max-w-md overflow-auto max-h-32" | |
| > | |
| Error details... | |
| </p> | |
| <button | |
| onclick="resetUI()" | |
| class="mt-8 px-8 py-3 bg-white text-black text-xs font-black uppercase tracking-widest rounded-xl hover:bg-zinc-200 transition-colors" | |
| > | |
| Reset Engine | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Three.js Background --> | |
| <script> | |
| const scene = new THREE.Scene(); | |
| const camera = new THREE.PerspectiveCamera( | |
| 75, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 1000 | |
| ); | |
| const renderer = new THREE.WebGLRenderer({ | |
| alpha: true, | |
| antialias: true, | |
| }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| document | |
| .getElementById("canvas-container") | |
| .appendChild(renderer.domElement); | |
| // Particles | |
| const particlesGeometry = new THREE.BufferGeometry(); | |
| const count = 1500; | |
| const positions = new Float32Array(count * 3); | |
| const colors = new Float32Array(count * 3); | |
| for (let i = 0; i < count * 3; i++) { | |
| positions[i] = (Math.random() - 0.5) * 15; | |
| // Orange to Black gradient for particles | |
| const mixedColor = new THREE.Color("#FF6B00").lerp( | |
| new THREE.Color("#000000"), | |
| Math.random() * 0.8 | |
| ); | |
| colors[i] = mixedColor.r; | |
| colors[i + 1] = mixedColor.g; | |
| colors[i + 2] = mixedColor.b; | |
| } | |
| particlesGeometry.setAttribute( | |
| "position", | |
| new THREE.BufferAttribute(positions, 3) | |
| ); | |
| particlesGeometry.setAttribute( | |
| "color", | |
| new THREE.BufferAttribute(colors, 3) | |
| ); | |
| const particlesMaterial = new THREE.PointsMaterial({ | |
| size: 0.015, | |
| vertexColors: true, | |
| transparent: true, | |
| opacity: 0.4, | |
| blending: THREE.AdditiveBlending, | |
| }); | |
| const particles = new THREE.Points(particlesGeometry, particlesMaterial); | |
| scene.add(particles); | |
| camera.position.z = 5; | |
| let mouseX = 0; | |
| let mouseY = 0; | |
| document.addEventListener("mousemove", (e) => { | |
| mouseX = e.clientX / window.innerWidth - 0.5; | |
| mouseY = e.clientY / window.innerHeight - 0.5; | |
| }); | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| particles.rotation.y += 0.001; | |
| particles.rotation.x += 0.0005; | |
| camera.position.x += (mouseX * 2 - camera.position.x) * 0.05; | |
| camera.position.y += (-mouseY * 2 - camera.position.y) * 0.05; | |
| camera.lookAt(scene.position); | |
| renderer.render(scene, camera); | |
| } | |
| animate(); | |
| window.addEventListener("resize", () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| </script> | |
| <!-- Logic --> | |
| <script> | |
| let statusCheckInterval = null; | |
| function setPrompt(text) { | |
| const input = document.getElementById("promptInput"); | |
| input.value = text; | |
| input.focus(); | |
| } | |
| function resetUI() { | |
| document.getElementById("errorSection").classList.add("hidden"); | |
| document.getElementById("statusSection").classList.add("hidden"); | |
| document.getElementById("standbyScreen").classList.remove("hidden"); | |
| document.getElementById("videoPlayer").classList.add("hidden"); | |
| document.getElementById("videoPlayer").pause(); | |
| const btn = document.getElementById("generateBtn"); | |
| btn.disabled = false; | |
| btn.querySelector("span").textContent = "Generate Animation"; | |
| } | |
| async function generate() { | |
| const prompt = document.getElementById("promptInput").value.trim(); | |
| if (!prompt) return; | |
| document.getElementById("statusSection").classList.remove("hidden"); | |
| document.getElementById("errorSection").classList.add("hidden"); | |
| document.getElementById("standbyScreen").classList.add("hidden"); | |
| const btn = document.getElementById("generateBtn"); | |
| btn.disabled = true; | |
| btn.querySelector("span").textContent = "Processing..."; | |
| try { | |
| const response = await fetch("/generate", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ prompt: prompt }), | |
| }); | |
| if (!response.ok) throw new Error("Network response was not ok"); | |
| startStatusPolling(); | |
| } catch (error) { | |
| showError(error.message); | |
| } | |
| } | |
| function startStatusPolling() { | |
| if (statusCheckInterval) clearInterval(statusCheckInterval); | |
| statusCheckInterval = setInterval(checkStatus, 1000); | |
| } | |
| async function checkStatus() { | |
| try { | |
| const response = await fetch("/status"); | |
| const status = await response.json(); | |
| updateUI(status); | |
| if (status.stage === "success" || status.stage === "failed") { | |
| clearInterval(statusCheckInterval); | |
| } | |
| } catch (error) { | |
| console.error(error); | |
| } | |
| } | |
| function updateUI(status) { | |
| const titles = { | |
| idle: "System Ready", | |
| planning: "Strategizing", | |
| coding: "Synthesizing Code", | |
| executing: "Rendering Reality", | |
| success: "Generation Complete", | |
| failed: "System Error", | |
| }; | |
| document.getElementById("statusTitle").textContent = | |
| titles[status.stage] || "Processing"; | |
| document.getElementById("statusMessage").textContent = status.message; | |
| const progressMap = { | |
| idle: 0, | |
| planning: 25, | |
| coding: 50, | |
| executing: 80, | |
| success: 100, | |
| failed: 100, | |
| }; | |
| document.getElementById("progressFill").style.width = | |
| (progressMap[status.stage] || 0) + "%"; | |
| if (status.stage === "success" && status.video_path) { | |
| setTimeout(() => { | |
| document.getElementById("statusSection").classList.add("hidden"); | |
| const video = document.getElementById("videoPlayer"); | |
| video.classList.remove("hidden"); | |
| document.getElementById("videoSource").src = | |
| "/video/" + status.video_path; | |
| video.load(); | |
| video.play(); | |
| const btn = document.getElementById("generateBtn"); | |
| btn.disabled = false; | |
| btn.querySelector("span").textContent = "Generate New"; | |
| }, 800); | |
| } | |
| if (status.stage === "failed") { | |
| showError(status.error || "Unknown error occurred"); | |
| } | |
| } | |
| function showError(msg) { | |
| document.getElementById("statusSection").classList.add("hidden"); | |
| document.getElementById("errorSection").classList.remove("hidden"); | |
| document.getElementById("errorMessage").textContent = msg; | |
| const btn = document.getElementById("generateBtn"); | |
| btn.disabled = false; | |
| btn.querySelector("span").textContent = "Retry"; | |
| } | |
| </script> | |
| </body> | |
| </html> | |