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
User prompt
update with: 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 = -12; game.growingBubble.driftX = (Math.random() * 2 - 1) * 2.5; game.growingBubble = null; game.mouthOpenDuration = 0; }
User prompt
update with: if (game.growingBubble) { if (facekit.mouthOpen) { 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 { // Recalculate properties based on final size game.growingBubble.initLifetime(); game.growingBubble.floatSpeed = 50 * (120 / game.growingBubble.size * (0.9 + Math.random() * 0.2)) / 60; game.growingBubble.verticalVelocity = -12; game.growingBubble.driftX = (Math.random() * 2 - 1) * 2.5; game.growingBubble = null; game.mouthOpenDuration = 0; } } βͺπ‘ Consider importing and using the following plugins: @upit/facekit.v1
User prompt
update with: self.activate = function(x, y, size, isPlayerBlown = 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 }); };
User prompt
update with: if (self.lifetime > self.maxLifetime) { console.log('Bubble dying - lifetime:', self.lifetime, 'maxLifetime:', self.maxLifetime); // rest of the code... }
User prompt
update with: self.activate = function(x, y, size, isPlayerBlown = false) { self.x = x; self.y = y; self.size = size; self.lifetime = 0; self.hasSplit = false; self.splitHeight = null; self.justSplit = false; self.autoPopDisplayed = false; // For player bubbles, base lifetime on max possible size if (isPlayerBlown) { self.maxLifetime = Math.floor(Math.random() * 960 + 1440); // Use game.maxBubbleSize instead of initial size self.maxLifetime *= Math.min(1, game.maxBubbleSize / 100); self.floatSpeed = (50 * (120 / size * (0.9 + Math.random() * 0.2)) / 60) * 0.7; console.log('Player bubble lifetime:', self.maxLifetime); } else { self.initLifetime(); self.floatSpeed = 50 * (120 / size * (0.9 + Math.random() * 0.2)) / 60; } self.visible = true; self.verticalVelocity = 0; self.driftX = (Math.random() * 20 - 10) / 60; };
User prompt
update with: self.activate = function(x, y, size, isPlayerBlown = false) { self.x = x; self.y = y; self.size = size; self.lifetime = 0; self.hasSplit = false; self.splitHeight = null; self.justSplit = false; self.autoPopDisplayed = false; // For player bubbles, base lifetime on max possible size if (isPlayerBlown) { self.maxLifetime = Math.floor(Math.random() * 960 + 1440); // Use game.maxBubbleSize instead of initial size self.maxLifetime *= Math.min(1, game.maxBubbleSize / 100); } else { self.initLifetime(); } self.visible = true; // Reset velocities self.verticalVelocity = 0; self.driftX = (Math.random() * 20 - 10) / 60; self.floatSpeed = 50 * (120 / size * (0.9 + Math.random() * 0.2)) / 60; };
User prompt
update with: 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); } }
User prompt
update with: 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; };
User prompt
update with: if (self.y < -self.size) { // Just deactivate, no points self.visible = false; const index = game.activeBubbles.indexOf(self); if (index > -1) { game.activeBubbles.splice(index, 1); } return; }
User prompt
update with: self.deactivate = function() { self.visible = false; const index = game.activeBubbles.indexOf(self); if (index > -1) { game.activeBubbles.splice(index, 1); } // Don't award points here - let the calling function handle it };
Code edit (1 edits merged)
Please save this source code
User prompt
update with: game.bubblePool = Array(250).fill(null).map(() => new Bubble()); game.activeBubbles = []; game.MAX_BUBBLES = 200; // Active bubble limit
User prompt
update with: UPGRADE_CONFIG.machines.basicClam.bubbleSize = 50; // Was 25 UPGRADE_CONFIG.machines.advancedClam.bubbleSize = 75; // Was 35 UPGRADE_CONFIG.machines.premiumClam.bubbleSize = 100; // Was 50
User prompt
update as needed with: // Add these calculations to updateClamVisuals: function updateClamVisuals() { // ... existing clear code ... const leftStart = game.width * 0.1; const rightStart = game.width * 0.9; const spacing = 250; // Add this to store spawn positions game.clamSpawnPoints = []; clamTypes.forEach((type, i) => { const isRight = i % 2 === 1; const baseX = isRight ? rightStart : leftStart; const direction = isRight ? -1 : 1; const position = Math.floor(i/2); const x = baseX + (direction * position * spacing); const sprite = LK.getAsset(type, { anchorX: 0.5, anchorY: 1, x: x, y: y, scaleX: isRight ? -0.5 : 0.5, 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 }); clamContainer.addChild(sprite); }); } // Update the clam spawning logic: function updateClams() { if (!game.clamSpawnPoints) return; game.clamSpawnPoints.forEach(spawnPoint => { const config = UPGRADE_CONFIG.machines[spawnPoint.type]; // Calculate production time with speed upgrade const baseTime = config.production * 60; // Convert to frames const speedMultiplier = Math.pow(1 - UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100, UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel); const adjustedTime = Math.max(1, Math.floor(baseTime * speedMultiplier)); if (LK.ticks % adjustedTime === 0) { if (game.bubbles.length < game.MAX_BUBBLES) { spawnSplitBubble( spawnPoint.x, spawnPoint.y, config.bubbleSize, spawnPoint.isRight ? -0.5 : 0.5 ); } } }); }
User prompt
update with: function updateClamVisuals() { while (clamContainer.children.length) { clamContainer.children[0].destroy(); } // Increase edge distance and spacing const leftStart = game.width * 0.1; // Moved further left from 0.15 const rightStart = game.width * 0.9; // Moved further right from 0.85 const spacing = 250; // Increased from 150 const y = game.height - 100; // Rest of clam type determination stays the same... // Place clams clamTypes.forEach((type, i) => { const isRight = i % 2 === 1; const baseX = isRight ? rightStart : leftStart; const direction = isRight ? -1 : 1; const position = Math.floor(i/2); const x = baseX + (direction * position * spacing); const 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 }); clamContainer.addChild(sprite); }); }
User prompt
update with: hitContainer.down = function () { const cost = getUpgradeCost(upgrade); if (game.bp >= cost) { if (category === 'machines') { const 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; } bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); updateClamVisuals(); } else { // Rest of upgrade logic stays the same... } } return true; };
User prompt
update with: function updateClamVisuals() { while (clamContainer.children.length) { clamContainer.children[0].destroy(); } const leftStart = game.width * 0.15; const rightStart = game.width * 0.85; const spacing = 150; const y = game.height - 100; // We'll store type of each clam position (0-3) const clamTypes = []; // Fill with basic clams first for (let i = 0; i < UPGRADE_CONFIG.machines.basicClam.amount; i++) { clamTypes.push('basicClam'); } // Replace some with advanced for (let i = 0; i < UPGRADE_CONFIG.machines.advancedClam.amount; i++) { if (clamTypes[i]) { clamTypes[i] = 'advancedClam'; } } // Replace some with premium for (let i = 0; i < UPGRADE_CONFIG.machines.premiumClam.amount; i++) { if (clamTypes[i]) { clamTypes[i] = 'premiumClam'; } } // Place clams clamTypes.forEach((type, i) => { const isRight = i % 2 === 1; const baseX = isRight ? rightStart : leftStart; const direction = isRight ? -1 : 1; const position = Math.floor(i/2); const x = baseX + (direction * position * spacing); const sprite = LK.getAsset(type, { anchorX: 0.5, anchorY: 1, x: x, y: y, scaleX: 0.5, scaleY: 0.5 }); clamContainer.addChild(sprite); }); }
User prompt
update with: function updateClamVisuals() { // Previous clear code stays the same... // Determine current clam type and count let clamType, count; if (UPGRADE_CONFIG.machines.premiumClam.amount > 0) { clamType = 'premiumClam'; count = UPGRADE_CONFIG.machines.premiumClam.amount; } else if (UPGRADE_CONFIG.machines.advancedClam.amount > 0) { clamType = 'advancedClam'; count = UPGRADE_CONFIG.machines.advancedClam.amount; } else { clamType = 'basicClam'; count = UPGRADE_CONFIG.machines.basicClam.amount; } // Rest of the function stays the same... }
User prompt
update with: function updateClamVisuals() { // Clear existing clams while (clamContainer.children.length) { clamContainer.children[0].destroy(); } // Calculate positions for clams on each side const leftStart = game.width * 0.15; const rightStart = game.width * 0.85; const spacing = 150; // Space between clams on same side const y = game.height - 100; // Offset from bottom // Get counts of each type const basicCount = UPGRADE_CONFIG.machines.basicClam.amount; const advancedCount = UPGRADE_CONFIG.machines.advancedClam.amount; const premiumCount = UPGRADE_CONFIG.machines.premiumClam.amount; // Determine current clam type let clamType = 'basicClam'; if (basicCount >= 4) { clamType = 'advancedClam'; if (advancedCount >= 4) { clamType = 'premiumClam'; } } const count = UPGRADE_CONFIG.machines[clamType].amount; // Place clams one by one, alternating sides for (let i = 0; i < count; i++) { const isRight = i % 2 === 1; // Odd numbers go on right const baseX = isRight ? rightStart : leftStart; const direction = isRight ? -1 : 1; const position = Math.floor(i/2); // How far from edge const x = baseX + (direction * position * spacing); const sprite = LK.getAsset(clamType, { anchorX: 0.5, anchorY: 1, x: x, y: y, scaleX: 0.5, scaleY: 0.5 }); clamContainer.addChild(sprite); } }
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'UPGRADE_CONFIG.machines[clamType] is undefined' in or related to this line: 'if (UPGRADE_CONFIG.machines[clamType].amount <= i) {' Line Number: 783
Code edit (2 edits merged)
Please save this source code
User prompt
update with: hitContainer.down = function () { console.log('Click on:', upgrade.name); const cost = getUpgradeCost(upgrade); if (game.bp >= cost) { if (category === 'machines') { if (upgrade.amount < 4) { // Limit to 4 clams upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); updateClamVisuals(); } } else if (upgrade.currentLevel < upgrade.maxLevel) { upgrade.currentLevel++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); } } return true; };
/**** 
* 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; // Add timestamp tracking
	var sprite = self.attachAsset('bubble', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.9
	});
	self.size = 100;
	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) {
		// Add cooldown check (100ms)
		var currentTime = Date.now();
		if (currentTime - self.lastPopTime < 100) {
			return true; // Ignore clicks too close together
		}
		self.lastPopTime = currentTime;
		var index = game.bubbles.indexOf(self);
		if (index > -1) {
			game.bubbles.splice(index, 1);
		}
		var points = self.getBP();
		game.addBP(points, self.x, self.y, false); // Pass position and flag for manual pop
		// Only split if manually popped and large enough
		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;
				spawnSplitBubble(self.x, self.y, newSize, Math.cos(angle),
				// More even distribution
				false);
			}
		}
		self.destroy();
		return true; // Stop event propagation
	};
	self.getBP = function () {
		return Math.max(1, Math.floor(Math.pow(self.size, 1.4) * 0.02));
	};
	self.update = function () {
		self.lifetime++;
		// Add subtle drift variation
		if (self.lifetime % 60 === 0) {
			// Every second
			self.driftX += (Math.random() - 0.5) * 0.3; // Add small random drift
		}
		// Increase overall horizontal movement
		self.x += self.driftX * 1.2; // 20% more horizontal movement
		// Auto-pop or split when lifetime exceeded
		if (self.lifetime > self.maxLifetime) {
			// Just check size and not already split
			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 = spawnSplitBubble(self.x, self.y, newSize, i === 0 ? -1 : 1, true);
					split.maxLifetime *= 0.7; // Shorter lifetime for split bubbles
				}
				self.destroy();
				return;
			}
			// If too small to split, just pop
			self.autoPop();
			return;
		}
		// Clear off-screen bubbles
		if (self.y < -self.size) {
			self.destroy();
			return;
		}
		self.justSplit = false; // Clear the flag after first update
		// More gradual vertical speed transition
		if (self.verticalVelocity < self.floatSpeed) {
			self.verticalVelocity += 0.08; // More gentle transition
		}
		self.y -= self.verticalVelocity;
		// Gradually reduce horizontal speed after split
		if (Math.abs(self.driftX) > (Math.random() * 20 - 10) / 60) {
			self.driftX *= 0.98; // Slowly return to normal drift speed
		}
		self.x += self.driftX;
		// Bounce off edges
		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 () {
		if (!self.autoPopDisplayed) {
			var points = Math.floor(self.getBP() * 0.5); // Half points for auto-pop
			game.addBP(points, self.x, self.y, true); // Added true flag for autoPop
			self.autoPopDisplayed = true;
		}
		self.destroy();
		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 = 4; // Units per frame
	self.update = function () {
		self.x += self.fromLeft ? self.speed : -self.speed;
		// 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() {
	Object.entries(UPGRADE_CONFIG.machines).forEach(function (_ref) {
		var _ref2 = _slicedToArray3(_ref, 2),
			type = _ref2[0],
			machine = _ref2[1];
		if (machine.amount > 0) {
			// Calculate production time with speed upgrade
			var baseTime = machine.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) {
				for (var i = 0; i < machine.amount; i++) {
					if (game.bubbles.length < game.MAX_BUBBLES) {
						var x = Math.random() * (game.width - 200) + 100;
						spawnSplitBubble(x, game.height + 100, machine.bubbleSize, 0);
					}
				}
			}
		}
	});
}
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: 1000,
			costScale: 2.3,
			maxLevel: 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: 25
		},
		advancedClam: {
			name: "Advanced Clam",
			baseCost: 2000,
			// Requires running basic clams for a while
			costScale: 1.3,
			amount: 0,
			production: 2,
			bubbleSize: 35,
			unlockCost: 2000
		},
		premiumClam: {
			name: "Premium Clam",
			baseCost: 15000,
			// True end-game content
			costScale: 1.35,
			amount: 0,
			production: 1,
			bubbleSize: 50,
			unlockCost: 15000
		}
	},
	machine: {
		bubbleDurability: {
			name: "Bubble Durability",
			baseCost: 250,
			costScale: 2.8,
			// Significant scaling
			maxLevel: 5,
			currentLevel: 0
		},
		autoBubbleSpeed: {
			name: "Auto-Bubble Speed",
			baseCost: 500,
			costScale: 2.5,
			maxLevel: 10,
			currentLevel: 0
		}
	}
};
// 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 () {
		console.log('Click on:', upgrade.name);
		var cost = getUpgradeCost(upgrade);
		if (game.bp >= cost) {
			if (category === 'machines') {
				if (upgrade.amount < 4) {
					// Limit to 4 clams
					upgrade.amount++;
					game.bp -= cost;
					bpText.setText(formatBP(game.bp) + " BP");
					costText.setText(getUpgradeCost(upgrade) + " BP");
					updateClamVisuals();
				}
			} else if (upgrade.currentLevel < upgrade.maxLevel) {
				upgrade.currentLevel++;
				game.bp -= cost;
				bpText.setText(formatBP(game.bp) + " BP");
				costText.setText(getUpgradeCost(upgrade) + " 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() {
	// Clear existing clams
	while (clamContainer.children.length) {
		clamContainer.children[0].destroy();
	}
	// Calculate positions for 4 clams
	var spacing = game.width / 5; // 5 segments for 4 clams
	var y = game.height - 100; // Offset from bottom
	// Determine highest unlocked clam type
	var clamType = 'basicClam';
	if (UPGRADE_CONFIG.machines.basicClam.amount >= 4) {
		clamType = 'advancedClam';
		if (UPGRADE_CONFIG.machines.advancedClam.amount >= 4) {
			clamType = 'premiumClam';
		}
	}
	// Create 4 clams
	for (var i = 0; i < 4; i++) {
		var x = spacing + i * spacing;
		// Skip middle positions (where upgrade tab is)
		if (i === 1 || i === 2) {
			continue;
		}
		var sprite = LK.getAsset(clamType, {
			anchorX: 0.5,
			anchorY: 1,
			x: x,
			y: y,
			scaleX: 0.5,
			scaleY: 0.5
		});
		if (UPGRADE_CONFIG.machines[clamType].amount <= i) {
			sprite.alpha = 0.5; // Grey out unpurchased clams
		}
		clamContainer.addChild(sprite);
	}
}
updateClamVisuals();
// 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 = 1.6; // Slightly increased to match new max size 
game.MAX_BUBBLES = 125;
game.baseSpawnRate = 180; // Every 3 seconds
function spawnSplitBubble(parentX, parentY, size, direction) {
	var isAutoPop = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
	var bubble = new Bubble();
	bubble.x = parentX;
	bubble.y = parentY;
	bubble.size = size;
	bubble.initLifetime(); // Recalculate after size is set
	bubble.justSplit = true;
	var speedMultiplier = 120 / size * (0.9 + Math.random() * 0.2);
	if (isAutoPop) {
		// Pure upward/sideways motion for natural splits
		bubble.verticalVelocity = bubble.floatSpeed; // Start at float speed
		bubble.driftX = direction * (Math.random() * 0.8 + 0.5);
	} else {
		// Manual pop physics
		bubble.verticalVelocity = -(Math.random() * 2 + 4);
		bubble.driftX = direction * (Math.random() * 1.5 + 2);
	}
	game.addChild(bubble);
	game.bubbles.push(bubble);
	return bubble;
}
game.bubbles = []; // Track bubble array
// 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) {
			// Calculate spawn position relative to pufferfish mask 
			var spawnX = playerMask.x;
			var spawnY = playerMask.y + playerMask.height * 0.15;
			game.growingBubble = new Bubble();
			game.growingBubble.size = game.MIN_SPAWN_SIZE;
			game.addChild(game.growingBubble);
			game.bubbles.push(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) {
			game.growingBubble.verticalVelocity = -12;
			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) {
		// Spawn fish every 5 seconds (300 frames), reduced by level
		if (LK.ticks % Math.max(120, 300 - UPGRADE_CONFIG.player.autoPop.currentLevel * 30) === 0) {
			var fish = new Fish();
			game.addChild(fish);
		}
	}
	// Update all children (bubbles) 
	// Only spawn if under max bubbles
	if (game.bubbles.length < game.MAX_BUBBLES) {
		if (LK.ticks % game.baseSpawnRate == 0) {
			var x = Math.random() * (game.width - 200) + 100;
			spawnSplitBubble(x, game.height + 100, 100, 0);
		}
	}
	// Clean up destroyed bubbles from array
	game.bubbles = game.bubbles.filter(function (bubble) {
		return !bubble.destroyed;
	});
	// Update all children (bubbles)
	for (var i = game.bubbles.length - 1; i >= 0; i--) {
		var bubble = game.bubbles[i];
		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;
	for (var i = game.bubbles.length - 1; i >= 0; i--) {
		var bubble = game.bubbles[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 + 10 && bubble.down) {
			bubble.down();
			popped = true;
			break;
		}
	}
}; ===================================================================
--- original.js
+++ change.js
@@ -744,11 +744,11 @@
 	var y = game.height - 100; // Offset from bottom
 	// Determine highest unlocked clam type
 	var clamType = 'basicClam';
 	if (UPGRADE_CONFIG.machines.basicClam.amount >= 4) {
-		clamType = 'advancedclam';
+		clamType = 'advancedClam';
 		if (UPGRADE_CONFIG.machines.advancedClam.amount >= 4) {
-			clamType = 'premiumclam';
+			clamType = 'premiumClam';
 		}
 	}
 	// Create 4 clams
 	for (var i = 0; i < 4; i++) {
: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