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 }) => (
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
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
{!isAccessible && (
)}
);
};
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