chatbot1-8 Exercise 3: A Contrast Checker ============================================ ex3.py (save in the project root): def lum(hex_colour): n = int(hex_colour.lstrip("#"), 16) channels = [] for shift in (16, 8, 0): v = ((n >> shift) & 255) / 255 channels.append(v / 12.92 if v <= 0.03928 else ((v + 0.055) / 1.055) ** 2.4) r, g, b = channels return 0.2126 * r + 0.7152 * g + 0.0722 * b def contrast(a, b): hi, lo = sorted((lum(a), lum(b)), reverse=True) return (hi + 0.05) / (lo + 0.05) PALETTE = {"text on page": ("#1a1a1a", "#ffffff"), "muted note": ("#9a9a9a", "#ffffff"), "error text": ("#e57373", "#f2f4f7"), "button text": ("#ffffff", "#1d4ed8")} FIXED = {**PALETTE, "muted note": ("#5a5a5a", "#ffffff"), "error text": ("#b42318", "#f2f4f7")} for title, palette in (("original", PALETTE), ("after the fix", FIXED)): print(title) for name, (fg, bg) in palette.items(): ratio = contrast(fg, bg) print(f" {name:<14} {fg} on {bg} {ratio:5.2f}:1 {'ok' if ratio >= 4.5 else 'FAILS (needs 4.5:1)'}") Run with: py ex3.py Output: original text on page #1a1a1a on #ffffff 17.40:1 ok muted note #9a9a9a on #ffffff 2.81:1 FAILS (needs 4.5:1) error text #e57373 on #f2f4f7 2.71:1 FAILS (needs 4.5:1) button text #ffffff on #1d4ed8 6.70:1 ok after the fix text on page #1a1a1a on #ffffff 17.40:1 ok muted note #5a5a5a on #ffffff 6.90:1 ok error text #b42318 on #f2f4f7 5.97:1 ok button text #ffffff on #1d4ed8 6.70:1 ok Replacement colours: #5a5a5a for the muted note on white (6.90:1) and #b42318 for the error text on the light grey bubble (5.97:1); these are the colours the chat page uses. What the check does not tell you: it measures one thing, the brightness difference between a text colour and its background. It says nothing about whether the page makes sense to a screen reader, whether everything can be used with the keyboard alone, whether text stays readable at 200% zoom, whether colour is the only thing carrying a meaning (the error is also written as text), whether focus is visible, or whether the language and structure are right. Those need other checks and, for a real service, testing with people who use assistive technology. WHY THIS WORKS AS AN ANSWER --------------------------- The WCAG formula converts each colour to a relative luminance (after undoing the gamma correction of each channel), then the contrast ratio is (lighter + 0.05) / (darker + 0.05). Normal-size text needs at least 4.5:1 for level AA. The pale grey note scored 2.81:1 and the light red error text 2.71:1, both well short; darkening them to the replacement values gave 6.90:1 and 5.97:1. The ratios for the page's real palette, measured in the browser, were computed with the same formula.