# 🔊 HEARING ACCESSIBILITY & CONTROLS - IMPLEMENTATION PLAN **Datum:** 12. December 2025 **Prioriteta:** HIGH **Estimated Time:** 3-4 ure --- ## 🎯 **CILJI:** Implementirati celoten accessibility sistem za gluhe in remappable controls: - Smart Subtitles - Visual Sound Cues - Subtitle System - Remappable Controls --- ## 📋 **FAZA 1: SUBTITLE SYSTEM** (45 min) ### **1.1 Subtitle Manager** **Datoteka:** `src/systems/SubtitleSystem.js` (nova) ```javascript class SubtitleSystem { constructor(scene) { this.scene = scene; this.enabled = true; // Always on by default this.size = 'medium'; // small, medium, large, very_large this.backgroundOpacity = 0.8; this.currentSubtitle = null; this.subtitleQueue = []; this.createSubtitleUI(); console.log('📝 SubtitleSystem: Initialized'); } createSubtitleUI() { const uiScene = this.scene.scene.get('UIScene'); if (!uiScene) return; // Subtitle container (bottom center) const width = this.scene.cameras.main.width; const height = this.scene.cameras.main.height; this.subtitleBg = uiScene.add.graphics(); this.subtitleBg.setScrollFactor(0); this.subtitleBg.setDepth(9000); this.subtitleText = uiScene.add.text( width / 2, height - 100, '', this.getTextStyle() ); this.subtitleText.setOrigin(0.5); this.subtitleText.setScrollFactor(0); this.subtitleText.setDepth(9001); this.subtitleText.setVisible(false); } getTextStyle() { const sizes = { small: '16px', medium: '20px', large: '24px', very_large: '32px' }; return { fontSize: sizes[this.size], fontFamily: 'Arial', color: '#ffffff', stroke: '#000000', strokeThickness: 4, align: 'center', wordWrap: { width: 800 } }; } showSubtitle(text, speaker = null, direction = null, duration = 3000) { if (!this.enabled) return; // Format subtitle let subtitle = ''; // Speaker name (colored) if (speaker) { const color = this.getSpeakerColor(speaker); subtitle += `[${speaker}]: `; } // Directional arrows if (direction) { subtitle = `${direction} ${subtitle}`; } subtitle += text; // Show subtitle this.subtitleText.setText(subtitle); this.subtitleText.setStyle(this.getTextStyle()); this.subtitleText.setVisible(true); // Background box const bounds = this.subtitleText.getBounds(); this.subtitleBg.clear(); this.subtitleBg.fillStyle(0x000000, this.backgroundOpacity); this.subtitleBg.fillRoundedRect( bounds.x - 10, bounds.y - 5, bounds.width + 20, bounds.height + 10, 8 ); // Auto-hide after duration this.scene.time.delayedCall(duration, () => { this.hideSubtitle(); }); console.log(`📝 Subtitle: ${subtitle}`); } hideSubtitle() { this.subtitleText.setVisible(false); this.subtitleBg.clear(); } getSpeakerColor(speaker) { const colors = { 'Player': '#00ff00', 'NPC': '#ffff00', 'System': '#ff00ff' }; return colors[speaker] || '#ffffff'; } // Sound effect captions showSoundEffect(effect, direction = null) { const captions = { 'dig': '[DIGGING SOUND]', 'plant': '[PLANTING SOUND]', 'harvest': '[HARVESTING SOUND]', 'build': '[BUILDING SOUND]', 'ui_click': '[CLICK]', 'footstep': '[FOOTSTEPS]', 'damage': '[DAMAGE]', 'death': '[DEATH SOUND]' }; const caption = captions[effect] || `[${effect.toUpperCase()}]`; this.showSubtitle(caption, null, direction, 1500); } } ``` --- ## 📋 **FAZA 2: VISUAL SOUND CUES** (60 min) ### **2.1 Visual Heartbeat (Low Health)** ```javascript class VisualSoundCues { constructor(scene) { this.scene = scene; this.heartbeatActive = false; this.createVisualCues(); } createVisualCues() { const uiScene = this.scene.scene.get('UIScene'); if (!uiScene) return; // Heartbeat overlay (red pulse) this.heartbeatOverlay = uiScene.add.graphics(); this.heartbeatOverlay.setScrollFactor(0); this.heartbeatOverlay.setDepth(8999); this.heartbeatOverlay.setAlpha(0); } showHeartbeat() { if (this.heartbeatActive) return; this.heartbeatActive = true; const width = this.scene.cameras.main.width; const height = this.scene.cameras.main.height; // Pulse effect this.scene.tweens.add({ targets: this.heartbeatOverlay, alpha: { from: 0, to: 0.3 }, duration: 500, yoyo: true, repeat: -1, onUpdate: () => { this.heartbeatOverlay.clear(); this.heartbeatOverlay.fillStyle(0xff0000, this.heartbeatOverlay.alpha); this.heartbeatOverlay.fillRect(0, 0, width, height); } }); } hideHeartbeat() { this.heartbeatActive = false; this.scene.tweens.killTweensOf(this.heartbeatOverlay); this.heartbeatOverlay.setAlpha(0); this.heartbeatOverlay.clear(); } } ``` ### **2.2 Damage Direction Indicator** ```javascript showDamageDirection(angle) { const uiScene = this.scene.scene.get('UIScene'); if (!uiScene) return; const centerX = this.scene.cameras.main.centerX; const centerY = this.scene.cameras.main.centerY; // Arrow pointing to damage source const arrow = uiScene.add.text( centerX + Math.cos(angle) * 100, centerY + Math.sin(angle) * 100, '⚠️', { fontSize: '32px' } ); arrow.setOrigin(0.5); arrow.setScrollFactor(0); arrow.setDepth(9000); // Fade out uiScene.tweens.add({ targets: arrow, alpha: 0, duration: 1000, onComplete: () => arrow.destroy() }); } ``` ### **2.3 Screen Flash Notifications** ```javascript showNotification(type) { const colors = { 'danger': 0xff0000, 'warning': 0xffff00, 'success': 0x00ff00, 'info': 0x00ffff }; const color = colors[type] || 0xffffff; // Flash screen border this.scene.cameras.main.flash(200, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff ); } ``` --- ## 📋 **FAZA 3: REMAPPABLE CONTROLS** (90 min) ### **3.1 Control Mapping System** **Datoteka:** `src/systems/ControlsSystem.js` (nova) ```javascript class ControlsSystem { constructor(scene) { this.scene = scene; // Default key mappings this.keyMappings = { 'move_up': 'W', 'move_down': 'S', 'move_left': 'A', 'move_right': 'D', 'interact': 'SPACE', 'build': 'B', 'craft': 'C', 'inventory': 'I', 'map': 'M', 'pause': 'ESC', 'save': 'F5', 'load': 'F9', 'rotate': 'R', 'confirm': 'E', 'cancel': 'ESC' }; // Control profiles this.profiles = { 'default': { ...this.keyMappings }, 'one_handed_left': { 'move_up': 'W', 'move_down': 'S', 'move_left': 'A', 'move_right': 'D', 'interact': 'Q', 'build': 'E', 'craft': 'R', 'inventory': 'F', 'map': 'TAB', 'pause': 'ESC' }, 'one_handed_right': { 'move_up': 'UP', 'move_down': 'DOWN', 'move_left': 'LEFT', 'move_right': 'RIGHT', 'interact': 'ENTER', 'build': 'NUMPAD_1', 'craft': 'NUMPAD_2', 'inventory': 'NUMPAD_3', 'map': 'NUMPAD_0', 'pause': 'ESC' } }; this.currentProfile = 'default'; this.loadMappings(); this.applyMappings(); console.log('🎮 ControlsSystem: Initialized'); } remapKey(action, newKey) { this.keyMappings[action] = newKey; this.saveMappings(); this.applyMappings(); console.log(`🎮 Remapped ${action} to ${newKey}`); } loadProfile(profileName) { if (!this.profiles[profileName]) return; this.currentProfile = profileName; this.keyMappings = { ...this.profiles[profileName] }; this.applyMappings(); console.log(`🎮 Loaded profile: ${profileName}`); } saveMappings() { localStorage.setItem('novafarma_controls', JSON.stringify(this.keyMappings)); } loadMappings() { const saved = localStorage.getItem('novafarma_controls'); if (saved) { this.keyMappings = JSON.parse(saved); } } applyMappings() { // Re-bind all keys based on current mappings // This would require refactoring existing key bindings console.log('🎮 Controls applied'); } } ``` ### **3.2 Controls Settings UI** ```javascript createControlsMenu() { const menu = this.scene.add.container( this.scene.cameras.main.centerX, this.scene.cameras.main.centerY ); // Title const title = this.scene.add.text(0, -250, '🎮 CONTROLS', { fontSize: '32px', fontStyle: 'bold' }).setOrigin(0.5); // Profile selector const profileLabel = this.scene.add.text(-200, -200, 'Profile:', { fontSize: '18px' }); const profileDropdown = this.createDropdown( 0, -200, ['Default', 'One-Handed Left', 'One-Handed Right'], (value) => this.loadProfile(value.toLowerCase().replace(' ', '_')) ); // Key mappings list let yOffset = -150; Object.entries(this.keyMappings).forEach(([action, key]) => { const actionLabel = this.scene.add.text(-200, yOffset, action, { fontSize: '16px' }); const keyButton = this.scene.add.text(100, yOffset, key, { fontSize: '16px', backgroundColor: '#333333', padding: { x: 10, y: 5 } }); keyButton.setInteractive({ useHandCursor: true }); keyButton.on('pointerdown', () => { this.startKeyRemap(action, keyButton); }); menu.add([actionLabel, keyButton]); yOffset += 30; }); menu.add(title); return menu; } startKeyRemap(action, button) { button.setText('Press key...'); // Listen for next key press const listener = (event) => { this.remapKey(action, event.key.toUpperCase()); button.setText(event.key.toUpperCase()); this.scene.input.keyboard.off('keydown', listener); }; this.scene.input.keyboard.on('keydown', listener); } ``` --- ## 📝 **IMPLEMENTATION STEPS:** 1. **Ustvari SubtitleSystem.js** (45 min) 2. **Ustvari VisualSoundCues.js** (60 min) 3. **Ustvari ControlsSystem.js** (90 min) 4. **Integracija v GameScene** (30 min) 5. **Settings Menu UI** (45 min) 6. **Testing** (30 min) **Total:** 5 ur --- ## 🔧 **DATOTEKE:** **Nove:** - `src/systems/SubtitleSystem.js` (~300 vrstic) - `src/systems/VisualSoundCues.js` (~200 vrstic) - `src/systems/ControlsSystem.js` (~400 vrstic) **Posodobljene:** - `src/scenes/GameScene.js` - Initialize systems - `src/scenes/UIScene.js` - Settings menu - `index.html` - Dodaj nove skripte --- ## 🎯 **PRIORITETA:** **HIGH** - Accessibility za gluhe je ključen za: - Večjo dostopnost - Širše občinstvo - Boljšo uporabniško izkušnjo - Compliance s standardi --- **Status:** ⏳ **PLAN PRIPRAVLJEN - ČAKA NA IMPLEMENTACIJO** **Priporočam:** Implementacija v naslednji seji (jutri) **Razlog:** Seja že traja 2h 28min, ta funkcionalnost zahteva 5 ur dela. **Seja bi trajala 7+ ur** - preveč za en dan. Želite začeti zdaj ali pustim za jutri? 🎮