User prompt
Update with: // Update this part in the createUpgradeText function // Replace the hitContainer positioning code with this: // Create hit container first (so it's behind text) var hitContainer = new Container(); // Create a shape for the hit area var hitArea = LK.getAsset('blower', { width: 400, height: 150, color: 0xFFFFFF, alpha: 0.0 // Set to 0.2 to see hit areas during debugging }); hitContainer.addChild(hitArea); // Center the hit area on the text instead of offsetting to the left hitContainer.x = xOffset; // Center on the text position hitContainer.y = yPos; // Align with the text // Adjust the hitArea position within the container to center properly hitArea.x = 0; // Center horizontally hitArea.y = -40; // Slightly above text to cover both name and cost
User prompt
Update with: // Replace the existing game.showError function with this: game.showError = function(message) { var errorText = new Text2(message, { size: 120, fill: 0xFF0000, stroke: 0x000000, strokeThickness: 5, font: "Impact" }); // Set the anchor point to center the text errorText.anchor = { x: 0.5, y: 0.5 }; // Position at the center of the screen errorText.x = game.width / 2; errorText.y = game.height / 2; game.addChild(errorText); // Animate the text tween(errorText, { alpha: 0, y: errorText.y - 50 }, { duration: 1200, onFinish: function() { errorText.destroy(); } }); }; βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
Change bubble alpha to 0.8
User prompt
Update as needed with: // Add this to the end of the game initialization code, after updating the clam visuals function updateAllUpgradeTexts() { // Process left column upgrades leftColumnUpgrades.forEach(function(upgrade) { var category = upgrade[0]; var key = upgrade[1]; var upgradeConfig = UPGRADE_CONFIG[category][key]; // Find the cost text for this upgrade menuTextContainer.children.forEach(function(child) { if (child.text && child.text.includes("BP") && child.y > menuTextContainer.children.find(function(c) { return c.text === upgradeConfig.name; }).y) { // Check if max level reached if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) { child.setText("SOLD OUT"); } } }); }); // Process right column upgrades rightColumnUpgrades.forEach(function(upgrade) { var category = upgrade[0]; var key = upgrade[1]; if (category === 'machines') { var upgradeConfig = UPGRADE_CONFIG[category][key]; var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Find the cost text for this upgrade menuTextContainer.children.forEach(function(child) { if (child.text && child.text.includes("BP") && child.y > menuTextContainer.children.find(function(c) { return c.text === upgradeConfig.name; }).y) { // Check if sold out based on clam logic if (totalClams >= 4 && key === 'basicClam' || UPGRADE_CONFIG.machines.basicClam.amount <= UPGRADE_CONFIG.machines.advancedClam.amount && key === 'advancedClam' || UPGRADE_CONFIG.machines.advancedClam.amount <= UPGRADE_CONFIG.machines.premiumClam.amount && key === 'premiumClam') { child.setText("SOLD OUT"); } } }); } else if (category === 'machine') { var upgradeConfig = UPGRADE_CONFIG[category][key]; // Find the cost text for this upgrade menuTextContainer.children.forEach(function(child) { if (child.text && child.text.includes("BP") && child.y > menuTextContainer.children.find(function(c) { return c.text === upgradeConfig.name; }).y) { // Check if max level reached if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) { child.setText("SOLD OUT"); } } }); } }); } // Call the function after creating all upgrades updateAllUpgradeTexts();
User prompt
Update with: // Replace the existing hitContainer.down function with this: hitContainer.down = function() { var cost = getUpgradeCost(upgrade); if (game.bp >= cost) { if (category === 'machines') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; if (key === 'basicClam' && totalClams < 4) { upgrade.amount++; game.bp -= cost; } else if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > UPGRADE_CONFIG.machines.advancedClam.amount) { upgrade.amount++; game.bp -= cost; } else if (key === 'premiumClam' && UPGRADE_CONFIG.machines.advancedClam.amount > UPGRADE_CONFIG.machines.premiumClam.amount) { upgrade.amount++; game.bp -= cost; } else { game.showError("Not enough space for more clams!"); return true; } bpText.setText(formatBP(game.bp) + " BP"); // Update cost text based on whether more can be purchased if (totalClams >= 4 && key === 'basicClam' || UPGRADE_CONFIG.machines.basicClam.amount <= UPGRADE_CONFIG.machines.advancedClam.amount && key === 'advancedClam' || UPGRADE_CONFIG.machines.advancedClam.amount <= UPGRADE_CONFIG.machines.premiumClam.amount && key === 'premiumClam') { costText.setText("SOLD OUT"); } else { costText.setText(getUpgradeCost(upgrade) + " BP"); } updateClamVisuals(); } else if (upgrade.currentLevel < upgrade.maxLevel) { upgrade.currentLevel++; game.bp -= cost; // Add this section to update maxBubbleSize when lung capacity changes if (category === 'player' && key === 'lungCapacity') { var baseSize = UPGRADE_EFFECTS.lungCapacity.baseValue; var increasePercent = UPGRADE_EFFECTS.lungCapacity.incrementPercent; var multiplier = 1 + increasePercent / 100 * upgrade.currentLevel; game.maxBubbleSize = baseSize * multiplier; } bpText.setText(formatBP(game.bp) + " BP"); // Update cost text based on whether more levels can be purchased if (upgrade.currentLevel >= upgrade.maxLevel) { costText.setText("SOLD OUT"); } else { costText.setText(getUpgradeCost(upgrade) + " BP"); } if (key === 'quickBreath') { game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * upgrade.currentLevel); } } } else { // Show not enough BP error game.showError("Not enough BP!"); } return true; };
Code edit (1 edits merged)
Please save this source code
User prompt
Increase fish bubble hit detection range
Code edit (1 edits merged)
Please save this source code
User prompt
Increase bubble size for clams by 25%
Code edit (1 edits merged)
Please save this source code
User prompt
Increase the size of all clam bubbles by 25%
User prompt
Update with: if (!popped && distance <= bubble.size / 2 + 30 && bubble.down) { // increased from +10 to +30
User prompt
take away the intial downwards velocity for clams.
User prompt
increase it more
User prompt
increase the X wandering of bubbles
User prompt
update with: // Inside the else if (upgrade.currentLevel < upgrade.maxLevel) block: upgrade.currentLevel++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); // Add this: if (key === 'quickBreath') { game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + (UPGRADE_EFFECTS.quickBreath.incrementPercent/100) * upgrade.currentLevel); }
User prompt
update with: // Replace the fixed growth rate with: game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + (UPGRADE_EFFECTS.quickBreath.incrementPercent/100) * UPGRADE_CONFIG.player.quickBreath.currentLevel);
User prompt
update with: self.update = function() { // Only increment lifetime if not being actively blown if (game.growingBubble !== self) { self.lifetime++; if (self.lifetime % 60 === 0) { self.driftX += (Math.random() - 0.5) * 0.3; } } // Rest of update method remains the same...
User prompt
update with: } else if (upgrade.currentLevel < upgrade.maxLevel) { upgrade.currentLevel++; game.bp -= cost; // Add this section to update maxBubbleSize when lung capacity changes if (category === 'player' && key === 'lungCapacity') { const baseSize = UPGRADE_EFFECTS.lungCapacity.baseValue; const increasePercent = UPGRADE_EFFECTS.lungCapacity.incrementPercent; const multiplier = 1 + (increasePercent / 100 * upgrade.currentLevel); game.maxBubbleSize = baseSize * multiplier; } bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); }
User prompt
update with: // Inside game.update if (UPGRADE_CONFIG.player.autoPop.currentLevel > 0) { // 8 seconds (480 frames) base, reduced by 2 seconds (120 frames) per level if (LK.ticks % Math.max(120, 480 - UPGRADE_CONFIG.player.autoPop.currentLevel * 120) === 0) { var fish = new Fish(); game.addChild(fish); } }
User prompt
update as needed with: var Fish = Container.expand(function() { var self = Container.call(this); // ... existing fish setup code ... self.speed = 8; // Changed from 4 self.update = function() { self.x += self.fromLeft ? self.speed : -self.speed; // Add bubble collision check game.activeBubbles.forEach(function(bubble) { if (bubble.visible) { var dx = self.x - bubble.x; var dy = self.y - bubble.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= bubble.size/2 + 30) { // 30px collision radius var points = bubble.getBP(); game.addBP(points, bubble.x, bubble.y, false); // false = manual pop points bubble.deactivate(); } } }); // Remove when off screen if (self.fromLeft && self.x > game.width + 100 || !self.fromLeft && self.x < -100) { self.destroy(); } }; return self; });
User prompt
update with: autoPop: { name: "Fish Friends", baseCost: 500, // Changed from 1000 costScale: 2.3, maxLevel: 3, // Changed from 5 currentLevel: 0 }
User prompt
update with: // Replace the existing updateClams() function function updateClams() { if (!game.clamSpawnPoints) { return; } game.clamSpawnPoints.forEach(function(spawnPoint) { var config = UPGRADE_CONFIG.machines[spawnPoint.type]; // Calculate production time with speed upgrade var baseTime = config.production * 60; // Convert to frames var speedMultiplier = Math.pow(1 - UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100, UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel); var adjustedTime = Math.max(1, Math.floor(baseTime * speedMultiplier)); if (LK.ticks % adjustedTime === 0) { // Find first available bubble in pool var bubble = game.bubblePool.find(b => !b.visible); if (bubble && game.activeBubbles.length < game.MAX_BUBBLES) { bubble.activate( spawnPoint.x, spawnPoint.y, config.bubbleSize, false // not player blown ); // Set initial velocities for clam bubbles bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = (spawnPoint.isRight ? -1 : 1) * (Math.random() * 1.5 + 2); game.activeBubbles.push(bubble); } } }); }
User prompt
Please fix the bug: 'TypeError: game.bubbles is undefined' in or related to this line: 'if (game.bubbles.length < game.MAX_BUBBLES) {' Line Number: 453
Code edit (6 edits merged)
Please save this source code
/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/**** 
* Classes
****/ 
// Bubble class to represent each bubble in the game
var Bubble = Container.expand(function () {
	var self = Container.call(this);
	self.lifetime = 0;
	self.hasSplit = false;
	self.splitHeight = null;
	self.AUTO_POP_SIZE = 40;
	self.MIN_SPLIT_SIZE = 30;
	self.lastPopTime = 0;
	self.visible = false; // Start invisible in pool
	var sprite = self.attachAsset('bubble', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.8
	});
	self.size = 100;
	self.activate = function (x, y, size) {
		var isPlayerBlown = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
		// Reset ALL state
		self.x = x;
		self.y = y;
		self.size = size;
		self.lifetime = 0;
		self.hasSplit = false;
		self.splitHeight = null;
		self.justSplit = false;
		self.autoPopDisplayed = false;
		self.lastPopTime = 0;
		self.verticalVelocity = 0;
		self.driftX = (Math.random() * 20 - 10) / 60;
		self.floatSpeed = 50 * (120 / size * (0.9 + Math.random() * 0.2)) / 60;
		self.initLifetime(); // Always get fresh lifetime
		self.visible = true;
		// Add some debug logs
		console.log('Activating bubble:', {
			size: self.size,
			maxLifetime: self.maxLifetime,
			isPlayerBlown: isPlayerBlown
		});
	};
	self.deactivate = function () {
		self.visible = false;
		var index = game.activeBubbles.indexOf(self);
		if (index > -1) {
			game.activeBubbles.splice(index, 1);
		}
		// Don't award points here - let the calling function handle it
	};
	self.initLifetime = function () {
		self.maxLifetime = Math.floor(Math.random() * 960 + 1440);
		self.maxLifetime *= Math.min(1, self.size / 100);
	};
	self.initLifetime();
	// Subtle size-based variance plus small random factor
	var speedMultiplier = 120 / self.size * (0.9 + Math.random() * 0.2); // Just 10% variance
	self.floatSpeed = 50 * speedMultiplier / 60;
	self.driftX = (Math.random() * 20 - 10) / 60; // Normal drift variance
	self.verticalVelocity = 0;
	self.down = function (e) {
		var currentTime = Date.now();
		if (currentTime - self.lastPopTime < 100) {
			return true;
		}
		self.lastPopTime = currentTime;
		var points = self.getBP();
		game.addBP(points, self.x, self.y, false);
		if (self.size > 60 && !self.justSplit) {
			var splitCount = 2 + UPGRADE_CONFIG.machine.bubbleDurability.currentLevel;
			var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6);
			for (var i = 0; i < splitCount; i++) {
				var angle = i / splitCount * Math.PI * 2;
				// Reduce the direction multiplier to 0.5 to make movement more gentle
				spawnBubble(self.x, self.y, newSize, Math.cos(angle) * 0.5, false);
			}
		}
		self.deactivate();
		return true;
	};
	self.getBP = function () {
		return Math.max(1, Math.floor(Math.pow(self.size, 1.4) * 0.02));
	};
	self.update = function () {
		// Only increment lifetime if not being actively blown
		if (game.growingBubble !== self) {
			self.lifetime++;
			if (self.lifetime % 60 === 0) {
				self.driftX += (Math.random() - 0.5) * 0.8;
			}
		}
		self.x += self.driftX * 1.2;
		if (self.lifetime > self.maxLifetime) {
			console.log('Bubble dying - lifetime:', self.lifetime, 'maxLifetime:', self.maxLifetime);
			if (self.size > 60 && !self.hasSplit) {
				self.hasSplit = true;
				var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6);
				for (var i = 0; i < 2; i++) {
					var split = spawnBubble(self.x, self.y, newSize, i === 0 ? -1 : 1, true);
					if (split) {
						split.maxLifetime *= 0.7;
					}
				}
				self.deactivate();
				return;
			}
			self.autoPop();
			return;
		}
		if (self.y < -self.size) {
			// Just deactivate, no points
			self.visible = false;
			var index = game.activeBubbles.indexOf(self);
			if (index > -1) {
				game.activeBubbles.splice(index, 1);
			}
			return;
		}
		self.justSplit = false;
		if (self.verticalVelocity < self.floatSpeed) {
			self.verticalVelocity += 0.08;
		}
		self.y -= self.verticalVelocity;
		if (Math.abs(self.driftX) > (Math.random() * 20 - 10) / 60) {
			self.driftX *= 0.98;
		}
		self.x += self.driftX;
		if (self.x < self.size) {
			self.x = self.size;
			self.driftX = Math.abs(self.driftX);
		} else if (self.x > game.width - self.size) {
			self.x = game.width - self.size;
			self.driftX = -Math.abs(self.driftX);
		}
		var scale = self.size / sprite.width;
		sprite.scaleX = scale;
		sprite.scaleY = scale;
	};
	self.autoPop = function () {
		// Only award points if bubble is on screen
		if (!self.autoPopDisplayed && self.y > -self.size) {
			var points = Math.floor(self.getBP() * 0.5);
			game.addBP(points, self.x, self.y, true);
			self.autoPopDisplayed = true;
		}
		self.deactivate();
		return;
	};
	return self;
});
var Fish = Container.expand(function () {
	var self = Container.call(this);
	var fishTypes = ['redfish', 'bluefish', 'yellowfish'];
	var fishType = fishTypes[Math.floor(Math.random() * fishTypes.length)];
	// Create fish sprite
	var sprite = self.attachAsset(fishType, {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Initialize position and movement
	self.fromLeft = Math.random() < 0.5;
	self.x = self.fromLeft ? -100 : game.width + 100;
	self.y = Math.random() * (game.height * 0.7) + game.height * 0.1;
	sprite.scaleX = self.fromLeft ? 1 : -1;
	self.speed = 8; // Changed from 4
	self.update = function () {
		self.x += self.fromLeft ? self.speed : -self.speed;
		// Add bubble collision check
		game.activeBubbles.forEach(function (bubble) {
			if (bubble.visible) {
				var dx = self.x - bubble.x;
				var dy = self.y - bubble.y;
				var distance = Math.sqrt(dx * dx + dy * dy);
				if (distance <= bubble.size / 2 + 50) {
					// Increased from 30 to 50 
					// 30px collision radius
					var points = bubble.getBP();
					game.addBP(points, bubble.x, bubble.y, false); // false = manual pop points
					bubble.deactivate();
				}
			}
		});
		// Remove when off screen
		if (self.fromLeft && self.x > game.width + 100 || !self.fromLeft && self.x < -100) {
			self.destroy();
		}
	};
	return self;
});
// Pufferfish mask that follows face
var pufferMask = Container.expand(function () {
	var self = Container.call(this);
	var sprite = self.attachAsset('pufferfish', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	var targetX = 0;
	var targetY = 0;
	var smoothingFactor = 0.12;
	var prevX = null;
	var prevY = null;
	var targetRotation = 0;
	var rotationSmoothingFactor = 0.1;
	var targetTilt = 0;
	var tiltSmoothingFactor = 0.11; // Reduced from 0.08 for smoother movement
	var tiltScaleFactor = 0.09; // Reduced from 0.15 for less tilt
	var scaleHistory = new Array(5).fill(0); // Keep last 5 scale values
	var scaleIndex = 0;
	var baseScale = 1;
	var minScale = 0.1;
	var maxScale = 3;
	self.update = function () {
		// Adjust scale based on face size
		if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
			var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
			var newScale = eyeDistance / 500;
			// Update rolling average
			scaleHistory[scaleIndex] = newScale;
			scaleIndex = (scaleIndex + 1) % scaleHistory.length;
			// Calculate average scale
			var avgScale = scaleHistory.reduce(function (a, b) {
				return a + b;
			}, 0) / scaleHistory.length;
			// More gentle smoothing
			sprite.scaleX = sprite.scaleX * 0.85 + avgScale * 0.15;
			sprite.scaleY = sprite.scaleY * 0.85 + avgScale * 0.15;
		}
		// Follow nose position for main face tracking
		if (facekit.noseTip) {
			targetX = facekit.noseTip.x;
			targetY = facekit.noseTip.y;
			// Initialize previous positions if not set
			if (prevX === null) {
				prevX = targetX;
				prevY = targetY;
			}
			// Weighted average between previous and target position
			var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor;
			var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor;
			self.x = newX;
			self.y = newY;
			// Update previous positions
			prevX = newX;
			prevY = newY;
		}
		if (facekit.leftEye && facekit.rightEye) {
			targetTilt = calculateFaceTilt() * tiltScaleFactor; // Scale down the tilt
			// Reduce max rotation to Β±15 degrees
			targetTilt = Math.max(-15, Math.min(15, targetTilt));
			self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor;
		}
	};
	function calculateFaceTilt() {
		if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
			// Calculate midpoint between eyes
			var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
			var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
			// Calculate angle between eye midpoint and mouth, negated to fix direction
			var dx = facekit.mouthCenter.x - eyeMidX;
			var dy = facekit.mouthCenter.y - eyeMidY;
			var angle = -(Math.atan2(dx, dy) * (180 / Math.PI));
			// Reduced max angle to Β±15 degrees and lowered multiplier
			return Math.max(-15, Math.min(15, angle * 0.15));
		}
		return 0; // Default to straight when face points aren't available
	}
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x87CEEB // Light blue background to represent the sky
});
/**** 
* Game Code
****/ 
var UPGRADE_EFFECTS = {
	lungCapacity: {
		baseValue: 160,
		// Base max bubble size
		incrementPercent: 25 // +25% per level
	},
	quickBreath: {
		baseValue: 1.6,
		// Base growth rate
		incrementPercent: 25 // +25% per level
	},
	autoBubbleSpeed: {
		decrementPercent: 10 // -10% production time per level
	},
	bubbleDurability: {
		extraSplits: 1 // +1 split per level
	},
	autoPop: {
		timeReduction: 0.8 // Reduces lifetime by 20% per level
	}
};
function _slicedToArray3(r, e) {
	return _arrayWithHoles3(r) || _iterableToArrayLimit3(r, e) || _unsupportedIterableToArray3(r, e) || _nonIterableRest3();
}
function _nonIterableRest3() {
	throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray3(r, a) {
	if (r) {
		if ("string" == typeof r) {
			return _arrayLikeToArray3(r, a);
		}
		var t = {}.toString.call(r).slice(8, -1);
		return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray3(r, a) : void 0;
	}
}
function _arrayLikeToArray3(r, a) {
	(null == a || a > r.length) && (a = r.length);
	for (var e = 0, n = Array(a); e < a; e++) {
		n[e] = r[e];
	}
	return n;
}
function _iterableToArrayLimit3(r, l) {
	var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
	if (null != t) {
		var e,
			n,
			i,
			u,
			a = [],
			f = !0,
			o = !1;
		try {
			if (i = (t = t.call(r)).next, 0 === l) {
				if (Object(t) !== t) {
					return;
				}
				f = !1;
			} else {
				for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) {
					;
				}
			}
		} catch (r) {
			o = !0, n = r;
		} finally {
			try {
				if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) {
					return;
				}
			} finally {
				if (o) {
					throw n;
				}
			}
		}
		return a;
	}
}
function _arrayWithHoles3(r) {
	if (Array.isArray(r)) {
		return r;
	}
}
function getUpgradeCost(upgrade) {
	if (upgrade.amount !== undefined) {
		// For clams
		return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, upgrade.amount));
	} else {
		// For regular upgrades
		return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, upgrade.currentLevel));
	}
}
function _slicedToArray2(r, e) {
	return _arrayWithHoles2(r) || _iterableToArrayLimit2(r, e) || _unsupportedIterableToArray2(r, e) || _nonIterableRest2();
}
function _nonIterableRest2() {
	throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray2(r, a) {
	if (r) {
		if ("string" == typeof r) {
			return _arrayLikeToArray2(r, a);
		}
		var t = {}.toString.call(r).slice(8, -1);
		return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray2(r, a) : void 0;
	}
}
function _arrayLikeToArray2(r, a) {
	(null == a || a > r.length) && (a = r.length);
	for (var e = 0, n = Array(a); e < a; e++) {
		n[e] = r[e];
	}
	return n;
}
function _iterableToArrayLimit2(r, l) {
	var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
	if (null != t) {
		var e,
			n,
			i,
			u,
			a = [],
			f = !0,
			o = !1;
		try {
			if (i = (t = t.call(r)).next, 0 === l) {
				if (Object(t) !== t) {
					return;
				}
				f = !1;
			} else {
				for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) {
					;
				}
			}
		} catch (r) {
			o = !0, n = r;
		} finally {
			try {
				if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) {
					return;
				}
			} finally {
				if (o) {
					throw n;
				}
			}
		}
		return a;
	}
}
function _arrayWithHoles2(r) {
	if (Array.isArray(r)) {
		return r;
	}
}
function updateClams() {
	if (!game.clamSpawnPoints) {
		return;
	}
	game.clamSpawnPoints.forEach(function (spawnPoint) {
		var config = UPGRADE_CONFIG.machines[spawnPoint.type];
		// Calculate production time with speed upgrade
		var baseTime = config.production * 60; // Convert to frames
		var speedMultiplier = Math.pow(1 - UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100, UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel);
		var adjustedTime = Math.max(1, Math.floor(baseTime * speedMultiplier));
		if (LK.ticks % adjustedTime === 0) {
			// Find first available bubble in pool
			var bubble = game.bubblePool.find(function (b) {
				return !b.visible;
			});
			if (bubble && game.activeBubbles.length < game.MAX_BUBBLES) {
				bubble.activate(spawnPoint.x, spawnPoint.y, config.bubbleSize, false // not player blown
				);
				// Set initial velocities for clam bubbles
				bubble.verticalVelocity = 0;
				bubble.driftX = (spawnPoint.isRight ? -1 : 1) * (Math.random() * 1.5 + 2);
				game.activeBubbles.push(bubble);
			}
		}
	});
}
function _slicedToArray(r, e) {
	return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _nonIterableRest() {
	throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(r, a) {
	if (r) {
		if ("string" == typeof r) {
			return _arrayLikeToArray(r, a);
		}
		var t = {}.toString.call(r).slice(8, -1);
		return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
	}
}
function _arrayLikeToArray(r, a) {
	(null == a || a > r.length) && (a = r.length);
	for (var e = 0, n = Array(a); e < a; e++) {
		n[e] = r[e];
	}
	return n;
}
function _iterableToArrayLimit(r, l) {
	var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
	if (null != t) {
		var e,
			n,
			i,
			u,
			a = [],
			f = !0,
			o = !1;
		try {
			if (i = (t = t.call(r)).next, 0 === l) {
				if (Object(t) !== t) {
					return;
				}
				f = !1;
			} else {
				for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) {
					;
				}
			}
		} catch (r) {
			o = !0, n = r;
		} finally {
			try {
				if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) {
					return;
				}
			} finally {
				if (o) {
					throw n;
				}
			}
		}
		return a;
	}
}
function _arrayWithHoles(r) {
	if (Array.isArray(r)) {
		return r;
	}
}
var background = LK.getAsset('background', {
	anchorX: 0.5,
	anchorY: 0.5,
	x: game.width / 2,
	y: game.height / 2
});
game.addChild(background);
var clamContainer = new Container();
game.addChild(clamContainer);
var playerMask = new pufferMask();
game.addChild(playerMask);
var UPGRADE_CONFIG = {
	player: {
		lungCapacity: {
			name: "Lung Capacity",
			baseCost: 50,
			// About 1-2 max bubbles
			costScale: 2.0,
			// Steeper scaling
			maxLevel: 10,
			currentLevel: 0
		},
		quickBreath: {
			name: "Quick Breath",
			baseCost: 100,
			// 2-3 max bubbles
			costScale: 2.0,
			// Steeper scaling
			maxLevel: 10,
			currentLevel: 0
		},
		autoPop: {
			// Moved here from machine section
			name: "Fish Friends",
			// Updated name
			baseCost: 500,
			// Changed from 1000
			costScale: 2.3,
			maxLevel: 5,
			// Changed from 5
			currentLevel: 0
		}
	},
	machines: {
		basicClam: {
			name: "Basic Clam",
			baseCost: 300,
			// 6-7 max bubbles
			costScale: 1.25,
			// Slightly steeper scaling
			amount: 0,
			production: 3,
			bubbleSize: 80
		},
		advancedClam: {
			name: "Advanced Clam",
			baseCost: 2000,
			// Requires running basic clams for a while
			costScale: 2.0,
			amount: 0,
			production: 2,
			bubbleSize: 120,
			unlockCost: 2000
		},
		premiumClam: {
			name: "Premium Clam",
			baseCost: 15000,
			// True end-game content
			costScale: 2.35,
			amount: 0,
			production: 1,
			bubbleSize: 150,
			unlockCost: 15000
		}
	},
	machine: {
		bubbleDurability: {
			name: "Bubble Durability",
			baseCost: 500,
			costScale: 3.5,
			// Significant scaling
			maxLevel: 3,
			currentLevel: 0
		},
		autoBubbleSpeed: {
			name: "Auto-Bubble Speed",
			baseCost: 500,
			costScale: 3.5,
			maxLevel: 10,
			currentLevel: 0
		}
	}
};
game.showError = function (message) {
	var errorText = new Text2(message, {
		size: 120,
		fill: 0xFF0000,
		stroke: 0x000000,
		strokeThickness: 5,
		font: "Impact"
	});
	// Set the anchor point to center the text
	errorText.anchor = {
		x: 0.5,
		y: 0.5
	};
	// Position at the center of the screen
	errorText.x = game.width / 2;
	errorText.y = game.height / 2;
	game.addChild(errorText);
	// Animate the text
	tween(errorText, {
		alpha: 0,
		y: errorText.y - 50
	}, {
		duration: 1200,
		onFinish: function onFinish() {
			errorText.destroy();
		}
	});
};
// Create upgrade menu elements
// Menu tab (handle)
// First position the panel relative to container at y=0
// Menu panel should be below the tab in the container
var menuTab = LK.getAsset('upgradetab', {
	anchorX: 0.5,
	anchorY: 1,
	// Change to bottom anchor
	y: 0,
	// Will be at container's position
	scaleX: 3,
	scaleY: 0.8,
	alpha: 0.9
});
var menuPanel = LK.getAsset('upgradetab', {
	anchorX: 0.5,
	anchorY: 0,
	y: -570,
	alpha: 0.9,
	scaleX: 2048 / 200,
	// Use the width of the asset directly
	scaleY: game.height * 0.4 / 100.3
});
// Initialize menu structure
// Initialize menu container at the right position
var menuContainer = new Container();
menuContainer.x = game.width / 2;
menuContainer.y = game.height; // Position at bottom
var menuPanel = LK.getAsset('upgradetab', {
	anchorX: 0.5,
	anchorY: 0,
	y: -570,
	alpha: 0.9,
	scaleX: 2048 / 200,
	scaleY: game.height * 0.4 / 100.3
});
var menuTab = LK.getAsset('upgradetab', {
	anchorX: 0.5,
	anchorY: 1,
	y: 0,
	scaleX: 3,
	scaleY: 0.8,
	alpha: 0.9
});
// Add panel first (so it's behind tab)
menuContainer.addChild(menuPanel);
menuContainer.addChild(menuTab);
// Menu text setup - should be good as is
var menuText = new Text2("Upgrades", {
	size: 90,
	fill: 0xFFFFFF,
	stroke: 0x000000,
	strokeThickness: 3,
	font: "Impact"
});
menuText.anchor = {
	x: 0.5,
	y: 0.5
};
menuText.x = 0; // Relative to container
menuText.y = -menuTab.height / 2; // Position relative to container bottom
menuContainer.addChild(menuText);
// Add to game
game.addChild(menuContainer);
// Create text container AFTER panel scaling
var menuTextContainer = new Container();
menuContainer.addChild(menuTextContainer);
// Add upgrade texts to text container instead of panel
var upgradeTexts = [];
var startY = 150;
var upgradeSpacing = 250;
var columnWidth = 1024;
// Create arrays to hold upgrade categories in desired order
var leftColumnUpgrades = [['player', 'lungCapacity'], ['player', 'quickBreath'], ['player', 'autoPop']];
var rightColumnUpgrades = [['machines', 'basicClam'], ['machines', 'advancedClam'], ['machines', 'premiumClam'], ['machine', 'bubbleDurability'], ['machine', 'autoBubbleSpeed']];
// Function to create upgrade text
function createUpgradeText(category, key, index, isLeftColumn) {
	var upgrade = UPGRADE_CONFIG[category][key];
	var xOffset = isLeftColumn ? -550 : 100;
	var yPos = startY + index * upgradeSpacing;
	// Create hit container first (so it's behind text)
	var hitContainer = new Container();
	// Create a shape for the hit area (can make visible for debugging)
	var hitArea = LK.getAsset('blower', {
		width: 400,
		height: 150,
		color: 0xFFFFFF,
		alpha: 0.0 // Set to 0.2 to see hit areas
	});
	hitContainer.addChild(hitArea);
	hitContainer.x = xOffset - 200; // Center on where text will be
	hitContainer.y = yPos - 40;
	// Create name text
	var nameText = new Text2(upgrade.name, {
		size: 96,
		fill: 0xFFFFFF,
		stroke: 0x000000,
		strokeThickness: 2,
		font: "Impact"
	});
	nameText.x = xOffset;
	nameText.y = yPos;
	// Create cost text
	var cost = getUpgradeCost(upgrade);
	var costText = new Text2(cost + " BP", {
		size: 96,
		fill: 0xFFFF00,
		stroke: 0x000000,
		strokeThickness: 2,
		font: "Impact"
	});
	costText.x = xOffset;
	costText.y = yPos + 100;
	// Add click handler to hit container
	hitContainer.down = function () {
		var cost = getUpgradeCost(upgrade);
		if (game.bp >= cost) {
			if (category === 'machines') {
				var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
				if (key === 'basicClam' && totalClams < 4) {
					upgrade.amount++;
					game.bp -= cost;
				} else if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > UPGRADE_CONFIG.machines.advancedClam.amount) {
					upgrade.amount++;
					game.bp -= cost;
				} else if (key === 'premiumClam' && UPGRADE_CONFIG.machines.advancedClam.amount > UPGRADE_CONFIG.machines.premiumClam.amount) {
					upgrade.amount++;
					game.bp -= cost;
				} else {
					game.showError("Not enough space for more clams!");
					return true;
				}
				bpText.setText(formatBP(game.bp) + " BP");
				// Update cost text based on whether more can be purchased
				if (totalClams >= 4 && key === 'basicClam' || UPGRADE_CONFIG.machines.basicClam.amount <= UPGRADE_CONFIG.machines.advancedClam.amount && key === 'advancedClam' || UPGRADE_CONFIG.machines.advancedClam.amount <= UPGRADE_CONFIG.machines.premiumClam.amount && key === 'premiumClam') {
					costText.setText("SOLD OUT");
				} else {
					costText.setText(getUpgradeCost(upgrade) + " BP");
				}
				updateClamVisuals();
			} else if (upgrade.currentLevel < upgrade.maxLevel) {
				upgrade.currentLevel++;
				game.bp -= cost;
				// Add this section to update maxBubbleSize when lung capacity changes
				if (category === 'player' && key === 'lungCapacity') {
					var baseSize = UPGRADE_EFFECTS.lungCapacity.baseValue;
					var increasePercent = UPGRADE_EFFECTS.lungCapacity.incrementPercent;
					var multiplier = 1 + increasePercent / 100 * upgrade.currentLevel;
					game.maxBubbleSize = baseSize * multiplier;
				}
				bpText.setText(formatBP(game.bp) + " BP");
				// Update cost text based on whether more levels can be purchased
				if (upgrade.currentLevel >= upgrade.maxLevel) {
					costText.setText("SOLD OUT");
				} else {
					costText.setText(getUpgradeCost(upgrade) + " BP");
				}
				if (key === 'quickBreath') {
					game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * upgrade.currentLevel);
				}
			}
		} else {
			// Show not enough BP error
			game.showError("Not enough BP!");
		}
		return true;
	};
	menuTextContainer.addChild(hitContainer);
	menuTextContainer.addChild(nameText);
	menuTextContainer.addChild(costText);
}
// Clear existing texts
upgradeTexts.forEach(function (text) {
	return text.destroy();
});
upgradeTexts = [];
// Create left column
leftColumnUpgrades.forEach(function (upgrade, index) {
	createUpgradeText(upgrade[0], upgrade[1], index, true);
});
// Create right column
rightColumnUpgrades.forEach(function (upgrade, index) {
	createUpgradeText(upgrade[0], upgrade[1], index, false);
});
// Move the entire text container down by adjusting its Y position
menuTextContainer.y = 0; // This should align it with the top of the panel instead of being above it
menuTextContainer.x = 0; // Center in panel
// Menu state and animation
var menuOpen = false;
var menuTargetY = game.height;
function updateClamVisuals() {
	while (clamContainer.children.length) {
		clamContainer.children[0].destroy();
	}
	var leftStart = game.width * 0.1; // Moved further left from 0.15
	var rightStart = game.width * 0.9; // Moved further right from 0.85
	var spacing = 250; // Increased from 150
	var y = game.height - 100;
	// We'll store type of each clam position (0-3)
	var clamTypes = [];
	// Fill with basic clams first
	for (var i = 0; i < UPGRADE_CONFIG.machines.basicClam.amount; i++) {
		clamTypes.push('basicClam');
	}
	// Replace some with advanced
	for (var i = 0; i < UPGRADE_CONFIG.machines.advancedClam.amount; i++) {
		if (clamTypes[i]) {
			clamTypes[i] = 'advancedClam';
		}
	}
	// Replace some with premium
	for (var i = 0; i < UPGRADE_CONFIG.machines.premiumClam.amount; i++) {
		if (clamTypes[i]) {
			clamTypes[i] = 'premiumClam';
		}
	}
	// Place clams
	game.clamSpawnPoints = [];
	clamTypes.forEach(function (type, i) {
		var isRight = i % 2 === 1;
		var baseX = isRight ? rightStart : leftStart;
		var direction = isRight ? -1 : 1;
		var position = Math.floor(i / 2);
		var x = baseX + direction * position * spacing;
		var sprite = LK.getAsset(type, {
			anchorX: 0.5,
			anchorY: 1,
			x: x,
			y: y,
			scaleX: isRight ? -0.5 : 0.5,
			// Flip right-side clams
			scaleY: 0.5
		});
		// Store spawn point for this clam
		game.clamSpawnPoints.push({
			x: x + (isRight ? -75 : 75),
			// 25% from edge
			y: y - 50,
			// Slightly above clam
			type: type,
			isRight: isRight
		});
		clamContainer.addChild(sprite);
	});
}
updateClamVisuals();
// Add this to the end of the game initialization code, after updating the clam visuals
function updateAllUpgradeTexts() {
	// Process left column upgrades
	leftColumnUpgrades.forEach(function (upgrade) {
		var category = upgrade[0];
		var key = upgrade[1];
		var upgradeConfig = UPGRADE_CONFIG[category][key];
		// Find the cost text for this upgrade
		menuTextContainer.children.forEach(function (child) {
			if (child.text && child.text.includes("BP") && child.y > menuTextContainer.children.find(function (c) {
				return c.text === upgradeConfig.name;
			}).y) {
				// Check if max level reached
				if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) {
					child.setText("SOLD OUT");
				}
			}
		});
	});
	// Process right column upgrades
	rightColumnUpgrades.forEach(function (upgrade) {
		var category = upgrade[0];
		var key = upgrade[1];
		if (category === 'machines') {
			var upgradeConfig = UPGRADE_CONFIG[category][key];
			var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
			// Find the cost text for this upgrade
			menuTextContainer.children.forEach(function (child) {
				if (child.text && child.text.includes("BP") && child.y > menuTextContainer.children.find(function (c) {
					return c.text === upgradeConfig.name;
				}).y) {
					// Check if sold out based on clam logic
					if (totalClams >= 4 && key === 'basicClam' || UPGRADE_CONFIG.machines.basicClam.amount <= UPGRADE_CONFIG.machines.advancedClam.amount && key === 'advancedClam' || UPGRADE_CONFIG.machines.advancedClam.amount <= UPGRADE_CONFIG.machines.premiumClam.amount && key === 'premiumClam') {
						child.setText("SOLD OUT");
					}
				}
			});
		} else if (category === 'machine') {
			var upgradeConfig = UPGRADE_CONFIG[category][key];
			// Find the cost text for this upgrade
			menuTextContainer.children.forEach(function (child) {
				if (child.text && child.text.includes("BP") && child.y > menuTextContainer.children.find(function (c) {
					return c.text === upgradeConfig.name;
				}).y) {
					// Check if max level reached
					if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) {
						child.setText("SOLD OUT");
					}
				}
			});
		}
	});
}
// Call the function after creating all upgrades
updateAllUpgradeTexts();
// Initialize game variables 
game.growingBubble = null;
game.lastMouthState = false; // Track previous mouth state
game.mouthOpenDuration = 0; // Track how long mouth has been open
game.MOUTH_OPEN_THRESHOLD = 10; // Frames required with mouth open to start bubble
game.MIN_SPAWN_SIZE = 25; // Minimum initial bubble size
game.blowCooldown = 0; // Cooldown timer between bubble starts
game.BLOW_COOLDOWN_TIME = 15; // Frames to wait between new bubbles
game.maxBubbleSize = 160; // Increased by 30% 
game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * UPGRADE_CONFIG.player.quickBreath.currentLevel);
game.bubblePool = Array(250).fill(null).map(function () {
	return new Bubble();
});
game.activeBubbles = [];
game.MAX_BUBBLES = 200; // Active bubble limit
game.bubblePool.forEach(function (bubble) {
	game.addChild(bubble);
});
game.baseSpawnRate = 180; // Every 3 seconds
function spawnBubble(x, y, size) {
	var direction = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
	var isAutoPop = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
	if (game.activeBubbles.length >= game.MAX_BUBBLES) {
		return null;
	}
	// Find first available bubble in pool
	var bubble = game.bubblePool.find(function (b) {
		return !b.visible;
	});
	if (!bubble) {
		return null;
	}
	bubble.activate(x, y, size);
	if (isAutoPop) {
		bubble.verticalVelocity = bubble.floatSpeed;
		bubble.driftX = direction * (Math.random() * 0.8 + 0.5);
	} else {
		bubble.verticalVelocity = -(Math.random() * 2 + 4);
		bubble.driftX = direction * (Math.random() * 1.5 + 2);
	}
	game.activeBubbles.push(bubble);
	return bubble;
}
// Initialize game variables
//<Assets used in the game will automatically appear here>
game.bp = 0; // Track total BP 
game.combo = 0;
game.lastPopTime = 0;
game.COMBO_WINDOW = 60; // 1 second in frames
function formatBP(value) {
	var units = ['', 'K', 'M', 'B', 'T'];
	var unitIndex = 0;
	while (value >= 1000 && unitIndex < units.length - 1) {
		value /= 1000;
		unitIndex++;
	}
	return Math.floor(value * 10) / 10 + units[unitIndex];
}
// Create BP display text (add near game initialization)
var bpText = new Text2("0 BP", {
	size: 120,
	fill: 0xFFFFFF,
	stroke: 0x33caf8,
	strokeThickness: 4,
	font: "Impact",
	fontWeight: "bold"
});
bpText.anchor.set(1, 0);
bpText.x = game.width - 20;
bpText.y = 20;
game.addChild(bpText);
game.addBP = function (points, x, y, isAutoPop) {
	var currentTime = LK.ticks;
	// Only update combo if it's not an auto-pop
	if (!isAutoPop) {
		if (currentTime - game.lastPopTime < game.COMBO_WINDOW) {
			game.combo++;
			points *= 1 + game.combo * 0.1; // 10% bonus per combo
		} else {
			game.combo = 0;
		}
		game.lastPopTime = currentTime;
	}
	game.bp += Math.floor(points);
	bpText.setText(formatBP(game.bp) + " BP");
	// Always show point text regardless of auto or manual pop
	var pointText = new Text2("+" + Math.floor(points), {
		size: 96,
		fill: 0xFFFF00,
		font: "Impact",
		fontWeight: 'bold'
	});
	pointText.anchorX = 0.5;
	pointText.anchorY = 0.5;
	pointText.x = x;
	pointText.y = y;
	game.addChild(pointText);
	tween(pointText, {
		y: pointText.y - 100,
		alpha: 0
	}, {
		duration: 1200,
		onFinish: function onFinish() {
			pointText.destroy();
		}
	});
	// Only show combo text if it's a manual pop and we have a combo
	if (!isAutoPop && game.combo > 0) {
		var comboText = new Text2("x" + (game.combo + 1), {
			size: 96,
			fill: 0xFFA500,
			stroke: 0x000000,
			strokeThickness: 4,
			fontWeight: 'bold'
		});
		comboText.anchorX = 0.5;
		comboText.anchorY = 0;
		comboText.x = game.width / 2;
		comboText.y = 20;
		game.addChild(comboText);
		tween(comboText, {
			alpha: 0
		}, {
			duration: 500,
			onFinish: function onFinish() {
				comboText.destroy();
			}
		});
	}
};
game.update = function () {
	// Update mouth state and duration
	if (!game.lastMouthState) {
		game.mouthOpenDuration = 0;
	}
	if (facekit.mouthOpen) {
		game.mouthOpenDuration++;
	} else {
		game.mouthOpenDuration = 0;
	}
	// Only allow bubble creation if menu is closed and mouth has been open long enough
	if (!menuOpen && facekit.mouthOpen && game.mouthOpenDuration >= game.MOUTH_OPEN_THRESHOLD) {
		// Only allow new bubbles after cooldown
		if (!game.growingBubble && game.blowCooldown <= 0) {
			var spawnX = playerMask.x;
			var spawnY = playerMask.y + playerMask.height * 0.15;
			game.growingBubble = spawnBubble(spawnX, spawnY, game.MIN_SPAWN_SIZE, 0, false);
			if (game.growingBubble) {
				game.blowCooldown = game.BLOW_COOLDOWN_TIME;
			}
		}
		if (game.growingBubble) {
			game.growingBubble.x = playerMask.x;
			game.growingBubble.y = playerMask.y + playerMask.height * 0.15;
			game.growingBubble.size = Math.min(game.growingBubble.size + game.growthRate, game.maxBubbleSize);
			game.growingBubble.verticalVelocity = 0;
			game.growingBubble.driftX = 0;
		}
	} else {
		if (game.growingBubble) {
			// Recalculate float speed and lifetime based on final size
			game.growingBubble.floatSpeed = 50 * (120 / game.growingBubble.size * (0.9 + Math.random() * 0.2)) / 60;
			game.growingBubble.initLifetime();
			// Then apply the release velocity
			game.growingBubble.verticalVelocity = -10;
			game.growingBubble.driftX = (Math.random() * 2 - 1) * 2.5;
			game.growingBubble = null;
			game.mouthOpenDuration = 0;
		}
	}
	// Update cooldown timer
	if (game.blowCooldown > 0) {
		game.blowCooldown--;
	}
	game.lastMouthState = facekit.mouthOpen;
	updateClams();
	// Inside game.update, after other updates
	if (UPGRADE_CONFIG.player.autoPop.currentLevel > 0) {
		// 8 seconds (480 frames) base, reduced by 2 seconds (120 frames) per level
		if (LK.ticks % Math.max(60, 660 - UPGRADE_CONFIG.player.autoPop.currentLevel * 120) === 0) {
			var fish = new Fish();
			game.addChild(fish);
		}
	}
	if (game.activeBubbles.length < game.MAX_BUBBLES) {
		if (LK.ticks % game.baseSpawnRate == 0) {
			var x = Math.random() * (game.width - 200) + 100;
			spawnBubble(x, game.height + 100, 100, 0, true);
		}
	}
	game.activeBubbles.forEach(function (bubble) {
		if (bubble.update) {
			bubble.update();
		}
	});
};
// Handle touch/mouse events for the game
game.down = function (x, y, obj) {
	var localX = x - menuContainer.x;
	var localY = y - menuContainer.y;
	// Tab click handling
	var tabBounds = {
		x: -menuTab.width * menuTab.scaleX / 2,
		y: -menuTab.height * menuTab.scaleY,
		width: menuTab.width * menuTab.scaleX,
		height: menuTab.height * menuTab.scaleY
	};
	if (localX >= tabBounds.x && localX <= tabBounds.x + tabBounds.width && localY >= tabBounds.y && localY <= tabBounds.y + tabBounds.height) {
		menuOpen = !menuOpen;
		var targetY = menuOpen ? menuTab.height : game.height;
		if (menuOpen) {
			game.setChildIndex(menuContainer, game.children.length - 1);
		}
		tween(menuContainer, {
			y: targetY
		}, {
			duration: 300,
			easing: tween.easeOutBack,
			onFinish: function onFinish() {
				if (!menuOpen) {
					game.setChildIndex(menuContainer, 1);
				}
			}
		});
		return true;
	}
	if (menuOpen) {
		return true; // Let containers handle their own clicks
	}
	// Bubble popping logic remains the same
	var popped = false;
	// NEW:
	for (var i = game.activeBubbles.length - 1; i >= 0; i--) {
		var bubble = game.activeBubbles[i];
		var dx = x - bubble.x;
		var dy = y - bubble.y;
		var distance = Math.sqrt(dx * dx + dy * dy);
		if (!popped && distance <= bubble.size / 2 + 30 && bubble.down) {
			bubble.down();
			popped = true;
			break;
		}
	}
}; ===================================================================
--- original.js
+++ change.js
@@ -632,13 +632,18 @@
 		stroke: 0x000000,
 		strokeThickness: 5,
 		font: "Impact"
 	});
-	errorText.anchorX = 0.5;
-	errorText.anchorY = 0.5;
+	// Set the anchor point to center the text
+	errorText.anchor = {
+		x: 0.5,
+		y: 0.5
+	};
+	// Position at the center of the screen
 	errorText.x = game.width / 2;
 	errorText.y = game.height / 2;
 	game.addChild(errorText);
+	// Animate the text
 	tween(errorText, {
 		alpha: 0,
 		y: errorText.y - 50
 	}, {
:quality(85)/https://cdn.frvr.ai/67ba4b7d38fe57e0d4bf9475.png%3F3) 
 A white bubble with a black outline Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67bfab127754338fc503f264.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67c22f097033e124bf42ebe7.png%3F3) 
 A filled in white circle.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67c240d2f2b58146864d0ea7.png%3F3) 
 A yellow star. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67c33edae3ef98e836e2092f.png%3F3) 
 a game logo for a game called 'Bubble Blower Tycoon' about a happy purple pufferfish with yellow fins and spines that builds an underwater empire of bubbles. Cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67c34097e3ef98e836e2095a.png%3F3) 
 an SVG of the word 'Start'. word should be yellow and the font should look like its made out of bubbles. cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67c52c6b3591963c82f8bf83.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67cb3ceb2dbb4136c61192f7.png%3F3) 
 A outstretched straight octopus tentacle. Green with purple suckers. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67cb782f9a1a3019c1ad978e.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67cb789e9a1a3019c1ad9792.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67cb790e9a1a3019c1ad9796.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e08245e50564a967e16f72.png%3F3) 
 A colorful underwater coral reef background. Cartoon Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67e0b732e50564a967e16fe5.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b774e50564a967e16fe9.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b7b4e50564a967e16fed.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b83be50564a967e16ff5.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b8b7e50564a967e17001.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b8fee7971efb59b948a3.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b906e50564a967e17005.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b94ae50564a967e17009.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b987e50564a967e1700d.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b9b1e50564a967e17011.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0b9e5e50564a967e17015.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0ba09e50564a967e17019.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0ba34e50564a967e1701d.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0ba60e50564a967e17021.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0ba8de50564a967e17025.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67e0bbe2e50564a967e17062.png%3F3) 
 A white bubble with a black outline. Pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/67e0bc03e50564a967e17067.png%3F3) 
 bubblelow
Sound effect
backgroundmusic
Music
bubblehigh
Sound effect
bubble1
Sound effect
bubble2
Sound effect
bubble3
Sound effect
bubble4
Sound effect
blowing
Sound effect
bubbleshoot
Sound effect
fishtank
Sound effect
menuopen
Sound effect
upgrade
Sound effect
jellyfish
Sound effect
titlemusic
Music
startbutton
Sound effect