// 🎬 INTRO SEQUENCE - VERSION B (30-45 seconds Fast-Cut) // "From Colors to Darkness" - Kai's Amnesia Awakening // Created: January 10, 2026 // Style: Style 32 Dark-Chibi Noir with ADHD energy class IntroScene extends Phaser.Scene { constructor() { super({ key: 'IntroScene' }); this.skipEnabled = false; this.currentPhase = 0; this.skipPrompt = null; this.currentShot = null; this.currentPolaroid = null; this.currentText = null; this.vhsNoise = null; this.scanlines = null; this.ambientAudio = null; this.projectorAudio = null; } preload() { console.log('🎬 IntroScene: Loading assets...'); // Base path for intro shots const introPath = 'assets/references/intro_shots/'; // PHASE 1: HAPPY FAMILY (5 shots) this.load.image('intro_otac_longboard', introPath + 'otac_longboard_pier.png'); this.load.image('intro_ana_barbershop', introPath + 'ana_barbershop_dreads.png'); this.load.image('intro_twins_childhood', introPath + 'kai_ana_twins_childhood.png'); this.load.image('intro_birthday_cake', introPath + 'birthday_cake_rd.png'); this.load.image('intro_family_portrait', introPath + 'family_portrait_punk_complete.png'); // PHASE 2: THE COLLAPSE (4 shots) this.load.image('intro_virus', introPath + 'virus_xnoir_microscope.png'); this.load.image('intro_zombies', introPath + 'zombie_silhouettes_panic.png'); this.load.image('intro_chaos', introPath + 'chaos_streets_apocalypse.png'); this.load.image('intro_ana_taken', introPath + 'ana_taken_military.png'); // PHASE 3: AMNESIA WAKE-UP (4 shots) this.load.image('intro_bedroom', introPath + 'kai_bedroom_wakeup.png'); this.load.image('intro_ana_memory', introPath + 'ana_memory_flash_purple.png'); this.load.image('intro_gronk', introPath + 'gronk_doorway_silhouette.png'); // 🎵 AUDIO (Optional - with safe loading) this.loadAudioSafe('noir_ambience', 'assets/audio/ambient/noir_ambience.ogg'); this.loadAudioSafe('projector_loop', 'assets/audio/ambient/projector_loop.ogg'); // Voice triggers (placeholders) this.loadAudioSafe('audio_kai_memory_01', 'assets/audio/voiceover/kai_memory_01.mp3'); this.loadAudioSafe('audio_kai_memory_02', 'assets/audio/voiceover/kai_memory_02.mp3'); this.loadAudioSafe('audio_kai_memory_03', 'assets/audio/voiceover/kai_memory_03.mp3'); } loadAudioSafe(key, path) { try { this.load.audio(key, path); } catch (error) { console.warn(`⚠️ Audio skipped: ${key} (file not found)`); } } create() { console.log('🎬 IntroScene: Starting intro sequence...'); // Black background this.cameras.main.setBackgroundColor('#000000'); // 🎵 Start ambient audio (if available) 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.playPhase1HappyFamily(); } setupSkipControls() { // Keyboard skip (X key or SPACE) this.input.keyboard.on('keydown-X', () => this.skipIntro()); this.input.keyboard.on('keydown-SPACE', () => this.skipIntro()); // Mouse/touch skip (click anywhere) this.input.on('pointerdown', () => { if (this.skipEnabled) { this.skipIntro(); } }); } startAmbientAudio() { // Noir ambience (constant low drone) try { if (this.cache.audio.exists('noir_ambience')) { this.ambientAudio = this.sound.add('noir_ambience', { volume: 0.15, loop: true }); this.ambientAudio.play(); } } catch (e) { console.warn('⚠️ Ambient audio not available'); } // Projector sound (old film aesthetic) try { if (this.cache.audio.exists('projector_loop')) { this.projectorAudio = this.sound.add('projector_loop', { volume: 0.2, loop: true }); this.projectorAudio.play(); } } catch (e) { console.warn('⚠️ Projector audio not available'); } } createVHSEffects() { const width = this.cameras.main.width; const height = this.cameras.main.height; // VHS Scanlines (horizontal lines) 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 (subtle static) this.vhsNoise = this.add.rectangle( width / 2, height / 2, width, height, 0xffffff, 0.03 ); this.vhsNoise.setDepth(901); // Animate noise (flicker) this.tweens.add({ targets: this.vhsNoise, alpha: { from: 0.01, to: 0.05 }, duration: 100, yoyo: true, repeat: -1 }); } showSkipPrompt() { if (this.skipPrompt) return; // Already showing 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); // Pulse animation 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 tweens and timers 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('GameScene'); }); } playPhase1HappyFamily() { // PHASE 1: HAPPY FAMILY MEMORIES (0-15s) console.log('🎬 Phase 1: Happy Family'); this.currentPhase = 1; const width = this.cameras.main.width; const height = this.cameras.main.height; // Text overlay const text1 = this.add.text( width / 2, height - 80, 'Nekoč smo imeli barve...', { fontFamily: 'Courier New', fontSize: '24px', fill: '#ffffff', stroke: '#00ffff', strokeThickness: 1, shadow: { offsetX: 2, offsetY: 2, color: '#ff00ff', blur: 5, stroke: false, fill: true } } ); text1.setOrigin(0.5); text1.setAlpha(0); text1.setDepth(100); this.tweens.add({ targets: text1, alpha: 1, duration: 1000, delay: 500 }); // Shots sequence - fast cuts (~3s each) this.showShot('intro_otac_longboard', 0, 3000, { warm: true }); this.showShot('intro_ana_barbershop', 3000, 6000, { warm: true }); this.showShot('intro_twins_childhood', 6000, 9000, { warm: true, clear: true }); // CLEAREST this.showShot('intro_birthday_cake', 9000, 12000, { warm: true }); this.showShot('intro_family_portrait', 12000, 15000, { warm: true, freeze: true }); // Transition to Phase 2 this.time.delayedCall(14500, () => { this.tweens.add({ targets: text1, alpha: 0, duration: 500 }); }); this.time.delayedCall(15000, () => { text1.destroy(); this.playPhase2TheCollapse(); }); } playPhase2TheCollapse() { // PHASE 2: THE COLLAPSE (15-30s) console.log('🎬 Phase 2: The Collapse'); this.currentPhase = 2; const width = this.cameras.main.width; const height = this.cameras.main.height; // Text overlay const text2 = this.add.text( width / 2, height - 80, 'Potem je prišla tema...', { fontFamily: 'Courier New', fontSize: '24px', fill: '#ff0000', stroke: '#00ff00', strokeThickness: 2, shadow: { offsetX: 3, offsetY: 3, color: '#000000', blur: 10, stroke: true, fill: true } } ); text2.setOrigin(0.5); text2.setAlpha(0); text2.setDepth(100); this.tweens.add({ targets: text2, alpha: 1, duration: 800, delay: 300 }); // Shots sequence - faster, chaotic (~4s each) this.showShot('intro_virus', 15000, 19000, { toxic: true, glitch: true }); this.showShot('intro_zombies', 19000, 23000, { red: true, glitch: true, strobe: true }); this.showShot('intro_chaos', 23000, 27000, { chaos: true }); this.showShot('intro_ana_taken', 27000, 30000, { blur: true, red: true }); // Add camera shake for chaos this.time.delayedCall(19000, () => { this.cameras.main.shake(4000, 0.005); }); // Transition to Phase 3 this.time.delayedCall(29500, () => { this.tweens.add({ targets: text2, alpha: 0, duration: 500 }); }); this.time.delayedCall(30000, () => { text2.destroy(); this.playPhase3AmnesiaWakeUp(); }); } playPhase3AmnesiaWakeUp() { // PHASE 3: AMNESIA WAKE-UP (30-45s) console.log('🎬 Phase 3: Amnesia Wake-Up'); this.currentPhase = 3; const width = this.cameras.main.width; const height = this.cameras.main.height; // Screen goes black first (amnesia darkness) const blackScreen = this.add.rectangle( width / 2, height / 2, width, height, 0x000000 ); blackScreen.setDepth(50); blackScreen.setAlpha(0); this.tweens.add({ targets: blackScreen, alpha: 1, duration: 1000, delay: 0 }); // Text overlays const text3a = this.add.text( width / 2, height / 2 - 40, 'In ostal sem sam...', { fontFamily: 'Courier New', fontSize: '20px', fill: '#666666', stroke: '#000000', strokeThickness: 2 } ); text3a.setOrigin(0.5); text3a.setAlpha(0); text3a.setDepth(100); const text3b = this.add.text( width / 2, height / 2, 'z luknjo v glavi.', { fontFamily: 'Courier New', fontSize: '20px', fill: '#999999', stroke: '#000000', strokeThickness: 2 } ); text3b.setOrigin(0.5); text3b.setAlpha(0); text3b.setDepth(100); this.tweens.add({ targets: text3a, alpha: 1, duration: 1500, delay: 1500 }); this.tweens.add({ targets: text3b, alpha: 1, duration: 1500, delay: 2500 }); // Fade out black screen and text, show bedroom this.time.delayedCall(5000, () => { this.tweens.add({ targets: [blackScreen, text3a, text3b], alpha: 0, duration: 2000 }); }); // Bedroom appears this.time.delayedCall(7000, () => { blackScreen.destroy(); text3a.destroy(); text3b.destroy(); this.showShot('intro_bedroom', 37000, 40000, { fadeIn: true }); }); // Ana memory flash this.time.delayedCall(10000, () => { const flash = this.add.image(width / 2, height / 2, 'intro_ana_memory'); flash.setAlpha(0); flash.setDepth(80); flash.setScale(0.8); this.tweens.add({ targets: flash, alpha: 1, duration: 300, yoyo: true, repeat: 2, onComplete: () => flash.destroy() }); }); // Gronk entrance this.time.delayedCall(12000, () => { this.showShot('intro_gronk', 42000, 45000, { fadeIn: true }); }); // Final text const textFinal = this.add.text( width / 2, height - 100, 'Moram jo najti.\nTudi če mi vzame celo življenje.', { fontFamily: 'Courier New', fontSize: '18px', fill: '#ffffff', stroke: '#ff00ff', strokeThickness: 1, align: 'center', lineSpacing: 10 } ); textFinal.setOrigin(0.5); textFinal.setAlpha(0); textFinal.setDepth(100); this.time.delayedCall(13000, () => { this.tweens.add({ targets: textFinal, alpha: 1, duration: 2000 }); }); // Transition to GameScene this.time.delayedCall(17000, () => { console.log('🎬 IntroScene: Complete! Starting game...'); this.cameras.main.fadeOut(2000, 0, 0, 0); this.cameras.main.once('camerafadeoutcomplete', () => { this.scene.start('GameScene'); }); }); } 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, () => { // Fade out previous polaroid if (this.currentPolaroid) { this.tweens.add({ targets: this.currentPolaroid, alpha: 0, duration: 300, onComplete: () => { if (this.currentPolaroid) { this.currentPolaroid.frame.destroy(); this.currentPolaroid.photo.destroy(); this.currentPolaroid = null; } } }); } // Create photo image (65% of screen) const photo = this.add.image(width / 2, height / 2 - 30, imageKey); photo.setAlpha(options.fadeIn ? 0 : 1); 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; // 20px padding each side const frameHeight = photo.displayHeight + 80; // 20px top, 60px bottom (Polaroid style!) const frame = this.add.graphics(); frame.setDepth(19); // Dirty white Polaroid background frame.fillStyle(0xf5f5dc, 1); // Beige white frame.fillRect( (width - frameWidth) / 2, (height - frameHeight) / 2 - 30, frameWidth, frameHeight ); // Add subtle 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 (5px up/down) this.tweens.add({ targets: [photo, frame], y: '-=5', duration: 2000, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' }); // Apply visual effects to photo if (options.warm) { photo.setTint(0xffddaa); // Warm nostalgic tint } if (options.red) { photo.setTint(0xff6666); // Red danger tint } if (options.toxic) { photo.setTint(0x66ff66); // Green toxic tint } // 2 SECOND FADE-IN if (options.fadeIn !== false) { this.tweens.add({ targets: [photo, frame], alpha: { from: 0, to: 1 }, duration: 2000, ease: 'Power2.easeOut' }); } // GLITCH-OUT transition for next shot this.time.delayedCall(duration - 500, () => { if (this.currentPolaroid) { // First, quickly fade out the frame (so it doesn't show during glitch!) this.tweens.add({ targets: frame, alpha: 0, duration: 100 }); // Then glitch effect on photo only this.tweens.add({ targets: photo, x: { from: photo.x - 10, to: photo.x + 10 }, duration: 50, repeat: 5, yoyo: true, onComplete: () => { // Chromatic aberration effect (RGB flash) photo.setTint(0xff0000); this.time.delayedCall(50, () => photo.setTint(0x00ff00)); this.time.delayedCall(100, () => photo.setTint(0x0000ff)); this.time.delayedCall(150, () => { // Final fade out this.tweens.add({ targets: photo, alpha: 0, duration: 200 }); }); } }); } }); if (options.glitch) { // Extra glitch during display 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) { // Strobe effect this.tweens.add({ targets: photo, alpha: { from: 0.7, to: 1.0 }, duration: 150, repeat: duration / 150, yoyo: true }); } }); } shutdown() { // Cleanup if (this.skipPrompt) { this.skipPrompt.destroy(); } if (this.currentShot) { this.currentShot.destroy(); } this.tweens.killAll(); this.time.removeAllEvents(); } }