A fun game created with AI
Created by BraveArtist0557 • November 22, 2025
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Balance Master - Physics Puzzle</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: 'Arial', sans-serif;
overflow: hidden;
touch-action: manipulation;
}
#gameContainer {
position: relative;
max-width: 100vw;
max-height: 100vh;
}
canvas {
border: 3px solid #fff;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
display: block;
touch-action: none;
background: #1a1a2e;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 18px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
pointer-events: none;
z-index: 10;
}
#controls {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
z-index: 10;
}
button {
padding: 12px 24px;
font-size: 16px;
background: #4CAF50;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
touch-action: manipulation;
}
button:hover {
background: #45a049;
}
button:active {
transform: translateY(2px);
}
.menu-btn {
background: #2196F3;
}
.menu-btn:hover {
background: #0b7dda;
}
.editor-btn {
background: #FF9800;
}
.editor-btn:hover {
background: #e68900;
}
#menu {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(26, 26, 46, 0.95);
padding: 30px;
border-radius: 15px;
text-align: center;
color: white;
z-index: 100;
min-width: 300px;
}
#menu h2 {
margin-bottom: 20px;
font-size: 28px;
}
#menu button {
margin: 10px;
min-width: 200px;
}
.hidden {
display: none !important;
}
/* Mobile optimization CSS */
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #000;
font-family: Arial, sans-serif;
overflow: hidden; /* Prevent scrolling on mobile */
/* Safe area insets for iPhone notch/home indicator */
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
/* Prevent text selection and long-press menus on mobile */
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
/* Disable tap highlight on mobile */
-webkit-tap-highlight-color: transparent;
/* Optimize for touch */
touch-action: manipulation;
}
#game {
border: 2px solid #fff;
max-width: 100vw;
max-height: 100vh;
display: block;
touch-action: none; /* Prevent default touch behaviors on canvas */
}
/* UI elements should be positioned safely */
.game-ui {
position: absolute;
color: #fff;
font-size: clamp(14px, 3vw, 24px); /* Responsive font size */
z-index: 10;
pointer-events: none; /* UI doesn't interfere with touch controls */
}
.score {
top: max(10px, env(safe-area-inset-top) + 10px);
left: max(10px, env(safe-area-inset-left) + 10px);
}
.lives {
top: max(10px, env(safe-area-inset-top) + 10px);
right: max(10px, env(safe-area-inset-right) + 10px);
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="game"></canvas>
<div id="ui">
<div id="levelInfo">Level: 1</div>
<div id="timeInfo">Time: 30s</div>
<div id="balanceInfo">Balance: 0%</div>
</div>
<div id="controls">
<button id="resetBtn" class="menu-btn">Reset</button>
<button id="menuBtn" class="menu-btn">Menu</button>
</div>
<div id="menu" class="hidden">
<h2>Balance Master</h2>
<button id="playBtn">Play Levels</button>
<button id="editorBtn" class="editor-btn">Level Editor</button>
<button id="loadBtn" class="editor-btn">Load Custom Level</button>
</div>
</div>
<script>
window.addEventListener('load', function() {
try {
const canvas = document.getElementById('game');
if (!canvas) { alert('Canvas not found'); return; }
const ctx = canvas.getContext('2d');
if (!ctx) { alert('Canvas context failed'); return; }
// Responsive canvas setup
function resizeCanvas() {
const maxWidth = Math.min(window.innerWidth - 20, 900);
const maxHeight = Math.min(window.innerHeight - 120, 600);
const size = Math.min(maxWidth, maxHeight * 1.5);
canvas.width = size;
canvas.height = size / 1.5;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Game state
let gameState = 'menu';
let currentLevel = 0;
let gameTime = 30;
let lastSecond = Date.now();
let balancePercentage = 0;
let selectedObject = null;
let dragOffset = { x: 0, y: 0 };
let isEditorMode = false;
let editorObjects = [];
let editorSelectedType = 'weight';
// Physics constants
const GRAVITY = 0.3;
const BALANCE_THRESHOLD = 10;
const FULCRUM_HEIGHT = 20;
// Game objects
class Weight {
constructor(x, y, mass, color = '#FFD700') {
this.x = x;
this.y = y;
this.mass = mass;
this.size = 20 + mass * 5;
this.color = color;
this.vx = 0;
this.vy = 0;
this.isDragging = false;
this.onPlatform = false;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size / 2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#000';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.mass, this.x, this.y);
}
contains(px, py) {
const dx = px - this.x;
const dy = py - this.y;
return dx * dx + dy * dy <= (this.size / 2) * (this.size / 2);
}
}
class Scale {
constructor(x, y, width, leftSide = true) {
this.x = x;
this.y = y;
this.width = width;
this.leftSide = leftSide;
this.angle = 0;
this.targetAngle = 0;
}
calculateTorque(weights) {
let leftTorque = 0;
let rightTorque = 0;
weights.forEach(weight => {
if (this.isOnPlatform(weight)) {
const distance = weight.x - this.x;
const torque = weight.mass * distance;
if (distance < 0) {
leftTorque += Math.abs(torque);
} else {
rightTorque += Math.abs(torque);
}
}
});
const netTorque = rightTorque - leftTorque;
this.targetAngle = Math.max(-0.3, Math.min(0.3, netTorque / 500));
this.angle += (this.targetAngle - this.angle) * 0.1;
return { leftTorque, rightTorque, netTorque };
}
isOnPlatform(weight) {
const platformY = this.y + Math.sin(this.angle) * (weight.x - this.x);
return Math.abs(weight.y - platformY) < 5 &&
weight.x > this.x - this.width / 2 &&
weight.x < this.x + this.width / 2;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
// Fulcrum
ctx.fillStyle = '#8B4513';
ctx.beginPath();
ctx.moveTo(-15, 0);
ctx.lineTo(15, 0);
ctx.lineTo(0, FULCRUM_HEIGHT);
ctx.closePath();
ctx.fill();
// Platform
ctx.rotate(this.angle);
ctx.fillStyle = '#CD853F';
ctx.fillRect(-this.width / 2, -10, this.width, 20);
ctx.strokeStyle = '#8B4513';
ctx.lineWidth = 3;
ctx.strokeRect(-this.width / 2, -10, this.width, 20);
ctx.restore();
}
}
class MovingPlatform {
constructor(x, y, width, speed, rangeX) {
this.x = x;
this.y = y;
this.startX = x;
this.width = width;
this.height = 15;
this.speed = speed;
this.rangeX = rangeX;
this.direction = 1;
}
update() {
this.x += this.speed * this.direction;
if (this.x > this.startX + this.rangeX || this.x < this.startX - this.rangeX) {
this.direction *= -1;
}
}
draw() {
ctx.fillStyle = '#4CAF50';
ctx.fillRect(this.x - this.width / 2, this.y, this.width, this.height);
ctx.strokeStyle = '#2E7D32';
ctx.lineWidth = 2;
ctx.strokeRect(this.x - this.width / 2, this.y, this.width, this.height);
}
isOn(weight) {
return weight.x > this.x - this.width / 2 &&
weight.x < this.x + this.width / 2 &&
Math.abs(weight.y - this.y) < 5;
}
}
// Level definitions
const levels = [
{
name: "Tutorial",
time: 60,
scale: { x: 400, y: 300, width: 300 },
weights: [
{ x: 100, y: 100, mass: 3, color: '#FFD700' },
{ x: 200, y: 100, mass: 3, color: '#FFD700' }
],
platforms: [],
goal: "Balance the scale!"
},
{
name: "Unequal Weights",
time: 45,
scale: { x: 400, y: 350, width: 300 },
weights: [
{ x: 150, y: 100, mass: 2, color: '#87CEEB' },
{ x: 250, y: 100, mass: 4, color: '#FF6347' },
{ x: 350, y: 100, mass: 2, color: '#87CEEB' }
],
platforms: [],
goal: "Use position to balance!"
},
{
name: "Moving Platform",
time: 50,
scale: { x: 400, y: 400, width: 300 },
weights: [
{ x: 100, y: 50, mass: 3, color: '#FFD700' },
{ x: 700, y: 50, mass: 3, color: '#FFD700' }
],
platforms: [
{ x: 400, y: 200, width: 80, speed: 2, rangeX: 150 }
],
goal: "Catch weights on platform!"
},
{
name: "Triple Threat",
time: 40,
scale: { x: 400, y: 380, width: 350 },
weights: [
{ x: 100, y: 100, mass: 5, color: '#FF6347' },
{ x: 300, y: 100, mass: 2, color: '#87CEEB' },
{ x: 500, y: 100, mass: 2, color: '#87CEEB' },
{ x: 700, y: 100, mass: 1, color: '#98FB98' }
],
platforms: [],
goal: "Perfect balance!"
},
{
name: "Speed Challenge",
time: 30,
scale: { x: 400, y: 350, width: 300 },
weights: [
{ x: 100, y: 100, mass: 4, color: '#FF6347' },
{ x: 200, y: 100, mass: 2, color: '#87CEEB' },
{ x: 600, y: 100, mass: 2, color: '#87CEEB' }
],
platforms: [
{ x: 400, y: 150, width: 60, speed: 3, rangeX: 200 }
],
goal: "Quick balance!"
},
{
name: "Precision",
time: 50,
scale: { x: 450, y: 400, width: 400 },
weights: [
{ x: 100, y: 100, mass: 1, color: '#98FB98' },
{ x: 200, y: 100, mass: 1, color: '#98FB98' },
{ x: 300, y: 100, mass: 1, color: '#98FB98' },
{ x: 600, y: 100, mass: 3, color: '#FFD700' }
],
platforms: [],
goal: "Precise placement!"
},
{
name: "Double Platform",
time: 45,
scale: { x: 400, y: 450, width: 300 },
weights: [
{ x: 100, y: 50, mass: 3, color: '#FFD700' },
{ x: 700, y: 50, mass: 3, color: '#FFD700' }
],
platforms: [
{ x: 250, y: 200, width: 70, speed: 2, rangeX: 100 },
{ x: 550, y: 250, width: 70, speed: -2.5, rangeX: 100 }
],
goal: "Coordinate platforms!"
},
{
name: "Heavy Lifting",
time: 40,
scale: { x: 400, y: 380, width: 350 },
weights: [
{ x: 150, y: 100, mass: 6, color: '#8B008B' },
{ x: 350, y: 100, mass: 2, color: '#87CEEB' },
{ x: 550, y: 100, mass: 2, color: '#87CEEB' },
{ x: 650, y: 100, mass: 2, color: '#87CEEB' }
],
platforms: [],
goal: "Balance the heavy one!"
},
{
name: "Chaos",
time: 35,
scale: { x: 400, y: 420, width: 350 },
weights: [
{ x: 100, y: 80, mass: 5, color: '#FF6347' },
{ x: 250, y: 80, mass: 1, color: '#98FB98' },
{ x: 550, y: 80, mass: 3, color: '#FFD700' },
{ x: 700, y: 80, mass: 1, color: '#98FB98' }
],
platforms: [
{ x: 400, y: 180, width: 60, speed: 3.5, rangeX: 180 }
],
goal: "Master the chaos!"
},
{
name: "Final Challenge",
time: 30,
scale: { x: 400, y: 450, width: 400 },
weights: [
{ x: 100, y: 60, mass: 4, color: '#FF6347' },
{ x: 250, y: 60, mass: 2, color: '#87CEEB' },
{ x: 550, y: 60, mass: 2, color: '#87CEEB' },
{ x: 700, y: 60, mass: 4, color: '#FF6347' }
],
platforms: [
{ x: 200, y: 200, width: 60, speed: 2.5, rangeX: 120 },
{ x: 600, y: 250, width: 60, speed: -3, rangeX: 120 }
],
goal: "Ultimate balance!"
}
];
let scale, weights, platforms;
function loadLevel(levelIndex) {
const level = levels[levelIndex];
if (!level) return false;
scale = new Scale(level.scale.x, level.scale.y, level.scale.width);
weights = level.weights.map(w => new Weight(w.x, w.y, w.mass, w.color));
platforms = level.platforms.map(p => new MovingPlatform(p.x, p.y, p.width, p.speed, p.rangeX));
gameTime = level.time;
lastSecond = Date.now();
balancePercentage = 0;
document.getElementById('levelInfo').textContent = `Level: ${levelIndex + 1} - ${level.name}`;
return true;
}
// Input handling
const keys = {};
window.addEventListener('keydown', (e) => {
keys[e.key] = true;
e.preventDefault();
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// Mouse/Touch handling
function getMousePos(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY
};
}
canvas.addEventListener('mousedown', handleStart);
canvas.addEventListener('touchstart', handleStart, { passive: false });
canvas.addEventListener('mousemove', handleMove);
canvas.addEventListener('touchmove', handleMove, { passive: false });
canvas.addEventListener('mouseup', handleEnd);
canvas.addEventListener('touchend', handleEnd);
function handleStart(e) {
e.preventDefault();
const pos = getMousePos(e);
if (isEditorMode) {
// Editor mode - add object
if (editorSelectedType === 'weight') {
editorObjects.push({
type: 'weight',
x: pos.x,
y: pos.y,
mass: 3,
color: '#FFD700'
});
} else if (editorSelectedType === 'scale') {
editorObjects.push({
type: 'scale',
x: pos.x,
y: pos.y,
width: 300
});
} else if (editorSelectedType === 'platform') {
editorObjects.push({
type: 'platform',
x: pos.x,
y: pos.y,
width: 80,
speed: 2,
rangeX: 100
});
}
return;
}
for (let i = weights.length - 1; i >= 0; i--) {
if (weights[i].contains(pos.x, pos.y)) {
selectedObject = weights[i];
selectedObject.isDragging = true;
dragOffset.x = pos.x - selectedObject.x;
dragOffset.y = pos.y - selectedObject.y;
break;
}
}
}
function handleMove(e) {
e.preventDefault();
if (selectedObject && selectedObject.isDragging) {
const pos = getMousePos(e);
selectedObject.x = pos.x - dragOffset.x;
selectedObject.y = pos.y - dragOffset.y;
selectedObject.vx = 0;
selectedObject.vy = 0;
}
}
function handleEnd(e) {
e.preventDefault();
if (selectedObject) {
selectedObject.isDragging = false;
selectedObject = null;
}
}
// UI Controls
document.getElementById('resetBtn').addEventListener('click', () => {
if (gameState === 'playing') {
loadLevel(currentLevel);
}
});
document.getElementById('menuBtn').addEventListener('click', () => {
gameState = 'menu';
document.getElementById('menu').classList.remove('hidden');
});
document.getElementById('playBtn').addEventListener('click', () => {
gameState = 'playing';
isEditorMode = false;
currentLevel = 0;
loadLevel(currentLevel);
document.getElementById('menu').classList.add('hidden');
});
document.getElementById('editorBtn').addEventListener('click', () => {
gameState = 'editor';
isEditorMode = true;
editorObjects = [];
document.getElementById('menu').classList.add('hidden');
});
document.getElementById('loadBtn').addEventListener('click', () => {
const levelData = prompt('Paste custom level code:');
if (levelData) {
try {
const customLevel = JSON.parse(atob(levelData));
gameState = 'playing';
isEditorMode = false;
scale = new Scale(customLevel.scale.x, customLevel.scale.y, customLevel.scale.width);
weights = customLevel.weights.map(w => new Weight(w.x, w.y, w.mass, w.color));
platforms = customLevel.platforms.map(p => new MovingPlatform(p.x, p.y, p.width, p.speed, p.rangeX));
gameTime = customLevel.time || 60;
document.getElementById('menu').classList.add('hidden');
} catch (e) {
alert('Invalid level code!');
}
}
});
// Game loop
function update() {
if (gameState === 'playing') {
// Update timer
const now = Date.now();
if (now - lastSecond >= 1000) {
gameTime--;
lastSecond = now;
if (gameTime <= 0) {
alert('Time\'s up! Try again.');
loadLevel(currentLevel);
}
}
// Update platforms
platforms.forEach(platform => platform.update());
// Update weights physics
weights.forEach(weight => {
if (!weight.isDragging) {
weight.vy += GRAVITY;
weight.y += weight.vy;
weight.x += weight.vx;
// Check platform collisions
let onPlatform = false;
platforms.forEach(platform => {
if (platform.isOn(weight) && weight.vy >= 0) {
weight.y = platform.y;
weight.vy = 0;
weight.vx = platform.speed * platform.direction;
onPlatform = true;
}
});
// Check scale collision
if (scale.isOnPlatform(weight)) {
weight.y = scale.y + Math.sin(scale.angle) * (weight.x - scale.x);
weight.vy = 0;
weight.vx *= 0.95;
onPlatform = true;
}
// Floor collision
if (weight.y > canvas.height - weight.size / 2) {
weight.y = canvas.height - weight.size / 2;
weight.vy = 0;
weight.vx *= 0.8;
}
// Wall collision
if (weight.x < weight.size / 2) {
weight.x = weight.size / 2;
weight.vx = 0;
}
if (weight.x > canvas.width - weight.size / 2) {
weight.x = canvas.width - weight.size / 2;
weight.vx = 0;
}
}
});
// Calculate balance
const torques = scale.calculateTorque(weights);
const totalTorque = torques.leftTorque + torques.rightTorque;
if (totalTorque > 0) {
const balance = 100 - (Math.abs(torques.netTorque) / totalTorque * 100);
balancePercentage = Math.max(0, balance);
} else {
balancePercentage = 0;
}
// Check win condition
if (balancePercentage >= 90) {
setTimeout(() => {
alert(`Level ${currentLevel + 1} Complete!
Balance: ${balancePercentage.toFixed(1)}%`);
currentLevel++;
if (currentLevel < levels.length) {
loadLevel(currentLevel);
} else {
alert('Congratulations! You completed all levels!');
gameState = 'menu';
document.getElementById('menu').classList.remove('hidden');
}
}, 100);
}
// Update UI
document.getElementById('timeInfo').textContent = `Time: ${gameTime}s`;
document.getElementById('balanceInfo').textContent = `Balance: ${balancePercentage.toFixed(1)}%`;
}
}
function draw() {
// Clear canvas
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (gameState === 'menu') {
// Menu is handled by HTML
return;
}
if (gameState === 'editor') {
// Draw editor
ctx.fillStyle = '#fff';
ctx.font = '20px Arial';
ctx.fillText('Level Editor - Click to place objects', 10, 30);
ctx.fillText('Press 1: Weight, 2: Scale, 3: Platform', 10, 60);
ctx.fillText('Press S to save level code', 10, 90);
editorObjects.forEach(obj => {
if (obj.type === 'weight') {
ctx.fillStyle = obj.color;
ctx.beginPath();
ctx.arc(obj.x, obj.y, 25, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
} else if (obj.type === 'scale') {
ctx.fillStyle = '#CD853F';
ctx.fillRect(obj.x - obj.width / 2, obj.y - 10, obj.width, 20);
} else if (obj.type === 'platform') {
ctx.fillStyle = '#4CAF50';
ctx.fillRect(obj.x - obj.width / 2, obj.y, obj.width, 15);
}
});
// Handle editor keys
if (keys['1']) editorSelectedType = 'weight';
if (keys['2']) editorSelectedType = 'scale';
if (keys['3']) editorSelectedType = 'platform';
if (keys['s'] || keys['S']) {
const levelData = {
scale: editorObjects.find(o => o.type === 'scale') || { x: 400, y: 300, width: 300 },
weights: editorObjects.filter(o => o.type === 'weight'),
platforms: editorObjects.filter(o => o.type === 'platform'),
time: 60
};
const code = btoa(JSON.stringify(levelData));
prompt('Level code (copy and share):', code);
keys['s'] = false;
keys['S'] = false;
}
return;
}
// Draw game
if (scale) scale.draw();
platforms.forEach(platform => platform.draw());
weights.forEach(weight => weight.draw());
// Draw balance indicator
const barWidth = 200;
const barHeight = 20;
const barX = canvas.width - barWidth - 20;
const barY = 20;
ctx.fillStyle = '#333';
ctx.fillRect(barX, barY, barWidth, barHeight);
const fillWidth = (balancePercentage / 100) * barWidth;
const color = balancePercentage >= 90 ? '#4CAF50' : balancePercentage >= 50 ? '#FFA500' : '#FF6347';
ctx.fillStyle = color;
ctx.fillRect(barX, barY, fillWidth, barHeight);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.strokeRect(barX, barY, barWidth, barHeight);
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
} catch(e) {
document.body.innerHTML = '<div style="color:white;padding:20px;font-size:20px;">Game error: ' + e.message + '<br><br>Stack: ' + e.stack + '</div>';
}
});
</script>
</body>
</html>
Install GB3A on your device for a better experience!