Skip to content

Commit 07d48da

Browse files
HeyItsGilbertclaudeCopilot
authored
feat: add color blind accessibility command suite (#6)
* feat: add color blind accessibility command suite Implements 11 new commands across 4 tiers covering the full accessibility surface described in the module manifest (colorblindness, screen readers, contrast analysis, and profile management). Tier 1 - Color accessibility (primary): - Set-ColorBlindProfile: applies Okabe-Ito based color schemes for Deuteranopia, Protanopia, Tritanopia, Achromatopsia, and AccessibleDefault; supports -Persist flag to write to $PROFILE for session persistence - Get-ColorBlindProfile: returns the active profile and its color table - Reset-ColorProfile: restores PSStyle formatting colors to PS defaults Tier 2 - Text & reading: - ConvertTo-PlainText: strips all ANSI escape codes (screen reader prep) - Enable-ScreenReaderMode / Disable-ScreenReaderMode: toggle PSStyle OutputRendering to PlainText for screen reader compatibility Tier 3 - Contrast analysis tools: - Test-ColorContrast: calculates WCAG 2.1 contrast ratio between two RGB colors and reports AA/AAA compliance (uses private WCAG math helpers) - Get-ColorBlindPalette: returns the full color table for a named profile Tier 4 - Profile management: - Get-AccessibilityProfile: snapshot of all active module settings - Export-AccessibilityProfile / Import-AccessibilityProfile: JSON serialization for sharing and restoring configurations Private helpers added: - Get-ColorBlindPaletteData: shared Okabe-Ito palette data store - Get-RelativeLuminance: W3C WCAG 2.1 relative luminance formula - Get-ContrastRatio: W3C WCAG 2.1 contrast ratio formula https://claude.ai/code/session_016yUMp2eoEEdVpEPAE98hDA * Enhance CI workflow with additional permissions Added permissions for contents and issues in CI workflow. * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 8c1ea47 commit 07d48da

15 files changed

Lines changed: 742 additions & 0 deletions

.github/workflows/CI.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
permissions:
66
checks: write
77
pull-requests: write
8+
contents: read
9+
issues: write
810
jobs:
911
ci:
12+
name: Continuous Integration
1013
uses: PSInclusive/.github/.github/workflows/ModuleCI.yml@main
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Okabe-Ito palette: peer-reviewed, safe across all common colorblindness types.
2+
# Source: https://jfly.uni-koeln.de/color/
3+
$script:OKABE_ITO = @{
4+
Black = @{ R = 0; G = 0; B = 0 }
5+
Orange = @{ R = 230; G = 159; B = 0 }
6+
SkyBlue = @{ R = 86; G = 180; B = 233 }
7+
BluishGreen = @{ R = 0; G = 158; B = 115 }
8+
Yellow = @{ R = 240; G = 228; B = 66 }
9+
Blue = @{ R = 0; G = 114; B = 178 }
10+
Vermillion = @{ R = 213; G = 94; B = 0 }
11+
ReddishPurple = @{ R = 204; G = 121; B = 167 }
12+
}
13+
14+
function Get-ColorBlindPaletteData {
15+
<#
16+
.SYNOPSIS
17+
Returns the RGB color table for a named color blind profile.
18+
19+
.DESCRIPTION
20+
Returns a hashtable of named color entries (each with R, G, B keys) for the specified
21+
color blindness profile. All profiles are derived from the Okabe-Ito palette, adjusted
22+
per the specific perceptual needs of each condition.
23+
24+
.PARAMETER ProfileType
25+
The color blindness profile to retrieve. Valid values: Deuteranopia, Protanopia,
26+
Tritanopia, Achromatopsia, AccessibleDefault.
27+
28+
.EXAMPLE
29+
Get-ColorBlindPaletteData -ProfileType Deuteranopia
30+
#>
31+
param (
32+
[Parameter(Mandatory = $true)]
33+
[ValidateSet('Deuteranopia', 'Protanopia', 'Tritanopia', 'Achromatopsia', 'AccessibleDefault')]
34+
[string]$ProfileType
35+
)
36+
37+
switch ($ProfileType) {
38+
'Deuteranopia' {
39+
# Red-green deficiency (most common). Maximize blue/orange/yellow contrast.
40+
# Red and green are shifted to orange and blue respectively.
41+
@{
42+
Error = $script:OKABE_ITO.Vermillion # Vermillion (not red)
43+
Warning = $script:OKABE_ITO.Orange # Orange
44+
Success = $script:OKABE_ITO.Blue # Blue (not green)
45+
Info = $script:OKABE_ITO.SkyBlue # Sky Blue
46+
Highlight = $script:OKABE_ITO.Yellow # Yellow
47+
Accent = $script:OKABE_ITO.ReddishPurple # Reddish Purple
48+
Text = $script:OKABE_ITO.Black # Black
49+
Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray (not part of Okabe-Ito)
50+
}
51+
}
52+
'Protanopia' {
53+
# Red-weak (cannot perceive red). Red-range hues shifted further toward orange/yellow.
54+
@{
55+
Error = @{ R = 230; G = 159; B = 0 } # Orange (red is invisible)
56+
Warning = @{ R = 240; G = 228; B = 66 } # Yellow
57+
Success = @{ R = 0; G = 114; B = 178 } # Blue
58+
Info = @{ R = 86; G = 180; B = 233 } # Sky Blue
59+
Highlight = @{ R = 204; G = 121; B = 167 } # Reddish Purple
60+
Accent = @{ R = 0; G = 158; B = 115 } # Bluish Green
61+
Text = @{ R = 0; G = 0; B = 0 } # Black
62+
Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray
63+
}
64+
}
65+
'Tritanopia' {
66+
# Blue-yellow deficiency. Shift to red/green contrast; replace blue with high-contrast cyan.
67+
@{
68+
Error = @{ R = 213; G = 94; B = 0 } # Vermillion
69+
Warning = @{ R = 204; G = 121; B = 167 } # Reddish Purple (not yellow)
70+
Success = @{ R = 0; G = 158; B = 115 } # Bluish Green
71+
Info = @{ R = 0; G = 200; B = 200 } # Cyan (high contrast, not pure blue)
72+
Highlight = @{ R = 255; G = 100; B = 100 } # Light Red (visible to tritanopes)
73+
Accent = @{ R = 180; G = 60; B = 60 } # Dark Red
74+
Text = @{ R = 0; G = 0; B = 0 } # Black
75+
Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray
76+
}
77+
}
78+
'Achromatopsia' {
79+
# Full colorblindness. High-contrast grayscale ladder designed for strong separation between steps.
80+
@{
81+
Error = @{ R = 30; G = 30; B = 30 } # Near-black (on white bg)
82+
Warning = @{ R = 80; G = 80; B = 80 } # Dark gray
83+
Success = @{ R = 50; G = 50; B = 50 } # Dark gray variant
84+
Info = @{ R = 110; G = 110; B = 110 } # Medium gray
85+
Highlight = @{ R = 230; G = 230; B = 230 } # Near-white
86+
Accent = @{ R = 0; G = 0; B = 0 } # Black
87+
Text = @{ R = 0; G = 0; B = 0 } # Black
88+
Muted = @{ R = 160; G = 160; B = 160 } # Light gray
89+
}
90+
}
91+
'AccessibleDefault' {
92+
# Okabe-Ito as-is: a safe general baseline for users unsure of their type.
93+
@{
94+
Error = @{ R = 213; G = 94; B = 0 } # Vermillion
95+
Warning = @{ R = 230; G = 159; B = 0 } # Orange
96+
Success = @{ R = 0; G = 158; B = 115 } # Bluish Green
97+
Info = @{ R = 86; G = 180; B = 233 } # Sky Blue
98+
Highlight = @{ R = 240; G = 228; B = 66 } # Yellow
99+
Accent = @{ R = 0; G = 114; B = 178 } # Blue
100+
Text = @{ R = 0; G = 0; B = 0 } # Black
101+
Muted = @{ R = 120; G = 120; B = 120 } # Mid-gray
102+
}
103+
}
104+
}
105+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
function Get-ContrastRatio {
2+
<#
3+
.SYNOPSIS
4+
Calculates the WCAG contrast ratio between two relative luminance values.
5+
6+
.DESCRIPTION
7+
Implements the W3C WCAG 2.1 contrast ratio formula. Returns a ratio between 1:1
8+
(no contrast) and 21:1 (maximum contrast, black on white).
9+
10+
.PARAMETER Luminance1
11+
Relative luminance of the first color (0.0 to 1.0).
12+
13+
.PARAMETER Luminance2
14+
Relative luminance of the second color (0.0 to 1.0).
15+
16+
.EXAMPLE
17+
Get-ContrastRatio -Luminance1 1.0 -Luminance2 0.0
18+
# Returns 21.0 (white on black)
19+
20+
.NOTES
21+
https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
22+
#>
23+
param (
24+
[Parameter(Mandatory = $true)]
25+
[double]$Luminance1,
26+
[Parameter(Mandatory = $true)]
27+
[double]$Luminance2
28+
)
29+
30+
$lighter = [Math]::Max($Luminance1, $Luminance2)
31+
$darker = [Math]::Min($Luminance1, $Luminance2)
32+
33+
return ($lighter + 0.05) / ($darker + 0.05)
34+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
function Get-RelativeLuminance {
2+
<#
3+
.SYNOPSIS
4+
Calculates the relative luminance of an RGB color per the W3C WCAG 2.1 formula.
5+
6+
.DESCRIPTION
7+
Implements the W3C relative luminance formula required for WCAG contrast ratio
8+
calculations. Returns a value between 0 (black) and 1 (white).
9+
10+
.PARAMETER R
11+
Red channel value (0-255).
12+
13+
.PARAMETER G
14+
Green channel value (0-255).
15+
16+
.PARAMETER B
17+
Blue channel value (0-255).
18+
19+
.EXAMPLE
20+
Get-RelativeLuminance -R 0 -G 0 -B 0
21+
# Returns 0 (black)
22+
23+
.EXAMPLE
24+
Get-RelativeLuminance -R 255 -G 255 -B 255
25+
# Returns 1 (white)
26+
27+
.NOTES
28+
https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
29+
#>
30+
param (
31+
[Parameter(Mandatory = $true)]
32+
[ValidateRange(0, 255)]
33+
[int]$R,
34+
[Parameter(Mandatory = $true)]
35+
[ValidateRange(0, 255)]
36+
[int]$G,
37+
[Parameter(Mandatory = $true)]
38+
[ValidateRange(0, 255)]
39+
[int]$B
40+
)
41+
42+
$channels = @($R / 255.0, $G / 255.0, $B / 255.0)
43+
$linearized = $channels | ForEach-Object {
44+
if ($_ -le 0.04045) {
45+
$_ / 12.92
46+
} else {
47+
[Math]::Pow(($_ + 0.055) / 1.055, 2.4)
48+
}
49+
}
50+
51+
return (0.2126 * $linearized[0]) + (0.7152 * $linearized[1]) + (0.0722 * $linearized[2])
52+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
function ConvertTo-PlainText {
2+
<#
3+
.SYNOPSIS
4+
Strips common ANSI color/style escape codes and PSStyle formatting from a string.
5+
6+
.DESCRIPTION
7+
Removes common ANSI/VT100 SGR-style escape sequences (colors, bold, italic, etc.)
8+
from text, returning clean plain text without those decorations. Useful for
9+
preparing terminal output for screen readers, plain-text logging, or piping to
10+
tools that do not handle such escape codes.
11+
12+
Supports pipeline input.
13+
14+
.PARAMETER InputText
15+
The text string to strip of ANSI color/style escape codes.
16+
17+
.EXAMPLE
18+
ConvertTo-PlainText -InputText "$($PSStyle.Bold)Hello$($PSStyle.BoldOff) World"
19+
20+
Returns 'Hello World' with no formatting.
21+
22+
.EXAMPLE
23+
ConvertTo-Bionic "The quick brown fox" | ConvertTo-PlainText
24+
25+
Converts text to bionic format then strips common ANSI color/style escape sequences
26+
for screen reader use.
27+
28+
.EXAMPLE
29+
Get-Content .\log.txt -Raw | ConvertTo-PlainText
30+
31+
Strips common ANSI color/style escape codes from a log file that may contain
32+
colored output.
33+
34+
.NOTES
35+
https://www.w3.org/WAI/WCAG21/Techniques/general/G166
36+
#>
37+
[CmdletBinding()]
38+
param (
39+
[Parameter(
40+
Mandatory = $true,
41+
ValueFromPipeline = $true,
42+
Position = 0
43+
)]
44+
[string]$InputText
45+
)
46+
47+
process {
48+
# Matches ESC (0x1B) followed by [ and any sequence of parameter/intermediate bytes and a final byte
49+
$InputText -replace '\x1B\[[0-9;]*[mABCDEFGHJKSTfhilmnprsu]', ''
50+
}
51+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function Disable-ScreenReaderMode {
2+
<#
3+
.SYNOPSIS
4+
Restores normal ANSI-formatted terminal output after screen reader mode.
5+
6+
.DESCRIPTION
7+
Clears the screen reader mode flag set by Enable-ScreenReaderMode, allowing
8+
Accessibility module commands to resume producing colored and styled terminal
9+
output using ANSI escape sequences.
10+
11+
.EXAMPLE
12+
Disable-ScreenReaderMode
13+
14+
Deactivates screen reader mode and restores ANSI-formatted output.
15+
16+
.EXAMPLE
17+
Enable-ScreenReaderMode
18+
ConvertTo-Bionic "Example text"
19+
Disable-ScreenReaderMode
20+
21+
Temporarily uses screen reader mode for one operation, then restores normal output.
22+
23+
.NOTES
24+
https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast
25+
#>
26+
[CmdletBinding()]
27+
param ()
28+
29+
$script:ScreenReaderMode = $false
30+
31+
if ($script:PreviousOutputRendering) {
32+
$PSStyle.OutputRendering = $script:PreviousOutputRendering
33+
Write-Verbose "Screen reader mode disabled. Previous output rendering restored."
34+
}
35+
else {
36+
# Fallback if no previous output rendering mode was saved.
37+
$PSStyle.OutputRendering = [System.Management.Automation.OutputRendering]::Host
38+
Write-Verbose "Screen reader mode disabled. Output rendering set to Host."
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function Enable-ScreenReaderMode {
2+
<#
3+
.SYNOPSIS
4+
Optimizes terminal output for screen reader assistive technology.
5+
6+
.DESCRIPTION
7+
Sets a module-wide flag that signals all Accessibility module commands to produce
8+
output that is compatible with screen readers: no ANSI color codes, no box-drawing
9+
characters, simplified structure, and plain descriptive text only.
10+
11+
Use Disable-ScreenReaderMode to return to normal output mode.
12+
13+
.EXAMPLE
14+
Enable-ScreenReaderMode
15+
16+
Activates screen reader-friendly output mode for all Accessibility module commands.
17+
18+
.EXAMPLE
19+
Enable-ScreenReaderMode
20+
ConvertTo-Bionic "The quick brown fox jumped over the lazy dog."
21+
22+
Enables screen reader mode then runs a bionic conversion. Output will contain no
23+
ANSI bold codes, just the plain text result.
24+
25+
.NOTES
26+
https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast
27+
#>
28+
[CmdletBinding()]
29+
param ()
30+
31+
$script:ScreenReaderMode = $true
32+
33+
if (-not (Get-Variable -Name PreviousOutputRendering -Scope Script -ErrorAction SilentlyContinue)) {
34+
$script:PreviousOutputRendering = $PSStyle.OutputRendering
35+
}
36+
37+
$PSStyle.OutputRendering = [System.Management.Automation.OutputRendering]::PlainText
38+
39+
Write-Verbose "Screen reader mode enabled. ANSI output suppressed."
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
function Export-AccessibilityProfile {
2+
<#
3+
.SYNOPSIS
4+
Exports the current accessibility settings to a JSON file.
5+
6+
.DESCRIPTION
7+
Serializes all active accessibility settings (color blind profile, screen reader mode,
8+
output rendering) to a JSON file at the specified path. The file can later be restored
9+
using Import-AccessibilityProfile, making it easy to share configurations or persist
10+
them outside of $PROFILE.
11+
12+
.PARAMETER Path
13+
The file path where the JSON settings file should be written.
14+
15+
.EXAMPLE
16+
Export-AccessibilityProfile -Path "$HOME\accessibility-settings.json"
17+
18+
Exports the current settings to a JSON file in the user's home directory.
19+
20+
.EXAMPLE
21+
Set-ColorBlindProfile -ProfileType Deuteranopia
22+
Export-AccessibilityProfile -Path ".\my-profile.json"
23+
24+
Applies a color profile then exports it to a local file.
25+
26+
.NOTES
27+
https://www.w3.org/WAI/WCAG21/Understanding/
28+
#>
29+
[CmdletBinding(SupportsShouldProcess)]
30+
param (
31+
[Parameter(Mandatory = $true, Position = 0)]
32+
[string]$Path
33+
)
34+
35+
$profile = Get-AccessibilityProfile
36+
37+
if ($PSCmdlet.ShouldProcess($Path, "Export accessibility settings")) {
38+
$profile | ConvertTo-Json -Depth 5 | Set-Content -Path $Path -Encoding UTF8
39+
Write-Verbose "Accessibility settings exported to: $Path"
40+
}
41+
}

0 commit comments

Comments
 (0)