User prompt
start ve endless assetlerini yeniden tanımla
User prompt
endless ve start buttonlarının asset idlerini güncelle
Code edit (1 edits merged)
Please save this source code
User prompt
I would love that the sound playing when you tap would blend better into the music. Like dance music and the sound is a vynil spin or something like that.
User prompt
devamiicin screeninde 3snden sonra ekrana basıldığında ana menüye atsın.
User prompt
Fix pause button functionality on game screen by properly implementing the click handler
User prompt
chapter 2 enddeki pause butona tıklandığında pausebg asseti, restart butonu ve main menü butonu gelsin
User prompt
chapter 2 enddeki pause button çalışmıyor
User prompt
ready info assetini 25px yukarı çıkar
User prompt
blurred assetini 50px yukarı çıkar
User prompt
devamiicin assetinin göründüğü ekranda pause buttonu da olsun
User prompt
tutorial bpm display text force 60 BPM
User prompt
song name text 8pxupward
User prompt
song name text 15px upward
User prompt
oyundaki bpm value ve şarkı ismi yazılarını 30px aşağı indir
User prompt
oyundaki bpm value ve şarkı ismi yazılarını 75px yukarı çıkar
User prompt
gameover assetini yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
oyundaki tüm metinleri tekrardan yüksek çözünürlükte görünecek şekilde düzenle. boyutları sabit kalsın
User prompt
creditstab yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
supporttab yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
0.3 yap
User prompt
settings menüsündeki settings assetini yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
0.18 yap
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { bpm: 100, soundVolume: 100, selectedKickSound: "kick", selectedHihatSound: "hihat", selectedSnareClapSound: "snareclap", selectedPercussionSound: "percussion", tutorialCompleted: false, currentLevel: 1 }); /**** * Classes ****/ // default bpm 100 // Beat marker class (moving dot) var BeatMarker = Container.expand(function () { var self = Container.call(this); var marker = self.attachAsset('beatMarker', { anchorX: 0.5, anchorY: 0.5 }); self.active = true; // Whether this beat is still hittable self.hit = false; // Whether this beat was hit // For feedback animation self.showFeedback = function (type) { var feedbackId = type === 'good' ? 'goodCircle' : 'missCircle'; var feedback = LK.getAsset(feedbackId, { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, alpha: 0.7, scaleX: 1, scaleY: 1 }); game.addChild(feedback); tween(feedback, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { feedback.destroy(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // black background }); /**** * Game Code ****/ // --- Game constants --- // Bass drum (kick) hit circle // Bass drum (kick) hit circle when active // Timeline bar // Beat marker // Miss feedback // Good feedback // Sound for kick // Music (looping, short demo) // --- BPM-specific music assets (kickless versions) --- var TIMELINE_WIDTH = 2048; var TIMELINE_HEIGHT = 25; var TIMELINE_Y = 2000; // Y position of timeline bar var TIMELINE_X = 0; var HIT_ZONE_X = 2048 / 2; // Center of screen var HIT_ZONE_Y = TIMELINE_Y + TIMELINE_HEIGHT / 2; var HIT_WINDOW = 120; // px distance for perfect hit // Beat speed remains constant - only spacing between beats changes based on BPM function getBeatSpeed() { return 400; // Constant speed of 400 px/s regardless of BPM } var INITIAL_BPM = 100; // Starting BPM var BEATS_PER_MEASURE = 4; var BEAT_INTERVAL = 60 / INITIAL_BPM; // seconds per beat var DIFFICULTY_STEP = 0.15; // How much to increase BPM per level var MAX_MISSES = 5; // --- Game state --- var beats = []; // Active BeatMarker objects var beatPattern = []; // Array of {time, type} var nextBeatIdx = 0; // Next beat to spawn var songTime = 0; // Elapsed time in seconds var lastTickTime = Date.now(); var score = 0; var misses = 0; var ignoredMisses = 0; // Track first 3 misses to ignore var combo = 0; var tutorialCompleted = false; var currentLevel = 1; // Reset storage values to ensure game always starts from beginning on reload storage.tutorialCompleted = false; storage.currentLevel = 1; var isTutorial = false; var bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM; var soundVolume = typeof storage.soundVolume === "number" ? storage.soundVolume : 100; var selectedKickSound = typeof storage.selectedKickSound === "string" ? storage.selectedKickSound : 'kick'; var selectedHihatSound = typeof storage.selectedHihatSound === "string" ? storage.selectedHihatSound : 'hihat'; var selectedSnareClapSound = typeof storage.selectedSnareClapSound === "string" ? storage.selectedSnareClapSound : 'snareclap'; var selectedPercussionSound = typeof storage.selectedPercussionSound === "string" ? storage.selectedPercussionSound : 'percussion'; // Function to update all sound volumes based on soundVolume setting function updateAllSoundVolumes() { var volumeMultiplier = soundVolume / 100; // Update kick sound volume var kickSound = LK.getSound(selectedKickSound); if (kickSound && kickSound.setVolume) { kickSound.setVolume(1 * volumeMultiplier); } // Update hihat sound volume var hihatSound = LK.getSound(selectedHihatSound); if (hihatSound && hihatSound.setVolume) { hihatSound.setVolume(0.5 * volumeMultiplier); } // Update snare/clap sound volume var snareClapSound = LK.getSound(selectedSnareClapSound); if (snareClapSound && snareClapSound.setVolume) { snareClapSound.setVolume(0.6 * volumeMultiplier); } // Update percussion sound volume var percussionSound = LK.getSound(selectedPercussionSound); if (percussionSound && percussionSound.setVolume) { percussionSound.setVolume(0.6 * volumeMultiplier); } // Update miss sound volume var missSound = LK.getSound('miss'); if (missSound && missSound.setVolume) { missSound.setVolume(0.6 * volumeMultiplier); } } var beatInterval = 60 / bpm; var gameActive = true; var feedbackTimeout = null; // --- UI elements --- // --- Heart/Life UI --- // (Initialization moved after scoreTxt is defined) var MAX_LIVES = 3; var lives = MAX_LIVES; var heartIcons = []; var heartSpacing = 40; var heartshields = 3; // Number of heartshields remaining // Heart/life UI will be initialized after scoreTxt is defined // --- Gameplay Screen Container --- var gamescreen = new Container(); gamescreen.visible = false; game.addChild(gamescreen); // Add isyeri background image, anchored top-left, covering the whole game area var isyeriBg = LK.getAsset('isyeri', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); gamescreen.addChild(isyeriBg); var timelineBar = LK.getAsset('timelineBar', { anchorX: 0, anchorY: 0.5, x: TIMELINE_X, y: TIMELINE_Y + TIMELINE_HEIGHT / 2 }); gamescreen.addChild(timelineBar); // Hit zone (kick target) var kickTarget = LK.getAsset('kickTarget', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X, y: HIT_ZONE_Y }); gamescreen.addChild(kickTarget); // Score text - pixelated effect using small size and scale var scoreTxt = new Text2('0', { size: 60, fill: 0xFFFFFF }); scoreTxt.scale.set(2, 2); // Scale up 2x to create more pixelated effect scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Combo text var comboTxt = new Text2('', { size: 36, fill: 0x00FF00 }); comboTxt.scale.set(2, 2); comboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(comboTxt); comboTxt.y = 120; // BPM display text - 135px below combo text (moved 30px down) var bpmDisplayTxt = new Text2(bpm + ' ' + 'BPM', { size: 36, fill: 0xef69b9 }); bpmDisplayTxt.scale.set(2, 2); bpmDisplayTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bpmDisplayTxt); bpmDisplayTxt.y = comboTxt.y + 135; // Song name display text - below BPM display var songNameTxt = new Text2('', { size: 36, fill: 0xffffff }); songNameTxt.scale.set(2, 2); songNameTxt.anchor.set(0.5, 0); LK.gui.top.addChild(songNameTxt); songNameTxt.y = bpmDisplayTxt.y + 82; // Pause button in top right corner (avoiding top-left platform menu) var pauseBtn = LK.getAsset('pause', { anchorX: 1, anchorY: 0, x: 2048 - 50, y: 220, scaleX: 1.6, scaleY: 1.6 }); gamescreen.addChild(pauseBtn); // No miss text, only hearts for lives display // --- Heart/Life UI --- (moved here to ensure scoreTxt is defined) // Calculate heartY based on scoreTxt position and height var heartY = scoreTxt.y + 50; // Score text'in 50px altında var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; // Sağdan 100px boşluk bırak // Remove any existing heart icons from their parents before recreating for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; for (var i = 0; i < MAX_LIVES; i++) { var heartshield = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartshield); heartIcons.push(heartshield); } // --- Beat pattern generation --- function generateBeatPattern(bpm, measures) { // Simple: 4/4, kick on every beat, can add more complex patterns later var pattern = []; var interval = 60 / bpm; // seconds per beat based on current BPM var totalBeats = 100000; // Target 100,000 total beats for (var beatIndex = 0; beatIndex < totalBeats; beatIndex++) { pattern.push({ time: beatIndex * interval, // Each beat spaced by BPM interval type: 'kick' }); } return pattern; } // --- Start game --- function startGame() { // Reset state for (var i = 0; i < beats.length; i++) { beats[i].destroy(); } beats = []; // Check if this is tutorial mode (chapter 1) if (!tutorialCompleted && currentLevel === 1) { isTutorial = true; bpm = 60; // Tutorial always starts at 60 BPM } else { isTutorial = false; // Use saved BPM from storage for regular play bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM; } // Always use current bpm and calculate beatInterval accordingly beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); // Generate 100,000 beats nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); score = 0; misses = 0; ignoredMisses = 0; // Reset ignored misses counter combo = 0; perfectCombo = 0; // Reset perfect combo lives = MAX_LIVES; heartshields = 3; // Reset heartshields lastBpmUpgradeScore = 0; // Reset BPM upgrade tracking // Remove any beat that is at the kick target at game start (defensive, in case of bug) // Also, prevent initial miss by ensuring no beat is at or past the hit zone at game start for (var i = beats.length - 1; i >= 0; i--) { if (typeof beats[i].x !== "undefined" && Math.abs(beats[i].x - HIT_ZONE_X) < 2 || typeof beats[i].x !== "undefined" && beats[i].x >= HIT_ZONE_X - HIT_WINDOW) { beats[i].destroy(); beats.splice(i, 1); } } // Defensive: Also ensure no beat is created at the kick target at game start if (beats.length > 0 && typeof beats[0].x !== "undefined" && Math.abs(beats[0].x - HIT_ZONE_X) < 2) { beats[0].destroy(); beats.splice(0, 1); } // Reset heart icons to full heartshields // Recalculate miss text width and heart positions on restart var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = scoreTxt.y + 50; // Remove all existing heart icons for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } // Recreate all heart icons as full heartshields heartIcons = []; heartshields = MAX_LIVES; // Reset heartshields counter first lives = MAX_LIVES; // Reset lives counter to ensure proper state for (var i = 0; i < MAX_LIVES; i++) { var heartAsset = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartAsset); heartIcons.push(heartAsset); } gameActive = true; gamePaused = false; scoreTxt.setText('0'); comboTxt.setText(''); // missTxt removed, only hearts for lives kickTarget.visible = true; LK.setScore(0); // Reapply glow effects after game restart applyGlowToVioletElements(); // Apply current sound volume settings updateAllSoundVolumes(); // Set timeout to remove heartshields after 3 seconds tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameActive && heartshields > 0) { // Convert all remaining heartshields to regular hearts for (var i = 0; i < MAX_LIVES; i++) { if (heartIcons[i] && heartIcons[i].parent) { // Check if this is still a heartshield by checking if heartshields counter allows it var shieldIndex = 3 - heartshields; if (i >= shieldIndex) { // This is a heartshield, convert it to heart var parent = heartIcons[i].parent; parent.removeChild(heartIcons[i]); heartIcons[i] = LK.getAsset('heart', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[i]); } } } // Reset heartshields counter to 0 heartshields = 0; } } }); // --- Play correct BPM music (kickless) --- var musicId = '100BPMSynthWave'; // Default fallback if (bpm === 60) { musicId = '60BPMLowKey'; } else if (bpm === 40) { musicId = '40BPMDeepVibes'; } else if (bpm === 45) { musicId = '45BPMChillWave'; } else if (bpm === 50) { musicId = '50BPMSlowMotion'; } else if (bpm === 55) { musicId = '55BPMDreamscape'; } else if (bpm === 60) { musicId = '60BPMLowKey'; } else if (bpm === 65) { musicId = '65BPMRelaxed'; } else if (bpm === 70) { musicId = '70BPMCalmVibes'; } else if (bpm === 75) { musicId = '75BPMElectricDreams'; } else if (bpm === 80) { musicId = '80BPMNeonNights'; } else if (bpm === 85) { musicId = '85BPMCyberFlow'; } else if (bpm === 90) { musicId = '90BPMDigitalPulse'; } else if (bpm === 95) { musicId = '95BPMFutureBass'; } else if (bpm === 100) { musicId = '100BPMSynthWave'; } else if (bpm === 105) { musicId = '105BPMHyperDrive'; } else if (bpm === 110) { musicId = '110BPMTechnoRush'; } else if (bpm === 115) { musicId = '115BPMHighEnergy'; } else if (bpm === 120) { musicId = '120BPMMaximum'; } var volumeMultiplier = soundVolume / 100; var musicVolume = isTutorial ? 0.5 : 1; // Tutorial uses 0.5 volume, regular play uses 1 LK.playMusic(musicId, { volume: musicVolume * volumeMultiplier, loop: true }); // Update song name display if (songNameTxt) { // Remove BPM number and "BPM" text from the beginning var songName = musicId.replace(/^\d+BPM/, ''); songNameTxt.setText(songName); } } // --- Main Menu Implementation --- // Add main menu background image, anchored top-left, covering the whole game area var mainMenuContainer = new Container(); mainMenuContainer.visible = true; var mainMenuBg = LK.getAsset('mainmanubg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); mainMenuContainer.addChild(mainMenuBg); // Title - DJ Asset var djAsset = LK.getAsset('DJ', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 315, scaleX: 0.2, scaleY: 0.2 }); mainMenuContainer.addChild(djAsset); // Calculate button positions with 5px spacing var buttonStartY = 1000; var buttonHeight = 289; // Approximate height with scale 5 var buttonSpacing = 5; // Start Button var startBtn = LK.getAsset('startbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY, scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(startBtn); // Settings Button var settingsBtn = LK.getAsset('settingsbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + buttonHeight + buttonSpacing, scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(settingsBtn); // Credits Button var creditsBtn = LK.getAsset('creditsbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + 2 * (buttonHeight + buttonSpacing), scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(creditsBtn); // Support Us Button var supportBtn = LK.getAsset('supportbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + 3 * (buttonHeight + buttonSpacing), scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(supportBtn); // Overlay for credits/settings/support var settingsmenu = new Container(); settingsmenu.visible = false; game.addChild(settingsmenu); var creditsmenu = new Container(); creditsmenu.visible = false; game.addChild(creditsmenu); var supportmenu = new Container(); supportmenu.visible = false; game.addChild(supportmenu); // Helper to hide all overlays function hideAllMenus() { settingsmenu.visible = false; creditsmenu.visible = false; supportmenu.visible = false; } // Show credits menu function showCreditsMenu() { hideAllMenus(); creditsmenu.removeChildren(); var bg = LK.getAsset('creditsbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); creditsmenu.addChild(bg); var creditsTab = LK.getAsset('creditstab', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 0.5, scaleY: 0.5 }); creditsmenu.addChild(creditsTab); // Close button (bottom right) var closeBtn = LK.getAsset('closebutton', { anchorX: 1, anchorY: 1, x: 2048 - 60, y: 2732 - 60, scaleX: 3, scaleY: 3 }); creditsmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(closeBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(closeBtn, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } }); creditsmenu.visible = false; mainMenuContainer.visible = true; }; creditsmenu.visible = true; // Remove tap-to-close on background creditsmenu.down = undefined; } // Show support menu function showSupportMenu() { hideAllMenus(); supportmenu.removeChildren(); var bg = LK.getAsset('supportbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); supportmenu.addChild(bg); var supportTab = LK.getAsset('supporttab', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 0.5, scaleY: 0.5 }); supportmenu.addChild(supportTab); // Close button (bottom right) var closeBtn = LK.getAsset('closebutton', { anchorX: 1, anchorY: 1, x: 2048 - 60, y: 2732 - 60, scaleX: 3, scaleY: 3 }); supportmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(closeBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(closeBtn, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } }); supportmenu.visible = false; mainMenuContainer.visible = true; }; supportmenu.visible = true; // Remove tap-to-close on background supportmenu.down = undefined; // Play thanks sound after 2 seconds LK.setTimeout(function () { LK.getSound('thanks').play(); }, 2000); } // Show settings menu function showSettingsMenu() { hideAllMenus(); settingsmenu.removeChildren(); var bg = LK.getAsset('settingsbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); settingsmenu.addChild(bg); var settingsAsset = LK.getAsset('settingsbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 700, scaleX: 0.3, scaleY: 0.3 }); settingsmenu.addChild(settingsAsset); // --- BPM CIRCLE UI --- // Container for the whole BPM control var bpmCircleContainer = new Container(); bpmCircleContainer.x = 2048 / 2 - 300; bpmCircleContainer.y = 1100; settingsmenu.addChild(bpmCircleContainer); // --- KICK SOUND SELECTION UI --- // Container for kick sound selection var kickSoundContainer = new Container(); kickSoundContainer.x = 2048 / 2; kickSoundContainer.y = 1400; settingsmenu.addChild(kickSoundContainer); // Kick sound label var kickSoundLabelTxt = new Text2("KICK SOUND", { size: 24, fill: 0xffffff }); kickSoundLabelTxt.scale.set(2, 2); kickSoundLabelTxt.anchor.set(0.5, 0.5); kickSoundLabelTxt.x = 0; kickSoundLabelTxt.y = -120; kickSoundContainer.addChild(kickSoundLabelTxt); // Add settingsbar background behind kick buttons var kickSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); kickSoundContainer.addChild(kickSettingsBar); // Create kick sound buttons var kickSounds = ['kick', 'kick2', 'kick3', 'kick4']; var kickButtons = []; var buttonSpacing = 220; var startX = -330; for (var i = 0; i < kickSounds.length; i++) { var kickButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: startX + i * buttonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedKickSound === kickSounds[i] ? 1 : 0.5 }); kickSoundContainer.addChild(kickButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = startX + i * buttonSpacing; numberTxt.y = 0; kickSoundContainer.addChild(numberTxt); kickButton.kickSoundId = kickSounds[i]; kickButton.buttonIndex = i; kickButtons.push(kickButton); // Button click handler kickButton.down = function (x, y, obj) { // If this is already selected kick sound and any other sound is selected, deselect all kick sounds if (selectedKickSound === this.kickSoundId && (selectedHihatSound !== '' || selectedSnareClapSound !== '' || selectedPercussionSound !== '')) { // Play the selected kick sound LK.getSound(this.kickSoundId).play(); // Deselect all kick sounds selectedKickSound = ''; storage.selectedKickSound = selectedKickSound; // Update button appearances - all kick buttons become inactive for (var j = 0; j < kickButtons.length; j++) { kickButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedKickSound === this.kickSoundId && selectedHihatSound === '' && selectedSnareClapSound === '' && selectedPercussionSound === '') { // Play the selected kick sound LK.getSound(this.kickSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected kick sound LK.getSound(this.kickSoundId).play(); // Update selected kick sound selectedKickSound = this.kickSoundId; storage.selectedKickSound = selectedKickSound; // Update button appearances for (var j = 0; j < kickButtons.length; j++) { kickButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(kickButton); } // --- HIHAT SOUND SELECTION UI --- // Container for hihat sound selection var hihatSoundContainer = new Container(); hihatSoundContainer.x = 2048 / 2; hihatSoundContainer.y = 1700; settingsmenu.addChild(hihatSoundContainer); // Hihat sound label var hihatSoundLabelTxt = new Text2("HI HATS", { size: 24, fill: 0xffffff }); hihatSoundLabelTxt.scale.set(2, 2); hihatSoundLabelTxt.anchor.set(0.5, 0.5); hihatSoundLabelTxt.x = 0; hihatSoundLabelTxt.y = -120; hihatSoundContainer.addChild(hihatSoundLabelTxt); // Add settingsbar background behind hihat buttons var hihatSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); hihatSoundContainer.addChild(hihatSettingsBar); // Create hihat sound buttons var hihatSounds = ['hihat', 'hihat2', 'hihat3', 'hihat4']; var hihatButtons = []; var hihatButtonSpacing = 220; var hihatStartX = -330; for (var i = 0; i < hihatSounds.length; i++) { var hihatButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: hihatStartX + i * hihatButtonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedHihatSound === hihatSounds[i] ? 1 : 0.5 }); hihatSoundContainer.addChild(hihatButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = hihatStartX + i * hihatButtonSpacing; numberTxt.y = 0; hihatSoundContainer.addChild(numberTxt); hihatButton.hihatSoundId = hihatSounds[i]; hihatButton.buttonIndex = i; hihatButtons.push(hihatButton); // Button click handler hihatButton.down = function (x, y, obj) { // If this is already selected hihat sound and any other sound is selected, deselect all hihat sounds if (selectedHihatSound === this.hihatSoundId && (selectedKickSound !== '' || selectedSnareClapSound !== '' || selectedPercussionSound !== '')) { // Play the selected hihat sound LK.getSound(this.hihatSoundId).play(); // Deselect all hihat sounds selectedHihatSound = ''; storage.selectedHihatSound = selectedHihatSound; // Update button appearances - all hihat buttons become inactive for (var j = 0; j < hihatButtons.length; j++) { hihatButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedHihatSound === this.hihatSoundId && selectedKickSound === '' && selectedSnareClapSound === '' && selectedPercussionSound === '') { // Play the selected hihat sound LK.getSound(this.hihatSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected hihat sound LK.getSound(this.hihatSoundId).play(); // Update selected hihat sound selectedHihatSound = this.hihatSoundId; storage.selectedHihatSound = selectedHihatSound; // Update button appearances for (var j = 0; j < hihatButtons.length; j++) { hihatButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(hihatButton); } // --- SNARE/CLAP SOUND SELECTION UI --- // Container for snare/clap sound selection var snareClapSoundContainer = new Container(); snareClapSoundContainer.x = 2048 / 2; snareClapSoundContainer.y = 2000; settingsmenu.addChild(snareClapSoundContainer); // Snare/clap sound label var snareClapSoundLabelTxt = new Text2("SNARE/CLAP", { size: 24, fill: 0xffffff }); snareClapSoundLabelTxt.scale.set(2, 2); snareClapSoundLabelTxt.anchor.set(0.5, 0.5); snareClapSoundLabelTxt.x = 0; snareClapSoundLabelTxt.y = -120; snareClapSoundContainer.addChild(snareClapSoundLabelTxt); // Add settingsbar background behind snare/clap buttons var snareClapSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); snareClapSoundContainer.addChild(snareClapSettingsBar); // Create snare/clap sound buttons var snareClapSounds = ['snareclap', 'snareclap2', 'snareclap3', 'snareclap4']; var snareClapButtons = []; var snareClapButtonSpacing = 220; var snareClapStartX = -330; for (var i = 0; i < snareClapSounds.length; i++) { var snareClapButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: snareClapStartX + i * snareClapButtonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedSnareClapSound === snareClapSounds[i] ? 1 : 0.5 }); snareClapSoundContainer.addChild(snareClapButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = snareClapStartX + i * snareClapButtonSpacing; numberTxt.y = 0; snareClapSoundContainer.addChild(numberTxt); snareClapButton.snareClapSoundId = snareClapSounds[i]; snareClapButton.buttonIndex = i; snareClapButtons.push(snareClapButton); // Button click handler snareClapButton.down = function (x, y, obj) { // If this is already selected snare/clap sound and any other sound is selected, deselect all snare/clap sounds if (selectedSnareClapSound === this.snareClapSoundId && (selectedKickSound !== '' || selectedHihatSound !== '' || selectedPercussionSound !== '')) { // Play the selected snare/clap sound LK.getSound(this.snareClapSoundId).play(); // Deselect all snare/clap sounds selectedSnareClapSound = ''; storage.selectedSnareClapSound = selectedSnareClapSound; // Update button appearances - all snare/clap buttons become inactive for (var j = 0; j < snareClapButtons.length; j++) { snareClapButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedSnareClapSound === this.snareClapSoundId && selectedKickSound === '' && selectedHihatSound === '' && selectedPercussionSound === '') { // Play the selected snare/clap sound LK.getSound(this.snareClapSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected snare/clap sound LK.getSound(this.snareClapSoundId).play(); // Update selected snare/clap sound selectedSnareClapSound = this.snareClapSoundId; storage.selectedSnareClapSound = selectedSnareClapSound; // Update button appearances for (var j = 0; j < snareClapButtons.length; j++) { snareClapButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(snareClapButton); } // --- PERCUSSION SOUND SELECTION UI --- // Container for percussion sound selection var percussionSoundContainer = new Container(); percussionSoundContainer.x = 2048 / 2; percussionSoundContainer.y = 2300; settingsmenu.addChild(percussionSoundContainer); // Percussion sound label var percussionSoundLabelTxt = new Text2("PERCUSSION", { size: 24, fill: 0xffffff }); percussionSoundLabelTxt.scale.set(2, 2); percussionSoundLabelTxt.anchor.set(0.5, 0.5); percussionSoundLabelTxt.x = 0; percussionSoundLabelTxt.y = -120; percussionSoundContainer.addChild(percussionSoundLabelTxt); // Add settingsbar background behind percussion buttons var percussionSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); percussionSoundContainer.addChild(percussionSettingsBar); // Create percussion sound buttons var percussionSounds = ['percussion', 'percussion2', 'percussion3', 'percussion4']; var percussionButtons = []; var percussionButtonSpacing = 220; var percussionStartX = -330; for (var i = 0; i < percussionSounds.length; i++) { var percussionButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: percussionStartX + i * percussionButtonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedPercussionSound === percussionSounds[i] ? 1 : 0.5 }); percussionSoundContainer.addChild(percussionButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = percussionStartX + i * percussionButtonSpacing; numberTxt.y = 0; percussionSoundContainer.addChild(numberTxt); percussionButton.percussionSoundId = percussionSounds[i]; percussionButton.buttonIndex = i; percussionButtons.push(percussionButton); // Button click handler percussionButton.down = function (x, y, obj) { // If this is already selected percussion sound and any other sound is selected, deselect all percussion sounds if (selectedPercussionSound === this.percussionSoundId && (selectedKickSound !== '' || selectedHihatSound !== '' || selectedSnareClapSound !== '')) { // Play the selected percussion sound LK.getSound(this.percussionSoundId).play(); // Deselect all percussion sounds selectedPercussionSound = ''; storage.selectedPercussionSound = selectedPercussionSound; // Update button appearances - all percussion buttons become inactive for (var j = 0; j < percussionButtons.length; j++) { percussionButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedPercussionSound === this.percussionSoundId && selectedKickSound === '' && selectedHihatSound === '' && selectedSnareClapSound === '') { // Play the selected percussion sound LK.getSound(this.percussionSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected percussion sound LK.getSound(this.percussionSoundId).play(); // Update selected percussion sound selectedPercussionSound = this.percussionSoundId; storage.selectedPercussionSound = selectedPercussionSound; // Update button appearances for (var j = 0; j < percussionButtons.length; j++) { percussionButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(percussionButton); } // --- SOUND VOLUME CIRCLE UI --- // Container for the whole sound volume control var volumeCircleContainer = new Container(); volumeCircleContainer.x = 2048 / 2 + 300; volumeCircleContainer.y = 1100; settingsmenu.addChild(volumeCircleContainer); // Circle background var bpmCircleBg = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.1, scaleY: 1.1, alpha: 0.85 }); bpmCircleContainer.addChild(bpmCircleBg); // BPM value text var bpmValueTxt = new Text2(bpm + "", { size: 54, fill: 0xffffff }); bpmValueTxt.scale.set(2, 2); bpmValueTxt.anchor.set(0.5, 0.5); bpmValueTxt.x = 0; bpmValueTxt.y = 0; bpmCircleContainer.addChild(bpmValueTxt); // BPM label text var bpmLabelTxt = new Text2("BPM", { size: 24, fill: 0xffffff }); bpmLabelTxt.scale.set(2, 2); bpmLabelTxt.anchor.set(0.5, 0.5); bpmLabelTxt.x = 0; bpmLabelTxt.y = 90; bpmCircleContainer.addChild(bpmLabelTxt); // Volume circle background var volumeCircleBg = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.1, scaleY: 1.1, alpha: 0.85 }); volumeCircleContainer.addChild(volumeCircleBg); // Volume value text var volumeValueTxt = new Text2((typeof storage.soundVolume === "number" ? storage.soundVolume : 100) + "", { size: 54, fill: 0xffffff }); volumeValueTxt.scale.set(2, 2); volumeValueTxt.anchor.set(0.5, 0.5); volumeValueTxt.x = 0; volumeValueTxt.y = 0; volumeCircleContainer.addChild(volumeValueTxt); // Volume label text var volumeLabelTxt = new Text2("SOUND", { size: 24, fill: 0xffffff }); volumeLabelTxt.scale.set(2, 2); volumeLabelTxt.anchor.set(0.5, 0.5); volumeLabelTxt.x = 0; volumeLabelTxt.y = 90; volumeCircleContainer.addChild(volumeLabelTxt); // --- Drag-to-change BPM logic --- var dragActive = false; var dragStartY = 0; var dragStartBpm = 0; var lastDragStep = 0; var BPM_MIN = 40; var BPM_MAX = 120; var BPM_STEP = 5; var PIXELS_PER_STEP = 20; // Touch start (down) on circle bpmCircleContainer.down = function (x, y, obj) { dragActive = true; dragStartY = y; dragStartBpm = bpm; lastDragStep = 0; // Animate circle slightly for feedback tween(bpmCircleBg, { scaleX: 1.18, scaleY: 1.18 }, { duration: 100, yoyo: true, repeat: 1 }); }; // Touch move on circle bpmCircleContainer.move = function (x, y, obj) { if (!dragActive) { return; } var dy = dragStartY - y; // Up is positive var step = Math.floor(dy / PIXELS_PER_STEP); if (step !== lastDragStep) { var newBpm = dragStartBpm + step * BPM_STEP; if (newBpm < BPM_MIN) { newBpm = BPM_MIN; } if (newBpm > BPM_MAX) { newBpm = BPM_MAX; } if (newBpm !== bpm) { bpm = newBpm; bpmValueTxt.setText(bpm + ""); beatInterval = 60 / bpm; // Recalculate beat interval for new BPM // Regenerate beat pattern with new BPM intervals beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; // Reset beat spawning storage.bpm = bpm; // persist bpm to storage // Play scratch sound when BPM changes during settings LK.getSound('scratch').play(); if (bpmDisplayTxt) { // Show the actual BPM of the currently playing song, not the settings BPM var currentPlayingBpm = bpm; // This is the actual BPM being used for gameplay bpmDisplayTxt.setText(currentPlayingBpm + ' BPM'); } } lastDragStep = step; } }; // Touch end (up) on circle bpmCircleContainer.up = function (x, y, obj) { dragActive = false; // Animate back to normal scale tween(bpmCircleBg, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100 }); }; // --- Volume drag logic --- var volumeDragActive = false; var volumeDragStartY = 0; var volumeDragStartValue = 0; var volumeLastDragStep = 0; var VOLUME_MIN = 0; var VOLUME_MAX = 100; var VOLUME_STEP = 5; var VOLUME_PIXELS_PER_STEP = 20; // Touch start (down) on volume circle volumeCircleContainer.down = function (x, y, obj) { volumeDragActive = true; volumeDragStartY = y; volumeDragStartValue = soundVolume; volumeLastDragStep = 0; // Animate circle slightly for feedback tween(volumeCircleBg, { scaleX: 1.18, scaleY: 1.18 }, { duration: 100, yoyo: true, repeat: 1 }); }; // Touch move on volume circle volumeCircleContainer.move = function (x, y, obj) { if (!volumeDragActive) { return; } var dy = volumeDragStartY - y; // Up is positive var step = Math.floor(dy / VOLUME_PIXELS_PER_STEP); if (step !== volumeLastDragStep) { var newVolume = volumeDragStartValue + step * VOLUME_STEP; if (newVolume < VOLUME_MIN) { newVolume = VOLUME_MIN; } if (newVolume > VOLUME_MAX) { newVolume = VOLUME_MAX; } if (newVolume !== soundVolume) { soundVolume = newVolume; volumeValueTxt.setText(soundVolume + ""); storage.soundVolume = soundVolume; // persist volume to storage updateAllSoundVolumes(); // Update all sound volumes immediately // Update current playing music volume immediately var currentVolumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * currentVolumeMultiplier, loop: true }); } volumeLastDragStep = step; } }; // Touch end (up) on volume circle volumeCircleContainer.up = function (x, y, obj) { volumeDragActive = false; // Animate back to normal scale tween(volumeCircleBg, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100 }); }; // Close button (bottom right) var closeBtn = LK.getAsset('closebutton', { anchorX: 1, anchorY: 1, x: 2048 - 60, y: 2732 - 60, scaleX: 3, scaleY: 3 }); settingsmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(closeBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(closeBtn, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } }); settingsmenu.visible = false; mainMenuContainer.visible = true; }; // Remove tap-to-close on background settingsmenu.down = undefined; settingsmenu.visible = true; } // Button event handlers // Countdown logic var countdownContainer = new Container(); countdownContainer.visible = false; game.addChild(countdownContainer); // Pre-game ready screen container var readyContainer = new Container(); readyContainer.visible = false; game.addChild(readyContainer); function showStoryIntro(onComplete) { storyContainer.visible = true; storyContainer.removeChildren(); // Dark background covering full screen var storyBg = LK.getAsset('defeatbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, tint: 0x000000 }); storyContainer.addChild(storyBg); // Play chapter1music during storytelling var volumeMultiplier = soundVolume / 100; LK.playMusic('chapter1music', { volume: 0.2 * volumeMultiplier, loop: true }); // Skip button in bottom right corner var skipBtn = new Text2("SKIP", { size: 36, fill: 0xffffff }); skipBtn.scale.set(2, 2); skipBtn.anchor.set(1, 1); skipBtn.x = 2048 - 50; skipBtn.y = 2732 - 50; storyContainer.addChild(skipBtn); // Skip button click handler skipBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); // Click animation tween(skipBtn, { scaleX: 3.6, scaleY: 3.6 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(skipBtn, { scaleX: 4, scaleY: 4 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { // Keep storyContainer visible to continue story narration // storyContainer.visible = false; // storyContainer.removeChildren(); if (typeof onComplete === "function") { onComplete(); } } }); } }); }; // Chapter 1 asset with fade in effect var chapter1Asset = LK.getAsset('Chapter1', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 100, alpha: 0, scaleX: 0.4, scaleY: 0.4 }); storyContainer.addChild(chapter1Asset); // Fade in effect for chapter1 asset with 0.5 second delay tween(chapter1Asset, { alpha: 1 }, { duration: 2000, delay: 500, easing: tween.easeOut }); // Echoes of the Past asset with fade in effect var echoesofthepastAsset = LK.getAsset('echoesofthepast', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 40, alpha: 0, scaleX: 0.3, scaleY: 0.3 }); storyContainer.addChild(echoesofthepastAsset); // Fade in effect for echoesofthepast asset with 0.8 second delay tween(echoesofthepastAsset, { alpha: 1 }, { duration: 2000, delay: 800, easing: tween.easeOut }); // Calculate sound duration (end - start) and fade out text when sound ends var soundDuration = (0.976 - 0.05) * 1000; // Convert to milliseconds var fadeoutDuration = 200; // 0.2 second fadeout // Play echoesofthepast sound synchronized with asset fade-in timing LK.setTimeout(function () { var echoesSound = LK.getSound('echoesofthepast'); echoesSound.play(); // Start fadeout 0.2 seconds before sound ends LK.setTimeout(function () { tween(echoesSound, { volume: 0 }, { duration: fadeoutDuration, easing: tween.easeOut }); }, soundDuration - fadeoutDuration); }, 500); // Start sound when chapter1 asset starts fading in for perfect sync // Remove assets 0.8 seconds after sound finishes (extended by 0.4 seconds) LK.setTimeout(function () { chapter1Asset.destroy(); echoesofthepastAsset.destroy(); // Add white stroke background for ch1pan1 asset var ch1pan1Stroke = LK.getAsset('ch1pan1', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, rotation: 15 * Math.PI / 180, // Convert 15 degrees to radians alpha: 0, scaleX: 0.87, scaleY: 0.87, tint: 0xffffff }); storyContainer.addChild(ch1pan1Stroke); // Add ch1pan1 asset to center screen with 15-degree rotation, starting from left side var ch1pan1Asset = LK.getAsset('ch1pan1', { anchorX: 0.5, anchorY: 0.5, x: -400, y: 2732 / 2, rotation: 15 * Math.PI / 180, // Convert 15 degrees to radians alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan1Asset); // Fade in the stroke background first tween(ch1pan1Stroke, { alpha: 1 }, { duration: 1000, easing: tween.easeOut }); // Play paperswosh sound when ch1pan1 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan1 asset from left to center with sliding motion effect tween(ch1pan1Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); // Track current panel being shown var currentPanel = 1; // Add touch handler to advance story storyContainer.down = function (x, y, obj) { // Check if chapter1 asset still exists - if so, don't show panel assets var chapter1Exists = false; for (var i = 0; i < storyContainer.children.length; i++) { var child = storyContainer.children[i]; if (child && child.texture && child.texture.baseTexture && child.texture.baseTexture.source && child.texture.baseTexture.source.src && child.texture.baseTexture.source.src.indexOf('Chapter1') !== -1) { chapter1Exists = true; break; } } // If chapter1 asset still exists, don't proceed with panel assets if (chapter1Exists) { return; } if (currentPanel === 1) { // Add ch1pan2 asset starting from right side with 2 degree left rotation var ch1pan2Asset = LK.getAsset('ch1pan2', { anchorX: 0.5, anchorY: 0.5, x: 2048 + 400, // Start from right side off-screen y: 2732 / 2, rotation: -2 * Math.PI / 180, // 2 degrees to the left (negative) alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan2Asset); // Play paperswosh sound when ch1pan2 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan2 asset from right to left tween(ch1pan2Asset, { alpha: 1, x: 2048 / 2 // Move to center }, { duration: 1000, easing: tween.easeOut }); currentPanel = 2; } else if (currentPanel === 2) { // Add ch1pan3 with random rotation (left or right) var randomRotation = (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2); // Random 2-7 degrees left or right var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var ch1pan3Asset = LK.getAsset('ch1pan3', { anchorX: 0.5, anchorY: 0.5, x: slideFromRight ? 2048 + 400 : -400, y: 2732 / 2, rotation: randomRotation * Math.PI / 180, alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan3Asset); // Play paperswosh sound when ch1pan3 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan3 asset to center tween(ch1pan3Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); currentPanel = 3; } else if (currentPanel === 3) { // Add ch1pan4 with random rotation (left or right) var randomRotation = (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2); // Random 2-7 degrees left or right var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var ch1pan4Asset = LK.getAsset('ch1pan4', { anchorX: 0.5, anchorY: 0.5, x: slideFromRight ? 2048 + 400 : -400, y: 2732 / 2, rotation: randomRotation * Math.PI / 180, alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan4Asset); // Play paperswosh sound when ch1pan4 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan4 asset to center tween(ch1pan4Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); currentPanel = 4; } else if (currentPanel === 4) { // Add ch1pan5 with random rotation (left or right) var randomRotation = (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2); // Random 2-7 degrees left or right var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var ch1pan5Asset = LK.getAsset('ch1pan5', { anchorX: 0.5, anchorY: 0.5, x: slideFromRight ? 2048 + 400 : -400, y: 2732 / 2, rotation: randomRotation * Math.PI / 180, alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan5Asset); // Play paperswosh sound when ch1pan5 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan5 asset to center tween(ch1pan5Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); currentPanel = 5; } else if (currentPanel === 5) { // Fade out all panel assets when touched after ch1pan5 var allPanelAssets = []; // Collect all panel assets from storyContainer for (var i = 0; i < storyContainer.children.length; i++) { var child = storyContainer.children[i]; // Check if it's a panel asset by checking if it has panel-like properties if (child && child.anchor && child.x !== undefined && child.y !== undefined && child.alpha !== undefined) { // Skip background and skip button if (child !== storyBg && child !== skipBtn) { allPanelAssets.push(child); } } } // Fade out all panel assets simultaneously for (var i = 0; i < allPanelAssets.length; i++) { tween(allPanelAssets[i], { alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function () { // Destroy the asset after fadeout if (this.parent) { this.parent.removeChild(this); } this.destroy(); }.bind(allPanelAssets[i]) }); } // After all panels are faded out, fade out the black background and transition to tutorial tween(storyBg, { alpha: 0 }, { duration: 200, delay: 700, easing: tween.easeOut, onFinish: function onFinish() { // Remove skip button if (skipBtn && skipBtn.parent) { skipBtn.parent.removeChild(skipBtn); } skipBtn.destroy(); // Hide story container and show tutorial/game screen storyContainer.visible = false; storyContainer.removeChildren(); // Call the onComplete callback to proceed to tutorial if (typeof onComplete === "function") { onComplete(); } } }); // Remove touch handler after fadeout starts storyContainer.down = undefined; currentPanel = 6; } }; // Keep storyContainer visible to continue story narration // storyContainer.visible = false; // storyContainer.removeChildren(); if (typeof onComplete === "function") { onComplete(); } ch1pan1Stroke.destroy(); }, 500 + soundDuration + 800); // Remove assets 0.8 seconds after sound ends // For now, skip button is the only way to proceed // Skip button can still be used to proceed early } function showReadyScreen(onReady) { gamescreen.visible = true; readyContainer.visible = true; readyContainer.removeChildren(); var arrowTap = LK.getAsset('arrowtap', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X - 150, y: HIT_ZONE_Y + 250, scaleX: 3, scaleY: 3 }); readyContainer.addChild(arrowTap); // Show ready button var readyBtn = LK.getAsset('readybutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 350, scaleX: 6, scaleY: 6 }); readyContainer.addChild(readyBtn); // Add readyinfo asset on top of readybutton var readyInfo = LK.getAsset('readyinfo', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 375, scaleX: 1.1, scaleY: 1.1 }); readyContainer.addChild(readyInfo); // Track if black screen is gone var blackScreenGone = false; // Check if black screen has been removed by listening for story completion var _checkStoryComplete = function checkStoryComplete() { // If storyContainer is not visible, black screen has been removed if (!storyContainer.visible) { blackScreenGone = true; readyBtn.alpha = 1; // Enable ready button } else { // Schedule another check LK.setTimeout(_checkStoryComplete, 100); } }; // Start checking for story completion LK.setTimeout(_checkStoryComplete, 100); // Initially disable ready button readyBtn.alpha = 0.5; // Ready button click handler readyBtn.down = function (x, y, obj) { // Don't allow button press if black screen is still visible if (!blackScreenGone) { return; } // Play button click sound LK.getSound('buttonclick').play(); // Click animation tween(readyBtn, { scaleX: 7.2, scaleY: 7.2 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(readyBtn, { scaleX: 8, scaleY: 8 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { readyContainer.visible = false; readyContainer.removeChildren(); if (typeof onReady === "function") { onReady(); } } }); } }); }; } function showCountdown(onFinish) { gamescreen.visible = true; countdownContainer.visible = true; countdownContainer.removeChildren(); var numbers = ["3", "2", "1"]; var idx = 0; function showNext() { if (idx >= numbers.length) { countdownContainer.visible = false; countdownContainer.removeChildren(); // Always show gamescreen after countdown (fix blackscreen bug) gamescreen.visible = true; if (typeof onFinish === "function") { onFinish(); } return; } countdownContainer.removeChildren(); var txt = new Text2(numbers[idx], { size: 600, fill: 0xffd700 }); txt.scale.set(160, 160); txt.anchor.set(0.5, 0.5); txt.x = 2048 / 2; txt.y = 1200; txt.alpha = 0; txt.scale.x = 0.6; txt.scale.y = 0.6; countdownContainer.addChild(txt); // Animate in tween(txt, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 250, easing: tween.cubicOut, onFinish: function onFinish() { // Hold, then animate out tween(txt, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 350, delay: 400, easing: tween.cubicIn, onFinish: function onFinish() { idx++; showNext(); } }); } }); } idx = 0; showNext(); } startBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Stop menu music immediately LK.stopMusic(); // Click animation - scale inward then outward tween(startBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(startBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showStoryIntro(function () { showReadyScreen(function () { showCountdown(function () { startGame(); }); }); }); } }); } }); }; settingsBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(settingsBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(settingsBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showSettingsMenu(); } }); } }); }; creditsBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(creditsBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(creditsBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showCreditsMenu(); } }); } }); }; supportBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(supportBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(supportBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showSupportMenu(); } }); } }); }; // Music mute button in bottom corner of main menu var musicMuted = typeof storage.musicMuted === "boolean" ? storage.musicMuted : false; var musicMuteBtn = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 150, y: 2732 - 150, scaleX: 0.8, scaleY: 0.8, alpha: musicMuted ? 0.5 : 1 }); mainMenuContainer.addChild(musicMuteBtn); // Music icon text on button var musicIconTxt = new Text2(musicMuted ? "M" : "♪", { size: 48, fill: 0xffffff }); musicIconTxt.scale.set(2, 2); musicIconTxt.anchor.set(0.5, 0.5); musicIconTxt.x = 2048 - 150; musicIconTxt.y = 2732 - 150; mainMenuContainer.addChild(musicIconTxt); // Music mute button handler musicMuteBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Toggle mute state musicMuted = !musicMuted; storage.musicMuted = musicMuted; // Update button appearance musicMuteBtn.alpha = musicMuted ? 0.5 : 1; musicIconTxt.setText(musicMuted ? "M" : "♪"); // Handle music playback if (musicMuted) { LK.stopMusic(); } else { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); } // Click animation tween(musicMuteBtn, { scaleX: 0.72, scaleY: 0.72 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(musicMuteBtn, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); } }); }; // Story intro screen container var storyContainer = new Container(); storyContainer.visible = false; game.addChild(storyContainer); // Add menu to game game.addChild(mainMenuContainer); // menus are already added above // Hide all gameplay UI until game starts scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; // missTxt removed, only hearts for lives // timelineBar and kickTarget are now children of gamescreen, so their visibility is managed by gamescreen // Patch startGame to show gameplay UI and gamescreen var _originalStartGame = startGame; startGame = function startGame() { scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; songNameTxt.visible = true; if (bpmDisplayTxt) { // Show the actual BPM of the currently playing song, not the settings BPM var currentPlayingBpm = isTutorial ? 60 : bpm; // Tutorial always shows 60 BPM bpmDisplayTxt.setText('60 BPM'); } // missTxt removed, only hearts for lives gamescreen.visible = true; mainMenuContainer.visible = false; // overlayContainer.visible = false; // Removed, not defined _originalStartGame(); }; // On menu, hide gameplay UI and gamescreen mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Play menu music when in main menu (only if not muted) if (!musicMuted) { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); } // Apply current sound volume settings to all sounds updateAllSoundVolumes(); // Pause button functionality pauseBtn.down = function (x, y, obj) { if (!gameActive || !gamescreen.visible) { return; } // Play button click sound LK.getSound('buttonclick').play(); // Pause the game gamePaused = true; showPauseMenu(); // No animation - button scale remains fixed at 1.6 }; // Show pause menu function function showPauseMenu() { pauseContainer.visible = true; pauseContainer.removeChildren(); // Hide BPM and song name in pause menu bpmDisplayTxt.visible = false; songNameTxt.visible = false; // Pause menu background var pauseMenuBg = LK.getAsset('pausemenu', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); pauseContainer.addChild(pauseMenuBg); // Button positions var pauseButtonStartY = 1200; var pauseButtonHeight = 289; var pauseButtonSpacing = 20; // Resume button var resumeBtn = LK.getAsset('resumebutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: pauseButtonStartY, scaleX: 5, scaleY: 5 }); pauseContainer.addChild(resumeBtn); // Restart button var restartBtn = LK.getAsset('restartbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: pauseButtonStartY + pauseButtonHeight + pauseButtonSpacing, scaleX: 5, scaleY: 5 }); pauseContainer.addChild(restartBtn); // Main menu button var pauseMainMenuBtn = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: pauseButtonStartY + 2 * (pauseButtonHeight + pauseButtonSpacing), scaleX: 5, scaleY: 5 }); pauseContainer.addChild(pauseMainMenuBtn); // Resume button handler resumeBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); tween(resumeBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(resumeBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { resumeGame(); } }); } }); }; // Restart button handler restartBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); tween(restartBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(restartBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { pauseContainer.visible = false; gamePaused = false; gameActive = false; // Stop game temporarily during restart maxCombo = 0; perfectCount = 0; maxPerfectCombo = 0; // Reset lives and hearts before showing ready screen lives = MAX_LIVES; heartshields = 3; // Reset heartshields // Continue from current level - if tutorial not completed, restart tutorial if (!tutorialCompleted) { currentLevel = 1; storage.currentLevel = currentLevel; } // Remove all existing heart icons for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } // Recreate all heart icons as full heartshields heartIcons = []; var heartY = scoreTxt.y + 50; var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; for (var i = 0; i < MAX_LIVES; i++) { var heartAsset = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartAsset); heartIcons.push(heartAsset); } showReadyScreen(function () { showCountdown(function () { startGame(); }); }); } }); } }); }; // Pause main menu button handler pauseMainMenuBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); tween(pauseMainMenuBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(pauseMainMenuBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { pauseContainer.visible = false; gamePaused = false; gameActive = false; maxCombo = 0; perfectCount = 0; maxPerfectCombo = 0; mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Stop current music and play menu music (only if not muted) LK.stopMusic(); if (!musicMuted) { LK.setTimeout(function () { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); }, 100); } } }); } }); }; } // Resume game function function resumeGame() { gamePaused = false; pauseContainer.visible = false; // Show BPM and song name when resuming bpmDisplayTxt.visible = true; songNameTxt.visible = true; lastTickTime = Date.now(); // Reset time to prevent time jump } // --- Beat spawning --- function spawnBeat(beat) { var marker = new BeatMarker(); // All beats spawn at left edge of screen (x=0) marker.x = 0; // Always spawn at left edge marker.y = HIT_ZONE_Y; marker.beatTime = beat.time; marker.type = beat.type; marker.active = true; marker.hit = false; marker.lastX = marker.x; // initialize lastX for correct miss logic marker.hasReachedHitZone = false; // initialize for correct miss logic beats.push(marker); gamescreen.addChild(marker); } // --- Game update --- game.update = function () { if (!gameActive || gamePaused) { return; } // Time management var now = Date.now(); var dt = (now - lastTickTime) / 1000; lastTickTime = now; songTime += dt; // Prevent beat spawning until countdown is finished and gameplay is visible if (!gamescreen.visible || countdownContainer.visible) { return; } // Only allow beat spawning after countdown is finished if (!countdownContainer.visible) { // Spawn new beats with constant speed and BPM-based timing var currentBeatSpeed = getBeatSpeed(); // Always 400 px/s // Calculate travel time from left edge (x=0) to hit zone var travelTime = HIT_ZONE_X / currentBeatSpeed; // Time needed to travel from x=0 to hit zone // Spawn beats at the right time so they arrive at hit zone exactly when needed while (nextBeatIdx < beatPattern.length && beatPattern[nextBeatIdx].time - songTime <= travelTime) { spawnBeat(beatPattern[nextBeatIdx]); nextBeatIdx++; } } // Tutorial completion check - 5k points for chapter 1 if (isTutorial && score >= 5000 && !tutorialCompleted) { tutorialCompleted = true; storage.tutorialCompleted = true; currentLevel = 2; storage.currentLevel = currentLevel; // Show tutorial completion message var tutorialCompleteTxt = new Text2('CHAPTER 1 COMPLETE!', { size: 72, fill: 0x00ff00 }); tutorialCompleteTxt.scale.set(2, 2); tutorialCompleteTxt.anchor.set(0.5, 0.5); tutorialCompleteTxt.x = 2048 / 2; tutorialCompleteTxt.y = 1200; game.addChild(tutorialCompleteTxt); tween(tutorialCompleteTxt, { alpha: 0, y: tutorialCompleteTxt.y - 100 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { tutorialCompleteTxt.destroy(); } }); } // Check if player reached 5k points - pause game and show black screen if (score >= 5000 && gameActive) { // Pause the game gameActive = false; gamePaused = true; // Stop current music LK.stopMusic(); // Hide all game UI elements scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Create ch1panF screen overlay var blackScreen = LK.getAsset('ch1panF', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0 }); game.addChild(blackScreen); // Play 60BPM music when ch1panF appears var volumeMultiplier = soundVolume / 100; LK.playMusic('60BPMLowKey', { volume: 1 * volumeMultiplier, loop: true }); // Fade in ch1panF screen tween(blackScreen, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); // Add ch1panFnarbox asset 1 second after ch1panF appears LK.setTimeout(function () { var ch1panFnarbox = LK.getAsset('ch1panFnarbox', { anchorX: 1, anchorY: 1, x: 2048 - 50, y: 2732 - 50, alpha: 0 }); game.addChild(ch1panFnarbox); // Fade in the narbox tween(ch1panFnarbox, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); // Play ch1voice1 and ch1voice2 randomly within 3 seconds after narbox appears var voice1Delay = Math.random() * 3000; // Random delay between 0-3 seconds var voice2Delay = Math.random() * 3000; // Random delay between 0-3 seconds LK.setTimeout(function () { LK.getSound('ch1voice1').play(); }, voice1Delay); LK.setTimeout(function () { LK.getSound('ch1voice2').play(); }, voice2Delay); // After 3 seconds, fade out music and assets, return to black screen LK.setTimeout(function () { // Fade out the music LK.playMusic('60BPMLowKey', { fade: { start: 1, end: 0, duration: 1000 } }); // Fade out ch1panF asset tween(blackScreen, { alpha: 0 }, { duration: 1000, easing: tween.easeOut }); // Fade out ch1panFnarbox asset tween(ch1panFnarbox, { alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Remove assets after fadeout blackScreen.destroy(); ch1panFnarbox.destroy(); // Stop the music completely LK.stopMusic(); // After fadeout, add Chapter 2 assets and sound LK.setTimeout(function () { // Set background to black game.setBackgroundColor(0x000000); // Add Chapter 2 asset with fade in effect var chapter2Asset = LK.getAsset('Chapter2', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 100, alpha: 0, scaleX: 0.4, scaleY: 0.4 }); game.addChild(chapter2Asset); // Add nightshift asset with fade in effect var nightshiftAsset = LK.getAsset('nightshift', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 80, // 20px yukarı çıkarıldı alpha: 0, scaleX: 0.25, scaleY: 0.25 }); game.addChild(nightshiftAsset); // Fade in chapter2 asset tween(chapter2Asset, { alpha: 1 }, { duration: 2000, easing: tween.easeOut }); // Fade in nightshift asset tween(nightshiftAsset, { alpha: 1 }, { duration: 2000, delay: 500, easing: tween.easeOut }); // Play nightshift sound when assets appear var nightshiftSound = LK.getSound('nightshift'); nightshiftSound.play(); // Get nightshift sound duration from asset definition (start:0, end:1, so use full duration) var nightshiftSoundAsset = LK.getSound('nightshift'); var nightshiftDuration = 0; // Defensive: try to get duration from sound asset, fallback to 3000ms if not available if (nightshiftSoundAsset && nightshiftSoundAsset.duration) { nightshiftDuration = nightshiftSoundAsset.duration * 1000; } else { nightshiftDuration = 3000; } // Remove chapter2 and nightshift assets when sound finishes LK.setTimeout(function () { // Remove chapter2 asset if (chapter2Asset && chapter2Asset.parent) { chapter2Asset.parent.removeChild(chapter2Asset); chapter2Asset.destroy(); } // Remove nightshift asset if (nightshiftAsset && nightshiftAsset.parent) { nightshiftAsset.parent.removeChild(nightshiftAsset); nightshiftAsset.destroy(); } // Play chapter2music after assets are removed var volumeMultiplier = soundVolume / 100; LK.playMusic('chapter2music', { volume: 0.3 * volumeMultiplier, loop: true }); // Add ch2pan1-5 assets when chapter2music starts, but only show ch2pan1 at first, and require tap to advance each panel var ch2panAssets = []; var ch2PanNames = ['ch2pan1', 'ch2pan2', 'ch2pan3', 'ch2pan4', 'ch2pan5']; var ch2CurrentPanel = 0; var ch2PanelActive = false; var ch2PanelContainer = new Container(); game.addChild(ch2PanelContainer); // Helper to show a panel by index function showCh2Panel(panelIdx) { // Show each panel (ch2pan1 through ch2pan5) before starting chapter 2 if (panelIdx < ch2PanNames.length) { // Create and show the current panel with sliding animation var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var startX = slideFromRight ? 2048 + 400 : -400; // Start from off-screen var panelAsset = LK.getAsset(ch2PanNames[panelIdx], { anchorX: 0.5, anchorY: 0.5, x: startX, y: 2732 / 2, alpha: 0, scaleX: 0.85, scaleY: 0.85, rotation: (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2) * Math.PI / 180 }); ch2PanelContainer.addChild(panelAsset); ch2panAssets.push(panelAsset); // Play paperswosh sound when panel appears LK.getSound('paperswosh').play(); // Fade in and slide the panel to center tween(panelAsset, { alpha: 1, x: 2048 / 2 // Slide to center }, { duration: 1000, easing: tween.easeOut }); ch2PanelActive = true; return; } // Only start chapter 2 after all panels have been shown if (panelIdx >= ch2PanNames.length) { var startChapter2 = function startChapter2() { // Set chapter 2 state isTutorial = false; bpm = 90; beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); score = 0; misses = 0; ignoredMisses = 0; combo = 0; perfectCombo = 0; lives = MAX_LIVES; heartshields = 3; lastBpmUpgradeScore = 0; // Set UI for gameplay scoreTxt.setText('0'); comboTxt.setText(''); bpmDisplayTxt.setText('90 BPM'); if (songNameTxt) { songNameTxt.setText('DigitalPulse'); } // Show gameplay UI scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; songNameTxt.visible = true; gamescreen.visible = true; mainMenuContainer.visible = false; // Play 90BPM music var volumeMultiplier = soundVolume / 100; LK.playMusic('90BPMDigitalPulse', { volume: 1 * volumeMultiplier, loop: true }); // Start countdown, then start game showCountdown(function () { // Patch win condition for chapter 2: 10K points // --- CHAPTER 2 GAMEPLAY: 90BPM, beats move at 90BPM, win at 10K points --- game.update = function () { if (!gameActive || gamePaused) { return; } // Time management var now = Date.now(); var dt = (now - lastTickTime) / 1000; lastTickTime = now; songTime += dt; // Prevent beat spawning until countdown is finished and gameplay is visible if (!gamescreen.visible || countdownContainer.visible) { return; } // Only allow beat spawning after countdown is finished if (!countdownContainer.visible) { // --- 90BPM: spawn beats at 90BPM --- var currentBeatSpeed = getBeatSpeed(); // Always 400 px/s var travelTime = HIT_ZONE_X / currentBeatSpeed; // Spawn beats at the right time so they arrive at hit zone exactly when needed while (nextBeatIdx < beatPattern.length && beatPattern[nextBeatIdx].time - songTime <= travelTime) { spawnBeat(beatPattern[nextBeatIdx]); nextBeatIdx++; } } // CH2: 10K puan - show blackscreen if (score >= 10000 && gameActive) { gameActive = false; gamePaused = true; // Stop current music LK.stopMusic(); // Hide all game UI elements scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Create black screen overlay var blackScreen = LK.getAsset('ch2panF', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0, tint: 0x000000 // Make it black }); game.addChild(blackScreen); // Add devamiicin asset on black screen var blackScreenAsset = LK.getAsset('devamiicin', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0, scaleX: 0.5, scaleY: 0.5 }); game.addChild(blackScreenAsset); // Fade in black screen tween(blackScreen, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); // Fade in devamiicin asset after black screen appears tween(blackScreenAsset, { alpha: 1 }, { duration: 1000, delay: 500, easing: tween.easeOut }); return; } // Move beats and check for misses (copy from original game.update) for (var i = beats.length - 1; i >= 0; i--) { var beat = beats[i]; var currentBeatSpeed = getBeatSpeed(); var elapsedTime = songTime - (beat.beatTime - HIT_ZONE_X / currentBeatSpeed); beat.x = elapsedTime * currentBeatSpeed; if (beat.x < -400) { beat.x = -400; } if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 400) { beat.x = TIMELINE_X + TIMELINE_WIDTH + 400; } if (kickTarget && kickTarget.parent) { var kickTargetIndex = kickTarget.parent.getChildIndex(kickTarget); var beatIndex = beat.parent.getChildIndex(beat); if (beatIndex > kickTargetIndex) { beat.parent.setChildIndex(beat, kickTargetIndex); } } if (typeof beat.lastX === "undefined") { beat.lastX = beat.x; } // Only check for miss if the beat has passed the hit zone (not before) // Also, do not count as miss if the beat has not yet reached the hit zone at least once if (!beat.hit && beat.active && typeof beat.hasReachedHitZone !== "undefined" && beat.hasReachedHitZone && beat.lastX <= HIT_ZONE_X + HIT_WINDOW && beat.x > HIT_ZONE_X + HIT_WINDOW) { // Missed beat.active = false; misses++; // Replace the beat marker asset with missCircle for missed beats var missedAsset = LK.getAsset('missCircle', { anchorX: 0.5, anchorY: 0.5, x: beat.x, y: beat.y, alpha: 0.8, width: 60, height: 59.77 }); // Replace the beat's current asset with missCircle if (beat.parent) { // Store parent reference before removing beat var beatParent = beat.parent; // Remove the old beat marker from its parent beatParent.removeChild(beat); // Add the missed asset to the stored parent reference beatParent.addChild(missedAsset); // Update the beat reference to point to the new asset beats[i] = missedAsset; // Copy over important properties missedAsset.beatTime = beat.beatTime; missedAsset.type = beat.type; missedAsset.active = false; missedAsset.hit = false; missedAsset.lastX = beat.x; missedAsset.hasReachedHitZone = true; missedAsset.isMissed = true; // Destroy the original beat asset beat.destroy(); } // Handle heartshield system if (heartshields > 0) { // Convert leftmost heartshield to heart (heartshields are lost left to right) var shieldIndex = 3 - heartshields; if (heartIcons[shieldIndex]) { var parent = heartIcons[shieldIndex].parent; if (parent) { parent.removeChild(heartIcons[shieldIndex]); } heartIcons[shieldIndex] = LK.getAsset('heart', { anchorX: 0, anchorY: 0, x: heartStartX + shieldIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[shieldIndex]); } heartshields--; // Lose one heartshield // No miss feedback, sound, or other penalties for heartshield hits } else { // All heartshields are gone, now show miss effects and lose hearts beat.showFeedback('miss'); combo = 0; perfectCombo = 0; comboTxt.setText(''); LK.effects.flashObject(kickTarget, 0xff0000, 200); LK.getSound('miss').play(); // Show MISS feedback text at hit zone var missText = new Text2('MISS', { size: 60, fill: 0xff0000 }); missText.scale.set(2, 2); missText.anchor.set(0.5, 0.5); missText.x = HIT_ZONE_X; missText.y = HIT_ZONE_Y - 180; game.addChild(missText); tween(missText, { alpha: 0, y: missText.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { missText.destroy(); } }); // Lose a heart and update heart icon (hearts are lost left to right) if (lives > 0) { var lostIndex = MAX_LIVES - lives; if (heartIcons[lostIndex]) { var parent = heartIcons[lostIndex].parent; if (parent) { parent.removeChild(heartIcons[lostIndex]); } heartIcons[lostIndex] = LK.getAsset('lostheart', { anchorX: 0, anchorY: 0, x: heartStartX + lostIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[lostIndex]); } lives--; } if (lives <= 0) { gameOver(); } } } // Track if beat has reached the hit zone at least once if (typeof beat.hasReachedHitZone === "undefined") { beat.hasReachedHitZone = false; } if (!beat.hasReachedHitZone && beat.x >= HIT_ZONE_X - HIT_WINDOW) { beat.hasReachedHitZone = true; } // Remove if off screen if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 100) { beat.destroy(); beats.splice(i, 1); } // Update lastX for next frame beat.lastX = beat.x; } }; // Start game state gameActive = true; gamePaused = false; // Defensive: clear beats array for (var i = 0; i < beats.length; i++) { beats[i].destroy(); } beats = []; // Remove any beat at the kick target for (var i = beats.length - 1; i >= 0; i--) { if (typeof beats[i].x !== "undefined" && Math.abs(beats[i].x - HIT_ZONE_X) < 2 || typeof beats[i].x !== "undefined" && beats[i].x >= HIT_ZONE_X - HIT_WINDOW) { beats[i].destroy(); beats.splice(i, 1); } } // Reset hearts for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; heartshields = MAX_LIVES; lives = MAX_LIVES; for (var i = 0; i < MAX_LIVES; i++) { var heartAsset = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartAsset); heartIcons.push(heartAsset); } // Set timeout to remove heartshields after 3 seconds in chapter 2 tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameActive && heartshields > 0) { // Convert all remaining heartshields to regular hearts for (var i = 0; i < MAX_LIVES; i++) { if (heartIcons[i] && heartIcons[i].parent) { // Check if this is still a heartshield by checking if heartshields counter allows it var shieldIndex = 3 - heartshields; if (i >= shieldIndex) { // This is a heartshield, convert it to heart var parent = heartIcons[i].parent; parent.removeChild(heartIcons[i]); heartIcons[i] = LK.getAsset('heart', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[i]); } } } // Reset heartshields counter to 0 heartshields = 0; } } }); }); }; // Remove tap handler // Remove all ch2pan panels and container for (var i = 0; i < ch2panAssets.length; i++) { if (ch2panAssets[i] && ch2panAssets[i].parent) { ch2panAssets[i].parent.removeChild(ch2panAssets[i]); ch2panAssets[i].destroy(); } } if (ch2PanelContainer && ch2PanelContainer.parent) { ch2PanelContainer.parent.removeChild(ch2PanelContainer); } // Show "Get ready to feel the beat." message before starting chapter 2 var readyMessage = new Text2('Get ready to feel the beat.', { size: 60, fill: 0xffffff }); readyMessage.scale.set(2, 2); readyMessage.anchor.set(0.5, 0.5); readyMessage.x = 2048 / 2; readyMessage.y = 2732 / 2; readyMessage.alpha = 0; game.addChild(readyMessage); // Fade in the message tween(readyMessage, { alpha: 1 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Hold for 2 seconds then fade out tween(readyMessage, { alpha: 0 }, { duration: 1000, delay: 2000, easing: tween.easeOut, onFinish: function onFinish() { readyMessage.destroy(); // Start chapter 2 after message disappears startChapter2(); } }); } }); game.down = originalGameDown; ch2PanelActive = false; return; } // If not first panel, do nothing (skip all ch2pan panels) ch2PanelActive = false; } // Save original game.down so we can restore it after panels var originalGameDown = game.down; // Start with ch2pan1 ch2CurrentPanel = 0; showCh2Panel(ch2CurrentPanel); // Override game.down to advance panels on tap anywhere game.down = function (x, y, obj) { if (!ch2PanelActive) { if (typeof originalGameDown === "function") { originalGameDown(x, y, obj); } return; } // If we've shown all panels, start chapter 2 if (ch2CurrentPanel >= ch2PanNames.length - 1) { ch2PanelActive = false; // Fade out all panels simultaneously var fadeCount = ch2panAssets.length; var faded = 0; for (var i = 0; i < ch2panAssets.length; i++) { if (ch2panAssets[i] && ch2panAssets[i].parent) { tween(ch2panAssets[i], { alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function () { if (this.parent) { this.parent.removeChild(this); } this.destroy(); faded++; // After all panels faded, start chapter 2 if (faded === fadeCount) { if (ch2PanelContainer && ch2PanelContainer.parent) { ch2PanelContainer.parent.removeChild(ch2PanelContainer); } // Start chapter 2 with showCh2Panel function that handles the actual chapter 2 logic showCh2Panel(ch2PanNames.length); // This will trigger chapter 2 start } }.bind(ch2panAssets[i]) }); } } return; } // Advance to next panel ch2CurrentPanel++; showCh2Panel(ch2CurrentPanel); }; }, nightshiftDuration); }, 1000); } }); }, 3000); }, 1000); } // Check for BPM progression every 10,000 points (only for non-tutorial) if (!isTutorial && score >= 10000 && score - lastBpmUpgradeScore >= 10000) { if (bpm < 120) { // Don't increase BPM beyond 120 lastBpmUpgradeScore = score; var oldBpm = bpm; bpm = Math.min(120, bpm + 5); // Increase by 5 BPM, cap at 120 if (bpm !== oldBpm) { // Update BPM display to show current playing song BPM if (bpmDisplayTxt) { // Show the actual BPM of the currently playing song, not the settings BPM var currentPlayingBpm = bpm; // This is the actual BPM being used for gameplay bpmDisplayTxt.setText(currentPlayingBpm + ' BPM'); } // Update beat interval for new beats beatInterval = 60 / bpm; // Get new music ID var newMusicId = '100BPMSynthWave'; // Default fallback if (bpm === 40) { newMusicId = '40BPMDeepVibes'; } else if (bpm === 45) { newMusicId = '45BPMChillWave'; } else if (bpm === 50) { newMusicId = '50BPMSlowMotion'; } else if (bpm === 55) { newMusicId = '55BPMDreamscape'; } else if (bpm === 60) { newMusicId = '60BPMLowKey'; } else if (bpm === 65) { newMusicId = '65BPMRelaxed'; } else if (bpm === 70) { newMusicId = '70BPMCalmVibes'; } else if (bpm === 75) { newMusicId = '75BPMElectricDreams'; } else if (bpm === 80) { newMusicId = '80BPMNeonNights'; } else if (bpm === 85) { newMusicId = '85BPMCyberFlow'; } else if (bpm === 90) { newMusicId = '90BPMDigitalPulse'; } else if (bpm === 95) { newMusicId = '95BPMFutureBass'; } else if (bpm === 100) { newMusicId = '100BPMSynthWave'; } else if (bpm === 105) { newMusicId = '105BPMHyperDrive'; } else if (bpm === 110) { newMusicId = '110BPMTechnoRush'; } else if (bpm === 115) { newMusicId = '115BPMHighEnergy'; } else if (bpm === 120) { newMusicId = '120BPMMaximum'; } // Fade out current music and fade in new music var volumeMultiplier = soundVolume / 100; LK.stopMusic(); LK.playMusic(newMusicId, { volume: 0, loop: true, fade: { start: 0, end: 1 * volumeMultiplier, duration: 1000 } }); // Play scratch sound when BPM changes LK.getSound('scratch').play(); // Update song name display if (songNameTxt) { var songName = newMusicId.replace(/^\d+BPM/, ''); songNameTxt.setText(songName); } } } } // Move beats and check for misses for (var i = beats.length - 1; i >= 0; i--) { var beat = beats[i]; // Move beats at constant speed from left to right var currentBeatSpeed = getBeatSpeed(); // Always 400 px/s // Calculate elapsed time since beat was spawned var elapsedTime = songTime - (beat.beatTime - HIT_ZONE_X / currentBeatSpeed); // Move beat from left edge at constant speed beat.x = elapsedTime * currentBeatSpeed; // Ensure beat stays within reasonable bounds if (beat.x < -400) { beat.x = -400; } if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 400) { beat.x = TIMELINE_X + TIMELINE_WIDTH + 400; } // Ensure beatMarker always passes below kickTarget if (kickTarget && kickTarget.parent) { var kickTargetIndex = kickTarget.parent.getChildIndex(kickTarget); var beatIndex = beat.parent.getChildIndex(beat); if (beatIndex > kickTargetIndex) { beat.parent.setChildIndex(beat, kickTargetIndex); } } // Track lastX for miss detection if (typeof beat.lastX === "undefined") { beat.lastX = beat.x; } // Only check for miss if the beat has passed the hit zone (not before) // Also, do not count as miss if the beat has not yet reached the hit zone at least once if (!beat.hit && beat.active && typeof beat.hasReachedHitZone !== "undefined" && beat.hasReachedHitZone && beat.lastX <= HIT_ZONE_X + HIT_WINDOW && beat.x > HIT_ZONE_X + HIT_WINDOW) { // Missed beat.active = false; misses++; // Replace the beat marker asset with missCircle for missed beats var missedAsset = LK.getAsset('missCircle', { anchorX: 0.5, anchorY: 0.5, x: beat.x, // Start from current beat position y: beat.y, alpha: 0.8, width: 60, height: 59.77 }); // Replace the beat's current asset with missCircle if (beat.parent) { // Store parent reference before removing beat var beatParent = beat.parent; // Remove the old beat marker from its parent beatParent.removeChild(beat); // Add the missed asset to the stored parent reference beatParent.addChild(missedAsset); // Update the beat reference to point to the new asset beats[i] = missedAsset; // Copy over important properties missedAsset.beatTime = beat.beatTime; missedAsset.type = beat.type; missedAsset.active = false; missedAsset.hit = false; missedAsset.lastX = beat.x; // Use current position missedAsset.hasReachedHitZone = true; missedAsset.isMissed = true; // Mark as missed for special handling // Destroy the original beat asset beat.destroy(); } // Handle heartshield system if (heartshields > 0) { // Convert leftmost heartshield to heart (heartshields are lost left to right) var shieldIndex = 3 - heartshields; // Get the leftmost heartshield index if (heartIcons[shieldIndex]) { var parent = heartIcons[shieldIndex].parent; if (parent) { parent.removeChild(heartIcons[shieldIndex]); } heartIcons[shieldIndex] = LK.getAsset('heart', { anchorX: 0, anchorY: 0, x: heartStartX + shieldIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[shieldIndex]); } heartshields--; // Lose one heartshield // No miss feedback, sound, or other penalties for heartshield hits } else { // All heartshields are gone, now show miss effects and lose hearts beat.showFeedback('miss'); combo = 0; perfectCombo = 0; // Reset perfect combo on miss // missTxt removed, only hearts for lives comboTxt.setText(''); LK.effects.flashObject(kickTarget, 0xff0000, 200); // Play miss sound LK.getSound('miss').play(); // Show MISS feedback text at hit zone var missText = new Text2('MISS', { size: 60, fill: 0xff0000 }); missText.scale.set(2, 2); missText.anchor.set(0.5, 0.5); missText.x = HIT_ZONE_X; missText.y = HIT_ZONE_Y - 180; game.addChild(missText); tween(missText, { alpha: 0, y: missText.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { missText.destroy(); } }); // Lose a heart and update heart icon (hearts are lost left to right) if (lives > 0) { var lostIndex = MAX_LIVES - lives; // soldan sağa kaybetmek için if (heartIcons[lostIndex]) { var parent = heartIcons[lostIndex].parent; if (parent) { parent.removeChild(heartIcons[lostIndex]); } heartIcons[lostIndex] = LK.getAsset('lostheart', { anchorX: 0, anchorY: 0, x: heartStartX + lostIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[lostIndex]); } lives--; } if (lives <= 0) { gameOver(); } } } // Track if beat has reached the hit zone at least once if (typeof beat.hasReachedHitZone === "undefined") { beat.hasReachedHitZone = false; } if (!beat.hasReachedHitZone && beat.x >= HIT_ZONE_X - HIT_WINDOW) { beat.hasReachedHitZone = true; } // Remove if off screen if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 100) { beat.destroy(); beats.splice(i, 1); } // Update lastX for next frame beat.lastX = beat.x; } }; // --- Input handling --- var isTouching = false; game.down = function (x, y, obj) { if (!gameActive || gamePaused) { return; } // Don't register hits during ready screen if (readyContainer.visible) { return; } // Don't register hits when gamescreen is not visible (e.g., in main menu) if (!gamescreen.visible) { return; } // Only register if touch is in hit zone var dx = x - HIT_ZONE_X; var dy = y - HIT_ZONE_Y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { isTouching = true; // Animate kick target kickTarget.destroy(); kickTarget = LK.getAsset('kickActive', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X, y: HIT_ZONE_Y }); gamescreen.addChild(kickTarget); // Check for beat hit var hit = false; for (var i = 0; i < beats.length; i++) { var beat = beats[i]; if (beat.active && !beat.hit && Math.abs(beat.x - HIT_ZONE_X) < HIT_WINDOW) { // Calculate timing accuracy var distFromCenter = Math.abs(beat.x - HIT_ZONE_X); var feedbackType = ''; var feedbackText = ''; var feedbackColor = 0xffffff; var scoreAdd = 0; if (distFromCenter < 20) { feedbackType = 'perfect'; feedbackText = 'PERFECT!'; feedbackColor = 0xffe600; scoreAdd = 200; perfectCount++; // Increment perfect count perfectCombo++; // Increment consecutive perfect hits if (perfectCombo > maxPerfectCombo) { maxPerfectCombo = perfectCombo; // Track maximum consecutive perfect hits } } else if (distFromCenter < 40) { feedbackType = 'awesome'; feedbackText = 'AWESOME!'; feedbackColor = 0x00e6ff; scoreAdd = 150; perfectCombo = 0; // Reset perfect combo for non-perfect hit } else if (distFromCenter < 70) { feedbackType = 'good'; feedbackText = 'GOOD'; feedbackColor = 0x00ff00; scoreAdd = 100; perfectCombo = 0; // Reset perfect combo for non-perfect hit } else { feedbackType = 'ok'; feedbackText = 'OK'; feedbackColor = 0xcccccc; scoreAdd = 50; perfectCombo = 0; // Reset perfect combo for non-perfect hit } beat.hit = true; beat.active = false; hit = true; score += scoreAdd; combo += 1; if (combo > maxCombo) { maxCombo = combo; // Track maximum combo achieved } LK.setScore(score); scoreTxt.setText(score); comboTxt.setText('Combo: ' + combo); // Show feedback text at hit zone var feedbackTxt = new Text2(feedbackText, { size: 60, fill: feedbackColor }); feedbackTxt.scale.set(2, 2); feedbackTxt.anchor.set(0.5, 0.5); feedbackTxt.x = HIT_ZONE_X; feedbackTxt.y = HIT_ZONE_Y - 180; game.addChild(feedbackTxt); // Show combo multiplier for perfect hits if (feedbackType === 'perfect' && perfectCombo > 1) { var comboMultiplierTxt = new Text2('x' + perfectCombo, { size: 21, fill: 0xff8c00 }); comboMultiplierTxt.scale.set(4, 4); comboMultiplierTxt.anchor.set(0, 0.5); comboMultiplierTxt.x = HIT_ZONE_X + 195; comboMultiplierTxt.y = HIT_ZONE_Y - 220; game.addChild(comboMultiplierTxt); tween(comboMultiplierTxt, { alpha: 0, y: comboMultiplierTxt.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { comboMultiplierTxt.destroy(); } }); } tween(feedbackTxt, { alpha: 0, y: feedbackTxt.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { feedbackTxt.destroy(); } }); beat.showFeedback('good'); // Play sounds based on selection - all selected sounds if (selectedKickSound !== '') { LK.getSound(selectedKickSound).play(); } if (selectedHihatSound !== '') { LK.getSound(selectedHihatSound).play(); } if (selectedSnareClapSound !== '') { LK.getSound(selectedSnareClapSound).play(); } if (selectedPercussionSound !== '') { LK.getSound(selectedPercussionSound).play(); } LK.effects.flashObject(kickTarget, feedbackColor, 200); // Replace the beat's current asset based on timing quality while maintaining position and movement var newAssetId; var tintColor = 0xffffff; if (feedbackType === 'perfect') { // Perfect hits: perfectcircle asset newAssetId = 'perfectcircle'; tintColor = 0xffffff; } else if (feedbackType === 'awesome' || feedbackType === 'good' || feedbackType === 'ok') { // Good/awesome/ok hits: green kickActive newAssetId = 'kickActive'; tintColor = 0x00ff00; } // Replace the beat's visual asset while maintaining its position and movement if (beat.parent) { // Store parent and current position var beatParent = beat.parent; var currentX = beat.x; var currentY = beat.y; // Remove the old beat marker from its parent beatParent.removeChild(beat); // Create new asset with current position var newBeatAsset = LK.getAsset(newAssetId, { anchorX: 0.5, anchorY: 0.5, x: currentX, y: currentY, tint: tintColor, width: 60, height: 59.77 }); // Add the new asset to the stored parent reference beatParent.addChild(newBeatAsset); // Update the beat reference to point to the new asset beats[i] = newBeatAsset; // Copy over important properties newBeatAsset.beatTime = beat.beatTime; newBeatAsset.type = beat.type; newBeatAsset.active = false; // Mark as inactive since it was hit newBeatAsset.hit = true; newBeatAsset.lastX = currentX; newBeatAsset.hasReachedHitZone = true; // Destroy the original beat asset beat.destroy(); } // BPM remains constant at settings value - no difficulty increase during gameplay break; } } if (!hit) { // Missed (tapped at wrong time) misses++; combo = 0; perfectCombo = 0; // Reset perfect combo on wrong time // missTxt removed, only hearts for lives comboTxt.setText(''); // Play sounds based on selection - all selected sounds if (selectedKickSound !== '') { LK.getSound(selectedKickSound).play(); } if (selectedHihatSound !== '') { LK.getSound(selectedHihatSound).play(); } if (selectedSnareClapSound !== '') { LK.getSound(selectedSnareClapSound).play(); } if (selectedPercussionSound !== '') { LK.getSound(selectedPercussionSound).play(); } LK.effects.flashObject(kickTarget, 0xff0000, 200); // Show WRONG TIME feedback text at hit zone var wrongTimeText = new Text2('WRONG TIME', { size: 60, fill: 0xff4500 }); wrongTimeText.scale.set(2, 2); wrongTimeText.anchor.set(0.5, 0.5); wrongTimeText.x = HIT_ZONE_X; wrongTimeText.y = HIT_ZONE_Y - 180; game.addChild(wrongTimeText); tween(wrongTimeText, { alpha: 0, y: wrongTimeText.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { wrongTimeText.destroy(); } }); // Only lose a life if countdown is not visible if (!countdownContainer.visible) { // Handle heartshield system for wrong time hits if (heartshields > 0) { // Convert leftmost heartshield to heart (heartshields are lost left to right) var shieldIndex = 3 - heartshields; // Get the leftmost heartshield index if (heartIcons[shieldIndex]) { var parent = heartIcons[shieldIndex].parent; if (parent) { parent.removeChild(heartIcons[shieldIndex]); } heartIcons[shieldIndex] = LK.getAsset('heart', { anchorX: 0, anchorY: 0, x: heartStartX + shieldIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[shieldIndex]); } heartshields--; // Lose one heartshield // No miss feedback, sound, or other penalties for heartshield hits } else { // All heartshields are gone, now lose hearts // Lose a life and update heart icon if (lives > 0) { var lostIndex = MAX_LIVES - lives; // soldan sağa kaybetmek için if (heartIcons[lostIndex]) { var parent = heartIcons[lostIndex].parent; if (parent) { parent.removeChild(heartIcons[lostIndex]); } heartIcons[lostIndex] = LK.getAsset('lostheart', { anchorX: 0, anchorY: 0, x: heartStartX + lostIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[lostIndex]); } lives--; } if (lives <= 0) { gameOver(); } } } } } }; game.up = function (x, y, obj) { isTouching = false; // Restore kick target kickTarget.destroy(); kickTarget = LK.getAsset('kickTarget', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X, y: HIT_ZONE_Y }); gamescreen.addChild(kickTarget); }; // --- Game over --- function gameOver() { gameActive = false; LK.effects.flashScreen(0xff0000, 800); showDefeatScreen(); LK.stopMusic(); } // --- Glow Effect for Purple Elements --- function addGlowEffect(element, glowColor, intensity) { if (!element || !element.visible) { return; } // Create glow animation using tint and alpha var originalTint = element.tint || 0xffffff; var glowTint = glowColor || 0xaa00ff; // Default purple glow var glowIntensity = intensity || 0.3; // Pulsing glow effect function startGlow() { tween(element, { alpha: 1 - glowIntensity }, { duration: 800, easing: tween.easeInOut, yoyo: true, repeat: -1 // Infinite repeat }); } startGlow(); } // Apply glow to purple elements function applyGlowToVioletElements() { // Apply glow to kick target (blue/violet color) if (kickTarget) { addGlowEffect(kickTarget, 0x8a2be2, 0.2); } // Apply glow to main menu buttons (purple/violet elements) if (startBtn) { addGlowEffect(startBtn, 0x8a2be2, 0.15); } if (settingsBtn) { addGlowEffect(settingsBtn, 0x8a2be2, 0.15); } if (creditsBtn) { addGlowEffect(creditsBtn, 0x8a2be2, 0.15); } if (supportBtn) { addGlowEffect(supportBtn, 0x8a2be2, 0.15); } // Apply glow to BPM circle container if it exists if (typeof bpmCircleContainer !== 'undefined' && bpmCircleContainer) { addGlowEffect(bpmCircleContainer, 0x8a2be2, 0.2); } } // Initialize glow effects applyGlowToVioletElements(); // --- Win condition --- function checkWin() { if (score >= 5000) { gameActive = false; LK.effects.flashScreen(0x00ff00, 800); LK.showYouWin(); LK.stopMusic(); } } // --- Defeat Screen Implementation --- var defeatContainer = new Container(); defeatContainer.visible = false; game.addChild(defeatContainer); var maxCombo = 0; // Track maximum combo achieved var perfectCount = 0; // Track number of perfect hits var perfectCombo = 0; // Track current consecutive perfect hits var maxPerfectCombo = 0; // Track maximum consecutive perfect hits var lastBpmUpgradeScore = 0; // Track last score where BPM was upgraded var gamePaused = false; // Track if game is paused var pauseContainer = new Container(); pauseContainer.visible = false; game.addChild(pauseContainer); function showDefeatScreen() { defeatContainer.visible = true; defeatContainer.removeChildren(); gamescreen.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; // Add defeat background using defeatbg asset var defeatBg = LK.getAsset('defeatbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); defeatContainer.addChild(defeatBg); // Add blurred background behind statistics var blurredBg = LK.getAsset('blurred', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1235, scaleX: 1.35, scaleY: 1.35, alpha: 1 }); defeatContainer.addChild(blurredBg); // Game Over title using gameover asset var gameOverAsset = LK.getAsset('gameover', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 600, scaleX: 0.5, scaleY: 0.5 }); defeatContainer.addChild(gameOverAsset); // Statistics arranged vertically with 5px spacing var statsStartY = 990; var statsSpacing = 5; var currentY = statsStartY; // Score display var finalScoreTxt = new Text2("Score: " + score, { size: 54, fill: 0xffffff }); finalScoreTxt.scale.set(2, 2); finalScoreTxt.anchor.set(0, 0.5); finalScoreTxt.x = 2048 / 2 - 550; finalScoreTxt.y = currentY; defeatContainer.addChild(finalScoreTxt); currentY += finalScoreTxt.height + statsSpacing; // Max combo display var maxComboTxt = new Text2("Max Combo: " + maxCombo, { size: 54, fill: 0x00ff00 }); maxComboTxt.scale.set(2, 2); maxComboTxt.anchor.set(0, 0.5); maxComboTxt.x = 2048 / 2 - 550; maxComboTxt.y = currentY; defeatContainer.addChild(maxComboTxt); currentY += maxComboTxt.height + statsSpacing; // Perfect count display var perfectCountTxt = new Text2("Perfect: " + perfectCount, { size: 54, fill: 0xffe600 }); perfectCountTxt.scale.set(2, 2); perfectCountTxt.anchor.set(0, 0.5); perfectCountTxt.x = 2048 / 2 - 550; perfectCountTxt.y = currentY; defeatContainer.addChild(perfectCountTxt); currentY += perfectCountTxt.height + statsSpacing; // Max perfect combo display (orange color) var maxPerfectComboTxt = new Text2("Max Perfect Combo: " + maxPerfectCombo, { size: 54, fill: 0xff8c00 }); maxPerfectComboTxt.scale.set(2, 2); maxPerfectComboTxt.anchor.set(0, 0.5); maxPerfectComboTxt.x = 2048 / 2 - 550; maxPerfectComboTxt.y = currentY; defeatContainer.addChild(maxPerfectComboTxt); // Calculate button positions var defeatButtonStartY = 1700; var defeatButtonHeight = 289; var defeatButtonSpacing = 20; // Tekrar Oyna (Play Again) Button var playAgainBtn = LK.getAsset('tryagain', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: defeatButtonStartY, scaleX: 5, scaleY: 5 }); defeatContainer.addChild(playAgainBtn); // Ana Menü (Main Menu) Button var mainMenuBtn = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: defeatButtonStartY + defeatButtonHeight + defeatButtonSpacing, scaleX: 5, scaleY: 5 }); defeatContainer.addChild(mainMenuBtn); // Button event handlers playAgainBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation tween(playAgainBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(playAgainBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { defeatContainer.visible = false; maxCombo = 0; // Reset max combo perfectCount = 0; // Reset perfect count maxPerfectCombo = 0; // Reset max perfect combo // Continue from current level - if tutorial not completed, restart tutorial if (!tutorialCompleted) { currentLevel = 1; storage.currentLevel = currentLevel; } showReadyScreen(function () { showCountdown(function () { startGame(); }); }); } }); } }); }; mainMenuBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation tween(mainMenuBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(mainMenuBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { defeatContainer.visible = false; maxCombo = 0; // Reset max combo perfectCount = 0; // Reset perfect count maxPerfectCombo = 0; // Reset max perfect combo mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Stop current music and play menu music with a small delay to ensure proper playback (only if not muted) LK.stopMusic(); if (!musicMuted) { LK.setTimeout(function () { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); }, 100); } } }); } }); }; } // --- Restart on game over --- LK.on('gameover', function () { showDefeatScreen(); }); LK.on('youwin', function () { startGame(); });
===================================================================
--- original.js
+++ change.js
@@ -2083,9 +2083,9 @@
songNameTxt.visible = true;
if (bpmDisplayTxt) {
// Show the actual BPM of the currently playing song, not the settings BPM
var currentPlayingBpm = isTutorial ? 60 : bpm; // Tutorial always shows 60 BPM
- bpmDisplayTxt.setText(currentPlayingBpm + ' BPM');
+ bpmDisplayTxt.setText('60 BPM');
}
// missTxt removed, only hearts for lives
gamescreen.visible = true;
mainMenuContainer.visible = false;
yellow ball pixel-art. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A minimalist horizontal pixel art timeline bar, 2048x40 resolution, designed for a lo-fi rhythm pixel game. The bar is sleek and thin, with subtle purple and violet tones, no numbers or markers. The style is consistent with a DJ-themed interface — soft glowing edges, pixel-perfect precision, and no background (transparent). The bar should feel modern yet retro, fitting into a dreamy neon-lit rhythm game UI.. In-Game asset. 2d. High contrast. No shadows
orange
Realistic comic book style, 2048x2048. Split-frame design: left half shows Noah mid-performance dissolving into shadows; right half shows a crumpled newspaper with the headline "DJ NOAH GONE?". Desaturated colors, somber mood. Add a transparent narrator box at the top-center with the text: "Then, silence." in comic font.. In-Game asset. 2d. High contrast. No shadows
Add white to the edges for a photo paper look
Add white to the edges for a photo paper look.
Add white to the edges for a photo paper look.
Add white to the edges for a photo paper look.
A cinematic comic book-style vertical panel, 2048x2732. Inside a moody, dimly lit underground bar, a lone DJ (Noah) stands behind a glowing DJ booth, facing forward but with his head slightly bowed. Sparse silhouettes of a few customers sit at shadowy tables, blurred by distance and smoke. Purple and blue neon lights gently illuminate the space. The atmosphere feels still, as if something important just began. A faint spotlight outlines Noah's figure. This scene conveys calm before momentum, subtle drama and emotional depth. No text or narration in the image.. In-Game asset. 2d. High contrast. No shadows
A rectangular comic-style narrator box placed near the bottom of a vertical panel (2048x2732). Dark purple background with rounded corners, white border. Inside, centered white text in clean comic font reads: "The lights may be dim, but the spark is back. Tonight, Noah spins not just music — but a second chance.". In-Game asset. 2d. High contrast. No shadows
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: A rainy city street at night, neon signs reflecting on wet pavement, Noah stands under a flickering bar sign, hood up, looking uncertain. Narrator box at the top reads: “The rain didn’t stop him — not tonight.”. In-Game asset. 2d. High contrast. No shadows
yazıyı güncelle "The place smelled of sweat, smoke, and forgotten dreams."
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: Close-up of a middle-aged woman with bold makeup, cigarette in hand, leaning over the bar. Her name tag reads “Manager.” Narrator box at the top reads: “She didn’t ask questions — just handed him the schedule.”. In-Game asset. 2d. High contrast. No shadows
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: A dusty DJ booth, some lights flickering, the equipment old but intact. Noah places his hand on the mixer with a hint of reverence. Narrator box at the top reads: “It wasn’t much — but it was a start.”. In-Game asset. 2d. High contrast. No shadows
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: Over-the-shoulder view of Noah at the booth, the crowd in the bar small but curious. One hand on the vinyl, lights begin to flicker rhythmically. Narrator box at the top reads: “And just like that, the night began to breathe.”. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The scene depicts a moody DJ booth inside a small urban apartment at night, overlooking a vibrant neon-lit city through large glass windows. The DJ setup is stylish but lived-in — with vinyl records, scattered sticky notes, a glowing laptop screen, and faint light reflections on the desk. Outside, rain trickles down the window, and distant buildings flicker with purples, blues, and pinks. The entire environment feels reflective, lo-fi, and intimate — as if the city hums with a quiet rhythm. No characters, no text — just a story-rich, immersive menu backdrop.. In-Game asset. 2d. High contrast. No shadows
Write SETTINGS.
write CREDITS.
write SUPPORT US.
A bold, comic-style game title reading 'DJ NOAH' in large uppercase letters. The text features a deep electric purple gradient with sharp highlights and bold white outlines, styled like a dramatic comic book title splash. Subtle halftone textures and a radial glow enhance its dynamic look. Behind the text, abstract comic-style sound wave elements stretch outward in purple and magenta hues. The composition is punchy, modern, and dramatic — designed as a central UI graphic with transparent background for in-game use. 2D, high contrast, no drop shadow.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The scene shows a quiet corner of the same small urban apartment, viewed from a different angle than the main menu. This part of the room features a vintage sound mixing rack, ambient LED lights softly glowing in purple and blue tones, and a corkboard on the wall filled with pinned notes, small polaroid photos, and scribbled music ideas. An open sketchbook with waveform doodles lies on a side table, and worn-out headphones rest beside it. A faint rain still falls outside the partially visible window, casting reflections on the floor. The overall mood remains lo-fi, introspective, and musically charged. No characters, no text — just a lived-in, creative space for the settings menu backdrop.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The setting reveals a nostalgic, dimly lit corner of the same urban apartment from the previous scenes. This part of the room showcases a personal wall collage filled with printed photographs, old event posters, and handwritten notes — all pinned with care and glowing softly under a small warm desk lamp. A wooden shelf holds vinyl covers, retro magazines, and a framed photo of a live DJ performance. On a nearby desk, a coffee mug steams next to an analog tape recorder, gently lit by the reflection of neon city lights from outside the window. The atmosphere is rich with memories, inspiration, and creative legacy. No characters, no text — just a sentimental space for the credits menu backdrop.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The scene takes place in another corner of the same urban apartment — this time showing an open space near a shelf filled with music memorabilia, fan-made items, and a pinboard with stickers like “Support Local DJs”, “Upvote Love”, and pixel-style hearts. A tablet on the desk is open to a game page with a “Leave a review” banner. There’s a soft spotlight on a coffee mug with “Thank you” written on it and glowing music notes floating around it. The city lights outside still shine in purples and blues, but this part of the room feels more hopeful and connected. The vibe is warm, welcoming, and subtly celebratory. No characters or text. In-game menu background, consistent with main menu themes.. In-Game asset. 2d. High contrast. No shadows
tahta texture
turuncu-kahverengi tonları
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
kalkan mavi tonlarında olsun
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
gri
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
write MAIN MENU
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, purple and blue tones, soft gradients
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
write RESUME
write RESTART
kick
Sound effect
miss
Sound effect
menumusic
Music
75BPMElectricDreams
Music
80BPMNeonNights
Music
85BPMCyberFlow
Music
90BPMDigitalPulse
Music
95BPMFutureBass
Music
100BPMSynthWave
Music
40BPMDeepVibes
Music
45BPMChillWave
Music
50BPMSlowMotion
Music
55BPMDreamscape
Music
60BPMLowKey
Music
65BPMRelaxed
Music
70BPMCalmVibes
Music
105BPMHyperDrive
Music
110BPMTechnoRush
Music
115BPMHighEnergy
Music
120BPMMaximum
Music
scratch
Sound effect
buttonclick
Sound effect
kick2
Sound effect
kick3
Sound effect
kick4
Sound effect
hihat
Sound effect
hihat2
Sound effect
hihat3
Sound effect
hihat4
Sound effect
snareclap
Sound effect
snareclap2
Sound effect
snareclap3
Sound effect
snareclap4
Sound effect
percussion
Sound effect
percussion2
Sound effect
percussion3
Sound effect
percussion4
Sound effect
thanks
Sound effect
echoesofthepast
Sound effect
chapter1music
Music
paperswosh
Sound effect
ch1voice1
Sound effect
ch1voice2
Sound effect
nightshift
Sound effect
chapter2music
Music