diff options
author | elioat <{ID}+{username}@users.noreply.github.com> | 2025-02-10 16:14:35 -0500 |
---|---|---|
committer | elioat <{ID}+{username}@users.noreply.github.com> | 2025-02-10 16:14:35 -0500 |
commit | 7114d6a9be6ee2f9bdea37c124d1ef3ca18854e1 (patch) | |
tree | 53316a4ce793f73d4b93aab8e36b350283e55bd8 | |
parent | f787f14bcbbe4ef0a15c25b9e2ad554461af0fc4 (diff) | |
download | tour-7114d6a9be6ee2f9bdea37c124d1ef3ca18854e1.tar.gz |
*
-rw-r--r-- | html/ccc/index.html | 185 |
1 files changed, 157 insertions, 28 deletions
diff --git a/html/ccc/index.html b/html/ccc/index.html index 864a18f..ef39110 100644 --- a/html/ccc/index.html +++ b/html/ccc/index.html @@ -12,6 +12,15 @@ .hidden { display: none; } .form-controls { display: flex; justify-content: space-between; align-items: center; } .checkbox-controls { display: flex; gap: 20px; align-items: center; } + .error { + color: #721c24; + background-color: #f8d7da; + border: 1px solid #f5c6cb; + padding: 1rem; + border-radius: 4px; + margin: 1rem 0; + grid-column: 1 / -1; + } </style> </head> <body> @@ -20,8 +29,8 @@ </header> <main> <form id="colorForm" onsubmit="event.preventDefault(); updateColors();"> - <label for="colors">Enter hex color codes (comma or newline-separated):</label> - <textarea id="colors" class="color-input" required></textarea> + <label for="colors">Enter colors as hex, rgb, or rgba values as either a comma separated list or a newline separated list:</label> + <textarea id="colors" class="color-input" required placeholder="Feel free to mix and match hex, rgb, and rgba values!"></textarea> <div class="form-controls"> <button type="submit">Check Contrast</button> <div class="checkbox-controls"> @@ -66,7 +75,82 @@ }); } - function getContrastRatio(c1, c2) { + function parseColor(color) { + // Remove all whitespace and convert to lowercase + const originalColor = color; // Store original format + color = color.replace(/\s/g, '').toLowerCase(); + + // If it's just numbers, assume it's an error + if (/^\d+$/.test(color)) { + throw new Error(`Invalid color format: ${color}. Please use hex (#RGB or #RRGGBB) or rgb()/rgba() format.`); + } + + // Add # prefix if it's a hex code without it + if (/^[0-9a-f]{3}$|^[0-9a-f]{6}$/.test(color)) { + color = '#' + color; + } + + // Handle hex colors + if (color.startsWith('#')) { + // Validate hex format + if (!/^#([0-9a-f]{3}|[0-9a-f]{6})$/.test(color)) { + throw new Error(`Invalid hex color format: ${color}. Use #RGB or #RRGGBB format.`); + } + + // Convert 3-digit hex to 6-digit + if (color.length === 4) { + color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; + } + // Remove # and convert to RGB array + const hex = color.substring(1); + return { + r: parseInt(hex.slice(0, 2), 16), + g: parseInt(hex.slice(2, 4), 16), + b: parseInt(hex.slice(4, 6), 16), + a: 1, + originalFormat: originalColor + }; + } + + // Handle rgb/rgba colors + if (color.startsWith('rgb')) { + // Validate rgb/rgba format + if (!/^rgba?\(\d+(\.\d+)?(,\d+(\.\d+)?){2}(,\d*\.?\d+)?\)$/.test(color)) { + throw new Error(`Invalid rgb/rgba format: ${color}. Use rgb(r,g,b) or rgba(r,g,b,a) format.`); + } + + const values = color.match(/[\d.]+/g); + const rgb = values.map(v => parseFloat(v)); + + // Validate rgb values are in range + if (rgb.slice(0, 3).some(v => v < 0 || v > 255)) { + throw new Error(`RGB values must be between 0 and 255: ${color}`); + } + if (rgb.length === 4 && (rgb[3] < 0 || rgb[3] > 1)) { + throw new Error(`Alpha value must be between 0 and 1: ${color}`); + } + + return { + r: rgb[0], + g: rgb[1], + b: rgb[2], + a: rgb.length === 4 ? rgb[3] : 1, + originalFormat: originalColor + }; + } + + throw new Error(`Unsupported color format: ${color}. Please use hex (#RGB or #RRGGBB) or rgb()/rgba() format.`); + } + + function colorToHex({r, g, b}) { + const toHex = (n) => { + const hex = Math.round(n).toString(16); + return hex.length === 1 ? '0' + hex : hex; + }; + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; + } + + function getContrastRatio(color1, color2) { function luminance(r, g, b) { let a = [r, g, b].map(v => { v /= 255; @@ -74,10 +158,28 @@ }); return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; } - let rgb1 = c1.match(/\w\w/g).map(x => parseInt(x, 16)); - let rgb2 = c2.match(/\w\w/g).map(x => parseInt(x, 16)); - let lum1 = luminance(...rgb1); - let lum2 = luminance(...rgb2); + + // Parse colors and handle alpha compositing with white background + const parseAndComposite = (color) => { + const parsed = parseColor(color); + if (parsed.a === 1) return parsed; + + // Composite with white background + const alpha = parsed.a; + return { + r: (parsed.r * alpha) + (255 * (1 - alpha)), + g: (parsed.g * alpha) + (255 * (1 - alpha)), + b: (parsed.b * alpha) + (255 * (1 - alpha)), + a: 1 + }; + }; + + const rgb1 = parseAndComposite(color1); + const rgb2 = parseAndComposite(color2); + + const lum1 = luminance(rgb1.r, rgb1.g, rgb1.b); + const lum2 = luminance(rgb2.r, rgb2.g, rgb2.b); + return (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05); } @@ -92,9 +194,13 @@ if (!accessible) classes.push("failing"); if (level === "AAA" && showGlow) classes.push("glowing-border"); + // Parse colors but use original format for display + const parsed1 = parseColor(color1); + const parsed2 = parseColor(color2); + return ` - <div class="${classes.join(' ')}" style="background: ${color1}; color: ${color2};"> - ${color1} on ${color2}<br> + <div class="${classes.join(' ')}" style="background: ${parsed1.originalFormat}; color: ${parsed2.originalFormat};"> + ${parsed1.originalFormat} on ${parsed2.originalFormat}<br> Ratio: ${contrast} ${accessible ? "✅" : "❌"}<br> WCAG: ${level} </div> @@ -103,29 +209,52 @@ function updateColors() { const input = document.getElementById("colors").value; - const colors = input.split(/[\n,]+/).map(c => c.trim()).filter(c => c); + + // First split by newlines + const colorLines = input.split(/\n+/).map(line => line.trim()).filter(line => line); + + // Then process each line to handle both comma-separated values and rgb/rgba formats + const colors = colorLines.flatMap(line => { + // Temporary replace commas in rgb/rgba values with a special marker + line = line.replace(/rgba?\([^)]+\)/g, match => match.replace(/,/g, '|')); + + // Split by commas and restore the original commas in rgb/rgba values + return line.split(',') + .map(color => color.trim().replace(/\|/g, ',')) + .filter(color => color); + }); + const results = document.getElementById("results"); - const colorCombinations = colors.flatMap((color1, i) => - colors.slice(i + 1).map(color2 => { - const contrast = getContrastRatio(color1, color2).toFixed(2); - const level = getWCAGLevel(contrast); - const accessible = level !== "Fail"; - return { color1, color2, contrast, level, accessible }; - }) - ); - - const sortByContrast = document.getElementById("sortContrast").checked; - if (sortByContrast) { - colorCombinations.sort((a, b) => b.contrast - a.contrast); - } + try { + const colorCombinations = colors.flatMap((color1, i) => + colors.slice(i + 1).map(color2 => { + try { + const contrast = getContrastRatio(color1, color2).toFixed(2); + const level = getWCAGLevel(contrast); + const accessible = level !== "Fail"; + return { color1, color2, contrast, level, accessible }; + } catch (error) { + console.error(`Error processing colors ${color1} and ${color2}:`, error); + return null; + } + }).filter(combo => combo !== null) + ); - const colorBoxes = colorCombinations.map(({ color1, color2, contrast, level, accessible }) => { - return createColorBox(color1, color2, contrast, level, accessible, false); - }); + const sortByContrast = document.getElementById("sortContrast").checked; + if (sortByContrast) { + colorCombinations.sort((a, b) => b.contrast - a.contrast); + } - results.innerHTML = colorBoxes.join(''); - updateVisibility(); + const colorBoxes = colorCombinations.map(({ color1, color2, contrast, level, accessible }) => { + return createColorBox(color1, color2, contrast, level, accessible, false); + }); + + results.innerHTML = colorBoxes.join(''); + updateVisibility(); + } catch (error) { + results.innerHTML = `<div class="error">Error: ${error.message}</div>`; + } } function updateVisibility() { |