/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
/**** 
* Classes
****/ 
var InteractiveElement = Container.expand(function () {
	var self = Container.call(this);
	self.label = "";
	self.description = "";
	self.interactText = "";
	self.onInteract = null;
	self.voiceOverAnnounceId = null;
	self.voiceOverInteractId = null;
	self.lastTapTime = 0;
	self.tapCount = 0;
	// Visual: use tapCircle for all, but color can be changed if needed
	var circle = self.attachAsset('tapCircle', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.7
	});
	// Apply global visibility state
	circle.visible = typeof tapCirclesVisible !== 'undefined' ? tapCirclesVisible : false;
	// Store the tap circle's radius for hit detection (half of width)
	self.hitRadius = circle.width / 2;
	// Set up element
	self.setup = function (label, description, interactText, onInteract, voiceOverAnnounceId, voiceOverInteractId) {
		self.label = label;
		self.description = description;
		self.interactText = interactText;
		self.onInteract = onInteract;
		self.voiceOverAnnounceId = voiceOverAnnounceId;
		self.voiceOverInteractId = voiceOverInteractId;
	};
	// Announce what this is
	self.announce = function () {
		setGuiText(self.label + "\n" + self.description + "\n\n(Double tap to interact)", self.voiceOverAnnounceId);
		// Optionally, play a sound or narration for the element
	};
	// Interact with the element
	self.interact = function () {
		// If onInteract is defined, it is responsible for calling setGuiText.
		if (typeof self.onInteract === "function") {
			self.onInteract();
		} else {
			// Only call setGuiText here if there's no specific onInteract handler
			// that would call it. This uses the generic interactText and voiceOverInteractId.
			setGuiText(self.interactText, self.voiceOverInteractId);
		}
	};
	// Track pending voice-over timeout
	self.pendingVOTimeout = null;
	self.justDoubleTapped = false;
	self.justDoubleTappedTimeout = null;
	// Handle tap/double-tap
	self.down = function (x, y, obj) {
		var now = Date.now();
		var timeSinceLastTap = now - self.lastTapTime;
		// If within double-tap window
		if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
			// Added minimum time to avoid accidental double taps
			// This is a double tap
			// Cancel any pending single-tap voice-over
			if (self.pendingVOTimeout) {
				LK.clearTimeout(self.pendingVOTimeout);
				self.pendingVOTimeout = null;
			}
			// Stop any current voice-over before playing the double-tap VO
			if (currentVoiceOver) {
				currentVoiceOver.stop();
				currentVoiceOver = null;
			}
			self.interact();
			self.lastTapTime = 0; // Reset to prevent triple taps
			// Cooldown after double tap to prevent immediate single tap on this element
			if (self.justDoubleTappedTimeout) {
				LK.clearTimeout(self.justDoubleTappedTimeout);
			}
			self.justDoubleTapped = true;
			self.justDoubleTappedTimeout = LK.setTimeout(function () {
				self.justDoubleTapped = false;
				self.justDoubleTappedTimeout = null;
			}, 700); // 700ms cooldown
			// Play a subtle sound to indicate interaction
			LK.getSound('sfx_tap').play();
		} else {
			// This is a single tap
			// If a double tap just occurred on this element, suppress the immediate single tap action
			// but record the tap time for a potential new double tap.
			if (self.justDoubleTapped) {
				self.lastTapTime = now;
				// Do not schedule announce, do not clear other timers or VOs, as the double tap just handled interaction.
			} else {
				// Standard single tap logic:
				// Cancel any existing pending voice-over
				if (self.pendingVOTimeout) {
					LK.clearTimeout(self.pendingVOTimeout); // {s}
					self.pendingVOTimeout = null; // Explicitly nullify the handle after clearing an old timeout
				} // {t}
				// Stop any current voice-over before scheduling new one
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay the announce to check if it's actually a double-tap
				self.pendingVOTimeout = LK.setTimeout(function () {
					self.announce();
					self.pendingVOTimeout = null;
				}, 300); // Wait 300ms to see if second tap comes
				self.lastTapTime = now;
			}
		}
	};
	return self;
});
// Stone Door for Puzzle 2
var StoneDoor = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.setup("Stone Door", "A massive stone door blocks your path.", "", function () {
		if (bothChestsOpened) {
			LK.getSound('sfx_rock_door_rumble').play();
			setGuiText("The stone door grinds open. You've solved the puzzle!", 'vo_stone_door_open');
			LK.getSound('sfx_success').play();
			goToState(STATE_PUZZLE2_SUCCESS);
		} else {
			setGuiText("The door won't budge. Perhaps something else needs to be done first.", 'vo_stone_door_locked');
		}
	}, 'vo_stone_door_label', 'vo_stone_door_locked');
	return self;
});
// Key
var Key = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.setup("Key", "Something lies on the floor. Cold, small... metal.", "You pick up a rusty key. It hums faintly in your hand.", function () {
		hasKey = true;
		LK.getSound('sfx_key_pickup').play();
		setGuiText("You pick up a rusty key. It hums faintly in your hand.", 'vo_key_pickup');
		self.visible = false; // Hide key after pickup
		self.interactive = false; // Make it non-interactive after pickup
		// Remove from puzzle elements to prevent further interaction
		if (puzzle1Elements) {
			var index = puzzle1Elements.indexOf(self);
			if (index !== -1) {
				puzzle1Elements.splice(index, 1);
			}
		}
	}, 'vo_key_label', 'vo_key_pickup');
	return self;
});
// Heavy Rock for Puzzle 3
var HeavyRock = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isDragging = false;
	self.startX = 0;
	self.startY = 0;
	self.lastX = 0; // Add lastX property
	self.lastY = 0; // Add lastY property
	self.lastDragSoundTime = 0;
	self.setup("Heavy Rock", "A large, heavy rock. It might be moveable with effort.", "The rock is too heavy to pick up, \nbut you might be able to drag it.", null, 'vo_heavy_rock_label', 'vo_rock_drag_hint');
	// Override down to start dragging
	var originalDown = self.down;
	self.down = function (x, y, obj) {
		originalDown.call(self, x, y, obj);
		// If originalDown (from InteractiveElement) processed this as a double tap,
		// self.justDoubleTapped will be true. The interaction VO is already handled.
		// In this case, we should not start dragging or interfere with the VO.
		if (self.justDoubleTapped) {
			// Double tap was handled by InteractiveElement's down method.
			// The interaction VO ('vo_rock_drag_hint') should be playing.
			// Do not initiate drag or stop the VO.
		} else {
			// This was not a double tap processed by originalDown.
			// This means it's likely a single tap intended to start a drag.
			self.isDragging = true;
			self.startX = self.x;
			self.startY = self.y;
			self.dragOffsetX = 0;
			self.dragOffsetY = 0;
			// For a single tap, InteractiveElement.down has scheduled self.announce().
			// We allow this to proceed to play the label VO.
			// If dragging starts immediately, the label VO will play concurrently if not finished.
		}
		// Always update lastX and lastY for consistent drag behavior or other positional logic
		self.lastX = self.x;
		self.lastY = self.y;
	};
	// Handle dragging
	self.handleDrag = function (x, y, obj) {
		if (self.isDragging) {
			// Check if rock would overlap with tile at new position
			var wouldOverlap50Percent = false;
			if (puzzle3FloorTile) {
				// Temporarily move rock to check intersection
				var oldX = self.x;
				var oldY = self.y;
				self.x = x;
				self.y = y;
				// Calculate overlap percentage
				if (self.intersects(puzzle3FloorTile)) {
					// Get bounds of both objects
					var rockLeft = self.x - self.hitRadius;
					var rockRight = self.x + self.hitRadius;
					var rockTop = self.y - self.hitRadius;
					var rockBottom = self.y + self.hitRadius;
					var tileLeft = puzzle3FloorTile.x - puzzle3FloorTile.hitRadius;
					var tileRight = puzzle3FloorTile.x + puzzle3FloorTile.hitRadius;
					var tileTop = puzzle3FloorTile.y - puzzle3FloorTile.hitRadius;
					var tileBottom = puzzle3FloorTile.y + puzzle3FloorTile.hitRadius;
					// Calculate overlap area
					var overlapLeft = Math.max(rockLeft, tileLeft);
					var overlapRight = Math.min(rockRight, tileRight);
					var overlapTop = Math.max(rockTop, tileTop);
					var overlapBottom = Math.min(rockBottom, tileBottom);
					var overlapWidth = Math.max(0, overlapRight - overlapLeft);
					var overlapHeight = Math.max(0, overlapBottom - overlapTop);
					var overlapArea = overlapWidth * overlapHeight;
					// Calculate rock area (assuming circular, use diameter squared)
					var rockArea = self.hitRadius * 2 * (self.hitRadius * 2);
					// Check if overlap is 50% or more
					var overlapPercentage = overlapArea / rockArea;
					wouldOverlap50Percent = overlapPercentage >= 0.5;
				}
				// Restore position
				self.x = oldX;
				self.y = oldY;
			}
			// Only move if not overlapping 50% with tile
			if (!wouldOverlap50Percent) {
				// Move rock directly to touch position (no offset)
				self.x = x;
				self.y = y;
				// Play drag sound periodically
				var now = Date.now();
				if (now - self.lastDragSoundTime > 500) {
					LK.getSound('sfx_rock_drag').play();
					self.lastDragSoundTime = now;
				}
			} else {
				// Rock overlaps 50% with tile - stop dragging and place it
				self.isDragging = false;
				// Play click sound
				LK.getSound('sfx_rock_place').play();
				// Play rock stuck sound
				LK.getSound('sfx_rock_stuck').play();
				// Trigger tile press
				puzzle3FloorTile.pressWithRock();
			}
		}
		;
		// Add update method to track position changes
		self.update = function () {
			self.lastX = self.x; // Update lastX
			self.lastY = self.y; // Update lastY
		};
	};
	// Stop dragging on up
	self.up = function (x, y, obj) {
		if (self.isDragging) {
			self.isDragging = false;
			// Check if rock is on the floor tile
			if (puzzle3FloorTile && self.intersects(puzzle3FloorTile)) {
				puzzle3FloorTile.pressWithRock();
			} else if (puzzle3FloorTile && puzzle3FloorTile.isPressedByRock) {
				// Rock was moved off the tile
				puzzle3FloorTile.releaseFromRock();
			}
		}
	};
	return self;
});
// Gate for Puzzle 3
var Gate = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isOpen = false;
	// Override announce to provide different instructions when open
	var originalAnnounce = self.announce;
	self.announce = function () {
		if (self.isOpen) {
			setGuiText("Gate\nThe gate stands open before you.\n\n(Double tap to pass through)", 'vo_gate_open_announce');
		} else {
			originalAnnounce.call(self);
		}
	};
	self.setup("Gate", "A heavy iron gate blocks your path.", "", function () {
		if (self.isOpen) {
			// When gate is open, double-tap allows passage
			setGuiText("The gate is open! You step through carefully...", 'vo_gate_passage');
			LK.getSound('sfx_success').play();
			goToState(STATE_PUZZLE3_SUCCESS);
		} else {
			setGuiText("The gate is closed. Something needs to keep it open.", 'vo_gate_closed');
		}
	}, 'vo_gate_label', 'vo_gate_closed');
	self.open = function () {
		if (!self.isOpen) {
			self.isOpen = true;
			LK.getSound('sfx_gate_open').play();
			setGuiText("The gate slowly grinds open!", 'vo_gate_opening');
		}
	};
	self.close = function () {
		if (self.isOpen) {
			self.isOpen = false;
			LK.getSound('sfx_gate_close').play();
		}
	};
	return self;
});
// Floor Tile for Puzzle 3
var FloorTile = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isPressed = false;
	self.isPressedByRock = false;
	self.linkedGate = null;
	self.setup("Floor Tile", "A loose stone tile. It seems to move slightly when touched.", "", function () {
		if (self.isPressedByRock) {
			setGuiText("The heavy rock keeps the tile pressed down.", 'vo_tile_pressed_by_rock');
		} else {
			// Updated message for double tap - play gate open/close sounds for feedback
			if (self.linkedGate) {
				self.linkedGate.open();
				// Schedule gate close after a short delay
				LK.setTimeout(function () {
					if (self.linkedGate && !self.isPressedByRock) {
						self.linkedGate.close();
					}
				}, 1000); // Gate stays open for 1 second
			}
			setGuiText("The gate opened, but shut down as soon as I lift my hand. I need something heavy to keep it pressed.", 'vo_tile_springs_back');
		}
	}, 'vo_floor_tile_label', 'vo_tile_springs_back');
	self.pressWithRock = function () {
		if (!self.isPressedByRock) {
			self.isPressedByRock = true;
			self.isPressed = true;
			if (self.linkedGate) {
				self.linkedGate.open();
			}
			LK.getSound('sfx_rock_place').play();
			setGuiText("The rock settles onto the tile with a satisfying thunk. The gate remains open!", 'vo_rock_on_tile');
		}
	};
	self.releaseFromRock = function () {
		if (self.isPressedByRock) {
			self.isPressedByRock = false;
			self.isPressed = false;
			if (self.linkedGate) {
				self.linkedGate.close();
			}
		}
	};
	return self;
});
// Door
var Door = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.setup("Door", "A heavy wooden door. It doesn't budge.", "", function () {
		// Door can only be opened if player has the key
		if (typeof hasKey !== "undefined" && hasKey) {
			self.interactText = "The key turns with a satisfying click. The path ahead opens.";
			setGuiText("The key turns with a satisfying click. The path ahead opens.", 'vo_door_unlocked');
			// Play new door unlock sound
			LK.getSound('sfx_door_unlock').play();
			goToState(STATE_PUZZLE1_SUCCESS);
		} else {
			self.interactText = "The door is locked tight. You'll need something to open it.";
			setGuiText("The door is locked tight. You'll need something to open it.", 'vo_door_locked');
		}
	}, 'vo_door_label', 'vo_door_locked');
	return self;
});
// Chime for Puzzle 4
var Chime = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.chimeIndex = 0;
	self.isCommitted = false;
	self.chimeSound = null;
	self.pendingSingleTapTimeout = null;
	self.setup = function (index) {
		self.chimeIndex = index;
		var label = "Chime " + (index + 1);
		var description = "";
		// Use specific voice-over for each chime description
		var chimeVoiceOverId = 'vo_chime_description_' + (index + 1);
		InteractiveElement.call(self).setup(label, description, "", function () {
			// Double tap - commit to this chime
			if (!self.isCommitted && !puzzle4Complete) {
				self.commit();
			}
		}, chimeVoiceOverId, null);
		// Override the circle color based on chime index
		if (self.children[0]) {
			var colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00]; // Red, Green, Blue, Yellow
			self.children[0].tint = colors[index % 4];
		}
	};
	// Override announce to play instruction VO first
	self.announce = function () {
		// Play the instruction VO first
		var instructionVO = LK.getSound('vo_chime_instruction');
		instructionVO.play();
		// Wait for instruction to finish before playing chime description
		LK.setTimeout(function () {
			// Call parent announce method which will play the chime description
			InteractiveElement.prototype.announce.call(self);
			// After chime description, play general description
			LK.setTimeout(function () {
				var generalDescVO = LK.getSound('vo_chime_general_description');
				generalDescVO.play();
			}, 2000); // Wait for chime description to finish
		}, 3000); // Adjust timing based on instruction VO length
	};
	// Override down method from InteractiveElement to handle chime-specific behavior
	self.down = function (x, y, obj) {
		var now = Date.now();
		var timeSinceLastTap = now - self.lastTapTime;
		// If within double-tap window
		if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
			// This is a double tap
			// Cancel any pending single-tap sound
			if (self.pendingSingleTapTimeout) {
				LK.clearTimeout(self.pendingSingleTapTimeout);
				self.pendingSingleTapTimeout = null;
			}
			// Cancel any pending single-tap voice-over
			if (self.pendingVOTimeout) {
				LK.clearTimeout(self.pendingVOTimeout);
				self.pendingVOTimeout = null;
			}
			// Stop any current voice-over before playing the double-tap VO
			if (currentVoiceOver) {
				currentVoiceOver.stop();
				currentVoiceOver = null;
			}
			self.interact();
			self.lastTapTime = 0; // Reset to prevent triple taps
			// Cooldown after double tap to prevent immediate single tap on this element
			if (self.justDoubleTappedTimeout) {
				LK.clearTimeout(self.justDoubleTappedTimeout);
			}
			self.justDoubleTapped = true;
			self.justDoubleTappedTimeout = LK.setTimeout(function () {
				self.justDoubleTapped = false;
				self.justDoubleTappedTimeout = null;
			}, 700); // 700ms cooldown
			// Play a subtle sound to indicate interaction
			LK.getSound('sfx_tap').play();
		} else {
			// This is a single tap
			// If a double tap just occurred on this element, suppress the immediate single tap action
			// but record the tap time for a potential new double tap.
			if (self.justDoubleTapped) {
				self.lastTapTime = now;
				// Do not schedule announce, do not clear other timers or VOs, as the double tap just handled interaction.
			} else {
				// Standard single tap logic:
				// Cancel any existing pending voice-over
				if (self.pendingVOTimeout) {
					LK.clearTimeout(self.pendingVOTimeout); // {s}
					self.pendingVOTimeout = null; // Explicitly nullify the handle after clearing an old timeout
				} // {t}
				// Cancel any existing pending single tap sound
				if (self.pendingSingleTapTimeout) {
					LK.clearTimeout(self.pendingSingleTapTimeout);
					self.pendingSingleTapTimeout = null;
				}
				// Stop any current voice-over before scheduling new one
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay both the announce and the soft chime sound to check if it's actually a double-tap
				self.pendingVOTimeout = LK.setTimeout(function () {
					self.announce();
					self.pendingVOTimeout = null;
				}, 300); // Wait 300ms to see if second tap comes
				// Schedule soft chime sound
				self.pendingSingleTapTimeout = LK.setTimeout(function () {
					// Play instruction VO first
					var instructionVO = LK.getSound('vo_chime_instruction');
					instructionVO.play();
					// Wait for instruction to finish before playing chime sound
					LK.setTimeout(function () {
						// Play soft chime sound at lower volume
						if (self.chimeSound && !self.isCommitted) {
							var softSound = self.chimeSound;
							softSound.volume = 0.3; // Set to 30% volume for soft tap
							softSound.play();
							// Reset volume after playing
							LK.setTimeout(function () {
								softSound.volume = 1.0; // Reset to full volume
							}, 100);
						}
					}, 3000); // Wait for instruction VO to finish
					self.pendingSingleTapTimeout = null;
				}, 300); // Same delay as voice-over
				self.lastTapTime = now;
			}
		}
	};
	self.commit = function () {
		self.isCommitted = true;
		playerChimeSequence.push(self.chimeIndex);
		// Play louder committed sound at full volume
		if (self.chimeSound) {
			self.chimeSound.volume = 1.0; // Ensure full volume for double tap
			self.chimeSound.play();
		}
		// Visual feedback - flash the chime
		tween(self, {
			alpha: 0.3
		}, {
			duration: 200,
			easing: tween.easeOut,
			onFinish: function onFinish() {
				tween(self, {
					alpha: 1
				}, {
					duration: 200,
					easing: tween.easeIn
				});
			}
		});
		// Check if player has entered all required chimes
		var requiredLength = correctChimeSequence.length;
		if (playerChimeSequence.length === requiredLength) {
			checkChimeSequence();
		} else {
			setGuiText("Chime " + playerChimeSequence.length + " of " + requiredLength + " selected.", 'vo_chime_selected');
		}
	};
	return self;
});
// Chest for Puzzle 2
var Chest = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isOpened = false;
	self.chestId = 0;
	self.isVialChest = false; // Will be set during setup
	self.isTrapChest = false; // Will be set during setup
	self.setup = function (id, isVial, isTrap) {
		self.chestId = id;
		self.isVialChest = !!isVial;
		self.isTrapChest = !!isTrap;
		// Inspection text
		var inspectText = "A wooden chest with intricate carvings.";
		if (self.isVialChest) {
			inspectText = "A wooden chest. It smells faintly of herbs.";
		} else if (self.isTrapChest) {
			inspectText = "Another chest, this one colder to the touch.";
		}
		// Interact logic
		InteractiveElement.call(self).setup("Chest " + (id + 1), inspectText, "", function () {
			if (!self.isOpened) {
				self.isOpened = true;
				// Play chest open sound
				LK.getSound('sfx_chest_open').play();
				// Vial Chest
				if (self.isVialChest) {
					hasVialOfClarity = true;
					// Play background music for vial chest open
					LK.playMusic('vial_chest_open_bgm', {
						loop: true
					});
					// If trap was already triggered, heal and stop distortion
					if (trapActive) {
						trapActive = false;
						// Stop annoying trap sound if running
						stopAnnoyingTrapSound();
						var vialText = "You open the chest… a soft chime rings out. \nInside, a small vial, warm to the touch. \nYou feel a little more focused. \nThe distortion fades.";
						// If this is the second chest, append the stone shifting message
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							vialText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(vialText, 'vo_vial_heals_trap');
						// Play soft chime, then steady hum (simulate with sfx_success for now)
						LK.getSound('sfx_success').play();
					} else {
						var vialText = "You open the chest… a soft chime rings out.\n Inside, a small vial, warm to the touch. \nYou feel a little more focused.";
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							vialText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(vialText, 'vo_vial_found');
						LK.getSound('sfx_success').play();
					}
				}
				// Trap Chest
				else if (self.isTrapChest) {
					// If player already has vial, neutralize trap
					if (hasVialOfClarity) {
						var trapText = "You open the chest… a rush of wind escapes,\n but the vial in your hand pulses gently, shielding your senses.";
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							trapText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(trapText, 'vo_trap_neutralized');
						// Play wind muffled/steady hum (simulate with sfx_tap for now)
						LK.getSound('sfx_tap').play();
						// Stop annoying trap sound if running
						stopAnnoyingTrapSound();
					} else {
						trapActive = true;
						var trapText = "You open the chest… a rush of wind escapes. A sharp whisper pierces your ears. Everything feels… twisted.";
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							trapText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(trapText, 'vo_trap_triggered');
						// Play rushing wind/sharp whisper (simulate with sfx_tap for now)
						LK.getSound('sfx_tap').play();
						// Start annoying trap sound
						startAnnoyingTrapSound();
						// TODO: Add actual distortion effect if available
					}
				}
				// Fallback (should not happen)
				else {
					var fallbackText = "The chest creaks open. You find nothing inside, but sense progress.";
					var openedCount = 0;
					for (var i = 0; i < puzzle2Elements.length; i++) {
						if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
							openedCount++;
						}
					}
					if (openedCount === 2) {
						fallbackText += "\nYou hear a heavy stone shifting nearby.";
					}
					setGuiText(fallbackText, 'vo_chest_opened');
					LK.getSound('sfx_success').play();
				}
				checkPuzzle2Progress();
			} else {
				setGuiText("The chest is already open.", 'vo_chest_empty');
			}
		},
		// Announce/inspect voice-over
		self.isVialChest ? 'vo_vial_inspect' : self.isTrapChest ? 'vo_trap_inspect' : 'vo_chest_label',
		// Interact voice-over
		self.isVialChest ? 'vo_vial_found' : self.isTrapChest ? 'vo_trap_triggered' : 'vo_chest_opened');
	};
	return self;
});
// Chest
// Simple visual feedback for taps (for sighted users or those with some vision)
var TapFeedback = Container.expand(function () {
	var self = Container.call(this);
	var circle = self.attachAsset('tapCircle', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.5
	});
	self.showAt = function (x, y) {
		self.x = x;
		self.y = y;
		self.alpha = 0.5;
		self.scaleX = 1;
		self.scaleY = 1;
		tween(self, {
			alpha: 0,
			scaleX: 2,
			scaleY: 2
		}, {
			duration: 400,
			easing: tween.easeOut,
			onFinish: function onFinish() {
				self.destroy();
			}
		});
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x000000
});
/**** 
* Game Code
****/ 
// Puzzle 1 & 2: All unique VO assets
// Voice-over assets for "nothing here" phrases
// Audio assets (narration and sfx) are referenced by id, LK will load them automatically.
// No visual assets needed for MVP, but we will use a simple shape for tap feedback.
// --- State Management ---
// Voice-over sounds for narration
// Voice-over assets for all text displays
// Narration assets
// Voice-over sounds for puzzle elements
// Voice-over sounds for game states and feedback
// Narration music tracks
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
var STATE_HEADPHONES = 0;
var STATE_MENU = 1;
var STATE_HOW_TO_PLAY = 2;
var STATE_CREDITS = 3;
var STATE_INTRO = 4;
var STATE_PUZZLE1 = 5;
var STATE_PUZZLE1_SUCCESS = 6;
var STATE_PUZZLE2 = 7;
var STATE_PUZZLE2_SUCCESS = 8;
var STATE_PUZZLE3 = 9;
var STATE_PUZZLE3_SUCCESS = 10;
var STATE_PUZZLE4 = 11;
var STATE_PUZZLE4_SUCCESS = 12;
var STATE_PUZZLE5 = 13;
var STATE_PUZZLE5_SUCCESS = 14;
var currentState = STATE_HEADPHONES;
var headphonesIcon = null; // Store reference to headphones icon
// Used to prevent double-tap triggers
var inputLocked = false;
// Used to track narration/music
var currentNarration = null;
// Used to track current voice-over sound
var currentVoiceOver = null;
// Used for puzzle state
var puzzle1Step = 0; // 0 = waiting for first tap, 1 = waiting for second tap
// Double-tap tracking at game level
var lastTapTime = 0;
var lastTapX = 0;
var lastTapY = 0;
var doubleTapThreshold = 500; // milliseconds
var doubleTapDistanceThreshold = 50; // pixels
var currentlySelectedOption = null; // Track currently selected menu option
// --- Puzzle 1 element state ---
var puzzle1Elements = [];
var chestOpened = false;
var hasKey = false;
// --- Puzzle 2 element state ---
var puzzle2Elements = [];
var bothChestsOpened = false;
// --- Puzzle 2: Vial/Trap state ---
var hasVialOfClarity = false;
var trapActive = false;
// --- Puzzle 3 element state ---
var puzzle3Elements = [];
var puzzle3Gate = null;
var puzzle3FloorTile = null;
var puzzle3Rock = null;
// --- Puzzle 4 element state ---
var puzzle4Elements = [];
var correctChimeSequence = [];
var playerChimeSequence = [];
var puzzle4Complete = false;
var isIntroPlaying = false; // Track when intro VO is playing
var gameCompleteShown = false; // Track if game complete message is shown
// --- Annoying sound for trap logic ---
var annoyingTrapSound = null;
var annoyingTrapSoundInterval = null;
var debugLevelLinks = []; // Array to store debug level links
var tapCirclesVisible = false; // Global state for tapCircle visibility - starts hidden
var tapCircleToggle = null; // Reference to toggle button
function startAnnoyingTrapSound() {
	if (annoyingTrapSoundInterval) {
		return;
	} // Already running
	// Play the annoying sound as looping music
	LK.playMusic('sfx_trap_trigger', {
		loop: true
	});
}
function stopAnnoyingTrapSound() {
	// Stop the annoying trap music if playing
	LK.stopMusic();
	if (annoyingTrapSoundInterval) {
		LK.clearInterval(annoyingTrapSoundInterval);
		annoyingTrapSoundInterval = null;
	}
	annoyingTrapSound = null;
}
// --- GUI Text for minimal visual feedback (for sighted users) ---
var guiText = new Text2('', {
	size: 50,
	fill: 0xFFFFFF,
	wordWrap: true,
	wordWrapWidth: 1600,
	// Reduced from 1800 to provide more margin
	align: 'center' // Center align the text lines within the text object
});
guiText.anchor.set(0.5, 1.0);
LK.gui.bottom.addChild(guiText);
guiText.y = -50; // Offset from bottom edge
// GUI text should always be visible
guiText.visible = true;
// --- Helper Functions ---
function updateTapCircleVisibility() {
	// Update puzzle 1 elements
	if (puzzle1Elements) {
		for (var i = 0; i < puzzle1Elements.length; i++) {
			var el = puzzle1Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
	// Update puzzle 2 elements
	if (puzzle2Elements) {
		for (var i = 0; i < puzzle2Elements.length; i++) {
			var el = puzzle2Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
	// Update puzzle 3 elements
	if (puzzle3Elements) {
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
	// Update puzzle 4 and 5 elements
	if (puzzle4Elements) {
		for (var i = 0; i < puzzle4Elements.length; i++) {
			var el = puzzle4Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
}
function playNarration(id, options) {
	// Stop any current narration/music
	LK.stopMusic();
	currentNarration = id;
	LK.playMusic(id, options || {});
}
function stopNarration() {
	LK.stopMusic();
	currentNarration = null;
}
function setGuiText(txt, voiceOverId) {
	guiText.setText(txt);
	// Add a subtle scale animation when text updates
	guiText.scaleX = 1.1;
	guiText.scaleY = 1.1;
	tween(guiText, {
		scaleX: 1,
		scaleY: 1
	}, {
		duration: 300,
		easing: tween.easeOut
	});
	// Stop current voice-over if playing
	if (currentVoiceOver) {
		currentVoiceOver.stop();
		currentVoiceOver = null;
	}
	// Play voice-over if provided, but delay if a SFX is currently playing
	if (voiceOverId) {
		// Check if a SFX was just played (by convention, SFX are played right before setGuiText)
		// We'll check if any SFX is currently playing and delay VO until it finishes
		var sfxIds = ['sfx_door_unlock', 'sfx_success', 'sfx_tap'];
		var maxSfxDuration = 0;
		for (var i = 0; i < sfxIds.length; i++) {
			var sfx = LK.getSound(sfxIds[i]);
			if (sfx && sfx.isPlaying && typeof sfx.duration === "number") {
				if (sfx.duration > maxSfxDuration) {
					maxSfxDuration = sfx.duration;
				}
			}
		}
		// If any SFX is playing, delay VO by the max duration (or a minimum of 400ms)
		if (maxSfxDuration > 0) {
			LK.setTimeout(function () {
				currentVoiceOver = LK.getSound(voiceOverId);
				currentVoiceOver.play();
			}, Math.max(400, maxSfxDuration * 1000));
		} else {
			currentVoiceOver = LK.getSound(voiceOverId);
			currentVoiceOver.play();
		}
	}
}
function lockInput(ms) {
	inputLocked = true;
	LK.setTimeout(function () {
		inputLocked = false;
	}, ms || 600);
}
// Check if both chests are opened in puzzle 2
function playChimeSequence() {
	setGuiText("Listen to the sequence...", 'vo_puzzle4_sequence_play');
	var index = 0;
	var _playNextChime = function playNextChime() {
		if (index < correctChimeSequence.length) {
			var chimeIndex = correctChimeSequence[index];
			var chime = puzzle4Elements[chimeIndex];
			if (chime && chime.chimeSound) {
				chime.chimeSound.play();
				// Visual feedback
				tween(chime, {
					scaleX: 1.2,
					scaleY: 1.2
				}, {
					duration: 300,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(chime, {
							scaleX: 1,
							scaleY: 1
						}, {
							duration: 300,
							easing: tween.easeIn
						});
					}
				});
			}
			index++;
			LK.setTimeout(_playNextChime, 800);
		} else {
			// Sequence complete, player can now interact
			setGuiText("The echoes guide you. Tap once to listen, \ntwice to step forward.", 'vo_repeat_sequence');
		}
	};
	_playNextChime();
}
function checkChimeSequence() {
	var correct = true;
	var expectedLength = correctChimeSequence.length;
	for (var i = 0; i < expectedLength; i++) {
		if (correctChimeSequence[i] !== playerChimeSequence[i]) {
			correct = false;
			break;
		}
	}
	if (correct) {
		puzzle4Complete = true;
		LK.getSound('sfx_success').play();
		if (currentState === STATE_PUZZLE4) {
			goToState(STATE_PUZZLE4_SUCCESS);
		} else if (currentState === STATE_PUZZLE5) {
			goToState(STATE_PUZZLE5_SUCCESS);
		}
	} else {
		// Wrong sequence behavior depends on current puzzle
		if (currentState === STATE_PUZZLE4) {
			// For simple puzzle 4, just reset and let them try again
			playerChimeSequence = [];
			// Reset all chimes
			for (var i = 0; i < puzzle4Elements.length; i++) {
				if (puzzle4Elements[i]) {
					puzzle4Elements[i].isCommitted = false;
				}
			}
			setGuiText("Not quite right. Listen again...\nRemember: from lowest tone to highest.", 'vo_puzzle4_simple_retry');
		} else if (currentState === STATE_PUZZLE5) {
			// Wrong sequence - just reset and let them try again
			playerChimeSequence = [];
			// Reset all chimes
			for (var i = 0; i < puzzle4Elements.length; i++) {
				if (puzzle4Elements[i]) {
					puzzle4Elements[i].isCommitted = false;
				}
			}
			setGuiText("The sequence is incorrect. The chimes await your touch again...\nListen carefully and try once more.", 'vo_puzzle5_wrong');
			// Play the sequence again after a short delay (just the sounds, no instruction VO)
			LK.setTimeout(function () {
				// Play only the chime sequence sounds without any voice-over
				var index = 0;
				var _playNextChime = function playNextChime() {
					if (index < correctChimeSequence.length) {
						var chimeIndex = correctChimeSequence[index];
						var chime = puzzle4Elements[chimeIndex];
						if (chime && chime.chimeSound) {
							chime.chimeSound.play();
							// Visual feedback
							tween(chime, {
								scaleX: 1.2,
								scaleY: 1.2
							}, {
								duration: 300,
								easing: tween.easeOut,
								onFinish: function onFinish() {
									tween(chime, {
										scaleX: 1,
										scaleY: 1
									}, {
										duration: 300,
										easing: tween.easeIn
									});
								}
							});
						}
						index++;
						LK.setTimeout(_playNextChime, 800);
					}
				};
				_playNextChime();
			}, 8000); // Wait 2 seconds after wrong sequence message
		}
	}
}
function checkPuzzle2Progress() {
	var openedCount = 0;
	for (var i = 0; i < puzzle2Elements.length; i++) {
		if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
			openedCount++;
		}
	}
	if (openedCount >= 2 && !bothChestsOpened) {
		bothChestsOpened = true;
		// No separate message for the door noise; handled in Chest logic after opening the second chest.
	}
}
// --- State Transitions ---
function goToState(state) {
	// Clear existing debug level links first
	if (debugLevelLinks.length > 0) {
		for (var i = 0; i < debugLevelLinks.length; i++) {
			if (debugLevelLinks[i] && typeof debugLevelLinks[i].destroy === 'function') {
				debugLevelLinks[i].destroy();
			}
		}
		debugLevelLinks = [];
	}
	stopNarration();
	// Clear selected menu option
	currentlySelectedOption = null;
	// Stop all playing sounds when restarting game
	if (state === STATE_HEADPHONES) {
		// Stop any current voice-over
		if (currentVoiceOver) {
			currentVoiceOver.stop();
			currentVoiceOver = null;
		}
		// Stop any trap sounds
		stopAnnoyingTrapSound();
		// Reset game complete flag
		gameCompleteShown = false;
	}
	// Remove headphones icon if it exists
	if (headphonesIcon) {
		// Clean up extra elements (like text below icon)
		if (headphonesIcon.extraElements) {
			for (var i = 0; i < headphonesIcon.extraElements.length; i++) {
				if (headphonesIcon.extraElements[i]) {
					headphonesIcon.extraElements[i].destroy();
				}
			}
		}
		headphonesIcon.destroy();
		headphonesIcon = null;
	}
	// Remove toggle button if it exists
	if (tapCircleToggle) {
		tapCircleToggle.destroy();
		tapCircleToggle = null;
	}
	// Clear all game children (including menu options and title) when transitioning states
	for (var i = game.children.length - 1; i >= 0; i--) {
		game.children[i].destroy();
	}
	// Clear all GUI elements
	LK.gui.top.removeChildren();
	LK.gui.bottom.removeChildren();
	// Re-add the guiText to bottom after clearing
	LK.gui.bottom.addChild(guiText);
	currentState = state;
	if (state === STATE_HEADPHONES) {
		setGuiText("(Double tap to continue)");
		// Voice-over is already handled by playNarration
		playNarration('screen_headphones_voice', {
			loop: false
		});
		// Add headphones icon
		headphonesIcon = LK.getAsset('headphonesIcon', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: 2048 / 2,
			y: 2732 / 2 - 200 // Move icon up to make room for text below
		});
		game.addChild(headphonesIcon);
		// Add text below headphones icon
		var headphonesText = new Text2("Use headphones for a better experience", {
			size: 100,
			fill: 0xFFFFFF,
			align: 'center',
			wordWrap: true,
			wordWrapWidth: 1600
		});
		headphonesText.anchor.set(0.5, 0.5);
		headphonesText.x = 2048 / 2;
		headphonesText.y = 2732 / 2 + 400; // Position further below the icon
		game.addChild(headphonesText);
		// Store reference to clean up later
		if (!headphonesIcon.extraElements) {
			headphonesIcon.extraElements = [];
		}
		headphonesIcon.extraElements.push(headphonesText);
		// Add debug links for development - COMMENTED OUT FOR PRODUCTION
		/* 
		var levelsForDebug = [{
			name: "Menu",
			state: STATE_MENU
		}, {
			name: "How to Play",
			state: STATE_HOW_TO_PLAY
		}, {
			name: "Credits",
			state: STATE_CREDITS
		}, {
			name: "Intro",
			state: STATE_INTRO
		}, {
			name: "Puzzle 1",
			state: STATE_PUZZLE1
		}, {
			name: "P1 Success",
			state: STATE_PUZZLE1_SUCCESS
		}, {
			name: "Puzzle 2",
			state: STATE_PUZZLE2
		}, {
			name: "P2 Success",
			state: STATE_PUZZLE2_SUCCESS
		}, {
			name: "Puzzle 3",
			state: STATE_PUZZLE3
		}, {
			name: "P3 Success",
			state: STATE_PUZZLE3_SUCCESS
		}, {
			name: "Puzzle 4",
			state: STATE_PUZZLE4
		}, {
			name: "P4 Success",
			state: STATE_PUZZLE4_SUCCESS
		}, {
			name: "Puzzle 5",
			state: STATE_PUZZLE5
		}, {
			name: "P5 Success",
			state: STATE_PUZZLE5_SUCCESS
		}];
		var startY = 120; // Start below the 100px top-left reserved area
		var spacingY = 55;
		for (var i = 0; i < levelsForDebug.length; i++) {
			var levelData = levelsForDebug[i];
			var linkText = new Text2(levelData.name, {
				size: 38,
				fill: 0xFFFF00,
				// Bright yellow for debug
				align: 'left',
				wordWrap: true,
				wordWrapWidth: 300 // Ensure debug links don't overflow
			});
			linkText.anchor.set(0, 0); // Anchor top-left
			linkText.x = 20; // Small offset from left edge
			linkText.y = startY + i * spacingY;
			linkText.interactive = true;
			linkText.buttonMode = true; // Shows hand cursor on desktop
			// IIFE to correctly capture levelData.state in the closure
			(function (targetState) {
				linkText.down = function () {
					// Stop any ongoing VO from the headphone screen before jumping
					if (currentVoiceOver) {
						currentVoiceOver.stop();
						currentVoiceOver = null;
					}
					goToState(targetState);
				};
			})(levelData.state);
			LK.gui.top.addChild(linkText); // Add to gui.top so it's above game content
			debugLevelLinks.push(linkText); // Store for cleanup
		}
		*/
	} else if (state === STATE_MENU) {
		setGuiText("", 'vo_menu_title'); // This ensures the menu title voice-over is played, but no text on bottom GUI
		// No narration for menu, or could add a short one if desired
		// Add "Main Menu" title text
		var menuTitle = new Text2("Main Screen", {
			size: 120,
			fill: 0xFFFFFF,
			align: 'center'
		});
		menuTitle.anchor.set(0.5, 0);
		menuTitle.interactive = true;
		menuTitle.buttonMode = true;
		// Add voice-over functionality to title
		menuTitle.lastTapTime = 0;
		menuTitle.pendingVOTimeout = null;
		menuTitle.justDoubleTapped = false;
		menuTitle.justDoubleTappedTimeout = null;
		menuTitle.down = function (x, y, obj) {
			var now = Date.now();
			var timeSinceLastTap = now - menuTitle.lastTapTime;
			// Single tap to announce title
			if (timeSinceLastTap > 500 || timeSinceLastTap < 50) {
				// Cancel any existing pending voice-over
				if (menuTitle.pendingVOTimeout) {
					LK.clearTimeout(menuTitle.pendingVOTimeout);
					menuTitle.pendingVOTimeout = null;
				}
				// Stop any current voice-over
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay the announce to check if it's actually a double-tap
				menuTitle.pendingVOTimeout = LK.setTimeout(function () {
					setGuiText("Main Menu\n\nChoose an option below", 'vo_menu_title_announce');
					menuTitle.pendingVOTimeout = null;
				}, 300);
				menuTitle.lastTapTime = now;
			}
		};
		LK.gui.top.addChild(menuTitle);
		menuTitle.y = 200; // Move title lower on the screen
		// Add menu options
		var menuOptions = [{
			text: "• Intro",
			label: "Intro",
			description: "Start the adventure story",
			voiceOverId: "vo_menu_intro",
			action: function action() {
				goToState(STATE_INTRO);
			}
		}, {
			text: "• How to Play",
			label: "How to Play",
			description: "Learn the game controls",
			voiceOverId: "vo_menu_how_to_play",
			action: function action() {
				goToState(STATE_HOW_TO_PLAY);
			}
		}, {
			text: "• Credits",
			label: "Credits",
			description: "View game credits",
			voiceOverId: "vo_menu_credits",
			action: function action() {
				goToState(STATE_CREDITS);
			}
		}];
		var spacingY = 250; // Increased spacing between options
		// Calculate vertical center position for menu items
		var totalMenuHeight = (menuOptions.length - 1) * spacingY;
		var startY = 2732 / 2 - totalMenuHeight / 2; // Center vertically on screen
		for (var i = 0; i < menuOptions.length; i++) {
			var option = menuOptions[i];
			var optionText = new Text2(option.text, {
				size: 120,
				fill: 0xFFFFFF,
				align: 'center',
				wordWrap: true,
				wordWrapWidth: 1600
			});
			optionText.anchor.set(0.5, 0.5);
			optionText.x = 2048 / 2;
			optionText.y = startY + i * spacingY;
			optionText.interactive = true;
			optionText.buttonMode = true;
			// Add double-tap functionality like InteractiveElement
			optionText.lastTapTime = 0;
			optionText.tapCount = 0;
			optionText.pendingVOTimeout = null;
			optionText.justDoubleTapped = false;
			optionText.justDoubleTappedTimeout = null;
			optionText.isSelected = false;
			optionText.isSelected = false;
			// Store the action function and option data for each option
			(function (optionAction, optionData) {
				optionText.down = function () {
					var now = Date.now();
					var timeSinceLastTap = now - optionText.lastTapTime;
					// If within double-tap window
					if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
						// This is a double tap - execute action only if this option is selected
						if (optionText.isSelected && currentlySelectedOption === optionText) {
							// Cancel any pending single-tap voice-over
							if (optionText.pendingVOTimeout) {
								LK.clearTimeout(optionText.pendingVOTimeout);
								optionText.pendingVOTimeout = null;
							}
							// Stop any current voice-over before executing
							if (currentVoiceOver) {
								currentVoiceOver.stop();
								currentVoiceOver = null;
							}
							optionAction();
							optionText.lastTapTime = 0; // Reset to prevent triple taps
							// Cooldown after double tap
							if (optionText.justDoubleTappedTimeout) {
								LK.clearTimeout(optionText.justDoubleTappedTimeout);
							}
							optionText.justDoubleTapped = true;
							optionText.justDoubleTappedTimeout = LK.setTimeout(function () {
								optionText.justDoubleTapped = false;
								optionText.justDoubleTappedTimeout = null;
							}, 700); // 700ms cooldown
							// Play interaction sound
							LK.getSound('sfx_tap').play();
						} else {
							// Double tap on unselected option - treat as single tap to select it
							optionText.lastTapTime = now;
						}
					} else {
						// This is a single tap - announce and show selection
						// If a double tap just occurred, suppress immediate single tap action
						if (optionText.justDoubleTapped) {
							optionText.lastTapTime = now;
						} else {
							// Clear any existing pending voice-over
							if (optionText.pendingVOTimeout) {
								LK.clearTimeout(optionText.pendingVOTimeout);
								optionText.pendingVOTimeout = null;
							}
							// Stop any current voice-over
							if (currentVoiceOver) {
								currentVoiceOver.stop();
								currentVoiceOver = null;
							}
							// Mark as selected and update appearance
							optionText.isSelected = true;
							currentlySelectedOption = optionText;
							// Animate to yellow highlight
							tween(optionText, {
								fill: 0xFFFF00
							}, {
								duration: 200,
								easing: tween.easeOut
							});
							// Clear selection from other options
							for (var j = 0; j < menuOptions.length; j++) {
								if (j !== i && game.children[j + 1]) {
									// +1 to account for menu title
									var otherOption = game.children[j + 1];
									if (otherOption !== optionText && otherOption.isSelected) {
										otherOption.isSelected = false;
										// Animate back to white
										tween(otherOption, {
											fill: 0xFFFFFF
										}, {
											duration: 200,
											easing: tween.easeOut
										});
									}
								}
							}
							// Delay the announce to check if it's actually a double-tap
							optionText.pendingVOTimeout = LK.setTimeout(function () {
								var announceText = optionData.label + " - " + optionData.description + "\n\n(Double tap to select)";
								// Use the voice-over ID from the option data
								setGuiText(announceText, optionData.voiceOverId);
								optionText.pendingVOTimeout = null;
							}, 300); // Wait 300ms to see if second tap comes
							optionText.lastTapTime = now;
						}
					}
				};
			})(option.action, option);
			game.addChild(optionText);
		}
	} else if (state === STATE_HOW_TO_PLAY) {
		setGuiText("How to Play\n\nThis is an audio-first adventure game.\n\nSingle tap to explore and hear descriptions.\nDouble tap to interact with objects.\nUse headphones for the best experience.\n\nThere's also a mysterious eye icon at the top...\nIt reveals where echoes hide, but true adventurers\ntrust their ears, not their eyes!\n\n(Double tap to return to menu)", 'vo_how_to_play_content');
	} else if (state === STATE_CREDITS) {
		setGuiText("Credits\n\nI am Juan and I wanted to create an \ninteractive audio story for the visually impaired first.\n\nDeveloped with accessibility in mind.\nThank you for playing!\n\n(Double tap to return to menu)", 'vo_credits_content');
	} else if (state === STATE_INTRO) {
		// Create a dedicated text object for the intro story with proper wrapping
		var introText = new Text2("Long ago, beneath the roots of the ancient world, a vault was sealed away—its halls forgotten, its treasures buried in silence. Many have tried to claim its secrets, but none have returned.\n\nYou, a Seeker of Echoes, are drawn by the voice of the Vault itself—a whisper only you can hear. It calls to you through stone and shadow, promising answers... if you can find the path.\n\nArmed with only your senses and your will, you step into the maze below...\n\n(Double tap to continue)", {
			size: 70,
			fill: 0xFFFFFF,
			wordWrap: true,
			wordWrapWidth: 1800,
			align: 'center'
		});
		introText.anchor.set(0.5, 0.5);
		introText.x = 2048 / 2;
		introText.y = 2732 / 2;
		game.addChild(introText);
		// Also set GUI text for voice-over
		setGuiText("", 'vo_loading_story');
		playNarration('narr_intro');
		// Wait for tap to continue instead of auto-advancing
	} else if (state === STATE_PUZZLE1) {
		isIntroPlaying = true;
		setGuiText("Darkness surrounds you.\nTap to feel your way forward.\nDouble tap when something stirs... it may be more than stone.", 'vo_dungeon_intro');
		playNarration('narr_puzzle1');
		// Clear intro playing flag after intro VO completes
		var introVO = LK.getSound('vo_dungeon_intro');
		var introDuration = 8000; // Estimated duration for intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
		}, introDuration);
		// Start dungeon background sounds
		LK.playMusic('dungeon_background_sounds');
		// Only create elements if they don't exist yet
		if (typeof puzzle1Elements === "undefined" || puzzle1Elements.length === 0) {
			puzzle1Step = 0;
			puzzle1Elements = [];
			chestOpened = false;
			hasKey = false;
			// Random positions for puzzle 1 elements
			var positions = [];
			// Generate random positions ensuring they don't overlap and are within bounds
			for (var p = 0; p < 2; p++) {
				var validPosition = false;
				var attempts = 0;
				while (!validPosition && attempts < 50) {
					var newX = 200 + Math.random() * (2048 - 400); // Keep away from edges
					var newY = 400 + Math.random() * (2732 - 800); // Keep away from edges and top menu
					// Check if position is far enough from existing positions
					var tooClose = false;
					for (var existingPos = 0; existingPos < positions.length; existingPos++) {
						var dx = newX - positions[existingPos].x;
						var dy = newY - positions[existingPos].y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						if (distance < 300) {
							// Minimum distance between elements
							tooClose = true;
							break;
						}
					}
					if (!tooClose) {
						positions.push({
							x: newX,
							y: newY
						});
						validPosition = true;
					}
					attempts++;
				}
				// Fallback if no valid position found after attempts
				if (!validPosition) {
					positions.push({
						x: 400 + p * 600,
						y: 800 + p * 400
					});
				}
			}
			// Key
			var key = new Key();
			key.x = positions[0].x;
			key.y = positions[0].y;
			game.addChild(key);
			puzzle1Elements.push(key);
			// Door
			var door = new Door();
			door.x = positions[1].x;
			door.y = positions[1].y;
			game.addChild(door);
			puzzle1Elements.push(door);
		}
	} else if (state === STATE_PUZZLE1_SUCCESS) {
		// Remove puzzle elements
		if (typeof puzzle1Elements !== "undefined") {
			for (var i = 0; i < puzzle1Elements.length; i++) {
				puzzle1Elements[i].destroy();
			}
		}
	} else if (state === STATE_PUZZLE2) {
		isIntroPlaying = true;
		setGuiText("A quiet chamber. \nFaint scents and the weight of stone surround you.", 'vo_puzzle2_intro');
		// Clear intro playing flag after intro VO completes
		var introVO = LK.getSound('vo_puzzle2_intro');
		var introDuration = 5000; // Estimated duration for intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
		}, introDuration);
		// Start dungeon background sounds
		LK.playMusic('dungeon_background_sounds');
		// Clear previous puzzle elements
		if (puzzle2Elements.length > 0) {
			for (var i = 0; i < puzzle2Elements.length; i++) {
				puzzle2Elements[i].destroy();
			}
		}
		puzzle2Elements = [];
		bothChestsOpened = false;
		hasVialOfClarity = false;
		trapActive = false;
		// Generate random positions for 2 chests and 1 door
		var positions = [];
		for (var p = 0; p < 3; p++) {
			var validPosition = false;
			var attempts = 0;
			while (!validPosition && attempts < 50) {
				var newX = 300 + Math.random() * (2048 - 600);
				var newY = 500 + Math.random() * (2732 - 1000);
				var tooClose = false;
				for (var existingPos = 0; existingPos < positions.length; existingPos++) {
					var dx = newX - positions[existingPos].x;
					var dy = newY - positions[existingPos].y;
					var distance = Math.sqrt(dx * dx + dy * dy);
					if (distance < 400) {
						tooClose = true;
						break;
					}
				}
				if (!tooClose) {
					positions.push({
						x: newX,
						y: newY
					});
					validPosition = true;
				}
				attempts++;
			}
			if (!validPosition) {
				positions.push({
					x: 400 + p * 500,
					y: 900 + p * 300
				});
			}
		}
		// Randomly assign which chest is Vial and which is Trap
		var vialChestIndex = Math.floor(Math.random() * 2);
		var trapChestIndex = 1 - vialChestIndex;
		// Create chests
		for (var c = 0; c < 2; c++) {
			var chest = new Chest();
			var isVial = c === vialChestIndex;
			var isTrap = c === trapChestIndex;
			chest.setup(c, isVial, isTrap);
			chest.x = positions[c].x;
			chest.y = positions[c].y;
			game.addChild(chest);
			puzzle2Elements.push(chest);
		}
		// Create stone door (always visible)
		var stoneDoor = new StoneDoor();
		stoneDoor.x = positions[2].x;
		stoneDoor.y = positions[2].y;
		stoneDoor.visible = true; // Always visible
		game.addChild(stoneDoor);
		puzzle2Elements.push(stoneDoor);
	} else if (state === STATE_PUZZLE2_SUCCESS) {
		// Clean up puzzle 2 elements
		if (puzzle2Elements.length > 0) {
			for (var i = 0; i < puzzle2Elements.length; i++) {
				puzzle2Elements[i].destroy();
			}
			puzzle2Elements = [];
		}
		// Stop annoying trap sound if running
		stopAnnoyingTrapSound();
		// Individual success narration for puzzle2
		setGuiText("A deep rumble. The door yields, revealing the unknown beyond.\n\n(Double tap to continue)", 'vo_puzzle2_success');
	} else if (state === STATE_PUZZLE3) {
		isIntroPlaying = true;
		setGuiText("Another chamber. \nYou sense a gate ahead, but something blocks it.", 'vo_puzzle3_intro');
		playNarration('vo_puzzle3_intro', {
			loop: false
		});
		// Clear intro playing flag after intro VO completes
		var introVO = LK.getSound('vo_puzzle3_intro');
		var introDuration = 5000; // Estimated duration for intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
		}, introDuration);
		// Start dungeon background sounds
		LK.playMusic('dungeon_background_sounds');
		// Don't clear puzzle 3 elements, just remove them from display if needed
		if (puzzle3Elements.length > 0) {
			for (var i = 0; i < puzzle3Elements.length; i++) {
				if (puzzle3Elements[i].parent) {
					puzzle3Elements[i].parent.removeChild(puzzle3Elements[i]);
				}
			}
		}
		// Only generate positions if elements don't exist yet
		if (puzzle3Elements.length === 0) {
			// Generate positions for puzzle 3 elements
			var positions = [];
			for (var p = 0; p < 3; p++) {
				var validPosition = false;
				var attempts = 0;
				while (!validPosition && attempts < 50) {
					var newX = 300 + Math.random() * (2048 - 600);
					var newY = 500 + Math.random() * (2732 - 1000);
					var tooClose = false;
					for (var existingPos = 0; existingPos < positions.length; existingPos++) {
						var dx = newX - positions[existingPos].x;
						var dy = newY - positions[existingPos].y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						if (distance < 400) {
							tooClose = true;
							break;
						}
					}
					if (!tooClose) {
						positions.push({
							x: newX,
							y: newY
						});
						validPosition = true;
					}
					attempts++;
				}
				if (!validPosition) {
					positions.push({
						x: 400 + p * 500,
						y: 900 + p * 300
					});
				}
			}
			// Create gate
			puzzle3Gate = new Gate();
			puzzle3Gate.x = positions[0].x;
			puzzle3Gate.y = positions[0].y;
			game.addChild(puzzle3Gate);
			puzzle3Elements.push(puzzle3Gate);
			// Create floor tile
			puzzle3FloorTile = new FloorTile();
			puzzle3FloorTile.x = positions[1].x;
			puzzle3FloorTile.y = positions[1].y;
			puzzle3FloorTile.linkedGate = puzzle3Gate;
			game.addChild(puzzle3FloorTile);
			puzzle3Elements.push(puzzle3FloorTile);
			// Create heavy rock
			puzzle3Rock = new HeavyRock();
			puzzle3Rock.x = positions[2].x;
			puzzle3Rock.y = positions[2].y;
			// Store initial position to prevent unwanted position changes
			puzzle3Rock.initialX = positions[2].x;
			puzzle3Rock.lastX = positions[2].x; // Initialize lastX
			puzzle3Rock.initialY = positions[2].y;
			puzzle3Rock.lastY = positions[2].y; // Initialize lastY
			game.addChild(puzzle3Rock);
			puzzle3Elements.push(puzzle3Rock);
		} else {
			// Re-add existing elements to the game
			for (var i = 0; i < puzzle3Elements.length; i++) {
				game.addChild(puzzle3Elements[i]);
			}
		}
	} else if (state === STATE_PUZZLE3_SUCCESS) {
		// Clean up puzzle 3 elements
		if (puzzle3Elements.length > 0) {
			for (var i = 0; i < puzzle3Elements.length; i++) {
				puzzle3Elements[i].destroy();
			}
			puzzle3Elements = [];
		}
		setGuiText("The way ahead lies open. You leave the chamber behind...\n\n(Double tap to continue)", 'vo_puzzle3_success');
	} else if (state === STATE_PUZZLE4) {
		isIntroPlaying = true;
		setGuiText("You enter a chamber of whispered melodies.\nThree chimes hang before you, each with its own voice.\nListen to their songs... from the deepest tone to the highest.\nA simple harmony awaits.", 'vo_puzzle4_simple_intro');
		// Clear previous puzzle elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
		}
		puzzle4Elements = [];
		correctChimeSequence = [0, 1, 2]; // Simple sequence: low to high pitch
		playerChimeSequence = [];
		puzzle4Complete = false;
		// Create 3 chimes arranged horizontally
		var chimeWidth = 2048 / 3;
		for (var i = 0; i < 3; i++) {
			var chime = new Chime();
			chime.setup(i);
			chime.x = chimeWidth * i + chimeWidth / 2;
			chime.y = 2732 / 2;
			// Assign chime sounds - use chimes 1, 2, 3 for low to high progression
			chime.chimeSound = LK.getSound('sfx_chime_' + (i + 1));
			game.addChild(chime);
			puzzle4Elements.push(chime);
		}
		// Wait for puzzle 4 intro VO to complete before showing "The echoes guide you"
		var puzzle4IntroVO = LK.getSound('vo_puzzle4_simple_intro');
		var introDuration = 14000; // Estimated duration for the intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
			setGuiText("The echoes guide you. Tap once to listen, \ntwice to step forward.", 'vo_repeat_sequence');
		}, introDuration);
	} else if (state === STATE_PUZZLE4_SUCCESS) {
		// Clean up puzzle 4 elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
			puzzle4Elements = [];
		}
		setGuiText("The gentle harmony resonates perfectly.\nYou've learned the language of the chimes.\nA passage opens to deeper mysteries.\n\n(Double tap to continue)", 'vo_puzzle4_simple_success');
	} else if (state === STATE_PUZZLE5) {
		isIntroPlaying = true;
		setGuiText("The room hums with delicate tension.\n Chimes sway in the unseen air.\n Some call to you gently, others pulse with danger. \n Listen carefully...", 'vo_puzzle5_intro');
		// Clear previous puzzle elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
		}
		puzzle4Elements = [];
		correctChimeSequence = [];
		playerChimeSequence = [];
		puzzle4Complete = false;
		// Create 4 chimes arranged horizontally
		var chimeWidth = 2048 / 4;
		for (var i = 0; i < 4; i++) {
			var chime = new Chime();
			chime.setup(i);
			chime.x = chimeWidth * i + chimeWidth / 2;
			chime.y = 2732 / 2;
			// Assign chime sounds
			chime.chimeSound = LK.getSound('sfx_chime_' + (i + 1));
			game.addChild(chime);
			puzzle4Elements.push(chime);
		}
		// Generate unique random sequence
		var availableChimes = [0, 1, 2, 3];
		for (var i = 0; i < 4; i++) {
			var randomIndex = Math.floor(Math.random() * availableChimes.length);
			var selectedChime = availableChimes.splice(randomIndex, 1)[0];
			correctChimeSequence.push(selectedChime);
		}
		// Wait for puzzle 5 intro VO to complete before playing chime sequence
		var puzzle5IntroVO = LK.getSound('vo_puzzle5_intro');
		var introDuration = 11000; // Estimated duration, adjust based on actual VO length
		LK.setTimeout(function () {
			isIntroPlaying = false;
			playChimeSequence();
		}, introDuration);
	} else if (state === STATE_PUZZLE5_SUCCESS) {
		// Clean up puzzle 5 elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
			puzzle4Elements = [];
		}
		setGuiText("Harmony at last. The chimes fall silent \n and a distant door creaks open.\n\n(Double tap to continue)", 'vo_puzzle5_success');
	}
	lockInput(800);
	// Create toggle button for tapCircle visibility (only when there are interactive elements)
	var hasInteractiveElements = state === STATE_PUZZLE1 && puzzle1Elements.length > 0 || state === STATE_PUZZLE2 && puzzle2Elements.length > 0 || state === STATE_PUZZLE3 && puzzle3Elements.length > 0 || state === STATE_PUZZLE4 && puzzle4Elements.length > 0 || state === STATE_PUZZLE5 && puzzle4Elements.length > 0;
	if (state !== STATE_HEADPHONES && hasInteractiveElements) {
		tapCircleToggle = new Container();
		var eyeIcon = tapCircleToggle.attachAsset(tapCirclesVisible ? 'eyeOpen' : 'eyeClosed', {
			anchorX: 0.5,
			anchorY: 0.5
		});
		tapCircleToggle.interactive = true;
		tapCircleToggle.buttonMode = true;
		// Add double-tap tracking properties
		tapCircleToggle.lastTapTime = 0;
		tapCircleToggle.pendingVOTimeout = null;
		tapCircleToggle.down = function () {
			var now = Date.now();
			var timeSinceLastTap = now - tapCircleToggle.lastTapTime;
			// Check if this is a double tap
			if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
				// This is a double tap - toggle visibility
				// Cancel any pending single-tap voice-over
				if (tapCircleToggle.pendingVOTimeout) {
					LK.clearTimeout(tapCircleToggle.pendingVOTimeout);
					tapCircleToggle.pendingVOTimeout = null;
				}
				// Stop any current voice-over
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				tapCirclesVisible = !tapCirclesVisible;
				// Update the eye icon
				tapCircleToggle.removeChildren();
				var newEyeIcon = tapCircleToggle.attachAsset(tapCirclesVisible ? 'eyeOpen' : 'eyeClosed', {
					anchorX: 0.5,
					anchorY: 0.5
				});
				// Update visibility of all tapCircles only
				updateTapCircleVisibility();
				tapCircleToggle.lastTapTime = 0; // Reset to prevent triple taps
			} else {
				// This is a single tap - show hint message
				// Cancel any existing pending voice-over
				if (tapCircleToggle.pendingVOTimeout) {
					LK.clearTimeout(tapCircleToggle.pendingVOTimeout);
					tapCircleToggle.pendingVOTimeout = null;
				}
				// Stop any current voice-over
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay the announce to check if it's actually a double-tap
				tapCircleToggle.pendingVOTimeout = LK.setTimeout(function () {
					setGuiText("Psst... the hint icon shows the secrets.\nReal explorers never touch it.", 'vo_hint_icon_message');
					tapCircleToggle.pendingVOTimeout = null;
				}, 300); // Wait 300ms to see if second tap comes
				tapCircleToggle.lastTapTime = now;
			}
		};
		LK.gui.top.addChild(tapCircleToggle);
		tapCircleToggle.y = 100; // Move toggle button 100 pixels down
	}
}
// --- Puzzle 1 Logic ---
// For MVP: Simple puzzle, e.g. "Tap twice to continue"
function handlePuzzle1Tap() {
	if (puzzle1Step === 0) {
		// First tap
		puzzle1Step = 1;
		LK.getSound('sfx_tap').play();
		// Optionally, play a short instruction or feedback
		setGuiText("Good! Tap again to solve the puzzle.");
		lockInput(600);
	} else if (puzzle1Step === 1) {
		// Second tap, puzzle solved
		LK.getSound('sfx_success').play();
		goToState(STATE_PUZZLE1_SUCCESS);
	}
}
// --- Input Handling ---
// Visual tap feedback for sighted users
function showTapFeedback(x, y) {
	var tapFx = new TapFeedback();
	tapFx.showAt(x, y);
	game.addChild(tapFx);
}
// Main tap handler
game.down = function (x, y, obj) {
	if (inputLocked || isIntroPlaying) {
		return;
	}
	// Check if this is a double-tap at game level
	var now = Date.now();
	var timeSinceLastTap = now - lastTapTime;
	var tapDistance = Math.sqrt((x - lastTapX) * (x - lastTapX) + (y - lastTapY) * (y - lastTapY));
	var isDoubleTap = timeSinceLastTap < doubleTapThreshold && timeSinceLastTap > 50 && tapDistance < doubleTapDistanceThreshold;
	// First check if tap is on an interactive element
	var tappedElement = null;
	var tapOnElement = false;
	// Check puzzle 1 elements
	if (currentState === STATE_PUZZLE1 && typeof puzzle1Elements !== "undefined") {
		for (var i = 0; i < puzzle1Elements.length; i++) {
			var el = puzzle1Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				tappedElement = el;
				tapOnElement = true;
				break;
			}
		}
	}
	// Check puzzle 2 elements
	if (currentState === STATE_PUZZLE2 && typeof puzzle2Elements !== "undefined") {
		for (var i = 0; i < puzzle2Elements.length; i++) {
			var el = puzzle2Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				tappedElement = el;
				tapOnElement = true;
				break;
			}
		}
	}
	// Check puzzle 3 elements
	if (currentState === STATE_PUZZLE3 && typeof puzzle3Elements !== "undefined") {
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				tappedElement = el;
				tapOnElement = true;
				break;
			}
		}
	}
	// If tapping on an element, let the element handle voice-over management
	// Only stop voice-over if NOT tapping on an element, or if it's a single tap on non-interactive area
	if (currentVoiceOver && !tapOnElement) {
		currentVoiceOver.stop();
		currentVoiceOver = null;
	}
	// Update last tap info
	lastTapTime = now;
	lastTapX = x;
	lastTapY = y;
	showTapFeedback(x, y);
	if (currentState === STATE_HEADPHONES) {
		if (isDoubleTap) {
			goToState(STATE_MENU);
		}
	} else if (currentState === STATE_MENU) {
		// Menu taps are handled by individual menu option elements
	} else if (currentState === STATE_HOW_TO_PLAY) {
		if (isDoubleTap) {
			goToState(STATE_MENU);
		}
	} else if (currentState === STATE_CREDITS) {
		if (isDoubleTap) {
			goToState(STATE_MENU);
		}
	} else if (currentState === STATE_INTRO) {
		// Double tap to continue from intro to puzzle
		if (isDoubleTap) {
			goToState(STATE_PUZZLE1);
		}
	} else if (currentState === STATE_PUZZLE1) {
		// Route tap to nearest element if within range, else generic feedback
		var tapped = false;
		if (typeof puzzle1Elements !== "undefined") {
			for (var i = 0; i < puzzle1Elements.length; i++) {
				var el = puzzle1Elements[i];
				var dx = x - el.x;
				var dy = y - el.y;
				var dist = Math.sqrt(dx * dx + dy * dy);
				if (dist < el.hitRadius) {
					// within tapCircle radius
					if (typeof el.down === "function") {
						el.down(x, y, obj);
						tapped = true;
						break;
					}
				}
			}
		}
		if (!tapped) {
			// Pool of random 'nothing here' phrases and their corresponding voice-over asset ids
			var nothingHerePhrasesVO = [{
				text: "Just cold stone beneath your fingers.",
				vo: "vo_nothing_cold_stone"
			}, {
				text: "Nothing unusual here.",
				vo: "vo_nothing_unusual"
			}, {
				text: "Rough wall. Nothing of interest.",
				vo: "vo_nothing_rough_wall"
			}, {
				text: "Only silence greets you.",
				vo: "vo_nothing_silence"
			}, {
				text: "You reach out… and find nothing new.",
				vo: "vo_nothing_reach_out"
			}, {
				text: "Your hand brushes empty air.",
				vo: "vo_nothing_brush_air"
			}, {
				text: "It’s quiet here. Too quiet.",
				vo: "vo_nothing_quiet"
			}, {
				text: "Nothing but shadows.",
				vo: "vo_nothing_shadows"
			}, {
				text: "This spot feels empty.",
				vo: "vo_nothing_spot_empty"
			}, {
				text: "Just part of the dungeon wall.",
				vo: "vo_nothing_dungeon_wall"
			}, {
				text: "You feel around, but there’s nothing here.",
				vo: "vo_nothing_feel_around"
			}, {
				text: "Only old stone. Try elsewhere.",
				vo: "vo_nothing_old_stone"
			}];
			// Pick a random phrase and its voice-over
			var idx = Math.floor(Math.random() * nothingHerePhrasesVO.length);
			setGuiText(nothingHerePhrasesVO[idx].text, nothingHerePhrasesVO[idx].vo);
		}
	} else if (currentState === STATE_PUZZLE1_SUCCESS) {
		// Double tap to continue to puzzle 2
		if (isDoubleTap) {
			goToState(STATE_PUZZLE2);
		}
	} else if (currentState === STATE_PUZZLE2) {
		// Route tap to nearest element
		var tapped = false;
		for (var i = 0; i < puzzle2Elements.length; i++) {
			var el = puzzle2Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			// Pool of random 'nothing here' phrases and their corresponding voice-over asset ids
			var nothingHerePhrasesVO = [{
				text: "Just cold stone beneath your fingers.",
				vo: "vo_nothing_cold_stone"
			}, {
				text: "Nothing unusual here.",
				vo: "vo_nothing_unusual"
			}, {
				text: "Rough wall. Nothing of interest.",
				vo: "vo_nothing_rough_wall"
			}, {
				text: "Only silence greets you.",
				vo: "vo_nothing_silence"
			}, {
				text: "You reach out… and find nothing new.",
				vo: "vo_nothing_reach_out"
			}, {
				text: "Your hand brushes empty air.",
				vo: "vo_nothing_brush_air"
			}, {
				text: "It’s quiet here. Too quiet.",
				vo: "vo_nothing_quiet"
			}, {
				text: "Nothing but shadows.",
				vo: "vo_nothing_shadows"
			}, {
				text: "This spot feels empty.",
				vo: "vo_nothing_spot_empty"
			}, {
				text: "Just part of the dungeon wall.",
				vo: "vo_nothing_dungeon_wall"
			}, {
				text: "You feel around, but there’s nothing here.",
				vo: "vo_nothing_feel_around"
			}, {
				text: "Only old stone. Try elsewhere.",
				vo: "vo_nothing_old_stone"
			}];
			// Pick a random phrase and its voice-over
			var idx = Math.floor(Math.random() * nothingHerePhrasesVO.length);
			setGuiText(nothingHerePhrasesVO[idx].text, nothingHerePhrasesVO[idx].vo);
		}
	} else if (currentState === STATE_PUZZLE2_SUCCESS) {
		// Double tap to continue to puzzle 3
		if (isDoubleTap) {
			goToState(STATE_PUZZLE3);
		}
	} else if (currentState === STATE_PUZZLE3) {
		// Route tap to nearest element
		var tapped = false;
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			// Random 'nothing here' feedback
			var nothingHerePhrasesVO = [{
				text: "Just cold stone beneath your fingers.",
				vo: "vo_nothing_cold_stone"
			}, {
				text: "Nothing unusual here.",
				vo: "vo_nothing_unusual"
			}, {
				text: "Rough wall. Nothing of interest.",
				vo: "vo_nothing_rough_wall"
			}, {
				text: "Only silence greets you.",
				vo: "vo_nothing_silence"
			}, {
				text: "You reach out… and find nothing new.",
				vo: "vo_nothing_reach_out"
			}];
			var idx = Math.floor(Math.random() * nothingHerePhrasesVO.length);
			setGuiText(nothingHerePhrasesVO[idx].text, nothingHerePhrasesVO[idx].vo);
		}
	} else if (currentState === STATE_PUZZLE3_SUCCESS) {
		// Double tap to continue to puzzle 4
		if (isDoubleTap) {
			goToState(STATE_PUZZLE4);
		}
	} else if (currentState === STATE_PUZZLE4) {
		// Route tap to chimes
		var tapped = false;
		for (var i = 0; i < puzzle4Elements.length; i++) {
			var el = puzzle4Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			setGuiText("The chimes hang in stillness… \naligned at the heart of the chamber....", 'vo_tap_chimes_hint');
		}
	} else if (currentState === STATE_PUZZLE4_SUCCESS) {
		// Double tap to continue to puzzle 5
		if (isDoubleTap) {
			goToState(STATE_PUZZLE5);
		}
	} else if (currentState === STATE_PUZZLE5) {
		// Route tap to chimes
		var tapped = false;
		for (var i = 0; i < puzzle4Elements.length; i++) {
			var el = puzzle4Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			setGuiText("The chimes hang in stillness… \naligned at the heart of the chamber....", 'vo_tap_chimes_hint');
		}
	} else if (currentState === STATE_PUZZLE5_SUCCESS) {
		// Double tap to show game complete message or game win
		if (isDoubleTap) {
			if (!gameCompleteShown) {
				// First double tap - show game complete message
				gameCompleteShown = true;
				setGuiText("You walk through the final door, \n leaving the last chamber behind.\nThe sounds of the Vault fall quiet, the puzzles now at rest.\n\nFor now, your journey ends.\nBut the Vault is old… and its echoes are never truly silent.\n\n(Double tap to complete)", 'vo_game_complete');
			} else {
				// Second double tap - show game win
				LK.showYouWin();
			}
		}
	}
};
// Prevent drag/hold from causing issues
game.move = function (x, y, obj) {
	// Handle rock dragging in puzzle 3
	if (currentState === STATE_PUZZLE3 && puzzle3Rock && puzzle3Rock.isDragging) {
		puzzle3Rock.handleDrag(x, y, obj);
	}
};
game.up = function (x, y, obj) {
	// Route up event to elements for hold detection
	if (currentState === STATE_PUZZLE1) {
		if (typeof puzzle1Elements !== "undefined") {
			for (var i = 0; i < puzzle1Elements.length; i++) {
				var el = puzzle1Elements[i];
				if (typeof el.up === "function" && el.isHolding) {
					el.up(x, y, obj);
				}
			}
		}
	} else if (currentState === STATE_PUZZLE3) {
		// Always stop rock dragging on any finger lift
		if (puzzle3Rock && puzzle3Rock.isDragging) {
			puzzle3Rock.isDragging = false;
			// Check if rock is on the floor tile
			if (puzzle3FloorTile && puzzle3Rock.intersects(puzzle3FloorTile)) {
				puzzle3FloorTile.pressWithRock();
			} else if (puzzle3FloorTile && puzzle3FloorTile.isPressedByRock) {
				// Rock was moved off the tile
				puzzle3FloorTile.releaseFromRock();
			}
		}
		// Handle release for puzzle 3 elements
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			if (typeof el.up === "function") {
				// Check if release is near the element
				var dx = x - el.x;
				var dy = y - el.y;
				var dist = Math.sqrt(dx * dx + dy * dy);
				if (dist < el.hitRadius || el.isPressed) {
					el.up(x, y, obj);
				}
			}
		}
		// Do not automatically complete puzzle when rock is on tile
		// Player must double-tap the open gate to proceed
	}
};
// --- Game Start ---
goToState(STATE_HEADPHONES); /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
/**** 
* Classes
****/ 
var InteractiveElement = Container.expand(function () {
	var self = Container.call(this);
	self.label = "";
	self.description = "";
	self.interactText = "";
	self.onInteract = null;
	self.voiceOverAnnounceId = null;
	self.voiceOverInteractId = null;
	self.lastTapTime = 0;
	self.tapCount = 0;
	// Visual: use tapCircle for all, but color can be changed if needed
	var circle = self.attachAsset('tapCircle', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.7
	});
	// Apply global visibility state
	circle.visible = typeof tapCirclesVisible !== 'undefined' ? tapCirclesVisible : false;
	// Store the tap circle's radius for hit detection (half of width)
	self.hitRadius = circle.width / 2;
	// Set up element
	self.setup = function (label, description, interactText, onInteract, voiceOverAnnounceId, voiceOverInteractId) {
		self.label = label;
		self.description = description;
		self.interactText = interactText;
		self.onInteract = onInteract;
		self.voiceOverAnnounceId = voiceOverAnnounceId;
		self.voiceOverInteractId = voiceOverInteractId;
	};
	// Announce what this is
	self.announce = function () {
		setGuiText(self.label + "\n" + self.description + "\n\n(Double tap to interact)", self.voiceOverAnnounceId);
		// Optionally, play a sound or narration for the element
	};
	// Interact with the element
	self.interact = function () {
		// If onInteract is defined, it is responsible for calling setGuiText.
		if (typeof self.onInteract === "function") {
			self.onInteract();
		} else {
			// Only call setGuiText here if there's no specific onInteract handler
			// that would call it. This uses the generic interactText and voiceOverInteractId.
			setGuiText(self.interactText, self.voiceOverInteractId);
		}
	};
	// Track pending voice-over timeout
	self.pendingVOTimeout = null;
	self.justDoubleTapped = false;
	self.justDoubleTappedTimeout = null;
	// Handle tap/double-tap
	self.down = function (x, y, obj) {
		var now = Date.now();
		var timeSinceLastTap = now - self.lastTapTime;
		// If within double-tap window
		if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
			// Added minimum time to avoid accidental double taps
			// This is a double tap
			// Cancel any pending single-tap voice-over
			if (self.pendingVOTimeout) {
				LK.clearTimeout(self.pendingVOTimeout);
				self.pendingVOTimeout = null;
			}
			// Stop any current voice-over before playing the double-tap VO
			if (currentVoiceOver) {
				currentVoiceOver.stop();
				currentVoiceOver = null;
			}
			self.interact();
			self.lastTapTime = 0; // Reset to prevent triple taps
			// Cooldown after double tap to prevent immediate single tap on this element
			if (self.justDoubleTappedTimeout) {
				LK.clearTimeout(self.justDoubleTappedTimeout);
			}
			self.justDoubleTapped = true;
			self.justDoubleTappedTimeout = LK.setTimeout(function () {
				self.justDoubleTapped = false;
				self.justDoubleTappedTimeout = null;
			}, 700); // 700ms cooldown
			// Play a subtle sound to indicate interaction
			LK.getSound('sfx_tap').play();
		} else {
			// This is a single tap
			// If a double tap just occurred on this element, suppress the immediate single tap action
			// but record the tap time for a potential new double tap.
			if (self.justDoubleTapped) {
				self.lastTapTime = now;
				// Do not schedule announce, do not clear other timers or VOs, as the double tap just handled interaction.
			} else {
				// Standard single tap logic:
				// Cancel any existing pending voice-over
				if (self.pendingVOTimeout) {
					LK.clearTimeout(self.pendingVOTimeout); // {s}
					self.pendingVOTimeout = null; // Explicitly nullify the handle after clearing an old timeout
				} // {t}
				// Stop any current voice-over before scheduling new one
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay the announce to check if it's actually a double-tap
				self.pendingVOTimeout = LK.setTimeout(function () {
					self.announce();
					self.pendingVOTimeout = null;
				}, 300); // Wait 300ms to see if second tap comes
				self.lastTapTime = now;
			}
		}
	};
	return self;
});
// Stone Door for Puzzle 2
var StoneDoor = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.setup("Stone Door", "A massive stone door blocks your path.", "", function () {
		if (bothChestsOpened) {
			LK.getSound('sfx_rock_door_rumble').play();
			setGuiText("The stone door grinds open. You've solved the puzzle!", 'vo_stone_door_open');
			LK.getSound('sfx_success').play();
			goToState(STATE_PUZZLE2_SUCCESS);
		} else {
			setGuiText("The door won't budge. Perhaps something else needs to be done first.", 'vo_stone_door_locked');
		}
	}, 'vo_stone_door_label', 'vo_stone_door_locked');
	return self;
});
// Key
var Key = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.setup("Key", "Something lies on the floor. Cold, small... metal.", "You pick up a rusty key. It hums faintly in your hand.", function () {
		hasKey = true;
		LK.getSound('sfx_key_pickup').play();
		setGuiText("You pick up a rusty key. It hums faintly in your hand.", 'vo_key_pickup');
		self.visible = false; // Hide key after pickup
		self.interactive = false; // Make it non-interactive after pickup
		// Remove from puzzle elements to prevent further interaction
		if (puzzle1Elements) {
			var index = puzzle1Elements.indexOf(self);
			if (index !== -1) {
				puzzle1Elements.splice(index, 1);
			}
		}
	}, 'vo_key_label', 'vo_key_pickup');
	return self;
});
// Heavy Rock for Puzzle 3
var HeavyRock = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isDragging = false;
	self.startX = 0;
	self.startY = 0;
	self.lastX = 0; // Add lastX property
	self.lastY = 0; // Add lastY property
	self.lastDragSoundTime = 0;
	self.setup("Heavy Rock", "A large, heavy rock. It might be moveable with effort.", "The rock is too heavy to pick up, \nbut you might be able to drag it.", null, 'vo_heavy_rock_label', 'vo_rock_drag_hint');
	// Override down to start dragging
	var originalDown = self.down;
	self.down = function (x, y, obj) {
		originalDown.call(self, x, y, obj);
		// If originalDown (from InteractiveElement) processed this as a double tap,
		// self.justDoubleTapped will be true. The interaction VO is already handled.
		// In this case, we should not start dragging or interfere with the VO.
		if (self.justDoubleTapped) {
			// Double tap was handled by InteractiveElement's down method.
			// The interaction VO ('vo_rock_drag_hint') should be playing.
			// Do not initiate drag or stop the VO.
		} else {
			// This was not a double tap processed by originalDown.
			// This means it's likely a single tap intended to start a drag.
			self.isDragging = true;
			self.startX = self.x;
			self.startY = self.y;
			self.dragOffsetX = 0;
			self.dragOffsetY = 0;
			// For a single tap, InteractiveElement.down has scheduled self.announce().
			// We allow this to proceed to play the label VO.
			// If dragging starts immediately, the label VO will play concurrently if not finished.
		}
		// Always update lastX and lastY for consistent drag behavior or other positional logic
		self.lastX = self.x;
		self.lastY = self.y;
	};
	// Handle dragging
	self.handleDrag = function (x, y, obj) {
		if (self.isDragging) {
			// Check if rock would overlap with tile at new position
			var wouldOverlap50Percent = false;
			if (puzzle3FloorTile) {
				// Temporarily move rock to check intersection
				var oldX = self.x;
				var oldY = self.y;
				self.x = x;
				self.y = y;
				// Calculate overlap percentage
				if (self.intersects(puzzle3FloorTile)) {
					// Get bounds of both objects
					var rockLeft = self.x - self.hitRadius;
					var rockRight = self.x + self.hitRadius;
					var rockTop = self.y - self.hitRadius;
					var rockBottom = self.y + self.hitRadius;
					var tileLeft = puzzle3FloorTile.x - puzzle3FloorTile.hitRadius;
					var tileRight = puzzle3FloorTile.x + puzzle3FloorTile.hitRadius;
					var tileTop = puzzle3FloorTile.y - puzzle3FloorTile.hitRadius;
					var tileBottom = puzzle3FloorTile.y + puzzle3FloorTile.hitRadius;
					// Calculate overlap area
					var overlapLeft = Math.max(rockLeft, tileLeft);
					var overlapRight = Math.min(rockRight, tileRight);
					var overlapTop = Math.max(rockTop, tileTop);
					var overlapBottom = Math.min(rockBottom, tileBottom);
					var overlapWidth = Math.max(0, overlapRight - overlapLeft);
					var overlapHeight = Math.max(0, overlapBottom - overlapTop);
					var overlapArea = overlapWidth * overlapHeight;
					// Calculate rock area (assuming circular, use diameter squared)
					var rockArea = self.hitRadius * 2 * (self.hitRadius * 2);
					// Check if overlap is 50% or more
					var overlapPercentage = overlapArea / rockArea;
					wouldOverlap50Percent = overlapPercentage >= 0.5;
				}
				// Restore position
				self.x = oldX;
				self.y = oldY;
			}
			// Only move if not overlapping 50% with tile
			if (!wouldOverlap50Percent) {
				// Move rock directly to touch position (no offset)
				self.x = x;
				self.y = y;
				// Play drag sound periodically
				var now = Date.now();
				if (now - self.lastDragSoundTime > 500) {
					LK.getSound('sfx_rock_drag').play();
					self.lastDragSoundTime = now;
				}
			} else {
				// Rock overlaps 50% with tile - stop dragging and place it
				self.isDragging = false;
				// Play click sound
				LK.getSound('sfx_rock_place').play();
				// Play rock stuck sound
				LK.getSound('sfx_rock_stuck').play();
				// Trigger tile press
				puzzle3FloorTile.pressWithRock();
			}
		}
		;
		// Add update method to track position changes
		self.update = function () {
			self.lastX = self.x; // Update lastX
			self.lastY = self.y; // Update lastY
		};
	};
	// Stop dragging on up
	self.up = function (x, y, obj) {
		if (self.isDragging) {
			self.isDragging = false;
			// Check if rock is on the floor tile
			if (puzzle3FloorTile && self.intersects(puzzle3FloorTile)) {
				puzzle3FloorTile.pressWithRock();
			} else if (puzzle3FloorTile && puzzle3FloorTile.isPressedByRock) {
				// Rock was moved off the tile
				puzzle3FloorTile.releaseFromRock();
			}
		}
	};
	return self;
});
// Gate for Puzzle 3
var Gate = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isOpen = false;
	// Override announce to provide different instructions when open
	var originalAnnounce = self.announce;
	self.announce = function () {
		if (self.isOpen) {
			setGuiText("Gate\nThe gate stands open before you.\n\n(Double tap to pass through)", 'vo_gate_open_announce');
		} else {
			originalAnnounce.call(self);
		}
	};
	self.setup("Gate", "A heavy iron gate blocks your path.", "", function () {
		if (self.isOpen) {
			// When gate is open, double-tap allows passage
			setGuiText("The gate is open! You step through carefully...", 'vo_gate_passage');
			LK.getSound('sfx_success').play();
			goToState(STATE_PUZZLE3_SUCCESS);
		} else {
			setGuiText("The gate is closed. Something needs to keep it open.", 'vo_gate_closed');
		}
	}, 'vo_gate_label', 'vo_gate_closed');
	self.open = function () {
		if (!self.isOpen) {
			self.isOpen = true;
			LK.getSound('sfx_gate_open').play();
			setGuiText("The gate slowly grinds open!", 'vo_gate_opening');
		}
	};
	self.close = function () {
		if (self.isOpen) {
			self.isOpen = false;
			LK.getSound('sfx_gate_close').play();
		}
	};
	return self;
});
// Floor Tile for Puzzle 3
var FloorTile = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isPressed = false;
	self.isPressedByRock = false;
	self.linkedGate = null;
	self.setup("Floor Tile", "A loose stone tile. It seems to move slightly when touched.", "", function () {
		if (self.isPressedByRock) {
			setGuiText("The heavy rock keeps the tile pressed down.", 'vo_tile_pressed_by_rock');
		} else {
			// Updated message for double tap - play gate open/close sounds for feedback
			if (self.linkedGate) {
				self.linkedGate.open();
				// Schedule gate close after a short delay
				LK.setTimeout(function () {
					if (self.linkedGate && !self.isPressedByRock) {
						self.linkedGate.close();
					}
				}, 1000); // Gate stays open for 1 second
			}
			setGuiText("The gate opened, but shut down as soon as I lift my hand. I need something heavy to keep it pressed.", 'vo_tile_springs_back');
		}
	}, 'vo_floor_tile_label', 'vo_tile_springs_back');
	self.pressWithRock = function () {
		if (!self.isPressedByRock) {
			self.isPressedByRock = true;
			self.isPressed = true;
			if (self.linkedGate) {
				self.linkedGate.open();
			}
			LK.getSound('sfx_rock_place').play();
			setGuiText("The rock settles onto the tile with a satisfying thunk. The gate remains open!", 'vo_rock_on_tile');
		}
	};
	self.releaseFromRock = function () {
		if (self.isPressedByRock) {
			self.isPressedByRock = false;
			self.isPressed = false;
			if (self.linkedGate) {
				self.linkedGate.close();
			}
		}
	};
	return self;
});
// Door
var Door = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.setup("Door", "A heavy wooden door. It doesn't budge.", "", function () {
		// Door can only be opened if player has the key
		if (typeof hasKey !== "undefined" && hasKey) {
			self.interactText = "The key turns with a satisfying click. The path ahead opens.";
			setGuiText("The key turns with a satisfying click. The path ahead opens.", 'vo_door_unlocked');
			// Play new door unlock sound
			LK.getSound('sfx_door_unlock').play();
			goToState(STATE_PUZZLE1_SUCCESS);
		} else {
			self.interactText = "The door is locked tight. You'll need something to open it.";
			setGuiText("The door is locked tight. You'll need something to open it.", 'vo_door_locked');
		}
	}, 'vo_door_label', 'vo_door_locked');
	return self;
});
// Chime for Puzzle 4
var Chime = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.chimeIndex = 0;
	self.isCommitted = false;
	self.chimeSound = null;
	self.pendingSingleTapTimeout = null;
	self.setup = function (index) {
		self.chimeIndex = index;
		var label = "Chime " + (index + 1);
		var description = "";
		// Use specific voice-over for each chime description
		var chimeVoiceOverId = 'vo_chime_description_' + (index + 1);
		InteractiveElement.call(self).setup(label, description, "", function () {
			// Double tap - commit to this chime
			if (!self.isCommitted && !puzzle4Complete) {
				self.commit();
			}
		}, chimeVoiceOverId, null);
		// Override the circle color based on chime index
		if (self.children[0]) {
			var colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00]; // Red, Green, Blue, Yellow
			self.children[0].tint = colors[index % 4];
		}
	};
	// Override announce to play instruction VO first
	self.announce = function () {
		// Play the instruction VO first
		var instructionVO = LK.getSound('vo_chime_instruction');
		instructionVO.play();
		// Wait for instruction to finish before playing chime description
		LK.setTimeout(function () {
			// Call parent announce method which will play the chime description
			InteractiveElement.prototype.announce.call(self);
			// After chime description, play general description
			LK.setTimeout(function () {
				var generalDescVO = LK.getSound('vo_chime_general_description');
				generalDescVO.play();
			}, 2000); // Wait for chime description to finish
		}, 3000); // Adjust timing based on instruction VO length
	};
	// Override down method from InteractiveElement to handle chime-specific behavior
	self.down = function (x, y, obj) {
		var now = Date.now();
		var timeSinceLastTap = now - self.lastTapTime;
		// If within double-tap window
		if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
			// This is a double tap
			// Cancel any pending single-tap sound
			if (self.pendingSingleTapTimeout) {
				LK.clearTimeout(self.pendingSingleTapTimeout);
				self.pendingSingleTapTimeout = null;
			}
			// Cancel any pending single-tap voice-over
			if (self.pendingVOTimeout) {
				LK.clearTimeout(self.pendingVOTimeout);
				self.pendingVOTimeout = null;
			}
			// Stop any current voice-over before playing the double-tap VO
			if (currentVoiceOver) {
				currentVoiceOver.stop();
				currentVoiceOver = null;
			}
			self.interact();
			self.lastTapTime = 0; // Reset to prevent triple taps
			// Cooldown after double tap to prevent immediate single tap on this element
			if (self.justDoubleTappedTimeout) {
				LK.clearTimeout(self.justDoubleTappedTimeout);
			}
			self.justDoubleTapped = true;
			self.justDoubleTappedTimeout = LK.setTimeout(function () {
				self.justDoubleTapped = false;
				self.justDoubleTappedTimeout = null;
			}, 700); // 700ms cooldown
			// Play a subtle sound to indicate interaction
			LK.getSound('sfx_tap').play();
		} else {
			// This is a single tap
			// If a double tap just occurred on this element, suppress the immediate single tap action
			// but record the tap time for a potential new double tap.
			if (self.justDoubleTapped) {
				self.lastTapTime = now;
				// Do not schedule announce, do not clear other timers or VOs, as the double tap just handled interaction.
			} else {
				// Standard single tap logic:
				// Cancel any existing pending voice-over
				if (self.pendingVOTimeout) {
					LK.clearTimeout(self.pendingVOTimeout); // {s}
					self.pendingVOTimeout = null; // Explicitly nullify the handle after clearing an old timeout
				} // {t}
				// Cancel any existing pending single tap sound
				if (self.pendingSingleTapTimeout) {
					LK.clearTimeout(self.pendingSingleTapTimeout);
					self.pendingSingleTapTimeout = null;
				}
				// Stop any current voice-over before scheduling new one
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay both the announce and the soft chime sound to check if it's actually a double-tap
				self.pendingVOTimeout = LK.setTimeout(function () {
					self.announce();
					self.pendingVOTimeout = null;
				}, 300); // Wait 300ms to see if second tap comes
				// Schedule soft chime sound
				self.pendingSingleTapTimeout = LK.setTimeout(function () {
					// Play instruction VO first
					var instructionVO = LK.getSound('vo_chime_instruction');
					instructionVO.play();
					// Wait for instruction to finish before playing chime sound
					LK.setTimeout(function () {
						// Play soft chime sound at lower volume
						if (self.chimeSound && !self.isCommitted) {
							var softSound = self.chimeSound;
							softSound.volume = 0.3; // Set to 30% volume for soft tap
							softSound.play();
							// Reset volume after playing
							LK.setTimeout(function () {
								softSound.volume = 1.0; // Reset to full volume
							}, 100);
						}
					}, 3000); // Wait for instruction VO to finish
					self.pendingSingleTapTimeout = null;
				}, 300); // Same delay as voice-over
				self.lastTapTime = now;
			}
		}
	};
	self.commit = function () {
		self.isCommitted = true;
		playerChimeSequence.push(self.chimeIndex);
		// Play louder committed sound at full volume
		if (self.chimeSound) {
			self.chimeSound.volume = 1.0; // Ensure full volume for double tap
			self.chimeSound.play();
		}
		// Visual feedback - flash the chime
		tween(self, {
			alpha: 0.3
		}, {
			duration: 200,
			easing: tween.easeOut,
			onFinish: function onFinish() {
				tween(self, {
					alpha: 1
				}, {
					duration: 200,
					easing: tween.easeIn
				});
			}
		});
		// Check if player has entered all required chimes
		var requiredLength = correctChimeSequence.length;
		if (playerChimeSequence.length === requiredLength) {
			checkChimeSequence();
		} else {
			setGuiText("Chime " + playerChimeSequence.length + " of " + requiredLength + " selected.", 'vo_chime_selected');
		}
	};
	return self;
});
// Chest for Puzzle 2
var Chest = InteractiveElement.expand(function () {
	var self = InteractiveElement.call(this);
	self.isOpened = false;
	self.chestId = 0;
	self.isVialChest = false; // Will be set during setup
	self.isTrapChest = false; // Will be set during setup
	self.setup = function (id, isVial, isTrap) {
		self.chestId = id;
		self.isVialChest = !!isVial;
		self.isTrapChest = !!isTrap;
		// Inspection text
		var inspectText = "A wooden chest with intricate carvings.";
		if (self.isVialChest) {
			inspectText = "A wooden chest. It smells faintly of herbs.";
		} else if (self.isTrapChest) {
			inspectText = "Another chest, this one colder to the touch.";
		}
		// Interact logic
		InteractiveElement.call(self).setup("Chest " + (id + 1), inspectText, "", function () {
			if (!self.isOpened) {
				self.isOpened = true;
				// Play chest open sound
				LK.getSound('sfx_chest_open').play();
				// Vial Chest
				if (self.isVialChest) {
					hasVialOfClarity = true;
					// Play background music for vial chest open
					LK.playMusic('vial_chest_open_bgm', {
						loop: true
					});
					// If trap was already triggered, heal and stop distortion
					if (trapActive) {
						trapActive = false;
						// Stop annoying trap sound if running
						stopAnnoyingTrapSound();
						var vialText = "You open the chest… a soft chime rings out. \nInside, a small vial, warm to the touch. \nYou feel a little more focused. \nThe distortion fades.";
						// If this is the second chest, append the stone shifting message
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							vialText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(vialText, 'vo_vial_heals_trap');
						// Play soft chime, then steady hum (simulate with sfx_success for now)
						LK.getSound('sfx_success').play();
					} else {
						var vialText = "You open the chest… a soft chime rings out.\n Inside, a small vial, warm to the touch. \nYou feel a little more focused.";
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							vialText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(vialText, 'vo_vial_found');
						LK.getSound('sfx_success').play();
					}
				}
				// Trap Chest
				else if (self.isTrapChest) {
					// If player already has vial, neutralize trap
					if (hasVialOfClarity) {
						var trapText = "You open the chest… a rush of wind escapes,\n but the vial in your hand pulses gently, shielding your senses.";
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							trapText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(trapText, 'vo_trap_neutralized');
						// Play wind muffled/steady hum (simulate with sfx_tap for now)
						LK.getSound('sfx_tap').play();
						// Stop annoying trap sound if running
						stopAnnoyingTrapSound();
					} else {
						trapActive = true;
						var trapText = "You open the chest… a rush of wind escapes. A sharp whisper pierces your ears. Everything feels… twisted.";
						var openedCount = 0;
						for (var i = 0; i < puzzle2Elements.length; i++) {
							if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
								openedCount++;
							}
						}
						if (openedCount === 2) {
							trapText += "\nYou hear a heavy stone shifting nearby.";
						}
						setGuiText(trapText, 'vo_trap_triggered');
						// Play rushing wind/sharp whisper (simulate with sfx_tap for now)
						LK.getSound('sfx_tap').play();
						// Start annoying trap sound
						startAnnoyingTrapSound();
						// TODO: Add actual distortion effect if available
					}
				}
				// Fallback (should not happen)
				else {
					var fallbackText = "The chest creaks open. You find nothing inside, but sense progress.";
					var openedCount = 0;
					for (var i = 0; i < puzzle2Elements.length; i++) {
						if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
							openedCount++;
						}
					}
					if (openedCount === 2) {
						fallbackText += "\nYou hear a heavy stone shifting nearby.";
					}
					setGuiText(fallbackText, 'vo_chest_opened');
					LK.getSound('sfx_success').play();
				}
				checkPuzzle2Progress();
			} else {
				setGuiText("The chest is already open.", 'vo_chest_empty');
			}
		},
		// Announce/inspect voice-over
		self.isVialChest ? 'vo_vial_inspect' : self.isTrapChest ? 'vo_trap_inspect' : 'vo_chest_label',
		// Interact voice-over
		self.isVialChest ? 'vo_vial_found' : self.isTrapChest ? 'vo_trap_triggered' : 'vo_chest_opened');
	};
	return self;
});
// Chest
// Simple visual feedback for taps (for sighted users or those with some vision)
var TapFeedback = Container.expand(function () {
	var self = Container.call(this);
	var circle = self.attachAsset('tapCircle', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.5
	});
	self.showAt = function (x, y) {
		self.x = x;
		self.y = y;
		self.alpha = 0.5;
		self.scaleX = 1;
		self.scaleY = 1;
		tween(self, {
			alpha: 0,
			scaleX: 2,
			scaleY: 2
		}, {
			duration: 400,
			easing: tween.easeOut,
			onFinish: function onFinish() {
				self.destroy();
			}
		});
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x000000
});
/**** 
* Game Code
****/ 
// Puzzle 1 & 2: All unique VO assets
// Voice-over assets for "nothing here" phrases
// Audio assets (narration and sfx) are referenced by id, LK will load them automatically.
// No visual assets needed for MVP, but we will use a simple shape for tap feedback.
// --- State Management ---
// Voice-over sounds for narration
// Voice-over assets for all text displays
// Narration assets
// Voice-over sounds for puzzle elements
// Voice-over sounds for game states and feedback
// Narration music tracks
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
// missing, added
var STATE_HEADPHONES = 0;
var STATE_MENU = 1;
var STATE_HOW_TO_PLAY = 2;
var STATE_CREDITS = 3;
var STATE_INTRO = 4;
var STATE_PUZZLE1 = 5;
var STATE_PUZZLE1_SUCCESS = 6;
var STATE_PUZZLE2 = 7;
var STATE_PUZZLE2_SUCCESS = 8;
var STATE_PUZZLE3 = 9;
var STATE_PUZZLE3_SUCCESS = 10;
var STATE_PUZZLE4 = 11;
var STATE_PUZZLE4_SUCCESS = 12;
var STATE_PUZZLE5 = 13;
var STATE_PUZZLE5_SUCCESS = 14;
var currentState = STATE_HEADPHONES;
var headphonesIcon = null; // Store reference to headphones icon
// Used to prevent double-tap triggers
var inputLocked = false;
// Used to track narration/music
var currentNarration = null;
// Used to track current voice-over sound
var currentVoiceOver = null;
// Used for puzzle state
var puzzle1Step = 0; // 0 = waiting for first tap, 1 = waiting for second tap
// Double-tap tracking at game level
var lastTapTime = 0;
var lastTapX = 0;
var lastTapY = 0;
var doubleTapThreshold = 500; // milliseconds
var doubleTapDistanceThreshold = 50; // pixels
var currentlySelectedOption = null; // Track currently selected menu option
// --- Puzzle 1 element state ---
var puzzle1Elements = [];
var chestOpened = false;
var hasKey = false;
// --- Puzzle 2 element state ---
var puzzle2Elements = [];
var bothChestsOpened = false;
// --- Puzzle 2: Vial/Trap state ---
var hasVialOfClarity = false;
var trapActive = false;
// --- Puzzle 3 element state ---
var puzzle3Elements = [];
var puzzle3Gate = null;
var puzzle3FloorTile = null;
var puzzle3Rock = null;
// --- Puzzle 4 element state ---
var puzzle4Elements = [];
var correctChimeSequence = [];
var playerChimeSequence = [];
var puzzle4Complete = false;
var isIntroPlaying = false; // Track when intro VO is playing
var gameCompleteShown = false; // Track if game complete message is shown
// --- Annoying sound for trap logic ---
var annoyingTrapSound = null;
var annoyingTrapSoundInterval = null;
var debugLevelLinks = []; // Array to store debug level links
var tapCirclesVisible = false; // Global state for tapCircle visibility - starts hidden
var tapCircleToggle = null; // Reference to toggle button
function startAnnoyingTrapSound() {
	if (annoyingTrapSoundInterval) {
		return;
	} // Already running
	// Play the annoying sound as looping music
	LK.playMusic('sfx_trap_trigger', {
		loop: true
	});
}
function stopAnnoyingTrapSound() {
	// Stop the annoying trap music if playing
	LK.stopMusic();
	if (annoyingTrapSoundInterval) {
		LK.clearInterval(annoyingTrapSoundInterval);
		annoyingTrapSoundInterval = null;
	}
	annoyingTrapSound = null;
}
// --- GUI Text for minimal visual feedback (for sighted users) ---
var guiText = new Text2('', {
	size: 50,
	fill: 0xFFFFFF,
	wordWrap: true,
	wordWrapWidth: 1600,
	// Reduced from 1800 to provide more margin
	align: 'center' // Center align the text lines within the text object
});
guiText.anchor.set(0.5, 1.0);
LK.gui.bottom.addChild(guiText);
guiText.y = -50; // Offset from bottom edge
// GUI text should always be visible
guiText.visible = true;
// --- Helper Functions ---
function updateTapCircleVisibility() {
	// Update puzzle 1 elements
	if (puzzle1Elements) {
		for (var i = 0; i < puzzle1Elements.length; i++) {
			var el = puzzle1Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
	// Update puzzle 2 elements
	if (puzzle2Elements) {
		for (var i = 0; i < puzzle2Elements.length; i++) {
			var el = puzzle2Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
	// Update puzzle 3 elements
	if (puzzle3Elements) {
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
	// Update puzzle 4 and 5 elements
	if (puzzle4Elements) {
		for (var i = 0; i < puzzle4Elements.length; i++) {
			var el = puzzle4Elements[i];
			if (el && el.children && el.children[0]) {
				el.children[0].visible = tapCirclesVisible;
			}
		}
	}
}
function playNarration(id, options) {
	// Stop any current narration/music
	LK.stopMusic();
	currentNarration = id;
	LK.playMusic(id, options || {});
}
function stopNarration() {
	LK.stopMusic();
	currentNarration = null;
}
function setGuiText(txt, voiceOverId) {
	guiText.setText(txt);
	// Add a subtle scale animation when text updates
	guiText.scaleX = 1.1;
	guiText.scaleY = 1.1;
	tween(guiText, {
		scaleX: 1,
		scaleY: 1
	}, {
		duration: 300,
		easing: tween.easeOut
	});
	// Stop current voice-over if playing
	if (currentVoiceOver) {
		currentVoiceOver.stop();
		currentVoiceOver = null;
	}
	// Play voice-over if provided, but delay if a SFX is currently playing
	if (voiceOverId) {
		// Check if a SFX was just played (by convention, SFX are played right before setGuiText)
		// We'll check if any SFX is currently playing and delay VO until it finishes
		var sfxIds = ['sfx_door_unlock', 'sfx_success', 'sfx_tap'];
		var maxSfxDuration = 0;
		for (var i = 0; i < sfxIds.length; i++) {
			var sfx = LK.getSound(sfxIds[i]);
			if (sfx && sfx.isPlaying && typeof sfx.duration === "number") {
				if (sfx.duration > maxSfxDuration) {
					maxSfxDuration = sfx.duration;
				}
			}
		}
		// If any SFX is playing, delay VO by the max duration (or a minimum of 400ms)
		if (maxSfxDuration > 0) {
			LK.setTimeout(function () {
				currentVoiceOver = LK.getSound(voiceOverId);
				currentVoiceOver.play();
			}, Math.max(400, maxSfxDuration * 1000));
		} else {
			currentVoiceOver = LK.getSound(voiceOverId);
			currentVoiceOver.play();
		}
	}
}
function lockInput(ms) {
	inputLocked = true;
	LK.setTimeout(function () {
		inputLocked = false;
	}, ms || 600);
}
// Check if both chests are opened in puzzle 2
function playChimeSequence() {
	setGuiText("Listen to the sequence...", 'vo_puzzle4_sequence_play');
	var index = 0;
	var _playNextChime = function playNextChime() {
		if (index < correctChimeSequence.length) {
			var chimeIndex = correctChimeSequence[index];
			var chime = puzzle4Elements[chimeIndex];
			if (chime && chime.chimeSound) {
				chime.chimeSound.play();
				// Visual feedback
				tween(chime, {
					scaleX: 1.2,
					scaleY: 1.2
				}, {
					duration: 300,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(chime, {
							scaleX: 1,
							scaleY: 1
						}, {
							duration: 300,
							easing: tween.easeIn
						});
					}
				});
			}
			index++;
			LK.setTimeout(_playNextChime, 800);
		} else {
			// Sequence complete, player can now interact
			setGuiText("The echoes guide you. Tap once to listen, \ntwice to step forward.", 'vo_repeat_sequence');
		}
	};
	_playNextChime();
}
function checkChimeSequence() {
	var correct = true;
	var expectedLength = correctChimeSequence.length;
	for (var i = 0; i < expectedLength; i++) {
		if (correctChimeSequence[i] !== playerChimeSequence[i]) {
			correct = false;
			break;
		}
	}
	if (correct) {
		puzzle4Complete = true;
		LK.getSound('sfx_success').play();
		if (currentState === STATE_PUZZLE4) {
			goToState(STATE_PUZZLE4_SUCCESS);
		} else if (currentState === STATE_PUZZLE5) {
			goToState(STATE_PUZZLE5_SUCCESS);
		}
	} else {
		// Wrong sequence behavior depends on current puzzle
		if (currentState === STATE_PUZZLE4) {
			// For simple puzzle 4, just reset and let them try again
			playerChimeSequence = [];
			// Reset all chimes
			for (var i = 0; i < puzzle4Elements.length; i++) {
				if (puzzle4Elements[i]) {
					puzzle4Elements[i].isCommitted = false;
				}
			}
			setGuiText("Not quite right. Listen again...\nRemember: from lowest tone to highest.", 'vo_puzzle4_simple_retry');
		} else if (currentState === STATE_PUZZLE5) {
			// Wrong sequence - just reset and let them try again
			playerChimeSequence = [];
			// Reset all chimes
			for (var i = 0; i < puzzle4Elements.length; i++) {
				if (puzzle4Elements[i]) {
					puzzle4Elements[i].isCommitted = false;
				}
			}
			setGuiText("The sequence is incorrect. The chimes await your touch again...\nListen carefully and try once more.", 'vo_puzzle5_wrong');
			// Play the sequence again after a short delay (just the sounds, no instruction VO)
			LK.setTimeout(function () {
				// Play only the chime sequence sounds without any voice-over
				var index = 0;
				var _playNextChime = function playNextChime() {
					if (index < correctChimeSequence.length) {
						var chimeIndex = correctChimeSequence[index];
						var chime = puzzle4Elements[chimeIndex];
						if (chime && chime.chimeSound) {
							chime.chimeSound.play();
							// Visual feedback
							tween(chime, {
								scaleX: 1.2,
								scaleY: 1.2
							}, {
								duration: 300,
								easing: tween.easeOut,
								onFinish: function onFinish() {
									tween(chime, {
										scaleX: 1,
										scaleY: 1
									}, {
										duration: 300,
										easing: tween.easeIn
									});
								}
							});
						}
						index++;
						LK.setTimeout(_playNextChime, 800);
					}
				};
				_playNextChime();
			}, 8000); // Wait 2 seconds after wrong sequence message
		}
	}
}
function checkPuzzle2Progress() {
	var openedCount = 0;
	for (var i = 0; i < puzzle2Elements.length; i++) {
		if (puzzle2Elements[i] instanceof Chest && puzzle2Elements[i].isOpened) {
			openedCount++;
		}
	}
	if (openedCount >= 2 && !bothChestsOpened) {
		bothChestsOpened = true;
		// No separate message for the door noise; handled in Chest logic after opening the second chest.
	}
}
// --- State Transitions ---
function goToState(state) {
	// Clear existing debug level links first
	if (debugLevelLinks.length > 0) {
		for (var i = 0; i < debugLevelLinks.length; i++) {
			if (debugLevelLinks[i] && typeof debugLevelLinks[i].destroy === 'function') {
				debugLevelLinks[i].destroy();
			}
		}
		debugLevelLinks = [];
	}
	stopNarration();
	// Clear selected menu option
	currentlySelectedOption = null;
	// Stop all playing sounds when restarting game
	if (state === STATE_HEADPHONES) {
		// Stop any current voice-over
		if (currentVoiceOver) {
			currentVoiceOver.stop();
			currentVoiceOver = null;
		}
		// Stop any trap sounds
		stopAnnoyingTrapSound();
		// Reset game complete flag
		gameCompleteShown = false;
	}
	// Remove headphones icon if it exists
	if (headphonesIcon) {
		// Clean up extra elements (like text below icon)
		if (headphonesIcon.extraElements) {
			for (var i = 0; i < headphonesIcon.extraElements.length; i++) {
				if (headphonesIcon.extraElements[i]) {
					headphonesIcon.extraElements[i].destroy();
				}
			}
		}
		headphonesIcon.destroy();
		headphonesIcon = null;
	}
	// Remove toggle button if it exists
	if (tapCircleToggle) {
		tapCircleToggle.destroy();
		tapCircleToggle = null;
	}
	// Clear all game children (including menu options and title) when transitioning states
	for (var i = game.children.length - 1; i >= 0; i--) {
		game.children[i].destroy();
	}
	// Clear all GUI elements
	LK.gui.top.removeChildren();
	LK.gui.bottom.removeChildren();
	// Re-add the guiText to bottom after clearing
	LK.gui.bottom.addChild(guiText);
	currentState = state;
	if (state === STATE_HEADPHONES) {
		setGuiText("(Double tap to continue)");
		// Voice-over is already handled by playNarration
		playNarration('screen_headphones_voice', {
			loop: false
		});
		// Add headphones icon
		headphonesIcon = LK.getAsset('headphonesIcon', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: 2048 / 2,
			y: 2732 / 2 - 200 // Move icon up to make room for text below
		});
		game.addChild(headphonesIcon);
		// Add text below headphones icon
		var headphonesText = new Text2("Use headphones for a better experience", {
			size: 100,
			fill: 0xFFFFFF,
			align: 'center',
			wordWrap: true,
			wordWrapWidth: 1600
		});
		headphonesText.anchor.set(0.5, 0.5);
		headphonesText.x = 2048 / 2;
		headphonesText.y = 2732 / 2 + 400; // Position further below the icon
		game.addChild(headphonesText);
		// Store reference to clean up later
		if (!headphonesIcon.extraElements) {
			headphonesIcon.extraElements = [];
		}
		headphonesIcon.extraElements.push(headphonesText);
		// Add debug links for development - COMMENTED OUT FOR PRODUCTION
		/* 
		var levelsForDebug = [{
			name: "Menu",
			state: STATE_MENU
		}, {
			name: "How to Play",
			state: STATE_HOW_TO_PLAY
		}, {
			name: "Credits",
			state: STATE_CREDITS
		}, {
			name: "Intro",
			state: STATE_INTRO
		}, {
			name: "Puzzle 1",
			state: STATE_PUZZLE1
		}, {
			name: "P1 Success",
			state: STATE_PUZZLE1_SUCCESS
		}, {
			name: "Puzzle 2",
			state: STATE_PUZZLE2
		}, {
			name: "P2 Success",
			state: STATE_PUZZLE2_SUCCESS
		}, {
			name: "Puzzle 3",
			state: STATE_PUZZLE3
		}, {
			name: "P3 Success",
			state: STATE_PUZZLE3_SUCCESS
		}, {
			name: "Puzzle 4",
			state: STATE_PUZZLE4
		}, {
			name: "P4 Success",
			state: STATE_PUZZLE4_SUCCESS
		}, {
			name: "Puzzle 5",
			state: STATE_PUZZLE5
		}, {
			name: "P5 Success",
			state: STATE_PUZZLE5_SUCCESS
		}];
		var startY = 120; // Start below the 100px top-left reserved area
		var spacingY = 55;
		for (var i = 0; i < levelsForDebug.length; i++) {
			var levelData = levelsForDebug[i];
			var linkText = new Text2(levelData.name, {
				size: 38,
				fill: 0xFFFF00,
				// Bright yellow for debug
				align: 'left',
				wordWrap: true,
				wordWrapWidth: 300 // Ensure debug links don't overflow
			});
			linkText.anchor.set(0, 0); // Anchor top-left
			linkText.x = 20; // Small offset from left edge
			linkText.y = startY + i * spacingY;
			linkText.interactive = true;
			linkText.buttonMode = true; // Shows hand cursor on desktop
			// IIFE to correctly capture levelData.state in the closure
			(function (targetState) {
				linkText.down = function () {
					// Stop any ongoing VO from the headphone screen before jumping
					if (currentVoiceOver) {
						currentVoiceOver.stop();
						currentVoiceOver = null;
					}
					goToState(targetState);
				};
			})(levelData.state);
			LK.gui.top.addChild(linkText); // Add to gui.top so it's above game content
			debugLevelLinks.push(linkText); // Store for cleanup
		}
		*/
	} else if (state === STATE_MENU) {
		setGuiText("", 'vo_menu_title'); // This ensures the menu title voice-over is played, but no text on bottom GUI
		// No narration for menu, or could add a short one if desired
		// Add "Main Menu" title text
		var menuTitle = new Text2("Main Screen", {
			size: 120,
			fill: 0xFFFFFF,
			align: 'center'
		});
		menuTitle.anchor.set(0.5, 0);
		menuTitle.interactive = true;
		menuTitle.buttonMode = true;
		// Add voice-over functionality to title
		menuTitle.lastTapTime = 0;
		menuTitle.pendingVOTimeout = null;
		menuTitle.justDoubleTapped = false;
		menuTitle.justDoubleTappedTimeout = null;
		menuTitle.down = function (x, y, obj) {
			var now = Date.now();
			var timeSinceLastTap = now - menuTitle.lastTapTime;
			// Single tap to announce title
			if (timeSinceLastTap > 500 || timeSinceLastTap < 50) {
				// Cancel any existing pending voice-over
				if (menuTitle.pendingVOTimeout) {
					LK.clearTimeout(menuTitle.pendingVOTimeout);
					menuTitle.pendingVOTimeout = null;
				}
				// Stop any current voice-over
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay the announce to check if it's actually a double-tap
				menuTitle.pendingVOTimeout = LK.setTimeout(function () {
					setGuiText("Main Menu\n\nChoose an option below", 'vo_menu_title_announce');
					menuTitle.pendingVOTimeout = null;
				}, 300);
				menuTitle.lastTapTime = now;
			}
		};
		LK.gui.top.addChild(menuTitle);
		menuTitle.y = 200; // Move title lower on the screen
		// Add menu options
		var menuOptions = [{
			text: "• Intro",
			label: "Intro",
			description: "Start the adventure story",
			voiceOverId: "vo_menu_intro",
			action: function action() {
				goToState(STATE_INTRO);
			}
		}, {
			text: "• How to Play",
			label: "How to Play",
			description: "Learn the game controls",
			voiceOverId: "vo_menu_how_to_play",
			action: function action() {
				goToState(STATE_HOW_TO_PLAY);
			}
		}, {
			text: "• Credits",
			label: "Credits",
			description: "View game credits",
			voiceOverId: "vo_menu_credits",
			action: function action() {
				goToState(STATE_CREDITS);
			}
		}];
		var spacingY = 250; // Increased spacing between options
		// Calculate vertical center position for menu items
		var totalMenuHeight = (menuOptions.length - 1) * spacingY;
		var startY = 2732 / 2 - totalMenuHeight / 2; // Center vertically on screen
		for (var i = 0; i < menuOptions.length; i++) {
			var option = menuOptions[i];
			var optionText = new Text2(option.text, {
				size: 120,
				fill: 0xFFFFFF,
				align: 'center',
				wordWrap: true,
				wordWrapWidth: 1600
			});
			optionText.anchor.set(0.5, 0.5);
			optionText.x = 2048 / 2;
			optionText.y = startY + i * spacingY;
			optionText.interactive = true;
			optionText.buttonMode = true;
			// Add double-tap functionality like InteractiveElement
			optionText.lastTapTime = 0;
			optionText.tapCount = 0;
			optionText.pendingVOTimeout = null;
			optionText.justDoubleTapped = false;
			optionText.justDoubleTappedTimeout = null;
			optionText.isSelected = false;
			optionText.isSelected = false;
			// Store the action function and option data for each option
			(function (optionAction, optionData) {
				optionText.down = function () {
					var now = Date.now();
					var timeSinceLastTap = now - optionText.lastTapTime;
					// If within double-tap window
					if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
						// This is a double tap - execute action only if this option is selected
						if (optionText.isSelected && currentlySelectedOption === optionText) {
							// Cancel any pending single-tap voice-over
							if (optionText.pendingVOTimeout) {
								LK.clearTimeout(optionText.pendingVOTimeout);
								optionText.pendingVOTimeout = null;
							}
							// Stop any current voice-over before executing
							if (currentVoiceOver) {
								currentVoiceOver.stop();
								currentVoiceOver = null;
							}
							optionAction();
							optionText.lastTapTime = 0; // Reset to prevent triple taps
							// Cooldown after double tap
							if (optionText.justDoubleTappedTimeout) {
								LK.clearTimeout(optionText.justDoubleTappedTimeout);
							}
							optionText.justDoubleTapped = true;
							optionText.justDoubleTappedTimeout = LK.setTimeout(function () {
								optionText.justDoubleTapped = false;
								optionText.justDoubleTappedTimeout = null;
							}, 700); // 700ms cooldown
							// Play interaction sound
							LK.getSound('sfx_tap').play();
						} else {
							// Double tap on unselected option - treat as single tap to select it
							optionText.lastTapTime = now;
						}
					} else {
						// This is a single tap - announce and show selection
						// If a double tap just occurred, suppress immediate single tap action
						if (optionText.justDoubleTapped) {
							optionText.lastTapTime = now;
						} else {
							// Clear any existing pending voice-over
							if (optionText.pendingVOTimeout) {
								LK.clearTimeout(optionText.pendingVOTimeout);
								optionText.pendingVOTimeout = null;
							}
							// Stop any current voice-over
							if (currentVoiceOver) {
								currentVoiceOver.stop();
								currentVoiceOver = null;
							}
							// Mark as selected and update appearance
							optionText.isSelected = true;
							currentlySelectedOption = optionText;
							// Animate to yellow highlight
							tween(optionText, {
								fill: 0xFFFF00
							}, {
								duration: 200,
								easing: tween.easeOut
							});
							// Clear selection from other options
							for (var j = 0; j < menuOptions.length; j++) {
								if (j !== i && game.children[j + 1]) {
									// +1 to account for menu title
									var otherOption = game.children[j + 1];
									if (otherOption !== optionText && otherOption.isSelected) {
										otherOption.isSelected = false;
										// Animate back to white
										tween(otherOption, {
											fill: 0xFFFFFF
										}, {
											duration: 200,
											easing: tween.easeOut
										});
									}
								}
							}
							// Delay the announce to check if it's actually a double-tap
							optionText.pendingVOTimeout = LK.setTimeout(function () {
								var announceText = optionData.label + " - " + optionData.description + "\n\n(Double tap to select)";
								// Use the voice-over ID from the option data
								setGuiText(announceText, optionData.voiceOverId);
								optionText.pendingVOTimeout = null;
							}, 300); // Wait 300ms to see if second tap comes
							optionText.lastTapTime = now;
						}
					}
				};
			})(option.action, option);
			game.addChild(optionText);
		}
	} else if (state === STATE_HOW_TO_PLAY) {
		setGuiText("How to Play\n\nThis is an audio-first adventure game.\n\nSingle tap to explore and hear descriptions.\nDouble tap to interact with objects.\nUse headphones for the best experience.\n\nThere's also a mysterious eye icon at the top...\nIt reveals where echoes hide, but true adventurers\ntrust their ears, not their eyes!\n\n(Double tap to return to menu)", 'vo_how_to_play_content');
	} else if (state === STATE_CREDITS) {
		setGuiText("Credits\n\nI am Juan and I wanted to create an \ninteractive audio story for the visually impaired first.\n\nDeveloped with accessibility in mind.\nThank you for playing!\n\n(Double tap to return to menu)", 'vo_credits_content');
	} else if (state === STATE_INTRO) {
		// Create a dedicated text object for the intro story with proper wrapping
		var introText = new Text2("Long ago, beneath the roots of the ancient world, a vault was sealed away—its halls forgotten, its treasures buried in silence. Many have tried to claim its secrets, but none have returned.\n\nYou, a Seeker of Echoes, are drawn by the voice of the Vault itself—a whisper only you can hear. It calls to you through stone and shadow, promising answers... if you can find the path.\n\nArmed with only your senses and your will, you step into the maze below...\n\n(Double tap to continue)", {
			size: 70,
			fill: 0xFFFFFF,
			wordWrap: true,
			wordWrapWidth: 1800,
			align: 'center'
		});
		introText.anchor.set(0.5, 0.5);
		introText.x = 2048 / 2;
		introText.y = 2732 / 2;
		game.addChild(introText);
		// Also set GUI text for voice-over
		setGuiText("", 'vo_loading_story');
		playNarration('narr_intro');
		// Wait for tap to continue instead of auto-advancing
	} else if (state === STATE_PUZZLE1) {
		isIntroPlaying = true;
		setGuiText("Darkness surrounds you.\nTap to feel your way forward.\nDouble tap when something stirs... it may be more than stone.", 'vo_dungeon_intro');
		playNarration('narr_puzzle1');
		// Clear intro playing flag after intro VO completes
		var introVO = LK.getSound('vo_dungeon_intro');
		var introDuration = 8000; // Estimated duration for intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
		}, introDuration);
		// Start dungeon background sounds
		LK.playMusic('dungeon_background_sounds');
		// Only create elements if they don't exist yet
		if (typeof puzzle1Elements === "undefined" || puzzle1Elements.length === 0) {
			puzzle1Step = 0;
			puzzle1Elements = [];
			chestOpened = false;
			hasKey = false;
			// Random positions for puzzle 1 elements
			var positions = [];
			// Generate random positions ensuring they don't overlap and are within bounds
			for (var p = 0; p < 2; p++) {
				var validPosition = false;
				var attempts = 0;
				while (!validPosition && attempts < 50) {
					var newX = 200 + Math.random() * (2048 - 400); // Keep away from edges
					var newY = 400 + Math.random() * (2732 - 800); // Keep away from edges and top menu
					// Check if position is far enough from existing positions
					var tooClose = false;
					for (var existingPos = 0; existingPos < positions.length; existingPos++) {
						var dx = newX - positions[existingPos].x;
						var dy = newY - positions[existingPos].y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						if (distance < 300) {
							// Minimum distance between elements
							tooClose = true;
							break;
						}
					}
					if (!tooClose) {
						positions.push({
							x: newX,
							y: newY
						});
						validPosition = true;
					}
					attempts++;
				}
				// Fallback if no valid position found after attempts
				if (!validPosition) {
					positions.push({
						x: 400 + p * 600,
						y: 800 + p * 400
					});
				}
			}
			// Key
			var key = new Key();
			key.x = positions[0].x;
			key.y = positions[0].y;
			game.addChild(key);
			puzzle1Elements.push(key);
			// Door
			var door = new Door();
			door.x = positions[1].x;
			door.y = positions[1].y;
			game.addChild(door);
			puzzle1Elements.push(door);
		}
	} else if (state === STATE_PUZZLE1_SUCCESS) {
		// Remove puzzle elements
		if (typeof puzzle1Elements !== "undefined") {
			for (var i = 0; i < puzzle1Elements.length; i++) {
				puzzle1Elements[i].destroy();
			}
		}
	} else if (state === STATE_PUZZLE2) {
		isIntroPlaying = true;
		setGuiText("A quiet chamber. \nFaint scents and the weight of stone surround you.", 'vo_puzzle2_intro');
		// Clear intro playing flag after intro VO completes
		var introVO = LK.getSound('vo_puzzle2_intro');
		var introDuration = 5000; // Estimated duration for intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
		}, introDuration);
		// Start dungeon background sounds
		LK.playMusic('dungeon_background_sounds');
		// Clear previous puzzle elements
		if (puzzle2Elements.length > 0) {
			for (var i = 0; i < puzzle2Elements.length; i++) {
				puzzle2Elements[i].destroy();
			}
		}
		puzzle2Elements = [];
		bothChestsOpened = false;
		hasVialOfClarity = false;
		trapActive = false;
		// Generate random positions for 2 chests and 1 door
		var positions = [];
		for (var p = 0; p < 3; p++) {
			var validPosition = false;
			var attempts = 0;
			while (!validPosition && attempts < 50) {
				var newX = 300 + Math.random() * (2048 - 600);
				var newY = 500 + Math.random() * (2732 - 1000);
				var tooClose = false;
				for (var existingPos = 0; existingPos < positions.length; existingPos++) {
					var dx = newX - positions[existingPos].x;
					var dy = newY - positions[existingPos].y;
					var distance = Math.sqrt(dx * dx + dy * dy);
					if (distance < 400) {
						tooClose = true;
						break;
					}
				}
				if (!tooClose) {
					positions.push({
						x: newX,
						y: newY
					});
					validPosition = true;
				}
				attempts++;
			}
			if (!validPosition) {
				positions.push({
					x: 400 + p * 500,
					y: 900 + p * 300
				});
			}
		}
		// Randomly assign which chest is Vial and which is Trap
		var vialChestIndex = Math.floor(Math.random() * 2);
		var trapChestIndex = 1 - vialChestIndex;
		// Create chests
		for (var c = 0; c < 2; c++) {
			var chest = new Chest();
			var isVial = c === vialChestIndex;
			var isTrap = c === trapChestIndex;
			chest.setup(c, isVial, isTrap);
			chest.x = positions[c].x;
			chest.y = positions[c].y;
			game.addChild(chest);
			puzzle2Elements.push(chest);
		}
		// Create stone door (always visible)
		var stoneDoor = new StoneDoor();
		stoneDoor.x = positions[2].x;
		stoneDoor.y = positions[2].y;
		stoneDoor.visible = true; // Always visible
		game.addChild(stoneDoor);
		puzzle2Elements.push(stoneDoor);
	} else if (state === STATE_PUZZLE2_SUCCESS) {
		// Clean up puzzle 2 elements
		if (puzzle2Elements.length > 0) {
			for (var i = 0; i < puzzle2Elements.length; i++) {
				puzzle2Elements[i].destroy();
			}
			puzzle2Elements = [];
		}
		// Stop annoying trap sound if running
		stopAnnoyingTrapSound();
		// Individual success narration for puzzle2
		setGuiText("A deep rumble. The door yields, revealing the unknown beyond.\n\n(Double tap to continue)", 'vo_puzzle2_success');
	} else if (state === STATE_PUZZLE3) {
		isIntroPlaying = true;
		setGuiText("Another chamber. \nYou sense a gate ahead, but something blocks it.", 'vo_puzzle3_intro');
		playNarration('vo_puzzle3_intro', {
			loop: false
		});
		// Clear intro playing flag after intro VO completes
		var introVO = LK.getSound('vo_puzzle3_intro');
		var introDuration = 5000; // Estimated duration for intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
		}, introDuration);
		// Start dungeon background sounds
		LK.playMusic('dungeon_background_sounds');
		// Don't clear puzzle 3 elements, just remove them from display if needed
		if (puzzle3Elements.length > 0) {
			for (var i = 0; i < puzzle3Elements.length; i++) {
				if (puzzle3Elements[i].parent) {
					puzzle3Elements[i].parent.removeChild(puzzle3Elements[i]);
				}
			}
		}
		// Only generate positions if elements don't exist yet
		if (puzzle3Elements.length === 0) {
			// Generate positions for puzzle 3 elements
			var positions = [];
			for (var p = 0; p < 3; p++) {
				var validPosition = false;
				var attempts = 0;
				while (!validPosition && attempts < 50) {
					var newX = 300 + Math.random() * (2048 - 600);
					var newY = 500 + Math.random() * (2732 - 1000);
					var tooClose = false;
					for (var existingPos = 0; existingPos < positions.length; existingPos++) {
						var dx = newX - positions[existingPos].x;
						var dy = newY - positions[existingPos].y;
						var distance = Math.sqrt(dx * dx + dy * dy);
						if (distance < 400) {
							tooClose = true;
							break;
						}
					}
					if (!tooClose) {
						positions.push({
							x: newX,
							y: newY
						});
						validPosition = true;
					}
					attempts++;
				}
				if (!validPosition) {
					positions.push({
						x: 400 + p * 500,
						y: 900 + p * 300
					});
				}
			}
			// Create gate
			puzzle3Gate = new Gate();
			puzzle3Gate.x = positions[0].x;
			puzzle3Gate.y = positions[0].y;
			game.addChild(puzzle3Gate);
			puzzle3Elements.push(puzzle3Gate);
			// Create floor tile
			puzzle3FloorTile = new FloorTile();
			puzzle3FloorTile.x = positions[1].x;
			puzzle3FloorTile.y = positions[1].y;
			puzzle3FloorTile.linkedGate = puzzle3Gate;
			game.addChild(puzzle3FloorTile);
			puzzle3Elements.push(puzzle3FloorTile);
			// Create heavy rock
			puzzle3Rock = new HeavyRock();
			puzzle3Rock.x = positions[2].x;
			puzzle3Rock.y = positions[2].y;
			// Store initial position to prevent unwanted position changes
			puzzle3Rock.initialX = positions[2].x;
			puzzle3Rock.lastX = positions[2].x; // Initialize lastX
			puzzle3Rock.initialY = positions[2].y;
			puzzle3Rock.lastY = positions[2].y; // Initialize lastY
			game.addChild(puzzle3Rock);
			puzzle3Elements.push(puzzle3Rock);
		} else {
			// Re-add existing elements to the game
			for (var i = 0; i < puzzle3Elements.length; i++) {
				game.addChild(puzzle3Elements[i]);
			}
		}
	} else if (state === STATE_PUZZLE3_SUCCESS) {
		// Clean up puzzle 3 elements
		if (puzzle3Elements.length > 0) {
			for (var i = 0; i < puzzle3Elements.length; i++) {
				puzzle3Elements[i].destroy();
			}
			puzzle3Elements = [];
		}
		setGuiText("The way ahead lies open. You leave the chamber behind...\n\n(Double tap to continue)", 'vo_puzzle3_success');
	} else if (state === STATE_PUZZLE4) {
		isIntroPlaying = true;
		setGuiText("You enter a chamber of whispered melodies.\nThree chimes hang before you, each with its own voice.\nListen to their songs... from the deepest tone to the highest.\nA simple harmony awaits.", 'vo_puzzle4_simple_intro');
		// Clear previous puzzle elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
		}
		puzzle4Elements = [];
		correctChimeSequence = [0, 1, 2]; // Simple sequence: low to high pitch
		playerChimeSequence = [];
		puzzle4Complete = false;
		// Create 3 chimes arranged horizontally
		var chimeWidth = 2048 / 3;
		for (var i = 0; i < 3; i++) {
			var chime = new Chime();
			chime.setup(i);
			chime.x = chimeWidth * i + chimeWidth / 2;
			chime.y = 2732 / 2;
			// Assign chime sounds - use chimes 1, 2, 3 for low to high progression
			chime.chimeSound = LK.getSound('sfx_chime_' + (i + 1));
			game.addChild(chime);
			puzzle4Elements.push(chime);
		}
		// Wait for puzzle 4 intro VO to complete before showing "The echoes guide you"
		var puzzle4IntroVO = LK.getSound('vo_puzzle4_simple_intro');
		var introDuration = 14000; // Estimated duration for the intro VO
		LK.setTimeout(function () {
			isIntroPlaying = false;
			setGuiText("The echoes guide you. Tap once to listen, \ntwice to step forward.", 'vo_repeat_sequence');
		}, introDuration);
	} else if (state === STATE_PUZZLE4_SUCCESS) {
		// Clean up puzzle 4 elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
			puzzle4Elements = [];
		}
		setGuiText("The gentle harmony resonates perfectly.\nYou've learned the language of the chimes.\nA passage opens to deeper mysteries.\n\n(Double tap to continue)", 'vo_puzzle4_simple_success');
	} else if (state === STATE_PUZZLE5) {
		isIntroPlaying = true;
		setGuiText("The room hums with delicate tension.\n Chimes sway in the unseen air.\n Some call to you gently, others pulse with danger. \n Listen carefully...", 'vo_puzzle5_intro');
		// Clear previous puzzle elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
		}
		puzzle4Elements = [];
		correctChimeSequence = [];
		playerChimeSequence = [];
		puzzle4Complete = false;
		// Create 4 chimes arranged horizontally
		var chimeWidth = 2048 / 4;
		for (var i = 0; i < 4; i++) {
			var chime = new Chime();
			chime.setup(i);
			chime.x = chimeWidth * i + chimeWidth / 2;
			chime.y = 2732 / 2;
			// Assign chime sounds
			chime.chimeSound = LK.getSound('sfx_chime_' + (i + 1));
			game.addChild(chime);
			puzzle4Elements.push(chime);
		}
		// Generate unique random sequence
		var availableChimes = [0, 1, 2, 3];
		for (var i = 0; i < 4; i++) {
			var randomIndex = Math.floor(Math.random() * availableChimes.length);
			var selectedChime = availableChimes.splice(randomIndex, 1)[0];
			correctChimeSequence.push(selectedChime);
		}
		// Wait for puzzle 5 intro VO to complete before playing chime sequence
		var puzzle5IntroVO = LK.getSound('vo_puzzle5_intro');
		var introDuration = 11000; // Estimated duration, adjust based on actual VO length
		LK.setTimeout(function () {
			isIntroPlaying = false;
			playChimeSequence();
		}, introDuration);
	} else if (state === STATE_PUZZLE5_SUCCESS) {
		// Clean up puzzle 5 elements
		if (puzzle4Elements.length > 0) {
			for (var i = 0; i < puzzle4Elements.length; i++) {
				puzzle4Elements[i].destroy();
			}
			puzzle4Elements = [];
		}
		setGuiText("Harmony at last. The chimes fall silent \n and a distant door creaks open.\n\n(Double tap to continue)", 'vo_puzzle5_success');
	}
	lockInput(800);
	// Create toggle button for tapCircle visibility (only when there are interactive elements)
	var hasInteractiveElements = state === STATE_PUZZLE1 && puzzle1Elements.length > 0 || state === STATE_PUZZLE2 && puzzle2Elements.length > 0 || state === STATE_PUZZLE3 && puzzle3Elements.length > 0 || state === STATE_PUZZLE4 && puzzle4Elements.length > 0 || state === STATE_PUZZLE5 && puzzle4Elements.length > 0;
	if (state !== STATE_HEADPHONES && hasInteractiveElements) {
		tapCircleToggle = new Container();
		var eyeIcon = tapCircleToggle.attachAsset(tapCirclesVisible ? 'eyeOpen' : 'eyeClosed', {
			anchorX: 0.5,
			anchorY: 0.5
		});
		tapCircleToggle.interactive = true;
		tapCircleToggle.buttonMode = true;
		// Add double-tap tracking properties
		tapCircleToggle.lastTapTime = 0;
		tapCircleToggle.pendingVOTimeout = null;
		tapCircleToggle.down = function () {
			var now = Date.now();
			var timeSinceLastTap = now - tapCircleToggle.lastTapTime;
			// Check if this is a double tap
			if (timeSinceLastTap < 500 && timeSinceLastTap > 50) {
				// This is a double tap - toggle visibility
				// Cancel any pending single-tap voice-over
				if (tapCircleToggle.pendingVOTimeout) {
					LK.clearTimeout(tapCircleToggle.pendingVOTimeout);
					tapCircleToggle.pendingVOTimeout = null;
				}
				// Stop any current voice-over
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				tapCirclesVisible = !tapCirclesVisible;
				// Update the eye icon
				tapCircleToggle.removeChildren();
				var newEyeIcon = tapCircleToggle.attachAsset(tapCirclesVisible ? 'eyeOpen' : 'eyeClosed', {
					anchorX: 0.5,
					anchorY: 0.5
				});
				// Update visibility of all tapCircles only
				updateTapCircleVisibility();
				tapCircleToggle.lastTapTime = 0; // Reset to prevent triple taps
			} else {
				// This is a single tap - show hint message
				// Cancel any existing pending voice-over
				if (tapCircleToggle.pendingVOTimeout) {
					LK.clearTimeout(tapCircleToggle.pendingVOTimeout);
					tapCircleToggle.pendingVOTimeout = null;
				}
				// Stop any current voice-over
				if (currentVoiceOver) {
					currentVoiceOver.stop();
					currentVoiceOver = null;
				}
				// Delay the announce to check if it's actually a double-tap
				tapCircleToggle.pendingVOTimeout = LK.setTimeout(function () {
					setGuiText("Psst... the hint icon shows the secrets.\nReal explorers never touch it.", 'vo_hint_icon_message');
					tapCircleToggle.pendingVOTimeout = null;
				}, 300); // Wait 300ms to see if second tap comes
				tapCircleToggle.lastTapTime = now;
			}
		};
		LK.gui.top.addChild(tapCircleToggle);
		tapCircleToggle.y = 100; // Move toggle button 100 pixels down
	}
}
// --- Puzzle 1 Logic ---
// For MVP: Simple puzzle, e.g. "Tap twice to continue"
function handlePuzzle1Tap() {
	if (puzzle1Step === 0) {
		// First tap
		puzzle1Step = 1;
		LK.getSound('sfx_tap').play();
		// Optionally, play a short instruction or feedback
		setGuiText("Good! Tap again to solve the puzzle.");
		lockInput(600);
	} else if (puzzle1Step === 1) {
		// Second tap, puzzle solved
		LK.getSound('sfx_success').play();
		goToState(STATE_PUZZLE1_SUCCESS);
	}
}
// --- Input Handling ---
// Visual tap feedback for sighted users
function showTapFeedback(x, y) {
	var tapFx = new TapFeedback();
	tapFx.showAt(x, y);
	game.addChild(tapFx);
}
// Main tap handler
game.down = function (x, y, obj) {
	if (inputLocked || isIntroPlaying) {
		return;
	}
	// Check if this is a double-tap at game level
	var now = Date.now();
	var timeSinceLastTap = now - lastTapTime;
	var tapDistance = Math.sqrt((x - lastTapX) * (x - lastTapX) + (y - lastTapY) * (y - lastTapY));
	var isDoubleTap = timeSinceLastTap < doubleTapThreshold && timeSinceLastTap > 50 && tapDistance < doubleTapDistanceThreshold;
	// First check if tap is on an interactive element
	var tappedElement = null;
	var tapOnElement = false;
	// Check puzzle 1 elements
	if (currentState === STATE_PUZZLE1 && typeof puzzle1Elements !== "undefined") {
		for (var i = 0; i < puzzle1Elements.length; i++) {
			var el = puzzle1Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				tappedElement = el;
				tapOnElement = true;
				break;
			}
		}
	}
	// Check puzzle 2 elements
	if (currentState === STATE_PUZZLE2 && typeof puzzle2Elements !== "undefined") {
		for (var i = 0; i < puzzle2Elements.length; i++) {
			var el = puzzle2Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				tappedElement = el;
				tapOnElement = true;
				break;
			}
		}
	}
	// Check puzzle 3 elements
	if (currentState === STATE_PUZZLE3 && typeof puzzle3Elements !== "undefined") {
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				tappedElement = el;
				tapOnElement = true;
				break;
			}
		}
	}
	// If tapping on an element, let the element handle voice-over management
	// Only stop voice-over if NOT tapping on an element, or if it's a single tap on non-interactive area
	if (currentVoiceOver && !tapOnElement) {
		currentVoiceOver.stop();
		currentVoiceOver = null;
	}
	// Update last tap info
	lastTapTime = now;
	lastTapX = x;
	lastTapY = y;
	showTapFeedback(x, y);
	if (currentState === STATE_HEADPHONES) {
		if (isDoubleTap) {
			goToState(STATE_MENU);
		}
	} else if (currentState === STATE_MENU) {
		// Menu taps are handled by individual menu option elements
	} else if (currentState === STATE_HOW_TO_PLAY) {
		if (isDoubleTap) {
			goToState(STATE_MENU);
		}
	} else if (currentState === STATE_CREDITS) {
		if (isDoubleTap) {
			goToState(STATE_MENU);
		}
	} else if (currentState === STATE_INTRO) {
		// Double tap to continue from intro to puzzle
		if (isDoubleTap) {
			goToState(STATE_PUZZLE1);
		}
	} else if (currentState === STATE_PUZZLE1) {
		// Route tap to nearest element if within range, else generic feedback
		var tapped = false;
		if (typeof puzzle1Elements !== "undefined") {
			for (var i = 0; i < puzzle1Elements.length; i++) {
				var el = puzzle1Elements[i];
				var dx = x - el.x;
				var dy = y - el.y;
				var dist = Math.sqrt(dx * dx + dy * dy);
				if (dist < el.hitRadius) {
					// within tapCircle radius
					if (typeof el.down === "function") {
						el.down(x, y, obj);
						tapped = true;
						break;
					}
				}
			}
		}
		if (!tapped) {
			// Pool of random 'nothing here' phrases and their corresponding voice-over asset ids
			var nothingHerePhrasesVO = [{
				text: "Just cold stone beneath your fingers.",
				vo: "vo_nothing_cold_stone"
			}, {
				text: "Nothing unusual here.",
				vo: "vo_nothing_unusual"
			}, {
				text: "Rough wall. Nothing of interest.",
				vo: "vo_nothing_rough_wall"
			}, {
				text: "Only silence greets you.",
				vo: "vo_nothing_silence"
			}, {
				text: "You reach out… and find nothing new.",
				vo: "vo_nothing_reach_out"
			}, {
				text: "Your hand brushes empty air.",
				vo: "vo_nothing_brush_air"
			}, {
				text: "It’s quiet here. Too quiet.",
				vo: "vo_nothing_quiet"
			}, {
				text: "Nothing but shadows.",
				vo: "vo_nothing_shadows"
			}, {
				text: "This spot feels empty.",
				vo: "vo_nothing_spot_empty"
			}, {
				text: "Just part of the dungeon wall.",
				vo: "vo_nothing_dungeon_wall"
			}, {
				text: "You feel around, but there’s nothing here.",
				vo: "vo_nothing_feel_around"
			}, {
				text: "Only old stone. Try elsewhere.",
				vo: "vo_nothing_old_stone"
			}];
			// Pick a random phrase and its voice-over
			var idx = Math.floor(Math.random() * nothingHerePhrasesVO.length);
			setGuiText(nothingHerePhrasesVO[idx].text, nothingHerePhrasesVO[idx].vo);
		}
	} else if (currentState === STATE_PUZZLE1_SUCCESS) {
		// Double tap to continue to puzzle 2
		if (isDoubleTap) {
			goToState(STATE_PUZZLE2);
		}
	} else if (currentState === STATE_PUZZLE2) {
		// Route tap to nearest element
		var tapped = false;
		for (var i = 0; i < puzzle2Elements.length; i++) {
			var el = puzzle2Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			// Pool of random 'nothing here' phrases and their corresponding voice-over asset ids
			var nothingHerePhrasesVO = [{
				text: "Just cold stone beneath your fingers.",
				vo: "vo_nothing_cold_stone"
			}, {
				text: "Nothing unusual here.",
				vo: "vo_nothing_unusual"
			}, {
				text: "Rough wall. Nothing of interest.",
				vo: "vo_nothing_rough_wall"
			}, {
				text: "Only silence greets you.",
				vo: "vo_nothing_silence"
			}, {
				text: "You reach out… and find nothing new.",
				vo: "vo_nothing_reach_out"
			}, {
				text: "Your hand brushes empty air.",
				vo: "vo_nothing_brush_air"
			}, {
				text: "It’s quiet here. Too quiet.",
				vo: "vo_nothing_quiet"
			}, {
				text: "Nothing but shadows.",
				vo: "vo_nothing_shadows"
			}, {
				text: "This spot feels empty.",
				vo: "vo_nothing_spot_empty"
			}, {
				text: "Just part of the dungeon wall.",
				vo: "vo_nothing_dungeon_wall"
			}, {
				text: "You feel around, but there’s nothing here.",
				vo: "vo_nothing_feel_around"
			}, {
				text: "Only old stone. Try elsewhere.",
				vo: "vo_nothing_old_stone"
			}];
			// Pick a random phrase and its voice-over
			var idx = Math.floor(Math.random() * nothingHerePhrasesVO.length);
			setGuiText(nothingHerePhrasesVO[idx].text, nothingHerePhrasesVO[idx].vo);
		}
	} else if (currentState === STATE_PUZZLE2_SUCCESS) {
		// Double tap to continue to puzzle 3
		if (isDoubleTap) {
			goToState(STATE_PUZZLE3);
		}
	} else if (currentState === STATE_PUZZLE3) {
		// Route tap to nearest element
		var tapped = false;
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			// Random 'nothing here' feedback
			var nothingHerePhrasesVO = [{
				text: "Just cold stone beneath your fingers.",
				vo: "vo_nothing_cold_stone"
			}, {
				text: "Nothing unusual here.",
				vo: "vo_nothing_unusual"
			}, {
				text: "Rough wall. Nothing of interest.",
				vo: "vo_nothing_rough_wall"
			}, {
				text: "Only silence greets you.",
				vo: "vo_nothing_silence"
			}, {
				text: "You reach out… and find nothing new.",
				vo: "vo_nothing_reach_out"
			}];
			var idx = Math.floor(Math.random() * nothingHerePhrasesVO.length);
			setGuiText(nothingHerePhrasesVO[idx].text, nothingHerePhrasesVO[idx].vo);
		}
	} else if (currentState === STATE_PUZZLE3_SUCCESS) {
		// Double tap to continue to puzzle 4
		if (isDoubleTap) {
			goToState(STATE_PUZZLE4);
		}
	} else if (currentState === STATE_PUZZLE4) {
		// Route tap to chimes
		var tapped = false;
		for (var i = 0; i < puzzle4Elements.length; i++) {
			var el = puzzle4Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			setGuiText("The chimes hang in stillness… \naligned at the heart of the chamber....", 'vo_tap_chimes_hint');
		}
	} else if (currentState === STATE_PUZZLE4_SUCCESS) {
		// Double tap to continue to puzzle 5
		if (isDoubleTap) {
			goToState(STATE_PUZZLE5);
		}
	} else if (currentState === STATE_PUZZLE5) {
		// Route tap to chimes
		var tapped = false;
		for (var i = 0; i < puzzle4Elements.length; i++) {
			var el = puzzle4Elements[i];
			var dx = x - el.x;
			var dy = y - el.y;
			var dist = Math.sqrt(dx * dx + dy * dy);
			if (dist < el.hitRadius) {
				if (typeof el.down === "function") {
					el.down(x, y, obj);
					tapped = true;
					break;
				}
			}
		}
		if (!tapped) {
			setGuiText("The chimes hang in stillness… \naligned at the heart of the chamber....", 'vo_tap_chimes_hint');
		}
	} else if (currentState === STATE_PUZZLE5_SUCCESS) {
		// Double tap to show game complete message or game win
		if (isDoubleTap) {
			if (!gameCompleteShown) {
				// First double tap - show game complete message
				gameCompleteShown = true;
				setGuiText("You walk through the final door, \n leaving the last chamber behind.\nThe sounds of the Vault fall quiet, the puzzles now at rest.\n\nFor now, your journey ends.\nBut the Vault is old… and its echoes are never truly silent.\n\n(Double tap to complete)", 'vo_game_complete');
			} else {
				// Second double tap - show game win
				LK.showYouWin();
			}
		}
	}
};
// Prevent drag/hold from causing issues
game.move = function (x, y, obj) {
	// Handle rock dragging in puzzle 3
	if (currentState === STATE_PUZZLE3 && puzzle3Rock && puzzle3Rock.isDragging) {
		puzzle3Rock.handleDrag(x, y, obj);
	}
};
game.up = function (x, y, obj) {
	// Route up event to elements for hold detection
	if (currentState === STATE_PUZZLE1) {
		if (typeof puzzle1Elements !== "undefined") {
			for (var i = 0; i < puzzle1Elements.length; i++) {
				var el = puzzle1Elements[i];
				if (typeof el.up === "function" && el.isHolding) {
					el.up(x, y, obj);
				}
			}
		}
	} else if (currentState === STATE_PUZZLE3) {
		// Always stop rock dragging on any finger lift
		if (puzzle3Rock && puzzle3Rock.isDragging) {
			puzzle3Rock.isDragging = false;
			// Check if rock is on the floor tile
			if (puzzle3FloorTile && puzzle3Rock.intersects(puzzle3FloorTile)) {
				puzzle3FloorTile.pressWithRock();
			} else if (puzzle3FloorTile && puzzle3FloorTile.isPressedByRock) {
				// Rock was moved off the tile
				puzzle3FloorTile.releaseFromRock();
			}
		}
		// Handle release for puzzle 3 elements
		for (var i = 0; i < puzzle3Elements.length; i++) {
			var el = puzzle3Elements[i];
			if (typeof el.up === "function") {
				// Check if release is near the element
				var dx = x - el.x;
				var dy = y - el.y;
				var dist = Math.sqrt(dx * dx + dy * dy);
				if (dist < el.hitRadius || el.isPressed) {
					el.up(x, y, obj);
				}
			}
		}
		// Do not automatically complete puzzle when rock is on tile
		// Player must double-tap the open gate to proceed
	}
};
// --- Game Start ---
goToState(STATE_HEADPHONES);
screen_headphones_voice
Sound effect
vo_menu_title
Sound effect
vo_loading_story
Sound effect
vo_dungeon_intro
Sound effect
vo_nothing_cold_stone
Sound effect
vo_nothing_unusual
Sound effect
vo_nothing_rough_wall
Sound effect
vo_nothing_silence
Sound effect
vo_nothing_reach_out
Sound effect
vo_nothing_brush_air
Sound effect
vo_nothing_quiet
Sound effect
vo_nothing_shadows
Sound effect
vo_nothing_spot_empty
Sound effect
vo_nothing_dungeon_wall
Sound effect
vo_nothing_feel_around
Sound effect
vo_nothing_old_stone
Sound effect
vo_stone_door_label
Sound effect
vo_key_label
Sound effect
vo_key_pickup
Sound effect
vo_door_label
Sound effect
vo_door_unlocked
Sound effect
vo_puzzle2_intro
Sound effect
vo_vial_inspect
Sound effect
vo_vial_found
Sound effect
vo_vial_heals_trap
Sound effect
vo_trap_inspect
Sound effect
vo_trap_triggered
Sound effect
vo_trap_neutralized
Sound effect
dungeon_background_sounds
Music
vo_door_locked
Sound effect
sfx_trap_trigger
Music
sfx_door_unlock
Sound effect
sfx_key_pickup
Sound effect
sfx_chest_open
Sound effect
vial_chest_open_bgm
Music
vo_puzzle2_success
Sound effect
sfx_rock_door_rumble
Sound effect
vo_puzzle3_intro
Sound effect
vo_heavy_rock_label
Sound effect
vo_rock_drag_hint
Sound effect
vo_rock_door_rumble
Sound effect
sfx_rock_drag
Sound effect
vo_gate_label
Sound effect
vo_floor_tile_label
Sound effect
vo_gate_closed
Sound effect
vo_menu_intro
Sound effect
vo_menu_how_to_play
Sound effect
vo_menu_credits
Sound effect
vo_how_to_play_content
Sound effect
vo_credits_content
Sound effect
vo_menu_title_announce
Sound effect
vo_stone_door_locked
Sound effect
sfx_chime_1
Sound effect
sfx_chime_2
Sound effect
sfx_chime_3
Sound effect
sfx_chime_4
Sound effect
vo_chime_description_1
Sound effect
vo_chime_description_2
Sound effect
vo_chime_description_3
Sound effect
vo_chime_description_4
Sound effect
vo_chime_instruction
Sound effect
vo_puzzle4_intro
Sound effect
vo_tap_chimes_hint
Sound effect
vo_puzzle4_wrong
Sound effect
vo_repeat_sequence
Sound effect
vo_puzzle4_success
Sound effect
vo_tile_double_tap
Sound effect
vo_rock_on_tile
Sound effect
sfx_rock_place
Sound effect
vo_gate_open_announce
Sound effect
vo_puzzle5_wrong
Sound effect
vo_puzzle5_intro
Sound effect
vo_puzzle4_simple_intro
Sound effect
vo_puzzle4_simple_success
Sound effect
vo_tile_springs_back
Sound effect
vo_chime_general_description
Sound effect
vo_puzzle3_success
Sound effect
vo_puzzle5_success
Sound effect
vo_game_complete
Sound effect
vo_hint_icon_message
Sound effect