// 🎬 INTRO SEQUENCE - 60-SECOND EPIC CINEMATIC // "From Colors to Darkness" - Complete Story // Created: January 10, 2026 // Style: Style 32 Dark-Chibi Noir + Polaroid + VHS // Voices: Kai (Rok) + Ana (Petra) + Gronk (Rok deep) class IntroScene extends Phaser.Scene { constructor() { super({ key: 'IntroScene' }); this.skipEnabled = false; this.currentPhase = 0; this.skipPrompt = null; this.currentPolaroid = null; this.currentText = null; this.vhsNoise = null; this.scanlines = null; this.ambientAudio = null; this.currentVoice = null; } preload() { console.log('🎬 IntroScene: Loading EPIC 60s assets...'); // Base path for intro shots const introPath = 'assets/references/intro_shots/'; // ALL 20 INTRO SHOTS this.load.image('intro_family_portrait', introPath + 'family_portrait_punk_complete.png'); this.load.image('intro_otac_longboard', introPath + 'otac_longboard_pier.png'); this.load.image('intro_kai_dreads', introPath + 'kai_first_dreads_family.png'); this.load.image('intro_ana_barbershop', introPath + 'ana_barbershop_dreads.png'); this.load.image('intro_birthday_cake', introPath + 'birthday_cake_rd.png'); this.load.image('intro_virus', introPath + 'virus_xnoir_microscope.png'); this.load.image('intro_chaos', introPath + 'chaos_streets_apocalypse.png'); this.load.image('intro_zombies', introPath + 'zombie_silhouettes_panic.png'); this.load.image('intro_parents_ghosts', introPath + 'parents_transparent_ghosts.png'); this.load.image('intro_ana_taken', introPath + 'ana_taken_military.png'); this.load.image('intro_kai_alone', introPath + 'kai_alone_basement.png'); this.load.image('intro_kai_young', introPath + 'kai_young_timelapse.png'); this.load.image('intro_kai_adult', introPath + 'kai_adult_35_timelapse.png'); this.load.image('intro_kai_elder', introPath + 'kai_elder_50_timelapse.png'); this.load.image('intro_ana_memory', introPath + 'ana_memory_flash_purple.png'); this.load.image('intro_bedroom', introPath + 'kai_bedroom_wakeup.png'); this.load.image('intro_gronk', introPath + 'gronk_doorway_silhouette.png'); this.load.image('intro_twins_childhood', introPath + 'kai_ana_twins_childhood.png'); // 🎵 AMBIENT MUSIC this.loadAudioSafe('noir_ambience', 'assets/audio/ambient/noir_ambience.mp3'); // 🎤 KAI VOICES (12 total - ENGLISH) this.loadAudioSafe('kai_01', 'assets/audio/voiceover/kai_en_01.mp3'); this.loadAudioSafe('kai_02', 'assets/audio/voiceover/kai_en_02.mp3'); this.loadAudioSafe('kai_03', 'assets/audio/voiceover/kai_en_03.mp3'); this.loadAudioSafe('kai_04', 'assets/audio/voiceover/kai_en_04.mp3'); this.loadAudioSafe('kai_05', 'assets/audio/voiceover/kai_en_05.mp3'); this.loadAudioSafe('kai_06', 'assets/audio/voiceover/kai_en_06.mp3'); this.loadAudioSafe('kai_07', 'assets/audio/voiceover/kai_en_07.mp3'); this.loadAudioSafe('kai_08', 'assets/audio/voiceover/kai_en_08.mp3'); this.loadAudioSafe('kai_09', 'assets/audio/voiceover/kai_en_09.mp3'); this.loadAudioSafe('kai_10', 'assets/audio/voiceover/kai_en_10.mp3'); this.loadAudioSafe('kai_11', 'assets/audio/voiceover/kai_en_11.mp3'); this.loadAudioSafe('kai_12', 'assets/audio/voiceover/kai_en_12.mp3'); // 🎤 ANA VOICES (8 total - ENGLISH) this.loadAudioSafe('ana_01', 'assets/audio/voiceover/ana_en_01.mp3'); this.loadAudioSafe('ana_02', 'assets/audio/voiceover/ana_en_02.mp3'); this.loadAudioSafe('ana_03', 'assets/audio/voiceover/ana_en_03.mp3'); this.loadAudioSafe('ana_04', 'assets/audio/voiceover/ana_en_04.mp3'); this.loadAudioSafe('ana_05', 'assets/audio/voiceover/ana_en_05.mp3'); this.loadAudioSafe('ana_06', 'assets/audio/voiceover/ana_en_06.mp3'); this.loadAudioSafe('ana_07', 'assets/audio/voiceover/ana_en_07.mp3'); this.loadAudioSafe('ana_08', 'assets/audio/voiceover/ana_en_08.mp3'); // 🎤 GRONK VOICE (ENGLISH - Deep UK) this.loadAudioSafe('gronk_01', 'assets/audio/voiceover/gronk_en_01.mp3'); } loadAudioSafe(key, path) { try { this.load.audio(key, path); } catch (error) { console.warn(`⚠️ Audio skipped: ${key}`); } } create() { console.log('🎬 IntroScene: Starting 60-SECOND EPIC...'); // Black background this.cameras.main.setBackgroundColor('#000000'); // 🎵 Start ambient audio this.startAmbientAudio(); // 📺 Create VHS overlay effects this.createVHSEffects(); // Setup input for skip this.setupSkipControls(); // Enable skip after 5 seconds this.time.delayedCall(5000, () => { this.skipEnabled = true; this.showSkipPrompt(); }); // Start Phase 1 this.playPhase1HappyChildhood(); } startAmbientAudio() { try { if (this.cache.audio.exists('noir_ambience')) { this.ambientAudio = this.sound.add('noir_ambience', { volume: 0.2, loop: true }); this.ambientAudio.play(); } } catch (e) { console.warn('⚠️ Ambient audio not available'); } } createVHSEffects() { const width = this.cameras.main.width; const height = this.cameras.main.height; // VHS Scanlines this.scanlines = this.add.graphics(); this.scanlines.setDepth(900); for (let y = 0; y < height; y += 4) { this.scanlines.fillStyle(0x000000, 0.15); this.scanlines.fillRect(0, y, width, 2); } // VHS Noise overlay this.vhsNoise = this.add.rectangle( width / 2, height / 2, width, height, 0xffffff, 0.03 ); this.vhsNoise.setDepth(901); // Animate noise this.tweens.add({ targets: this.vhsNoise, alpha: { from: 0.01, to: 0.05 }, duration: 100, yoyo: true, repeat: -1 }); } setupSkipControls() { this.input.keyboard.on('keydown-X', () => this.skipIntro()); this.input.keyboard.on('keydown-SPACE', () => this.skipIntro()); this.input.on('pointerdown', () => { if (this.skipEnabled) { this.skipIntro(); } }); } showSkipPrompt() { if (this.skipPrompt) return; const width = this.cameras.main.width; const height = this.cameras.main.height; this.skipPrompt = this.add.text( width - 20, height - 20, '[Hold X to face reality]', { fontFamily: 'Courier New', fontSize: '14px', fill: '#00ffff', stroke: '#000000', strokeThickness: 2 } ); this.skipPrompt.setOrigin(1, 1); this.skipPrompt.setAlpha(0.7); this.skipPrompt.setDepth(1000); this.tweens.add({ targets: this.skipPrompt, alpha: { from: 0.7, to: 1.0 }, duration: 800, yoyo: true, repeat: -1 }); } skipIntro() { if (!this.skipEnabled) return; console.log('⏭️ IntroScene: Skipped by user'); // Stop all if (this.ambientAudio) this.ambientAudio.stop(); if (this.currentVoice) this.currentVoice.stop(); this.tweens.killAll(); this.time.removeAllEvents(); // Fade to GameScene this.cameras.main.fadeOut(500, 0, 0, 0); this.cameras.main.once('camerafadeoutcomplete', () => { this.scene.start('StoryScene'); // Main Menu }); } playVoice(key, subtitleText) { try { if (this.cache.audio.exists(key)) { if (this.currentVoice) this.currentVoice.stop(); this.currentVoice = this.sound.add(key, { volume: 0.8 }); this.currentVoice.play(); } } catch (e) { console.warn(`⚠️ Voice ${key} not available`); } // Show subtitle at bottom if (subtitleText) { this.showSubtitle(subtitleText, 2800); // 2.8s duration (slightly less than 3s shot) } } showSubtitle(text, duration = 3000) { // Remove previous subtitle if (this.currentText) { this.currentText.destroy(); } const width = this.cameras.main.width; const height = this.cameras.main.height; // Create subtitle at bottom this.currentText = this.add.text( width / 2, height - 100, // 100px from bottom text, { fontFamily: 'Courier New', fontSize: '20px', fill: '#ffffff', stroke: '#000000', strokeThickness: 3, shadow: { offsetX: 2, offsetY: 2, color: '#00ffff', blur: 8, stroke: false, fill: true }, align: 'center', wordWrap: { width: width - 100 } } ); this.currentText.setOrigin(0.5); this.currentText.setAlpha(0); this.currentText.setDepth(950); // Below VHS but above Polaroid // Fade in this.tweens.add({ targets: this.currentText, alpha: 1, duration: 500, ease: 'Power2.easeOut' }); // Fade out before end this.time.delayedCall(duration - 300, () => { if (this.currentText) { this.tweens.add({ targets: this.currentText, alpha: 0, duration: 300, onComplete: () => { if (this.currentText) this.currentText.destroy(); } }); } }); } // ═══════════════════════════════════════════════════════════ // PHASE 1: HAPPY MEMORIES (0-15s) - REDESIGNED! // ═══════════════════════════════════════════════════════════ playPhase1HappyChildhood() { console.log('🎬 Phase 1: Happy Memories (0-15s) - NEW SEQUENCE'); this.currentPhase = 1; // Shot 1 (0-2.5s): Kai + Dad Longboard this.showShot('intro_otac_longboard', 0, 2500, { warm: true }); this.time.delayedCall(100, () => this.playVoice('kai_01', "Dad and I. Before everything changed.")); // Shot 2 (2.5-5s): Barbershop - Him (dreads) + Her (coloring) this.showShot('intro_ana_barbershop', 2500, 5000, { warm: true }); this.time.delayedCall(2600, () => this.playVoice('ana_01', "Getting ready. We always did things together.")); // Shot 3 (5-7.5s): Birthday Cake - "HERE WE WERE STILL HAPPY" this.showShot('intro_birthday_cake', 5000, 7500, { warm: true }); this.time.delayedCall(5100, () => this.playVoice('kai_02', "Here we were still happy. Still a family.")); // Shot 4 (7.5-10s): Family Portrait this.showShot('intro_family_portrait', 7500, 10000, { warm: true }); this.time.delayedCall(7600, () => this.playVoice('ana_02', "All of us. Together. Perfect.")); // Shot 5 (10-12.5s): Kai + Ana Holding Hands as Kids this.showShot('intro_twins_childhood', 10000, 12500, { warm: true }); this.time.delayedCall(10100, () => this.playVoice('kai_03', "We were always two. Inseparable.")); // Shot 6 (12.5-15s): Kai's Bedroom this.showShot('intro_bedroom', 12500, 15000, { warm: true }); this.time.delayedCall(12600, () => this.playVoice('ana_03', "Our room. Our sanctuary.")); // Transition to Phase 2 this.time.delayedCall(15000, () => this.playPhase2TheVirus()); } // ═══════════════════════════════════════════════════════════ // PHASE 2: THE VIRUS (15-30s) // ═══════════════════════════════════════════════════════════ playPhase2TheVirus() { console.log('🎬 Phase 2: The Virus (15-30s)'); this.currentPhase = 2; // Shot 7 (15-17.5s): Virus this.showShot('intro_virus', 15000, 17500, { toxic: true, glitch: true }); this.time.delayedCall(15100, () => this.playVoice('kai_04', "Then came X-Noir. The virus.")); // Shot 8 (17.5-20s): Chaos this.showShot('intro_chaos', 17500, 20000, { red: true, glitch: true }); this.time.delayedCall(17600, () => this.playVoice('ana_04', "Everyone changed. Streets burned.")); this.cameras.main.shake(2500, 0.005); // Shot 9 (20-22.5s): Zombies this.showShot('intro_zombies', 20000, 22500, { red: true, strobe: true }); this.time.delayedCall(20100, () => this.playVoice('kai_05', "Friends became zombies.")); // Shot 10 (22.5-25s): Parents Ghosts this.showShot('intro_parents_ghosts', 22500, 25000, { fadeIn: true }); this.time.delayedCall(22600, () => this.playVoice('ana_05', "Our parents fought... and lost.")); // Shot 11 (25-30s): Ana Taken this.showShot('intro_ana_taken', 25000, 30000, { red: true }); this.time.delayedCall(25100, () => this.playVoice('ana_06', "KAI! DON'T FORGET ME!")); // Transition to Phase 3 this.time.delayedCall(30000, () => this.playPhase3TheAmnesia()); } // ═══════════════════════════════════════════════════════════ // PHASE 3: AMNESIA & ANA MEMORY (30-60s) - NO AGING SPOILERS! // ═══════════════════════════════════════════════════════════ playPhase3TheAmnesia() { console.log('🎬 Phase 3: Amnesia & Ana Memory (30-60s)'); this.currentPhase = 3; // Shot 12 (30-35s): BLACK SCREEN - "I have no memory" const width = this.cameras.main.width; const height = this.cameras.main.height; const blackScreen = this.add.rectangle(width / 2, height / 2, width, height, 0x000000); blackScreen.setAlpha(0); blackScreen.setDepth(50); this.tweens.add({ targets: blackScreen, alpha: 1, duration: 1000 }); this.time.delayedCall(30500, () => this.playVoice('kai_06', "I have no memory. Everything is... gone.")); this.time.delayedCall(33000, () => this.playVoice('kai_07', "They say I'm fourteen. But I don't remember... anything.")); // Shot 13 (35-40s): Kai Alone in Basement this.time.delayedCall(35000, () => { this.tweens.add({ targets: blackScreen, alpha: 0, duration: 1000, onComplete: () => blackScreen.destroy() }); }); this.showShot('intro_kai_alone', 35000, 40000, { fadeIn: true }); this.time.delayedCall(35500, () => this.playVoice('kai_08', "Alone. In darkness. With only... this.")); // Shot 14 (40-50s): Ana Memory - LAST TIME THEY SAW EACH OTHER this.showShot('intro_ana_memory', 40000, 50000, { fadeIn: true }); this.time.delayedCall(40500, () => this.playVoice('ana_07', "Her face. The only thing I remember.")); this.time.delayedCall(43500, () => this.playVoice('kai_09', "Ana. My sister. My twin. The last thing I saw... before everything went dark.")); // Shot 15 (50-55s): Gronk Arrival this.showShot('intro_gronk', 50000, 55000, { fadeIn: true }); this.time.delayedCall(50500, () => this.playVoice('gronk_01', "Finally awake, old man. Your mission awaits.")); // Shot 16 (55-60s): Ana Photo + FINAL DETERMINATION this.showShot('intro_ana_memory', 55000, 60000, { fadeIn: true }); this.time.delayedCall(55500, () => this.playVoice('kai_11', "I must find her.")); this.time.delayedCall(57500, () => this.playVoice('kai_12', "...even if it takes my entire life.")); // Transition to Main Menu this.time.delayedCall(60000, () => { console.log('🎬 IntroScene: EPIC COMPLETE! Starting menu...'); if (this.ambientAudio) this.ambientAudio.stop(); this.cameras.main.fadeOut(1000, 0, 0, 0); this.cameras.main.once('camerafadeoutcomplete', () => { this.scene.start('StoryScene'); // Main Menu }); }); } // Phase 4 removed - integrated into Phase 3! showShot(imageKey, startTime, endTime, options = {}) { const width = this.cameras.main.width; const height = this.cameras.main.height; const duration = endTime - startTime; this.time.delayedCall(startTime, () => { // CROSSFADE: Store old reference for simultaneous fade const oldPolaroid = this.currentPolaroid; // (Old will fade out WHILE new fades in - see below) // Create photo image (65% of screen) const photo = this.add.image(width / 2, height / 2 - 30, imageKey); photo.setAlpha(0); photo.setDepth(20); // Scale to 65% of screen size const targetSize = Math.min(width, height) * 0.65; const photoScale = targetSize / Math.max(photo.width, photo.height); photo.setScale(photoScale); // Create Polaroid white frame const frameWidth = photo.displayWidth + 40; const frameHeight = photo.displayHeight + 80; const frame = this.add.graphics(); frame.setDepth(19); frame.setAlpha(0); // Dirty white Polaroid background frame.fillStyle(0xf5f5dc, 1); frame.fillRect( (width - frameWidth) / 2, (height - frameHeight) / 2 - 30, frameWidth, frameHeight ); // Add dirt/grain texture for (let i = 0; i < 50; i++) { const x = (width - frameWidth) / 2 + Math.random() * frameWidth; const y = (height - frameHeight) / 2 - 30 + Math.random() * frameHeight; frame.fillStyle(0xccccbb, 0.3); frame.fillCircle(x, y, 1); } this.currentPolaroid = { photo, frame }; // 🌊 FLOATING ANIMATION this.tweens.add({ targets: [photo, frame], y: '-=5', duration: 2000, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' }); // Apply visual effects if (options.warm) { photo.setTint(0xffddaa); } if (options.red) { photo.setTint(0xff6666); } if (options.toxic) { photo.setTint(0x66ff66); } // CROSSFADE: NEW fades IN (800ms) this.tweens.add({ targets: [photo, frame], alpha: 1, duration: 800, ease: 'Power2.easeOut' }); // SIMULTANEOUSLY: OLD fades OUT (same 800ms) if (oldPolaroid) { this.tweens.add({ targets: [oldPolaroid.photo, oldPolaroid.frame], alpha: 0, duration: 800, // Same duration = perfect crossfade! ease: 'Power2.easeIn', onComplete: () => { oldPolaroid.frame.destroy(); oldPolaroid.photo.destroy(); } }); } // OLD GLITCH-OUT removed - crossfade handles transitions now! if (options.glitch) { this.tweens.add({ targets: [photo, frame], x: { from: width / 2 - 5, to: width / 2 + 5 }, duration: 100, repeat: duration / 100, yoyo: true }); } if (options.strobe) { this.tweens.add({ targets: photo, alpha: { from: 0.7, to: 1.0 }, duration: 150, repeat: duration / 150, yoyo: true }); } }); } shutdown() { if (this.ambientAudio) this.ambientAudio.stop(); if (this.currentVoice) this.currentVoice.stop(); if (this.skipPrompt) this.skipPrompt.destroy(); if (this.currentPolaroid) { this.currentPolaroid.frame.destroy(); this.currentPolaroid.photo.destroy(); } this.tweens.killAll(); this.time.removeAllEvents(); } }