Files
novafarma/docs/guides/HEARING_ACCESSIBILITY_PLAN.md
2025-12-12 13:48:49 +01:00

12 KiB

🔊 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)

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)

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

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

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)

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

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? 🎮