/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/**** 
* Classes
****/ 
var ButtonPunchline = Container.expand(function () {
	var self = Container.call(this);
	// Attach the ButtonPunchline asset
	var buttonGraphics = self.attachAsset('ButtonPunchline', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Add down event handler for punchline button
	self.down = function (x, y, obj) {
		// Play sound if enabled
		if (soundEnabled) {
			// Play the currently selected punchline sound
			if (currentPunchlineSound >= 0 && currentPunchlineSound < punchlineSounds.length && punchlineSounds[currentPunchlineSound].asset) {
				LK.getSound(punchlineSounds[currentPunchlineSound].asset).play();
			}
		}
		// Create confetti effect if enabled
		playConfettiEffect(self.x, self.y - 100);
	};
	return self;
});
var ButtonSettings = Container.expand(function () {
	var self = Container.call(this);
	// Attach the ButtonSettings asset
	var buttonGraphics = self.attachAsset('ButtonSettingsBackground', {
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0x000000,
		alpha: 0.5
	});
	var buttonGraphics = self.attachAsset('ButtonSettings', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Add down event handler to show settings popup
	self.down = function (x, y, obj) {
		// Toggle settings popup visibility
		if (settingsPopup) {
			settingsPopup.visible = !settingsPopup.visible;
			bypassTracking = settingsPopup.visible; // Toggle bypassTracking based on visibility 
			trollFace.visible = !settingsPopup.visible; // Hide trollface when settings popup is shown 
			// Call onShow when the popup becomes visible
			if (settingsPopup.visible && settingsPopup.onShow) {
				settingsPopup.onShow();
			}
		}
		// Play beep sound when settings button is toggled
		LK.getSound('beep').play();
		// Stop event propagation to prevent changing the face style
		if (obj.event && typeof obj.event.stopPropagation === 'function') {
			obj.event.stopPropagation();
		}
	};
	// Define any additional properties or methods for the ButtonSettings here
	return self;
});
var Confetti = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 50;
	self.colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
	self.gravity = 0.5;
	self.active = false;
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		// Create new particles
		for (var i = 0; i < self.particleCount; i++) {
			var particle = LK.getAsset('debugFacePoints', {
				anchorX: 0.5,
				anchorY: 0.5
			});
			particle.tint = self.colors[Math.floor(Math.random() * self.colors.length)];
			particle.scale.set(0.2, 0.2);
			// Set initial position
			particle.x = x;
			particle.y = y;
			// Set random velocity
			particle.vx = (Math.random() - 0.5) * 20;
			particle.vy = (Math.random() - 0.5) * 20 - 10;
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.active = true;
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var allSettled = true;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Apply gravity
			particle.vy += self.gravity;
			// Update position
			particle.x += particle.vx;
			particle.y += particle.vy;
			// Check if particle is still moving
			if (particle.vy < 10) {
				allSettled = false;
			}
			// Check if particle is off screen
			if (particle.y > 2732) {
				particle.vy = 0;
				particle.vx = 0;
			}
		}
		// If all particles have settled, stop updating
		if (allSettled) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var ConfettisHaha = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 10;
	self.active = false;
	self.screenWidth = 2048;
	self.screenHeight = 2732;
	// Configuration properties
	self.initialScale = 0.1; // Initial scale when particles appear
	self.minScale = 0.5; // Minimum final scale of particles
	self.scaleVariation = 0.5; // Random variation added to scale
	self.minDuration = 2; // Minimum animation duration in seconds
	self.durationVariation = 2; // Random variation added to duration
	self.maxDelaySeconds = 0.5; // Maximum random delay before animation starts
	self.fadeInThreshold = 0.2; // Progress threshold for fade in (0-1)
	self.fadeOutThreshold = 0.8; // Progress threshold for fade out (0-1)
	self.scaleUpThreshold = 0.25; // Progress threshold for scale up (0-1)
	self.settleThreshold = 0.35; // Progress threshold for settling to final scale (0-1)
	self.overshootFactor = 1.15; // How much to overshoot during scale up
	self.oscillationFrequency = 12; // Frequency of oscillation
	self.oscillationAmplitude = 0.05; // Amplitude of oscillation
	self.rotationRange = 30; // Range of random rotation in degrees (Β±30Β°)
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		// Create new particles
		for (var i = 0; i < self.particleCount; i++) {
			// Randomly select one of the Haha assets
			var assetIndex = Math.floor(Math.random() * 3) + 1;
			var particle = LK.getAsset('ConfettisHaha' + assetIndex, {
				anchorX: 0.5,
				anchorY: 0.5
			});
			// Set initial position randomly on the screen
			particle.x = Math.random() * self.screenWidth;
			particle.y = Math.random() * self.screenHeight;
			// Set initial scale and alpha
			particle.scale.set(self.initialScale, self.initialScale);
			particle.alpha = 0;
			// Set random initial rotation (Β±15 degrees)
			particle.rotation = (Math.random() * 60 - 30) * (Math.PI / 180); // Convert to radians
			// Set animation parameters
			particle.duration = self.minDuration + Math.random() * self.durationVariation; // 2-4 seconds (longer overall duration)
			particle.age = 0;
			particle.maxScale = self.minScale + Math.random() * self.scaleVariation; // 0.5-1.0
			// Add a small random delay for each particle (slightly longer for less abrupt appearance)
			particle.delay = Math.random() * self.maxDelaySeconds; // 0-0.5 seconds delay
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.active = true;
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var activeParticles = false;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Handle delay
			if (particle.delay > 0) {
				particle.delay -= 1 / 60; // Assuming 60fps
				activeParticles = true;
				continue;
			}
			// Update age
			particle.age += 1 / 60; // Assuming 60fps
			// Calculate progress (0 to 1)
			var progress = particle.age / particle.duration;
			if (progress < 1) {
				activeParticles = true;
				// Fade in gradually, stay visible longer, fade out gradually
				if (progress < self.fadeInThreshold) {
					particle.alpha = progress * (1 / self.fadeInThreshold); // 0 to 1 in first 20% of time
				} else if (progress > self.fadeOutThreshold) {
					particle.alpha = (1 - progress) * (1 / (1 - self.fadeOutThreshold)); // 1 to 0 in last 20% of time
				} else {
					particle.alpha = 1; // Stay fully visible for 60% of time
				}
				// Explosive scale effect - slightly slower scale up, then stay at max
				var scaleProgress;
				if (progress < self.scaleUpThreshold) {
					// Slightly slower elastic-like scaling in first 25% of time
					// Overshoot slightly and then settle
					scaleProgress = self.overshootFactor * Math.pow(progress / self.scaleUpThreshold, 0.8);
					if (scaleProgress > 1.1) {
						scaleProgress = 1.1;
					}
				} else if (progress < self.settleThreshold) {
					// Slower settle to normal scale
					scaleProgress = 1.1 - (progress - self.scaleUpThreshold) * (0.1 / (self.settleThreshold - self.scaleUpThreshold)); // 1.1 to 1.0
				} else {
					scaleProgress = 1; // Stay at max scale
				}
				var currentScale = particle.maxScale * scaleProgress;
				// Add oscillation after reaching max scale to simulate laughing
				if (progress >= self.settleThreshold) {
					// Calculate oscillation frequency and amplitude
					var oscillationFreq = self.oscillationFrequency; // Higher = faster oscillation
					var oscillationAmplitude = self.oscillationAmplitude; // Higher = more intense oscillation
					// Apply sine wave oscillation that decreases in amplitude over time
					var oscillationFactor = Math.sin(progress * oscillationFreq * Math.PI) * oscillationAmplitude;
					// Gradually reduce oscillation amplitude as we approach the end
					var dampingFactor = 1 - (progress - self.settleThreshold) / (1 - self.settleThreshold);
					oscillationFactor *= dampingFactor;
					// Apply oscillation to scale
					currentScale *= 1 + oscillationFactor;
				}
				particle.scale.set(currentScale, currentScale);
			}
		}
		// If no active particles, stop updating
		if (!activeParticles) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var ConfettisParty = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 200;
	self.colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
	self.gravity = 0.25;
	self.active = false;
	self.screenWidth = 2048;
	self.screenHeight = 2732;
	self.spawnedParticles = 0; // Track how many particles have been spawned
	self.spawnRate = 10; // Particles to spawn per frame
	self.spawnComplete = false; // Flag to track if all particles have been spawned
	// Configuration properties
	self.minScale = 0.5; // Minimum scale of particles
	self.scaleVariation = 1.0; // Random variation added to scale
	self.yStartPosition = 600; // Starting Y position for particles
	self.yVariation = self.screenHeight / 2; // Random variation in Y position
	self.centerY = self.screenHeight / 8; // Target center Y position
	self.minSpeedX = 5; // Minimum horizontal speed
	self.speedXVariation = 5; // Random variation in X speed
	self.minSpeedY = 15; // Minimum vertical speed
	self.speedYVariation = 10; // Random variation in Y speed
	self.spawnInterval = 50; // Milliseconds between spawning batches
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		self.spawnedParticles = 0;
		self.spawnComplete = false;
		self.active = true;
		// Initial spawn
		self.spawnParticles();
		// Set up interval for continued spawning
		var spawnInterval = LK.setInterval(function () {
			self.spawnParticles();
			if (self.spawnComplete) {
				LK.clearInterval(spawnInterval);
			}
		}, self.spawnInterval); // Spawn every 50ms for a more gradual effect
	};
	// Spawn a batch of particles
	self.spawnParticles = function () {
		if (self.spawnComplete) {
			return;
		}
		var particlesToSpawn = Math.min(self.spawnRate, self.particleCount - self.spawnedParticles);
		for (var i = 0; i < particlesToSpawn; i++) {
			var particle = LK.getAsset('ConfettiPartyBase', {
				anchorX: 0.5,
				anchorY: 0.5
			});
			// Random color
			particle.tint = self.colors[Math.floor(Math.random() * self.colors.length)];
			// Random scale between 0.5 and 1.5
			var scale = self.minScale + Math.random() * self.scaleVariation;
			particle.scale.set(scale, scale);
			// Random rotation
			particle.rotation = Math.random() * Math.PI * 2;
			// Set initial position at left or right border
			var startFromLeft = Math.random() > 0.5;
			particle.x = startFromLeft ? 0 : self.screenWidth;
			particle.y = self.yStartPosition + Math.random() * self.yVariation;
			// Set velocity toward upper center
			var centerX = self.screenWidth / 2;
			var centerY = self.centerY;
			// Calculate angle to center
			var dx = centerX - particle.x;
			var dy = centerY - particle.y;
			var angle = Math.atan2(dy, dx);
			// Set velocity based on angle with some randomness
			var speedX = self.minSpeedX + Math.random() * self.speedXVariation;
			var speedY = self.minSpeedY + Math.random() * self.speedYVariation;
			particle.vx = Math.cos(angle) * speedX;
			particle.vy = Math.sin(angle) * speedY;
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.spawnedParticles += particlesToSpawn;
		if (self.spawnedParticles >= self.particleCount) {
			self.spawnComplete = true;
		}
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var activeParticles = false;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Apply gravity after particles reach their peak
			if (particle.vy > 0) {
				particle.vy += self.gravity;
			} else {
				// Slow down upward movement
				particle.vy += self.gravity * 0.5;
			}
			// Update position
			particle.x += particle.vx;
			particle.y += particle.vy;
			// Rotate particle
			particle.rotation += 0.05;
			// Check if particle is still active
			if (particle.y < self.screenHeight) {
				activeParticles = true;
			}
		}
		// If no active particles, stop updating
		if (!activeParticles) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var ConfettisSmiley = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 10;
	self.active = false;
	self.screenWidth = 2048;
	self.screenHeight = 2732;
	// Configuration properties
	self.baseOffsetX = 250; // How far offscreen particles start
	self.minSpeed = 9; // Minimum horizontal speed
	self.speedVariation = 3; // Random variation added to speed
	self.rotationSpeedRatio = 0.005; // Ratio of rotation speed to horizontal speed
	self.minScale = 0.7; // Minimum scale of particles
	self.scaleVariation = 0.3; // Random variation added to scale
	self.maxDelaySeconds = 2; // Maximum random delay before movement starts
	self.yMargin = 200; // Margin from top and bottom of screen
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		// Create new particles
		for (var i = 0; i < self.particleCount; i++) {
			// Randomly select one of the Smiley assets
			var assetIndex = Math.floor(Math.random() * 3) + 1;
			var particle = LK.getAsset('ConfettisSmiley' + assetIndex, {
				anchorX: 0.5,
				anchorY: 0.5
			});
			// Set initial position at left or right border
			var startFromLeft = Math.random() > 0.5;
			particle.x = startFromLeft ? -self.baseOffsetX : self.screenWidth + self.baseOffsetX;
			particle.y = self.yMargin + Math.random() * (self.screenHeight - 2 * self.yMargin); // Random Y position with margin
			// Set horizontal velocity
			particle.vx = startFromLeft ? self.minSpeed + Math.random() * self.speedVariation : -(self.minSpeed + Math.random() * self.speedVariation);
			// Set rotation speed (faster for faster moving particles)
			particle.rotationSpeed = particle.vx * self.rotationSpeedRatio;
			// Set scale
			var scale = self.minScale + Math.random() * self.scaleVariation;
			particle.scale.set(scale, scale);
			// Add delay before starting movement
			particle.delay = Math.random() * self.maxDelaySeconds; // 0-maxDelaySeconds seconds delay
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.active = true;
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var activeParticles = false;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Handle delay
			if (particle.delay > 0) {
				particle.delay -= 1 / 60; // Assuming 60fps
				activeParticles = true;
				continue;
			}
			// Update position
			particle.x += particle.vx;
			// Rotate particle (rolling effect)
			particle.rotation += particle.rotationSpeed;
			// Check if particle is still on screen (with margin)
			if (particle.vx > 0 && particle.x < self.screenWidth + self.baseOffsetX || particle.vx < 0 && particle.x > -self.baseOffsetX) {
				activeParticles = true;
			}
		}
		// If no active particles, stop updating
		if (!activeParticles) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var DebugPoints = Container.expand(function () {
	var self = Container.call(this);
	// Create points for face tracking debugging
	self.points = {
		leftEye: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		rightEye: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		noseTip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		mouthCenter: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		upperLip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		lowerLip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		chin: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		})
	};
	// Update debug points to match face points
	self.update = function () {
		if (!facekit) {
			return;
		}
		if (facekit.leftEye) {
			self.points.leftEye.x = facekit.leftEye.x;
			self.points.leftEye.y = facekit.leftEye.y;
		}
		if (facekit.rightEye) {
			self.points.rightEye.x = facekit.rightEye.x;
			self.points.rightEye.y = facekit.rightEye.y;
		}
		if (facekit.noseTip) {
			self.points.noseTip.x = facekit.noseTip.x;
			self.points.noseTip.y = facekit.noseTip.y;
		}
		if (facekit.mouthCenter) {
			self.points.mouthCenter.x = facekit.mouthCenter.x;
			self.points.mouthCenter.y = facekit.mouthCenter.y;
		}
		if (facekit.upperLip) {
			self.points.upperLip.x = facekit.upperLip.x;
			self.points.upperLip.y = facekit.upperLip.y;
		}
		if (facekit.lowerLip) {
			self.points.lowerLip.x = facekit.lowerLip.x;
			self.points.lowerLip.y = facekit.lowerLip.y;
		}
		if (facekit.chin) {
			self.points.chin.x = facekit.chin.x;
			self.points.chin.y = facekit.chin.y;
		}
	};
	return self;
});
var SettingsPopup = Container.expand(function () {
	var self = Container.call(this);
	var popupGraphics = self.attachAsset('FrameSettingsPopup', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	var popupGraphics = self.attachAsset('FrameSettingsTitle', {
		anchorX: 0.5,
		anchorY: 0.5,
		y: -1060,
		blendMode: 0
	});
	// Create sound toggle container
	self.soundToggle = new Container();
	self.soundToggle.x = -450;
	self.soundToggle.y = -700;
	self.addChild(self.soundToggle);
	// Add sound icon
	var soundIcon = self.soundToggle.attachAsset('IconSound', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -0
	});
	// Add click handler for sound icon
	soundIcon.down = function (x, y, obj) {
		// Play the current sound if it's not "None" and sound is enabled
		if (soundEnabled && punchlineSounds[currentPunchlineSound].asset) {
			LK.getSound(punchlineSounds[currentPunchlineSound].asset).play();
		}
	};
	// Add left chevron button
	self.soundLeftChevronButton = self.soundToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.soundLeftChevronButton.down = function (x, y, obj) {
		self.changeSound(-1);
	};
	// Add sound text that displays "None" by default
	self.soundText = new Text2("None", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.soundText.x = 320;
	self.soundText.y = -40;
	self.soundToggle.addChild(self.soundText);
	// Add right chevron button
	self.soundRightChevronButton = self.soundToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.soundRightChevronButton.down = function (x, y, obj) {
		self.changeSound(1);
	};
	// Function to change the selected sound
	self.changeSound = function (direction) {
		// Update the current sound index
		currentPunchlineSound += direction;
		storage.currentPunchlineSound = currentPunchlineSound;
		// Cycle through the sounds instead of clamping
		if (currentPunchlineSound < 0) {
			currentPunchlineSound = punchlineSounds.length - 1;
		} else if (currentPunchlineSound >= punchlineSounds.length) {
			currentPunchlineSound = 0;
		}
		// Ensure currentPunchlineSound is within bounds
		if (currentPunchlineSound >= 0 && currentPunchlineSound < punchlineSounds.length) {
			// Update the sound text
			self.soundText.setText(punchlineSounds[currentPunchlineSound].name);
		} else {
			// Fallback to "None" if out of bounds
			self.soundText.setText("None");
		}
		// Play the sound if it's not "None" and sound is enabled
		if (soundEnabled && punchlineSounds[currentPunchlineSound].asset) {
			LK.getSound(punchlineSounds[currentPunchlineSound].asset).play();
		}
		updatePunchlineButtonVisibility();
	};
	// Initialize sound text with current selection
	if (currentPunchlineSound >= 0 && currentPunchlineSound < punchlineSounds.length) {
		self.soundText.setText(punchlineSounds[currentPunchlineSound].name);
	} else {
		self.soundText.setText("None");
	}
	// Create confetti toggle container
	self.confettiToggle = new Container();
	self.confettiToggle.x = -450;
	self.confettiToggle.y = -300;
	self.addChild(self.confettiToggle);
	// Add confetti icon
	var confettiIcon = self.confettiToggle.attachAsset('IconConfetti', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0
	});
	// Add click handler for confetti icon
	confettiIcon.down = function (x, y, obj) {
		// Play the selected confetti effect when icon is tapped
		playConfettiEffect(self.x, self.y);
		// Play beep sound when confetti is toggled
		LK.getSound('beep').play();
	};
	// Add left chevron button
	self.confettiLeftChevronButton = self.confettiToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.confettiLeftChevronButton.down = function (x, y, obj) {
		self.changeConfetti(-1);
	};
	// Add confetti text that displays "None" by default
	self.confettiText = new Text2("None", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.confettiText.x = 320;
	self.confettiText.y = -40;
	self.confettiToggle.addChild(self.confettiText);
	// Add right chevron button
	self.confettiRightChevronButton = self.confettiToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.confettiRightChevronButton.down = function (x, y, obj) {
		self.changeConfetti(1);
	};
	// Function to change the selected confetti effect
	self.changeConfetti = function (direction) {
		// Update the current confetti index
		currentPunchlineConfetti += direction;
		// Validate the index to ensure it's within bounds
		if (currentPunchlineConfetti < 0) {
			currentPunchlineConfetti = punchlineConfettis.length - 1;
		} else if (currentPunchlineConfetti >= punchlineConfettis.length) {
			currentPunchlineConfetti = 0;
		}
		// Store the validated index
		storage.currentPunchlineConfetti = currentPunchlineConfetti;
		// Update the confetti text
		self.confettiText.setText(punchlineConfettis[currentPunchlineConfetti].name);
		// Play the selected confetti effect when changing types
		playConfettiEffect(self.x, self.y);
		updatePunchlineButtonVisibility();
		// Play beep sound when confetti is toggled
		LK.getSound('beep').play();
	};
	// Initialize confetti text with current selection
	self.confettiText.setText(punchlineConfettis[currentPunchlineConfetti].name);
	// Create background toggle container
	self.backgroundToggle = new Container();
	self.backgroundToggle.x = -450;
	self.backgroundToggle.y = 100;
	self.addChild(self.backgroundToggle);
	// Add background icon
	var backgroundIcon = self.backgroundToggle.attachAsset('IconBackgroundToggle', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0
	});
	// Add click handler for background icon
	backgroundIcon.down = function (x, y, obj) {
		// Toggle background visibility
		toggleBackground();
	};
	// Add background text
	self.backgroundText = new Text2(backgroundVisible ? "Blank" : "Camera", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.backgroundText.x = 320;
	self.backgroundText.y = -40;
	self.backgroundToggle.addChild(self.backgroundText);
	// Add left chevron button
	self.backgroundLeftChevronButton = self.backgroundToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.backgroundLeftChevronButton.down = function (x, y, obj) {
		self.changeBackground(-1);
	};
	// Add right chevron button
	self.backgroundRightChevronButton = self.backgroundToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.backgroundRightChevronButton.down = function (x, y, obj) {
		self.changeBackground(1);
	};
	// Function to change the background visibility
	self.changeBackground = function (direction) {
		// Toggle background visibility using the central function
		// which will also update the text in the settings popup
		toggleBackground();
	};
	// Initialize background text with current selection
	self.backgroundText.setText(backgroundVisible ? "Blank" : "Camera");
	// Create zoom toggle container
	self.zoomToggle = new Container();
	self.zoomToggle.x = -450;
	self.zoomToggle.y = 500; // Position below background toggle
	self.addChild(self.zoomToggle);
	// Add zoom icon
	var zoomIcon = self.zoomToggle.attachAsset('IconZoom', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0
	});
	// Add click handler for zoom icon
	zoomIcon.down = function (x, y, obj) {
		// Play beep sound when zoom icon is clicked
		LK.getSound('beep').play();
	};
	// Add zoom text
	self.zoomText = new Text2("Zoom " + Math.round(currentZoomLevel * 100) + "%", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.zoomText.x = 320;
	self.zoomText.y = -40;
	self.zoomToggle.addChild(self.zoomText);
	// Add left chevron button
	self.zoomLeftChevronButton = self.zoomToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.zoomLeftChevronButton.down = function (x, y, obj) {
		self.changeZoom(-0.1); // Decrease zoom by 10%
	};
	// Add right chevron button
	self.zoomRightChevronButton = self.zoomToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.zoomRightChevronButton.down = function (x, y, obj) {
		self.changeZoom(0.1); // Increase zoom by 10%
	};
	// Function to change the zoom level
	self.changeZoom = function (delta) {
		// Update zoom level with bounds checking (0-100%)
		currentZoomLevel = Math.max(0, Math.min(1, currentZoomLevel + delta));
		// Calculate the current scale ratio based on zoom level
		// Linear interpolation between minScaleRatio and maxScaleRatio
		currentScaleRatio = minScaleRatio + (maxScaleRatio - minScaleRatio) * currentZoomLevel;
		// Apply the new scale ratio to the troll face container
		if (trollFace) {
			trollFace.scale.x = currentScaleRatio;
			trollFace.scale.y = currentScaleRatio;
		}
		// Save to storage
		storage.currentZoomLevel = currentZoomLevel;
		// Update the zoom text
		self.updateZoomText();
		// Play beep sound when zoom is changed
		LK.getSound('beep').play();
	};
	// Function to update zoom text
	self.updateZoomText = function () {
		self.zoomText.setText("Zoom " + Math.round(currentZoomLevel * 100) + "%");
	};
	// Initialize zoom text with current level
	self.updateZoomText();
	// Add ButtonOk at the bottom of the settingsPopup
	var buttonOk = self.attachAsset('ButtonOk', {
		anchorX: 0.5,
		anchorY: 0.5,
		y: 1060 // Position at the bottom of the popup
	});
	buttonOk.down = function (x, y, obj) {
		// Hide the settings popup when ButtonOk is pressed
		self.visible = false;
		bypassTracking = false; // Reset bypassTracking when closing settings
		trollFace.visible = true; // Show trollface when settings popup is hidden
		// Play beep sound when settings popup is closed
		LK.getSound('beep').play();
	};
	// Define any additional properties or methods for the SettingsPopup here
	// Add a function to update the background toggle label
	self.updateBackgroundToggleLabel = function () {
		// Update the background text to reflect the current state
		self.backgroundText.setText(backgroundVisible ? "Blank" : "Camera");
	};
	// Define onShow to call the update function
	self.onShow = function () {
		self.updateBackgroundToggleLabel();
		self.updateZoomText();
	};
	return self;
});
var TrollFace = Container.expand(function () {
	var self = Container.call(this);
	self.scales = {
		head: {
			x: 1,
			y: 1
		},
		leftEye: {
			x: 1,
			y: 1
		},
		rightEye: {
			x: 1,
			y: 1
		},
		upperLip: {
			x: 1,
			y: 1
		},
		lowerLip: {
			x: 1,
			y: 1
		}
	};
	// Properties
	self.currentStyle = 1;
	self.currentOffsets = trollFaceOffsets[self.currentStyle - 1];
	self.elements = {};
	// Initialize cached position objects to reuse
	self.positionCache = {
		head: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		leftEye: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		rightEye: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		upperLip: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		lowerLip: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		}
	};
	// Initialize the troll face elements
	self.initialize = function (style) {
		// Clear previous elements
		self.removeAllElements();
		// Create new elements for the selected style
		self.createFaceElements(style || self.currentStyle);
	};
	// Create face elements for a specific style
	self.createFaceElements = function (style) {
		// Create head
		self.elements.head = self.attachAsset('trollHead' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1,
			alpha: 1 // DEBUG
		});
		// Create eyes
		self.elements.leftEye = self.attachAsset('trollLeftEye' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		self.elements.rightEye = self.attachAsset('trollRightEye' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		// Create upper lip
		self.elements.upperLip = self.attachAsset('trollUpperLip' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		// Create lower lip
		self.elements.lowerLip = self.attachAsset('trollLowerLip' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
	};
	// Remove all face elements
	self.removeAllElements = function () {
		if (self.elements.head) {
			self.removeChild(self.elements.head);
		}
		if (self.elements.leftEye) {
			self.removeChild(self.elements.leftEye);
		}
		if (self.elements.rightEye) {
			self.removeChild(self.elements.rightEye);
		}
		if (self.elements.upperLip) {
			self.removeChild(self.elements.upperLip);
		}
		if (self.elements.lowerLip) {
			self.removeChild(self.elements.lowerLip);
		}
	};
	// Change to the next troll face style
	self.nextStyle = function (forcedStyle) {
		self.currentStyle = forcedStyle || self.currentStyle % trollFaceOffsets.length + 1;
		self.initialize();
		self.currentOffsets = trollFaceOffsets[self.currentStyle - 1];
		// Calculate face scale once and reuse
		var baseScale = self.calculateFaceScale(facekit);
		// Calculate all scales upfront
		self.scales = {
			head: {
				x: baseScale * self.currentOffsets.head.sx,
				y: baseScale * self.currentOffsets.head.sy
			},
			leftEye: {
				x: baseScale * self.currentOffsets.leftEye.sx,
				y: baseScale * self.currentOffsets.leftEye.sy
			},
			rightEye: {
				x: baseScale * self.currentOffsets.rightEye.sx,
				y: baseScale * self.currentOffsets.rightEye.sy
			},
			upperLip: {
				x: baseScale * self.currentOffsets.upperLip.sx,
				y: baseScale * self.currentOffsets.upperLip.sy
			},
			lowerLip: {
				x: baseScale * self.currentOffsets.lowerLip.sx,
				y: baseScale * self.currentOffsets.lowerLip.sy
			}
		};
		return self.currentStyle;
	};
	// Helper function to clamp a value between min and max
	self.clampPosition = function (value, min, max) {
		return Math.min(Math.max(value, min), max);
	};
	// Helper function to ensure scale is an object
	self.ensureScaleIsObject = function (element) {
		if (_typeof(element.scale) !== 'object') {
			element.scale = {
				x: 1,
				y: 1
			};
		}
	};
	// Helper function to update a face element
	self.updateFaceElement = function (elementName, x, y, scaleX, scaleY, makeVisible) {
		var element = self.elements[elementName];
		if (!element) {
			return;
		}
		// Ensure scale is an object
		self.ensureScaleIsObject(element);
		// Apply position with clamping using scaled boundaries
		var elementOffset = self.currentOffsets[elementName];
		element.x = self.clampPosition(x, elementOffset.minX * currentEyeDistance, elementOffset.maxX * currentEyeDistance);
		element.y = self.clampPosition(y, elementOffset.minY * currentEyeDistance, elementOffset.maxY * currentEyeDistance);
		// Apply scale
		element.scale.x = scaleX;
		element.scale.y = scaleY;
		// Set visibility if needed
		if (makeVisible) {
			element.visible = true;
		}
	};
	// Helper function to update all face elements
	self.updateAllFaceElements = function (kit, makeVisible, useDefaultScales) {
		var elementNames = ['head', 'leftEye', 'rightEye', 'upperLip', 'lowerLip'];
		var positions = self.positionCache;
		// Calculate positions for all elements
		positions.head.x = 0;
		positions.head.y = 0;
		positions.head.scaleX = useDefaultScales ? 1 : self.scales.head.x;
		positions.head.scaleY = useDefaultScales ? 1 : self.scales.head.y;
		// Get the rotation angle for constraint calculations
		var rotationAngle = self.rotation;
		var cosAngle = Math.cos(-rotationAngle); // Negative to counter-rotate
		var sinAngle = Math.sin(-rotationAngle);
		// For other elements, calculate based on kit positions
		for (var i = 1; i < elementNames.length; i++) {
			var name = elementNames[i];
			var kitElement = kit[name];
			if (kitElement) {
				var scaleX, scaleY;
				// Determine which scale to use based on element type and useDefaultScales flag
				if (useDefaultScales) {
					scaleX = 1 * self.currentOffsets[name].sx;
					scaleY = 1 * self.currentOffsets[name].sy;
				} else {
					if (name === 'leftEye') {
						scaleX = self.scales.leftEye.x;
						scaleY = self.scales.leftEye.y;
					} else if (name === 'rightEye') {
						scaleX = self.scales.rightEye.x;
						scaleY = self.scales.rightEye.y;
					} else if (name === 'upperLip') {
						scaleX = self.scales.upperLip.x;
						scaleY = self.scales.upperLip.y;
					} else if (name === 'lowerLip') {
						scaleX = self.scales.lowerLip.x;
						scaleY = self.scales.lowerLip.y;
					}
				}
				// Calculate position using relative offsets scaled by eye distance
				var rawX = kitElement.x - self.x + self.currentOffsets[name].x * currentEyeDistance;
				var rawY = kitElement.y - self.y + self.currentOffsets[name].y * currentEyeDistance;
				// Apply rotation constraints to maintain relative positions
				// This counter-rotates the positions to keep elements in proper alignment
				positions[name].x = rawX * cosAngle - rawY * sinAngle;
				positions[name].y = rawX * sinAngle + rawY * cosAngle;
				positions[name].scaleX = scaleX;
				positions[name].scaleY = scaleY;
			}
		}
		// Update each element with calculated positions
		for (var j = 0; j < elementNames.length; j++) {
			var elemName = elementNames[j];
			if (self.elements[elemName] && positions[elemName]) {
				var pos = positions[elemName];
				self.updateFaceElement(elemName, pos.x, pos.y, pos.scaleX, pos.scaleY, makeVisible);
			}
		}
		// Handle mouth open adjustment
		if (kit.mouthOpen && self.elements.lowerLip) {
			//self.elements.lowerLip.scale.y = self.scales.lip.y * 1.5;
		}
	};
	// Update face elements to match real face
	self.updateFacePosition = function () {
		if (!facekit) {
			return;
		}
		// If in first phase of centering, directly apply stored positions with translation
		if (self.isFirstPhaseCentering && self.relativePositions) {
			// Apply stored positions to each element
			for (var name in self.elements) {
				if (self.relativePositions[name]) {
					var element = self.elements[name];
					element.x = self.relativePositions[name].x;
					element.y = self.relativePositions[name].y;
					element.scale.x = self.relativePositions[name].scaleX;
					element.scale.y = self.relativePositions[name].scaleY;
				}
			}
			// Set rotation to stored value
			self.rotation = self.relativePositions.rotation;
			return; // Skip the normal update process
		}
		// Get kit based on tracking state
		var kit = bypassTracking ? fakeCamera : facekitMgr.isTracking ? facekitMgr.currentFacekit : facekit;
		// If re-centering, use fakeCamera data for positioning without changing bypassTracking
		if (self.isRecentering) {
			kit = fakeCamera;
		}
		// Update global eye distance once per frame
		currentEyeDistance = self.getEyeDistance(kit);
		var baseScale = self.calculateFaceScale(kit);
		// Calculate face rotation - use FacekitManager if tracking, otherwise use local calculation
		var rotation = facekitMgr.isTracking ? facekitMgr.currentRotation + Math.PI : self.calculateFaceRotation(kit);
		// If re-centering or in first phase, reset rotation to 0
		if (self.isRecentering || self.isFirstPhaseCentering) {
			rotation = 0;
		}
		// Update scales
		self.scales = {
			head: {
				x: baseScale * self.currentOffsets.head.sx,
				y: baseScale * self.currentOffsets.head.sy
			},
			leftEye: {
				x: baseScale * self.currentOffsets.leftEye.sx,
				y: baseScale * self.currentOffsets.leftEye.sy
			},
			rightEye: {
				x: baseScale * self.currentOffsets.rightEye.sx,
				y: baseScale * self.currentOffsets.rightEye.sy
			},
			upperLip: {
				x: baseScale * self.currentOffsets.upperLip.sx,
				y: baseScale * self.currentOffsets.upperLip.sy
			},
			lowerLip: {
				x: baseScale * self.currentOffsets.lowerLip.sx,
				y: baseScale * self.currentOffsets.lowerLip.sy
			}
		};
		if (bypassTracking) {
			self.x = 2048 / 2;
			self.y = 2732 / 2;
			self.rotation = 0; // Reset rotation in bypass mode
			// Use the global fakeCamera with dynamic scales
			self.updateAllFaceElements(fakeCamera, true, false);
			return;
		}
		// Apply rotation to the entire troll face container
		self.rotation = rotation;
		// Update all elements with kit
		self.updateAllFaceElements(kit, true, false);
	};
	// Calculate scale based on eye distance
	self.calculateFaceScale = function (kit) {
		return currentEyeDistance * 0.005;
	};
	// Calculate face rotation angle based on eye positions
	self.calculateFaceRotation = function (kit) {
		if (kit && kit.leftEye && kit.rightEye) {
			// Get eye positions
			var leftEye = kit.leftEye;
			var rightEye = kit.rightEye;
			// Calculate angle - simpler approach
			// This gives us the angle of the line connecting the eyes
			var angle = Math.atan2(rightEye.y - leftEye.y, rightEye.x - leftEye.x);
			// More efficient angle wrapping using modulo
			var angleDiff = angle - currentRotation;
			// Normalize the difference to [-PI, PI] range
			// Using efficient modulo approach rather than while loops
			angleDiff = (angleDiff + Math.PI) % (2 * Math.PI) - Math.PI;
			// Apply smoothing to the rotation
			currentRotation = currentRotation + angleDiff * 0.2;
			return currentRotation + Math.PI;
		}
		return 0; // Default rotation (no rotation)
	};
	// Get eye distance from kit
	self.getEyeDistance = function (kit) {
		if (kit && kit.leftEye && kit.rightEye) {
			var dx = kit.leftEye.x - kit.rightEye.x;
			var dy = kit.leftEye.y - kit.rightEye.y;
			return Math.sqrt(dx * dx + dy * dy);
		}
		return 200; // Default eye distance
	};
	// Add a property to track if we're in re-centering mode
	self.isRecentering = false;
	// Add a property to track if we're in the first phase of centering (maintaining relative positions)
	self.isFirstPhaseCentering = false;
	// Store relative positions of elements during centering
	self.relativePositions = {};
	// Capture current face positions for first phase centering
	self.captureCurrentPositions = function () {
		// Store the exact current positions and scales of all elements
		if (!self.elements) {
			return;
		}
		// Create a snapshot of the current state
		self.relativePositions = {
			// Store positions and scales for each element
			head: self.elements.head ? {
				x: self.elements.head.x,
				y: self.elements.head.y,
				scaleX: self.elements.head.scale.x,
				scaleY: self.elements.head.scale.y
			} : null,
			leftEye: self.elements.leftEye ? {
				x: self.elements.leftEye.x,
				y: self.elements.leftEye.y,
				scaleX: self.elements.leftEye.scale.x,
				scaleY: self.elements.leftEye.scale.y
			} : null,
			rightEye: self.elements.rightEye ? {
				x: self.elements.rightEye.x,
				y: self.elements.rightEye.y,
				scaleX: self.elements.rightEye.scale.x,
				scaleY: self.elements.rightEye.scale.y
			} : null,
			upperLip: self.elements.upperLip ? {
				x: self.elements.upperLip.x,
				y: self.elements.upperLip.y,
				scaleX: self.elements.upperLip.scale.x,
				scaleY: self.elements.upperLip.scale.y
			} : null,
			lowerLip: self.elements.lowerLip ? {
				x: self.elements.lowerLip.x,
				y: self.elements.lowerLip.y,
				scaleX: self.elements.lowerLip.scale.x,
				scaleY: self.elements.lowerLip.scale.y
			} : null,
			// Store current rotation
			rotation: self.rotation,
			// Store current container position
			containerX: self.x,
			containerY: self.y
		};
	};
	return self;
});
/**** 
* Initialize Game
****/ 
// FacekitManager: handles face tracking, smoothing, and fallback
var game = new LK.Game({
	backgroundColor: 0xFFFFFF
});
/**** 
* Game Code
****/ 
// FacekitManager: handles face tracking, smoothing, and fallback
/**** 
* Global Variables
****/ 
var FacekitManager = function FacekitManager() {
	var self = new Container();
	// Public properties
	self.currentFacekit = null; // Current smoothed face data
	self.isTracking = false; // Current tracking state
	self.currentRotation = 0; // Current smoothed rotation (accessible directly for performance)
	self.smoothingFactor = 0.5; // Default smoothing factor for facial features
	self.headSmoothingFactor = 0.2; // Separate smoothing factor for head position (noseTip)
	self.lastLipPosition = null; // Last lower lip position for tracking detection
	self.trackingStoppedCounter = 100; // Counter for tracking detection
	self.trackingStoppedDelay = 100; // Delay threshold for tracking detection
	// Default positions - defined inline
	var defaultFacekit = {
		leftEye: {
			x: 1380,
			y: 958
		},
		rightEye: {
			x: 673,
			y: 970
		},
		upperLip: {
			x: 1027,
			y: 1610
		},
		lowerLip: {
			x: 1030,
			y: 1613
		},
		noseTip: {
			x: 1024,
			y: 1366
		},
		mouthOpen: false
	};
	// Initialize manager
	self.initialize = function (fakeFacekit) {
		// Use provided fallback or default
		self.fallbackFacekit = fakeFacekit || defaultFacekit;
		// Initialize current data as reference to fallback
		self.currentFacekit = self.fallbackFacekit;
		// Create a reusable object for smoothing calculations
		self.smoothFacekit = {
			leftEye: {
				x: 0,
				y: 0
			},
			rightEye: {
				x: 0,
				y: 0
			},
			upperLip: {
				x: 0,
				y: 0
			},
			lowerLip: {
				x: 0,
				y: 0
			},
			noseTip: {
				x: 0,
				y: 0
			},
			mouthOpen: false
		};
		return self;
	};
	// Process new tracking data and update tracking status
	self.updateTrackingStatus = function (rawFacekit) {
		// Check if there's valid face data
		var hasFaceData = !!(rawFacekit && rawFacekit.lowerLip);
		if (!hasFaceData) {
			self.isTracking = false;
			return false;
		}
		// Check if lower lip position has changed using existing tracking mechanism
		if (self.lastLipPosition === rawFacekit.lowerLip.y) {
			self.trackingStoppedCounter--;
			if (self.trackingStoppedCounter <= 0) {
				self.isTracking = false;
				self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
			}
		} else {
			self.isTracking = true;
			self.lastLipPosition = rawFacekit.lowerLip.y;
			self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
		}
		// If tracking, process the face data
		if (self.isTracking) {
			// If tracking just started, initialize smooth values
			if (self.currentFacekit === self.fallbackFacekit) {
				self._initSmoothValues(rawFacekit);
			}
			// Apply smoothing to each facial feature
			self._smoothValues(rawFacekit);
			// Set currentFacekit to the smoothed values
			self.currentFacekit = self.smoothFacekit;
		} else {
			// Use fallback when not tracking
			self.currentFacekit = self.fallbackFacekit;
		}
		// Return tracking state
		return self.isTracking;
	};
	// Initialize smooth values with raw data
	self._initSmoothValues = function (rawFacekit) {
		// Directly set initial values from raw data
		for (var key in rawFacekit) {
			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
				self.smoothFacekit[key].x = rawFacekit[key].x;
				self.smoothFacekit[key].y = rawFacekit[key].y;
			}
		}
		// Initialize rotation directly
		if (rawFacekit.leftEye && rawFacekit.rightEye) {
			self.currentRotation = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
		}
	};
	// Apply smoothing to all values
	self._smoothValues = function (rawFacekit) {
		// Smooth positions (reuse existing objects)
		for (var key in rawFacekit) {
			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
				// Apply position smoothing directly with different factors for head vs. other elements
				var factor = key === 'noseTip' ? self.headSmoothingFactor : self.smoothingFactor;
				self.smoothFacekit[key].x += (rawFacekit[key].x - self.smoothFacekit[key].x) * factor;
				self.smoothFacekit[key].y += (rawFacekit[key].y - self.smoothFacekit[key].y) * factor;
			}
		}
		// Calculate and smooth rotation
		if (rawFacekit.leftEye && rawFacekit.rightEye) {
			var newAngle = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
			// Improved angle normalization to prevent full rotations - no while loops
			var angleDiff = newAngle - self.currentRotation;
			// Efficiently handle -PI/PI boundary crossing (replaces the while loops)
			if (angleDiff > Math.PI) {
				angleDiff -= 2 * Math.PI;
			} else if (angleDiff < -Math.PI) {
				angleDiff += 2 * Math.PI;
			}
			// Apply safeguard against large changes
			var maxRotationPerFrame = Math.PI / 10; // Limit rotation change
			// Clamp the rotation change to prevent sudden large rotations
			if (angleDiff > maxRotationPerFrame) {
				angleDiff = maxRotationPerFrame;
			}
			if (angleDiff < -maxRotationPerFrame) {
				angleDiff = -maxRotationPerFrame;
			}
			// Apply smoothing
			self.currentRotation += angleDiff * self.smoothingFactor;
			// Normalize the result angle to ensure it stays in -PI to PI range
			// Using efficient modulo approach rather than while loops
			self.currentRotation = (self.currentRotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
		}
		// Copy mouth state
		self.smoothFacekit.mouthOpen = rawFacekit.mouthOpen;
	};
	return self;
};
var debugMode = false; // DEBUG MODE DEBUG MODE  DEBUG MODE
var bypassTracking = false; // Global variable to bypass face tracking
var currentEyeDistance = 200; // Global variable to store current eye distance
var currentRotation = 0; // Current smoothed rotation value
var settingsPopup; // Global variable for settings popup
var soundEnabled = true; // Global variable to track if sound is enabled
var confettiEnabled = true; // Global variable to track if confetti is enabled
var backgroundVisible = true; // Global variable to track if background is visible
var currentZoomLevel = 0.5; // Global variable to track zoom level (50%)
var minScaleRatio = 0.5; // Minimum scale ratio for zoom
var maxScaleRatio = 2; // Maximum scale ratio for zoom
var currentScaleRatio = 1.0; // Current scale ratio based on zoom level
var punchlineSounds = [{
	name: "None",
	asset: ""
}, {
	name: "Rimshot",
	asset: "punchlineSound1"
}, {
	name: "Trombone 1",
	asset: "punchlineSound2"
}, {
	name: "Trombone 2",
	asset: "punchlineSound3"
}, {
	name: "Laugh",
	asset: "punchlineSound4"
}, {
	name: "Crowd Laugh",
	asset: "punchlineSound5"
}]; // Array to store available punchline sounds
var punchlineConfettis = [{
	name: "None"
}, {
	name: "Confettis",
	"class": "ConfettisParty"
}, {
	name: "Ha ha ha!",
	"class": "ConfettisHaha"
}, {
	name: "Smileys",
	"class": "ConfettisSmiley"
}]; // Array to store available punchline confetti effects
var currentPunchlineSound = 5; // Default to "Crowd Laugh"
var storedSoundIndex = storage.currentPunchlineSound;
// If there's a valid stored sound index, use it
if (typeof storedSoundIndex === 'number' && storedSoundIndex >= 0 && storedSoundIndex < punchlineSounds.length) {
	currentPunchlineSound = storedSoundIndex;
}
var currentPunchlineConfetti = 2; // Default to "Ha ha ha"
var storedConfettiIndex = storage.currentPunchlineConfetti;
// If there's a valid stored confetti index, use it
if (typeof storedConfettiIndex === 'number' && storedConfettiIndex >= 0 && storedConfettiIndex < punchlineConfettis.length) {
	currentPunchlineConfetti = storedConfettiIndex;
}
var facekitMgr;
var background;
var instructionText;
var styleText;
var trollFace;
var debugPoints;
var backgroundContainer;
var middlegroundContainer;
var foregroundContainer;
var isTrackingFace = false; // Global flag to track face detection state
var targetPosition;
var lastNosePosition = null; // Global variable to store last facekit.noseTip.x
var trackingStoppedDelay = 100;
var trackingStoppedCounter = trackingStoppedDelay;
var buttonSettings;
var buttonPunchline; // Global variable for punchline button
var faceContainerBg; // Global variable for face container background
var settingsButtonBounds;
var trollFaceOffsets = [{
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0.13,
		y: 0.5,
		sx: 0.4,
		sy: 0.4,
		minX: 0.7,
		maxX: 0.9,
		minY: -0.5,
		maxY: 0
	},
	rightEye: {
		x: 0.2,
		y: 0.5,
		sx: 0.6,
		sy: 0.6,
		minX: -0.5,
		maxX: -0.2,
		minY: -0.5,
		maxY: 0
	},
	upperLip: {
		x: 0.25,
		y: 0.05,
		sx: 0.9,
		sy: 0.9,
		minX: -1,
		maxX: 1,
		minY: 0,
		maxY: 0.42
	},
	lowerLip: {
		x: 0.25,
		y: 0.06,
		sx: 0.9,
		sy: 0.9,
		minX: -1,
		maxX: 0.25,
		minY: 0,
		maxY: 0.9
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0,
		y: 0.5,
		sx: 0.6,
		sy: 0.6,
		minX: 0.2,
		maxX: 0.7,
		minY: 0,
		maxY: 0.6
	},
	rightEye: {
		x: 0.1,
		y: 0.3,
		sx: 0.6,
		sy: 0.6,
		minX: -0.7,
		maxX: 0.1,
		minY: 0,
		maxY: 0.4
	},
	upperLip: {
		x: -0.2,
		y: 0.1,
		sx: 0.5,
		sy: 0.5,
		minX: -0.3,
		maxX: 0,
		minY: -1,
		maxY: 1
	},
	lowerLip: {
		x: -0.10,
		y: 0.12,
		sx: 0.5,
		sy: 0.5,
		minX: -0.2,
		maxX: 0,
		minY: 0,
		maxY: 0.8
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0.13,
		y: -0.02,
		sx: 0.8,
		sy: 0.8,
		minX: 0.6,
		maxX: 0.72,
		minY: -0.75,
		maxY: -0.6
	},
	rightEye: {
		x: 0.15,
		y: -0.02,
		sx: 0.8,
		sy: 0.8,
		minX: -0.6,
		maxX: -0.3,
		minY: -0.75,
		maxY: -0.6
	},
	upperLip: {
		x: 0.1,
		y: -0.3,
		sx: 1,
		sy: 1,
		minX: -0.5,
		maxX: 0.5,
		minY: -1.0,
		maxY: 1.0
	},
	lowerLip: {
		x: 0.15,
		y: -0.10,
		sx: 0.93,
		sy: 0.93,
		minX: -0.5,
		maxX: 0.5,
		minY: -1.0,
		maxY: 0.55
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: -0.27,
		y: 0.325,
		sx: 0.4,
		sy: 0.4,
		minX: -0.3,
		maxX: 0.4,
		minY: -0.3,
		maxY: 0
	},
	rightEye: {
		x: -0.1,
		y: 0.5,
		sx: 0.4,
		sy: 0.4,
		minX: -0.6,
		maxX: -0.3,
		minY: 0,
		maxY: 0.2
	},
	upperLip: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1,
		minX: -1,
		maxX: 1,
		minY: -1,
		maxY: 1
	},
	lowerLip: {
		x: 0,
		y: 0.25,
		sx: 0.6,
		sy: 0.6,
		minX: -0.2,
		maxX: 0.2,
		minY: 0,
		maxY: 0.8
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0.45,
		y: 0.35,
		sx: 0.3,
		sy: 0.3,
		minX: 0.9,
		maxX: 0.95,
		minY: -0.3,
		maxY: -0.2
	},
	rightEye: {
		x: 0.85,
		y: 0.16,
		sx: 0.6,
		sy: 0.6,
		minX: 0.3,
		maxX: 0.5,
		minY: -0.3,
		maxY: -0.2
	},
	upperLip: {
		x: 0.46,
		y: 0.125,
		sx: 0.45,
		sy: 0.45,
		minX: 0,
		maxX: 0.5,
		minY: -0.1,
		maxY: 0.5
	},
	lowerLip: {
		x: 0.45,
		y: 0.125,
		sx: 0.45,
		sy: 0.45,
		minX: -0.45,
		maxX: 0.45,
		minY: 0.6,
		maxY: 0.8
	}
}];
// Define fakeCamera globally with fixed positions for testing
var fakeCamera = {
	leftEye: {
		x: 1380,
		y: 958
	},
	rightEye: {
		x: 673,
		y: 1050
	},
	upperLip: {
		x: 1027,
		y: 1610
	},
	lowerLip: {
		x: 1030,
		y: 1713
	},
	mouthOpen: false,
	noseTip: {
		x: 1024,
		y: 1366
	}
}; // Global object for bypass tracking mode
function _typeof(o) {
	"@babel/helpers - typeof";
	return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
		return typeof o;
	} : function (o) {
		return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
	}, _typeof(o);
}
/**** 
* Game Functions
****/ 
// Handle tap anywhere on the screen to change face
game.down = function (x, y, obj) {
	log("Game Down:", obj);
	// Check if the tap is within the settings button coordinates
	if (settingsPopup.visible || x >= settingsButtonBounds.x && x <= settingsButtonBounds.x + settingsButtonBounds.width && y >= settingsButtonBounds.y && y <= settingsButtonBounds.y + settingsButtonBounds.height) {
		return;
	}
	// Face style changing is now handled by the faceContainer's down event
};
// Update function called every frame
game.update = function () {
	if (bypassTracking) {
		// When bypassing tracking, position the troll face at the center and update its elements
		trollFace.updateFacePosition();
		return;
	}
	if (!facekit || !facekit.noseTip) {
		return;
	}
	// Update tracking status and get smoothed face data
	var isCurrentlyTracking = facekitMgr.updateTrackingStatus(facekit);
	// Update tracking state UI if changed
	if (isCurrentlyTracking !== isTrackingFace) {
		isTrackingFace = isCurrentlyTracking;
		instructionText.setText(isCurrentlyTracking ? "Tracking..." : "No Face found");
	}
	// Update troll face position to match real face
	if (isTrackingFace) {
		// Reset isRecentering flag when face tracking is detected
		trollFace.isRecentering = false;
		trollFace.isFirstPhaseCentering = false;
		// Clear stored positions to prevent any lingering effects
		trollFace.relativePositions = {};
		// Reset head position to default (0,0) relative to container
		if (trollFace.elements && trollFace.elements.head) {
			trollFace.elements.head.x = 0;
			trollFace.elements.head.y = 0;
		}
		// Use the smoothed nose tip position
		trollFace.x = facekitMgr.currentFacekit.noseTip.x;
		trollFace.y = facekitMgr.currentFacekit.noseTip.y;
		// Use the original updateFacePosition with smoothed data
		trollFace.updateFacePosition();
		trollFace.isCentered = false;
		trollFace.isCentering = false;
	} else {
		// If face is not detected, return the face to the center
		if (!trollFace.isCentered) {
			if (trollFace.isCentering) {
				// Don't exit the update function, just skip starting a new tween
				// This allows other updates to continue	
				// Continue updating face elements during centering
				trollFace.updateFacePosition();
			} else {
				trollFace.isCentering = true;
				// First phase: Capture current positions and maintain them during animation
				trollFace.captureCurrentPositions();
				trollFace.isFirstPhaseCentering = true;
				trollFace.isRecentering = false;
				//LK.effects.flashScreen(0xFFFFFF, 300); // Flash screen
				tween(trollFace, {
					x: 2048 / 2,
					y: 2732 / 2
				}, {
					duration: 2000,
					easing: tween.easeInOut,
					onFinish: function onFinish() {
						// Second phase: Switch to default positions
						trollFace.isFirstPhaseCentering = false;
						trollFace.isRecentering = true;
						trollFace.isCentered = true;
						trollFace.isCentering = false;
						// First call updateFacePosition to calculate proper scales
						// This won't affect positions yet since we'll animate them
						trollFace.updateFacePosition();
						// Get positions directly from the updateAllFaceElements function
						var fakePositions = {};
						// Store the original updateFaceElement function
						var originalUpdateFaceElement = trollFace.updateFaceElement;
						// Override the function temporarily to capture positions
						trollFace.updateFaceElement = function (name, x, y, scaleX, scaleY) {
							// Store the position and scale that would be applied
							fakePositions[name] = {
								x: x,
								y: y,
								scaleX: scaleX,
								scaleY: scaleY
							};
						};
						// Call the function to calculate positions without applying them
						trollFace.updateAllFaceElements(fakeCamera, false, false);
						// Restore the original function
						trollFace.updateFaceElement = originalUpdateFaceElement;
						// Animate each element independently to the correct positions
						for (var name in trollFace.elements) {
							if (trollFace.elements[name] && fakePositions[name]) {
								var element = trollFace.elements[name];
								var target = fakePositions[name];
								// Create tween for this element
								tween(element, {
									x: target.x,
									y: target.y,
									scaleX: target.scaleX,
									scaleY: target.scaleY
								}, {
									duration: 500,
									easing: tween.easeOut
								});
							}
						}
					}
				});
			}
		}
		// Update face tracking state
		if (isTrackingFace) {
			isTrackingFace = false;
			instructionText.setText("No Face found");
		}
	}
	// If in debug mode, display the face elements positions
	if (debugMode) {
		// Clear existing debug points
		if (debugPoints) {
			for (var i = 0; i < debugPoints.children.length; i++) {
				debugPoints.children[i].alpha = 0;
			}
		}
		// Draw debug points for each face element
		if (facekit) {
			var pointIndex = 0;
			// Draw a point for each facial feature
			for (var key in facekit) {
				if (facekit[key] && typeof facekit[key].x !== 'undefined') {
					var point = debugPoints.children[pointIndex++];
					if (point) {
						point.x = facekit[key].x;
						point.y = facekit[key].y;
						point.alpha = 1;
					}
				}
			}
			// Display rotation in degrees
			instructionText.setText("Rotation: " + Math.round(trollFace.rotation * 180 / Math.PI) + "Β°");
		}
	}
};
// Function to play confetti effect at specified position
function playConfettiEffect(x, y) {
	if (confettiEnabled) {
		var confetti;
		// Check if a confetti class is specified
		var selectedConfetti = punchlineConfettis[currentPunchlineConfetti];
		if (selectedConfetti && selectedConfetti["class"]) {
			// Create the appropriate confetti effect based on the selected type
			switch (selectedConfetti["class"]) {
				case "ConfettisParty":
					confetti = new ConfettisParty();
					break;
				case "ConfettisHaha":
					confetti = new ConfettisHaha();
					break;
				case "ConfettisSmiley":
					confetti = new ConfettisSmiley();
					break;
				default:
					// Should not reach here with our current configuration
					return;
			}
			// Initialize the confetti effect
			confetti.createParticles(x, y);
			foregroundContainer.addChild(confetti);
			// Update confetti particles
			var updateInterval = LK.setInterval(function () {
				if (confetti.active) {
					confetti.update();
				} else {
					LK.clearInterval(updateInterval);
				}
			}, 16);
		}
		// If no class specified (None option), do nothing
	}
}
// Function to handle face container tap
function handleFaceContainerTap() {
	// Prevent action if settings popup is visible
	if (settingsPopup && settingsPopup.visible) {
		return;
	}
	// Switch to the next troll face style
	var newStyle = trollFace.nextStyle();
	// Save the current style to storage
	storage.lastTrollStyle = newStyle;
	// Update the style text with fade animation
	updateStyleText(newStyle);
	// Play switch sound
	LK.getSound('switchTroll').play();
	// If no face tracking, ensure the face is displayed
	if (!facekit || !facekit.noseTip || !facekitMgr.isTracking) {
		// Set bypass tracking temporarily to ensure face is displayed
		var originalBypass = bypassTracking;
		bypassTracking = true;
		// Update face position to show the face
		trollFace.updateFacePosition();
		// Restore original bypass value
		bypassTracking = originalBypass;
	}
}
// Function to update punchline button visibility
function updatePunchlineButtonVisibility() {
	// For debugging
	log("Updating button visibility. Sound: " + currentPunchlineSound + ", Confetti: " + currentPunchlineConfetti);
	// Hide button if both sound and confetti are set to "None" (index 0)
	if (currentPunchlineSound === 0 && currentPunchlineConfetti === 0) {
		buttonPunchline.visible = false;
		log("Hiding punchline button");
	} else {
		buttonPunchline.visible = true;
		log("Showing punchline button");
	}
}
// Function to update style text with fade animation
function updateStyleText(newStyle) {
	// Update text immediately
	styleText.setText('Face ' + newStyle);
	// Reset to starting state
	styleText.alpha = 0;
	// First phase: Fade in with slight scale up
	tween(styleText, {
		alpha: 1
	}, {
		duration: 1000,
		easing: tween.easeOut,
		onFinish: function onFinish() {
			// Second phase: Fade out with scale down
			tween(styleText, {
				alpha: 0
			}, {
				duration: 1000,
				easing: tween.easeIn,
				onFinish: function onFinish() {}
			});
		}
	});
}
function log() {
	if (debugMode) {
		console.log.apply(console, arguments);
	}
}
function initializeGame() {
	// Initialize game
	// Create containers for layering
	backgroundContainer = new Container();
	middlegroundContainer = new Container();
	foregroundContainer = new Container();
	// Add containers to game
	game.addChild(backgroundContainer);
	game.addChild(middlegroundContainer);
	game.addChild(foregroundContainer);
	// Initialize FacekitManager
	facekitMgr = new FacekitManager();
	facekitMgr.initialize(fakeCamera);
	// Global target position for the troll face
	targetPosition = {
		x: 2048 / 2,
		y: 2732 / 2
	};
	// Setup background
	background = LK.getAsset('whiteBackground', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 2048 / 2,
		y: 2732 / 2,
		visible: true
	});
	backgroundContainer.addChild(background);
	// Setup UI text
	instructionText = new Text2('Tap anywhere to change troll face', {
		size: 50,
		fill: 0xFF0000
	});
	instructionText.anchor.set(0.5, 0);
	LK.gui.top.addChild(instructionText);
	instructionText.y = 1800;
	instructionText.x = -500;
	instructionText.visible = debugMode;
	// Load the last used style from storage
	var lastStyle = storage.lastTrollStyle || 3;
	// Create a container for the face
	var faceContainer = new Container();
	faceContainerBg = LK.getAsset('whiteBackground', {
		anchorX: 0,
		anchorY: 0,
		x: 0,
		y: 230,
		width: 2048,
		height: 2732 - 230 - 430,
		visible: backgroundVisible
	});
	faceContainer.addChild(faceContainerBg);
	middlegroundContainer.addChild(faceContainer);
	// Create the troll face
	trollFace = new TrollFace();
	trollFace.currentStyle = lastStyle;
	trollFace.initialize();
	trollFace.nextStyle(lastStyle);
	// Apply zoom scale to the entire troll face container
	trollFace.scale.x = currentScaleRatio;
	trollFace.scale.y = currentScaleRatio;
	faceContainer.addChild(trollFace);
	// Add tap handler to the face container
	faceContainer.down = function (x, y, obj) {
		handleFaceContainerTap();
	};
	// Initialize tracking state
	isTrackingFace = false;
	// Add ButtonPunchline to the foreground at the bottom center
	buttonPunchline = new ButtonPunchline();
	buttonPunchline.x = 2048 / 2; // Center horizontally
	buttonPunchline.y = 2732 - 300; // Position at the bottom
	foregroundContainer.addChild(buttonPunchline);
	// Set initial visibility based on current settings
	updatePunchlineButtonVisibility();
	// Add ButtonSettings to the foreground at the bottom right
	buttonSettings = new ButtonSettings();
	buttonSettings.x = 2048 - 200; // Position at the bottom right
	buttonSettings.y = 140; // Position at the bottom
	foregroundContainer.addChild(buttonSettings);
	settingsButtonBounds = {
		x: buttonSettings.x - buttonSettings.width / 2,
		y: buttonSettings.y - buttonSettings.height / 2,
		width: buttonSettings.width,
		height: buttonSettings.height
	};
	// Initialize the settings popup and add it to foreground
	settingsPopup = new SettingsPopup();
	settingsPopup.x = 2048 / 2; // Center horizontally
	settingsPopup.y = 2732 / 2; // Center vertically
	settingsPopup.visible = false; // Initially not visible
	foregroundContainer.addChild(settingsPopup);
	// Add style text
	styleText = new Text2('Face ' + lastStyle, {
		size: 80,
		fill: 0x1d3242,
		align: "left",
		fontWeight: "bold"
	});
	styleText.anchor.set(0.5, 0);
	LK.gui.top.addChild(styleText);
	styleText.y = 40;
	styleText.alpha = 1; // Ensure initial alpha is set to 1
	updateStyleText(lastStyle);
	// Debug mode (turn on for development, off for production)
	debugPoints = null;
	if (debugMode) {
		debugPoints = new DebugPoints();
		foregroundContainer.addChild(debugPoints);
		// Log facekit to console every second
		LK.setInterval(function () {
			log(facekit);
			if (facekit.lowerLip) {
				var elementOffset = trollFace.currentOffsets.lowerLip;
				var eyeDistance = trollFace.getEyeDistance(facekit);
				log("lowerLip y:", facekit.lowerLip.y, "minY:", elementOffset.minY * eyeDistance, "maxY:", elementOffset.maxY * eyeDistance);
			}
			// Display rotation angle in degrees for debugging
			var rotationDegrees = Math.round(trollFace.rotation * (180 / Math.PI));
			instructionText.setText("le:".concat(Math.round(facekit.leftEye.x), ",").concat(Math.round(facekit.leftEye.y), " / ") + "re:".concat(Math.round(facekit.rightEye.x), ",").concat(Math.round(facekit.rightEye.y), " / ") + "ul:".concat(Math.round(facekit.upperLip.x), ",").concat(Math.round(facekit.upperLip.y), " / ") + "ll:".concat(Math.round(facekit.lowerLip.x), ",").concat(Math.round(facekit.lowerLip.y), " / ") + "rot:".concat(rotationDegrees, "Β°"));
		}, 1000);
	}
	// Load preferences from storage
	soundEnabled = storage.soundEnabled !== undefined ? storage.soundEnabled : true;
	confettiEnabled = storage.confettiEnabled !== undefined ? storage.confettiEnabled : true;
	backgroundVisible = storage.backgroundVisible !== undefined ? storage.backgroundVisible : true;
	currentZoomLevel = storage.currentZoomLevel !== undefined ? storage.currentZoomLevel : 0.5;
	// Calculate initial scale ratio based on zoom level
	currentScaleRatio = minScaleRatio + (maxScaleRatio - minScaleRatio) * currentZoomLevel;
	// Apply background visibility setting
	if (background) {
		background.visible = backgroundVisible;
	}
	if (faceContainerBg) {
		faceContainerBg.visible = backgroundVisible;
	}
}
// Initialize the game
initializeGame();
// Function to toggle background visibility
function toggleBackground() {
	// Toggle the visibility state
	backgroundVisible = !backgroundVisible;
	// Save to storage
	storage.backgroundVisible = backgroundVisible;
	// Update visibility of both backgrounds
	if (faceContainerBg) {
		faceContainerBg.visible = backgroundVisible;
	}
	if (background) {
		background.visible = backgroundVisible;
	}
	// Update text in the settings popup if it exists and is visible
	if (settingsPopup && settingsPopup.visible) {
		settingsPopup.updateBackgroundToggleLabel();
	}
	// Play beep sound when background is toggled
	LK.getSound('beep').play();
} /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/**** 
* Classes
****/ 
var ButtonPunchline = Container.expand(function () {
	var self = Container.call(this);
	// Attach the ButtonPunchline asset
	var buttonGraphics = self.attachAsset('ButtonPunchline', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Add down event handler for punchline button
	self.down = function (x, y, obj) {
		// Play sound if enabled
		if (soundEnabled) {
			// Play the currently selected punchline sound
			if (currentPunchlineSound >= 0 && currentPunchlineSound < punchlineSounds.length && punchlineSounds[currentPunchlineSound].asset) {
				LK.getSound(punchlineSounds[currentPunchlineSound].asset).play();
			}
		}
		// Create confetti effect if enabled
		playConfettiEffect(self.x, self.y - 100);
	};
	return self;
});
var ButtonSettings = Container.expand(function () {
	var self = Container.call(this);
	// Attach the ButtonSettings asset
	var buttonGraphics = self.attachAsset('ButtonSettingsBackground', {
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0x000000,
		alpha: 0.5
	});
	var buttonGraphics = self.attachAsset('ButtonSettings', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Add down event handler to show settings popup
	self.down = function (x, y, obj) {
		// Toggle settings popup visibility
		if (settingsPopup) {
			settingsPopup.visible = !settingsPopup.visible;
			bypassTracking = settingsPopup.visible; // Toggle bypassTracking based on visibility 
			trollFace.visible = !settingsPopup.visible; // Hide trollface when settings popup is shown 
			// Call onShow when the popup becomes visible
			if (settingsPopup.visible && settingsPopup.onShow) {
				settingsPopup.onShow();
			}
		}
		// Play beep sound when settings button is toggled
		LK.getSound('beep').play();
		// Stop event propagation to prevent changing the face style
		if (obj.event && typeof obj.event.stopPropagation === 'function') {
			obj.event.stopPropagation();
		}
	};
	// Define any additional properties or methods for the ButtonSettings here
	return self;
});
var Confetti = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 50;
	self.colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
	self.gravity = 0.5;
	self.active = false;
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		// Create new particles
		for (var i = 0; i < self.particleCount; i++) {
			var particle = LK.getAsset('debugFacePoints', {
				anchorX: 0.5,
				anchorY: 0.5
			});
			particle.tint = self.colors[Math.floor(Math.random() * self.colors.length)];
			particle.scale.set(0.2, 0.2);
			// Set initial position
			particle.x = x;
			particle.y = y;
			// Set random velocity
			particle.vx = (Math.random() - 0.5) * 20;
			particle.vy = (Math.random() - 0.5) * 20 - 10;
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.active = true;
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var allSettled = true;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Apply gravity
			particle.vy += self.gravity;
			// Update position
			particle.x += particle.vx;
			particle.y += particle.vy;
			// Check if particle is still moving
			if (particle.vy < 10) {
				allSettled = false;
			}
			// Check if particle is off screen
			if (particle.y > 2732) {
				particle.vy = 0;
				particle.vx = 0;
			}
		}
		// If all particles have settled, stop updating
		if (allSettled) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var ConfettisHaha = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 10;
	self.active = false;
	self.screenWidth = 2048;
	self.screenHeight = 2732;
	// Configuration properties
	self.initialScale = 0.1; // Initial scale when particles appear
	self.minScale = 0.5; // Minimum final scale of particles
	self.scaleVariation = 0.5; // Random variation added to scale
	self.minDuration = 2; // Minimum animation duration in seconds
	self.durationVariation = 2; // Random variation added to duration
	self.maxDelaySeconds = 0.5; // Maximum random delay before animation starts
	self.fadeInThreshold = 0.2; // Progress threshold for fade in (0-1)
	self.fadeOutThreshold = 0.8; // Progress threshold for fade out (0-1)
	self.scaleUpThreshold = 0.25; // Progress threshold for scale up (0-1)
	self.settleThreshold = 0.35; // Progress threshold for settling to final scale (0-1)
	self.overshootFactor = 1.15; // How much to overshoot during scale up
	self.oscillationFrequency = 12; // Frequency of oscillation
	self.oscillationAmplitude = 0.05; // Amplitude of oscillation
	self.rotationRange = 30; // Range of random rotation in degrees (Β±30Β°)
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		// Create new particles
		for (var i = 0; i < self.particleCount; i++) {
			// Randomly select one of the Haha assets
			var assetIndex = Math.floor(Math.random() * 3) + 1;
			var particle = LK.getAsset('ConfettisHaha' + assetIndex, {
				anchorX: 0.5,
				anchorY: 0.5
			});
			// Set initial position randomly on the screen
			particle.x = Math.random() * self.screenWidth;
			particle.y = Math.random() * self.screenHeight;
			// Set initial scale and alpha
			particle.scale.set(self.initialScale, self.initialScale);
			particle.alpha = 0;
			// Set random initial rotation (Β±15 degrees)
			particle.rotation = (Math.random() * 60 - 30) * (Math.PI / 180); // Convert to radians
			// Set animation parameters
			particle.duration = self.minDuration + Math.random() * self.durationVariation; // 2-4 seconds (longer overall duration)
			particle.age = 0;
			particle.maxScale = self.minScale + Math.random() * self.scaleVariation; // 0.5-1.0
			// Add a small random delay for each particle (slightly longer for less abrupt appearance)
			particle.delay = Math.random() * self.maxDelaySeconds; // 0-0.5 seconds delay
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.active = true;
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var activeParticles = false;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Handle delay
			if (particle.delay > 0) {
				particle.delay -= 1 / 60; // Assuming 60fps
				activeParticles = true;
				continue;
			}
			// Update age
			particle.age += 1 / 60; // Assuming 60fps
			// Calculate progress (0 to 1)
			var progress = particle.age / particle.duration;
			if (progress < 1) {
				activeParticles = true;
				// Fade in gradually, stay visible longer, fade out gradually
				if (progress < self.fadeInThreshold) {
					particle.alpha = progress * (1 / self.fadeInThreshold); // 0 to 1 in first 20% of time
				} else if (progress > self.fadeOutThreshold) {
					particle.alpha = (1 - progress) * (1 / (1 - self.fadeOutThreshold)); // 1 to 0 in last 20% of time
				} else {
					particle.alpha = 1; // Stay fully visible for 60% of time
				}
				// Explosive scale effect - slightly slower scale up, then stay at max
				var scaleProgress;
				if (progress < self.scaleUpThreshold) {
					// Slightly slower elastic-like scaling in first 25% of time
					// Overshoot slightly and then settle
					scaleProgress = self.overshootFactor * Math.pow(progress / self.scaleUpThreshold, 0.8);
					if (scaleProgress > 1.1) {
						scaleProgress = 1.1;
					}
				} else if (progress < self.settleThreshold) {
					// Slower settle to normal scale
					scaleProgress = 1.1 - (progress - self.scaleUpThreshold) * (0.1 / (self.settleThreshold - self.scaleUpThreshold)); // 1.1 to 1.0
				} else {
					scaleProgress = 1; // Stay at max scale
				}
				var currentScale = particle.maxScale * scaleProgress;
				// Add oscillation after reaching max scale to simulate laughing
				if (progress >= self.settleThreshold) {
					// Calculate oscillation frequency and amplitude
					var oscillationFreq = self.oscillationFrequency; // Higher = faster oscillation
					var oscillationAmplitude = self.oscillationAmplitude; // Higher = more intense oscillation
					// Apply sine wave oscillation that decreases in amplitude over time
					var oscillationFactor = Math.sin(progress * oscillationFreq * Math.PI) * oscillationAmplitude;
					// Gradually reduce oscillation amplitude as we approach the end
					var dampingFactor = 1 - (progress - self.settleThreshold) / (1 - self.settleThreshold);
					oscillationFactor *= dampingFactor;
					// Apply oscillation to scale
					currentScale *= 1 + oscillationFactor;
				}
				particle.scale.set(currentScale, currentScale);
			}
		}
		// If no active particles, stop updating
		if (!activeParticles) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var ConfettisParty = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 200;
	self.colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
	self.gravity = 0.25;
	self.active = false;
	self.screenWidth = 2048;
	self.screenHeight = 2732;
	self.spawnedParticles = 0; // Track how many particles have been spawned
	self.spawnRate = 10; // Particles to spawn per frame
	self.spawnComplete = false; // Flag to track if all particles have been spawned
	// Configuration properties
	self.minScale = 0.5; // Minimum scale of particles
	self.scaleVariation = 1.0; // Random variation added to scale
	self.yStartPosition = 600; // Starting Y position for particles
	self.yVariation = self.screenHeight / 2; // Random variation in Y position
	self.centerY = self.screenHeight / 8; // Target center Y position
	self.minSpeedX = 5; // Minimum horizontal speed
	self.speedXVariation = 5; // Random variation in X speed
	self.minSpeedY = 15; // Minimum vertical speed
	self.speedYVariation = 10; // Random variation in Y speed
	self.spawnInterval = 50; // Milliseconds between spawning batches
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		self.spawnedParticles = 0;
		self.spawnComplete = false;
		self.active = true;
		// Initial spawn
		self.spawnParticles();
		// Set up interval for continued spawning
		var spawnInterval = LK.setInterval(function () {
			self.spawnParticles();
			if (self.spawnComplete) {
				LK.clearInterval(spawnInterval);
			}
		}, self.spawnInterval); // Spawn every 50ms for a more gradual effect
	};
	// Spawn a batch of particles
	self.spawnParticles = function () {
		if (self.spawnComplete) {
			return;
		}
		var particlesToSpawn = Math.min(self.spawnRate, self.particleCount - self.spawnedParticles);
		for (var i = 0; i < particlesToSpawn; i++) {
			var particle = LK.getAsset('ConfettiPartyBase', {
				anchorX: 0.5,
				anchorY: 0.5
			});
			// Random color
			particle.tint = self.colors[Math.floor(Math.random() * self.colors.length)];
			// Random scale between 0.5 and 1.5
			var scale = self.minScale + Math.random() * self.scaleVariation;
			particle.scale.set(scale, scale);
			// Random rotation
			particle.rotation = Math.random() * Math.PI * 2;
			// Set initial position at left or right border
			var startFromLeft = Math.random() > 0.5;
			particle.x = startFromLeft ? 0 : self.screenWidth;
			particle.y = self.yStartPosition + Math.random() * self.yVariation;
			// Set velocity toward upper center
			var centerX = self.screenWidth / 2;
			var centerY = self.centerY;
			// Calculate angle to center
			var dx = centerX - particle.x;
			var dy = centerY - particle.y;
			var angle = Math.atan2(dy, dx);
			// Set velocity based on angle with some randomness
			var speedX = self.minSpeedX + Math.random() * self.speedXVariation;
			var speedY = self.minSpeedY + Math.random() * self.speedYVariation;
			particle.vx = Math.cos(angle) * speedX;
			particle.vy = Math.sin(angle) * speedY;
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.spawnedParticles += particlesToSpawn;
		if (self.spawnedParticles >= self.particleCount) {
			self.spawnComplete = true;
		}
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var activeParticles = false;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Apply gravity after particles reach their peak
			if (particle.vy > 0) {
				particle.vy += self.gravity;
			} else {
				// Slow down upward movement
				particle.vy += self.gravity * 0.5;
			}
			// Update position
			particle.x += particle.vx;
			particle.y += particle.vy;
			// Rotate particle
			particle.rotation += 0.05;
			// Check if particle is still active
			if (particle.y < self.screenHeight) {
				activeParticles = true;
			}
		}
		// If no active particles, stop updating
		if (!activeParticles) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var ConfettisSmiley = Container.expand(function () {
	var self = Container.call(this);
	// Properties
	self.particles = [];
	self.particleCount = 10;
	self.active = false;
	self.screenWidth = 2048;
	self.screenHeight = 2732;
	// Configuration properties
	self.baseOffsetX = 250; // How far offscreen particles start
	self.minSpeed = 9; // Minimum horizontal speed
	self.speedVariation = 3; // Random variation added to speed
	self.rotationSpeedRatio = 0.005; // Ratio of rotation speed to horizontal speed
	self.minScale = 0.7; // Minimum scale of particles
	self.scaleVariation = 0.3; // Random variation added to scale
	self.maxDelaySeconds = 2; // Maximum random delay before movement starts
	self.yMargin = 200; // Margin from top and bottom of screen
	// Create confetti particles
	self.createParticles = function (x, y) {
		// Clear existing particles
		for (var i = 0; i < self.particles.length; i++) {
			if (self.particles[i].parent) {
				self.particles[i].parent.removeChild(self.particles[i]);
			}
		}
		self.particles = [];
		// Create new particles
		for (var i = 0; i < self.particleCount; i++) {
			// Randomly select one of the Smiley assets
			var assetIndex = Math.floor(Math.random() * 3) + 1;
			var particle = LK.getAsset('ConfettisSmiley' + assetIndex, {
				anchorX: 0.5,
				anchorY: 0.5
			});
			// Set initial position at left or right border
			var startFromLeft = Math.random() > 0.5;
			particle.x = startFromLeft ? -self.baseOffsetX : self.screenWidth + self.baseOffsetX;
			particle.y = self.yMargin + Math.random() * (self.screenHeight - 2 * self.yMargin); // Random Y position with margin
			// Set horizontal velocity
			particle.vx = startFromLeft ? self.minSpeed + Math.random() * self.speedVariation : -(self.minSpeed + Math.random() * self.speedVariation);
			// Set rotation speed (faster for faster moving particles)
			particle.rotationSpeed = particle.vx * self.rotationSpeedRatio;
			// Set scale
			var scale = self.minScale + Math.random() * self.scaleVariation;
			particle.scale.set(scale, scale);
			// Add delay before starting movement
			particle.delay = Math.random() * self.maxDelaySeconds; // 0-maxDelaySeconds seconds delay
			// Add to container
			self.addChild(particle);
			self.particles.push(particle);
		}
		self.active = true;
	};
	// Update particles
	self.update = function () {
		if (!self.active) {
			return;
		}
		var activeParticles = false;
		for (var i = 0; i < self.particles.length; i++) {
			var particle = self.particles[i];
			// Handle delay
			if (particle.delay > 0) {
				particle.delay -= 1 / 60; // Assuming 60fps
				activeParticles = true;
				continue;
			}
			// Update position
			particle.x += particle.vx;
			// Rotate particle (rolling effect)
			particle.rotation += particle.rotationSpeed;
			// Check if particle is still on screen (with margin)
			if (particle.vx > 0 && particle.x < self.screenWidth + self.baseOffsetX || particle.vx < 0 && particle.x > -self.baseOffsetX) {
				activeParticles = true;
			}
		}
		// If no active particles, stop updating
		if (!activeParticles) {
			self.active = false;
			// Remove particles
			for (var i = 0; i < self.particles.length; i++) {
				if (self.particles[i].parent) {
					self.particles[i].parent.removeChild(self.particles[i]);
				}
			}
			self.particles = [];
		}
	};
	return self;
});
var DebugPoints = Container.expand(function () {
	var self = Container.call(this);
	// Create points for face tracking debugging
	self.points = {
		leftEye: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		rightEye: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		noseTip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		mouthCenter: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		upperLip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		lowerLip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		chin: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		})
	};
	// Update debug points to match face points
	self.update = function () {
		if (!facekit) {
			return;
		}
		if (facekit.leftEye) {
			self.points.leftEye.x = facekit.leftEye.x;
			self.points.leftEye.y = facekit.leftEye.y;
		}
		if (facekit.rightEye) {
			self.points.rightEye.x = facekit.rightEye.x;
			self.points.rightEye.y = facekit.rightEye.y;
		}
		if (facekit.noseTip) {
			self.points.noseTip.x = facekit.noseTip.x;
			self.points.noseTip.y = facekit.noseTip.y;
		}
		if (facekit.mouthCenter) {
			self.points.mouthCenter.x = facekit.mouthCenter.x;
			self.points.mouthCenter.y = facekit.mouthCenter.y;
		}
		if (facekit.upperLip) {
			self.points.upperLip.x = facekit.upperLip.x;
			self.points.upperLip.y = facekit.upperLip.y;
		}
		if (facekit.lowerLip) {
			self.points.lowerLip.x = facekit.lowerLip.x;
			self.points.lowerLip.y = facekit.lowerLip.y;
		}
		if (facekit.chin) {
			self.points.chin.x = facekit.chin.x;
			self.points.chin.y = facekit.chin.y;
		}
	};
	return self;
});
var SettingsPopup = Container.expand(function () {
	var self = Container.call(this);
	var popupGraphics = self.attachAsset('FrameSettingsPopup', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	var popupGraphics = self.attachAsset('FrameSettingsTitle', {
		anchorX: 0.5,
		anchorY: 0.5,
		y: -1060,
		blendMode: 0
	});
	// Create sound toggle container
	self.soundToggle = new Container();
	self.soundToggle.x = -450;
	self.soundToggle.y = -700;
	self.addChild(self.soundToggle);
	// Add sound icon
	var soundIcon = self.soundToggle.attachAsset('IconSound', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -0
	});
	// Add click handler for sound icon
	soundIcon.down = function (x, y, obj) {
		// Play the current sound if it's not "None" and sound is enabled
		if (soundEnabled && punchlineSounds[currentPunchlineSound].asset) {
			LK.getSound(punchlineSounds[currentPunchlineSound].asset).play();
		}
	};
	// Add left chevron button
	self.soundLeftChevronButton = self.soundToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.soundLeftChevronButton.down = function (x, y, obj) {
		self.changeSound(-1);
	};
	// Add sound text that displays "None" by default
	self.soundText = new Text2("None", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.soundText.x = 320;
	self.soundText.y = -40;
	self.soundToggle.addChild(self.soundText);
	// Add right chevron button
	self.soundRightChevronButton = self.soundToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.soundRightChevronButton.down = function (x, y, obj) {
		self.changeSound(1);
	};
	// Function to change the selected sound
	self.changeSound = function (direction) {
		// Update the current sound index
		currentPunchlineSound += direction;
		storage.currentPunchlineSound = currentPunchlineSound;
		// Cycle through the sounds instead of clamping
		if (currentPunchlineSound < 0) {
			currentPunchlineSound = punchlineSounds.length - 1;
		} else if (currentPunchlineSound >= punchlineSounds.length) {
			currentPunchlineSound = 0;
		}
		// Ensure currentPunchlineSound is within bounds
		if (currentPunchlineSound >= 0 && currentPunchlineSound < punchlineSounds.length) {
			// Update the sound text
			self.soundText.setText(punchlineSounds[currentPunchlineSound].name);
		} else {
			// Fallback to "None" if out of bounds
			self.soundText.setText("None");
		}
		// Play the sound if it's not "None" and sound is enabled
		if (soundEnabled && punchlineSounds[currentPunchlineSound].asset) {
			LK.getSound(punchlineSounds[currentPunchlineSound].asset).play();
		}
		updatePunchlineButtonVisibility();
	};
	// Initialize sound text with current selection
	if (currentPunchlineSound >= 0 && currentPunchlineSound < punchlineSounds.length) {
		self.soundText.setText(punchlineSounds[currentPunchlineSound].name);
	} else {
		self.soundText.setText("None");
	}
	// Create confetti toggle container
	self.confettiToggle = new Container();
	self.confettiToggle.x = -450;
	self.confettiToggle.y = -300;
	self.addChild(self.confettiToggle);
	// Add confetti icon
	var confettiIcon = self.confettiToggle.attachAsset('IconConfetti', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0
	});
	// Add click handler for confetti icon
	confettiIcon.down = function (x, y, obj) {
		// Play the selected confetti effect when icon is tapped
		playConfettiEffect(self.x, self.y);
		// Play beep sound when confetti is toggled
		LK.getSound('beep').play();
	};
	// Add left chevron button
	self.confettiLeftChevronButton = self.confettiToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.confettiLeftChevronButton.down = function (x, y, obj) {
		self.changeConfetti(-1);
	};
	// Add confetti text that displays "None" by default
	self.confettiText = new Text2("None", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.confettiText.x = 320;
	self.confettiText.y = -40;
	self.confettiToggle.addChild(self.confettiText);
	// Add right chevron button
	self.confettiRightChevronButton = self.confettiToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.confettiRightChevronButton.down = function (x, y, obj) {
		self.changeConfetti(1);
	};
	// Function to change the selected confetti effect
	self.changeConfetti = function (direction) {
		// Update the current confetti index
		currentPunchlineConfetti += direction;
		// Validate the index to ensure it's within bounds
		if (currentPunchlineConfetti < 0) {
			currentPunchlineConfetti = punchlineConfettis.length - 1;
		} else if (currentPunchlineConfetti >= punchlineConfettis.length) {
			currentPunchlineConfetti = 0;
		}
		// Store the validated index
		storage.currentPunchlineConfetti = currentPunchlineConfetti;
		// Update the confetti text
		self.confettiText.setText(punchlineConfettis[currentPunchlineConfetti].name);
		// Play the selected confetti effect when changing types
		playConfettiEffect(self.x, self.y);
		updatePunchlineButtonVisibility();
		// Play beep sound when confetti is toggled
		LK.getSound('beep').play();
	};
	// Initialize confetti text with current selection
	self.confettiText.setText(punchlineConfettis[currentPunchlineConfetti].name);
	// Create background toggle container
	self.backgroundToggle = new Container();
	self.backgroundToggle.x = -450;
	self.backgroundToggle.y = 100;
	self.addChild(self.backgroundToggle);
	// Add background icon
	var backgroundIcon = self.backgroundToggle.attachAsset('IconBackgroundToggle', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0
	});
	// Add click handler for background icon
	backgroundIcon.down = function (x, y, obj) {
		// Toggle background visibility
		toggleBackground();
	};
	// Add background text
	self.backgroundText = new Text2(backgroundVisible ? "Blank" : "Camera", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.backgroundText.x = 320;
	self.backgroundText.y = -40;
	self.backgroundToggle.addChild(self.backgroundText);
	// Add left chevron button
	self.backgroundLeftChevronButton = self.backgroundToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.backgroundLeftChevronButton.down = function (x, y, obj) {
		self.changeBackground(-1);
	};
	// Add right chevron button
	self.backgroundRightChevronButton = self.backgroundToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.backgroundRightChevronButton.down = function (x, y, obj) {
		self.changeBackground(1);
	};
	// Function to change the background visibility
	self.changeBackground = function (direction) {
		// Toggle background visibility using the central function
		// which will also update the text in the settings popup
		toggleBackground();
	};
	// Initialize background text with current selection
	self.backgroundText.setText(backgroundVisible ? "Blank" : "Camera");
	// Create zoom toggle container
	self.zoomToggle = new Container();
	self.zoomToggle.x = -450;
	self.zoomToggle.y = 500; // Position below background toggle
	self.addChild(self.zoomToggle);
	// Add zoom icon
	var zoomIcon = self.zoomToggle.attachAsset('IconZoom', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0
	});
	// Add click handler for zoom icon
	zoomIcon.down = function (x, y, obj) {
		// Play beep sound when zoom icon is clicked
		LK.getSound('beep').play();
	};
	// Add zoom text
	self.zoomText = new Text2("Zoom " + Math.round(currentZoomLevel * 100) + "%", {
		size: 80,
		fill: 0x333344,
		align: "left",
		fontWeight: "bold"
	});
	self.zoomText.x = 320;
	self.zoomText.y = -40;
	self.zoomToggle.addChild(self.zoomText);
	// Add left chevron button
	self.zoomLeftChevronButton = self.zoomToggle.attachAsset('ButtonLeftChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 220,
		y: 0
	});
	// Add click handler for left chevron button
	self.zoomLeftChevronButton.down = function (x, y, obj) {
		self.changeZoom(-0.1); // Decrease zoom by 10%
	};
	// Add right chevron button
	self.zoomRightChevronButton = self.zoomToggle.attachAsset('ButtonRightChevron', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 920,
		y: 0
	});
	// Add click handler for right chevron button
	self.zoomRightChevronButton.down = function (x, y, obj) {
		self.changeZoom(0.1); // Increase zoom by 10%
	};
	// Function to change the zoom level
	self.changeZoom = function (delta) {
		// Update zoom level with bounds checking (0-100%)
		currentZoomLevel = Math.max(0, Math.min(1, currentZoomLevel + delta));
		// Calculate the current scale ratio based on zoom level
		// Linear interpolation between minScaleRatio and maxScaleRatio
		currentScaleRatio = minScaleRatio + (maxScaleRatio - minScaleRatio) * currentZoomLevel;
		// Apply the new scale ratio to the troll face container
		if (trollFace) {
			trollFace.scale.x = currentScaleRatio;
			trollFace.scale.y = currentScaleRatio;
		}
		// Save to storage
		storage.currentZoomLevel = currentZoomLevel;
		// Update the zoom text
		self.updateZoomText();
		// Play beep sound when zoom is changed
		LK.getSound('beep').play();
	};
	// Function to update zoom text
	self.updateZoomText = function () {
		self.zoomText.setText("Zoom " + Math.round(currentZoomLevel * 100) + "%");
	};
	// Initialize zoom text with current level
	self.updateZoomText();
	// Add ButtonOk at the bottom of the settingsPopup
	var buttonOk = self.attachAsset('ButtonOk', {
		anchorX: 0.5,
		anchorY: 0.5,
		y: 1060 // Position at the bottom of the popup
	});
	buttonOk.down = function (x, y, obj) {
		// Hide the settings popup when ButtonOk is pressed
		self.visible = false;
		bypassTracking = false; // Reset bypassTracking when closing settings
		trollFace.visible = true; // Show trollface when settings popup is hidden
		// Play beep sound when settings popup is closed
		LK.getSound('beep').play();
	};
	// Define any additional properties or methods for the SettingsPopup here
	// Add a function to update the background toggle label
	self.updateBackgroundToggleLabel = function () {
		// Update the background text to reflect the current state
		self.backgroundText.setText(backgroundVisible ? "Blank" : "Camera");
	};
	// Define onShow to call the update function
	self.onShow = function () {
		self.updateBackgroundToggleLabel();
		self.updateZoomText();
	};
	return self;
});
var TrollFace = Container.expand(function () {
	var self = Container.call(this);
	self.scales = {
		head: {
			x: 1,
			y: 1
		},
		leftEye: {
			x: 1,
			y: 1
		},
		rightEye: {
			x: 1,
			y: 1
		},
		upperLip: {
			x: 1,
			y: 1
		},
		lowerLip: {
			x: 1,
			y: 1
		}
	};
	// Properties
	self.currentStyle = 1;
	self.currentOffsets = trollFaceOffsets[self.currentStyle - 1];
	self.elements = {};
	// Initialize cached position objects to reuse
	self.positionCache = {
		head: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		leftEye: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		rightEye: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		upperLip: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		lowerLip: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		}
	};
	// Initialize the troll face elements
	self.initialize = function (style) {
		// Clear previous elements
		self.removeAllElements();
		// Create new elements for the selected style
		self.createFaceElements(style || self.currentStyle);
	};
	// Create face elements for a specific style
	self.createFaceElements = function (style) {
		// Create head
		self.elements.head = self.attachAsset('trollHead' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1,
			alpha: 1 // DEBUG
		});
		// Create eyes
		self.elements.leftEye = self.attachAsset('trollLeftEye' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		self.elements.rightEye = self.attachAsset('trollRightEye' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		// Create upper lip
		self.elements.upperLip = self.attachAsset('trollUpperLip' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		// Create lower lip
		self.elements.lowerLip = self.attachAsset('trollLowerLip' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
	};
	// Remove all face elements
	self.removeAllElements = function () {
		if (self.elements.head) {
			self.removeChild(self.elements.head);
		}
		if (self.elements.leftEye) {
			self.removeChild(self.elements.leftEye);
		}
		if (self.elements.rightEye) {
			self.removeChild(self.elements.rightEye);
		}
		if (self.elements.upperLip) {
			self.removeChild(self.elements.upperLip);
		}
		if (self.elements.lowerLip) {
			self.removeChild(self.elements.lowerLip);
		}
	};
	// Change to the next troll face style
	self.nextStyle = function (forcedStyle) {
		self.currentStyle = forcedStyle || self.currentStyle % trollFaceOffsets.length + 1;
		self.initialize();
		self.currentOffsets = trollFaceOffsets[self.currentStyle - 1];
		// Calculate face scale once and reuse
		var baseScale = self.calculateFaceScale(facekit);
		// Calculate all scales upfront
		self.scales = {
			head: {
				x: baseScale * self.currentOffsets.head.sx,
				y: baseScale * self.currentOffsets.head.sy
			},
			leftEye: {
				x: baseScale * self.currentOffsets.leftEye.sx,
				y: baseScale * self.currentOffsets.leftEye.sy
			},
			rightEye: {
				x: baseScale * self.currentOffsets.rightEye.sx,
				y: baseScale * self.currentOffsets.rightEye.sy
			},
			upperLip: {
				x: baseScale * self.currentOffsets.upperLip.sx,
				y: baseScale * self.currentOffsets.upperLip.sy
			},
			lowerLip: {
				x: baseScale * self.currentOffsets.lowerLip.sx,
				y: baseScale * self.currentOffsets.lowerLip.sy
			}
		};
		return self.currentStyle;
	};
	// Helper function to clamp a value between min and max
	self.clampPosition = function (value, min, max) {
		return Math.min(Math.max(value, min), max);
	};
	// Helper function to ensure scale is an object
	self.ensureScaleIsObject = function (element) {
		if (_typeof(element.scale) !== 'object') {
			element.scale = {
				x: 1,
				y: 1
			};
		}
	};
	// Helper function to update a face element
	self.updateFaceElement = function (elementName, x, y, scaleX, scaleY, makeVisible) {
		var element = self.elements[elementName];
		if (!element) {
			return;
		}
		// Ensure scale is an object
		self.ensureScaleIsObject(element);
		// Apply position with clamping using scaled boundaries
		var elementOffset = self.currentOffsets[elementName];
		element.x = self.clampPosition(x, elementOffset.minX * currentEyeDistance, elementOffset.maxX * currentEyeDistance);
		element.y = self.clampPosition(y, elementOffset.minY * currentEyeDistance, elementOffset.maxY * currentEyeDistance);
		// Apply scale
		element.scale.x = scaleX;
		element.scale.y = scaleY;
		// Set visibility if needed
		if (makeVisible) {
			element.visible = true;
		}
	};
	// Helper function to update all face elements
	self.updateAllFaceElements = function (kit, makeVisible, useDefaultScales) {
		var elementNames = ['head', 'leftEye', 'rightEye', 'upperLip', 'lowerLip'];
		var positions = self.positionCache;
		// Calculate positions for all elements
		positions.head.x = 0;
		positions.head.y = 0;
		positions.head.scaleX = useDefaultScales ? 1 : self.scales.head.x;
		positions.head.scaleY = useDefaultScales ? 1 : self.scales.head.y;
		// Get the rotation angle for constraint calculations
		var rotationAngle = self.rotation;
		var cosAngle = Math.cos(-rotationAngle); // Negative to counter-rotate
		var sinAngle = Math.sin(-rotationAngle);
		// For other elements, calculate based on kit positions
		for (var i = 1; i < elementNames.length; i++) {
			var name = elementNames[i];
			var kitElement = kit[name];
			if (kitElement) {
				var scaleX, scaleY;
				// Determine which scale to use based on element type and useDefaultScales flag
				if (useDefaultScales) {
					scaleX = 1 * self.currentOffsets[name].sx;
					scaleY = 1 * self.currentOffsets[name].sy;
				} else {
					if (name === 'leftEye') {
						scaleX = self.scales.leftEye.x;
						scaleY = self.scales.leftEye.y;
					} else if (name === 'rightEye') {
						scaleX = self.scales.rightEye.x;
						scaleY = self.scales.rightEye.y;
					} else if (name === 'upperLip') {
						scaleX = self.scales.upperLip.x;
						scaleY = self.scales.upperLip.y;
					} else if (name === 'lowerLip') {
						scaleX = self.scales.lowerLip.x;
						scaleY = self.scales.lowerLip.y;
					}
				}
				// Calculate position using relative offsets scaled by eye distance
				var rawX = kitElement.x - self.x + self.currentOffsets[name].x * currentEyeDistance;
				var rawY = kitElement.y - self.y + self.currentOffsets[name].y * currentEyeDistance;
				// Apply rotation constraints to maintain relative positions
				// This counter-rotates the positions to keep elements in proper alignment
				positions[name].x = rawX * cosAngle - rawY * sinAngle;
				positions[name].y = rawX * sinAngle + rawY * cosAngle;
				positions[name].scaleX = scaleX;
				positions[name].scaleY = scaleY;
			}
		}
		// Update each element with calculated positions
		for (var j = 0; j < elementNames.length; j++) {
			var elemName = elementNames[j];
			if (self.elements[elemName] && positions[elemName]) {
				var pos = positions[elemName];
				self.updateFaceElement(elemName, pos.x, pos.y, pos.scaleX, pos.scaleY, makeVisible);
			}
		}
		// Handle mouth open adjustment
		if (kit.mouthOpen && self.elements.lowerLip) {
			//self.elements.lowerLip.scale.y = self.scales.lip.y * 1.5;
		}
	};
	// Update face elements to match real face
	self.updateFacePosition = function () {
		if (!facekit) {
			return;
		}
		// If in first phase of centering, directly apply stored positions with translation
		if (self.isFirstPhaseCentering && self.relativePositions) {
			// Apply stored positions to each element
			for (var name in self.elements) {
				if (self.relativePositions[name]) {
					var element = self.elements[name];
					element.x = self.relativePositions[name].x;
					element.y = self.relativePositions[name].y;
					element.scale.x = self.relativePositions[name].scaleX;
					element.scale.y = self.relativePositions[name].scaleY;
				}
			}
			// Set rotation to stored value
			self.rotation = self.relativePositions.rotation;
			return; // Skip the normal update process
		}
		// Get kit based on tracking state
		var kit = bypassTracking ? fakeCamera : facekitMgr.isTracking ? facekitMgr.currentFacekit : facekit;
		// If re-centering, use fakeCamera data for positioning without changing bypassTracking
		if (self.isRecentering) {
			kit = fakeCamera;
		}
		// Update global eye distance once per frame
		currentEyeDistance = self.getEyeDistance(kit);
		var baseScale = self.calculateFaceScale(kit);
		// Calculate face rotation - use FacekitManager if tracking, otherwise use local calculation
		var rotation = facekitMgr.isTracking ? facekitMgr.currentRotation + Math.PI : self.calculateFaceRotation(kit);
		// If re-centering or in first phase, reset rotation to 0
		if (self.isRecentering || self.isFirstPhaseCentering) {
			rotation = 0;
		}
		// Update scales
		self.scales = {
			head: {
				x: baseScale * self.currentOffsets.head.sx,
				y: baseScale * self.currentOffsets.head.sy
			},
			leftEye: {
				x: baseScale * self.currentOffsets.leftEye.sx,
				y: baseScale * self.currentOffsets.leftEye.sy
			},
			rightEye: {
				x: baseScale * self.currentOffsets.rightEye.sx,
				y: baseScale * self.currentOffsets.rightEye.sy
			},
			upperLip: {
				x: baseScale * self.currentOffsets.upperLip.sx,
				y: baseScale * self.currentOffsets.upperLip.sy
			},
			lowerLip: {
				x: baseScale * self.currentOffsets.lowerLip.sx,
				y: baseScale * self.currentOffsets.lowerLip.sy
			}
		};
		if (bypassTracking) {
			self.x = 2048 / 2;
			self.y = 2732 / 2;
			self.rotation = 0; // Reset rotation in bypass mode
			// Use the global fakeCamera with dynamic scales
			self.updateAllFaceElements(fakeCamera, true, false);
			return;
		}
		// Apply rotation to the entire troll face container
		self.rotation = rotation;
		// Update all elements with kit
		self.updateAllFaceElements(kit, true, false);
	};
	// Calculate scale based on eye distance
	self.calculateFaceScale = function (kit) {
		return currentEyeDistance * 0.005;
	};
	// Calculate face rotation angle based on eye positions
	self.calculateFaceRotation = function (kit) {
		if (kit && kit.leftEye && kit.rightEye) {
			// Get eye positions
			var leftEye = kit.leftEye;
			var rightEye = kit.rightEye;
			// Calculate angle - simpler approach
			// This gives us the angle of the line connecting the eyes
			var angle = Math.atan2(rightEye.y - leftEye.y, rightEye.x - leftEye.x);
			// More efficient angle wrapping using modulo
			var angleDiff = angle - currentRotation;
			// Normalize the difference to [-PI, PI] range
			// Using efficient modulo approach rather than while loops
			angleDiff = (angleDiff + Math.PI) % (2 * Math.PI) - Math.PI;
			// Apply smoothing to the rotation
			currentRotation = currentRotation + angleDiff * 0.2;
			return currentRotation + Math.PI;
		}
		return 0; // Default rotation (no rotation)
	};
	// Get eye distance from kit
	self.getEyeDistance = function (kit) {
		if (kit && kit.leftEye && kit.rightEye) {
			var dx = kit.leftEye.x - kit.rightEye.x;
			var dy = kit.leftEye.y - kit.rightEye.y;
			return Math.sqrt(dx * dx + dy * dy);
		}
		return 200; // Default eye distance
	};
	// Add a property to track if we're in re-centering mode
	self.isRecentering = false;
	// Add a property to track if we're in the first phase of centering (maintaining relative positions)
	self.isFirstPhaseCentering = false;
	// Store relative positions of elements during centering
	self.relativePositions = {};
	// Capture current face positions for first phase centering
	self.captureCurrentPositions = function () {
		// Store the exact current positions and scales of all elements
		if (!self.elements) {
			return;
		}
		// Create a snapshot of the current state
		self.relativePositions = {
			// Store positions and scales for each element
			head: self.elements.head ? {
				x: self.elements.head.x,
				y: self.elements.head.y,
				scaleX: self.elements.head.scale.x,
				scaleY: self.elements.head.scale.y
			} : null,
			leftEye: self.elements.leftEye ? {
				x: self.elements.leftEye.x,
				y: self.elements.leftEye.y,
				scaleX: self.elements.leftEye.scale.x,
				scaleY: self.elements.leftEye.scale.y
			} : null,
			rightEye: self.elements.rightEye ? {
				x: self.elements.rightEye.x,
				y: self.elements.rightEye.y,
				scaleX: self.elements.rightEye.scale.x,
				scaleY: self.elements.rightEye.scale.y
			} : null,
			upperLip: self.elements.upperLip ? {
				x: self.elements.upperLip.x,
				y: self.elements.upperLip.y,
				scaleX: self.elements.upperLip.scale.x,
				scaleY: self.elements.upperLip.scale.y
			} : null,
			lowerLip: self.elements.lowerLip ? {
				x: self.elements.lowerLip.x,
				y: self.elements.lowerLip.y,
				scaleX: self.elements.lowerLip.scale.x,
				scaleY: self.elements.lowerLip.scale.y
			} : null,
			// Store current rotation
			rotation: self.rotation,
			// Store current container position
			containerX: self.x,
			containerY: self.y
		};
	};
	return self;
});
/**** 
* Initialize Game
****/ 
// FacekitManager: handles face tracking, smoothing, and fallback
var game = new LK.Game({
	backgroundColor: 0xFFFFFF
});
/**** 
* Game Code
****/ 
// FacekitManager: handles face tracking, smoothing, and fallback
/**** 
* Global Variables
****/ 
var FacekitManager = function FacekitManager() {
	var self = new Container();
	// Public properties
	self.currentFacekit = null; // Current smoothed face data
	self.isTracking = false; // Current tracking state
	self.currentRotation = 0; // Current smoothed rotation (accessible directly for performance)
	self.smoothingFactor = 0.5; // Default smoothing factor for facial features
	self.headSmoothingFactor = 0.2; // Separate smoothing factor for head position (noseTip)
	self.lastLipPosition = null; // Last lower lip position for tracking detection
	self.trackingStoppedCounter = 100; // Counter for tracking detection
	self.trackingStoppedDelay = 100; // Delay threshold for tracking detection
	// Default positions - defined inline
	var defaultFacekit = {
		leftEye: {
			x: 1380,
			y: 958
		},
		rightEye: {
			x: 673,
			y: 970
		},
		upperLip: {
			x: 1027,
			y: 1610
		},
		lowerLip: {
			x: 1030,
			y: 1613
		},
		noseTip: {
			x: 1024,
			y: 1366
		},
		mouthOpen: false
	};
	// Initialize manager
	self.initialize = function (fakeFacekit) {
		// Use provided fallback or default
		self.fallbackFacekit = fakeFacekit || defaultFacekit;
		// Initialize current data as reference to fallback
		self.currentFacekit = self.fallbackFacekit;
		// Create a reusable object for smoothing calculations
		self.smoothFacekit = {
			leftEye: {
				x: 0,
				y: 0
			},
			rightEye: {
				x: 0,
				y: 0
			},
			upperLip: {
				x: 0,
				y: 0
			},
			lowerLip: {
				x: 0,
				y: 0
			},
			noseTip: {
				x: 0,
				y: 0
			},
			mouthOpen: false
		};
		return self;
	};
	// Process new tracking data and update tracking status
	self.updateTrackingStatus = function (rawFacekit) {
		// Check if there's valid face data
		var hasFaceData = !!(rawFacekit && rawFacekit.lowerLip);
		if (!hasFaceData) {
			self.isTracking = false;
			return false;
		}
		// Check if lower lip position has changed using existing tracking mechanism
		if (self.lastLipPosition === rawFacekit.lowerLip.y) {
			self.trackingStoppedCounter--;
			if (self.trackingStoppedCounter <= 0) {
				self.isTracking = false;
				self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
			}
		} else {
			self.isTracking = true;
			self.lastLipPosition = rawFacekit.lowerLip.y;
			self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
		}
		// If tracking, process the face data
		if (self.isTracking) {
			// If tracking just started, initialize smooth values
			if (self.currentFacekit === self.fallbackFacekit) {
				self._initSmoothValues(rawFacekit);
			}
			// Apply smoothing to each facial feature
			self._smoothValues(rawFacekit);
			// Set currentFacekit to the smoothed values
			self.currentFacekit = self.smoothFacekit;
		} else {
			// Use fallback when not tracking
			self.currentFacekit = self.fallbackFacekit;
		}
		// Return tracking state
		return self.isTracking;
	};
	// Initialize smooth values with raw data
	self._initSmoothValues = function (rawFacekit) {
		// Directly set initial values from raw data
		for (var key in rawFacekit) {
			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
				self.smoothFacekit[key].x = rawFacekit[key].x;
				self.smoothFacekit[key].y = rawFacekit[key].y;
			}
		}
		// Initialize rotation directly
		if (rawFacekit.leftEye && rawFacekit.rightEye) {
			self.currentRotation = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
		}
	};
	// Apply smoothing to all values
	self._smoothValues = function (rawFacekit) {
		// Smooth positions (reuse existing objects)
		for (var key in rawFacekit) {
			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
				// Apply position smoothing directly with different factors for head vs. other elements
				var factor = key === 'noseTip' ? self.headSmoothingFactor : self.smoothingFactor;
				self.smoothFacekit[key].x += (rawFacekit[key].x - self.smoothFacekit[key].x) * factor;
				self.smoothFacekit[key].y += (rawFacekit[key].y - self.smoothFacekit[key].y) * factor;
			}
		}
		// Calculate and smooth rotation
		if (rawFacekit.leftEye && rawFacekit.rightEye) {
			var newAngle = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
			// Improved angle normalization to prevent full rotations - no while loops
			var angleDiff = newAngle - self.currentRotation;
			// Efficiently handle -PI/PI boundary crossing (replaces the while loops)
			if (angleDiff > Math.PI) {
				angleDiff -= 2 * Math.PI;
			} else if (angleDiff < -Math.PI) {
				angleDiff += 2 * Math.PI;
			}
			// Apply safeguard against large changes
			var maxRotationPerFrame = Math.PI / 10; // Limit rotation change
			// Clamp the rotation change to prevent sudden large rotations
			if (angleDiff > maxRotationPerFrame) {
				angleDiff = maxRotationPerFrame;
			}
			if (angleDiff < -maxRotationPerFrame) {
				angleDiff = -maxRotationPerFrame;
			}
			// Apply smoothing
			self.currentRotation += angleDiff * self.smoothingFactor;
			// Normalize the result angle to ensure it stays in -PI to PI range
			// Using efficient modulo approach rather than while loops
			self.currentRotation = (self.currentRotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
		}
		// Copy mouth state
		self.smoothFacekit.mouthOpen = rawFacekit.mouthOpen;
	};
	return self;
};
var debugMode = false; // DEBUG MODE DEBUG MODE  DEBUG MODE
var bypassTracking = false; // Global variable to bypass face tracking
var currentEyeDistance = 200; // Global variable to store current eye distance
var currentRotation = 0; // Current smoothed rotation value
var settingsPopup; // Global variable for settings popup
var soundEnabled = true; // Global variable to track if sound is enabled
var confettiEnabled = true; // Global variable to track if confetti is enabled
var backgroundVisible = true; // Global variable to track if background is visible
var currentZoomLevel = 0.5; // Global variable to track zoom level (50%)
var minScaleRatio = 0.5; // Minimum scale ratio for zoom
var maxScaleRatio = 2; // Maximum scale ratio for zoom
var currentScaleRatio = 1.0; // Current scale ratio based on zoom level
var punchlineSounds = [{
	name: "None",
	asset: ""
}, {
	name: "Rimshot",
	asset: "punchlineSound1"
}, {
	name: "Trombone 1",
	asset: "punchlineSound2"
}, {
	name: "Trombone 2",
	asset: "punchlineSound3"
}, {
	name: "Laugh",
	asset: "punchlineSound4"
}, {
	name: "Crowd Laugh",
	asset: "punchlineSound5"
}]; // Array to store available punchline sounds
var punchlineConfettis = [{
	name: "None"
}, {
	name: "Confettis",
	"class": "ConfettisParty"
}, {
	name: "Ha ha ha!",
	"class": "ConfettisHaha"
}, {
	name: "Smileys",
	"class": "ConfettisSmiley"
}]; // Array to store available punchline confetti effects
var currentPunchlineSound = 5; // Default to "Crowd Laugh"
var storedSoundIndex = storage.currentPunchlineSound;
// If there's a valid stored sound index, use it
if (typeof storedSoundIndex === 'number' && storedSoundIndex >= 0 && storedSoundIndex < punchlineSounds.length) {
	currentPunchlineSound = storedSoundIndex;
}
var currentPunchlineConfetti = 2; // Default to "Ha ha ha"
var storedConfettiIndex = storage.currentPunchlineConfetti;
// If there's a valid stored confetti index, use it
if (typeof storedConfettiIndex === 'number' && storedConfettiIndex >= 0 && storedConfettiIndex < punchlineConfettis.length) {
	currentPunchlineConfetti = storedConfettiIndex;
}
var facekitMgr;
var background;
var instructionText;
var styleText;
var trollFace;
var debugPoints;
var backgroundContainer;
var middlegroundContainer;
var foregroundContainer;
var isTrackingFace = false; // Global flag to track face detection state
var targetPosition;
var lastNosePosition = null; // Global variable to store last facekit.noseTip.x
var trackingStoppedDelay = 100;
var trackingStoppedCounter = trackingStoppedDelay;
var buttonSettings;
var buttonPunchline; // Global variable for punchline button
var faceContainerBg; // Global variable for face container background
var settingsButtonBounds;
var trollFaceOffsets = [{
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0.13,
		y: 0.5,
		sx: 0.4,
		sy: 0.4,
		minX: 0.7,
		maxX: 0.9,
		minY: -0.5,
		maxY: 0
	},
	rightEye: {
		x: 0.2,
		y: 0.5,
		sx: 0.6,
		sy: 0.6,
		minX: -0.5,
		maxX: -0.2,
		minY: -0.5,
		maxY: 0
	},
	upperLip: {
		x: 0.25,
		y: 0.05,
		sx: 0.9,
		sy: 0.9,
		minX: -1,
		maxX: 1,
		minY: 0,
		maxY: 0.42
	},
	lowerLip: {
		x: 0.25,
		y: 0.06,
		sx: 0.9,
		sy: 0.9,
		minX: -1,
		maxX: 0.25,
		minY: 0,
		maxY: 0.9
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0,
		y: 0.5,
		sx: 0.6,
		sy: 0.6,
		minX: 0.2,
		maxX: 0.7,
		minY: 0,
		maxY: 0.6
	},
	rightEye: {
		x: 0.1,
		y: 0.3,
		sx: 0.6,
		sy: 0.6,
		minX: -0.7,
		maxX: 0.1,
		minY: 0,
		maxY: 0.4
	},
	upperLip: {
		x: -0.2,
		y: 0.1,
		sx: 0.5,
		sy: 0.5,
		minX: -0.3,
		maxX: 0,
		minY: -1,
		maxY: 1
	},
	lowerLip: {
		x: -0.10,
		y: 0.12,
		sx: 0.5,
		sy: 0.5,
		minX: -0.2,
		maxX: 0,
		minY: 0,
		maxY: 0.8
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0.13,
		y: -0.02,
		sx: 0.8,
		sy: 0.8,
		minX: 0.6,
		maxX: 0.72,
		minY: -0.75,
		maxY: -0.6
	},
	rightEye: {
		x: 0.15,
		y: -0.02,
		sx: 0.8,
		sy: 0.8,
		minX: -0.6,
		maxX: -0.3,
		minY: -0.75,
		maxY: -0.6
	},
	upperLip: {
		x: 0.1,
		y: -0.3,
		sx: 1,
		sy: 1,
		minX: -0.5,
		maxX: 0.5,
		minY: -1.0,
		maxY: 1.0
	},
	lowerLip: {
		x: 0.15,
		y: -0.10,
		sx: 0.93,
		sy: 0.93,
		minX: -0.5,
		maxX: 0.5,
		minY: -1.0,
		maxY: 0.55
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: -0.27,
		y: 0.325,
		sx: 0.4,
		sy: 0.4,
		minX: -0.3,
		maxX: 0.4,
		minY: -0.3,
		maxY: 0
	},
	rightEye: {
		x: -0.1,
		y: 0.5,
		sx: 0.4,
		sy: 0.4,
		minX: -0.6,
		maxX: -0.3,
		minY: 0,
		maxY: 0.2
	},
	upperLip: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1,
		minX: -1,
		maxX: 1,
		minY: -1,
		maxY: 1
	},
	lowerLip: {
		x: 0,
		y: 0.25,
		sx: 0.6,
		sy: 0.6,
		minX: -0.2,
		maxX: 0.2,
		minY: 0,
		maxY: 0.8
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0.45,
		y: 0.35,
		sx: 0.3,
		sy: 0.3,
		minX: 0.9,
		maxX: 0.95,
		minY: -0.3,
		maxY: -0.2
	},
	rightEye: {
		x: 0.85,
		y: 0.16,
		sx: 0.6,
		sy: 0.6,
		minX: 0.3,
		maxX: 0.5,
		minY: -0.3,
		maxY: -0.2
	},
	upperLip: {
		x: 0.46,
		y: 0.125,
		sx: 0.45,
		sy: 0.45,
		minX: 0,
		maxX: 0.5,
		minY: -0.1,
		maxY: 0.5
	},
	lowerLip: {
		x: 0.45,
		y: 0.125,
		sx: 0.45,
		sy: 0.45,
		minX: -0.45,
		maxX: 0.45,
		minY: 0.6,
		maxY: 0.8
	}
}];
// Define fakeCamera globally with fixed positions for testing
var fakeCamera = {
	leftEye: {
		x: 1380,
		y: 958
	},
	rightEye: {
		x: 673,
		y: 1050
	},
	upperLip: {
		x: 1027,
		y: 1610
	},
	lowerLip: {
		x: 1030,
		y: 1713
	},
	mouthOpen: false,
	noseTip: {
		x: 1024,
		y: 1366
	}
}; // Global object for bypass tracking mode
function _typeof(o) {
	"@babel/helpers - typeof";
	return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
		return typeof o;
	} : function (o) {
		return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
	}, _typeof(o);
}
/**** 
* Game Functions
****/ 
// Handle tap anywhere on the screen to change face
game.down = function (x, y, obj) {
	log("Game Down:", obj);
	// Check if the tap is within the settings button coordinates
	if (settingsPopup.visible || x >= settingsButtonBounds.x && x <= settingsButtonBounds.x + settingsButtonBounds.width && y >= settingsButtonBounds.y && y <= settingsButtonBounds.y + settingsButtonBounds.height) {
		return;
	}
	// Face style changing is now handled by the faceContainer's down event
};
// Update function called every frame
game.update = function () {
	if (bypassTracking) {
		// When bypassing tracking, position the troll face at the center and update its elements
		trollFace.updateFacePosition();
		return;
	}
	if (!facekit || !facekit.noseTip) {
		return;
	}
	// Update tracking status and get smoothed face data
	var isCurrentlyTracking = facekitMgr.updateTrackingStatus(facekit);
	// Update tracking state UI if changed
	if (isCurrentlyTracking !== isTrackingFace) {
		isTrackingFace = isCurrentlyTracking;
		instructionText.setText(isCurrentlyTracking ? "Tracking..." : "No Face found");
	}
	// Update troll face position to match real face
	if (isTrackingFace) {
		// Reset isRecentering flag when face tracking is detected
		trollFace.isRecentering = false;
		trollFace.isFirstPhaseCentering = false;
		// Clear stored positions to prevent any lingering effects
		trollFace.relativePositions = {};
		// Reset head position to default (0,0) relative to container
		if (trollFace.elements && trollFace.elements.head) {
			trollFace.elements.head.x = 0;
			trollFace.elements.head.y = 0;
		}
		// Use the smoothed nose tip position
		trollFace.x = facekitMgr.currentFacekit.noseTip.x;
		trollFace.y = facekitMgr.currentFacekit.noseTip.y;
		// Use the original updateFacePosition with smoothed data
		trollFace.updateFacePosition();
		trollFace.isCentered = false;
		trollFace.isCentering = false;
	} else {
		// If face is not detected, return the face to the center
		if (!trollFace.isCentered) {
			if (trollFace.isCentering) {
				// Don't exit the update function, just skip starting a new tween
				// This allows other updates to continue	
				// Continue updating face elements during centering
				trollFace.updateFacePosition();
			} else {
				trollFace.isCentering = true;
				// First phase: Capture current positions and maintain them during animation
				trollFace.captureCurrentPositions();
				trollFace.isFirstPhaseCentering = true;
				trollFace.isRecentering = false;
				//LK.effects.flashScreen(0xFFFFFF, 300); // Flash screen
				tween(trollFace, {
					x: 2048 / 2,
					y: 2732 / 2
				}, {
					duration: 2000,
					easing: tween.easeInOut,
					onFinish: function onFinish() {
						// Second phase: Switch to default positions
						trollFace.isFirstPhaseCentering = false;
						trollFace.isRecentering = true;
						trollFace.isCentered = true;
						trollFace.isCentering = false;
						// First call updateFacePosition to calculate proper scales
						// This won't affect positions yet since we'll animate them
						trollFace.updateFacePosition();
						// Get positions directly from the updateAllFaceElements function
						var fakePositions = {};
						// Store the original updateFaceElement function
						var originalUpdateFaceElement = trollFace.updateFaceElement;
						// Override the function temporarily to capture positions
						trollFace.updateFaceElement = function (name, x, y, scaleX, scaleY) {
							// Store the position and scale that would be applied
							fakePositions[name] = {
								x: x,
								y: y,
								scaleX: scaleX,
								scaleY: scaleY
							};
						};
						// Call the function to calculate positions without applying them
						trollFace.updateAllFaceElements(fakeCamera, false, false);
						// Restore the original function
						trollFace.updateFaceElement = originalUpdateFaceElement;
						// Animate each element independently to the correct positions
						for (var name in trollFace.elements) {
							if (trollFace.elements[name] && fakePositions[name]) {
								var element = trollFace.elements[name];
								var target = fakePositions[name];
								// Create tween for this element
								tween(element, {
									x: target.x,
									y: target.y,
									scaleX: target.scaleX,
									scaleY: target.scaleY
								}, {
									duration: 500,
									easing: tween.easeOut
								});
							}
						}
					}
				});
			}
		}
		// Update face tracking state
		if (isTrackingFace) {
			isTrackingFace = false;
			instructionText.setText("No Face found");
		}
	}
	// If in debug mode, display the face elements positions
	if (debugMode) {
		// Clear existing debug points
		if (debugPoints) {
			for (var i = 0; i < debugPoints.children.length; i++) {
				debugPoints.children[i].alpha = 0;
			}
		}
		// Draw debug points for each face element
		if (facekit) {
			var pointIndex = 0;
			// Draw a point for each facial feature
			for (var key in facekit) {
				if (facekit[key] && typeof facekit[key].x !== 'undefined') {
					var point = debugPoints.children[pointIndex++];
					if (point) {
						point.x = facekit[key].x;
						point.y = facekit[key].y;
						point.alpha = 1;
					}
				}
			}
			// Display rotation in degrees
			instructionText.setText("Rotation: " + Math.round(trollFace.rotation * 180 / Math.PI) + "Β°");
		}
	}
};
// Function to play confetti effect at specified position
function playConfettiEffect(x, y) {
	if (confettiEnabled) {
		var confetti;
		// Check if a confetti class is specified
		var selectedConfetti = punchlineConfettis[currentPunchlineConfetti];
		if (selectedConfetti && selectedConfetti["class"]) {
			// Create the appropriate confetti effect based on the selected type
			switch (selectedConfetti["class"]) {
				case "ConfettisParty":
					confetti = new ConfettisParty();
					break;
				case "ConfettisHaha":
					confetti = new ConfettisHaha();
					break;
				case "ConfettisSmiley":
					confetti = new ConfettisSmiley();
					break;
				default:
					// Should not reach here with our current configuration
					return;
			}
			// Initialize the confetti effect
			confetti.createParticles(x, y);
			foregroundContainer.addChild(confetti);
			// Update confetti particles
			var updateInterval = LK.setInterval(function () {
				if (confetti.active) {
					confetti.update();
				} else {
					LK.clearInterval(updateInterval);
				}
			}, 16);
		}
		// If no class specified (None option), do nothing
	}
}
// Function to handle face container tap
function handleFaceContainerTap() {
	// Prevent action if settings popup is visible
	if (settingsPopup && settingsPopup.visible) {
		return;
	}
	// Switch to the next troll face style
	var newStyle = trollFace.nextStyle();
	// Save the current style to storage
	storage.lastTrollStyle = newStyle;
	// Update the style text with fade animation
	updateStyleText(newStyle);
	// Play switch sound
	LK.getSound('switchTroll').play();
	// If no face tracking, ensure the face is displayed
	if (!facekit || !facekit.noseTip || !facekitMgr.isTracking) {
		// Set bypass tracking temporarily to ensure face is displayed
		var originalBypass = bypassTracking;
		bypassTracking = true;
		// Update face position to show the face
		trollFace.updateFacePosition();
		// Restore original bypass value
		bypassTracking = originalBypass;
	}
}
// Function to update punchline button visibility
function updatePunchlineButtonVisibility() {
	// For debugging
	log("Updating button visibility. Sound: " + currentPunchlineSound + ", Confetti: " + currentPunchlineConfetti);
	// Hide button if both sound and confetti are set to "None" (index 0)
	if (currentPunchlineSound === 0 && currentPunchlineConfetti === 0) {
		buttonPunchline.visible = false;
		log("Hiding punchline button");
	} else {
		buttonPunchline.visible = true;
		log("Showing punchline button");
	}
}
// Function to update style text with fade animation
function updateStyleText(newStyle) {
	// Update text immediately
	styleText.setText('Face ' + newStyle);
	// Reset to starting state
	styleText.alpha = 0;
	// First phase: Fade in with slight scale up
	tween(styleText, {
		alpha: 1
	}, {
		duration: 1000,
		easing: tween.easeOut,
		onFinish: function onFinish() {
			// Second phase: Fade out with scale down
			tween(styleText, {
				alpha: 0
			}, {
				duration: 1000,
				easing: tween.easeIn,
				onFinish: function onFinish() {}
			});
		}
	});
}
function log() {
	if (debugMode) {
		console.log.apply(console, arguments);
	}
}
function initializeGame() {
	// Initialize game
	// Create containers for layering
	backgroundContainer = new Container();
	middlegroundContainer = new Container();
	foregroundContainer = new Container();
	// Add containers to game
	game.addChild(backgroundContainer);
	game.addChild(middlegroundContainer);
	game.addChild(foregroundContainer);
	// Initialize FacekitManager
	facekitMgr = new FacekitManager();
	facekitMgr.initialize(fakeCamera);
	// Global target position for the troll face
	targetPosition = {
		x: 2048 / 2,
		y: 2732 / 2
	};
	// Setup background
	background = LK.getAsset('whiteBackground', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 2048 / 2,
		y: 2732 / 2,
		visible: true
	});
	backgroundContainer.addChild(background);
	// Setup UI text
	instructionText = new Text2('Tap anywhere to change troll face', {
		size: 50,
		fill: 0xFF0000
	});
	instructionText.anchor.set(0.5, 0);
	LK.gui.top.addChild(instructionText);
	instructionText.y = 1800;
	instructionText.x = -500;
	instructionText.visible = debugMode;
	// Load the last used style from storage
	var lastStyle = storage.lastTrollStyle || 3;
	// Create a container for the face
	var faceContainer = new Container();
	faceContainerBg = LK.getAsset('whiteBackground', {
		anchorX: 0,
		anchorY: 0,
		x: 0,
		y: 230,
		width: 2048,
		height: 2732 - 230 - 430,
		visible: backgroundVisible
	});
	faceContainer.addChild(faceContainerBg);
	middlegroundContainer.addChild(faceContainer);
	// Create the troll face
	trollFace = new TrollFace();
	trollFace.currentStyle = lastStyle;
	trollFace.initialize();
	trollFace.nextStyle(lastStyle);
	// Apply zoom scale to the entire troll face container
	trollFace.scale.x = currentScaleRatio;
	trollFace.scale.y = currentScaleRatio;
	faceContainer.addChild(trollFace);
	// Add tap handler to the face container
	faceContainer.down = function (x, y, obj) {
		handleFaceContainerTap();
	};
	// Initialize tracking state
	isTrackingFace = false;
	// Add ButtonPunchline to the foreground at the bottom center
	buttonPunchline = new ButtonPunchline();
	buttonPunchline.x = 2048 / 2; // Center horizontally
	buttonPunchline.y = 2732 - 300; // Position at the bottom
	foregroundContainer.addChild(buttonPunchline);
	// Set initial visibility based on current settings
	updatePunchlineButtonVisibility();
	// Add ButtonSettings to the foreground at the bottom right
	buttonSettings = new ButtonSettings();
	buttonSettings.x = 2048 - 200; // Position at the bottom right
	buttonSettings.y = 140; // Position at the bottom
	foregroundContainer.addChild(buttonSettings);
	settingsButtonBounds = {
		x: buttonSettings.x - buttonSettings.width / 2,
		y: buttonSettings.y - buttonSettings.height / 2,
		width: buttonSettings.width,
		height: buttonSettings.height
	};
	// Initialize the settings popup and add it to foreground
	settingsPopup = new SettingsPopup();
	settingsPopup.x = 2048 / 2; // Center horizontally
	settingsPopup.y = 2732 / 2; // Center vertically
	settingsPopup.visible = false; // Initially not visible
	foregroundContainer.addChild(settingsPopup);
	// Add style text
	styleText = new Text2('Face ' + lastStyle, {
		size: 80,
		fill: 0x1d3242,
		align: "left",
		fontWeight: "bold"
	});
	styleText.anchor.set(0.5, 0);
	LK.gui.top.addChild(styleText);
	styleText.y = 40;
	styleText.alpha = 1; // Ensure initial alpha is set to 1
	updateStyleText(lastStyle);
	// Debug mode (turn on for development, off for production)
	debugPoints = null;
	if (debugMode) {
		debugPoints = new DebugPoints();
		foregroundContainer.addChild(debugPoints);
		// Log facekit to console every second
		LK.setInterval(function () {
			log(facekit);
			if (facekit.lowerLip) {
				var elementOffset = trollFace.currentOffsets.lowerLip;
				var eyeDistance = trollFace.getEyeDistance(facekit);
				log("lowerLip y:", facekit.lowerLip.y, "minY:", elementOffset.minY * eyeDistance, "maxY:", elementOffset.maxY * eyeDistance);
			}
			// Display rotation angle in degrees for debugging
			var rotationDegrees = Math.round(trollFace.rotation * (180 / Math.PI));
			instructionText.setText("le:".concat(Math.round(facekit.leftEye.x), ",").concat(Math.round(facekit.leftEye.y), " / ") + "re:".concat(Math.round(facekit.rightEye.x), ",").concat(Math.round(facekit.rightEye.y), " / ") + "ul:".concat(Math.round(facekit.upperLip.x), ",").concat(Math.round(facekit.upperLip.y), " / ") + "ll:".concat(Math.round(facekit.lowerLip.x), ",").concat(Math.round(facekit.lowerLip.y), " / ") + "rot:".concat(rotationDegrees, "Β°"));
		}, 1000);
	}
	// Load preferences from storage
	soundEnabled = storage.soundEnabled !== undefined ? storage.soundEnabled : true;
	confettiEnabled = storage.confettiEnabled !== undefined ? storage.confettiEnabled : true;
	backgroundVisible = storage.backgroundVisible !== undefined ? storage.backgroundVisible : true;
	currentZoomLevel = storage.currentZoomLevel !== undefined ? storage.currentZoomLevel : 0.5;
	// Calculate initial scale ratio based on zoom level
	currentScaleRatio = minScaleRatio + (maxScaleRatio - minScaleRatio) * currentZoomLevel;
	// Apply background visibility setting
	if (background) {
		background.visible = backgroundVisible;
	}
	if (faceContainerBg) {
		faceContainerBg.visible = backgroundVisible;
	}
}
// Initialize the game
initializeGame();
// Function to toggle background visibility
function toggleBackground() {
	// Toggle the visibility state
	backgroundVisible = !backgroundVisible;
	// Save to storage
	storage.backgroundVisible = backgroundVisible;
	// Update visibility of both backgrounds
	if (faceContainerBg) {
		faceContainerBg.visible = backgroundVisible;
	}
	if (background) {
		background.visible = backgroundVisible;
	}
	// Update text in the settings popup if it exists and is visible
	if (settingsPopup && settingsPopup.visible) {
		settingsPopup.updateBackgroundToggleLabel();
	}
	// Play beep sound when background is toggled
	LK.getSound('beep').play();
}