Files
whisper_voice/docs/plans/2026-02-18-wcag-aaa-implementation.md
Your Name 4615f3084f Docs: Add WCAG 2.2 AAA step-by-step implementation plan
15 tasks covering: design tokens, 6 component fixes, Settings/Overlay/Loader
hardcoded colors, accessibility properties, keyboard nav, reduced motion.
2026-02-18 20:56:51 +02:00

30 KiB

WCAG 2.2 AAA Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Bring WhisperVoice to full WCAG 2.2 AAA compliance while preserving the existing visual identity.

Architecture: Style-system-first approach. Update the central SettingsStyle.qml design token singleton first so color/contrast fixes cascade to all consumers. Then fix per-component issues (sizes, focus rings, I/O marks). Then add accessibility properties, keyboard navigation, and reduced-motion support through the Python bridge layer.

Tech Stack: PySide6/QML (Qt 6), Python 3.x, ctypes (Windows API for reduced motion detection)


Task 1: Update SettingsStyle.qml Design Tokens

Files:

  • Modify: src/ui/qml/SettingsStyle.qml (entire file, 25 lines)

Step 1: Edit SettingsStyle.qml

Replace the entire content with updated AAA-compliant tokens:

import QtQuick

pragma Singleton

QtObject {
    // Colors
    readonly property color background: "#F2121212" // Deep Obsidian with 95% opacity
    readonly property color surfaceCard: "#1A1A1A" // Layer 1
    readonly property color surfaceHover: "#2A2A2A" // Layer 2
    readonly property color borderSubtle: Qt.rgba(1, 1, 1, 0.22) // WCAG 3:1 non-text contrast

    readonly property color textPrimary: "#FAFAFA"
    readonly property color textSecondary: "#ABABAB" // WCAG AAA 8.1:1 on #121212
    readonly property color textDisabled: "#808080" // 4.0:1 minimum for disabled states

    readonly property color accentPurple: "#B794F6" // WCAG AAA 7.2:1 on #121212
    readonly property color accentCyan: "#00F2FF"

    // Configurable active accent
    property color accent: accentPurple

    // Dimensions
    readonly property int cardRadius: 16
    readonly property int itemRadius: 8
    readonly property int itemHeight: 60

    // Accessibility
    readonly property int focusRingWidth: 2
    readonly property int minTargetSize: 24
}

What changed:

  • accentPurple: #7000FF -> #B794F6 (1.6:1 -> 7.2:1 contrast ratio)
  • textSecondary: #999999 -> #ABABAB (6.3:1 -> 8.1:1)
  • borderSubtle: rgba(1,1,1,0.08) -> rgba(1,1,1,0.22) (3:1 non-text)
  • New: textDisabled, focusRingWidth, minTargetSize

Step 2: Verify the app launches without errors

Run: cd "D:/!!! SYSTEM DATA !!!/Desktop/python crap/whisper_voice" && python main.py

Expected: App loads, Loader appears with updated accent colors. Settings opens with lighter purple accents. No QML errors in console.

Step 3: Commit

git add src/ui/qml/SettingsStyle.qml
git commit -m "WCAG: Update design tokens for AAA contrast compliance"

Task 2: Fix ModernSlider.qml (Size + Focus Ring + Accessibility)

Files:

  • Modify: src/ui/qml/ModernSlider.qml (entire file, 61 lines)

Step 1: Edit ModernSlider.qml

Replace the entire content:

import QtQuick
import QtQuick.Controls
import QtQuick.Effects

Slider {
    id: control

    Accessible.role: Accessible.Slider
    Accessible.name: control.value.toString()
    activeFocusOnTab: true

    background: Rectangle {
        x: control.leftPadding
        y: control.topPadding + control.availableHeight / 2 - height / 2
        implicitWidth: 200
        implicitHeight: 6
        width: control.availableWidth
        height: implicitHeight
        radius: 3
        color: "#2d2d3d"

        Rectangle {
            width: control.visualPosition * parent.width
            height: parent.height
            color: SettingsStyle.accent
            radius: 3
        }
    }

    handle: Item {
        x: control.leftPadding + control.visualPosition * (control.availableWidth - width)
        y: control.topPadding + control.availableHeight / 2 - height / 2
        implicitWidth: SettingsStyle.minTargetSize
        implicitHeight: SettingsStyle.minTargetSize

        // Focus ring
        Rectangle {
            anchors.centerIn: parent
            width: parent.width + SettingsStyle.focusRingWidth * 2 + 2
            height: width
            radius: width / 2
            color: "transparent"
            border.width: SettingsStyle.focusRingWidth
            border.color: SettingsStyle.accent
            visible: control.activeFocus
        }

        Rectangle {
            anchors.fill: parent
            radius: width / 2
            color: "white"
            border.color: SettingsStyle.accent
            border.width: 2

            layer.enabled: control.pressed
            layer.effect: MultiEffect {
                blurEnabled: true
                blur: 0.5
                shadowEnabled: true
                shadowColor: SettingsStyle.accent
            }
        }
    }

    // Value Readout
    Text {
        anchors.right: parent.left
        anchors.rightMargin: 12
        anchors.verticalCenter: parent.verticalCenter
        horizontalAlignment: Text.AlignRight

        text: {
            var val = control.value
            return (val % 1 === 0) ? val.toFixed(0) : val.toFixed(1)
        }

        color: SettingsStyle.textSecondary
        font.family: "JetBrains Mono"
        font.pixelSize: 12
        font.weight: Font.Medium
    }
}

What changed:

  • Handle: implicitWidth/Height: 18 -> SettingsStyle.minTargetSize (24px)
  • Track: implicitHeight: 4 -> 6, radius 2 -> 3
  • Added Accessible.role: Accessible.Slider, Accessible.name
  • Added activeFocusOnTab: true
  • Added focus ring Rectangle that shows on control.activeFocus
  • Handle is now an Item wrapping the circle + focus ring (avoids clipping)

Step 2: Verify

Run the app, open Settings, check sliders render with larger handles. Tab to a slider - focus ring should appear.

Step 3: Commit

git add src/ui/qml/ModernSlider.qml
git commit -m "WCAG: Slider - 24px target, focus ring, accessible role"

Task 3: Fix ModernSwitch.qml (I/O Marks + Border + Accessibility)

Files:

  • Modify: src/ui/qml/ModernSwitch.qml (entire file, 40 lines)

Step 1: Edit ModernSwitch.qml

Replace the entire content:

import QtQuick
import QtQuick.Controls

Switch {
    id: control

    Accessible.role: Accessible.CheckBox
    Accessible.name: control.text + (control.checked ? " on" : " off")
    activeFocusOnTab: true

    indicator: Rectangle {
        implicitWidth: 44
        implicitHeight: 24
        x: control.leftPadding
        y: parent.height / 2 - height / 2
        radius: 12
        color: control.checked ? SettingsStyle.accent : "#2d2d3d"
        border.color: control.checked ? SettingsStyle.accent : SettingsStyle.borderSubtle
        border.width: control.activeFocus ? SettingsStyle.focusRingWidth : 1

        Behavior on color { ColorAnimation { duration: 200 } }
        Behavior on border.color { ColorAnimation { duration: 200 } }

        Rectangle {
            x: control.checked ? parent.width - width - 3 : 3
            y: 3
            width: 18
            height: 18
            radius: 9
            color: "white"

            Behavior on x {
                NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
            }

            // I/O pip marks for non-color state indication
            Text {
                anchors.centerIn: parent
                text: control.checked ? "I" : "O"
                font.pixelSize: 9
                font.bold: true
                color: control.checked ? SettingsStyle.accent : "#666666"
            }
        }
    }

    contentItem: Text {
        text: control.text
        font: control.font
        opacity: enabled ? 1.0 : 0.3
        color: "white"
        verticalAlignment: Text.AlignVCenter
        leftPadding: control.indicator.width + control.spacing
    }
}

What changed:

  • Added Accessible.role: Accessible.CheckBox with state in name
  • Added activeFocusOnTab: true
  • Border: "#3d3d4d" -> SettingsStyle.borderSubtle token
  • Focus: border.width switches to focusRingWidth on activeFocus
  • Added "I"/"O" Text inside the thumb circle for non-color state indication

Step 2: Verify

Open Settings, toggle switches. "I" shows when on, "O" when off. Tab to switches - border thickens.

Step 3: Commit

git add src/ui/qml/ModernSwitch.qml
git commit -m "WCAG: Switch - I/O marks, border token, focus ring, accessible role"

Task 4: Fix ModernTextField.qml (Placeholder + Focus + Accessibility)

Files:

  • Modify: src/ui/qml/ModernTextField.qml (entire file, 27 lines)

Step 1: Edit ModernTextField.qml

Replace the entire content:

import QtQuick
import QtQuick.Controls

TextField {
    id: control

    property color accentColor: "#00f2ff"
    property color bgColor: "#1a1a20"

    Accessible.role: Accessible.EditableText
    Accessible.name: control.placeholderText || "Text input"

    placeholderTextColor: SettingsStyle.textDisabled
    color: "#ffffff"
    font.family: "JetBrains Mono"
    font.pixelSize: 14
    selectedTextColor: "#000000"
    selectionColor: accentColor

    background: Rectangle {
        implicitWidth: 200
        implicitHeight: 40
        color: control.bgColor
        border.color: control.activeFocus ? control.accentColor : SettingsStyle.borderSubtle
        border.width: control.activeFocus ? SettingsStyle.focusRingWidth : 1
        radius: 6

        Behavior on border.color { ColorAnimation { duration: 150 } }
    }
}

What changed:

  • placeholderTextColor: "#606060" -> SettingsStyle.textDisabled (#808080, 3.7:1)
  • Border default: "#40ffffff" -> SettingsStyle.borderSubtle
  • Focus border.width: 1 -> SettingsStyle.focusRingWidth (2)
  • Added Accessible.role: Accessible.EditableText

Step 2: Verify

Open Settings > AI Engine > Custom Prompt field. Placeholder text should be lighter. Focus border thicker.

Step 3: Commit

git add src/ui/qml/ModernTextField.qml
git commit -m "WCAG: TextField - placeholder contrast, focus ring, accessible role"

Task 5: Fix ModernComboBox.qml (Border + Arrow + Focus + Accessibility)

Files:

  • Modify: src/ui/qml/ModernComboBox.qml:88-98 (background section)
  • Modify: src/ui/qml/ModernComboBox.qml:65-73 (indicator paint)

Step 1: Update background border

In ModernComboBox.qml, change the background Rectangle (line 88-98):

    background: Rectangle {
        implicitWidth: 140
        implicitHeight: 40
        color: control.bgColor
        border.color: control.pressed || control.activeFocus ? control.accentColor : SettingsStyle.borderSubtle
        border.width: control.activeFocus ? SettingsStyle.focusRingWidth : 1
        radius: 6

        Behavior on border.color { ColorAnimation { duration: 150 } }
    }

What changed:

  • Default border: "#40ffffff" -> SettingsStyle.borderSubtle
  • Focus border.width: 1 -> SettingsStyle.focusRingWidth (2)

Step 2: Update arrow indicator color

In the onPaint handler (line 71), change the non-pressed fill color:

Old: context.fillStyle = control.pressed ? control.accentColor : "#888888"; New: context.fillStyle = control.pressed ? control.accentColor : "#ABABAB";

Step 3: Add accessibility properties

After line 11 (property color popupColor: "#252530"), add:

    Accessible.role: Accessible.ComboBox
    Accessible.name: control.displayText

Step 4: Also update popup border

In the popup background (line 115-119), change:

Old: border.color: "#40ffffff" New: border.color: SettingsStyle.borderSubtle

Step 5: Verify

Open Settings, check combo boxes render with visible borders. Arrow icon is brighter.

Step 6: Commit

git add src/ui/qml/ModernComboBox.qml
git commit -m "WCAG: ComboBox - border token, arrow contrast, focus ring, accessible role"

Task 6: Fix ModernKeySequenceRecorder.qml (Border + Colors + Focus + Accessibility)

Files:

  • Modify: src/ui/qml/ModernKeySequenceRecorder.qml (lines 10-11, 29, plus add new properties)

Step 1: Update border color

Line 11, change:

Old: border.color: activeFocus || recording ? SettingsStyle.accent : "#40ffffff" New: border.color: activeFocus || recording ? SettingsStyle.accent : SettingsStyle.borderSubtle

Step 2: Update border width for focus

Add after border.color line:

Add to existing line 10: change from border.width: 1 to:

    border.width: (activeFocus || recording) ? SettingsStyle.focusRingWidth : 1

Step 3: Update "None" text color

Line 29, the color for empty state. Change:

Old: color: control.recording ? SettingsStyle.accent : (control.currentSequence ? "#ffffff" : "#808080") New: color: control.recording ? SettingsStyle.accent : (control.currentSequence ? "#ffffff" : "#ABABAB")

Step 4: Add accessibility and tab focus

After line 3 (Rectangle {), inside the root Rectangle, add:

    activeFocusOnTab: true
    Accessible.role: Accessible.Button
    Accessible.name: control.currentSequence ? "Hotkey: " + control.currentSequence + ". Click to change" : "No hotkey set. Click to record"

Note: activeFocusOnTab: true is critical since the recorder uses forceActiveFocus() on click but needs to also be reachable by Tab.

Step 5: Verify

Open Settings > General tab. Hotkey recorders should have visible borders, "None" text brighter. Tab to recorders.

Step 6: Commit

git add src/ui/qml/ModernKeySequenceRecorder.qml
git commit -m "WCAG: KeySequenceRecorder - border token, focus ring, accessible role"

Task 7: Fix GlowButton.qml (Text Color + Border + Accessibility)

Files:

  • Modify: src/ui/qml/GlowButton.qml (lines 14, 28, plus add new properties)

Step 1: Update default text color

Line 14, change:

Old: color: control.hovered ? "white" : "#9499b0" New: color: control.hovered ? "white" : "#ABABAB"

Step 2: Update default border color

Line 28, change:

Old: border.color: control.hovered ? control.accentColor : Qt.rgba(1, 1, 1, 0.1) New: border.color: control.hovered ? control.accentColor : SettingsStyle.borderSubtle

Step 3: Add accessibility and focus ring

After line 8 (property color accentColor: "#00f2ff"), add:

    Accessible.role: Accessible.Button
    Accessible.name: control.text
    activeFocusOnTab: true

Also update the background border.width (line 29):

Old: border.width: 1 New: border.width: control.activeFocus ? SettingsStyle.focusRingWidth : 1

Step 4: Verify

Check any GlowButton instances in the app (Download button uses Button directly, so GlowButton may only appear if used elsewhere — verify usage).

Step 5: Commit

git add src/ui/qml/GlowButton.qml
git commit -m "WCAG: GlowButton - text contrast, border token, focus ring, accessible role"

Task 8: Fix ModernSettingsSection.qml + ModernSettingsItem.qml (Accessibility)

Files:

  • Modify: src/ui/qml/ModernSettingsSection.qml (add 2 lines)
  • Modify: src/ui/qml/ModernSettingsItem.qml (add 2 lines)

Step 1: Add accessibility to ModernSettingsSection

After line 8 (property string title: ""), add:

    Accessible.name: root.title + " settings group"
    Accessible.role: Accessible.Grouping

Step 2: Add accessibility to ModernSettingsItem

After line 20 (property bool showSeparator: true), add:

    Accessible.name: root.label
    Accessible.role: Accessible.Row

Step 3: Verify

Open Settings. Use Windows Narrator or Accessibility Insights to confirm sections and items are announced with their labels.

Step 4: Commit

git add src/ui/qml/ModernSettingsSection.qml src/ui/qml/ModernSettingsItem.qml
git commit -m "WCAG: Add accessible name/role to settings sections and items"

Task 9: Fix Settings.qml (Hardcoded Colors + Keyboard Nav + Accessibility)

This is the largest file (1116 lines). Multiple targeted edits.

Files:

  • Modify: src/ui/qml/Settings.qml

Step 1: Update window title

Line 15, change:

Old: title: "Settings" New: title: "WhisperVoice Settings"

Step 2: Add window-level accessibility

After line 15, add:

    Accessible.name: "WhisperVoice Settings"

Step 3: Fix close button hover colors

Line 137, change:

Old: color: closeMa.containsMouse ? "#20ff4b4b" : "transparent" New: color: closeMa.containsMouse ? "#20FF8A8A" : "transparent"

Line 138, change:

Old: border.color: closeMa.containsMouse ? "#40ff4b4b" : "transparent" New: border.color: closeMa.containsMouse ? "#40FF8A8A" : "transparent"

Line 144, change:

Old: color: closeMa.containsMouse ? "#ff4b4b" : SettingsStyle.textSecondary New: color: closeMa.containsMouse ? "#FF8A8A" : SettingsStyle.textSecondary

Step 4: Add close button keyboard support and accessibility

After the close button Rectangle opening (line 134), add:

                    activeFocusOnTab: true
                    Accessible.name: "Close settings"
                    Accessible.role: Accessible.Button
                    Keys.onReturnPressed: root.close()
                    Keys.onSpacePressed: root.close()

Add a focus indicator. After Behavior on border.color (line 159), add:

                    // Focus ring
                    Rectangle {
                        anchors.fill: parent
                        radius: 8
                        color: "transparent"
                        border.width: SettingsStyle.focusRingWidth
                        border.color: SettingsStyle.accent
                        visible: parent.activeFocus
                    }

Step 5: Fix nav sidebar keyboard support

In the Repeater delegate (line 203), after radius: 6, add:

                            activeFocusOnTab: true
                            Accessible.name: name
                            Accessible.role: Accessible.Tab
                            Keys.onReturnPressed: stack.currentIndex = index
                            Keys.onSpacePressed: stack.currentIndex = index
                            Keys.onDownPressed: {
                                if (index < navModel.count - 1) {
                                    // Move focus to next nav item
                                    var nextItem = parent.children[index + 1]
                                    if (nextItem) nextItem.forceActiveFocus()
                                }
                            }
                            Keys.onUpPressed: {
                                if (index > 0) {
                                    var prevItem = parent.children[index - 1]
                                    if (prevItem) prevItem.forceActiveFocus()
                                }
                            }

Add focus indicator inside the nav delegate, after the MouseArea (line 258):

                            // Focus ring
                            Rectangle {
                                anchors.fill: parent
                                radius: 6
                                color: "transparent"
                                border.width: SettingsStyle.focusRingWidth
                                border.color: SettingsStyle.accent
                                visible: parent.activeFocus
                            }

Step 6: Fix StatBox accent colors

Line 1068, change:

Old: StatBox { label: "APP RAM"; value: ui.appRamMb; unit: "MB"; accent: "#bd93f9" } New: StatBox { label: "APP RAM"; value: ui.appRamMb; unit: "MB"; accent: "#CAA9FF" }

Line 1069, change:

Old: StatBox { label: "GPU VRAM"; value: ui.appVramMb; unit: "MB"; accent: "#ff79c6" } New: StatBox { label: "GPU VRAM"; value: ui.appVramMb; unit: "MB"; accent: "#FF8FD0" }

Line 1070, change:

Old: StatBox { label: "GPU LOAD"; value: ui.appVramPercent; unit: "%"; accent: "#ff5555" } New: StatBox { label: "GPU LOAD"; value: ui.appVramPercent; unit: "%"; accent: "#FF8A8A" }

Step 7: Fix model info text

Line 755, change:

Old: font.pixelSize: 10 New: font.pixelSize: 11

Line 756, change (remove opacity):

Old: opacity: 0.7 New: opacity: 1.0

Step 8: Verify

Open Settings. Check:

  • Close button hover is softer red (#FF8A8A)
  • StatBox accents are lighter pastels
  • Model info text is readable without opacity reduction
  • Tab through nav items, close button
  • Arrow keys move between nav items

Step 9: Commit

git add src/ui/qml/Settings.qml
git commit -m "WCAG: Settings - AAA colors, keyboard nav, accessible roles"

Task 10: Fix Overlay.qml (Border + Keyboard + Accessibility)

Files:

  • Modify: src/ui/qml/Overlay.qml

Step 1: Add window-level accessibility

After line 15 (color: "transparent"), add:

    title: "WhisperVoice"
    Accessible.name: "WhisperVoice Overlay"

Step 2: Fix border color

Line 175, change:

Old: border.color: "#40ffffff" New: border.color: Qt.rgba(1, 1, 1, 0.22)

Step 3: Add mic button keyboard support and accessibility

In the micContainer Item (line 206), after anchors.verticalCenter: parent.verticalCenter, add:

            activeFocusOnTab: true
            Accessible.name: ui.isRecording ? "Stop recording" : "Start recording"
            Accessible.role: Accessible.Button
            Keys.onReturnPressed: ui.toggleRecordingRequested()
            Keys.onSpacePressed: ui.toggleRecordingRequested()

Step 4: Add focus ring to mic button

Inside micContainer, add a focus indicator after the micCircle Rectangle (after line 265):

            // Focus ring
            Rectangle {
                anchors.fill: micCircle
                anchors.margins: -SettingsStyle.focusRingWidth - 1
                radius: width / 2
                color: "transparent"
                border.width: SettingsStyle.focusRingWidth
                border.color: SettingsStyle.accent
                visible: micContainer.activeFocus
            }

Note: This requires importing SettingsStyle. Check if it's already available in Overlay.qml — it likely isn't since Overlay uses hardcoded values. If SettingsStyle is not importable in Overlay.qml, use a literal 2 for border width and "#B794F6" for color.

Step 5: Add accessibility to recording timer text

In the timer Text (around line 333), add:

                    Accessible.role: Accessible.StaticText
                    Accessible.name: "Recording time: " + text

Step 6: Verify

Launch app, trigger recording. Border should be more visible. Tab to mic button - focus ring appears. Press Enter on focused mic button - recording toggles.

Step 7: Commit

git add src/ui/qml/Overlay.qml
git commit -m "WCAG: Overlay - border contrast, keyboard nav, accessible roles"

Task 11: Fix Loader.qml (Colors + Accessibility)

Files:

  • Modify: src/ui/qml/Loader.qml

Step 1: Add window-level accessibility

After line 16 (color: "transparent"), add:

    title: "WhisperVoice"
    Accessible.name: "WhisperVoice Loading"

Step 2: Fix border color

Line 24, change:

Old: border.color: "#40ffffff" New: border.color: Qt.rgba(1, 1, 1, 0.22)

Step 3: Fix subtitle color

Line 98, change:

Old: color: "#80ffffff" New: color: "#ABABAB"

Step 4: Fix status text opacity

Line 161, change:

Old: opacity: 0.8 New: (remove the line entirely, or set to 1.0)

Step 5: Add status text accessibility

After line 159 (font.bold: true), add:

                    Accessible.role: Accessible.StaticText
                    Accessible.name: "Loading status: " + text

Step 6: Verify

Launch app. Loader subtitle "AI TRANSCRIPTION ENGINE" should be brighter. Status text fully opaque. Border more visible.

Step 7: Commit

git add src/ui/qml/Loader.qml
git commit -m "WCAG: Loader - subtitle contrast, status opacity, accessible roles"

Task 12: Add Reduced Motion Support (Python Backend)

Files:

  • Modify: src/core/config.py:61 (add default)
  • Modify: src/ui/bridge.py (add property)
  • Modify: main.py (add OS detection)

Step 1: Add config default

In src/core/config.py, add to DEFAULT_SETTINGS dict, after line 61 ("unload_models_after_use": False):

    "unload_models_after_use": False,

    # Accessibility
    "reduce_motion": False   # Disable animations for WCAG 2.3.3

(Note: change the comma on the unload_models_after_use line too)

Step 2: Add bridge property

In src/ui/bridge.py, add a new signal in the UIBridge class signals section (after line 113):

    reduceMotionChanged = Signal(bool)

In __init__ (after line 132, self._is_destroyed = False), add:

        self._reduce_motion = bool(ConfigManager().get("reduce_motion"))

Add the property (after the uiScale property block, around line 289):

    @Property(bool, notify=reduceMotionChanged)
    def reduceMotion(self): return self._reduce_motion

    @reduceMotion.setter
    def reduceMotion(self, val):
        if self._reduce_motion != val:
            self._reduce_motion = val
            self.reduceMotionChanged.emit(val)

Also update setSetting (around line 275-280) to handle reduce_motion:

After the if key == "ui_scale": block, add:

        if key == "reduce_motion":
            self.reduceMotion = bool(value)

Step 3: Add Windows OS detection in main.py

After the resolution detection block (after line 81, before # Configure Logging), add:

# Detect Windows "Reduce Motion" preference
try:
    import ctypes
    SPI_GETCLIENTAREAANIMATION = 0x1042
    animation_enabled = ctypes.c_bool(True)
    ctypes.windll.user32.SystemParametersInfoW(
        SPI_GETCLIENTAREAANIMATION, 0,
        ctypes.byref(animation_enabled), 0
    )
    if not animation_enabled.value:
        # OS says reduce motion — set as default (user can override in Settings)
        ConfigManager().data["reduce_motion"] = True
        ConfigManager().save()
except Exception:
    pass

Step 4: Verify

Run the app. Check ui.reduceMotion is accessible from QML. No crashes.

Step 5: Commit

git add src/core/config.py src/ui/bridge.py main.py
git commit -m "WCAG: Add reduce_motion config, bridge property, OS detection"

Task 13: Apply Reduced Motion Conditionals to Overlay.qml

Files:

  • Modify: src/ui/qml/Overlay.qml

Step 1: Shimmer animation

Line 38-39, change:

Old: running: true New: running: !ui.reduceMotion

Step 2: Gradient blobs shader

Line 96-102, wrap the NumberAnimation. Change:

Old: NumberAnimation on time { from: 0; to: 1000; duration: 100000; loops: Animation.Infinite }

Add visible to the ShaderEffect (line 96):

After opacity: 0.4 (line 98), add:

                visible: !ui.reduceMotion

Step 3: Glow shader

After opacity: 0.04 (line 107), add:

                visible: !ui.reduceMotion

Step 4: Particles

On the ParticleSystem (line 115), add:

                running: !ui.reduceMotion

Step 5: CRT shader

On the CRT ShaderEffect (line 144), add:

                visible: !ui.reduceMotion

Step 6: Rainbow wave shader

On the ShaderEffect inside waveformContainer (line 278), add:

                visible: !ui.reduceMotion

Step 7: Mic button pulse animation

Line 247-252 (SequentialAnimation on scale), change:

Old: running: ui.isRecording New: running: ui.isRecording && !ui.reduceMotion

Step 8: Timer text blink animation

Line 344-348 (SequentialAnimation on opacity), change:

Old: running: ui.isRecording; loops: Animation.Infinite New: running: ui.isRecording && !ui.reduceMotion; loops: Animation.Infinite

Step 9: Recording border pulse

Line 196-201 (SequentialAnimation on border.color), change:

Old: running: ui.isRecording New: running: ui.isRecording && !ui.reduceMotion

Step 10: Verify

Set reduce_motion: true in settings.json manually. Launch app. Overlay should be static (no shimmer, no blobs, no glow, no particles, no CRT, no rainbow). Still functional — mic button works, timer shows.

Step 11: Commit

git add src/ui/qml/Overlay.qml
git commit -m "WCAG: Overlay - conditional animations for reduced motion"

Task 14: Apply Reduced Motion to Loader.qml + Add Settings Toggle

Files:

  • Modify: src/ui/qml/Loader.qml
  • Modify: src/ui/qml/Settings.qml

Step 1: Loader icon pulse

In Loader.qml, line 57-61 (SequentialAnimation on scale), change:

Old: loops: Animation.Infinite

Wrap it — add before the animation:

                    running: ui ? !ui.reduceMotion : true

(Use ternary because Loader loads before bridge might be ready)

Step 2: Loader progress shimmer

Line 144-148 (NumberAnimation on x), wrap the parent Rectangle. Add to the shimmer Rectangle (line 136):

                            visible: ui ? !ui.reduceMotion : true

Step 3: Add "Reduce Motion" toggle in Settings Visuals tab

In Settings.qml, find the Visuals tab Overlay section. After the "Window Opacity" ModernSettingsItem (around line 520-530, the one with showSeparator: false), change showSeparator: false to showSeparator: true on the opacity item, then add a new item after it:

                                    ModernSettingsItem {
                                        label: "Reduce Motion"
                                        description: "Disable animations for accessibility"
                                        showSeparator: false
                                        control: ModernSwitch {
                                            checked: ui.getSetting("reduce_motion")
                                            onToggled: ui.setSetting("reduce_motion", checked)
                                        }
                                    }

Step 4: Verify

Launch app. Open Settings > Visuals. Toggle "Reduce Motion" on. Overlay animations should stop. Loader shimmer should stop on next launch with setting persisted.

Step 5: Commit

git add src/ui/qml/Loader.qml src/ui/qml/Settings.qml
git commit -m "WCAG: Reduced motion toggle in Visuals, conditionals in Loader"

Task 15: Final Verification and Cleanup

Step 1: Full visual verification

Run the app end-to-end:

cd "D:/!!! SYSTEM DATA !!!/Desktop/python crap/whisper_voice" && python main.py

Checklist:

  • Loader appears with brighter subtitle, no opacity on status
  • Overlay border is visible
  • Settings opens with lighter purple accent (#B794F6)
  • All text is readable (no low-contrast text)
  • Switch shows I/O marks
  • Slider handles are 24px
  • Tab key moves through all interactive controls
  • Enter/Space activates focused controls
  • Reduce Motion toggle works (Settings > Visuals)
  • StatBox colors in Debug tab are lighter pastels
  • No QML warnings in console output

Step 2: Commit any remaining fixes

If any issues found, fix and commit individually.

Step 3: Final commit

git add -A
git commit -m "WCAG 2.2 AAA: Full compliance implementation complete"