/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	language: "en"
});
/**** 
* Classes
****/ 
// Note types: 'tap', 'hold', 'swipe'
var Note = Container.expand(function () {
	var self = Container.call(this);
	self.type = 'tap'; // tap, hold, swipe
	self.lane = 0; // 0-3
	self.time = 0; // ms, when the note should be hit
	self.duration = 0; // for hold notes, ms
	self.hit = false;
	self.active = true;
	self.swipeDir = null; // 'left', 'right', 'up', 'down' for swipe notes
	// Visuals
	self.noteAsset = null;
	self.trailAsset = null;
	self.init = function (type, lane, time, duration, swipeDir) {
		self.type = type;
		self.lane = lane;
		self.time = time;
		self.duration = duration || 0;
		self.swipeDir = swipeDir || null;
		self.hit = false;
		self.active = true;
		if (self.noteAsset) {
			self.removeChild(self.noteAsset);
		}
		if (self.trailAsset) {
			self.removeChild(self.trailAsset);
		}
		if (type === 'tap') {
			self.noteAsset = self.attachAsset('tapNote', {
				anchorX: 0.5,
				anchorY: 0.5
			});
		} else if (type === 'hold') {
			self.noteAsset = self.attachAsset('holdNote', {
				anchorX: 0.5,
				anchorY: 0.5,
				scaleX: 1.6,
				// Make hold notes bigger
				scaleY: 1.6
			});
		} else if (type === 'swipe') {
			// Calculate scale to ensure swipe notes cross two lanes
			var laneSpacing = 2048 / 4; // 4 lanes
			var swipeWidth = laneSpacing * 2.2; // Cross 2+ lanes
			self.noteAsset = self.attachAsset('swipeNote', {
				anchorX: 0.5,
				anchorY: 0.5,
				scaleX: swipeWidth / 120,
				// Scale based on original width of 120px
				scaleY: 2.2 // Make swipe notes taller too
			});
		}
	};
	self.flash = function () {
		if (self.noteAsset) {
			tween(self.noteAsset, {
				alpha: 0.2
			}, {
				duration: 80,
				onFinish: function onFinish() {
					tween(self.noteAsset, {
						alpha: 1
					}, {
						duration: 80
					});
				}
			});
		}
	};
	self.update = function () {
		// Position is handled by main game loop
	};
	return self;
});
// Score popup for visual feedback
var ScorePopup = Container.expand(function () {
	var self = Container.call(this);
	self.scoreText = null;
	self.startTime = 0;
	self.duration = 1000;
	self.init = function (scoreType, x, y) {
		self.x = x;
		self.y = y;
		self.startTime = Date.now();
		var text = "";
		var color = "#fff";
		var scoreValue = 0;
		switch (scoreType) {
			case 'perfect':
				text = "PERFECT";
				color = "#FFD700";
				scoreValue = 150;
				break;
			case 'great':
				text = "GREAT";
				color = "#00FF00";
				scoreValue = 120;
				break;
			case 'good':
				text = "GOOD";
				color = "#FFFF00";
				scoreValue = 80;
				break;
			case 'bad':
				text = "BAD";
				color = "#FFA500";
				scoreValue = 40;
				break;
			case 'miss':
				text = "MISS";
				color = "#FF0000";
				scoreValue = 0;
				break;
		}
		self.scoreText = new Text2(text + "\n+" + scoreValue, {
			size: 60,
			fill: color,
			align: "center"
		});
		self.scoreText.anchor.set(0.5, 0.5);
		self.addChild(self.scoreText);
		// Animate popup
		tween(self, {
			y: self.y - 100,
			alpha: 0
		}, {
			duration: self.duration,
			easing: tween.easeOut,
			onFinish: function onFinish() {
				if (self.parent) {
					self.parent.removeChild(self);
				}
			}
		});
	};
	return self;
});
// Song selection card
var SongCard = Container.expand(function () {
	var self = Container.call(this);
	self.songId = '';
	self.cover = null;
	self.init = function (songId, colorAsset) {
		self.songId = songId;
		if (self.cover) {
			self.removeChild(self.cover);
		}
		if (colorAsset) {
			self.cover = self.attachAsset(colorAsset, {
				anchorX: 0.5,
				anchorY: 0.5
			});
		}
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x181c20
});
/**** 
* Game Code
****/ 
// --- GLOBALS ---
// Note: Assets are auto-initialized by LK based on usage below.
// We'll use shapes for notes, and images for backgrounds and song covers as needed.
// Example note assets:
// Sounds and music (placeholders, actual music not implemented here)
var _templateObject;
function _taggedTemplateLiteral(e, t) {
	return t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, {
		raw: {
			value: Object.freeze(t)
		}
	}));
}
var gameplayBackground = null; // Track the current gameplay background
var GAME_STATE = 'LANGUAGE'; // LANGUAGE, HOME, STORY, DIFFICULTY, PLAY, RESULT
var selectedLanguage = storage.language || 'en';
var selectedSong = null;
var selectedDifficulty = null;
var currentSongData = null;
var currentNotes = [];
var noteIndex = 0;
var startTime = 0;
var score = 0;
var combo = 0;
var maxCombo = 0;
var accuracy = 0;
var totalNotes = 0;
var hitNotes = 0;
var missNotes = 0;
var badNotes = 0;
var goodNotes = 0;
var greatNotes = 0;
var perfectNotes = 0;
var holdActive = null;
var swipeStart = null;
var swipeNote = null;
var isPlaying = false;
var resultTimeout = null;
var scorePopups = [];
// Scoring thresholds in milliseconds
var PERFECT_WINDOW = 50;
var GREAT_WINDOW = 100;
var GOOD_WINDOW = 150;
var BAD_WINDOW = 200;
// Lanes: 4 lanes, evenly spaced
var LANE_COUNT = 4;
var LANE_WIDTH = 400;
var LANE_MARGIN = 40;
var LANE_START_X = (2048 - (LANE_COUNT * LANE_WIDTH + (LANE_COUNT - 1) * LANE_MARGIN)) / 2;
var HIT_LINE_Y = 2200; // Where notes should be hit
// --- SONG DATA (MVP: 3 songs, 3 difficulties, simple patterns) ---
var SONGS = [{
	id: 'UnknownJourney',
	title: 'Unknown Journey',
	title_zh: '莫问前程',
	cover: 'songCover4',
	duration: 240000,
	story: {
		en: "A song that mix Chinese traditional music with dark ambient music style, depicting the swordswoman making peace with her dark past while maintaining her courage to explore the mysterious and unknown journey.",
		zh: "一首混合中华传统音乐与黑暗氛围风格的歌曲,展现出女侠对于黑暗过去的释怀,保持对探索神秘未知前程的勇气。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Unknown Journey Easy: Continuous tap flow, 120 BPM (500ms/beat), 4/4, 240s = 480 beats
		// Intro (first 8 beats, spaced)
		['tap', 0, 2000], ['tap', 1, 3000], ['tap', 2, 4000], ['tap', 3, 5000], ['tap', 0, 6000], ['tap', 1, 7000], ['tap', 2, 8000], ['tap', 3, 9000]
		// Main section: every 500ms, alternate lanes, full song
		// 480 beats, start at 10000ms, end at 240000ms
		].concat(function () {
			var arr = [];
			var t = 10000;
			var lane = 0;
			for (; t <= 238000; t += 500) {
				arr.push(['tap', lane, t]);
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1000ms)
			arr.push(['tap', 0, 239000]);
			arr.push(['tap', 1, 240000]);
			return arr;
		}()),
		Medium: [
		// Unknown Journey Medium: More density, add some holds and swipes, 120 BPM (500ms/beat)
		// Intro (first 8 beats, spaced)
		['tap', 0, 1500], ['tap', 1, 2500], ['hold', 2, 3500, 1200], ['tap', 3, 5000], ['tap', 0, 6500], ['tap', 1, 8000], ['hold', 2, 9500, 1200], ['tap', 3, 11000]
		// Main flow: every 500ms, alternate lanes, add swipes every 4th beat, holds every 8th beat
		].concat(function () {
			var arr = [];
			var t = 12000;
			var lane = 0;
			for (var i = 0; t <= 238000; t += 500, i++) {
				if (i % 8 === 4) {
					// Every 8th beat, add a hold
					arr.push(['hold', lane, t, 1000]);
				} else if (i % 4 === 2) {
					// Every 4th beat, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 4 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1000ms)
			arr.push(['tap', 0, 239000]);
			arr.push(['tap', 1, 240000]);
			return arr;
		}()),
		Hard: [
		// Unknown Journey Hard: High density, more holds and swipes, 120 BPM (500ms/beat), 16th notes (250ms), cross-lane
		// Intro (first 8 beats, 250ms apart)
		['tap', 0, 1000], ['tap', 1, 1250], ['tap', 2, 1500], ['tap', 3, 1750], ['tap', 0, 2000], ['tap', 1, 2250], ['tap', 2, 2500], ['tap', 3, 2750]
		// Main flow: every 250ms, alternate lanes, add swipes every 8th, holds every 16th
		].concat(function () {
			var arr = [];
			var t = 3000;
			var lane = 0;
			for (var i = 0; t <= 239000; t += 250, i++) {
				if (i % 16 === 8) {
					// Every 16th, add a hold
					arr.push(['hold', lane, t, 900]);
				} else if (i % 8 === 4) {
					// Every 8th, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 8 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 500ms)
			arr.push(['tap', 0, 239500]);
			arr.push(['tap', 1, 240000]);
			return arr;
		}())
	}
},
// --- New Era Malay Style song entry ---
{
	id: 'NewEraMalayStyle',
	title: 'New Era Malay Style',
	title_zh: '新纪元马来风',
	cover: 'songCoverNewEraMalayStyle',
	duration: 180000,
	story: {
		en: "A vibrant fusion of Malay traditional melodies and modern beats, celebrating the spirit of a new era.",
		zh: "融合马来传统旋律与现代节奏,展现新时代的活力与精神。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Simple tap pattern, 100 BPM (600ms/beat), 3/4, 180s = 300 beats
		// Intro (first 4 beats, spaced)
		['tap', 0, 2000], ['tap', 1, 3200], ['tap', 2, 4400], ['tap', 3, 5600]].concat(function () {
			var arr = [];
			var t = 7000;
			var lane = 0;
			for (; t <= 178000; t += 600) {
				arr.push(['tap', lane, t]);
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1200ms)
			arr.push(['tap', 0, 179200]);
			arr.push(['tap', 1, 180000]);
			return arr;
		}()),
		Medium: [
		// More density, add some holds and swipes, 100 BPM (600ms/beat)
		// Intro (first 4 beats, spaced)
		['tap', 0, 1500], ['tap', 1, 2700], ['hold', 2, 3900, 1200], ['tap', 3, 5600]].concat(function () {
			var arr = [];
			var t = 7000;
			var lane = 0;
			for (var i = 0; t <= 178000; t += 600, i++) {
				if (i % 8 === 4) {
					// Every 8th beat, add a hold
					arr.push(['hold', lane, t, 1000]);
				} else if (i % 4 === 2) {
					// Every 4th beat, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 4 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1200ms)
			arr.push(['tap', 0, 179200]);
			arr.push(['tap', 1, 180000]);
			return arr;
		}()),
		Hard: [
		// High density, more holds and swipes, 100 BPM (600ms/beat), 16th notes (300ms), cross-lane
		// Intro (first 8 beats, 300ms apart)
		['tap', 0, 1000], ['tap', 1, 1300], ['tap', 2, 1600], ['tap', 3, 1900], ['tap', 0, 2200], ['tap', 1, 2500], ['tap', 2, 2800], ['tap', 3, 3100]].concat(function () {
			var arr = [];
			var t = 3400;
			var lane = 0;
			for (var i = 0; t <= 179000; t += 300, i++) {
				if (i % 16 === 8) {
					// Every 16th, add a hold
					arr.push(['hold', lane, t, 900]);
				} else if (i % 8 === 4) {
					// Every 8th, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 8 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 600ms)
			arr.push(['tap', 0, 179600]);
			arr.push(['tap', 1, 180000]);
			return arr;
		}())
	}
}, {
	id: 'SkyJourney',
	title: 'Sky Journey',
	cover: 'songCover1',
	duration: 180000,
	// 3 minutes full song
	story: {
		en: "You soar above the clouds, chasing the horizon. The sky is endless, and your journey has just begun.",
		zh: "你在云端翱翔,追逐地平线。天空无垠,你的旅程才刚刚开始。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Full song analysis: Sky Journey - Uplifting 4/4 tempo, soaring melodies, 120 BPM (500ms per beat, 125ms per 16th note)
		// Intro - soft atmospheric entrance
		['tap', 1, 2000], ['tap', 2, 3000], ['tap', 0, 4000], ['tap', 3, 5000],
		// Verse 1 - building energy
		['tap', 2, 9000], ['tap', 3, 10000], ['tap', 1, 12500], ['tap', 2, 14000], ['tap', 0, 18000],
		// Chorus 1 - soaring melody
		['tap', 0, 22000], ['tap', 3, 24000], ['tap', 2, 30000],
		// Bridge - melodic interlude
		['tap', 1, 35000], ['tap', 2, 36500], ['tap', 0, 38000], ['tap', 0, 45000],
		// Verse 2 - increased complexity
		['tap', 3, 47000], ['tap', 2, 52500], ['tap', 1, 57000], ['tap', 2, 59000], ['tap', 0, 64000],
		// Chorus 2 - full energy
		['tap', 0, 68000], ['tap', 3, 70000], ['tap', 2, 76500],
		// Final section - climactic ending
		['tap', 1, 82000], ['tap', 2, 84000], ['tap', 3, 89500], ['tap', 0, 95000],
		// Outro - graceful conclusion
		['tap', 1, 102000], ['tap', 2, 105000], ['tap', 2, 118000], ['tap', 0, 122000], ['tap', 3, 126000],
		// Extended ending with cross-lane patterns
		['tap', 3, 138000], ['tap', 1, 142000], ['tap', 1, 156000], ['tap', 2, 160000], ['tap', 3, 164000]],
		Medium: [
		// Increased complexity with eighth note patterns and more frequent notes
		['tap', 1, 1500], ['tap', 2, 2250], ['tap', 0, 3000], ['tap', 3, 3750], ['hold', 1, 4500, 1500], ['tap', 2, 6750], ['tap', 3, 7500], ['swipe', 0, 8250, 0, 'left'], ['swipe', 1, 8250, 0, 'left'], ['tap', 2, 9000], ['tap', 0, 10500], ['hold', 3, 12000, 1200], ['tap', 1, 14250], ['tap', 2, 15750], ['swipe', 0, 17250, 0, 'right'], ['swipe', 3, 17250, 0, 'right'], ['tap', 1, 18750], ['tap', 2, 20250], ['hold', 0, 21750, 2000], ['tap', 3, 24750], ['swipe', 1, 26250, 0, 'up'], ['swipe', 2, 26250, 0, 'up'], ['tap', 0, 28500], ['tap', 3, 30000], ['hold', 1, 31500, 1800], ['tap', 2, 34500], ['tap', 0, 36000], ['swipe', 3, 37500, 0, 'down'], ['swipe', 1, 37500, 0, 'down'], ['tap', 2, 39000], ['hold', 0, 40500, 1500], ['tap', 3, 43500], ['tap', 1, 45000], ['swipe', 2, 46500, 0, 'left'], ['swipe', 0, 46500, 0, 'left'], ['hold', 3, 48000, 2250], ['tap', 1, 51750], ['tap', 2, 53250], ['tap', 0, 54750], ['swipe', 1, 56250, 0, 'right'], ['swipe', 3, 56250, 0, 'right'], ['hold', 2, 58500, 2000], ['tap', 0, 62250], ['tap', 1, 64500], ['swipe', 3, 66750, 0, 'up'], ['swipe', 2, 66750, 0, 'up'], ['hold', 0, 69000, 2500], ['tap', 1, 73125], ['tap', 3, 75375], ['tap', 2, 77625], ['swipe', 0, 79875, 0, 'down'], ['swipe', 1, 79875, 0, 'down'], ['hold', 3, 82125, 1750], ['tap', 2, 85875], ['tap', 0, 88125], ['swipe', 1, 90375, 0, 'left'], ['swipe', 3, 90375, 0, 'left'], ['hold', 2, 92625, 3000], ['tap', 0, 97125], ['tap', 1, 100125], ['tap', 3, 103125], ['swipe', 2, 106125, 0, 'right'], ['swipe', 0, 106125, 0, 'right'], ['hold', 1, 109125, 2750], ['tap', 3, 113625], ['tap', 2, 117375], ['swipe', 0, 121125, 0, 'up'], ['swipe', 1, 121125, 0, 'up'], ['hold', 3, 124875, 3250], ['tap', 2, 130125], ['tap', 0, 134625], ['tap', 1, 139125], ['swipe', 3, 143625, 0, 'down'], ['swipe', 2, 143625, 0, 'down'], ['hold', 0, 148125, 4000], ['tap', 1, 154125], ['tap', 3, 159375], ['swipe', 2, 164625, 0, 'left'], ['swipe', 0, 164625, 0, 'left'], ['hold', 1, 169875, 4500], ['tap', 3, 176375], ['tap', 2, 180000]],
		Hard: [
		// Sky Journey Hard - full song, continuous tempo flow, player-friendly density
		// 120 BPM = 500ms/beat, 16th = 125ms, 8th = 250ms, 4/4
		// Intro (continuous 8ths, gentle)
		['tap', 1, 2000], ['tap', 2, 2500], ['tap', 0, 3000], ['tap', 3, 3500], ['tap', 1, 4000], ['tap', 2, 4500], ['tap', 0, 5000], ['tap', 3, 5500], ['tap', 1, 6000], ['tap', 2, 6500], ['tap', 0, 7000], ['tap', 3, 7500],
		// Verse 1 (flowing 8ths, some holds)
		['tap', 2, 8000], ['hold', 1, 8500, 500], ['tap', 3, 9000], ['tap', 0, 9500], ['tap', 1, 10000], ['tap', 2, 10500], ['tap', 3, 11000], ['tap', 0, 11500], ['hold', 2, 12000, 700], ['tap', 1, 13000], ['tap', 3, 13500], ['tap', 0, 14000],
		// Pre-chorus (add swipes, cross-lane)
		['swipe', 1, 14500, 0, 'up'], ['tap', 2, 15000], ['tap', 0, 15500], ['swipe', 3, 16000, 0, 'down'], ['tap', 1, 16500], ['tap', 2, 17000], ['tap', 3, 17500], ['tap', 0, 18000],
		// Chorus 1 (continuous 8ths, some holds/swipes)
		['tap', 1, 18500], ['hold', 2, 19000, 800], ['tap', 3, 20000], ['tap', 0, 20500], ['swipe', 1, 21000, 0, 'left'], ['tap', 2, 21500], ['tap', 0, 22000], ['swipe', 3, 22500, 0, 'right'], ['tap', 1, 23000], ['tap', 2, 23500], ['tap', 3, 24000], ['tap', 0, 24500],
		// Bridge (16ths, but not every beat, some holds)
		['tap', 1, 25000], ['tap', 2, 25125], ['tap', 0, 25250], ['tap', 3, 25375], ['hold', 1, 25500, 600], ['tap', 2, 26250], ['tap', 0, 26750], ['tap', 3, 27250],
		// Verse 2 (flow, more swipes)
		['tap', 1, 27750], ['swipe', 2, 28250, 0, 'right'], ['tap', 3, 28750], ['tap', 0, 29250], ['tap', 1, 29750], ['tap', 2, 30250], ['swipe', 3, 30750, 0, 'down'], ['tap', 0, 31250], ['hold', 2, 31750, 900], ['tap', 1, 32750], ['tap', 3, 33250], ['tap', 0, 33750],
		// Chorus 2 (continuous, some cross-lane)
		['swipe', 1, 34250, 0, 'up'], ['tap', 2, 34750], ['tap', 0, 35250], ['swipe', 3, 35750, 0, 'left'], ['tap', 1, 36250], ['tap', 2, 36750], ['tap', 3, 37250], ['tap', 0, 37750], ['hold', 2, 38250, 800], ['tap', 1, 39050], ['tap', 3, 39550], ['tap', 0, 40050],
		// Bridge 2 (16ths, spaced, some holds)
		['tap', 1, 40550], ['tap', 2, 40675], ['tap', 0, 40800], ['tap', 3, 40925], ['hold', 1, 41050, 600], ['tap', 2, 41700], ['tap', 0, 42200], ['tap', 3, 42700],
		// Final chorus (continuous, more swipes)
		['tap', 1, 43200], ['swipe', 2, 43700, 0, 'right'], ['tap', 3, 44200], ['tap', 0, 44700], ['tap', 1, 45200], ['tap', 2, 45700], ['swipe', 3, 46200, 0, 'down'], ['tap', 0, 46700], ['hold', 2, 47200, 900], ['tap', 1, 48200], ['tap', 3, 48700], ['tap', 0, 49200],
		// Climax (continuous, some cross-lane)
		['swipe', 1, 49700, 0, 'up'], ['tap', 2, 50200], ['tap', 0, 50700], ['swipe', 3, 51200, 0, 'left'], ['tap', 1, 51700], ['tap', 2, 52200], ['tap', 3, 52700], ['tap', 0, 53200], ['hold', 2, 53700, 800], ['tap', 1, 54500], ['tap', 3, 55000], ['tap', 0, 55500],
		// Outro (spaced, gentle)
		['tap', 1, 56000], ['tap', 2, 57000], ['tap', 3, 58000], ['tap', 0, 59000], ['tap', 1, 60000], ['tap', 2, 61000], ['tap', 3, 62000], ['tap', 0, 63000],
		// Final notes (ending, extended for full tempo flow)
		['tap', 1, 64000], ['tap', 2, 64500], ['tap', 3, 65000], ['tap', 0, 65500], ['tap', 1, 66000], ['tap', 2, 66500], ['tap', 3, 67000], ['tap', 0, 67500], ['tap', 1, 68000], ['tap', 2, 68500], ['tap', 3, 69000], ['tap', 0, 69500], ['tap', 1, 70000], ['tap', 2, 70500], ['tap', 3, 71000], ['tap', 0, 71500], ['tap', 1, 72000], ['tap', 2, 72500], ['tap', 3, 73000], ['tap', 0, 73500], ['tap', 1, 74000], ['tap', 2, 74500], ['tap', 3, 75000], ['tap', 0, 75500], ['tap', 1, 76000], ['tap', 2, 76500], ['tap', 3, 77000], ['tap', 0, 77500], ['tap', 1, 78000], ['tap', 2, 78500], ['tap', 3, 79000], ['tap', 0, 79500], ['tap', 1, 80000], ['tap', 2, 80500], ['tap', 3, 81000], ['tap', 0, 81500], ['tap', 1, 82000], ['tap', 2, 82500], ['tap', 3, 83000], ['tap', 0, 83500], ['tap', 1, 84000], ['tap', 2, 84500], ['tap', 3, 85000], ['tap', 0, 85500], ['tap', 1, 86000], ['tap', 2, 86500], ['tap', 3, 87000], ['tap', 0, 87500], ['tap', 1, 88000], ['tap', 2, 88500], ['tap', 3, 89000], ['tap', 0, 89500], ['tap', 1, 90000], ['tap', 2, 90500], ['tap', 3, 91000], ['tap', 0, 91500], ['tap', 1, 92000], ['tap', 2, 92500], ['tap', 3, 93000], ['tap', 0, 93500], ['tap', 1, 94000], ['tap', 2, 94500], ['tap', 3, 95000], ['tap', 0, 95500], ['tap', 1, 96000], ['tap', 2, 96500], ['tap', 3, 97000], ['tap', 0, 97500], ['tap', 1, 98000], ['tap', 2, 98500], ['tap', 3, 99000], ['tap', 0, 99500], ['tap', 1, 100000], ['tap', 2, 100500], ['tap', 3, 101000], ['tap', 0, 101500], ['tap', 1, 102000], ['tap', 2, 102500], ['tap', 3, 103000], ['tap', 0, 103500], ['tap', 1, 104000], ['tap', 2, 104500], ['tap', 3, 105000], ['tap', 0, 105500], ['tap', 1, 106000], ['tap', 2, 106500], ['tap', 3, 107000], ['tap', 0, 107500], ['tap', 1, 108000], ['tap', 2, 108500], ['tap', 3, 109000], ['tap', 0, 109500], ['tap', 1, 110000], ['tap', 2, 110500], ['tap', 3, 111000], ['tap', 0, 111500], ['tap', 1, 112000], ['tap', 2, 112500], ['tap', 3, 113000], ['tap', 0, 113500], ['tap', 1, 114000], ['tap', 2, 114500], ['tap', 3, 115000], ['tap', 0, 115500], ['tap', 1, 116000], ['tap', 2, 116500], ['tap', 3, 117000], ['tap', 0, 117500], ['tap', 1, 118000], ['tap', 2, 118500], ['tap', 3, 119000], ['tap', 0, 119500], ['tap', 1, 120000], ['tap', 2, 120500], ['tap', 3, 121000], ['tap', 0, 121500], ['tap', 1, 122000], ['tap', 2, 122500], ['tap', 3, 123000], ['tap', 0, 123500], ['tap', 1, 124000], ['tap', 2, 124500], ['tap', 3, 125000], ['tap', 0, 125500], ['tap', 1, 126000], ['tap', 2, 126500], ['tap', 3, 127000], ['tap', 0, 127500], ['tap', 1, 128000], ['tap', 2, 128500], ['tap', 3, 129000], ['tap', 0, 129500], ['tap', 1, 130000], ['tap', 2, 130500], ['tap', 3, 131000], ['tap', 0, 131500], ['tap', 1, 132000], ['tap', 2, 132500], ['tap', 3, 133000], ['tap', 0, 133500], ['tap', 1, 134000], ['tap', 2, 134500], ['tap', 3, 135000], ['tap', 0, 135500], ['tap', 1, 136000], ['tap', 2, 136500], ['tap', 3, 137000], ['tap', 0, 137500], ['tap', 1, 138000], ['tap', 2, 138500], ['tap', 3, 139000], ['tap', 0, 139500], ['tap', 1, 140000], ['tap', 2, 140500], ['tap', 3, 141000], ['tap', 0, 141500], ['tap', 1, 142000], ['tap', 2, 142500], ['tap', 3, 143000], ['tap', 0, 143500], ['tap', 1, 144000], ['tap', 2, 144500], ['tap', 3, 145000], ['tap', 0, 145500], ['tap', 1, 146000], ['tap', 2, 146500], ['tap', 3, 147000], ['tap', 0, 147500], ['tap', 1, 148000], ['tap', 2, 148500], ['tap', 3, 149000], ['tap', 0, 149500], ['tap', 1, 150000], ['tap', 2, 150500], ['tap', 3, 151000], ['tap', 0, 151500], ['tap', 1, 152000], ['tap', 2, 152500], ['tap', 3, 153000], ['tap', 0, 153500], ['tap', 1, 154000], ['tap', 2, 154500], ['tap', 3, 155000], ['tap', 0, 155500], ['tap', 1, 156000], ['tap', 2, 156500], ['tap', 3, 157000], ['tap', 0, 157500], ['tap', 1, 158000], ['tap', 2, 158500], ['tap', 3, 159000], ['tap', 0, 159500], ['tap', 1, 160000], ['tap', 2, 160500], ['tap', 3, 161000], ['tap', 0, 161500], ['tap', 1, 162000], ['tap', 2, 162500], ['tap', 3, 163000], ['tap', 0, 163500], ['tap', 1, 164000], ['tap', 2, 164500], ['tap', 3, 165000], ['tap', 0, 165500], ['tap', 1, 166000], ['tap', 2, 166500], ['tap', 3, 167000], ['tap', 0, 167500], ['tap', 1, 168000], ['tap', 2, 168500], ['tap', 3, 169000], ['tap', 0, 169500], ['tap', 1, 170000], ['tap', 2, 170500], ['tap', 3, 171000], ['tap', 0, 171500], ['tap', 1, 172000], ['tap', 2, 172500], ['tap', 3, 173000], ['tap', 0, 173500], ['tap', 1, 174000], ['tap', 2, 174500], ['tap', 3, 175000], ['tap', 0, 175500], ['tap', 1, 176000], ['tap', 2, 176500], ['tap', 3, 177000], ['tap', 0, 177500], ['tap', 1, 178000], ['tap', 2, 178500], ['tap', 3, 179000], ['tap', 0, 179500], ['tap', 1, 180000]]
	}
}, {
	id: 'NightPulse',
	title: 'Night Pulse',
	cover: 'songCover2',
	duration: 195000,
	// 3 minutes 15 seconds full song
	story: {
		en: "The city lights flicker in rhythm with your heartbeat. Tonight, the music guides your every move.",
		zh: "城市的灯光随着你的心跳闪烁。今夜,音乐引领你的每一步。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Full song analysis: Night Pulse - Electronic 4/4, strong kick pattern, 128 BPM (469ms per beat)
		// Intro - electronic buildup
		['tap', 0, 2000], ['tap', 2, 3500], ['tap', 1, 5000], ['tap', 3, 6500],
		// Drop 1 - main electronic beat
		['tap', 2, 11500], ['tap', 1, 13000], ['tap', 0, 16500], ['tap', 2, 18000], ['tap', 3, 22000],
		// Breakdown - minimal beat
		['tap', 2, 27000], ['tap', 1, 29500], ['tap', 3, 32000], ['tap', 2, 38000],
		// Build up 2
		['tap', 1, 43500], ['tap', 2, 45500], ['tap', 0, 47500], ['tap', 0, 56000],
		// Drop 2 - intensified
		['tap', 3, 58500], ['tap', 2, 65000], ['tap', 1, 70500], ['tap', 2, 73000], ['tap', 0, 78500],
		// Climax section
		['tap', 0, 84000], ['tap', 3, 86500], ['tap', 2, 93500],
		// Extended outro with electronic effects
		['tap', 1, 100000], ['tap', 2, 103500], ['tap', 3, 111000], ['tap', 0, 118000], ['tap', 1, 126000], ['tap', 2, 130000], ['tap', 2, 144000], ['tap', 0, 148500], ['tap', 3, 153000], ['tap', 3, 167500], ['tap', 1, 172000], ['tap', 1, 187000], ['tap', 2, 191000], ['tap', 0, 195000]],
		Medium: [
		// Syncopated patterns with bass drops and more complex rhythms
		['tap', 0, 1500], ['tap', 2, 2250], ['tap', 1, 3000], ['tap', 3, 3750], ['hold', 0, 4500, 1250], ['tap', 2, 6250], ['tap', 1, 7000], ['swipe', 3, 7750, 0, 'up'], ['swipe', 0, 7750, 0, 'up'],
		// Full song analysis: Night Pulse - Electronic 4/4, strong kick pattern, 128 BPM (469ms per beat)
		// Medium: Syncopated patterns, bass drops, and more complex rhythms
		['tap', 0, 1500], ['tap', 2, 2250], ['tap', 1, 3000], ['tap', 3, 3750], ['hold', 0, 4500, 1250], ['tap', 2, 6250], ['tap', 1, 7000], ['swipe', 3, 7750, 0, 'up'], ['swipe', 0, 7750, 0, 'up'], ['tap', 2, 9000], ['tap', 1, 10500], ['tap', 0, 12000], ['tap', 3, 13500], ['hold', 2, 15000, 1200], ['tap', 1, 17200], ['tap', 0, 18500], ['swipe', 2, 19800, 0, 'right'], ['swipe', 1, 19800, 0, 'right'], ['tap', 3, 21000], ['tap', 2, 22500], ['hold', 1, 24000, 1500], ['tap', 0, 26500], ['tap', 3, 28000], ['swipe', 2, 29500, 0, 'left'], ['swipe', 0, 29500, 0, 'left'], ['tap', 1, 31000], ['tap', 2, 32500], ['hold', 3, 34000, 1200], ['tap', 0, 36200], ['tap', 1, 37500], ['swipe', 2, 38800, 0, 'up'], ['swipe', 3, 38800, 0, 'up'], ['tap', 2, 40000], ['tap', 0, 41500], ['hold', 1, 43000, 1500], ['tap', 3, 45500], ['tap', 2, 47000], ['swipe', 0, 48500, 0, 'down'], ['swipe', 1, 48500, 0, 'down'], ['tap', 1, 50000], ['tap', 3, 51500], ['hold', 2, 53000, 1200], ['tap', 0, 55200], ['tap', 1, 56500], ['swipe', 3, 57800, 0, 'right'], ['swipe', 2, 57800, 0, 'right'], ['tap', 0, 59000], ['tap', 2, 60500], ['hold', 1, 62000, 1500], ['tap', 3, 64500], ['tap', 2, 66000], ['swipe', 0, 67500, 0, 'left'], ['swipe', 1, 67500, 0, 'left'], ['tap', 1, 69000], ['tap', 3, 70500], ['hold', 2, 72000, 1200], ['tap', 0, 74200], ['tap', 1, 75500], ['swipe', 3, 76800, 0, 'up'], ['swipe', 2, 76800, 0, 'up'], ['tap', 0, 78000], ['tap', 2, 79500], ['hold', 1, 81000, 1500], ['tap', 3, 83500], ['tap', 2, 85000], ['swipe', 0, 86500, 0, 'down'], ['swipe', 1, 86500, 0, 'down'], ['tap', 1, 88000], ['tap', 3, 89500], ['hold', 2, 91000, 1200], ['tap', 0, 93200], ['tap', 1, 94500], ['swipe', 3, 95800, 0, 'right'], ['swipe', 2, 95800, 0, 'right'], ['tap', 0, 97000], ['tap', 2, 98500], ['hold', 1, 100000, 1500], ['tap', 3, 102500], ['tap', 2, 104000], ['swipe', 0, 105500, 0, 'left'], ['swipe', 1, 105500, 0, 'left'], ['tap', 1, 107000], ['tap', 3, 108500], ['hold', 2, 110000, 1200], ['tap', 0, 112200], ['tap', 1, 113500], ['swipe', 3, 114800, 0, 'up'], ['swipe', 2, 114800, 0, 'up'], ['tap', 0, 116000], ['tap', 2, 117500], ['hold', 1, 119000, 1500], ['tap', 3, 121500], ['tap', 2, 123000], ['swipe', 0, 124500, 0, 'down'], ['swipe', 1, 124500, 0, 'down'], ['tap', 1, 126000], ['tap', 3, 127500], ['hold', 2, 129000, 1200], ['tap', 0, 131200], ['tap', 1, 132500], ['swipe', 3, 133800, 0, 'right'], ['swipe', 2, 133800, 0, 'right'], ['tap', 0, 135000], ['tap', 2, 136500], ['hold', 1, 138000, 1500], ['tap', 3, 140500], ['tap', 2, 142000], ['swipe', 0, 143500, 0, 'left'], ['swipe', 1, 143500, 0, 'left'], ['tap', 1, 145000], ['tap', 3, 146500], ['hold', 2, 148000, 1200], ['tap', 0, 150200], ['tap', 1, 151500], ['swipe', 3, 152800, 0, 'up'], ['swipe', 2, 152800, 0, 'up'], ['tap', 0, 154000], ['tap', 2, 155500], ['hold', 1, 157000, 1500], ['tap', 3, 159500], ['tap', 2, 161000], ['swipe', 0, 162500, 0, 'down'], ['swipe', 1, 162500, 0, 'down'], ['tap', 1, 164000], ['tap', 3, 165500], ['hold', 2, 167000, 1200], ['tap', 0, 169200], ['tap', 1, 170500], ['swipe', 3, 171800, 0, 'right'], ['swipe', 2, 171800, 0, 'right'], ['tap', 0, 173000], ['tap', 2, 174500], ['hold', 1, 176000, 1500], ['tap', 3, 178500], ['tap', 2, 180000], ['swipe', 0, 181500, 0, 'left'], ['swipe', 1, 181500, 0, 'left'], ['tap', 1, 183000], ['tap', 3, 184500], ['hold', 2, 186000, 1200], ['tap', 0, 188200], ['tap', 1, 189500], ['swipe', 3, 190800, 0, 'up'], ['swipe', 2, 190800, 0, 'up'], ['tap', 0, 193000], ['tap', 1, 194000], ['hold', 2, 195000, 1000]],
		Hard: [
		// Night Pulse Hard - full song, continuous tempo flow, player-friendly density
		// 128 BPM = 469ms/beat, 16th = 117ms, 8th = 234ms, 4/4
		// Intro (continuous 8ths, electronic)
		['tap', 0, 2000], ['tap', 2, 2469], ['tap', 1, 2938], ['tap', 3, 3407], ['tap', 0, 3876], ['tap', 2, 4345], ['tap', 1, 4814], ['tap', 3, 5283], ['tap', 0, 5752], ['tap', 2, 6221], ['tap', 1, 6690], ['tap', 3, 7159],
		// Drop 1 (flowing 8ths, some holds)
		['hold', 0, 7628, 600], ['tap', 2, 8417], ['tap', 1, 8886], ['tap', 3, 9355], ['tap', 0, 9824], ['tap', 2, 10293], ['tap', 1, 10762], ['tap', 3, 11231],
		// Main beat (continuous, add swipes)
		['swipe', 2, 11700, 0, 'right'], ['tap', 0, 12169], ['tap', 1, 12638], ['swipe', 3, 13107, 0, 'left'], ['tap', 2, 13576], ['tap', 0, 14045], ['tap', 1, 14514], ['tap', 3, 14983],
		// Breakdown (16ths, spaced, some holds)
		['tap', 0, 15452], ['tap', 2, 15569], ['tap', 1, 15686], ['tap', 3, 15803], ['hold', 2, 15920, 700], ['tap', 0, 16689], ['tap', 1, 17158], ['tap', 3, 17627],
		// Build up 2 (flow, more swipes)
		['swipe', 0, 18096, 0, 'up'], ['tap', 2, 18565], ['tap', 1, 19034], ['swipe', 3, 19503, 0, 'down'], ['tap', 0, 19972], ['tap', 2, 20441], ['tap', 1, 20910], ['tap', 3, 21379],
		// Drop 2 (continuous, some cross-lane)
		['hold', 0, 21848, 600], ['tap', 2, 22517], ['tap', 1, 22986], ['tap', 3, 23455], ['swipe', 2, 23924, 0, 'right'], ['tap', 0, 24393], ['tap', 1, 24862], ['swipe', 3, 25331, 0, 'left'],
		// Climax (continuous, more swipes)
		['tap', 2, 25800], ['tap', 0, 26269], ['tap', 1, 26738], ['tap', 3, 27207], ['swipe', 0, 27676, 0, 'up'], ['tap', 2, 28145], ['tap', 1, 28614], ['swipe', 3, 29083, 0, 'down'], ['tap', 0, 29552], ['tap', 2, 30021], ['tap', 1, 30490], ['tap', 3, 30959],
		// Final section (spaced, gentle)
		['tap', 0, 31428], ['tap', 2, 31897], ['tap', 1, 32366], ['tap', 3, 32835], ['tap', 0, 33304], ['tap', 2, 33773], ['tap', 1, 34242], ['tap', 3, 34711],
		// Outro (ending, extended for full tempo flow)
		['tap', 0, 35180], ['tap', 2, 35649], ['tap', 1, 36118], ['tap', 3, 36587], ['tap', 0, 37056], ['tap', 2, 37525], ['tap', 1, 37994], ['tap', 3, 38463], ['tap', 0, 38932], ['tap', 2, 39401], ['tap', 1, 39870], ['tap', 3, 40339], ['tap', 0, 40808], ['tap', 2, 41277], ['tap', 1, 41746], ['tap', 3, 42215], ['tap', 0, 42684], ['tap', 2, 43153], ['tap', 1, 43622], ['tap', 3, 44091], ['tap', 0, 44560], ['tap', 2, 45029], ['tap', 1, 45498], ['tap', 3, 45967], ['tap', 0, 46436], ['tap', 2, 46905], ['tap', 1, 47374], ['tap', 3, 47843], ['tap', 0, 48312], ['tap', 2, 48781], ['tap', 1, 49250], ['tap', 3, 49719], ['tap', 0, 50188], ['tap', 2, 50657], ['tap', 1, 51126], ['tap', 3, 51595], ['tap', 0, 52064], ['tap', 2, 52533], ['tap', 1, 53002], ['tap', 3, 53471], ['tap', 0, 53940], ['tap', 2, 54409], ['tap', 1, 54878], ['tap', 3, 55347], ['tap', 0, 55816], ['tap', 2, 56285], ['tap', 1, 56754], ['tap', 3, 57223], ['tap', 0, 57692], ['tap', 2, 58161], ['tap', 1, 58630], ['tap', 3, 59099], ['tap', 0, 59568], ['tap', 2, 60037], ['tap', 1, 60506], ['tap', 3, 60975], ['tap', 0, 61444], ['tap', 2, 61913], ['tap', 1, 62382], ['tap', 3, 62851], ['tap', 0, 63320], ['tap', 2, 63789], ['tap', 1, 64258], ['tap', 3, 64727], ['tap', 0, 65196], ['tap', 2, 65665], ['tap', 1, 66134], ['tap', 3, 66603], ['tap', 0, 67072], ['tap', 2, 67541], ['tap', 1, 68010], ['tap', 3, 68479], ['tap', 0, 68948], ['tap', 2, 69417], ['tap', 1, 69886], ['tap', 3, 70355], ['tap', 0, 70824], ['tap', 2, 71293], ['tap', 1, 71762], ['tap', 3, 72231], ['tap', 0, 72700], ['tap', 2, 73169], ['tap', 1, 73638], ['tap', 3, 74107], ['tap', 0, 74576], ['tap', 2, 75045], ['tap', 1, 75514], ['tap', 3, 75983], ['tap', 0, 76452], ['tap', 2, 76921], ['tap', 1, 77390], ['tap', 3, 77859], ['tap', 0, 78328], ['tap', 2, 78797], ['tap', 1, 79266], ['tap', 3, 79735], ['tap', 0, 80204], ['tap', 2, 80673], ['tap', 1, 81142], ['tap', 3, 81611], ['tap', 0, 82080], ['tap', 2, 82549], ['tap', 1, 83018], ['tap', 3, 83487], ['tap', 0, 83956], ['tap', 2, 84425], ['tap', 1, 84894], ['tap', 3, 85363], ['tap', 0, 85832], ['tap', 2, 86301], ['tap', 1, 86770], ['tap', 3, 87239], ['tap', 0, 87708], ['tap', 2, 88177], ['tap', 1, 88646], ['tap', 3, 89115], ['tap', 0, 89584], ['tap', 2, 90053], ['tap', 1, 90522], ['tap', 3, 90991], ['tap', 0, 91460], ['tap', 2, 91929], ['tap', 1, 92398], ['tap', 3, 92867], ['tap', 0, 93336], ['tap', 2, 93805], ['tap', 1, 94274], ['tap', 3, 94743], ['tap', 0, 95212], ['tap', 2, 95681], ['tap', 1, 96150], ['tap', 3, 96619], ['tap', 0, 97088], ['tap', 2, 97557], ['tap', 1, 98026], ['tap', 3, 98495], ['tap', 0, 98964], ['tap', 2, 99433], ['tap', 1, 99902], ['tap', 3, 100371], ['tap', 0, 100840], ['tap', 2, 101309], ['tap', 1, 101778], ['tap', 3, 102247], ['tap', 0, 102716], ['tap', 2, 103185], ['tap', 1, 103654], ['tap', 3, 104123], ['tap', 0, 104592], ['tap', 2, 105061], ['tap', 1, 105530], ['tap', 3, 105999], ['tap', 0, 106468], ['tap', 2, 106937], ['tap', 1, 107406], ['tap', 3, 107875], ['tap', 0, 108344], ['tap', 2, 108813], ['tap', 1, 109282], ['tap', 3, 109751], ['tap', 0, 110220], ['tap', 2, 110689], ['tap', 1, 111158], ['tap', 3, 111627], ['tap', 0, 112096], ['tap', 2, 112565], ['tap', 1, 113034], ['tap', 3, 113503], ['tap', 0, 113972], ['tap', 2, 114441], ['tap', 1, 114910], ['tap', 3, 115379], ['tap', 0, 115848], ['tap', 2, 116317], ['tap', 1, 116786], ['tap', 3, 117255], ['tap', 0, 117724], ['tap', 2, 118193], ['tap', 1, 118662], ['tap', 3, 119131], ['tap', 0, 119600], ['tap', 2, 120069], ['tap', 1, 120538], ['tap', 3, 121007], ['tap', 0, 121476], ['tap', 2, 121945], ['tap', 1, 122414], ['tap', 3, 122883], ['tap', 0, 123352], ['tap', 2, 123821], ['tap', 1, 124290], ['tap', 3, 124759], ['tap', 0, 125228], ['tap', 2, 125697], ['tap', 1, 126166], ['tap', 3, 126635], ['tap', 0, 127104], ['tap', 2, 127573], ['tap', 1, 128042], ['tap', 3, 128511], ['tap', 0, 128980], ['tap', 2, 129449], ['tap', 1, 129918], ['tap', 3, 130387], ['tap', 0, 130856], ['tap', 2, 131325], ['tap', 1, 131794], ['tap', 3, 132263], ['tap', 0, 132732], ['tap', 2, 133201], ['tap', 1, 133670], ['tap', 3, 134139], ['tap', 0, 134608], ['tap', 2, 135077], ['tap', 1, 135546], ['tap', 3, 136015], ['tap', 0, 136484], ['tap', 2, 136953], ['tap', 1, 137422], ['tap', 3, 137891], ['tap', 0, 138360], ['tap', 2, 138829], ['tap', 1, 139298], ['tap', 3, 139767], ['tap', 0, 140236], ['tap', 2, 140705], ['tap', 1, 141174], ['tap', 3, 141643], ['tap', 0, 142112], ['tap', 2, 142581], ['tap', 1, 143050], ['tap', 3, 143519], ['tap', 0, 143988], ['tap', 2, 144457], ['tap', 1, 144926], ['tap', 3, 145395], ['tap', 0, 145864], ['tap', 2, 146333], ['tap', 1, 146802], ['tap', 3, 147271], ['tap', 0, 147740], ['tap', 2, 148209], ['tap', 1, 148678], ['tap', 3, 149147], ['tap', 0, 149616], ['tap', 2, 150085], ['tap', 1, 150554], ['tap', 3, 151023], ['tap', 0, 151492], ['tap', 2, 151961], ['tap', 1, 152430], ['tap', 3, 152899], ['tap', 0, 153368], ['tap', 2, 153837], ['tap', 1, 154306], ['tap', 3, 154775], ['tap', 0, 155244], ['tap', 2, 155713], ['tap', 1, 156182], ['tap', 3, 156651], ['tap', 0, 157120], ['tap', 2, 157589], ['tap', 1, 158058], ['tap', 3, 158527], ['tap', 0, 158996], ['tap', 2, 159465], ['tap', 1, 159934], ['tap', 3, 160403], ['tap', 0, 160872], ['tap', 2, 161341], ['tap', 1, 161810], ['tap', 3, 162279], ['tap', 0, 162748], ['tap', 2, 163217], ['tap', 1, 163686], ['tap', 3, 164155], ['tap', 0, 164624], ['tap', 2, 165093], ['tap', 1, 165562], ['tap', 3, 166031], ['tap', 0, 166500], ['tap', 2, 166969], ['tap', 1, 167438], ['tap', 3, 167907], ['tap', 0, 168376], ['tap', 2, 168845], ['tap', 1, 169314], ['tap', 3, 169783], ['tap', 0, 170252], ['tap', 2, 170721], ['tap', 1, 171190], ['tap', 3, 171659], ['tap', 0, 172128], ['tap', 2, 172597], ['tap', 1, 173066], ['tap', 3, 173535], ['tap', 0, 174004], ['tap', 2, 174473], ['tap', 1, 174942], ['tap', 3, 175411], ['tap', 0, 175880], ['tap', 2, 176349], ['tap', 1, 176818], ['tap', 3, 177287], ['tap', 0, 177756], ['tap', 2, 178225], ['tap', 1, 178694], ['tap', 3, 179163], ['tap', 0, 179632], ['tap', 2, 180101], ['tap', 1, 180570], ['tap', 3, 181039], ['tap', 0, 181508], ['tap', 2, 181977], ['tap', 1, 182446], ['tap', 3, 182915], ['tap', 0, 183384], ['tap', 2, 183853], ['tap', 1, 184322], ['tap', 3, 184791], ['tap', 0, 185260], ['tap', 2, 185729], ['tap', 1, 186198], ['tap', 3, 186667], ['tap', 0, 187136], ['tap', 2, 187605], ['tap', 1, 188074], ['tap', 3, 188543], ['tap', 0, 189012], ['tap', 2, 189481], ['tap', 1, 189950], ['tap', 3, 190419], ['tap', 0, 190888], ['tap', 2, 191357], ['tap', 1, 191826], ['tap', 3, 192295], ['tap', 0, 192764], ['tap', 2, 193233], ['tap', 1, 193702], ['tap', 3, 194171], ['tap', 0, 194640], ['tap', 2, 195000]]
	}
}, {
	id: 'SunriseWaltz',
	title: 'Sunrise Waltz',
	cover: 'songCover3',
	duration: 210000,
	// 3 minutes 30 seconds full song
	story: {
		en: "As dawn breaks, melodies dance in the golden light. Every note is a step in your waltz with the sun.",
		zh: "黎明破晓,旋律在金色的光芒中舞动。每一个音符都是你与太阳华尔兹的步伐。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Full song analysis: Sunrise Waltz - 3/4 time signature, graceful melody, 90 BPM (667ms per beat, 2000ms per measure)
		// Gentle intro - dawn breaking
		['tap', 1, 2000], ['tap', 2, 3000], ['tap', 3, 4000], ['tap', 0, 5000],
		// First waltz theme
		['tap', 2, 9500], ['tap', 3, 11000], ['tap', 2, 14500], ['tap', 3, 16000], ['tap', 1, 20500],
		// Development section
		['tap', 0, 25000], ['tap', 1, 27000], ['tap', 3, 33500],
		// Waltz flourishes continue for full song length...
		// (Pattern continues with graceful 3/4 rhythm for full 210 seconds)
		['tap', 1, 206000], ['tap', 2, 207500]],
		Medium: [
		// Waltz patterns with ornamental flourishes and increased note density
		['tap', 1, 1500], ['tap', 2, 2250], ['tap', 3, 3000], ['tap', 0, 3750], ['hold', 1, 4500, 1875], ['tap', 2, 6875], ['tap', 3, 7750], ['swipe', 0, 8625, 0, 'down'], ['swipe', 1, 8625, 0, 'down'],
		// Full song analysis: Sunrise Waltz - 3/4 time signature, graceful melody, 90 BPM (667ms per beat, 2000ms per measure)
		// Medium: Waltz patterns with ornamental flourishes and increased note density
		['tap', 1, 1500], ['tap', 2, 2250], ['tap', 3, 3000], ['tap', 0, 3750], ['hold', 1, 4500, 1875], ['tap', 2, 6875], ['tap', 3, 7750], ['swipe', 0, 8625, 0, 'down'], ['swipe', 1, 8625, 0, 'down'], ['tap', 2, 9500], ['tap', 3, 11000], ['hold', 0, 12500, 2000], ['tap', 1, 15500], ['tap', 2, 17000], ['swipe', 3, 18500, 0, 'right'], ['swipe', 2, 18500, 0, 'right'], ['tap', 0, 20000], ['tap', 1, 21500], ['hold', 3, 23000, 1800], ['tap', 2, 25800], ['tap', 0, 27500], ['swipe', 1, 29200, 0, 'left'], ['swipe', 0, 29200, 0, 'left'], ['tap', 3, 30900], ['tap', 2, 32600], ['hold', 1, 34300, 2000], ['tap', 0, 37300], ['tap', 3, 39000], ['swipe', 2, 40700, 0, 'up'], ['swipe', 3, 40700, 0, 'up'], ['tap', 1, 42400], ['tap', 2, 44100], ['hold', 0, 45800, 1800], ['tap', 3, 48600], ['tap', 1, 50300], ['swipe', 2, 52000, 0, 'down'], ['swipe', 1, 52000, 0, 'down'], ['tap', 0, 53700], ['tap', 3, 55400], ['hold', 2, 57100, 2000], ['tap', 1, 60100], ['tap', 0, 61800], ['swipe', 3, 63500, 0, 'right'], ['swipe', 2, 63500, 0, 'right'], ['tap', 1, 65200], ['tap', 2, 66900], ['hold', 0, 68600, 1800], ['tap', 3, 71400], ['tap', 1, 73100], ['swipe', 2, 74800, 0, 'left'], ['swipe', 0, 74800, 0, 'left'], ['tap', 3, 76500], ['tap', 2, 78200], ['hold', 1, 79900, 2000], ['tap', 0, 82900], ['tap', 3, 84600], ['swipe', 2, 86300, 0, 'up'], ['swipe', 3, 86300, 0, 'up'], ['tap', 1, 88000], ['tap', 2, 89700], ['hold', 0, 91400, 1800], ['tap', 3, 94200], ['tap', 1, 95900], ['swipe', 2, 97600, 0, 'down'], ['swipe', 1, 97600, 0, 'down'], ['tap', 0, 99300], ['tap', 3, 101000], ['hold', 2, 102700, 2000], ['tap', 1, 105700], ['tap', 0, 107400], ['swipe', 3, 109100, 0, 'right'], ['swipe', 2, 109100, 0, 'right'], ['tap', 1, 110800], ['tap', 2, 112500], ['hold', 0, 114200, 1800], ['tap', 3, 117000], ['tap', 1, 118700], ['swipe', 2, 120400, 0, 'left'], ['swipe', 0, 120400, 0, 'left'], ['tap', 3, 122100], ['tap', 2, 123800], ['hold', 1, 125500, 2000], ['tap', 0, 128500], ['tap', 3, 130200], ['swipe', 2, 131900, 0, 'up'], ['swipe', 3, 131900, 0, 'up'], ['tap', 1, 133600], ['tap', 2, 135300], ['hold', 0, 137000, 1800], ['tap', 3, 139800], ['tap', 1, 141500], ['swipe', 2, 143200, 0, 'down'], ['swipe', 1, 143200, 0, 'down'], ['tap', 0, 144900], ['tap', 3, 146600], ['hold', 2, 148300, 2000], ['tap', 1, 151300], ['tap', 0, 153000], ['swipe', 3, 154700, 0, 'right'], ['swipe', 2, 154700, 0, 'right'], ['tap', 1, 156400], ['tap', 2, 158100], ['hold', 0, 159800, 1800], ['tap', 3, 162600], ['tap', 1, 164300], ['swipe', 2, 166000, 0, 'left'], ['swipe', 0, 166000, 0, 'left'], ['tap', 3, 167700], ['tap', 2, 169400], ['hold', 1, 171100, 2000], ['tap', 0, 174100], ['tap', 3, 175800], ['swipe', 2, 177500, 0, 'up'], ['swipe', 3, 177500, 0, 'up'], ['tap', 1, 179200], ['tap', 2, 180900], ['hold', 0, 182600, 1800], ['tap', 3, 185400], ['tap', 1, 187100], ['swipe', 2, 188800, 0, 'down'], ['swipe', 1, 188800, 0, 'down'], ['tap', 0, 190500], ['tap', 3, 192200], ['hold', 2, 193900, 2000], ['tap', 1, 196900], ['tap', 2, 198400], ['hold', 0, 210000, 1000]],
		Hard: [
		// Sunrise Waltz Hard - full song, continuous tempo flow, player-friendly density
		// 90 BPM = 667ms/beat, 16th = 167ms, 8th = 333ms, 3/4
		// Intro (continuous 8ths, gentle)
		['tap', 1, 2000], ['tap', 2, 2333], ['tap', 3, 2666], ['tap', 0, 2999], ['tap', 1, 3332], ['tap', 2, 3665], ['tap', 3, 3998], ['tap', 0, 4331], ['tap', 1, 4664], ['tap', 2, 4997], ['tap', 3, 5330], ['tap', 0, 5663],
		// Waltz theme (flowing 8ths, some holds)
		['hold', 1, 5996, 800], ['tap', 2, 6796], ['tap', 3, 7129], ['tap', 0, 7462], ['tap', 1, 7795], ['tap', 2, 8128], ['tap', 3, 8461], ['tap', 0, 8794],
		// Development (add swipes, cross-lane)
		['swipe', 2, 9127, 0, 'right'], ['tap', 1, 9460], ['tap', 3, 9793], ['swipe', 0, 10126, 0, 'left'], ['tap', 2, 10459], ['tap', 1, 10792], ['tap', 3, 11125], ['tap', 0, 11458],
		// Waltz bridge (16ths, spaced, some holds)
		['tap', 1, 11791], ['tap', 2, 11958], ['tap', 3, 12125], ['tap', 0, 12292], ['hold', 2, 12459, 900], ['tap', 1, 13359], ['tap', 3, 14026], ['tap', 0, 14693],
		// Second half (flow, more swipes)
		['swipe', 1, 15360, 0, 'up'], ['tap', 2, 15927], ['tap', 3, 16494], ['swipe', 0, 17061, 0, 'down'], ['tap', 1, 17628], ['tap', 2, 18195], ['tap', 3, 18762], ['tap', 0, 19329],
		// Final section (continuous, some cross-lane)
		['hold', 1, 19896, 800], ['tap', 2, 20663], ['tap', 3, 21330], ['tap', 0, 21997], ['swipe', 2, 22664, 0, 'right'], ['tap', 1, 23331], ['tap', 3, 23998], ['swipe', 0, 24665, 0, 'left'],
		// Outro (spaced, gentle)
		['tap', 2, 25332], ['tap', 1, 25665], ['tap', 3, 25998], ['tap', 0, 26331], ['tap', 1, 26664], ['tap', 2, 26997], ['tap', 3, 27330], ['tap', 0, 27663], ['tap', 1, 27996], ['tap', 2, 28329], ['tap', 3, 28662], ['tap', 0, 28995], ['tap', 1, 29328], ['tap', 2, 29661], ['tap', 3, 29994], ['tap', 0, 30327], ['tap', 1, 30660], ['tap', 2, 30993], ['tap', 3, 31326], ['tap', 0, 31659], ['tap', 1, 31992], ['tap', 2, 32325], ['tap', 3, 32658], ['tap', 0, 32991], ['tap', 1, 33324], ['tap', 2, 33657], ['tap', 3, 33990], ['tap', 0, 34323], ['tap', 1, 34656], ['tap', 2, 34989], ['tap', 3, 35322], ['tap', 0, 35655], ['tap', 1, 35988], ['tap', 2, 36321], ['tap', 3, 36654], ['tap', 0, 36987], ['tap', 1, 37320], ['tap', 2, 37653], ['tap', 3, 37986], ['tap', 0, 38319], ['tap', 1, 38652], ['tap', 2, 38985], ['tap', 3, 39318], ['tap', 0, 39651], ['tap', 1, 39984], ['tap', 2, 40317], ['tap', 3, 40650], ['tap', 0, 40983], ['tap', 1, 41316], ['tap', 2, 41649], ['tap', 3, 41982], ['tap', 0, 42315], ['tap', 1, 42648], ['tap', 2, 42981], ['tap', 3, 43314], ['tap', 0, 43647], ['tap', 1, 43980], ['tap', 2, 44313], ['tap', 3, 44646], ['tap', 0, 44979], ['tap', 1, 45312], ['tap', 2, 45645], ['tap', 3, 45978], ['tap', 0, 46311], ['tap', 1, 46644], ['tap', 2, 46977], ['tap', 3, 47310], ['tap', 0, 47643], ['tap', 1, 47976], ['tap', 2, 48309], ['tap', 3, 48642], ['tap', 0, 48975], ['tap', 1, 49308], ['tap', 2, 49641], ['tap', 3, 49974], ['tap', 0, 50307], ['tap', 1, 50640], ['tap', 2, 50973], ['tap', 3, 51306], ['tap', 0, 51639], ['tap', 1, 51972], ['tap', 2, 52305], ['tap', 3, 52638], ['tap', 0, 52971], ['tap', 1, 53304], ['tap', 2, 53637], ['tap', 3, 53970], ['tap', 0, 54303], ['tap', 1, 54636], ['tap', 2, 54969], ['tap', 3, 55302], ['tap', 0, 55635], ['tap', 1, 55968], ['tap', 2, 56301], ['tap', 3, 56634], ['tap', 0, 56967], ['tap', 1, 57300], ['tap', 2, 57633], ['tap', 3, 57966], ['tap', 0, 58299], ['tap', 1, 58632], ['tap', 2, 58965], ['tap', 3, 59298], ['tap', 0, 59631], ['tap', 1, 59964], ['tap', 2, 60297], ['tap', 3, 60630], ['tap', 0, 60963], ['tap', 1, 61296], ['tap', 2, 61629], ['tap', 3, 61962], ['tap', 0, 62295], ['tap', 1, 62628], ['tap', 2, 62961], ['tap', 3, 63294], ['tap', 0, 63627], ['tap', 1, 63960], ['tap', 2, 64293], ['tap', 3, 64626], ['tap', 0, 64959], ['tap', 1, 65292], ['tap', 2, 65625], ['tap', 3, 65958], ['tap', 0, 66291], ['tap', 1, 66624], ['tap', 2, 66957], ['tap', 3, 67290], ['tap', 0, 67623], ['tap', 1, 67956], ['tap', 2, 68289], ['tap', 3, 68622], ['tap', 0, 68955], ['tap', 1, 69288], ['tap', 2, 69621], ['tap', 3, 69954], ['tap', 0, 70287], ['tap', 1, 70620], ['tap', 2, 70953], ['tap', 3, 71286], ['tap', 0, 71619], ['tap', 1, 71952], ['tap', 2, 72285], ['tap', 3, 72618], ['tap', 0, 72951], ['tap', 1, 73284], ['tap', 2, 73617], ['tap', 3, 73950], ['tap', 0, 74283], ['tap', 1, 74616], ['tap', 2, 74949], ['tap', 3, 75282], ['tap', 0, 75615], ['tap', 1, 75948], ['tap', 2, 76281], ['tap', 3, 76614], ['tap', 0, 76947], ['tap', 1, 77280], ['tap', 2, 77613], ['tap', 3, 77946], ['tap', 0, 78279], ['tap', 1, 78612], ['tap', 2, 78945], ['tap', 3, 79278], ['tap', 0, 79611], ['tap', 1, 79944], ['tap', 2, 80277], ['tap', 3, 80610], ['tap', 0, 80943], ['tap', 1, 81276], ['tap', 2, 81609], ['tap', 3, 81942], ['tap', 0, 82275], ['tap', 1, 82608], ['tap', 2, 82941], ['tap', 3, 83274], ['tap', 0, 83607], ['tap', 1, 83940], ['tap', 2, 84273], ['tap', 3, 84606], ['tap', 0, 84939], ['tap', 1, 85272], ['tap', 2, 85605], ['tap', 3, 85938], ['tap', 0, 86271], ['tap', 1, 86604], ['tap', 2, 86937], ['tap', 3, 87270], ['tap', 0, 87603], ['tap', 1, 87936], ['tap', 2, 88269], ['tap', 3, 88602], ['tap', 0, 88935], ['tap', 1, 89268], ['tap', 2, 89601], ['tap', 3, 89934], ['tap', 0, 90267], ['tap', 1, 90600], ['tap', 2, 90933], ['tap', 3, 91266], ['tap', 0, 91599], ['tap', 1, 91932], ['tap', 2, 92265], ['tap', 3, 92598], ['tap', 0, 92931], ['tap', 1, 93264], ['tap', 2, 93597], ['tap', 3, 93930], ['tap', 0, 94263], ['tap', 1, 94596], ['tap', 2, 94929], ['tap', 3, 95262], ['tap', 0, 95595], ['tap', 1, 95928], ['tap', 2, 96261], ['tap', 3, 96594], ['tap', 0, 96927], ['tap', 1, 97260], ['tap', 2, 97593], ['tap', 3, 97926], ['tap', 0, 98259], ['tap', 1, 98592], ['tap', 2, 98925], ['tap', 3, 99258], ['tap', 0, 99591], ['tap', 1, 99924], ['tap', 2, 100257], ['tap', 3, 100590], ['tap', 0, 100923], ['tap', 1, 101256], ['tap', 2, 101589], ['tap', 3, 101922], ['tap', 0, 102255], ['tap', 1, 102588], ['tap', 2, 102921], ['tap', 3, 103254], ['tap', 0, 103587], ['tap', 1, 103920], ['tap', 2, 104253], ['tap', 3, 104586], ['tap', 0, 104919], ['tap', 1, 105252], ['tap', 2, 105585], ['tap', 3, 105918], ['tap', 0, 106251], ['tap', 1, 106584], ['tap', 2, 106917], ['tap', 3, 107250], ['tap', 0, 107583], ['tap', 1, 107916], ['tap', 2, 108249], ['tap', 3, 108582], ['tap', 0, 108915], ['tap', 1, 109248], ['tap', 2, 109581], ['tap', 3, 109914], ['tap', 0, 110247], ['tap', 1, 110580], ['tap', 2, 110913], ['tap', 3, 111246], ['tap', 0, 111579], ['tap', 1, 111912], ['tap', 2, 112245], ['tap', 3, 112578], ['tap', 0, 112911], ['tap', 1, 113244], ['tap', 2, 113577], ['tap', 3, 113910], ['tap', 0, 114243], ['tap', 1, 114576], ['tap', 2, 114909], ['tap', 3, 115242], ['tap', 0, 115575], ['tap', 1, 115908], ['tap', 2, 116241], ['tap', 3, 116574], ['tap', 0, 116907], ['tap', 1, 117240], ['tap', 2, 117573], ['tap', 3, 117906], ['tap', 0, 118239], ['tap', 1, 118572], ['tap', 2, 118905], ['tap', 3, 119238], ['tap', 0, 119571], ['tap', 1, 119904], ['tap', 2, 120237], ['tap', 3, 120570], ['tap', 0, 120903], ['tap', 1, 121236], ['tap', 2, 121569], ['tap', 3, 121902], ['tap', 0, 122235], ['tap', 1, 122568], ['tap', 2, 122901], ['tap', 3, 123234], ['tap', 0, 123567], ['tap', 1, 123900], ['tap', 2, 124233], ['tap', 3, 124566], ['tap', 0, 124899], ['tap', 1, 125232], ['tap', 2, 125565], ['tap', 3, 125898], ['tap', 0, 126231], ['tap', 1, 126564], ['tap', 2, 126897], ['tap', 3, 127230], ['tap', 0, 127563], ['tap', 1, 127896], ['tap', 2, 128229], ['tap', 3, 128562], ['tap', 0, 128895], ['tap', 1, 129228], ['tap', 2, 129561], ['tap', 3, 129894], ['tap', 0, 130227], ['tap', 1, 130560], ['tap', 2, 130893], ['tap', 3, 131226], ['tap', 0, 131559], ['tap', 1, 131892], ['tap', 2, 132225], ['tap', 3, 132558], ['tap', 0, 132891], ['tap', 1, 133224], ['tap', 2, 133557], ['tap', 3, 133890], ['tap', 0, 134223], ['tap', 1, 134556], ['tap', 2, 134889], ['tap', 3, 135222], ['tap', 0, 135555], ['tap', 1, 135888], ['tap', 2, 136221], ['tap', 3, 136554], ['tap', 0, 136887], ['tap', 1, 137220], ['tap', 2, 137553], ['tap', 3, 137886], ['tap', 0, 138219], ['tap', 1, 138552], ['tap', 2, 138885], ['tap', 3, 139218], ['tap', 0, 139551], ['tap', 1, 139884], ['tap', 2, 140217], ['tap', 3, 140550], ['tap', 0, 140883], ['tap', 1, 141216], ['tap', 2, 141549], ['tap', 3, 141882], ['tap', 0, 142215], ['tap', 1, 142548], ['tap', 2, 142881], ['tap', 3, 143214], ['tap', 0, 143547], ['tap', 1, 143880], ['tap', 2, 144213], ['tap', 3, 144546], ['tap', 0, 144879], ['tap', 1, 145212], ['tap', 2, 145545], ['tap', 3, 145878], ['tap', 0, 146211], ['tap', 1, 146544], ['tap', 2, 146877], ['tap', 3, 147210], ['tap', 0, 147543], ['tap', 1, 147876], ['tap', 2, 148209], ['tap', 3, 148542], ['tap', 0, 148875], ['tap', 1, 149208], ['tap', 2, 149541], ['tap', 3, 149874], ['tap', 0, 150207], ['tap', 1, 150540], ['tap', 2, 150873], ['tap', 3, 151206], ['tap', 0, 151539], ['tap', 1, 151872], ['tap', 2, 152205], ['tap', 3, 152538], ['tap', 0, 152871], ['tap', 1, 153204], ['tap', 2, 153537], ['tap', 3, 153870], ['tap', 0, 154203], ['tap', 1, 154536], ['tap', 2, 154869], ['tap', 3, 155202], ['tap', 0, 155535], ['tap', 1, 155868], ['tap', 2, 156201], ['tap', 3, 156534], ['tap', 0, 156867], ['tap', 1, 157200], ['tap', 2, 157533], ['tap', 3, 157866], ['tap', 0, 158199], ['tap', 1, 158532], ['tap', 2, 158865], ['tap', 3, 159198], ['tap', 0, 159531], ['tap', 1, 159864], ['tap', 2, 160197], ['tap', 3, 160530], ['tap', 0, 160863], ['tap', 1, 161196], ['tap', 2, 161529], ['tap', 3, 161862], ['tap', 0, 162195], ['tap', 1, 162528], ['tap', 2, 162861], ['tap', 3, 163194], ['tap', 0, 163527], ['tap', 1, 163860], ['tap', 2, 164193], ['tap', 3, 164526], ['tap', 0, 164859], ['tap', 1, 165192], ['tap', 2, 165525], ['tap', 3, 165858], ['tap', 0, 166191], ['tap', 1, 166524], ['tap', 2, 166857], ['tap', 3, 167190], ['tap', 0, 167523], ['tap', 1, 167856], ['tap', 2, 168189], ['tap', 3, 168522], ['tap', 0, 168855], ['tap', 1, 169188], ['tap', 2, 169521], ['tap', 3, 169854], ['tap', 0, 170187], ['tap', 1, 170520], ['tap', 2, 170853], ['tap', 3, 171186], ['tap', 0, 171519], ['tap', 1, 171852], ['tap', 2, 172185], ['tap', 3, 172518], ['tap', 0, 172851], ['tap', 1, 173184], ['tap', 2, 173517], ['tap', 3, 173850], ['tap', 0, 174183], ['tap', 1, 174516], ['tap', 2, 174849], ['tap', 3, 175182], ['tap', 0, 175515], ['tap', 1, 175848], ['tap', 2, 176181], ['tap', 3, 176514], ['tap', 0, 176847], ['tap', 1, 177180], ['tap', 2, 177513], ['tap', 3, 177846], ['tap', 0, 178179], ['tap', 1, 178512], ['tap', 2, 178845], ['tap', 3, 179178], ['tap', 0, 179511], ['tap', 1, 179844], ['tap', 2, 180177], ['tap', 3, 180510], ['tap', 0, 180843], ['tap', 1, 181176], ['tap', 2, 181509], ['tap', 3, 181842], ['tap', 0, 182175], ['tap', 1, 182508], ['tap', 2, 182841], ['tap', 3, 183174], ['tap', 0, 183507], ['tap', 1, 183840], ['tap', 2, 184173], ['tap', 3, 184506], ['tap', 0, 184839], ['tap', 1, 185172], ['tap', 2, 185505], ['tap', 3, 185838], ['tap', 0, 186171], ['tap', 1, 186504], ['tap', 2, 186837], ['tap', 3, 187170], ['tap', 0, 187503], ['tap', 1, 187836], ['tap', 2, 188169], ['tap', 3, 188502], ['tap', 0, 188835], ['tap', 1, 189168], ['tap', 2, 189501], ['tap', 3, 189834], ['tap', 0, 190167], ['tap', 1, 190500], ['tap', 2, 190833], ['tap', 3, 191166], ['tap', 0, 191499], ['tap', 1, 191832], ['tap', 2, 192165], ['tap', 3, 192498], ['tap', 0, 192831], ['tap', 1, 193164], ['tap', 2, 193497], ['tap', 3, 193830], ['tap', 0, 194163], ['tap', 1, 194496], ['tap', 2, 194829], ['tap', 3, 195000]]
	}
}];
// --- GUI ELEMENTS ---
var gui = LK.gui;
var languageButtons = [];
var startButton = null;
var songCards = [];
var storyText = null;
var continueButton = null;
var difficultyButtons = [];
var scoreText = null;
var comboText = null;
var accuracyText = null;
var resultText = null;
var homeButton = null;
// --- GAME ELEMENTS ---
var lanes = [];
var laneDividers = [];
var hitLine = null;
// --- UTILS ---
function clearGUI() {
	// Remove all children from gui overlays, but only if they exist
	if (gui.top && gui.top.removeChildren) {
		gui.top.removeChildren();
	}
	if (gui.topRight && gui.topRight.removeChildren) {
		gui.topRight.removeChildren();
	}
	if (gui.topLeft && gui.topLeft.removeChildren) {
		gui.topLeft.removeChildren();
	}
	if (gui.left && gui.left.removeChildren) {
		gui.left.removeChildren();
	}
	if (gui.right && gui.right.removeChildren) {
		gui.right.removeChildren();
	}
	if (gui.bottom && gui.bottom.removeChildren) {
		gui.bottom.removeChildren();
	}
	if (gui.bottomLeft && gui.bottomLeft.removeChildren) {
		gui.bottomLeft.removeChildren();
	}
	if (gui.bottomRight && gui.bottomRight.removeChildren) {
		gui.bottomRight.removeChildren();
	}
	if (gui.center && gui.center.removeChildren) {
		gui.center.removeChildren();
	}
	// Reset GUI-related global state
	languageButtons = [];
	startButton = null;
	songCards = [];
	storyText = null;
	continueButton = null;
	difficultyButtons = [];
	scoreText = null;
	comboText = null;
	accuracyText = null;
	resultText = null;
	homeButton = null;
}
function clearGameObjects() {
	removeGameplayBackgrounds(); // Ensure backgrounds are removed
	// Remove all notes, lanes, etc.
	for (var i = 0; i < lanes.length; ++i) {
		if (lanes[i] && lanes[i].parent) {
			lanes[i].parent.removeChild(lanes[i]);
		}
	}
	for (var i = 0; i < laneDividers.length; ++i) {
		if (laneDividers[i] && laneDividers[i].parent) {
			laneDividers[i].parent.removeChild(laneDividers[i]);
		}
	}
	if (hitLine && hitLine.parent) {
		hitLine.parent.removeChild(hitLine);
	}
	for (var i = 0; i < currentNotes.length; ++i) {
		if (currentNotes[i] && currentNotes[i].parent) {
			currentNotes[i].parent.removeChild(currentNotes[i]);
		}
	}
	lanes = [];
	laneDividers = [];
	hitLine = null;
	currentNotes = [];
	noteIndex = 0;
	holdActive = null;
	swipeStart = null;
	swipeNote = null;
	isPlaying = false;
	// Do not remove Stopsymbol pause button here; it should persist during gameplay
}
function setGameState(state) {
	GAME_STATE = state;
	// Always remove gameplay backgrounds from the game scene (prevents background leak to other screens)
	removeGameplayBackgrounds();
	removeGameplayBackgrounds(); // Double-call for safety
	// Stop any playing music when changing states (except when going to PLAY)
	if (state !== 'PLAY') {
		LK.stopMusic();
	}
	clearGUI();
	clearGameObjects();
	// Reset global state for each screen
	dragLane = null;
	dragY = null;
	dragNote = null;
	dragStartX = null;
	dragStartY = null;
	dragStartTime = null;
	if (state === 'LANGUAGE') {
		showLanguageSelector();
	} else if (state === 'HOME') {
		selectedSong = null;
		selectedDifficulty = null;
		showHome();
	} else if (state === 'STORY') {
		showStory();
	} else if (state === 'DIFFICULTY') {
		showDifficulty();
	} else if (state === 'PLAY') {
		startGameplay();
	} else if (state === 'RESULT') {
		showResult();
	}
}
// --- LANGUAGE SELECTOR ---
function showLanguageSelector() {
	languageButtons = [];
	// Get current screen size from LK
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Create opening screen container (with background)
	var openingScreen = new Container();
	// Add background asset, centered
	var bgAsset = LK.getAsset('Openingscreen', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0
	});
	openingScreen.addChild(bgAsset);
	// Add three SelectorUI assets as backgrounds for English, 中文, and play button
	// Calculate button positions and sizes to fit text
	var langBtnYStart = Math.floor(screenHeight * 0.45) - screenHeight / 2;
	var langBtnSpacing = Math.floor(screenHeight * 0.08);
	// English SelectorUI
	var selectorBgEn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: langBtnYStart,
		scaleX: 3.2,
		scaleY: 1.3
	});
	openingScreen.addChild(selectorBgEn);
	// 中文 SelectorUI
	var selectorBgZh = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: langBtnYStart + langBtnSpacing,
		scaleX: 2.2,
		scaleY: 1.3
	});
	openingScreen.addChild(selectorBgZh);
	// Play button SelectorUI
	var playBtnY = Math.floor(screenHeight * 0.70) - screenHeight / 2;
	var selectorBgPlay = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: playBtnY,
		scaleX: 3.2,
		scaleY: 1.5
	});
	openingScreen.addChild(selectorBgPlay);
	// Center all elements inside the openingScreen container
	// Title
	var titleText = new Text2("Kaleidoscope of Music Rhythm", {
		size: 80,
		fill: "#fff"
	});
	titleText.anchor.set(0.5, 0.5);
	// Place title at 23% of screen height, centered horizontally, and ensure it's not above y=80
	titleText.x = 0;
	titleText.y = Math.max(80 - screenHeight / 2, Math.floor(screenHeight * 0.23) - screenHeight / 2);
	openingScreen.addChild(titleText);
	// Language selector
	var langs = [{
		code: 'en',
		label: 'English'
	}, {
		code: 'zh',
		label: '中文'
	}];
	// Place language buttons at 45% and 53% of screen height, centered horizontally
	var langBtnYStart = Math.floor(screenHeight * 0.45) - screenHeight / 2;
	var langBtnSpacing = Math.floor(screenHeight * 0.08);
	for (var i = 0; i < langs.length; ++i) {
		(function (idx) {
			var btn = new Text2(langs[idx].label, {
				size: 64,
				fill: "#fff"
			});
			btn.anchor.set(0.5, 0.5);
			btn.x = 0; // centered in container
			btn.y = langBtnYStart + idx * langBtnSpacing;
			btn.interactive = true;
			btn.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedLanguage = langs[idx].code;
				storage.language = selectedLanguage;
				// Do not advance to HOME yet, just set language
				clearGUI();
				// Remove Stopsymbol pause button when returning to language selector
				removePauseButtonFromScoreboard();
				showLanguageSelector();
			};
			openingScreen.addChild(btn);
			languageButtons.push(btn);
		})(i);
	}
	// Start button
	startButton = new Text2(selectedLanguage === 'zh' ? "开始" : "Start", {
		size: 90,
		fill: "#fff"
	});
	startButton.anchor.set(0.5, 0.5);
	// Place start button at 70% of screen height, centered horizontally
	startButton.x = 0;
	startButton.y = Math.floor(screenHeight * 0.70) - screenHeight / 2;
	startButton.interactive = true;
	startButton.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		setGameState('HOME');
	};
	openingScreen.addChild(startButton);
	// Add the opening screen container to gui.center
	gui.center.addChild(openingScreen);
	// Play background music for opening screen
	LK.playMusic('KaleidoscopeMusicRhythm');
}
// --- HOME / SONG SELECTION ---
function showHome() {
	removeGameplayBackgrounds();
	clearGUI();
	clearGameObjects();
	// Remove Stopsymbol pause button when returning to home
	removePauseButtonFromScoreboard();
	// Remove gameplay backgrounds again to ensure they do not reappear on homepage
	removeGameplayBackgrounds();
	songCards = [];
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Create homeScreen container before adding children
	var homeScreen = new Container();
	// Add Backgroundhomepage asset as background, fully fit to screen
	var bgAsset = LK.getAsset('Backgroundhomepage', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: screenWidth / 2048,
		scaleY: screenHeight / 2732
	});
	homeScreen.addChild(bgAsset);
	bgAsset.width = screenWidth;
	bgAsset.height = screenHeight;
	// Title: "Select a Song", in white with proper positioning
	var title = new Text2(selectedLanguage === 'zh' ? "选择歌曲" : "Select a Song", {
		size: 90,
		fill: "#fff",
		// White text for contrast
		font: "Arial Black"
	});
	title.anchor.set(0.5, 0);
	// Place title at 15% of screen height, centered horizontally, and ensure it's not above y=120
	// For gui.center containers, (0,0) is the center, so offset from center
	title.x = 0;
	title.y = -screenHeight / 2 + Math.max(120, Math.floor(screenHeight * 0.15));
	homeScreen.addChild(title);
	// Card layout - vertical stacking with proper spacing
	var cardCount = SONGS.length;
	var cardHeight = 200;
	var cardSpacing = 80;
	var titleSpacing = 100;
	// Place the title at 15% of screen height, and cards start at 25% of screen height
	// We want all cards to be below the title and fully within SelectorUI
	// Calculate the starting Y so that the first card is just below the title, and all cards fit on screen
	var cardsStartY = title.y + title.height + titleSpacing; // 100px gap below title
	for (var i = 0; i < SONGS.length; i++) {
		(function (index) {
			var song = SONGS[index];
			// Card Y position
			var cardY = cardsStartY + index * (cardHeight + cardSpacing);
			// Create SelectorUI background for song card
			var selectorScaleX = 4.0;
			var selectorScaleY = 2.0;
			if (song.id === "NewEraMalayStyle") {
				selectorScaleX = 5.0;
				selectorScaleY = 2.5;
			}
			var cardBg = LK.getAsset('SelectorUI', {
				anchorX: 0.5,
				anchorY: 0.5,
				x: 0,
				y: cardY,
				scaleX: selectorScaleX,
				scaleY: selectorScaleY
			});
			homeScreen.addChild(cardBg);
			// Make card interactive
			cardBg.interactive = true;
			cardBg.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedSong = song;
				setGameState('STORY');
			};
			// Add song title (centered within SelectorUI)
			var songNameText;
			if (song.id === "UnknownJourney") {
				songNameText = new Text2(song.title, {
					size: 44,
					fill: "#fff",
					font: "Arial"
				});
			} else if (song.id === "NewEraMalayStyle") {
				var displayTitle = selectedLanguage === 'zh' ? song.title_zh : song.title;
				songNameText = new Text2(displayTitle, {
					size: 48,
					fill: "#fff",
					font: "Arial"
				});
			} else {
				songNameText = new Text2(song.title, {
					size: 64,
					fill: "#fff",
					font: "Arial"
				});
			}
			songNameText.anchor.set(0.5, 0.5);
			songNameText.x = 0;
			songNameText.y = song.id === "NewEraMalayStyle" ? cardY - 20 : cardY - 30;
			homeScreen.addChild(songNameText);
			// Add subtitle (optional: show "Sky Journey", "Night Pulse", "Sunrise Waltz", "Unknown Journey")
			var subtitleZh = "";
			if (selectedLanguage === 'zh') {
				if (song.title === "Sky Journey") subtitleZh = "天空之旅";else if (song.title === "Night Pulse") subtitleZh = "夜之律动";else if (song.title === "Sunrise Waltz") subtitleZh = "日出圆舞曲";else if (song.title === "Unknown Journey") subtitleZh = "莫问前程";else if (song.title === "New Era Malay Style") subtitleZh = "新纪元马来风";
			}
			var subtitleText;
			if (song.id === "UnknownJourney") {
				subtitleText = new Text2(subtitleZh, {
					size: 28,
					fill: 0xE0E0E0,
					font: "Arial"
				});
			} else if (song.id === "NewEraMalayStyle" && selectedLanguage === 'zh') {
				// Only show one instance of 新纪元马来风 (no duplicate small subtitle)
				subtitleText = new Text2("New Era Malay Style", {
					size: 36,
					fill: 0xE0E0E0,
					font: "Arial"
				});
			} else {
				subtitleText = new Text2(subtitleZh, {
					size: 40,
					fill: 0xE0E0E0,
					font: "Arial"
				});
			}
			subtitleText.anchor.set(0.5, 0.5);
			subtitleText.x = 0;
			subtitleText.y = cardY + 30;
			homeScreen.addChild(subtitleText);
			songCards.push(cardBg);
		})(i);
	}
	// Add Return button (top right, avoid 100x100 area, larger for visibility)
	var returnBtn = LK.getAsset('Return', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -120,
		//{6p} // Move leftwards to prevent off screen
		y: 120,
		//{6q} // Move downwards to prevent off screen
		scaleX: 2.6,
		scaleY: 2.6
	});
	returnBtn.interactive = true;
	returnBtn.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		setGameState('LANGUAGE');
	};
	// Add to gui.topRight for consistent top-right placement
	if (gui.topRight && gui.topRight.addChild) {
		gui.topRight.addChild(returnBtn);
	}
	// Add homeScreen to GUI, centered
	homeScreen.x = 0;
	homeScreen.y = 0;
	if (gui.center && gui.center.addChild) {
		gui.center.addChild(homeScreen);
	}
	// Play background music for homepage
	LK.playMusic('KaleidoscopeMusicRhythm');
}
// --- STORY INTRO ---
function showStory() {
	clearGUI();
	var story = selectedSong.story[selectedLanguage] || selectedSong.story['en'];
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Create SongStorybox container
	var storyBox = new Container();
	// Add SongStorybox asset as background, centered and scaled up for showcase
	var boxAsset = LK.getAsset('SongStorybox', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 13,
		// Increased for larger showcase
		scaleY: 11
	});
	storyBox.addChild(boxAsset);
	// Calculate box bounds for layout
	var boxW = boxAsset.width * boxAsset.scaleX;
	var boxH = boxAsset.height * boxAsset.scaleY;
	var boxLeftX = -boxW / 2;
	var boxRightX = boxW / 2;
	var boxTopY = -boxH / 2;
	var boxBottomY = boxH / 2;
	// Add smaller song cover on the left side of the box
	var coverAsset = LK.getAsset(selectedSong.cover, {
		anchorX: 0.5,
		anchorY: 0.5,
		x: boxLeftX + boxW * 0.25,
		// Position at 25% from left edge
		y: boxTopY + boxH * 0.35,
		// Position at 35% from top
		scaleX: 1.2,
		// Smaller cover
		scaleY: 1.2
	});
	storyBox.addChild(coverAsset);
	// Add story text on the larger right side of the box
	// For Chinese, ensure paragraph form and improved word wrap
	var storyParagraphs = [];
	if (selectedLanguage === 'zh') {
		// Remove all whitespace and line breaks
		var cleaned = story.replace(/[\r\n]/g, '');
		// Split at Chinese fullstop (。), Chinese comma (,), normal comma (,), normal fullstop (.), exclamation (!,!), question (?,?)
		var parts = cleaned.split(/(。|,|,|\.|!|!|\?|?)/);
		var temp = "";
		for (var i = 0; i < parts.length; i++) {
			temp += parts[i];
			// If the part is a punctuation, treat as paragraph end
			if (parts[i] === "。" || parts[i] === "," || parts[i] === "," || parts[i] === "." || parts[i] === "!" || parts[i] === "!" || parts[i] === "?" || parts[i] === "?") {
				if (temp.trim().length > 0) {
					storyParagraphs.push(temp.trim());
				}
				temp = "";
			}
		}
		// If any text remains (no ending punctuation), add as last paragraph
		if (temp.trim().length > 0) {
			storyParagraphs.push(temp.trim());
		}
		// Further split any paragraph that is too long to fit, to prevent off-screen/overlap
		var maxCharsPerParagraph = 13; // Tighter for best fit with cover
		var adjustedParagraphs = [];
		for (var i = 0; i < storyParagraphs.length; ++i) {
			var para = storyParagraphs[i];
			// If paragraph is too long, split into multiple
			while (para.length > maxCharsPerParagraph) {
				adjustedParagraphs.push(para.substring(0, maxCharsPerParagraph));
				para = para.substring(maxCharsPerParagraph);
			}
			if (para.length > 0) {
				adjustedParagraphs.push(para);
			}
		}
		storyParagraphs = adjustedParagraphs;
	} else {
		storyParagraphs = [story];
	}
	// Calculate Y start so text never overlaps cover and never goes off box
	var storyTextY = boxTopY + boxH * 0.13;
	for (var i = 0; i < storyParagraphs.length; ++i) {
		var para = storyParagraphs[i];
		var paraText = new Text2(para, {
			size: 55,
			fill: "#fff",
			wordWrap: true,
			wordWrapWidth: selectedLanguage === 'zh' ? boxW * 0.54 : boxW * 0.45,
			align: "left"
		});
		if (selectedLanguage === 'zh') {
			paraText.anchor.set(0, 0); // Left align for Chinese
			paraText.x = boxLeftX + boxW * 0.48; // Start further right for Chinese, away from cover
		} else {
			paraText.anchor.set(0, 0);
			paraText.x = boxLeftX + boxW * 0.52; // Start at 52% from left edge
		}
		paraText.y = storyTextY;
		storyBox.addChild(paraText);
		// Move down for next paragraph, add extra spacing for Chinese
		storyTextY += paraText.height + (selectedLanguage === 'zh' ? 18 : 16);
	}
	// Create 3 separate SelectorUI elements for difficulty buttons arranged horizontally
	var diffY = boxBottomY - boxH * 0.15; // Y position for all difficulty buttons
	var totalWidth = boxW * 0.8; // Use 80% of box width for all buttons
	var buttonWidth = totalWidth / selectedSong.difficulties.length;
	var startX = -totalWidth / 2 + buttonWidth / 2; // Center the button group
	difficultyButtons = [];
	for (var i = 0; i < selectedSong.difficulties.length; ++i) {
		(function (idx) {
			var label = selectedSong.difficulties[idx];
			// Create individual SelectorUI for this difficulty button
			var selectorAsset = LK.getAsset('SelectorUI', {
				anchorX: 0.5,
				anchorY: 0.5,
				x: startX + idx * buttonWidth,
				y: diffY,
				scaleX: 2.5,
				scaleY: 2.2
			});
			selectorAsset.interactive = true;
			selectorAsset.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedDifficulty = label;
				setGameState('PLAY');
			};
			storyBox.addChild(selectorAsset);
			// Add difficulty button text centered on the SelectorUI
			var btn = new Text2(label, {
				size: 55,
				fill: "#fff"
			});
			btn.anchor.set(0.5, 0.5);
			btn.x = startX + idx * buttonWidth;
			btn.y = diffY - 15;
			storyBox.addChild(btn);
			// Add high score display below difficulty button
			var highScore = getHighScore(selectedSong.id, label);
			var highScoreText = new Text2((selectedLanguage === 'zh' ? "最高: " : "Best: ") + highScore, {
				size: 36,
				fill: 0xFFD700
			});
			highScoreText.anchor.set(0.5, 0.5);
			highScoreText.x = startX + idx * buttonWidth;
			highScoreText.y = diffY + 25;
			storyBox.addChild(highScoreText);
			difficultyButtons.push(btn);
		})(i);
	}
	// Start storyBox at bottom-right corner of screen
	storyBox.x = screenWidth;
	storyBox.y = screenHeight;
	gui.center.addChild(storyBox);
	// Add Return button (top right, avoid 100x100 area, larger for visibility)
	var returnBtn = LK.getAsset('Return', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -120,
		//{7Q} // Move leftwards to prevent off screen
		y: 120,
		//{7R} // Move downwards to prevent off screen
		scaleX: 2.6,
		scaleY: 2.6
	});
	returnBtn.interactive = true;
	returnBtn.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		setGameState('HOME');
	};
	// Add to gui.topRight for consistent top-right placement
	if (gui.topRight && gui.topRight.addChild) {
		gui.topRight.addChild(returnBtn);
	}
	// Animate storyBox to center of screen
	tween(storyBox, {
		x: 0,
		y: 0
	}, {
		duration: 800,
		easing: tween.easeOut
	});
}
// --- DIFFICULTY SELECT ---
function showDifficulty() {
	difficultyButtons = [];
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	var title = new Text2(selectedLanguage === 'zh' ? "选择难度" : "Select Difficulty", {
		size: 100,
		fill: "#fff"
	});
	title.anchor.set(0.5, 0);
	title.x = screenWidth / 2;
	title.y = Math.max(100, Math.floor(screenHeight * 0.08));
	gui.top.addChild(title);
	var btnYStart = Math.floor(screenHeight * 0.40);
	var btnSpacing = Math.floor(screenHeight * 0.13);
	for (var i = 0; i < selectedSong.difficulties.length; ++i) {
		(function (idx) {
			var label = selectedSong.difficulties[idx];
			var btn = new Text2(label, {
				size: 90,
				fill: "#fff"
			});
			btn.anchor.set(0.5, 0.5);
			btn.x = screenWidth / 2;
			btn.y = btnYStart + idx * btnSpacing;
			btn.interactive = true;
			btn.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedDifficulty = label;
				setGameState('PLAY');
			};
			gui.center.addChild(btn);
			difficultyButtons.push(btn);
		})(i);
	}
}
// --- GAMEPLAY ---
function startGameplay() {
	removeGameplayBackgrounds(); // Clear previous backgrounds
	// Add new background for gameplay and track it
	if (selectedSong && selectedSong.id === 'SkyJourney') {
		gameplayBackground = LK.getAsset('BgSkyJourney', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'NightPulse') {
		gameplayBackground = LK.getAsset('BgNightPulse', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'SunriseWaltz') {
		gameplayBackground = LK.getAsset('BgSunriseWaltz', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'UnknownJourney') {
		gameplayBackground = LK.getAsset('BgUnknownJourney', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'NewEraMalayStyle') {
		gameplayBackground = LK.getAsset('BgNewEraMalayStyle', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3066.01
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	}
	lanes = [];
	laneDividers = [];
	var laneSpacing = 2048 / LANE_COUNT; // Equal spacing for 4 lanes
	for (var i = 0; i < LANE_COUNT; ++i) {
		var lane = LK.getAsset('lane', {
			anchorX: 0.5,
			anchorY: 0
		});
		lane.x = (i + 0.5) * laneSpacing; // Center each lane in its section
		lane.y = 0;
		lane.scaleX = laneSpacing / lane.width; // Scale to fit lane width
		lane.scaleY = 2732 / lane.height; // Scale to full screen height
		lanes.push(lane);
		game.addChild(lane);
	}
	// Create lane dividers as hit areas at the bottom of lanes
	for (var i = 0; i < LANE_COUNT; ++i) {
		var divider = LK.getAsset('laneDivider', {
			anchorX: 0.5,
			anchorY: 1
		});
		divider.x = (i + 0.5) * laneSpacing; // Center in each lane
		divider.y = HIT_LINE_Y + 100; // Position at bottom as hit area
		divider.scaleX = laneSpacing * 0.8 / divider.width; // Scale to 80% of lane width
		divider.scaleY = 0.3; // Make it thinner for hit area
		laneDividers.push(divider);
		game.addChild(divider);
	}
	// Hit line (visual indicator)
	hitLine = LK.getAsset('laneDivider', {
		anchorX: 0,
		anchorY: 0.5
	});
	hitLine.width = 2048;
	hitLine.height = 8;
	hitLine.x = 0;
	hitLine.y = HIT_LINE_Y;
	hitLine.alpha = 0.7; // Make it semi-transparent
	game.addChild(hitLine);
	// Load notes
	currentSongData = selectedSong.notes[selectedDifficulty];
	currentNotes = [];
	noteIndex = 0;
	score = 0;
	combo = 0;
	maxCombo = 0;
	accuracy = 0;
	totalNotes = currentSongData.length;
	hitNotes = 0;
	missNotes = 0;
	badNotes = 0;
	goodNotes = 0;
	greatNotes = 0;
	perfectNotes = 0;
	scorePopups = [];
	holdActive = null;
	swipeStart = null;
	swipeNote = null;
	isPlaying = true;
	for (var i = 0; i < currentSongData.length; ++i) {
		var n = currentSongData[i];
		var note = new Note();
		note.init(n[0], n[1], n[2], n[3], n[4]);
		// Initial position: y far above screen, will be updated in update loop
		var laneSpacing = 2048 / LANE_COUNT;
		note.x = (n[1] + 0.5) * laneSpacing; // Center in the lane
		note.y = -200;
		currentNotes.push(note);
		game.addChild(note);
	}
	// Create scoreboard container with SelectorUI background
	var scoreboardBg = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0,
		x: 0,
		y: 0,
		scaleX: 8.0,
		scaleY: 2.0
	});
	gui.top.addChild(scoreboardBg);
	// Score/Combo/Accuracy GUI positioned on scoreboard
	var scoreboardFontColor = selectedSong && selectedSong.id === 'UnknownJourney' ? "#000" : "#fff";
	scoreText = new Text2("Score: 0", {
		size: 70,
		fill: scoreboardFontColor
	});
	scoreText.anchor.set(0.5, 0);
	scoreText.x = 0;
	scoreText.y = 20;
	gui.top.addChild(scoreText);
	comboText = new Text2("Combo: 0", {
		size: 70,
		fill: scoreboardFontColor
	});
	comboText.anchor.set(0.5, 0);
	comboText.x = 0;
	comboText.y = 70;
	gui.top.addChild(comboText);
	accuracyText = new Text2("Accuracy: 100%", {
		size: 70,
		fill: scoreboardFontColor
	});
	accuracyText.anchor.set(0.5, 0);
	accuracyText.x = 0;
	accuracyText.y = 120;
	gui.top.addChild(accuracyText);
	// Stop any playing background music immediately before starting gameplay music
	LK.stopMusic();
	// Start music - play full song for each gameplay without looping
	LK.playMusic(selectedSong.id, {
		loop: false
	});
	// Start timer
	startTime = Date.now();
	// Always add Stopsymbol pause button at the start of gameplay
	addPauseButtonToScoreboard();
}
// --- GAMEPLAY LOGIC ---
function getCurrentTime() {
	return Date.now() - startTime;
}
function getNoteY(noteTime) {
	// Notes fall from y = -200 to HIT_LINE_Y at the time they should be hit
	// We'll use a fixed speed so that notes reach HIT_LINE_Y at noteTime
	var speed = (HIT_LINE_Y + 200) / 2000; // 2 seconds to fall
	var t = getCurrentTime();
	var dt = noteTime - t;
	return HIT_LINE_Y - dt * speed;
}
function getHoldTrailLength(note, t) {
	// For hold notes, trail from note head to end time
	var endTime = note.time + note.duration;
	var y1 = getNoteY(note.time);
	var y2 = getNoteY(endTime);
	return Math.max(0, y2 - y1);
}
function judgeNote(note, lane, y, eventType, swipeDir) {
	if (!note.active || note.hit) {
		return false;
	}
	var noteY = getNoteY(note.time);
	var t = getCurrentTime();
	var dt = Math.abs(note.time - t);
	var scoreType = "";
	var scoreValue = 0;
	// Determine hit quality based on timing
	if (dt <= PERFECT_WINDOW) {
		scoreType = "perfect";
		scoreValue = 150;
		perfectNotes += 1;
	} else if (dt <= GREAT_WINDOW) {
		scoreType = "great";
		scoreValue = 120;
		greatNotes += 1;
	} else if (dt <= GOOD_WINDOW) {
		scoreType = "good";
		scoreValue = 80;
		goodNotes += 1;
	} else if (dt <= BAD_WINDOW) {
		scoreType = "bad";
		scoreValue = 40;
		badNotes += 1;
	} else {
		return false; // Miss
	}
	if (note.type === 'tap') {
		if (lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW) {
			note.hit = true;
			note.active = false;
			note.flash();
			// Create sparkling effect on the note itself
			tween(note, {
				scaleX: note.noteAsset.scaleX * 1.3,
				scaleY: note.noteAsset.scaleY * 1.3,
				rotation: 0.2
			}, {
				duration: 100,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					tween(note, {
						scaleX: note.noteAsset.scaleX / 1.3,
						scaleY: note.noteAsset.scaleY / 1.3,
						rotation: 0
					}, {
						duration: 100,
						easing: tween.easeIn
					});
				}
			});
			// Immediately remove from currentNotes array to prevent flow issues
			var tapIndex = currentNotes.indexOf(note);
			if (tapIndex > -1) {
				currentNotes.splice(tapIndex, 1);
			}
			// Make note disappear immediately after touch with burst animation
			tween(note, {
				alpha: 0,
				scaleX: 1.5,
				scaleY: 1.5
			}, {
				duration: 150,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			LK.getSound('tap').play();
			score += scoreValue;
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit area
			var hitArea = laneDividers[lane];
			if (hitArea) {
				// Store original tint if not already stored
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				// Light up with golden glow
				hitArea.tint = 0xFFD700;
				hitArea.alpha = 1.0;
				// Store original scale if not already stored
				if (hitArea.originalScaleX === undefined) {
					hitArea.originalScaleX = hitArea.scaleX;
					hitArea.originalScaleY = hitArea.scaleY;
				}
				// Create sparkling effect with scaling animation, but never allow scale to exceed original
				tween(hitArea, {
					scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.3),
					scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.3)
				}, {
					duration: 150,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(hitArea, {
							scaleX: hitArea.originalScaleX,
							scaleY: hitArea.originalScaleY
						}, {
							duration: 150,
							easing: tween.easeIn
						});
					}
				});
				// Fade back to normal after short duration
				tween(hitArea, {
					alpha: 0.7
				}, {
					duration: 200,
					onFinish: function onFinish() {
						hitArea.tint = hitArea.originalTint;
					}
				});
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			return true;
		}
	} else if (note.type === 'hold') {
		// EXTREMELY player-friendly hold detection for both Hard and Medium:
		// - Allow full-lane (all lanes) detection
		// - Allow very wide vertical margin
		// - Greatly increase timing window for initial press
		// - Allow initial press anywhere in the lower half of the screen
		// - Allow initial press up to 500ms before/after the note
		// This is now applied to both Hard and Medium difficulties
		var isMediumOrHard = selectedDifficulty === 'Medium' || selectedDifficulty === 'Hard';
		var isValidLane = true; // Allow any lane for initial press
		var verticalLeniency = isMediumOrHard ? 950 : 350; // Very wide for Medium/Hard, default for Easy
		var timingLeniency = isMediumOrHard ? BAD_WINDOW + 500 : BAD_WINDOW; // More lenient for Medium/Hard
		if (eventType === 'down' && isValidLane && Math.abs(y - HIT_LINE_Y) < verticalLeniency && dt < timingLeniency) {
			holdActive = {
				note: note,
				start: t,
				scoreType: scoreType,
				scoreValue: scoreValue
			};
			note.flash();
			// Start continuous sparkling animation on hit area during hold
			var hitArea = laneDividers[note.lane];
			if (hitArea) {
				// Create pulsing sparkling effect
				var _createSparkle = function createSparkle() {
					if (holdActive && holdActive.note === note) {
						hitArea.tint = 0xFFD700; // Golden sparkle
						tween(hitArea, {
							scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.2),
							scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.2),
							alpha: 1.0
						}, {
							duration: 200,
							easing: tween.easeOut,
							onFinish: function onFinish() {
								tween(hitArea, {
									scaleX: hitArea.originalScaleX,
									scaleY: hitArea.originalScaleY,
									alpha: 0.8
								}, {
									duration: 200,
									easing: tween.easeIn,
									onFinish: function onFinish() {
										// Continue sparkling if still holding
										if (holdActive && holdActive.note === note) {
											LK.setTimeout(_createSparkle, 100);
										} else {
											// Reset hit area when hold ends
											hitArea.tint = hitArea.originalTint;
											hitArea.scaleX = hitArea.originalScaleX;
											hitArea.scaleY = hitArea.originalScaleY;
											hitArea.alpha = 0.7;
										}
									}
								});
							}
						});
					}
				};
				// Store original properties
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				if (hitArea.originalScaleX === undefined) {
					hitArea.originalScaleX = hitArea.scaleX;
					hitArea.originalScaleY = hitArea.scaleY;
				}
				_createSparkle();
			}
			LK.getSound('hold').play();
			return true;
		}
	} else if (note.type === 'swipe') {
		// Check if swipe affects multiple lanes - enhanced detection for bigger swipe notes
		var validLanes = [note.lane];
		var laneSpacing = 2048 / LANE_COUNT;
		var swipeWidth = note.noteAsset.width * note.noteAsset.scaleX;
		// Enhanced cross-lane detection for bigger swipe notes
		if (swipeWidth > laneSpacing * 1.8) {
			// More generous threshold
			// Add adjacent lanes for cross-lane swipes
			if (note.lane > 0) validLanes.push(note.lane - 1);
			if (note.lane < LANE_COUNT - 1) validLanes.push(note.lane + 1);
			// For very wide swipes, add one more lane on each side
			if (swipeWidth > laneSpacing * 2.5) {
				if (note.lane > 1) validLanes.push(note.lane - 2);
				if (note.lane < LANE_COUNT - 2) validLanes.push(note.lane + 2);
			}
		}
		var isValidLane = validLanes.indexOf(lane) !== -1;
		// Improved sensitivity - increased distance threshold and relaxed timing for better swipe detection
		// Further increase vertical margin and timing window for better swipe detection
		// Even more forgiving: vertical margin 1200, timing window BAD_WINDOW+800
		if (eventType === 'swipe' && isValidLane && Math.abs(y - HIT_LINE_Y) < 1200 && dt < BAD_WINDOW + 800 && swipeDir === note.swipeDir) {
			note.hit = true;
			note.active = false;
			note.flash();
			// Make swipe note disappear immediately after touch with directional slide animation
			var slideDir = note.swipeDir;
			var targetX = note.x;
			var targetY = note.y;
			var slideDistance = 400; // Increased slide distance
			if (slideDir === 'left') targetX -= slideDistance;else if (slideDir === 'right') targetX += slideDistance;else if (slideDir === 'up') targetY -= slideDistance;else if (slideDir === 'down') targetY += slideDistance;
			// Store reference to note for proper cleanup and prevent red tinting
			var noteToRemove = note;
			// Ensure note is removed from currentNotes array immediately to prevent red tinting and flow issues
			var index = currentNotes.indexOf(noteToRemove);
			if (index > -1) {
				currentNotes.splice(index, 1);
			}
			// Prevent any color tinting by ensuring clean removal
			noteToRemove.tint = 0xFFFFFF;
			noteToRemove.alpha = 0; // Make invisible immediately
			// Remove from parent immediately to prevent any visual artifacts
			if (noteToRemove.parent) {
				noteToRemove.parent.removeChild(noteToRemove);
			}
			// Optional visual effect for feedback
			var effectNote = LK.getAsset('swipeNote', {
				anchorX: 0.5,
				anchorY: 0.5,
				x: note.x,
				y: note.y,
				scaleX: note.noteAsset.scaleX,
				scaleY: note.noteAsset.scaleY
			});
			game.addChild(effectNote);
			tween(effectNote, {
				x: targetX,
				y: targetY,
				alpha: 0,
				scaleX: note.noteAsset.scaleX * 0.3,
				scaleY: note.noteAsset.scaleY * 0.3
			}, {
				duration: 150,
				// Faster animation for better responsiveness
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (effectNote && effectNote.parent) {
						effectNote.parent.removeChild(effectNote);
					}
				}
			});
			LK.getSound('swipe').play();
			score += scoreValue + 50; // Bonus for swipe notes
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit areas for swipe notes (can affect multiple lanes)
			for (var v = 0; v < validLanes.length; v++) {
				var hitArea = laneDividers[validLanes[v]];
				if (hitArea) {
					// Store original tint if not already stored
					if (hitArea.originalTint === undefined) {
						hitArea.originalTint = hitArea.tint || 0xFFFFFF;
					}
					// Light up with cyan glow for swipe
					hitArea.tint = 0x00FFFF;
					hitArea.alpha = 1.0;
					// Create sparkling wave effect with scaling animation, but never allow scale to exceed original
					var delay = v * 50;
					tween(hitArea, {
						scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.4),
						scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.4)
					}, {
						duration: 200,
						delay: delay,
						easing: tween.easeOut,
						onFinish: function onFinish() {
							tween(hitArea, {
								scaleX: hitArea.originalScaleX,
								scaleY: hitArea.originalScaleY
							}, {
								duration: 200,
								easing: tween.easeIn
							});
						}
					});
					// Stagger the fade timing for visual wave effect
					tween(hitArea, {
						alpha: 0.7
					}, {
						duration: 250,
						delay: delay,
						onFinish: function onFinish() {
							hitArea.tint = hitArea.originalTint;
						}
					});
				}
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			return true;
		}
	}
	return false;
}
function endHold() {
	if (holdActive && holdActive.note && !holdActive.note.hit) {
		var t = getCurrentTime();
		var note = holdActive.note;
		var holdEnd = note.time + note.duration;
		// Only complete hold if duration has been fully held AND player is still holding
		if (t >= holdEnd) {
			note.hit = true;
			note.active = false;
			// Immediately remove from currentNotes array to prevent flow issues
			var holdIndex = currentNotes.indexOf(note);
			if (holdIndex > -1) {
				currentNotes.splice(holdIndex, 1);
			}
			// Make hold note disappear slowly with longer satisfying animation
			tween(note, {
				alpha: 0,
				scaleX: 0.1,
				scaleY: 0.1,
				tint: 0x00FF00 // Green glow for successful hold completion
			}, {
				duration: 800,
				//{8j} // Much longer fade
				easing: tween.easeIn,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			score += holdActive.scoreValue + 50; // Bonus for completing hold
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit area for completed hold
			var lane = note.lane;
			var hitArea = laneDividers[lane];
			if (hitArea) {
				// Store original tint if not already stored
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				// Light up with green glow for hold completion
				hitArea.tint = 0x00FF00;
				hitArea.alpha = 1.0;
				// Create sparkling effect with pulsing animation for hold completion, but never allow scale to exceed original
				tween(hitArea, {
					scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.5),
					scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.5)
				}, {
					duration: 200,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(hitArea, {
							scaleX: hitArea.originalScaleX,
							scaleY: hitArea.originalScaleY
						}, {
							duration: 200,
							easing: tween.easeIn
						});
					}
				});
				// Fade back to normal after short duration
				tween(hitArea, {
					alpha: 0.7
				}, {
					duration: 300,
					onFinish: function onFinish() {
						hitArea.tint = hitArea.originalTint;
					}
				});
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(holdActive.scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			holdActive = null;
		}
	}
}
// --- INPUT HANDLING ---
var dragLane = null;
var dragY = null;
var dragNote = null;
var dragStartX = null;
var dragStartY = null;
var dragStartTime = null;
function getLaneFromX(x) {
	var laneSpacing = 2048 / LANE_COUNT;
	for (var i = 0; i < LANE_COUNT; ++i) {
		var laneStart = i * laneSpacing;
		var laneEnd = (i + 1) * laneSpacing;
		if (x >= laneStart && x < laneEnd) {
			return i;
		}
	}
	return -1;
}
function handleGameDown(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	var lane = getLaneFromX(x);
	if (lane === -1) {
		return;
	}
	dragLane = lane;
	dragY = y;
	dragStartX = x;
	dragStartY = y;
	dragStartTime = Date.now();
	// Check for tap/hold notes
	for (var i = 0; i < currentNotes.length; ++i) {
		var note = currentNotes[i];
		if (!note.active || note.hit) {
			continue;
		}
		if (note.type === 'tap' || note.type === 'hold') {
			if (judgeNote(note, lane, y, 'down')) {
				if (note.type === 'tap') {
					note.hit = true;
					note.active = false;
				}
				if (note.type === 'hold') {
					dragNote = note;
				}
				break;
			}
		}
	}
}
function handleGameUp(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	if (holdActive && holdActive.note) {
		var t = getCurrentTime();
		var note = holdActive.note;
		var holdEnd = note.time + note.duration;
		// Check if hold was completed (reached full duration)
		if (t >= holdEnd) {
			endHold();
		} else {
			// Released too early - but if the button detected the hold note with sparkling light, count as good/great/perfect based on timing
			// Use the timing at release to judge
			var dt = Math.abs(note.time - t);
			var scoreType = "";
			var scoreValue = 0;
			if (dt <= PERFECT_WINDOW) {
				scoreType = "perfect";
				scoreValue = 150;
				perfectNotes += 1;
			} else if (dt <= GREAT_WINDOW) {
				scoreType = "great";
				scoreValue = 120;
				greatNotes += 1;
			} else if (dt <= GOOD_WINDOW) {
				scoreType = "good";
				scoreValue = 80;
				goodNotes += 1;
			} else if (dt <= BAD_WINDOW) {
				scoreType = "bad";
				scoreValue = 40;
				badNotes += 1;
			} else {
				// If not within any window, count as miss
				note.hit = false;
				note.active = false;
				// Make failed hold note fade out with red tint
				tween(note, {
					alpha: 0.3,
					tint: 0xFF0000
				}, {
					duration: 400,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						if (note.parent) {
							note.parent.removeChild(note);
						}
					}
				});
				combo = 0;
				missNotes += 1;
				// Show miss popup
				var popup = new ScorePopup();
				popup.init("miss", note.x, note.y);
				game.addChild(popup);
				scorePopups.push(popup);
				holdActive = null;
				return;
			}
			// If we get here, it's a good/great/perfect/bad (not miss)
			note.hit = true;
			note.active = false;
			// Remove from currentNotes array
			var holdIndex = currentNotes.indexOf(note);
			if (holdIndex > -1) {
				currentNotes.splice(holdIndex, 1);
			}
			// Make hold note disappear with a fade animation
			tween(note, {
				alpha: 0,
				scaleX: 0.1,
				scaleY: 0.1,
				tint: 0xFFD700 // Gold for partial hold
			}, {
				duration: 500,
				easing: tween.easeIn,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			score += scoreValue;
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit area for partial hold
			var lane = note.lane;
			var hitArea = laneDividers[lane];
			if (hitArea) {
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				hitArea.tint = 0xFFD700;
				hitArea.alpha = 1.0;
				tween(hitArea, {
					scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.2),
					scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.2)
				}, {
					duration: 150,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(hitArea, {
							scaleX: hitArea.originalScaleX,
							scaleY: hitArea.originalScaleY
						}, {
							duration: 150,
							easing: tween.easeIn
						});
					}
				});
				tween(hitArea, {
					alpha: 0.7
				}, {
					duration: 200,
					onFinish: function onFinish() {
						hitArea.tint = hitArea.originalTint;
					}
				});
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			holdActive = null;
		}
	}
	dragLane = null;
	dragNote = null;
	dragStartX = null;
	dragStartY = null;
	dragStartTime = null;
}
function handleGameMove(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	// For hold notes, check if finger is still on lane - must maintain contact until completion
	if (holdActive && dragNote) {
		var t = getCurrentTime();
		var note = holdActive.note;
		var holdEnd = note.time + note.duration;
		// Allow movement anywhere horizontally (any lane) for holds
		// Allow extremely wide vertical margin for forgiving detection
		var verticalLeniency = 950;
		if (Math.abs(y - HIT_LINE_Y) > verticalLeniency) {
			// Released too early or moved too far - fail the hold note
			note.hit = false;
			note.active = false;
			// Make failed hold note fade out with red tint
			tween(note, {
				alpha: 0.3,
				tint: 0xFF0000
			}, {
				duration: 400,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			combo = 0;
			missNotes += 1;
			// Show miss popup
			var popup = new ScorePopup();
			popup.init("miss", note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			holdActive = null;
			dragNote = null;
		} else if (t >= holdEnd) {
			// Successfully held until completion
			endHold();
		}
	}
}
function handleGameSwipe(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	if (dragStartX === null || dragStartY === null) {
		return;
	}
	var dx = x - dragStartX;
	var dy = y - dragStartY;
	var absDx = Math.abs(dx);
	var absDy = Math.abs(dy);
	// Enhanced sensitivity - reduced movement threshold and require minimum distance
	var minSwipeDistance = 10; // Lowered from 18 to 10 for even more responsive swipe detection
	var swipeDistance = Math.sqrt(dx * dx + dy * dy);
	if (swipeDistance < minSwipeDistance) {
		return;
	} // Not enough movement
	// Only allow left-to-right (right) swipes for swipe notes
	var dir = null;
	if (absDx > absDy && absDx > 20 && dx > 0) {
		// Only allow right swipes (left-to-right)
		dir = 'right';
	} else {
		return;
	}
	var lane = getLaneFromX(dragStartX);
	// Find the best matching swipe note that is near the hit area
	var bestNote = null;
	var bestScore = -1;
	for (var i = 0; i < currentNotes.length; ++i) {
		var note = currentNotes[i];
		if (!note.active || note.hit || note.type !== 'swipe') {
			continue;
		}
		// Only allow swipe detection when note is close to the hit area - more lenient threshold
		var noteY = getNoteY(note.time);
		var distanceToHitArea = Math.abs(noteY - HIT_LINE_Y);
		if (distanceToHitArea > 1200) {
			// Increased from 700 to 1200 for even better sensitivity
			continue; // Note is too far from hit area, skip
		}
		// Check if this swipe note matches the gesture direction
		if (note.swipeDir === dir) {
			var t = getCurrentTime();
			var dt = Math.abs(note.time - t);
			// Score based on timing and position accuracy
			var timingScore = Math.max(0, BAD_WINDOW - dt);
			var positionScore = Math.max(0, 280 - Math.abs(dragStartY - HIT_LINE_Y));
			var totalScore = timingScore + positionScore;
			if (totalScore > bestScore) {
				bestScore = totalScore;
				bestNote = note;
			}
		}
	}
	// Execute the best matching swipe note
	if (bestNote && judgeNote(bestNote, lane, dragStartY, 'swipe', dir)) {
		// Successfully completed swipe
	}
	dragStartX = null;
	dragStartY = null;
}
// --- PAUSE MENU OVERLAY ---
var pauseMenuOverlay = null;
var pauseMenuResumeBtn = null;
var pauseMenuRestartBtn = null;
var pauseMenuExitBtn = null;
var isPauseMenuActive = false;
function showPauseMenu() {
	if (isPauseMenuActive) return;
	isPauseMenuActive = true;
	isPlaying = false;
	LK.stopMusic();
	// Create overlay container
	pauseMenuOverlay = new Container();
	// Dimmed background
	var overlayBg = LK.getAsset('Menu', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 30,
		scaleY: 30
	});
	overlayBg.alpha = 0.7;
	pauseMenuOverlay.addChild(overlayBg);
	// Menu panel (increased size)
	var menuPanel = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 8.5,
		// increased width
		scaleY: 9.5 // increased height
	});
	pauseMenuOverlay.addChild(menuPanel);
	// Title
	var pauseTitle = new Text2(selectedLanguage === 'zh' ? "暂停" : "Paused", {
		size: 110,
		// larger title
		fill: "#fff"
	});
	pauseTitle.anchor.set(0.5, 0.5);
	pauseTitle.x = 0;
	pauseTitle.y = -420; // move up for larger panel
	pauseMenuOverlay.addChild(pauseTitle);
	// Resume button
	pauseMenuResumeBtn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: -180,
		// move up for larger panel
		scaleX: 4.5,
		scaleY: 2.2
	});
	pauseMenuResumeBtn.interactive = true;
	pauseMenuOverlay.addChild(pauseMenuResumeBtn);
	var resumeText = new Text2(selectedLanguage === 'zh' ? "继续" : "Resume", {
		size: 90,
		fill: "#fff"
	});
	resumeText.anchor.set(0.5, 0.5);
	resumeText.x = 0;
	resumeText.y = -180;
	pauseMenuOverlay.addChild(resumeText);
	// Restart button
	pauseMenuRestartBtn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 20,
		scaleX: 4.5,
		scaleY: 2.2
	});
	pauseMenuRestartBtn.interactive = true;
	pauseMenuOverlay.addChild(pauseMenuRestartBtn);
	var restartText = new Text2(selectedLanguage === 'zh' ? "重来" : "Restart", {
		size: 90,
		fill: "#fff"
	});
	restartText.anchor.set(0.5, 0.5);
	restartText.x = 0;
	restartText.y = 20;
	pauseMenuOverlay.addChild(restartText);
	// Exit button
	pauseMenuExitBtn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 220,
		scaleX: 4.5,
		scaleY: 2.2
	});
	pauseMenuExitBtn.interactive = true;
	pauseMenuOverlay.addChild(pauseMenuExitBtn);
	var exitText = new Text2(selectedLanguage === 'zh' ? "退出" : "Exit", {
		size: 90,
		fill: "#fff"
	});
	exitText.anchor.set(0.5, 0.5);
	exitText.x = 0;
	exitText.y = 220;
	pauseMenuOverlay.addChild(exitText);
	// Start hidden and scale up with animation
	pauseMenuOverlay.alpha = 0;
	pauseMenuOverlay.scaleX = 0.7;
	pauseMenuOverlay.scaleY = 0.7;
	gui.center.addChild(pauseMenuOverlay);
	tween(pauseMenuOverlay, {
		alpha: 1,
		scaleX: 1,
		scaleY: 1
	}, {
		duration: 350,
		easing: tween.easeOut
	});
	// Button handlers: animate out, then call action
	pauseMenuResumeBtn.down = function () {
		LK.getSound('Touch').play();
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 250,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				hidePauseMenu();
				// Always re-add pause button after resuming
				addPauseButtonToScoreboard();
			}
		});
	};
	pauseMenuRestartBtn.down = function () {
		LK.getSound('Touch').play();
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 250,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				hidePauseMenu();
				// Stop and restart the music from the beginning
				LK.stopMusic();
				setGameState('PLAY');
				// Always re-add pause button after restarting
				addPauseButtonToScoreboard();
			}
		});
	};
	pauseMenuExitBtn.down = function () {
		LK.getSound('Touch').play();
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 250,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				hidePauseMenu();
				setGameState('HOME');
				// Always re-add pause button after exiting (if needed)
				addPauseButtonToScoreboard();
			}
		});
	};
}
function hidePauseMenu() {
	if (!isPauseMenuActive) return;
	isPauseMenuActive = false;
	isPlaying = true;
	// If overlay is still visible, animate out and remove after
	if (pauseMenuOverlay && pauseMenuOverlay.parent) {
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 200,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				if (pauseMenuOverlay && pauseMenuOverlay.parent) {
					pauseMenuOverlay.parent.removeChild(pauseMenuOverlay);
				}
				pauseMenuOverlay = null;
				pauseMenuResumeBtn = null;
				pauseMenuRestartBtn = null;
				pauseMenuExitBtn = null;
				// Always re-add pause button after closing pause menu
				addPauseButtonToScoreboard();
			}
		});
	} else {
		pauseMenuOverlay = null;
		pauseMenuResumeBtn = null;
		pauseMenuRestartBtn = null;
		pauseMenuExitBtn = null;
		// Always re-add pause button after closing pause menu
		addPauseButtonToScoreboard();
	}
}
// Add pause button to gameplay scoreboard
function addPauseButtonToScoreboard() {
	// Only add if in PLAY state
	if (GAME_STATE !== 'PLAY') return;
	// Remove any existing pause button to prevent duplicates
	if (gui.topRight && gui.topRight.pauseBtn) {
		if (gui.topRight.pauseBtn.parent) {
			gui.topRight.pauseBtn.parent.removeChild(gui.topRight.pauseBtn);
		}
		gui.topRight.pauseBtn = null;
	}
	// Use Stopsymbol asset for pause button
	if (gui.topRight) {
		var pauseBtn = LK.getAsset('Stopsymbol', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: -60,
			y: 60,
			scaleX: 2.2,
			scaleY: 2.2
		});
		pauseBtn.interactive = true;
		pauseBtn.down = function () {
			LK.getSound('Touch').play();
			showPauseMenu();
		};
		gui.topRight.addChild(pauseBtn);
		gui.topRight.pauseBtn = pauseBtn;
	}
}
// Remove pause button from scoreboard
function removePauseButtonFromScoreboard() {
	if (gui.topRight && gui.topRight.pauseBtn) {
		if (gui.topRight.pauseBtn.parent) {
			gui.topRight.pauseBtn.parent.removeChild(gui.topRight.pauseBtn);
		}
		gui.topRight.pauseBtn = null;
	}
}
// Attach input handlers
game.down = function (x, y, obj) {
	// If pause menu is active, block gameplay input
	if (isPauseMenuActive) return;
	handleGameDown(x, y, obj);
};
game.up = function (x, y, obj) {
	if (isPauseMenuActive) return;
	handleGameUp(x, y, obj);
};
game.move = function (x, y, obj) {
	if (isPauseMenuActive) return;
	handleGameMove(x, y, obj);
	// Detect swipe with improved sensitivity - reduced movement threshold
	if (dragStartX !== null && dragStartY !== null) {
		var dx = x - dragStartX;
		var dy = y - dragStartY;
		// Improved sensitivity - reduced movement threshold for better swipe detection
		if (Math.abs(dx) > 30 || Math.abs(dy) > 30) {
			handleGameSwipe(x, y, obj);
		}
	}
};
// Add pause button when entering gameplay, remove when leaving
var _originalSetGameState = setGameState;
setGameState = function setGameState(state) {
	_originalSetGameState(state);
	if (state === 'PLAY') {
		// Do not remove Stopsymbol pause button in PLAY mode
		addPauseButtonToScoreboard();
	} else {
		removePauseButtonFromScoreboard();
		hidePauseMenu();
	}
};
// --- GAME UPDATE LOOP ---
game.update = function () {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	var t = getCurrentTime();
	// Update notes
	for (var i = 0; i < currentNotes.length; ++i) {
		var note = currentNotes[i];
		if (!note.active) {
			continue;
		}
		// Always update note position every frame to prevent stuck notes
		if (note.time > 0) {
			var laneSpacing = 2048 / LANE_COUNT;
			note.x = (note.lane + 0.5) * laneSpacing; // Center in the lane
			// Always update Y, even if out of visible range, to ensure flow
			note.y = getNoteY(note.time);
		}
		// Only mark notes as missed if they have passed VERY far beyond the hit area to prevent automatic disappearing
		var timePassed = t - note.time;
		if (!note.hit && note.y > HIT_LINE_Y + 1000 && timePassed > BAD_WINDOW + 1000 && note.type !== 'hold') {
			note.active = false;
			// Do not set note.hit = true here, so that removal logic is consistent
			// Immediately remove from currentNotes array to prevent flow issues
			var missedIndex = currentNotes.indexOf(note);
			if (missedIndex > -1) {
				currentNotes.splice(missedIndex, 1);
				// Do not decrement i here, as we always iterate forward and skip removed notes
			}
			// Make missed note fade out and disappear completely
			tween(note, {
				alpha: 0,
				scaleX: 0.5,
				scaleY: 0.5
			}, {
				duration: 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			combo = 0;
			missNotes += 1;
			// Show miss popup
			var popup = new ScorePopup();
			popup.init("miss", note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			continue; // Skip to next note since this one was processed
		}
		// For hold notes, only mark as missed if they are VERY far past the hit area to prevent automatic disappearing
		var holdTimePassed = t - note.time;
		if (note.type === 'hold' && !note.hit && note.y > HIT_LINE_Y + 1200 && holdActive === null && holdTimePassed > BAD_WINDOW + 1500) {
			note.active = false;
			// Do not set note.hit = true here, so that removal logic is consistent
			// Immediately remove from currentNotes array
			var holdMissedIndex = currentNotes.indexOf(note);
			if (holdMissedIndex > -1) {
				currentNotes.splice(holdMissedIndex, 1);
				// Do not decrement i here, as we always iterate forward and skip removed notes
			}
			// Make missed hold note fade out with red tint
			tween(note, {
				alpha: 0,
				scaleX: 0.5,
				scaleY: 0.5
			}, {
				duration: 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			combo = 0;
			missNotes += 1;
			// Show miss popup
			var popup = new ScorePopup();
			popup.init("miss", note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			continue; // Skip to next note since this one was processed
		}
	}
	// Remove notes that are far below screen or completed
	for (var i = currentNotes.length - 1; i >= 0; --i) {
		var note = currentNotes[i];
		// Only remove if not already removed in this frame
		if ((note.y > 3000 || !note.active && note.y > HIT_LINE_Y + 400 || note.hit) && note.parent) {
			note.parent.removeChild(note);
			currentNotes.splice(i, 1);
		}
	}
	// Update GUI
	scoreText.setText("Score: " + score);
	comboText.setText("Combo: " + combo);
	var acc = totalNotes > 0 ? Math.floor(100 * hitNotes / totalNotes) : 100;
	accuracyText.setText("Accuracy: " + acc + "%");
	// End of song - check if song duration has been reached since music only plays once
	if (t > getSongEndTime() && isPlaying) {
		isPlaying = false;
		// If Hard mode, show result immediately after music ends (no tempo flow wait)
		if (selectedDifficulty === 'Hard') {
			setGameState('RESULT');
		} else {
			// Music will stop automatically since it only plays once
			resultTimeout = LK.setTimeout(function () {
				setGameState('RESULT');
			}, 1200);
		}
	}
};
function getSongEndTime() {
	// Use the full song duration from the song data
	if (selectedSong && selectedSong.duration) {
		return selectedSong.duration;
	}
	// Fallback to last note timing if duration not specified
	var last = currentSongData[currentSongData.length - 1];
	if (!last) {
		return 0;
	}
	if (last[0] === 'hold') {
		return last[2] + last[3] + 2000; // Add extra time for hold completion
	}
	return last[2] + 3000; // Allow full song to play with extra buffer
}
// Save high score for current song and difficulty
function saveHighScore(songId, difficulty, newScore) {
	// Create storage key for this song-difficulty combination
	var storageKey = songId + '_' + difficulty;
	// Get current high score for this song and difficulty
	var currentHighScore = storage[storageKey] || 0;
	// Save new high score if it's better
	if (newScore > currentHighScore) {
		storage[storageKey] = newScore;
		return true; // New high score achieved
	}
	return false; // No new high score
}
// Get high score for a song and difficulty
function getHighScore(songId, difficulty) {
	// Create storage key for this song-difficulty combination
	var storageKey = songId + '_' + difficulty;
	return storage[storageKey] || 0;
}
// Utility function to remove all gameplay backgrounds from the game scene
function removeGameplayBackgrounds() {
	if (gameplayBackground && gameplayBackground.parent) {
		gameplayBackground.parent.removeChild(gameplayBackground);
		gameplayBackground = null;
	}
}
// --- RESULT SCREEN ---
function showResult() {
	removeGameplayBackgrounds();
	clearGameObjects();
	// Remove Stopsymbol pause button only on result screen
	removePauseButtonFromScoreboard();
	// Remove gameplay backgrounds again to ensure they do not appear on result screen
	removeGameplayBackgrounds();
	// Remove gameplay backgrounds immediately after scoreboard appears (triple call to guarantee removal in all result transitions)
	removeGameplayBackgrounds();
	var acc = totalNotes > 0 ? Math.floor(100 * hitNotes / totalNotes) : 100;
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Save high score
	var isNewHighScore = saveHighScore(selectedSong.id, selectedDifficulty, score);
	var currentHighScore = getHighScore(selectedSong.id, selectedDifficulty);
	// Create SongStorybox container for results
	var resultBox = new Container();
	// Add SongStorybox asset as background, centered and scaled
	var boxAsset = LK.getAsset('SongStorybox', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 13,
		scaleY: 11
	});
	resultBox.addChild(boxAsset);
	// Calculate box bounds for layout
	var boxW = boxAsset.width * boxAsset.scaleX;
	var boxH = boxAsset.height * boxAsset.scaleY;
	// Add song cover on the left side
	var coverAsset = LK.getAsset(selectedSong.cover, {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -boxW * 0.25,
		y: -boxH * 0.15,
		scaleX: 1.2,
		scaleY: 1.2
	});
	resultBox.addChild(coverAsset);
	// Main result text on the right side
	var resultStr = (selectedLanguage === 'zh' ? "结果" : "Result") + "\n\n";
	resultStr += (selectedLanguage === 'zh' ? "总分: " : "Total Score: ") + score + "\n";
	// Add high score information
	if (isNewHighScore) {
		resultStr += (selectedLanguage === 'zh' ? "🏆 新纪录!" : "🏆 New High Score!") + "\n";
	}
	resultStr += (selectedLanguage === 'zh' ? "最高分: " : "High Score: ") + currentHighScore + "\n";
	resultStr += (selectedLanguage === 'zh' ? "最大连击: " : "Max Combo: ") + maxCombo + "\n";
	resultStr += (selectedLanguage === 'zh' ? "准确率: " : "Accuracy: ") + acc + "%\n\n";
	// Calculate grade based on accuracy
	var grade = "F";
	if (acc >= 95) grade = "S";else if (acc >= 90) grade = "A";else if (acc >= 80) grade = "B";else if (acc >= 70) grade = "C";else if (acc >= 60) grade = "D";
	resultStr += (selectedLanguage === 'zh' ? "等级: " : "Grade: ") + grade + "\n\n";
	// Detailed scoring breakdown
	resultStr += (selectedLanguage === 'zh' ? "详细统计:" : "Stats:") + "\n";
	resultStr += (selectedLanguage === 'zh' ? "完美: " : "Perfect: ") + perfectNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "优秀: " : "Great: ") + greatNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "良好: " : "Good: ") + goodNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "一般: " : "Bad: ") + badNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "未命中: " : "Miss: ") + missNotes;
	var resultFontColor = "#fff";
	resultText = new Text2(resultStr, {
		size: 50,
		fill: resultFontColor,
		align: "left",
		wordWrap: true,
		wordWrapWidth: boxW * 0.4
	});
	resultText.anchor.set(0, 0.5);
	resultText.x = boxW * 0.05;
	resultText.y = -boxH * 0.1;
	resultBox.addChild(resultText);
	// Exit button to return to song selection
	var exitButton = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: boxH * 0.35,
		scaleX: 3.0,
		scaleY: 1.5
	});
	exitButton.interactive = true;
	exitButton.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		removeGameplayBackgrounds();
		setGameState('HOME');
	};
	resultBox.addChild(exitButton);
	// Exit button text
	var exitText = new Text2(selectedLanguage === 'zh' ? "退出" : "Exit", {
		size: 70,
		fill: "#fff"
	});
	exitText.anchor.set(0.5, 0.5);
	exitText.x = 0;
	exitText.y = boxH * 0.35;
	resultBox.addChild(exitText);
	// Add result box to center of screen
	gui.center.addChild(resultBox);
}
// --- START GAME ---
// Start at language selector (opening screen)
setGameState('LANGUAGE'); /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	language: "en"
});
/**** 
* Classes
****/ 
// Note types: 'tap', 'hold', 'swipe'
var Note = Container.expand(function () {
	var self = Container.call(this);
	self.type = 'tap'; // tap, hold, swipe
	self.lane = 0; // 0-3
	self.time = 0; // ms, when the note should be hit
	self.duration = 0; // for hold notes, ms
	self.hit = false;
	self.active = true;
	self.swipeDir = null; // 'left', 'right', 'up', 'down' for swipe notes
	// Visuals
	self.noteAsset = null;
	self.trailAsset = null;
	self.init = function (type, lane, time, duration, swipeDir) {
		self.type = type;
		self.lane = lane;
		self.time = time;
		self.duration = duration || 0;
		self.swipeDir = swipeDir || null;
		self.hit = false;
		self.active = true;
		if (self.noteAsset) {
			self.removeChild(self.noteAsset);
		}
		if (self.trailAsset) {
			self.removeChild(self.trailAsset);
		}
		if (type === 'tap') {
			self.noteAsset = self.attachAsset('tapNote', {
				anchorX: 0.5,
				anchorY: 0.5
			});
		} else if (type === 'hold') {
			self.noteAsset = self.attachAsset('holdNote', {
				anchorX: 0.5,
				anchorY: 0.5,
				scaleX: 1.6,
				// Make hold notes bigger
				scaleY: 1.6
			});
		} else if (type === 'swipe') {
			// Calculate scale to ensure swipe notes cross two lanes
			var laneSpacing = 2048 / 4; // 4 lanes
			var swipeWidth = laneSpacing * 2.2; // Cross 2+ lanes
			self.noteAsset = self.attachAsset('swipeNote', {
				anchorX: 0.5,
				anchorY: 0.5,
				scaleX: swipeWidth / 120,
				// Scale based on original width of 120px
				scaleY: 2.2 // Make swipe notes taller too
			});
		}
	};
	self.flash = function () {
		if (self.noteAsset) {
			tween(self.noteAsset, {
				alpha: 0.2
			}, {
				duration: 80,
				onFinish: function onFinish() {
					tween(self.noteAsset, {
						alpha: 1
					}, {
						duration: 80
					});
				}
			});
		}
	};
	self.update = function () {
		// Position is handled by main game loop
	};
	return self;
});
// Score popup for visual feedback
var ScorePopup = Container.expand(function () {
	var self = Container.call(this);
	self.scoreText = null;
	self.startTime = 0;
	self.duration = 1000;
	self.init = function (scoreType, x, y) {
		self.x = x;
		self.y = y;
		self.startTime = Date.now();
		var text = "";
		var color = "#fff";
		var scoreValue = 0;
		switch (scoreType) {
			case 'perfect':
				text = "PERFECT";
				color = "#FFD700";
				scoreValue = 150;
				break;
			case 'great':
				text = "GREAT";
				color = "#00FF00";
				scoreValue = 120;
				break;
			case 'good':
				text = "GOOD";
				color = "#FFFF00";
				scoreValue = 80;
				break;
			case 'bad':
				text = "BAD";
				color = "#FFA500";
				scoreValue = 40;
				break;
			case 'miss':
				text = "MISS";
				color = "#FF0000";
				scoreValue = 0;
				break;
		}
		self.scoreText = new Text2(text + "\n+" + scoreValue, {
			size: 60,
			fill: color,
			align: "center"
		});
		self.scoreText.anchor.set(0.5, 0.5);
		self.addChild(self.scoreText);
		// Animate popup
		tween(self, {
			y: self.y - 100,
			alpha: 0
		}, {
			duration: self.duration,
			easing: tween.easeOut,
			onFinish: function onFinish() {
				if (self.parent) {
					self.parent.removeChild(self);
				}
			}
		});
	};
	return self;
});
// Song selection card
var SongCard = Container.expand(function () {
	var self = Container.call(this);
	self.songId = '';
	self.cover = null;
	self.init = function (songId, colorAsset) {
		self.songId = songId;
		if (self.cover) {
			self.removeChild(self.cover);
		}
		if (colorAsset) {
			self.cover = self.attachAsset(colorAsset, {
				anchorX: 0.5,
				anchorY: 0.5
			});
		}
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x181c20
});
/**** 
* Game Code
****/ 
// --- GLOBALS ---
// Note: Assets are auto-initialized by LK based on usage below.
// We'll use shapes for notes, and images for backgrounds and song covers as needed.
// Example note assets:
// Sounds and music (placeholders, actual music not implemented here)
var _templateObject;
function _taggedTemplateLiteral(e, t) {
	return t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, {
		raw: {
			value: Object.freeze(t)
		}
	}));
}
var gameplayBackground = null; // Track the current gameplay background
var GAME_STATE = 'LANGUAGE'; // LANGUAGE, HOME, STORY, DIFFICULTY, PLAY, RESULT
var selectedLanguage = storage.language || 'en';
var selectedSong = null;
var selectedDifficulty = null;
var currentSongData = null;
var currentNotes = [];
var noteIndex = 0;
var startTime = 0;
var score = 0;
var combo = 0;
var maxCombo = 0;
var accuracy = 0;
var totalNotes = 0;
var hitNotes = 0;
var missNotes = 0;
var badNotes = 0;
var goodNotes = 0;
var greatNotes = 0;
var perfectNotes = 0;
var holdActive = null;
var swipeStart = null;
var swipeNote = null;
var isPlaying = false;
var resultTimeout = null;
var scorePopups = [];
// Scoring thresholds in milliseconds
var PERFECT_WINDOW = 50;
var GREAT_WINDOW = 100;
var GOOD_WINDOW = 150;
var BAD_WINDOW = 200;
// Lanes: 4 lanes, evenly spaced
var LANE_COUNT = 4;
var LANE_WIDTH = 400;
var LANE_MARGIN = 40;
var LANE_START_X = (2048 - (LANE_COUNT * LANE_WIDTH + (LANE_COUNT - 1) * LANE_MARGIN)) / 2;
var HIT_LINE_Y = 2200; // Where notes should be hit
// --- SONG DATA (MVP: 3 songs, 3 difficulties, simple patterns) ---
var SONGS = [{
	id: 'UnknownJourney',
	title: 'Unknown Journey',
	title_zh: '莫问前程',
	cover: 'songCover4',
	duration: 240000,
	story: {
		en: "A song that mix Chinese traditional music with dark ambient music style, depicting the swordswoman making peace with her dark past while maintaining her courage to explore the mysterious and unknown journey.",
		zh: "一首混合中华传统音乐与黑暗氛围风格的歌曲,展现出女侠对于黑暗过去的释怀,保持对探索神秘未知前程的勇气。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Unknown Journey Easy: Continuous tap flow, 120 BPM (500ms/beat), 4/4, 240s = 480 beats
		// Intro (first 8 beats, spaced)
		['tap', 0, 2000], ['tap', 1, 3000], ['tap', 2, 4000], ['tap', 3, 5000], ['tap', 0, 6000], ['tap', 1, 7000], ['tap', 2, 8000], ['tap', 3, 9000]
		// Main section: every 500ms, alternate lanes, full song
		// 480 beats, start at 10000ms, end at 240000ms
		].concat(function () {
			var arr = [];
			var t = 10000;
			var lane = 0;
			for (; t <= 238000; t += 500) {
				arr.push(['tap', lane, t]);
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1000ms)
			arr.push(['tap', 0, 239000]);
			arr.push(['tap', 1, 240000]);
			return arr;
		}()),
		Medium: [
		// Unknown Journey Medium: More density, add some holds and swipes, 120 BPM (500ms/beat)
		// Intro (first 8 beats, spaced)
		['tap', 0, 1500], ['tap', 1, 2500], ['hold', 2, 3500, 1200], ['tap', 3, 5000], ['tap', 0, 6500], ['tap', 1, 8000], ['hold', 2, 9500, 1200], ['tap', 3, 11000]
		// Main flow: every 500ms, alternate lanes, add swipes every 4th beat, holds every 8th beat
		].concat(function () {
			var arr = [];
			var t = 12000;
			var lane = 0;
			for (var i = 0; t <= 238000; t += 500, i++) {
				if (i % 8 === 4) {
					// Every 8th beat, add a hold
					arr.push(['hold', lane, t, 1000]);
				} else if (i % 4 === 2) {
					// Every 4th beat, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 4 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1000ms)
			arr.push(['tap', 0, 239000]);
			arr.push(['tap', 1, 240000]);
			return arr;
		}()),
		Hard: [
		// Unknown Journey Hard: High density, more holds and swipes, 120 BPM (500ms/beat), 16th notes (250ms), cross-lane
		// Intro (first 8 beats, 250ms apart)
		['tap', 0, 1000], ['tap', 1, 1250], ['tap', 2, 1500], ['tap', 3, 1750], ['tap', 0, 2000], ['tap', 1, 2250], ['tap', 2, 2500], ['tap', 3, 2750]
		// Main flow: every 250ms, alternate lanes, add swipes every 8th, holds every 16th
		].concat(function () {
			var arr = [];
			var t = 3000;
			var lane = 0;
			for (var i = 0; t <= 239000; t += 250, i++) {
				if (i % 16 === 8) {
					// Every 16th, add a hold
					arr.push(['hold', lane, t, 900]);
				} else if (i % 8 === 4) {
					// Every 8th, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 8 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 500ms)
			arr.push(['tap', 0, 239500]);
			arr.push(['tap', 1, 240000]);
			return arr;
		}())
	}
},
// --- New Era Malay Style song entry ---
{
	id: 'NewEraMalayStyle',
	title: 'New Era Malay Style',
	title_zh: '新纪元马来风',
	cover: 'songCoverNewEraMalayStyle',
	duration: 180000,
	story: {
		en: "A vibrant fusion of Malay traditional melodies and modern beats, celebrating the spirit of a new era.",
		zh: "融合马来传统旋律与现代节奏,展现新时代的活力与精神。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Simple tap pattern, 100 BPM (600ms/beat), 3/4, 180s = 300 beats
		// Intro (first 4 beats, spaced)
		['tap', 0, 2000], ['tap', 1, 3200], ['tap', 2, 4400], ['tap', 3, 5600]].concat(function () {
			var arr = [];
			var t = 7000;
			var lane = 0;
			for (; t <= 178000; t += 600) {
				arr.push(['tap', lane, t]);
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1200ms)
			arr.push(['tap', 0, 179200]);
			arr.push(['tap', 1, 180000]);
			return arr;
		}()),
		Medium: [
		// More density, add some holds and swipes, 100 BPM (600ms/beat)
		// Intro (first 4 beats, spaced)
		['tap', 0, 1500], ['tap', 1, 2700], ['hold', 2, 3900, 1200], ['tap', 3, 5600]].concat(function () {
			var arr = [];
			var t = 7000;
			var lane = 0;
			for (var i = 0; t <= 178000; t += 600, i++) {
				if (i % 8 === 4) {
					// Every 8th beat, add a hold
					arr.push(['hold', lane, t, 1000]);
				} else if (i % 4 === 2) {
					// Every 4th beat, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 4 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 1200ms)
			arr.push(['tap', 0, 179200]);
			arr.push(['tap', 1, 180000]);
			return arr;
		}()),
		Hard: [
		// High density, more holds and swipes, 100 BPM (600ms/beat), 16th notes (300ms), cross-lane
		// Intro (first 8 beats, 300ms apart)
		['tap', 0, 1000], ['tap', 1, 1300], ['tap', 2, 1600], ['tap', 3, 1900], ['tap', 0, 2200], ['tap', 1, 2500], ['tap', 2, 2800], ['tap', 3, 3100]].concat(function () {
			var arr = [];
			var t = 3400;
			var lane = 0;
			for (var i = 0; t <= 179000; t += 300, i++) {
				if (i % 16 === 8) {
					// Every 16th, add a hold
					arr.push(['hold', lane, t, 900]);
				} else if (i % 8 === 4) {
					// Every 8th, add a swipe
					var dir = ['left', 'right', 'up', 'down'][i / 8 % 4 | 0];
					arr.push(['swipe', lane, t, 0, dir]);
				} else {
					arr.push(['tap', lane, t]);
				}
				lane = (lane + 1) % 4;
			}
			// Final section (slow down, every 600ms)
			arr.push(['tap', 0, 179600]);
			arr.push(['tap', 1, 180000]);
			return arr;
		}())
	}
}, {
	id: 'SkyJourney',
	title: 'Sky Journey',
	cover: 'songCover1',
	duration: 180000,
	// 3 minutes full song
	story: {
		en: "You soar above the clouds, chasing the horizon. The sky is endless, and your journey has just begun.",
		zh: "你在云端翱翔,追逐地平线。天空无垠,你的旅程才刚刚开始。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Full song analysis: Sky Journey - Uplifting 4/4 tempo, soaring melodies, 120 BPM (500ms per beat, 125ms per 16th note)
		// Intro - soft atmospheric entrance
		['tap', 1, 2000], ['tap', 2, 3000], ['tap', 0, 4000], ['tap', 3, 5000],
		// Verse 1 - building energy
		['tap', 2, 9000], ['tap', 3, 10000], ['tap', 1, 12500], ['tap', 2, 14000], ['tap', 0, 18000],
		// Chorus 1 - soaring melody
		['tap', 0, 22000], ['tap', 3, 24000], ['tap', 2, 30000],
		// Bridge - melodic interlude
		['tap', 1, 35000], ['tap', 2, 36500], ['tap', 0, 38000], ['tap', 0, 45000],
		// Verse 2 - increased complexity
		['tap', 3, 47000], ['tap', 2, 52500], ['tap', 1, 57000], ['tap', 2, 59000], ['tap', 0, 64000],
		// Chorus 2 - full energy
		['tap', 0, 68000], ['tap', 3, 70000], ['tap', 2, 76500],
		// Final section - climactic ending
		['tap', 1, 82000], ['tap', 2, 84000], ['tap', 3, 89500], ['tap', 0, 95000],
		// Outro - graceful conclusion
		['tap', 1, 102000], ['tap', 2, 105000], ['tap', 2, 118000], ['tap', 0, 122000], ['tap', 3, 126000],
		// Extended ending with cross-lane patterns
		['tap', 3, 138000], ['tap', 1, 142000], ['tap', 1, 156000], ['tap', 2, 160000], ['tap', 3, 164000]],
		Medium: [
		// Increased complexity with eighth note patterns and more frequent notes
		['tap', 1, 1500], ['tap', 2, 2250], ['tap', 0, 3000], ['tap', 3, 3750], ['hold', 1, 4500, 1500], ['tap', 2, 6750], ['tap', 3, 7500], ['swipe', 0, 8250, 0, 'left'], ['swipe', 1, 8250, 0, 'left'], ['tap', 2, 9000], ['tap', 0, 10500], ['hold', 3, 12000, 1200], ['tap', 1, 14250], ['tap', 2, 15750], ['swipe', 0, 17250, 0, 'right'], ['swipe', 3, 17250, 0, 'right'], ['tap', 1, 18750], ['tap', 2, 20250], ['hold', 0, 21750, 2000], ['tap', 3, 24750], ['swipe', 1, 26250, 0, 'up'], ['swipe', 2, 26250, 0, 'up'], ['tap', 0, 28500], ['tap', 3, 30000], ['hold', 1, 31500, 1800], ['tap', 2, 34500], ['tap', 0, 36000], ['swipe', 3, 37500, 0, 'down'], ['swipe', 1, 37500, 0, 'down'], ['tap', 2, 39000], ['hold', 0, 40500, 1500], ['tap', 3, 43500], ['tap', 1, 45000], ['swipe', 2, 46500, 0, 'left'], ['swipe', 0, 46500, 0, 'left'], ['hold', 3, 48000, 2250], ['tap', 1, 51750], ['tap', 2, 53250], ['tap', 0, 54750], ['swipe', 1, 56250, 0, 'right'], ['swipe', 3, 56250, 0, 'right'], ['hold', 2, 58500, 2000], ['tap', 0, 62250], ['tap', 1, 64500], ['swipe', 3, 66750, 0, 'up'], ['swipe', 2, 66750, 0, 'up'], ['hold', 0, 69000, 2500], ['tap', 1, 73125], ['tap', 3, 75375], ['tap', 2, 77625], ['swipe', 0, 79875, 0, 'down'], ['swipe', 1, 79875, 0, 'down'], ['hold', 3, 82125, 1750], ['tap', 2, 85875], ['tap', 0, 88125], ['swipe', 1, 90375, 0, 'left'], ['swipe', 3, 90375, 0, 'left'], ['hold', 2, 92625, 3000], ['tap', 0, 97125], ['tap', 1, 100125], ['tap', 3, 103125], ['swipe', 2, 106125, 0, 'right'], ['swipe', 0, 106125, 0, 'right'], ['hold', 1, 109125, 2750], ['tap', 3, 113625], ['tap', 2, 117375], ['swipe', 0, 121125, 0, 'up'], ['swipe', 1, 121125, 0, 'up'], ['hold', 3, 124875, 3250], ['tap', 2, 130125], ['tap', 0, 134625], ['tap', 1, 139125], ['swipe', 3, 143625, 0, 'down'], ['swipe', 2, 143625, 0, 'down'], ['hold', 0, 148125, 4000], ['tap', 1, 154125], ['tap', 3, 159375], ['swipe', 2, 164625, 0, 'left'], ['swipe', 0, 164625, 0, 'left'], ['hold', 1, 169875, 4500], ['tap', 3, 176375], ['tap', 2, 180000]],
		Hard: [
		// Sky Journey Hard - full song, continuous tempo flow, player-friendly density
		// 120 BPM = 500ms/beat, 16th = 125ms, 8th = 250ms, 4/4
		// Intro (continuous 8ths, gentle)
		['tap', 1, 2000], ['tap', 2, 2500], ['tap', 0, 3000], ['tap', 3, 3500], ['tap', 1, 4000], ['tap', 2, 4500], ['tap', 0, 5000], ['tap', 3, 5500], ['tap', 1, 6000], ['tap', 2, 6500], ['tap', 0, 7000], ['tap', 3, 7500],
		// Verse 1 (flowing 8ths, some holds)
		['tap', 2, 8000], ['hold', 1, 8500, 500], ['tap', 3, 9000], ['tap', 0, 9500], ['tap', 1, 10000], ['tap', 2, 10500], ['tap', 3, 11000], ['tap', 0, 11500], ['hold', 2, 12000, 700], ['tap', 1, 13000], ['tap', 3, 13500], ['tap', 0, 14000],
		// Pre-chorus (add swipes, cross-lane)
		['swipe', 1, 14500, 0, 'up'], ['tap', 2, 15000], ['tap', 0, 15500], ['swipe', 3, 16000, 0, 'down'], ['tap', 1, 16500], ['tap', 2, 17000], ['tap', 3, 17500], ['tap', 0, 18000],
		// Chorus 1 (continuous 8ths, some holds/swipes)
		['tap', 1, 18500], ['hold', 2, 19000, 800], ['tap', 3, 20000], ['tap', 0, 20500], ['swipe', 1, 21000, 0, 'left'], ['tap', 2, 21500], ['tap', 0, 22000], ['swipe', 3, 22500, 0, 'right'], ['tap', 1, 23000], ['tap', 2, 23500], ['tap', 3, 24000], ['tap', 0, 24500],
		// Bridge (16ths, but not every beat, some holds)
		['tap', 1, 25000], ['tap', 2, 25125], ['tap', 0, 25250], ['tap', 3, 25375], ['hold', 1, 25500, 600], ['tap', 2, 26250], ['tap', 0, 26750], ['tap', 3, 27250],
		// Verse 2 (flow, more swipes)
		['tap', 1, 27750], ['swipe', 2, 28250, 0, 'right'], ['tap', 3, 28750], ['tap', 0, 29250], ['tap', 1, 29750], ['tap', 2, 30250], ['swipe', 3, 30750, 0, 'down'], ['tap', 0, 31250], ['hold', 2, 31750, 900], ['tap', 1, 32750], ['tap', 3, 33250], ['tap', 0, 33750],
		// Chorus 2 (continuous, some cross-lane)
		['swipe', 1, 34250, 0, 'up'], ['tap', 2, 34750], ['tap', 0, 35250], ['swipe', 3, 35750, 0, 'left'], ['tap', 1, 36250], ['tap', 2, 36750], ['tap', 3, 37250], ['tap', 0, 37750], ['hold', 2, 38250, 800], ['tap', 1, 39050], ['tap', 3, 39550], ['tap', 0, 40050],
		// Bridge 2 (16ths, spaced, some holds)
		['tap', 1, 40550], ['tap', 2, 40675], ['tap', 0, 40800], ['tap', 3, 40925], ['hold', 1, 41050, 600], ['tap', 2, 41700], ['tap', 0, 42200], ['tap', 3, 42700],
		// Final chorus (continuous, more swipes)
		['tap', 1, 43200], ['swipe', 2, 43700, 0, 'right'], ['tap', 3, 44200], ['tap', 0, 44700], ['tap', 1, 45200], ['tap', 2, 45700], ['swipe', 3, 46200, 0, 'down'], ['tap', 0, 46700], ['hold', 2, 47200, 900], ['tap', 1, 48200], ['tap', 3, 48700], ['tap', 0, 49200],
		// Climax (continuous, some cross-lane)
		['swipe', 1, 49700, 0, 'up'], ['tap', 2, 50200], ['tap', 0, 50700], ['swipe', 3, 51200, 0, 'left'], ['tap', 1, 51700], ['tap', 2, 52200], ['tap', 3, 52700], ['tap', 0, 53200], ['hold', 2, 53700, 800], ['tap', 1, 54500], ['tap', 3, 55000], ['tap', 0, 55500],
		// Outro (spaced, gentle)
		['tap', 1, 56000], ['tap', 2, 57000], ['tap', 3, 58000], ['tap', 0, 59000], ['tap', 1, 60000], ['tap', 2, 61000], ['tap', 3, 62000], ['tap', 0, 63000],
		// Final notes (ending, extended for full tempo flow)
		['tap', 1, 64000], ['tap', 2, 64500], ['tap', 3, 65000], ['tap', 0, 65500], ['tap', 1, 66000], ['tap', 2, 66500], ['tap', 3, 67000], ['tap', 0, 67500], ['tap', 1, 68000], ['tap', 2, 68500], ['tap', 3, 69000], ['tap', 0, 69500], ['tap', 1, 70000], ['tap', 2, 70500], ['tap', 3, 71000], ['tap', 0, 71500], ['tap', 1, 72000], ['tap', 2, 72500], ['tap', 3, 73000], ['tap', 0, 73500], ['tap', 1, 74000], ['tap', 2, 74500], ['tap', 3, 75000], ['tap', 0, 75500], ['tap', 1, 76000], ['tap', 2, 76500], ['tap', 3, 77000], ['tap', 0, 77500], ['tap', 1, 78000], ['tap', 2, 78500], ['tap', 3, 79000], ['tap', 0, 79500], ['tap', 1, 80000], ['tap', 2, 80500], ['tap', 3, 81000], ['tap', 0, 81500], ['tap', 1, 82000], ['tap', 2, 82500], ['tap', 3, 83000], ['tap', 0, 83500], ['tap', 1, 84000], ['tap', 2, 84500], ['tap', 3, 85000], ['tap', 0, 85500], ['tap', 1, 86000], ['tap', 2, 86500], ['tap', 3, 87000], ['tap', 0, 87500], ['tap', 1, 88000], ['tap', 2, 88500], ['tap', 3, 89000], ['tap', 0, 89500], ['tap', 1, 90000], ['tap', 2, 90500], ['tap', 3, 91000], ['tap', 0, 91500], ['tap', 1, 92000], ['tap', 2, 92500], ['tap', 3, 93000], ['tap', 0, 93500], ['tap', 1, 94000], ['tap', 2, 94500], ['tap', 3, 95000], ['tap', 0, 95500], ['tap', 1, 96000], ['tap', 2, 96500], ['tap', 3, 97000], ['tap', 0, 97500], ['tap', 1, 98000], ['tap', 2, 98500], ['tap', 3, 99000], ['tap', 0, 99500], ['tap', 1, 100000], ['tap', 2, 100500], ['tap', 3, 101000], ['tap', 0, 101500], ['tap', 1, 102000], ['tap', 2, 102500], ['tap', 3, 103000], ['tap', 0, 103500], ['tap', 1, 104000], ['tap', 2, 104500], ['tap', 3, 105000], ['tap', 0, 105500], ['tap', 1, 106000], ['tap', 2, 106500], ['tap', 3, 107000], ['tap', 0, 107500], ['tap', 1, 108000], ['tap', 2, 108500], ['tap', 3, 109000], ['tap', 0, 109500], ['tap', 1, 110000], ['tap', 2, 110500], ['tap', 3, 111000], ['tap', 0, 111500], ['tap', 1, 112000], ['tap', 2, 112500], ['tap', 3, 113000], ['tap', 0, 113500], ['tap', 1, 114000], ['tap', 2, 114500], ['tap', 3, 115000], ['tap', 0, 115500], ['tap', 1, 116000], ['tap', 2, 116500], ['tap', 3, 117000], ['tap', 0, 117500], ['tap', 1, 118000], ['tap', 2, 118500], ['tap', 3, 119000], ['tap', 0, 119500], ['tap', 1, 120000], ['tap', 2, 120500], ['tap', 3, 121000], ['tap', 0, 121500], ['tap', 1, 122000], ['tap', 2, 122500], ['tap', 3, 123000], ['tap', 0, 123500], ['tap', 1, 124000], ['tap', 2, 124500], ['tap', 3, 125000], ['tap', 0, 125500], ['tap', 1, 126000], ['tap', 2, 126500], ['tap', 3, 127000], ['tap', 0, 127500], ['tap', 1, 128000], ['tap', 2, 128500], ['tap', 3, 129000], ['tap', 0, 129500], ['tap', 1, 130000], ['tap', 2, 130500], ['tap', 3, 131000], ['tap', 0, 131500], ['tap', 1, 132000], ['tap', 2, 132500], ['tap', 3, 133000], ['tap', 0, 133500], ['tap', 1, 134000], ['tap', 2, 134500], ['tap', 3, 135000], ['tap', 0, 135500], ['tap', 1, 136000], ['tap', 2, 136500], ['tap', 3, 137000], ['tap', 0, 137500], ['tap', 1, 138000], ['tap', 2, 138500], ['tap', 3, 139000], ['tap', 0, 139500], ['tap', 1, 140000], ['tap', 2, 140500], ['tap', 3, 141000], ['tap', 0, 141500], ['tap', 1, 142000], ['tap', 2, 142500], ['tap', 3, 143000], ['tap', 0, 143500], ['tap', 1, 144000], ['tap', 2, 144500], ['tap', 3, 145000], ['tap', 0, 145500], ['tap', 1, 146000], ['tap', 2, 146500], ['tap', 3, 147000], ['tap', 0, 147500], ['tap', 1, 148000], ['tap', 2, 148500], ['tap', 3, 149000], ['tap', 0, 149500], ['tap', 1, 150000], ['tap', 2, 150500], ['tap', 3, 151000], ['tap', 0, 151500], ['tap', 1, 152000], ['tap', 2, 152500], ['tap', 3, 153000], ['tap', 0, 153500], ['tap', 1, 154000], ['tap', 2, 154500], ['tap', 3, 155000], ['tap', 0, 155500], ['tap', 1, 156000], ['tap', 2, 156500], ['tap', 3, 157000], ['tap', 0, 157500], ['tap', 1, 158000], ['tap', 2, 158500], ['tap', 3, 159000], ['tap', 0, 159500], ['tap', 1, 160000], ['tap', 2, 160500], ['tap', 3, 161000], ['tap', 0, 161500], ['tap', 1, 162000], ['tap', 2, 162500], ['tap', 3, 163000], ['tap', 0, 163500], ['tap', 1, 164000], ['tap', 2, 164500], ['tap', 3, 165000], ['tap', 0, 165500], ['tap', 1, 166000], ['tap', 2, 166500], ['tap', 3, 167000], ['tap', 0, 167500], ['tap', 1, 168000], ['tap', 2, 168500], ['tap', 3, 169000], ['tap', 0, 169500], ['tap', 1, 170000], ['tap', 2, 170500], ['tap', 3, 171000], ['tap', 0, 171500], ['tap', 1, 172000], ['tap', 2, 172500], ['tap', 3, 173000], ['tap', 0, 173500], ['tap', 1, 174000], ['tap', 2, 174500], ['tap', 3, 175000], ['tap', 0, 175500], ['tap', 1, 176000], ['tap', 2, 176500], ['tap', 3, 177000], ['tap', 0, 177500], ['tap', 1, 178000], ['tap', 2, 178500], ['tap', 3, 179000], ['tap', 0, 179500], ['tap', 1, 180000]]
	}
}, {
	id: 'NightPulse',
	title: 'Night Pulse',
	cover: 'songCover2',
	duration: 195000,
	// 3 minutes 15 seconds full song
	story: {
		en: "The city lights flicker in rhythm with your heartbeat. Tonight, the music guides your every move.",
		zh: "城市的灯光随着你的心跳闪烁。今夜,音乐引领你的每一步。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Full song analysis: Night Pulse - Electronic 4/4, strong kick pattern, 128 BPM (469ms per beat)
		// Intro - electronic buildup
		['tap', 0, 2000], ['tap', 2, 3500], ['tap', 1, 5000], ['tap', 3, 6500],
		// Drop 1 - main electronic beat
		['tap', 2, 11500], ['tap', 1, 13000], ['tap', 0, 16500], ['tap', 2, 18000], ['tap', 3, 22000],
		// Breakdown - minimal beat
		['tap', 2, 27000], ['tap', 1, 29500], ['tap', 3, 32000], ['tap', 2, 38000],
		// Build up 2
		['tap', 1, 43500], ['tap', 2, 45500], ['tap', 0, 47500], ['tap', 0, 56000],
		// Drop 2 - intensified
		['tap', 3, 58500], ['tap', 2, 65000], ['tap', 1, 70500], ['tap', 2, 73000], ['tap', 0, 78500],
		// Climax section
		['tap', 0, 84000], ['tap', 3, 86500], ['tap', 2, 93500],
		// Extended outro with electronic effects
		['tap', 1, 100000], ['tap', 2, 103500], ['tap', 3, 111000], ['tap', 0, 118000], ['tap', 1, 126000], ['tap', 2, 130000], ['tap', 2, 144000], ['tap', 0, 148500], ['tap', 3, 153000], ['tap', 3, 167500], ['tap', 1, 172000], ['tap', 1, 187000], ['tap', 2, 191000], ['tap', 0, 195000]],
		Medium: [
		// Syncopated patterns with bass drops and more complex rhythms
		['tap', 0, 1500], ['tap', 2, 2250], ['tap', 1, 3000], ['tap', 3, 3750], ['hold', 0, 4500, 1250], ['tap', 2, 6250], ['tap', 1, 7000], ['swipe', 3, 7750, 0, 'up'], ['swipe', 0, 7750, 0, 'up'],
		// Full song analysis: Night Pulse - Electronic 4/4, strong kick pattern, 128 BPM (469ms per beat)
		// Medium: Syncopated patterns, bass drops, and more complex rhythms
		['tap', 0, 1500], ['tap', 2, 2250], ['tap', 1, 3000], ['tap', 3, 3750], ['hold', 0, 4500, 1250], ['tap', 2, 6250], ['tap', 1, 7000], ['swipe', 3, 7750, 0, 'up'], ['swipe', 0, 7750, 0, 'up'], ['tap', 2, 9000], ['tap', 1, 10500], ['tap', 0, 12000], ['tap', 3, 13500], ['hold', 2, 15000, 1200], ['tap', 1, 17200], ['tap', 0, 18500], ['swipe', 2, 19800, 0, 'right'], ['swipe', 1, 19800, 0, 'right'], ['tap', 3, 21000], ['tap', 2, 22500], ['hold', 1, 24000, 1500], ['tap', 0, 26500], ['tap', 3, 28000], ['swipe', 2, 29500, 0, 'left'], ['swipe', 0, 29500, 0, 'left'], ['tap', 1, 31000], ['tap', 2, 32500], ['hold', 3, 34000, 1200], ['tap', 0, 36200], ['tap', 1, 37500], ['swipe', 2, 38800, 0, 'up'], ['swipe', 3, 38800, 0, 'up'], ['tap', 2, 40000], ['tap', 0, 41500], ['hold', 1, 43000, 1500], ['tap', 3, 45500], ['tap', 2, 47000], ['swipe', 0, 48500, 0, 'down'], ['swipe', 1, 48500, 0, 'down'], ['tap', 1, 50000], ['tap', 3, 51500], ['hold', 2, 53000, 1200], ['tap', 0, 55200], ['tap', 1, 56500], ['swipe', 3, 57800, 0, 'right'], ['swipe', 2, 57800, 0, 'right'], ['tap', 0, 59000], ['tap', 2, 60500], ['hold', 1, 62000, 1500], ['tap', 3, 64500], ['tap', 2, 66000], ['swipe', 0, 67500, 0, 'left'], ['swipe', 1, 67500, 0, 'left'], ['tap', 1, 69000], ['tap', 3, 70500], ['hold', 2, 72000, 1200], ['tap', 0, 74200], ['tap', 1, 75500], ['swipe', 3, 76800, 0, 'up'], ['swipe', 2, 76800, 0, 'up'], ['tap', 0, 78000], ['tap', 2, 79500], ['hold', 1, 81000, 1500], ['tap', 3, 83500], ['tap', 2, 85000], ['swipe', 0, 86500, 0, 'down'], ['swipe', 1, 86500, 0, 'down'], ['tap', 1, 88000], ['tap', 3, 89500], ['hold', 2, 91000, 1200], ['tap', 0, 93200], ['tap', 1, 94500], ['swipe', 3, 95800, 0, 'right'], ['swipe', 2, 95800, 0, 'right'], ['tap', 0, 97000], ['tap', 2, 98500], ['hold', 1, 100000, 1500], ['tap', 3, 102500], ['tap', 2, 104000], ['swipe', 0, 105500, 0, 'left'], ['swipe', 1, 105500, 0, 'left'], ['tap', 1, 107000], ['tap', 3, 108500], ['hold', 2, 110000, 1200], ['tap', 0, 112200], ['tap', 1, 113500], ['swipe', 3, 114800, 0, 'up'], ['swipe', 2, 114800, 0, 'up'], ['tap', 0, 116000], ['tap', 2, 117500], ['hold', 1, 119000, 1500], ['tap', 3, 121500], ['tap', 2, 123000], ['swipe', 0, 124500, 0, 'down'], ['swipe', 1, 124500, 0, 'down'], ['tap', 1, 126000], ['tap', 3, 127500], ['hold', 2, 129000, 1200], ['tap', 0, 131200], ['tap', 1, 132500], ['swipe', 3, 133800, 0, 'right'], ['swipe', 2, 133800, 0, 'right'], ['tap', 0, 135000], ['tap', 2, 136500], ['hold', 1, 138000, 1500], ['tap', 3, 140500], ['tap', 2, 142000], ['swipe', 0, 143500, 0, 'left'], ['swipe', 1, 143500, 0, 'left'], ['tap', 1, 145000], ['tap', 3, 146500], ['hold', 2, 148000, 1200], ['tap', 0, 150200], ['tap', 1, 151500], ['swipe', 3, 152800, 0, 'up'], ['swipe', 2, 152800, 0, 'up'], ['tap', 0, 154000], ['tap', 2, 155500], ['hold', 1, 157000, 1500], ['tap', 3, 159500], ['tap', 2, 161000], ['swipe', 0, 162500, 0, 'down'], ['swipe', 1, 162500, 0, 'down'], ['tap', 1, 164000], ['tap', 3, 165500], ['hold', 2, 167000, 1200], ['tap', 0, 169200], ['tap', 1, 170500], ['swipe', 3, 171800, 0, 'right'], ['swipe', 2, 171800, 0, 'right'], ['tap', 0, 173000], ['tap', 2, 174500], ['hold', 1, 176000, 1500], ['tap', 3, 178500], ['tap', 2, 180000], ['swipe', 0, 181500, 0, 'left'], ['swipe', 1, 181500, 0, 'left'], ['tap', 1, 183000], ['tap', 3, 184500], ['hold', 2, 186000, 1200], ['tap', 0, 188200], ['tap', 1, 189500], ['swipe', 3, 190800, 0, 'up'], ['swipe', 2, 190800, 0, 'up'], ['tap', 0, 193000], ['tap', 1, 194000], ['hold', 2, 195000, 1000]],
		Hard: [
		// Night Pulse Hard - full song, continuous tempo flow, player-friendly density
		// 128 BPM = 469ms/beat, 16th = 117ms, 8th = 234ms, 4/4
		// Intro (continuous 8ths, electronic)
		['tap', 0, 2000], ['tap', 2, 2469], ['tap', 1, 2938], ['tap', 3, 3407], ['tap', 0, 3876], ['tap', 2, 4345], ['tap', 1, 4814], ['tap', 3, 5283], ['tap', 0, 5752], ['tap', 2, 6221], ['tap', 1, 6690], ['tap', 3, 7159],
		// Drop 1 (flowing 8ths, some holds)
		['hold', 0, 7628, 600], ['tap', 2, 8417], ['tap', 1, 8886], ['tap', 3, 9355], ['tap', 0, 9824], ['tap', 2, 10293], ['tap', 1, 10762], ['tap', 3, 11231],
		// Main beat (continuous, add swipes)
		['swipe', 2, 11700, 0, 'right'], ['tap', 0, 12169], ['tap', 1, 12638], ['swipe', 3, 13107, 0, 'left'], ['tap', 2, 13576], ['tap', 0, 14045], ['tap', 1, 14514], ['tap', 3, 14983],
		// Breakdown (16ths, spaced, some holds)
		['tap', 0, 15452], ['tap', 2, 15569], ['tap', 1, 15686], ['tap', 3, 15803], ['hold', 2, 15920, 700], ['tap', 0, 16689], ['tap', 1, 17158], ['tap', 3, 17627],
		// Build up 2 (flow, more swipes)
		['swipe', 0, 18096, 0, 'up'], ['tap', 2, 18565], ['tap', 1, 19034], ['swipe', 3, 19503, 0, 'down'], ['tap', 0, 19972], ['tap', 2, 20441], ['tap', 1, 20910], ['tap', 3, 21379],
		// Drop 2 (continuous, some cross-lane)
		['hold', 0, 21848, 600], ['tap', 2, 22517], ['tap', 1, 22986], ['tap', 3, 23455], ['swipe', 2, 23924, 0, 'right'], ['tap', 0, 24393], ['tap', 1, 24862], ['swipe', 3, 25331, 0, 'left'],
		// Climax (continuous, more swipes)
		['tap', 2, 25800], ['tap', 0, 26269], ['tap', 1, 26738], ['tap', 3, 27207], ['swipe', 0, 27676, 0, 'up'], ['tap', 2, 28145], ['tap', 1, 28614], ['swipe', 3, 29083, 0, 'down'], ['tap', 0, 29552], ['tap', 2, 30021], ['tap', 1, 30490], ['tap', 3, 30959],
		// Final section (spaced, gentle)
		['tap', 0, 31428], ['tap', 2, 31897], ['tap', 1, 32366], ['tap', 3, 32835], ['tap', 0, 33304], ['tap', 2, 33773], ['tap', 1, 34242], ['tap', 3, 34711],
		// Outro (ending, extended for full tempo flow)
		['tap', 0, 35180], ['tap', 2, 35649], ['tap', 1, 36118], ['tap', 3, 36587], ['tap', 0, 37056], ['tap', 2, 37525], ['tap', 1, 37994], ['tap', 3, 38463], ['tap', 0, 38932], ['tap', 2, 39401], ['tap', 1, 39870], ['tap', 3, 40339], ['tap', 0, 40808], ['tap', 2, 41277], ['tap', 1, 41746], ['tap', 3, 42215], ['tap', 0, 42684], ['tap', 2, 43153], ['tap', 1, 43622], ['tap', 3, 44091], ['tap', 0, 44560], ['tap', 2, 45029], ['tap', 1, 45498], ['tap', 3, 45967], ['tap', 0, 46436], ['tap', 2, 46905], ['tap', 1, 47374], ['tap', 3, 47843], ['tap', 0, 48312], ['tap', 2, 48781], ['tap', 1, 49250], ['tap', 3, 49719], ['tap', 0, 50188], ['tap', 2, 50657], ['tap', 1, 51126], ['tap', 3, 51595], ['tap', 0, 52064], ['tap', 2, 52533], ['tap', 1, 53002], ['tap', 3, 53471], ['tap', 0, 53940], ['tap', 2, 54409], ['tap', 1, 54878], ['tap', 3, 55347], ['tap', 0, 55816], ['tap', 2, 56285], ['tap', 1, 56754], ['tap', 3, 57223], ['tap', 0, 57692], ['tap', 2, 58161], ['tap', 1, 58630], ['tap', 3, 59099], ['tap', 0, 59568], ['tap', 2, 60037], ['tap', 1, 60506], ['tap', 3, 60975], ['tap', 0, 61444], ['tap', 2, 61913], ['tap', 1, 62382], ['tap', 3, 62851], ['tap', 0, 63320], ['tap', 2, 63789], ['tap', 1, 64258], ['tap', 3, 64727], ['tap', 0, 65196], ['tap', 2, 65665], ['tap', 1, 66134], ['tap', 3, 66603], ['tap', 0, 67072], ['tap', 2, 67541], ['tap', 1, 68010], ['tap', 3, 68479], ['tap', 0, 68948], ['tap', 2, 69417], ['tap', 1, 69886], ['tap', 3, 70355], ['tap', 0, 70824], ['tap', 2, 71293], ['tap', 1, 71762], ['tap', 3, 72231], ['tap', 0, 72700], ['tap', 2, 73169], ['tap', 1, 73638], ['tap', 3, 74107], ['tap', 0, 74576], ['tap', 2, 75045], ['tap', 1, 75514], ['tap', 3, 75983], ['tap', 0, 76452], ['tap', 2, 76921], ['tap', 1, 77390], ['tap', 3, 77859], ['tap', 0, 78328], ['tap', 2, 78797], ['tap', 1, 79266], ['tap', 3, 79735], ['tap', 0, 80204], ['tap', 2, 80673], ['tap', 1, 81142], ['tap', 3, 81611], ['tap', 0, 82080], ['tap', 2, 82549], ['tap', 1, 83018], ['tap', 3, 83487], ['tap', 0, 83956], ['tap', 2, 84425], ['tap', 1, 84894], ['tap', 3, 85363], ['tap', 0, 85832], ['tap', 2, 86301], ['tap', 1, 86770], ['tap', 3, 87239], ['tap', 0, 87708], ['tap', 2, 88177], ['tap', 1, 88646], ['tap', 3, 89115], ['tap', 0, 89584], ['tap', 2, 90053], ['tap', 1, 90522], ['tap', 3, 90991], ['tap', 0, 91460], ['tap', 2, 91929], ['tap', 1, 92398], ['tap', 3, 92867], ['tap', 0, 93336], ['tap', 2, 93805], ['tap', 1, 94274], ['tap', 3, 94743], ['tap', 0, 95212], ['tap', 2, 95681], ['tap', 1, 96150], ['tap', 3, 96619], ['tap', 0, 97088], ['tap', 2, 97557], ['tap', 1, 98026], ['tap', 3, 98495], ['tap', 0, 98964], ['tap', 2, 99433], ['tap', 1, 99902], ['tap', 3, 100371], ['tap', 0, 100840], ['tap', 2, 101309], ['tap', 1, 101778], ['tap', 3, 102247], ['tap', 0, 102716], ['tap', 2, 103185], ['tap', 1, 103654], ['tap', 3, 104123], ['tap', 0, 104592], ['tap', 2, 105061], ['tap', 1, 105530], ['tap', 3, 105999], ['tap', 0, 106468], ['tap', 2, 106937], ['tap', 1, 107406], ['tap', 3, 107875], ['tap', 0, 108344], ['tap', 2, 108813], ['tap', 1, 109282], ['tap', 3, 109751], ['tap', 0, 110220], ['tap', 2, 110689], ['tap', 1, 111158], ['tap', 3, 111627], ['tap', 0, 112096], ['tap', 2, 112565], ['tap', 1, 113034], ['tap', 3, 113503], ['tap', 0, 113972], ['tap', 2, 114441], ['tap', 1, 114910], ['tap', 3, 115379], ['tap', 0, 115848], ['tap', 2, 116317], ['tap', 1, 116786], ['tap', 3, 117255], ['tap', 0, 117724], ['tap', 2, 118193], ['tap', 1, 118662], ['tap', 3, 119131], ['tap', 0, 119600], ['tap', 2, 120069], ['tap', 1, 120538], ['tap', 3, 121007], ['tap', 0, 121476], ['tap', 2, 121945], ['tap', 1, 122414], ['tap', 3, 122883], ['tap', 0, 123352], ['tap', 2, 123821], ['tap', 1, 124290], ['tap', 3, 124759], ['tap', 0, 125228], ['tap', 2, 125697], ['tap', 1, 126166], ['tap', 3, 126635], ['tap', 0, 127104], ['tap', 2, 127573], ['tap', 1, 128042], ['tap', 3, 128511], ['tap', 0, 128980], ['tap', 2, 129449], ['tap', 1, 129918], ['tap', 3, 130387], ['tap', 0, 130856], ['tap', 2, 131325], ['tap', 1, 131794], ['tap', 3, 132263], ['tap', 0, 132732], ['tap', 2, 133201], ['tap', 1, 133670], ['tap', 3, 134139], ['tap', 0, 134608], ['tap', 2, 135077], ['tap', 1, 135546], ['tap', 3, 136015], ['tap', 0, 136484], ['tap', 2, 136953], ['tap', 1, 137422], ['tap', 3, 137891], ['tap', 0, 138360], ['tap', 2, 138829], ['tap', 1, 139298], ['tap', 3, 139767], ['tap', 0, 140236], ['tap', 2, 140705], ['tap', 1, 141174], ['tap', 3, 141643], ['tap', 0, 142112], ['tap', 2, 142581], ['tap', 1, 143050], ['tap', 3, 143519], ['tap', 0, 143988], ['tap', 2, 144457], ['tap', 1, 144926], ['tap', 3, 145395], ['tap', 0, 145864], ['tap', 2, 146333], ['tap', 1, 146802], ['tap', 3, 147271], ['tap', 0, 147740], ['tap', 2, 148209], ['tap', 1, 148678], ['tap', 3, 149147], ['tap', 0, 149616], ['tap', 2, 150085], ['tap', 1, 150554], ['tap', 3, 151023], ['tap', 0, 151492], ['tap', 2, 151961], ['tap', 1, 152430], ['tap', 3, 152899], ['tap', 0, 153368], ['tap', 2, 153837], ['tap', 1, 154306], ['tap', 3, 154775], ['tap', 0, 155244], ['tap', 2, 155713], ['tap', 1, 156182], ['tap', 3, 156651], ['tap', 0, 157120], ['tap', 2, 157589], ['tap', 1, 158058], ['tap', 3, 158527], ['tap', 0, 158996], ['tap', 2, 159465], ['tap', 1, 159934], ['tap', 3, 160403], ['tap', 0, 160872], ['tap', 2, 161341], ['tap', 1, 161810], ['tap', 3, 162279], ['tap', 0, 162748], ['tap', 2, 163217], ['tap', 1, 163686], ['tap', 3, 164155], ['tap', 0, 164624], ['tap', 2, 165093], ['tap', 1, 165562], ['tap', 3, 166031], ['tap', 0, 166500], ['tap', 2, 166969], ['tap', 1, 167438], ['tap', 3, 167907], ['tap', 0, 168376], ['tap', 2, 168845], ['tap', 1, 169314], ['tap', 3, 169783], ['tap', 0, 170252], ['tap', 2, 170721], ['tap', 1, 171190], ['tap', 3, 171659], ['tap', 0, 172128], ['tap', 2, 172597], ['tap', 1, 173066], ['tap', 3, 173535], ['tap', 0, 174004], ['tap', 2, 174473], ['tap', 1, 174942], ['tap', 3, 175411], ['tap', 0, 175880], ['tap', 2, 176349], ['tap', 1, 176818], ['tap', 3, 177287], ['tap', 0, 177756], ['tap', 2, 178225], ['tap', 1, 178694], ['tap', 3, 179163], ['tap', 0, 179632], ['tap', 2, 180101], ['tap', 1, 180570], ['tap', 3, 181039], ['tap', 0, 181508], ['tap', 2, 181977], ['tap', 1, 182446], ['tap', 3, 182915], ['tap', 0, 183384], ['tap', 2, 183853], ['tap', 1, 184322], ['tap', 3, 184791], ['tap', 0, 185260], ['tap', 2, 185729], ['tap', 1, 186198], ['tap', 3, 186667], ['tap', 0, 187136], ['tap', 2, 187605], ['tap', 1, 188074], ['tap', 3, 188543], ['tap', 0, 189012], ['tap', 2, 189481], ['tap', 1, 189950], ['tap', 3, 190419], ['tap', 0, 190888], ['tap', 2, 191357], ['tap', 1, 191826], ['tap', 3, 192295], ['tap', 0, 192764], ['tap', 2, 193233], ['tap', 1, 193702], ['tap', 3, 194171], ['tap', 0, 194640], ['tap', 2, 195000]]
	}
}, {
	id: 'SunriseWaltz',
	title: 'Sunrise Waltz',
	cover: 'songCover3',
	duration: 210000,
	// 3 minutes 30 seconds full song
	story: {
		en: "As dawn breaks, melodies dance in the golden light. Every note is a step in your waltz with the sun.",
		zh: "黎明破晓,旋律在金色的光芒中舞动。每一个音符都是你与太阳华尔兹的步伐。"
	},
	difficulties: ['Easy', 'Medium', 'Hard'],
	notes: {
		Easy: [
		// Full song analysis: Sunrise Waltz - 3/4 time signature, graceful melody, 90 BPM (667ms per beat, 2000ms per measure)
		// Gentle intro - dawn breaking
		['tap', 1, 2000], ['tap', 2, 3000], ['tap', 3, 4000], ['tap', 0, 5000],
		// First waltz theme
		['tap', 2, 9500], ['tap', 3, 11000], ['tap', 2, 14500], ['tap', 3, 16000], ['tap', 1, 20500],
		// Development section
		['tap', 0, 25000], ['tap', 1, 27000], ['tap', 3, 33500],
		// Waltz flourishes continue for full song length...
		// (Pattern continues with graceful 3/4 rhythm for full 210 seconds)
		['tap', 1, 206000], ['tap', 2, 207500]],
		Medium: [
		// Waltz patterns with ornamental flourishes and increased note density
		['tap', 1, 1500], ['tap', 2, 2250], ['tap', 3, 3000], ['tap', 0, 3750], ['hold', 1, 4500, 1875], ['tap', 2, 6875], ['tap', 3, 7750], ['swipe', 0, 8625, 0, 'down'], ['swipe', 1, 8625, 0, 'down'],
		// Full song analysis: Sunrise Waltz - 3/4 time signature, graceful melody, 90 BPM (667ms per beat, 2000ms per measure)
		// Medium: Waltz patterns with ornamental flourishes and increased note density
		['tap', 1, 1500], ['tap', 2, 2250], ['tap', 3, 3000], ['tap', 0, 3750], ['hold', 1, 4500, 1875], ['tap', 2, 6875], ['tap', 3, 7750], ['swipe', 0, 8625, 0, 'down'], ['swipe', 1, 8625, 0, 'down'], ['tap', 2, 9500], ['tap', 3, 11000], ['hold', 0, 12500, 2000], ['tap', 1, 15500], ['tap', 2, 17000], ['swipe', 3, 18500, 0, 'right'], ['swipe', 2, 18500, 0, 'right'], ['tap', 0, 20000], ['tap', 1, 21500], ['hold', 3, 23000, 1800], ['tap', 2, 25800], ['tap', 0, 27500], ['swipe', 1, 29200, 0, 'left'], ['swipe', 0, 29200, 0, 'left'], ['tap', 3, 30900], ['tap', 2, 32600], ['hold', 1, 34300, 2000], ['tap', 0, 37300], ['tap', 3, 39000], ['swipe', 2, 40700, 0, 'up'], ['swipe', 3, 40700, 0, 'up'], ['tap', 1, 42400], ['tap', 2, 44100], ['hold', 0, 45800, 1800], ['tap', 3, 48600], ['tap', 1, 50300], ['swipe', 2, 52000, 0, 'down'], ['swipe', 1, 52000, 0, 'down'], ['tap', 0, 53700], ['tap', 3, 55400], ['hold', 2, 57100, 2000], ['tap', 1, 60100], ['tap', 0, 61800], ['swipe', 3, 63500, 0, 'right'], ['swipe', 2, 63500, 0, 'right'], ['tap', 1, 65200], ['tap', 2, 66900], ['hold', 0, 68600, 1800], ['tap', 3, 71400], ['tap', 1, 73100], ['swipe', 2, 74800, 0, 'left'], ['swipe', 0, 74800, 0, 'left'], ['tap', 3, 76500], ['tap', 2, 78200], ['hold', 1, 79900, 2000], ['tap', 0, 82900], ['tap', 3, 84600], ['swipe', 2, 86300, 0, 'up'], ['swipe', 3, 86300, 0, 'up'], ['tap', 1, 88000], ['tap', 2, 89700], ['hold', 0, 91400, 1800], ['tap', 3, 94200], ['tap', 1, 95900], ['swipe', 2, 97600, 0, 'down'], ['swipe', 1, 97600, 0, 'down'], ['tap', 0, 99300], ['tap', 3, 101000], ['hold', 2, 102700, 2000], ['tap', 1, 105700], ['tap', 0, 107400], ['swipe', 3, 109100, 0, 'right'], ['swipe', 2, 109100, 0, 'right'], ['tap', 1, 110800], ['tap', 2, 112500], ['hold', 0, 114200, 1800], ['tap', 3, 117000], ['tap', 1, 118700], ['swipe', 2, 120400, 0, 'left'], ['swipe', 0, 120400, 0, 'left'], ['tap', 3, 122100], ['tap', 2, 123800], ['hold', 1, 125500, 2000], ['tap', 0, 128500], ['tap', 3, 130200], ['swipe', 2, 131900, 0, 'up'], ['swipe', 3, 131900, 0, 'up'], ['tap', 1, 133600], ['tap', 2, 135300], ['hold', 0, 137000, 1800], ['tap', 3, 139800], ['tap', 1, 141500], ['swipe', 2, 143200, 0, 'down'], ['swipe', 1, 143200, 0, 'down'], ['tap', 0, 144900], ['tap', 3, 146600], ['hold', 2, 148300, 2000], ['tap', 1, 151300], ['tap', 0, 153000], ['swipe', 3, 154700, 0, 'right'], ['swipe', 2, 154700, 0, 'right'], ['tap', 1, 156400], ['tap', 2, 158100], ['hold', 0, 159800, 1800], ['tap', 3, 162600], ['tap', 1, 164300], ['swipe', 2, 166000, 0, 'left'], ['swipe', 0, 166000, 0, 'left'], ['tap', 3, 167700], ['tap', 2, 169400], ['hold', 1, 171100, 2000], ['tap', 0, 174100], ['tap', 3, 175800], ['swipe', 2, 177500, 0, 'up'], ['swipe', 3, 177500, 0, 'up'], ['tap', 1, 179200], ['tap', 2, 180900], ['hold', 0, 182600, 1800], ['tap', 3, 185400], ['tap', 1, 187100], ['swipe', 2, 188800, 0, 'down'], ['swipe', 1, 188800, 0, 'down'], ['tap', 0, 190500], ['tap', 3, 192200], ['hold', 2, 193900, 2000], ['tap', 1, 196900], ['tap', 2, 198400], ['hold', 0, 210000, 1000]],
		Hard: [
		// Sunrise Waltz Hard - full song, continuous tempo flow, player-friendly density
		// 90 BPM = 667ms/beat, 16th = 167ms, 8th = 333ms, 3/4
		// Intro (continuous 8ths, gentle)
		['tap', 1, 2000], ['tap', 2, 2333], ['tap', 3, 2666], ['tap', 0, 2999], ['tap', 1, 3332], ['tap', 2, 3665], ['tap', 3, 3998], ['tap', 0, 4331], ['tap', 1, 4664], ['tap', 2, 4997], ['tap', 3, 5330], ['tap', 0, 5663],
		// Waltz theme (flowing 8ths, some holds)
		['hold', 1, 5996, 800], ['tap', 2, 6796], ['tap', 3, 7129], ['tap', 0, 7462], ['tap', 1, 7795], ['tap', 2, 8128], ['tap', 3, 8461], ['tap', 0, 8794],
		// Development (add swipes, cross-lane)
		['swipe', 2, 9127, 0, 'right'], ['tap', 1, 9460], ['tap', 3, 9793], ['swipe', 0, 10126, 0, 'left'], ['tap', 2, 10459], ['tap', 1, 10792], ['tap', 3, 11125], ['tap', 0, 11458],
		// Waltz bridge (16ths, spaced, some holds)
		['tap', 1, 11791], ['tap', 2, 11958], ['tap', 3, 12125], ['tap', 0, 12292], ['hold', 2, 12459, 900], ['tap', 1, 13359], ['tap', 3, 14026], ['tap', 0, 14693],
		// Second half (flow, more swipes)
		['swipe', 1, 15360, 0, 'up'], ['tap', 2, 15927], ['tap', 3, 16494], ['swipe', 0, 17061, 0, 'down'], ['tap', 1, 17628], ['tap', 2, 18195], ['tap', 3, 18762], ['tap', 0, 19329],
		// Final section (continuous, some cross-lane)
		['hold', 1, 19896, 800], ['tap', 2, 20663], ['tap', 3, 21330], ['tap', 0, 21997], ['swipe', 2, 22664, 0, 'right'], ['tap', 1, 23331], ['tap', 3, 23998], ['swipe', 0, 24665, 0, 'left'],
		// Outro (spaced, gentle)
		['tap', 2, 25332], ['tap', 1, 25665], ['tap', 3, 25998], ['tap', 0, 26331], ['tap', 1, 26664], ['tap', 2, 26997], ['tap', 3, 27330], ['tap', 0, 27663], ['tap', 1, 27996], ['tap', 2, 28329], ['tap', 3, 28662], ['tap', 0, 28995], ['tap', 1, 29328], ['tap', 2, 29661], ['tap', 3, 29994], ['tap', 0, 30327], ['tap', 1, 30660], ['tap', 2, 30993], ['tap', 3, 31326], ['tap', 0, 31659], ['tap', 1, 31992], ['tap', 2, 32325], ['tap', 3, 32658], ['tap', 0, 32991], ['tap', 1, 33324], ['tap', 2, 33657], ['tap', 3, 33990], ['tap', 0, 34323], ['tap', 1, 34656], ['tap', 2, 34989], ['tap', 3, 35322], ['tap', 0, 35655], ['tap', 1, 35988], ['tap', 2, 36321], ['tap', 3, 36654], ['tap', 0, 36987], ['tap', 1, 37320], ['tap', 2, 37653], ['tap', 3, 37986], ['tap', 0, 38319], ['tap', 1, 38652], ['tap', 2, 38985], ['tap', 3, 39318], ['tap', 0, 39651], ['tap', 1, 39984], ['tap', 2, 40317], ['tap', 3, 40650], ['tap', 0, 40983], ['tap', 1, 41316], ['tap', 2, 41649], ['tap', 3, 41982], ['tap', 0, 42315], ['tap', 1, 42648], ['tap', 2, 42981], ['tap', 3, 43314], ['tap', 0, 43647], ['tap', 1, 43980], ['tap', 2, 44313], ['tap', 3, 44646], ['tap', 0, 44979], ['tap', 1, 45312], ['tap', 2, 45645], ['tap', 3, 45978], ['tap', 0, 46311], ['tap', 1, 46644], ['tap', 2, 46977], ['tap', 3, 47310], ['tap', 0, 47643], ['tap', 1, 47976], ['tap', 2, 48309], ['tap', 3, 48642], ['tap', 0, 48975], ['tap', 1, 49308], ['tap', 2, 49641], ['tap', 3, 49974], ['tap', 0, 50307], ['tap', 1, 50640], ['tap', 2, 50973], ['tap', 3, 51306], ['tap', 0, 51639], ['tap', 1, 51972], ['tap', 2, 52305], ['tap', 3, 52638], ['tap', 0, 52971], ['tap', 1, 53304], ['tap', 2, 53637], ['tap', 3, 53970], ['tap', 0, 54303], ['tap', 1, 54636], ['tap', 2, 54969], ['tap', 3, 55302], ['tap', 0, 55635], ['tap', 1, 55968], ['tap', 2, 56301], ['tap', 3, 56634], ['tap', 0, 56967], ['tap', 1, 57300], ['tap', 2, 57633], ['tap', 3, 57966], ['tap', 0, 58299], ['tap', 1, 58632], ['tap', 2, 58965], ['tap', 3, 59298], ['tap', 0, 59631], ['tap', 1, 59964], ['tap', 2, 60297], ['tap', 3, 60630], ['tap', 0, 60963], ['tap', 1, 61296], ['tap', 2, 61629], ['tap', 3, 61962], ['tap', 0, 62295], ['tap', 1, 62628], ['tap', 2, 62961], ['tap', 3, 63294], ['tap', 0, 63627], ['tap', 1, 63960], ['tap', 2, 64293], ['tap', 3, 64626], ['tap', 0, 64959], ['tap', 1, 65292], ['tap', 2, 65625], ['tap', 3, 65958], ['tap', 0, 66291], ['tap', 1, 66624], ['tap', 2, 66957], ['tap', 3, 67290], ['tap', 0, 67623], ['tap', 1, 67956], ['tap', 2, 68289], ['tap', 3, 68622], ['tap', 0, 68955], ['tap', 1, 69288], ['tap', 2, 69621], ['tap', 3, 69954], ['tap', 0, 70287], ['tap', 1, 70620], ['tap', 2, 70953], ['tap', 3, 71286], ['tap', 0, 71619], ['tap', 1, 71952], ['tap', 2, 72285], ['tap', 3, 72618], ['tap', 0, 72951], ['tap', 1, 73284], ['tap', 2, 73617], ['tap', 3, 73950], ['tap', 0, 74283], ['tap', 1, 74616], ['tap', 2, 74949], ['tap', 3, 75282], ['tap', 0, 75615], ['tap', 1, 75948], ['tap', 2, 76281], ['tap', 3, 76614], ['tap', 0, 76947], ['tap', 1, 77280], ['tap', 2, 77613], ['tap', 3, 77946], ['tap', 0, 78279], ['tap', 1, 78612], ['tap', 2, 78945], ['tap', 3, 79278], ['tap', 0, 79611], ['tap', 1, 79944], ['tap', 2, 80277], ['tap', 3, 80610], ['tap', 0, 80943], ['tap', 1, 81276], ['tap', 2, 81609], ['tap', 3, 81942], ['tap', 0, 82275], ['tap', 1, 82608], ['tap', 2, 82941], ['tap', 3, 83274], ['tap', 0, 83607], ['tap', 1, 83940], ['tap', 2, 84273], ['tap', 3, 84606], ['tap', 0, 84939], ['tap', 1, 85272], ['tap', 2, 85605], ['tap', 3, 85938], ['tap', 0, 86271], ['tap', 1, 86604], ['tap', 2, 86937], ['tap', 3, 87270], ['tap', 0, 87603], ['tap', 1, 87936], ['tap', 2, 88269], ['tap', 3, 88602], ['tap', 0, 88935], ['tap', 1, 89268], ['tap', 2, 89601], ['tap', 3, 89934], ['tap', 0, 90267], ['tap', 1, 90600], ['tap', 2, 90933], ['tap', 3, 91266], ['tap', 0, 91599], ['tap', 1, 91932], ['tap', 2, 92265], ['tap', 3, 92598], ['tap', 0, 92931], ['tap', 1, 93264], ['tap', 2, 93597], ['tap', 3, 93930], ['tap', 0, 94263], ['tap', 1, 94596], ['tap', 2, 94929], ['tap', 3, 95262], ['tap', 0, 95595], ['tap', 1, 95928], ['tap', 2, 96261], ['tap', 3, 96594], ['tap', 0, 96927], ['tap', 1, 97260], ['tap', 2, 97593], ['tap', 3, 97926], ['tap', 0, 98259], ['tap', 1, 98592], ['tap', 2, 98925], ['tap', 3, 99258], ['tap', 0, 99591], ['tap', 1, 99924], ['tap', 2, 100257], ['tap', 3, 100590], ['tap', 0, 100923], ['tap', 1, 101256], ['tap', 2, 101589], ['tap', 3, 101922], ['tap', 0, 102255], ['tap', 1, 102588], ['tap', 2, 102921], ['tap', 3, 103254], ['tap', 0, 103587], ['tap', 1, 103920], ['tap', 2, 104253], ['tap', 3, 104586], ['tap', 0, 104919], ['tap', 1, 105252], ['tap', 2, 105585], ['tap', 3, 105918], ['tap', 0, 106251], ['tap', 1, 106584], ['tap', 2, 106917], ['tap', 3, 107250], ['tap', 0, 107583], ['tap', 1, 107916], ['tap', 2, 108249], ['tap', 3, 108582], ['tap', 0, 108915], ['tap', 1, 109248], ['tap', 2, 109581], ['tap', 3, 109914], ['tap', 0, 110247], ['tap', 1, 110580], ['tap', 2, 110913], ['tap', 3, 111246], ['tap', 0, 111579], ['tap', 1, 111912], ['tap', 2, 112245], ['tap', 3, 112578], ['tap', 0, 112911], ['tap', 1, 113244], ['tap', 2, 113577], ['tap', 3, 113910], ['tap', 0, 114243], ['tap', 1, 114576], ['tap', 2, 114909], ['tap', 3, 115242], ['tap', 0, 115575], ['tap', 1, 115908], ['tap', 2, 116241], ['tap', 3, 116574], ['tap', 0, 116907], ['tap', 1, 117240], ['tap', 2, 117573], ['tap', 3, 117906], ['tap', 0, 118239], ['tap', 1, 118572], ['tap', 2, 118905], ['tap', 3, 119238], ['tap', 0, 119571], ['tap', 1, 119904], ['tap', 2, 120237], ['tap', 3, 120570], ['tap', 0, 120903], ['tap', 1, 121236], ['tap', 2, 121569], ['tap', 3, 121902], ['tap', 0, 122235], ['tap', 1, 122568], ['tap', 2, 122901], ['tap', 3, 123234], ['tap', 0, 123567], ['tap', 1, 123900], ['tap', 2, 124233], ['tap', 3, 124566], ['tap', 0, 124899], ['tap', 1, 125232], ['tap', 2, 125565], ['tap', 3, 125898], ['tap', 0, 126231], ['tap', 1, 126564], ['tap', 2, 126897], ['tap', 3, 127230], ['tap', 0, 127563], ['tap', 1, 127896], ['tap', 2, 128229], ['tap', 3, 128562], ['tap', 0, 128895], ['tap', 1, 129228], ['tap', 2, 129561], ['tap', 3, 129894], ['tap', 0, 130227], ['tap', 1, 130560], ['tap', 2, 130893], ['tap', 3, 131226], ['tap', 0, 131559], ['tap', 1, 131892], ['tap', 2, 132225], ['tap', 3, 132558], ['tap', 0, 132891], ['tap', 1, 133224], ['tap', 2, 133557], ['tap', 3, 133890], ['tap', 0, 134223], ['tap', 1, 134556], ['tap', 2, 134889], ['tap', 3, 135222], ['tap', 0, 135555], ['tap', 1, 135888], ['tap', 2, 136221], ['tap', 3, 136554], ['tap', 0, 136887], ['tap', 1, 137220], ['tap', 2, 137553], ['tap', 3, 137886], ['tap', 0, 138219], ['tap', 1, 138552], ['tap', 2, 138885], ['tap', 3, 139218], ['tap', 0, 139551], ['tap', 1, 139884], ['tap', 2, 140217], ['tap', 3, 140550], ['tap', 0, 140883], ['tap', 1, 141216], ['tap', 2, 141549], ['tap', 3, 141882], ['tap', 0, 142215], ['tap', 1, 142548], ['tap', 2, 142881], ['tap', 3, 143214], ['tap', 0, 143547], ['tap', 1, 143880], ['tap', 2, 144213], ['tap', 3, 144546], ['tap', 0, 144879], ['tap', 1, 145212], ['tap', 2, 145545], ['tap', 3, 145878], ['tap', 0, 146211], ['tap', 1, 146544], ['tap', 2, 146877], ['tap', 3, 147210], ['tap', 0, 147543], ['tap', 1, 147876], ['tap', 2, 148209], ['tap', 3, 148542], ['tap', 0, 148875], ['tap', 1, 149208], ['tap', 2, 149541], ['tap', 3, 149874], ['tap', 0, 150207], ['tap', 1, 150540], ['tap', 2, 150873], ['tap', 3, 151206], ['tap', 0, 151539], ['tap', 1, 151872], ['tap', 2, 152205], ['tap', 3, 152538], ['tap', 0, 152871], ['tap', 1, 153204], ['tap', 2, 153537], ['tap', 3, 153870], ['tap', 0, 154203], ['tap', 1, 154536], ['tap', 2, 154869], ['tap', 3, 155202], ['tap', 0, 155535], ['tap', 1, 155868], ['tap', 2, 156201], ['tap', 3, 156534], ['tap', 0, 156867], ['tap', 1, 157200], ['tap', 2, 157533], ['tap', 3, 157866], ['tap', 0, 158199], ['tap', 1, 158532], ['tap', 2, 158865], ['tap', 3, 159198], ['tap', 0, 159531], ['tap', 1, 159864], ['tap', 2, 160197], ['tap', 3, 160530], ['tap', 0, 160863], ['tap', 1, 161196], ['tap', 2, 161529], ['tap', 3, 161862], ['tap', 0, 162195], ['tap', 1, 162528], ['tap', 2, 162861], ['tap', 3, 163194], ['tap', 0, 163527], ['tap', 1, 163860], ['tap', 2, 164193], ['tap', 3, 164526], ['tap', 0, 164859], ['tap', 1, 165192], ['tap', 2, 165525], ['tap', 3, 165858], ['tap', 0, 166191], ['tap', 1, 166524], ['tap', 2, 166857], ['tap', 3, 167190], ['tap', 0, 167523], ['tap', 1, 167856], ['tap', 2, 168189], ['tap', 3, 168522], ['tap', 0, 168855], ['tap', 1, 169188], ['tap', 2, 169521], ['tap', 3, 169854], ['tap', 0, 170187], ['tap', 1, 170520], ['tap', 2, 170853], ['tap', 3, 171186], ['tap', 0, 171519], ['tap', 1, 171852], ['tap', 2, 172185], ['tap', 3, 172518], ['tap', 0, 172851], ['tap', 1, 173184], ['tap', 2, 173517], ['tap', 3, 173850], ['tap', 0, 174183], ['tap', 1, 174516], ['tap', 2, 174849], ['tap', 3, 175182], ['tap', 0, 175515], ['tap', 1, 175848], ['tap', 2, 176181], ['tap', 3, 176514], ['tap', 0, 176847], ['tap', 1, 177180], ['tap', 2, 177513], ['tap', 3, 177846], ['tap', 0, 178179], ['tap', 1, 178512], ['tap', 2, 178845], ['tap', 3, 179178], ['tap', 0, 179511], ['tap', 1, 179844], ['tap', 2, 180177], ['tap', 3, 180510], ['tap', 0, 180843], ['tap', 1, 181176], ['tap', 2, 181509], ['tap', 3, 181842], ['tap', 0, 182175], ['tap', 1, 182508], ['tap', 2, 182841], ['tap', 3, 183174], ['tap', 0, 183507], ['tap', 1, 183840], ['tap', 2, 184173], ['tap', 3, 184506], ['tap', 0, 184839], ['tap', 1, 185172], ['tap', 2, 185505], ['tap', 3, 185838], ['tap', 0, 186171], ['tap', 1, 186504], ['tap', 2, 186837], ['tap', 3, 187170], ['tap', 0, 187503], ['tap', 1, 187836], ['tap', 2, 188169], ['tap', 3, 188502], ['tap', 0, 188835], ['tap', 1, 189168], ['tap', 2, 189501], ['tap', 3, 189834], ['tap', 0, 190167], ['tap', 1, 190500], ['tap', 2, 190833], ['tap', 3, 191166], ['tap', 0, 191499], ['tap', 1, 191832], ['tap', 2, 192165], ['tap', 3, 192498], ['tap', 0, 192831], ['tap', 1, 193164], ['tap', 2, 193497], ['tap', 3, 193830], ['tap', 0, 194163], ['tap', 1, 194496], ['tap', 2, 194829], ['tap', 3, 195000]]
	}
}];
// --- GUI ELEMENTS ---
var gui = LK.gui;
var languageButtons = [];
var startButton = null;
var songCards = [];
var storyText = null;
var continueButton = null;
var difficultyButtons = [];
var scoreText = null;
var comboText = null;
var accuracyText = null;
var resultText = null;
var homeButton = null;
// --- GAME ELEMENTS ---
var lanes = [];
var laneDividers = [];
var hitLine = null;
// --- UTILS ---
function clearGUI() {
	// Remove all children from gui overlays, but only if they exist
	if (gui.top && gui.top.removeChildren) {
		gui.top.removeChildren();
	}
	if (gui.topRight && gui.topRight.removeChildren) {
		gui.topRight.removeChildren();
	}
	if (gui.topLeft && gui.topLeft.removeChildren) {
		gui.topLeft.removeChildren();
	}
	if (gui.left && gui.left.removeChildren) {
		gui.left.removeChildren();
	}
	if (gui.right && gui.right.removeChildren) {
		gui.right.removeChildren();
	}
	if (gui.bottom && gui.bottom.removeChildren) {
		gui.bottom.removeChildren();
	}
	if (gui.bottomLeft && gui.bottomLeft.removeChildren) {
		gui.bottomLeft.removeChildren();
	}
	if (gui.bottomRight && gui.bottomRight.removeChildren) {
		gui.bottomRight.removeChildren();
	}
	if (gui.center && gui.center.removeChildren) {
		gui.center.removeChildren();
	}
	// Reset GUI-related global state
	languageButtons = [];
	startButton = null;
	songCards = [];
	storyText = null;
	continueButton = null;
	difficultyButtons = [];
	scoreText = null;
	comboText = null;
	accuracyText = null;
	resultText = null;
	homeButton = null;
}
function clearGameObjects() {
	removeGameplayBackgrounds(); // Ensure backgrounds are removed
	// Remove all notes, lanes, etc.
	for (var i = 0; i < lanes.length; ++i) {
		if (lanes[i] && lanes[i].parent) {
			lanes[i].parent.removeChild(lanes[i]);
		}
	}
	for (var i = 0; i < laneDividers.length; ++i) {
		if (laneDividers[i] && laneDividers[i].parent) {
			laneDividers[i].parent.removeChild(laneDividers[i]);
		}
	}
	if (hitLine && hitLine.parent) {
		hitLine.parent.removeChild(hitLine);
	}
	for (var i = 0; i < currentNotes.length; ++i) {
		if (currentNotes[i] && currentNotes[i].parent) {
			currentNotes[i].parent.removeChild(currentNotes[i]);
		}
	}
	lanes = [];
	laneDividers = [];
	hitLine = null;
	currentNotes = [];
	noteIndex = 0;
	holdActive = null;
	swipeStart = null;
	swipeNote = null;
	isPlaying = false;
	// Do not remove Stopsymbol pause button here; it should persist during gameplay
}
function setGameState(state) {
	GAME_STATE = state;
	// Always remove gameplay backgrounds from the game scene (prevents background leak to other screens)
	removeGameplayBackgrounds();
	removeGameplayBackgrounds(); // Double-call for safety
	// Stop any playing music when changing states (except when going to PLAY)
	if (state !== 'PLAY') {
		LK.stopMusic();
	}
	clearGUI();
	clearGameObjects();
	// Reset global state for each screen
	dragLane = null;
	dragY = null;
	dragNote = null;
	dragStartX = null;
	dragStartY = null;
	dragStartTime = null;
	if (state === 'LANGUAGE') {
		showLanguageSelector();
	} else if (state === 'HOME') {
		selectedSong = null;
		selectedDifficulty = null;
		showHome();
	} else if (state === 'STORY') {
		showStory();
	} else if (state === 'DIFFICULTY') {
		showDifficulty();
	} else if (state === 'PLAY') {
		startGameplay();
	} else if (state === 'RESULT') {
		showResult();
	}
}
// --- LANGUAGE SELECTOR ---
function showLanguageSelector() {
	languageButtons = [];
	// Get current screen size from LK
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Create opening screen container (with background)
	var openingScreen = new Container();
	// Add background asset, centered
	var bgAsset = LK.getAsset('Openingscreen', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0
	});
	openingScreen.addChild(bgAsset);
	// Add three SelectorUI assets as backgrounds for English, 中文, and play button
	// Calculate button positions and sizes to fit text
	var langBtnYStart = Math.floor(screenHeight * 0.45) - screenHeight / 2;
	var langBtnSpacing = Math.floor(screenHeight * 0.08);
	// English SelectorUI
	var selectorBgEn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: langBtnYStart,
		scaleX: 3.2,
		scaleY: 1.3
	});
	openingScreen.addChild(selectorBgEn);
	// 中文 SelectorUI
	var selectorBgZh = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: langBtnYStart + langBtnSpacing,
		scaleX: 2.2,
		scaleY: 1.3
	});
	openingScreen.addChild(selectorBgZh);
	// Play button SelectorUI
	var playBtnY = Math.floor(screenHeight * 0.70) - screenHeight / 2;
	var selectorBgPlay = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: playBtnY,
		scaleX: 3.2,
		scaleY: 1.5
	});
	openingScreen.addChild(selectorBgPlay);
	// Center all elements inside the openingScreen container
	// Title
	var titleText = new Text2("Kaleidoscope of Music Rhythm", {
		size: 80,
		fill: "#fff"
	});
	titleText.anchor.set(0.5, 0.5);
	// Place title at 23% of screen height, centered horizontally, and ensure it's not above y=80
	titleText.x = 0;
	titleText.y = Math.max(80 - screenHeight / 2, Math.floor(screenHeight * 0.23) - screenHeight / 2);
	openingScreen.addChild(titleText);
	// Language selector
	var langs = [{
		code: 'en',
		label: 'English'
	}, {
		code: 'zh',
		label: '中文'
	}];
	// Place language buttons at 45% and 53% of screen height, centered horizontally
	var langBtnYStart = Math.floor(screenHeight * 0.45) - screenHeight / 2;
	var langBtnSpacing = Math.floor(screenHeight * 0.08);
	for (var i = 0; i < langs.length; ++i) {
		(function (idx) {
			var btn = new Text2(langs[idx].label, {
				size: 64,
				fill: "#fff"
			});
			btn.anchor.set(0.5, 0.5);
			btn.x = 0; // centered in container
			btn.y = langBtnYStart + idx * langBtnSpacing;
			btn.interactive = true;
			btn.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedLanguage = langs[idx].code;
				storage.language = selectedLanguage;
				// Do not advance to HOME yet, just set language
				clearGUI();
				// Remove Stopsymbol pause button when returning to language selector
				removePauseButtonFromScoreboard();
				showLanguageSelector();
			};
			openingScreen.addChild(btn);
			languageButtons.push(btn);
		})(i);
	}
	// Start button
	startButton = new Text2(selectedLanguage === 'zh' ? "开始" : "Start", {
		size: 90,
		fill: "#fff"
	});
	startButton.anchor.set(0.5, 0.5);
	// Place start button at 70% of screen height, centered horizontally
	startButton.x = 0;
	startButton.y = Math.floor(screenHeight * 0.70) - screenHeight / 2;
	startButton.interactive = true;
	startButton.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		setGameState('HOME');
	};
	openingScreen.addChild(startButton);
	// Add the opening screen container to gui.center
	gui.center.addChild(openingScreen);
	// Play background music for opening screen
	LK.playMusic('KaleidoscopeMusicRhythm');
}
// --- HOME / SONG SELECTION ---
function showHome() {
	removeGameplayBackgrounds();
	clearGUI();
	clearGameObjects();
	// Remove Stopsymbol pause button when returning to home
	removePauseButtonFromScoreboard();
	// Remove gameplay backgrounds again to ensure they do not reappear on homepage
	removeGameplayBackgrounds();
	songCards = [];
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Create homeScreen container before adding children
	var homeScreen = new Container();
	// Add Backgroundhomepage asset as background, fully fit to screen
	var bgAsset = LK.getAsset('Backgroundhomepage', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: screenWidth / 2048,
		scaleY: screenHeight / 2732
	});
	homeScreen.addChild(bgAsset);
	bgAsset.width = screenWidth;
	bgAsset.height = screenHeight;
	// Title: "Select a Song", in white with proper positioning
	var title = new Text2(selectedLanguage === 'zh' ? "选择歌曲" : "Select a Song", {
		size: 90,
		fill: "#fff",
		// White text for contrast
		font: "Arial Black"
	});
	title.anchor.set(0.5, 0);
	// Place title at 15% of screen height, centered horizontally, and ensure it's not above y=120
	// For gui.center containers, (0,0) is the center, so offset from center
	title.x = 0;
	title.y = -screenHeight / 2 + Math.max(120, Math.floor(screenHeight * 0.15));
	homeScreen.addChild(title);
	// Card layout - vertical stacking with proper spacing
	var cardCount = SONGS.length;
	var cardHeight = 200;
	var cardSpacing = 80;
	var titleSpacing = 100;
	// Place the title at 15% of screen height, and cards start at 25% of screen height
	// We want all cards to be below the title and fully within SelectorUI
	// Calculate the starting Y so that the first card is just below the title, and all cards fit on screen
	var cardsStartY = title.y + title.height + titleSpacing; // 100px gap below title
	for (var i = 0; i < SONGS.length; i++) {
		(function (index) {
			var song = SONGS[index];
			// Card Y position
			var cardY = cardsStartY + index * (cardHeight + cardSpacing);
			// Create SelectorUI background for song card
			var selectorScaleX = 4.0;
			var selectorScaleY = 2.0;
			if (song.id === "NewEraMalayStyle") {
				selectorScaleX = 5.0;
				selectorScaleY = 2.5;
			}
			var cardBg = LK.getAsset('SelectorUI', {
				anchorX: 0.5,
				anchorY: 0.5,
				x: 0,
				y: cardY,
				scaleX: selectorScaleX,
				scaleY: selectorScaleY
			});
			homeScreen.addChild(cardBg);
			// Make card interactive
			cardBg.interactive = true;
			cardBg.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedSong = song;
				setGameState('STORY');
			};
			// Add song title (centered within SelectorUI)
			var songNameText;
			if (song.id === "UnknownJourney") {
				songNameText = new Text2(song.title, {
					size: 44,
					fill: "#fff",
					font: "Arial"
				});
			} else if (song.id === "NewEraMalayStyle") {
				var displayTitle = selectedLanguage === 'zh' ? song.title_zh : song.title;
				songNameText = new Text2(displayTitle, {
					size: 48,
					fill: "#fff",
					font: "Arial"
				});
			} else {
				songNameText = new Text2(song.title, {
					size: 64,
					fill: "#fff",
					font: "Arial"
				});
			}
			songNameText.anchor.set(0.5, 0.5);
			songNameText.x = 0;
			songNameText.y = song.id === "NewEraMalayStyle" ? cardY - 20 : cardY - 30;
			homeScreen.addChild(songNameText);
			// Add subtitle (optional: show "Sky Journey", "Night Pulse", "Sunrise Waltz", "Unknown Journey")
			var subtitleZh = "";
			if (selectedLanguage === 'zh') {
				if (song.title === "Sky Journey") subtitleZh = "天空之旅";else if (song.title === "Night Pulse") subtitleZh = "夜之律动";else if (song.title === "Sunrise Waltz") subtitleZh = "日出圆舞曲";else if (song.title === "Unknown Journey") subtitleZh = "莫问前程";else if (song.title === "New Era Malay Style") subtitleZh = "新纪元马来风";
			}
			var subtitleText;
			if (song.id === "UnknownJourney") {
				subtitleText = new Text2(subtitleZh, {
					size: 28,
					fill: 0xE0E0E0,
					font: "Arial"
				});
			} else if (song.id === "NewEraMalayStyle" && selectedLanguage === 'zh') {
				// Only show one instance of 新纪元马来风 (no duplicate small subtitle)
				subtitleText = new Text2("New Era Malay Style", {
					size: 36,
					fill: 0xE0E0E0,
					font: "Arial"
				});
			} else {
				subtitleText = new Text2(subtitleZh, {
					size: 40,
					fill: 0xE0E0E0,
					font: "Arial"
				});
			}
			subtitleText.anchor.set(0.5, 0.5);
			subtitleText.x = 0;
			subtitleText.y = cardY + 30;
			homeScreen.addChild(subtitleText);
			songCards.push(cardBg);
		})(i);
	}
	// Add Return button (top right, avoid 100x100 area, larger for visibility)
	var returnBtn = LK.getAsset('Return', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -120,
		//{6p} // Move leftwards to prevent off screen
		y: 120,
		//{6q} // Move downwards to prevent off screen
		scaleX: 2.6,
		scaleY: 2.6
	});
	returnBtn.interactive = true;
	returnBtn.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		setGameState('LANGUAGE');
	};
	// Add to gui.topRight for consistent top-right placement
	if (gui.topRight && gui.topRight.addChild) {
		gui.topRight.addChild(returnBtn);
	}
	// Add homeScreen to GUI, centered
	homeScreen.x = 0;
	homeScreen.y = 0;
	if (gui.center && gui.center.addChild) {
		gui.center.addChild(homeScreen);
	}
	// Play background music for homepage
	LK.playMusic('KaleidoscopeMusicRhythm');
}
// --- STORY INTRO ---
function showStory() {
	clearGUI();
	var story = selectedSong.story[selectedLanguage] || selectedSong.story['en'];
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Create SongStorybox container
	var storyBox = new Container();
	// Add SongStorybox asset as background, centered and scaled up for showcase
	var boxAsset = LK.getAsset('SongStorybox', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 13,
		// Increased for larger showcase
		scaleY: 11
	});
	storyBox.addChild(boxAsset);
	// Calculate box bounds for layout
	var boxW = boxAsset.width * boxAsset.scaleX;
	var boxH = boxAsset.height * boxAsset.scaleY;
	var boxLeftX = -boxW / 2;
	var boxRightX = boxW / 2;
	var boxTopY = -boxH / 2;
	var boxBottomY = boxH / 2;
	// Add smaller song cover on the left side of the box
	var coverAsset = LK.getAsset(selectedSong.cover, {
		anchorX: 0.5,
		anchorY: 0.5,
		x: boxLeftX + boxW * 0.25,
		// Position at 25% from left edge
		y: boxTopY + boxH * 0.35,
		// Position at 35% from top
		scaleX: 1.2,
		// Smaller cover
		scaleY: 1.2
	});
	storyBox.addChild(coverAsset);
	// Add story text on the larger right side of the box
	// For Chinese, ensure paragraph form and improved word wrap
	var storyParagraphs = [];
	if (selectedLanguage === 'zh') {
		// Remove all whitespace and line breaks
		var cleaned = story.replace(/[\r\n]/g, '');
		// Split at Chinese fullstop (。), Chinese comma (,), normal comma (,), normal fullstop (.), exclamation (!,!), question (?,?)
		var parts = cleaned.split(/(。|,|,|\.|!|!|\?|?)/);
		var temp = "";
		for (var i = 0; i < parts.length; i++) {
			temp += parts[i];
			// If the part is a punctuation, treat as paragraph end
			if (parts[i] === "。" || parts[i] === "," || parts[i] === "," || parts[i] === "." || parts[i] === "!" || parts[i] === "!" || parts[i] === "?" || parts[i] === "?") {
				if (temp.trim().length > 0) {
					storyParagraphs.push(temp.trim());
				}
				temp = "";
			}
		}
		// If any text remains (no ending punctuation), add as last paragraph
		if (temp.trim().length > 0) {
			storyParagraphs.push(temp.trim());
		}
		// Further split any paragraph that is too long to fit, to prevent off-screen/overlap
		var maxCharsPerParagraph = 13; // Tighter for best fit with cover
		var adjustedParagraphs = [];
		for (var i = 0; i < storyParagraphs.length; ++i) {
			var para = storyParagraphs[i];
			// If paragraph is too long, split into multiple
			while (para.length > maxCharsPerParagraph) {
				adjustedParagraphs.push(para.substring(0, maxCharsPerParagraph));
				para = para.substring(maxCharsPerParagraph);
			}
			if (para.length > 0) {
				adjustedParagraphs.push(para);
			}
		}
		storyParagraphs = adjustedParagraphs;
	} else {
		storyParagraphs = [story];
	}
	// Calculate Y start so text never overlaps cover and never goes off box
	var storyTextY = boxTopY + boxH * 0.13;
	for (var i = 0; i < storyParagraphs.length; ++i) {
		var para = storyParagraphs[i];
		var paraText = new Text2(para, {
			size: 55,
			fill: "#fff",
			wordWrap: true,
			wordWrapWidth: selectedLanguage === 'zh' ? boxW * 0.54 : boxW * 0.45,
			align: "left"
		});
		if (selectedLanguage === 'zh') {
			paraText.anchor.set(0, 0); // Left align for Chinese
			paraText.x = boxLeftX + boxW * 0.48; // Start further right for Chinese, away from cover
		} else {
			paraText.anchor.set(0, 0);
			paraText.x = boxLeftX + boxW * 0.52; // Start at 52% from left edge
		}
		paraText.y = storyTextY;
		storyBox.addChild(paraText);
		// Move down for next paragraph, add extra spacing for Chinese
		storyTextY += paraText.height + (selectedLanguage === 'zh' ? 18 : 16);
	}
	// Create 3 separate SelectorUI elements for difficulty buttons arranged horizontally
	var diffY = boxBottomY - boxH * 0.15; // Y position for all difficulty buttons
	var totalWidth = boxW * 0.8; // Use 80% of box width for all buttons
	var buttonWidth = totalWidth / selectedSong.difficulties.length;
	var startX = -totalWidth / 2 + buttonWidth / 2; // Center the button group
	difficultyButtons = [];
	for (var i = 0; i < selectedSong.difficulties.length; ++i) {
		(function (idx) {
			var label = selectedSong.difficulties[idx];
			// Create individual SelectorUI for this difficulty button
			var selectorAsset = LK.getAsset('SelectorUI', {
				anchorX: 0.5,
				anchorY: 0.5,
				x: startX + idx * buttonWidth,
				y: diffY,
				scaleX: 2.5,
				scaleY: 2.2
			});
			selectorAsset.interactive = true;
			selectorAsset.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedDifficulty = label;
				setGameState('PLAY');
			};
			storyBox.addChild(selectorAsset);
			// Add difficulty button text centered on the SelectorUI
			var btn = new Text2(label, {
				size: 55,
				fill: "#fff"
			});
			btn.anchor.set(0.5, 0.5);
			btn.x = startX + idx * buttonWidth;
			btn.y = diffY - 15;
			storyBox.addChild(btn);
			// Add high score display below difficulty button
			var highScore = getHighScore(selectedSong.id, label);
			var highScoreText = new Text2((selectedLanguage === 'zh' ? "最高: " : "Best: ") + highScore, {
				size: 36,
				fill: 0xFFD700
			});
			highScoreText.anchor.set(0.5, 0.5);
			highScoreText.x = startX + idx * buttonWidth;
			highScoreText.y = diffY + 25;
			storyBox.addChild(highScoreText);
			difficultyButtons.push(btn);
		})(i);
	}
	// Start storyBox at bottom-right corner of screen
	storyBox.x = screenWidth;
	storyBox.y = screenHeight;
	gui.center.addChild(storyBox);
	// Add Return button (top right, avoid 100x100 area, larger for visibility)
	var returnBtn = LK.getAsset('Return', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -120,
		//{7Q} // Move leftwards to prevent off screen
		y: 120,
		//{7R} // Move downwards to prevent off screen
		scaleX: 2.6,
		scaleY: 2.6
	});
	returnBtn.interactive = true;
	returnBtn.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		setGameState('HOME');
	};
	// Add to gui.topRight for consistent top-right placement
	if (gui.topRight && gui.topRight.addChild) {
		gui.topRight.addChild(returnBtn);
	}
	// Animate storyBox to center of screen
	tween(storyBox, {
		x: 0,
		y: 0
	}, {
		duration: 800,
		easing: tween.easeOut
	});
}
// --- DIFFICULTY SELECT ---
function showDifficulty() {
	difficultyButtons = [];
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	var title = new Text2(selectedLanguage === 'zh' ? "选择难度" : "Select Difficulty", {
		size: 100,
		fill: "#fff"
	});
	title.anchor.set(0.5, 0);
	title.x = screenWidth / 2;
	title.y = Math.max(100, Math.floor(screenHeight * 0.08));
	gui.top.addChild(title);
	var btnYStart = Math.floor(screenHeight * 0.40);
	var btnSpacing = Math.floor(screenHeight * 0.13);
	for (var i = 0; i < selectedSong.difficulties.length; ++i) {
		(function (idx) {
			var label = selectedSong.difficulties[idx];
			var btn = new Text2(label, {
				size: 90,
				fill: "#fff"
			});
			btn.anchor.set(0.5, 0.5);
			btn.x = screenWidth / 2;
			btn.y = btnYStart + idx * btnSpacing;
			btn.interactive = true;
			btn.down = function (x, y, obj) {
				LK.getSound('Touch').play();
				selectedDifficulty = label;
				setGameState('PLAY');
			};
			gui.center.addChild(btn);
			difficultyButtons.push(btn);
		})(i);
	}
}
// --- GAMEPLAY ---
function startGameplay() {
	removeGameplayBackgrounds(); // Clear previous backgrounds
	// Add new background for gameplay and track it
	if (selectedSong && selectedSong.id === 'SkyJourney') {
		gameplayBackground = LK.getAsset('BgSkyJourney', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'NightPulse') {
		gameplayBackground = LK.getAsset('BgNightPulse', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'SunriseWaltz') {
		gameplayBackground = LK.getAsset('BgSunriseWaltz', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'UnknownJourney') {
		gameplayBackground = LK.getAsset('BgUnknownJourney', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3640.89
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	} else if (selectedSong && selectedSong.id === 'NewEraMalayStyle') {
		gameplayBackground = LK.getAsset('BgNewEraMalayStyle', {
			anchorX: 0.5,
			anchorY: 0,
			x: 2048 / 2,
			y: 0,
			scaleX: 2048 / 2048,
			scaleY: 2732 / 3066.01
		});
		gameplayBackground.width = 2048;
		gameplayBackground.height = 2732;
		game.addChildAt(gameplayBackground, 0);
	}
	lanes = [];
	laneDividers = [];
	var laneSpacing = 2048 / LANE_COUNT; // Equal spacing for 4 lanes
	for (var i = 0; i < LANE_COUNT; ++i) {
		var lane = LK.getAsset('lane', {
			anchorX: 0.5,
			anchorY: 0
		});
		lane.x = (i + 0.5) * laneSpacing; // Center each lane in its section
		lane.y = 0;
		lane.scaleX = laneSpacing / lane.width; // Scale to fit lane width
		lane.scaleY = 2732 / lane.height; // Scale to full screen height
		lanes.push(lane);
		game.addChild(lane);
	}
	// Create lane dividers as hit areas at the bottom of lanes
	for (var i = 0; i < LANE_COUNT; ++i) {
		var divider = LK.getAsset('laneDivider', {
			anchorX: 0.5,
			anchorY: 1
		});
		divider.x = (i + 0.5) * laneSpacing; // Center in each lane
		divider.y = HIT_LINE_Y + 100; // Position at bottom as hit area
		divider.scaleX = laneSpacing * 0.8 / divider.width; // Scale to 80% of lane width
		divider.scaleY = 0.3; // Make it thinner for hit area
		laneDividers.push(divider);
		game.addChild(divider);
	}
	// Hit line (visual indicator)
	hitLine = LK.getAsset('laneDivider', {
		anchorX: 0,
		anchorY: 0.5
	});
	hitLine.width = 2048;
	hitLine.height = 8;
	hitLine.x = 0;
	hitLine.y = HIT_LINE_Y;
	hitLine.alpha = 0.7; // Make it semi-transparent
	game.addChild(hitLine);
	// Load notes
	currentSongData = selectedSong.notes[selectedDifficulty];
	currentNotes = [];
	noteIndex = 0;
	score = 0;
	combo = 0;
	maxCombo = 0;
	accuracy = 0;
	totalNotes = currentSongData.length;
	hitNotes = 0;
	missNotes = 0;
	badNotes = 0;
	goodNotes = 0;
	greatNotes = 0;
	perfectNotes = 0;
	scorePopups = [];
	holdActive = null;
	swipeStart = null;
	swipeNote = null;
	isPlaying = true;
	for (var i = 0; i < currentSongData.length; ++i) {
		var n = currentSongData[i];
		var note = new Note();
		note.init(n[0], n[1], n[2], n[3], n[4]);
		// Initial position: y far above screen, will be updated in update loop
		var laneSpacing = 2048 / LANE_COUNT;
		note.x = (n[1] + 0.5) * laneSpacing; // Center in the lane
		note.y = -200;
		currentNotes.push(note);
		game.addChild(note);
	}
	// Create scoreboard container with SelectorUI background
	var scoreboardBg = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0,
		x: 0,
		y: 0,
		scaleX: 8.0,
		scaleY: 2.0
	});
	gui.top.addChild(scoreboardBg);
	// Score/Combo/Accuracy GUI positioned on scoreboard
	var scoreboardFontColor = selectedSong && selectedSong.id === 'UnknownJourney' ? "#000" : "#fff";
	scoreText = new Text2("Score: 0", {
		size: 70,
		fill: scoreboardFontColor
	});
	scoreText.anchor.set(0.5, 0);
	scoreText.x = 0;
	scoreText.y = 20;
	gui.top.addChild(scoreText);
	comboText = new Text2("Combo: 0", {
		size: 70,
		fill: scoreboardFontColor
	});
	comboText.anchor.set(0.5, 0);
	comboText.x = 0;
	comboText.y = 70;
	gui.top.addChild(comboText);
	accuracyText = new Text2("Accuracy: 100%", {
		size: 70,
		fill: scoreboardFontColor
	});
	accuracyText.anchor.set(0.5, 0);
	accuracyText.x = 0;
	accuracyText.y = 120;
	gui.top.addChild(accuracyText);
	// Stop any playing background music immediately before starting gameplay music
	LK.stopMusic();
	// Start music - play full song for each gameplay without looping
	LK.playMusic(selectedSong.id, {
		loop: false
	});
	// Start timer
	startTime = Date.now();
	// Always add Stopsymbol pause button at the start of gameplay
	addPauseButtonToScoreboard();
}
// --- GAMEPLAY LOGIC ---
function getCurrentTime() {
	return Date.now() - startTime;
}
function getNoteY(noteTime) {
	// Notes fall from y = -200 to HIT_LINE_Y at the time they should be hit
	// We'll use a fixed speed so that notes reach HIT_LINE_Y at noteTime
	var speed = (HIT_LINE_Y + 200) / 2000; // 2 seconds to fall
	var t = getCurrentTime();
	var dt = noteTime - t;
	return HIT_LINE_Y - dt * speed;
}
function getHoldTrailLength(note, t) {
	// For hold notes, trail from note head to end time
	var endTime = note.time + note.duration;
	var y1 = getNoteY(note.time);
	var y2 = getNoteY(endTime);
	return Math.max(0, y2 - y1);
}
function judgeNote(note, lane, y, eventType, swipeDir) {
	if (!note.active || note.hit) {
		return false;
	}
	var noteY = getNoteY(note.time);
	var t = getCurrentTime();
	var dt = Math.abs(note.time - t);
	var scoreType = "";
	var scoreValue = 0;
	// Determine hit quality based on timing
	if (dt <= PERFECT_WINDOW) {
		scoreType = "perfect";
		scoreValue = 150;
		perfectNotes += 1;
	} else if (dt <= GREAT_WINDOW) {
		scoreType = "great";
		scoreValue = 120;
		greatNotes += 1;
	} else if (dt <= GOOD_WINDOW) {
		scoreType = "good";
		scoreValue = 80;
		goodNotes += 1;
	} else if (dt <= BAD_WINDOW) {
		scoreType = "bad";
		scoreValue = 40;
		badNotes += 1;
	} else {
		return false; // Miss
	}
	if (note.type === 'tap') {
		if (lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW) {
			note.hit = true;
			note.active = false;
			note.flash();
			// Create sparkling effect on the note itself
			tween(note, {
				scaleX: note.noteAsset.scaleX * 1.3,
				scaleY: note.noteAsset.scaleY * 1.3,
				rotation: 0.2
			}, {
				duration: 100,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					tween(note, {
						scaleX: note.noteAsset.scaleX / 1.3,
						scaleY: note.noteAsset.scaleY / 1.3,
						rotation: 0
					}, {
						duration: 100,
						easing: tween.easeIn
					});
				}
			});
			// Immediately remove from currentNotes array to prevent flow issues
			var tapIndex = currentNotes.indexOf(note);
			if (tapIndex > -1) {
				currentNotes.splice(tapIndex, 1);
			}
			// Make note disappear immediately after touch with burst animation
			tween(note, {
				alpha: 0,
				scaleX: 1.5,
				scaleY: 1.5
			}, {
				duration: 150,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			LK.getSound('tap').play();
			score += scoreValue;
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit area
			var hitArea = laneDividers[lane];
			if (hitArea) {
				// Store original tint if not already stored
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				// Light up with golden glow
				hitArea.tint = 0xFFD700;
				hitArea.alpha = 1.0;
				// Store original scale if not already stored
				if (hitArea.originalScaleX === undefined) {
					hitArea.originalScaleX = hitArea.scaleX;
					hitArea.originalScaleY = hitArea.scaleY;
				}
				// Create sparkling effect with scaling animation, but never allow scale to exceed original
				tween(hitArea, {
					scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.3),
					scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.3)
				}, {
					duration: 150,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(hitArea, {
							scaleX: hitArea.originalScaleX,
							scaleY: hitArea.originalScaleY
						}, {
							duration: 150,
							easing: tween.easeIn
						});
					}
				});
				// Fade back to normal after short duration
				tween(hitArea, {
					alpha: 0.7
				}, {
					duration: 200,
					onFinish: function onFinish() {
						hitArea.tint = hitArea.originalTint;
					}
				});
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			return true;
		}
	} else if (note.type === 'hold') {
		// EXTREMELY player-friendly hold detection for both Hard and Medium:
		// - Allow full-lane (all lanes) detection
		// - Allow very wide vertical margin
		// - Greatly increase timing window for initial press
		// - Allow initial press anywhere in the lower half of the screen
		// - Allow initial press up to 500ms before/after the note
		// This is now applied to both Hard and Medium difficulties
		var isMediumOrHard = selectedDifficulty === 'Medium' || selectedDifficulty === 'Hard';
		var isValidLane = true; // Allow any lane for initial press
		var verticalLeniency = isMediumOrHard ? 950 : 350; // Very wide for Medium/Hard, default for Easy
		var timingLeniency = isMediumOrHard ? BAD_WINDOW + 500 : BAD_WINDOW; // More lenient for Medium/Hard
		if (eventType === 'down' && isValidLane && Math.abs(y - HIT_LINE_Y) < verticalLeniency && dt < timingLeniency) {
			holdActive = {
				note: note,
				start: t,
				scoreType: scoreType,
				scoreValue: scoreValue
			};
			note.flash();
			// Start continuous sparkling animation on hit area during hold
			var hitArea = laneDividers[note.lane];
			if (hitArea) {
				// Create pulsing sparkling effect
				var _createSparkle = function createSparkle() {
					if (holdActive && holdActive.note === note) {
						hitArea.tint = 0xFFD700; // Golden sparkle
						tween(hitArea, {
							scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.2),
							scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.2),
							alpha: 1.0
						}, {
							duration: 200,
							easing: tween.easeOut,
							onFinish: function onFinish() {
								tween(hitArea, {
									scaleX: hitArea.originalScaleX,
									scaleY: hitArea.originalScaleY,
									alpha: 0.8
								}, {
									duration: 200,
									easing: tween.easeIn,
									onFinish: function onFinish() {
										// Continue sparkling if still holding
										if (holdActive && holdActive.note === note) {
											LK.setTimeout(_createSparkle, 100);
										} else {
											// Reset hit area when hold ends
											hitArea.tint = hitArea.originalTint;
											hitArea.scaleX = hitArea.originalScaleX;
											hitArea.scaleY = hitArea.originalScaleY;
											hitArea.alpha = 0.7;
										}
									}
								});
							}
						});
					}
				};
				// Store original properties
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				if (hitArea.originalScaleX === undefined) {
					hitArea.originalScaleX = hitArea.scaleX;
					hitArea.originalScaleY = hitArea.scaleY;
				}
				_createSparkle();
			}
			LK.getSound('hold').play();
			return true;
		}
	} else if (note.type === 'swipe') {
		// Check if swipe affects multiple lanes - enhanced detection for bigger swipe notes
		var validLanes = [note.lane];
		var laneSpacing = 2048 / LANE_COUNT;
		var swipeWidth = note.noteAsset.width * note.noteAsset.scaleX;
		// Enhanced cross-lane detection for bigger swipe notes
		if (swipeWidth > laneSpacing * 1.8) {
			// More generous threshold
			// Add adjacent lanes for cross-lane swipes
			if (note.lane > 0) validLanes.push(note.lane - 1);
			if (note.lane < LANE_COUNT - 1) validLanes.push(note.lane + 1);
			// For very wide swipes, add one more lane on each side
			if (swipeWidth > laneSpacing * 2.5) {
				if (note.lane > 1) validLanes.push(note.lane - 2);
				if (note.lane < LANE_COUNT - 2) validLanes.push(note.lane + 2);
			}
		}
		var isValidLane = validLanes.indexOf(lane) !== -1;
		// Improved sensitivity - increased distance threshold and relaxed timing for better swipe detection
		// Further increase vertical margin and timing window for better swipe detection
		// Even more forgiving: vertical margin 1200, timing window BAD_WINDOW+800
		if (eventType === 'swipe' && isValidLane && Math.abs(y - HIT_LINE_Y) < 1200 && dt < BAD_WINDOW + 800 && swipeDir === note.swipeDir) {
			note.hit = true;
			note.active = false;
			note.flash();
			// Make swipe note disappear immediately after touch with directional slide animation
			var slideDir = note.swipeDir;
			var targetX = note.x;
			var targetY = note.y;
			var slideDistance = 400; // Increased slide distance
			if (slideDir === 'left') targetX -= slideDistance;else if (slideDir === 'right') targetX += slideDistance;else if (slideDir === 'up') targetY -= slideDistance;else if (slideDir === 'down') targetY += slideDistance;
			// Store reference to note for proper cleanup and prevent red tinting
			var noteToRemove = note;
			// Ensure note is removed from currentNotes array immediately to prevent red tinting and flow issues
			var index = currentNotes.indexOf(noteToRemove);
			if (index > -1) {
				currentNotes.splice(index, 1);
			}
			// Prevent any color tinting by ensuring clean removal
			noteToRemove.tint = 0xFFFFFF;
			noteToRemove.alpha = 0; // Make invisible immediately
			// Remove from parent immediately to prevent any visual artifacts
			if (noteToRemove.parent) {
				noteToRemove.parent.removeChild(noteToRemove);
			}
			// Optional visual effect for feedback
			var effectNote = LK.getAsset('swipeNote', {
				anchorX: 0.5,
				anchorY: 0.5,
				x: note.x,
				y: note.y,
				scaleX: note.noteAsset.scaleX,
				scaleY: note.noteAsset.scaleY
			});
			game.addChild(effectNote);
			tween(effectNote, {
				x: targetX,
				y: targetY,
				alpha: 0,
				scaleX: note.noteAsset.scaleX * 0.3,
				scaleY: note.noteAsset.scaleY * 0.3
			}, {
				duration: 150,
				// Faster animation for better responsiveness
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (effectNote && effectNote.parent) {
						effectNote.parent.removeChild(effectNote);
					}
				}
			});
			LK.getSound('swipe').play();
			score += scoreValue + 50; // Bonus for swipe notes
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit areas for swipe notes (can affect multiple lanes)
			for (var v = 0; v < validLanes.length; v++) {
				var hitArea = laneDividers[validLanes[v]];
				if (hitArea) {
					// Store original tint if not already stored
					if (hitArea.originalTint === undefined) {
						hitArea.originalTint = hitArea.tint || 0xFFFFFF;
					}
					// Light up with cyan glow for swipe
					hitArea.tint = 0x00FFFF;
					hitArea.alpha = 1.0;
					// Create sparkling wave effect with scaling animation, but never allow scale to exceed original
					var delay = v * 50;
					tween(hitArea, {
						scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.4),
						scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.4)
					}, {
						duration: 200,
						delay: delay,
						easing: tween.easeOut,
						onFinish: function onFinish() {
							tween(hitArea, {
								scaleX: hitArea.originalScaleX,
								scaleY: hitArea.originalScaleY
							}, {
								duration: 200,
								easing: tween.easeIn
							});
						}
					});
					// Stagger the fade timing for visual wave effect
					tween(hitArea, {
						alpha: 0.7
					}, {
						duration: 250,
						delay: delay,
						onFinish: function onFinish() {
							hitArea.tint = hitArea.originalTint;
						}
					});
				}
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			return true;
		}
	}
	return false;
}
function endHold() {
	if (holdActive && holdActive.note && !holdActive.note.hit) {
		var t = getCurrentTime();
		var note = holdActive.note;
		var holdEnd = note.time + note.duration;
		// Only complete hold if duration has been fully held AND player is still holding
		if (t >= holdEnd) {
			note.hit = true;
			note.active = false;
			// Immediately remove from currentNotes array to prevent flow issues
			var holdIndex = currentNotes.indexOf(note);
			if (holdIndex > -1) {
				currentNotes.splice(holdIndex, 1);
			}
			// Make hold note disappear slowly with longer satisfying animation
			tween(note, {
				alpha: 0,
				scaleX: 0.1,
				scaleY: 0.1,
				tint: 0x00FF00 // Green glow for successful hold completion
			}, {
				duration: 800,
				//{8j} // Much longer fade
				easing: tween.easeIn,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			score += holdActive.scoreValue + 50; // Bonus for completing hold
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit area for completed hold
			var lane = note.lane;
			var hitArea = laneDividers[lane];
			if (hitArea) {
				// Store original tint if not already stored
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				// Light up with green glow for hold completion
				hitArea.tint = 0x00FF00;
				hitArea.alpha = 1.0;
				// Create sparkling effect with pulsing animation for hold completion, but never allow scale to exceed original
				tween(hitArea, {
					scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.5),
					scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.5)
				}, {
					duration: 200,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(hitArea, {
							scaleX: hitArea.originalScaleX,
							scaleY: hitArea.originalScaleY
						}, {
							duration: 200,
							easing: tween.easeIn
						});
					}
				});
				// Fade back to normal after short duration
				tween(hitArea, {
					alpha: 0.7
				}, {
					duration: 300,
					onFinish: function onFinish() {
						hitArea.tint = hitArea.originalTint;
					}
				});
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(holdActive.scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			holdActive = null;
		}
	}
}
// --- INPUT HANDLING ---
var dragLane = null;
var dragY = null;
var dragNote = null;
var dragStartX = null;
var dragStartY = null;
var dragStartTime = null;
function getLaneFromX(x) {
	var laneSpacing = 2048 / LANE_COUNT;
	for (var i = 0; i < LANE_COUNT; ++i) {
		var laneStart = i * laneSpacing;
		var laneEnd = (i + 1) * laneSpacing;
		if (x >= laneStart && x < laneEnd) {
			return i;
		}
	}
	return -1;
}
function handleGameDown(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	var lane = getLaneFromX(x);
	if (lane === -1) {
		return;
	}
	dragLane = lane;
	dragY = y;
	dragStartX = x;
	dragStartY = y;
	dragStartTime = Date.now();
	// Check for tap/hold notes
	for (var i = 0; i < currentNotes.length; ++i) {
		var note = currentNotes[i];
		if (!note.active || note.hit) {
			continue;
		}
		if (note.type === 'tap' || note.type === 'hold') {
			if (judgeNote(note, lane, y, 'down')) {
				if (note.type === 'tap') {
					note.hit = true;
					note.active = false;
				}
				if (note.type === 'hold') {
					dragNote = note;
				}
				break;
			}
		}
	}
}
function handleGameUp(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	if (holdActive && holdActive.note) {
		var t = getCurrentTime();
		var note = holdActive.note;
		var holdEnd = note.time + note.duration;
		// Check if hold was completed (reached full duration)
		if (t >= holdEnd) {
			endHold();
		} else {
			// Released too early - but if the button detected the hold note with sparkling light, count as good/great/perfect based on timing
			// Use the timing at release to judge
			var dt = Math.abs(note.time - t);
			var scoreType = "";
			var scoreValue = 0;
			if (dt <= PERFECT_WINDOW) {
				scoreType = "perfect";
				scoreValue = 150;
				perfectNotes += 1;
			} else if (dt <= GREAT_WINDOW) {
				scoreType = "great";
				scoreValue = 120;
				greatNotes += 1;
			} else if (dt <= GOOD_WINDOW) {
				scoreType = "good";
				scoreValue = 80;
				goodNotes += 1;
			} else if (dt <= BAD_WINDOW) {
				scoreType = "bad";
				scoreValue = 40;
				badNotes += 1;
			} else {
				// If not within any window, count as miss
				note.hit = false;
				note.active = false;
				// Make failed hold note fade out with red tint
				tween(note, {
					alpha: 0.3,
					tint: 0xFF0000
				}, {
					duration: 400,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						if (note.parent) {
							note.parent.removeChild(note);
						}
					}
				});
				combo = 0;
				missNotes += 1;
				// Show miss popup
				var popup = new ScorePopup();
				popup.init("miss", note.x, note.y);
				game.addChild(popup);
				scorePopups.push(popup);
				holdActive = null;
				return;
			}
			// If we get here, it's a good/great/perfect/bad (not miss)
			note.hit = true;
			note.active = false;
			// Remove from currentNotes array
			var holdIndex = currentNotes.indexOf(note);
			if (holdIndex > -1) {
				currentNotes.splice(holdIndex, 1);
			}
			// Make hold note disappear with a fade animation
			tween(note, {
				alpha: 0,
				scaleX: 0.1,
				scaleY: 0.1,
				tint: 0xFFD700 // Gold for partial hold
			}, {
				duration: 500,
				easing: tween.easeIn,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			score += scoreValue;
			combo += 1;
			hitNotes += 1;
			maxCombo = Math.max(combo, maxCombo);
			// Light up hit area for partial hold
			var lane = note.lane;
			var hitArea = laneDividers[lane];
			if (hitArea) {
				if (hitArea.originalTint === undefined) {
					hitArea.originalTint = hitArea.tint || 0xFFFFFF;
				}
				hitArea.tint = 0xFFD700;
				hitArea.alpha = 1.0;
				tween(hitArea, {
					scaleX: Math.max(hitArea.originalScaleX, hitArea.originalScaleX * 1.2),
					scaleY: Math.max(hitArea.originalScaleY, hitArea.originalScaleY * 1.2)
				}, {
					duration: 150,
					easing: tween.easeOut,
					onFinish: function onFinish() {
						tween(hitArea, {
							scaleX: hitArea.originalScaleX,
							scaleY: hitArea.originalScaleY
						}, {
							duration: 150,
							easing: tween.easeIn
						});
					}
				});
				tween(hitArea, {
					alpha: 0.7
				}, {
					duration: 200,
					onFinish: function onFinish() {
						hitArea.tint = hitArea.originalTint;
					}
				});
			}
			// Show score popup
			var popup = new ScorePopup();
			popup.init(scoreType, note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			holdActive = null;
		}
	}
	dragLane = null;
	dragNote = null;
	dragStartX = null;
	dragStartY = null;
	dragStartTime = null;
}
function handleGameMove(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	// For hold notes, check if finger is still on lane - must maintain contact until completion
	if (holdActive && dragNote) {
		var t = getCurrentTime();
		var note = holdActive.note;
		var holdEnd = note.time + note.duration;
		// Allow movement anywhere horizontally (any lane) for holds
		// Allow extremely wide vertical margin for forgiving detection
		var verticalLeniency = 950;
		if (Math.abs(y - HIT_LINE_Y) > verticalLeniency) {
			// Released too early or moved too far - fail the hold note
			note.hit = false;
			note.active = false;
			// Make failed hold note fade out with red tint
			tween(note, {
				alpha: 0.3,
				tint: 0xFF0000
			}, {
				duration: 400,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			combo = 0;
			missNotes += 1;
			// Show miss popup
			var popup = new ScorePopup();
			popup.init("miss", note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			holdActive = null;
			dragNote = null;
		} else if (t >= holdEnd) {
			// Successfully held until completion
			endHold();
		}
	}
}
function handleGameSwipe(x, y, obj) {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	if (dragStartX === null || dragStartY === null) {
		return;
	}
	var dx = x - dragStartX;
	var dy = y - dragStartY;
	var absDx = Math.abs(dx);
	var absDy = Math.abs(dy);
	// Enhanced sensitivity - reduced movement threshold and require minimum distance
	var minSwipeDistance = 10; // Lowered from 18 to 10 for even more responsive swipe detection
	var swipeDistance = Math.sqrt(dx * dx + dy * dy);
	if (swipeDistance < minSwipeDistance) {
		return;
	} // Not enough movement
	// Only allow left-to-right (right) swipes for swipe notes
	var dir = null;
	if (absDx > absDy && absDx > 20 && dx > 0) {
		// Only allow right swipes (left-to-right)
		dir = 'right';
	} else {
		return;
	}
	var lane = getLaneFromX(dragStartX);
	// Find the best matching swipe note that is near the hit area
	var bestNote = null;
	var bestScore = -1;
	for (var i = 0; i < currentNotes.length; ++i) {
		var note = currentNotes[i];
		if (!note.active || note.hit || note.type !== 'swipe') {
			continue;
		}
		// Only allow swipe detection when note is close to the hit area - more lenient threshold
		var noteY = getNoteY(note.time);
		var distanceToHitArea = Math.abs(noteY - HIT_LINE_Y);
		if (distanceToHitArea > 1200) {
			// Increased from 700 to 1200 for even better sensitivity
			continue; // Note is too far from hit area, skip
		}
		// Check if this swipe note matches the gesture direction
		if (note.swipeDir === dir) {
			var t = getCurrentTime();
			var dt = Math.abs(note.time - t);
			// Score based on timing and position accuracy
			var timingScore = Math.max(0, BAD_WINDOW - dt);
			var positionScore = Math.max(0, 280 - Math.abs(dragStartY - HIT_LINE_Y));
			var totalScore = timingScore + positionScore;
			if (totalScore > bestScore) {
				bestScore = totalScore;
				bestNote = note;
			}
		}
	}
	// Execute the best matching swipe note
	if (bestNote && judgeNote(bestNote, lane, dragStartY, 'swipe', dir)) {
		// Successfully completed swipe
	}
	dragStartX = null;
	dragStartY = null;
}
// --- PAUSE MENU OVERLAY ---
var pauseMenuOverlay = null;
var pauseMenuResumeBtn = null;
var pauseMenuRestartBtn = null;
var pauseMenuExitBtn = null;
var isPauseMenuActive = false;
function showPauseMenu() {
	if (isPauseMenuActive) return;
	isPauseMenuActive = true;
	isPlaying = false;
	LK.stopMusic();
	// Create overlay container
	pauseMenuOverlay = new Container();
	// Dimmed background
	var overlayBg = LK.getAsset('Menu', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 30,
		scaleY: 30
	});
	overlayBg.alpha = 0.7;
	pauseMenuOverlay.addChild(overlayBg);
	// Menu panel (increased size)
	var menuPanel = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 8.5,
		// increased width
		scaleY: 9.5 // increased height
	});
	pauseMenuOverlay.addChild(menuPanel);
	// Title
	var pauseTitle = new Text2(selectedLanguage === 'zh' ? "暂停" : "Paused", {
		size: 110,
		// larger title
		fill: "#fff"
	});
	pauseTitle.anchor.set(0.5, 0.5);
	pauseTitle.x = 0;
	pauseTitle.y = -420; // move up for larger panel
	pauseMenuOverlay.addChild(pauseTitle);
	// Resume button
	pauseMenuResumeBtn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: -180,
		// move up for larger panel
		scaleX: 4.5,
		scaleY: 2.2
	});
	pauseMenuResumeBtn.interactive = true;
	pauseMenuOverlay.addChild(pauseMenuResumeBtn);
	var resumeText = new Text2(selectedLanguage === 'zh' ? "继续" : "Resume", {
		size: 90,
		fill: "#fff"
	});
	resumeText.anchor.set(0.5, 0.5);
	resumeText.x = 0;
	resumeText.y = -180;
	pauseMenuOverlay.addChild(resumeText);
	// Restart button
	pauseMenuRestartBtn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 20,
		scaleX: 4.5,
		scaleY: 2.2
	});
	pauseMenuRestartBtn.interactive = true;
	pauseMenuOverlay.addChild(pauseMenuRestartBtn);
	var restartText = new Text2(selectedLanguage === 'zh' ? "重来" : "Restart", {
		size: 90,
		fill: "#fff"
	});
	restartText.anchor.set(0.5, 0.5);
	restartText.x = 0;
	restartText.y = 20;
	pauseMenuOverlay.addChild(restartText);
	// Exit button
	pauseMenuExitBtn = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 220,
		scaleX: 4.5,
		scaleY: 2.2
	});
	pauseMenuExitBtn.interactive = true;
	pauseMenuOverlay.addChild(pauseMenuExitBtn);
	var exitText = new Text2(selectedLanguage === 'zh' ? "退出" : "Exit", {
		size: 90,
		fill: "#fff"
	});
	exitText.anchor.set(0.5, 0.5);
	exitText.x = 0;
	exitText.y = 220;
	pauseMenuOverlay.addChild(exitText);
	// Start hidden and scale up with animation
	pauseMenuOverlay.alpha = 0;
	pauseMenuOverlay.scaleX = 0.7;
	pauseMenuOverlay.scaleY = 0.7;
	gui.center.addChild(pauseMenuOverlay);
	tween(pauseMenuOverlay, {
		alpha: 1,
		scaleX: 1,
		scaleY: 1
	}, {
		duration: 350,
		easing: tween.easeOut
	});
	// Button handlers: animate out, then call action
	pauseMenuResumeBtn.down = function () {
		LK.getSound('Touch').play();
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 250,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				hidePauseMenu();
				// Always re-add pause button after resuming
				addPauseButtonToScoreboard();
			}
		});
	};
	pauseMenuRestartBtn.down = function () {
		LK.getSound('Touch').play();
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 250,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				hidePauseMenu();
				// Stop and restart the music from the beginning
				LK.stopMusic();
				setGameState('PLAY');
				// Always re-add pause button after restarting
				addPauseButtonToScoreboard();
			}
		});
	};
	pauseMenuExitBtn.down = function () {
		LK.getSound('Touch').play();
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 250,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				hidePauseMenu();
				setGameState('HOME');
				// Always re-add pause button after exiting (if needed)
				addPauseButtonToScoreboard();
			}
		});
	};
}
function hidePauseMenu() {
	if (!isPauseMenuActive) return;
	isPauseMenuActive = false;
	isPlaying = true;
	// If overlay is still visible, animate out and remove after
	if (pauseMenuOverlay && pauseMenuOverlay.parent) {
		tween(pauseMenuOverlay, {
			alpha: 0,
			scaleX: 0.7,
			scaleY: 0.7
		}, {
			duration: 200,
			easing: tween.easeIn,
			onFinish: function onFinish() {
				if (pauseMenuOverlay && pauseMenuOverlay.parent) {
					pauseMenuOverlay.parent.removeChild(pauseMenuOverlay);
				}
				pauseMenuOverlay = null;
				pauseMenuResumeBtn = null;
				pauseMenuRestartBtn = null;
				pauseMenuExitBtn = null;
				// Always re-add pause button after closing pause menu
				addPauseButtonToScoreboard();
			}
		});
	} else {
		pauseMenuOverlay = null;
		pauseMenuResumeBtn = null;
		pauseMenuRestartBtn = null;
		pauseMenuExitBtn = null;
		// Always re-add pause button after closing pause menu
		addPauseButtonToScoreboard();
	}
}
// Add pause button to gameplay scoreboard
function addPauseButtonToScoreboard() {
	// Only add if in PLAY state
	if (GAME_STATE !== 'PLAY') return;
	// Remove any existing pause button to prevent duplicates
	if (gui.topRight && gui.topRight.pauseBtn) {
		if (gui.topRight.pauseBtn.parent) {
			gui.topRight.pauseBtn.parent.removeChild(gui.topRight.pauseBtn);
		}
		gui.topRight.pauseBtn = null;
	}
	// Use Stopsymbol asset for pause button
	if (gui.topRight) {
		var pauseBtn = LK.getAsset('Stopsymbol', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: -60,
			y: 60,
			scaleX: 2.2,
			scaleY: 2.2
		});
		pauseBtn.interactive = true;
		pauseBtn.down = function () {
			LK.getSound('Touch').play();
			showPauseMenu();
		};
		gui.topRight.addChild(pauseBtn);
		gui.topRight.pauseBtn = pauseBtn;
	}
}
// Remove pause button from scoreboard
function removePauseButtonFromScoreboard() {
	if (gui.topRight && gui.topRight.pauseBtn) {
		if (gui.topRight.pauseBtn.parent) {
			gui.topRight.pauseBtn.parent.removeChild(gui.topRight.pauseBtn);
		}
		gui.topRight.pauseBtn = null;
	}
}
// Attach input handlers
game.down = function (x, y, obj) {
	// If pause menu is active, block gameplay input
	if (isPauseMenuActive) return;
	handleGameDown(x, y, obj);
};
game.up = function (x, y, obj) {
	if (isPauseMenuActive) return;
	handleGameUp(x, y, obj);
};
game.move = function (x, y, obj) {
	if (isPauseMenuActive) return;
	handleGameMove(x, y, obj);
	// Detect swipe with improved sensitivity - reduced movement threshold
	if (dragStartX !== null && dragStartY !== null) {
		var dx = x - dragStartX;
		var dy = y - dragStartY;
		// Improved sensitivity - reduced movement threshold for better swipe detection
		if (Math.abs(dx) > 30 || Math.abs(dy) > 30) {
			handleGameSwipe(x, y, obj);
		}
	}
};
// Add pause button when entering gameplay, remove when leaving
var _originalSetGameState = setGameState;
setGameState = function setGameState(state) {
	_originalSetGameState(state);
	if (state === 'PLAY') {
		// Do not remove Stopsymbol pause button in PLAY mode
		addPauseButtonToScoreboard();
	} else {
		removePauseButtonFromScoreboard();
		hidePauseMenu();
	}
};
// --- GAME UPDATE LOOP ---
game.update = function () {
	if (GAME_STATE !== 'PLAY' || !isPlaying) {
		return;
	}
	var t = getCurrentTime();
	// Update notes
	for (var i = 0; i < currentNotes.length; ++i) {
		var note = currentNotes[i];
		if (!note.active) {
			continue;
		}
		// Always update note position every frame to prevent stuck notes
		if (note.time > 0) {
			var laneSpacing = 2048 / LANE_COUNT;
			note.x = (note.lane + 0.5) * laneSpacing; // Center in the lane
			// Always update Y, even if out of visible range, to ensure flow
			note.y = getNoteY(note.time);
		}
		// Only mark notes as missed if they have passed VERY far beyond the hit area to prevent automatic disappearing
		var timePassed = t - note.time;
		if (!note.hit && note.y > HIT_LINE_Y + 1000 && timePassed > BAD_WINDOW + 1000 && note.type !== 'hold') {
			note.active = false;
			// Do not set note.hit = true here, so that removal logic is consistent
			// Immediately remove from currentNotes array to prevent flow issues
			var missedIndex = currentNotes.indexOf(note);
			if (missedIndex > -1) {
				currentNotes.splice(missedIndex, 1);
				// Do not decrement i here, as we always iterate forward and skip removed notes
			}
			// Make missed note fade out and disappear completely
			tween(note, {
				alpha: 0,
				scaleX: 0.5,
				scaleY: 0.5
			}, {
				duration: 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			combo = 0;
			missNotes += 1;
			// Show miss popup
			var popup = new ScorePopup();
			popup.init("miss", note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			continue; // Skip to next note since this one was processed
		}
		// For hold notes, only mark as missed if they are VERY far past the hit area to prevent automatic disappearing
		var holdTimePassed = t - note.time;
		if (note.type === 'hold' && !note.hit && note.y > HIT_LINE_Y + 1200 && holdActive === null && holdTimePassed > BAD_WINDOW + 1500) {
			note.active = false;
			// Do not set note.hit = true here, so that removal logic is consistent
			// Immediately remove from currentNotes array
			var holdMissedIndex = currentNotes.indexOf(note);
			if (holdMissedIndex > -1) {
				currentNotes.splice(holdMissedIndex, 1);
				// Do not decrement i here, as we always iterate forward and skip removed notes
			}
			// Make missed hold note fade out with red tint
			tween(note, {
				alpha: 0,
				scaleX: 0.5,
				scaleY: 0.5
			}, {
				duration: 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					if (note.parent) {
						note.parent.removeChild(note);
					}
				}
			});
			combo = 0;
			missNotes += 1;
			// Show miss popup
			var popup = new ScorePopup();
			popup.init("miss", note.x, note.y);
			game.addChild(popup);
			scorePopups.push(popup);
			continue; // Skip to next note since this one was processed
		}
	}
	// Remove notes that are far below screen or completed
	for (var i = currentNotes.length - 1; i >= 0; --i) {
		var note = currentNotes[i];
		// Only remove if not already removed in this frame
		if ((note.y > 3000 || !note.active && note.y > HIT_LINE_Y + 400 || note.hit) && note.parent) {
			note.parent.removeChild(note);
			currentNotes.splice(i, 1);
		}
	}
	// Update GUI
	scoreText.setText("Score: " + score);
	comboText.setText("Combo: " + combo);
	var acc = totalNotes > 0 ? Math.floor(100 * hitNotes / totalNotes) : 100;
	accuracyText.setText("Accuracy: " + acc + "%");
	// End of song - check if song duration has been reached since music only plays once
	if (t > getSongEndTime() && isPlaying) {
		isPlaying = false;
		// If Hard mode, show result immediately after music ends (no tempo flow wait)
		if (selectedDifficulty === 'Hard') {
			setGameState('RESULT');
		} else {
			// Music will stop automatically since it only plays once
			resultTimeout = LK.setTimeout(function () {
				setGameState('RESULT');
			}, 1200);
		}
	}
};
function getSongEndTime() {
	// Use the full song duration from the song data
	if (selectedSong && selectedSong.duration) {
		return selectedSong.duration;
	}
	// Fallback to last note timing if duration not specified
	var last = currentSongData[currentSongData.length - 1];
	if (!last) {
		return 0;
	}
	if (last[0] === 'hold') {
		return last[2] + last[3] + 2000; // Add extra time for hold completion
	}
	return last[2] + 3000; // Allow full song to play with extra buffer
}
// Save high score for current song and difficulty
function saveHighScore(songId, difficulty, newScore) {
	// Create storage key for this song-difficulty combination
	var storageKey = songId + '_' + difficulty;
	// Get current high score for this song and difficulty
	var currentHighScore = storage[storageKey] || 0;
	// Save new high score if it's better
	if (newScore > currentHighScore) {
		storage[storageKey] = newScore;
		return true; // New high score achieved
	}
	return false; // No new high score
}
// Get high score for a song and difficulty
function getHighScore(songId, difficulty) {
	// Create storage key for this song-difficulty combination
	var storageKey = songId + '_' + difficulty;
	return storage[storageKey] || 0;
}
// Utility function to remove all gameplay backgrounds from the game scene
function removeGameplayBackgrounds() {
	if (gameplayBackground && gameplayBackground.parent) {
		gameplayBackground.parent.removeChild(gameplayBackground);
		gameplayBackground = null;
	}
}
// --- RESULT SCREEN ---
function showResult() {
	removeGameplayBackgrounds();
	clearGameObjects();
	// Remove Stopsymbol pause button only on result screen
	removePauseButtonFromScoreboard();
	// Remove gameplay backgrounds again to ensure they do not appear on result screen
	removeGameplayBackgrounds();
	// Remove gameplay backgrounds immediately after scoreboard appears (triple call to guarantee removal in all result transitions)
	removeGameplayBackgrounds();
	var acc = totalNotes > 0 ? Math.floor(100 * hitNotes / totalNotes) : 100;
	var screenWidth = LK.width || 2048;
	var screenHeight = LK.height || 2732;
	// Save high score
	var isNewHighScore = saveHighScore(selectedSong.id, selectedDifficulty, score);
	var currentHighScore = getHighScore(selectedSong.id, selectedDifficulty);
	// Create SongStorybox container for results
	var resultBox = new Container();
	// Add SongStorybox asset as background, centered and scaled
	var boxAsset = LK.getAsset('SongStorybox', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: 0,
		scaleX: 13,
		scaleY: 11
	});
	resultBox.addChild(boxAsset);
	// Calculate box bounds for layout
	var boxW = boxAsset.width * boxAsset.scaleX;
	var boxH = boxAsset.height * boxAsset.scaleY;
	// Add song cover on the left side
	var coverAsset = LK.getAsset(selectedSong.cover, {
		anchorX: 0.5,
		anchorY: 0.5,
		x: -boxW * 0.25,
		y: -boxH * 0.15,
		scaleX: 1.2,
		scaleY: 1.2
	});
	resultBox.addChild(coverAsset);
	// Main result text on the right side
	var resultStr = (selectedLanguage === 'zh' ? "结果" : "Result") + "\n\n";
	resultStr += (selectedLanguage === 'zh' ? "总分: " : "Total Score: ") + score + "\n";
	// Add high score information
	if (isNewHighScore) {
		resultStr += (selectedLanguage === 'zh' ? "🏆 新纪录!" : "🏆 New High Score!") + "\n";
	}
	resultStr += (selectedLanguage === 'zh' ? "最高分: " : "High Score: ") + currentHighScore + "\n";
	resultStr += (selectedLanguage === 'zh' ? "最大连击: " : "Max Combo: ") + maxCombo + "\n";
	resultStr += (selectedLanguage === 'zh' ? "准确率: " : "Accuracy: ") + acc + "%\n\n";
	// Calculate grade based on accuracy
	var grade = "F";
	if (acc >= 95) grade = "S";else if (acc >= 90) grade = "A";else if (acc >= 80) grade = "B";else if (acc >= 70) grade = "C";else if (acc >= 60) grade = "D";
	resultStr += (selectedLanguage === 'zh' ? "等级: " : "Grade: ") + grade + "\n\n";
	// Detailed scoring breakdown
	resultStr += (selectedLanguage === 'zh' ? "详细统计:" : "Stats:") + "\n";
	resultStr += (selectedLanguage === 'zh' ? "完美: " : "Perfect: ") + perfectNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "优秀: " : "Great: ") + greatNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "良好: " : "Good: ") + goodNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "一般: " : "Bad: ") + badNotes + "\n";
	resultStr += (selectedLanguage === 'zh' ? "未命中: " : "Miss: ") + missNotes;
	var resultFontColor = "#fff";
	resultText = new Text2(resultStr, {
		size: 50,
		fill: resultFontColor,
		align: "left",
		wordWrap: true,
		wordWrapWidth: boxW * 0.4
	});
	resultText.anchor.set(0, 0.5);
	resultText.x = boxW * 0.05;
	resultText.y = -boxH * 0.1;
	resultBox.addChild(resultText);
	// Exit button to return to song selection
	var exitButton = LK.getAsset('SelectorUI', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: boxH * 0.35,
		scaleX: 3.0,
		scaleY: 1.5
	});
	exitButton.interactive = true;
	exitButton.down = function (x, y, obj) {
		LK.getSound('Touch').play();
		removeGameplayBackgrounds();
		setGameState('HOME');
	};
	resultBox.addChild(exitButton);
	// Exit button text
	var exitText = new Text2(selectedLanguage === 'zh' ? "退出" : "Exit", {
		size: 70,
		fill: "#fff"
	});
	exitText.anchor.set(0.5, 0.5);
	exitText.x = 0;
	exitText.y = boxH * 0.35;
	resultBox.addChild(exitText);
	// Add result box to center of screen
	gui.center.addChild(resultBox);
}
// --- START GAME ---
// Start at language selector (opening screen)
setGameState('LANGUAGE');
:quality(85)/https://cdn.frvr.ai/68415b4cca168837478c58a1.png%3F3) 
 Game design for Kaleidoscope of Music Rhythm without game title, just the design for opening screen background. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/68415de3ca168837478c58b8.png%3F3) 
 Cyberpunk style design empty selector UI. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6841b6f4312143797ebc0f32.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6841b788312143797ebc0f3a.png%3F3) 
 Cyberpunk style hold note for music rhythm game. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6841b8ca312143797ebc0f4d.png%3F3) 
 Cyberpunk style swipe note for music rhythm game. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6841b90a312143797ebc0f56.png%3F3) 
 Cyberpunk style tap note for music rhythm game. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6841bf0dbbbdba8aed376689.png%3F3) 
 Cyberpunk style kaleidoscope pattern design for homepage. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6841c3c3bbbdba8aed3766b0.png%3F3) 
 Cyberpunk style lane divider for music rhythm game that is horizontal and used for judging the touching of rhythm In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6842bdb5dcea7f5eba9f76e8.png%3F3) 
 Empty cyberpunk style storybox design in which it's size enable song cover, difficulty level and song description to be added in the storybox. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6842be90dcea7f5eba9f76f9.png%3F3) 
 Round shape of song cover of anime style with Sky Journey theme. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6842c199dcea7f5eba9f7711.png%3F3) 
 Round shape song cover of cyberpunk anime style with the themed “Night Pulse”. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6842c205dcea7f5eba9f771d.png%3F3) 
 Round shape of anime style song cover with the themed Sunrise Waltz. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6842ea16871954dfb8061d65.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6842ea9b871954dfb8061d67.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6842eaaf871954dfb8061d69.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6844fc3899870c5d0f1c3fea.png%3F3) 
 Anime style design for the round shape song cover of New Era Malay Style
:quality(85)/https://cdn.frvr.ai/6844fd5899870c5d0f1c4000.png%3F3) 
 Round shape, Replace Chinese word from “不知途”to“莫问前程”,others remaining the same
:quality(85)/https://cdn.frvr.ai/6845114175a2ad68c6ca85d5.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/68451241935510c7f8bf1d71.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6847ad24106ea20a1c3c2b7e.png%3F3) 
 Empty cyberpunk style menu design. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6847b6ebb05ffc4b0739bfd1.png%3F3) 
 Cyberpunk style pause symbol. In-Game asset. 2d. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/6848d9ee106ea20a1c3c30e5.png%3F3) 
 Cyberpunk style return symbol. In-Game asset. 2d. High contrast. No shadows