type RGB = { r: number; g: number; b: number; a?: number }; const namedColors: Record = { transparent: 'rgba(0,0,0,0)', black: '#000000', white: '#ffffff', red: '#ff0000', green: '#008000', blue: '#0000ff', yellow: '#ffff00', cyan: '#00ffff', magenta: '#ff00ff', gray: '#808080', grey: '#808080', silver: '#c0c0c0', maroon: '#800000', olive: '#808000', lime: '#00ff00', aqua: '#00ffff', teal: '#008080', navy: '#000080', fuchsia: '#ff00ff', purple: '#800080', }; export function parseColor(colorString: string): RGB | null { if (!colorString || typeof colorString !== 'string') { return null; } const color = colorString.trim().toLowerCase(); if (color === 'inherit' || color === 'currentcolor') { return null; } if (namedColors[color]) { return parseColor(namedColors[color]); } if (color === 'transparent') { return { r: 0, g: 0, b: 0, a: 0 }; } const hexMatch = color.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i); if (hexMatch) { const hex = hexMatch[1]; if (hex.length === 3) { return { r: parseInt(hex[0] + hex[0], 16), g: parseInt(hex[1] + hex[1], 16), b: parseInt(hex[2] + hex[2], 16), }; } return { r: parseInt(hex.substr(0, 2), 16), g: parseInt(hex.substr(2, 2), 16), b: parseInt(hex.substr(4, 2), 16), }; } const rgbMatch = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)$/); if (rgbMatch) { const r = parseInt(rgbMatch[1], 10); const g = parseInt(rgbMatch[2], 10); const b = parseInt(rgbMatch[3], 10); const a = rgbMatch[4] ? parseFloat(rgbMatch[4]) : undefined; if (r > 255 || g > 255 || b > 255 || r < 0 || g < 0 || b < 0) { return null; } if (a !== undefined && (a < 0 || a > 1)) { return null; } return { r, g, b, a }; } const hslMatch = color.match(/^hsla?\((\d+),\s*([\d.]+)%,\s*([\d.]+)%(?:,\s*([\d.]+))?\)$/); if (hslMatch) { const h = parseInt(hslMatch[1], 10) / 360; const s = parseFloat(hslMatch[2]) / 100; const l = parseFloat(hslMatch[3]) / 100; const a = hslMatch[4] ? parseFloat(hslMatch[4]) : undefined; const hue2rgb = (p: number, q: number, t: number) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; return { r: Math.round(hue2rgb(p, q, h + 1 / 3) * 255), g: Math.round(hue2rgb(p, q, h) * 255), b: Math.round(hue2rgb(p, q, h - 1 / 3) * 255), a, }; } return null; } export function getLuminance(r: number, g: number, b: number): number { const [rs, gs, bs] = [r, g, b].map((c) => { const val = c / 255; return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4); }); return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs; } export function isDarkColor(colorString: string): boolean { const rgb = parseColor(colorString); if (!rgb) return false; const luminance = getLuminance(rgb.r, rgb.g, rgb.b); return luminance < 0.5; } export function transformColorForDarkMode(colorString: string): string { const rgb = parseColor(colorString); if (!rgb) return colorString; if (rgb.a !== undefined && rgb.a < 0.1) { return colorString; } const luminance = getLuminance(rgb.r, rgb.g, rgb.b); if (luminance < 0.4) { const invR = 255 - rgb.r; const invG = 255 - rgb.g; const invB = 255 - rgb.b; const boost = 1.3; const r = Math.min(255, Math.round(invR * boost)); const g = Math.min(255, Math.round(invG * boost)); const b = Math.min(255, Math.round(invB * boost)); return rgb.a !== undefined ? `rgba(${r}, ${g}, ${b}, ${rgb.a})` : `rgb(${r}, ${g}, ${b})`; } if (luminance >= 0.4 && luminance < 0.6) { const factor = 1.5; const r = Math.min(255, Math.round(rgb.r + (255 - rgb.r) * factor * 0.4)); const g = Math.min(255, Math.round(rgb.g + (255 - rgb.g) * factor * 0.4)); const b = Math.min(255, Math.round(rgb.b + (255 - rgb.b) * factor * 0.4)); return rgb.a !== undefined ? `rgba(${r}, ${g}, ${b}, ${rgb.a})` : `rgb(${r}, ${g}, ${b})`; } return colorString; } export function transformInlineStyles(cssText: string, theme: 'light' | 'dark'): string { if (theme !== 'dark' || !cssText) { return cssText; } const styleProps = cssText.split(';').map((prop) => prop.trim()).filter(Boolean); const transformedProps = styleProps.map((prop) => { const colonIndex = prop.indexOf(':'); if (colonIndex === -1) return prop; const property = prop.slice(0, colonIndex).trim(); const value = prop.slice(colonIndex + 1).trim(); if (property === 'color') { const hasImportant = value.includes('!important'); const colorValue = value.replace('!important', '').trim(); const transformed = transformColorForDarkMode(colorValue); return `${property}: ${transformed}${hasImportant ? ' !important' : ''}`; } if (property === 'background-color') { const hasImportant = value.includes('!important'); const colorValue = value.replace('!important', '').trim(); const transformed = transformColorForDarkMode(colorValue); return `${property}: ${transformed}${hasImportant ? ' !important' : ''}`; } if (property === 'background' && !value.includes('url(')) { const colorMatch = value.match(/#[0-9a-f]{3,6}|rgba?\([^)]+\)|hsla?\([^)]+\)|[a-z]+/i); if (colorMatch) { const hasImportant = value.includes('!important'); const originalColor = colorMatch[0]; const transformed = transformColorForDarkMode(originalColor); const newValue = value.replace(originalColor, transformed); return `${property}: ${newValue.replace('!important', '').trim()}${hasImportant ? ' !important' : ''}`; } } if (property === 'border-color') { const hasImportant = value.includes('!important'); const colorValue = value.replace('!important', '').trim(); const transformed = transformColorForDarkMode(colorValue); return `${property}: ${transformed}${hasImportant ? ' !important' : ''}`; } return prop; }); return transformedProps.join('; '); }