/** * Composable para personalización del color principal del tema * Guarda la preferencia en localStorage y aplica los gradientes dinámicamente */ interface ColorHSL { h: number s: number l: number } /** * Convierte un color hex a HSL */ function hexToHSL(hex: string): ColorHSL { // Remover el # si existe hex = hex.replace('#', '') // Convertir a RGB const r = parseInt(hex.substring(0, 2), 16) / 255 const g = parseInt(hex.substring(2, 4), 16) / 255 const b = parseInt(hex.substring(4, 6), 16) / 255 const max = Math.max(r, g, b) const min = Math.min(r, g, b) let h = 0 let s = 0 const l = (max + min) / 2 if (max !== min) { 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)) / 6 break case g: h = ((b - r) / d + 2) / 6 break case b: h = ((r - g) / d + 4) / 6 break } } return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100), } } /** * Genera una paleta de colores basada en el color base */ function generateColorPalette(baseColor: string): Record { const hsl = hexToHSL(baseColor) // Generar escala de luminosidad const palette: Record = { 50: `${hsl.h} ${Math.min(100, hsl.s + 20)}% 95%`, 100: `${hsl.h} ${Math.min(100, hsl.s + 15)}% 90%`, 200: `${hsl.h} ${Math.min(100, hsl.s + 10)}% 80%`, 300: `${hsl.h} ${hsl.s}% 70%`, 400: `${hsl.h} ${hsl.s}% 60%`, 500: `${hsl.h} ${hsl.s}% ${hsl.l}%`, // Base 600: `${hsl.h} ${Math.max(0, hsl.s - 5)}% ${Math.max(0, hsl.l - 10)}%`, 700: `${hsl.h} ${Math.max(0, hsl.s - 10)}% ${Math.max(0, hsl.l - 20)}%`, 800: `${hsl.h} ${Math.max(0, hsl.s - 15)}% ${Math.max(0, hsl.l - 30)}%`, 900: `${hsl.h} ${Math.max(0, hsl.s - 20)}% ${Math.max(0, hsl.l - 40)}%`, 950: `${hsl.h} ${Math.max(0, hsl.s - 25)}% ${Math.max(0, hsl.l - 45)}%`, } return palette } /** * Convierte HSL a HEX */ function hslToHex(h: number, s: number, l: number): string { const sDecimal = s / 100 const lDecimal = l / 100 const c = (1 - Math.abs(2 * lDecimal - 1)) * sDecimal const x = c * (1 - Math.abs((h / 60) % 2 - 1)) const m = lDecimal - c / 2 let r = 0 let g = 0 let b = 0 if (h >= 0 && h < 60) { r = c; g = x; b = 0 } else if (h >= 60 && h < 120) { r = x; g = c; b = 0 } else if (h >= 120 && h < 180) { r = 0; g = c; b = x } else if (h >= 180 && h < 240) { r = 0; g = x; b = c } else if (h >= 240 && h < 300) { r = x; g = 0; b = c } else if (h >= 300 && h < 360) { r = c; g = 0; b = x } const toHex = (value: number) => { const hex = Math.round((value + m) * 255).toString(16) return hex.length === 1 ? '0' + hex : hex } return `#${toHex(r)}${toHex(g)}${toHex(b)}` } /** * Aplica el color personalizado a las variables CSS */ function applyCustomColor(color: string, isDark: boolean) { if (import.meta.server) return const hsl = hexToHSL(color) // Aplicar color principal en formato HEX document.documentElement.style.setProperty('--cata-primary', color) // Generar variantes light y dark const lightHsl = { ...hsl, l: Math.min(hsl.l + 15, 95) } const darkHsl = { ...hsl, l: Math.max(hsl.l - 15, 10) } const lightColor = hslToHex(lightHsl.h, lightHsl.s, lightHsl.l) const darkColor = hslToHex(darkHsl.h, darkHsl.s, darkHsl.l) document.documentElement.style.setProperty('--cata-primary-light', lightColor) document.documentElement.style.setProperty('--cata-primary-dark', darkColor) // Aplicar paleta completa para Nuxt UI (en formato HSL) const palette = generateColorPalette(color) Object.entries(palette).forEach(([key, value]) => { document.documentElement.style.setProperty(`--color-primary-${key}`, value) }) } /** * Colores por defecto según el tema */ const DEFAULT_COLORS = { light: '#4682B4', // SteelBlue dark: '#00FF00', // Verde terminal } const STORAGE_KEY = 'riocata-custom-colors' export const useColorCustomization = () => { const colorMode = useColorMode() const isDark = computed(() => colorMode.value === 'dark') // Estado reactivo del color personalizado const customColors = useState<{ light: string | null; dark: string | null }>( 'custom-colors', () => ({ light: null, dark: null }) ) /** * Carga los colores personalizados desde localStorage */ const loadCustomColors = () => { if (import.meta.server) return try { const stored = localStorage.getItem(STORAGE_KEY) if (stored) { const colors = JSON.parse(stored) customColors.value = colors } } catch (error) { console.error('Error al cargar colores personalizados:', error) } } /** * Guarda los colores personalizados en localStorage */ const saveCustomColors = () => { if (import.meta.server) return try { localStorage.setItem(STORAGE_KEY, JSON.stringify(customColors.value)) } catch (error) { console.error('Error al guardar colores personalizados:', error) } } /** * Establece un color personalizado para el tema actual */ const setCustomColor = (color: string) => { if (import.meta.server) return const theme = isDark.value ? 'dark' : 'light' customColors.value[theme] = color saveCustomColors() applyCustomColor(color, isDark.value) } /** * Resetea el color al valor por defecto del tema actual */ const resetColor = () => { if (import.meta.server) return const theme = isDark.value ? 'dark' : 'light' customColors.value[theme] = null saveCustomColors() // Limpiar las variables CSS personalizadas para que vuelvan a los valores del CSS document.documentElement.style.removeProperty('--cata-primary') document.documentElement.style.removeProperty('--cata-primary-light') document.documentElement.style.removeProperty('--cata-primary-dark') // Limpiar paleta de Nuxt UI for (let i = 50; i <= 950; i += (i < 100 ? 50 : 100)) { document.documentElement.style.removeProperty(`--color-primary-${i}`) } } /** * Obtiene el color actual (personalizado o por defecto) */ const getCurrentColor = computed(() => { const theme = isDark.value ? 'dark' : 'light' return customColors.value[theme] || DEFAULT_COLORS[theme] }) /** * Verifica si hay un color personalizado para el tema actual */ const hasCustomColor = computed(() => { const theme = isDark.value ? 'dark' : 'light' return customColors.value[theme] !== null }) /** * Inicializa el composable */ const inicializar = () => { if (import.meta.server) return loadCustomColors() // Solo aplicar colores si hay colores personalizados guardados const theme = isDark.value ? 'dark' : 'light' if (customColors.value[theme]) { applyCustomColor(customColors.value[theme], isDark.value) } // Observar cambios en el modo de color watch(isDark, (newIsDark) => { const theme = newIsDark ? 'dark' : 'light' // Solo aplicar si hay color personalizado para este tema if (customColors.value[theme]) { applyCustomColor(customColors.value[theme], newIsDark) } }) } return { customColors: readonly(customColors), getCurrentColor, hasCustomColor, setCustomColor, resetColor, inicializar, } }