import { AppColorScheme, ColorPalette } from '../types'; interface SchemeColors { text: string; textSecondary: string; textMuted: string; textOnImage: string; background: string; surface: string; surfaceMuted: string; surfaceStrong: string; border: string; borderStrong: string; overlay: string; overlayStrong: string; heroButton: string; heroButtonBorder: string; tabBarBg: string; tabBarBorder: string; inputBg: string; inputBorder: string; cardBg: string; cardBorder: string; cardShadow: string; chipBg: string; chipBorder: string; pageBase: string; pageGradientStart: string; pageGradientEnd: string; pageTexture: string; } interface PaletteColors { primary: string; primaryDark: string; info: string; warning: string; danger: string; success: string; accent: string; } interface PaletteSurfaceTone { background: string; surface: string; surfaceMuted: string; surfaceStrong: string; border: string; borderStrong: string; tabBarBg: string; tabBarBorder: string; pageBase: string; pageGradientStart: string; pageGradientEnd: string; pageTexture: string; } interface PaletteThemeTone { light: PaletteSurfaceTone; dark: PaletteSurfaceTone; } export interface AppColors extends SchemeColors, PaletteColors { onPrimary: string; primarySoft: string; infoSoft: string; warningSoft: string; dangerSoft: string; successSoft: string; primaryTint: string; infoTint: string; warningTint: string; dangerTint: string; successTint: string; mutedChip: string; fabBg: string; fabShadow: string; iconOnImage: string; } const textByScheme: Record< AppColorScheme, Pick > = { light: { text: '#1f2520', textSecondary: '#4d5650', textMuted: '#6e7871', textOnImage: '#f7f8f7', overlay: 'rgba(16, 20, 17, 0.4)', overlayStrong: 'rgba(13, 17, 14, 0.78)', }, dark: { text: '#f0f3f1', textSecondary: '#c9d0cb', textMuted: '#a1aba4', textOnImage: '#f5f7f6', overlay: 'rgba(9, 11, 10, 0.52)', overlayStrong: 'rgba(8, 10, 9, 0.86)', }, }; const paletteSurfaces: Record = { forest: { light: { background: '#ecf3ed', surface: '#f5faf6', surfaceMuted: '#e4ede6', surfaceStrong: '#d8e3d9', border: '#c7d4c8', borderStrong: '#b3c4b5', tabBarBg: '#f1f7f2', tabBarBorder: '#ccd9ce', pageBase: '#e9f1ea', pageGradientStart: '#dde9de', pageGradientEnd: '#f1f5f0', pageTexture: '#4b6a50', }, dark: { background: '#111813', surface: '#17211a', surfaceMuted: '#1d2920', surfaceStrong: '#243128', border: '#2b3a2f', borderStrong: '#394c3d', tabBarBg: '#141d17', tabBarBorder: '#2f4033', pageBase: '#111813', pageGradientStart: '#15211a', pageGradientEnd: '#0f1511', pageTexture: '#91b196', }, }, ocean: { light: { background: '#ebf1f8', surface: '#f4f8fc', surfaceMuted: '#e1e8f1', surfaceStrong: '#d4deeb', border: '#c2cfde', borderStrong: '#acbdd1', tabBarBg: '#eef4fa', tabBarBorder: '#c8d6e6', pageBase: '#e9f0f7', pageGradientStart: '#d9e3f0', pageGradientEnd: '#eef3f8', pageTexture: '#4c6480', }, dark: { background: '#10171f', surface: '#16202b', surfaceMuted: '#1c2734', surfaceStrong: '#243143', border: '#2c3b4d', borderStrong: '#3a4e64', tabBarBg: '#131c26', tabBarBorder: '#324357', pageBase: '#10171f', pageGradientStart: '#152230', pageGradientEnd: '#0f151d', pageTexture: '#96aac2', }, }, sunset: { light: { background: '#f7eee8', surface: '#fdf7f3', surfaceMuted: '#f2e5da', surfaceStrong: '#ead8c8', border: '#dbc4b1', borderStrong: '#cfb197', tabBarBg: '#f9f0e8', tabBarBorder: '#decaB9', pageBase: '#f6ece4', pageGradientStart: '#efdccd', pageGradientEnd: '#f8f1eb', pageTexture: '#8a644e', }, dark: { background: '#1b1410', surface: '#271c16', surfaceMuted: '#31231b', surfaceStrong: '#3f2d22', border: '#4a372a', borderStrong: '#604633', tabBarBg: '#231913', tabBarBorder: '#553d2c', pageBase: '#1a1410', pageGradientStart: '#2a1d15', pageGradientEnd: '#16110d', pageTexture: '#c8a690', }, }, mono: { light: { background: '#eff1f3', surface: '#f7f8fa', surfaceMuted: '#e5e8ec', surfaceStrong: '#d8dde3', border: '#c8ced6', borderStrong: '#b4bdc8', tabBarBg: '#f2f4f7', tabBarBorder: '#cdd3db', pageBase: '#eceff2', pageGradientStart: '#dfe4ea', pageGradientEnd: '#f2f4f6', pageTexture: '#666f7b', }, dark: { background: '#131518', surface: '#1b1f24', surfaceMuted: '#232932', surfaceStrong: '#2d3540', border: '#343f4c', borderStrong: '#455364', tabBarBg: '#171b20', tabBarBorder: '#3a4552', pageBase: '#131518', pageGradientStart: '#1a2028', pageGradientEnd: '#111418', pageTexture: '#a1adbc', }, }, }; const palettes: Record = { forest: { primary: '#5fa779', primaryDark: '#3d7f57', info: '#4e7fb3', warning: '#bb8a36', danger: '#be5d5d', success: '#4f9767', accent: '#4a8e7f', }, ocean: { primary: '#5a90be', primaryDark: '#3d6f99', info: '#4e79b1', warning: '#bc8b37', danger: '#be6464', success: '#4f8b80', accent: '#4f8fa0', }, sunset: { primary: '#c98965', primaryDark: '#a36442', info: '#6c89b4', warning: '#bd8742', danger: '#b8666d', success: '#769f6e', accent: '#b47453', }, mono: { primary: '#7b8796', primaryDark: '#5b6574', info: '#748498', warning: '#9d8b5a', danger: '#a86868', success: '#6f8b75', accent: '#6c7785', }, }; const hexToRgb = (hex: string) => { const cleaned = hex.replace('#', ''); const normalized = cleaned.length === 3 ? cleaned .split('') .map((char) => `${char}${char}`) .join('') : cleaned; const int = Number.parseInt(normalized, 16); return { r: (int >> 16) & 255, g: (int >> 8) & 255, b: int & 255, }; }; const withOpacity = (hex: string, opacity: number) => { const { r, g, b } = hexToRgb(hex); return `rgba(${r}, ${g}, ${b}, ${opacity})`; }; const mixColors = (baseHex: string, tintHex: string, tintWeight: number) => { const base = hexToRgb(baseHex); const tint = hexToRgb(tintHex); const weight = Math.max(0, Math.min(1, tintWeight)); const r = Math.round(base.r + (tint.r - base.r) * weight); const g = Math.round(base.g + (tint.g - base.g) * weight); const b = Math.round(base.b + (tint.b - base.b) * weight); return `rgb(${r}, ${g}, ${b})`; }; const getOnColor = (hex: string) => { const { r, g, b } = hexToRgb(hex); const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; return luminance > 0.58 ? '#111111' : '#ffffff'; }; export const useColors = ( isDark: boolean, palette: ColorPalette = 'forest' ): AppColors => { const schemeKey: AppColorScheme = isDark ? 'dark' : 'light'; const textColors = textByScheme[schemeKey]; const paletteColors = palettes[palette]; const tone = paletteSurfaces[palette][schemeKey]; return { ...textColors, ...paletteColors, background: tone.background, surface: tone.surface, surfaceMuted: tone.surfaceMuted, surfaceStrong: tone.surfaceStrong, border: tone.border, borderStrong: tone.borderStrong, tabBarBg: tone.tabBarBg, tabBarBorder: tone.tabBarBorder, inputBg: tone.surfaceMuted, inputBorder: tone.borderStrong, cardBg: withOpacity(tone.surface, isDark ? 0.9 : 0.92), cardBorder: withOpacity(tone.borderStrong, isDark ? 0.7 : 0.82), cardShadow: withOpacity('#000000', isDark ? 0.26 : 0.12), chipBg: tone.surfaceStrong, chipBorder: tone.border, pageBase: tone.pageBase, pageGradientStart: withOpacity(tone.pageGradientStart, isDark ? 0.44 : 0.52), pageGradientEnd: withOpacity(tone.pageGradientEnd, isDark ? 0.34 : 0.44), pageTexture: withOpacity(tone.pageTexture, isDark ? 0.09 : 0.07), overlay: textColors.overlay, overlayStrong: textColors.overlayStrong, heroButton: withOpacity(tone.surface, isDark ? 0.9 : 0.94), heroButtonBorder: withOpacity(tone.borderStrong, isDark ? 0.7 : 0.8), onPrimary: getOnColor(paletteColors.primary), primarySoft: withOpacity(paletteColors.primary, isDark ? 0.26 : 0.18), infoSoft: withOpacity(paletteColors.info, isDark ? 0.24 : 0.16), warningSoft: withOpacity(paletteColors.warning, isDark ? 0.24 : 0.18), dangerSoft: withOpacity(paletteColors.danger, isDark ? 0.2 : 0.14), successSoft: withOpacity(paletteColors.success, isDark ? 0.2 : 0.14), primaryTint: mixColors(tone.surfaceStrong, paletteColors.primary, isDark ? 0.22 : 0.2), infoTint: mixColors(tone.surfaceStrong, paletteColors.info, isDark ? 0.2 : 0.18), warningTint: mixColors(tone.surfaceStrong, paletteColors.warning, isDark ? 0.22 : 0.2), dangerTint: mixColors(tone.surfaceStrong, paletteColors.danger, isDark ? 0.2 : 0.18), successTint: mixColors(tone.surfaceStrong, paletteColors.success, isDark ? 0.2 : 0.18), mutedChip: tone.surfaceStrong, fabBg: paletteColors.primary, fabShadow: withOpacity(paletteColors.primaryDark, 0.75), iconOnImage: '#ffffff', }; };