""" Color Contrast Compliance System This module ensures WCAG color contrast compliance, provides high contrast modes, and validates color accessibility across the interface. """ from typing import Dict, List, Any, Optional, Tuple from flask import Blueprint, request, jsonify import colorsys class ColorContrastManager: """Manages color contrast compliance and accessibility.""" def __init__(self, app=None): self.app = app self.wcag_ratios = { 'AA': {'normal': 4.5, 'large': 3.0}, 'AAA': {'normal': 7.0, 'large': 4.5} } self.color_palette = {} def init_app(self, app): """Initialize with Flask app.""" self.app = app def calculate_contrast_ratio(self, color1: str, color2: str) -> float: """Calculate contrast ratio between two colors.""" # Convert colors to RGB rgb1 = self.hex_to_rgb(color1) rgb2 = self.hex_to_rgb(color2) # Calculate relative luminance lum1 = self.relative_luminance(rgb1) lum2 = self.relative_luminance(rgb2) # Calculate contrast ratio lighter = max(lum1, lum2) darker = min(lum1, lum2) return (lighter + 0.05) / (darker + 0.05) def hex_to_rgb(self, hex_color: str) -> Tuple[int, int, int]: """Convert hex color to RGB.""" hex_color = hex_color.lstrip('#') return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) def relative_luminance(self, rgb: Tuple[int, int, int]) -> float: """Calculate relative luminance of RGB color.""" def gamma_correct(channel): channel = channel / 255.0 return channel / 12.92 if channel <= 0.03928 else pow((channel + 0.055) / 1.055, 2.4) r, g, b = rgb return 0.2126 * gamma_correct(r) + 0.7152 * gamma_correct(g) + 0.0722 * gamma_correct(b) def get_contrast_css(self): """Generate CSS for color contrast compliance.""" return """ /* WCAG Color Contrast Compliance Styles */ /* Base color variables with WCAG AA compliant ratios */ :root { /* Primary colors - AA compliant */ --color-primary: #0066cc; --color-primary-contrast: #ffffff; --color-primary-hover: #004d99; --color-primary-light: #e6f2ff; /* Secondary colors - AA compliant */ --color-secondary: #6c757d; --color-secondary-contrast: #ffffff; --color-secondary-hover: #545b62; --color-secondary-light: #f8f9fa; /* Status colors - AA compliant */ --color-success: #28a745; --color-success-contrast: #ffffff; --color-success-light: #d4edda; --color-danger: #dc3545; --color-danger-contrast: #ffffff; --color-danger-light: #f8d7da; --color-warning: #ffc107; --color-warning-contrast: #000000; --color-warning-light: #fff3cd; --color-info: #17a2b8; --color-info-contrast: #ffffff; --color-info-light: #d1ecf1; /* Background colors */ --color-bg-primary: #ffffff; --color-bg-secondary: #f8f9fa; --color-bg-tertiary: #e9ecef; /* Text colors */ --color-text-primary: #212529; --color-text-secondary: #6c757d; --color-text-tertiary: #495057; --color-text-muted: #868e96; /* Border colors */ --color-border: #dee2e6; --color-border-dark: #6c757d; /* Link colors */ --color-link: #0066cc; --color-link-hover: #004d99; --color-link-visited: #551a8b; /* Focus colors */ --color-focus: #0066cc; --color-focus-shadow: rgba(0, 102, 204, 0.25); } /* Dark theme with AAA compliance where possible */ [data-bs-theme="dark"] { --color-primary: #4dabf7; --color-primary-contrast: #000000; --color-primary-hover: #339af0; --color-primary-light: #0c3653; --color-secondary: #adb5bd; --color-secondary-contrast: #000000; --color-secondary-hover: #95a3b0; --color-secondary-light: #343a40; --color-success: #51cf66; --color-success-contrast: #000000; --color-success-light: #0f3f1f; --color-danger: #ff6b6b; --color-danger-contrast: #000000; --color-danger-light: #4a1a1a; --color-warning: #ffd43b; --color-warning-contrast: #000000; --color-warning-light: #4a3a00; --color-info: #74c0fc; --color-info-contrast: #000000; --color-info-light: #0f2b3c; --color-bg-primary: #212529; --color-bg-secondary: #343a40; --color-bg-tertiary: #495057; --color-text-primary: #ffffff; --color-text-secondary: #adb5bd; --color-text-tertiary: #ced4da; --color-text-muted: #6c757d; --color-border: #495057; --color-border-dark: #343a40; --color-link: #4dabf7; --color-link-hover: #339af0; --color-link-visited: #9775fa; --color-focus: #4dabf7; --color-focus-shadow: rgba(77, 171, 247, 0.25); } /* High contrast mode - AAA compliance */ .high-contrast-mode { --color-primary: #000000; --color-primary-contrast: #ffffff; --color-primary-hover: #333333; --color-primary-light: #f0f0f0; --color-secondary: #000000; --color-secondary-contrast: #ffffff; --color-secondary-hover: #333333; --color-secondary-light: #f0f0f0; --color-success: #000000; --color-success-contrast: #ffffff; --color-success-light: #e6ffe6; --color-danger: #ffffff; --color-danger-contrast: #000000; --color-danger-light: #ffe6e6; --color-warning: #000000; --color-warning-contrast: #ffff00; --color-warning-light: #ffffcc; --color-info: #000000; --color-info-contrast: #ffffff; --color-info-light: #e6f7ff; --color-bg-primary: #ffffff; --color-bg-secondary: #f0f0f0; --color-bg-tertiary: #e0e0e0; --color-text-primary: #000000; --color-text-secondary: #000000; --color-text-tertiary: #000000; --color-text-muted: #666666; --color-border: #000000; --color-border-dark: #000000; --color-link: #0000ee; --color-link-hover: #000080; --color-link-visited: #800080; --color-focus: #ffff00; --color-focus-shadow: rgba(255, 255, 0, 0.5); } /* Apply WCAG compliant colors */ body { background-color: var(--color-bg-primary); color: var(--color-text-primary); } /* Button contrast compliance */ .btn-primary { background-color: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-contrast); } .btn-primary:hover, .btn-primary:focus { background-color: var(--color-primary-hover); border-color: var(--color-primary-hover); color: var(--color-primary-contrast); } .btn-secondary { background-color: var(--color-secondary); border-color: var(--color-secondary); color: var(--color-secondary-contrast); } .btn-secondary:hover, .btn-secondary:focus { background-color: var(--color-secondary-hover); border-color: var(--color-secondary-hover); color: var(--color-secondary-contrast); } /* Status button colors */ .btn-success { background-color: var(--color-success); border-color: var(--color-success); color: var(--color-success-contrast); } .btn-danger { background-color: var(--color-danger); border-color: var(--color-danger); color: var(--color-danger-contrast); } .btn-warning { background-color: var(--color-warning); border-color: var(--color-warning); color: var(--color-warning-contrast); } .btn-info { background-color: var(--color-info); border-color: var(--color-info); color: var(--color-info-contrast); } /* Link contrast compliance */ a { color: var(--color-link); } a:hover, a:focus { color: var(--color-link-hover); } a:visited { color: var(--color-link-visited); } /* Form element contrast */ .form-control { background-color: var(--color-bg-primary); border-color: var(--color-border); color: var(--color-text-primary); } .form-control:focus { background-color: var(--color-bg-primary); border-color: var(--color-focus); color: var(--color-text-primary); box-shadow: 0 0 0 0.25rem var(--color-focus-shadow); } .form-control::placeholder { color: var(--color-text-muted); } /* Alert contrast compliance */ .alert { border-left: 4px solid currentColor; } .alert-success { background-color: var(--color-success-light); border-color: var(--color-success); color: var(--color-text-primary); } .alert-danger { background-color: var(--color-danger-light); border-color: var(--color-danger); color: var(--color-text-primary); } .alert-warning { background-color: var(--color-warning-light); border-color: var(--color-warning); color: var(--color-text-primary); } .alert-info { background-color: var(--color-info-light); border-color: var(--color-info); color: var(--color-text-primary); } /* Card contrast */ .card { background-color: var(--color-bg-primary); border-color: var(--color-border); color: var(--color-text-primary); } .card-header { background-color: var(--color-bg-secondary); border-bottom-color: var(--color-border); } /* Table contrast */ .table { color: var(--color-text-primary); } .table th { background-color: var(--color-bg-secondary); border-color: var(--color-border); } .table td { border-color: var(--color-border); } .table-striped tbody tr:nth-of-type(odd) { background-color: var(--color-bg-secondary); } /* Navigation contrast */ .navbar { background-color: var(--color-bg-primary); border-color: var(--color-border); } .navbar .nav-link { color: var(--color-text-secondary); } .navbar .nav-link:hover, .navbar .nav-link:focus { color: var(--color-text-primary); } .navbar .nav-link.active { color: var(--color-primary); } /* Dropdown contrast */ .dropdown-menu { background-color: var(--color-bg-primary); border-color: var(--color-border); } .dropdown-item { color: var(--color-text-primary); } .dropdown-item:hover, .dropdown-item:focus { background-color: var(--color-primary-light); color: var(--color-text-primary); } /* Progress bar contrast */ .progress { background-color: var(--color-bg-secondary); } .progress-bar { background-color: var(--color-primary); color: var(--color-primary-contrast); } /* Badge contrast */ .badge { color: var(--color-primary-contrast); } .badge-primary { background-color: var(--color-primary); } .badge-secondary { background-color: var(--color-secondary); } .badge-success { background-color: var(--color-success); } .badge-danger { background-color: var(--color-danger); } .badge-warning { background-color: var(--color-warning); color: var(--color-warning-contrast); } .badge-info { background-color: var(--color-info); } /* Series card contrast */ .series-card { background-color: var(--color-bg-primary); border-color: var(--color-border); color: var(--color-text-primary); } .series-card:hover { border-color: var(--color-border-dark); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .series-card .series-title { color: var(--color-text-primary); } .series-card .series-meta { color: var(--color-text-secondary); } /* Focus indicators with proper contrast */ :focus { outline: 2px solid var(--color-focus); outline-offset: 2px; } :focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; box-shadow: 0 0 0 4px var(--color-focus-shadow); } /* Modal contrast */ .modal-content { background-color: var(--color-bg-primary); border-color: var(--color-border); color: var(--color-text-primary); } .modal-header { border-bottom-color: var(--color-border); } .modal-footer { border-top-color: var(--color-border); } /* Tooltip contrast */ .tooltip .tooltip-inner { background-color: var(--color-text-primary); color: var(--color-bg-primary); } /* Breadcrumb contrast */ .breadcrumb { background-color: var(--color-bg-secondary); } .breadcrumb-item { color: var(--color-text-secondary); } .breadcrumb-item.active { color: var(--color-text-primary); } .breadcrumb-item a { color: var(--color-link); } .breadcrumb-item a:hover { color: var(--color-link-hover); } /* Pagination contrast */ .pagination .page-link { background-color: var(--color-bg-primary); border-color: var(--color-border); color: var(--color-link); } .pagination .page-link:hover { background-color: var(--color-bg-secondary); border-color: var(--color-border-dark); color: var(--color-link-hover); } .pagination .page-item.active .page-link { background-color: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-contrast); } .pagination .page-item.disabled .page-link { background-color: var(--color-bg-secondary); border-color: var(--color-border); color: var(--color-text-muted); } /* List group contrast */ .list-group-item { background-color: var(--color-bg-primary); border-color: var(--color-border); color: var(--color-text-primary); } .list-group-item:hover { background-color: var(--color-bg-secondary); } .list-group-item.active { background-color: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-contrast); } /* Spinner contrast */ .spinner-border { border-color: var(--color-border); border-left-color: var(--color-primary); } /* Text utilities with proper contrast */ .text-primary { color: var(--color-primary) !important; } .text-secondary { color: var(--color-text-secondary) !important; } .text-success { color: var(--color-success) !important; } .text-danger { color: var(--color-danger) !important; } .text-warning { color: var(--color-warning-contrast) !important; } .text-info { color: var(--color-info) !important; } .text-muted { color: var(--color-text-muted) !important; } /* Background utilities with proper contrast */ .bg-primary { background-color: var(--color-primary) !important; color: var(--color-primary-contrast) !important; } .bg-secondary { background-color: var(--color-secondary) !important; color: var(--color-secondary-contrast) !important; } .bg-success { background-color: var(--color-success) !important; color: var(--color-success-contrast) !important; } .bg-danger { background-color: var(--color-danger) !important; color: var(--color-danger-contrast) !important; } .bg-warning { background-color: var(--color-warning) !important; color: var(--color-warning-contrast) !important; } .bg-info { background-color: var(--color-info) !important; color: var(--color-info-contrast) !important; } /* Selection highlight with proper contrast */ ::selection { background-color: var(--color-primary); color: var(--color-primary-contrast); } ::-moz-selection { background-color: var(--color-primary); color: var(--color-primary-contrast); } /* Scrollbar contrast (webkit) */ ::-webkit-scrollbar { background-color: var(--color-bg-secondary); } ::-webkit-scrollbar-thumb { background-color: var(--color-border-dark); } ::-webkit-scrollbar-thumb:hover { background-color: var(--color-text-muted); } /* High contrast mode enhancements */ .high-contrast-mode .btn { border-width: 2px; font-weight: bold; } .high-contrast-mode .form-control { border-width: 2px; } .high-contrast-mode .card { border-width: 2px; } .high-contrast-mode .series-card { border-width: 2px; } .high-contrast-mode :focus { outline-width: 3px; outline-color: var(--color-focus); box-shadow: 0 0 0 5px var(--color-focus-shadow); } /* Media queries for contrast preferences */ @media (prefers-contrast: high) { :root { --color-focus-shadow: rgba(0, 102, 204, 0.5); } .btn { border-width: 2px; } .form-control { border-width: 2px; } :focus { outline-width: 3px; box-shadow: 0 0 0 5px var(--color-focus-shadow); } } @media (prefers-contrast: more) { body { --color-text-primary: #000000; --color-bg-primary: #ffffff; --color-border: #000000; } [data-bs-theme="dark"] { --color-text-primary: #ffffff; --color-bg-primary: #000000; --color-border: #ffffff; } } /* Force colors mode support (Windows High Contrast) */ @media (forced-colors: active) { .btn { forced-color-adjust: none; border: 2px solid ButtonBorder; background: ButtonFace; color: ButtonText; } .btn:hover, .btn:focus { background: Highlight; color: HighlightText; border-color: HighlightText; } .form-control { forced-color-adjust: none; border: 2px solid FieldText; background: Field; color: FieldText; } .form-control:focus { outline: 2px solid Highlight; outline-offset: 2px; } .series-card { forced-color-adjust: none; border: 2px solid CanvasText; background: Canvas; color: CanvasText; } .series-card:focus { outline: 2px solid Highlight; outline-offset: 2px; } } /* Print contrast optimization */ @media print { :root { --color-text-primary: #000000; --color-bg-primary: #ffffff; --color-border: #000000; --color-link: #000080; } .btn { border: 2px solid #000000; background: #ffffff; color: #000000; } .series-card { border: 1px solid #000000; } } /* Reduced transparency for better contrast */ .contrast-enhanced .modal-backdrop { opacity: 0.8; } .contrast-enhanced .dropdown-menu { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } .contrast-enhanced .card { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } /* Color blind friendly alternatives */ .colorblind-friendly .text-success { text-decoration: underline; } .colorblind-friendly .text-danger { font-weight: bold; text-decoration: underline; } .colorblind-friendly .badge-success::before { content: "✓ "; } .colorblind-friendly .badge-danger::before { content: "✗ "; } .colorblind-friendly .badge-warning::before { content: "⚠ "; } /* Ensure minimum 3:1 ratio for large text */ h1, h2, h3, .h1, .h2, .h3, .btn-lg, .display-1, .display-2, .display-3 { /* These elements use large text AA standard (3:1 ratio) */ } /* Ensure minimum 4.5:1 ratio for normal text */ p, .btn, .form-control, .card-text, h4, h5, h6, .h4, .h5, .h6 { /* These elements use normal text AA standard (4.5:1 ratio) */ } """ def get_contrast_js(self): """Generate JavaScript for contrast management.""" return """ // AniWorld Color Contrast Manager class ColorContrastManager { constructor() { this.contrastLevel = 'AA'; // AA or AAA this.highContrastMode = false; this.colorBlindMode = false; this.customColors = {}; this.wcagRatios = { 'AA': { normal: 4.5, large: 3.0 }, 'AAA': { normal: 7.0, large: 4.5 } }; this.init(); } init() { this.detectContrastPreferences(); this.setupContrastControls(); this.validatePageContrast(); this.setupContrastMonitoring(); console.log('Color contrast manager initialized'); } detectContrastPreferences() { // Detect system contrast preferences if (window.matchMedia('(prefers-contrast: high)').matches) { this.enableHighContrast(); } if (window.matchMedia('(prefers-contrast: more)').matches) { this.contrastLevel = 'AAA'; } // Listen for changes window.matchMedia('(prefers-contrast: high)').addEventListener('change', (e) => { if (e.matches) { this.enableHighContrast(); } else { this.disableHighContrast(); } }); // Check for forced colors (Windows High Contrast) if (window.matchMedia('(forced-colors: active)').matches) { this.enableForcedColors(); } } setupContrastControls() { // Create contrast control panel this.createContrastPanel(); // Add keyboard shortcuts this.setupKeyboardShortcuts(); } createContrastPanel() { const panel = document.createElement('div'); panel.id = 'contrast-panel'; panel.className = 'contrast-panel'; panel.innerHTML = `