/** * 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 } /** * Aplica el color personalizado a las variables CSS */ function applyCustomColor(color: string, isDark: boolean) { if (import.meta.server) return const hsl = hexToHSL(color) const palette = generateColorPalette(color) // Aplicar color principal document.documentElement.style.setProperty('--cata-primary', `${hsl.h} ${hsl.s}% ${hsl.l}%`) // Aplicar paleta completa Object.entries(palette).forEach(([key, value]) => { document.documentElement.style.setProperty(`--color-primary-${key}`, value) }) // Ajustar border y otros colores derivados según el modo if (isDark) { document.documentElement.style.setProperty('--cata-border', `${hsl.h} 100% 40%`) document.documentElement.style.setProperty('--cata-muted', `${hsl.h} 80% 30%`) } else { document.documentElement.style.setProperty('--cata-border', `${hsl.h} 50% 70%`) document.documentElement.style.setProperty('--cata-muted', `${hsl.h} 30% 40%`) } } /** * 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() applyCustomColor(DEFAULT_COLORS[theme], isDark.value) } /** * 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() // Aplicar el color personalizado si existe const theme = isDark.value ? 'dark' : 'light' const color = customColors.value[theme] || DEFAULT_COLORS[theme] applyCustomColor(color, isDark.value) // Observar cambios en el modo de color watch(isDark, (newIsDark) => { const theme = newIsDark ? 'dark' : 'light' const color = customColors.value[theme] || DEFAULT_COLORS[theme] applyCustomColor(color, newIsDark) }) } return { customColors: readonly(customColors), getCurrentColor, hasCustomColor, setCustomColor, resetColor, inicializar, } }