html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #1a1a2e;
font-family: 'Courier New', Courier, monospace;
color: #e0e1dd;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.game-container {
width: 100vmin;
height: calc(100vmin / var(--table-aspect-ratio));
max-width: calc(100vh * var(--table-aspect-ratio));
max-height: 100vh;
position: relative;
transform-style: preserve-3d;
}
@keyframes screen-shake {
0%, 100% { transform: translate(0, 0) rotate(0); }
10% { transform: translate(-2px, -3px) rotate(-0.5deg); }
20% { transform: translate(3px, 1px) rotate(0.5deg); }
30% { transform: translate(-1px, 2px) rotate(-0.5deg); }
40% { transform: translate(2px, -1px) rotate(0.5deg); }
50% { transform: translate(-3px, 1px) rotate(0); }
}
.shake {
animation: screen-shake 0.3s ease-in-out;
}
.pinball-table {
width: 100%;
height: 100%;
background: linear-gradient(145deg, #2c3e50, #34495e);
border: 15px solid #7f8c8d;
border-bottom: none;
box-shadow: inset 0 0 20px rgba(0,0,0,0.5), 0 10px 30px rgba(0,0,0,0.7);
border-radius: 40px 40px 0 0;
position: relative;
overflow: hidden;
}
.ball {
position: absolute;
width: var(--ball-size);
height: var(--ball-size);
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #ffffff, #silver, #7f8c8d);
box-shadow: inset -5px -5px 10px rgba(0,0,0,0.3);
filter: drop-shadow(3px 3px 5px rgba(0,0,0,0.6));
z-index: 10;
}
.flipper {
position: absolute;
width: var(--flipper-width);
height: var(--flipper-height);
background: #e74c3c;
border: 2px solid #c0392b;
border-radius: var(--flipper-height);
bottom: 60px;
z-index: 5;
box-shadow: 0 5px 10px rgba(0,0,0,0.4);
transition: transform 0.07s ease-out;
}
#flipper-left {
left: 28%;
transform-origin: 10% 50%;
transform: translateX(-50%) rotate(30deg);
}
#flipper-left.active {
transform: translateX(-50%) rotate(-30deg);
}
#flipper-right {
right: 28%;
transform-origin: 90% 50%;
transform: translateX(50%) rotate(-30deg);
}
#flipper-right.active {
transform: translateX(50%) rotate(30deg);
}
.bumper {
position: absolute;
width: var(--bumper-size);
height: var(--bumper-size);
background: radial-gradient(circle, #f1c40f, #f39c12);
border-radius: 50%;
box-shadow: inset 0 0 10px #e67e22, 0 0 15px rgba(243, 156, 18, 0.5);
z-index: 4;
}
@keyframes bumper-flash {
0%, 100% {
transform: scale(1);
background: radial-gradient(circle, #f1c40f, #f39c12);
box-shadow: inset 0 0 10px #e67e22, 0 0 15px rgba(243, 156, 18, 0.5);
}
50% {
transform: scale(1.1);
background: radial-gradient(circle, #fff, #f1c40f);
box-shadow: inset 0 0 15px #f39c12, 0 0 40px rgba(255, 255, 255, 0.9);
}
}
.bumper.flash {
animation: bumper-flash 0.15s linear;
}
.bumper1 { top: 25%; left: 50%; transform: translateX(-50%); }
.bumper2 { top: 40%; left: 25%; transform: translateX(-50%); }
.bumper3 { top: 40%; left: 75%; transform: translateX(-50%); }
.ui-display {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.5);
padding: 10px 20px;
border-radius: 10px;
text-align: center;
font-size: 24px;
letter-spacing: 2px;
border: 2px solid #4a4a68;
z-index: 20;
}
.game-over-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
font-size: 8vw;
color: #e74c3c;
text-shadow: 0 0 20px #c0392b;
opacity: 0;
visibility: hidden;
transition: opacity 1.5s ease-in, visibility 1.5s;
z-index: 100;
}
.game-over-overlay.show {
opacity: 1;
visibility: visible;
}
.side-wall {
position: absolute;
background: #8e44ad;
box-shadow: inset 0 0 10px #2c3e50;
}
#wall-left { top: 30%; left: 5%; width: 5px; height: 55%; transform: rotate(10deg); }
#wall-right { top: 30%; right: 5%; width: 5px; height: 55%; transform: rotate(-10deg); }
.gutter {
position: absolute;
bottom: 0;
width: 100%;
height: 5px;
background: transparent;
}
</style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #1a1a2e;
font-family: 'Courier New', Courier, monospace;
color: #e0e1dd;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.game-container {
width: 100vmin;
height: calc(100vmin / var(--table-aspect-ratio));
max-width: calc(100vh * var(--table-aspect-ratio));
max-height: 100vh;
position: relative;
transform-style: preserve-3d;
}
@keyframes screen-shake {
0%, 100% { transform: translate(0, 0) rotate(0); }
10% { transform: translate(-2px, -3px) rotate(-0.5deg); }
20% { transform: translate(3px, 1px) rotate(0.5deg); }
30% { transform: translate(-1px, 2px) rotate(-0.5deg); }
40% { transform: translate(2px, -1px) rotate(0.5deg); }
50% { transform: translate(-3px, 1px) rotate(0); }
}
.shake {
animation: screen-shake 0.3s ease-in-out;
}
.pinball-table {
width: 100%;
height: 100%;
background: linear-gradient(145deg, #2c3e50, #34495e);
border: 15px solid #7f8c8d;
border-bottom: none;
box-shadow: inset 0 0 20px rgba(0,0,0,0.5), 0 10px 30px rgba(0,0,0,0.7);
border-radius: 40px 40px 0 0;
position: relative;
overflow: hidden;
}
.ball {
position: absolute;
width: var(--ball-size);
height: var(--ball-size);
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #ffffff, #silver, #7f8c8d);
box-shadow: inset -5px -5px 10px rgba(0,0,0,0.3);
filter: drop-shadow(3px 3px 5px rgba(0,0,0,0.6));
z-index: 10;
}
.flipper {
position: absolute;
width: var(--flipper-width);
height: var(--flipper-height);
background: #e74c3c;
border: 2px solid #c0392b;
border-radius: var(--flipper-height);
bottom: 60px;
z-index: 5;
box-shadow: 0 5px 10px rgba(0,0,0,0.4);
transition: transform 0.07s ease-out;
}
#flipper-left {
left: 28%;
transform-origin: 10% 50%;
transform: translateX(-50%) rotate(30deg);
}
#flipper-left.active {
transform: translateX(-50%) rotate(-30deg);
}
#flipper-right {
right: 28%;
transform-origin: 90% 50%;
transform: translateX(50%) rotate(-30deg);
}
#flipper-right.active {
transform: translateX(50%) rotate(30deg);
}
.bumper {
position: absolute;
width: var(--bumper-size);
height: var(--bumper-size);
background: radial-gradient(circle, #f1c40f, #f39c12);
border-radius: 50%;
box-shadow: inset 0 0 10px #e67e22, 0 0 15px rgba(243, 156, 18, 0.5);
z-index: 4;
}
@keyframes bumper-flash {
0%, 100% {
transform: scale(1);
background: radial-gradient(circle, #f1c40f, #f39c12);
box-shadow: inset 0 0 10px #e67e22, 0 0 15px rgba(243, 156, 18, 0.5);
}
50% {
transform: scale(1.1);
background: radial-gradient(circle, #fff, #f1c40f);
box-shadow: inset 0 0 15px #f39c12, 0 0 40px rgba(255, 255, 255, 0.9);
}
}
.bumper.flash {
animation: bumper-flash 0.15s linear;
}
.bumper1 { top: 25%; left: 50%; transform: translateX(-50%); }
.bumper2 { top: 40%; left: 25%; transform: translateX(-50%); }
.bumper3 { top: 40%; left: 75%; transform: translateX(-50%); }
.ui-display {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.5);
padding: 10px 20px;
border-radius: 10px;
text-align: center;
font-size: 24px;
letter-spacing: 2px;
border: 2px solid #4a4a68;
z-index: 20;
}
.game-over-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
font-size: 8vw;
color: #e74c3c;
text-shadow: 0 0 20px #c0392b;
opacity: 0;
visibility: hidden;
transition: opacity 1.5s ease-in, visibility 1.5s;
z-index: 100;
}
.game-over-overlay.show {
opacity: 1;
visibility: visible;
}
.side-wall {
position: absolute;
background: #8e44ad;
box-shadow: inset 0 0 10px #2c3e50;
}
#wall-left { top: 30%; left: 5%; width: 5px; height: 55%; transform: rotate(10deg); }
#wall-right { top: 30%; right: 5%; width: 5px; height: 55%; transform: rotate(-10deg); }
.gutter {
position: absolute;
bottom: 0;
width: 100%;
height: 5px;
background: transparent;
}
</style>
<div class="flipper" id="flipper-left"></div>
<div class="flipper" id="flipper-right"></div>
<div class="bumper bumper1" data-score="100"></div>
<div class="bumper bumper2" data-score="100"></div>
<div class="bumper bumper3" data-score="100"></div>
<div class="side-wall" id="wall-left"></div>
<div class="side-wall" id="wall-right"></div>
<div class="gutter" id="gutter"></div>
</div>
<div class="game-over-overlay" id="game-over">GAME OVER</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const gameContainer = document.getElementById('game-container');
const table = document.getElementById('pinball-table');
const ballElement = document.getElementById('ball');
const flipperLeft = document.getElementById('flipper-left');
const flipperRight = document.getElementById('flipper-right');
const bumpers = Array.from(document.querySelectorAll('.bumper'));
const scoreDisplay = document.getElementById('score-display');
const ballsDisplay = document.getElementById('balls-display');
const gameOverDisplay = document.getElementById('game-over');
let tableRect;
let scaleFactor = 1;
const ball = {
x: 0, y: 0,
vx: 0, vy: 0,
radius: 0
};
const physics = {
gravity: 0.25,
friction: 0.998,
flipperForce: -15,
bumperForce: 1.2
};
let score = 0;
let ballsRemaining = 3;
let isGameOver = false;
let ballInPlay = false;
function updateDimensions() {
tableRect = table.getBoundingClientRect();
const originalWidth = 600; // From CSS var --table-width
scaleFactor = tableRect.width / originalWidth;
ball.radius = (parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--ball-size')) / 2) * scaleFactor;
}
function init() {
updateDimensions();
resetGame();
window.addEventListener('resize', updateDimensions);
gameLoop();
}
function resetGame() {
score = 0;
ballsRemaining = 3;
isGameOver = false;
ballInPlay = false;
gameOverDisplay.classList.remove('show');
updateUI();
resetBall();
setTimeout(launchBall, 1000);
}
function resetBall() {
ball.x = tableRect.width * 0.9;
ball.y = tableRect.height * 0.7;
ball.vx = 0;
ball.vy = 0;
ballInPlay = false;
}
function launchBall() {
if (isGameOver || ballInPlay) return;
ballInPlay = true;
ball.vy = -15 * scaleFactor;
ball.vx = -3 * scaleFactor;
}
function updateUI() {
scoreDisplay.textContent = `SCORE: ${score}`;
ballsDisplay.textContent = `BALLS: ${ballsRemaining}`;
}
function gameOver() {
isGameOver = true;
gameOverDisplay.classList.add('show');
setTimeout(resetGame, 5000);
}
function loseBall() {
ballsRemaining--;
ballInPlay = false;
if (ballsRemaining < 0) {
ballsRemaining = 0;
gameOver();
} else {
updateUI();
resetBall();
setTimeout(launchBall, 2000);
}
}
function activateFlipper(flipper) {
if (flipper.cooldown) return;
flipper.classList.add('active');
flipper.cooldown = true;
setTimeout(() => {
flipper.classList.remove('active');
}, 150);
setTimeout(() => {
flipper.cooldown = false;
}, 300);
}
function autoPlay() {
const ballBottom = ball.y + ball.radius;
const flipperLeftRect = flipperLeft.getBoundingClientRect();
const flipperLeftY = flipperLeftRect.top - tableRect.top + flipperLeftRect.height / 2;
const flipperRightRect = flipperRight.getBoundingClientRect();
const flipperRightY = flipperRightRect.top - tableRect.top + flipperRightRect.height / 2;
if (ball.vy > 0) { // Ball is moving down
// Left flipper AI
if (ballBottom > flipperLeftY - 50 * scaleFactor && ballBottom < flipperLeftY + 20 * scaleFactor) {
if (ball.x > flipperLeftRect.left - tableRect.left && ball.x < flipperLeftRect.right - tableRect.left + 20 * scaleFactor) {
activateFlipper(flipperLeft);
}
}
// Right flipper AI
if (ballBottom > flipperRightY - 50 * scaleFactor && ballBottom < flipperRightY + 20 * scaleFactor) {
if (ball.x < flipperRightRect.right - tableRect.left && ball.x > flipperRightRect.left - tableRect.left - 20 * scaleFactor) {
activateFlipper(flipperRight);
}
}
}
}
function handleCollisions() {
// Wall collisions
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx *= -0.8;
}
if (ball.x + ball.radius > tableRect.width) {
ball.x = tableRect.width - ball.radius;
ball.vx *= -0.8;
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
ball.vy *= -0.8;
}
// Gutter
if (ball.y + ball.radius > tableRect.height) {
loseBall();
return;
}
// Bumper collisions
bumpers.forEach(bumper => {
const bumperRect = bumper.getBoundingClientRect();
const bumperX = bumperRect.left - tableRect.left + bumperRect.width / 2;
const bumperY = bumperRect.top - tableRect.top + bumperRect.height / 2;
const bumperRadius = bumperRect.width / 2;
const dx = ball.x - bumperX;
const dy = ball.y - bumperY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball.radius + bumperRadius) {
if (!bumper.classList.contains('flash')) {
const scoreValue = parseInt(bumper.getAttribute('data-score')) || 50;
score += scoreValue;
updateUI();
bumper.classList.add('flash');
setTimeout(() => bumper.classList.remove('flash'), 150);
gameContainer.classList.remove('shake');
void gameContainer.offsetWidth; // Trigger reflow
gameContainer.classList.add('shake');
}
const angle = Math.atan2(dy, dx);
const magnitude = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
ball.vx = Math.cos(angle) * magnitude * physics.bumperForce;
ball.vy = Math.sin(angle) * magnitude * physics.bumperForce;
// Move ball out of bumper
const overlap = ball.radius + bumperRadius - distance;
ball.x += Math.cos(angle) * overlap;
ball.y += Math.sin(angle) * overlap;
}
});
// Flipper collisions
[flipperLeft, flipperRight].forEach(flipper => {
if (flipper.classList.contains('active')) {
const flipperRect = flipper.getBoundingClientRect();
const flipperY = flipperRect.top - tableRect.top;
if (ball.y + ball.radius > flipperY && ball.y - ball.radius < flipperY + flipperRect.height) {
if (ball.x > flipperRect.left - tableRect.left && ball.x < flipperRect.right - tableRect.left) {
ball.vy = physics.flipperForce * scaleFactor;
if (flipper.id === 'flipper-left') {
ball.vx += (ball.x - (flipperRect.left - tableRect.left)) / (flipperRect.width) * 5;
} else {
ball.vx -= ((flipperRect.right - tableRect.left) - ball.x) / (flipperRect.width) * 5;
}
gameContainer.classList.remove('shake');
void gameContainer.offsetWidth; // Trigger reflow
gameContainer.classList.add('shake');
}
}
}
});
}
function gameLoop() {
if (isGameOver) {
requestAnimationFrame(gameLoop);
return;
}
if (ballInPlay) {
ball.vy += physics.gravity * scaleFactor;
ball.vx *= physics.friction;
ball.vy *= physics.friction;
ball.x += ball.vx;
ball.y += ball.vy;
handleCollisions();
autoPlay();
}
ballElement.style.transform = `translate(${ball.x - ball.radius}px, ${ball.y - ball.radius}px)`;
requestAnimationFrame(gameLoop);
}
init();
});
</script>
<div class="flipper" id="flipper-left"></div>
<div class="flipper" id="flipper-right"></div>
<div class="bumper bumper1" data-score="100"></div>
<div class="bumper bumper2" data-score="100"></div>
<div class="bumper bumper3" data-score="100"></div>
<div class="side-wall" id="wall-left"></div>
<div class="side-wall" id="wall-right"></div>
<div class="gutter" id="gutter"></div>
</div>
<div class="game-over-overlay" id="game-over">GAME OVER</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const gameContainer = document.getElementById('game-container');
const table = document.getElementById('pinball-table');
const ballElement = document.getElementById('ball');
const flipperLeft = document.getElementById('flipper-left');
const flipperRight = document.getElementById('flipper-right');
const bumpers = Array.from(document.querySelectorAll('.bumper'));
const scoreDisplay = document.getElementById('score-display');
const ballsDisplay = document.getElementById('balls-display');
const gameOverDisplay = document.getElementById('game-over');
let tableRect;
let scaleFactor = 1;
const ball = {
x: 0, y: 0,
vx: 0, vy: 0,
radius: 0
};
const physics = {
gravity: 0.25,
friction: 0.998,
flipperForce: -15,
bumperForce: 1.2
};
let score = 0;
let ballsRemaining = 3;
let isGameOver = false;
let ballInPlay = false;
function updateDimensions() {
tableRect = table.getBoundingClientRect();
const originalWidth = 600; // From CSS var --table-width
scaleFactor = tableRect.width / originalWidth;
ball.radius = (parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--ball-size')) / 2) * scaleFactor;
}
function init() {
updateDimensions();
resetGame();
window.addEventListener('resize', updateDimensions);
gameLoop();
}
function resetGame() {
score = 0;
ballsRemaining = 3;
isGameOver = false;
ballInPlay = false;
gameOverDisplay.classList.remove('show');
updateUI();
resetBall();
setTimeout(launchBall, 1000);
}
function resetBall() {
ball.x = tableRect.width * 0.9;
ball.y = tableRect.height * 0.7;
ball.vx = 0;
ball.vy = 0;
ballInPlay = false;
}
function launchBall() {
if (isGameOver || ballInPlay) return;
ballInPlay = true;
ball.vy = -15 * scaleFactor;
ball.vx = -3 * scaleFactor;
}
function updateUI() {
scoreDisplay.textContent = `SCORE: ${score}`;
ballsDisplay.textContent = `BALLS: ${ballsRemaining}`;
}
function gameOver() {
isGameOver = true;
gameOverDisplay.classList.add('show');
setTimeout(resetGame, 5000);
}
function loseBall() {
ballsRemaining--;
ballInPlay = false;
if (ballsRemaining < 0) {
ballsRemaining = 0;
gameOver();
} else {
updateUI();
resetBall();
setTimeout(launchBall, 2000);
}
}
function activateFlipper(flipper) {
if (flipper.cooldown) return;
flipper.classList.add('active');
flipper.cooldown = true;
setTimeout(() => {
flipper.classList.remove('active');
}, 150);
setTimeout(() => {
flipper.cooldown = false;
}, 300);
}
function autoPlay() {
const ballBottom = ball.y + ball.radius;
const flipperLeftRect = flipperLeft.getBoundingClientRect();
const flipperLeftY = flipperLeftRect.top - tableRect.top + flipperLeftRect.height / 2;
const flipperRightRect = flipperRight.getBoundingClientRect();
const flipperRightY = flipperRightRect.top - tableRect.top + flipperRightRect.height / 2;
if (ball.vy > 0) { // Ball is moving down
// Left flipper AI
if (ballBottom > flipperLeftY - 50 * scaleFactor && ballBottom < flipperLeftY + 20 * scaleFactor) {
if (ball.x > flipperLeftRect.left - tableRect.left && ball.x < flipperLeftRect.right - tableRect.left + 20 * scaleFactor) {
activateFlipper(flipperLeft);
}
}
// Right flipper AI
if (ballBottom > flipperRightY - 50 * scaleFactor && ballBottom < flipperRightY + 20 * scaleFactor) {
if (ball.x < flipperRightRect.right - tableRect.left && ball.x > flipperRightRect.left - tableRect.left - 20 * scaleFactor) {
activateFlipper(flipperRight);
}
}
}
}
function handleCollisions() {
// Wall collisions
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx *= -0.8;
}
if (ball.x + ball.radius > tableRect.width) {
ball.x = tableRect.width - ball.radius;
ball.vx *= -0.8;
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
ball.vy *= -0.8;
}
// Gutter
if (ball.y + ball.radius > tableRect.height) {
loseBall();
return;
}
// Bumper collisions
bumpers.forEach(bumper => {
const bumperRect = bumper.getBoundingClientRect();
const bumperX = bumperRect.left - tableRect.left + bumperRect.width / 2;
const bumperY = bumperRect.top - tableRect.top + bumperRect.height / 2;
const bumperRadius = bumperRect.width / 2;
const dx = ball.x - bumperX;
const dy = ball.y - bumperY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball.radius + bumperRadius) {
if (!bumper.classList.contains('flash')) {
const scoreValue = parseInt(bumper.getAttribute('data-score')) || 50;
score += scoreValue;
updateUI();
bumper.classList.add('flash');
setTimeout(() => bumper.classList.remove('flash'), 150);
gameContainer.classList.remove('shake');
void gameContainer.offsetWidth; // Trigger reflow
gameContainer.classList.add('shake');
}
const angle = Math.atan2(dy, dx);
const magnitude = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
ball.vx = Math.cos(angle) * magnitude * physics.bumperForce;
ball.vy = Math.sin(angle) * magnitude * physics.bumperForce;
// Move ball out of bumper
const overlap = ball.radius + bumperRadius - distance;
ball.x += Math.cos(angle) * overlap;
ball.y += Math.sin(angle) * overlap;
}
});
// Flipper collisions
[flipperLeft, flipperRight].forEach(flipper => {
if (flipper.classList.contains('active')) {
const flipperRect = flipper.getBoundingClientRect();
const flipperY = flipperRect.top - tableRect.top;
if (ball.y + ball.radius > flipperY && ball.y - ball.radius < flipperY + flipperRect.height) {
if (ball.x > flipperRect.left - tableRect.left && ball.x < flipperRect.right - tableRect.left) {
ball.vy = physics.flipperForce * scaleFactor;
if (flipper.id === 'flipper-left') {
ball.vx += (ball.x - (flipperRect.left - tableRect.left)) / (flipperRect.width) * 5;
} else {
ball.vx -= ((flipperRect.right - tableRect.left) - ball.x) / (flipperRect.width) * 5;
}
gameContainer.classList.remove('shake');
void gameContainer.offsetWidth; // Trigger reflow
gameContainer.classList.add('shake');
}
}
}
});
}
function gameLoop() {
if (isGameOver) {
requestAnimationFrame(gameLoop);
return;
}
if (ballInPlay) {
ball.vy += physics.gravity * scaleFactor;
ball.vx *= physics.friction;
ball.vy *= physics.friction;
ball.x += ball.vx;
ball.y += ball.vy;
handleCollisions();
autoPlay();
}
ballElement.style.transform = `translate(${ball.x - ball.radius}px, ${ball.y - ball.radius}px)`;
requestAnimationFrame(gameLoop);
}
init();
});
</script>