Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(contentContainer);' Line Number: 741
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(contentContainer);' Line Number: 741
User prompt
Please fix the bug: 'Can't find variable: getTabForUpgrade' in or related to this line: 'var targetContainer = tabContainers[getTabForUpgrade(category, key)];' Line Number: 914
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(contentContainer);' Line Number: 741
User prompt
Update with: Object.values(tabContainers).forEach(function(tabContainer) { tabContainer.children.forEach(function (child) { // ...existing code that finds and updates text elements }); });
Code edit (1 edits merged)
Please save this source code
User prompt
Update with: // Update the tween function where menu is opened/closed tween(menuContainer, { y: targetY }, { duration: 300, easing: tween.easeOutBack, onUpdate: function(progress) { // Show tabs only when menu is fully open tabsContainer.visible = progress > 0.9 && menuOpen; }, onFinish: function() { tabsContainer.visible = menuOpen; if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (2 edits merged)
Please save this source code
User prompt
Update as needed with: // Set up menus var currentTab = 'bubbles'; // Default tab var menuTabs = ['bubbles', 'clams', 'colors', 'decorations']; var tabButtons = {}; // Will hold references to tab buttons var menuOpen = false; // Create menu container var menuContainer = new Container(); menuContainer.x = game.width / 2; menuContainer.y = game.height; // Position at bottom initially // Create main panel first (taller to fill more screen space) var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, // Anchor at bottom y: 0, scaleX: game.width / 200, scaleY: game.height * 0.8 / 299, // Make it taller - 80% of screen height alpha: 0.9 }); menuContainer.addChild(menuPanel); // Create upgrade button at top of panel var upgradeTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, // Anchor at bottom y: -menuPanel.height * menuPanel.scaleY, // Position at top of panel scaleX: 3, scaleY: 0.8, alpha: 0.9 }); menuContainer.addChild(upgradeTab); // Add upgrade text to button (correctly sized) var upgradeText = new Text2("Upgrades", { size: 70, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); upgradeText.anchor = { x: 0.5, y: 0.5 }; upgradeText.x = 0; upgradeText.y = -upgradeTab.height * upgradeTab.scaleY / 2; upgradeTab.addChild(upgradeText); // Create text container for upgrades var menuTextContainer = new Container(); menuTextContainer.y = -menuPanel.height * menuPanel.scaleY / 2; // Center in panel menuContainer.addChild(menuTextContainer); // Create upgrade sections var upgradeSections = { bubbles: new Container(), clams: new Container(), colors: new Container(), decorations: new Container() }; // Add sections to text container Object.values(upgradeSections).forEach(function(section) { menuTextContainer.addChild(section); section.visible = false; // Hide all initially }); // Create tab container at the BOTTOM of the menu panel var tabContainer = new Container(); tabContainer.y = -50; // Position near bottom of panel, adjust as needed menuContainer.addChild(tabContainer); function switchToTab(tabName) { if (currentTab === tabName) return; // Update current tab currentTab = tabName; // Highlight active tab highlightTab(tabName); // Hide all upgrade sections Object.values(upgradeSections).forEach(function(section) { section.visible = false; }); // Show selected section upgradeSections[tabName].visible = true; } function highlightTab(tabName) { // Reset all tabs Object.keys(tabButtons).forEach(function(tab) { tabButtons[tab].bg.alpha = 0.6; tabButtons[tab].text.fill = 0xFFFFFF; }); // Highlight selected tab tabButtons[tabName].bg.alpha = 1; tabButtons[tabName].text.fill = 0xFFFF00; } function initializeMenuTabs() { // Create tab buttons var tabWidth = menuPanel.width * menuPanel.scaleX / menuTabs.length; menuTabs.forEach(function(tabName, index) { // Create tab background var tabBg = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0.5, x: (index - 1.5) * tabWidth + tabWidth / 2, scaleX: tabWidth / 200, scaleY: 0.5, alpha: 0.6 }); // Tab text var tabText = new Text2(tabName.charAt(0).toUpperCase() + tabName.slice(1), { size: 60, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); tabText.anchor = { x: 0.5, y: 0.5 }; tabText.x = (index - 1.5) * tabWidth + tabWidth / 2; // Add to tab container tabContainer.addChild(tabBg); tabContainer.addChild(tabText); // Store reference to tab button tabButtons[tabName] = { bg: tabBg, text: tabText }; // Add click handler tabBg.down = function() { switchToTab(tabName); return true; }; }); // Highlight default tab highlightTab(currentTab); // Show bubbles section by default upgradeSections.bubbles.visible = true; } // Add menu container to game game.addChild(menuContainer); initializeMenuTabs(); // Update click handling in game.down upgradeTab.down = function() { menuOpen = !menuOpen; var targetY = menuOpen ? menuPanel.height * menuPanel.scaleY : game.height; if (menuOpen) { game.setChildIndex(menuContainer, game.children.length - 1); } tween(menuContainer, { y: targetY }, { duration: 300, easing: tween.easeOutBack, onFinish: function() { if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); return true; }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Update only as needed with: // Set up menus var currentTab = 'bubbles'; // Default tab var menuTabs = ['bubbles', 'clams', 'colors', 'decorations']; var tabButtons = {}; // Will hold references to tab buttons var menuOpen = false; // Create menu container var menuContainer = new Container(); menuContainer.x = game.width / 2; menuContainer.y = game.height; // Position at bottom initially // Create main panel first (taller to fill more screen space) var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, // Anchor at bottom y: 0, scaleX: game.width / 200, scaleY: game.height * 0.8 / 299, // Make it taller - 80% of screen height alpha: 0.9 }); menuContainer.addChild(menuPanel); // Create upgrade button at top of panel var upgradeTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, // Anchor at bottom y: -menuPanel.height * menuPanel.scaleY, // Position at top of panel scaleX: 3, scaleY: 0.8, alpha: 0.9 }); menuContainer.addChild(upgradeTab); // Add upgrade text to button (correctly sized) var upgradeText = new Text2("Upgrades", { size: 70, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); upgradeText.anchor = { x: 0.5, y: 0.5 }; upgradeText.x = 0; upgradeText.y = -upgradeTab.height * upgradeTab.scaleY / 2; upgradeTab.addChild(upgradeText); // Create text container for upgrades var menuTextContainer = new Container(); menuTextContainer.y = -menuPanel.height * menuPanel.scaleY / 2; // Center in panel menuContainer.addChild(menuTextContainer); // Create upgrade sections var upgradeSections = { bubbles: new Container(), clams: new Container(), colors: new Container(), decorations: new Container() }; // Add sections to text container Object.values(upgradeSections).forEach(function(section) { menuTextContainer.addChild(section); section.visible = false; // Hide all initially }); // Create tab container at the BOTTOM of the menu panel var tabContainer = new Container(); tabContainer.y = -50; // Position near bottom of panel, adjust as needed menuContainer.addChild(tabContainer); function switchToTab(tabName) { if (currentTab === tabName) return; // Update current tab currentTab = tabName; // Highlight active tab highlightTab(tabName); // Hide all upgrade sections Object.values(upgradeSections).forEach(function(section) { section.visible = false; }); // Show selected section upgradeSections[tabName].visible = true; } function highlightTab(tabName) { // Reset all tabs Object.keys(tabButtons).forEach(function(tab) { tabButtons[tab].bg.alpha = 0.6; tabButtons[tab].text.fill = 0xFFFFFF; }); // Highlight selected tab tabButtons[tabName].bg.alpha = 1; tabButtons[tabName].text.fill = 0xFFFF00; } function initializeMenuTabs() { // Create tab buttons var tabWidth = menuPanel.width * menuPanel.scaleX / menuTabs.length; menuTabs.forEach(function(tabName, index) { // Create tab background var tabBg = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0.5, x: (index - 1.5) * tabWidth + tabWidth / 2, scaleX: tabWidth / 200, scaleY: 0.5, alpha: 0.6 }); // Tab text var tabText = new Text2(tabName.charAt(0).toUpperCase() + tabName.slice(1), { size: 60, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); tabText.anchor = { x: 0.5, y: 0.5 }; tabText.x = (index - 1.5) * tabWidth + tabWidth / 2; // Add to tab container tabContainer.addChild(tabBg); tabContainer.addChild(tabText); // Store reference to tab button tabButtons[tabName] = { bg: tabBg, text: tabText }; // Add click handler tabBg.down = function() { switchToTab(tabName); return true; }; }); // Highlight default tab highlightTab(currentTab); // Show bubbles section by default upgradeSections.bubbles.visible = true; } // Add menu container to game game.addChild(menuContainer); initializeMenuTabs(); // Update click handling in game.down upgradeTab.down = function() { menuOpen = !menuOpen; var targetY = menuOpen ? menuPanel.height * menuPanel.scaleY : game.height; if (menuOpen) { game.setChildIndex(menuContainer, game.children.length - 1); } tween(menuContainer, { y: targetY }, { duration: 300, easing: tween.easeOutBack, onFinish: function() { if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); return true; }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Update with: // Inside game.down function // Check if upgrade tab was clicked var localX = x - menuContainer.x; var localY = y - menuContainer.y; if (localX >= -upgradeTab.width * upgradeTab.scaleX / 2 && localX <= upgradeTab.width * upgradeTab.scaleX / 2 && localY >= -upgradeTab.height * upgradeTab.scaleY && localY <= 0) { menuOpen = !menuOpen; var targetY = menuOpen ? upgradeTab.height * upgradeTab.scaleY : game.height; if (menuOpen) { game.setChildIndex(menuContainer, game.children.length - 1); } tween(menuContainer, { y: targetY }, { duration: 300, easing: tween.easeOutBack, onFinish: function() { if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); return true; } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Update with: // Set up menus var currentTab = 'bubbles'; // Default tab var menuTabs = ['bubbles', 'clams', 'colors', 'decorations']; var tabButtons = {}; // Will hold references to tab buttons var menuOpen = false; // Create menu container var menuContainer = new Container(); menuContainer.x = game.width / 2; menuContainer.y = game.height; // Position at bottom initially // Create upgrade button (visible when menu closed) var upgradeTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, y: 0, scaleX: 3, scaleY: 0.8, alpha: 0.9 }); // Add button text var upgradeText = new Text2("Upgrades", { size: 70, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); upgradeText.anchor = { x: 0.5, y: 0.5 }; upgradeText.x = 0; upgradeText.y = -upgradeTab.height * upgradeTab.scaleY / 2; upgradeTab.addChild(upgradeText); // Create main panel (full screen size minus margins) var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, y: -upgradeTab.height * upgradeTab.scaleY, // Position above the button scaleX: 2048 / 200, scaleY: (game.height - 2 * (upgradeTab.height * upgradeTab.scaleY)) / 299, alpha: 0.9 }); // Add panel and button to container menuContainer.addChild(menuPanel); menuContainer.addChild(upgradeTab); // Create text container for upgrades var menuTextContainer = new Container(); menuTextContainer.y = -menuPanel.height * menuPanel.scaleY / 2; // Center in panel menuContainer.addChild(menuTextContainer); // Create tab container at the bottom of the menu panel var tabContainer = new Container(); tabContainer.y = -menuPanel.height * menuPanel.scaleY + 100; // Position at bottom of panel menuContainer.addChild(tabContainer); // Create upgrade sections var upgradeSections = { bubbles: new Container(), clams: new Container(), colors: new Container(), decorations: new Container() }; // Add sections to text container Object.values(upgradeSections).forEach(function(section) { menuTextContainer.addChild(section); section.visible = false; // Hide all initially }); // Add menu container to game game.addChild(menuContainer);
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(section);' Line Number: 406
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(section);' Line Number: 406
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'upgradeSections.bubbles')' in or related to this line: 'upgradeSections.bubbles.visible = true; // Show default tab' Line Number: 397
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'upgradeSections.bubbles')' in or related to this line: 'upgradeSections.bubbles.visible = true; // Show default tab' Line Number: 397
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'upgradeSections.bubbles')' in or related to this line: 'upgradeSections.bubbles.visible = true; // Show default tab' Line Number: 403
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'upgradeTexts.forEach')' in or related to this line: 'upgradeTexts.forEach(function (text) {' Line Number: 398
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'upgradeTexts.forEach')' in or related to this line: 'upgradeTexts.forEach(function (text) {' Line Number: 398
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Can't find variable: menuContainer' in or related to this line: 'menuContainer.addChild(tabContainer);' Line Number: 443
/**** 
* 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
****/ 
// Initialize game variables 
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: 100,
			costScale: 3.0,
			maxLevel: 10,
			currentLevel: 0
		},
		quickBreath: {
			name: "Quick Breath",
			baseCost: 100,
			costScale: 3.0,
			maxLevel: 10,
			currentLevel: 0
		},
		autoPop: {
			name: "Fish Friends",
			baseCost: 400,
			costScale: 3,
			maxLevel: 6,
			currentLevel: 0
		}
	},
	machines: {
		basicClam: {
			name: "Basic Clam",
			baseCost: 300,
			costScale: 2,
			amount: 0,
			production: 3,
			bubbleSize: 80
		},
		advancedClam: {
			name: "Advanced Clam",
			baseCost: 3000,
			costScale: 3.0,
			amount: 0,
			production: 2,
			bubbleSize: 100,
			unlockCost: 3000
		},
		premiumClam: {
			name: "Premium Clam",
			baseCost: 20000,
			costScale: 3,
			amount: 0,
			production: 1,
			bubbleSize: 150,
			unlockCost: 20000
		}
	},
	machine: {
		bubbleDurability: {
			name: "Bubble Splitting",
			baseCost: 500,
			costScale: 5,
			maxLevel: 3,
			currentLevel: 0
		},
		autoBubbleSpeed: {
			name: "Clam Speed",
			baseCost: 500,
			costScale: 3.5,
			maxLevel: 8,
			currentLevel: 0
		}
	}
};
var UPGRADE_EFFECTS = {
	lungCapacity: {
		baseValue: 160,
		incrementPercent: 25
	},
	quickBreath: {
		baseValue: 1.6,
		incrementPercent: 25
	},
	autoBubbleSpeed: {
		decrementPercent: 10
	},
	bubbleDurability: {
		extraSplits: 1
	},
	autoPop: {
		timeReduction: 0.8
	}
};
game.showError = function (message) {
	var errorText = new Text2(message, {
		size: 120,
		fill: 0xFF0000,
		stroke: 0x000000,
		strokeThickness: 5,
		font: "Impact"
	});
	errorText.anchor = {
		x: 0.5,
		y: 0.5
	};
	errorText.x = game.width / 2;
	errorText.y = game.height / 2;
	game.addChild(errorText);
	tween(errorText, {
		alpha: 0,
		y: errorText.y - 50
	}, {
		duration: 1200,
		onFinish: function onFinish() {
			errorText.destroy();
		}
	});
};
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 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];
}
// Set up menus
var currentTab = 'bubbles'; // Default tab
var menuTabs = ['bubbles', 'clams', 'colors', 'decorations'];
var tabButtons = {}; // Will hold references to tab buttons
var menuOpen = false;
// Create menu container
var menuContainer = new Container();
menuContainer.x = game.width / 2;
menuContainer.y = game.height; // Position at bottom initially
// Create upgrade button (visible when menu closed)
var upgradeTab = LK.getAsset('upgradetab', {
	anchorX: 0.5,
	anchorY: 1,
	y: 0,
	scaleX: 3,
	scaleY: 0.8,
	alpha: 0.9
});
// Add button text
var upgradeText = new Text2("Upgrades", {
	size: 70,
	fill: 0xFFFFFF,
	stroke: 0x000000,
	strokeThickness: 3,
	font: "Impact"
});
upgradeText.anchor = {
	x: 0.5,
	y: 0.5
};
upgradeText.x = 0;
upgradeText.y = -upgradeTab.height * upgradeTab.scaleY / 2;
upgradeTab.addChild(upgradeText);
// Create main panel (full screen size minus margins)
var menuPanel = LK.getAsset('upgradetab', {
	anchorX: 0.5,
	anchorY: 0,
	y: -upgradeTab.height * upgradeTab.scaleY,
	// Position above the button
	scaleX: 2048 / 200,
	scaleY: (game.height - 2 * (upgradeTab.height * upgradeTab.scaleY)) / 299,
	alpha: 0.9
});
// Add panel and button to container
menuContainer.addChild(menuPanel);
menuContainer.addChild(upgradeTab);
// Create text container for upgrades
var menuTextContainer = new Container();
menuTextContainer.y = -menuPanel.height * menuPanel.scaleY / 2; // Center in panel
menuContainer.addChild(menuTextContainer);
// Create tab container at the bottom of the menu panel
var tabContainer = new Container();
tabContainer.y = -menuPanel.height * menuPanel.scaleY + 100; // Position at bottom of panel
menuContainer.addChild(tabContainer);
// Create upgrade sections
var upgradeSections = {
	bubbles: new Container(),
	clams: new Container(),
	colors: new Container(),
	decorations: new Container()
};
// Add sections to text container
Object.values(upgradeSections).forEach(function (section) {
	menuTextContainer.addChild(section);
	section.visible = false; // Hide all initially
});
// Add menu container to game
game.addChild(menuContainer);
function switchToTab(tabName) {
	if (currentTab === tabName) {
		return;
	}
	// Update current tab
	currentTab = tabName;
	// Highlight active tab
	highlightTab(tabName);
	// Hide all upgrade sections
	Object.values(upgradeSections).forEach(function (section) {
		section.visible = false;
	});
	// Show selected section
	upgradeSections[tabName].visible = true;
}
function highlightTab(tabName) {
	// Reset all tabs
	Object.keys(tabButtons).forEach(function (tab) {
		tabButtons[tab].bg.alpha = 0.8;
		tabButtons[tab].text.fill = 0xFFFFFF;
	});
	// Highlight selected tab
	tabButtons[tabName].bg.alpha = 1;
	tabButtons[tabName].text.fill = 0xFFFF00;
}
function initializeMenuTabs() {
	// Create tab container at the bottom of the menu panel
	var tabContainer = new Container();
	tabContainer.y = -150; // Position above the bottom of the panel
	menuContainer.addChild(tabContainer);
	// Create tab buttons
	var tabWidth = menuPanel.width * menuPanel.scaleX / menuTabs.length;
	menuTabs.forEach(function (tabName, index) {
		// Create tab background
		var tabBg = LK.getAsset('upgradetab', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: (index - 1.5) * tabWidth + tabWidth / 2,
			scaleX: tabWidth / 200,
			scaleY: 0.5,
			alpha: 0.8
		});
		// Tab text
		var tabText = new Text2(tabName.charAt(0).toUpperCase() + tabName.slice(1), {
			size: 60,
			fill: 0xFFFFFF,
			stroke: 0x000000,
			strokeThickness: 2,
			font: "Impact"
		});
		tabText.anchor = {
			x: 0.5,
			y: 0.5
		};
		tabText.x = (index - 1.5) * tabWidth + tabWidth / 2;
		// Add to tab container
		tabContainer.addChild(tabBg);
		tabContainer.addChild(tabText);
		// Store reference to tab button
		tabButtons[tabName] = {
			bg: tabBg,
			text: tabText
		};
		// Add click handler
		tabBg.down = function () {
			switchToTab(tabName);
			return true;
		};
	});
	// Highlight default tab
	highlightTab(currentTab);
}
var startY = 150;
var upgradeSpacing = 250;
var columnWidth = 1024;
function createUpgradeText(category, key, index, isLeftColumn, tabSection) {
	var upgrade = UPGRADE_CONFIG[category][key];
	var xOffset = isLeftColumn ? -550 : 100;
	var yPos = startY + index % 5 * upgradeSpacing;
	// 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
	});
	hitContainer.addChild(hitArea);
	hitContainer.x = xOffset;
	hitContainer.y = yPos;
	// Adjust the hitArea position within the container to center properly
	hitArea.x = 0;
	hitArea.y = -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 () {
		// Your existing upgrade logic here
		return true;
	};
	// Add to the correct section
	upgradeSections[tabSection].addChild(hitContainer);
	upgradeSections[tabSection].addChild(nameText);
	upgradeSections[tabSection].addChild(costText);
}
// Organize upgrades by tab
var bubbleUpgrades = [['player', 'lungCapacity'], ['player', 'quickBreath'], ['machine', 'bubbleDurability']];
var clamUpgrades = [['machines', 'basicClam'], ['machines', 'advancedClam'], ['machines', 'premiumClam'], ['machine', 'autoBubbleSpeed'], ['player', 'autoPop']];
// Add color upgrades from the new plans
var colorUpgrades = [
	// Will add color upgrades from the Prismatic Bubbles section later
];
// Add decoration upgrades from the new plans
var decorationUpgrades = [
	// Will add Bubble Coral and Sunken Treasures later
];
function initializeAllUpgrades() {
	// Add bubble upgrades
	bubbleUpgrades.forEach(function (upgrade, index) {
		createUpgradeText(upgrade[0], upgrade[1], index, index % 2 === 0, 'bubbles');
	});
	// Add clam upgrades
	clamUpgrades.forEach(function (upgrade, index) {
		createUpgradeText(upgrade[0], upgrade[1], index, index % 2 === 0, 'clams');
	});
	// Will add color and decoration upgrades later
}
function updateAllUpgradeTexts() {
	// Process bubble upgrades
	bubbleUpgrades.forEach(function (upgrade) {
		var category = upgrade[0];
		var key = upgrade[1];
		var upgradeConfig = UPGRADE_CONFIG[category][key];
		// Find the cost text for this upgrade in the bubbles section
		upgradeSections.bubbles.children.forEach(function (child) {
			if (child.text && child.text.includes("BP") && upgradeSections.bubbles.children.some(function (c) {
				return c.text === upgradeConfig.name && child.y > c.y;
			})) {
				// Check if max level reached
				if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) {
					child.setText("SOLD OUT");
				} else {
					child.setText(getUpgradeCost(upgradeConfig) + " BP");
				}
			}
		});
	});
	// Process clam upgrades
	clamUpgrades.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 in the clams section
			upgradeSections.clams.children.forEach(function (child) {
				if (child.text && child.text.includes("BP") && upgradeSections.clams.children.some(function (c) {
					return c.text === upgradeConfig.name && child.y > c.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 {
						child.setText(getUpgradeCost(upgradeConfig) + " BP");
					}
				}
			});
		} else {
			var upgradeConfig = UPGRADE_CONFIG[category][key];
			// Find the cost text for this upgrade in the clams section
			upgradeSections.clams.children.forEach(function (child) {
				if (child.text && child.text.includes("BP") && upgradeSections.clams.children.some(function (c) {
					return c.text === upgradeConfig.name && child.y > c.y;
				})) {
					// Check if max level reached
					if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) {
						child.setText("SOLD OUT");
					} else {
						child.setText(getUpgradeCost(upgradeConfig) + " BP");
					}
				}
			});
		}
	});
}
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);
	});
}
// 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 BP system
game.bp = 0; // Track total BP 
game.combo = 0;
game.lastPopTime = 0;
game.COMBO_WINDOW = 60; // 1 second in frames
// Create BP display text
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;
	}
	// Ensure points is at least 1
	points = Math.max(1, Math.floor(points));
	game.bp += points;
	bpText.setText(formatBP(game.bp) + " BP");
	// Set size and color based on point value
	var textSize = 96;
	var textColor = 0xFFFF00; // Default yellow
	if (points > 100) {
		textSize = 120;
		textColor = 0xFF0000; // Red for >100
	} else if (points > 50) {
		textSize = 108;
		textColor = 0xFFA500; // Orange for 51-100
	}
	// Always show point text regardless of auto or manual pop
	var pointText = new Text2("+" + points, {
		size: textSize,
		fill: textColor,
		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();
			}
		});
	}
};
// Menu state and animation
var menuOpen = false;
var menuTargetY = game.height;
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);
			}
		}
	});
}
// Set the default visible tab
upgradeSections.bubbles.visible = true;
// Initialize tabs and upgrades
initializeMenuTabs();
initializeAllUpgrades();
// Update clam visuals initially
updateClamVisuals();
// Update upgrade texts initially
updateAllUpgradeTexts();
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
@@ -430,52 +430,58 @@
 // Set up menus
 var currentTab = 'bubbles'; // Default tab
 var menuTabs = ['bubbles', 'clams', 'colors', 'decorations'];
 var tabButtons = {}; // Will hold references to tab buttons
+var menuOpen = false;
 // Create menu container
 var menuContainer = new Container();
 menuContainer.x = game.width / 2;
-menuContainer.y = game.height; // Position at bottom
-// Create menu panel and tab
-var menuPanel = LK.getAsset('upgradetab', {
+menuContainer.y = game.height; // Position at bottom initially
+// Create upgrade button (visible when menu closed)
+var upgradeTab = LK.getAsset('upgradetab', {
 	anchorX: 0.5,
-	anchorY: 0,
-	y: 0,
-	alpha: 0.9,
-	scaleX: 2048 / 200,
-	scaleY: game.height * 0.4 / 299
-});
-var menuTab = LK.getAsset('upgradetab', {
-	anchorX: 0.5,
 	anchorY: 1,
-	y: -menuPanel.height * menuPanel.scaleY,
+	y: 0,
 	scaleX: 3,
 	scaleY: 0.8,
 	alpha: 0.9
 });
-// Add panel and tab to container
-menuContainer.addChild(menuPanel);
-menuContainer.addChild(menuTab);
-// Add menu text
-var menuText = new Text2("Upgrades", {
-	size: 90,
+// Add button text
+var upgradeText = new Text2("Upgrades", {
+	size: 70,
 	fill: 0xFFFFFF,
 	stroke: 0x000000,
 	strokeThickness: 3,
 	font: "Impact"
 });
-menuText.anchor = {
+upgradeText.anchor = {
 	x: 0.5,
 	y: 0.5
 };
-menuText.x = 0; // Relative to container
-menuText.y = -menuPanel.height * menuPanel.scaleY - menuTab.height * menuTab.scaleY / 2; // Position relative to container bottom
-menuContainer.addChild(menuText);
-// Add menu container to game
-game.addChild(menuContainer);
+upgradeText.x = 0;
+upgradeText.y = -upgradeTab.height * upgradeTab.scaleY / 2;
+upgradeTab.addChild(upgradeText);
+// Create main panel (full screen size minus margins)
+var menuPanel = LK.getAsset('upgradetab', {
+	anchorX: 0.5,
+	anchorY: 0,
+	y: -upgradeTab.height * upgradeTab.scaleY,
+	// Position above the button
+	scaleX: 2048 / 200,
+	scaleY: (game.height - 2 * (upgradeTab.height * upgradeTab.scaleY)) / 299,
+	alpha: 0.9
+});
+// Add panel and button to container
+menuContainer.addChild(menuPanel);
+menuContainer.addChild(upgradeTab);
 // Create text container for upgrades
 var menuTextContainer = new Container();
+menuTextContainer.y = -menuPanel.height * menuPanel.scaleY / 2; // Center in panel
 menuContainer.addChild(menuTextContainer);
+// Create tab container at the bottom of the menu panel
+var tabContainer = new Container();
+tabContainer.y = -menuPanel.height * menuPanel.scaleY + 100; // Position at bottom of panel
+menuContainer.addChild(tabContainer);
 // Create upgrade sections
 var upgradeSections = {
 	bubbles: new Container(),
 	clams: new Container(),
@@ -486,8 +492,10 @@
 Object.values(upgradeSections).forEach(function (section) {
 	menuTextContainer.addChild(section);
 	section.visible = false; // Hide all initially
 });
+// Add menu container to game
+game.addChild(menuContainer);
 function switchToTab(tabName) {
 	if (currentTab === tabName) {
 		return;
 	}
: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