about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <{ID}+{username}@users.noreply.github.com>2025-02-10 16:14:35 -0500
committerelioat <{ID}+{username}@users.noreply.github.com>2025-02-10 16:14:35 -0500
commit7114d6a9be6ee2f9bdea37c124d1ef3ca18854e1 (patch)
tree53316a4ce793f73d4b93aab8e36b350283e55bd8
parentf787f14bcbbe4ef0a15c25b9e2ad554461af0fc4 (diff)
downloadtour-7114d6a9be6ee2f9bdea37c124d1ef3ca18854e1.tar.gz
*
-rw-r--r--html/ccc/index.html185
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() {