diff --git a/wcag-checker/README.md b/wcag-checker/README.md new file mode 100644 index 00000000..b7222e8f --- /dev/null +++ b/wcag-checker/README.md @@ -0,0 +1,185 @@ +# WCAG Contrast Checker - Chrome Extension + +> A lightweight Chrome extension for checking WCAG color contrast compliance, built as part of the [100LinesOfCode](https://github.com/josharsh/100LinesOfCode) project. + +This browser extension provides an interactive way for developers and designers to check color contrast ratios and ensure their designs meet WCAG accessibility standards - right from their browser toolbar. + +## Features + +* **Triple Color Selection Methods:** + - 🎨 Interactive color pickers + - 💧 EyeDropper tool to pick colors directly from any webpage + - ⌨️ Manual input (HEX or RGB format) +* **Real-time Contrast Calculation:** Instantly see the contrast ratio between your selected colors +* **WCAG Compliance Check:** Automatic validation against WCAG AA (4.5:1) and AAA (7:1) standards +* **Multiple Format Support:** Get and input color values in both HEX and RGB formats +* **Live Preview:** See exactly how your text will look with the selected color combination +* **Copy to Clipboard:** One-click copy functionality for all color values +* **Compact Design:** Optimized popup interface (400px width) perfect for quick checks +* **Accessible Design:** Built with accessibility in mind, following best practices + +## Project Structure + +``` +wcag-checker/ +├── index.html # Main popup HTML structure +├── style.css # Optimized styling with clean organization +├── script.js # JavaScript logic (under 100 lines!) +├── manifest.json # Chrome extension manifest v3 +└── README.md # Project documentation +``` + +## Installation + +### Option 1: Install from Source (Developer Mode) +1. Clone or download this repository +2. Open Chrome and navigate to `chrome://extensions/` +3. Enable "Developer mode" (toggle in top-right corner) +4. Click "Load unpacked" +5. Select the `wcag-checker` folder +6. The extension icon will appear in your toolbar! + +### Option 2: Test Locally (Without Installing) +```bash +# Navigate to the project folder +cd wcag-checker + +# Start a local server +python -m http.server 8000 +# or +npx http-server +``` + +Then open your browser and navigate to `http://localhost:8000` + +## How to Use + +1. **Click the extension icon** in your Chrome toolbar +2. **Choose your colors** using one of three methods: + - Click the color picker boxes + - Click "Pick from Page" to use the EyeDropper on any visible color + - Type directly in the HEX (`#FF5733`) or RGB (`rgb(255, 87, 51)`) fields +3. **View instant results:** + - Contrast ratio displayed prominently + - PASS/FAIL status for WCAG AA and AAA standards + - Live preview of text appearance +4. **Copy values** with one click for use in your projects + +## WCAG Standards + +- **WCAG AA (4.5:1):** Minimum contrast ratio for normal text, required for most web content +- **WCAG AAA (7:1):** Enhanced contrast ratio for the highest level of accessibility + +## Tech Stack + +* **HTML5:** Semantic structure +* **CSS3:** Modern styling with Flexbox layout +* **Vanilla JavaScript:** All logic in under 100 lines (96 lines) +* **Chrome Extension APIs:** Manifest V3, EyeDropper API +* **Web APIs:** Clipboard API for copy functionality + +## Browser Support + +Requires: +- Chrome 95+ (for EyeDropper API) +- Clipboard API support +- ES6+ JavaScript features + +## Permissions + +- `activeTab`: Required for EyeDropper functionality to pick colors from the current page +- `scripting`: Required for extension popup interaction + +--- + +# WCAG Contrast Checker - Extensão Chrome + +> Uma extensão leve para Chrome que verifica a conformidade de contraste de cores WCAG, construída como parte do projeto [100LinesOfCode](https://github.com/josharsh/100LinesOfCode). + +Esta extensão oferece uma forma interativa para desenvolvedores e designers verificarem taxas de contraste de cores e garantir que seus designs atendem aos padrões de acessibilidade WCAG - direto da barra de ferramentas do navegador. + +## Funcionalidades + +* **Três Métodos de Seleção de Cores:** + - 🎨 Seletores de cores interativos + - 💧 Ferramenta conta-gotas para pegar cores diretamente de qualquer página + - ⌨️ Entrada manual (formato HEX ou RGB) +* **Cálculo de Contraste em Tempo Real:** Veja instantaneamente a taxa de contraste entre suas cores +* **Verificação de Conformidade WCAG:** Validação automática contra padrões WCAG AA (4.5:1) e AAA (7:1) +* **Suporte a Múltiplos Formatos:** Obtenha e insira valores de cores em formatos HEX e RGB +* **Visualização ao Vivo:** Veja exatamente como seu texto ficará com a combinação de cores +* **Copiar para Área de Transferência:** Funcionalidade de cópia com um clique +* **Design Compacto:** Interface otimizada (400px de largura) perfeita para verificações rápidas +* **Design Acessível:** Construído pensando em acessibilidade + +## Estrutura do Projeto + +``` +wcag-checker/ +├── index.html # Estrutura HTML do popup principal +├── style.css # Estilos otimizados com organização limpa +├── script.js # Lógica JavaScript (menos de 100 linhas!) +├── manifest.json # Manifest da extensão Chrome v3 +└── README.md # Documentação do projeto +``` + +## Instalação + +### Opção 1: Instalar do Código Fonte (Modo Desenvolvedor) +1. Clone ou baixe este repositório +2. Abra o Chrome e navegue até `chrome://extensions/` +3. Ative o "Modo do desenvolvedor" (toggle no canto superior direito) +4. Clique em "Carregar sem compactação" +5. Selecione a pasta `wcag-checker` +6. O ícone da extensão aparecerá na sua barra de ferramentas! + +### Opção 2: Testar Localmente (Sem Instalar) +```bash +# Navegue até a pasta do projeto +cd wcag-checker + +# Inicie um servidor local +python -m http.server 8000 +# ou +npx http-server +``` + +Em seguida, abra seu navegador e navegue até `http://localhost:8000` + +## Como Usar + +1. **Clique no ícone da extensão** na barra de ferramentas do Chrome +2. **Escolha suas cores** usando um dos três métodos: + - Clique nas caixas de seleção de cor + - Clique em "Pick from Page" para usar o conta-gotas em qualquer cor visível + - Digite diretamente nos campos HEX (`#FF5733`) ou RGB (`rgb(255, 87, 51)`) +3. **Veja os resultados instantâneos:** + - Taxa de contraste exibida de forma proeminente + - Status PASS/FAIL para padrões WCAG AA e AAA + - Pré-visualização ao vivo da aparência do texto +4. **Copie valores** com um clique para usar em seus projetos + +## Padrões WCAG + +- **WCAG AA (4.5:1):** Taxa de contraste mínima para texto normal, necessária para a maioria dos conteúdos web +- **WCAG AAA (7:1):** Taxa de contraste aprimorada para o mais alto nível de acessibilidade + +## Stack Tecnológica + +* **HTML5:** Estrutura semântica +* **CSS3:** Estilização moderna com layout Flexbox +* **JavaScript Vanilla:** Toda a lógica em menos de 100 linhas (96 linhas) +* **APIs de Extensão Chrome:** Manifest V3, EyeDropper API +* **Web APIs:** API Clipboard para funcionalidade de cópia + +## Suporte de Navegadores + +Requer: +- Chrome 95+ (para EyeDropper API) +- Suporte à API Clipboard +- Recursos JavaScript ES6+ + +## Permissões + +- `activeTab`: Necessária para a funcionalidade do conta-gotas pegar cores da página atual +- `scripting`: Necessária para interação do popup da extensão \ No newline at end of file diff --git a/wcag-checker/index.html b/wcag-checker/index.html new file mode 100644 index 00000000..2e1a40be --- /dev/null +++ b/wcag-checker/index.html @@ -0,0 +1,140 @@ + + + + + + WCAG Contrast Checker + + + +
+

WCAG Contrast Checker

+ +
+
+
+

Choose Colors

+
+
+ +
+ +
+ +
+ + +
+
+ + +
+
+
+ +
+ +
+ +
+ + +
+
+ + +
+
+
+ +

Preview

+
+
+

This is how your text will look.

+

This is bold text.

+
+
+
+
+ +
+
+

Contrast Score

+
+
21.00 : 1
+
+ PASS + +
+
+ PASS + +
+
+ +

+ + + + + + About WCAG +

+
+

+ This tool measures the contrast ratio based on the + WCAG (Web Content Accessibility Guidelines), the global standard for ensuring web + content is accessible to people with visual impairments. +

+
    +
  • + Level AA (Passes at 4.5:1): + This is the accepted minimum standard for most web content. +
  • +
  • + Level AAA (Passes at 7:1): + This is the enhanced standard for the highest level of accessibility. +
  • +
+
+
+
+
+
+ + + diff --git a/wcag-checker/manifest.json b/wcag-checker/manifest.json new file mode 100644 index 00000000..51e0bf40 --- /dev/null +++ b/wcag-checker/manifest.json @@ -0,0 +1,13 @@ +{ + "manifest_version": 3, + "name": "WCAG Contrast Checker", + "version": "1.0", + "description": "Check color contrast and accessibility (WCAG) compliance for any website.", + "action": { + "default_popup": "index.html" + }, + "permissions": [ + "activeTab", + "scripting" + ] +} \ No newline at end of file diff --git a/wcag-checker/script.js b/wcag-checker/script.js new file mode 100644 index 00000000..01c3d3b3 --- /dev/null +++ b/wcag-checker/script.js @@ -0,0 +1,96 @@ +// SVG Icons +const iconCopy = ``; +const iconCheck = ``; + +// DOM Elements +const textColorInput = document.getElementById("text-color-input"); +const bgColorInput = document.getElementById("bg-color-input"); +const previewBox = document.getElementById("preview-box"); +const textHexValue = document.getElementById("text-hex-value"); +const textRgbValue = document.getElementById("text-rgb-value"); +const bgHexValue = document.getElementById("bg-hex-value"); +const bgRgbValue = document.getElementById("bg-rgb-value"); +const contrastRatioValue = document.getElementById("contrast-ratio-value"); +const wcagAAStatus = document.getElementById("wcag-aa-status"); +const wcagAAAStatus = document.getElementById("wcag-aaa-status"); + +// Event Listeners +[textColorInput, bgColorInput].forEach(el => el.addEventListener("input", handleColorChange)); + +function handleColorChange() { + const textHex = textColorInput.value.toUpperCase(), bgHex = bgColorInput.value.toUpperCase(); + const textRGB = hexToRgb(textHex), bgRGB = hexToRgb(bgHex); + previewBox.style.color = textHex; + previewBox.style.backgroundColor = bgHex; + const ratio = calculateContrastRatio(getLuminance(textRGB.r, textRGB.g, textRGB.b), getLuminance(bgRGB.r, bgRGB.g, bgRGB.b)); + contrastRatioValue.innerText = `${ratio.toFixed(2)} : 1`; + checkWCAGStandards(ratio); + textHexValue.value = textHex; + textRgbValue.value = `rgb(${textRGB.r}, ${textRGB.g}, ${textRGB.b})`; + bgHexValue.value = bgHex; + bgRgbValue.value = `rgb(${bgRGB.r}, ${bgRGB.g}, ${bgRGB.b})`; +} + +function hexToRgb(hex) { + const h = hex.replace("#", ""); + return { r: parseInt(h.substring(0, 2), 16), g: parseInt(h.substring(2, 4), 16), b: parseInt(h.substring(4, 6), 16) }; +} + +function getLuminance(r, g, b) { + const a = [r, g, b].map((v) => { + v /= 255; + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); + }); + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; +} + +function calculateContrastRatio(lum1, lum2) { + const L1 = Math.max(lum1, lum2); + const L2 = Math.min(lum1, lum2); + return (L1 + 0.05) / (L2 + 0.05); +} + +function checkWCAGStandards(contrastRatio) { + const updateStatus = (el, passes) => { el.innerText = passes ? "PASS" : "FAIL"; el.style.color = passes ? "green" : "red"; }; + updateStatus(wcagAAStatus, contrastRatio >= 4.5); + updateStatus(wcagAAAStatus, contrastRatio >= 7); +} + +// Copy buttons functionality +document.querySelectorAll(".copy-btn").forEach((btn) => { + btn.addEventListener("click", async () => { + const target = document.getElementById(btn.dataset.target); + try { + await navigator.clipboard.writeText(target.value); + btn.innerHTML = iconCheck; btn.disabled = true; + setTimeout(() => { btn.innerHTML = iconCopy; btn.disabled = false; }, 2000); + } catch (err) { console.error("Failed to copy: ", err); } + }); +}); + +// EyeDropper functionality +async function pickColor(target) { + if (!window.EyeDropper) { alert('Your browser does not support EyeDropper API.'); return; } + try { + const result = await new EyeDropper().open(); + target.value = result.sRGBHex.toUpperCase(); + handleColorChange(); + } catch (e) { console.log('Color selection cancelled'); } +} + +// Manual input handlers +function rgbToHex(r, g, b) { return "#" + [r, g, b].map(x => { const h = x.toString(16); return h.length === 1 ? "0" + h : h; }).join(""); } +function handleManualInput(input, colorInput) { + const v = input.value.trim().toUpperCase(); + if (v.startsWith("#") && /^#[0-9A-F]{6}$/i.test(v)) { colorInput.value = v; handleColorChange(); } + else if (v.startsWith("RGB")) { + const m = v.match(/RGB\((\d+),\s*(\d+),\s*(\d+)\)/); + if (m) { const [, r, g, b] = m.map(Number); if (r <= 255 && g <= 255 && b <= 255) { colorInput.value = rgbToHex(r, g, b); handleColorChange(); } } + } +} +[textHexValue, textRgbValue].forEach(el => el.addEventListener('blur', () => handleManualInput(el, textColorInput))); +[bgHexValue, bgRgbValue].forEach(el => el.addEventListener('blur', () => handleManualInput(el, bgColorInput))); + +// Attach EyeDropper to buttons +document.getElementById('text-eye-dropper-btn')?.addEventListener('click', () => pickColor(textColorInput)); +document.getElementById('bg-eye-dropper-btn')?.addEventListener('click', () => pickColor(bgColorInput)); \ No newline at end of file diff --git a/wcag-checker/style.css b/wcag-checker/style.css new file mode 100644 index 00000000..9f9ca3ab --- /dev/null +++ b/wcag-checker/style.css @@ -0,0 +1,297 @@ +/* Font Import and Base Styles */ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); + +svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } + +body { + font-family: "Inter", sans-serif; + background-color: #f7fafc; + width: 400px; + height: auto; + margin: 0; + padding: 1rem; + box-sizing: border-box; + display: block; +} + +/* Main Container */ +.picker-container { + padding: 0; + max-width: 100%; +} + +h1 { + margin-top: 0; + color: #1a202c; + font-size: 1.75rem; + font-weight: 700; + text-align: center; + margin-bottom: 2.5rem; +} + +/* Two Column Layout */ +.main-content-wrapper { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.column-left, +.column-right { + display: flex; + flex-direction: column; +} + +/* Card Sections */ +.card-section { + padding: 1rem; + border: 1px solid #e2e8f0; +} + +.card-section h2 { + margin-top: 0; + margin-bottom: 1.5rem; + color: #1a202c; + font-size: 1.25rem; + font-weight: 600; + padding-bottom: 1rem; + border-bottom: 1px solid #e2e8f0; + text-align: center; +} + +.info-title { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.info-title svg { + stroke: #1a202c; +} + +.section-divider { + margin-top: 2rem; + padding-top: 1.5rem; +} + +/* Color Selectors */ +.color-selectors { + display: flex; + justify-content: space-between; + gap: 1.5rem; +} + +.selector-group { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.75rem; + width: 48%; +} + +.selector-group label[for*="color-input"] { + font-weight: 600; + color: #2d3748; + font-size: 1.1rem; +} + +.color-input-wrapper { + width: 75%; + aspect-ratio: 1 / 1; + border-radius: 12px; + overflow: hidden; + border: 1px solid #e2e8f0; + margin-bottom: 0.5rem; +} + +input[type="color"] { + width: calc(100% + 10px); + height: calc(100% + 10px); + border: none; + padding: 0; + transform: translate(-5px, -5px); + cursor: pointer; +} + +/* Output Groups */ +.output-group { + display: flex; + width: 100%; + gap: 0.5rem; +} + +.output-group input[type="text"] { + flex-grow: 1; + border: 1px solid #cbd5e0; + border-radius: 8px; + padding: 0.5rem 0.75rem; + font-size: 0.9rem; + font-family: "Inter", sans-serif; + background-color: #ffffff; + color: #4a5568; + min-width: 0; + cursor: text; + transition: border-color 0.2s ease, background-color 0.2s ease; +} + +.output-group input[type="text"]:focus { + outline: none; + border-color: #3b82f6; + background-color: #f0f9ff; +} + +.output-group input[type="text"]:hover { + border-color: #94a3b8; +} + +/* Copy Buttons */ +.copy-btn { + background-color: #3b82f6; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: 500; + font-family: "Inter", sans-serif; + transition: background-color 0.2s ease, transform 0.1s ease; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + padding: 0; +} + +.copy-btn:hover { + background-color: #2563eb; +} + +.copy-btn:disabled { + background-color: #16a34a; +} + +.copy-btn:active { + transform: scale(0.98); +} + +/* Preview Box */ +#preview-box { + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 1.5rem; + font-size: 1rem; + line-height: 1.5; + background-color: #ffffff; + color: #000000; + transition: background-color 0.2s ease, color 0.2s ease; + text-align: center; + min-height: 100px; +} + +#preview-box p { + margin: 0 0 1rem 0; +} + +#preview-box p:last-child { + margin-bottom: 0; +} + +/* Contrast Results */ +.results-container { + text-align: center; + margin-bottom: 1.5rem; +} + +#contrast-ratio-value { + font-size: 2.5rem; + font-weight: 700; + color: #1a202c; + margin-bottom: 1.5rem; +} + +.status-group { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; +} + +.status-group:first-of-type { + border-bottom: 1px solid #e2e8f0; +} + +.status-group label { + font-size: 1rem; + color: #4a5568; + font-weight: 500; +} + +.status-group span { + font-size: 1rem; + font-weight: 700; + padding: 0.25rem 0.5rem; + border-radius: 4px; +} + +.status-group span[style*="color: green;"] { + background-color: rgba(22, 163, 74, 0.1); + color: rgb(21, 128, 61) !important; +} + +.status-group span[style*="color: red;"] { + background-color: rgba(220, 38, 38, 0.1); + color: rgb(185, 28, 28) !important; +} + +/* Info Container */ +.info-container p, +.info-container li { + font-size: 0.875rem; + color: #718096; + line-height: 1.6; + text-align: left; +} + +.info-container ul { + padding-left: 20px; + margin-top: 1rem; + margin-bottom: 0; +} + +.info-container li { + margin-bottom: 0.5rem; +} + +/* Eye Dropper Button */ +.dropper-btn-small { + display: flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + width: 100%; + padding: 0.5rem; + margin-bottom: 0.75rem; + background-color: #1a202c; + color: white; + border: none; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 500; + font-family: 'Inter', sans-serif; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.dropper-btn-small:hover { + background-color: #2d3748; +} + +.dropper-btn-small:active { + transform: scale(0.98); +} + +.dropper-btn-small svg { + stroke: white; +}