lokiai / index.html
ParthSadaria's picture
Update index.html
3727c60 verified
raw
history blame
38.7 kB
<!DOCTYPE html>
<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>