export interface SpreadsheetTheme { headerBg: string; headerText: string; cellBg: string; border: string; text: string; textMuted: string; selectionBg: string; selectionBorder: string; dirtyBg: string; rowNumBg: string; fillHandle: string; fixedShadow: string; imagePlaceholder: string; findMatchBg: string; findActiveMatchBg: string; findActiveBorder: string; } export function getSpreadsheetTheme(isDark: boolean): SpreadsheetTheme { if (isDark) { return { headerBg: '#1f1f1f', headerText: 'rgba(255, 255, 255, 0.85)', cellBg: '#141414', border: '#424242', text: 'rgba(255, 255, 255, 0.85)', textMuted: 'rgba(255, 255, 255, 0.45)', selectionBg: 'rgba(22, 93, 255, 0.25)', selectionBorder: '#1668dc', dirtyBg: 'rgba(255, 125, 0, 0.2)', rowNumBg: '#1a1a1a', fillHandle: '#1668dc', fixedShadow: 'rgba(0, 0, 0, 0.45)', imagePlaceholder: '#303030', findMatchBg: 'rgba(255, 212, 0, 0.35)', findActiveMatchBg: 'rgba(255, 212, 0, 0.55)', findActiveBorder: '#faad14', }; } return { headerBg: '#f5f7fa', headerText: '#1d2129', cellBg: '#ffffff', border: '#e5e6eb', text: '#1d2129', textMuted: '#86909c', selectionBg: 'rgba(22, 93, 255, 0.08)', selectionBorder: '#165dff', dirtyBg: 'rgba(255, 125, 0, 0.12)', rowNumBg: '#fafafa', fillHandle: '#165dff', fixedShadow: 'rgba(0, 0, 0, 0.12)', imagePlaceholder: '#f2f3f5', findMatchBg: '#fff566', findActiveMatchBg: '#ffe58f', findActiveBorder: '#faad14', }; } export const HEADER_COLOR_PRESETS = [ '#f0f5ff', '#fff7e6', '#f6ffed', '#fff1f0', '#f9f0ff', '#e6fffb', ]; function parseHexColor(hex: string): { r: number; g: number; b: number } | null { const normalized = hex.trim().replace('#', ''); if (normalized.length === 3) { return { r: Number.parseInt(normalized[0]! + normalized[0], 16), g: Number.parseInt(normalized[1]! + normalized[1], 16), b: Number.parseInt(normalized[2]! + normalized[2], 16), }; } if (normalized.length === 6) { return { r: Number.parseInt(normalized.slice(0, 2), 16), g: Number.parseInt(normalized.slice(2, 4), 16), b: Number.parseInt(normalized.slice(4, 6), 16), }; } return null; } function rgbToHsl(r: number, g: number, b: number) { const rn = r / 255; const gn = g / 255; const bn = b / 255; const max = Math.max(rn, gn, bn); const min = Math.min(rn, gn, bn); const l = (max + min) / 2; if (max === min) { return { h: 0, s: 0, l }; } const d = max - min; const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); let h = 0; if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6; else if (max === gn) h = ((bn - rn) / d + 2) / 6; else h = ((rn - gn) / d + 4) / 6; return { h, s, l }; } function hslToHex(h: number, s: number, l: number): string { if (s === 0) { const v = Math.round(l * 255); const hex = v.toString(16).padStart(2, '0'); return `#${hex}${hex}${hex}`; } const hue2rgb = (p: number, q: number, t: number) => { let tt = t; if (tt < 0) tt += 1; if (tt > 1) tt -= 1; if (tt < 1 / 6) return p + (q - p) * 6 * tt; if (tt < 1 / 2) return q; if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6; return p; }; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; const r = Math.round(hue2rgb(p, q, h + 1 / 3) * 255); const g = Math.round(hue2rgb(p, q, h) * 255); const b = Math.round(hue2rgb(p, q, h - 1 / 3) * 255); return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; } export function resolveHeaderColor( color: string | undefined, isDark: boolean, fallback: string, ): string { if (!color) return fallback; if (!isDark) return color; const rgb = parseHexColor(color); if (!rgb) return fallback; const { h, s, l } = rgbToHsl(rgb.r, rgb.g, rgb.b); const darkL = Math.min(0.28, Math.max(0.16, l * 0.35 + 0.12)); const darkS = Math.min(0.55, s * 0.75 + 0.08); return hslToHex(h, darkS, darkL); }