JIT
Grid - Untitled
Self-playing side-view pinball in a single HTML file. Pure CSS/JS, no assets. Features chrome ball physics, shadows, screen shake, and flashing bumpers. Auto-flippers play through 3 balls, updating the score until a 'GAME OVER' fade.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Auto Pinball</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }

body { width: 100vw; height: 100vh; overflow: hidden; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); font-family: 'Courier New', monospace; display: flex; justify-content: center; align-items: center; }

#game-container { position: relative; width: 90vmin; height: 95vh; max-width: 800px; max-height: 900px; background: linear-gradient(180deg, #0f3460 0%, #16213e 100%); border: 4px solid #e94560; border-radius: 20px; box-shadow: 0 0 40px rgba(233, 69, 96, 0.5), inset 0 0 60px rgba(0,0,0,0.5); overflow: hidden; }

#playfield { position: absolute; width: 100%; height: 100%; transition: transform 0.05s ease-out; }

#score-display { position: absolute; top: 3%; left: 50%; transform: translateX(-50%); color: #00d9ff; font-size: 3.5vmin; font-weight: bold; text-shadow: 0 0 10px #00d9ff, 0 0 20px #00d9ff; z-index: 100; letter-spacing: 0.2vmin; }

#ball-count { position: absolute; top: 10%; left: 50%; transform: translateX(-50%); color: #fff; font-size: 2vmin; z-index: 100; text-shadow: 0 0 5px #fff; }

.ball { position: absolute; width: 3vmin; height: 3vmin; border-radius: 50%; background: radial-gradient(circle at 30% 30%, #ffffff, #c0c0c0 40%, #808080 70%, #404040); box-shadow: 0 0.5vmin 1vmin rgba(0,0,0,0.6), inset -0.3vmin -0.3vmin 0.5vmin rgba(0,0,0,0.4), inset 0.3vmin 0.3vmin 0.5vmin rgba(255,255,255,0.6); z-index: 50; }

.ball-shadow { position: absolute; width: 3vmin; height: 1.5vmin; border-radius: 50%; background: radial-gradient(ellipse at center, rgba(0,0,0,0.6), transparent); filter: blur(0.3vmin); z-index: 1; }

.bumper { position: absolute; border-radius: 50%; background: radial-gradient(circle at 30% 30%, #ff6b9d, #c94b7a 60%, #8b2e5a); box-shadow: 0 0.5vmin 1vmin rgba(0,0,0,0.5), inset 0 0 1vmin rgba(255,255,255,0.3); transition: all 0.1s ease; }

.bumper.hit { background: radial-gradient(circle at 30% 30%, #ffff00, #ffaa00 60%, #ff6600); box-shadow: 0 0 2vmin #ffff00, 0 0 4vmin #ffaa00; transform: scale(1.2); }

.flipper { position: absolute; background: linear-gradient(135deg, #00d9ff, #0095b6); box-shadow: 0 0.3vmin 0.8vmin rgba(0,0,0,0.6), inset 0 0 0.5vmin rgba(255,255,255,0.4); transform-origin: left center; transition: transform 0.08s ease-out; border-radius: 1vmin; }

.flipper.left { left: 15%; bottom: 5%; width: 15vmin; height: 2vmin; }

.flipper.right { right: 15%; bottom: 5%; width: 15vmin; height: 2vmin; transform-origin: right center; }

.flipper.active { transform: rotate(-45deg); }

.flipper.right.active { transform: rotate(45deg); }

.wall { position: absolute; background: linear-gradient(90deg, #e94560, #c13854); box-shadow: inset 0 0 1vmin rgba(0,0,0,0.5); }

.wall.left { left: 0; top: 0; width: 2vmin; height: 100%; }

.wall.right { right: 0; top: 0; width: 2vmin; height: 100%; }

.wall.top { left: 0; top: 0; width: 100%; height: 2vmin; }

.plunger-lane { position: absolute; right: 2vmin; top: 20%; width: 8vmin; height: 40%; background: rgba(0,0,0,0.3); border-left: 0.3vmin solid #e94560; border-radius: 1vmin; }

#game-over { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 8vmin; color: #e94560; text-shadow: 0 0 2vmin #e94560, 0 0 4vmin #e94560; opacity: 0; pointer-events: none; transition: opacity 2s ease; z-index: 200; font-weight: bold; letter-spacing: 1vmin; }

#game-over.show { opacity: 1; }

.slingshot { position: absolute; background: linear-gradient(45deg, #ff6b9d, #e94560); box-shadow: 0 0.3vmin 0.8vmin rgba(0,0,0,0.5); }

.slingshot.left { left: 18%; bottom: 15%; width: 2vmin; height: 10vmin; transform: rotate(45deg); }

.slingshot.right { right: 18%; bottom: 15%; width: 2vmin; height: 10vmin; transform: rotate(-45deg); }

.slingshot.hit { background: linear-gradient(45deg, #ffff00, #ffaa00); box-shadow: 0 0 1vmin #ffff00; } </style>

</head> <div id="game-container"> <div id="playfield"> <div id="score-display">SCORE: 0</div> <div id="ball-count">BALL: 1/3</div>
<div class="wall left"></div>
<div class="wall right"></div>
<div class="wall top"></div>

<div class="plunger-lane"></div>

<div class="flipper left" id="flipper-left"></div>
<div class="flipper right" id="flipper-right"></div>

<div class="slingshot left" id="sling-left"></div>
<div class="slingshot right" id="sling-right"></div>

<div id="game-over">GAME OVER</div>
</div> </div>

const container = document.getElementById('game-container'); const playfield = document.getElementById('playfield'); const scoreDisplay = document.getElementById('score-display'); const ballCountDisplay = document.getElementById('ball-count'); const gameOverDisplay = document.getElementById('game-over'); const flipperLeft = document.getElementById('flipper-left'); const flipperRight = document.getElementById('flipper-right'); const slingLeft = document.getElementById('sling-left'); const slingRight = document.getElementById('sling-right');

const bounds = { width: container.offsetWidth, height: container.offsetHeight };

let score = 0; let currentBall = 1; let gameActive = true;

// Physics constants const GRAVITY = 0.4; const BOUNCE = 0.7; const FRICTION = 0.99;

// Ball state let ball = { x: bounds.width * 0.85, y: bounds.height * 0.3, vx: 0, vy: 0, radius: bounds.width * 0.015, element: null, shadow: null };

// Bumpers const bumpers = [ { x: bounds.width * 0.35, y: bounds.height * 0.25, radius: bounds.width * 0.04, element: null, points: 100 }, { x: bounds.width * 0.55, y: bounds.height * 0.25, radius: bounds.width * 0.04, element: null, points: 100 }, { x: bounds.width * 0.45, y: bounds.height * 0.35, radius: bounds.width * 0.04, element: null, points: 100 }, { x: bounds.width * 0.30, y: bounds.height * 0.45, radius: bounds.width * 0.035, element: null, points: 50 }, { x: bounds.width * 0.60, y: bounds.height * 0.45, radius: bounds.width * 0.035, element: null, points: 50 }, ];

// Flippers const flippers = [ { x: bounds.width * 0.22, y: bounds.height * 0.9, width: bounds.width * 0.08, height: bounds.height * 0.015, angle: 0, active: false, isLeft: true }, { x: bounds.width * 0.7, y: bounds.height * 0.9, width: bounds.width * 0.08, height: bounds.height * 0.015, angle: 0, active: false, isLeft: false } ];

// Create ball element function createBall() { ball.element = document.createElement('div'); ball.element.className = 'ball'; playfield.appendChild(ball.element);

ball.shadow = document.createElement('div'); ball.shadow.className = 'ball-shadow'; playfield.appendChild(ball.shadow);

updateBallPosition(); }

// Create bumpers bumpers.forEach(bumper => { bumper.element = document.createElement('div'); bumper.element.className = 'bumper'; bumper.element.style.width = bumper.radius * 2 + 'px'; bumper.element.style.height = bumper.radius * 2 + 'px'; bumper.element.style.left = (bumper.x - bumper.radius) + 'px'; bumper.element.style.top = (bumper.y - bumper.radius) + 'px'; playfield.appendChild(bumper.element); });

function updateBallPosition() { if (ball.element) { ball.element.style.left = (ball.x - ball.radius) + 'px'; ball.element.style.top = (ball.y - ball.radius) + 'px';

// Shadow offset
const shadowOffset = 5;
ball.shadow.style.left = (ball.x - ball.radius + shadowOffset) + 'px';
ball.shadow.style.top = (ball.y + shadowOffset) + 'px';

} }

function updateScore(points) { score += points; scoreDisplay.textContent = SCORE: ${score};

// Screen shake const shake = 3; playfield.style.transform = translate(${(Math.random() - 0.5) * shake}px, ${(Math.random() - 0.5) * shake}px); setTimeout(() => { playfield.style.transform = 'translate(0, 0)'; }, 50); }

function checkBumperCollision() { bumpers.forEach(bumper => { const dx = ball.x - bumper.x; const dy = ball.y - bumper.y; const distance = Math.sqrt(dx * dx + dy * dy);

if (distance < ball.radius + bumper.radius) {
  // Collision response
  const angle = Math.atan2(dy, dx);
  const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
  const bounceSpeed = Math.max(speed * 1.5, 8);
  
  ball.vx = Math.cos(angle) * bounceSpeed;
  ball.vy = Math.sin(angle) * bounceSpeed;
  
  // Move ball outside bumper
  const overlap = ball.radius + bumper.radius - distance;
  ball.x += Math.cos(angle) * overlap;
  ball.y += Math.sin(angle) * overlap;
  
  // Visual feedback
  bumper.element.classList.add('hit');
  setTimeout(() => bumper.element.classList.remove('hit'), 100);
  
  updateScore(bumper.points);
}

}); }

function checkWallCollision() { const wallThickness = bounds.width * 0.01;

// Left wall if (ball.x - ball.radius < wallThickness) { ball.x = wallThickness + ball.radius; ball.vx = Math.abs(ball.vx) * BOUNCE; }

// Right wall (before plunger lane) const plungerLaneX = bounds.width * 0.88; if (ball.x + ball.radius > plungerLaneX && ball.y < bounds.height * 0.7) { ball.x = plungerLaneX - ball.radius; ball.vx = -Math.abs(ball.vx) * BOUNCE; }

// Top wall if (ball.y - ball.radius < wallThickness * 2) { ball.y = wallThickness * 2 + ball.radius; ball.vy = Math.abs(ball.vy) * BOUNCE; }

// Bottom (game over) if (ball.y > bounds.height + ball.radius) { nextBall(); } }

function checkFlipperCollision() { flippers.forEach((flipper, index) => { const flipperElement = index === 0 ? flipperLeft : flipperRight; const rect = flipper<</html>

Updated files
v1