/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	highScore: 0
});
/**** 
* Classes
****/ 
var NonPerfectMessage = Container.expand(function () {
	var self = Container.call(this);
	var messageText = new Text2('Bad timing', {
		size: 120,
		fill: 0xFF0000,
		stroke: 0xffffff,
		strokeThickness: 8
	});
	messageText.anchor.set(0.5, 0.5);
	self.addChild(messageText);
	self.alpha = 1;
	self.scale.set(0.1);
	self.lifespan = 60; // 1 second at 60fps
	self.initialized = false;
	self.creationTime = Date.now(); // Track when message was created
	self.init = function () {
		if (self.initialized) {
			return;
		}
		self.initialized = true;
		// First tween: grow from small to large
		tween(self.scale, {
			x: 1.0,
			y: 1.0
		}, {
			duration: 300,
			easing: tween.easeOut,
			onComplete: function onComplete() {
				// Second tween: wait a bit then fade out
				LK.setTimeout(function () {
					tween(self, {
						alpha: 0
					}, {
						duration: 300,
						easing: tween.easeIn,
						onComplete: function onComplete() {
							// Find and remove from perfectMessages array
							var index = perfectMessages.indexOf(self);
							if (index > -1) {
								perfectMessages.splice(index, 1);
							}
							// Remove from game first before destroying
							if (self.parent) {
								self.parent.removeChild(self);
							}
							self.destroy();
						}
					});
				}, 600);
			}
		});
	};
	self.update = function () {
		if (!self.initialized) {
			self.init();
		}
		// Manual animation as fallback if tweens fail
		if (!self.initialized) {
			self.lifespan--;
			if (self.lifespan <= 0) {
				// Find and remove from perfectMessages array
				var index = perfectMessages.indexOf(self);
				if (index > -1) {
					perfectMessages.splice(index, 1);
				}
				self.destroy();
				return;
			}
			// Animation effect
			if (self.lifespan > 45) {
				// Growing phase
				self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
			} else if (self.lifespan < 15) {
				// Fading out phase
				self.alpha = self.lifespan / 15;
			}
		}
	};
	return self;
});
var Palm = Container.expand(function () {
	var self = Container.call(this);
	var palmGraphics = self.attachAsset('palm', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = palmGraphics.width;
	self.height = palmGraphics.height;
	self.isMoving = false;
	self.direction = "left"; // Default direction
	self.lastY = 0;
	self.update = function () {
		// Animation will be handled by tweens
		// This just ensures we have an update method for tracking
	};
	return self;
});
var PerfectMessage = Container.expand(function () {
	var self = Container.call(this);
	var messageText = new Text2('PERFECT! +2!', {
		size: 120,
		fill: 0xFFF000,
		stroke: 0xFF0000,
		strokeThickness: 8
	});
	messageText.anchor.set(0.5, 0.5);
	self.addChild(messageText);
	self.alpha = 1;
	self.scale.set(0.1);
	self.lifespan = 60; // 1 second at 60fps
	self.initialized = false;
	self.creationTime = Date.now(); // Track when message was created
	self.init = function () {
		if (self.initialized) {
			return;
		}
		self.initialized = true;
		// First tween: grow from small to large
		tween(self.scale, {
			x: 1.0,
			y: 1.0
		}, {
			duration: 300,
			easing: tween.easeOut,
			onComplete: function onComplete() {
				// Second tween: wait a bit then fade out
				LK.setTimeout(function () {
					tween(self, {
						alpha: 0
					}, {
						duration: 300,
						easing: tween.easeIn,
						onComplete: function onComplete() {
							// Find and remove from perfectMessages array
							var index = perfectMessages.indexOf(self);
							if (index > -1) {
								perfectMessages.splice(index, 1);
							}
							// Remove from game first before destroying
							if (self.parent) {
								self.parent.removeChild(self);
							}
							self.destroy();
						}
					});
				}, 600);
			}
		});
	};
	self.update = function () {
		if (!self.initialized) {
			self.init();
		}
		// Manual animation as fallback if tweens fail
		if (!self.initialized) {
			self.lifespan--;
			if (self.lifespan <= 0) {
				// Find and remove from perfectMessages array
				var index = perfectMessages.indexOf(self);
				if (index > -1) {
					perfectMessages.splice(index, 1);
				}
				self.destroy();
				return;
			}
			// Animation effect
			if (self.lifespan > 45) {
				// Growing phase
				self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
			} else if (self.lifespan < 15) {
				// Fading out phase
				self.alpha = self.lifespan / 15;
			}
		}
	};
	return self;
});
var PlayerCar = Container.expand(function () {
	var self = Container.call(this);
	var carGraphics = self.attachAsset('playerCar', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = carGraphics.width;
	self.height = carGraphics.height;
	self.speed = 0;
	self.maxSpeed = 15;
	self.steering = 0;
	self.crashed = false;
	self.lastX = 0;
	self.bubble = null;
	self.crash = function () {
		if (!self.crashed) {
			self.crashed = true;
			// 1. First, play the crash sound
			LK.getSound('crash').play();
			// 2. Second, mirror vertically the player Car
			carGraphics.scaleY = -1;
			// Flash effect for visual feedback
			LK.effects.flashObject(self, 0xFF0000, 1000);
			// 3. Stop the game by setting speed to 0
			self.speed = 0;
			// 4. After 2 seconds, show GameOver
			LK.setTimeout(function () {
				LK.showGameOver();
			}, 2000);
		}
	};
	self.update = function () {
		// Save last position for movement detection
		self.lastX = self.x;
		// Handle car physics
		if (!self.crashed) {
			self.speed = Math.min(self.maxSpeed, self.speed + 0.1);
		}
		// Apply steering if not crashed
		if (!self.crashed) {
			self.x += self.steering;
		}
		// Keep car within bounds
		if (self.x < self.width / 2) {
			self.x = self.width / 2;
		}
		if (self.x > 2048 - self.width / 2) {
			self.x = 2048 - self.width / 2;
		}
		// Check if car has moved horizontally and has a bubble
		if (Math.abs(self.x - self.lastX) > 0.1 && self.bubble && self.bubble.parent) {
			// Remove bubble immediately when car moves
			if (self.bubble.parent) {
				self.bubble.parent.removeChild(self.bubble);
			}
			self.bubble.destroy();
			self.bubble = null;
		}
	};
	return self;
});
var RoadLine = Container.expand(function () {
	var self = Container.call(this);
	var lineGraphics = self.attachAsset('bullet', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = lineGraphics.width;
	self.height = lineGraphics.height;
	self.isMoving = false;
	self.lastY = 0;
	self.update = function () {
		// Animation will be handled by tweens
		// This just ensures we have an update method for tracking
	};
	return self;
});
// RoadLine class removed as requested
var TrafficCar = Container.expand(function () {
	var self = Container.call(this);
	// Randomly select one of the available traffic car assets
	var carAssets = ['trafficCar', 'trafficCar2', 'trafficCar3', 'trafficCar4'];
	var selectedCarAsset = carAssets[Math.floor(Math.random() * carAssets.length)];
	self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
	var carGraphics = self.attachAsset(selectedCarAsset, {
		anchorX: 0.5,
		anchorY: 0.5,
		x: self.direction == -1 ? 0 : 200
	});
	self.width = carGraphics.width;
	self.height = carGraphics.height;
	self.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
	self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
	self.startY = 1300; // Starting Y position (further up the screen)
	self.targetY = 2732 + self.height; // Y position when fully off-screen (bottom)
	self.initialScale = 0.1;
	self.targetScale = 2.0;
	// Set target X position in one of the specified ranges
	self.targetX = Math.random() > 0.5 ? Math.random() * 500 + 2000 : Math.random() * 100;
	// Set initial scale
	self.scale.set(self.initialScale);
	self.update = function () {
		// Calculate progress towards target Y
		var progress = (self.y - self.startY) / (self.targetY - self.startY);
		progress = Math.max(0, Math.min(1, progress)); // Clamp between 0 and 1
		// Interpolate scale based on progress
		var currentScale = self.initialScale + (self.targetScale - self.initialScale) * progress;
		self.scale.set(currentScale);
		// Calculate X position based on progress (linear interpolation)
		var startX = 2048 / 2; // Center of screen
		self.x = startX + (self.targetX - startX) * progress;
		// Apply level-based speed increase
		self.speed += progress * level * SPEED_LEVEL_FACTOR;
		// Update Y position with speed that increases as the car grows
		self.y += self.speed;
		// Destroy if off screen
		if (self.y > self.targetY) {
			// Check against targetY
			// Find and remove self from trafficCars array
			var index = trafficCars.indexOf(self);
			if (index > -1) {
				trafficCars.splice(index, 1);
				// Check for perfect timing - if honk was within 1 second (1000ms) before or after car disappears
				var currentTime = Date.now();
				var timeDifference = Math.abs(currentTime - lastHonkTime);
				if (timeDifference <= 500 * 2 / Math.min(3, level)) {
					// Perfect timing! Add 2 extra points
					LK.setScore(LK.getScore() + 3); // +1 for normal disappearance, +2 for perfect timing
					// Create and display perfect message
					var perfectMsg = new PerfectMessage();
					perfectMsg.x = centerX;
					perfectMsg.y = centerY;
					perfectMsg.creationTime = Date.now(); // Set creation time
					game.addChild(perfectMsg);
					perfectMessages.push(perfectMsg);
					// Explicitly call init to ensure animation starts immediately
					perfectMsg.init();
				} else if (timeDifference > 500 * 2 / Math.min(3, level) && lastHonkTime > 0 && lastHonkTime > currentTime - 3000) {
					// Bad timing! Show a non-perfect message only if honk was played AFTER this car crossed
					// (checking if honk happened within last 3 seconds to ensure it relates to this car)
					var nonPerfectMsg = new NonPerfectMessage();
					nonPerfectMsg.x = centerX;
					nonPerfectMsg.y = centerY;
					nonPerfectMsg.creationTime = Date.now(); // Set creation time
					game.addChild(nonPerfectMsg);
					perfectMessages.push(nonPerfectMsg); // Add to the same array for cleanup
					// Explicitly call init to ensure animation starts immediately
					nonPerfectMsg.init();
					// Normal score increment
					LK.setScore(LK.getScore() + 1);
				} else {
					// Normal score increment
					LK.setScore(LK.getScore() + 1);
				}
				scoreText.setText(LK.getScore().toString());
				scoreText.setStyle({
					tint: 0xffffff,
					stroke: 0xffffff,
					strokeThickness: 5
				});
				// List of available sounds excluding crash
				var sounds = ['blind', 'getOff', 'holdingTraffic', 'learnToDrive', 'license', 'moveOver', 'speedUp', 'whatAreYouDoing'];
				// Get current last played sound from game variable or initialize it
				var lastPlayedSound = game.lastPlayedSound || '';
				// Filter out the last played sound if it exists
				var availableSounds = sounds.filter(function (sound) {
					return sound !== lastPlayedSound;
				});
				// Select a random sound from the filtered list
				var randomSound = availableSounds[Math.floor(Math.random() * availableSounds.length)];
				// Store the selected sound as the last played sound
				game.lastPlayedSound = randomSound;
				// Play the selected sound
				LK.getSound(randomSound).play();
				// Check if player already has a bubble
				if (player.bubble && player.bubble.parent) {
					player.bubble.parent.removeChild(player.bubble);
					player.bubble.destroy();
				}
				// Show bubble over player car
				var bubble = LK.getAsset('bubble', {
					anchorX: 0.5,
					anchorY: 1.0,
					x: player.x,
					y: player.y - player.height / 2
				});
				game.addChild(bubble);
				bubble.scale.set(0);
				// Store reference to the bubble in player object
				player.bubble = bubble;
				// Store creation time for auto-cleanup
				bubble.creationTime = Date.now();
				// Animate the bubble appearing and disappearing
				tween(bubble.scale, {
					x: 0.8,
					y: 0.8
				}, {
					duration: 200,
					easing: tween.easeOut,
					onComplete: function onComplete() {
						// Wait for 0.3 seconds then fade out (this happens if no movement removed it)
						LK.setTimeout(function () {
							// Check if bubble still exists and hasn't been removed by movement
							if (bubble && bubble.parent) {
								tween(bubble, {
									alpha: 0
								}, {
									duration: 200,
									easing: tween.easeIn,
									onComplete: function onComplete() {
										if (bubble.parent) {
											bubble.parent.removeChild(bubble);
										}
										bubble.destroy();
										// Clear reference in player
										if (player.bubble === bubble) {
											player.bubble = null;
										}
									}
								});
							}
						}, 800); // Increased to ensure bubble shows for 1 second total
					}
				});
			}
			self.destroy();
		}
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x000000
});
/**** 
* Game Code
****/ 
// Game state variables
var gameStarted = false;
var gameTime = 30; // Initial time in seconds
var distance = 0;
var level = 1;
var checkpointDistance = 500; // Distance between checkpoints
var nextCheckpointAt = checkpointDistance;
var trafficCars = [];
var obstacles = [];
var checkpoints = [];
var timeBonuses = [];
var difficultyTimer;
var lastHonkTime = 0; // Track when player last honked
var perfectMessages = []; // Array to store perfect timing messages
// Create container groups
var palmGroup = new Container();
var roadLinesGroup = new Container();
var SPEED_LEVEL_FACTOR = 3;
// Create road lines
var ROAD_LINE_COUNT = 15;
var roadLines = [];
var BASE_ROAD_LINE_COOLDOWN = 20; // Base spawn cooldown
var roadLineSpawnCooldown = 20; // Spawn a road line every 0.33 seconds if needed
var roadLineSpawnTimer = 0; // Timer to track cooldown
// Create palm trees
var PALM_COUNT = 40;
var palms = [];
var centerX = 2048 / 2; // Center of screen X
var centerY = 2732 / 2; // Center of screen Y
var BASE_PALM_COOLDOWN = 10; // Base spawn cooldown
var palmSpawnCooldown = 10; // Spawn a palm every 0.5 seconds if needed
var palmSpawnTimer = 0; // Timer to track cooldown
var lastSpawnSide = "right"; // Track the last side a palm was spawned on
// Sway parameters
var swayCounter = 0;
// Create road
var road = LK.getAsset('road', {
	anchorX: 0.5,
	anchorY: 0.5,
	x: 2048 / 2 + 50,
	y: 2732 / 2 - 100
	// y: 1000
});
game.addChild(road);
game.addChild(palmGroup);
game.addChild(roadLinesGroup);
// Road lines removed as requested
// Create player car
var player = new PlayerCar();
player.x = 2048 / 2;
player.y = 2732 - 300;
game.addChild(player);
var scoreLabel = new Text2('Score:', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
scoreLabel.anchor.set(0, 0);
scoreLabel.x = -150;
LK.gui.top.addChild(scoreLabel);
var scoreText = new Text2('0', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
scoreText.anchor.set(0, 0);
scoreText.x = 100;
LK.gui.top.addChild(scoreText);
var levelLabel = new Text2('Level:', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
levelLabel.anchor.set(0, 0);
levelLabel.x = -150;
levelLabel.y = 100; // Offset below time
LK.gui.top.addChild(levelLabel);
var levelText = new Text2('1', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
levelText.anchor.set(0, 0);
levelText.x = 100;
levelText.y = 100; // Offset below time
LK.gui.top.addChild(levelText);
var startText = new Text2('TAP TO START\nTAP TO 🔊\nPERFECTLY TIMED 🔊 = +2', {
	size: 120,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 8,
	align: 'center'
});
startText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startText);
// Functions to spawn game elements
function spawnTrafficCar() {
	// Only spawn a new car if there are no cars on screen
	if (trafficCars.length === 0) {
		var car = new TrafficCar();
		car.x = 2048 / 2; // Start in the middle of the screen
		car.y = car.startY; // Start at the defined startY
		car.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
		trafficCars.push(car);
		game.addChild(car);
	}
	// Schedule next check for spawning with faster spawns at higher levels
	var nextSpawnTime = Math.max(200, 1000 / level); // Faster spawns at higher levels
	LK.setTimeout(spawnTrafficCar, nextSpawnTime);
}
// Start game function
function startGame() {
	gameStarted = true;
	gameTime = 30;
	distance = 0;
	level = 1; // Initial level starts at 1
	nextCheckpointAt = checkpointDistance;
	LK.setScore(0);
	// Initialize road lines with pre-animated positions as if they had already been running for 10 seconds
	roadLines = [];
	for (var i = 0; i < roadLinesGroup.children.length; i++) {
		roadLinesGroup.children[i].destroy();
	}
	roadLinesGroup.removeChildren();
	roadLineSpawnTimer = 0;
	// Create pre-existing road lines as if they were already running for 10 seconds
	for (var i = 0; i < ROAD_LINE_COUNT; i++) {
		var newRoadLine = new RoadLine();
		// Calculate a position along the animation path as if it had been running
		// Progress ranges from 0 (just started) to 1 (almost finished)
		var progress = i / ROAD_LINE_COUNT;
		// Start at center X and interpolate Y based on progress
		newRoadLine.x = centerX + 15;
		// Calculate Y position - from center-50 to beyond bottom of screen
		var startY = centerY - 50;
		var targetY = 2732 + 100;
		newRoadLine.y = startY + (targetY - startY) * progress;
		// Calculate scale based on progress (0.1 to 1.0)
		var startScale = 0.1;
		var endScale = 1.0;
		var currentScale = startScale + (endScale - startScale) * progress;
		newRoadLine.scale.set(currentScale);
		newRoadLine.lastY = newRoadLine.y;
		newRoadLine.isMoving = true;
		// Only add the road line and start animation if it's still on screen
		if (newRoadLine.y < 2732) {
			// Create tween animation with perspective effect - already in progress
			var remainingDuration = 4000 * (1 - progress) / level;
			tween(newRoadLine, {
				y: targetY,
				scaleX: endScale,
				scaleY: endScale
			}, {
				duration: remainingDuration,
				easing: tween.easeIn
			});
			roadLines.push(newRoadLine);
			roadLinesGroup.addChild(newRoadLine);
		} else {
			newRoadLine.destroy();
		}
	}
	// Update UI
	scoreText.setText('0');
	levelText.setText(level.toString());
	levelText.setStyle({
		tint: 0xffffff,
		stroke: 0xffffff,
		strokeThickness: 5
	});
	// Remove start text
	LK.gui.center.removeChild(startText);
	// Start spawning game elements
	spawnTrafficCar();
	// Level is now calculated based on score/10+1, no need for a timer to increase it
	// Play background music
	LK.playMusic('bgmusic');
	// Palm spawning is handled in the update function
}
// Handle touch input
game.down = function (x, y) {
	// Only play honk sound if game has started
	if (!gameStarted) {
		startGame();
		return;
	}
	// Play honk sound when screen is clicked/tapped after game has started
	LK.getSound('honk').play();
	// Record the time of honk for perfect timing detection
	lastHonkTime = Date.now();
	// Remove any existing bubble when player honks
	if (player.bubble && player.bubble.parent) {
		player.bubble.parent.removeChild(player.bubble);
		player.bubble.destroy();
		player.bubble = null;
	}
};
game.move = function (x, y) {
	if (!gameStarted) {
		return;
	}
	// Move player car directly to mouse X position
	var targetX = x;
	// Apply smooth movement by calculating the difference
	var deltaX = targetX - player.x;
	player.steering = deltaX * 0.1; // Smooth movement factor
};
game.up = function () {
	if (gameStarted) {
		// No need to reset steering as we'll continue to follow mouse
	}
};
// Main game update function
game.update = function () {
	if (!gameStarted) {
		return;
	}
	// Update road lines
	if (gameStarted) {
		// Update road line animations
		for (var r = roadLines.length - 1; r >= 0; r--) {
			var roadLine = roadLines[r];
			// Check if roadLine exists before accessing its properties
			if (roadLine) {
				roadLine.lastY = roadLine.y;
				// Start a new roadLine animation if roadLine is not already moving
				if (roadLine.isMoving === false) {
					roadLine.isMoving = true;
					// Set target position - straight down the screen
					var targetY = 2732 + 100; // Below bottom of screen
					var targetScale = 1.0; // End larger
					// Create tween animation with perspective effect
					tween(roadLine, {
						y: targetY,
						scaleX: targetScale,
						scaleY: targetScale
					}, {
						duration: 4000 / level,
						easing: tween.easeIn
					});
				}
				// Check if roadLine has moved significantly past the bottom of the screen and destroy it
				var destroyYPosition = 2732 - 300; // Check if center is below the bottom edge
				if (roadLine.isMoving && roadLine.y > destroyYPosition) {
					// RoadLine is off-screen, destroy it
					var index = roadLines.indexOf(roadLine);
					if (index > -1) {
						roadLinesGroup.removeChild(roadLine);
						roadLines.splice(index, 1);
						roadLine.destroy();
					}
					// Skip to next roadLine as this one is destroyed
					continue;
				}
			}
		} // End of loop processing existing road lines
		// Calculate spawn cooldown based on level
		roadLineSpawnCooldown = Math.max(5, BASE_ROAD_LINE_COOLDOWN / level);
		// Update road line spawn timer and generate new road lines if needed
		roadLineSpawnTimer -= 1;
		if (roadLineSpawnTimer <= 0 && roadLines.length < ROAD_LINE_COUNT) {
			roadLineSpawnTimer = roadLineSpawnCooldown; // Reset timer
			var newRoadLine = new RoadLine();
			// Start at center X and at center Y
			newRoadLine.x = centerX + 15;
			newRoadLine.y = centerY - 50;
			newRoadLine.scale.set(0.1); // Start small
			newRoadLine.lastY = newRoadLine.y;
			newRoadLine.isMoving = false; // Flag to track if road line is currently animating
			roadLines.push(newRoadLine);
			roadLinesGroup.addChild(newRoadLine);
		}
	}
	// Road lines removed as requested
	// Update game time
	gameTime -= 1 / 60; // Assuming 60 FPS
	// Calculate level based on score/10, but minimum of 1
	level = Math.floor(LK.getScore() / 10) + 1;
	levelText.setText(level.toString());
	// Update palm animations section
	if (gameStarted) {
		distance += 10 + level;
		// Don't update score based on distance anymore
		// Keep updating the distance for internal calculations if needed
		// Update palm animations
		for (var p = palms.length - 1; p >= 0; p--) {
			var palm = palms[p];
			// Check if palm exists before accessing its properties
			if (palm) {
				palm.lastY = palm.y;
				// Start a new palm animation if palm is not already moving
				if (palm.isMoving === false) {
					palm.isMoving = true;
					// Set target position based on direction (left or right corner)
					var targetX = palm.direction === "left" ? -12000 : 14500; // Left or right edge
					var targetY = 2732 + 100; // Below bottom of screen
					var targetScale = 2.0; // End larger
					// Create tween animation with perspective effect - use same duration for all palms
					tween(palm, {
						x: targetX,
						y: targetY,
						scaleX: targetScale * (lastSpawnSide === "right" ? 1 : -1),
						scaleY: targetScale
					}, {
						duration: 7000 / level,
						// Duration decreases as level increases
						easing: tween.easeIn // Removed onFinish callback
					});
				}
				// Check if palm has moved significantly past the bottom of the screen and destroy it
				var destroyYPosition = 2500; // Check if center is 150px below the bottom edge
				if (palm.isMoving && palm.y > destroyYPosition) {
					// Palm is off-screen, destroy it
					var index = palms.indexOf(palm);
					if (index > -1) {
						palmGroup.removeChild(palm);
						palms.splice(index, 1);
						palm.destroy();
					}
					// Skip to next palm as this one is destroyed
					continue;
				}
			}
		} // End of loop processing existing palms
		// Calculate palm spawn cooldown based on level
		palmSpawnCooldown = Math.max(3, BASE_PALM_COOLDOWN / level);
		// Update palm spawn timer and generate new palms if needed
		palmSpawnTimer -= 1;
		if (palmSpawnTimer <= 0 && palms.length < PALM_COUNT) {
			palmSpawnTimer = palmSpawnCooldown; // Reset timer
			var newPalm = new Palm();
			// Alternate spawn side based on the last spawned side
			lastSpawnSide = lastSpawnSide === "right" ? "left" : "right";
			// Start from exact center point
			newPalm.x = centerX + (lastSpawnSide === "right" ? 50 : -50);
			newPalm.y = centerY - 100; // Start at center
			newPalm.scale.set(0.005); // Start small
			newPalm.scaleX *= lastSpawnSide === "right" ? 1 : -1;
			newPalm.lastY = newPalm.y;
			newPalm.direction = lastSpawnSide; // Use the determined side
			newPalm.isMoving = false; // Flag to track if palm is currently animating
			palms.push(newPalm);
			palmGroup.addChild(newPalm);
		}
	}
	// Game will continue indefinitely - removed game over condition
	gameTime = Math.max(gameTime, 0.1); // Keep time from reaching zero
	// Update perfect messages
	for (var m = perfectMessages.length - 1; m >= 0; m--) {
		perfectMessages[m].update();
	}
	// Check for old perfect and non-perfect messages every 2 seconds
	if (LK.ticks % 60 === 0) {
		var currentTime = Date.now();
		for (var i = perfectMessages.length - 1; i >= 0; i--) {
			// If message is older than 2 seconds and still exists
			if (perfectMessages[i] && perfectMessages[i].creationTime && currentTime - perfectMessages[i].creationTime > 500) {
				// Remove from game if it's still attached
				if (perfectMessages[i].parent) {
					perfectMessages[i].parent.removeChild(perfectMessages[i]);
				}
				// Destroy and remove from array
				perfectMessages[i].destroy();
				perfectMessages.splice(i, 1);
			}
		}
		// Check for speech bubble timing
		if (player.bubble && player.bubble.creationTime && currentTime - player.bubble.creationTime > 1000) {
			// Remove bubble if it's been showing for more than 1 second
			if (player.bubble.parent) {
				player.bubble.parent.removeChild(player.bubble);
			}
			player.bubble.destroy();
			player.bubble = null;
		}
	}
	// Check collisions with traffic cars
	for (var i = trafficCars.length - 1; i >= 0; i--) {
		var car = trafficCars[i];
		if (player.intersects(car) && !player.crashed) {
			player.crash();
			trafficCars.splice(i, 1);
			car.destroy();
			// GameOver will be called after the crash sequence
		}
	}
};
function endGame() {
	if (!gameStarted) {
		return;
	}
	// Just save high score without ending the game
	if (LK.getScore() > storage.highScore) {
		storage.highScore = LK.getScore();
	}
	// Game continues - no game over screen
} /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	highScore: 0
});
/**** 
* Classes
****/ 
var NonPerfectMessage = Container.expand(function () {
	var self = Container.call(this);
	var messageText = new Text2('Bad timing', {
		size: 120,
		fill: 0xFF0000,
		stroke: 0xffffff,
		strokeThickness: 8
	});
	messageText.anchor.set(0.5, 0.5);
	self.addChild(messageText);
	self.alpha = 1;
	self.scale.set(0.1);
	self.lifespan = 60; // 1 second at 60fps
	self.initialized = false;
	self.creationTime = Date.now(); // Track when message was created
	self.init = function () {
		if (self.initialized) {
			return;
		}
		self.initialized = true;
		// First tween: grow from small to large
		tween(self.scale, {
			x: 1.0,
			y: 1.0
		}, {
			duration: 300,
			easing: tween.easeOut,
			onComplete: function onComplete() {
				// Second tween: wait a bit then fade out
				LK.setTimeout(function () {
					tween(self, {
						alpha: 0
					}, {
						duration: 300,
						easing: tween.easeIn,
						onComplete: function onComplete() {
							// Find and remove from perfectMessages array
							var index = perfectMessages.indexOf(self);
							if (index > -1) {
								perfectMessages.splice(index, 1);
							}
							// Remove from game first before destroying
							if (self.parent) {
								self.parent.removeChild(self);
							}
							self.destroy();
						}
					});
				}, 600);
			}
		});
	};
	self.update = function () {
		if (!self.initialized) {
			self.init();
		}
		// Manual animation as fallback if tweens fail
		if (!self.initialized) {
			self.lifespan--;
			if (self.lifespan <= 0) {
				// Find and remove from perfectMessages array
				var index = perfectMessages.indexOf(self);
				if (index > -1) {
					perfectMessages.splice(index, 1);
				}
				self.destroy();
				return;
			}
			// Animation effect
			if (self.lifespan > 45) {
				// Growing phase
				self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
			} else if (self.lifespan < 15) {
				// Fading out phase
				self.alpha = self.lifespan / 15;
			}
		}
	};
	return self;
});
var Palm = Container.expand(function () {
	var self = Container.call(this);
	var palmGraphics = self.attachAsset('palm', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = palmGraphics.width;
	self.height = palmGraphics.height;
	self.isMoving = false;
	self.direction = "left"; // Default direction
	self.lastY = 0;
	self.update = function () {
		// Animation will be handled by tweens
		// This just ensures we have an update method for tracking
	};
	return self;
});
var PerfectMessage = Container.expand(function () {
	var self = Container.call(this);
	var messageText = new Text2('PERFECT! +2!', {
		size: 120,
		fill: 0xFFF000,
		stroke: 0xFF0000,
		strokeThickness: 8
	});
	messageText.anchor.set(0.5, 0.5);
	self.addChild(messageText);
	self.alpha = 1;
	self.scale.set(0.1);
	self.lifespan = 60; // 1 second at 60fps
	self.initialized = false;
	self.creationTime = Date.now(); // Track when message was created
	self.init = function () {
		if (self.initialized) {
			return;
		}
		self.initialized = true;
		// First tween: grow from small to large
		tween(self.scale, {
			x: 1.0,
			y: 1.0
		}, {
			duration: 300,
			easing: tween.easeOut,
			onComplete: function onComplete() {
				// Second tween: wait a bit then fade out
				LK.setTimeout(function () {
					tween(self, {
						alpha: 0
					}, {
						duration: 300,
						easing: tween.easeIn,
						onComplete: function onComplete() {
							// Find and remove from perfectMessages array
							var index = perfectMessages.indexOf(self);
							if (index > -1) {
								perfectMessages.splice(index, 1);
							}
							// Remove from game first before destroying
							if (self.parent) {
								self.parent.removeChild(self);
							}
							self.destroy();
						}
					});
				}, 600);
			}
		});
	};
	self.update = function () {
		if (!self.initialized) {
			self.init();
		}
		// Manual animation as fallback if tweens fail
		if (!self.initialized) {
			self.lifespan--;
			if (self.lifespan <= 0) {
				// Find and remove from perfectMessages array
				var index = perfectMessages.indexOf(self);
				if (index > -1) {
					perfectMessages.splice(index, 1);
				}
				self.destroy();
				return;
			}
			// Animation effect
			if (self.lifespan > 45) {
				// Growing phase
				self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
			} else if (self.lifespan < 15) {
				// Fading out phase
				self.alpha = self.lifespan / 15;
			}
		}
	};
	return self;
});
var PlayerCar = Container.expand(function () {
	var self = Container.call(this);
	var carGraphics = self.attachAsset('playerCar', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = carGraphics.width;
	self.height = carGraphics.height;
	self.speed = 0;
	self.maxSpeed = 15;
	self.steering = 0;
	self.crashed = false;
	self.lastX = 0;
	self.bubble = null;
	self.crash = function () {
		if (!self.crashed) {
			self.crashed = true;
			// 1. First, play the crash sound
			LK.getSound('crash').play();
			// 2. Second, mirror vertically the player Car
			carGraphics.scaleY = -1;
			// Flash effect for visual feedback
			LK.effects.flashObject(self, 0xFF0000, 1000);
			// 3. Stop the game by setting speed to 0
			self.speed = 0;
			// 4. After 2 seconds, show GameOver
			LK.setTimeout(function () {
				LK.showGameOver();
			}, 2000);
		}
	};
	self.update = function () {
		// Save last position for movement detection
		self.lastX = self.x;
		// Handle car physics
		if (!self.crashed) {
			self.speed = Math.min(self.maxSpeed, self.speed + 0.1);
		}
		// Apply steering if not crashed
		if (!self.crashed) {
			self.x += self.steering;
		}
		// Keep car within bounds
		if (self.x < self.width / 2) {
			self.x = self.width / 2;
		}
		if (self.x > 2048 - self.width / 2) {
			self.x = 2048 - self.width / 2;
		}
		// Check if car has moved horizontally and has a bubble
		if (Math.abs(self.x - self.lastX) > 0.1 && self.bubble && self.bubble.parent) {
			// Remove bubble immediately when car moves
			if (self.bubble.parent) {
				self.bubble.parent.removeChild(self.bubble);
			}
			self.bubble.destroy();
			self.bubble = null;
		}
	};
	return self;
});
var RoadLine = Container.expand(function () {
	var self = Container.call(this);
	var lineGraphics = self.attachAsset('bullet', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = lineGraphics.width;
	self.height = lineGraphics.height;
	self.isMoving = false;
	self.lastY = 0;
	self.update = function () {
		// Animation will be handled by tweens
		// This just ensures we have an update method for tracking
	};
	return self;
});
// RoadLine class removed as requested
var TrafficCar = Container.expand(function () {
	var self = Container.call(this);
	// Randomly select one of the available traffic car assets
	var carAssets = ['trafficCar', 'trafficCar2', 'trafficCar3', 'trafficCar4'];
	var selectedCarAsset = carAssets[Math.floor(Math.random() * carAssets.length)];
	self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
	var carGraphics = self.attachAsset(selectedCarAsset, {
		anchorX: 0.5,
		anchorY: 0.5,
		x: self.direction == -1 ? 0 : 200
	});
	self.width = carGraphics.width;
	self.height = carGraphics.height;
	self.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
	self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
	self.startY = 1300; // Starting Y position (further up the screen)
	self.targetY = 2732 + self.height; // Y position when fully off-screen (bottom)
	self.initialScale = 0.1;
	self.targetScale = 2.0;
	// Set target X position in one of the specified ranges
	self.targetX = Math.random() > 0.5 ? Math.random() * 500 + 2000 : Math.random() * 100;
	// Set initial scale
	self.scale.set(self.initialScale);
	self.update = function () {
		// Calculate progress towards target Y
		var progress = (self.y - self.startY) / (self.targetY - self.startY);
		progress = Math.max(0, Math.min(1, progress)); // Clamp between 0 and 1
		// Interpolate scale based on progress
		var currentScale = self.initialScale + (self.targetScale - self.initialScale) * progress;
		self.scale.set(currentScale);
		// Calculate X position based on progress (linear interpolation)
		var startX = 2048 / 2; // Center of screen
		self.x = startX + (self.targetX - startX) * progress;
		// Apply level-based speed increase
		self.speed += progress * level * SPEED_LEVEL_FACTOR;
		// Update Y position with speed that increases as the car grows
		self.y += self.speed;
		// Destroy if off screen
		if (self.y > self.targetY) {
			// Check against targetY
			// Find and remove self from trafficCars array
			var index = trafficCars.indexOf(self);
			if (index > -1) {
				trafficCars.splice(index, 1);
				// Check for perfect timing - if honk was within 1 second (1000ms) before or after car disappears
				var currentTime = Date.now();
				var timeDifference = Math.abs(currentTime - lastHonkTime);
				if (timeDifference <= 500 * 2 / Math.min(3, level)) {
					// Perfect timing! Add 2 extra points
					LK.setScore(LK.getScore() + 3); // +1 for normal disappearance, +2 for perfect timing
					// Create and display perfect message
					var perfectMsg = new PerfectMessage();
					perfectMsg.x = centerX;
					perfectMsg.y = centerY;
					perfectMsg.creationTime = Date.now(); // Set creation time
					game.addChild(perfectMsg);
					perfectMessages.push(perfectMsg);
					// Explicitly call init to ensure animation starts immediately
					perfectMsg.init();
				} else if (timeDifference > 500 * 2 / Math.min(3, level) && lastHonkTime > 0 && lastHonkTime > currentTime - 3000) {
					// Bad timing! Show a non-perfect message only if honk was played AFTER this car crossed
					// (checking if honk happened within last 3 seconds to ensure it relates to this car)
					var nonPerfectMsg = new NonPerfectMessage();
					nonPerfectMsg.x = centerX;
					nonPerfectMsg.y = centerY;
					nonPerfectMsg.creationTime = Date.now(); // Set creation time
					game.addChild(nonPerfectMsg);
					perfectMessages.push(nonPerfectMsg); // Add to the same array for cleanup
					// Explicitly call init to ensure animation starts immediately
					nonPerfectMsg.init();
					// Normal score increment
					LK.setScore(LK.getScore() + 1);
				} else {
					// Normal score increment
					LK.setScore(LK.getScore() + 1);
				}
				scoreText.setText(LK.getScore().toString());
				scoreText.setStyle({
					tint: 0xffffff,
					stroke: 0xffffff,
					strokeThickness: 5
				});
				// List of available sounds excluding crash
				var sounds = ['blind', 'getOff', 'holdingTraffic', 'learnToDrive', 'license', 'moveOver', 'speedUp', 'whatAreYouDoing'];
				// Get current last played sound from game variable or initialize it
				var lastPlayedSound = game.lastPlayedSound || '';
				// Filter out the last played sound if it exists
				var availableSounds = sounds.filter(function (sound) {
					return sound !== lastPlayedSound;
				});
				// Select a random sound from the filtered list
				var randomSound = availableSounds[Math.floor(Math.random() * availableSounds.length)];
				// Store the selected sound as the last played sound
				game.lastPlayedSound = randomSound;
				// Play the selected sound
				LK.getSound(randomSound).play();
				// Check if player already has a bubble
				if (player.bubble && player.bubble.parent) {
					player.bubble.parent.removeChild(player.bubble);
					player.bubble.destroy();
				}
				// Show bubble over player car
				var bubble = LK.getAsset('bubble', {
					anchorX: 0.5,
					anchorY: 1.0,
					x: player.x,
					y: player.y - player.height / 2
				});
				game.addChild(bubble);
				bubble.scale.set(0);
				// Store reference to the bubble in player object
				player.bubble = bubble;
				// Store creation time for auto-cleanup
				bubble.creationTime = Date.now();
				// Animate the bubble appearing and disappearing
				tween(bubble.scale, {
					x: 0.8,
					y: 0.8
				}, {
					duration: 200,
					easing: tween.easeOut,
					onComplete: function onComplete() {
						// Wait for 0.3 seconds then fade out (this happens if no movement removed it)
						LK.setTimeout(function () {
							// Check if bubble still exists and hasn't been removed by movement
							if (bubble && bubble.parent) {
								tween(bubble, {
									alpha: 0
								}, {
									duration: 200,
									easing: tween.easeIn,
									onComplete: function onComplete() {
										if (bubble.parent) {
											bubble.parent.removeChild(bubble);
										}
										bubble.destroy();
										// Clear reference in player
										if (player.bubble === bubble) {
											player.bubble = null;
										}
									}
								});
							}
						}, 800); // Increased to ensure bubble shows for 1 second total
					}
				});
			}
			self.destroy();
		}
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x000000
});
/**** 
* Game Code
****/ 
// Game state variables
var gameStarted = false;
var gameTime = 30; // Initial time in seconds
var distance = 0;
var level = 1;
var checkpointDistance = 500; // Distance between checkpoints
var nextCheckpointAt = checkpointDistance;
var trafficCars = [];
var obstacles = [];
var checkpoints = [];
var timeBonuses = [];
var difficultyTimer;
var lastHonkTime = 0; // Track when player last honked
var perfectMessages = []; // Array to store perfect timing messages
// Create container groups
var palmGroup = new Container();
var roadLinesGroup = new Container();
var SPEED_LEVEL_FACTOR = 3;
// Create road lines
var ROAD_LINE_COUNT = 15;
var roadLines = [];
var BASE_ROAD_LINE_COOLDOWN = 20; // Base spawn cooldown
var roadLineSpawnCooldown = 20; // Spawn a road line every 0.33 seconds if needed
var roadLineSpawnTimer = 0; // Timer to track cooldown
// Create palm trees
var PALM_COUNT = 40;
var palms = [];
var centerX = 2048 / 2; // Center of screen X
var centerY = 2732 / 2; // Center of screen Y
var BASE_PALM_COOLDOWN = 10; // Base spawn cooldown
var palmSpawnCooldown = 10; // Spawn a palm every 0.5 seconds if needed
var palmSpawnTimer = 0; // Timer to track cooldown
var lastSpawnSide = "right"; // Track the last side a palm was spawned on
// Sway parameters
var swayCounter = 0;
// Create road
var road = LK.getAsset('road', {
	anchorX: 0.5,
	anchorY: 0.5,
	x: 2048 / 2 + 50,
	y: 2732 / 2 - 100
	// y: 1000
});
game.addChild(road);
game.addChild(palmGroup);
game.addChild(roadLinesGroup);
// Road lines removed as requested
// Create player car
var player = new PlayerCar();
player.x = 2048 / 2;
player.y = 2732 - 300;
game.addChild(player);
var scoreLabel = new Text2('Score:', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
scoreLabel.anchor.set(0, 0);
scoreLabel.x = -150;
LK.gui.top.addChild(scoreLabel);
var scoreText = new Text2('0', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
scoreText.anchor.set(0, 0);
scoreText.x = 100;
LK.gui.top.addChild(scoreText);
var levelLabel = new Text2('Level:', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
levelLabel.anchor.set(0, 0);
levelLabel.x = -150;
levelLabel.y = 100; // Offset below time
LK.gui.top.addChild(levelLabel);
var levelText = new Text2('1', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 5
});
levelText.anchor.set(0, 0);
levelText.x = 100;
levelText.y = 100; // Offset below time
LK.gui.top.addChild(levelText);
var startText = new Text2('TAP TO START\nTAP TO 🔊\nPERFECTLY TIMED 🔊 = +2', {
	size: 120,
	fill: 0xFFFFFF,
	stroke: 0xFFFFFF,
	strokeThickness: 8,
	align: 'center'
});
startText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startText);
// Functions to spawn game elements
function spawnTrafficCar() {
	// Only spawn a new car if there are no cars on screen
	if (trafficCars.length === 0) {
		var car = new TrafficCar();
		car.x = 2048 / 2; // Start in the middle of the screen
		car.y = car.startY; // Start at the defined startY
		car.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
		trafficCars.push(car);
		game.addChild(car);
	}
	// Schedule next check for spawning with faster spawns at higher levels
	var nextSpawnTime = Math.max(200, 1000 / level); // Faster spawns at higher levels
	LK.setTimeout(spawnTrafficCar, nextSpawnTime);
}
// Start game function
function startGame() {
	gameStarted = true;
	gameTime = 30;
	distance = 0;
	level = 1; // Initial level starts at 1
	nextCheckpointAt = checkpointDistance;
	LK.setScore(0);
	// Initialize road lines with pre-animated positions as if they had already been running for 10 seconds
	roadLines = [];
	for (var i = 0; i < roadLinesGroup.children.length; i++) {
		roadLinesGroup.children[i].destroy();
	}
	roadLinesGroup.removeChildren();
	roadLineSpawnTimer = 0;
	// Create pre-existing road lines as if they were already running for 10 seconds
	for (var i = 0; i < ROAD_LINE_COUNT; i++) {
		var newRoadLine = new RoadLine();
		// Calculate a position along the animation path as if it had been running
		// Progress ranges from 0 (just started) to 1 (almost finished)
		var progress = i / ROAD_LINE_COUNT;
		// Start at center X and interpolate Y based on progress
		newRoadLine.x = centerX + 15;
		// Calculate Y position - from center-50 to beyond bottom of screen
		var startY = centerY - 50;
		var targetY = 2732 + 100;
		newRoadLine.y = startY + (targetY - startY) * progress;
		// Calculate scale based on progress (0.1 to 1.0)
		var startScale = 0.1;
		var endScale = 1.0;
		var currentScale = startScale + (endScale - startScale) * progress;
		newRoadLine.scale.set(currentScale);
		newRoadLine.lastY = newRoadLine.y;
		newRoadLine.isMoving = true;
		// Only add the road line and start animation if it's still on screen
		if (newRoadLine.y < 2732) {
			// Create tween animation with perspective effect - already in progress
			var remainingDuration = 4000 * (1 - progress) / level;
			tween(newRoadLine, {
				y: targetY,
				scaleX: endScale,
				scaleY: endScale
			}, {
				duration: remainingDuration,
				easing: tween.easeIn
			});
			roadLines.push(newRoadLine);
			roadLinesGroup.addChild(newRoadLine);
		} else {
			newRoadLine.destroy();
		}
	}
	// Update UI
	scoreText.setText('0');
	levelText.setText(level.toString());
	levelText.setStyle({
		tint: 0xffffff,
		stroke: 0xffffff,
		strokeThickness: 5
	});
	// Remove start text
	LK.gui.center.removeChild(startText);
	// Start spawning game elements
	spawnTrafficCar();
	// Level is now calculated based on score/10+1, no need for a timer to increase it
	// Play background music
	LK.playMusic('bgmusic');
	// Palm spawning is handled in the update function
}
// Handle touch input
game.down = function (x, y) {
	// Only play honk sound if game has started
	if (!gameStarted) {
		startGame();
		return;
	}
	// Play honk sound when screen is clicked/tapped after game has started
	LK.getSound('honk').play();
	// Record the time of honk for perfect timing detection
	lastHonkTime = Date.now();
	// Remove any existing bubble when player honks
	if (player.bubble && player.bubble.parent) {
		player.bubble.parent.removeChild(player.bubble);
		player.bubble.destroy();
		player.bubble = null;
	}
};
game.move = function (x, y) {
	if (!gameStarted) {
		return;
	}
	// Move player car directly to mouse X position
	var targetX = x;
	// Apply smooth movement by calculating the difference
	var deltaX = targetX - player.x;
	player.steering = deltaX * 0.1; // Smooth movement factor
};
game.up = function () {
	if (gameStarted) {
		// No need to reset steering as we'll continue to follow mouse
	}
};
// Main game update function
game.update = function () {
	if (!gameStarted) {
		return;
	}
	// Update road lines
	if (gameStarted) {
		// Update road line animations
		for (var r = roadLines.length - 1; r >= 0; r--) {
			var roadLine = roadLines[r];
			// Check if roadLine exists before accessing its properties
			if (roadLine) {
				roadLine.lastY = roadLine.y;
				// Start a new roadLine animation if roadLine is not already moving
				if (roadLine.isMoving === false) {
					roadLine.isMoving = true;
					// Set target position - straight down the screen
					var targetY = 2732 + 100; // Below bottom of screen
					var targetScale = 1.0; // End larger
					// Create tween animation with perspective effect
					tween(roadLine, {
						y: targetY,
						scaleX: targetScale,
						scaleY: targetScale
					}, {
						duration: 4000 / level,
						easing: tween.easeIn
					});
				}
				// Check if roadLine has moved significantly past the bottom of the screen and destroy it
				var destroyYPosition = 2732 - 300; // Check if center is below the bottom edge
				if (roadLine.isMoving && roadLine.y > destroyYPosition) {
					// RoadLine is off-screen, destroy it
					var index = roadLines.indexOf(roadLine);
					if (index > -1) {
						roadLinesGroup.removeChild(roadLine);
						roadLines.splice(index, 1);
						roadLine.destroy();
					}
					// Skip to next roadLine as this one is destroyed
					continue;
				}
			}
		} // End of loop processing existing road lines
		// Calculate spawn cooldown based on level
		roadLineSpawnCooldown = Math.max(5, BASE_ROAD_LINE_COOLDOWN / level);
		// Update road line spawn timer and generate new road lines if needed
		roadLineSpawnTimer -= 1;
		if (roadLineSpawnTimer <= 0 && roadLines.length < ROAD_LINE_COUNT) {
			roadLineSpawnTimer = roadLineSpawnCooldown; // Reset timer
			var newRoadLine = new RoadLine();
			// Start at center X and at center Y
			newRoadLine.x = centerX + 15;
			newRoadLine.y = centerY - 50;
			newRoadLine.scale.set(0.1); // Start small
			newRoadLine.lastY = newRoadLine.y;
			newRoadLine.isMoving = false; // Flag to track if road line is currently animating
			roadLines.push(newRoadLine);
			roadLinesGroup.addChild(newRoadLine);
		}
	}
	// Road lines removed as requested
	// Update game time
	gameTime -= 1 / 60; // Assuming 60 FPS
	// Calculate level based on score/10, but minimum of 1
	level = Math.floor(LK.getScore() / 10) + 1;
	levelText.setText(level.toString());
	// Update palm animations section
	if (gameStarted) {
		distance += 10 + level;
		// Don't update score based on distance anymore
		// Keep updating the distance for internal calculations if needed
		// Update palm animations
		for (var p = palms.length - 1; p >= 0; p--) {
			var palm = palms[p];
			// Check if palm exists before accessing its properties
			if (palm) {
				palm.lastY = palm.y;
				// Start a new palm animation if palm is not already moving
				if (palm.isMoving === false) {
					palm.isMoving = true;
					// Set target position based on direction (left or right corner)
					var targetX = palm.direction === "left" ? -12000 : 14500; // Left or right edge
					var targetY = 2732 + 100; // Below bottom of screen
					var targetScale = 2.0; // End larger
					// Create tween animation with perspective effect - use same duration for all palms
					tween(palm, {
						x: targetX,
						y: targetY,
						scaleX: targetScale * (lastSpawnSide === "right" ? 1 : -1),
						scaleY: targetScale
					}, {
						duration: 7000 / level,
						// Duration decreases as level increases
						easing: tween.easeIn // Removed onFinish callback
					});
				}
				// Check if palm has moved significantly past the bottom of the screen and destroy it
				var destroyYPosition = 2500; // Check if center is 150px below the bottom edge
				if (palm.isMoving && palm.y > destroyYPosition) {
					// Palm is off-screen, destroy it
					var index = palms.indexOf(palm);
					if (index > -1) {
						palmGroup.removeChild(palm);
						palms.splice(index, 1);
						palm.destroy();
					}
					// Skip to next palm as this one is destroyed
					continue;
				}
			}
		} // End of loop processing existing palms
		// Calculate palm spawn cooldown based on level
		palmSpawnCooldown = Math.max(3, BASE_PALM_COOLDOWN / level);
		// Update palm spawn timer and generate new palms if needed
		palmSpawnTimer -= 1;
		if (palmSpawnTimer <= 0 && palms.length < PALM_COUNT) {
			palmSpawnTimer = palmSpawnCooldown; // Reset timer
			var newPalm = new Palm();
			// Alternate spawn side based on the last spawned side
			lastSpawnSide = lastSpawnSide === "right" ? "left" : "right";
			// Start from exact center point
			newPalm.x = centerX + (lastSpawnSide === "right" ? 50 : -50);
			newPalm.y = centerY - 100; // Start at center
			newPalm.scale.set(0.005); // Start small
			newPalm.scaleX *= lastSpawnSide === "right" ? 1 : -1;
			newPalm.lastY = newPalm.y;
			newPalm.direction = lastSpawnSide; // Use the determined side
			newPalm.isMoving = false; // Flag to track if palm is currently animating
			palms.push(newPalm);
			palmGroup.addChild(newPalm);
		}
	}
	// Game will continue indefinitely - removed game over condition
	gameTime = Math.max(gameTime, 0.1); // Keep time from reaching zero
	// Update perfect messages
	for (var m = perfectMessages.length - 1; m >= 0; m--) {
		perfectMessages[m].update();
	}
	// Check for old perfect and non-perfect messages every 2 seconds
	if (LK.ticks % 60 === 0) {
		var currentTime = Date.now();
		for (var i = perfectMessages.length - 1; i >= 0; i--) {
			// If message is older than 2 seconds and still exists
			if (perfectMessages[i] && perfectMessages[i].creationTime && currentTime - perfectMessages[i].creationTime > 500) {
				// Remove from game if it's still attached
				if (perfectMessages[i].parent) {
					perfectMessages[i].parent.removeChild(perfectMessages[i]);
				}
				// Destroy and remove from array
				perfectMessages[i].destroy();
				perfectMessages.splice(i, 1);
			}
		}
		// Check for speech bubble timing
		if (player.bubble && player.bubble.creationTime && currentTime - player.bubble.creationTime > 1000) {
			// Remove bubble if it's been showing for more than 1 second
			if (player.bubble.parent) {
				player.bubble.parent.removeChild(player.bubble);
			}
			player.bubble.destroy();
			player.bubble = null;
		}
	}
	// Check collisions with traffic cars
	for (var i = trafficCars.length - 1; i >= 0; i--) {
		var car = trafficCars[i];
		if (player.intersects(car) && !player.crashed) {
			player.crash();
			trafficCars.splice(i, 1);
			car.destroy();
			// GameOver will be called after the crash sequence
		}
	}
};
function endGame() {
	if (!gameStarted) {
		return;
	}
	// Just save high score without ending the game
	if (LK.getScore() > storage.highScore) {
		storage.highScore = LK.getScore();
	}
	// Game continues - no game over screen
}