From 4615f3084ffe7b344749d2e8aea84480975689e0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 20:56:51 +0200 Subject: [PATCH] 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-wcag-aaa-implementation.md | 1087 +++++++++++++++++ 1 file changed, 1087 insertions(+) create mode 100644 docs/plans/2026-02-18-wcag-aaa-implementation.md diff --git a/docs/plans/2026-02-18-wcag-aaa-implementation.md b/docs/plans/2026-02-18-wcag-aaa-implementation.md new file mode 100644 index 0000000..85d64c6 --- /dev/null +++ b/docs/plans/2026-02-18-wcag-aaa-implementation.md @@ -0,0 +1,1087 @@ +# 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: + +```qml +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** + +```bash +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: + +```qml +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** + +```bash +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: + +```qml +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** + +```bash +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: + +```qml +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** + +```bash +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): + +```qml + 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: + +```qml + 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** + +```bash +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: + +```qml + 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** + +```bash +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: + +```qml + 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** + +```bash +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: + +```qml + Accessible.name: root.title + " settings group" + Accessible.role: Accessible.Grouping +``` + +**Step 2: Add accessibility to ModernSettingsItem** + +After line 20 (`property bool showSeparator: true`), add: + +```qml + 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** + +```bash +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: + +```qml + 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: + +```qml + 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: + +```qml + // 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: + +```qml + 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): + +```qml + // 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** + +```bash +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: + +```qml + 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: + +```qml + 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): + +```qml + // 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: + +```qml + 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** + +```bash +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: + +```qml + 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: + +```qml + 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** + +```bash +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`): + +```python + "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): + +```python + reduceMotionChanged = Signal(bool) +``` + +In `__init__` (after line 132, `self._is_destroyed = False`), add: + +```python + self._reduce_motion = bool(ConfigManager().get("reduce_motion")) +``` + +Add the property (after the `uiScale` property block, around line 289): + +```python + @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: + +```python + 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: + +```python +# 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** + +```bash +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: +```qml + visible: !ui.reduceMotion +``` + +**Step 3: Glow shader** + +After `opacity: 0.04` (line 107), add: +```qml + visible: !ui.reduceMotion +``` + +**Step 4: Particles** + +On the ParticleSystem (line 115), add: +```qml + running: !ui.reduceMotion +``` + +**Step 5: CRT shader** + +On the CRT ShaderEffect (line 144), add: +```qml + visible: !ui.reduceMotion +``` + +**Step 6: Rainbow wave shader** + +On the ShaderEffect inside waveformContainer (line 278), add: +```qml + 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** + +```bash +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: +```qml + 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): + +```qml + 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: + +```qml + 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** + +```bash +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: + +```bash +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** + +```bash +git add -A +git commit -m "WCAG 2.2 AAA: Full compliance implementation complete" +```