import React, { useState, useMemo, useEffect } from 'react'; import { CheckCircle2, XCircle, RefreshCw, Sun, Moon, Undo2, Type, Square, Star } from 'lucide-react'; const Confetti = () => { return (
{[...Array(12)].map((_, i) => (
))}
); }; const App = () => { const [bgColor, setBgColor] = useState('#ffffff'); const [textColor, setTextColor] = useState('#184563'); const [history, setHistory] = useState([]); const [showCelebration, setShowCelebration] = useState(false); const hexToRgb = (hex) => { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return [r, g, b]; }; const 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; }; const getContrast = (hex1, hex2) => { const rgb1 = hexToRgb(hex1); const rgb2 = hexToRgb(hex2); const l1 = getLuminance(...rgb1); const l2 = getLuminance(...rgb2); const brightest = Math.max(l1, l2); const darkest = Math.min(l1, l2); return (brightest + 0.05) / (darkest + 0.05); }; const contrastRatio = useMemo(() => getContrast(bgColor, textColor), [bgColor, textColor]); // WCAG Compliance Logic const compliance = useMemo(() => ({ normal: { aa: contrastRatio >= 4.5, aaa: contrastRatio >= 7.0 }, large: { aa: contrastRatio >= 3.0, aaa: contrastRatio >= 4.5 } }), [contrastRatio]); const isAccessible = compliance.normal.aa; useEffect(() => { if (isAccessible) { setShowCelebration(true); const timer = setTimeout(() => setShowCelebration(false), 1000); return () => clearTimeout(timer); } }, [isAccessible]); const updateColors = (newBg, newText) => { if (newBg.toLowerCase() === bgColor.toLowerCase() && newText.toLowerCase() === textColor.toLowerCase()) return; setHistory(prev => [{ bg: bgColor, text: textColor }, ...prev].slice(0, 20)); setBgColor(newBg); setTextColor(newText); }; const handleUndo = () => { if (history.length === 0) return; const previous = history[0]; setBgColor(previous.bg); setTextColor(previous.text); setHistory(prev => prev.slice(1)); }; const hexToHsl = (hex) => { let [r, g, b] = hexToRgb(hex); r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b), min = Math.min(r, g, b); let h, s, l = (max + min) / 2; if (max === min) { h = s = 0; } else { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h * 360, s * 100, l * 100]; }; const hslToHex = (h, s, l) => { l /= 100; const a = s * Math.min(l, 1 - l) / 100; const f = n => { const k = (n + h / 30) % 12; const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * color).toString(16).padStart(2, '0'); }; return `#${f(0)}${f(8)}${f(4)}`; }; const allAlternatives = useMemo(() => { const generateFor = (baseColor, targetToChange, fixedColor) => { const [h, s, l] = hexToHsl(baseColor); const lighter = []; const darker = []; for (let i = 0; i <= 100; i += 1) { const testHex = hslToHex(h, s, i); const ratio = targetToChange === 'text' ? getContrast(fixedColor, testHex) : getContrast(testHex, fixedColor); if (ratio >= 4.5) { if (i > l) lighter.push({ hex: testHex, ratio, l: i }); if (i < l) darker.push({ hex: testHex, ratio, l: i }); } } const pickFive = (arr, reverse = false) => { if (arr.length === 0) return []; const sorted = arr.sort((a, b) => reverse ? b.l - a.l : a.l - b.l); const result = []; const step = Math.max(1, Math.floor(sorted.length / 5)); for (let j = 0; j < sorted.length && result.length < 5; j += step) { result.push(sorted[j]); } return result; }; return { lighter: pickFive(lighter), darker: pickFive(darker, true) }; }; return { text: generateFor(textColor, 'text', bgColor), bg: generateFor(bgColor, 'bg', textColor) }; }, [bgColor, textColor]); const ComplianceBadge = ({ label, passed }) => (
{passed ? ( ) : ( )} {label}
); const ColorSection = ({ title, icon: IconComp, alts, type }) => (

{title}

Heller
{alts.lighter.map((alt, i) => ( ))}
Dunkler
{alts.darker.map((alt, i) => ( ))}
); return (

Der Kontrast-Prüfer Pro

{compliance.normal.aaa && } WCAG 2.1 Standard

Die Barrierefreiheit prüfen und die passenden Farbalternativen finden

updateColors(e.target.value, textColor)} className="w-20 h-20 rounded-[1.5rem] cursor-pointer border-none bg-transparent shadow-inner" /> updateColors(e.target.value, textColor)} className="flex-1 bg-transparent border-none focus:ring-0 font-mono text-3xl font-bold uppercase text-slate-700" />
updateColors(bgColor, e.target.value)} className="w-20 h-20 rounded-[1.5rem] cursor-pointer border-none bg-transparent shadow-inner" /> updateColors(bgColor, e.target.value)} className="flex-1 bg-transparent border-none focus:ring-0 font-mono text-3xl font-bold uppercase text-slate-700" />

Beispieltext

Dieser Bereich zeigt sofort, ob die gewählten Farben zusammen harmonieren

{contrastRatio.toFixed(2)} : 1
{/* Detailed Compliance Section */}
{showCelebration && }
Das Kontrastverhältnis
{contrastRatio.toFixed(2)} : 1
WCAG-Konformität
Normaler Text
Großer Text
{!isAccessible && (
)}
AA Standard
AAA Standard
); }; export default App; https://digital-workshop.at/post-sitemap1.xml 2026-03-25T13:28:32+01:00 https://digital-workshop.at/page-sitemap1.xml 2026-03-25T20:00:10+01:00 https://digital-workshop.at/portfolio-sitemap1.xml 2026-03-20T11:25:32+01:00 https://digital-workshop.at/category-sitemap1.xml https://digital-workshop.at/news.xml 2026-03-25T13:28:32+00:00