/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	coins: 0,
	unlockedSkins: [],
	selectedSkin: 0
});
/**** 
* Classes
****/ 
// Ball class
var Ball = Container.expand(function () {
	var self = Container.call(this);
	// Ball color index: 0=red, 1=blue, 2=green, 3=yellow
	self.colorIndex = 0;
	self.colors = ['red', 'blue', 'green', 'yellow'];
	self.asset = null;
	// Attach initial asset
	self.setColor = function (idx) {
		if (self.asset) {
			self.removeChild(self.asset);
		}
		self.colorIndex = idx;
		var colorName = self.colors[self.colorIndex];
		self.asset = self.attachAsset('ball_' + colorName, {
			anchorX: 0.5,
			anchorY: 0.5
		});
	};
	self.cycleColor = function () {
		var next = (self.colorIndex + 1) % self.colors.length;
		self.setColor(next);
	};
	// For simple trail effect (MVP: just a quick flash)
	self.flash = function () {
		tween(self.asset, {
			alpha: 0.5
		}, {
			duration: 60,
			easing: tween.linear,
			onFinish: function onFinish() {
				tween(self.asset, {
					alpha: 1
				}, {
					duration: 80
				});
			}
		});
	};
	// Set initial color
	self.setColor(0);
	return self;
});
// Coin class
var Coin = Container.expand(function () {
	var self = Container.call(this);
	self.asset = self.attachAsset('coin', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.update = function () {
		// MVP: no animation
	};
	return self;
});
// Platform class
var Platform = Container.expand(function () {
	var self = Container.call(this);
	// Color index: 0=red, 1=blue, 2=green, 3=yellow
	self.colorIndex = 0;
	self.colors = ['red', 'blue', 'green', 'yellow'];
	self.asset = null;
	// For moving platforms (future)
	self.vx = 0;
	self.setColor = function (idx) {
		if (self.asset) {
			self.removeChild(self.asset);
		}
		self.colorIndex = idx;
		var colorName = self.colors[self.colorIndex];
		self.asset = self.attachAsset('platform_' + colorName, {
			anchorX: 0.5,
			anchorY: 0.5
		});
	};
	// For MVP, platforms are static
	self.update = function () {
		// No horizontal movement; platforms remain centered
	};
	// Set initial color
	self.setColor(0);
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x181c20
});
/**** 
* Game Code
****/ 
// --- Game variables ---
// Ball colors (platforms and ball)
// SFX
// Music (stub, not played in MVP)
var ball = null;
var platforms = [];
var coins = [];
var score = 0;
var coinCount = 0;
var platformSpacing = 400; // vertical distance between platforms
// platformSpeed is now calculated dynamically based on score
var gravity = 1; // increases as score increases (slightly higher)
var bounceVelocity = -31; // initial bounce velocity (slightly higher magnitude)
var ballVy = 0;
var isFalling = true;
var currentPlatformIdx = 0;
var lastTapTick = 0;
var gameStarted = false;
var dragNode = null; // not used, but required by guidelines
var lastIntersecting = false;
var canTap = true; // prevent double tap in one frame
// GUI
var scoreTxt = new Text2('0', {
	size: 120,
	fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
	size: 80,
	fill: '#ffd700'
});
coinTxt.anchor.set(1, 0);
coinTxt.x = -40;
coinTxt.y = 20;
LK.gui.topRight.addChild(coinTxt);
// --- Music Play/Pause and Volume Switch (Top Left) ---
var musicPlaying = true;
var musicVolume = 1; // 1 = max, 0 = muted
// Play music at start
LK.playMusic('fallingdown', {
	loop: true,
	fade: {
		start: 0,
		end: musicVolume,
		duration: 600
	}
});
// Play/Pause Button
var playPauseBtn = new Text2('⏸', {
	size: 90,
	fill: '#fff'
});
playPauseBtn.anchor.set(0, 0);
playPauseBtn.x = 110;
playPauseBtn.y = 10;
// Volume Button
var volumeBtn = new Text2('🔊', {
	size: 90,
	fill: '#fff'
});
volumeBtn.anchor.set(0, 0);
volumeBtn.x = 220;
volumeBtn.y = 10;
// Add to top left GUI (leave 100x100 px clear for menu)
LK.gui.topLeft.addChild(playPauseBtn);
LK.gui.topLeft.addChild(volumeBtn);
// Play/Pause toggle
playPauseBtn.down = function (x, y, obj) {
	if (musicPlaying) {
		LK.stopMusic();
		playPauseBtn.setText('▶️');
		musicPlaying = false;
	} else {
		LK.playMusic('fallingdown', {
			loop: true,
			fade: {
				start: 0,
				end: musicVolume,
				duration: 400
			}
		});
		playPauseBtn.setText('⏸');
		musicPlaying = true;
	}
};
// Volume toggle
volumeBtn.down = function (x, y, obj) {
	if (musicVolume === 1) {
		musicVolume = 0;
		volumeBtn.setText('🔇');
		if (musicPlaying) {
			LK.playMusic('fallingdown', {
				loop: true,
				fade: {
					start: 1,
					end: 0,
					duration: 300
				}
			});
		}
	} else {
		musicVolume = 1;
		volumeBtn.setText('🔊');
		if (musicPlaying) {
			LK.playMusic('fallingdown', {
				loop: true,
				fade: {
					start: 0,
					end: 1,
					duration: 300
				}
			});
		}
	}
};
// --- Helper functions ---
function getRandomColorIdx() {
	// 0=red, 1=blue, 2=green, 3=yellow
	return Math.floor(Math.random() * 4);
}
function spawnPlatform(y, colorIdx) {
	var plat = new Platform();
	plat.setColor(colorIdx);
	plat.x = 2048 / 2;
	plat.y = y;
	// No horizontal movement; platforms remain centered
	plat.vx = 0;
	platforms.push(plat);
	game.addChild(plat);
	return plat;
}
function spawnCoin(x, y) {
	var c = new Coin();
	c.x = x;
	c.y = y - 60;
	coins.push(c);
	game.addChild(c);
	return c;
}
function resetGameVars() {
	score = 0;
	coinCount = 0;
	// platformSpeed is now calculated dynamically based on score
	gravity = 1.23; // increased initial gravity for even faster ball
	bounceVelocity = -43; // increased initial bounce velocity for even faster ball
	ballVy = 0;
	isFalling = true;
	currentPlatformIdx = 0;
	lastTapTick = 0;
	canTap = true;
	gameStarted = false;
	lastIntersecting = false;
	// Remove old platforms and coins
	for (var i = 0; i < platforms.length; ++i) {
		platforms[i].destroy();
	}
	for (var i = 0; i < coins.length; ++i) {
		coins[i].destroy();
	}
	platforms = [];
	coins = [];
	// Reset lastPlatformY to initial spawn value
	lastPlatformY = 1600;
	// Remove ball
	if (ball) {
		ball.destroy();
		ball = null;
	}
}
// --- Game start ---
function startGame() {
	resetGameVars();
	// Add background image behind all gameplay elements
	var bg = LK.getAsset('background', {
		anchorX: 0,
		anchorY: 0,
		x: 0,
		y: 0,
		width: 2048,
		height: 2732
	});
	game.addChild(bg);
	// Ball and Platforms: fill screen
	// Place first platform lower to give player time to react
	var y = 1600; // Lower on screen for first platform
	var firstPlatformColorIdx = getRandomColorIdx();
	ball = new Ball();
	ball.setColor(firstPlatformColorIdx);
	ball.x = 2048 / 2;
	ball.y = 600;
	game.addChild(ball);
	for (var i = 0; i < 8; ++i) {
		var colorIdx = i === 0 ? firstPlatformColorIdx : getRandomColorIdx();
		var plat = spawnPlatform(y, colorIdx);
		lastPlatformY = plat.y;
		// Place a coin on every 3rd platform
		if (i > 0 && i % 3 === 0) {
			spawnCoin(plat.x, plat.y);
		}
		y += platformSpacing;
	}
	currentPlatformIdx = 0;
	isFalling = true;
	ballVy = 0;
	score = 0;
	coinCount = 0;
	scoreTxt.setText('0');
	coinTxt.setText(storage.coins || 0);
	gameStarted = true;
}
// --- Input: tap to cycle color ---
game.down = function (x, y, obj) {
	if (!gameStarted) {
		startGame();
		return;
	}
	if (!canTap) {
		return;
	}
	canTap = false;
	if (ball) {
		ball.cycleColor();
		ball.flash();
	}
	lastTapTick = LK.ticks;
};
game.up = function (x, y, obj) {
	canTap = true;
};
// --- Main game loop ---
game.update = function () {
	if (!gameStarted) {
		return;
	}
	// Ball physics
	if (isFalling) {
		ballVy += gravity;
		ball.y += ballVy;
	}
	// Move platforms upward to simulate ball falling
	// Platform upward speed: increases slightly at each difficulty level for smooth progression
	var moveUp = 0;
	if (score >= 0) {
		// Base speed (increased even more at first interval)
		moveUp = 6.2;
		// For every 7 points, increase speed by 0.27, up to a max
		var level = Math.floor(score / 7);
		moveUp += Math.min(level * 0.27, 4.5); // max speed increase is 4.5 (so max moveUp = 10.7)
		// Prevent platforms from moving up so fast that the ball can exit the top
		// If the ball is within 200px of the top, pause upward movement
		if (ball && ball.y < 200) {
			moveUp = 0;
		}
	}
	for (var i = 0; i < platforms.length; ++i) {
		platforms[i].y -= moveUp;
		platforms[i].update();
	}
	for (var i = 0; i < coins.length; ++i) {
		coins[i].y -= moveUp;
		coins[i].update();
	}
	// Remove platforms that go off top and immediately recycle/spawn new ones at the bottom to maintain infinite flow
	if (typeof lastPlatformY === "undefined") {
		var lastPlatformY = 1600;
	}
	// Remove platforms that go off top and robustly recycle to always maintain at least 3 platforms below the ball
	for (var i = platforms.length - 1; i >= 0; --i) {
		if (platforms[i].y < -100) {
			// Remove the platform from the game and array
			platforms[i].destroy();
			platforms.splice(i, 1);
		}
	}
	// After removals, ensure at least 3 platforms exist, all spaced consistently below the lowest
	// Find the current lowest platform Y
	var lowestY = -Infinity;
	for (var j = 0; j < platforms.length; ++j) {
		if (platforms[j].y > lowestY) {
			lowestY = platforms[j].y;
		}
	}
	// If there are no platforms left, use the last known platform Y
	if (lowestY === -Infinity) {
		lowestY = lastPlatformY;
	}
	// Always keep at least 3 platforms
	while (platforms.length < 3) {
		var colorIdx = getRandomColorIdx();
		var plat = spawnPlatform(lowestY + platformSpacing, colorIdx);
		lastPlatformY = plat.y;
		lowestY = plat.y;
		// Place a coin on every 3rd platform
		if ((score + platforms.length) % 3 === 0) {
			spawnCoin(plat.x, plat.y);
		}
	}
	for (var i = coins.length - 1; i >= 0; --i) {
		if (coins[i].y < -100) {
			coins[i].destroy();
			coins.splice(i, 1);
		}
	}
	// (Platform count is now managed by recycling above. No need for extra always-ensure loop.)
	// Ball collision with platform (only check nearest platform)
	var hit = false;
	for (var i = 0; i < platforms.length; ++i) {
		var plat = platforms[i];
		// Only check platforms below ball
		if (plat.y > ball.y && plat.y - ball.y < 120) {
			// Check horizontal overlap
			var dx = Math.abs(ball.x - plat.x);
			if (dx < plat.asset.width / 2 - 30) {
				// Check vertical overlap
				var dy = Math.abs(ball.y - plat.y);
				if (dy < 90) {
					// Ball is landing on platform
					hit = true;
					// Only trigger bounce if falling
					if (ballVy > 0) {
						// Color match?
						if (ball.colorIndex === plat.colorIndex) {
							// Success!
							ballVy = bounceVelocity;
							isFalling = true;
							score += 1;
							scoreTxt.setText(score);
							LK.setScore(score);
							LK.getSound('bounce').play();
							// Flash ball
							ball.flash();
							// Remove platform after bounce
							plat.destroy();
							platforms.splice(i, 1);
							// Increase difficulty
							if (score === 3) {
								// After first 3 bounces, increase ball speed (slightly more)
								gravity += 0.18;
								bounceVelocity -= 3.5;
							}
							// Difficulty increases every 7 points, with smooth transitions and more levels
							if (score % 7 === 0) {
								// Level up: increase gravity and platform speed smoothly
								// Add more levels with different increments for smoothness
								if (score < 21) {
									gravity += 0.09;
								} else if (score < 35) {
									gravity += 0.08;
								} else if (score < 56) {
									gravity += 0.07;
								} else if (score < 84) {
									gravity += 0.06;
								} else {
									gravity += 0.05;
								}
							}
						} else {
							// Fail: wrong color
							LK.getSound('fail').play();
							LK.effects.flashScreen(0xff0000, 600);
							LK.showGameOver();
							return;
						}
					}
				}
			}
		}
	}
	// Ball falls off bottom
	if (ball.y > 2732 + 100) {
		LK.getSound('fail').play();
		LK.effects.flashScreen(0xff0000, 600);
		LK.showGameOver();
		return;
	}
	// Coin collection
	for (var i = coins.length - 1; i >= 0; --i) {
		var c = coins[i];
		var dx = Math.abs(ball.x - c.x);
		var dy = Math.abs(ball.y - c.y);
		if (dx < 90 && dy < 90) {
			// Collect coin
			LK.getSound('coin').play();
			c.destroy();
			coins.splice(i, 1);
			coinCount += 1;
			storage.coins = (storage.coins || 0) + 1;
			coinTxt.setText(storage.coins);
			// Simple coin pop
			// (future: animate)
		}
	}
};
// --- Game over / win handling is automatic by LK ---
// --- Start screen: tap to start ---
startGame(); /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	coins: 0,
	unlockedSkins: [],
	selectedSkin: 0
});
/**** 
* Classes
****/ 
// Ball class
var Ball = Container.expand(function () {
	var self = Container.call(this);
	// Ball color index: 0=red, 1=blue, 2=green, 3=yellow
	self.colorIndex = 0;
	self.colors = ['red', 'blue', 'green', 'yellow'];
	self.asset = null;
	// Attach initial asset
	self.setColor = function (idx) {
		if (self.asset) {
			self.removeChild(self.asset);
		}
		self.colorIndex = idx;
		var colorName = self.colors[self.colorIndex];
		self.asset = self.attachAsset('ball_' + colorName, {
			anchorX: 0.5,
			anchorY: 0.5
		});
	};
	self.cycleColor = function () {
		var next = (self.colorIndex + 1) % self.colors.length;
		self.setColor(next);
	};
	// For simple trail effect (MVP: just a quick flash)
	self.flash = function () {
		tween(self.asset, {
			alpha: 0.5
		}, {
			duration: 60,
			easing: tween.linear,
			onFinish: function onFinish() {
				tween(self.asset, {
					alpha: 1
				}, {
					duration: 80
				});
			}
		});
	};
	// Set initial color
	self.setColor(0);
	return self;
});
// Coin class
var Coin = Container.expand(function () {
	var self = Container.call(this);
	self.asset = self.attachAsset('coin', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.update = function () {
		// MVP: no animation
	};
	return self;
});
// Platform class
var Platform = Container.expand(function () {
	var self = Container.call(this);
	// Color index: 0=red, 1=blue, 2=green, 3=yellow
	self.colorIndex = 0;
	self.colors = ['red', 'blue', 'green', 'yellow'];
	self.asset = null;
	// For moving platforms (future)
	self.vx = 0;
	self.setColor = function (idx) {
		if (self.asset) {
			self.removeChild(self.asset);
		}
		self.colorIndex = idx;
		var colorName = self.colors[self.colorIndex];
		self.asset = self.attachAsset('platform_' + colorName, {
			anchorX: 0.5,
			anchorY: 0.5
		});
	};
	// For MVP, platforms are static
	self.update = function () {
		// No horizontal movement; platforms remain centered
	};
	// Set initial color
	self.setColor(0);
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x181c20
});
/**** 
* Game Code
****/ 
// --- Game variables ---
// Ball colors (platforms and ball)
// SFX
// Music (stub, not played in MVP)
var ball = null;
var platforms = [];
var coins = [];
var score = 0;
var coinCount = 0;
var platformSpacing = 400; // vertical distance between platforms
// platformSpeed is now calculated dynamically based on score
var gravity = 1; // increases as score increases (slightly higher)
var bounceVelocity = -31; // initial bounce velocity (slightly higher magnitude)
var ballVy = 0;
var isFalling = true;
var currentPlatformIdx = 0;
var lastTapTick = 0;
var gameStarted = false;
var dragNode = null; // not used, but required by guidelines
var lastIntersecting = false;
var canTap = true; // prevent double tap in one frame
// GUI
var scoreTxt = new Text2('0', {
	size: 120,
	fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
	size: 80,
	fill: '#ffd700'
});
coinTxt.anchor.set(1, 0);
coinTxt.x = -40;
coinTxt.y = 20;
LK.gui.topRight.addChild(coinTxt);
// --- Music Play/Pause and Volume Switch (Top Left) ---
var musicPlaying = true;
var musicVolume = 1; // 1 = max, 0 = muted
// Play music at start
LK.playMusic('fallingdown', {
	loop: true,
	fade: {
		start: 0,
		end: musicVolume,
		duration: 600
	}
});
// Play/Pause Button
var playPauseBtn = new Text2('⏸', {
	size: 90,
	fill: '#fff'
});
playPauseBtn.anchor.set(0, 0);
playPauseBtn.x = 110;
playPauseBtn.y = 10;
// Volume Button
var volumeBtn = new Text2('🔊', {
	size: 90,
	fill: '#fff'
});
volumeBtn.anchor.set(0, 0);
volumeBtn.x = 220;
volumeBtn.y = 10;
// Add to top left GUI (leave 100x100 px clear for menu)
LK.gui.topLeft.addChild(playPauseBtn);
LK.gui.topLeft.addChild(volumeBtn);
// Play/Pause toggle
playPauseBtn.down = function (x, y, obj) {
	if (musicPlaying) {
		LK.stopMusic();
		playPauseBtn.setText('▶️');
		musicPlaying = false;
	} else {
		LK.playMusic('fallingdown', {
			loop: true,
			fade: {
				start: 0,
				end: musicVolume,
				duration: 400
			}
		});
		playPauseBtn.setText('⏸');
		musicPlaying = true;
	}
};
// Volume toggle
volumeBtn.down = function (x, y, obj) {
	if (musicVolume === 1) {
		musicVolume = 0;
		volumeBtn.setText('🔇');
		if (musicPlaying) {
			LK.playMusic('fallingdown', {
				loop: true,
				fade: {
					start: 1,
					end: 0,
					duration: 300
				}
			});
		}
	} else {
		musicVolume = 1;
		volumeBtn.setText('🔊');
		if (musicPlaying) {
			LK.playMusic('fallingdown', {
				loop: true,
				fade: {
					start: 0,
					end: 1,
					duration: 300
				}
			});
		}
	}
};
// --- Helper functions ---
function getRandomColorIdx() {
	// 0=red, 1=blue, 2=green, 3=yellow
	return Math.floor(Math.random() * 4);
}
function spawnPlatform(y, colorIdx) {
	var plat = new Platform();
	plat.setColor(colorIdx);
	plat.x = 2048 / 2;
	plat.y = y;
	// No horizontal movement; platforms remain centered
	plat.vx = 0;
	platforms.push(plat);
	game.addChild(plat);
	return plat;
}
function spawnCoin(x, y) {
	var c = new Coin();
	c.x = x;
	c.y = y - 60;
	coins.push(c);
	game.addChild(c);
	return c;
}
function resetGameVars() {
	score = 0;
	coinCount = 0;
	// platformSpeed is now calculated dynamically based on score
	gravity = 1.23; // increased initial gravity for even faster ball
	bounceVelocity = -43; // increased initial bounce velocity for even faster ball
	ballVy = 0;
	isFalling = true;
	currentPlatformIdx = 0;
	lastTapTick = 0;
	canTap = true;
	gameStarted = false;
	lastIntersecting = false;
	// Remove old platforms and coins
	for (var i = 0; i < platforms.length; ++i) {
		platforms[i].destroy();
	}
	for (var i = 0; i < coins.length; ++i) {
		coins[i].destroy();
	}
	platforms = [];
	coins = [];
	// Reset lastPlatformY to initial spawn value
	lastPlatformY = 1600;
	// Remove ball
	if (ball) {
		ball.destroy();
		ball = null;
	}
}
// --- Game start ---
function startGame() {
	resetGameVars();
	// Add background image behind all gameplay elements
	var bg = LK.getAsset('background', {
		anchorX: 0,
		anchorY: 0,
		x: 0,
		y: 0,
		width: 2048,
		height: 2732
	});
	game.addChild(bg);
	// Ball and Platforms: fill screen
	// Place first platform lower to give player time to react
	var y = 1600; // Lower on screen for first platform
	var firstPlatformColorIdx = getRandomColorIdx();
	ball = new Ball();
	ball.setColor(firstPlatformColorIdx);
	ball.x = 2048 / 2;
	ball.y = 600;
	game.addChild(ball);
	for (var i = 0; i < 8; ++i) {
		var colorIdx = i === 0 ? firstPlatformColorIdx : getRandomColorIdx();
		var plat = spawnPlatform(y, colorIdx);
		lastPlatformY = plat.y;
		// Place a coin on every 3rd platform
		if (i > 0 && i % 3 === 0) {
			spawnCoin(plat.x, plat.y);
		}
		y += platformSpacing;
	}
	currentPlatformIdx = 0;
	isFalling = true;
	ballVy = 0;
	score = 0;
	coinCount = 0;
	scoreTxt.setText('0');
	coinTxt.setText(storage.coins || 0);
	gameStarted = true;
}
// --- Input: tap to cycle color ---
game.down = function (x, y, obj) {
	if (!gameStarted) {
		startGame();
		return;
	}
	if (!canTap) {
		return;
	}
	canTap = false;
	if (ball) {
		ball.cycleColor();
		ball.flash();
	}
	lastTapTick = LK.ticks;
};
game.up = function (x, y, obj) {
	canTap = true;
};
// --- Main game loop ---
game.update = function () {
	if (!gameStarted) {
		return;
	}
	// Ball physics
	if (isFalling) {
		ballVy += gravity;
		ball.y += ballVy;
	}
	// Move platforms upward to simulate ball falling
	// Platform upward speed: increases slightly at each difficulty level for smooth progression
	var moveUp = 0;
	if (score >= 0) {
		// Base speed (increased even more at first interval)
		moveUp = 6.2;
		// For every 7 points, increase speed by 0.27, up to a max
		var level = Math.floor(score / 7);
		moveUp += Math.min(level * 0.27, 4.5); // max speed increase is 4.5 (so max moveUp = 10.7)
		// Prevent platforms from moving up so fast that the ball can exit the top
		// If the ball is within 200px of the top, pause upward movement
		if (ball && ball.y < 200) {
			moveUp = 0;
		}
	}
	for (var i = 0; i < platforms.length; ++i) {
		platforms[i].y -= moveUp;
		platforms[i].update();
	}
	for (var i = 0; i < coins.length; ++i) {
		coins[i].y -= moveUp;
		coins[i].update();
	}
	// Remove platforms that go off top and immediately recycle/spawn new ones at the bottom to maintain infinite flow
	if (typeof lastPlatformY === "undefined") {
		var lastPlatformY = 1600;
	}
	// Remove platforms that go off top and robustly recycle to always maintain at least 3 platforms below the ball
	for (var i = platforms.length - 1; i >= 0; --i) {
		if (platforms[i].y < -100) {
			// Remove the platform from the game and array
			platforms[i].destroy();
			platforms.splice(i, 1);
		}
	}
	// After removals, ensure at least 3 platforms exist, all spaced consistently below the lowest
	// Find the current lowest platform Y
	var lowestY = -Infinity;
	for (var j = 0; j < platforms.length; ++j) {
		if (platforms[j].y > lowestY) {
			lowestY = platforms[j].y;
		}
	}
	// If there are no platforms left, use the last known platform Y
	if (lowestY === -Infinity) {
		lowestY = lastPlatformY;
	}
	// Always keep at least 3 platforms
	while (platforms.length < 3) {
		var colorIdx = getRandomColorIdx();
		var plat = spawnPlatform(lowestY + platformSpacing, colorIdx);
		lastPlatformY = plat.y;
		lowestY = plat.y;
		// Place a coin on every 3rd platform
		if ((score + platforms.length) % 3 === 0) {
			spawnCoin(plat.x, plat.y);
		}
	}
	for (var i = coins.length - 1; i >= 0; --i) {
		if (coins[i].y < -100) {
			coins[i].destroy();
			coins.splice(i, 1);
		}
	}
	// (Platform count is now managed by recycling above. No need for extra always-ensure loop.)
	// Ball collision with platform (only check nearest platform)
	var hit = false;
	for (var i = 0; i < platforms.length; ++i) {
		var plat = platforms[i];
		// Only check platforms below ball
		if (plat.y > ball.y && plat.y - ball.y < 120) {
			// Check horizontal overlap
			var dx = Math.abs(ball.x - plat.x);
			if (dx < plat.asset.width / 2 - 30) {
				// Check vertical overlap
				var dy = Math.abs(ball.y - plat.y);
				if (dy < 90) {
					// Ball is landing on platform
					hit = true;
					// Only trigger bounce if falling
					if (ballVy > 0) {
						// Color match?
						if (ball.colorIndex === plat.colorIndex) {
							// Success!
							ballVy = bounceVelocity;
							isFalling = true;
							score += 1;
							scoreTxt.setText(score);
							LK.setScore(score);
							LK.getSound('bounce').play();
							// Flash ball
							ball.flash();
							// Remove platform after bounce
							plat.destroy();
							platforms.splice(i, 1);
							// Increase difficulty
							if (score === 3) {
								// After first 3 bounces, increase ball speed (slightly more)
								gravity += 0.18;
								bounceVelocity -= 3.5;
							}
							// Difficulty increases every 7 points, with smooth transitions and more levels
							if (score % 7 === 0) {
								// Level up: increase gravity and platform speed smoothly
								// Add more levels with different increments for smoothness
								if (score < 21) {
									gravity += 0.09;
								} else if (score < 35) {
									gravity += 0.08;
								} else if (score < 56) {
									gravity += 0.07;
								} else if (score < 84) {
									gravity += 0.06;
								} else {
									gravity += 0.05;
								}
							}
						} else {
							// Fail: wrong color
							LK.getSound('fail').play();
							LK.effects.flashScreen(0xff0000, 600);
							LK.showGameOver();
							return;
						}
					}
				}
			}
		}
	}
	// Ball falls off bottom
	if (ball.y > 2732 + 100) {
		LK.getSound('fail').play();
		LK.effects.flashScreen(0xff0000, 600);
		LK.showGameOver();
		return;
	}
	// Coin collection
	for (var i = coins.length - 1; i >= 0; --i) {
		var c = coins[i];
		var dx = Math.abs(ball.x - c.x);
		var dy = Math.abs(ball.y - c.y);
		if (dx < 90 && dy < 90) {
			// Collect coin
			LK.getSound('coin').play();
			c.destroy();
			coins.splice(i, 1);
			coinCount += 1;
			storage.coins = (storage.coins || 0) + 1;
			coinTxt.setText(storage.coins);
			// Simple coin pop
			// (future: animate)
		}
	}
};
// --- Game over / win handling is automatic by LK ---
// --- Start screen: tap to start ---
startGame();
:quality(85)/https://cdn.frvr.ai/682cde1ad0b77f9bdbb62cf3.png%3F3) 
 blue ball. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682cde72d0b77f9bdbb62d0c.png%3F3) 
 green ball. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682cdeacd0b77f9bdbb62d28.png%3F3) 
 red ball. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682cdf03d0b77f9bdbb62d3c.png%3F3) 
 yellow ball. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682cdf43d0b77f9bdbb62d47.png%3F3) 
 coin. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682cdf7ad0b77f9bdbb62d5d.png%3F3) 
 blue platform. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682cdfb1d0b77f9bdbb62d72.png%3F3) 
 green platform. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682cdfe5d0b77f9bdbb62d80.png%3F3) 
 red platform. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682ce01bd0b77f9bdbb62d9a.png%3F3) 
 yellow platform. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/682d486cd7fd029a046a0a1b.png%3F3) 
 Create a vertical-scrolling, futuristic game background for a rhythm-based mobile game. The style should be vibrant and glowing, with a deep gradient from dark purple to electric blue. Include subtle abstract shapes like floating geometric particles, soft energy lines, and a faint digital grid. The mood should feel like you're inside a pulsing rhythm tunnel or neon cyber tower. No text, no characters — just atmospheric depth and motion-friendly layers. Must loop seamlessly for vertical scrolling. Style: sleek, minimal, energetic.. In-Game asset. 2d. High contrast. No shadows