Files
novafarma/docs/ACCESSIBILITY_PLAN.md
2026-01-20 01:05:17 +01:00

18 KiB

ACCESSIBILITY OPTIONS - IMPLEMENTATION PLAN

Game: Mrtva Dolina / Death Valley
Date: January 4, 2026
Priority: HIGH (Inclusive Gaming Standard)


🎯 ACCESSIBILITY FEATURES (From Screenshot)

Required Features:

  1. High Contrast Mode
  2. Large Text Mode
  3. Color Blind Mode
  4. Screen Reader Support
  5. Reduce Flashing (Epilepsy)
  6. One-Handed Controls
  7. Audio Cues

Quick Toggle: Press 1-7 to toggle features
Full Settings: ESC → Settings → Accessibility


📋 DETAILED IMPLEMENTATION

1. HIGH CONTRAST MODE 🎨

Purpose: Better visibility for low vision players

Changes:

  • Background: Pure black (#000000)
  • Text: Pure white (#FFFFFF)
  • UI borders: Bright yellow (#FFFF00)
  • Buttons: High contrast colors
  • Dialogue box: Black with white border
  • Character portraits: Increased brightness +30%

Implementation:

// src/systems/AccessibilitySystem.js
class AccessibilitySystem {
    toggleHighContrast() {
        this.highContrast = !this.highContrast;
        
        if (this.highContrast) {
            // Override all colors
            this.scene.dialogueBg.setFillStyle(0x000000, 1.0);
            this.scene.dialogueBg.setStrokeStyle(4, 0xFFFF00);
            this.scene.dialogueText.setColor('#FFFFFF');
            this.scene.speakerText.setColor('#FFFF00');
            
            // Apply to all scenes
            this.game.scene.scenes.forEach(scene => {
                if (scene.applyHighContrast) {
                    scene.applyHighContrast();
                }
            });
        } else {
            // Restore original colors
            this.restoreDefaultColors();
        }
        
        this.save();
    }
}

Keyboard: Press 1 to toggle


2. LARGE TEXT MODE 📝

Purpose: Readability for visually impaired

Changes:

  • All text +50% larger
  • Dialogue: 18px → 27px
  • UI labels: 16px → 24px
  • Buttons: 14px → 21px
  • Line spacing: +25%

Implementation:

toggleLargeText() {
    this.largeText = !this.largeText;
    const multiplier = this.largeText ? 1.5 : 1.0;
    
    // Update all text objects
    this.scene.dialogueText.setFontSize(18 * multiplier);
    this.scene.speakerText.setFontSize(22 * multiplier);
    
    // Resize dialogue box to fit
    const newHeight = this.largeText ? 240 : 180;
    this.scene.dialogueBg.setDisplaySize(
        this.scene.dialogueBg.width,
        newHeight
    );
    
    this.save();
}

Keyboard: Press 2 to toggle


3. COLOR BLIND MODE 🌈

Purpose: Support for color blindness (8% of men, 0.5% of women)

Types Supported:

  • Deuteranopia (Red-Green, most common)
  • Protanopia (Red-Green)
  • Tritanopia (Blue-Yellow, rare)

Changes:

  • Health bars: Red → Orange with pattern
  • Stamina: Blue → Cyan with stripes
  • Quest markers: Color + Icon
  • Item rarity: Color + Border style
  • Enemy indicators: Color + Shape

Color Palette Remapping:

const COLOR_BLIND_PALETTES = {
    deuteranopia: {
        red: 0xFF8C00,    // Orange instead of red
        green: 0x00CED1,  // Dark cyan instead of green
        blue: 0x4169E1,   // Royal blue (unchanged)
        purple: 0xFF00FF  // Magenta (more distinct)
    },
    protanopia: {
        red: 0xFFD700,    // Gold instead of red
        green: 0x1E90FF,  // Dodger blue instead of green
        blue: 0x8A2BE2,   // Blue violet
        purple: 0xFF69B4  // Hot pink
    },
    tritanopia: {
        red: 0xFF0000,    // Red (unchanged)
        green: 0x00FF00,  // Green (unchanged)
        blue: 0xFF1493,   // Deep pink instead of blue
        yellow: 0x00FFFF  // Cyan instead of yellow
    }
};

Implementation:

setColorBlindMode(type) {
    this.colorBlindMode = type; // 'none', 'deuteranopia', 'protanopia', 'tritanopia'
    
    if (type !== 'none') {
        const palette = COLOR_BLIND_PALETTES[type];
        
        // Remap all color references
        this.game.registry.set('palette', palette);
        
        // Add patterns to critical elements
        this.addAccessibilityPatterns();
    }
    
    this.save();
}

addAccessibilityPatterns() {
    // Add stripes to health bar
    this.healthBar.setTexture('health_bar_striped');
    
    // Add icons to color-coded elements
    this.questMarkers.forEach(marker => {
        marker.addIcon(); // Add symbol alongside color
    });
}

UI: Dropdown menu: None / Deuteranopia / Protanopia / Tritanopia
Keyboard: Press 3 to cycle modes


4. SCREEN READER SUPPORT 🔊

Purpose: Blind/low-vision accessibility

Features:

  • Announce all UI changes
  • Read dialogue text aloud
  • Describe scene changes
  • Navigate menus with keyboard
  • Audio feedback for all actions

Implementation:

class ScreenReaderSystem {
    constructor(game) {
        this.game = game;
        this.enabled = false;
        this.speechSynth = window.speechSynthesis;
        this.currentUtterance = null;
    }

    enable() {
        this.enabled = true;
        this.announce("Screen reader enabled. Use arrow keys to navigate.");
    }

    announce(text, interrupt = false) {
        if (!this.enabled) return;
        
        // Stop current speech if interrupting
        if (interrupt && this.speechSynth.speaking) {
            this.speechSynth.cancel();
        }
        
        const utterance = new SpeechSynthesisUtterance(text);
        utterance.lang = this.game.settings.language || 'sl-SI';
        utterance.rate = 1.0;
        utterance.pitch = 1.0;
        utterance.volume = 1.0;
        
        this.speechSynth.speak(utterance);
        this.currentUtterance = utterance;
    }

    announceDialogue(speaker, text) {
        const fullText = `${speaker} says: ${text}`;
        this.announce(fullText, true);
    }

    announceSceneChange(sceneName) {
        this.announce(`Entering ${sceneName}`, true);
    }

    announceButton(buttonLabel) {
        this.announce(`Button: ${buttonLabel}. Press Enter to activate.`);
    }
}

Integration with PrologueScene:

showDialogue(index) {
    const dialogue = this.dialogueData[index];
    
    // Screen reader support
    if (this.game.accessibility.screenReader.enabled) {
        this.game.accessibility.screenReader.announceDialogue(
            dialogue.speaker,
            dialogue.text
        );
    }
    
    // ... rest of dialogue logic
}

Keyboard: Press 4 to toggle


5. REDUCE FLASHING (EPILEPSY)

Purpose: Prevent seizures (photosensitive epilepsy affects ~3%)

Changes:

  • Disable camera shake
  • Disable screen flash effects
  • Reduce particle effects
  • Smooth transitions only
  • Warning on startup if disabled

Removed Effects:

toggleReduceFlashing() {
    this.reduceFlashing = !this.reduceFlashing;
    
    if (this.reduceFlashing) {
        // Disable dangerous effects
        this.game.cameras.main.shake(0); // No shake
        this.game.cameras.main.flash(0); // No flash
        
        // Reduce particle systems
        this.game.particles.setEmitterRate(0.5); // 50% particles
        
        // Disable lightning
        this.weatherSystem.disableLightning = true;
        
        // Smooth transitions only
        this.transitionDuration = 2000; // Slower fades
    }
    
    this.save();
}

Startup Warning:

create() {
    if (!this.game.accessibility.reduceFlashing) {
        this.showFlashWarning();
    }
}

showFlashWarning() {
    const warning = this.add.text(
        this.cameras.main.width / 2,
        this.cameras.main.height / 2,
        '⚠️ WARNING ⚠️\n\n' +
        'This game contains flashing lights\n' +
        'and rapid scene changes.\n\n' +
        'Press 5 to enable Epilepsy Safety Mode\n' +
        'Press any other key to continue',
        {
            fontSize: '24px',
            align: 'center',
            backgroundColor: '#000000',
            padding: { x: 40, y: 40 }
        }
    );
    warning.setOrigin(0.5);
    
    // Wait for input
    this.input.keyboard.once('keydown', (event) => {
        if (event.key === '5') {
            this.game.accessibility.toggleReduceFlashing();
        }
        warning.destroy();
    });
}

Keyboard: Press 5 to toggle


6. ONE-HANDED CONTROLS 🎮

Purpose: Physical disability support (one hand, motor impairments)

Modes:

  • Left-Hand Mode (WASD + nearby keys)
  • Right-Hand Mode (Arrow keys + numpad)
  • Both modes support mouse-only gameplay

Left-Hand Layout:

Movement: WASD
Action: Q
Menu: E
Inventory: R
Map: T
Sprint: Shift
Crouch: Ctrl

Right-Hand Layout:

Movement: Arrow Keys
Action: Numpad 0
Menu: Numpad Enter
Inventory: Numpad +
Map: Numpad -
Sprint: Numpad /
Crouch: Numpad *

Implementation:

class OneHandedControls {
    setMode(mode) {
        this.mode = mode; // 'left', 'right', 'both'
        
        if (mode === 'left') {
            this.bindLeftHandKeys();
        } else if (mode === 'right') {
            this.bindRightHandKeys();
        } else {
            this.bindBothHands();
        }
    }

    bindLeftHandKeys() {
        const keys = {
            up: 'W',
            down: 'S',
            left: 'A',
            right: 'D',
            action: 'Q',
            menu: 'E',
            inventory: 'R',
            map: 'T',
            sprint: 'SHIFT',
            crouch: 'CTRL'
        };
        
        this.remapKeys(keys);
    }

    bindRightHandKeys() {
        const keys = {
            up: 'UP',
            down: 'DOWN',
            left: 'LEFT',
            right: 'RIGHT',
            action: 'NUMPAD_ZERO',
            menu: 'NUMPAD_ENTER',
            inventory: 'NUMPAD_ADD',
            map: 'NUMPAD_SUBTRACT',
            sprint: 'NUMPAD_DIVIDE',
            crouch: 'NUMPAD_MULTIPLY'
        };
        
        this.remapKeys(keys);
    }
}

UI Toggle:

  • Settings → Accessibility → One-Handed Controls
  • Options: Both Hands / Left Hand Only / Right Hand Only

Keyboard: Press 6 to cycle modes


7. AUDIO CUES 🔔

Purpose: Additional feedback for deaf/hard-of-hearing players

Features:

  • Visual sound indicators
  • Subtitles for all dialogue (already done!)
  • Direction indicators for off-screen sounds
  • Vibration feedback (controller)

Visual Sound System:

class VisualSoundCueSystem {
    constructor(scene) {
        this.scene = scene;
        this.cues = [];
    }

    showCue(soundType, direction, distance) {
        // Create visual indicator
        const cue = this.scene.add.sprite(x, y, 'sound_cue_' + soundType);
        cue.setAlpha(0.7);
        
        // Position based on direction
        const angle = this.getAngleFromDirection(direction);
        const radius = 200; // Distance from player
        
        cue.x = this.scene.player.x + Math.cos(angle) * radius;
        cue.y = this.scene.player.y + Math.sin(angle) * radius;
        
        // Add text label
        const label = this.scene.add.text(cue.x, cue.y + 30, soundType, {
            fontSize: '14px',
            backgroundColor: '#000000'
        });
        
        // Fade out after 2 seconds
        this.scene.tweens.add({
            targets: [cue, label],
            alpha: 0,
            duration: 2000,
            onComplete: () => {
                cue.destroy();
                label.destroy();
            }
        });
    }

    // Example usage
    onZombieGrowl(zombiePosition) {
        const direction = this.getDirectionToPlayer(zombiePosition);
        const distance = Phaser.Math.Distance.Between(
            this.scene.player.x, this.scene.player.y,
            zombiePosition.x, zombiePosition.y
        );
        
        this.showCue('Zombie Growl', direction, distance);
    }
}

Sound Types with Icons:

  • 🧟 Zombie nearby
  • 💥 Explosion
  • 🔔 Quest update
  • 💬 NPC speaking
  • ⚔️ Combat
  • 🚪 Door opening/closing
  • 📦 Item pickup

Keyboard: Press 7 to toggle


🎨 UI IMPLEMENTATION

Accessibility Options Menu

class AccessibilityMenu extends Phaser.Scene {
    create() {
        // Title
        this.add.text(width/2, 50, '♿ ACCESSIBILITY OPTIONS', {
            fontSize: '32px',
            color: '#FFFFFF'
        }).setOrigin(0.5);

        // Options list
        const options = [
            { id: 1, name: 'High Contrast Mode', key: '1' },
            { id: 2, name: 'Large Text Mode', key: '2' },
            { id: 3, name: 'Color Blind Mode', key: '3' },
            { id: 4, name: 'Screen Reader Support', key: '4' },
            { id: 5, name: 'Reduce Flashing (Epilepsy)', key: '5' },
            { id: 6, name: 'One-Handed Controls', key: '6' },
            { id: 7, name: 'Audio Cues', key: '7' }
        ];

        let y = 150;
        options.forEach(option => {
            const text = this.add.text(100, y, 
                `${option.id}. ${option.name}`,
                { fontSize: '20px' }
            );
            
            // Toggle indicator
            const indicator = this.add.text(width - 100, y, 
                this.getStatusText(option.id),
                { fontSize: '20px' }
            );
            
            y += 50;
        });

        // Footer
        this.add.text(width/2, height - 100,
            'Full accessibility settings available\n' +
            'in-game (ESC → Settings)\n\n' +
            'Tip: Press 1-7 to toggle these features',
            {
                fontSize: '16px',
                align: 'center',
                color: '#AAAAAA'
            }
        ).setOrigin(0.5);

        // OK button
        const okButton = this.add.rectangle(
            width/2, height - 40,
            200, 50,
            0x0066FF
        );
        this.add.text(width/2, height - 40, 'OK', {
            fontSize: '24px'
        }).setOrigin(0.5);

        okButton.setInteractive();
        okButton.on('pointerdown', () => {
            this.scene.start('MainMenu');
        });

        // Keyboard shortcuts
        this.input.keyboard.on('keydown', (event) => {
            const key = parseInt(event.key);
            if (key >= 1 && key <= 7) {
                this.toggleOption(key);
            }
        });
    }

    toggleOption(id) {
        const accessibility = this.game.registry.get('accessibility');
        
        switch(id) {
            case 1: accessibility.toggleHighContrast(); break;
            case 2: accessibility.toggleLargeText(); break;
            case 3: accessibility.cycleColorBlindMode(); break;
            case 4: accessibility.toggleScreenReader(); break;
            case 5: accessibility.toggleReduceFlashing(); break;
            case 6: accessibility.cycleOneHandedMode(); break;
            case 7: accessibility.toggleAudioCues(); break;
        }
        
        // Refresh UI
        this.scene.restart();
    }

    getStatusText(id) {
        const accessibility = this.game.registry.get('accessibility');
        const statuses = [
            accessibility.highContrast ? 'ON' : 'OFF',
            accessibility.largeText ? 'ON' : 'OFF',
            accessibility.colorBlindMode || 'None',
            accessibility.screenReader ? 'ON' : 'OFF',
            accessibility.reduceFlashing ? 'ON' : 'OFF',
            accessibility.oneHandedMode || 'Both',
            accessibility.audioCues ? 'ON' : 'OFF'
        ];
        
        return statuses[id - 1];
    }
}

💾 PERSISTENCE

// Save accessibility settings to localStorage
class AccessibilitySystem {
    save() {
        const settings = {
            highContrast: this.highContrast,
            largeText: this.largeText,
            colorBlindMode: this.colorBlindMode,
            screenReader: this.screenReader,
            reduceFlashing: this.reduceFlashing,
            oneHandedMode: this.oneHandedMode,
            audioCues: this.audioCues
        };
        
        localStorage.setItem('accessibility_settings', JSON.stringify(settings));
    }

    load() {
        const saved = localStorage.getItem('accessibility_settings');
        if (saved) {
            const settings = JSON.parse(saved);
            Object.assign(this, settings);
            this.applyAll();
        }
    }

    applyAll() {
        if (this.highContrast) this.toggleHighContrast();
        if (this.largeText) this.toggleLargeText();
        if (this.colorBlindMode !== 'none') this.setColorBlindMode(this.colorBlindMode);
        if (this.screenReader) this.screenReaderSystem.enable();
        if (this.reduceFlashing) this.toggleReduceFlashing();
        if (this.oneHandedMode !== 'both') this.controls.setMode(this.oneHandedMode);
        if (this.audioCues) this.visualSoundCues.enable();
    }
}

📊 IMPLEMENTATION TIMELINE

Week Task Deliverable
1 Core System AccessibilitySystem.js
2 High Contrast + Large Text Visual modes
3 Color Blind Mode 3 palette variants
4 Screen Reader Text-to-speech
5 Epilepsy Safety Effect reduction
6 One-Handed + Audio Cues Control schemes
7 UI Integration Settings menu
8 Testing User testing

Total Time: 8 weeks (~2 months)


SUCCESS CRITERIA

  • All 7 features implemented
  • Keyboard shortcuts (1-7) working
  • Settings persist across sessions
  • No performance impact
  • Screen reader announces all interactions
  • Color blind modes tested with simulators
  • One-handed mode fully playable
  • Epilepsy warning on first launch

🎯 PRIORITY ORDER

Phase 1 (Critical):

  1. Reduce Flashing - Safety first!
  2. Large Text Mode - Easy win
  3. High Contrast Mode - Visual accessibility

Phase 2 (Important):

  1. Color Blind Mode - 8% of players
  2. One-Handed Controls - Physical accessibility
  3. Audio Cues - Deaf/HoH support

Phase 3 (Advanced):

  1. Screen Reader Support - Complex but essential

🏆 INDUSTRY STANDARDS

Following guidelines from:

  • Game Accessibility Guidelines (gameaccessibilityguidelines.com)
  • Xbox Accessibility standards
  • PlayStation Accessibility standards
  • WCAG 2.1 (Web Content Accessibility Guidelines)

Certification Goal: Xbox Accessibility Features badge


Last Updated: January 4, 2026
Status: Planning → Implementation Starting Tonight