Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Loki.AI - Access Premium AI Models Free</title> | |
| <!-- Favicon (Optional but recommended) --> | |
| <link rel="icon" href="favicon.ico" type="image/x-icon"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --bg-primary: #0a0a0a; | |
| --bg-card: rgba(20, 20, 20, 0.7); | |
| --bg-card-hover: rgba(30, 30, 30, 0.85); | |
| --text-primary: #e8e8e8; | |
| --text-secondary: #a0a0a0; | |
| --accent: #ffffff; | |
| --border: rgba(255, 255, 255, 0.1); | |
| --glow: rgba(255, 255, 255, 0.3); | |
| } | |
| body { | |
| font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| background: #000000; | |
| color: var(--text-primary); | |
| overflow-x: hidden; | |
| line-height: 1.6; | |
| position: relative; | |
| min-height: 100vh; | |
| } | |
| /* Starfield Canvas */ | |
| #starfield { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: 0; | |
| background: radial-gradient(ellipse at center, #0a0a0a 0%, #000000 100%); | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| /* padding: 4rem 2rem; */ | |
| position: relative; | |
| z-index: 1; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| } | |
| /* Header Section */ | |
| .header-section { | |
| text-align: center; | |
| margin-bottom: 18rem; | |
| margin-top: 18rem; | |
| opacity: 0; | |
| position: relative; | |
| } | |
| h1 { | |
| font-size: clamp(3.5rem, 10vw, 7rem); | |
| font-weight: 700; | |
| margin-bottom: 0.5rem; | |
| background: linear-gradient(135deg, #ffffff 0%, #666666 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| letter-spacing: -0.03em; | |
| position: relative; | |
| text-shadow: 0 0 60px rgba(255, 255, 255, 0.3); | |
| } | |
| .subtitle { | |
| font-size: 1.1rem; | |
| color: var(--text-secondary); | |
| letter-spacing: 0.1em; | |
| font-weight: 400; | |
| text-transform: uppercase; | |
| margin-bottom: 1rem; | |
| } | |
| .tagline { | |
| font-size: 1.3rem; | |
| color: var(--text-primary); | |
| font-weight: 300; | |
| margin-top: 1.5rem; | |
| opacity: 0.9; | |
| } | |
| /* Scroll Indicator */ | |
| .scroll-indicator { | |
| position: absolute; | |
| bottom: -8rem; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 0.5rem; | |
| opacity: 0; | |
| animation: fadeInBounce 1s ease-out 2s forwards, float 2s ease-in-out infinite 3s; | |
| } | |
| .scroll-text { | |
| font-size: 0.9rem; | |
| color: var(--text-secondary); | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| } | |
| .scroll-arrow { | |
| width: 30px; | |
| height: 30px; | |
| border-left: 2px solid var(--text-secondary); | |
| border-bottom: 2px solid var(--text-secondary); | |
| transform: rotate(-45deg); | |
| } | |
| @keyframes fadeInBounce { | |
| 0% { | |
| opacity: 0; | |
| transform: translateX(-50%) translateY(-20px); | |
| } | |
| 100% { | |
| opacity: 0.8; | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| } | |
| @keyframes float { | |
| 0%, 100% { | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| 50% { | |
| transform: translateX(-50%) translateY(10px); | |
| } | |
| } | |
| /* Game Button */ | |
| .game-btn { | |
| position: absolute; | |
| right: 2rem; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| z-index: 1000; | |
| width: 70px; | |
| height: 70px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 2rem; | |
| cursor: pointer; | |
| /* dark mode sexy look */ | |
| background: radial-gradient(circle at top left, rgba(30, 30, 30, 0.9), rgba(10, 10, 10, 0.8)); | |
| border: 2px solid rgba(255, 255, 255, 0.08); | |
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.6), | |
| inset 0 0 10px rgba(255, 255, 255, 0.05); | |
| color: #eee; | |
| transition: all 0.4s ease; | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| } | |
| .game-btn:hover { | |
| transform: translateY(-50%) scale(1.1); | |
| background: radial-gradient(circle at bottom right, rgba(50, 50, 50, 0.9), rgba(15, 15, 15, 0.8)); | |
| box-shadow: 0 10px 40px rgba(0, 255, 255, 0.3), | |
| inset 0 0 15px rgba(0, 255, 255, 0.15); | |
| border-color: rgba(0, 255, 255, 0.5); | |
| color: cyan; | |
| } | |
| .game-btn:active { | |
| transform: translateY(-50%) scale(0.9); | |
| box-shadow: 0 5px 20px rgba(0, 255, 255, 0.2); | |
| } | |
| /* Game Modal */ | |
| .game-modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.95); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| z-index: 2000; | |
| justify-content: center; | |
| align-items: center; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .game-modal.active { | |
| display: flex; | |
| opacity: 1; | |
| } | |
| .game-container { | |
| position: relative; | |
| background: var(--bg-card); | |
| border: 2px solid var(--border); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| box-shadow: 0 30px 100px rgba(255, 255, 255, 0.2); | |
| } | |
| .game-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| } | |
| .game-title { | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| } | |
| .game-close { | |
| background: rgba(255, 255, 255, 0.1); | |
| border: 1px solid var(--border); | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| font-size: 1.5rem; | |
| color: var(--text-primary); | |
| } | |
| .game-close:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| transform: rotate(90deg); | |
| } | |
| #gameCanvas { | |
| border-radius: 12px; | |
| background: #1a1a1a; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); | |
| width: 1000px; | |
| height: 400px; | |
| max-width: 100%; | |
| display: block; | |
| margin: 0 auto; | |
| } | |
| .game-controls { | |
| margin-top: 1rem; | |
| text-align: center; | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| } | |
| .game-stats { | |
| display: flex; | |
| justify-content: space-around; | |
| margin-top: 1rem; | |
| padding: 1rem; | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 10px; | |
| } | |
| .stat { | |
| text-align: center; | |
| } | |
| .stat-label { | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| } | |
| .stat-value { | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| color: var(--accent); | |
| margin-top: 0.3rem; | |
| } | |
| /* Models Section */ | |
| .models-section { | |
| margin-bottom: 5rem; | |
| opacity: 0; | |
| } | |
| .section-title { | |
| font-size: clamp(1.8rem, 4vw, 2.8rem); | |
| text-align: center; | |
| margin-bottom: 3rem; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| letter-spacing: -0.02em; | |
| } | |
| .models-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); | |
| gap: 2rem; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| .model-card { | |
| background: var(--bg-card); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| position: relative; | |
| overflow: hidden; | |
| cursor: pointer; | |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| opacity: 0; | |
| transform: translateY(50px) scale(0.95); | |
| } | |
| .model-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(135deg, | |
| rgba(255, 255, 255, 0.05) 0%, | |
| transparent 100%); | |
| opacity: 0; | |
| transition: opacity 0.4s; | |
| } | |
| .model-card::after { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 0; | |
| height: 0; | |
| background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); | |
| transform: translate(-50%, -50%); | |
| transition: width 0.6s, height 0.6s; | |
| border-radius: 50%; | |
| } | |
| .model-card:hover { | |
| background: var(--bg-card-hover); | |
| border-color: var(--accent); | |
| transform: translateY(-10px) scale(1.02); | |
| box-shadow: 0 20px 60px rgba(255, 255, 255, 0.15), | |
| 0 0 40px rgba(255, 255, 255, 0.1); | |
| } | |
| .model-card:hover::before { | |
| opacity: 1; | |
| } | |
| .model-card:hover::after { | |
| width: 400px; | |
| height: 400px; | |
| } | |
| .model-icon { | |
| width: 50px; | |
| height: 50px; | |
| background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.05)); | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .model-name { | |
| font-size: 1.4rem; | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| position: relative; | |
| z-index: 1; | |
| color: var(--text-primary); | |
| } | |
| .model-description { | |
| font-size: 0.95rem; | |
| color: var(--text-secondary); | |
| line-height: 1.6; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .model-special { | |
| grid-column: 1 / -1; | |
| background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05)); | |
| text-align: center; | |
| padding: 2.5rem; | |
| } | |
| .model-special .model-name { | |
| font-size: 1.6rem; | |
| margin-bottom: 1rem; | |
| } | |
| /* Playgrounds Section */ | |
| .playgrounds-section { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 2rem; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| opacity: 0; | |
| } | |
| .playground-card { | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 3rem 2rem; | |
| background: var(--bg-card); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 2px solid var(--border); | |
| border-radius: 24px; | |
| text-decoration: none; | |
| color: var(--text-primary); | |
| font-size: 1.3rem; | |
| font-weight: 600; | |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| overflow: hidden; | |
| min-height: 200px; | |
| transform: translateY(50px); | |
| } | |
| .playground-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: -50%; | |
| left: -50%; | |
| width: 200%; | |
| height: 200%; | |
| background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); | |
| opacity: 0; | |
| transition: opacity 0.6s; | |
| } | |
| .playground-card:hover { | |
| transform: translateY(-15px) scale(1.03); | |
| box-shadow: 0 30px 80px rgba(255, 255, 255, 0.2), | |
| 0 0 50px rgba(255, 255, 255, 0.15); | |
| } | |
| .playground-card:hover::before { | |
| opacity: 1; | |
| } | |
| .playground-icon { | |
| font-size: 3rem; | |
| margin-bottom: 1rem; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .playground-title { | |
| position: relative; | |
| z-index: 1; | |
| letter-spacing: 0.02em; | |
| } | |
| .playground-subtitle { | |
| font-size: 0.9rem; | |
| color: var(--text-secondary); | |
| margin-top: 0.5rem; | |
| font-weight: 400; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 2rem 1.5rem; | |
| } | |
| h1 { | |
| font-size: 3rem; | |
| } | |
| .models-grid { | |
| grid-template-columns: 1fr; | |
| gap: 1.5rem; | |
| } | |
| .model-special { | |
| grid-column: 1; | |
| } | |
| .playgrounds-section { | |
| grid-template-columns: 1fr; | |
| } | |
| .game-btn { | |
| width: 60px; | |
| height: 60px; | |
| right: 1rem; | |
| font-size: 1.5rem; | |
| } | |
| #gameCanvas { | |
| max-width: 100%; | |
| height: auto; | |
| } | |
| } | |
| /* Smooth scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg-primary); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: rgba(255, 255, 255, 0.3); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="starfield"></canvas> | |
| <!-- Game Button --> | |
| <div class="game-btn" id="gameBtn" title="Play Drift Racer"> | |
| 🏎️ | |
| </div> | |
| <!-- Game Modal --> | |
| <div class="game-modal" id="gameModal"> | |
| <div class="game-container"> | |
| <div class="game-header"> | |
| <div class="game-title">🏎️ BROAD CAR GOOOO VROOOMMMMMM :p</div> | |
| <div class="game-close" id="closeGame">✕</div> | |
| </div> | |
| <canvas id="gameCanvas" width="800" height="600"></canvas> | |
| <div class="game-controls"> | |
| Use <strong>WASD</strong> to control • <strong>W</strong>: Accelerate • <strong>S</strong>: Brake • <strong>A/D</strong>: Turn | |
| </div> | |
| <div class="game-stats"> | |
| <div class="stat"> | |
| <div class="stat-label">Speed</div> | |
| <div class="stat-value" id="speedStat">0</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-label">Drift Score</div> | |
| <div class="stat-value" id="driftScore">0</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="container"> | |
| <div class="header-section"> | |
| <h1>Loki.AI</h1> | |
| <p class="subtitle">By Parth Sadaria</p> | |
| <!-- <p class="tagline">Best Models For Free, No API Key Needed :)</p> --> | |
| <!-- Scroll Indicator --> | |
| <div class="scroll-indicator"> | |
| <span class="scroll-text">Scroll Down</span> | |
| <div class="scroll-arrow"></div> | |
| </div> | |
| </div> | |
| <div class="models-section"> | |
| <h2 class="section-title">Premium AI Models</h2> | |
| <div class="models-grid"> | |
| <div class="model-card"> | |
| <div class="model-icon">🤖</div> | |
| <div class="model-name">GPT-4o</div> | |
| <div class="model-description">OpenAI's most advanced multimodal model with vision and reasoning capabilities</div> | |
| </div> | |
| <div class="model-card"> | |
| <div class="model-icon">⚡</div> | |
| <div class="model-name">GPT-4o-mini</div> | |
| <div class="model-description">Lightning-fast responses with impressive accuracy for everyday tasks</div> | |
| </div> | |
| <div class="model-card"> | |
| <div class="model-icon">🚀</div> | |
| <div class="model-name">GPT-4.1</div> | |
| <div class="model-description">Enhanced GPT-4 with improved context and accuracy</div> | |
| </div> | |
| <div class="model-card"> | |
| <div class="model-icon">🎯</div> | |
| <div class="model-name">Claude 4.5 Haiku</div> | |
| <div class="model-description">Anthropic's swift and elegant AI with exceptional precision</div> | |
| </div> | |
| <div class="model-card"> | |
| <div class="model-icon">✨</div> | |
| <div class="model-name">Gemini 2.5 Pro</div> | |
| <div class="model-description">Google's powerful multimodal AI for complex tasks</div> | |
| </div> | |
| <div class="model-card"> | |
| <div class="model-icon">⚡</div> | |
| <div class="model-name">Gemini 2.5 Flash</div> | |
| <div class="model-description">Ultra-fast inference with impressive capabilities</div> | |
| </div> | |
| <div class="model-card model-special"> | |
| <div class="model-name">...And Many More Top Models!</div> | |
| <div class="model-description">Explore the complete collection of premium AI models, all accessible without API keys</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="playgrounds-section"> | |
| <a href="https://huggingface.co/proxy/parthsadaria-lokiai.hf.space/playground" class="playground-card" target="_blank" rel="noopener noreferrer"> | |
| <div class="playground-icon">🎮</div> | |
| <div class="playground-title">AI Playground</div> | |
| <div class="playground-subtitle">Chat with premium models</div> | |
| </a> | |
| <a href="https://huggingface.co/proxy/parthsadaria-lokiai.hf.space/image-playground" class="playground-card" target="_blank" rel="noopener noreferrer"> | |
| <div class="playground-icon">🎨</div> | |
| <div class="playground-title">Image Playground</div> | |
| <div class="playground-subtitle">Generate stunning visuals</div> | |
| </a> | |
| </div> | |
| </div> | |
| <script> | |
| // Starfield Animation | |
| const canvas = document.getElementById('starfield'); | |
| const ctx = canvas.getContext('2d'); | |
| let canvasWidth = canvas.width = window.innerWidth; | |
| let canvasHeight = canvas.height = window.innerHeight; | |
| let centerX = canvasWidth * 0.5; | |
| let centerY = canvasHeight * 0.5; | |
| const numberOfStars = 40; | |
| const stars = []; | |
| let mouse = { x: centerX, y: centerY, active: false }; | |
| // Mouse event listeners | |
| canvas.addEventListener('mousemove', (e) => { | |
| mouse.x = e.clientX; | |
| mouse.y = e.clientY; | |
| mouse.active = true; | |
| }); | |
| canvas.addEventListener('mouseleave', () => { | |
| mouse.active = false; | |
| }); | |
| class Star { | |
| constructor() { | |
| this.reset(); | |
| this.vx = 0; | |
| this.vy = 0; | |
| this.bounce = false; | |
| } | |
| reset() { | |
| this.x = Math.random() * canvasWidth - centerX; | |
| this.y = Math.random() * canvasHeight - centerY; | |
| this.z = Math.random() * canvasWidth; | |
| this.speed = Math.random() * 3 + 2; | |
| this.prevX = this.x; | |
| this.prevY = this.y; | |
| this.vx = 0; | |
| this.vy = 0; | |
| this.bounce = false; | |
| } | |
| update() { | |
| const projectedX = this.x / this.z * 200 + centerX; | |
| const projectedY = this.y / this.z * 200 + centerY; | |
| if (mouse.active && !this.bounce) { | |
| const dx = mouse.x - projectedX; | |
| const dy = mouse.y - projectedY; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < 1000) { | |
| this.vx = -dx * 0.25 + (Math.random() - 0.5) * 12; | |
| this.vy = -dy * 0.25 + (Math.random() - 0.5) * 12; | |
| this.bounce = true; | |
| } | |
| } | |
| if (this.bounce) { | |
| this.x += this.vx; | |
| this.y += this.vy; | |
| this.vx *= 0.7; | |
| this.vy *= 0.7; | |
| if (Math.abs(this.vx) < 0.5 && Math.abs(this.vy) < 0.5) { | |
| this.bounce = false; | |
| } | |
| } | |
| this.prevX = projectedX; | |
| this.prevY = projectedY; | |
| this.z -= this.speed; | |
| if (this.z <= 0) { | |
| this.reset(); | |
| } | |
| this.currentX = this.x / this.z * 200 + centerX; | |
| this.currentY = this.y / this.z * 200 + centerY; | |
| } | |
| draw() { | |
| const radius = Math.max(0, (1 - this.z / canvasWidth) * 2.5); | |
| const opacity = Math.max(0, 1 - this.z / canvasWidth); | |
| ctx.beginPath(); | |
| ctx.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.5})`; | |
| ctx.lineWidth = radius; | |
| ctx.moveTo(this.prevX, this.prevY); | |
| ctx.lineTo(this.currentX, this.currentY); | |
| ctx.stroke(); | |
| ctx.beginPath(); | |
| ctx.arc(this.currentX, this.currentY, radius, 0, Math.PI * 2); | |
| const gradient = ctx.createRadialGradient( | |
| this.currentX, this.currentY, 0, | |
| this.currentX, this.currentY, radius * 2 | |
| ); | |
| gradient.addColorStop(0, `rgba(255, 255, 255, ${opacity})`); | |
| gradient.addColorStop(0.5, `rgba(255, 255, 255, ${opacity * 0.5})`); | |
| gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); | |
| ctx.fillStyle = gradient; | |
| ctx.fill(); | |
| if (this.bounce) { | |
| ctx.save(); | |
| ctx.translate(this.currentX, this.currentY); | |
| ctx.rotate(Math.sin(Date.now()/200) * 0.2); | |
| ctx.fillStyle = 'yellow'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, radius * 1.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = 'black'; | |
| ctx.beginPath(); | |
| ctx.arc(-radius * 0.5, -radius * 0.3, radius * 0.3, 0, Math.PI * 2); | |
| ctx.arc(radius * 0.5, -radius * 0.3, radius * 0.3, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.strokeStyle = 'black'; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.arc(0, radius * 0.3, radius * 0.6, 0, Math.PI); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| } | |
| } | |
| } | |
| for (let i = 0; i < numberOfStars; i++) { | |
| stars.push(new Star()); | |
| } | |
| function animate() { | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; | |
| ctx.fillRect(0, 0, canvasWidth, canvasHeight); | |
| ctx.save(); | |
| for (let star of stars) { | |
| star.update(); | |
| star.draw(); | |
| } | |
| ctx.restore(); | |
| requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| window.addEventListener('resize', () => { | |
| canvasWidth = canvas.width = window.innerWidth; | |
| canvasHeight = canvas.height = window.innerHeight; | |
| centerX = canvasWidth * 0.5; | |
| centerY = canvasHeight * 0.5; | |
| }); | |
| // Anime.js Animations | |
| anime.timeline({ | |
| easing: 'easeOutExpo' | |
| }) | |
| .add({ | |
| targets: '.header-section', | |
| opacity: [0, 1], | |
| translateY: [80, 0], | |
| duration: 1400, | |
| delay: 400 | |
| }); | |
| anime({ | |
| targets: '.models-section', | |
| opacity: [0, 1], | |
| translateY: [60, 0], | |
| duration: 1200, | |
| delay: 800, | |
| easing: 'easeOutExpo' | |
| }); | |
| const cards = document.querySelectorAll('.model-card'); | |
| anime({ | |
| targets: cards, | |
| opacity: [0, 1], | |
| translateY: [50, 0], | |
| scale: [0.95, 1], | |
| duration: 1000, | |
| delay: anime.stagger(100, { | |
| start: 1200, | |
| grid: [3, 3], | |
| from: 'center' | |
| }), | |
| easing: 'spring(1, 80, 10, 0)' | |
| }); | |
| anime({ | |
| targets: '.model-card', | |
| translateY: function() { | |
| return anime.random(-10, 10); | |
| }, | |
| duration: function() { | |
| return anime.random(2500, 4000); | |
| }, | |
| easing: 'easeInOutSine', | |
| direction: 'alternate', | |
| loop: true, | |
| delay: anime.stagger(150) | |
| }); | |
| anime({ | |
| targets: '.playgrounds-section', | |
| opacity: [0, 1], | |
| duration: 1000, | |
| delay: 1600, | |
| easing: 'easeOutExpo' | |
| }); | |
| anime({ | |
| targets: '.playground-card', | |
| opacity: [0, 1], | |
| translateY: [50, 0], | |
| scale: [0.9, 1], | |
| duration: 1200, | |
| delay: anime.stagger(200, { start: 1800 }), | |
| easing: 'spring(1, 70, 10, 0)' | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| const { clientX, clientY } = e; | |
| const centerX = window.innerWidth / 2; | |
| const centerY = window.innerHeight / 2; | |
| const moveX = (clientX - centerX) / 80; | |
| const moveY = (clientY - centerY) / 80; | |
| anime({ | |
| targets: '.model-card, .playground-card', | |
| translateX: moveX, | |
| translateY: moveY, | |
| duration: 1200, | |
| easing: 'easeOutQuad' | |
| }); | |
| }); | |
| cards.forEach(card => { | |
| card.addEventListener('mouseenter', function() { | |
| anime({ | |
| targets: this.querySelector('.model-icon'), | |
| scale: [1, 1.2, 1], | |
| rotate: [0, 10, -10, 0], | |
| duration: 600, | |
| easing: 'easeInOutQuad' | |
| }); | |
| }); | |
| }); | |
| document.querySelectorAll('.playground-card').forEach(card => { | |
| card.addEventListener('mouseenter', function() { | |
| anime({ | |
| targets: this.querySelector('.playground-icon'), | |
| scale: [1, 1.3, 1], | |
| duration: 500, | |
| easing: 'easeOutElastic(1, .6)' | |
| }); | |
| }); | |
| }); | |
| // ======================================== | |
| // 2D CAR DRIFTING GAME | |
| // ======================================== | |
| const gameModal = document.getElementById('gameModal'); | |
| const gameBtn = document.getElementById('gameBtn'); | |
| const closeGame = document.getElementById('closeGame'); | |
| const gameCanvas = document.getElementById('gameCanvas'); | |
| const gameCtx = gameCanvas.getContext('2d'); | |
| let gameRunning = false; | |
| let driftScore = 0; | |
| // Game state | |
| const car = { | |
| x: 500, | |
| y: 200, | |
| width: 40, | |
| height: 60, | |
| angle: 0, | |
| speed: 0, | |
| maxSpeed: 10, | |
| acceleration: 0.1, | |
| friction: 0.1, | |
| turnSpeed: 0.09, | |
| driftFactor: 0.82, // higher value = less drift | |
| vx: 0, | |
| vy: 0 | |
| }; | |
| const keys = { | |
| w: false, | |
| a: false, | |
| s: false, | |
| d: false, | |
| space: false | |
| }; | |
| const driftMarks = []; | |
| // Open game modal | |
| gameBtn.addEventListener('click', () => { | |
| gameModal.classList.add('active'); | |
| if (!gameRunning) { | |
| gameRunning = true; | |
| playVroom(); | |
| gameLoop(); | |
| } | |
| }); | |
| // Close game modal | |
| closeGame.addEventListener('click', () => { | |
| gameModal.classList.remove('active'); | |
| stopVroom(); | |
| gameRunning = false; | |
| }); | |
| // Keyboard controls | |
| document.addEventListener('keydown', (e) => { | |
| const key = e.key.toLowerCase(); | |
| if (keys.hasOwnProperty(key)) { | |
| keys[key] = true; | |
| e.preventDefault(); | |
| } | |
| if (e.code === "Space") { | |
| keys.space = true; | |
| e.preventDefault(); | |
| } | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| const key = e.key.toLowerCase(); | |
| if (keys.hasOwnProperty(key)) { | |
| keys[key] = false; | |
| e.preventDefault(); | |
| } | |
| if (e.code === "Space") { | |
| keys.space = false; | |
| e.preventDefault(); | |
| } | |
| }); | |
| // Update car physics | |
| function updateCar() { | |
| // Acceleration | |
| if (keys.w) { | |
| car.speed = Math.min(car.speed + car.acceleration, car.maxSpeed); | |
| } | |
| // Braking | |
| if (keys.s) { | |
| car.speed = Math.max(car.speed - car.acceleration * 1.5, -car.maxSpeed * 0.5); | |
| } | |
| // Friction | |
| if (!keys.w && !keys.s) { | |
| car.speed *= (1 - car.friction); | |
| if (Math.abs(car.speed) < 0.05) car.speed = 0; | |
| } | |
| // Handbrake (spacebar) | |
| let drifting = false; | |
| let driftAmount = keys.space ? 0.5 : 1 - car.driftFactor; | |
| if (keys.space) { | |
| car.speed *= 0.97; // slow down forward speed | |
| drifting = true; | |
| } | |
| // Turning and drifting | |
| if ((keys.a || keys.d) && Math.abs(car.speed) > 0.5) { | |
| let turn = car.turnSpeed * (car.speed / car.maxSpeed); | |
| if (keys.a) car.angle -= turn; | |
| if (keys.d) car.angle += turn; | |
| // Add lateral velocity for drift | |
| let driftAngle = car.angle + (keys.a ? -Math.PI/2 : Math.PI/2); | |
| let driftPower = Math.abs(car.speed) * driftAmount * (keys.space ? 2.5 : 1.5); | |
| car.vx += Math.cos(driftAngle) * driftPower * 0.07; | |
| car.vy += Math.sin(driftAngle) * driftPower * 0.07; | |
| drifting = true; | |
| } | |
| // Forward velocity | |
| car.vx += Math.sin(car.angle) * car.speed * 0.15; | |
| car.vy -= Math.cos(car.angle) * car.speed * 0.15; | |
| // Apply drift decay | |
| car.vx *= keys.space ? 0.98 : car.driftFactor; | |
| car.vy *= keys.space ? 0.98 : car.driftFactor; | |
| // Update position | |
| car.x += car.vx; | |
| car.y += car.vy; | |
| // Wrap around screen | |
| if (car.x < -50) car.x = gameCanvas.width + 50; | |
| if (car.x > gameCanvas.width + 50) car.x = -50; | |
| if (car.y < -50) car.y = gameCanvas.height + 50; | |
| if (car.y > gameCanvas.height + 50) car.y = -50; | |
| // Drift marks | |
| if (drifting && Math.random() > 0.3) { | |
| driftMarks.push({ | |
| x: car.x, | |
| y: car.y, | |
| alpha: 1 | |
| }); | |
| driftScore += Math.floor(Math.abs(car.vx + car.vy) * 2); | |
| } | |
| // Fade drift marks | |
| driftMarks.forEach((mark, index) => { | |
| mark.alpha -= 0.01; | |
| if (mark.alpha <= 0) { | |
| driftMarks.splice(index, 1); | |
| } | |
| }); | |
| } | |
| // Draw car | |
| function drawCar() { | |
| gameCtx.save(); | |
| gameCtx.translate(car.x, car.y); | |
| gameCtx.rotate(car.angle); | |
| // Car body | |
| gameCtx.fillStyle = '#ffffff'; | |
| gameCtx.fillRect(-car.width / 2, -car.height / 2, car.width, car.height); | |
| // Car details | |
| gameCtx.fillStyle = '#222222'; | |
| gameCtx.fillRect(-car.width / 2 + 5, -car.height / 2 + 10, car.width - 10, 15); | |
| gameCtx.fillRect(-car.width / 2 + 5, car.height / 2 - 25, car.width - 10, 15); | |
| // Headlights | |
| gameCtx.fillStyle = '#ffff00'; | |
| gameCtx.fillRect(-car.width / 2 + 5, -car.height / 2, 8, 5); | |
| gameCtx.fillRect(car.width / 2 - 13, -car.height / 2, 8, 5); | |
| gameCtx.restore(); | |
| } | |
| // Draw drift marks | |
| function drawDriftMarks() { | |
| driftMarks.forEach(mark => { | |
| gameCtx.fillStyle = `rgba(50, 50, 50, ${mark.alpha})`; | |
| gameCtx.beginPath(); | |
| gameCtx.arc(mark.x, mark.y, 3, 0, Math.PI * 2); | |
| gameCtx.fill(); | |
| }); | |
| } | |
| // Draw background grid | |
| function drawBackground() { | |
| gameCtx.fillStyle = '#1a1a1a'; | |
| gameCtx.fillRect(0, 0, gameCanvas.width, gameCanvas.height); | |
| gameCtx.strokeStyle = 'rgba(255, 255, 255, 0.05)'; | |
| gameCtx.lineWidth = 1; | |
| for (let x = 0; x < gameCanvas.width; x += 50) { | |
| gameCtx.beginPath(); | |
| gameCtx.moveTo(x, 0); | |
| gameCtx.lineTo(x, gameCanvas.height); | |
| gameCtx.stroke(); | |
| } | |
| for (let y = 0; y < gameCanvas.height; y += 50) { | |
| gameCtx.beginPath(); | |
| gameCtx.moveTo(0, y); | |
| gameCtx.lineTo(gameCanvas.width, y); | |
| gameCtx.stroke(); | |
| } | |
| } | |
| // Update stats display | |
| function updateStats() { | |
| document.getElementById('speedStat').textContent = Math.abs(Math.floor(car.speed * 10)); | |
| document.getElementById('driftScore').textContent = driftScore; | |
| } | |
| // Vroom Audio Setup | |
| let audioCtx, audioSource, gainNode, vroomBuffer; | |
| let vroomPlaying = false; | |
| // Load engine sound and trim last 0.1s | |
| function loadVroomAudio() { | |
| return fetch("rev.wav") | |
| .then(res => res.arrayBuffer()) | |
| .then(buf => { | |
| audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
| return audioCtx.decodeAudioData(buf); | |
| }) | |
| .then(decoded => { | |
| const trimDuration = 0.25; // seconds to cut off | |
| const newLength = Math.max(0, decoded.length - decoded.sampleRate * trimDuration); | |
| // Create a new trimmed buffer | |
| const trimmed = audioCtx.createBuffer( | |
| decoded.numberOfChannels, | |
| newLength, | |
| decoded.sampleRate | |
| ); | |
| for (let i = 0; i < decoded.numberOfChannels; i++) { | |
| const oldData = decoded.getChannelData(i); | |
| const newData = trimmed.getChannelData(i); | |
| newData.set(oldData.subarray(0, newLength)); | |
| } | |
| vroomBuffer = trimmed; | |
| }); | |
| } | |
| // Play engine sound loop | |
| function playVroom() { | |
| if (!vroomBuffer || vroomPlaying) return; | |
| audioSource = audioCtx.createBufferSource(); | |
| gainNode = audioCtx.createGain(); | |
| audioSource.buffer = vroomBuffer; | |
| audioSource.loop = true; | |
| audioSource.connect(gainNode).connect(audioCtx.destination); | |
| audioSource.start(0); | |
| vroomPlaying = true; | |
| } | |
| // Stop engine sound | |
| function stopVroom() { | |
| if (audioSource) { | |
| audioSource.stop(); | |
| audioSource.disconnect(); | |
| gainNode.disconnect(); | |
| vroomPlaying = false; | |
| } | |
| } | |
| // Update engine sound pitch/volume | |
| function updateVroom() { | |
| if (!audioSource || !gainNode) return; | |
| // Map car speed to playback rate (pitch) and volume | |
| let speedNorm = Math.abs(car.speed) / car.maxSpeed; // 0 to 1 | |
| audioSource.playbackRate.value = 0.7 + speedNorm * 1.2; // 0.7 to ~1.9 | |
| gainNode.gain.value = 0.15 + speedNorm * 0.35; // 0.15 to 0.5 | |
| } | |
| // Load audio on page load | |
| loadVroomAudio(); | |
| // Game loop | |
| function gameLoop() { | |
| if (!gameRunning) return; | |
| drawBackground(); | |
| drawDriftMarks(); | |
| updateCar(); | |
| drawCar(); | |
| updateStats(); | |
| updateVroom(); // <-- Add this line | |
| requestAnimationFrame(gameLoop); | |
| } | |
| </script> | |
| </body> | |
| </html> | |