UNPKG

@casoon/auditmysite

Version:

Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe

238 lines 8.59 kB
"use strict"; /** * 🎨 Color Contrast Helper * * Analyzes color contrast issues and provides actionable recommendations * with specific color suggestions that meet WCAG standards. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateContrastRatio = calculateContrastRatio; exports.getColorRecommendations = getColorRecommendations; exports.analyzeTailwindContrast = analyzeTailwindContrast; exports.generateContrastFixCSS = generateContrastFixCSS; exports.analyzeContrastIssues = analyzeContrastIssues; /** * Calculate relative luminance of a color * Formula from WCAG 2.0: https://www.w3.org/TR/WCAG20/#relativeluminancedef */ function getLuminance(r, g, b) { const [rs, gs, bs] = [r, g, b].map(c => { c = c / 255; return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); }); return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs; } /** * Calculate contrast ratio between two colors * Formula from WCAG 2.0: https://www.w3.org/TR/WCAG20/#contrast-ratiodef */ function calculateContrastRatio(fg, bg) { const fgRgb = parseColor(fg); const bgRgb = parseColor(bg); if (!fgRgb || !bgRgb) return 0; const l1 = getLuminance(fgRgb.r, fgRgb.g, fgRgb.b); const l2 = getLuminance(bgRgb.r, bgRgb.g, bgRgb.b); const lighter = Math.max(l1, l2); const darker = Math.min(l1, l2); return (lighter + 0.05) / (darker + 0.05); } /** * Parse CSS color to RGB */ function parseColor(color) { // Handle hex colors if (color.startsWith('#')) { const hex = color.replace('#', ''); if (hex.length === 3) { return { r: parseInt(hex[0] + hex[0], 16), g: parseInt(hex[1] + hex[1], 16), b: parseInt(hex[2] + hex[2], 16) }; } if (hex.length === 6) { return { r: parseInt(hex.substring(0, 2), 16), g: parseInt(hex.substring(2, 4), 16), b: parseInt(hex.substring(4, 6), 16) }; } } // Handle rgb/rgba const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); if (rgbMatch) { return { r: parseInt(rgbMatch[1]), g: parseInt(rgbMatch[2]), b: parseInt(rgbMatch[3]) }; } // Handle common color names const colorNames = { 'white': '#FFFFFF', 'black': '#000000', 'red': '#FF0000', 'green': '#008000', 'blue': '#0000FF', 'gray': '#808080', 'grey': '#808080' }; if (colorNames[color.toLowerCase()]) { return parseColor(colorNames[color.toLowerCase()]); } return null; } /** * Get color recommendations for contrast issues */ function getColorRecommendations(foreground, background, textSize = 'normal', targetLevel = 'AA') { const currentRatio = calculateContrastRatio(foreground, background); // WCAG 2.0 requirements const requiredRatio = targetLevel === 'AAA' ? (textSize === 'large' ? 4.5 : 7) : (textSize === 'large' ? 3 : 4.5); const recommendation = { originalForeground: foreground, originalBackground: background, currentRatio, requiredRatio, level: targetLevel, suggestions: { explanation: '' } }; if (currentRatio >= requiredRatio) { recommendation.suggestions.explanation = `Current contrast ratio ${currentRatio.toFixed(2)}:1 meets ${targetLevel} standards`; return recommendation; } // Generate suggestions const fgRgb = parseColor(foreground); const bgRgb = parseColor(background); if (!fgRgb || !bgRgb) { recommendation.suggestions.explanation = 'Could not parse colors for recommendations'; return recommendation; } // Determine if we should darken foreground or lighten background const bgLuminance = getLuminance(bgRgb.r, bgRgb.g, bgRgb.b); if (bgLuminance > 0.5) { // Light background - suggest darker foreground recommendation.suggestions.foreground = [ '#1F2937', // gray-800 '#111827', // gray-900 '#000000', // black '#374151' // gray-700 ]; recommendation.suggestions.explanation = `Current ratio ${currentRatio.toFixed(2)}:1 is below ${targetLevel} standard (${requiredRatio}:1). ` + `With a light background, use darker text colors. Suggested: #1F2937 (gray-800) or #111827 (gray-900).`; } else { // Dark background - suggest lighter foreground recommendation.suggestions.foreground = [ '#FFFFFF', // white '#F9FAFB', // gray-50 '#F3F4F6', // gray-100 '#E5E7EB' // gray-200 ]; recommendation.suggestions.explanation = `Current ratio ${currentRatio.toFixed(2)}:1 is below ${targetLevel} standard (${requiredRatio}:1). ` + `With a dark background, use lighter text colors. Suggested: #FFFFFF (white) or #F9FAFB (gray-50).`; } return recommendation; } /** * Analyze common Tailwind CSS color combinations */ function analyzeTailwindContrast(className, background = '#FFFFFF') { const tailwindColors = { 'text-gray-50': '#F9FAFB', 'text-gray-100': '#F3F4F6', 'text-gray-200': '#E5E7EB', 'text-gray-300': '#D1D5DB', 'text-gray-400': '#9CA3AF', 'text-gray-500': '#6B7280', 'text-gray-600': '#4B5563', 'text-gray-700': '#374151', 'text-gray-800': '#1F2937', 'text-gray-900': '#111827', 'text-black': '#000000', 'text-white': '#FFFFFF' }; const color = tailwindColors[className]; if (!color) { return { meetsAA: false, meetsAAA: false, ratio: 0, recommendation: 'Unknown Tailwind class' }; } const ratio = calculateContrastRatio(color, background); const meetsAA = ratio >= 4.5; const meetsAAA = ratio >= 7; let recommendation; if (!meetsAA) { if (background === '#FFFFFF' || background.toLowerCase() === 'white') { recommendation = 'Use text-gray-800 or text-gray-900 for better contrast on white backgrounds'; } else { recommendation = 'Adjust text color to meet WCAG AA standards (4.5:1 ratio)'; } } return { meetsAA, meetsAAA, ratio, recommendation }; } /** * Generate contrast fix CSS */ function generateContrastFixCSS(selector, originalColor, background) { const recommendation = getColorRecommendations(originalColor, background); if (!recommendation.suggestions.foreground || recommendation.suggestions.foreground.length === 0) { return `/* No specific recommendation available for ${selector} */`; } const suggestedColor = recommendation.suggestions.foreground[0]; return ` /* Original: ${originalColor} (contrast ratio: ${recommendation.currentRatio.toFixed(2)}:1) */ /* Required: ${recommendation.requiredRatio}:1 for WCAG ${recommendation.level} */ ${selector} { color: ${suggestedColor}; /* Meets ${recommendation.level} standards */ }`; } /** * Batch analyze colors from accessibility report */ function analyzeContrastIssues(issues) { const results = []; for (const issue of issues) { if (issue.code !== 'color-contrast') continue; // Try to extract colors from context const classMatch = issue.context.match(/class="[^"]*text-gray-(\d+)/); const bgMatch = issue.context.match(/bg-(\w+)/); if (classMatch) { const textClass = `text-gray-${classMatch[1]}`; const background = bgMatch ? `#FFFFFF` : '#FFFFFF'; // Default to white const recommendation = getColorRecommendations(getTailwindColor(textClass) || '#000000', background); const fixCSS = generateContrastFixCSS(issue.selector, getTailwindColor(textClass) || '#000000', background); results.push({ selector: issue.selector, issue: `Text class ${textClass} has insufficient contrast`, recommendation, fixCSS }); } } return results; } /** * Helper to get Tailwind color value */ function getTailwindColor(className) { const colors = { 'text-gray-700': '#374151', 'text-gray-800': '#1F2937', 'text-gray-900': '#111827' }; return colors[className] || null; } //# sourceMappingURL=color-contrast-helper.js.map