/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
/**** 
* Classes
****/ 
var BubbleParticle = Container.expand(function (startX, startY) {
	var self = Container.call(this);
	self.gfx = self.attachAsset('bubbles', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1 + Math.random() * 0.2,
		// Start with some variation
		scaleX: 1.2 + Math.random() * 0.25,
		// Larger bubbles
		// Bubbles are now 20% to 45% of original asset size
		scaleY: this.scaleX // Keep aspect ratio initially, can be changed in update if desired
	});
	self.x = startX;
	self.y = startY;
	self.vx = (Math.random() - 0.5) * 0.4; // Even slower horizontal drift
	self.vy = -(0.4 + Math.random() * 0.3); // Slower upward movement
	self.life = 120 + Math.random() * 60; // Lifespan in frames (2 to 3 seconds)
	self.age = 0;
	self.isDone = false;
	var initialAlpha = self.gfx.alpha;
	var initialScale = self.gfx.scaleX; // Assuming scaleX and scaleY start the same
	self.update = function () {
		if (self.isDone) {
			return;
		}
		self.age++;
		self.x += self.vx;
		self.y += self.vy;
		// Fade out
		self.gfx.alpha = Math.max(0, initialAlpha * (1 - self.age / self.life));
		// Optionally shrink a bit more, or grow slightly then shrink
		var scaleFactor = 1 - self.age / self.life; // Simple shrink
		self.gfx.scaleX = initialScale * scaleFactor;
		self.gfx.scaleY = initialScale * scaleFactor;
		if (self.age >= self.life || self.gfx.alpha <= 0 || self.gfx.scaleX <= 0.01) {
			self.isDone = true;
		}
	};
	return self;
});
var CloudParticle = Container.expand(function () {
	var self = Container.call(this);
	self.gfx = self.attachAsset('cloud', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Cloud spawn location - 30% chance to spawn on screen
	var spawnOnScreen = Math.random() < 0.3;
	if (spawnOnScreen) {
		// Spawn randomly across the screen width
		self.x = 200 + Math.random() * 1648; // Between 200 and 1848 to avoid edges
		// Random horizontal drift direction
		self.vx = (Math.random() < 0.5 ? 1 : -1) * (0.1 + Math.random() * 0.2);
	} else {
		// Original off-screen spawn logic (70% of the time)
		var spawnFromLeft = Math.random() < 0.5;
		if (spawnFromLeft) {
			self.x = -100; // Start off-screen left
			self.vx = 0.1 + Math.random() * 0.2; // Slower drift right at 0.1-0.3 pixels/frame
		} else {
			self.x = 2048 + 100; // Start off-screen right
			self.vx = -(0.1 + Math.random() * 0.2); // Slower drift left
		}
	}
	// Spawn in sky area (above water surface)
	var skyTop = -500; // Where sky background starts
	var skyBottom = GAME_CONFIG.WATER_SURFACE_Y - 100; // Stay well above water
	self.y = skyTop + Math.random() * (skyBottom - skyTop);
	// Cloud properties
	var baseScale = 0.8 + Math.random() * 0.6; // Scale: 0.8x to 1.4x
	self.gfx.scale.set(baseScale);
	// Subtle vertical drift
	self.vy = (Math.random() - 0.5) * 0.02; // Even slower up/down drift
	// Opacity for atmospheric effect
	var targetAlpha = 0.4 + Math.random() * 0.3; // Alpha: 0.4 to 0.7
	self.gfx.alpha = 0; // Start transparent
	self.isDone = false;
	self.fadingOut = false;
	self.hasFadedIn = false; // Track if initial fade in completed
	// Define screen boundaries for fade in/out
	var fadeInStartX = 400; // Start fading in when cloud reaches this X
	var fadeInEndX = 800; // Fully visible by this X
	var fadeOutStartX = 1248; // Start fading out at this X (2048 - 800)
	var fadeOutEndX = 1648; // Fully transparent by this X (2048 - 400)
	self.update = function () {
		if (self.isDone) {
			return;
		}
		// Apply movement
		self.x += self.vx;
		self.y += self.vy;
		// Handle mid-screen fade in/out based on position
		var currentAlpha = self.gfx.alpha;
		if (spawnFromLeft) {
			// Moving right: fade in then fade out
			if (!self.hasFadedIn && self.x >= fadeInStartX && self.x <= fadeInEndX) {
				// Calculate fade in progress
				var fadeInProgress = (self.x - fadeInStartX) / (fadeInEndX - fadeInStartX);
				self.gfx.alpha = targetAlpha * fadeInProgress;
				if (fadeInProgress >= 1) {
					self.hasFadedIn = true;
				}
			} else if (self.hasFadedIn && self.x >= fadeOutStartX && self.x <= fadeOutEndX) {
				// Calculate fade out progress
				var fadeOutProgress = (self.x - fadeOutStartX) / (fadeOutEndX - fadeOutStartX);
				self.gfx.alpha = targetAlpha * (1 - fadeOutProgress);
			} else if (self.hasFadedIn && self.x > fadeInEndX && self.x < fadeOutStartX) {
				// Maintain full opacity in middle section
				self.gfx.alpha = targetAlpha;
			}
		} else {
			// Moving left: fade in then fade out (reversed positions)
			if (!self.hasFadedIn && self.x <= fadeOutEndX && self.x >= fadeOutStartX) {
				// Calculate fade in progress (reversed)
				var fadeInProgress = (fadeOutEndX - self.x) / (fadeOutEndX - fadeOutStartX);
				self.gfx.alpha = targetAlpha * fadeInProgress;
				if (fadeInProgress >= 1) {
					self.hasFadedIn = true;
				}
			} else if (self.hasFadedIn && self.x <= fadeInEndX && self.x >= fadeInStartX) {
				// Calculate fade out progress (reversed)
				var fadeOutProgress = (fadeInEndX - self.x) / (fadeInEndX - fadeInStartX);
				self.gfx.alpha = targetAlpha * (1 - fadeOutProgress);
			} else if (self.hasFadedIn && self.x < fadeOutStartX && self.x > fadeInEndX) {
				// Maintain full opacity in middle section
				self.gfx.alpha = targetAlpha;
			}
		}
		// Check if completely off-screen
		var currentWidth = self.gfx.width * self.gfx.scale.x;
		if (self.x < -currentWidth || self.x > 2048 + currentWidth) {
			self.isDone = true;
		}
	};
	return self;
});
var FeedbackIndicator = Container.expand(function (type) {
	var self = Container.call(this);
	var indicator = self.attachAsset(type + 'Indicator', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0
	});
	self.show = function () {
		indicator.alpha = 1;
		indicator.scaleX = 0.5;
		indicator.scaleY = 0.5;
		tween(indicator, {
			scaleX: 1.5,
			scaleY: 1.5,
			alpha: 0
		}, {
			duration: 1400,
			easing: tween.easeOut
		});
	};
	return self;
});
/**** 
* Title Screen
****/ 
var Fish = Container.expand(function (type, value, speed, lane) {
	var self = Container.call(this);
	var assetName = type + 'Fish';
	// Randomly pick from the three shallowFish assets if type is shallow
	if (type === 'shallow') {
		var shallowFishAssets = ['shallowFish', 'shallowFish2', 'shallowFish3'];
		assetName = shallowFishAssets[Math.floor(Math.random() * shallowFishAssets.length)];
	}
	self.fishGraphics = self.attachAsset(assetName, {
		anchorX: 0.5,
		anchorY: 0.5
	});
	if (speed > 0) {
		// Moving right (coming from left), flip horizontally
		self.fishGraphics.scaleX = -1;
	}
	self.type = type;
	self.value = value;
	self.speed = speed;
	self.lane = lane; // Index of the lane (0, 1, or 2)
	self.caught = false;
	self.missed = false; // True if the fish passed the hook without being caught
	self.lastX = 0; // Stores the x position from the previous frame for miss detection
	self.isSpecial = type === 'rare'; // For shimmer effect
	self.shimmerTime = 0;
	// Bubble properties
	self.lastBubbleSpawnTime = 0;
	self.bubbleSpawnInterval = 120 + Math.random() * 80; // 120-200ms interval (fewer bubbles)
	// Add properties for swimming animation
	self.swimTime = Math.random() * Math.PI * 2; // Random starting phase for variety
	self.baseY = self.y; // Store initial Y position
	self.scaleTime = 0;
	self.baseScale = 1;
	self.update = function () {
		if (!self.caught) {
			// Horizontal movement
			self.x += self.speed;
			// Sine wave vertical movement
			self.swimTime += 0.08; // Speed of sine wave oscillation
			var swimAmplitude = 15; // Pixels of vertical movement
			self.y = self.baseY + Math.sin(self.swimTime) * swimAmplitude;
			// Beat-synchronized scale pulsing
			if (GameState.gameActive && GameState.songStartTime > 0) {
				var currentTime = LK.ticks * (1000 / 60);
				var songConfig = GameState.getCurrentSongConfig();
				var beatInterval = 60000 / songConfig.bpm;
				var timeSinceLastBeat = (currentTime - GameState.songStartTime) % beatInterval;
				var beatProgress = timeSinceLastBeat / beatInterval;
				// Create a pulse effect that peaks at the beat
				var scalePulse = 1 + Math.sin(beatProgress * Math.PI) * 0.15; // 15% scale variation
				// Determine base scaleX considering direction
				var baseScaleXDirection = (self.speed > 0 ? -1 : 1) * self.baseScale;
				self.fishGraphics.scaleX = baseScaleXDirection * scalePulse;
				self.fishGraphics.scaleY = scalePulse * self.baseScale;
			}
			if (self.isSpecial) {
				// Shimmer effect for rare fish
				self.shimmerTime += 0.1;
				self.fishGraphics.alpha = 0.8 + Math.sin(self.shimmerTime) * 0.2;
			} else {
				// Reset alpha if not special
				self.fishGraphics.alpha = 1.0;
			}
		}
	};
	self.catchFish = function () {
		self.caught = true;
		// Animation: Fish arcs up over the boat, then down into it.
		var currentFishX = self.x;
		var currentFishY = self.y;
		var boatCenterX = GAME_CONFIG.SCREEN_CENTER_X;
		var boatLandingY = GAME_CONFIG.BOAT_Y; // Y-coordinate where the fish "lands" in the boat
		// Define the peak of the arc - e.g., 150 pixels above the boat's landing spot
		var peakArcY = boatLandingY - 150;
		// Define the X coordinate at the peak of the arc - halfway between current X and boat center X
		var peakArcX = currentFishX + (boatCenterX - currentFishX) * 0.5;
		var durationPhase1 = 350; // Duration for the first part of the arc (upwards)
		var durationPhase2 = 250; // Duration for the second part of the arc (downwards), total 600ms
		// Phase 1: Arc upwards and towards the boat's horizontal center.
		// The container 'self' is tweened for position, scale, and alpha.
		// Initial self.scale.x and self.scale.y are expected to be 1 for the container.
		tween(self, {
			x: peakArcX,
			y: peakArcY,
			scaleX: 0.75,
			// Scale container to 75% of its original size
			scaleY: 0.75,
			alpha: 0.8 // Partially fade
		}, {
			duration: durationPhase1,
			easing: tween.easeOut,
			// Ease out for the upward motion to the peak
			onFinish: function onFinish() {
				// Phase 2: Arc downwards into the boat and disappear.
				tween(self, {
					x: boatCenterX,
					y: boatLandingY,
					scaleX: 0.2,
					// Shrink container further to 20% of its original size
					scaleY: 0.2,
					alpha: 0 // Fade out completely
				}, {
					duration: durationPhase2,
					easing: tween.easeIn,
					// Ease in for the downward motion into the boat
					onFinish: function onFinish() {
						self.destroy(); // Remove fish once animation is complete
					}
				});
			}
		});
	};
	return self;
});
var MusicNoteParticle = Container.expand(function (startX, startY) {
	var self = Container.call(this);
	var FADE_IN_DURATION_MS = 600; // Duration for the note to fade in
	var TARGET_ALPHA = 0.6 + Math.random() * 0.4; // Target alpha between 0.6 and 1.0 for variety
	self.gfx = self.attachAsset('musicnote', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0,
		// Start invisible for fade-in
		scaleX: 0.4 + Math.random() * 0.4 // Random initial scale (0.4x to 0.8x)
	});
	self.gfx.scaleY = self.gfx.scaleX; // Maintain aspect ratio
	self.x = startX;
	self.y = startY;
	// Animation properties for lazy floating
	self.vx = (Math.random() - 0.5) * 0.8; // Slow horizontal drift speed
	self.vy = -(0.8 + Math.random() * 0.7); // Steady upward speed (0.8 to 1.5 pixels/frame)
	self.rotationSpeed = (Math.random() - 0.5) * 0.008; // Very slow rotation
	self.life = 240 + Math.random() * 120; // Lifespan in frames (4 to 6 seconds)
	self.age = 0;
	self.isDone = false;
	// Initial fade-in tween
	tween(self.gfx, {
		alpha: TARGET_ALPHA
	}, {
		duration: FADE_IN_DURATION_MS,
		easing: tween.easeOut
	});
	self.update = function () {
		if (self.isDone) {
			return;
		}
		self.age++;
		self.x += self.vx;
		self.y += self.vy;
		self.gfx.rotation += self.rotationSpeed;
		var FADE_IN_TICKS = FADE_IN_DURATION_MS / (1000 / 60); // Fade-in duration in ticks
		// Only manage alpha manually after the fade-in tween is expected to be complete.
		if (self.age > FADE_IN_TICKS) {
			var lifePortionForFadeOut = 0.6; // Use last 60% of life for fade out
			var fadeOutStartTimeTicks = self.life * (1 - lifePortionForFadeOut);
			if (self.age >= fadeOutStartTimeTicks && self.life > fadeOutStartTimeTicks) {
				// ensure self.life > fadeOutStartTimeTicks to avoid division by zero
				var progressInFadeOut = (self.age - fadeOutStartTimeTicks) / (self.life * lifePortionForFadeOut);
				self.gfx.alpha = TARGET_ALPHA * (1 - progressInFadeOut);
				self.gfx.alpha = Math.max(0, self.gfx.alpha); // Clamp at 0
			} else if (self.age <= fadeOutStartTimeTicks) {
				// At this point, the initial fade-in tween to TARGET_ALPHA should have completed.
				// The alpha value is expected to remain at TARGET_ALPHA (as set by the initial tween)
				// until the fade-out logic (in the 'if (self.age >= fadeOutStartTimeTicks)' block) begins.
				// The previous 'tween.isTweening' check was removed as the method does not exist in the plugin.
			}
		}
		// Check if particle's life is over or it has faded out
		if (self.age >= self.life || self.gfx.alpha !== undefined && self.gfx.alpha <= 0.01 && self.age > FADE_IN_TICKS) {
			self.isDone = true;
		}
	};
	return self;
});
var OceanBubbleParticle = Container.expand(function () {
	var self = Container.call(this);
	self.gfx = self.attachAsset('oceanbubbles', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.initialX = Math.random() * 2048;
	var waterTop = GAME_CONFIG.WATER_SURFACE_Y;
	var waterBottom = 2732;
	self.x = self.initialX;
	// Allow bubbles to spawn anywhere in the water, not just below the bottom
	self.y = waterTop + Math.random() * (waterBottom - waterTop);
	var baseScale = 0.1 + Math.random() * 0.4; // Scale: 0.1x to 0.5x
	self.gfx.scale.set(baseScale);
	self.vy = -(0.25 + Math.random() * 0.5); // Upward speed: 0.25 to 0.75 pixels/frame (slower, less variance)
	self.naturalVy = self.vy; // Store natural velocity for recovery
	self.driftAmplitude = 20 + Math.random() * 40; // Sideways drift: 20px to 60px amplitude
	self.naturalDriftAmplitude = self.driftAmplitude; // Store natural drift for recovery
	self.driftFrequency = (0.005 + Math.random() * 0.015) * (Math.random() < 0.5 ? 1 : -1); // Sideways drift speed/direction
	self.driftPhase = Math.random() * Math.PI * 2; // Initial phase for sine wave
	self.rotationSpeed = (Math.random() - 0.5) * 0.01; // Slow random rotation
	var targetAlpha = 0.2 + Math.random() * 0.3; // Max alpha: 0.2 to 0.5 (dimmer background bubbles)
	self.gfx.alpha = 0; // Start transparent
	self.isDone = false;
	self.fadingOut = false;
	tween(self.gfx, {
		alpha: targetAlpha
	}, {
		duration: 1000 + Math.random() * 1000,
		// Slow fade in: 1 to 2 seconds
		easing: tween.easeIn
	});
	self.update = function () {
		if (self.isDone) {
			return;
		}
		self.y += self.vy;
		// Increment age
		self.age++;
		// Check if lifespan exceeded
		if (!self.fadingOut && self.age >= self.lifespan) {
			self.fadingOut = true;
			tween.stop(self.gfx);
			tween(self.gfx, {
				alpha: 0
			}, {
				duration: 600 + Math.random() * 400,
				// 0.6-1 second fade
				easing: tween.easeOut,
				onFinish: function onFinish() {
					self.isDone = true;
				}
			});
		}
		self.driftPhase += self.driftFrequency;
		self.x = self.initialX + Math.sin(self.driftPhase) * self.driftAmplitude;
		self.gfx.rotation += self.rotationSpeed;
		// Recovery mechanism: gradually return to natural upward movement
		var naturalVy = -(0.25 + Math.random() * 0.5); // Natural upward speed
		var recoveryRate = 0.02; // How quickly bubble recovers (2% per frame)
		// If bubble is moving slower than its natural speed or downward, recover
		if (self.vy > naturalVy) {
			self.vy = self.vy + (naturalVy - self.vy) * recoveryRate;
		}
		// Also gradually reduce excessive drift amplitude back to normal
		var normalDriftAmplitude = 20 + Math.random() * 40;
		if (self.driftAmplitude > normalDriftAmplitude) {
			self.driftAmplitude = self.driftAmplitude + (normalDriftAmplitude - self.driftAmplitude) * recoveryRate;
		}
		// Check if bubble reached surface or went off-screen
		// Use gfx.height * current scale for accurate boundary check
		var currentHeight = self.gfx.height * self.gfx.scale.y;
		var currentWidth = self.gfx.width * self.gfx.scale.x;
		if (!self.fadingOut && self.y <= GAME_CONFIG.WATER_SURFACE_Y - currentHeight * 0.5) {
			self.fadingOut = true;
			tween.stop(self.gfx); // Stop fade-in if ongoing
			tween(self.gfx, {
				alpha: 0
			}, {
				duration: 300 + Math.random() * 200,
				// Quick fade out
				easing: tween.easeOut,
				onFinish: function onFinish() {
					self.isDone = true; // Mark as fully done for removal
				}
			});
		} else if (!self.fadingOut && (self.y < -currentHeight || self.x < -currentWidth || self.x > 2048 + currentWidth)) {
			// Off screen top/sides before reaching surface and starting fade
			self.isDone = true;
			self.gfx.alpha = 0; // Disappear immediately
		}
	};
	return self;
});
var SeaweedParticle = Container.expand(function () {
	var self = Container.call(this);
	self.gfx = self.attachAsset('kelp', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Determine spawn location
	var spawnType = Math.random();
	var waterTop = GAME_CONFIG.WATER_SURFACE_Y;
	var waterBottom = 2732;
	if (spawnType < 0.4) {
		// 40% spawn from bottom
		self.x = Math.random() * 2048;
		self.y = waterBottom + 50;
		self.vx = (Math.random() - 0.5) * 0.3; // Slight horizontal drift
		self.vy = -(0.4 + Math.random() * 0.3); // Upward movement
	} else if (spawnType < 0.7) {
		// 30% spawn from left
		self.x = -50;
		self.y = waterTop + Math.random() * (waterBottom - waterTop);
		self.vx = 0.4 + Math.random() * 0.3; // Rightward movement
		self.vy = -(0.1 + Math.random() * 0.2); // Slight upward drift
	} else {
		// 30% spawn from right
		self.x = 2048 + 50;
		self.y = waterTop + Math.random() * (waterBottom - waterTop);
		self.vx = -(0.4 + Math.random() * 0.3); // Leftward movement
		self.vy = -(0.1 + Math.random() * 0.2); // Slight upward drift
	}
	self.initialX = self.x;
	self.naturalVx = self.vx; // Store natural velocity for recovery
	self.naturalVy = self.vy;
	// Seaweed properties
	var baseScale = 0.6 + Math.random() * 0.6; // Scale: 0.6x to 1.2x
	self.gfx.scale.set(baseScale);
	self.swayAmplitude = 15 + Math.random() * 25; // Sway: 15px to 40px
	self.swayFrequency = (0.003 + Math.random() * 0.007) * (Math.random() < 0.5 ? 1 : -1);
	self.swayPhase = Math.random() * Math.PI * 2;
	// Random initial rotation (full 360 degrees)
	self.gfx.rotation = Math.random() * Math.PI * 2;
	// Random continuous rotation speed (slower than ocean bubbles)
	self.continuousRotationSpeed = (Math.random() - 0.5) * 0.003; // -0.0015 to 0.0015 radians per frame
	var targetAlpha = 0.3 + Math.random() * 0.3; // Alpha: 0.3 to 0.6
	self.gfx.alpha = 0; // Start transparent
	self.isDone = false;
	self.fadingOut = false;
	self.reachedSurface = false;
	// Add random lifespan (10-30 seconds)
	self.lifespan = 600 + Math.random() * 1200; // 600-1800 frames (10-30 seconds at 60fps)
	self.age = 0;
	tween(self.gfx, {
		alpha: targetAlpha
	}, {
		duration: 1500 + Math.random() * 1000,
		easing: tween.easeIn
	});
	self.update = function () {
		if (self.isDone) {
			return;
		}
		// Apply movement
		self.x += self.vx;
		self.y += self.vy;
		// Add sway effect
		self.swayPhase += self.swayFrequency;
		var swayOffset = Math.sin(self.swayPhase) * self.swayAmplitude;
		// Apply continuous rotation plus sway-based rotation
		self.gfx.rotation += self.continuousRotationSpeed + swayOffset * 0.0001; // Reduced sway rotation influence
		// Recovery mechanism for velocity
		var recoveryRate = 0.015;
		if (self.vx !== self.naturalVx) {
			self.vx = self.vx + (self.naturalVx - self.vx) * recoveryRate;
		}
		if (self.vy !== self.naturalVy) {
			self.vy = self.vy + (self.naturalVy - self.vy) * recoveryRate;
		}
		// Check if reached surface
		var currentHeight = self.gfx.height * self.gfx.scale.y;
		var currentWidth = self.gfx.width * self.gfx.scale.x;
		if (!self.reachedSurface && self.y <= GAME_CONFIG.WATER_SURFACE_Y + currentHeight * 0.3) {
			self.reachedSurface = true;
			// Change to horizontal drift at surface
			self.vy = 0;
			self.vx = (Math.random() < 0.5 ? 1 : -1) * (0.5 + Math.random() * 0.5);
			self.naturalVx = self.vx;
			self.naturalVy = 0;
		}
		// Check if off-screen
		if (!self.fadingOut && (self.y < -currentHeight || self.x < -currentWidth || self.x > 2048 + currentWidth || self.y > waterBottom + currentHeight)) {
			self.fadingOut = true;
			tween.stop(self.gfx);
			tween(self.gfx, {
				alpha: 0
			}, {
				duration: 400 + Math.random() * 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					self.isDone = true;
				}
			});
		}
	};
	return self;
});
/**** 
* Initialize Game
****/ 
/**** 
* Screen Containers
****/ 
var game = new LK.Game({
	backgroundColor: 0x87CEEB
});
/**** 
* Game Code
****/ 
// Constants for Title Screen Animation
var TITLE_ANIM_CONSTANTS = {
	INITIAL_GROUP_ALPHA: 0,
	FINAL_GROUP_ALPHA: 1,
	INITIAL_UI_ALPHA: 0,
	FINAL_UI_ALPHA: 1,
	INITIAL_GROUP_SCALE: 3.5,
	// Start with an extreme closeup
	FINAL_GROUP_SCALE: 2.8,
	// Zoom out slightly, staying closer
	GROUP_ANIM_DURATION: 4000,
	// Slower duration for group fade-in and zoom
	TEXT_FADE_DURATION: 1000,
	// Duration for title text fade-in
	BUTTON_FADE_DURATION: 800,
	// Duration for buttons fade-in
	// Positioning constants relative to titleAnimationGroup's origin (0,0) which is boat's center
	BOAT_ANCHOR_X: 0.5,
	BOAT_ANCHOR_Y: 0.5,
	FISHERMAN_ANCHOR_X: 0.5,
	FISHERMAN_ANCHOR_Y: 0.9,
	// Anchor at feet
	FISHERMAN_X_OFFSET: -20,
	// Relative to boat center
	FISHERMAN_Y_OFFSET: -100,
	// Relative to boat center, fisherman sits on boat
	LINE_ANCHOR_X: 0.5,
	LINE_ANCHOR_Y: 0,
	// Anchor at top of line
	LINE_X_OFFSET_FROM_FISHERMAN: 70,
	// Rod tip X from fisherman center
	LINE_Y_OFFSET_FROM_FISHERMAN: -130,
	// Rod tip Y from fisherman center (fisherman height ~200, anchorY 0.9)
	HOOK_ANCHOR_X: 0.5,
	HOOK_ANCHOR_Y: 0.5,
	HOOK_Y_DEPTH_FROM_LINE_START: 700,
	// Slightly longer line for the closeup
	// titleAnimationGroup positioning
	GROUP_PIVOT_X: 0,
	// Boat's X in the group
	GROUP_PIVOT_Y: 0,
	// Boat's Y in the group
	GROUP_INITIAL_Y_SCREEN_OFFSET: -450 // Adjusted for closer initial zoom
};
// If game.up already exists, integrate the 'fishing' case. Otherwise, this defines game.up.
/**** 
* Pattern Generation System
****/ 
var PatternGenerator = {
	lastLane: -1,
	minDistanceBetweenFish: 300,
	// Used by spawnFish internal check
	// Minimum X distance between fish for visual clarity on hook
	lastActualSpawnTime: -100000,
	// Time of the last actual fish spawn
	getNextLane: function getNextLane() {
		if (this.lastLane === -1) {
			// First fish, start in middle lane
			this.lastLane = 1;
			return 1;
		}
		// Prefer staying in same lane or moving to adjacent lane
		var possibleLanes = [this.lastLane];
		// Add adjacent lanes
		if (this.lastLane > 0) {
			possibleLanes.push(this.lastLane - 1);
		}
		if (this.lastLane < 2) {
			possibleLanes.push(this.lastLane + 1);
		}
		// 70% chance to stay in same/adjacent lane
		if (Math.random() < 0.7) {
			this.lastLane = possibleLanes[Math.floor(Math.random() * possibleLanes.length)];
		} else {
			// 30% chance for any lane
			this.lastLane = Math.floor(Math.random() * 3);
		}
		return this.lastLane;
	},
	// New method: Checks if enough time has passed since the last spawn.
	canSpawnFishOnBeat: function canSpawnFishOnBeat(currentTime, configuredSpawnInterval) {
		var timeSinceLast = currentTime - this.lastActualSpawnTime;
		var minRequiredGap = configuredSpawnInterval; // Default gap is the song's beat interval for fish
		return timeSinceLast >= minRequiredGap;
	},
	// New method: Registers details of the fish that was just spawned.
	registerFishSpawn: function registerFishSpawn(spawnTime) {
		this.lastActualSpawnTime = spawnTime;
	},
	reset: function reset() {
		this.lastLane = -1;
		this.lastActualSpawnTime = -100000; // Set far in the past to allow first spawn
	}
};
/**** 
* Game Configuration
****/ 
game.up = function (x, y, obj) {
	// Note: We don't play buttonClick sound on 'up' typically, only on 'down'.
	switch (GameState.currentScreen) {
		case 'title':
			// title screen up actions (if any)
			break;
		case 'levelSelect':
			// level select screen up actions (if any, usually 'down' is enough for buttons)
			break;
		case 'fishing':
			handleFishingInput(x, y, false); // false for isUp
			break;
		case 'results':
			// results screen up actions (if any)
			break;
	}
};
var GAME_CONFIG = {
	SCREEN_CENTER_X: 1024,
	SCREEN_CENTER_Y: 900,
	// Adjusted, though less critical with lanes
	BOAT_Y: 710,
	// Original 300 + 273 (10%) + 137 (5%) = 710
	WATER_SURFACE_Y: 760,
	// Original 350 + 273 (10%) + 137 (5%) = 760
	// 3 Lane System
	// Y-positions for each lane, adjusted downwards further
	LANES: [{
		y: 1133,
		// Top lane Y: (723 + 273) + 137 = 996 + 137 = 1133
		name: "shallow"
	}, {
		y: 1776,
		// Middle lane Y: (996 + 643) + 137 = 1639 + 137 = 1776
		name: "medium"
	}, {
		y: 2419,
		// Bottom lane Y: (1639 + 643) + 137 = 2282 + 137 = 2419
		name: "deep"
	}],
	// Timing windows
	PERFECT_WINDOW: 40,
	GOOD_WINDOW: 80,
	MISS_WINDOW: 120,
	// Depth levels - reduced money values!
	DEPTHS: [{
		level: 1,
		name: "Shallow Waters",
		fishSpeed: 6,
		fishValue: 1,
		upgradeCost: 0,
		// Starting depth
		songs: [{
			name: "Gentle Waves",
			bpm: 90,
			duration: 202000,
			// 3:22
			pattern: "gentle_waves_custom",
			cost: 0
		}, {
			name: "Morning Tide",
			bpm: 90,
			duration: 156827,
			pattern: "morning_tide_custom",
			cost: 0,
			musicId: 'morningtide'
		}, {
			name: "Sunny Afternoon",
			bpm: 97,
			duration: 181800,
			// 3:01.8
			pattern: "sunny_afternoon_custom",
			cost: 0,
			musicId: 'sunnyafternoon'
		}]
	}, {
		level: 2,
		name: "Mid Waters",
		fishSpeed: 7,
		fishValue: 2,
		upgradeCost: 100,
		songs: [{
			name: "Ocean Current",
			bpm: 120,
			duration: 90000,
			pattern: "medium",
			cost: 0
		}, {
			name: "Deep Flow",
			bpm: 125,
			duration: 100000,
			pattern: "medium",
			cost: 150
		}]
	}, {
		level: 3,
		name: "Deep Waters",
		fishSpeed: 8,
		fishValue: 3,
		upgradeCost: 400,
		songs: [{
			name: "Storm Surge",
			bpm: 140,
			duration: 120000,
			pattern: "complex",
			cost: 0
		}, {
			name: "Whirlpool",
			bpm: 150,
			duration: 135000,
			pattern: "complex",
			cost: 300
		}]
	}, {
		level: 4,
		name: "Abyss",
		fishSpeed: 9,
		fishValue: 6,
		upgradeCost: 1000,
		songs: [{
			name: "Leviathan",
			bpm: 160,
			duration: 150000,
			pattern: "expert",
			cost: 0
		}, {
			name: "Deep Trench",
			bpm: 170,
			duration: 180000,
			pattern: "expert",
			cost: 600
		}]
	}],
	// Updated patterns
	PATTERNS: {
		simple: {
			beatsPerFish: 2,
			// Increased from 1 - fish every 2 beats
			doubleSpawnChance: 0.10,
			// 10% chance of a double beat in simple
			rareSpawnChance: 0.02
		},
		medium: {
			beatsPerFish: 1.5,
			// Increased from 0.75
			doubleSpawnChance: 0.15,
			//{1R} // 15% chance of a double beat
			rareSpawnChance: 0.05
		},
		complex: {
			beatsPerFish: 1,
			// Increased from 0.5
			doubleSpawnChance: 0.25,
			//{1U} // 25% chance of a double beat
			rareSpawnChance: 0.08
		},
		expert: {
			beatsPerFish: 0.75,
			// Increased from 0.25
			doubleSpawnChance: 0.35,
			//{1X} // 35% chance of a double beat
			tripletSpawnChance: 0.20,
			// 20% chance a double beat becomes a triplet
			rareSpawnChance: 0.12
		},
		gentle_waves_custom: {
			beatsPerFish: 1.5,
			// Slightly more frequent than default simple
			doubleSpawnChance: 0.05,
			// Very rare double beats for shallow
			rareSpawnChance: 0.01,
			// Almost no rare fish
			// Custom timing sections based on the musical structure
			sections: [
			// Opening - Simple chord pattern (0-30 seconds)
			{
				startTime: 0,
				endTime: 30000,
				spawnModifier: 1.0,
				// Normal spawn rate
				description: "steady_chords"
			},
			// Melody Introduction (30-60 seconds)
			{
				startTime: 30000,
				endTime: 60000,
				spawnModifier: 0.9,
				// Slightly fewer fish
				description: "simple_melody"
			},
			// Development (60-120 seconds) - Gets a bit busier
			{
				startTime: 60000,
				endTime: 120000,
				spawnModifier: 1.1,
				// Slightly more fish
				description: "melody_development"
			},
			// Climax (120-180 seconds) - Busiest section but still shallow
			{
				startTime: 120000,
				endTime: 180000,
				spawnModifier: 1.3,
				// More fish, but not overwhelming
				description: "gentle_climax"
			},
			// Ending (180-202 seconds) - Calming down
			{
				startTime: 180000,
				endTime: 202000,
				spawnModifier: 0.8,
				// Fewer fish for gentle ending
				description: "peaceful_ending"
			}]
		},
		morning_tide_custom: {
			beatsPerFish: 1.2,
			// More frequent than gentle_waves_custom (1.5) and much more than simple (2)
			doubleSpawnChance: 0.12,
			// Higher than simple (0.10)
			rareSpawnChance: 0.03,
			// Higher than simple (0.02)
			// Custom timing sections based on musical structure
			sections: [
			// Gentle opening - ease into the song (0-25 seconds)
			{
				startTime: 0,
				endTime: 25000,
				spawnModifier: 0.9,
				// Slightly reduced for intro
				description: "calm_opening"
			},
			// Building energy - first wave (25-50 seconds)
			{
				startTime: 25000,
				endTime: 50000,
				spawnModifier: 1.2,
				// More active
				description: "first_wave"
			},
			// Peak intensity - morning rush (50-80 seconds)
			{
				startTime: 50000,
				endTime: 80000,
				spawnModifier: 1.5,
				// Most intense section
				description: "morning_rush"
			},
			// Sustained energy - second wave (80-110 seconds)
			{
				startTime: 80000,
				endTime: 110000,
				spawnModifier: 1.3,
				// High but slightly less than peak
				description: "second_wave"
			},
			// Climactic finish (110-140 seconds)
			{
				startTime: 110000,
				endTime: 140000,
				spawnModifier: 1.4,
				// Building back up
				description: "climactic_finish"
			},
			// Gentle fade out (140-156.8 seconds)
			{
				startTime: 140000,
				endTime: 156827,
				spawnModifier: 0.8,
				// Calm ending
				description: "peaceful_fade"
			}]
		},
		sunny_afternoon_custom: {
			beatsPerFish: 1.3,
			// Slightly faster than morning_tide_custom but still beginner-friendly
			doubleSpawnChance: 0.08,
			// Moderate chance for variety
			rareSpawnChance: 0.025,
			// Slightly better rewards than gentle_waves
			sections: [
			// Gentle warm-up (0-20 seconds)
			{
				startTime: 0,
				endTime: 20000,
				spawnModifier: 0.8,
				description: "warm_sunny_start"
			},
			// First activity burst (20-35 seconds)
			{
				startTime: 20000,
				endTime: 35000,
				spawnModifier: 1.4,
				description: "first_sunny_burst"
			},
			// Breathing room (35-50 seconds)
			{
				startTime: 35000,
				endTime: 50000,
				spawnModifier: 0.7,
				description: "sunny_breather_1"
			},
			// Second activity burst (50-70 seconds)
			{
				startTime: 50000,
				endTime: 70000,
				spawnModifier: 1.5,
				description: "second_sunny_burst"
			},
			// Extended breathing room (70-90 seconds)
			{
				startTime: 70000,
				endTime: 90000,
				spawnModifier: 0.6,
				description: "sunny_breather_2"
			},
			// Third activity burst (90-110 seconds)
			{
				startTime: 90000,
				endTime: 110000,
				spawnModifier: 1.3,
				description: "third_sunny_burst"
			},
			// Breathing room (110-125 seconds)
			{
				startTime: 110000,
				endTime: 125000,
				spawnModifier: 0.8,
				description: "sunny_breather_3"
			},
			// Final activity section (125-150 seconds)
			{
				startTime: 125000,
				endTime: 150000,
				spawnModifier: 1.2,
				description: "sunny_finale_buildup"
			},
			// Wind down (150-181.8 seconds)
			{
				startTime: 150000,
				endTime: 181800,
				spawnModifier: 0.9,
				description: "sunny_afternoon_fade"
			}]
		}
	}
};
/**** 
* Game State Management
****/ 
var MULTI_BEAT_SPAWN_DELAY_MS = 250; // ms delay for sequential spawns in multi-beats (increased for more space)
var TRIPLET_BEAT_SPAWN_DELAY_MS = 350; // ms delay for third fish in a triplet (even more space)
var FISH_SPAWN_END_BUFFER_MS = 500; // Just 0.5 seconds buffer
var ImprovedRhythmSpawner = {
	nextBeatToSchedule: 1,
	scheduledBeats: [],
	update: function update(currentTime) {
		if (!GameState.gameActive || GameState.songStartTime === 0) {
			return;
		}
		var songConfig = GameState.getCurrentSongConfig();
		var pattern = GAME_CONFIG.PATTERNS[songConfig.pattern];
		var beatInterval = 60000 / songConfig.bpm;
		var spawnInterval = beatInterval * pattern.beatsPerFish;
		// Calculate how far ahead to look (use original logic)
		var depthConfig = GameState.getCurrentDepthConfig();
		var fishSpeed = Math.abs(depthConfig.fishSpeed);
		var distanceToHook = GAME_CONFIG.SCREEN_CENTER_X + 150;
		var travelTimeMs = distanceToHook / fishSpeed * (1000 / 60);
		var beatsAhead = Math.ceil(travelTimeMs / spawnInterval) + 2;
		// Schedule beats ahead
		var songElapsed = currentTime - GameState.songStartTime;
		var currentSongBeat = songElapsed / spawnInterval;
		var maxBeatToSchedule = Math.floor(currentSongBeat) + beatsAhead;
		while (this.nextBeatToSchedule <= maxBeatToSchedule) {
			if (this.scheduledBeats.indexOf(this.nextBeatToSchedule) === -1) {
				this.scheduleBeatFish(this.nextBeatToSchedule, spawnInterval, travelTimeMs);
				this.scheduledBeats.push(this.nextBeatToSchedule);
			}
			this.nextBeatToSchedule++;
		}
		// NO FISH SYNCING - let them move naturally!
	},
	scheduleBeatFish: function scheduleBeatFish(beatNumber, spawnInterval, travelTimeMs) {
		var targetArrivalTime = GameState.songStartTime + beatNumber * spawnInterval;
		var songConfig = GameState.getCurrentSongConfig();
		// FIXED: The buffer should be applied to when the song ACTUALLY ends,
		// not when the fish arrives at the hook
		var songEndTime = GameState.songStartTime + songConfig.duration;
		var lastValidArrivalTime = songEndTime - FISH_SPAWN_END_BUFFER_MS;
		if (songConfig && GameState.songStartTime > 0 && targetArrivalTime > lastValidArrivalTime) {
			return; // Don't schedule this fish, it would arrive too close to song end
		}
		var spawnTime = targetArrivalTime - travelTimeMs;
		var currentTime = LK.ticks * (1000 / 60);
		// Rest of function stays the same...
		if (spawnTime >= currentTime - 100) {
			var delay = Math.max(0, spawnTime - currentTime);
			var self = this;
			LK.setTimeout(function () {
				if (GameState.gameActive && GameState.songStartTime !== 0) {
					self.spawnRhythmFish(beatNumber, targetArrivalTime);
				}
			}, delay);
		}
	},
	spawnRhythmFish: function spawnRhythmFish(beatNumber, targetArrivalTime) {
		if (!GameState.gameActive) {
			return;
		}
		var currentTime = LK.ticks * (1000 / 60);
		var depthConfig = GameState.getCurrentDepthConfig();
		var songConfig = GameState.getCurrentSongConfig();
		var pattern = GAME_CONFIG.PATTERNS[songConfig.pattern];
		// Calculate spawnInterval for this pattern
		var beatInterval = 60000 / songConfig.bpm;
		var spawnInterval = beatInterval * pattern.beatsPerFish;
		// Apply section modifiers if pattern has sections
		var spawnModifier = 1.0;
		if (pattern.sections) {
			var songElapsed = currentTime - GameState.songStartTime;
			for (var s = 0; s < pattern.sections.length; s++) {
				var section = pattern.sections[s];
				if (songElapsed >= section.startTime && songElapsed <= section.endTime) {
					spawnModifier = section.spawnModifier;
					break;
				}
			}
		}
		// Check if we should spawn
		if (!PatternGenerator.canSpawnFishOnBeat(currentTime, beatInterval)) {
			return;
		}
		// Apply spawn modifier chance
		if (Math.random() > spawnModifier) {
			return;
		}
		var laneIndex = PatternGenerator.getNextLane();
		var targetLane = GAME_CONFIG.LANES[laneIndex];
		// Fish type selection (original logic)
		var fishType, fishValue;
		var rand = Math.random();
		if (rand < pattern.rareSpawnChance) {
			fishType = 'rare';
			fishValue = Math.floor(depthConfig.fishValue * 4);
		} else if (GameState.selectedDepth >= 2 && rand < 0.3) {
			fishType = 'deep';
			fishValue = Math.floor(depthConfig.fishValue * 2);
		} else if (GameState.selectedDepth >= 1 && rand < 0.6) {
			fishType = 'medium';
			fishValue = Math.floor(depthConfig.fishValue * 1.5);
		} else {
			fishType = 'shallow';
			fishValue = Math.floor(depthConfig.fishValue);
		}
		// Calculate precise speed to arrive exactly on beat
		var timeRemainingMs = targetArrivalTime - currentTime;
		if (timeRemainingMs <= 0) {
			return;
		}
		var distanceToHook = GAME_CONFIG.SCREEN_CENTER_X + 150;
		var spawnSide = Math.random() < 0.5 ? -1 : 1;
		// Calculate frames remaining and required speed per frame
		var framesRemaining = timeRemainingMs / (1000 / 60);
		if (framesRemaining <= 0) {
			return;
		}
		var requiredSpeedPerFrame = distanceToHook / framesRemaining;
		if (spawnSide === 1) {
			requiredSpeedPerFrame *= -1; // Moving left
		}
		// Create fish with exact calculated speed - NO SYNC DATA
		var newFish = new Fish(fishType, fishValue, requiredSpeedPerFrame, laneIndex);
		newFish.spawnSide = spawnSide;
		newFish.targetArrivalTime = targetArrivalTime;
		newFish.x = requiredSpeedPerFrame > 0 ? -150 : 2048 + 150;
		newFish.y = targetLane.y;
		newFish.baseY = targetLane.y;
		newFish.lastX = newFish.x; // Initialize lastX for miss detection
		// newFish.missed is false by default from constructor
		// Don't add any syncData - let fish move naturally!
		fishArray.push(newFish);
		fishingScreen.addChild(newFish);
		GameState.sessionFishSpawned++;
		PatternGenerator.registerFishSpawn(currentTime);
		// Handle multi-spawns
		this.handleMultiSpawns(beatNumber, pattern, songConfig, spawnInterval, currentTime);
		return newFish;
	},
	handleMultiSpawns: function handleMultiSpawns(beatNumber, pattern, songConfig, spawnInterval, currentTime) {
		if (pattern.doubleSpawnChance > 0 && Math.random() < pattern.doubleSpawnChance) {
			var travelTimeMs = this.calculateTravelTime();
			var nextFishBeat = beatNumber + 1;
			if (this.scheduledBeats.indexOf(nextFishBeat) === -1) {
				this.scheduleBeatFish(nextFishBeat, spawnInterval, travelTimeMs);
				this.scheduledBeats.push(nextFishBeat);
			}
			// Handle triplets
			if (pattern.tripletSpawnChance && pattern.tripletSpawnChance > 0 && Math.random() < pattern.tripletSpawnChance) {
				var thirdFishBeat = beatNumber + 2;
				if (this.scheduledBeats.indexOf(thirdFishBeat) === -1) {
					this.scheduleBeatFish(thirdFishBeat, spawnInterval, travelTimeMs);
					this.scheduledBeats.push(thirdFishBeat);
				}
			}
		}
	},
	calculateTravelTime: function calculateTravelTime() {
		var depthConfig = GameState.getCurrentDepthConfig();
		var fishSpeed = Math.abs(depthConfig.fishSpeed);
		if (fishSpeed === 0) {
			return Infinity;
		}
		var distanceToHook = GAME_CONFIG.SCREEN_CENTER_X + 150;
		return distanceToHook / fishSpeed * (1000 / 60);
	},
	reset: function reset() {
		this.nextBeatToSchedule = 1;
		this.scheduledBeats = [];
	},
	getDebugInfo: function getDebugInfo() {
		return {
			nextBeat: this.nextBeatToSchedule,
			scheduledCount: this.scheduledBeats.length,
			scheduledBeatsPreview: this.scheduledBeats.slice(-10)
		};
	}
};
/**** 
* Tutorial System - Global Scope
****/ 
function updateLaneBracketsVisuals() {
	if (laneBrackets && laneBrackets.length === GAME_CONFIG.LANES.length) {
		for (var i = 0; i < laneBrackets.length; i++) {
			var isActiveLane = i === GameState.hookTargetLaneIndex;
			var targetAlpha = isActiveLane ? 0.9 : 0.5;
			if (laneBrackets[i] && laneBrackets[i].left && !laneBrackets[i].left.destroyed) {
				if (laneBrackets[i].left.alpha !== targetAlpha) {
					laneBrackets[i].left.alpha = targetAlpha;
				}
			}
			if (laneBrackets[i] && laneBrackets[i].right && !laneBrackets[i].right.destroyed) {
				if (laneBrackets[i].right.alpha !== targetAlpha) {
					laneBrackets[i].right.alpha = targetAlpha;
				}
			}
		}
	}
}
function createTutorialElements() {
	tutorialOverlayContainer.removeChildren(); // Clear previous elements
	tutorialTextBackground = tutorialOverlayContainer.addChild(LK.getAsset('screenBackground', {
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 2732 * 0.85,
		// Position towards the bottom
		width: 1800,
		height: 450,
		// Increased height
		color: 0x000000,
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.75
	}));
	tutorialTextDisplay = tutorialOverlayContainer.addChild(new Text2('', {
		size: 55,
		// Increased font size
		fill: 0xFFFFFF,
		wordWrap: true,
		wordWrapWidth: 1700,
		// Keep wordWrapWidth, adjust if lines are too cramped
		// Slightly less than background width
		align: 'center',
		lineHeight: 65 // Increased line height
	}));
	tutorialTextDisplay.anchor.set(0.5, 0.5);
	tutorialTextDisplay.x = tutorialTextBackground.x;
	tutorialTextDisplay.y = tutorialTextBackground.y - 50; // Adjusted Y for new background height and text size
	tutorialContinueButton = tutorialOverlayContainer.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: tutorialTextBackground.x,
		y: tutorialTextBackground.y + tutorialTextBackground.height / 2 - 55,
		// Adjusted Y for new background height
		// Positioned at the bottom of the text background
		tint: 0x1976d2,
		// Blue color
		width: 350,
		// Standard button size
		height: 70
	}));
	tutorialContinueText = tutorialOverlayContainer.addChild(new Text2('CONTINUE', {
		// Changed text
		size: 34,
		fill: 0xFFFFFF
	}));
	tutorialContinueText.anchor.set(0.5, 0.5);
	tutorialContinueText.x = tutorialContinueButton.x;
	tutorialContinueText.y = tutorialContinueButton.y;
	tutorialOverlayContainer.visible = false; // Initially hidden
}
function setTutorialText(newText, showContinue) {
	if (showContinue === undefined) {
		showContinue = true;
	}
	if (!tutorialTextDisplay || !tutorialContinueButton || !tutorialContinueText) {
		createTutorialElements(); // Ensure elements exist
	}
	tutorialTextDisplay.setText(newText);
	tutorialContinueButton.visible = showContinue;
	tutorialContinueText.visible = showContinue;
	tutorialOverlayContainer.visible = true;
}
function spawnTutorialFishHelper(config) {
	var fishType = config.type || 'shallow';
	// Use a consistent depth config for tutorial fish, e.g., shallowest
	var depthConfig = GAME_CONFIG.DEPTHS[0];
	var fishValue = Math.floor(depthConfig.fishValue / 2); // Tutorial fish might be worth less or nothing
	var baseSpeed = depthConfig.fishSpeed;
	var speedMultiplier = config.speedMultiplier || 0.5; // Slower for tutorial
	var laneIndex = config.lane !== undefined ? config.lane : 1;
	var spawnSide = config.spawnSide !== undefined ? config.spawnSide : Math.random() < 0.5 ? -1 : 1;
	var actualFishSpeed = Math.abs(baseSpeed) * speedMultiplier * spawnSide;
	var newFish = new Fish(fishType, fishValue, actualFishSpeed, laneIndex);
	newFish.x = actualFishSpeed > 0 ? -150 : 2048 + 150; // Start off-screen
	newFish.y = GAME_CONFIG.LANES[laneIndex].y + (config.yOffset || 0);
	newFish.baseY = newFish.y;
	newFish.lastX = newFish.x;
	newFish.tutorialFish = true; // Custom flag
	fishArray.push(newFish); // Add to global fishArray for rendering by main loop
	fishingScreen.addChild(newFish); // Add to fishingScreen for visibility
	return newFish;
}
function runTutorialStep() {
	GameState.tutorialPaused = false;
	GameState.tutorialAwaitingTap = false; // Will be set by steps if needed for "tap screen"
	// Clear lane highlights from previous step if any
	if (tutorialLaneHighlights.length > 0) {
		tutorialLaneHighlights.forEach(function (overlay) {
			if (overlay && !overlay.destroyed) {
				overlay.destroy();
			}
		});
		tutorialLaneHighlights = [];
	}
	if (tutorialContinueButton) {
		tutorialContinueButton.visible = true;
	} // Default to visible
	if (tutorialContinueText) {
		tutorialContinueText.visible = true;
	}
	// Clear previous tutorial fish unless current step needs it (now steps 3 and 4)
	if (GameState.tutorialFish && GameState.tutorialStep !== 3 && GameState.tutorialStep !== 4) {
		if (!GameState.tutorialFish.destroyed) {
			GameState.tutorialFish.destroy();
		}
		var idx = fishArray.indexOf(GameState.tutorialFish);
		if (idx > -1) {
			fishArray.splice(idx, 1);
		}
		GameState.tutorialFish = null;
	}
	// Ensure fishing screen elements are visually active
	// Adjusted max step for animations to 8 (new end tutorial step is 7)
	if (fishingElements) {
		if (typeof fishingElements.startWaterSurfaceAnimation === 'function' && GameState.tutorialStep < 8) {
			fishingElements.startWaterSurfaceAnimation();
		}
		if (typeof fishingElements.startBoatAndFishermanAnimation === 'function' && GameState.tutorialStep < 8) {
			fishingElements.startBoatAndFishermanAnimation();
		}
		if (fishingElements.hook && GameState.tutorialStep < 8) {
			fishingElements.hook.y = GAME_CONFIG.LANES[1].y; // Reset hook
			GameState.hookTargetLaneIndex = 1;
		}
	}
	switch (GameState.tutorialStep) {
		case 0:
			// Welcome
			setTutorialText("Welcome to Beat Fisher! Let's learn the basics. Tap 'CONTINUE'.");
			GameState.tutorialPaused = true;
			break;
		case 1:
			// The Hook Explanation
			setTutorialText("This is your hook. It automatically moves to the lane with the closest approaching fish. Tap 'CONTINUE'.");
			if (fishingElements.hook && !fishingElements.hook.destroyed) {
				tween(fishingElements.hook.scale, {
					x: 1.2,
					y: 1.2
				}, {
					duration: 250,
					onFinish: function onFinish() {
						if (fishingElements.hook && !fishingElements.hook.destroyed) {
							tween(fishingElements.hook.scale, {
								x: 1,
								y: 1
							}, {
								duration: 250
							});
						}
					}
				});
			}
			GameState.tutorialPaused = true;
			break;
		case 2:
			// Lanes & Tapping Explanation with Overlay
			setTutorialText("Fish swim in three lanes. When a fish is over the hook in its lane, TAP THE SCREEN in that lane to catch it. The lanes are highlighted. Tap 'CONTINUE'.");
			// Add highlights behind the text box elements
			for (var i = 0; i < GAME_CONFIG.LANES.length; i++) {
				var laneYPos = GAME_CONFIG.LANES[i].y;
				var highlight = LK.getAsset('laneHighlight', {
					anchorX: 0.5,
					anchorY: 0.5,
					x: GAME_CONFIG.SCREEN_CENTER_X,
					y: laneYPos,
					alpha: 0.25,
					// Semi-transparent
					width: 1800,
					height: 200
				});
				tutorialOverlayContainer.addChildAt(highlight, 0); // Add at the bottom of the container
				tutorialLaneHighlights.push(highlight);
			}
			GameState.tutorialPaused = true;
			break;
		case 3:
			// Was case 2
			// Fish Approaching & First Catch Attempt
			setTutorialText("A fish will now approach. Tap in its lane when it's under the hook!");
			tutorialContinueButton.visible = false; // No continue button, action is tapping screen
			tutorialContinueText.visible = false;
			GameState.tutorialPaused = false; // Fish moves
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
				GameState.tutorialFish.destroy();
			}
			GameState.tutorialFish = spawnTutorialFishHelper({
				type: 'shallow',
				speedMultiplier: 0.35,
				lane: 1
			});
			break;
		case 4:
			// Was case 3
			// Perfect/Good Timing (after first successful catch)
			setTutorialText("Great! Timing is key. 'Perfect' or 'Good' catches earn more. Try to catch this next fish!");
			tutorialContinueButton.visible = false;
			tutorialContinueText.visible = false;
			GameState.tutorialPaused = false;
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
				GameState.tutorialFish.destroy();
			}
			GameState.tutorialFish = spawnTutorialFishHelper({
				type: 'shallow',
				speedMultiplier: 0.45,
				lane: 1
			});
			break;
		case 5:
			// Was case 4
			// Combos
			setTutorialText("Nice one! Catch fish consecutively to build a COMBO for bonus points! Tap 'CONTINUE'.");
			GameState.tutorialPaused = true;
			break;
		case 6:
			// Was case 5
			// Music & Rhythm
			setTutorialText("Fish will approach the hook on the beat with the music's rhythm. Listen to the beat! Tap 'CONTINUE'.");
			GameState.tutorialPaused = true;
			break;
		case 7:
			// Was case 6
			// End Tutorial
			setTutorialText("You're all set! Tap 'CONTINUE' to go to the fishing spots!");
			GameState.tutorialPaused = true;
			break;
		default:
			// Effectively step 8
			// Tutorial finished
			GameState.tutorialMode = false;
			tutorialOverlayContainer.visible = false;
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
				GameState.tutorialFish.destroy();
				var idxDefault = fishArray.indexOf(GameState.tutorialFish);
				if (idxDefault > -1) {
					fishArray.splice(idxDefault, 1);
				}
				GameState.tutorialFish = null;
			}
			// Clear lane highlights if any persist
			if (tutorialLaneHighlights.length > 0) {
				tutorialLaneHighlights.forEach(function (overlay) {
					if (overlay && !overlay.destroyed) {
						overlay.destroy();
					}
				});
				tutorialLaneHighlights = [];
			}
			// Clean up fishing screen elements explicitly if they were started by tutorial
			if (fishingElements && fishingElements.boat && !fishingElements.boat.destroyed) {
				tween.stop(fishingElements.boat);
			}
			if (fishingElements && fishingElements.fishermanContainer && !fishingElements.fishermanContainer.destroyed) {
				tween.stop(fishingElements.fishermanContainer);
			}
			// Stop water surface animations
			if (fishingElements && fishingElements.waterSurfaceSegments) {
				fishingElements.waterSurfaceSegments.forEach(function (segment) {
					if (segment && !segment.destroyed) {
						tween.stop(segment);
					}
				});
			}
			showScreen('levelSelect');
			break;
	}
}
function startTutorial() {
	GameState.tutorialMode = true;
	GameState.tutorialStep = 0;
	GameState.gameActive = false;
	// Ensure fishing screen is visible and setup for tutorial
	showScreen('fishing');
	fishingScreen.alpha = 1;
	// Clear any active game elements from a previous session
	fishArray.forEach(function (f) {
		if (f && !f.destroyed) {
			f.destroy();
		}
	});
	fishArray = [];
	ImprovedRhythmSpawner.reset();
	// Clear existing UI text
	if (fishingElements.scoreText) {
		fishingElements.scoreText.setText('');
	}
	if (fishingElements.fishText) {
		fishingElements.fishText.setText('');
	}
	if (fishingElements.comboText) {
		fishingElements.comboText.setText('');
	}
	if (fishingElements.progressText) {
		fishingElements.progressText.setText('');
	}
	// CREATE LANE BRACKETS FOR TUTORIAL
	var bracketAssetHeight = 150;
	var bracketAssetWidth = 75;
	// Clear any existing brackets first
	if (laneBrackets && laneBrackets.length > 0) {
		laneBrackets.forEach(function (bracketPair) {
			if (bracketPair.left && !bracketPair.left.destroyed) {
				bracketPair.left.destroy();
			}
			if (bracketPair.right && !bracketPair.right.destroyed) {
				bracketPair.right.destroy();
			}
		});
	}
	laneBrackets = [];
	if (fishingScreen && !fishingScreen.destroyed) {
		for (var i = 0; i < GAME_CONFIG.LANES.length; i++) {
			var laneY = GAME_CONFIG.LANES[i].y;
			var leftBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				x: bracketAssetWidth / 2,
				y: laneY,
				height: bracketAssetHeight
			}));
			var rightBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				scaleX: -1,
				x: 2048 - bracketAssetWidth / 2,
				y: laneY,
				height: bracketAssetHeight
			}));
			laneBrackets.push({
				left: leftBracket,
				right: rightBracket
			});
		}
	}
	// Don't create tutorial elements here - wait for intro to finish
	// createTutorialElements() and runTutorialStep() will be called after intro
}
// This function is called from game.update when tutorialFish exists
function checkTutorialFishState() {
	var fish = GameState.tutorialFish;
	if (!fish || fish.destroyed || fish.caught || fish.missed) {
		return;
	}
	var hookX = fishingElements.hook.x;
	// Fish passes hook without interaction during catch steps (now steps 3 or 4)
	if (GameState.tutorialStep === 3 || GameState.tutorialStep === 4) {
		var passedHook = fish.speed > 0 && fish.x > hookX + GAME_CONFIG.MISS_WINDOW + fish.fishGraphics.width / 2 ||
		// fish right edge passed hook right boundary
		fish.speed < 0 && fish.x < hookX - GAME_CONFIG.MISS_WINDOW - fish.fishGraphics.width / 2; // fish left edge passed hook left boundary
		if (passedHook) {
			fish.missed = true; // Mark to prevent further attempts on this specific fish instance
			GameState.tutorialPaused = true; // Pause to show message
			setTutorialText("It got away! Tap 'CONTINUE' to try that part again.");
			// Next tap on "CONTINUE" will call runTutorialStep(), which will re-run current step.
		}
	}
	// Generic off-screen removal for tutorial fish
	if (fish.x < -250 || fish.x > 2048 + 250) {
		var wasCriticalStep = GameState.tutorialStep === 3 || GameState.tutorialStep === 4; // Adjusted step numbers
		var fishIndex = fishArray.indexOf(fish);
		if (fishIndex > -1) {
			fishArray.splice(fishIndex, 1);
		}
		fish.destroy();
		GameState.tutorialFish = null;
		if (wasCriticalStep && !fish.caught && !fish.missed) {
			// If it just went off screen without resolution
			GameState.tutorialPaused = true;
			setTutorialText("The fish swam off. Tap 'CONTINUE' to try that part again.");
		}
	}
}
var GameState = {
	// Game flow
	currentScreen: 'title',
	// 'title', 'levelSelect', 'fishing', 'results'
	// Player progression
	currentDepth: 0,
	money: 0,
	totalFishCaught: 0,
	ownedSongs: [],
	// Array of {depth, songIndex} objects
	// Level selection
	selectedDepth: 0,
	selectedSong: 0,
	// Current session
	sessionScore: 0,
	sessionFishCaught: 0,
	sessionFishSpawned: 0,
	combo: 0,
	maxCombo: 0,
	// Game state
	gameActive: false,
	songStartTime: 0,
	lastBeatTime: 0,
	beatCount: 0,
	introPlaying: false,
	// Tracks if the intro animation is currently playing
	musicNotesActive: false,
	// Tracks if music note particle system is active
	currentPlayingMusicId: 'rhythmTrack',
	// ID of the music track currently playing in a session
	currentPlayingMusicInitialVolume: 0.8,
	// Initial volume of the current music track for fade reference
	hookTargetLaneIndex: 1,
	// Start with hook targeting the middle lane (index 1)
	// Tutorial State
	tutorialMode: false,
	// Is the tutorial currently active?
	tutorialStep: 0,
	// Current step in the tutorial sequence
	tutorialPaused: false,
	// Is the tutorial paused (e.g., for text display)?
	tutorialAwaitingTap: false,
	// Is the tutorial waiting for a generic tap to continue?
	tutorialFish: null,
	// Stores the fish object used in a tutorial step
	// Initialize owned songs (first song of each unlocked depth is free)
	initOwnedSongs: function initOwnedSongs() {
		this.ownedSongs = [];
		for (var i = 0; i <= this.currentDepth; i++) {
			this.ownedSongs.push({
				depth: i,
				songIndex: 0
			});
		}
	},
	hasSong: function hasSong(depth, songIndex) {
		return this.ownedSongs.some(function (song) {
			return song.depth === depth && song.songIndex === songIndex;
		});
	},
	buySong: function buySong(depth, songIndex) {
		var song = GAME_CONFIG.DEPTHS[depth].songs[songIndex];
		if (this.money >= song.cost && !this.hasSong(depth, songIndex)) {
			this.money -= song.cost;
			this.ownedSongs.push({
				depth: depth,
				songIndex: songIndex
			});
			return true;
		}
		return false;
	},
	getCurrentDepthConfig: function getCurrentDepthConfig() {
		return GAME_CONFIG.DEPTHS[this.selectedDepth];
	},
	getCurrentSongConfig: function getCurrentSongConfig() {
		return GAME_CONFIG.DEPTHS[this.selectedDepth].songs[this.selectedSong];
	},
	canUpgrade: function canUpgrade() {
		var nextDepth = GAME_CONFIG.DEPTHS[this.currentDepth + 1];
		return nextDepth && this.money >= nextDepth.upgradeCost;
	},
	upgrade: function upgrade() {
		if (this.canUpgrade()) {
			var nextDepth = GAME_CONFIG.DEPTHS[this.currentDepth + 1];
			this.money -= nextDepth.upgradeCost;
			this.currentDepth++;
			// Give free first song of new depth
			this.ownedSongs.push({
				depth: this.currentDepth,
				songIndex: 0
			});
			return true;
		}
		return false;
	}
};
var titleScreen = game.addChild(new Container());
var levelSelectScreen = game.addChild(new Container());
var fishingScreen = game.addChild(new Container());
var resultsScreen = game.addChild(new Container());
// Initialize
GameState.initOwnedSongs();
/**** 
* Title Screen
****/ 
/**** 
* Title Screen - FIXED VERSION
****/ 
function createTitleScreen() {
	var titleBg = titleScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		alpha: 1,
		height: 2732,
		color: 0x87CEEB
	}));
	// ANIMATED CONTAINER GROUP - This will contain ALL visual elements including backgrounds
	var titleAnimationGroup = titleScreen.addChild(new Container());
	// Sky background image - NOW INSIDE animation group
	var titleSky = titleAnimationGroup.addChild(LK.getAsset('skybackground', {
		x: 0,
		y: -500
	}));
	// Water background image - NOW INSIDE animation group
	var titleWater = titleAnimationGroup.addChild(LK.getAsset('water', {
		x: 0,
		y: GAME_CONFIG.WATER_SURFACE_Y,
		width: 2048,
		height: 2732 - GAME_CONFIG.WATER_SURFACE_Y
	}));
	// Initialize containers for title screen ambient particles - INSIDE animation group
	titleScreenOceanBubbleContainer = titleAnimationGroup.addChild(new Container());
	titleScreenSeaweedContainer = titleAnimationGroup.addChild(new Container());
	titleScreenCloudContainer = titleAnimationGroup.addChild(new Container());
	// Create a single container for boat, fisherman, and line that all move together
	var titleBoatGroup = titleAnimationGroup.addChild(new Container());
	// Boat positioned at origin of the group
	var titleBoat = titleBoatGroup.addChild(LK.getAsset('boat', {
		anchorX: 0.5,
		anchorY: 0.74,
		x: 0,
		// Relative to group
		y: 0 // Relative to group
	}));
	// Fisherman positioned relative to boat within the same group
	var titleFisherman = titleBoatGroup.addChild(LK.getAsset('fisherman', {
		anchorX: 0.5,
		anchorY: 1,
		x: -100,
		// Relative to boat position
		y: -70 // Relative to boat position
	}));
	// Fishing line positioned relative to boat within the same group
	var rodTipX = -100 + 85; // fisherman offset + rod offset from fisherman center
	var rodTipY = -70 - 200; // fisherman y (feet) - fisherman height (to get to head area for rod)
	// initialHookY needs to be relative to the group's origin (which is boat's origin at WATER_SURFACE_Y)
	// GAME_CONFIG.LANES[1].y is an absolute world Y.
	// titleBoatGroup.y is GAME_CONFIG.WATER_SURFACE_Y.
	// So, hook's Y relative to group = absolute hook Y - group's absolute Y
	var initialHookYInGroup = GAME_CONFIG.LANES[1].y - GAME_CONFIG.WATER_SURFACE_Y;
	var titleLine = titleBoatGroup.addChild(LK.getAsset('fishingLine', {
		anchorX: 0.5,
		anchorY: 0,
		x: rodTipX,
		y: rodTipY,
		// Relative to group
		width: 6,
		height: initialHookYInGroup - rodTipY // Length from rod tip to hook, all relative to group
	}));
	var titleHook = titleBoatGroup.addChild(LK.getAsset('hook', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: rodTipX,
		// Hook X matches line X initially
		y: initialHookYInGroup // Relative to group
	}));
	// Position the entire group in the world (within titleAnimationGroup)
	titleBoatGroup.x = GAME_CONFIG.SCREEN_CENTER_X;
	titleBoatGroup.y = GAME_CONFIG.WATER_SURFACE_Y;
	// Store base position for wave animation
	var boatGroupBaseY = titleBoatGroup.y; // This is the group's world Y
	var boatWaveAmplitude = 10;
	var boatWaveHalfCycleDuration = 2000;
	var boatRotationAmplitude = 0.03;
	var boatRotationDuration = 3000;
	// Wave animation variables for line (used in updateTitleFishingLineWave)
	var lineWaveAmplitude = 12;
	var lineWaveSpeed = 0.03;
	var linePhaseOffset = 0;
	// Animated Water Surface segments - INSIDE animation group
	var titleWaterSurfaceSegments = [];
	var NUM_WAVE_SEGMENTS_TITLE = 32;
	var SEGMENT_WIDTH_TITLE = 2048 / NUM_WAVE_SEGMENTS_TITLE;
	var SEGMENT_HEIGHT_TITLE = 24;
	var WAVE_AMPLITUDE_TITLE = 12;
	var WAVE_HALF_PERIOD_MS_TITLE = 2500;
	var PHASE_DELAY_MS_PER_SEGMENT_TITLE = WAVE_HALF_PERIOD_MS_TITLE * 2 / NUM_WAVE_SEGMENTS_TITLE;
	// Create water surface segments
	for (var i = 0; i < NUM_WAVE_SEGMENTS_TITLE; i++) {
		var segment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH_TITLE,
			y: GAME_CONFIG.WATER_SURFACE_Y,
			width: SEGMENT_WIDTH_TITLE + 1,
			height: SEGMENT_HEIGHT_TITLE,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.8,
			tint: 0x4fc3f7
		});
		segment.baseY = GAME_CONFIG.WATER_SURFACE_Y;
		titleAnimationGroup.addChild(segment); // Water surface is part of main animation group, not boat group
		titleWaterSurfaceSegments.push(segment);
	}
	for (var i = 0; i < NUM_WAVE_SEGMENTS_TITLE; i++) {
		var whiteSegment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH_TITLE,
			y: GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT_TITLE / 2,
			width: SEGMENT_WIDTH_TITLE + 1,
			height: SEGMENT_HEIGHT_TITLE / 2,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.6,
			tint: 0xffffff
		});
		whiteSegment.baseY = GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT_TITLE / 2;
		titleAnimationGroup.addChild(whiteSegment); // Water surface is part of main animation group
		titleWaterSurfaceSegments.push(whiteSegment);
	}
	// Animation group setup for zoom effect - PROPERLY CENTER THE BOAT ON SCREEN
	var boatCenterX = GAME_CONFIG.SCREEN_CENTER_X; // 1024
	var targetBoatScreenY = GAME_CONFIG.SCREEN_CENTER_Y + 300; // Change +100 to move boat lower/higher
	var boatWorldY = GAME_CONFIG.WATER_SURFACE_Y; // 760 - where titleBoatGroup actually is
	var pivotY = boatWorldY - (targetBoatScreenY - boatWorldY); // Calculate proper pivot offset
	// Set pivot to calculated position and position group so boat ends up at screen center
	titleAnimationGroup.pivot.set(boatCenterX, pivotY);
	titleAnimationGroup.x = GAME_CONFIG.SCREEN_CENTER_X; // 1024
	titleAnimationGroup.y = GAME_CONFIG.SCREEN_CENTER_Y; // 1366 - this will center the boat at targetBoatScreenY
	// Initial zoom state
	var INITIAL_ZOOM_FACTOR = 3.0;
	var FINAL_ZOOM_FACTOR = 1.8; // This matches existing logic if it's used in showScreen
	titleAnimationGroup.scale.set(INITIAL_ZOOM_FACTOR);
	titleAnimationGroup.alpha = 1; // Main animation group is always visible
	// Single wave animation function - moves entire group together
	var targetUpY = boatGroupBaseY - boatWaveAmplitude; // boatGroupBaseY is absolute world Y
	var targetDownY = boatGroupBaseY + boatWaveAmplitude; // boatGroupBaseY is absolute world Y
	function moveTitleBoatGroupUp() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		// We tween the group's world Y position
		tween(titleBoatGroup, {
			y: targetUpY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveTitleBoatGroupDown
		});
	}
	function moveTitleBoatGroupDown() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		tween(titleBoatGroup, {
			y: targetDownY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveTitleBoatGroupUp
		});
	}
	// Single rotation animation - rotates entire group together
	function rockTitleBoatGroupLeft() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		tween(titleBoatGroup, {
			rotation: -boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockTitleBoatGroupRight
		});
	}
	function rockTitleBoatGroupRight() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		tween(titleBoatGroup, {
			rotation: boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockTitleBoatGroupLeft
		});
	}
	// Simplified line wave function - just the sway effect
	function updateTitleFishingLineWave() {
		// titleLine and titleHook are children of titleBoatGroup, their x/y are relative to it.
		// rodTipX, rodTipY, initialHookYInGroup are also relative to titleBoatGroup.
		if (!titleLine || titleLine.destroyed || !titleHook || titleHook.destroyed) {
			return;
		}
		linePhaseOffset += lineWaveSpeed;
		var waveOffset = Math.sin(linePhaseOffset) * lineWaveAmplitude;
		// Only apply the wave sway to X, positions stay relative to group's origin
		// rodTipX is the line's base X relative to the group.
		titleLine.x = rodTipX + waveOffset * 0.3;
		titleHook.x = rodTipX + waveOffset;
		// Y positions of line and hook (titleLine.y and titleHook.y) are static relative to the group.
		// Recalculate line rotation based on hook position relative to line's anchor
		// All coordinates here are relative to titleBoatGroup.
		var deltaX = titleHook.x - titleLine.x; // Difference in x relative to group
		var deltaY = titleHook.y - titleLine.y; // Difference in y relative to group
		var actualLineLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
		titleLine.height = actualLineLength;
		if (actualLineLength > 0.001) {
			titleLine.rotation = Math.atan2(deltaY, deltaX) - Math.PI / 2;
		} else {
			titleLine.rotation = 0;
		}
		titleHook.rotation = titleLine.rotation; // Hook rotation matches line's
	}
	// Water surface animation function
	function startTitleWaterSurfaceAnimationFunc() {
		for (var k = 0; k < titleWaterSurfaceSegments.length; k++) {
			var segment = titleWaterSurfaceSegments[k];
			if (!segment || segment.destroyed) {
				continue;
			}
			var segmentIndexForDelay = k % NUM_WAVE_SEGMENTS_TITLE;
			(function (currentLocalSegment, currentLocalSegmentIndexForDelay) {
				var animUp, animDown;
				animDown = function animDown() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY + WAVE_AMPLITUDE_TITLE
					}, {
						duration: WAVE_HALF_PERIOD_MS_TITLE,
						easing: tween.easeInOut,
						onFinish: animUp
					});
				};
				animUp = function animUp() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY - WAVE_AMPLITUDE_TITLE
					}, {
						duration: WAVE_HALF_PERIOD_MS_TITLE,
						easing: tween.easeInOut,
						onFinish: animDown
					});
				};
				LK.setTimeout(function () {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					// Start with DOWN movement first
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY + WAVE_AMPLITUDE_TITLE
					}, {
						duration: WAVE_HALF_PERIOD_MS_TITLE,
						easing: tween.easeInOut,
						onFinish: animUp // This should call animUp after going down
					});
				}, currentLocalSegmentIndexForDelay * PHASE_DELAY_MS_PER_SEGMENT_TITLE);
			})(segment, segmentIndexForDelay);
		}
	}
	// BLACK OVERLAY for reveal effect - this goes OVER everything
	var blackOverlay = titleScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		width: 2048,
		height: 2732,
		color: 0x000000,
		// Pure black
		alpha: 1 // Starts fully opaque
	}));
	// UI elements - OUTSIDE animation group so they don't zoom, ABOVE black overlay
	var titleImage = titleScreen.addChild(LK.getAsset('titleimage', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 700,
		// Positioned where the subtitle roughly was, adjust as needed
		alpha: 0,
		scaleX: 0.8,
		// Adjust scale as needed for optimal size
		scaleY: 0.8 // Adjust scale as needed for optimal size
	}));
	// Buttons - OUTSIDE animation group, ABOVE black overlay
	// New Y positions: 1/3 from bottom of screen (2732 / 3 = 910.66. Y = 2732 - 910.66 = 1821.33)
	var startButtonY = 2732 - 2732 / 3.5; // Approx 1821
	var tutorialButtonY = startButtonY + 600; // Maintain 150px gap
	var startButton = titleScreen.addChild(LK.getAsset('startbutton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: startButtonY,
		alpha: 0
	}));
	var tutorialButton = titleScreen.addChild(LK.getAsset('tutorialbutton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: tutorialButtonY,
		alpha: 0
	}));
	// No assignment here; assign tutorialButtonGfx after createTitleScreen returns
	return {
		startButton: startButton,
		tutorialButton: tutorialButton,
		// This is the graphics object
		titleImage: titleImage,
		titleAnimationGroup: titleAnimationGroup,
		blackOverlay: blackOverlay,
		// Return the group and its specific animation functions
		titleBoatGroup: titleBoatGroup,
		moveTitleBoatGroupUp: moveTitleBoatGroupUp,
		rockTitleBoatGroupLeft: rockTitleBoatGroupLeft,
		// Individual elements that might still be needed for direct access or other effects
		titleSky: titleSky,
		// Sky is not in titleBoatGroup
		titleWater: titleWater,
		// Water background is not in titleBoatGroup
		titleWaterSurfaceSegments: titleWaterSurfaceSegments,
		// Water surface segments are not in titleBoatGroup
		// Line and hook are part of titleBoatGroup, but if direct reference is needed for some reason, they could be returned.
		// However, following the goal's spirit of "Return the group instead of individual elements [that are part of it]"
		// titleLine and titleHook might be omitted here unless specifically needed by other parts of the code.
		// For now, including them if they were returned before and are still locally defined.
		// updateTitleFishingLineWave accesses titleLine and titleHook locally.
		titleLine: titleLine,
		// Still defined locally, might be useful for direct reference
		titleHook: titleHook,
		// Still defined locally
		startTitleWaterSurfaceAnimation: startTitleWaterSurfaceAnimationFunc,
		updateTitleFishingLineWave: updateTitleFishingLineWave // The new simplified version
	};
}
/**** 
* Level Select Screen
****/ 
function createLevelSelectScreen() {
	var selectBg = levelSelectScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		alpha: 0.8,
		height: 2732
	}));
	// Top section - Title, Money, and Back button
	var title = new Text2('SELECT FISHING SPOT', {
		size: 90,
		// Increased from 80
		fill: 0xFFFFFF
	});
	title.anchor.set(0.5, 0.5);
	title.x = GAME_CONFIG.SCREEN_CENTER_X;
	title.y = 180; // Moved up slightly
	levelSelectScreen.addChild(title);
	// Money display (keep at top)
	var moneyDisplay = new Text2('Money: $0', {
		size: 70,
		// Increased from 60
		fill: 0xFFD700
	});
	moneyDisplay.anchor.set(1, 0);
	moneyDisplay.x = 1900;
	moneyDisplay.y = 80; // Moved up slightly
	levelSelectScreen.addChild(moneyDisplay);
	// Back button (moved to bottom center)
	var backButton = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 2732 - 300,
		// Positioned 100px from the bottom edge (center of button)
		tint: 0x757575
	}));
	var backButtonText = new Text2('BACK', {
		size: 45,
		// Increased from 40
		fill: 0xFFFFFF
	});
	backButtonText.anchor.set(0.5, 0.5);
	backButtonText.x = backButton.x;
	backButtonText.y = backButton.y;
	levelSelectScreen.addChild(backButtonText);
	// Depth tabs (moved down and spaced out more)
	var depthTabs = [];
	// Song display area (moved to center of screen)
	var songCard = levelSelectScreen.addChild(LK.getAsset('songCard', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1100,
		// Moved down significantly from 700
		width: 900,
		// Made wider
		height: 300 // Made taller
	}));
	// Song navigation arrows (repositioned around the larger song card)
	var leftArrow = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 350,
		// Moved further left
		y: 1100,
		// Moved down with song card
		tint: 0x666666,
		width: 120,
		// Made bigger
		height: 120
	}));
	var leftArrowText = new Text2('<', {
		size: 80,
		// Increased from 60
		fill: 0xFFFFFF
	});
	leftArrowText.anchor.set(0.5, 0.5);
	leftArrowText.x = 350;
	leftArrowText.y = 1100;
	levelSelectScreen.addChild(leftArrowText);
	var rightArrow = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 1698,
		// Moved further right
		y: 1100,
		// Moved down with song card
		tint: 0x666666,
		width: 120,
		// Made bigger
		height: 120
	}));
	var rightArrowText = new Text2('>', {
		size: 80,
		// Increased from 60
		fill: 0xFFFFFF
	});
	rightArrowText.anchor.set(0.5, 0.5);
	rightArrowText.x = 1698;
	rightArrowText.y = 1100;
	levelSelectScreen.addChild(rightArrowText);
	// Song info (repositioned within the larger song card area)
	var songTitle = new Text2('Song Title', {
		size: 60,
		// Increased from 50
		fill: 0xFFFFFF
	});
	songTitle.anchor.set(0.5, 0.5);
	songTitle.x = GAME_CONFIG.SCREEN_CENTER_X;
	songTitle.y = 1020; // Positioned above song card center
	levelSelectScreen.addChild(songTitle);
	var songInfo = new Text2('BPM: 120 | Duration: 2:00', {
		size: 40,
		// Increased from 30
		fill: 0xCCCCCC
	});
	songInfo.anchor.set(0.5, 0.5);
	songInfo.x = GAME_CONFIG.SCREEN_CENTER_X;
	songInfo.y = 1100; // Centered in song card
	levelSelectScreen.addChild(songInfo);
	var songEarnings = new Text2('Potential Earnings: $50-100', {
		size: 40,
		// Increased from 30
		fill: 0x4CAF50
	});
	songEarnings.anchor.set(0.5, 0.5);
	songEarnings.x = GAME_CONFIG.SCREEN_CENTER_X;
	songEarnings.y = 1180; // Positioned below song card center
	levelSelectScreen.addChild(songEarnings);
	// Play/Buy button (moved down and made larger)
	var playButton = levelSelectScreen.addChild(LK.getAsset('bigButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1400,
		// Moved down significantly from 900
		width: 500,
		// Made wider
		height: 130 // Made taller
	}));
	var playButtonText = new Text2('PLAY', {
		size: 60,
		// Increased from 50
		fill: 0xFFFFFF
	});
	playButtonText.anchor.set(0.5, 0.5);
	playButtonText.x = GAME_CONFIG.SCREEN_CENTER_X;
	playButtonText.y = 1400;
	levelSelectScreen.addChild(playButtonText);
	// Shop button (moved to bottom and made larger)
	var shopButton = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1650,
		// Moved down significantly from 1100
		tint: 0x666666,
		// Grayed out since it's not available
		width: 450,
		// Made wider
		height: 120 // Made taller
	}));
	var shopButtonText = new Text2('UPGRADE ROD', {
		size: 50,
		// Increased from 40
		fill: 0xFFFFFF
	});
	shopButtonText.anchor.set(0.5, 0.5);
	shopButtonText.x = GAME_CONFIG.SCREEN_CENTER_X;
	shopButtonText.y = 1650;
	levelSelectScreen.addChild(shopButtonText);
	// Add "Coming soon!" text underneath
	var comingSoonText = new Text2('Coming soon!', {
		size: 35,
		fill: 0xFFD700 // Gold color to make it stand out
	});
	comingSoonText.anchor.set(0.5, 0.5);
	comingSoonText.x = GAME_CONFIG.SCREEN_CENTER_X;
	comingSoonText.y = 1730; // Position below the button
	levelSelectScreen.addChild(comingSoonText);
	return {
		moneyDisplay: moneyDisplay,
		depthTabs: depthTabs,
		leftArrow: leftArrow,
		rightArrow: rightArrow,
		songTitle: songTitle,
		songInfo: songInfo,
		songEarnings: songEarnings,
		playButton: playButton,
		playButtonText: playButtonText,
		shopButton: shopButton,
		shopButtonText: shopButtonText,
		backButton: backButton
	};
}
/**** 
* Fishing Screen
****/ 
function createFishingScreen() {
	// Sky background - should be added first to be behind everything else
	var sky = fishingScreen.addChild(LK.getAsset('skybackground', {
		x: 0,
		y: -500
	}));
	// Water background
	var water = fishingScreen.addChild(LK.getAsset('water', {
		x: 0,
		y: GAME_CONFIG.WATER_SURFACE_Y,
		width: 2048,
		height: 2732 - GAME_CONFIG.WATER_SURFACE_Y
	}));
	// Create a container for ambient ocean bubbles (from bottom of screen)
	// This should be layered above the 'water' background, but below fish, boat, etc.
	globalOceanBubbleContainer = fishingScreen.addChild(new Container());
	// Create a container for seaweed particles
	globalSeaweedContainer = fishingScreen.addChild(new Container());
	// Create a container for cloud particles (added early so clouds appear behind UI)
	globalCloudContainer = fishingScreen.addChild(new Container());
	// Create a container for bubbles to render them behind fish and other elements
	bubbleContainer = fishingScreen.addChild(new Container());
	// Create a container for music notes
	musicNotesContainer = fishingScreen.addChild(new Container());
	// Music notes should visually appear to come from the boat area, so their container
	// should ideally be layered accordingly. Adding it here means it's on top of water,
	// but if boat/fisherman are added later, notes might appear behind them if not managed.
	// For now, notes will be added to this container, which itself is added to fishingScreen.
	// Animated Water Surface segments code
	var waterSurfaceSegments = []; // This will be populated for returning and cleanup
	var waterSurfaceSegmentsBlueTemp = []; // Temporary array for blue segments
	var waterSurfaceSegmentsWhiteTemp = []; // Temporary array for white segments
	var NUM_WAVE_SEGMENTS = 32;
	var SEGMENT_WIDTH = 2048 / NUM_WAVE_SEGMENTS;
	var SEGMENT_HEIGHT = 24;
	var WAVE_AMPLITUDE = 12;
	var WAVE_HALF_PERIOD_MS = 2500;
	var PHASE_DELAY_MS_PER_SEGMENT = WAVE_HALF_PERIOD_MS * 2 / NUM_WAVE_SEGMENTS;
	// Create blue segments (assets only, not added to fishingScreen yet)
	for (var i = 0; i < NUM_WAVE_SEGMENTS; i++) {
		var segment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH,
			y: GAME_CONFIG.WATER_SURFACE_Y,
			width: SEGMENT_WIDTH + 1,
			height: SEGMENT_HEIGHT,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.8,
			tint: 0x4fc3f7
		});
		segment.baseY = GAME_CONFIG.WATER_SURFACE_Y;
		// Animation functions will be defined and started by startWaterSurfaceAnimationFunc
		waterSurfaceSegmentsBlueTemp.push(segment);
	}
	// Create white segments (assets only, not added to fishingScreen yet)
	for (var i = 0; i < NUM_WAVE_SEGMENTS; i++) {
		var whiteSegment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH,
			y: GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT / 2,
			width: SEGMENT_WIDTH + 1,
			height: SEGMENT_HEIGHT / 2,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.6,
			tint: 0xffffff
		});
		whiteSegment.baseY = GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT / 2;
		// Animation functions will be defined and started by startWaterSurfaceAnimationFunc
		waterSurfaceSegmentsWhiteTemp.push(whiteSegment);
	}
	// Boat - Add this to fishingScreen first
	var boat = fishingScreen.addChild(LK.getAsset('boat', {
		anchorX: 0.5,
		anchorY: 0.74,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: GAME_CONFIG.WATER_SURFACE_Y
	}));
	// Now add the water segments to fishingScreen, so they render on top of the boat
	for (var i = 0; i < waterSurfaceSegmentsBlueTemp.length; i++) {
		fishingScreen.addChild(waterSurfaceSegmentsBlueTemp[i]);
		waterSurfaceSegments.push(waterSurfaceSegmentsBlueTemp[i]); // Also add to the main array for cleanup
	}
	for (var i = 0; i < waterSurfaceSegmentsWhiteTemp.length; i++) {
		fishingScreen.addChild(waterSurfaceSegmentsWhiteTemp[i]);
		waterSurfaceSegments.push(waterSurfaceSegmentsWhiteTemp[i]); // Also add to the main array for cleanup
	}
	// Create separate fisherman container that will sync with boat movement
	var fishermanContainer = fishingScreen.addChild(new Container());
	// Fisherman (now in its own container, positioned to match boat)
	var fisherman = fishermanContainer.addChild(LK.getAsset('fisherman', {
		anchorX: 0.5,
		anchorY: 1,
		x: GAME_CONFIG.SCREEN_CENTER_X - 100,
		y: GAME_CONFIG.WATER_SURFACE_Y - 70
	}));
	// Store references for wave animation sync
	var boatBaseY = boat.y;
	var fishermanBaseY = fishermanContainer.y;
	var boatWaveAmplitude = 10;
	var boatWaveHalfCycleDuration = 2000;
	// SINGLE ANIMATED FISHING LINE
	var initialHookY = GAME_CONFIG.LANES[1].y;
	var fishingLineStartY = -100;
	var line = fishingScreen.addChild(LK.getAsset('fishingLine', {
		anchorX: 0.5,
		anchorY: 0,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: GAME_CONFIG.WATER_SURFACE_Y + fishingLineStartY,
		width: 6,
		height: initialHookY - (GAME_CONFIG.WATER_SURFACE_Y + fishingLineStartY)
	}));
	var hook = fishingScreen.addChild(LK.getAsset('hook', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: initialHookY
	}));
	hook.originalY = initialHookY;
	var lineWaveAmplitude = 12;
	var lineWaveSpeed = 0.03;
	var linePhaseOffset = 0;
	function updateFishingLineWave() {
		linePhaseOffset += lineWaveSpeed;
		var rodTipX = fishermanContainer.x + fisherman.x + 85;
		var rodTipY = fishermanContainer.y + fisherman.y - fisherman.height;
		var waveOffset = Math.sin(linePhaseOffset) * lineWaveAmplitude;
		line.x = rodTipX + waveOffset * 0.3;
		line.y = rodTipY;
		hook.x = rodTipX + waveOffset;
		var hookAttachX = hook.x;
		var hookAttachY = hook.y - hook.height / 2;
		var deltaX = hookAttachX - line.x;
		var deltaY = hookAttachY - line.y;
		var actualLineLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
		line.height = actualLineLength;
		if (actualLineLength > 0.001) {
			line.rotation = Math.atan2(deltaY, deltaX) - Math.PI / 2;
		} else {
			line.rotation = 0;
		}
		hook.rotation = line.rotation;
	}
	// Calculate target positions for boat wave animation
	var targetUpY = boatBaseY - boatWaveAmplitude;
	var targetDownY = boatBaseY + boatWaveAmplitude;
	var fishermanTargetUpY = fishermanBaseY - boatWaveAmplitude;
	var fishermanTargetDownY = fishermanBaseY + boatWaveAmplitude;
	// Synchronized wave animation functions (defined here to be closured)
	function moveBoatAndFishermanUp() {
		if (!boat || boat.destroyed || !fishermanContainer || fishermanContainer.destroyed) {
			return;
		}
		tween(boat, {
			y: targetUpY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveBoatAndFishermanDown
		});
		tween(fishermanContainer, {
			y: fishermanTargetUpY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut
		});
	}
	function moveBoatAndFishermanDown() {
		if (!boat || boat.destroyed || !fishermanContainer || fishermanContainer.destroyed) {
			return;
		}
		tween(boat, {
			y: targetDownY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveBoatAndFishermanUp
		});
		tween(fishermanContainer, {
			y: fishermanTargetDownY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut
		});
	}
	var boatRotationAmplitude = 0.03;
	var boatRotationDuration = 3000;
	function rockBoatLeft() {
		if (!boat || boat.destroyed || !fisherman || fisherman.destroyed) {
			return;
		}
		tween(boat, {
			rotation: -boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockBoatRight
		});
		tween(fisherman, {
			rotation: boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut
		});
	}
	function rockBoatRight() {
		if (!boat || boat.destroyed || !fisherman || fisherman.destroyed) {
			return;
		}
		tween(boat, {
			rotation: boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockBoatLeft
		});
		tween(fisherman, {
			rotation: -boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut
		});
	}
	// Function to start/restart water surface animations
	function startWaterSurfaceAnimationFunc() {
		var allSegments = waterSurfaceSegments; // Use the populated array from fishingElements via closure
		for (var k = 0; k < allSegments.length; k++) {
			var segment = allSegments[k];
			if (!segment || segment.destroyed) {
				continue;
			}
			var segmentIndexForDelay = k % NUM_WAVE_SEGMENTS;
			(function (currentLocalSegment, currentLocalSegmentIndexForDelay) {
				var animUp, animDown;
				animDown = function animDown() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY + WAVE_AMPLITUDE
					}, {
						duration: WAVE_HALF_PERIOD_MS,
						easing: tween.easeInOut,
						onFinish: animUp
					});
				};
				animUp = function animUp() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY - WAVE_AMPLITUDE
					}, {
						duration: WAVE_HALF_PERIOD_MS,
						easing: tween.easeInOut,
						onFinish: animDown
					});
				};
				LK.setTimeout(function () {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY - WAVE_AMPLITUDE
					}, {
						// Initial move up
						duration: WAVE_HALF_PERIOD_MS,
						easing: tween.easeInOut,
						onFinish: animDown
					});
				}, currentLocalSegmentIndexForDelay * PHASE_DELAY_MS_PER_SEGMENT);
			})(segment, segmentIndexForDelay);
		}
	}
	// Function to start/restart boat and fisherman animations
	function startBoatAndFishermanAnimationFunc() {
		if (boat && !boat.destroyed && fishermanContainer && !fishermanContainer.destroyed) {
			tween(boat, {
				y: targetUpY
			}, {
				duration: boatWaveHalfCycleDuration / 2,
				easing: tween.easeOut,
				onFinish: moveBoatAndFishermanDown
			});
			tween(fishermanContainer, {
				y: fishermanTargetUpY
			}, {
				duration: boatWaveHalfCycleDuration / 2,
				easing: tween.easeOut
			});
			rockBoatLeft();
		}
	}
	// UI elements (from existing)
	var scoreText = new Text2('Score: 0', {
		size: 70,
		fill: 0xFFFFFF
	});
	scoreText.anchor.set(1, 0);
	scoreText.x = 2048 - 50;
	scoreText.y = 50;
	fishingScreen.addChild(scoreText);
	var fishText = new Text2('Fish: 0/0', {
		size: 55,
		fill: 0xFFFFFF
	});
	fishText.anchor.set(1, 0);
	fishText.x = 2048 - 50;
	fishText.y = 140;
	fishingScreen.addChild(fishText);
	var comboText = new Text2('Combo: 0', {
		size: 55,
		fill: 0xFF9800
	});
	comboText.anchor.set(1, 0);
	comboText.x = 2048 - 50;
	comboText.y = 210;
	fishingScreen.addChild(comboText);
	var progressText = new Text2('0:00 / 0:00', {
		size: 50,
		fill: 0x4FC3F7
	});
	progressText.anchor.set(1, 0);
	progressText.x = 2048 - 50;
	progressText.y = 280;
	fishingScreen.addChild(progressText);
	return {
		boat: boat,
		fishermanContainer: fishermanContainer,
		fisherman: fisherman,
		hook: hook,
		line: line,
		updateFishingLineWave: updateFishingLineWave,
		scoreText: scoreText,
		fishText: fishText,
		comboText: comboText,
		progressText: progressText,
		waterSurfaceSegments: waterSurfaceSegments,
		bubbleContainer: bubbleContainer,
		musicNotesContainer: musicNotesContainer,
		startWaterSurfaceAnimation: startWaterSurfaceAnimationFunc,
		startBoatAndFishermanAnimation: startBoatAndFishermanAnimationFunc
	};
}
/**** 
* Initialize Screen Elements
****/ 
var titleElements = createTitleScreen();
titleElements.tutorialButtonGfx = titleElements.tutorialButton; // Store the graphical button for compatibility
var levelSelectElements = createLevelSelectScreen();
var fishingElements = createFishingScreen();
// Tutorial UI Elements - MUST be at global scope
var tutorialOverlayContainer = game.addChild(new Container());
tutorialOverlayContainer.visible = false;
var tutorialTextBackground;
var tutorialTextDisplay;
var tutorialContinueButton;
var tutorialContinueText;
var tutorialLaneHighlights = []; // To store lane highlight graphics for the tutorial
// Feedback indicators are now created on-demand by the showFeedback function.
// The global feedbackIndicators object is no longer needed.
// Game variables
var fishArray = [];
var bubblesArray = [];
var bubbleContainer; // Container for bubbles, initialized in createFishingScreen
var musicNotesArray = [];
var musicNotesContainer; // Container for music notes
var laneBrackets = []; // Stores the visual bracket pairs for each lane
var musicNoteSpawnCounter = 0;
var MUSIC_NOTE_SPAWN_INTERVAL_TICKS = 45; // Spawn a note roughly every 0.75 seconds
// Ocean Bubbles (ambient background)
var globalOceanBubblesArray = [];
var globalOceanBubbleContainer;
var globalOceanBubbleSpawnCounter = 0;
// Increase interval to reduce amount (higher = less frequent)
var OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS = 40; // Spawn new ocean bubbles roughly every 2/3 second (was 20)
// Seaweed particles (ambient background)
var globalSeaweedArray = [];
var globalSeaweedContainer;
var globalSeaweedSpawnCounter = 0;
var SEAWEED_SPAWN_INTERVAL_TICKS = 120; // Spawn seaweed less frequently than bubbles
var MAX_SEAWEED_COUNT = 8; // Maximum number of seaweed particles at once
// Cloud particles (ambient sky)
var globalCloudArray = [];
var globalCloudContainer;
var globalCloudSpawnCounter = 0;
var CLOUD_SPAWN_INTERVAL_TICKS = 180; // Spawn clouds less frequently than seaweed
var MAX_CLOUD_COUNT = 5; // Maximum number of cloud particles at once
// Title Screen Ambient Particle Systems
var titleScreenOceanBubblesArray = [];
var titleScreenOceanBubbleContainer; // Will be initialized in createTitleScreen
var titleScreenOceanBubbleSpawnCounter = 0;
var titleScreenSeaweedArray = [];
var titleScreenSeaweedContainer; // Will be initialized in createTitleScreen
var titleScreenSeaweedSpawnCounter = 0;
var titleScreenCloudArray = [];
var titleScreenCloudContainer; // Will be initialized in createTitleScreen
var titleScreenCloudSpawnCounter = 0;
// Timers for title screen ambient sounds
var titleSeagullSoundTimer = null;
var titleBoatSoundTimer = null;
/**** 
* Input State and Helpers for Fishing
****/ 
var inputState = {
	touching: false,
	// Is the screen currently being touched?
	touchLane: -1,
	// Which lane was the touch initiated in? (0, 1, 2)
	touchStartTime: 0 // Timestamp of when the touch started (LK.ticks based)
};
// Helper function to determine which lane a Y coordinate falls into
function getTouchLane(y) {
	// Define boundaries based on the midpoints between lane Y coordinates
	// These are calculated from GAME_CONFIG.LANES[i].y values
	// Lane 0: y = 723
	// Lane 1: y = 1366
	// Lane 2: y = 2009
	var boundary_lane0_lane1 = (GAME_CONFIG.LANES[0].y + GAME_CONFIG.LANES[1].y) / 2; // Approx 1044.5
	var boundary_lane1_lane2 = (GAME_CONFIG.LANES[1].y + GAME_CONFIG.LANES[2].y) / 2; // Approx 1687.5
	if (y < boundary_lane0_lane1) {
		return 0; // Top lane (e.g., shallow)
	} else if (y < boundary_lane1_lane2) {
		return 1; // Middle lane (e.g., medium)
	} else {
		return 2; // Bottom lane (e.g., deep)
	}
}
// Shows feedback (perfect, good, miss) at the specified lane
// Shows feedback (perfect, good, miss) at the specified lane
function showFeedback(type, laneIndex) {
	var feedbackY = GAME_CONFIG.LANES[laneIndex].y;
	var indicator = new FeedbackIndicator(type); // Creates a new indicator e.g. FeedbackIndicator('perfect')
	// Position feedback at the single hook's X coordinate and the fish's lane Y
	indicator.x = fishingElements.hook.x; // Use the single hook's X
	indicator.y = feedbackY; // Feedback appears at the fish's lane Y
	fishingScreen.addChild(indicator);
	indicator.show(); // Triggers the animation and self-destruction
}
// Animates the hook in a specific lane after a catch attempt
// Animates the single hook after a catch attempt
function animateHookCatch() {
	var hook = fishingElements.hook;
	// We need a stable originalY. The hook.originalY might change if we re-assign it during tweens.
	// Let's use the target Y of the current fish lane for the "resting" position after animation.
	var restingY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	// Quick bobbing animation for the single hook
	tween(hook, {
		y: restingY - 30
	}, {
		duration: 150,
		easing: tween.easeOut,
		onFinish: function onFinish() {
			tween(hook, {
				y: restingY
			}, {
				duration: 150,
				easing: tween.easeIn,
				onFinish: function onFinish() {
					// Ensure originalY reflects the current target lane after animation.
					hook.originalY = restingY;
				}
			});
		}
	});
}
// Handles input specifically for the fishing screen (down and up events)
function handleFishingInput(x, y, isDown) {
	// If in tutorial mode, and it's a 'down' event during an active catch step (now steps 3 or 4)
	if (GameState.tutorialMode && isDown && (GameState.tutorialStep === 3 || GameState.tutorialStep === 4) && !GameState.tutorialPaused) {
		if (GameState.tutorialFish && !GameState.tutorialFish.caught && !GameState.tutorialFish.missed) {
			checkCatch(getTouchLane(y)); // Attempt catch
		}
		return; // Tutorial catch handled
	}
	// Existing game active check
	if (!GameState.gameActive) {
		return;
	}
	var currentTime = LK.ticks * (1000 / 60); // Current time in ms
	if (isDown) {
		// Touch started
		inputState.touching = true;
		inputState.touchLane = getTouchLane(y);
		inputState.touchStartTime = currentTime;
		// A normal tap action will be processed on 'up'.
	} else {
		// Touch ended (isUp)
		if (inputState.touching) {
			// This was a normal tap.
			checkCatch(inputState.touchLane);
		}
		inputState.touching = false;
	}
}
/**** 
* Screen Management
****/ 
function showScreen(screenName) {
	titleScreen.visible = false;
	levelSelectScreen.visible = false;
	fishingScreen.visible = false;
	resultsScreen.visible = false;
	// Stop any ongoing tweens and clear particles if switching FROM title screen
	if (GameState.currentScreen === 'title' && titleElements) {
		tween.stop(titleElements.titleAnimationGroup);
		tween.stop(titleElements.blackOverlay);
		if (titleElements.titleImage) {
			tween.stop(titleElements.titleImage);
		} // Stop titleImage tween
		if (titleElements.logo) {
			tween.stop(titleElements.logo);
		} // Keep for safety if old code path
		if (titleElements.subtitle) {
			tween.stop(titleElements.subtitle);
		} // Keep for safety
		tween.stop(titleElements.startButton);
		tween.stop(titleElements.tutorialButton);
		// Clear title screen sound timers
		if (titleSeagullSoundTimer) {
			LK.clearTimeout(titleSeagullSoundTimer);
			titleSeagullSoundTimer = null;
		}
		if (titleBoatSoundTimer) {
			LK.clearTimeout(titleBoatSoundTimer);
			titleBoatSoundTimer = null;
		}
		// Stop water surface animations for title screen
		if (titleElements.titleWaterSurfaceSegments) {
			titleElements.titleWaterSurfaceSegments.forEach(function (segment) {
				if (segment && !segment.destroyed) {
					tween.stop(segment);
				}
			});
		}
		// Clear title screen particles
		if (titleScreenOceanBubbleContainer) {
			titleScreenOceanBubbleContainer.removeChildren();
		}
		titleScreenOceanBubblesArray.forEach(function (p) {
			if (p && !p.destroyed) {
				p.destroy();
			}
		});
		titleScreenOceanBubblesArray = [];
		if (titleScreenSeaweedContainer) {
			titleScreenSeaweedContainer.removeChildren();
		}
		titleScreenSeaweedArray.forEach(function (p) {
			if (p && !p.destroyed) {
				p.destroy();
			}
		});
		titleScreenSeaweedArray = [];
		if (titleScreenCloudContainer) {
			titleScreenCloudContainer.removeChildren();
		}
		titleScreenCloudArray.forEach(function (p) {
			if (p && !p.destroyed) {
				p.destroy();
			}
		});
		titleScreenCloudArray = [];
	}
	// Cleanup tutorial elements if switching away from fishing/tutorial
	if (GameState.currentScreen === 'fishing' && GameState.tutorialMode && screenName !== 'fishing') {
		tutorialOverlayContainer.visible = false;
		if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
			GameState.tutorialFish.destroy();
			var idx = fishArray.indexOf(GameState.tutorialFish);
			if (idx > -1) {
				fishArray.splice(idx, 1);
			}
			GameState.tutorialFish = null;
		}
		// Clear lane highlights if any exist when switching screen away from tutorial
		if (tutorialLaneHighlights.length > 0) {
			tutorialLaneHighlights.forEach(function (overlay) {
				if (overlay && !overlay.destroyed) {
					overlay.destroy();
				}
			});
			tutorialLaneHighlights = [];
		}
		GameState.tutorialMode = false; // Ensure tutorial mode is exited
	}
	GameState.currentScreen = screenName;
	switch (screenName) {
		case 'title':
			// Title Screen Sounds
			var _scheduleNextSeagullSound = function scheduleNextSeagullSound() {
				if (GameState.currentScreen !== 'title') {
					// If screen changed, ensure timer is stopped and not rescheduled
					if (titleSeagullSoundTimer) {
						LK.clearTimeout(titleSeagullSoundTimer);
						titleSeagullSoundTimer = null;
					}
					return;
				}
				var randomDelay = 5000 + Math.random() * 10000; // 5-15 seconds
				titleSeagullSoundTimer = LK.setTimeout(function () {
					if (GameState.currentScreen !== 'title') {
						return; // Don't play or reschedule if not on title screen
					}
					var seagullSounds = ['seagull1', 'seagull2', 'seagull3'];
					var randomSoundId = seagullSounds[Math.floor(Math.random() * seagullSounds.length)];
					LK.getSound(randomSoundId).play();
					_scheduleNextSeagullSound(); // Reschedule
				}, randomDelay);
			};
			var _scheduleNextBoatSound = function scheduleNextBoatSound() {
				if (GameState.currentScreen !== 'title') {
					// If screen changed, ensure timer is stopped and not rescheduled
					if (titleBoatSoundTimer) {
						LK.clearTimeout(titleBoatSoundTimer);
						titleBoatSoundTimer = null;
					}
					return;
				}
				var fixedBoatSoundInterval = 6000; // Rhythmic interval: 8 seconds (reduced from 15)
				titleBoatSoundTimer = LK.setTimeout(function () {
					if (GameState.currentScreen !== 'title') {
						return; // Don't play or reschedule if not on title screen
					}
					LK.getSound('boatsounds').play();
					_scheduleNextBoatSound(); // Reschedule
				}, fixedBoatSoundInterval);
			}; // Play initial random seagull sound
			titleScreen.visible = true;
			// Start all animations like in fishing screen
			if (titleElements.startTitleWaterSurfaceAnimation) {
				titleElements.startTitleWaterSurfaceAnimation();
			}
			// Start boat group animations (single animations for the whole group)
			if (titleElements.moveTitleBoatGroupUp) {
				titleElements.moveTitleBoatGroupUp();
			}
			if (titleElements.rockTitleBoatGroupLeft) {
				titleElements.rockTitleBoatGroupLeft();
			}
			var initialSeagullSounds = ['seagull1', 'seagull2', 'seagull3'];
			var initialRandomSoundId = initialSeagullSounds[Math.floor(Math.random() * initialSeagullSounds.length)];
			LK.getSound(initialRandomSoundId).play();
			LK.getSound('boatsounds').play(); // Play boat sound immediately after initial seagull
			// Start the timed sounds (seagulls random, subsequent boats rhythmic)
			_scheduleNextSeagullSound();
			_scheduleNextBoatSound(); // Schedules the *next* boat sound rhythmically
			// Reset particle spawn counters
			titleScreenOceanBubbleSpawnCounter = 0;
			titleScreenSeaweedSpawnCounter = 0;
			titleScreenCloudSpawnCounter = 0;
			// Animation timing
			var ZOOM_DURATION = 8000; // 8-second zoom
			var OVERLAY_FADE_DELAY = 1000; // Black overlay starts fading after 1 second (was 2)
			var OVERLAY_FADE_DURATION = 3000; // 3 seconds to fade out overlay
			var TEXT_DELAY = 4000; // Text appears as overlay finishes fading (was 5500)
			var BUTTON_DELAY = 5500; // Buttons appear sooner (was 7000)
			// Reset states
			titleElements.titleAnimationGroup.x = GAME_CONFIG.SCREEN_CENTER_X;
			titleElements.titleAnimationGroup.y = GAME_CONFIG.SCREEN_CENTER_Y; // Centers boat vertically!
			titleElements.titleAnimationGroup.alpha = 1; // No alpha animation for main content
			titleElements.titleAnimationGroup.scale.set(3.0);
			// Reset black overlay to fully opaque
			titleElements.blackOverlay.alpha = 1;
			// Reset UI alphas
			if (titleElements.titleImage) {
				titleElements.titleImage.alpha = 0;
			}
			if (titleElements.logo) {
				titleElements.logo.alpha = 0;
			}
			if (titleElements.subtitle) {
				titleElements.subtitle.alpha = 0;
			}
			titleElements.startButton.alpha = 0;
			titleElements.tutorialButton.alpha = 0;
			// Main zoom animation (no alpha change)
			tween(titleElements.titleAnimationGroup, {
				scaleX: 1.8,
				scaleY: 1.8
			}, {
				duration: ZOOM_DURATION,
				easing: tween.easeInOut
			});
			// Black overlay fade out (the reveal effect)
			LK.setTimeout(function () {
				tween(titleElements.blackOverlay, {
					alpha: 0
				}, {
					duration: OVERLAY_FADE_DURATION,
					easing: tween.easeInOut
				});
			}, OVERLAY_FADE_DELAY);
			// Fade in title image after overlay is mostly gone
			LK.setTimeout(function () {
				if (titleElements.titleImage) {
					tween(titleElements.titleImage, {
						alpha: 1
					}, {
						duration: 1200,
						easing: tween.easeOut
					});
				} else if (titleElements.logo && titleElements.subtitle) {
					// Fallback for old structure if needed
					tween(titleElements.logo, {
						alpha: 1
					}, {
						duration: 1200,
						easing: tween.easeOut
					});
					tween(titleElements.subtitle, {
						alpha: 1
					}, {
						duration: 1200,
						easing: tween.easeOut
					});
				}
			}, TEXT_DELAY);
			// Fade in buttons near the end
			LK.setTimeout(function () {
				tween(titleElements.startButton, {
					alpha: 1
				}, {
					duration: 1000,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						// Fade in tutorial button AFTER start button finishes
						tween(titleElements.tutorialButton, {
							alpha: 1
						}, {
							duration: 1000,
							easing: tween.easeOut
						});
					}
				});
			}, BUTTON_DELAY);
			break;
		case 'levelSelect':
			levelSelectScreen.visible = true;
			updateLevelSelectScreen();
			break;
		case 'fishing':
			fishingScreen.visible = true;
			playIntroAnimation(); // Play the intro sequence
			break;
		case 'results':
			resultsScreen.visible = true;
			break;
	}
	// Tutorial System functions moved to global scope.
}
/**** 
* Intro Animation
****/ 
function playIntroAnimation() {
	GameState.introPlaying = true;
	GameState.gameActive = false;
	// Start animations for water, boat, and fisherman at the beginning of the intro
	if (fishingElements) {
		if (typeof fishingElements.startWaterSurfaceAnimation === 'function') {
			fishingElements.startWaterSurfaceAnimation();
		}
		if (typeof fishingElements.startBoatAndFishermanAnimation === 'function') {
			fishingElements.startBoatAndFishermanAnimation();
		}
	}
	// Calculate rod tip position (relative to fishingScreen)
	var fc = fishingElements.fishermanContainer;
	var f = fishingElements.fisherman;
	var rodTipCalculatedX = fc.x + f.x + 85;
	var rodTipCalculatedY = fc.y + f.y - f.height;
	var initialHookDangleY = rodTipCalculatedY + 50;
	fishingElements.hook.y = initialHookDangleY;
	// Setup initial zoom and camera position for fishingScreen
	var INITIAL_ZOOM_FACTOR = 1.5;
	// Pivot around the boat's visual center
	var pivotX = fishingElements.boat.x;
	var pivotY = fishingElements.boat.y - fishingElements.boat.height * (fishingElements.boat.anchor.y - 0.5);
	fishingScreen.pivot.set(pivotX, pivotY);
	// Position screen so the pivot appears at screen center when zoomed
	var screenCenterX = 2048 / 2;
	var screenCenterY = 2732 / 2;
	fishingScreen.x = screenCenterX;
	fishingScreen.y = screenCenterY;
	fishingScreen.scale.set(INITIAL_ZOOM_FACTOR, INITIAL_ZOOM_FACTOR);
	var introDuration = 2000;
	// Tween for zoom out
	tween(fishingScreen.scale, {
		x: 1,
		y: 1
	}, {
		duration: introDuration,
		easing: tween.easeInOut
	});
	// Tween screen position to compensate for the zoom change
	tween(fishingScreen, {
		x: pivotX,
		y: pivotY
	}, {
		duration: introDuration,
		easing: tween.easeInOut
	});
	// Hook drop animation
	var targetHookY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	// Play reel sound effect with 300ms delay during hook animation
	LK.setTimeout(function () {
		LK.getSound('reel').play();
	}, 600);
	tween(fishingElements.hook, {
		y: targetHookY
	}, {
		duration: introDuration * 0.8,
		delay: introDuration * 0.2,
		easing: tween.easeOut,
		onFinish: function onFinish() {
			GameState.introPlaying = false;
			// Reset to normal view
			fishingScreen.pivot.set(0, 0);
			fishingScreen.x = 0;
			fishingScreen.y = 0;
			// Check if we're in tutorial mode
			if (GameState.tutorialMode) {
				// Start the tutorial properly after intro
				GameState.gameActive = false; // Keep game inactive for tutorial
				createTutorialElements(); // Create tutorial UI
				runTutorialStep(); // Start with step 0
			} else {
				// Normal fishing session
				startFishingSession();
			}
		}
	});
}
/**** 
* Level Select Logic
****/ 
function updateLevelSelectScreen() {
	var elements = levelSelectElements;
	// Update money display
	elements.moneyDisplay.setText('Money: $' + GameState.money);
	// Create depth tabs
	createDepthTabs();
	// Update song display
	updateSongDisplay();
	// Update shop button
	updateShopButton();
}
function createDepthTabs() {
	// Clear existing tabs
	levelSelectElements.depthTabs.forEach(function (tab) {
		if (tab.container) {
			tab.container.destroy();
		}
	});
	levelSelectElements.depthTabs = [];
	// Create tabs for unlocked depths (positioned in the middle area)
	var tabStartY = 600; // Moved down from 400
	var tabSpacing = 250; // Increased spacing between tabs
	for (var i = 0; i <= GameState.currentDepth; i++) {
		var depth = GAME_CONFIG.DEPTHS[i];
		var isSelected = i === GameState.selectedDepth;
		var tabContainer = levelSelectScreen.addChild(new Container());
		var tab = tabContainer.addChild(LK.getAsset('depthTab', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: 200 + i * tabSpacing,
			// Increased spacing
			y: tabStartY,
			tint: isSelected ? 0x1976d2 : 0x455a64,
			width: 400,
			// Made wider
			height: 160 // Made taller
		}));
		var tabText = new Text2(depth.name.split(' ')[0], {
			size: 40,
			// Increased from 30
			fill: 0xFFFFFF
		});
		tabText.anchor.set(0.5, 0.5);
		tabText.x = 200 + i * tabSpacing;
		tabText.y = tabStartY;
		tabContainer.addChild(tabText);
		levelSelectElements.depthTabs.push({
			container: tabContainer,
			tab: tab,
			depthIndex: i
		});
	}
}
function updateSongDisplay() {
	var elements = levelSelectElements;
	var depth = GAME_CONFIG.DEPTHS[GameState.selectedDepth];
	var song = depth.songs[GameState.selectedSong];
	var owned = GameState.hasSong(GameState.selectedDepth, GameState.selectedSong);
	// Update song info
	elements.songTitle.setText(song.name);
	elements.songInfo.setText('BPM: ' + song.bpm + ' | Duration: ' + formatTime(song.duration));
	// Calculate potential earnings
	var minEarnings = Math.floor(depth.fishValue * 20); // Conservative estimate
	var maxEarnings = Math.floor(depth.fishValue * 60); // With combos and rare fish
	elements.songEarnings.setText('Potential Earnings: $' + minEarnings + '-$' + maxEarnings);
	// Update play/buy button
	if (owned) {
		elements.playButtonText.setText('PLAY');
		elements.playButton.tint = 0x1976d2;
	} else {
		elements.playButtonText.setText('BUY ($' + song.cost + ')');
		elements.playButton.tint = GameState.money >= song.cost ? 0x2e7d32 : 0x666666;
	}
	// Update arrow states
	elements.leftArrow.tint = GameState.selectedSong > 0 ? 0x1976d2 : 0x666666;
	elements.rightArrow.tint = GameState.selectedSong < depth.songs.length - 1 ? 0x1976d2 : 0x666666;
}
function updateShopButton() {
	var elements = levelSelectElements;
	// Always show as disabled - coming soon
	elements.shopButtonText.setText('UPGRADE ROD');
	elements.shopButton.tint = 0x666666; // Always grayed out
}
function formatTime(ms) {
	var seconds = Math.floor(ms / 1000);
	var minutes = Math.floor(seconds / 60);
	seconds = seconds % 60;
	return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
/**** 
* Fishing Game Logic
****/ 
function startFishingSession() {
	// Reset session state
	GameState.tutorialMode = false; // Ensure tutorial mode is off
	GameState.sessionScore = 0;
	GameState.sessionFishCaught = 0;
	GameState.sessionFishSpawned = 0;
	GameState.combo = 0;
	GameState.maxCombo = 0;
	GameState.gameActive = true;
	GameState.songStartTime = 0;
	GameState.lastBeatTime = 0;
	GameState.beatCount = 0;
	GameState.musicNotesActive = true;
	ImprovedRhythmSpawner.reset();
	musicNotesArray = [];
	if (fishingElements && fishingElements.musicNotesContainer) {
		fishingElements.musicNotesContainer.removeChildren();
	}
	musicNoteSpawnCounter = 0;
	// Reset ocean bubbles
	globalOceanBubblesArray = [];
	if (globalOceanBubbleContainer) {
		globalOceanBubbleContainer.removeChildren();
	}
	globalOceanBubbleSpawnCounter = 0;
	// Reset seaweed
	globalSeaweedArray = [];
	if (globalSeaweedContainer) {
		globalSeaweedContainer.removeChildren();
	}
	globalSeaweedSpawnCounter = 0;
	// Reset clouds
	globalCloudArray = [];
	if (globalCloudContainer) {
		globalCloudContainer.removeChildren();
	}
	globalCloudSpawnCounter = 0;
	// Animations for water, boat, and fisherman are now started in playIntroAnimation
	// Clear any existing fish
	fishArray.forEach(function (fish) {
		fish.destroy();
	});
	fishArray = [];
	// Reset pattern generator for new session
	PatternGenerator.reset();
	// Clear and create lane brackets
	if (laneBrackets && laneBrackets.length > 0) {
		laneBrackets.forEach(function (bracketPair) {
			if (bracketPair.left && !bracketPair.left.destroyed) {
				bracketPair.left.destroy();
			}
			if (bracketPair.right && !bracketPair.right.destroyed) {
				bracketPair.right.destroy();
			}
		});
	}
	laneBrackets = [];
	var bracketAssetHeight = 150; // Height of the lanebracket asset
	var bracketAssetWidth = 75; // Width of the lanebracket asset
	if (fishingScreen && !fishingScreen.destroyed) {
		// Ensure fishingScreen is available
		for (var i = 0; i < GAME_CONFIG.LANES.length; i++) {
			var laneY = GAME_CONFIG.LANES[i].y;
			var leftBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				x: bracketAssetWidth / 2,
				// Position its center so left edge is at 0
				y: laneY,
				height: bracketAssetHeight
			}));
			var rightBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				scaleX: -1,
				// Flipped horizontally
				x: 2048 - bracketAssetWidth / 2,
				// Position its center so right edge is at 2048
				y: laneY,
				height: bracketAssetHeight
			}));
			laneBrackets.push({
				left: leftBracket,
				right: rightBracket
			});
		}
	}
	// Start music
	var songConfig = GameState.getCurrentSongConfig();
	var musicIdToPlay = songConfig.musicId || 'rhythmTrack'; // Default to rhythmTrack if no specific id
	GameState.currentPlayingMusicId = musicIdToPlay;
	// Determine initial volume based on known assets for correct fade-out later
	if (musicIdToPlay === 'morningtide') {
		GameState.currentPlayingMusicInitialVolume = 1.0; // Volume defined in LK.init.music for 'morningtide'
	} else {
		// Default for 'rhythmTrack' or other unspecified tracks
		GameState.currentPlayingMusicInitialVolume = 0.8; // Volume defined in LK.init.music for 'rhythmTrack'
	}
	LK.playMusic(GameState.currentPlayingMusicId); // Play the selected music track
}
function spawnFish(currentTimeForRegistration, options) {
	options = options || {}; // Ensure options is an object
	var depthConfig = GameState.getCurrentDepthConfig();
	var songConfig = GameState.getCurrentSongConfig();
	var pattern = GAME_CONFIG.PATTERNS[songConfig.pattern];
	// Proximity check: Skip spawn if too close to existing fish.
	// This check is generally for the first fish of a beat or non-forced spawns.
	// For forced multi-beat spawns, this might prevent them if they are too close.
	// Consider if this rule should be relaxed for forced multi-beat spawns if visual overlap is acceptable for quick succession.
	// For now, keeping it as is. If a spawn is skipped, the multi-beat sequence might be shorter.
	var isFirstFishOfBeat = !options.laneIndexToUse && !options.forcedSpawnSide;
	if (isFirstFishOfBeat) {
		// Apply stricter proximity for non-forced spawns
		for (var i = 0; i < fishArray.length; i++) {
			var existingFish = fishArray[i];
			if (Math.abs(existingFish.x - GAME_CONFIG.SCREEN_CENTER_X) < PatternGenerator.minDistanceBetweenFish) {
				return null; // Skip this spawn, do not register
			}
		}
	}
	var laneIndex;
	if (options.laneIndexToUse !== undefined) {
		laneIndex = options.laneIndexToUse;
		PatternGenerator.lastLane = laneIndex; // Update generator's state if lane is forced
	} else {
		laneIndex = PatternGenerator.getNextLane();
	}
	var targetLane = GAME_CONFIG.LANES[laneIndex];
	var fishType, fishValue;
	var rand = Math.random();
	if (rand < pattern.rareSpawnChance) {
		fishType = 'rare';
		fishValue = Math.floor(depthConfig.fishValue * 4);
	} else if (GameState.selectedDepth >= 2 && rand < 0.3) {
		fishType = 'deep';
		fishValue = Math.floor(depthConfig.fishValue * 2);
	} else if (GameState.selectedDepth >= 1 && rand < 0.6) {
		fishType = 'medium';
		fishValue = Math.floor(depthConfig.fishValue * 1.5);
	} else {
		fishType = 'shallow';
		fishValue = Math.floor(depthConfig.fishValue);
	}
	var fishSpeedValue = depthConfig.fishSpeed;
	var spawnSide; // -1 for left, 1 for right
	var actualFishSpeed;
	if (options.forcedSpawnSide !== undefined) {
		spawnSide = options.forcedSpawnSide;
	} else {
		spawnSide = Math.random() < 0.5 ? -1 : 1;
	}
	actualFishSpeed = Math.abs(fishSpeedValue) * spawnSide;
	var newFish = new Fish(fishType, fishValue, actualFishSpeed, laneIndex);
	newFish.spawnSide = spawnSide; // Store the side it spawned from
	newFish.x = actualFishSpeed > 0 ? -150 : 2048 + 150; // Start off-screen
	newFish.y = targetLane.y;
	newFish.baseY = targetLane.y; // Set baseY for swimming animation
	fishArray.push(newFish);
	fishingScreen.addChild(newFish);
	GameState.sessionFishSpawned++;
	PatternGenerator.registerFishSpawn(currentTimeForRegistration);
	return newFish;
}
function checkCatch(fishLane) {
	var hookX = fishingElements.hook.x;
	if (GameState.tutorialMode) {
		var tutorialFish = GameState.tutorialFish;
		if (!tutorialFish || tutorialFish.lane !== fishLane || tutorialFish.caught || tutorialFish.missed) {
			// Check if it's a critical catch step (now steps 3 or 4)
			if (GameState.tutorialStep === 3 || GameState.tutorialStep === 4) {
				setTutorialText("Oops! Make sure to tap when the fish is in the correct lane and over the hook. Tap 'CONTINUE' to try again.");
				GameState.tutorialPaused = true;
			}
			LK.getSound('miss').play();
			return;
		}
		var distance = Math.abs(tutorialFish.x - hookX);
		var caughtType = null;
		if (distance < GAME_CONFIG.PERFECT_WINDOW) {
			caughtType = 'perfect';
		} else if (distance < GAME_CONFIG.GOOD_WINDOW) {
			caughtType = 'good';
		} else if (distance < GAME_CONFIG.MISS_WINDOW) {
			// For tutorial, make 'miss window' taps still count as 'good' to be more forgiving
			caughtType = 'good';
		} else {
			caughtType = 'miss';
		}
		showFeedback(caughtType, fishLane); // Show visual feedback based on derived type
		if (caughtType === 'perfect' || caughtType === 'good') {
			tutorialFish.catchFish();
			var fishIndex = fishArray.indexOf(tutorialFish);
			if (fishIndex > -1) {
				fishArray.splice(fishIndex, 1);
			}
			// No points/money in tutorial normally, but can add if desired.
			LK.getSound('catch').play();
			animateHookCatch();
			GameState.tutorialPaused = true; // Pause to show message
			if (GameState.tutorialStep === 3) {
				// Was step 2
				setTutorialText("Great catch! That's how you do it. Tap 'CONTINUE'.");
				// GameState.tutorialStep = 4; // Advance to next conceptual phase -- Handled by game.down
			} else if (GameState.tutorialStep === 4) {
				// Was step 3
				setTutorialText("Nice one! You're getting the hang of timing. Tap 'CONTINUE'.");
				// GameState.tutorialStep = 5; // Advance to Combo explanation -- Handled by game.down
			}
		} else {
			// Miss
			// Feedback 'miss' was already shown
			LK.getSound('miss').play();
			tutorialFish.missed = true;
			GameState.tutorialPaused = true;
			setTutorialText("Almost! Try to tap when the fish is closer. Tap 'CONTINUE' to try this part again.");
			// The runTutorialStep logic on "CONTINUE" will handle respawning for this step.
		}
		return; // Tutorial catch logic finished
	}
	var closestFishInLane = null;
	var closestDistance = Infinity;
	// This is a tap action, find the closest fish in the tapped lane.
	for (var i = 0; i < fishArray.length; i++) {
		var fish = fishArray[i];
		// Ensure fish is not caught, not already missed, and in the correct lane
		if (!fish.caught && !fish.missed && fish.lane === fishLane) {
			var distance = Math.abs(fish.x - hookX);
			if (distance < closestDistance) {
				closestDistance = distance;
				closestFishInLane = fish;
			}
		}
	}
	if (!closestFishInLane) {
		// No fish found for tap
		// Play miss sound
		LK.getSound('miss').play();
		// Tint incorrect lane indicators red briefly using tween
		if (laneBrackets && laneBrackets[fishLane]) {
			var leftBracket = laneBrackets[fishLane].left;
			var rightBracket = laneBrackets[fishLane].right;
			var tintToRedDuration = 50; // Duration to tween to red (ms)
			var holdRedDuration = 100; // How long it stays fully red (ms)
			var tintToWhiteDuration = 150; // Duration to tween back to white (ms)
			if (leftBracket && !leftBracket.destroyed) {
				// Tween to red
				tween(leftBracket, {
					tint: 0xFF0000
				}, {
					duration: tintToRedDuration,
					easing: tween.linear,
					onFinish: function onFinish() {
						// After tinting to red, wait, then tween back to white
						LK.setTimeout(function () {
							if (leftBracket && !leftBracket.destroyed) {
								tween(leftBracket, {
									tint: 0xFFFFFF
								}, {
									duration: tintToWhiteDuration,
									easing: tween.linear
								});
							}
						}, holdRedDuration);
					}
				});
			}
			if (rightBracket && !rightBracket.destroyed) {
				// Tween to red
				tween(rightBracket, {
					tint: 0xFF0000
				}, {
					duration: tintToRedDuration,
					easing: tween.linear,
					onFinish: function onFinish() {
						// After tinting to red, wait, then tween back to white
						LK.setTimeout(function () {
							if (rightBracket && !rightBracket.destroyed) {
								tween(rightBracket, {
									tint: 0xFFFFFF
								}, {
									duration: tintToWhiteDuration,
									easing: tween.linear
								});
							}
						}, holdRedDuration);
					}
				});
			}
		}
		GameState.combo = 0;
		return;
	}
	// --- Normal Fish Catch Logic ---
	var points = 0;
	var multiplier = Math.max(1, Math.floor(GameState.combo / 10) + 1);
	if (closestDistance < GAME_CONFIG.PERFECT_WINDOW) {
		points = closestFishInLane.value * 2 * multiplier;
		showFeedback('perfect', fishLane);
		GameState.combo++;
	} else if (closestDistance < GAME_CONFIG.GOOD_WINDOW) {
		points = closestFishInLane.value * multiplier;
		showFeedback('good', fishLane);
		GameState.combo++;
	} else if (closestDistance < GAME_CONFIG.MISS_WINDOW) {
		points = Math.max(1, Math.floor(closestFishInLane.value * 0.5 * multiplier));
		showFeedback('good', fishLane);
		GameState.combo++;
	} else {
		showFeedback('miss', fishLane);
		LK.getSound('miss').play();
		GameState.combo = 0;
		// Mark the specific fish that was tapped but missed
		if (closestFishInLane) {
			closestFishInLane.missed = true;
		}
		return;
	}
	// Successfully caught fish
	closestFishInLane.catchFish();
	var fishIndex = fishArray.indexOf(closestFishInLane);
	if (fishIndex > -1) {
		fishArray.splice(fishIndex, 1);
	}
	GameState.sessionScore += points;
	GameState.money += points;
	GameState.sessionFishCaught++;
	GameState.totalFishCaught++;
	GameState.maxCombo = Math.max(GameState.maxCombo, GameState.combo);
	// Play a random catch sound effect
	var catchSounds = ['catch', 'catch2', 'catch3', 'catch4'];
	var randomCatchSound = catchSounds[Math.floor(Math.random() * catchSounds.length)];
	LK.getSound(randomCatchSound).play();
	animateHookCatch(); // Call parameterless animateHookCatch for the single hook
	// Score pop-up animation
	if (points > 0) {
		var scorePopupText = new Text2('+' + points, {
			size: 140,
			// Slightly larger for impact
			fill: 0xFFD700,
			// Gold color for score
			align: 'center',
			stroke: 0x000000,
			// Black stroke
			strokeThickness: 6 // Thickness of the stroke
		});
		scorePopupText.anchor.set(0.5, 0.5);
		scorePopupText.x = GAME_CONFIG.SCREEN_CENTER_X; // Centered with the boat
		scorePopupText.y = GAME_CONFIG.BOAT_Y - 70; // Start slightly above the boat's deck line
		// Add to fishingScreen so it's part of the game view
		if (fishingScreen && !fishingScreen.destroyed) {
			fishingScreen.addChild(scorePopupText);
		}
		tween(scorePopupText, {
			y: scorePopupText.y - 200,
			// Float up by 200 pixels
			alpha: 0
		}, {
			duration: 1800,
			// Slightly longer duration for a nice float
			easing: tween.easeOut,
			onFinish: function onFinish() {
				if (scorePopupText && !scorePopupText.destroyed) {
					scorePopupText.destroy();
				}
			}
		});
	}
}
// Note: The old animateHookCatch function that was defined right after checkCatch
// is now a global helper: animateHookCatch(laneIndex), defined earlier.
// We remove the old local one if it existed here by not re-inserting it.
function updateFishingUI() {
	var elements = fishingElements;
	elements.scoreText.setText('Score: ' + GameState.sessionScore);
	elements.fishText.setText('Fish: ' + GameState.sessionFishCaught + '/' + GameState.sessionFishSpawned);
	elements.comboText.setText('Combo: ' + GameState.combo);
	// Update progress
	if (GameState.songStartTime > 0) {
		var currentTime = LK.ticks * (1000 / 60);
		var elapsed = currentTime - GameState.songStartTime;
		var songConfig = GameState.getCurrentSongConfig();
		elements.progressText.setText(formatTime(elapsed) + ' / ' + formatTime(songConfig.duration));
	}
}
function endFishingSession() {
	GameState.gameActive = false;
	GameState.tutorialMode = false; // Ensure tutorial mode is off
	// Stop the boat's wave animation to prevent it from running after the session
	if (fishingElements && fishingElements.boat) {
		tween.stop(fishingElements.boat);
	}
	// Stop fisherman container animation
	if (fishingElements && fishingElements.fishermanContainer) {
		tween.stop(fishingElements.fishermanContainer);
	}
	// Stop fisherman rotation animation
	if (fishingElements && fishingElements.fisherman) {
		tween.stop(fishingElements.fisherman);
	}
	// Stop water surface wave animations
	if (fishingElements && fishingElements.waterSurfaceSegments) {
		fishingElements.waterSurfaceSegments.forEach(function (segment) {
			if (segment && !segment.destroyed) {
				tween.stop(segment);
			}
		});
	}
	// Stop music immediately
	LK.stopMusic();
	ImprovedRhythmSpawner.reset();
	// Clear lane brackets
	if (laneBrackets && laneBrackets.length > 0) {
		laneBrackets.forEach(function (bracketPair) {
			if (bracketPair.left && !bracketPair.left.destroyed) {
				bracketPair.left.destroy();
			}
			if (bracketPair.right && !bracketPair.right.destroyed) {
				bracketPair.right.destroy();
			}
		});
		laneBrackets = [];
	}
	// Clear fish
	fishArray.forEach(function (fish) {
		fish.destroy();
	});
	fishArray = [];
	GameState.musicNotesActive = false;
	if (fishingElements && fishingElements.musicNotesContainer) {
		fishingElements.musicNotesContainer.removeChildren();
	}
	// The MusicNoteParticle instances themselves will be garbage collected.
	// Clearing the array is important.
	musicNotesArray = [];
	// Clear ocean bubbles
	if (globalOceanBubbleContainer) {
		globalOceanBubbleContainer.removeChildren();
	}
	globalOceanBubblesArray = [];
	// Clear seaweed
	if (globalSeaweedContainer) {
		globalSeaweedContainer.removeChildren();
	}
	globalSeaweedArray = [];
	// Clear clouds
	if (globalCloudContainer) {
		globalCloudContainer.removeChildren();
	}
	globalCloudArray = [];
	// Create results screen
	createResultsScreen();
	showScreen('results');
}
function createResultsScreen() {
	// Clear previous results
	resultsScreen.removeChildren();
	var resultsBg = resultsScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		alpha: 0.9,
		height: 2732
	}));
	var title = new Text2('Fishing Complete!', {
		size: 100,
		fill: 0xFFFFFF
	});
	title.anchor.set(0.5, 0.5);
	title.x = GAME_CONFIG.SCREEN_CENTER_X;
	title.y = 400;
	resultsScreen.addChild(title);
	var scoreResult = new Text2('Score: ' + GameState.sessionScore, {
		size: 70,
		fill: 0xFFD700
	});
	scoreResult.anchor.set(0.5, 0.5);
	scoreResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	scoreResult.y = 550;
	resultsScreen.addChild(scoreResult);
	var fishResult = new Text2('Fish Caught: ' + GameState.sessionFishCaught + '/' + GameState.sessionFishSpawned, {
		size: 50,
		fill: 0xFFFFFF
	});
	fishResult.anchor.set(0.5, 0.5);
	fishResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	fishResult.y = 650;
	resultsScreen.addChild(fishResult);
	var comboResult = new Text2('Max Combo: ' + GameState.maxCombo, {
		size: 50,
		fill: 0xFF9800
	});
	comboResult.anchor.set(0.5, 0.5);
	comboResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	comboResult.y = 750;
	resultsScreen.addChild(comboResult);
	var moneyEarned = new Text2('Money Earned: $' + GameState.sessionScore, {
		size: 50,
		fill: 0x4CAF50
	});
	moneyEarned.anchor.set(0.5, 0.5);
	moneyEarned.x = GAME_CONFIG.SCREEN_CENTER_X;
	moneyEarned.y = 850;
	resultsScreen.addChild(moneyEarned);
	// Accuracy
	var accuracy = GameState.sessionFishSpawned > 0 ? Math.round(GameState.sessionFishCaught / GameState.sessionFishSpawned * 100) : 0;
	var accuracyResult = new Text2('Accuracy: ' + accuracy + '%', {
		size: 50,
		fill: 0x2196F3
	});
	accuracyResult.anchor.set(0.5, 0.5);
	accuracyResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	accuracyResult.y = 950;
	resultsScreen.addChild(accuracyResult);
	// Continue button
	var continueButton = resultsScreen.addChild(LK.getAsset('bigButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1200
	}));
	var continueText = new Text2('CONTINUE', {
		size: 50,
		fill: 0xFFFFFF
	});
	continueText.anchor.set(0.5, 0.5);
	continueText.x = GAME_CONFIG.SCREEN_CENTER_X;
	continueText.y = 1200;
	resultsScreen.addChild(continueText);
	// Fade in
	resultsScreen.alpha = 0;
	tween(resultsScreen, {
		alpha: 1
	}, {
		duration: 500,
		easing: tween.easeOut
	});
}
/**** 
* Input Handling
****/ 
game.down = function (x, y, obj) {
	LK.getSound('buttonClick').play();
	var currentScreen = GameState.currentScreen;
	// If tutorial mode is active, treat as 'tutorial' screen for input
	if (GameState.tutorialMode && (currentScreen === 'fishing' || currentScreen === 'tutorial')) {
		// New case for tutorial input
		if (tutorialOverlayContainer.visible && tutorialContinueButton && tutorialContinueButton.visible && x >= tutorialContinueButton.x - tutorialContinueButton.width / 2 && x <= tutorialContinueButton.x + tutorialContinueButton.width / 2 && y >= tutorialContinueButton.y - tutorialContinueButton.height / 2 && y <= tutorialContinueButton.y + tutorialContinueButton.height / 2) {
			LK.getSound('buttonClick').play();
			// If tutorial is paused and it's a catch instruction step (3 or 4),
			// clicking "CONTINUE" implies a retry of that step.
			if (GameState.tutorialPaused && (GameState.tutorialStep === 3 || GameState.tutorialStep === 4)) {
				// Logic for catch steps 3 or 4 when "CONTINUE" is pressed.
				// This covers scenarios where fish was caught, missed, passed hook, or swam off screen.
				var advanceAfterCatch = false;
				// First, check the state of the existing tutorial fish, if any.
				if (GameState.tutorialFish && GameState.tutorialFish.caught) {
					advanceAfterCatch = true;
				}
				// Regardless of success or failure, if there was a tutorial fish, clean it up.
				// This handles the fish that was just caught OR missed/swam_off.
				if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
					var idx = fishArray.indexOf(GameState.tutorialFish);
					if (idx > -1) {
						fishArray.splice(idx, 1);
					}
					GameState.tutorialFish.destroy();
				}
				// Ensure GameState.tutorialFish is null before runTutorialStep,
				// as runTutorialStep might spawn a new one for the current or next step.
				GameState.tutorialFish = null; //{pq} // Ensure it's null before re-running step to spawn a new one.
				if (advanceAfterCatch) {
					// If the fish was caught, advance to the next tutorial step.
					GameState.tutorialStep++;
					runTutorialStep();
				} else {
					// If the fish was missed, swam off, or there was no fish to catch (e.g. error state)
					// then retry the current step. runTutorialStep will handle re-spawning.
					runTutorialStep(); // Re-runs current step to spawn new fish
				}
			} else {
				// For any other tutorial step, or if not paused in a catch step, "CONTINUE" advances.
				GameState.tutorialStep++;
				runTutorialStep();
			}
		} else if ((GameState.tutorialStep === 3 || GameState.tutorialStep === 4) && !GameState.tutorialPaused) {
			// Catch attempt steps are now 3 and 4
			// Catch attempt by tapping screen, not the continue button
			handleFishingInput(x, y, true); // True for isDown
		}
		return;
	}
	switch (currentScreen) {
		case 'title':
			// Check if click is within start button bounds
			var startButton = titleElements.startButton;
			if (x >= startButton.x - startButton.width / 2 && x <= startButton.x + startButton.width / 2 && y >= startButton.y - startButton.height / 2 && y <= startButton.y + startButton.height / 2) {
				showScreen('levelSelect');
			}
			// Check if click is within tutorial button bounds
			var tutorialButtonGfx = titleElements.tutorialButtonGfx || titleElements.tutorialButton; // Compatibility
			if (x >= tutorialButtonGfx.x - tutorialButtonGfx.width / 2 && x <= tutorialButtonGfx.x + tutorialButtonGfx.width / 2 && y >= tutorialButtonGfx.y - tutorialButtonGfx.height / 2 && y <= tutorialButtonGfx.y + tutorialButtonGfx.height / 2) {
				// Defensive: check if startTutorial is defined before calling
				if (typeof startTutorial === "function") {
					startTutorial();
				}
			}
			break;
		case 'levelSelect':
			handleLevelSelectInput(x, y);
			break;
		case 'fishing':
			handleFishingInput(x, y, true); // true for isDown
			break;
		case 'results':
			showScreen('levelSelect');
			break;
	}
};
function handleLevelSelectInput(x, y) {
	var elements = levelSelectElements;
	// Check depth tabs
	elements.depthTabs.forEach(function (tab) {
		var tabAsset = tab.tab;
		if (x >= tabAsset.x - tabAsset.width / 2 && x <= tabAsset.x + tabAsset.width / 2 && y >= tabAsset.y - tabAsset.height / 2 && y <= tabAsset.y + tabAsset.height / 2) {
			GameState.selectedDepth = tab.depthIndex;
			GameState.selectedSong = 0; // Reset to first song
			updateLevelSelectScreen();
		}
	});
	// Check song navigation
	var leftArrow = elements.leftArrow;
	if (x >= leftArrow.x - leftArrow.width / 2 && x <= leftArrow.x + leftArrow.width / 2 && y >= leftArrow.y - leftArrow.height / 2 && y <= leftArrow.y + leftArrow.height / 2 && GameState.selectedSong > 0) {
		GameState.selectedSong--;
		updateSongDisplay();
	}
	var rightArrow = elements.rightArrow;
	if (x >= rightArrow.x - rightArrow.width / 2 && x <= rightArrow.x + rightArrow.width / 2 && y >= rightArrow.y - rightArrow.height / 2 && y <= rightArrow.y + rightArrow.height / 2) {
		var depth = GAME_CONFIG.DEPTHS[GameState.selectedDepth];
		if (GameState.selectedSong < depth.songs.length - 1) {
			GameState.selectedSong++;
			updateSongDisplay();
		}
	}
	// Check play/buy button
	var playButton = elements.playButton;
	if (x >= playButton.x - playButton.width / 2 && x <= playButton.x + playButton.width / 2 && y >= playButton.y - playButton.height / 2 && y <= playButton.y + playButton.height / 2) {
		var owned = GameState.hasSong(GameState.selectedDepth, GameState.selectedSong);
		if (owned) {
			showScreen('fishing');
		} else {
			// Try to buy song
			if (GameState.buySong(GameState.selectedDepth, GameState.selectedSong)) {
				updateLevelSelectScreen();
			}
		}
	}
	// Check shop button - disabled for "coming soon"
	var shopButton = elements.shopButton;
	if (x >= shopButton.x - shopButton.width / 2 && x <= shopButton.x + shopButton.width / 2 && y >= shopButton.y - shopButton.height / 2 && y <= shopButton.y + shopButton.height / 2) {
		// Do nothing - button is disabled
	}
	// Check back button
	var backButton = elements.backButton;
	if (x >= backButton.x - backButton.width / 2 && x <= backButton.x + backButton.width / 2 && y >= backButton.y - backButton.height / 2 && y <= backButton.y + backButton.height / 2) {
		showScreen('title');
	}
}
/**** 
* Main Game Loop
****/ 
game.update = function () {
	// Always update fishing line wave visuals if on fishing screen and elements are ready.
	// This needs to run even during the intro when gameActive might be false.
	if (GameState.currentScreen === 'fishing' && fishingElements && fishingElements.updateFishingLineWave) {
		fishingElements.updateFishingLineWave();
	}
	// Update title screen ambient particles if title screen is active
	if (GameState.currentScreen === 'title') {
		// Add this to the game.update function, in the title screen section:
		if (GameState.currentScreen === 'title' && titleElements && titleElements.updateTitleFishingLineWave) {
			titleElements.updateTitleFishingLineWave();
		}
		// Title Screen Ocean Bubbles
		if (titleScreenOceanBubbleContainer) {
			titleScreenOceanBubbleSpawnCounter++;
			if (titleScreenOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) {
				// Use same interval as fishing
				titleScreenOceanBubbleSpawnCounter = 0;
				var newOceanBubble = new OceanBubbleParticle(); // Assuming OceanBubbleParticle is general enough
				titleScreenOceanBubbleContainer.addChild(newOceanBubble);
				titleScreenOceanBubblesArray.push(newOceanBubble);
			}
			for (var obIdx = titleScreenOceanBubblesArray.length - 1; obIdx >= 0; obIdx--) {
				var oceanBubble = titleScreenOceanBubblesArray[obIdx];
				if (oceanBubble) {
					oceanBubble.update(); // No fish interaction on title screen
					if (oceanBubble.isDone) {
						oceanBubble.destroy();
						titleScreenOceanBubblesArray.splice(obIdx, 1);
					}
				} else {
					titleScreenOceanBubblesArray.splice(obIdx, 1);
				}
			}
		}
		// Title Screen Seaweed
		if (titleScreenSeaweedContainer) {
			titleScreenSeaweedSpawnCounter++;
			if (titleScreenSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && titleScreenSeaweedArray.length < MAX_SEAWEED_COUNT) {
				titleScreenSeaweedSpawnCounter = 0;
				var newSeaweed = new SeaweedParticle();
				titleScreenSeaweedContainer.addChild(newSeaweed);
				titleScreenSeaweedArray.push(newSeaweed);
			}
			for (var swIdx = titleScreenSeaweedArray.length - 1; swIdx >= 0; swIdx--) {
				var seaweed = titleScreenSeaweedArray[swIdx];
				if (seaweed) {
					seaweed.update(); // No fish interaction on title screen
					if (seaweed.isDone) {
						seaweed.destroy();
						titleScreenSeaweedArray.splice(swIdx, 1);
					}
				} else {
					titleScreenSeaweedArray.splice(swIdx, 1);
				}
			}
		}
		// Title Screen Clouds
		if (titleScreenCloudContainer) {
			titleScreenCloudSpawnCounter++;
			if (titleScreenCloudSpawnCounter >= CLOUD_SPAWN_INTERVAL_TICKS && titleScreenCloudArray.length < MAX_CLOUD_COUNT) {
				titleScreenCloudSpawnCounter = 0;
				var newCloud = new CloudParticle();
				titleScreenCloudContainer.addChild(newCloud);
				titleScreenCloudArray.push(newCloud);
			}
			for (var cldIdx = titleScreenCloudArray.length - 1; cldIdx >= 0; cldIdx--) {
				var cloud = titleScreenCloudArray[cldIdx];
				if (cloud) {
					cloud.update();
					if (cloud.isDone) {
						cloud.destroy();
						titleScreenCloudArray.splice(cldIdx, 1);
					}
				} else {
					titleScreenCloudArray.splice(cldIdx, 1);
				}
			}
		}
	}
	// Spawn and update ambient ocean bubbles during intro and gameplay (fishing screen)
	if (GameState.currentScreen === 'fishing' && globalOceanBubbleContainer) {
		// Spawn bubbles during intro and gameplay
		globalOceanBubbleSpawnCounter++;
		if (globalOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) {
			globalOceanBubbleSpawnCounter = 0;
			var numToSpawn = 1; // Always spawn only 1 bubble per interval (was 1 or 2)
			for (var i = 0; i < numToSpawn; i++) {
				var newOceanBubble = new OceanBubbleParticle();
				globalOceanBubbleContainer.addChild(newOceanBubble);
				globalOceanBubblesArray.push(newOceanBubble);
			}
		}
		// Update existing ocean bubbles
		for (var obIdx = globalOceanBubblesArray.length - 1; obIdx >= 0; obIdx--) {
			var oceanBubble = globalOceanBubblesArray[obIdx];
			if (oceanBubble) {
				// Apply fish physics to bubble
				for (var fishIdx = 0; fishIdx < fishArray.length; fishIdx++) {
					var fish = fishArray[fishIdx];
					if (fish && !fish.caught) {
						var dx = oceanBubble.x - fish.x;
						var dy = oceanBubble.y - fish.y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						var influenceRadius = 150; // Radius of fish influence on bubbles
						var minDistance = 30; // Minimum distance to avoid division issues
						if (distance < influenceRadius && distance > minDistance) {
							// Calculate influence strength (stronger when closer)
							var influence = 1 - distance / influenceRadius;
							influence = influence * influence; // Square for more dramatic close-range effect
							// Calculate normalized direction away from fish
							var dirX = dx / distance;
							var dirY = dy / distance;
							// Apply force based on fish speed and direction
							var fishSpeedFactor = Math.abs(fish.speed) * 0.15; // Scale down fish speed influence
							var pushForce = fishSpeedFactor * influence;
							// Add horizontal push (stronger in direction of fish movement)
							oceanBubble.x += dirX * pushForce * 2; // Stronger horizontal push
							// Add vertical component (bubbles get pushed up/down)
							oceanBubble.vy += dirY * pushForce * 0.5; // Gentler vertical influence
							// Add some swirl/turbulence to drift
							oceanBubble.driftAmplitude = Math.min(80, oceanBubble.driftAmplitude + pushForce * 10);
							oceanBubble.driftFrequency *= 1 + influence * 0.1; // Slightly increase oscillation when disturbed
						}
					}
				}
				oceanBubble.update();
				if (oceanBubble.isDone) {
					oceanBubble.destroy();
					globalOceanBubblesArray.splice(obIdx, 1);
				}
			} else {
				globalOceanBubblesArray.splice(obIdx, 1); // Safeguard for null entries
			}
		}
	}
	// Spawn and update seaweed particles during intro and gameplay
	if (GameState.currentScreen === 'fishing' && globalSeaweedContainer) {
		// Spawn seaweed during intro and gameplay
		globalSeaweedSpawnCounter++;
		if (globalSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && globalSeaweedArray.length < MAX_SEAWEED_COUNT) {
			globalSeaweedSpawnCounter = 0;
			var newSeaweed = new SeaweedParticle();
			globalSeaweedContainer.addChild(newSeaweed);
			globalSeaweedArray.push(newSeaweed);
		}
		// Update existing seaweed
		for (var swIdx = globalSeaweedArray.length - 1; swIdx >= 0; swIdx--) {
			var seaweed = globalSeaweedArray[swIdx];
			if (seaweed) {
				// Apply fish physics to seaweed
				for (var fishIdx = 0; fishIdx < fishArray.length; fishIdx++) {
					var fish = fishArray[fishIdx];
					if (fish && !fish.caught) {
						var dx = seaweed.x - fish.x;
						var dy = seaweed.y - fish.y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						var influenceRadius = 180; // Slightly larger influence for seaweed
						var minDistance = 40;
						if (distance < influenceRadius && distance > minDistance) {
							// Calculate influence strength
							var influence = 1 - distance / influenceRadius;
							influence = influence * influence;
							// Calculate normalized direction away from fish
							var dirX = dx / distance;
							var dirY = dy / distance;
							// Apply force based on fish speed
							var fishSpeedFactor = Math.abs(fish.speed) * 0.2; // Stronger influence on seaweed
							var pushForce = fishSpeedFactor * influence;
							// Add push forces
							seaweed.vx += dirX * pushForce * 1.5; // Seaweed is affected more horizontally
							seaweed.vy += dirY * pushForce * 0.8; // And moderately vertically
							// Increase sway when disturbed
							seaweed.swayAmplitude = Math.min(60, seaweed.swayAmplitude + pushForce * 15);
						}
					}
				}
				seaweed.update();
				if (seaweed.isDone) {
					seaweed.destroy();
					globalSeaweedArray.splice(swIdx, 1);
				}
			} else {
				globalSeaweedArray.splice(swIdx, 1);
			}
		}
	}
	// Spawn and update cloud particles during intro and gameplay
	if (GameState.currentScreen === 'fishing' && globalCloudContainer) {
		// Spawn clouds during intro and gameplay
		globalCloudSpawnCounter++;
		if (globalCloudSpawnCounter >= CLOUD_SPAWN_INTERVAL_TICKS && globalCloudArray.length < MAX_CLOUD_COUNT) {
			globalCloudSpawnCounter = 0;
			var newCloud = new CloudParticle();
			globalCloudContainer.addChild(newCloud);
			globalCloudArray.push(newCloud);
		}
		// Update existing clouds
		for (var cldIdx = globalCloudArray.length - 1; cldIdx >= 0; cldIdx--) {
			var cloud = globalCloudArray[cldIdx];
			if (cloud) {
				cloud.update();
				if (cloud.isDone) {
					cloud.destroy();
					globalCloudArray.splice(cldIdx, 1);
				}
			} else {
				globalCloudArray.splice(cldIdx, 1);
			}
		}
	}
	// Standard game active check; if intro is playing, gameActive will be false.
	// Tutorial mode has its own logic path
	if (GameState.currentScreen === 'fishing' && GameState.tutorialMode) {
		// Update essential non-paused elements like fishing line wave
		if (fishingElements && fishingElements.updateFishingLineWave) {
			fishingElements.updateFishingLineWave();
		}
		// Update ambient particles (clouds, background bubbles, seaweed can run if desired)
		// ... (Can copy particle update logic here if they should be active in tutorial)
		if (!GameState.tutorialPaused) {
			// Update tutorial fish if one exists and is active
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed && !GameState.tutorialFish.caught) {
				GameState.tutorialFish.update();
				checkTutorialFishState(); // Check its state (missed, off-screen)
			}
			// Hook follows tutorial fish
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed && !GameState.tutorialFish.caught) {
				if (GameState.hookTargetLaneIndex !== GameState.tutorialFish.lane) {
					GameState.hookTargetLaneIndex = GameState.tutorialFish.lane;
				}
				var targetLaneY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
				if (fishingElements.hook && Math.abs(fishingElements.hook.y - targetLaneY) > 1) {
					// Smoother threshold
					// fishingElements.hook.y = targetLaneY; // Instant for tutorial responsiveness
					tween(fishingElements.hook, {
						y: targetLaneY
					}, {
						duration: 100,
						easing: tween.linear
					});
					fishingElements.hook.originalY = targetLaneY;
				}
			} else if (fishingElements.hook) {
				// If no tutorial fish, hook stays in middle or last targeted lane
				var middleLaneY = GAME_CONFIG.LANES[1].y;
				if (fishingElements.hook.y !== middleLaneY) {
					// tween(fishingElements.hook, { y: middleLaneY }, { duration: 150, easing: tween.easeOut });
					// fishingElements.hook.originalY = middleLaneY;
				}
			}
		}
		updateLaneBracketsVisuals();
		return; // End tutorial update logic
	}
	if (GameState.currentScreen !== 'fishing' || !GameState.gameActive) {
		return;
	}
	// Note: The fishing line wave update was previously here, it's now moved up.
	var currentTime = LK.ticks * (1000 / 60);
	// Initialize game timer
	if (GameState.songStartTime === 0) {
		GameState.songStartTime = currentTime;
	}
	// Check song end
	var songConfig = GameState.getCurrentSongConfig();
	if (currentTime - GameState.songStartTime >= songConfig.duration) {
		endFishingSession();
		return;
	}
	// Use RhythmSpawner to handle fish spawning
	ImprovedRhythmSpawner.update(currentTime);
	// Dynamic Hook Movement Logic
	var approachingFish = null;
	var minDistanceToCenter = Infinity;
	for (var i = 0; i < fishArray.length; i++) {
		var f = fishArray[i];
		if (!f.caught) {
			var distanceToHookX = Math.abs(f.x - fishingElements.hook.x);
			var isApproachingOrAtHook = f.speed > 0 && f.x < fishingElements.hook.x || f.speed < 0 && f.x > fishingElements.hook.x || distanceToHookX < GAME_CONFIG.MISS_WINDOW * 2;
			if (isApproachingOrAtHook && distanceToHookX < minDistanceToCenter) {
				minDistanceToCenter = distanceToHookX;
				approachingFish = f;
			}
		}
	}
	var targetLaneY;
	if (approachingFish) {
		if (GameState.hookTargetLaneIndex !== approachingFish.lane) {
			GameState.hookTargetLaneIndex = approachingFish.lane;
		}
		targetLaneY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	} else {
		targetLaneY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	}
	// Update hook Y position (X is handled by the wave animation)
	if (Math.abs(fishingElements.hook.y - targetLaneY) > 5) {
		// Only tween if significantly different
		tween(fishingElements.hook, {
			y: targetLaneY
		}, {
			duration: 150,
			easing: tween.easeOut
		});
		fishingElements.hook.originalY = targetLaneY;
	} //{bO} // Re-using last relevant ID from replaced block for context if appropriate
	updateLaneBracketsVisuals();
	// Update fish
	for (var i = fishArray.length - 1; i >= 0; i--) {
		var fish = fishArray[i];
		var previousFrameX = fish.lastX; // X position from the end of the previous game tick
		fish.update(); // Fish updates its own movement (fish.x) and appearance
		var currentFrameX = fish.x; // X position after this tick's update
		// Check for miss only if fish is active (not caught, not already missed)
		if (!fish.caught && !fish.missed) {
			var hookCenterX = fishingElements.hook.x;
			// GAME_CONFIG.MISS_WINDOW is the distance from hook center that still counts as a "miss" tap.
			// If fish center passes this boundary, it's considered a full pass.
			var missCheckBoundary = GAME_CONFIG.MISS_WINDOW;
			if (fish.speed > 0) {
				// Moving Left to Right
				// Fish's center (previousFrameX) was to the left of/at the hook's right miss boundary,
				// and its center (currentFrameX) is now to the right of it.
				if (previousFrameX <= hookCenterX + missCheckBoundary && currentFrameX > hookCenterX + missCheckBoundary) {
					showFeedback('miss', fish.lane);
					LK.getSound('miss').play();
					GameState.combo = 0;
					fish.missed = true; // Mark as missed
				}
			} else if (fish.speed < 0) {
				// Moving Right to Left
				// Fish's center (previousFrameX) was to the right of/at the hook's left miss boundary,
				// and its center (currentFrameX) is now to the left of it.
				if (previousFrameX >= hookCenterX - missCheckBoundary && currentFrameX < hookCenterX - missCheckBoundary) {
					showFeedback('miss', fish.lane);
					LK.getSound('miss').play();
					GameState.combo = 0;
					fish.missed = true; // Mark as missed
				}
			}
		}
		// Update lastX for the next frame, using the fish's position *after* its update this frame
		fish.lastX = currentFrameX;
		// Remove off-screen fish (if not caught).
		// If a fish is `missed`, it's also `!caught`, so it will be removed by this logic.
		if (!fish.caught && (fish.x < -250 || fish.x > 2048 + 250)) {
			// Increased buffer slightly
			fish.destroy();
			fishArray.splice(i, 1);
		}
	}
	// Update UI
	updateFishingUI();
	// Spawn and update music notes if active
	if (GameState.musicNotesActive && fishingElements && fishingElements.hook && !fishingElements.hook.destroyed && musicNotesContainer) {
		musicNoteSpawnCounter++;
		if (musicNoteSpawnCounter >= MUSIC_NOTE_SPAWN_INTERVAL_TICKS) {
			musicNoteSpawnCounter = 0;
			// Spawn notes from the fishing hook's position
			var spawnX = fishingElements.hook.x;
			var spawnY = fishingElements.hook.y - 30; // Spawn slightly above the hook's center for better visual origin
			var newNote = new MusicNoteParticle(spawnX, spawnY);
			musicNotesContainer.addChild(newNote);
			musicNotesArray.push(newNote);
			// Add scale pulse to the hook, synced with BPM
			if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) {
				var currentSongConfig = GameState.getCurrentSongConfig();
				var bpm = currentSongConfig && currentSongConfig.bpm ? currentSongConfig.bpm : 90; // Default to 90 BPM
				var beatDurationMs = 60000 / bpm;
				var pulsePhaseDuration = Math.max(50, beatDurationMs / 2); // Each phase (up/down) is half a beat, min 50ms
				var pulseScaleFactor = 1.2;
				// Ensure we have valid original scales, defaulting to 1 if undefined
				var originalScaleX = fishingElements.hook.scale.x !== undefined ? fishingElements.hook.scale.x : 1;
				var originalScaleY = fishingElements.hook.scale.y !== undefined ? fishingElements.hook.scale.y : 1;
				// Stop any previous scale tweens on the hook to prevent conflicts
				tween.stop(fishingElements.hook.scale);
				tween(fishingElements.hook.scale, {
					x: originalScaleX * pulseScaleFactor,
					y: originalScaleY * pulseScaleFactor
				}, {
					duration: pulsePhaseDuration,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) {
							tween(fishingElements.hook.scale, {
								x: originalScaleX,
								y: originalScaleY
							}, {
								duration: pulsePhaseDuration,
								easing: tween.easeIn // Or tween.easeOut for a softer return
							});
						}
					}
				});
			}
		}
	}
	// Update existing music notes
	for (var mnIdx = musicNotesArray.length - 1; mnIdx >= 0; mnIdx--) {
		var note = musicNotesArray[mnIdx];
		if (note) {
			note.update();
			if (note.isDone) {
				note.destroy();
				musicNotesArray.splice(mnIdx, 1);
			}
		} else {
			// Should not happen, but good to safeguard
			musicNotesArray.splice(mnIdx, 1);
		}
	}
	// Spawn bubbles for active fish
	if (bubbleContainer) {
		for (var f = 0; f < fishArray.length; f++) {
			var fish = fishArray[f];
			if (fish && !fish.caught && !fish.isHeld && fish.fishGraphics) {
				if (currentTime - fish.lastBubbleSpawnTime > fish.bubbleSpawnInterval) {
					fish.lastBubbleSpawnTime = currentTime;
					// Calculate tail position based on fish direction and width
					// fish.fishGraphics.width is the original asset width.
					// fish.fishGraphics.scale.x might be negative, but width property itself is positive.
					// The anchor is 0.5, so width/2 is distance from center to edge.
					var tailOffsetDirection = Math.sign(fish.speed) * -1; // Bubbles appear opposite to movement direction
					var bubbleX = fish.x + tailOffsetDirection * (fish.fishGraphics.width * Math.abs(fish.fishGraphics.scaleX) / 2) * 0.8; // 80% towards tail
					var bubbleY = fish.y + (Math.random() - 0.5) * (fish.fishGraphics.height * Math.abs(fish.fishGraphics.scaleY) / 4); // Slight Y variance around fish center
					var newBubble = new BubbleParticle(bubbleX, bubbleY);
					bubbleContainer.addChild(newBubble);
					bubblesArray.push(newBubble);
				}
			}
		}
	}
	// Update and remove bubbles
	for (var b = bubblesArray.length - 1; b >= 0; b--) {
		var bubble = bubblesArray[b];
		if (bubble) {
			// Extra safety check
			bubble.update();
			if (bubble.isDone) {
				bubble.destroy();
				bubblesArray.splice(b, 1);
			}
		} else {
			// If a null/undefined somehow got in
			bubblesArray.splice(b, 1);
		}
	}
};
// Initialize game
showScreen('title'); /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
/**** 
* Classes
****/ 
var BubbleParticle = Container.expand(function (startX, startY) {
	var self = Container.call(this);
	self.gfx = self.attachAsset('bubbles', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1 + Math.random() * 0.2,
		// Start with some variation
		scaleX: 1.2 + Math.random() * 0.25,
		// Larger bubbles
		// Bubbles are now 20% to 45% of original asset size
		scaleY: this.scaleX // Keep aspect ratio initially, can be changed in update if desired
	});
	self.x = startX;
	self.y = startY;
	self.vx = (Math.random() - 0.5) * 0.4; // Even slower horizontal drift
	self.vy = -(0.4 + Math.random() * 0.3); // Slower upward movement
	self.life = 120 + Math.random() * 60; // Lifespan in frames (2 to 3 seconds)
	self.age = 0;
	self.isDone = false;
	var initialAlpha = self.gfx.alpha;
	var initialScale = self.gfx.scaleX; // Assuming scaleX and scaleY start the same
	self.update = function () {
		if (self.isDone) {
			return;
		}
		self.age++;
		self.x += self.vx;
		self.y += self.vy;
		// Fade out
		self.gfx.alpha = Math.max(0, initialAlpha * (1 - self.age / self.life));
		// Optionally shrink a bit more, or grow slightly then shrink
		var scaleFactor = 1 - self.age / self.life; // Simple shrink
		self.gfx.scaleX = initialScale * scaleFactor;
		self.gfx.scaleY = initialScale * scaleFactor;
		if (self.age >= self.life || self.gfx.alpha <= 0 || self.gfx.scaleX <= 0.01) {
			self.isDone = true;
		}
	};
	return self;
});
var CloudParticle = Container.expand(function () {
	var self = Container.call(this);
	self.gfx = self.attachAsset('cloud', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Cloud spawn location - 30% chance to spawn on screen
	var spawnOnScreen = Math.random() < 0.3;
	if (spawnOnScreen) {
		// Spawn randomly across the screen width
		self.x = 200 + Math.random() * 1648; // Between 200 and 1848 to avoid edges
		// Random horizontal drift direction
		self.vx = (Math.random() < 0.5 ? 1 : -1) * (0.1 + Math.random() * 0.2);
	} else {
		// Original off-screen spawn logic (70% of the time)
		var spawnFromLeft = Math.random() < 0.5;
		if (spawnFromLeft) {
			self.x = -100; // Start off-screen left
			self.vx = 0.1 + Math.random() * 0.2; // Slower drift right at 0.1-0.3 pixels/frame
		} else {
			self.x = 2048 + 100; // Start off-screen right
			self.vx = -(0.1 + Math.random() * 0.2); // Slower drift left
		}
	}
	// Spawn in sky area (above water surface)
	var skyTop = -500; // Where sky background starts
	var skyBottom = GAME_CONFIG.WATER_SURFACE_Y - 100; // Stay well above water
	self.y = skyTop + Math.random() * (skyBottom - skyTop);
	// Cloud properties
	var baseScale = 0.8 + Math.random() * 0.6; // Scale: 0.8x to 1.4x
	self.gfx.scale.set(baseScale);
	// Subtle vertical drift
	self.vy = (Math.random() - 0.5) * 0.02; // Even slower up/down drift
	// Opacity for atmospheric effect
	var targetAlpha = 0.4 + Math.random() * 0.3; // Alpha: 0.4 to 0.7
	self.gfx.alpha = 0; // Start transparent
	self.isDone = false;
	self.fadingOut = false;
	self.hasFadedIn = false; // Track if initial fade in completed
	// Define screen boundaries for fade in/out
	var fadeInStartX = 400; // Start fading in when cloud reaches this X
	var fadeInEndX = 800; // Fully visible by this X
	var fadeOutStartX = 1248; // Start fading out at this X (2048 - 800)
	var fadeOutEndX = 1648; // Fully transparent by this X (2048 - 400)
	self.update = function () {
		if (self.isDone) {
			return;
		}
		// Apply movement
		self.x += self.vx;
		self.y += self.vy;
		// Handle mid-screen fade in/out based on position
		var currentAlpha = self.gfx.alpha;
		if (spawnFromLeft) {
			// Moving right: fade in then fade out
			if (!self.hasFadedIn && self.x >= fadeInStartX && self.x <= fadeInEndX) {
				// Calculate fade in progress
				var fadeInProgress = (self.x - fadeInStartX) / (fadeInEndX - fadeInStartX);
				self.gfx.alpha = targetAlpha * fadeInProgress;
				if (fadeInProgress >= 1) {
					self.hasFadedIn = true;
				}
			} else if (self.hasFadedIn && self.x >= fadeOutStartX && self.x <= fadeOutEndX) {
				// Calculate fade out progress
				var fadeOutProgress = (self.x - fadeOutStartX) / (fadeOutEndX - fadeOutStartX);
				self.gfx.alpha = targetAlpha * (1 - fadeOutProgress);
			} else if (self.hasFadedIn && self.x > fadeInEndX && self.x < fadeOutStartX) {
				// Maintain full opacity in middle section
				self.gfx.alpha = targetAlpha;
			}
		} else {
			// Moving left: fade in then fade out (reversed positions)
			if (!self.hasFadedIn && self.x <= fadeOutEndX && self.x >= fadeOutStartX) {
				// Calculate fade in progress (reversed)
				var fadeInProgress = (fadeOutEndX - self.x) / (fadeOutEndX - fadeOutStartX);
				self.gfx.alpha = targetAlpha * fadeInProgress;
				if (fadeInProgress >= 1) {
					self.hasFadedIn = true;
				}
			} else if (self.hasFadedIn && self.x <= fadeInEndX && self.x >= fadeInStartX) {
				// Calculate fade out progress (reversed)
				var fadeOutProgress = (fadeInEndX - self.x) / (fadeInEndX - fadeInStartX);
				self.gfx.alpha = targetAlpha * (1 - fadeOutProgress);
			} else if (self.hasFadedIn && self.x < fadeOutStartX && self.x > fadeInEndX) {
				// Maintain full opacity in middle section
				self.gfx.alpha = targetAlpha;
			}
		}
		// Check if completely off-screen
		var currentWidth = self.gfx.width * self.gfx.scale.x;
		if (self.x < -currentWidth || self.x > 2048 + currentWidth) {
			self.isDone = true;
		}
	};
	return self;
});
var FeedbackIndicator = Container.expand(function (type) {
	var self = Container.call(this);
	var indicator = self.attachAsset(type + 'Indicator', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0
	});
	self.show = function () {
		indicator.alpha = 1;
		indicator.scaleX = 0.5;
		indicator.scaleY = 0.5;
		tween(indicator, {
			scaleX: 1.5,
			scaleY: 1.5,
			alpha: 0
		}, {
			duration: 1400,
			easing: tween.easeOut
		});
	};
	return self;
});
/**** 
* Title Screen
****/ 
var Fish = Container.expand(function (type, value, speed, lane) {
	var self = Container.call(this);
	var assetName = type + 'Fish';
	// Randomly pick from the three shallowFish assets if type is shallow
	if (type === 'shallow') {
		var shallowFishAssets = ['shallowFish', 'shallowFish2', 'shallowFish3'];
		assetName = shallowFishAssets[Math.floor(Math.random() * shallowFishAssets.length)];
	}
	self.fishGraphics = self.attachAsset(assetName, {
		anchorX: 0.5,
		anchorY: 0.5
	});
	if (speed > 0) {
		// Moving right (coming from left), flip horizontally
		self.fishGraphics.scaleX = -1;
	}
	self.type = type;
	self.value = value;
	self.speed = speed;
	self.lane = lane; // Index of the lane (0, 1, or 2)
	self.caught = false;
	self.missed = false; // True if the fish passed the hook without being caught
	self.lastX = 0; // Stores the x position from the previous frame for miss detection
	self.isSpecial = type === 'rare'; // For shimmer effect
	self.shimmerTime = 0;
	// Bubble properties
	self.lastBubbleSpawnTime = 0;
	self.bubbleSpawnInterval = 120 + Math.random() * 80; // 120-200ms interval (fewer bubbles)
	// Add properties for swimming animation
	self.swimTime = Math.random() * Math.PI * 2; // Random starting phase for variety
	self.baseY = self.y; // Store initial Y position
	self.scaleTime = 0;
	self.baseScale = 1;
	self.update = function () {
		if (!self.caught) {
			// Horizontal movement
			self.x += self.speed;
			// Sine wave vertical movement
			self.swimTime += 0.08; // Speed of sine wave oscillation
			var swimAmplitude = 15; // Pixels of vertical movement
			self.y = self.baseY + Math.sin(self.swimTime) * swimAmplitude;
			// Beat-synchronized scale pulsing
			if (GameState.gameActive && GameState.songStartTime > 0) {
				var currentTime = LK.ticks * (1000 / 60);
				var songConfig = GameState.getCurrentSongConfig();
				var beatInterval = 60000 / songConfig.bpm;
				var timeSinceLastBeat = (currentTime - GameState.songStartTime) % beatInterval;
				var beatProgress = timeSinceLastBeat / beatInterval;
				// Create a pulse effect that peaks at the beat
				var scalePulse = 1 + Math.sin(beatProgress * Math.PI) * 0.15; // 15% scale variation
				// Determine base scaleX considering direction
				var baseScaleXDirection = (self.speed > 0 ? -1 : 1) * self.baseScale;
				self.fishGraphics.scaleX = baseScaleXDirection * scalePulse;
				self.fishGraphics.scaleY = scalePulse * self.baseScale;
			}
			if (self.isSpecial) {
				// Shimmer effect for rare fish
				self.shimmerTime += 0.1;
				self.fishGraphics.alpha = 0.8 + Math.sin(self.shimmerTime) * 0.2;
			} else {
				// Reset alpha if not special
				self.fishGraphics.alpha = 1.0;
			}
		}
	};
	self.catchFish = function () {
		self.caught = true;
		// Animation: Fish arcs up over the boat, then down into it.
		var currentFishX = self.x;
		var currentFishY = self.y;
		var boatCenterX = GAME_CONFIG.SCREEN_CENTER_X;
		var boatLandingY = GAME_CONFIG.BOAT_Y; // Y-coordinate where the fish "lands" in the boat
		// Define the peak of the arc - e.g., 150 pixels above the boat's landing spot
		var peakArcY = boatLandingY - 150;
		// Define the X coordinate at the peak of the arc - halfway between current X and boat center X
		var peakArcX = currentFishX + (boatCenterX - currentFishX) * 0.5;
		var durationPhase1 = 350; // Duration for the first part of the arc (upwards)
		var durationPhase2 = 250; // Duration for the second part of the arc (downwards), total 600ms
		// Phase 1: Arc upwards and towards the boat's horizontal center.
		// The container 'self' is tweened for position, scale, and alpha.
		// Initial self.scale.x and self.scale.y are expected to be 1 for the container.
		tween(self, {
			x: peakArcX,
			y: peakArcY,
			scaleX: 0.75,
			// Scale container to 75% of its original size
			scaleY: 0.75,
			alpha: 0.8 // Partially fade
		}, {
			duration: durationPhase1,
			easing: tween.easeOut,
			// Ease out for the upward motion to the peak
			onFinish: function onFinish() {
				// Phase 2: Arc downwards into the boat and disappear.
				tween(self, {
					x: boatCenterX,
					y: boatLandingY,
					scaleX: 0.2,
					// Shrink container further to 20% of its original size
					scaleY: 0.2,
					alpha: 0 // Fade out completely
				}, {
					duration: durationPhase2,
					easing: tween.easeIn,
					// Ease in for the downward motion into the boat
					onFinish: function onFinish() {
						self.destroy(); // Remove fish once animation is complete
					}
				});
			}
		});
	};
	return self;
});
var MusicNoteParticle = Container.expand(function (startX, startY) {
	var self = Container.call(this);
	var FADE_IN_DURATION_MS = 600; // Duration for the note to fade in
	var TARGET_ALPHA = 0.6 + Math.random() * 0.4; // Target alpha between 0.6 and 1.0 for variety
	self.gfx = self.attachAsset('musicnote', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0,
		// Start invisible for fade-in
		scaleX: 0.4 + Math.random() * 0.4 // Random initial scale (0.4x to 0.8x)
	});
	self.gfx.scaleY = self.gfx.scaleX; // Maintain aspect ratio
	self.x = startX;
	self.y = startY;
	// Animation properties for lazy floating
	self.vx = (Math.random() - 0.5) * 0.8; // Slow horizontal drift speed
	self.vy = -(0.8 + Math.random() * 0.7); // Steady upward speed (0.8 to 1.5 pixels/frame)
	self.rotationSpeed = (Math.random() - 0.5) * 0.008; // Very slow rotation
	self.life = 240 + Math.random() * 120; // Lifespan in frames (4 to 6 seconds)
	self.age = 0;
	self.isDone = false;
	// Initial fade-in tween
	tween(self.gfx, {
		alpha: TARGET_ALPHA
	}, {
		duration: FADE_IN_DURATION_MS,
		easing: tween.easeOut
	});
	self.update = function () {
		if (self.isDone) {
			return;
		}
		self.age++;
		self.x += self.vx;
		self.y += self.vy;
		self.gfx.rotation += self.rotationSpeed;
		var FADE_IN_TICKS = FADE_IN_DURATION_MS / (1000 / 60); // Fade-in duration in ticks
		// Only manage alpha manually after the fade-in tween is expected to be complete.
		if (self.age > FADE_IN_TICKS) {
			var lifePortionForFadeOut = 0.6; // Use last 60% of life for fade out
			var fadeOutStartTimeTicks = self.life * (1 - lifePortionForFadeOut);
			if (self.age >= fadeOutStartTimeTicks && self.life > fadeOutStartTimeTicks) {
				// ensure self.life > fadeOutStartTimeTicks to avoid division by zero
				var progressInFadeOut = (self.age - fadeOutStartTimeTicks) / (self.life * lifePortionForFadeOut);
				self.gfx.alpha = TARGET_ALPHA * (1 - progressInFadeOut);
				self.gfx.alpha = Math.max(0, self.gfx.alpha); // Clamp at 0
			} else if (self.age <= fadeOutStartTimeTicks) {
				// At this point, the initial fade-in tween to TARGET_ALPHA should have completed.
				// The alpha value is expected to remain at TARGET_ALPHA (as set by the initial tween)
				// until the fade-out logic (in the 'if (self.age >= fadeOutStartTimeTicks)' block) begins.
				// The previous 'tween.isTweening' check was removed as the method does not exist in the plugin.
			}
		}
		// Check if particle's life is over or it has faded out
		if (self.age >= self.life || self.gfx.alpha !== undefined && self.gfx.alpha <= 0.01 && self.age > FADE_IN_TICKS) {
			self.isDone = true;
		}
	};
	return self;
});
var OceanBubbleParticle = Container.expand(function () {
	var self = Container.call(this);
	self.gfx = self.attachAsset('oceanbubbles', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.initialX = Math.random() * 2048;
	var waterTop = GAME_CONFIG.WATER_SURFACE_Y;
	var waterBottom = 2732;
	self.x = self.initialX;
	// Allow bubbles to spawn anywhere in the water, not just below the bottom
	self.y = waterTop + Math.random() * (waterBottom - waterTop);
	var baseScale = 0.1 + Math.random() * 0.4; // Scale: 0.1x to 0.5x
	self.gfx.scale.set(baseScale);
	self.vy = -(0.25 + Math.random() * 0.5); // Upward speed: 0.25 to 0.75 pixels/frame (slower, less variance)
	self.naturalVy = self.vy; // Store natural velocity for recovery
	self.driftAmplitude = 20 + Math.random() * 40; // Sideways drift: 20px to 60px amplitude
	self.naturalDriftAmplitude = self.driftAmplitude; // Store natural drift for recovery
	self.driftFrequency = (0.005 + Math.random() * 0.015) * (Math.random() < 0.5 ? 1 : -1); // Sideways drift speed/direction
	self.driftPhase = Math.random() * Math.PI * 2; // Initial phase for sine wave
	self.rotationSpeed = (Math.random() - 0.5) * 0.01; // Slow random rotation
	var targetAlpha = 0.2 + Math.random() * 0.3; // Max alpha: 0.2 to 0.5 (dimmer background bubbles)
	self.gfx.alpha = 0; // Start transparent
	self.isDone = false;
	self.fadingOut = false;
	tween(self.gfx, {
		alpha: targetAlpha
	}, {
		duration: 1000 + Math.random() * 1000,
		// Slow fade in: 1 to 2 seconds
		easing: tween.easeIn
	});
	self.update = function () {
		if (self.isDone) {
			return;
		}
		self.y += self.vy;
		// Increment age
		self.age++;
		// Check if lifespan exceeded
		if (!self.fadingOut && self.age >= self.lifespan) {
			self.fadingOut = true;
			tween.stop(self.gfx);
			tween(self.gfx, {
				alpha: 0
			}, {
				duration: 600 + Math.random() * 400,
				// 0.6-1 second fade
				easing: tween.easeOut,
				onFinish: function onFinish() {
					self.isDone = true;
				}
			});
		}
		self.driftPhase += self.driftFrequency;
		self.x = self.initialX + Math.sin(self.driftPhase) * self.driftAmplitude;
		self.gfx.rotation += self.rotationSpeed;
		// Recovery mechanism: gradually return to natural upward movement
		var naturalVy = -(0.25 + Math.random() * 0.5); // Natural upward speed
		var recoveryRate = 0.02; // How quickly bubble recovers (2% per frame)
		// If bubble is moving slower than its natural speed or downward, recover
		if (self.vy > naturalVy) {
			self.vy = self.vy + (naturalVy - self.vy) * recoveryRate;
		}
		// Also gradually reduce excessive drift amplitude back to normal
		var normalDriftAmplitude = 20 + Math.random() * 40;
		if (self.driftAmplitude > normalDriftAmplitude) {
			self.driftAmplitude = self.driftAmplitude + (normalDriftAmplitude - self.driftAmplitude) * recoveryRate;
		}
		// Check if bubble reached surface or went off-screen
		// Use gfx.height * current scale for accurate boundary check
		var currentHeight = self.gfx.height * self.gfx.scale.y;
		var currentWidth = self.gfx.width * self.gfx.scale.x;
		if (!self.fadingOut && self.y <= GAME_CONFIG.WATER_SURFACE_Y - currentHeight * 0.5) {
			self.fadingOut = true;
			tween.stop(self.gfx); // Stop fade-in if ongoing
			tween(self.gfx, {
				alpha: 0
			}, {
				duration: 300 + Math.random() * 200,
				// Quick fade out
				easing: tween.easeOut,
				onFinish: function onFinish() {
					self.isDone = true; // Mark as fully done for removal
				}
			});
		} else if (!self.fadingOut && (self.y < -currentHeight || self.x < -currentWidth || self.x > 2048 + currentWidth)) {
			// Off screen top/sides before reaching surface and starting fade
			self.isDone = true;
			self.gfx.alpha = 0; // Disappear immediately
		}
	};
	return self;
});
var SeaweedParticle = Container.expand(function () {
	var self = Container.call(this);
	self.gfx = self.attachAsset('kelp', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Determine spawn location
	var spawnType = Math.random();
	var waterTop = GAME_CONFIG.WATER_SURFACE_Y;
	var waterBottom = 2732;
	if (spawnType < 0.4) {
		// 40% spawn from bottom
		self.x = Math.random() * 2048;
		self.y = waterBottom + 50;
		self.vx = (Math.random() - 0.5) * 0.3; // Slight horizontal drift
		self.vy = -(0.4 + Math.random() * 0.3); // Upward movement
	} else if (spawnType < 0.7) {
		// 30% spawn from left
		self.x = -50;
		self.y = waterTop + Math.random() * (waterBottom - waterTop);
		self.vx = 0.4 + Math.random() * 0.3; // Rightward movement
		self.vy = -(0.1 + Math.random() * 0.2); // Slight upward drift
	} else {
		// 30% spawn from right
		self.x = 2048 + 50;
		self.y = waterTop + Math.random() * (waterBottom - waterTop);
		self.vx = -(0.4 + Math.random() * 0.3); // Leftward movement
		self.vy = -(0.1 + Math.random() * 0.2); // Slight upward drift
	}
	self.initialX = self.x;
	self.naturalVx = self.vx; // Store natural velocity for recovery
	self.naturalVy = self.vy;
	// Seaweed properties
	var baseScale = 0.6 + Math.random() * 0.6; // Scale: 0.6x to 1.2x
	self.gfx.scale.set(baseScale);
	self.swayAmplitude = 15 + Math.random() * 25; // Sway: 15px to 40px
	self.swayFrequency = (0.003 + Math.random() * 0.007) * (Math.random() < 0.5 ? 1 : -1);
	self.swayPhase = Math.random() * Math.PI * 2;
	// Random initial rotation (full 360 degrees)
	self.gfx.rotation = Math.random() * Math.PI * 2;
	// Random continuous rotation speed (slower than ocean bubbles)
	self.continuousRotationSpeed = (Math.random() - 0.5) * 0.003; // -0.0015 to 0.0015 radians per frame
	var targetAlpha = 0.3 + Math.random() * 0.3; // Alpha: 0.3 to 0.6
	self.gfx.alpha = 0; // Start transparent
	self.isDone = false;
	self.fadingOut = false;
	self.reachedSurface = false;
	// Add random lifespan (10-30 seconds)
	self.lifespan = 600 + Math.random() * 1200; // 600-1800 frames (10-30 seconds at 60fps)
	self.age = 0;
	tween(self.gfx, {
		alpha: targetAlpha
	}, {
		duration: 1500 + Math.random() * 1000,
		easing: tween.easeIn
	});
	self.update = function () {
		if (self.isDone) {
			return;
		}
		// Apply movement
		self.x += self.vx;
		self.y += self.vy;
		// Add sway effect
		self.swayPhase += self.swayFrequency;
		var swayOffset = Math.sin(self.swayPhase) * self.swayAmplitude;
		// Apply continuous rotation plus sway-based rotation
		self.gfx.rotation += self.continuousRotationSpeed + swayOffset * 0.0001; // Reduced sway rotation influence
		// Recovery mechanism for velocity
		var recoveryRate = 0.015;
		if (self.vx !== self.naturalVx) {
			self.vx = self.vx + (self.naturalVx - self.vx) * recoveryRate;
		}
		if (self.vy !== self.naturalVy) {
			self.vy = self.vy + (self.naturalVy - self.vy) * recoveryRate;
		}
		// Check if reached surface
		var currentHeight = self.gfx.height * self.gfx.scale.y;
		var currentWidth = self.gfx.width * self.gfx.scale.x;
		if (!self.reachedSurface && self.y <= GAME_CONFIG.WATER_SURFACE_Y + currentHeight * 0.3) {
			self.reachedSurface = true;
			// Change to horizontal drift at surface
			self.vy = 0;
			self.vx = (Math.random() < 0.5 ? 1 : -1) * (0.5 + Math.random() * 0.5);
			self.naturalVx = self.vx;
			self.naturalVy = 0;
		}
		// Check if off-screen
		if (!self.fadingOut && (self.y < -currentHeight || self.x < -currentWidth || self.x > 2048 + currentWidth || self.y > waterBottom + currentHeight)) {
			self.fadingOut = true;
			tween.stop(self.gfx);
			tween(self.gfx, {
				alpha: 0
			}, {
				duration: 400 + Math.random() * 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					self.isDone = true;
				}
			});
		}
	};
	return self;
});
/**** 
* Initialize Game
****/ 
/**** 
* Screen Containers
****/ 
var game = new LK.Game({
	backgroundColor: 0x87CEEB
});
/**** 
* Game Code
****/ 
// Constants for Title Screen Animation
var TITLE_ANIM_CONSTANTS = {
	INITIAL_GROUP_ALPHA: 0,
	FINAL_GROUP_ALPHA: 1,
	INITIAL_UI_ALPHA: 0,
	FINAL_UI_ALPHA: 1,
	INITIAL_GROUP_SCALE: 3.5,
	// Start with an extreme closeup
	FINAL_GROUP_SCALE: 2.8,
	// Zoom out slightly, staying closer
	GROUP_ANIM_DURATION: 4000,
	// Slower duration for group fade-in and zoom
	TEXT_FADE_DURATION: 1000,
	// Duration for title text fade-in
	BUTTON_FADE_DURATION: 800,
	// Duration for buttons fade-in
	// Positioning constants relative to titleAnimationGroup's origin (0,0) which is boat's center
	BOAT_ANCHOR_X: 0.5,
	BOAT_ANCHOR_Y: 0.5,
	FISHERMAN_ANCHOR_X: 0.5,
	FISHERMAN_ANCHOR_Y: 0.9,
	// Anchor at feet
	FISHERMAN_X_OFFSET: -20,
	// Relative to boat center
	FISHERMAN_Y_OFFSET: -100,
	// Relative to boat center, fisherman sits on boat
	LINE_ANCHOR_X: 0.5,
	LINE_ANCHOR_Y: 0,
	// Anchor at top of line
	LINE_X_OFFSET_FROM_FISHERMAN: 70,
	// Rod tip X from fisherman center
	LINE_Y_OFFSET_FROM_FISHERMAN: -130,
	// Rod tip Y from fisherman center (fisherman height ~200, anchorY 0.9)
	HOOK_ANCHOR_X: 0.5,
	HOOK_ANCHOR_Y: 0.5,
	HOOK_Y_DEPTH_FROM_LINE_START: 700,
	// Slightly longer line for the closeup
	// titleAnimationGroup positioning
	GROUP_PIVOT_X: 0,
	// Boat's X in the group
	GROUP_PIVOT_Y: 0,
	// Boat's Y in the group
	GROUP_INITIAL_Y_SCREEN_OFFSET: -450 // Adjusted for closer initial zoom
};
// If game.up already exists, integrate the 'fishing' case. Otherwise, this defines game.up.
/**** 
* Pattern Generation System
****/ 
var PatternGenerator = {
	lastLane: -1,
	minDistanceBetweenFish: 300,
	// Used by spawnFish internal check
	// Minimum X distance between fish for visual clarity on hook
	lastActualSpawnTime: -100000,
	// Time of the last actual fish spawn
	getNextLane: function getNextLane() {
		if (this.lastLane === -1) {
			// First fish, start in middle lane
			this.lastLane = 1;
			return 1;
		}
		// Prefer staying in same lane or moving to adjacent lane
		var possibleLanes = [this.lastLane];
		// Add adjacent lanes
		if (this.lastLane > 0) {
			possibleLanes.push(this.lastLane - 1);
		}
		if (this.lastLane < 2) {
			possibleLanes.push(this.lastLane + 1);
		}
		// 70% chance to stay in same/adjacent lane
		if (Math.random() < 0.7) {
			this.lastLane = possibleLanes[Math.floor(Math.random() * possibleLanes.length)];
		} else {
			// 30% chance for any lane
			this.lastLane = Math.floor(Math.random() * 3);
		}
		return this.lastLane;
	},
	// New method: Checks if enough time has passed since the last spawn.
	canSpawnFishOnBeat: function canSpawnFishOnBeat(currentTime, configuredSpawnInterval) {
		var timeSinceLast = currentTime - this.lastActualSpawnTime;
		var minRequiredGap = configuredSpawnInterval; // Default gap is the song's beat interval for fish
		return timeSinceLast >= minRequiredGap;
	},
	// New method: Registers details of the fish that was just spawned.
	registerFishSpawn: function registerFishSpawn(spawnTime) {
		this.lastActualSpawnTime = spawnTime;
	},
	reset: function reset() {
		this.lastLane = -1;
		this.lastActualSpawnTime = -100000; // Set far in the past to allow first spawn
	}
};
/**** 
* Game Configuration
****/ 
game.up = function (x, y, obj) {
	// Note: We don't play buttonClick sound on 'up' typically, only on 'down'.
	switch (GameState.currentScreen) {
		case 'title':
			// title screen up actions (if any)
			break;
		case 'levelSelect':
			// level select screen up actions (if any, usually 'down' is enough for buttons)
			break;
		case 'fishing':
			handleFishingInput(x, y, false); // false for isUp
			break;
		case 'results':
			// results screen up actions (if any)
			break;
	}
};
var GAME_CONFIG = {
	SCREEN_CENTER_X: 1024,
	SCREEN_CENTER_Y: 900,
	// Adjusted, though less critical with lanes
	BOAT_Y: 710,
	// Original 300 + 273 (10%) + 137 (5%) = 710
	WATER_SURFACE_Y: 760,
	// Original 350 + 273 (10%) + 137 (5%) = 760
	// 3 Lane System
	// Y-positions for each lane, adjusted downwards further
	LANES: [{
		y: 1133,
		// Top lane Y: (723 + 273) + 137 = 996 + 137 = 1133
		name: "shallow"
	}, {
		y: 1776,
		// Middle lane Y: (996 + 643) + 137 = 1639 + 137 = 1776
		name: "medium"
	}, {
		y: 2419,
		// Bottom lane Y: (1639 + 643) + 137 = 2282 + 137 = 2419
		name: "deep"
	}],
	// Timing windows
	PERFECT_WINDOW: 40,
	GOOD_WINDOW: 80,
	MISS_WINDOW: 120,
	// Depth levels - reduced money values!
	DEPTHS: [{
		level: 1,
		name: "Shallow Waters",
		fishSpeed: 6,
		fishValue: 1,
		upgradeCost: 0,
		// Starting depth
		songs: [{
			name: "Gentle Waves",
			bpm: 90,
			duration: 202000,
			// 3:22
			pattern: "gentle_waves_custom",
			cost: 0
		}, {
			name: "Morning Tide",
			bpm: 90,
			duration: 156827,
			pattern: "morning_tide_custom",
			cost: 0,
			musicId: 'morningtide'
		}, {
			name: "Sunny Afternoon",
			bpm: 97,
			duration: 181800,
			// 3:01.8
			pattern: "sunny_afternoon_custom",
			cost: 0,
			musicId: 'sunnyafternoon'
		}]
	}, {
		level: 2,
		name: "Mid Waters",
		fishSpeed: 7,
		fishValue: 2,
		upgradeCost: 100,
		songs: [{
			name: "Ocean Current",
			bpm: 120,
			duration: 90000,
			pattern: "medium",
			cost: 0
		}, {
			name: "Deep Flow",
			bpm: 125,
			duration: 100000,
			pattern: "medium",
			cost: 150
		}]
	}, {
		level: 3,
		name: "Deep Waters",
		fishSpeed: 8,
		fishValue: 3,
		upgradeCost: 400,
		songs: [{
			name: "Storm Surge",
			bpm: 140,
			duration: 120000,
			pattern: "complex",
			cost: 0
		}, {
			name: "Whirlpool",
			bpm: 150,
			duration: 135000,
			pattern: "complex",
			cost: 300
		}]
	}, {
		level: 4,
		name: "Abyss",
		fishSpeed: 9,
		fishValue: 6,
		upgradeCost: 1000,
		songs: [{
			name: "Leviathan",
			bpm: 160,
			duration: 150000,
			pattern: "expert",
			cost: 0
		}, {
			name: "Deep Trench",
			bpm: 170,
			duration: 180000,
			pattern: "expert",
			cost: 600
		}]
	}],
	// Updated patterns
	PATTERNS: {
		simple: {
			beatsPerFish: 2,
			// Increased from 1 - fish every 2 beats
			doubleSpawnChance: 0.10,
			// 10% chance of a double beat in simple
			rareSpawnChance: 0.02
		},
		medium: {
			beatsPerFish: 1.5,
			// Increased from 0.75
			doubleSpawnChance: 0.15,
			//{1R} // 15% chance of a double beat
			rareSpawnChance: 0.05
		},
		complex: {
			beatsPerFish: 1,
			// Increased from 0.5
			doubleSpawnChance: 0.25,
			//{1U} // 25% chance of a double beat
			rareSpawnChance: 0.08
		},
		expert: {
			beatsPerFish: 0.75,
			// Increased from 0.25
			doubleSpawnChance: 0.35,
			//{1X} // 35% chance of a double beat
			tripletSpawnChance: 0.20,
			// 20% chance a double beat becomes a triplet
			rareSpawnChance: 0.12
		},
		gentle_waves_custom: {
			beatsPerFish: 1.5,
			// Slightly more frequent than default simple
			doubleSpawnChance: 0.05,
			// Very rare double beats for shallow
			rareSpawnChance: 0.01,
			// Almost no rare fish
			// Custom timing sections based on the musical structure
			sections: [
			// Opening - Simple chord pattern (0-30 seconds)
			{
				startTime: 0,
				endTime: 30000,
				spawnModifier: 1.0,
				// Normal spawn rate
				description: "steady_chords"
			},
			// Melody Introduction (30-60 seconds)
			{
				startTime: 30000,
				endTime: 60000,
				spawnModifier: 0.9,
				// Slightly fewer fish
				description: "simple_melody"
			},
			// Development (60-120 seconds) - Gets a bit busier
			{
				startTime: 60000,
				endTime: 120000,
				spawnModifier: 1.1,
				// Slightly more fish
				description: "melody_development"
			},
			// Climax (120-180 seconds) - Busiest section but still shallow
			{
				startTime: 120000,
				endTime: 180000,
				spawnModifier: 1.3,
				// More fish, but not overwhelming
				description: "gentle_climax"
			},
			// Ending (180-202 seconds) - Calming down
			{
				startTime: 180000,
				endTime: 202000,
				spawnModifier: 0.8,
				// Fewer fish for gentle ending
				description: "peaceful_ending"
			}]
		},
		morning_tide_custom: {
			beatsPerFish: 1.2,
			// More frequent than gentle_waves_custom (1.5) and much more than simple (2)
			doubleSpawnChance: 0.12,
			// Higher than simple (0.10)
			rareSpawnChance: 0.03,
			// Higher than simple (0.02)
			// Custom timing sections based on musical structure
			sections: [
			// Gentle opening - ease into the song (0-25 seconds)
			{
				startTime: 0,
				endTime: 25000,
				spawnModifier: 0.9,
				// Slightly reduced for intro
				description: "calm_opening"
			},
			// Building energy - first wave (25-50 seconds)
			{
				startTime: 25000,
				endTime: 50000,
				spawnModifier: 1.2,
				// More active
				description: "first_wave"
			},
			// Peak intensity - morning rush (50-80 seconds)
			{
				startTime: 50000,
				endTime: 80000,
				spawnModifier: 1.5,
				// Most intense section
				description: "morning_rush"
			},
			// Sustained energy - second wave (80-110 seconds)
			{
				startTime: 80000,
				endTime: 110000,
				spawnModifier: 1.3,
				// High but slightly less than peak
				description: "second_wave"
			},
			// Climactic finish (110-140 seconds)
			{
				startTime: 110000,
				endTime: 140000,
				spawnModifier: 1.4,
				// Building back up
				description: "climactic_finish"
			},
			// Gentle fade out (140-156.8 seconds)
			{
				startTime: 140000,
				endTime: 156827,
				spawnModifier: 0.8,
				// Calm ending
				description: "peaceful_fade"
			}]
		},
		sunny_afternoon_custom: {
			beatsPerFish: 1.3,
			// Slightly faster than morning_tide_custom but still beginner-friendly
			doubleSpawnChance: 0.08,
			// Moderate chance for variety
			rareSpawnChance: 0.025,
			// Slightly better rewards than gentle_waves
			sections: [
			// Gentle warm-up (0-20 seconds)
			{
				startTime: 0,
				endTime: 20000,
				spawnModifier: 0.8,
				description: "warm_sunny_start"
			},
			// First activity burst (20-35 seconds)
			{
				startTime: 20000,
				endTime: 35000,
				spawnModifier: 1.4,
				description: "first_sunny_burst"
			},
			// Breathing room (35-50 seconds)
			{
				startTime: 35000,
				endTime: 50000,
				spawnModifier: 0.7,
				description: "sunny_breather_1"
			},
			// Second activity burst (50-70 seconds)
			{
				startTime: 50000,
				endTime: 70000,
				spawnModifier: 1.5,
				description: "second_sunny_burst"
			},
			// Extended breathing room (70-90 seconds)
			{
				startTime: 70000,
				endTime: 90000,
				spawnModifier: 0.6,
				description: "sunny_breather_2"
			},
			// Third activity burst (90-110 seconds)
			{
				startTime: 90000,
				endTime: 110000,
				spawnModifier: 1.3,
				description: "third_sunny_burst"
			},
			// Breathing room (110-125 seconds)
			{
				startTime: 110000,
				endTime: 125000,
				spawnModifier: 0.8,
				description: "sunny_breather_3"
			},
			// Final activity section (125-150 seconds)
			{
				startTime: 125000,
				endTime: 150000,
				spawnModifier: 1.2,
				description: "sunny_finale_buildup"
			},
			// Wind down (150-181.8 seconds)
			{
				startTime: 150000,
				endTime: 181800,
				spawnModifier: 0.9,
				description: "sunny_afternoon_fade"
			}]
		}
	}
};
/**** 
* Game State Management
****/ 
var MULTI_BEAT_SPAWN_DELAY_MS = 250; // ms delay for sequential spawns in multi-beats (increased for more space)
var TRIPLET_BEAT_SPAWN_DELAY_MS = 350; // ms delay for third fish in a triplet (even more space)
var FISH_SPAWN_END_BUFFER_MS = 500; // Just 0.5 seconds buffer
var ImprovedRhythmSpawner = {
	nextBeatToSchedule: 1,
	scheduledBeats: [],
	update: function update(currentTime) {
		if (!GameState.gameActive || GameState.songStartTime === 0) {
			return;
		}
		var songConfig = GameState.getCurrentSongConfig();
		var pattern = GAME_CONFIG.PATTERNS[songConfig.pattern];
		var beatInterval = 60000 / songConfig.bpm;
		var spawnInterval = beatInterval * pattern.beatsPerFish;
		// Calculate how far ahead to look (use original logic)
		var depthConfig = GameState.getCurrentDepthConfig();
		var fishSpeed = Math.abs(depthConfig.fishSpeed);
		var distanceToHook = GAME_CONFIG.SCREEN_CENTER_X + 150;
		var travelTimeMs = distanceToHook / fishSpeed * (1000 / 60);
		var beatsAhead = Math.ceil(travelTimeMs / spawnInterval) + 2;
		// Schedule beats ahead
		var songElapsed = currentTime - GameState.songStartTime;
		var currentSongBeat = songElapsed / spawnInterval;
		var maxBeatToSchedule = Math.floor(currentSongBeat) + beatsAhead;
		while (this.nextBeatToSchedule <= maxBeatToSchedule) {
			if (this.scheduledBeats.indexOf(this.nextBeatToSchedule) === -1) {
				this.scheduleBeatFish(this.nextBeatToSchedule, spawnInterval, travelTimeMs);
				this.scheduledBeats.push(this.nextBeatToSchedule);
			}
			this.nextBeatToSchedule++;
		}
		// NO FISH SYNCING - let them move naturally!
	},
	scheduleBeatFish: function scheduleBeatFish(beatNumber, spawnInterval, travelTimeMs) {
		var targetArrivalTime = GameState.songStartTime + beatNumber * spawnInterval;
		var songConfig = GameState.getCurrentSongConfig();
		// FIXED: The buffer should be applied to when the song ACTUALLY ends,
		// not when the fish arrives at the hook
		var songEndTime = GameState.songStartTime + songConfig.duration;
		var lastValidArrivalTime = songEndTime - FISH_SPAWN_END_BUFFER_MS;
		if (songConfig && GameState.songStartTime > 0 && targetArrivalTime > lastValidArrivalTime) {
			return; // Don't schedule this fish, it would arrive too close to song end
		}
		var spawnTime = targetArrivalTime - travelTimeMs;
		var currentTime = LK.ticks * (1000 / 60);
		// Rest of function stays the same...
		if (spawnTime >= currentTime - 100) {
			var delay = Math.max(0, spawnTime - currentTime);
			var self = this;
			LK.setTimeout(function () {
				if (GameState.gameActive && GameState.songStartTime !== 0) {
					self.spawnRhythmFish(beatNumber, targetArrivalTime);
				}
			}, delay);
		}
	},
	spawnRhythmFish: function spawnRhythmFish(beatNumber, targetArrivalTime) {
		if (!GameState.gameActive) {
			return;
		}
		var currentTime = LK.ticks * (1000 / 60);
		var depthConfig = GameState.getCurrentDepthConfig();
		var songConfig = GameState.getCurrentSongConfig();
		var pattern = GAME_CONFIG.PATTERNS[songConfig.pattern];
		// Calculate spawnInterval for this pattern
		var beatInterval = 60000 / songConfig.bpm;
		var spawnInterval = beatInterval * pattern.beatsPerFish;
		// Apply section modifiers if pattern has sections
		var spawnModifier = 1.0;
		if (pattern.sections) {
			var songElapsed = currentTime - GameState.songStartTime;
			for (var s = 0; s < pattern.sections.length; s++) {
				var section = pattern.sections[s];
				if (songElapsed >= section.startTime && songElapsed <= section.endTime) {
					spawnModifier = section.spawnModifier;
					break;
				}
			}
		}
		// Check if we should spawn
		if (!PatternGenerator.canSpawnFishOnBeat(currentTime, beatInterval)) {
			return;
		}
		// Apply spawn modifier chance
		if (Math.random() > spawnModifier) {
			return;
		}
		var laneIndex = PatternGenerator.getNextLane();
		var targetLane = GAME_CONFIG.LANES[laneIndex];
		// Fish type selection (original logic)
		var fishType, fishValue;
		var rand = Math.random();
		if (rand < pattern.rareSpawnChance) {
			fishType = 'rare';
			fishValue = Math.floor(depthConfig.fishValue * 4);
		} else if (GameState.selectedDepth >= 2 && rand < 0.3) {
			fishType = 'deep';
			fishValue = Math.floor(depthConfig.fishValue * 2);
		} else if (GameState.selectedDepth >= 1 && rand < 0.6) {
			fishType = 'medium';
			fishValue = Math.floor(depthConfig.fishValue * 1.5);
		} else {
			fishType = 'shallow';
			fishValue = Math.floor(depthConfig.fishValue);
		}
		// Calculate precise speed to arrive exactly on beat
		var timeRemainingMs = targetArrivalTime - currentTime;
		if (timeRemainingMs <= 0) {
			return;
		}
		var distanceToHook = GAME_CONFIG.SCREEN_CENTER_X + 150;
		var spawnSide = Math.random() < 0.5 ? -1 : 1;
		// Calculate frames remaining and required speed per frame
		var framesRemaining = timeRemainingMs / (1000 / 60);
		if (framesRemaining <= 0) {
			return;
		}
		var requiredSpeedPerFrame = distanceToHook / framesRemaining;
		if (spawnSide === 1) {
			requiredSpeedPerFrame *= -1; // Moving left
		}
		// Create fish with exact calculated speed - NO SYNC DATA
		var newFish = new Fish(fishType, fishValue, requiredSpeedPerFrame, laneIndex);
		newFish.spawnSide = spawnSide;
		newFish.targetArrivalTime = targetArrivalTime;
		newFish.x = requiredSpeedPerFrame > 0 ? -150 : 2048 + 150;
		newFish.y = targetLane.y;
		newFish.baseY = targetLane.y;
		newFish.lastX = newFish.x; // Initialize lastX for miss detection
		// newFish.missed is false by default from constructor
		// Don't add any syncData - let fish move naturally!
		fishArray.push(newFish);
		fishingScreen.addChild(newFish);
		GameState.sessionFishSpawned++;
		PatternGenerator.registerFishSpawn(currentTime);
		// Handle multi-spawns
		this.handleMultiSpawns(beatNumber, pattern, songConfig, spawnInterval, currentTime);
		return newFish;
	},
	handleMultiSpawns: function handleMultiSpawns(beatNumber, pattern, songConfig, spawnInterval, currentTime) {
		if (pattern.doubleSpawnChance > 0 && Math.random() < pattern.doubleSpawnChance) {
			var travelTimeMs = this.calculateTravelTime();
			var nextFishBeat = beatNumber + 1;
			if (this.scheduledBeats.indexOf(nextFishBeat) === -1) {
				this.scheduleBeatFish(nextFishBeat, spawnInterval, travelTimeMs);
				this.scheduledBeats.push(nextFishBeat);
			}
			// Handle triplets
			if (pattern.tripletSpawnChance && pattern.tripletSpawnChance > 0 && Math.random() < pattern.tripletSpawnChance) {
				var thirdFishBeat = beatNumber + 2;
				if (this.scheduledBeats.indexOf(thirdFishBeat) === -1) {
					this.scheduleBeatFish(thirdFishBeat, spawnInterval, travelTimeMs);
					this.scheduledBeats.push(thirdFishBeat);
				}
			}
		}
	},
	calculateTravelTime: function calculateTravelTime() {
		var depthConfig = GameState.getCurrentDepthConfig();
		var fishSpeed = Math.abs(depthConfig.fishSpeed);
		if (fishSpeed === 0) {
			return Infinity;
		}
		var distanceToHook = GAME_CONFIG.SCREEN_CENTER_X + 150;
		return distanceToHook / fishSpeed * (1000 / 60);
	},
	reset: function reset() {
		this.nextBeatToSchedule = 1;
		this.scheduledBeats = [];
	},
	getDebugInfo: function getDebugInfo() {
		return {
			nextBeat: this.nextBeatToSchedule,
			scheduledCount: this.scheduledBeats.length,
			scheduledBeatsPreview: this.scheduledBeats.slice(-10)
		};
	}
};
/**** 
* Tutorial System - Global Scope
****/ 
function updateLaneBracketsVisuals() {
	if (laneBrackets && laneBrackets.length === GAME_CONFIG.LANES.length) {
		for (var i = 0; i < laneBrackets.length; i++) {
			var isActiveLane = i === GameState.hookTargetLaneIndex;
			var targetAlpha = isActiveLane ? 0.9 : 0.5;
			if (laneBrackets[i] && laneBrackets[i].left && !laneBrackets[i].left.destroyed) {
				if (laneBrackets[i].left.alpha !== targetAlpha) {
					laneBrackets[i].left.alpha = targetAlpha;
				}
			}
			if (laneBrackets[i] && laneBrackets[i].right && !laneBrackets[i].right.destroyed) {
				if (laneBrackets[i].right.alpha !== targetAlpha) {
					laneBrackets[i].right.alpha = targetAlpha;
				}
			}
		}
	}
}
function createTutorialElements() {
	tutorialOverlayContainer.removeChildren(); // Clear previous elements
	tutorialTextBackground = tutorialOverlayContainer.addChild(LK.getAsset('screenBackground', {
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 2732 * 0.85,
		// Position towards the bottom
		width: 1800,
		height: 450,
		// Increased height
		color: 0x000000,
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.75
	}));
	tutorialTextDisplay = tutorialOverlayContainer.addChild(new Text2('', {
		size: 55,
		// Increased font size
		fill: 0xFFFFFF,
		wordWrap: true,
		wordWrapWidth: 1700,
		// Keep wordWrapWidth, adjust if lines are too cramped
		// Slightly less than background width
		align: 'center',
		lineHeight: 65 // Increased line height
	}));
	tutorialTextDisplay.anchor.set(0.5, 0.5);
	tutorialTextDisplay.x = tutorialTextBackground.x;
	tutorialTextDisplay.y = tutorialTextBackground.y - 50; // Adjusted Y for new background height and text size
	tutorialContinueButton = tutorialOverlayContainer.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: tutorialTextBackground.x,
		y: tutorialTextBackground.y + tutorialTextBackground.height / 2 - 55,
		// Adjusted Y for new background height
		// Positioned at the bottom of the text background
		tint: 0x1976d2,
		// Blue color
		width: 350,
		// Standard button size
		height: 70
	}));
	tutorialContinueText = tutorialOverlayContainer.addChild(new Text2('CONTINUE', {
		// Changed text
		size: 34,
		fill: 0xFFFFFF
	}));
	tutorialContinueText.anchor.set(0.5, 0.5);
	tutorialContinueText.x = tutorialContinueButton.x;
	tutorialContinueText.y = tutorialContinueButton.y;
	tutorialOverlayContainer.visible = false; // Initially hidden
}
function setTutorialText(newText, showContinue) {
	if (showContinue === undefined) {
		showContinue = true;
	}
	if (!tutorialTextDisplay || !tutorialContinueButton || !tutorialContinueText) {
		createTutorialElements(); // Ensure elements exist
	}
	tutorialTextDisplay.setText(newText);
	tutorialContinueButton.visible = showContinue;
	tutorialContinueText.visible = showContinue;
	tutorialOverlayContainer.visible = true;
}
function spawnTutorialFishHelper(config) {
	var fishType = config.type || 'shallow';
	// Use a consistent depth config for tutorial fish, e.g., shallowest
	var depthConfig = GAME_CONFIG.DEPTHS[0];
	var fishValue = Math.floor(depthConfig.fishValue / 2); // Tutorial fish might be worth less or nothing
	var baseSpeed = depthConfig.fishSpeed;
	var speedMultiplier = config.speedMultiplier || 0.5; // Slower for tutorial
	var laneIndex = config.lane !== undefined ? config.lane : 1;
	var spawnSide = config.spawnSide !== undefined ? config.spawnSide : Math.random() < 0.5 ? -1 : 1;
	var actualFishSpeed = Math.abs(baseSpeed) * speedMultiplier * spawnSide;
	var newFish = new Fish(fishType, fishValue, actualFishSpeed, laneIndex);
	newFish.x = actualFishSpeed > 0 ? -150 : 2048 + 150; // Start off-screen
	newFish.y = GAME_CONFIG.LANES[laneIndex].y + (config.yOffset || 0);
	newFish.baseY = newFish.y;
	newFish.lastX = newFish.x;
	newFish.tutorialFish = true; // Custom flag
	fishArray.push(newFish); // Add to global fishArray for rendering by main loop
	fishingScreen.addChild(newFish); // Add to fishingScreen for visibility
	return newFish;
}
function runTutorialStep() {
	GameState.tutorialPaused = false;
	GameState.tutorialAwaitingTap = false; // Will be set by steps if needed for "tap screen"
	// Clear lane highlights from previous step if any
	if (tutorialLaneHighlights.length > 0) {
		tutorialLaneHighlights.forEach(function (overlay) {
			if (overlay && !overlay.destroyed) {
				overlay.destroy();
			}
		});
		tutorialLaneHighlights = [];
	}
	if (tutorialContinueButton) {
		tutorialContinueButton.visible = true;
	} // Default to visible
	if (tutorialContinueText) {
		tutorialContinueText.visible = true;
	}
	// Clear previous tutorial fish unless current step needs it (now steps 3 and 4)
	if (GameState.tutorialFish && GameState.tutorialStep !== 3 && GameState.tutorialStep !== 4) {
		if (!GameState.tutorialFish.destroyed) {
			GameState.tutorialFish.destroy();
		}
		var idx = fishArray.indexOf(GameState.tutorialFish);
		if (idx > -1) {
			fishArray.splice(idx, 1);
		}
		GameState.tutorialFish = null;
	}
	// Ensure fishing screen elements are visually active
	// Adjusted max step for animations to 8 (new end tutorial step is 7)
	if (fishingElements) {
		if (typeof fishingElements.startWaterSurfaceAnimation === 'function' && GameState.tutorialStep < 8) {
			fishingElements.startWaterSurfaceAnimation();
		}
		if (typeof fishingElements.startBoatAndFishermanAnimation === 'function' && GameState.tutorialStep < 8) {
			fishingElements.startBoatAndFishermanAnimation();
		}
		if (fishingElements.hook && GameState.tutorialStep < 8) {
			fishingElements.hook.y = GAME_CONFIG.LANES[1].y; // Reset hook
			GameState.hookTargetLaneIndex = 1;
		}
	}
	switch (GameState.tutorialStep) {
		case 0:
			// Welcome
			setTutorialText("Welcome to Beat Fisher! Let's learn the basics. Tap 'CONTINUE'.");
			GameState.tutorialPaused = true;
			break;
		case 1:
			// The Hook Explanation
			setTutorialText("This is your hook. It automatically moves to the lane with the closest approaching fish. Tap 'CONTINUE'.");
			if (fishingElements.hook && !fishingElements.hook.destroyed) {
				tween(fishingElements.hook.scale, {
					x: 1.2,
					y: 1.2
				}, {
					duration: 250,
					onFinish: function onFinish() {
						if (fishingElements.hook && !fishingElements.hook.destroyed) {
							tween(fishingElements.hook.scale, {
								x: 1,
								y: 1
							}, {
								duration: 250
							});
						}
					}
				});
			}
			GameState.tutorialPaused = true;
			break;
		case 2:
			// Lanes & Tapping Explanation with Overlay
			setTutorialText("Fish swim in three lanes. When a fish is over the hook in its lane, TAP THE SCREEN in that lane to catch it. The lanes are highlighted. Tap 'CONTINUE'.");
			// Add highlights behind the text box elements
			for (var i = 0; i < GAME_CONFIG.LANES.length; i++) {
				var laneYPos = GAME_CONFIG.LANES[i].y;
				var highlight = LK.getAsset('laneHighlight', {
					anchorX: 0.5,
					anchorY: 0.5,
					x: GAME_CONFIG.SCREEN_CENTER_X,
					y: laneYPos,
					alpha: 0.25,
					// Semi-transparent
					width: 1800,
					height: 200
				});
				tutorialOverlayContainer.addChildAt(highlight, 0); // Add at the bottom of the container
				tutorialLaneHighlights.push(highlight);
			}
			GameState.tutorialPaused = true;
			break;
		case 3:
			// Was case 2
			// Fish Approaching & First Catch Attempt
			setTutorialText("A fish will now approach. Tap in its lane when it's under the hook!");
			tutorialContinueButton.visible = false; // No continue button, action is tapping screen
			tutorialContinueText.visible = false;
			GameState.tutorialPaused = false; // Fish moves
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
				GameState.tutorialFish.destroy();
			}
			GameState.tutorialFish = spawnTutorialFishHelper({
				type: 'shallow',
				speedMultiplier: 0.35,
				lane: 1
			});
			break;
		case 4:
			// Was case 3
			// Perfect/Good Timing (after first successful catch)
			setTutorialText("Great! Timing is key. 'Perfect' or 'Good' catches earn more. Try to catch this next fish!");
			tutorialContinueButton.visible = false;
			tutorialContinueText.visible = false;
			GameState.tutorialPaused = false;
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
				GameState.tutorialFish.destroy();
			}
			GameState.tutorialFish = spawnTutorialFishHelper({
				type: 'shallow',
				speedMultiplier: 0.45,
				lane: 1
			});
			break;
		case 5:
			// Was case 4
			// Combos
			setTutorialText("Nice one! Catch fish consecutively to build a COMBO for bonus points! Tap 'CONTINUE'.");
			GameState.tutorialPaused = true;
			break;
		case 6:
			// Was case 5
			// Music & Rhythm
			setTutorialText("Fish will approach the hook on the beat with the music's rhythm. Listen to the beat! Tap 'CONTINUE'.");
			GameState.tutorialPaused = true;
			break;
		case 7:
			// Was case 6
			// End Tutorial
			setTutorialText("You're all set! Tap 'CONTINUE' to go to the fishing spots!");
			GameState.tutorialPaused = true;
			break;
		default:
			// Effectively step 8
			// Tutorial finished
			GameState.tutorialMode = false;
			tutorialOverlayContainer.visible = false;
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
				GameState.tutorialFish.destroy();
				var idxDefault = fishArray.indexOf(GameState.tutorialFish);
				if (idxDefault > -1) {
					fishArray.splice(idxDefault, 1);
				}
				GameState.tutorialFish = null;
			}
			// Clear lane highlights if any persist
			if (tutorialLaneHighlights.length > 0) {
				tutorialLaneHighlights.forEach(function (overlay) {
					if (overlay && !overlay.destroyed) {
						overlay.destroy();
					}
				});
				tutorialLaneHighlights = [];
			}
			// Clean up fishing screen elements explicitly if they were started by tutorial
			if (fishingElements && fishingElements.boat && !fishingElements.boat.destroyed) {
				tween.stop(fishingElements.boat);
			}
			if (fishingElements && fishingElements.fishermanContainer && !fishingElements.fishermanContainer.destroyed) {
				tween.stop(fishingElements.fishermanContainer);
			}
			// Stop water surface animations
			if (fishingElements && fishingElements.waterSurfaceSegments) {
				fishingElements.waterSurfaceSegments.forEach(function (segment) {
					if (segment && !segment.destroyed) {
						tween.stop(segment);
					}
				});
			}
			showScreen('levelSelect');
			break;
	}
}
function startTutorial() {
	GameState.tutorialMode = true;
	GameState.tutorialStep = 0;
	GameState.gameActive = false;
	// Ensure fishing screen is visible and setup for tutorial
	showScreen('fishing');
	fishingScreen.alpha = 1;
	// Clear any active game elements from a previous session
	fishArray.forEach(function (f) {
		if (f && !f.destroyed) {
			f.destroy();
		}
	});
	fishArray = [];
	ImprovedRhythmSpawner.reset();
	// Clear existing UI text
	if (fishingElements.scoreText) {
		fishingElements.scoreText.setText('');
	}
	if (fishingElements.fishText) {
		fishingElements.fishText.setText('');
	}
	if (fishingElements.comboText) {
		fishingElements.comboText.setText('');
	}
	if (fishingElements.progressText) {
		fishingElements.progressText.setText('');
	}
	// CREATE LANE BRACKETS FOR TUTORIAL
	var bracketAssetHeight = 150;
	var bracketAssetWidth = 75;
	// Clear any existing brackets first
	if (laneBrackets && laneBrackets.length > 0) {
		laneBrackets.forEach(function (bracketPair) {
			if (bracketPair.left && !bracketPair.left.destroyed) {
				bracketPair.left.destroy();
			}
			if (bracketPair.right && !bracketPair.right.destroyed) {
				bracketPair.right.destroy();
			}
		});
	}
	laneBrackets = [];
	if (fishingScreen && !fishingScreen.destroyed) {
		for (var i = 0; i < GAME_CONFIG.LANES.length; i++) {
			var laneY = GAME_CONFIG.LANES[i].y;
			var leftBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				x: bracketAssetWidth / 2,
				y: laneY,
				height: bracketAssetHeight
			}));
			var rightBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				scaleX: -1,
				x: 2048 - bracketAssetWidth / 2,
				y: laneY,
				height: bracketAssetHeight
			}));
			laneBrackets.push({
				left: leftBracket,
				right: rightBracket
			});
		}
	}
	// Don't create tutorial elements here - wait for intro to finish
	// createTutorialElements() and runTutorialStep() will be called after intro
}
// This function is called from game.update when tutorialFish exists
function checkTutorialFishState() {
	var fish = GameState.tutorialFish;
	if (!fish || fish.destroyed || fish.caught || fish.missed) {
		return;
	}
	var hookX = fishingElements.hook.x;
	// Fish passes hook without interaction during catch steps (now steps 3 or 4)
	if (GameState.tutorialStep === 3 || GameState.tutorialStep === 4) {
		var passedHook = fish.speed > 0 && fish.x > hookX + GAME_CONFIG.MISS_WINDOW + fish.fishGraphics.width / 2 ||
		// fish right edge passed hook right boundary
		fish.speed < 0 && fish.x < hookX - GAME_CONFIG.MISS_WINDOW - fish.fishGraphics.width / 2; // fish left edge passed hook left boundary
		if (passedHook) {
			fish.missed = true; // Mark to prevent further attempts on this specific fish instance
			GameState.tutorialPaused = true; // Pause to show message
			setTutorialText("It got away! Tap 'CONTINUE' to try that part again.");
			// Next tap on "CONTINUE" will call runTutorialStep(), which will re-run current step.
		}
	}
	// Generic off-screen removal for tutorial fish
	if (fish.x < -250 || fish.x > 2048 + 250) {
		var wasCriticalStep = GameState.tutorialStep === 3 || GameState.tutorialStep === 4; // Adjusted step numbers
		var fishIndex = fishArray.indexOf(fish);
		if (fishIndex > -1) {
			fishArray.splice(fishIndex, 1);
		}
		fish.destroy();
		GameState.tutorialFish = null;
		if (wasCriticalStep && !fish.caught && !fish.missed) {
			// If it just went off screen without resolution
			GameState.tutorialPaused = true;
			setTutorialText("The fish swam off. Tap 'CONTINUE' to try that part again.");
		}
	}
}
var GameState = {
	// Game flow
	currentScreen: 'title',
	// 'title', 'levelSelect', 'fishing', 'results'
	// Player progression
	currentDepth: 0,
	money: 0,
	totalFishCaught: 0,
	ownedSongs: [],
	// Array of {depth, songIndex} objects
	// Level selection
	selectedDepth: 0,
	selectedSong: 0,
	// Current session
	sessionScore: 0,
	sessionFishCaught: 0,
	sessionFishSpawned: 0,
	combo: 0,
	maxCombo: 0,
	// Game state
	gameActive: false,
	songStartTime: 0,
	lastBeatTime: 0,
	beatCount: 0,
	introPlaying: false,
	// Tracks if the intro animation is currently playing
	musicNotesActive: false,
	// Tracks if music note particle system is active
	currentPlayingMusicId: 'rhythmTrack',
	// ID of the music track currently playing in a session
	currentPlayingMusicInitialVolume: 0.8,
	// Initial volume of the current music track for fade reference
	hookTargetLaneIndex: 1,
	// Start with hook targeting the middle lane (index 1)
	// Tutorial State
	tutorialMode: false,
	// Is the tutorial currently active?
	tutorialStep: 0,
	// Current step in the tutorial sequence
	tutorialPaused: false,
	// Is the tutorial paused (e.g., for text display)?
	tutorialAwaitingTap: false,
	// Is the tutorial waiting for a generic tap to continue?
	tutorialFish: null,
	// Stores the fish object used in a tutorial step
	// Initialize owned songs (first song of each unlocked depth is free)
	initOwnedSongs: function initOwnedSongs() {
		this.ownedSongs = [];
		for (var i = 0; i <= this.currentDepth; i++) {
			this.ownedSongs.push({
				depth: i,
				songIndex: 0
			});
		}
	},
	hasSong: function hasSong(depth, songIndex) {
		return this.ownedSongs.some(function (song) {
			return song.depth === depth && song.songIndex === songIndex;
		});
	},
	buySong: function buySong(depth, songIndex) {
		var song = GAME_CONFIG.DEPTHS[depth].songs[songIndex];
		if (this.money >= song.cost && !this.hasSong(depth, songIndex)) {
			this.money -= song.cost;
			this.ownedSongs.push({
				depth: depth,
				songIndex: songIndex
			});
			return true;
		}
		return false;
	},
	getCurrentDepthConfig: function getCurrentDepthConfig() {
		return GAME_CONFIG.DEPTHS[this.selectedDepth];
	},
	getCurrentSongConfig: function getCurrentSongConfig() {
		return GAME_CONFIG.DEPTHS[this.selectedDepth].songs[this.selectedSong];
	},
	canUpgrade: function canUpgrade() {
		var nextDepth = GAME_CONFIG.DEPTHS[this.currentDepth + 1];
		return nextDepth && this.money >= nextDepth.upgradeCost;
	},
	upgrade: function upgrade() {
		if (this.canUpgrade()) {
			var nextDepth = GAME_CONFIG.DEPTHS[this.currentDepth + 1];
			this.money -= nextDepth.upgradeCost;
			this.currentDepth++;
			// Give free first song of new depth
			this.ownedSongs.push({
				depth: this.currentDepth,
				songIndex: 0
			});
			return true;
		}
		return false;
	}
};
var titleScreen = game.addChild(new Container());
var levelSelectScreen = game.addChild(new Container());
var fishingScreen = game.addChild(new Container());
var resultsScreen = game.addChild(new Container());
// Initialize
GameState.initOwnedSongs();
/**** 
* Title Screen
****/ 
/**** 
* Title Screen - FIXED VERSION
****/ 
function createTitleScreen() {
	var titleBg = titleScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		alpha: 1,
		height: 2732,
		color: 0x87CEEB
	}));
	// ANIMATED CONTAINER GROUP - This will contain ALL visual elements including backgrounds
	var titleAnimationGroup = titleScreen.addChild(new Container());
	// Sky background image - NOW INSIDE animation group
	var titleSky = titleAnimationGroup.addChild(LK.getAsset('skybackground', {
		x: 0,
		y: -500
	}));
	// Water background image - NOW INSIDE animation group
	var titleWater = titleAnimationGroup.addChild(LK.getAsset('water', {
		x: 0,
		y: GAME_CONFIG.WATER_SURFACE_Y,
		width: 2048,
		height: 2732 - GAME_CONFIG.WATER_SURFACE_Y
	}));
	// Initialize containers for title screen ambient particles - INSIDE animation group
	titleScreenOceanBubbleContainer = titleAnimationGroup.addChild(new Container());
	titleScreenSeaweedContainer = titleAnimationGroup.addChild(new Container());
	titleScreenCloudContainer = titleAnimationGroup.addChild(new Container());
	// Create a single container for boat, fisherman, and line that all move together
	var titleBoatGroup = titleAnimationGroup.addChild(new Container());
	// Boat positioned at origin of the group
	var titleBoat = titleBoatGroup.addChild(LK.getAsset('boat', {
		anchorX: 0.5,
		anchorY: 0.74,
		x: 0,
		// Relative to group
		y: 0 // Relative to group
	}));
	// Fisherman positioned relative to boat within the same group
	var titleFisherman = titleBoatGroup.addChild(LK.getAsset('fisherman', {
		anchorX: 0.5,
		anchorY: 1,
		x: -100,
		// Relative to boat position
		y: -70 // Relative to boat position
	}));
	// Fishing line positioned relative to boat within the same group
	var rodTipX = -100 + 85; // fisherman offset + rod offset from fisherman center
	var rodTipY = -70 - 200; // fisherman y (feet) - fisherman height (to get to head area for rod)
	// initialHookY needs to be relative to the group's origin (which is boat's origin at WATER_SURFACE_Y)
	// GAME_CONFIG.LANES[1].y is an absolute world Y.
	// titleBoatGroup.y is GAME_CONFIG.WATER_SURFACE_Y.
	// So, hook's Y relative to group = absolute hook Y - group's absolute Y
	var initialHookYInGroup = GAME_CONFIG.LANES[1].y - GAME_CONFIG.WATER_SURFACE_Y;
	var titleLine = titleBoatGroup.addChild(LK.getAsset('fishingLine', {
		anchorX: 0.5,
		anchorY: 0,
		x: rodTipX,
		y: rodTipY,
		// Relative to group
		width: 6,
		height: initialHookYInGroup - rodTipY // Length from rod tip to hook, all relative to group
	}));
	var titleHook = titleBoatGroup.addChild(LK.getAsset('hook', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: rodTipX,
		// Hook X matches line X initially
		y: initialHookYInGroup // Relative to group
	}));
	// Position the entire group in the world (within titleAnimationGroup)
	titleBoatGroup.x = GAME_CONFIG.SCREEN_CENTER_X;
	titleBoatGroup.y = GAME_CONFIG.WATER_SURFACE_Y;
	// Store base position for wave animation
	var boatGroupBaseY = titleBoatGroup.y; // This is the group's world Y
	var boatWaveAmplitude = 10;
	var boatWaveHalfCycleDuration = 2000;
	var boatRotationAmplitude = 0.03;
	var boatRotationDuration = 3000;
	// Wave animation variables for line (used in updateTitleFishingLineWave)
	var lineWaveAmplitude = 12;
	var lineWaveSpeed = 0.03;
	var linePhaseOffset = 0;
	// Animated Water Surface segments - INSIDE animation group
	var titleWaterSurfaceSegments = [];
	var NUM_WAVE_SEGMENTS_TITLE = 32;
	var SEGMENT_WIDTH_TITLE = 2048 / NUM_WAVE_SEGMENTS_TITLE;
	var SEGMENT_HEIGHT_TITLE = 24;
	var WAVE_AMPLITUDE_TITLE = 12;
	var WAVE_HALF_PERIOD_MS_TITLE = 2500;
	var PHASE_DELAY_MS_PER_SEGMENT_TITLE = WAVE_HALF_PERIOD_MS_TITLE * 2 / NUM_WAVE_SEGMENTS_TITLE;
	// Create water surface segments
	for (var i = 0; i < NUM_WAVE_SEGMENTS_TITLE; i++) {
		var segment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH_TITLE,
			y: GAME_CONFIG.WATER_SURFACE_Y,
			width: SEGMENT_WIDTH_TITLE + 1,
			height: SEGMENT_HEIGHT_TITLE,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.8,
			tint: 0x4fc3f7
		});
		segment.baseY = GAME_CONFIG.WATER_SURFACE_Y;
		titleAnimationGroup.addChild(segment); // Water surface is part of main animation group, not boat group
		titleWaterSurfaceSegments.push(segment);
	}
	for (var i = 0; i < NUM_WAVE_SEGMENTS_TITLE; i++) {
		var whiteSegment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH_TITLE,
			y: GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT_TITLE / 2,
			width: SEGMENT_WIDTH_TITLE + 1,
			height: SEGMENT_HEIGHT_TITLE / 2,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.6,
			tint: 0xffffff
		});
		whiteSegment.baseY = GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT_TITLE / 2;
		titleAnimationGroup.addChild(whiteSegment); // Water surface is part of main animation group
		titleWaterSurfaceSegments.push(whiteSegment);
	}
	// Animation group setup for zoom effect - PROPERLY CENTER THE BOAT ON SCREEN
	var boatCenterX = GAME_CONFIG.SCREEN_CENTER_X; // 1024
	var targetBoatScreenY = GAME_CONFIG.SCREEN_CENTER_Y + 300; // Change +100 to move boat lower/higher
	var boatWorldY = GAME_CONFIG.WATER_SURFACE_Y; // 760 - where titleBoatGroup actually is
	var pivotY = boatWorldY - (targetBoatScreenY - boatWorldY); // Calculate proper pivot offset
	// Set pivot to calculated position and position group so boat ends up at screen center
	titleAnimationGroup.pivot.set(boatCenterX, pivotY);
	titleAnimationGroup.x = GAME_CONFIG.SCREEN_CENTER_X; // 1024
	titleAnimationGroup.y = GAME_CONFIG.SCREEN_CENTER_Y; // 1366 - this will center the boat at targetBoatScreenY
	// Initial zoom state
	var INITIAL_ZOOM_FACTOR = 3.0;
	var FINAL_ZOOM_FACTOR = 1.8; // This matches existing logic if it's used in showScreen
	titleAnimationGroup.scale.set(INITIAL_ZOOM_FACTOR);
	titleAnimationGroup.alpha = 1; // Main animation group is always visible
	// Single wave animation function - moves entire group together
	var targetUpY = boatGroupBaseY - boatWaveAmplitude; // boatGroupBaseY is absolute world Y
	var targetDownY = boatGroupBaseY + boatWaveAmplitude; // boatGroupBaseY is absolute world Y
	function moveTitleBoatGroupUp() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		// We tween the group's world Y position
		tween(titleBoatGroup, {
			y: targetUpY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveTitleBoatGroupDown
		});
	}
	function moveTitleBoatGroupDown() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		tween(titleBoatGroup, {
			y: targetDownY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveTitleBoatGroupUp
		});
	}
	// Single rotation animation - rotates entire group together
	function rockTitleBoatGroupLeft() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		tween(titleBoatGroup, {
			rotation: -boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockTitleBoatGroupRight
		});
	}
	function rockTitleBoatGroupRight() {
		if (!titleBoatGroup || titleBoatGroup.destroyed) {
			return;
		}
		tween(titleBoatGroup, {
			rotation: boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockTitleBoatGroupLeft
		});
	}
	// Simplified line wave function - just the sway effect
	function updateTitleFishingLineWave() {
		// titleLine and titleHook are children of titleBoatGroup, their x/y are relative to it.
		// rodTipX, rodTipY, initialHookYInGroup are also relative to titleBoatGroup.
		if (!titleLine || titleLine.destroyed || !titleHook || titleHook.destroyed) {
			return;
		}
		linePhaseOffset += lineWaveSpeed;
		var waveOffset = Math.sin(linePhaseOffset) * lineWaveAmplitude;
		// Only apply the wave sway to X, positions stay relative to group's origin
		// rodTipX is the line's base X relative to the group.
		titleLine.x = rodTipX + waveOffset * 0.3;
		titleHook.x = rodTipX + waveOffset;
		// Y positions of line and hook (titleLine.y and titleHook.y) are static relative to the group.
		// Recalculate line rotation based on hook position relative to line's anchor
		// All coordinates here are relative to titleBoatGroup.
		var deltaX = titleHook.x - titleLine.x; // Difference in x relative to group
		var deltaY = titleHook.y - titleLine.y; // Difference in y relative to group
		var actualLineLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
		titleLine.height = actualLineLength;
		if (actualLineLength > 0.001) {
			titleLine.rotation = Math.atan2(deltaY, deltaX) - Math.PI / 2;
		} else {
			titleLine.rotation = 0;
		}
		titleHook.rotation = titleLine.rotation; // Hook rotation matches line's
	}
	// Water surface animation function
	function startTitleWaterSurfaceAnimationFunc() {
		for (var k = 0; k < titleWaterSurfaceSegments.length; k++) {
			var segment = titleWaterSurfaceSegments[k];
			if (!segment || segment.destroyed) {
				continue;
			}
			var segmentIndexForDelay = k % NUM_WAVE_SEGMENTS_TITLE;
			(function (currentLocalSegment, currentLocalSegmentIndexForDelay) {
				var animUp, animDown;
				animDown = function animDown() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY + WAVE_AMPLITUDE_TITLE
					}, {
						duration: WAVE_HALF_PERIOD_MS_TITLE,
						easing: tween.easeInOut,
						onFinish: animUp
					});
				};
				animUp = function animUp() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY - WAVE_AMPLITUDE_TITLE
					}, {
						duration: WAVE_HALF_PERIOD_MS_TITLE,
						easing: tween.easeInOut,
						onFinish: animDown
					});
				};
				LK.setTimeout(function () {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					// Start with DOWN movement first
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY + WAVE_AMPLITUDE_TITLE
					}, {
						duration: WAVE_HALF_PERIOD_MS_TITLE,
						easing: tween.easeInOut,
						onFinish: animUp // This should call animUp after going down
					});
				}, currentLocalSegmentIndexForDelay * PHASE_DELAY_MS_PER_SEGMENT_TITLE);
			})(segment, segmentIndexForDelay);
		}
	}
	// BLACK OVERLAY for reveal effect - this goes OVER everything
	var blackOverlay = titleScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		width: 2048,
		height: 2732,
		color: 0x000000,
		// Pure black
		alpha: 1 // Starts fully opaque
	}));
	// UI elements - OUTSIDE animation group so they don't zoom, ABOVE black overlay
	var titleImage = titleScreen.addChild(LK.getAsset('titleimage', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 700,
		// Positioned where the subtitle roughly was, adjust as needed
		alpha: 0,
		scaleX: 0.8,
		// Adjust scale as needed for optimal size
		scaleY: 0.8 // Adjust scale as needed for optimal size
	}));
	// Buttons - OUTSIDE animation group, ABOVE black overlay
	// New Y positions: 1/3 from bottom of screen (2732 / 3 = 910.66. Y = 2732 - 910.66 = 1821.33)
	var startButtonY = 2732 - 2732 / 3.5; // Approx 1821
	var tutorialButtonY = startButtonY + 600; // Maintain 150px gap
	var startButton = titleScreen.addChild(LK.getAsset('startbutton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: startButtonY,
		alpha: 0
	}));
	var tutorialButton = titleScreen.addChild(LK.getAsset('tutorialbutton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: tutorialButtonY,
		alpha: 0
	}));
	// No assignment here; assign tutorialButtonGfx after createTitleScreen returns
	return {
		startButton: startButton,
		tutorialButton: tutorialButton,
		// This is the graphics object
		titleImage: titleImage,
		titleAnimationGroup: titleAnimationGroup,
		blackOverlay: blackOverlay,
		// Return the group and its specific animation functions
		titleBoatGroup: titleBoatGroup,
		moveTitleBoatGroupUp: moveTitleBoatGroupUp,
		rockTitleBoatGroupLeft: rockTitleBoatGroupLeft,
		// Individual elements that might still be needed for direct access or other effects
		titleSky: titleSky,
		// Sky is not in titleBoatGroup
		titleWater: titleWater,
		// Water background is not in titleBoatGroup
		titleWaterSurfaceSegments: titleWaterSurfaceSegments,
		// Water surface segments are not in titleBoatGroup
		// Line and hook are part of titleBoatGroup, but if direct reference is needed for some reason, they could be returned.
		// However, following the goal's spirit of "Return the group instead of individual elements [that are part of it]"
		// titleLine and titleHook might be omitted here unless specifically needed by other parts of the code.
		// For now, including them if they were returned before and are still locally defined.
		// updateTitleFishingLineWave accesses titleLine and titleHook locally.
		titleLine: titleLine,
		// Still defined locally, might be useful for direct reference
		titleHook: titleHook,
		// Still defined locally
		startTitleWaterSurfaceAnimation: startTitleWaterSurfaceAnimationFunc,
		updateTitleFishingLineWave: updateTitleFishingLineWave // The new simplified version
	};
}
/**** 
* Level Select Screen
****/ 
function createLevelSelectScreen() {
	var selectBg = levelSelectScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		alpha: 0.8,
		height: 2732
	}));
	// Top section - Title, Money, and Back button
	var title = new Text2('SELECT FISHING SPOT', {
		size: 90,
		// Increased from 80
		fill: 0xFFFFFF
	});
	title.anchor.set(0.5, 0.5);
	title.x = GAME_CONFIG.SCREEN_CENTER_X;
	title.y = 180; // Moved up slightly
	levelSelectScreen.addChild(title);
	// Money display (keep at top)
	var moneyDisplay = new Text2('Money: $0', {
		size: 70,
		// Increased from 60
		fill: 0xFFD700
	});
	moneyDisplay.anchor.set(1, 0);
	moneyDisplay.x = 1900;
	moneyDisplay.y = 80; // Moved up slightly
	levelSelectScreen.addChild(moneyDisplay);
	// Back button (moved to bottom center)
	var backButton = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 2732 - 300,
		// Positioned 100px from the bottom edge (center of button)
		tint: 0x757575
	}));
	var backButtonText = new Text2('BACK', {
		size: 45,
		// Increased from 40
		fill: 0xFFFFFF
	});
	backButtonText.anchor.set(0.5, 0.5);
	backButtonText.x = backButton.x;
	backButtonText.y = backButton.y;
	levelSelectScreen.addChild(backButtonText);
	// Depth tabs (moved down and spaced out more)
	var depthTabs = [];
	// Song display area (moved to center of screen)
	var songCard = levelSelectScreen.addChild(LK.getAsset('songCard', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1100,
		// Moved down significantly from 700
		width: 900,
		// Made wider
		height: 300 // Made taller
	}));
	// Song navigation arrows (repositioned around the larger song card)
	var leftArrow = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 350,
		// Moved further left
		y: 1100,
		// Moved down with song card
		tint: 0x666666,
		width: 120,
		// Made bigger
		height: 120
	}));
	var leftArrowText = new Text2('<', {
		size: 80,
		// Increased from 60
		fill: 0xFFFFFF
	});
	leftArrowText.anchor.set(0.5, 0.5);
	leftArrowText.x = 350;
	leftArrowText.y = 1100;
	levelSelectScreen.addChild(leftArrowText);
	var rightArrow = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 1698,
		// Moved further right
		y: 1100,
		// Moved down with song card
		tint: 0x666666,
		width: 120,
		// Made bigger
		height: 120
	}));
	var rightArrowText = new Text2('>', {
		size: 80,
		// Increased from 60
		fill: 0xFFFFFF
	});
	rightArrowText.anchor.set(0.5, 0.5);
	rightArrowText.x = 1698;
	rightArrowText.y = 1100;
	levelSelectScreen.addChild(rightArrowText);
	// Song info (repositioned within the larger song card area)
	var songTitle = new Text2('Song Title', {
		size: 60,
		// Increased from 50
		fill: 0xFFFFFF
	});
	songTitle.anchor.set(0.5, 0.5);
	songTitle.x = GAME_CONFIG.SCREEN_CENTER_X;
	songTitle.y = 1020; // Positioned above song card center
	levelSelectScreen.addChild(songTitle);
	var songInfo = new Text2('BPM: 120 | Duration: 2:00', {
		size: 40,
		// Increased from 30
		fill: 0xCCCCCC
	});
	songInfo.anchor.set(0.5, 0.5);
	songInfo.x = GAME_CONFIG.SCREEN_CENTER_X;
	songInfo.y = 1100; // Centered in song card
	levelSelectScreen.addChild(songInfo);
	var songEarnings = new Text2('Potential Earnings: $50-100', {
		size: 40,
		// Increased from 30
		fill: 0x4CAF50
	});
	songEarnings.anchor.set(0.5, 0.5);
	songEarnings.x = GAME_CONFIG.SCREEN_CENTER_X;
	songEarnings.y = 1180; // Positioned below song card center
	levelSelectScreen.addChild(songEarnings);
	// Play/Buy button (moved down and made larger)
	var playButton = levelSelectScreen.addChild(LK.getAsset('bigButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1400,
		// Moved down significantly from 900
		width: 500,
		// Made wider
		height: 130 // Made taller
	}));
	var playButtonText = new Text2('PLAY', {
		size: 60,
		// Increased from 50
		fill: 0xFFFFFF
	});
	playButtonText.anchor.set(0.5, 0.5);
	playButtonText.x = GAME_CONFIG.SCREEN_CENTER_X;
	playButtonText.y = 1400;
	levelSelectScreen.addChild(playButtonText);
	// Shop button (moved to bottom and made larger)
	var shopButton = levelSelectScreen.addChild(LK.getAsset('button', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1650,
		// Moved down significantly from 1100
		tint: 0x666666,
		// Grayed out since it's not available
		width: 450,
		// Made wider
		height: 120 // Made taller
	}));
	var shopButtonText = new Text2('UPGRADE ROD', {
		size: 50,
		// Increased from 40
		fill: 0xFFFFFF
	});
	shopButtonText.anchor.set(0.5, 0.5);
	shopButtonText.x = GAME_CONFIG.SCREEN_CENTER_X;
	shopButtonText.y = 1650;
	levelSelectScreen.addChild(shopButtonText);
	// Add "Coming soon!" text underneath
	var comingSoonText = new Text2('Coming soon!', {
		size: 35,
		fill: 0xFFD700 // Gold color to make it stand out
	});
	comingSoonText.anchor.set(0.5, 0.5);
	comingSoonText.x = GAME_CONFIG.SCREEN_CENTER_X;
	comingSoonText.y = 1730; // Position below the button
	levelSelectScreen.addChild(comingSoonText);
	return {
		moneyDisplay: moneyDisplay,
		depthTabs: depthTabs,
		leftArrow: leftArrow,
		rightArrow: rightArrow,
		songTitle: songTitle,
		songInfo: songInfo,
		songEarnings: songEarnings,
		playButton: playButton,
		playButtonText: playButtonText,
		shopButton: shopButton,
		shopButtonText: shopButtonText,
		backButton: backButton
	};
}
/**** 
* Fishing Screen
****/ 
function createFishingScreen() {
	// Sky background - should be added first to be behind everything else
	var sky = fishingScreen.addChild(LK.getAsset('skybackground', {
		x: 0,
		y: -500
	}));
	// Water background
	var water = fishingScreen.addChild(LK.getAsset('water', {
		x: 0,
		y: GAME_CONFIG.WATER_SURFACE_Y,
		width: 2048,
		height: 2732 - GAME_CONFIG.WATER_SURFACE_Y
	}));
	// Create a container for ambient ocean bubbles (from bottom of screen)
	// This should be layered above the 'water' background, but below fish, boat, etc.
	globalOceanBubbleContainer = fishingScreen.addChild(new Container());
	// Create a container for seaweed particles
	globalSeaweedContainer = fishingScreen.addChild(new Container());
	// Create a container for cloud particles (added early so clouds appear behind UI)
	globalCloudContainer = fishingScreen.addChild(new Container());
	// Create a container for bubbles to render them behind fish and other elements
	bubbleContainer = fishingScreen.addChild(new Container());
	// Create a container for music notes
	musicNotesContainer = fishingScreen.addChild(new Container());
	// Music notes should visually appear to come from the boat area, so their container
	// should ideally be layered accordingly. Adding it here means it's on top of water,
	// but if boat/fisherman are added later, notes might appear behind them if not managed.
	// For now, notes will be added to this container, which itself is added to fishingScreen.
	// Animated Water Surface segments code
	var waterSurfaceSegments = []; // This will be populated for returning and cleanup
	var waterSurfaceSegmentsBlueTemp = []; // Temporary array for blue segments
	var waterSurfaceSegmentsWhiteTemp = []; // Temporary array for white segments
	var NUM_WAVE_SEGMENTS = 32;
	var SEGMENT_WIDTH = 2048 / NUM_WAVE_SEGMENTS;
	var SEGMENT_HEIGHT = 24;
	var WAVE_AMPLITUDE = 12;
	var WAVE_HALF_PERIOD_MS = 2500;
	var PHASE_DELAY_MS_PER_SEGMENT = WAVE_HALF_PERIOD_MS * 2 / NUM_WAVE_SEGMENTS;
	// Create blue segments (assets only, not added to fishingScreen yet)
	for (var i = 0; i < NUM_WAVE_SEGMENTS; i++) {
		var segment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH,
			y: GAME_CONFIG.WATER_SURFACE_Y,
			width: SEGMENT_WIDTH + 1,
			height: SEGMENT_HEIGHT,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.8,
			tint: 0x4fc3f7
		});
		segment.baseY = GAME_CONFIG.WATER_SURFACE_Y;
		// Animation functions will be defined and started by startWaterSurfaceAnimationFunc
		waterSurfaceSegmentsBlueTemp.push(segment);
	}
	// Create white segments (assets only, not added to fishingScreen yet)
	for (var i = 0; i < NUM_WAVE_SEGMENTS; i++) {
		var whiteSegment = LK.getAsset('waterSurface', {
			x: i * SEGMENT_WIDTH,
			y: GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT / 2,
			width: SEGMENT_WIDTH + 1,
			height: SEGMENT_HEIGHT / 2,
			anchorX: 0,
			anchorY: 0.5,
			alpha: 0.6,
			tint: 0xffffff
		});
		whiteSegment.baseY = GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT / 2;
		// Animation functions will be defined and started by startWaterSurfaceAnimationFunc
		waterSurfaceSegmentsWhiteTemp.push(whiteSegment);
	}
	// Boat - Add this to fishingScreen first
	var boat = fishingScreen.addChild(LK.getAsset('boat', {
		anchorX: 0.5,
		anchorY: 0.74,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: GAME_CONFIG.WATER_SURFACE_Y
	}));
	// Now add the water segments to fishingScreen, so they render on top of the boat
	for (var i = 0; i < waterSurfaceSegmentsBlueTemp.length; i++) {
		fishingScreen.addChild(waterSurfaceSegmentsBlueTemp[i]);
		waterSurfaceSegments.push(waterSurfaceSegmentsBlueTemp[i]); // Also add to the main array for cleanup
	}
	for (var i = 0; i < waterSurfaceSegmentsWhiteTemp.length; i++) {
		fishingScreen.addChild(waterSurfaceSegmentsWhiteTemp[i]);
		waterSurfaceSegments.push(waterSurfaceSegmentsWhiteTemp[i]); // Also add to the main array for cleanup
	}
	// Create separate fisherman container that will sync with boat movement
	var fishermanContainer = fishingScreen.addChild(new Container());
	// Fisherman (now in its own container, positioned to match boat)
	var fisherman = fishermanContainer.addChild(LK.getAsset('fisherman', {
		anchorX: 0.5,
		anchorY: 1,
		x: GAME_CONFIG.SCREEN_CENTER_X - 100,
		y: GAME_CONFIG.WATER_SURFACE_Y - 70
	}));
	// Store references for wave animation sync
	var boatBaseY = boat.y;
	var fishermanBaseY = fishermanContainer.y;
	var boatWaveAmplitude = 10;
	var boatWaveHalfCycleDuration = 2000;
	// SINGLE ANIMATED FISHING LINE
	var initialHookY = GAME_CONFIG.LANES[1].y;
	var fishingLineStartY = -100;
	var line = fishingScreen.addChild(LK.getAsset('fishingLine', {
		anchorX: 0.5,
		anchorY: 0,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: GAME_CONFIG.WATER_SURFACE_Y + fishingLineStartY,
		width: 6,
		height: initialHookY - (GAME_CONFIG.WATER_SURFACE_Y + fishingLineStartY)
	}));
	var hook = fishingScreen.addChild(LK.getAsset('hook', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: initialHookY
	}));
	hook.originalY = initialHookY;
	var lineWaveAmplitude = 12;
	var lineWaveSpeed = 0.03;
	var linePhaseOffset = 0;
	function updateFishingLineWave() {
		linePhaseOffset += lineWaveSpeed;
		var rodTipX = fishermanContainer.x + fisherman.x + 85;
		var rodTipY = fishermanContainer.y + fisherman.y - fisherman.height;
		var waveOffset = Math.sin(linePhaseOffset) * lineWaveAmplitude;
		line.x = rodTipX + waveOffset * 0.3;
		line.y = rodTipY;
		hook.x = rodTipX + waveOffset;
		var hookAttachX = hook.x;
		var hookAttachY = hook.y - hook.height / 2;
		var deltaX = hookAttachX - line.x;
		var deltaY = hookAttachY - line.y;
		var actualLineLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
		line.height = actualLineLength;
		if (actualLineLength > 0.001) {
			line.rotation = Math.atan2(deltaY, deltaX) - Math.PI / 2;
		} else {
			line.rotation = 0;
		}
		hook.rotation = line.rotation;
	}
	// Calculate target positions for boat wave animation
	var targetUpY = boatBaseY - boatWaveAmplitude;
	var targetDownY = boatBaseY + boatWaveAmplitude;
	var fishermanTargetUpY = fishermanBaseY - boatWaveAmplitude;
	var fishermanTargetDownY = fishermanBaseY + boatWaveAmplitude;
	// Synchronized wave animation functions (defined here to be closured)
	function moveBoatAndFishermanUp() {
		if (!boat || boat.destroyed || !fishermanContainer || fishermanContainer.destroyed) {
			return;
		}
		tween(boat, {
			y: targetUpY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveBoatAndFishermanDown
		});
		tween(fishermanContainer, {
			y: fishermanTargetUpY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut
		});
	}
	function moveBoatAndFishermanDown() {
		if (!boat || boat.destroyed || !fishermanContainer || fishermanContainer.destroyed) {
			return;
		}
		tween(boat, {
			y: targetDownY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut,
			onFinish: moveBoatAndFishermanUp
		});
		tween(fishermanContainer, {
			y: fishermanTargetDownY
		}, {
			duration: boatWaveHalfCycleDuration,
			easing: tween.easeInOut
		});
	}
	var boatRotationAmplitude = 0.03;
	var boatRotationDuration = 3000;
	function rockBoatLeft() {
		if (!boat || boat.destroyed || !fisherman || fisherman.destroyed) {
			return;
		}
		tween(boat, {
			rotation: -boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockBoatRight
		});
		tween(fisherman, {
			rotation: boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut
		});
	}
	function rockBoatRight() {
		if (!boat || boat.destroyed || !fisherman || fisherman.destroyed) {
			return;
		}
		tween(boat, {
			rotation: boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut,
			onFinish: rockBoatLeft
		});
		tween(fisherman, {
			rotation: -boatRotationAmplitude
		}, {
			duration: boatRotationDuration,
			easing: tween.easeInOut
		});
	}
	// Function to start/restart water surface animations
	function startWaterSurfaceAnimationFunc() {
		var allSegments = waterSurfaceSegments; // Use the populated array from fishingElements via closure
		for (var k = 0; k < allSegments.length; k++) {
			var segment = allSegments[k];
			if (!segment || segment.destroyed) {
				continue;
			}
			var segmentIndexForDelay = k % NUM_WAVE_SEGMENTS;
			(function (currentLocalSegment, currentLocalSegmentIndexForDelay) {
				var animUp, animDown;
				animDown = function animDown() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY + WAVE_AMPLITUDE
					}, {
						duration: WAVE_HALF_PERIOD_MS,
						easing: tween.easeInOut,
						onFinish: animUp
					});
				};
				animUp = function animUp() {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY - WAVE_AMPLITUDE
					}, {
						duration: WAVE_HALF_PERIOD_MS,
						easing: tween.easeInOut,
						onFinish: animDown
					});
				};
				LK.setTimeout(function () {
					if (!currentLocalSegment || currentLocalSegment.destroyed) {
						return;
					}
					tween(currentLocalSegment, {
						y: currentLocalSegment.baseY - WAVE_AMPLITUDE
					}, {
						// Initial move up
						duration: WAVE_HALF_PERIOD_MS,
						easing: tween.easeInOut,
						onFinish: animDown
					});
				}, currentLocalSegmentIndexForDelay * PHASE_DELAY_MS_PER_SEGMENT);
			})(segment, segmentIndexForDelay);
		}
	}
	// Function to start/restart boat and fisherman animations
	function startBoatAndFishermanAnimationFunc() {
		if (boat && !boat.destroyed && fishermanContainer && !fishermanContainer.destroyed) {
			tween(boat, {
				y: targetUpY
			}, {
				duration: boatWaveHalfCycleDuration / 2,
				easing: tween.easeOut,
				onFinish: moveBoatAndFishermanDown
			});
			tween(fishermanContainer, {
				y: fishermanTargetUpY
			}, {
				duration: boatWaveHalfCycleDuration / 2,
				easing: tween.easeOut
			});
			rockBoatLeft();
		}
	}
	// UI elements (from existing)
	var scoreText = new Text2('Score: 0', {
		size: 70,
		fill: 0xFFFFFF
	});
	scoreText.anchor.set(1, 0);
	scoreText.x = 2048 - 50;
	scoreText.y = 50;
	fishingScreen.addChild(scoreText);
	var fishText = new Text2('Fish: 0/0', {
		size: 55,
		fill: 0xFFFFFF
	});
	fishText.anchor.set(1, 0);
	fishText.x = 2048 - 50;
	fishText.y = 140;
	fishingScreen.addChild(fishText);
	var comboText = new Text2('Combo: 0', {
		size: 55,
		fill: 0xFF9800
	});
	comboText.anchor.set(1, 0);
	comboText.x = 2048 - 50;
	comboText.y = 210;
	fishingScreen.addChild(comboText);
	var progressText = new Text2('0:00 / 0:00', {
		size: 50,
		fill: 0x4FC3F7
	});
	progressText.anchor.set(1, 0);
	progressText.x = 2048 - 50;
	progressText.y = 280;
	fishingScreen.addChild(progressText);
	return {
		boat: boat,
		fishermanContainer: fishermanContainer,
		fisherman: fisherman,
		hook: hook,
		line: line,
		updateFishingLineWave: updateFishingLineWave,
		scoreText: scoreText,
		fishText: fishText,
		comboText: comboText,
		progressText: progressText,
		waterSurfaceSegments: waterSurfaceSegments,
		bubbleContainer: bubbleContainer,
		musicNotesContainer: musicNotesContainer,
		startWaterSurfaceAnimation: startWaterSurfaceAnimationFunc,
		startBoatAndFishermanAnimation: startBoatAndFishermanAnimationFunc
	};
}
/**** 
* Initialize Screen Elements
****/ 
var titleElements = createTitleScreen();
titleElements.tutorialButtonGfx = titleElements.tutorialButton; // Store the graphical button for compatibility
var levelSelectElements = createLevelSelectScreen();
var fishingElements = createFishingScreen();
// Tutorial UI Elements - MUST be at global scope
var tutorialOverlayContainer = game.addChild(new Container());
tutorialOverlayContainer.visible = false;
var tutorialTextBackground;
var tutorialTextDisplay;
var tutorialContinueButton;
var tutorialContinueText;
var tutorialLaneHighlights = []; // To store lane highlight graphics for the tutorial
// Feedback indicators are now created on-demand by the showFeedback function.
// The global feedbackIndicators object is no longer needed.
// Game variables
var fishArray = [];
var bubblesArray = [];
var bubbleContainer; // Container for bubbles, initialized in createFishingScreen
var musicNotesArray = [];
var musicNotesContainer; // Container for music notes
var laneBrackets = []; // Stores the visual bracket pairs for each lane
var musicNoteSpawnCounter = 0;
var MUSIC_NOTE_SPAWN_INTERVAL_TICKS = 45; // Spawn a note roughly every 0.75 seconds
// Ocean Bubbles (ambient background)
var globalOceanBubblesArray = [];
var globalOceanBubbleContainer;
var globalOceanBubbleSpawnCounter = 0;
// Increase interval to reduce amount (higher = less frequent)
var OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS = 40; // Spawn new ocean bubbles roughly every 2/3 second (was 20)
// Seaweed particles (ambient background)
var globalSeaweedArray = [];
var globalSeaweedContainer;
var globalSeaweedSpawnCounter = 0;
var SEAWEED_SPAWN_INTERVAL_TICKS = 120; // Spawn seaweed less frequently than bubbles
var MAX_SEAWEED_COUNT = 8; // Maximum number of seaweed particles at once
// Cloud particles (ambient sky)
var globalCloudArray = [];
var globalCloudContainer;
var globalCloudSpawnCounter = 0;
var CLOUD_SPAWN_INTERVAL_TICKS = 180; // Spawn clouds less frequently than seaweed
var MAX_CLOUD_COUNT = 5; // Maximum number of cloud particles at once
// Title Screen Ambient Particle Systems
var titleScreenOceanBubblesArray = [];
var titleScreenOceanBubbleContainer; // Will be initialized in createTitleScreen
var titleScreenOceanBubbleSpawnCounter = 0;
var titleScreenSeaweedArray = [];
var titleScreenSeaweedContainer; // Will be initialized in createTitleScreen
var titleScreenSeaweedSpawnCounter = 0;
var titleScreenCloudArray = [];
var titleScreenCloudContainer; // Will be initialized in createTitleScreen
var titleScreenCloudSpawnCounter = 0;
// Timers for title screen ambient sounds
var titleSeagullSoundTimer = null;
var titleBoatSoundTimer = null;
/**** 
* Input State and Helpers for Fishing
****/ 
var inputState = {
	touching: false,
	// Is the screen currently being touched?
	touchLane: -1,
	// Which lane was the touch initiated in? (0, 1, 2)
	touchStartTime: 0 // Timestamp of when the touch started (LK.ticks based)
};
// Helper function to determine which lane a Y coordinate falls into
function getTouchLane(y) {
	// Define boundaries based on the midpoints between lane Y coordinates
	// These are calculated from GAME_CONFIG.LANES[i].y values
	// Lane 0: y = 723
	// Lane 1: y = 1366
	// Lane 2: y = 2009
	var boundary_lane0_lane1 = (GAME_CONFIG.LANES[0].y + GAME_CONFIG.LANES[1].y) / 2; // Approx 1044.5
	var boundary_lane1_lane2 = (GAME_CONFIG.LANES[1].y + GAME_CONFIG.LANES[2].y) / 2; // Approx 1687.5
	if (y < boundary_lane0_lane1) {
		return 0; // Top lane (e.g., shallow)
	} else if (y < boundary_lane1_lane2) {
		return 1; // Middle lane (e.g., medium)
	} else {
		return 2; // Bottom lane (e.g., deep)
	}
}
// Shows feedback (perfect, good, miss) at the specified lane
// Shows feedback (perfect, good, miss) at the specified lane
function showFeedback(type, laneIndex) {
	var feedbackY = GAME_CONFIG.LANES[laneIndex].y;
	var indicator = new FeedbackIndicator(type); // Creates a new indicator e.g. FeedbackIndicator('perfect')
	// Position feedback at the single hook's X coordinate and the fish's lane Y
	indicator.x = fishingElements.hook.x; // Use the single hook's X
	indicator.y = feedbackY; // Feedback appears at the fish's lane Y
	fishingScreen.addChild(indicator);
	indicator.show(); // Triggers the animation and self-destruction
}
// Animates the hook in a specific lane after a catch attempt
// Animates the single hook after a catch attempt
function animateHookCatch() {
	var hook = fishingElements.hook;
	// We need a stable originalY. The hook.originalY might change if we re-assign it during tweens.
	// Let's use the target Y of the current fish lane for the "resting" position after animation.
	var restingY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	// Quick bobbing animation for the single hook
	tween(hook, {
		y: restingY - 30
	}, {
		duration: 150,
		easing: tween.easeOut,
		onFinish: function onFinish() {
			tween(hook, {
				y: restingY
			}, {
				duration: 150,
				easing: tween.easeIn,
				onFinish: function onFinish() {
					// Ensure originalY reflects the current target lane after animation.
					hook.originalY = restingY;
				}
			});
		}
	});
}
// Handles input specifically for the fishing screen (down and up events)
function handleFishingInput(x, y, isDown) {
	// If in tutorial mode, and it's a 'down' event during an active catch step (now steps 3 or 4)
	if (GameState.tutorialMode && isDown && (GameState.tutorialStep === 3 || GameState.tutorialStep === 4) && !GameState.tutorialPaused) {
		if (GameState.tutorialFish && !GameState.tutorialFish.caught && !GameState.tutorialFish.missed) {
			checkCatch(getTouchLane(y)); // Attempt catch
		}
		return; // Tutorial catch handled
	}
	// Existing game active check
	if (!GameState.gameActive) {
		return;
	}
	var currentTime = LK.ticks * (1000 / 60); // Current time in ms
	if (isDown) {
		// Touch started
		inputState.touching = true;
		inputState.touchLane = getTouchLane(y);
		inputState.touchStartTime = currentTime;
		// A normal tap action will be processed on 'up'.
	} else {
		// Touch ended (isUp)
		if (inputState.touching) {
			// This was a normal tap.
			checkCatch(inputState.touchLane);
		}
		inputState.touching = false;
	}
}
/**** 
* Screen Management
****/ 
function showScreen(screenName) {
	titleScreen.visible = false;
	levelSelectScreen.visible = false;
	fishingScreen.visible = false;
	resultsScreen.visible = false;
	// Stop any ongoing tweens and clear particles if switching FROM title screen
	if (GameState.currentScreen === 'title' && titleElements) {
		tween.stop(titleElements.titleAnimationGroup);
		tween.stop(titleElements.blackOverlay);
		if (titleElements.titleImage) {
			tween.stop(titleElements.titleImage);
		} // Stop titleImage tween
		if (titleElements.logo) {
			tween.stop(titleElements.logo);
		} // Keep for safety if old code path
		if (titleElements.subtitle) {
			tween.stop(titleElements.subtitle);
		} // Keep for safety
		tween.stop(titleElements.startButton);
		tween.stop(titleElements.tutorialButton);
		// Clear title screen sound timers
		if (titleSeagullSoundTimer) {
			LK.clearTimeout(titleSeagullSoundTimer);
			titleSeagullSoundTimer = null;
		}
		if (titleBoatSoundTimer) {
			LK.clearTimeout(titleBoatSoundTimer);
			titleBoatSoundTimer = null;
		}
		// Stop water surface animations for title screen
		if (titleElements.titleWaterSurfaceSegments) {
			titleElements.titleWaterSurfaceSegments.forEach(function (segment) {
				if (segment && !segment.destroyed) {
					tween.stop(segment);
				}
			});
		}
		// Clear title screen particles
		if (titleScreenOceanBubbleContainer) {
			titleScreenOceanBubbleContainer.removeChildren();
		}
		titleScreenOceanBubblesArray.forEach(function (p) {
			if (p && !p.destroyed) {
				p.destroy();
			}
		});
		titleScreenOceanBubblesArray = [];
		if (titleScreenSeaweedContainer) {
			titleScreenSeaweedContainer.removeChildren();
		}
		titleScreenSeaweedArray.forEach(function (p) {
			if (p && !p.destroyed) {
				p.destroy();
			}
		});
		titleScreenSeaweedArray = [];
		if (titleScreenCloudContainer) {
			titleScreenCloudContainer.removeChildren();
		}
		titleScreenCloudArray.forEach(function (p) {
			if (p && !p.destroyed) {
				p.destroy();
			}
		});
		titleScreenCloudArray = [];
	}
	// Cleanup tutorial elements if switching away from fishing/tutorial
	if (GameState.currentScreen === 'fishing' && GameState.tutorialMode && screenName !== 'fishing') {
		tutorialOverlayContainer.visible = false;
		if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
			GameState.tutorialFish.destroy();
			var idx = fishArray.indexOf(GameState.tutorialFish);
			if (idx > -1) {
				fishArray.splice(idx, 1);
			}
			GameState.tutorialFish = null;
		}
		// Clear lane highlights if any exist when switching screen away from tutorial
		if (tutorialLaneHighlights.length > 0) {
			tutorialLaneHighlights.forEach(function (overlay) {
				if (overlay && !overlay.destroyed) {
					overlay.destroy();
				}
			});
			tutorialLaneHighlights = [];
		}
		GameState.tutorialMode = false; // Ensure tutorial mode is exited
	}
	GameState.currentScreen = screenName;
	switch (screenName) {
		case 'title':
			// Title Screen Sounds
			var _scheduleNextSeagullSound = function scheduleNextSeagullSound() {
				if (GameState.currentScreen !== 'title') {
					// If screen changed, ensure timer is stopped and not rescheduled
					if (titleSeagullSoundTimer) {
						LK.clearTimeout(titleSeagullSoundTimer);
						titleSeagullSoundTimer = null;
					}
					return;
				}
				var randomDelay = 5000 + Math.random() * 10000; // 5-15 seconds
				titleSeagullSoundTimer = LK.setTimeout(function () {
					if (GameState.currentScreen !== 'title') {
						return; // Don't play or reschedule if not on title screen
					}
					var seagullSounds = ['seagull1', 'seagull2', 'seagull3'];
					var randomSoundId = seagullSounds[Math.floor(Math.random() * seagullSounds.length)];
					LK.getSound(randomSoundId).play();
					_scheduleNextSeagullSound(); // Reschedule
				}, randomDelay);
			};
			var _scheduleNextBoatSound = function scheduleNextBoatSound() {
				if (GameState.currentScreen !== 'title') {
					// If screen changed, ensure timer is stopped and not rescheduled
					if (titleBoatSoundTimer) {
						LK.clearTimeout(titleBoatSoundTimer);
						titleBoatSoundTimer = null;
					}
					return;
				}
				var fixedBoatSoundInterval = 6000; // Rhythmic interval: 8 seconds (reduced from 15)
				titleBoatSoundTimer = LK.setTimeout(function () {
					if (GameState.currentScreen !== 'title') {
						return; // Don't play or reschedule if not on title screen
					}
					LK.getSound('boatsounds').play();
					_scheduleNextBoatSound(); // Reschedule
				}, fixedBoatSoundInterval);
			}; // Play initial random seagull sound
			titleScreen.visible = true;
			// Start all animations like in fishing screen
			if (titleElements.startTitleWaterSurfaceAnimation) {
				titleElements.startTitleWaterSurfaceAnimation();
			}
			// Start boat group animations (single animations for the whole group)
			if (titleElements.moveTitleBoatGroupUp) {
				titleElements.moveTitleBoatGroupUp();
			}
			if (titleElements.rockTitleBoatGroupLeft) {
				titleElements.rockTitleBoatGroupLeft();
			}
			var initialSeagullSounds = ['seagull1', 'seagull2', 'seagull3'];
			var initialRandomSoundId = initialSeagullSounds[Math.floor(Math.random() * initialSeagullSounds.length)];
			LK.getSound(initialRandomSoundId).play();
			LK.getSound('boatsounds').play(); // Play boat sound immediately after initial seagull
			// Start the timed sounds (seagulls random, subsequent boats rhythmic)
			_scheduleNextSeagullSound();
			_scheduleNextBoatSound(); // Schedules the *next* boat sound rhythmically
			// Reset particle spawn counters
			titleScreenOceanBubbleSpawnCounter = 0;
			titleScreenSeaweedSpawnCounter = 0;
			titleScreenCloudSpawnCounter = 0;
			// Animation timing
			var ZOOM_DURATION = 8000; // 8-second zoom
			var OVERLAY_FADE_DELAY = 1000; // Black overlay starts fading after 1 second (was 2)
			var OVERLAY_FADE_DURATION = 3000; // 3 seconds to fade out overlay
			var TEXT_DELAY = 4000; // Text appears as overlay finishes fading (was 5500)
			var BUTTON_DELAY = 5500; // Buttons appear sooner (was 7000)
			// Reset states
			titleElements.titleAnimationGroup.x = GAME_CONFIG.SCREEN_CENTER_X;
			titleElements.titleAnimationGroup.y = GAME_CONFIG.SCREEN_CENTER_Y; // Centers boat vertically!
			titleElements.titleAnimationGroup.alpha = 1; // No alpha animation for main content
			titleElements.titleAnimationGroup.scale.set(3.0);
			// Reset black overlay to fully opaque
			titleElements.blackOverlay.alpha = 1;
			// Reset UI alphas
			if (titleElements.titleImage) {
				titleElements.titleImage.alpha = 0;
			}
			if (titleElements.logo) {
				titleElements.logo.alpha = 0;
			}
			if (titleElements.subtitle) {
				titleElements.subtitle.alpha = 0;
			}
			titleElements.startButton.alpha = 0;
			titleElements.tutorialButton.alpha = 0;
			// Main zoom animation (no alpha change)
			tween(titleElements.titleAnimationGroup, {
				scaleX: 1.8,
				scaleY: 1.8
			}, {
				duration: ZOOM_DURATION,
				easing: tween.easeInOut
			});
			// Black overlay fade out (the reveal effect)
			LK.setTimeout(function () {
				tween(titleElements.blackOverlay, {
					alpha: 0
				}, {
					duration: OVERLAY_FADE_DURATION,
					easing: tween.easeInOut
				});
			}, OVERLAY_FADE_DELAY);
			// Fade in title image after overlay is mostly gone
			LK.setTimeout(function () {
				if (titleElements.titleImage) {
					tween(titleElements.titleImage, {
						alpha: 1
					}, {
						duration: 1200,
						easing: tween.easeOut
					});
				} else if (titleElements.logo && titleElements.subtitle) {
					// Fallback for old structure if needed
					tween(titleElements.logo, {
						alpha: 1
					}, {
						duration: 1200,
						easing: tween.easeOut
					});
					tween(titleElements.subtitle, {
						alpha: 1
					}, {
						duration: 1200,
						easing: tween.easeOut
					});
				}
			}, TEXT_DELAY);
			// Fade in buttons near the end
			LK.setTimeout(function () {
				tween(titleElements.startButton, {
					alpha: 1
				}, {
					duration: 1000,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						// Fade in tutorial button AFTER start button finishes
						tween(titleElements.tutorialButton, {
							alpha: 1
						}, {
							duration: 1000,
							easing: tween.easeOut
						});
					}
				});
			}, BUTTON_DELAY);
			break;
		case 'levelSelect':
			levelSelectScreen.visible = true;
			updateLevelSelectScreen();
			break;
		case 'fishing':
			fishingScreen.visible = true;
			playIntroAnimation(); // Play the intro sequence
			break;
		case 'results':
			resultsScreen.visible = true;
			break;
	}
	// Tutorial System functions moved to global scope.
}
/**** 
* Intro Animation
****/ 
function playIntroAnimation() {
	GameState.introPlaying = true;
	GameState.gameActive = false;
	// Start animations for water, boat, and fisherman at the beginning of the intro
	if (fishingElements) {
		if (typeof fishingElements.startWaterSurfaceAnimation === 'function') {
			fishingElements.startWaterSurfaceAnimation();
		}
		if (typeof fishingElements.startBoatAndFishermanAnimation === 'function') {
			fishingElements.startBoatAndFishermanAnimation();
		}
	}
	// Calculate rod tip position (relative to fishingScreen)
	var fc = fishingElements.fishermanContainer;
	var f = fishingElements.fisherman;
	var rodTipCalculatedX = fc.x + f.x + 85;
	var rodTipCalculatedY = fc.y + f.y - f.height;
	var initialHookDangleY = rodTipCalculatedY + 50;
	fishingElements.hook.y = initialHookDangleY;
	// Setup initial zoom and camera position for fishingScreen
	var INITIAL_ZOOM_FACTOR = 1.5;
	// Pivot around the boat's visual center
	var pivotX = fishingElements.boat.x;
	var pivotY = fishingElements.boat.y - fishingElements.boat.height * (fishingElements.boat.anchor.y - 0.5);
	fishingScreen.pivot.set(pivotX, pivotY);
	// Position screen so the pivot appears at screen center when zoomed
	var screenCenterX = 2048 / 2;
	var screenCenterY = 2732 / 2;
	fishingScreen.x = screenCenterX;
	fishingScreen.y = screenCenterY;
	fishingScreen.scale.set(INITIAL_ZOOM_FACTOR, INITIAL_ZOOM_FACTOR);
	var introDuration = 2000;
	// Tween for zoom out
	tween(fishingScreen.scale, {
		x: 1,
		y: 1
	}, {
		duration: introDuration,
		easing: tween.easeInOut
	});
	// Tween screen position to compensate for the zoom change
	tween(fishingScreen, {
		x: pivotX,
		y: pivotY
	}, {
		duration: introDuration,
		easing: tween.easeInOut
	});
	// Hook drop animation
	var targetHookY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	// Play reel sound effect with 300ms delay during hook animation
	LK.setTimeout(function () {
		LK.getSound('reel').play();
	}, 600);
	tween(fishingElements.hook, {
		y: targetHookY
	}, {
		duration: introDuration * 0.8,
		delay: introDuration * 0.2,
		easing: tween.easeOut,
		onFinish: function onFinish() {
			GameState.introPlaying = false;
			// Reset to normal view
			fishingScreen.pivot.set(0, 0);
			fishingScreen.x = 0;
			fishingScreen.y = 0;
			// Check if we're in tutorial mode
			if (GameState.tutorialMode) {
				// Start the tutorial properly after intro
				GameState.gameActive = false; // Keep game inactive for tutorial
				createTutorialElements(); // Create tutorial UI
				runTutorialStep(); // Start with step 0
			} else {
				// Normal fishing session
				startFishingSession();
			}
		}
	});
}
/**** 
* Level Select Logic
****/ 
function updateLevelSelectScreen() {
	var elements = levelSelectElements;
	// Update money display
	elements.moneyDisplay.setText('Money: $' + GameState.money);
	// Create depth tabs
	createDepthTabs();
	// Update song display
	updateSongDisplay();
	// Update shop button
	updateShopButton();
}
function createDepthTabs() {
	// Clear existing tabs
	levelSelectElements.depthTabs.forEach(function (tab) {
		if (tab.container) {
			tab.container.destroy();
		}
	});
	levelSelectElements.depthTabs = [];
	// Create tabs for unlocked depths (positioned in the middle area)
	var tabStartY = 600; // Moved down from 400
	var tabSpacing = 250; // Increased spacing between tabs
	for (var i = 0; i <= GameState.currentDepth; i++) {
		var depth = GAME_CONFIG.DEPTHS[i];
		var isSelected = i === GameState.selectedDepth;
		var tabContainer = levelSelectScreen.addChild(new Container());
		var tab = tabContainer.addChild(LK.getAsset('depthTab', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: 200 + i * tabSpacing,
			// Increased spacing
			y: tabStartY,
			tint: isSelected ? 0x1976d2 : 0x455a64,
			width: 400,
			// Made wider
			height: 160 // Made taller
		}));
		var tabText = new Text2(depth.name.split(' ')[0], {
			size: 40,
			// Increased from 30
			fill: 0xFFFFFF
		});
		tabText.anchor.set(0.5, 0.5);
		tabText.x = 200 + i * tabSpacing;
		tabText.y = tabStartY;
		tabContainer.addChild(tabText);
		levelSelectElements.depthTabs.push({
			container: tabContainer,
			tab: tab,
			depthIndex: i
		});
	}
}
function updateSongDisplay() {
	var elements = levelSelectElements;
	var depth = GAME_CONFIG.DEPTHS[GameState.selectedDepth];
	var song = depth.songs[GameState.selectedSong];
	var owned = GameState.hasSong(GameState.selectedDepth, GameState.selectedSong);
	// Update song info
	elements.songTitle.setText(song.name);
	elements.songInfo.setText('BPM: ' + song.bpm + ' | Duration: ' + formatTime(song.duration));
	// Calculate potential earnings
	var minEarnings = Math.floor(depth.fishValue * 20); // Conservative estimate
	var maxEarnings = Math.floor(depth.fishValue * 60); // With combos and rare fish
	elements.songEarnings.setText('Potential Earnings: $' + minEarnings + '-$' + maxEarnings);
	// Update play/buy button
	if (owned) {
		elements.playButtonText.setText('PLAY');
		elements.playButton.tint = 0x1976d2;
	} else {
		elements.playButtonText.setText('BUY ($' + song.cost + ')');
		elements.playButton.tint = GameState.money >= song.cost ? 0x2e7d32 : 0x666666;
	}
	// Update arrow states
	elements.leftArrow.tint = GameState.selectedSong > 0 ? 0x1976d2 : 0x666666;
	elements.rightArrow.tint = GameState.selectedSong < depth.songs.length - 1 ? 0x1976d2 : 0x666666;
}
function updateShopButton() {
	var elements = levelSelectElements;
	// Always show as disabled - coming soon
	elements.shopButtonText.setText('UPGRADE ROD');
	elements.shopButton.tint = 0x666666; // Always grayed out
}
function formatTime(ms) {
	var seconds = Math.floor(ms / 1000);
	var minutes = Math.floor(seconds / 60);
	seconds = seconds % 60;
	return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
/**** 
* Fishing Game Logic
****/ 
function startFishingSession() {
	// Reset session state
	GameState.tutorialMode = false; // Ensure tutorial mode is off
	GameState.sessionScore = 0;
	GameState.sessionFishCaught = 0;
	GameState.sessionFishSpawned = 0;
	GameState.combo = 0;
	GameState.maxCombo = 0;
	GameState.gameActive = true;
	GameState.songStartTime = 0;
	GameState.lastBeatTime = 0;
	GameState.beatCount = 0;
	GameState.musicNotesActive = true;
	ImprovedRhythmSpawner.reset();
	musicNotesArray = [];
	if (fishingElements && fishingElements.musicNotesContainer) {
		fishingElements.musicNotesContainer.removeChildren();
	}
	musicNoteSpawnCounter = 0;
	// Reset ocean bubbles
	globalOceanBubblesArray = [];
	if (globalOceanBubbleContainer) {
		globalOceanBubbleContainer.removeChildren();
	}
	globalOceanBubbleSpawnCounter = 0;
	// Reset seaweed
	globalSeaweedArray = [];
	if (globalSeaweedContainer) {
		globalSeaweedContainer.removeChildren();
	}
	globalSeaweedSpawnCounter = 0;
	// Reset clouds
	globalCloudArray = [];
	if (globalCloudContainer) {
		globalCloudContainer.removeChildren();
	}
	globalCloudSpawnCounter = 0;
	// Animations for water, boat, and fisherman are now started in playIntroAnimation
	// Clear any existing fish
	fishArray.forEach(function (fish) {
		fish.destroy();
	});
	fishArray = [];
	// Reset pattern generator for new session
	PatternGenerator.reset();
	// Clear and create lane brackets
	if (laneBrackets && laneBrackets.length > 0) {
		laneBrackets.forEach(function (bracketPair) {
			if (bracketPair.left && !bracketPair.left.destroyed) {
				bracketPair.left.destroy();
			}
			if (bracketPair.right && !bracketPair.right.destroyed) {
				bracketPair.right.destroy();
			}
		});
	}
	laneBrackets = [];
	var bracketAssetHeight = 150; // Height of the lanebracket asset
	var bracketAssetWidth = 75; // Width of the lanebracket asset
	if (fishingScreen && !fishingScreen.destroyed) {
		// Ensure fishingScreen is available
		for (var i = 0; i < GAME_CONFIG.LANES.length; i++) {
			var laneY = GAME_CONFIG.LANES[i].y;
			var leftBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				x: bracketAssetWidth / 2,
				// Position its center so left edge is at 0
				y: laneY,
				height: bracketAssetHeight
			}));
			var rightBracket = fishingScreen.addChild(LK.getAsset('lanebracket', {
				anchorX: 0.5,
				anchorY: 0.5,
				alpha: 0.5,
				scaleX: -1,
				// Flipped horizontally
				x: 2048 - bracketAssetWidth / 2,
				// Position its center so right edge is at 2048
				y: laneY,
				height: bracketAssetHeight
			}));
			laneBrackets.push({
				left: leftBracket,
				right: rightBracket
			});
		}
	}
	// Start music
	var songConfig = GameState.getCurrentSongConfig();
	var musicIdToPlay = songConfig.musicId || 'rhythmTrack'; // Default to rhythmTrack if no specific id
	GameState.currentPlayingMusicId = musicIdToPlay;
	// Determine initial volume based on known assets for correct fade-out later
	if (musicIdToPlay === 'morningtide') {
		GameState.currentPlayingMusicInitialVolume = 1.0; // Volume defined in LK.init.music for 'morningtide'
	} else {
		// Default for 'rhythmTrack' or other unspecified tracks
		GameState.currentPlayingMusicInitialVolume = 0.8; // Volume defined in LK.init.music for 'rhythmTrack'
	}
	LK.playMusic(GameState.currentPlayingMusicId); // Play the selected music track
}
function spawnFish(currentTimeForRegistration, options) {
	options = options || {}; // Ensure options is an object
	var depthConfig = GameState.getCurrentDepthConfig();
	var songConfig = GameState.getCurrentSongConfig();
	var pattern = GAME_CONFIG.PATTERNS[songConfig.pattern];
	// Proximity check: Skip spawn if too close to existing fish.
	// This check is generally for the first fish of a beat or non-forced spawns.
	// For forced multi-beat spawns, this might prevent them if they are too close.
	// Consider if this rule should be relaxed for forced multi-beat spawns if visual overlap is acceptable for quick succession.
	// For now, keeping it as is. If a spawn is skipped, the multi-beat sequence might be shorter.
	var isFirstFishOfBeat = !options.laneIndexToUse && !options.forcedSpawnSide;
	if (isFirstFishOfBeat) {
		// Apply stricter proximity for non-forced spawns
		for (var i = 0; i < fishArray.length; i++) {
			var existingFish = fishArray[i];
			if (Math.abs(existingFish.x - GAME_CONFIG.SCREEN_CENTER_X) < PatternGenerator.minDistanceBetweenFish) {
				return null; // Skip this spawn, do not register
			}
		}
	}
	var laneIndex;
	if (options.laneIndexToUse !== undefined) {
		laneIndex = options.laneIndexToUse;
		PatternGenerator.lastLane = laneIndex; // Update generator's state if lane is forced
	} else {
		laneIndex = PatternGenerator.getNextLane();
	}
	var targetLane = GAME_CONFIG.LANES[laneIndex];
	var fishType, fishValue;
	var rand = Math.random();
	if (rand < pattern.rareSpawnChance) {
		fishType = 'rare';
		fishValue = Math.floor(depthConfig.fishValue * 4);
	} else if (GameState.selectedDepth >= 2 && rand < 0.3) {
		fishType = 'deep';
		fishValue = Math.floor(depthConfig.fishValue * 2);
	} else if (GameState.selectedDepth >= 1 && rand < 0.6) {
		fishType = 'medium';
		fishValue = Math.floor(depthConfig.fishValue * 1.5);
	} else {
		fishType = 'shallow';
		fishValue = Math.floor(depthConfig.fishValue);
	}
	var fishSpeedValue = depthConfig.fishSpeed;
	var spawnSide; // -1 for left, 1 for right
	var actualFishSpeed;
	if (options.forcedSpawnSide !== undefined) {
		spawnSide = options.forcedSpawnSide;
	} else {
		spawnSide = Math.random() < 0.5 ? -1 : 1;
	}
	actualFishSpeed = Math.abs(fishSpeedValue) * spawnSide;
	var newFish = new Fish(fishType, fishValue, actualFishSpeed, laneIndex);
	newFish.spawnSide = spawnSide; // Store the side it spawned from
	newFish.x = actualFishSpeed > 0 ? -150 : 2048 + 150; // Start off-screen
	newFish.y = targetLane.y;
	newFish.baseY = targetLane.y; // Set baseY for swimming animation
	fishArray.push(newFish);
	fishingScreen.addChild(newFish);
	GameState.sessionFishSpawned++;
	PatternGenerator.registerFishSpawn(currentTimeForRegistration);
	return newFish;
}
function checkCatch(fishLane) {
	var hookX = fishingElements.hook.x;
	if (GameState.tutorialMode) {
		var tutorialFish = GameState.tutorialFish;
		if (!tutorialFish || tutorialFish.lane !== fishLane || tutorialFish.caught || tutorialFish.missed) {
			// Check if it's a critical catch step (now steps 3 or 4)
			if (GameState.tutorialStep === 3 || GameState.tutorialStep === 4) {
				setTutorialText("Oops! Make sure to tap when the fish is in the correct lane and over the hook. Tap 'CONTINUE' to try again.");
				GameState.tutorialPaused = true;
			}
			LK.getSound('miss').play();
			return;
		}
		var distance = Math.abs(tutorialFish.x - hookX);
		var caughtType = null;
		if (distance < GAME_CONFIG.PERFECT_WINDOW) {
			caughtType = 'perfect';
		} else if (distance < GAME_CONFIG.GOOD_WINDOW) {
			caughtType = 'good';
		} else if (distance < GAME_CONFIG.MISS_WINDOW) {
			// For tutorial, make 'miss window' taps still count as 'good' to be more forgiving
			caughtType = 'good';
		} else {
			caughtType = 'miss';
		}
		showFeedback(caughtType, fishLane); // Show visual feedback based on derived type
		if (caughtType === 'perfect' || caughtType === 'good') {
			tutorialFish.catchFish();
			var fishIndex = fishArray.indexOf(tutorialFish);
			if (fishIndex > -1) {
				fishArray.splice(fishIndex, 1);
			}
			// No points/money in tutorial normally, but can add if desired.
			LK.getSound('catch').play();
			animateHookCatch();
			GameState.tutorialPaused = true; // Pause to show message
			if (GameState.tutorialStep === 3) {
				// Was step 2
				setTutorialText("Great catch! That's how you do it. Tap 'CONTINUE'.");
				// GameState.tutorialStep = 4; // Advance to next conceptual phase -- Handled by game.down
			} else if (GameState.tutorialStep === 4) {
				// Was step 3
				setTutorialText("Nice one! You're getting the hang of timing. Tap 'CONTINUE'.");
				// GameState.tutorialStep = 5; // Advance to Combo explanation -- Handled by game.down
			}
		} else {
			// Miss
			// Feedback 'miss' was already shown
			LK.getSound('miss').play();
			tutorialFish.missed = true;
			GameState.tutorialPaused = true;
			setTutorialText("Almost! Try to tap when the fish is closer. Tap 'CONTINUE' to try this part again.");
			// The runTutorialStep logic on "CONTINUE" will handle respawning for this step.
		}
		return; // Tutorial catch logic finished
	}
	var closestFishInLane = null;
	var closestDistance = Infinity;
	// This is a tap action, find the closest fish in the tapped lane.
	for (var i = 0; i < fishArray.length; i++) {
		var fish = fishArray[i];
		// Ensure fish is not caught, not already missed, and in the correct lane
		if (!fish.caught && !fish.missed && fish.lane === fishLane) {
			var distance = Math.abs(fish.x - hookX);
			if (distance < closestDistance) {
				closestDistance = distance;
				closestFishInLane = fish;
			}
		}
	}
	if (!closestFishInLane) {
		// No fish found for tap
		// Play miss sound
		LK.getSound('miss').play();
		// Tint incorrect lane indicators red briefly using tween
		if (laneBrackets && laneBrackets[fishLane]) {
			var leftBracket = laneBrackets[fishLane].left;
			var rightBracket = laneBrackets[fishLane].right;
			var tintToRedDuration = 50; // Duration to tween to red (ms)
			var holdRedDuration = 100; // How long it stays fully red (ms)
			var tintToWhiteDuration = 150; // Duration to tween back to white (ms)
			if (leftBracket && !leftBracket.destroyed) {
				// Tween to red
				tween(leftBracket, {
					tint: 0xFF0000
				}, {
					duration: tintToRedDuration,
					easing: tween.linear,
					onFinish: function onFinish() {
						// After tinting to red, wait, then tween back to white
						LK.setTimeout(function () {
							if (leftBracket && !leftBracket.destroyed) {
								tween(leftBracket, {
									tint: 0xFFFFFF
								}, {
									duration: tintToWhiteDuration,
									easing: tween.linear
								});
							}
						}, holdRedDuration);
					}
				});
			}
			if (rightBracket && !rightBracket.destroyed) {
				// Tween to red
				tween(rightBracket, {
					tint: 0xFF0000
				}, {
					duration: tintToRedDuration,
					easing: tween.linear,
					onFinish: function onFinish() {
						// After tinting to red, wait, then tween back to white
						LK.setTimeout(function () {
							if (rightBracket && !rightBracket.destroyed) {
								tween(rightBracket, {
									tint: 0xFFFFFF
								}, {
									duration: tintToWhiteDuration,
									easing: tween.linear
								});
							}
						}, holdRedDuration);
					}
				});
			}
		}
		GameState.combo = 0;
		return;
	}
	// --- Normal Fish Catch Logic ---
	var points = 0;
	var multiplier = Math.max(1, Math.floor(GameState.combo / 10) + 1);
	if (closestDistance < GAME_CONFIG.PERFECT_WINDOW) {
		points = closestFishInLane.value * 2 * multiplier;
		showFeedback('perfect', fishLane);
		GameState.combo++;
	} else if (closestDistance < GAME_CONFIG.GOOD_WINDOW) {
		points = closestFishInLane.value * multiplier;
		showFeedback('good', fishLane);
		GameState.combo++;
	} else if (closestDistance < GAME_CONFIG.MISS_WINDOW) {
		points = Math.max(1, Math.floor(closestFishInLane.value * 0.5 * multiplier));
		showFeedback('good', fishLane);
		GameState.combo++;
	} else {
		showFeedback('miss', fishLane);
		LK.getSound('miss').play();
		GameState.combo = 0;
		// Mark the specific fish that was tapped but missed
		if (closestFishInLane) {
			closestFishInLane.missed = true;
		}
		return;
	}
	// Successfully caught fish
	closestFishInLane.catchFish();
	var fishIndex = fishArray.indexOf(closestFishInLane);
	if (fishIndex > -1) {
		fishArray.splice(fishIndex, 1);
	}
	GameState.sessionScore += points;
	GameState.money += points;
	GameState.sessionFishCaught++;
	GameState.totalFishCaught++;
	GameState.maxCombo = Math.max(GameState.maxCombo, GameState.combo);
	// Play a random catch sound effect
	var catchSounds = ['catch', 'catch2', 'catch3', 'catch4'];
	var randomCatchSound = catchSounds[Math.floor(Math.random() * catchSounds.length)];
	LK.getSound(randomCatchSound).play();
	animateHookCatch(); // Call parameterless animateHookCatch for the single hook
	// Score pop-up animation
	if (points > 0) {
		var scorePopupText = new Text2('+' + points, {
			size: 140,
			// Slightly larger for impact
			fill: 0xFFD700,
			// Gold color for score
			align: 'center',
			stroke: 0x000000,
			// Black stroke
			strokeThickness: 6 // Thickness of the stroke
		});
		scorePopupText.anchor.set(0.5, 0.5);
		scorePopupText.x = GAME_CONFIG.SCREEN_CENTER_X; // Centered with the boat
		scorePopupText.y = GAME_CONFIG.BOAT_Y - 70; // Start slightly above the boat's deck line
		// Add to fishingScreen so it's part of the game view
		if (fishingScreen && !fishingScreen.destroyed) {
			fishingScreen.addChild(scorePopupText);
		}
		tween(scorePopupText, {
			y: scorePopupText.y - 200,
			// Float up by 200 pixels
			alpha: 0
		}, {
			duration: 1800,
			// Slightly longer duration for a nice float
			easing: tween.easeOut,
			onFinish: function onFinish() {
				if (scorePopupText && !scorePopupText.destroyed) {
					scorePopupText.destroy();
				}
			}
		});
	}
}
// Note: The old animateHookCatch function that was defined right after checkCatch
// is now a global helper: animateHookCatch(laneIndex), defined earlier.
// We remove the old local one if it existed here by not re-inserting it.
function updateFishingUI() {
	var elements = fishingElements;
	elements.scoreText.setText('Score: ' + GameState.sessionScore);
	elements.fishText.setText('Fish: ' + GameState.sessionFishCaught + '/' + GameState.sessionFishSpawned);
	elements.comboText.setText('Combo: ' + GameState.combo);
	// Update progress
	if (GameState.songStartTime > 0) {
		var currentTime = LK.ticks * (1000 / 60);
		var elapsed = currentTime - GameState.songStartTime;
		var songConfig = GameState.getCurrentSongConfig();
		elements.progressText.setText(formatTime(elapsed) + ' / ' + formatTime(songConfig.duration));
	}
}
function endFishingSession() {
	GameState.gameActive = false;
	GameState.tutorialMode = false; // Ensure tutorial mode is off
	// Stop the boat's wave animation to prevent it from running after the session
	if (fishingElements && fishingElements.boat) {
		tween.stop(fishingElements.boat);
	}
	// Stop fisherman container animation
	if (fishingElements && fishingElements.fishermanContainer) {
		tween.stop(fishingElements.fishermanContainer);
	}
	// Stop fisherman rotation animation
	if (fishingElements && fishingElements.fisherman) {
		tween.stop(fishingElements.fisherman);
	}
	// Stop water surface wave animations
	if (fishingElements && fishingElements.waterSurfaceSegments) {
		fishingElements.waterSurfaceSegments.forEach(function (segment) {
			if (segment && !segment.destroyed) {
				tween.stop(segment);
			}
		});
	}
	// Stop music immediately
	LK.stopMusic();
	ImprovedRhythmSpawner.reset();
	// Clear lane brackets
	if (laneBrackets && laneBrackets.length > 0) {
		laneBrackets.forEach(function (bracketPair) {
			if (bracketPair.left && !bracketPair.left.destroyed) {
				bracketPair.left.destroy();
			}
			if (bracketPair.right && !bracketPair.right.destroyed) {
				bracketPair.right.destroy();
			}
		});
		laneBrackets = [];
	}
	// Clear fish
	fishArray.forEach(function (fish) {
		fish.destroy();
	});
	fishArray = [];
	GameState.musicNotesActive = false;
	if (fishingElements && fishingElements.musicNotesContainer) {
		fishingElements.musicNotesContainer.removeChildren();
	}
	// The MusicNoteParticle instances themselves will be garbage collected.
	// Clearing the array is important.
	musicNotesArray = [];
	// Clear ocean bubbles
	if (globalOceanBubbleContainer) {
		globalOceanBubbleContainer.removeChildren();
	}
	globalOceanBubblesArray = [];
	// Clear seaweed
	if (globalSeaweedContainer) {
		globalSeaweedContainer.removeChildren();
	}
	globalSeaweedArray = [];
	// Clear clouds
	if (globalCloudContainer) {
		globalCloudContainer.removeChildren();
	}
	globalCloudArray = [];
	// Create results screen
	createResultsScreen();
	showScreen('results');
}
function createResultsScreen() {
	// Clear previous results
	resultsScreen.removeChildren();
	var resultsBg = resultsScreen.addChild(LK.getAsset('screenBackground', {
		x: 0,
		y: 0,
		alpha: 0.9,
		height: 2732
	}));
	var title = new Text2('Fishing Complete!', {
		size: 100,
		fill: 0xFFFFFF
	});
	title.anchor.set(0.5, 0.5);
	title.x = GAME_CONFIG.SCREEN_CENTER_X;
	title.y = 400;
	resultsScreen.addChild(title);
	var scoreResult = new Text2('Score: ' + GameState.sessionScore, {
		size: 70,
		fill: 0xFFD700
	});
	scoreResult.anchor.set(0.5, 0.5);
	scoreResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	scoreResult.y = 550;
	resultsScreen.addChild(scoreResult);
	var fishResult = new Text2('Fish Caught: ' + GameState.sessionFishCaught + '/' + GameState.sessionFishSpawned, {
		size: 50,
		fill: 0xFFFFFF
	});
	fishResult.anchor.set(0.5, 0.5);
	fishResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	fishResult.y = 650;
	resultsScreen.addChild(fishResult);
	var comboResult = new Text2('Max Combo: ' + GameState.maxCombo, {
		size: 50,
		fill: 0xFF9800
	});
	comboResult.anchor.set(0.5, 0.5);
	comboResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	comboResult.y = 750;
	resultsScreen.addChild(comboResult);
	var moneyEarned = new Text2('Money Earned: $' + GameState.sessionScore, {
		size: 50,
		fill: 0x4CAF50
	});
	moneyEarned.anchor.set(0.5, 0.5);
	moneyEarned.x = GAME_CONFIG.SCREEN_CENTER_X;
	moneyEarned.y = 850;
	resultsScreen.addChild(moneyEarned);
	// Accuracy
	var accuracy = GameState.sessionFishSpawned > 0 ? Math.round(GameState.sessionFishCaught / GameState.sessionFishSpawned * 100) : 0;
	var accuracyResult = new Text2('Accuracy: ' + accuracy + '%', {
		size: 50,
		fill: 0x2196F3
	});
	accuracyResult.anchor.set(0.5, 0.5);
	accuracyResult.x = GAME_CONFIG.SCREEN_CENTER_X;
	accuracyResult.y = 950;
	resultsScreen.addChild(accuracyResult);
	// Continue button
	var continueButton = resultsScreen.addChild(LK.getAsset('bigButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: GAME_CONFIG.SCREEN_CENTER_X,
		y: 1200
	}));
	var continueText = new Text2('CONTINUE', {
		size: 50,
		fill: 0xFFFFFF
	});
	continueText.anchor.set(0.5, 0.5);
	continueText.x = GAME_CONFIG.SCREEN_CENTER_X;
	continueText.y = 1200;
	resultsScreen.addChild(continueText);
	// Fade in
	resultsScreen.alpha = 0;
	tween(resultsScreen, {
		alpha: 1
	}, {
		duration: 500,
		easing: tween.easeOut
	});
}
/**** 
* Input Handling
****/ 
game.down = function (x, y, obj) {
	LK.getSound('buttonClick').play();
	var currentScreen = GameState.currentScreen;
	// If tutorial mode is active, treat as 'tutorial' screen for input
	if (GameState.tutorialMode && (currentScreen === 'fishing' || currentScreen === 'tutorial')) {
		// New case for tutorial input
		if (tutorialOverlayContainer.visible && tutorialContinueButton && tutorialContinueButton.visible && x >= tutorialContinueButton.x - tutorialContinueButton.width / 2 && x <= tutorialContinueButton.x + tutorialContinueButton.width / 2 && y >= tutorialContinueButton.y - tutorialContinueButton.height / 2 && y <= tutorialContinueButton.y + tutorialContinueButton.height / 2) {
			LK.getSound('buttonClick').play();
			// If tutorial is paused and it's a catch instruction step (3 or 4),
			// clicking "CONTINUE" implies a retry of that step.
			if (GameState.tutorialPaused && (GameState.tutorialStep === 3 || GameState.tutorialStep === 4)) {
				// Logic for catch steps 3 or 4 when "CONTINUE" is pressed.
				// This covers scenarios where fish was caught, missed, passed hook, or swam off screen.
				var advanceAfterCatch = false;
				// First, check the state of the existing tutorial fish, if any.
				if (GameState.tutorialFish && GameState.tutorialFish.caught) {
					advanceAfterCatch = true;
				}
				// Regardless of success or failure, if there was a tutorial fish, clean it up.
				// This handles the fish that was just caught OR missed/swam_off.
				if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) {
					var idx = fishArray.indexOf(GameState.tutorialFish);
					if (idx > -1) {
						fishArray.splice(idx, 1);
					}
					GameState.tutorialFish.destroy();
				}
				// Ensure GameState.tutorialFish is null before runTutorialStep,
				// as runTutorialStep might spawn a new one for the current or next step.
				GameState.tutorialFish = null; //{pq} // Ensure it's null before re-running step to spawn a new one.
				if (advanceAfterCatch) {
					// If the fish was caught, advance to the next tutorial step.
					GameState.tutorialStep++;
					runTutorialStep();
				} else {
					// If the fish was missed, swam off, or there was no fish to catch (e.g. error state)
					// then retry the current step. runTutorialStep will handle re-spawning.
					runTutorialStep(); // Re-runs current step to spawn new fish
				}
			} else {
				// For any other tutorial step, or if not paused in a catch step, "CONTINUE" advances.
				GameState.tutorialStep++;
				runTutorialStep();
			}
		} else if ((GameState.tutorialStep === 3 || GameState.tutorialStep === 4) && !GameState.tutorialPaused) {
			// Catch attempt steps are now 3 and 4
			// Catch attempt by tapping screen, not the continue button
			handleFishingInput(x, y, true); // True for isDown
		}
		return;
	}
	switch (currentScreen) {
		case 'title':
			// Check if click is within start button bounds
			var startButton = titleElements.startButton;
			if (x >= startButton.x - startButton.width / 2 && x <= startButton.x + startButton.width / 2 && y >= startButton.y - startButton.height / 2 && y <= startButton.y + startButton.height / 2) {
				showScreen('levelSelect');
			}
			// Check if click is within tutorial button bounds
			var tutorialButtonGfx = titleElements.tutorialButtonGfx || titleElements.tutorialButton; // Compatibility
			if (x >= tutorialButtonGfx.x - tutorialButtonGfx.width / 2 && x <= tutorialButtonGfx.x + tutorialButtonGfx.width / 2 && y >= tutorialButtonGfx.y - tutorialButtonGfx.height / 2 && y <= tutorialButtonGfx.y + tutorialButtonGfx.height / 2) {
				// Defensive: check if startTutorial is defined before calling
				if (typeof startTutorial === "function") {
					startTutorial();
				}
			}
			break;
		case 'levelSelect':
			handleLevelSelectInput(x, y);
			break;
		case 'fishing':
			handleFishingInput(x, y, true); // true for isDown
			break;
		case 'results':
			showScreen('levelSelect');
			break;
	}
};
function handleLevelSelectInput(x, y) {
	var elements = levelSelectElements;
	// Check depth tabs
	elements.depthTabs.forEach(function (tab) {
		var tabAsset = tab.tab;
		if (x >= tabAsset.x - tabAsset.width / 2 && x <= tabAsset.x + tabAsset.width / 2 && y >= tabAsset.y - tabAsset.height / 2 && y <= tabAsset.y + tabAsset.height / 2) {
			GameState.selectedDepth = tab.depthIndex;
			GameState.selectedSong = 0; // Reset to first song
			updateLevelSelectScreen();
		}
	});
	// Check song navigation
	var leftArrow = elements.leftArrow;
	if (x >= leftArrow.x - leftArrow.width / 2 && x <= leftArrow.x + leftArrow.width / 2 && y >= leftArrow.y - leftArrow.height / 2 && y <= leftArrow.y + leftArrow.height / 2 && GameState.selectedSong > 0) {
		GameState.selectedSong--;
		updateSongDisplay();
	}
	var rightArrow = elements.rightArrow;
	if (x >= rightArrow.x - rightArrow.width / 2 && x <= rightArrow.x + rightArrow.width / 2 && y >= rightArrow.y - rightArrow.height / 2 && y <= rightArrow.y + rightArrow.height / 2) {
		var depth = GAME_CONFIG.DEPTHS[GameState.selectedDepth];
		if (GameState.selectedSong < depth.songs.length - 1) {
			GameState.selectedSong++;
			updateSongDisplay();
		}
	}
	// Check play/buy button
	var playButton = elements.playButton;
	if (x >= playButton.x - playButton.width / 2 && x <= playButton.x + playButton.width / 2 && y >= playButton.y - playButton.height / 2 && y <= playButton.y + playButton.height / 2) {
		var owned = GameState.hasSong(GameState.selectedDepth, GameState.selectedSong);
		if (owned) {
			showScreen('fishing');
		} else {
			// Try to buy song
			if (GameState.buySong(GameState.selectedDepth, GameState.selectedSong)) {
				updateLevelSelectScreen();
			}
		}
	}
	// Check shop button - disabled for "coming soon"
	var shopButton = elements.shopButton;
	if (x >= shopButton.x - shopButton.width / 2 && x <= shopButton.x + shopButton.width / 2 && y >= shopButton.y - shopButton.height / 2 && y <= shopButton.y + shopButton.height / 2) {
		// Do nothing - button is disabled
	}
	// Check back button
	var backButton = elements.backButton;
	if (x >= backButton.x - backButton.width / 2 && x <= backButton.x + backButton.width / 2 && y >= backButton.y - backButton.height / 2 && y <= backButton.y + backButton.height / 2) {
		showScreen('title');
	}
}
/**** 
* Main Game Loop
****/ 
game.update = function () {
	// Always update fishing line wave visuals if on fishing screen and elements are ready.
	// This needs to run even during the intro when gameActive might be false.
	if (GameState.currentScreen === 'fishing' && fishingElements && fishingElements.updateFishingLineWave) {
		fishingElements.updateFishingLineWave();
	}
	// Update title screen ambient particles if title screen is active
	if (GameState.currentScreen === 'title') {
		// Add this to the game.update function, in the title screen section:
		if (GameState.currentScreen === 'title' && titleElements && titleElements.updateTitleFishingLineWave) {
			titleElements.updateTitleFishingLineWave();
		}
		// Title Screen Ocean Bubbles
		if (titleScreenOceanBubbleContainer) {
			titleScreenOceanBubbleSpawnCounter++;
			if (titleScreenOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) {
				// Use same interval as fishing
				titleScreenOceanBubbleSpawnCounter = 0;
				var newOceanBubble = new OceanBubbleParticle(); // Assuming OceanBubbleParticle is general enough
				titleScreenOceanBubbleContainer.addChild(newOceanBubble);
				titleScreenOceanBubblesArray.push(newOceanBubble);
			}
			for (var obIdx = titleScreenOceanBubblesArray.length - 1; obIdx >= 0; obIdx--) {
				var oceanBubble = titleScreenOceanBubblesArray[obIdx];
				if (oceanBubble) {
					oceanBubble.update(); // No fish interaction on title screen
					if (oceanBubble.isDone) {
						oceanBubble.destroy();
						titleScreenOceanBubblesArray.splice(obIdx, 1);
					}
				} else {
					titleScreenOceanBubblesArray.splice(obIdx, 1);
				}
			}
		}
		// Title Screen Seaweed
		if (titleScreenSeaweedContainer) {
			titleScreenSeaweedSpawnCounter++;
			if (titleScreenSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && titleScreenSeaweedArray.length < MAX_SEAWEED_COUNT) {
				titleScreenSeaweedSpawnCounter = 0;
				var newSeaweed = new SeaweedParticle();
				titleScreenSeaweedContainer.addChild(newSeaweed);
				titleScreenSeaweedArray.push(newSeaweed);
			}
			for (var swIdx = titleScreenSeaweedArray.length - 1; swIdx >= 0; swIdx--) {
				var seaweed = titleScreenSeaweedArray[swIdx];
				if (seaweed) {
					seaweed.update(); // No fish interaction on title screen
					if (seaweed.isDone) {
						seaweed.destroy();
						titleScreenSeaweedArray.splice(swIdx, 1);
					}
				} else {
					titleScreenSeaweedArray.splice(swIdx, 1);
				}
			}
		}
		// Title Screen Clouds
		if (titleScreenCloudContainer) {
			titleScreenCloudSpawnCounter++;
			if (titleScreenCloudSpawnCounter >= CLOUD_SPAWN_INTERVAL_TICKS && titleScreenCloudArray.length < MAX_CLOUD_COUNT) {
				titleScreenCloudSpawnCounter = 0;
				var newCloud = new CloudParticle();
				titleScreenCloudContainer.addChild(newCloud);
				titleScreenCloudArray.push(newCloud);
			}
			for (var cldIdx = titleScreenCloudArray.length - 1; cldIdx >= 0; cldIdx--) {
				var cloud = titleScreenCloudArray[cldIdx];
				if (cloud) {
					cloud.update();
					if (cloud.isDone) {
						cloud.destroy();
						titleScreenCloudArray.splice(cldIdx, 1);
					}
				} else {
					titleScreenCloudArray.splice(cldIdx, 1);
				}
			}
		}
	}
	// Spawn and update ambient ocean bubbles during intro and gameplay (fishing screen)
	if (GameState.currentScreen === 'fishing' && globalOceanBubbleContainer) {
		// Spawn bubbles during intro and gameplay
		globalOceanBubbleSpawnCounter++;
		if (globalOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) {
			globalOceanBubbleSpawnCounter = 0;
			var numToSpawn = 1; // Always spawn only 1 bubble per interval (was 1 or 2)
			for (var i = 0; i < numToSpawn; i++) {
				var newOceanBubble = new OceanBubbleParticle();
				globalOceanBubbleContainer.addChild(newOceanBubble);
				globalOceanBubblesArray.push(newOceanBubble);
			}
		}
		// Update existing ocean bubbles
		for (var obIdx = globalOceanBubblesArray.length - 1; obIdx >= 0; obIdx--) {
			var oceanBubble = globalOceanBubblesArray[obIdx];
			if (oceanBubble) {
				// Apply fish physics to bubble
				for (var fishIdx = 0; fishIdx < fishArray.length; fishIdx++) {
					var fish = fishArray[fishIdx];
					if (fish && !fish.caught) {
						var dx = oceanBubble.x - fish.x;
						var dy = oceanBubble.y - fish.y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						var influenceRadius = 150; // Radius of fish influence on bubbles
						var minDistance = 30; // Minimum distance to avoid division issues
						if (distance < influenceRadius && distance > minDistance) {
							// Calculate influence strength (stronger when closer)
							var influence = 1 - distance / influenceRadius;
							influence = influence * influence; // Square for more dramatic close-range effect
							// Calculate normalized direction away from fish
							var dirX = dx / distance;
							var dirY = dy / distance;
							// Apply force based on fish speed and direction
							var fishSpeedFactor = Math.abs(fish.speed) * 0.15; // Scale down fish speed influence
							var pushForce = fishSpeedFactor * influence;
							// Add horizontal push (stronger in direction of fish movement)
							oceanBubble.x += dirX * pushForce * 2; // Stronger horizontal push
							// Add vertical component (bubbles get pushed up/down)
							oceanBubble.vy += dirY * pushForce * 0.5; // Gentler vertical influence
							// Add some swirl/turbulence to drift
							oceanBubble.driftAmplitude = Math.min(80, oceanBubble.driftAmplitude + pushForce * 10);
							oceanBubble.driftFrequency *= 1 + influence * 0.1; // Slightly increase oscillation when disturbed
						}
					}
				}
				oceanBubble.update();
				if (oceanBubble.isDone) {
					oceanBubble.destroy();
					globalOceanBubblesArray.splice(obIdx, 1);
				}
			} else {
				globalOceanBubblesArray.splice(obIdx, 1); // Safeguard for null entries
			}
		}
	}
	// Spawn and update seaweed particles during intro and gameplay
	if (GameState.currentScreen === 'fishing' && globalSeaweedContainer) {
		// Spawn seaweed during intro and gameplay
		globalSeaweedSpawnCounter++;
		if (globalSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && globalSeaweedArray.length < MAX_SEAWEED_COUNT) {
			globalSeaweedSpawnCounter = 0;
			var newSeaweed = new SeaweedParticle();
			globalSeaweedContainer.addChild(newSeaweed);
			globalSeaweedArray.push(newSeaweed);
		}
		// Update existing seaweed
		for (var swIdx = globalSeaweedArray.length - 1; swIdx >= 0; swIdx--) {
			var seaweed = globalSeaweedArray[swIdx];
			if (seaweed) {
				// Apply fish physics to seaweed
				for (var fishIdx = 0; fishIdx < fishArray.length; fishIdx++) {
					var fish = fishArray[fishIdx];
					if (fish && !fish.caught) {
						var dx = seaweed.x - fish.x;
						var dy = seaweed.y - fish.y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						var influenceRadius = 180; // Slightly larger influence for seaweed
						var minDistance = 40;
						if (distance < influenceRadius && distance > minDistance) {
							// Calculate influence strength
							var influence = 1 - distance / influenceRadius;
							influence = influence * influence;
							// Calculate normalized direction away from fish
							var dirX = dx / distance;
							var dirY = dy / distance;
							// Apply force based on fish speed
							var fishSpeedFactor = Math.abs(fish.speed) * 0.2; // Stronger influence on seaweed
							var pushForce = fishSpeedFactor * influence;
							// Add push forces
							seaweed.vx += dirX * pushForce * 1.5; // Seaweed is affected more horizontally
							seaweed.vy += dirY * pushForce * 0.8; // And moderately vertically
							// Increase sway when disturbed
							seaweed.swayAmplitude = Math.min(60, seaweed.swayAmplitude + pushForce * 15);
						}
					}
				}
				seaweed.update();
				if (seaweed.isDone) {
					seaweed.destroy();
					globalSeaweedArray.splice(swIdx, 1);
				}
			} else {
				globalSeaweedArray.splice(swIdx, 1);
			}
		}
	}
	// Spawn and update cloud particles during intro and gameplay
	if (GameState.currentScreen === 'fishing' && globalCloudContainer) {
		// Spawn clouds during intro and gameplay
		globalCloudSpawnCounter++;
		if (globalCloudSpawnCounter >= CLOUD_SPAWN_INTERVAL_TICKS && globalCloudArray.length < MAX_CLOUD_COUNT) {
			globalCloudSpawnCounter = 0;
			var newCloud = new CloudParticle();
			globalCloudContainer.addChild(newCloud);
			globalCloudArray.push(newCloud);
		}
		// Update existing clouds
		for (var cldIdx = globalCloudArray.length - 1; cldIdx >= 0; cldIdx--) {
			var cloud = globalCloudArray[cldIdx];
			if (cloud) {
				cloud.update();
				if (cloud.isDone) {
					cloud.destroy();
					globalCloudArray.splice(cldIdx, 1);
				}
			} else {
				globalCloudArray.splice(cldIdx, 1);
			}
		}
	}
	// Standard game active check; if intro is playing, gameActive will be false.
	// Tutorial mode has its own logic path
	if (GameState.currentScreen === 'fishing' && GameState.tutorialMode) {
		// Update essential non-paused elements like fishing line wave
		if (fishingElements && fishingElements.updateFishingLineWave) {
			fishingElements.updateFishingLineWave();
		}
		// Update ambient particles (clouds, background bubbles, seaweed can run if desired)
		// ... (Can copy particle update logic here if they should be active in tutorial)
		if (!GameState.tutorialPaused) {
			// Update tutorial fish if one exists and is active
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed && !GameState.tutorialFish.caught) {
				GameState.tutorialFish.update();
				checkTutorialFishState(); // Check its state (missed, off-screen)
			}
			// Hook follows tutorial fish
			if (GameState.tutorialFish && !GameState.tutorialFish.destroyed && !GameState.tutorialFish.caught) {
				if (GameState.hookTargetLaneIndex !== GameState.tutorialFish.lane) {
					GameState.hookTargetLaneIndex = GameState.tutorialFish.lane;
				}
				var targetLaneY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
				if (fishingElements.hook && Math.abs(fishingElements.hook.y - targetLaneY) > 1) {
					// Smoother threshold
					// fishingElements.hook.y = targetLaneY; // Instant for tutorial responsiveness
					tween(fishingElements.hook, {
						y: targetLaneY
					}, {
						duration: 100,
						easing: tween.linear
					});
					fishingElements.hook.originalY = targetLaneY;
				}
			} else if (fishingElements.hook) {
				// If no tutorial fish, hook stays in middle or last targeted lane
				var middleLaneY = GAME_CONFIG.LANES[1].y;
				if (fishingElements.hook.y !== middleLaneY) {
					// tween(fishingElements.hook, { y: middleLaneY }, { duration: 150, easing: tween.easeOut });
					// fishingElements.hook.originalY = middleLaneY;
				}
			}
		}
		updateLaneBracketsVisuals();
		return; // End tutorial update logic
	}
	if (GameState.currentScreen !== 'fishing' || !GameState.gameActive) {
		return;
	}
	// Note: The fishing line wave update was previously here, it's now moved up.
	var currentTime = LK.ticks * (1000 / 60);
	// Initialize game timer
	if (GameState.songStartTime === 0) {
		GameState.songStartTime = currentTime;
	}
	// Check song end
	var songConfig = GameState.getCurrentSongConfig();
	if (currentTime - GameState.songStartTime >= songConfig.duration) {
		endFishingSession();
		return;
	}
	// Use RhythmSpawner to handle fish spawning
	ImprovedRhythmSpawner.update(currentTime);
	// Dynamic Hook Movement Logic
	var approachingFish = null;
	var minDistanceToCenter = Infinity;
	for (var i = 0; i < fishArray.length; i++) {
		var f = fishArray[i];
		if (!f.caught) {
			var distanceToHookX = Math.abs(f.x - fishingElements.hook.x);
			var isApproachingOrAtHook = f.speed > 0 && f.x < fishingElements.hook.x || f.speed < 0 && f.x > fishingElements.hook.x || distanceToHookX < GAME_CONFIG.MISS_WINDOW * 2;
			if (isApproachingOrAtHook && distanceToHookX < minDistanceToCenter) {
				minDistanceToCenter = distanceToHookX;
				approachingFish = f;
			}
		}
	}
	var targetLaneY;
	if (approachingFish) {
		if (GameState.hookTargetLaneIndex !== approachingFish.lane) {
			GameState.hookTargetLaneIndex = approachingFish.lane;
		}
		targetLaneY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	} else {
		targetLaneY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y;
	}
	// Update hook Y position (X is handled by the wave animation)
	if (Math.abs(fishingElements.hook.y - targetLaneY) > 5) {
		// Only tween if significantly different
		tween(fishingElements.hook, {
			y: targetLaneY
		}, {
			duration: 150,
			easing: tween.easeOut
		});
		fishingElements.hook.originalY = targetLaneY;
	} //{bO} // Re-using last relevant ID from replaced block for context if appropriate
	updateLaneBracketsVisuals();
	// Update fish
	for (var i = fishArray.length - 1; i >= 0; i--) {
		var fish = fishArray[i];
		var previousFrameX = fish.lastX; // X position from the end of the previous game tick
		fish.update(); // Fish updates its own movement (fish.x) and appearance
		var currentFrameX = fish.x; // X position after this tick's update
		// Check for miss only if fish is active (not caught, not already missed)
		if (!fish.caught && !fish.missed) {
			var hookCenterX = fishingElements.hook.x;
			// GAME_CONFIG.MISS_WINDOW is the distance from hook center that still counts as a "miss" tap.
			// If fish center passes this boundary, it's considered a full pass.
			var missCheckBoundary = GAME_CONFIG.MISS_WINDOW;
			if (fish.speed > 0) {
				// Moving Left to Right
				// Fish's center (previousFrameX) was to the left of/at the hook's right miss boundary,
				// and its center (currentFrameX) is now to the right of it.
				if (previousFrameX <= hookCenterX + missCheckBoundary && currentFrameX > hookCenterX + missCheckBoundary) {
					showFeedback('miss', fish.lane);
					LK.getSound('miss').play();
					GameState.combo = 0;
					fish.missed = true; // Mark as missed
				}
			} else if (fish.speed < 0) {
				// Moving Right to Left
				// Fish's center (previousFrameX) was to the right of/at the hook's left miss boundary,
				// and its center (currentFrameX) is now to the left of it.
				if (previousFrameX >= hookCenterX - missCheckBoundary && currentFrameX < hookCenterX - missCheckBoundary) {
					showFeedback('miss', fish.lane);
					LK.getSound('miss').play();
					GameState.combo = 0;
					fish.missed = true; // Mark as missed
				}
			}
		}
		// Update lastX for the next frame, using the fish's position *after* its update this frame
		fish.lastX = currentFrameX;
		// Remove off-screen fish (if not caught).
		// If a fish is `missed`, it's also `!caught`, so it will be removed by this logic.
		if (!fish.caught && (fish.x < -250 || fish.x > 2048 + 250)) {
			// Increased buffer slightly
			fish.destroy();
			fishArray.splice(i, 1);
		}
	}
	// Update UI
	updateFishingUI();
	// Spawn and update music notes if active
	if (GameState.musicNotesActive && fishingElements && fishingElements.hook && !fishingElements.hook.destroyed && musicNotesContainer) {
		musicNoteSpawnCounter++;
		if (musicNoteSpawnCounter >= MUSIC_NOTE_SPAWN_INTERVAL_TICKS) {
			musicNoteSpawnCounter = 0;
			// Spawn notes from the fishing hook's position
			var spawnX = fishingElements.hook.x;
			var spawnY = fishingElements.hook.y - 30; // Spawn slightly above the hook's center for better visual origin
			var newNote = new MusicNoteParticle(spawnX, spawnY);
			musicNotesContainer.addChild(newNote);
			musicNotesArray.push(newNote);
			// Add scale pulse to the hook, synced with BPM
			if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) {
				var currentSongConfig = GameState.getCurrentSongConfig();
				var bpm = currentSongConfig && currentSongConfig.bpm ? currentSongConfig.bpm : 90; // Default to 90 BPM
				var beatDurationMs = 60000 / bpm;
				var pulsePhaseDuration = Math.max(50, beatDurationMs / 2); // Each phase (up/down) is half a beat, min 50ms
				var pulseScaleFactor = 1.2;
				// Ensure we have valid original scales, defaulting to 1 if undefined
				var originalScaleX = fishingElements.hook.scale.x !== undefined ? fishingElements.hook.scale.x : 1;
				var originalScaleY = fishingElements.hook.scale.y !== undefined ? fishingElements.hook.scale.y : 1;
				// Stop any previous scale tweens on the hook to prevent conflicts
				tween.stop(fishingElements.hook.scale);
				tween(fishingElements.hook.scale, {
					x: originalScaleX * pulseScaleFactor,
					y: originalScaleY * pulseScaleFactor
				}, {
					duration: pulsePhaseDuration,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) {
							tween(fishingElements.hook.scale, {
								x: originalScaleX,
								y: originalScaleY
							}, {
								duration: pulsePhaseDuration,
								easing: tween.easeIn // Or tween.easeOut for a softer return
							});
						}
					}
				});
			}
		}
	}
	// Update existing music notes
	for (var mnIdx = musicNotesArray.length - 1; mnIdx >= 0; mnIdx--) {
		var note = musicNotesArray[mnIdx];
		if (note) {
			note.update();
			if (note.isDone) {
				note.destroy();
				musicNotesArray.splice(mnIdx, 1);
			}
		} else {
			// Should not happen, but good to safeguard
			musicNotesArray.splice(mnIdx, 1);
		}
	}
	// Spawn bubbles for active fish
	if (bubbleContainer) {
		for (var f = 0; f < fishArray.length; f++) {
			var fish = fishArray[f];
			if (fish && !fish.caught && !fish.isHeld && fish.fishGraphics) {
				if (currentTime - fish.lastBubbleSpawnTime > fish.bubbleSpawnInterval) {
					fish.lastBubbleSpawnTime = currentTime;
					// Calculate tail position based on fish direction and width
					// fish.fishGraphics.width is the original asset width.
					// fish.fishGraphics.scale.x might be negative, but width property itself is positive.
					// The anchor is 0.5, so width/2 is distance from center to edge.
					var tailOffsetDirection = Math.sign(fish.speed) * -1; // Bubbles appear opposite to movement direction
					var bubbleX = fish.x + tailOffsetDirection * (fish.fishGraphics.width * Math.abs(fish.fishGraphics.scaleX) / 2) * 0.8; // 80% towards tail
					var bubbleY = fish.y + (Math.random() - 0.5) * (fish.fishGraphics.height * Math.abs(fish.fishGraphics.scaleY) / 4); // Slight Y variance around fish center
					var newBubble = new BubbleParticle(bubbleX, bubbleY);
					bubbleContainer.addChild(newBubble);
					bubblesArray.push(newBubble);
				}
			}
		}
	}
	// Update and remove bubbles
	for (var b = bubblesArray.length - 1; b >= 0; b--) {
		var bubble = bubblesArray[b];
		if (bubble) {
			// Extra safety check
			bubble.update();
			if (bubble.isDone) {
				bubble.destroy();
				bubblesArray.splice(b, 1);
			}
		} else {
			// If a null/undefined somehow got in
			bubblesArray.splice(b, 1);
		}
	}
};
// Initialize game
showScreen('title');
 No background.
 
 A music note. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 
 A white bubble. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 Blue gradient background starting lighter blue at the top of the image and going to a darker blue at the bottom.. In-Game asset. 2d. High contrast. No shadows
 A small single strand of loose floating seaweed. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 A thin wispy white cloud. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 Game logo for the game ‘Beat Fisher’. High def 80’s color themed SVG of the word.. In-Game asset. 2d. High contrast. No shadows
 A yellow star burst that says 'Perfect!' in the center. 80s arcade machine graphics. In-Game asset. 2d. High contrast. No shadows
 A red starburst with the word ‘Miss!’ In it. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 A green starburst with the word ‘Good!’ In it. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 White stylized square bracket. Pixelated. In-Game asset. 2d. High contrast. No shadows
 A sardine. 80s arcade machine graphics. Swimming Side profile. In-Game asset. 2d. High contrast. No shadows
 An anchovy. 80s arcade machine graphics. Swimming Side profile. In-Game asset. 2d. High contrast. No shadows
 A mackerel. 80s arcade machine graphics. Swimming Side profile. In-Game asset. 2d. High contrast. No shadows
 A golden fish. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 A double sided fishing hook with a small speaker in the center. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
 An SVG that says ‘Start’
 Blue to green gradient on the word instead of pink.