Hello from MCP server
import { ref, watch } from "vue";
import { useSessionStore } from "@/stores/session";
import { usePreferencesStore } from "@/stores/preferences";
export interface ColorSet {
primary: string;
secondary: string;
tertiary: string;
success: string;
warning: string;
danger: string;
light: string;
medium: string;
dark: string;
}
export interface ThemeColors {
light: ColorSet;
dark: ColorSet;
}
export interface MenuThemeColors {
platinum: string;
gold: string;
silver: string;
bronze: string;
bandaid: string;
}
export const DEFAULT_THEME: ThemeColors = {
light: {
primary: "#000000",
secondary: "#00beff",
tertiary: "#76dd54",
success: "#76dd54",
warning: "#ffea00",
danger: "#ff1f1f",
light: "#e3e4e6",
medium: "#4f5863",
dark: "#0f172a",
},
dark: {
primary: "#76DD54",
secondary: "#00beff",
tertiary: "#F0EA1C",
success: "#76dd54",
warning: "#f0ea1c",
danger: "#dc2626",
light: "#606666",
medium: "#454545",
dark: "#0f172a",
},
};
export const DEFAULT_MENU_THEME: MenuThemeColors = {
platinum: "#3db4d6",
gold: "#ffd83b",
silver: "#bfbfbf",
bronze: "#ffad2b",
bandaid: "#ff8073",
};
export const THEME_PRESETS: Record<string, ThemeColors> = {
default: DEFAULT_THEME,
ionic: {
light: {
primary: "#0054e9",
secondary: "#0163aa",
tertiary: "#6030ff",
success: "#2dd55b",
warning: "#ffc409",
danger: "#c5000f",
light: "#f4f5f8",
medium: "#636469",
dark: "#222428",
},
dark: {
primary: "#4d8dff",
secondary: "#46b1ff",
tertiary: "#8482fb",
success: "#2dd55b",
warning: "#ffc409",
danger: "#f24c58",
light: "#222428",
medium: "#989aa2",
dark: "#f4f5f8",
},
},
ocean: {
light: {
primary: "#0ea5e9",
secondary: "#06b6d4",
tertiary: "#8b5cf6",
success: "#10b981",
warning: "#f59e0b",
danger: "#ef4444",
light: "#f0f9ff",
medium: "#64748b",
dark: "#0c4a6e",
},
dark: {
primary: "#22d3ee",
secondary: "#14b8a6",
tertiary: "#a78bfa",
success: "#34d399",
warning: "#fbbf24",
danger: "#f87171",
light: "#0c4a6e",
medium: "#94a3b8",
dark: "#f0f9ff",
},
},
sunset: {
light: {
primary: "#f97316",
secondary: "#ec4899",
tertiary: "#eab308",
success: "#22c55e",
warning: "#f59e0b",
danger: "#dc2626",
light: "#fff7ed",
medium: "#78716c",
dark: "#7c2d12",
},
dark: {
primary: "#fb923c",
secondary: "#f472b6",
tertiary: "#facc15",
success: "#4ade80",
warning: "#fbbf24",
danger: "#ef4444",
light: "#7c2d12",
medium: "#a8a29e",
dark: "#fff7ed",
},
},
forest: {
light: {
primary: "#22c55e",
secondary: "#84cc16",
tertiary: "#eab308",
success: "#10b981",
warning: "#f59e0b",
danger: "#ef4444",
light: "#f0fdf4",
medium: "#6b7280",
dark: "#14532d",
},
dark: {
primary: "#4ade80",
secondary: "#a3e635",
tertiary: "#facc15",
success: "#34d399",
warning: "#fbbf24",
danger: "#f87171",
light: "#14532d",
medium: "#9ca3af",
dark: "#f0fdf4",
},
},
patriot: {
light: {
primary: "#1e3a8a",
secondary: "#dc2626",
tertiary: "#fbbf24",
success: "#16a34a",
warning: "#f59e0b",
danger: "#b91c1c",
light: "#f9fafb",
medium: "#6b7280",
dark: "#1e293b",
},
dark: {
primary: "#3b82f6",
secondary: "#ef4444",
tertiary: "#fde047",
success: "#22c55e",
warning: "#fbbf24",
danger: "#dc2626",
light: "#1e293b",
medium: "#9ca3af",
dark: "#f9fafb",
},
},
steel: {
light: {
primary: "#475569",
secondary: "#64748b",
tertiary: "#94a3b8",
success: "#059669",
warning: "#f59e0b",
danger: "#dc2626",
light: "#f1f5f9",
medium: "#64748b",
dark: "#334155",
},
dark: {
primary: "#64748b",
secondary: "#94a3b8",
tertiary: "#cbd5e1",
success: "#10b981",
warning: "#fbbf24",
danger: "#ef4444",
light: "#334155",
medium: "#94a3b8",
dark: "#f1f5f9",
},
},
corporate: {
light: {
primary: "#1e40af",
secondary: "#6b7280",
tertiary: "#d4af37",
success: "#047857",
warning: "#f59e0b",
danger: "#991b1b",
light: "#f3f4f6",
medium: "#6b7280",
dark: "#1e3a8a",
},
dark: {
primary: "#3b82f6",
secondary: "#9ca3af",
tertiary: "#fbbf24",
success: "#10b981",
warning: "#fbbf24",
danger: "#dc2626",
light: "#1e3a8a",
medium: "#9ca3af",
dark: "#f3f4f6",
},
},
tradesman: {
light: {
primary: "#92400e",
secondary: "#ea580c",
tertiary: "#fbbf24",
success: "#15803d",
warning: "#f59e0b",
danger: "#dc2626",
light: "#fef3c7",
medium: "#78716c",
dark: "#78350f",
},
dark: {
primary: "#c2410c",
secondary: "#fb923c",
tertiary: "#fde047",
success: "#22c55e",
warning: "#fbbf24",
danger: "#ef4444",
light: "#78350f",
medium: "#a8a29e",
dark: "#fef3c7",
},
},
};
export const MENU_THEME_PRESETS: Record<string, MenuThemeColors> = {
default: DEFAULT_MENU_THEME,
classic: {
platinum: "#3db4d6",
gold: "#ffd83b",
silver: "#bfbfbf",
bronze: "#ffad2b",
bandaid: "#ff8073",
},
vibrant: {
platinum: "#06b6d4",
gold: "#fbbf24",
silver: "#94a3b8",
bronze: "#f97316",
bandaid: "#ef4444",
},
professional: {
platinum: "#0ea5e9",
gold: "#eab308",
silver: "#9ca3af",
bronze: "#ea580c",
bandaid: "#dc2626",
},
patriotic: {
platinum: "#1e40af",
gold: "#fbbf24",
silver: "#e5e7eb",
bronze: "#ea580c",
bandaid: "#dc2626",
},
earth: {
platinum: "#0891b2",
gold: "#ca8a04",
silver: "#78716c",
bronze: "#92400e",
bandaid: "#991b1b",
},
};
// Shared state across all useTheme() calls
const sharedColors = ref<ThemeColors>({ ...DEFAULT_THEME });
const sharedMenuColors = ref<MenuThemeColors>({ ...DEFAULT_MENU_THEME });
// Convert hex to RGB
export const hexToRgb = (hex: string): { r: number; g: number; b: number } | null => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) return null;
return {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
};
};
// Convert RGB to hex
export const rgbToHex = (r: number, g: number, b: number): string => {
return "#" + [r, g, b].map(x => {
const hex = Math.round(x).toString(16);
return hex.length === 1 ? "0" + hex : hex;
}).join("");
};
// Calculate luminance for contrast determination
export const getLuminance = (hex: string): number => {
const rgb = hexToRgb(hex);
if (!rgb) return 0;
const { r, g, b } = rgb;
const [rs, gs, bs] = [r, g, b].map(c => {
const s = c / 255;
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
};
// Determine if text should be black or white based on background
export const getContrastColor = (hex: string): string => {
const luminance = getLuminance(hex);
return luminance > 0.5 ? "#000000" : "#ffffff";
};
// Adjust color brightness (positive = lighter, negative = darker)
export const adjustColor = (hex: string, percent: number): string => {
const rgb = hexToRgb(hex);
if (!rgb) return hex;
const { r, g, b } = rgb;
const amount = Math.round(2.55 * percent);
const newR = Math.max(0, Math.min(255, r + amount));
const newG = Math.max(0, Math.min(255, g + amount));
const newB = Math.max(0, Math.min(255, b + amount));
return rgbToHex(newR, newG, newB);
};
export const useTheme = () => {
const sessionStore = useSessionStore();
const preferences = usePreferencesStore();
// Use shared refs instead of creating new ones
const colors = sharedColors;
const menuColors = sharedMenuColors;
// Check if dark mode is currently active
const isDarkMode = () => {
return document.documentElement.classList.contains('ion-palette-dark');
};
// Apply a single color to CSS variables
const applyColor = (colorName: keyof ColorSet, hexValue: string) => {
const rgb = hexToRgb(hexValue);
if (!rgb) return;
const shade = adjustColor(hexValue, -12);
const tint = adjustColor(hexValue, 12);
const contrast = getContrastColor(hexValue);
const contrastRgb = hexToRgb(contrast);
// Update CSS variables
document.documentElement.style.setProperty(
`--ion-color-${colorName}`,
hexValue
);
document.documentElement.style.setProperty(
`--ion-color-${colorName}-rgb`,
`${rgb.r},${rgb.g},${rgb.b}`
);
document.documentElement.style.setProperty(
`--ion-color-${colorName}-shade`,
shade
);
document.documentElement.style.setProperty(
`--ion-color-${colorName}-tint`,
tint
);
document.documentElement.style.setProperty(
`--ion-color-${colorName}-contrast`,
contrast
);
if (contrastRgb) {
document.documentElement.style.setProperty(
`--ion-color-${colorName}-contrast-rgb`,
`${contrastRgb.r},${contrastRgb.g},${contrastRgb.b}`
);
}
};
// Apply a single menu color to CSS variables
const applyMenuColor = (colorName: keyof MenuThemeColors, hexValue: string) => {
const rgb = hexToRgb(hexValue);
if (!rgb) return;
const shade = adjustColor(hexValue, -12);
const tint = adjustColor(hexValue, 12);
// Update CSS variables for menu tier colors
document.documentElement.style.setProperty(
`--menu-color-${colorName}`,
hexValue
);
document.documentElement.style.setProperty(
`--menu-color-${colorName}-rgb`,
`${rgb.r},${rgb.g},${rgb.b}`
);
document.documentElement.style.setProperty(
`--menu-color-${colorName}-shade`,
shade
);
document.documentElement.style.setProperty(
`--menu-color-${colorName}-tint`,
tint
);
};
// Apply all colors from theme object
const applyTheme = (theme: ThemeColors) => {
// Validate theme structure before applying
if (!theme || typeof theme !== 'object' || !theme.light || !theme.dark) {
colors.value = { ...DEFAULT_THEME };
} else {
colors.value = { ...theme };
}
// Determine which color set to use based on current mode
const colorSet = isDarkMode() ? colors.value.dark : colors.value.light;
// Validate colorSet is an object
if (!colorSet || typeof colorSet !== 'object') {
const defaultColorSet = isDarkMode() ? DEFAULT_THEME.dark : DEFAULT_THEME.light;
colors.value[isDarkMode() ? 'dark' : 'light'] = { ...defaultColorSet };
Object.entries(defaultColorSet).forEach(([colorName, hexValue]) => {
applyColor(colorName as keyof ColorSet, hexValue);
});
return;
}
// Apply each color from the appropriate set
Object.entries(colorSet).forEach(([colorName, hexValue]) => {
applyColor(colorName as keyof ColorSet, hexValue);
});
};
// Apply all menu colors from menu theme object
const applyMenuTheme = (theme: MenuThemeColors) => {
menuColors.value = { ...theme };
Object.entries(theme).forEach(([colorName, hexValue]) => {
applyMenuColor(colorName as keyof MenuThemeColors, hexValue);
});
};
// Set a single color and persist to store
const setColor = async (colorName: keyof ColorSet, hexValue: string, mode: 'light' | 'dark') => {
// Ensure colors.value has proper structure
if (!colors.value || typeof colors.value !== 'object') {
colors.value = { ...DEFAULT_THEME };
}
if (!colors.value[mode] || typeof colors.value[mode] !== 'object') {
colors.value[mode] = { ...DEFAULT_THEME[mode] };
}
colors.value[mode][colorName] = hexValue;
// Only apply if we're currently in that mode
if ((mode === 'dark' && isDarkMode()) || (mode === 'light' && !isDarkMode())) {
applyColor(colorName, hexValue);
}
// Session store theme is legacy - not using anymore
};
// Set a single menu color and persist to store
const setMenuColor = async (colorName: keyof MenuThemeColors, hexValue: string) => {
menuColors.value[colorName] = hexValue;
applyMenuColor(colorName, hexValue);
await sessionStore.updateMenuThemeColor(colorName, hexValue);
};
// Apply a preset theme
const applyPreset = async (presetName: string) => {
const preset = THEME_PRESETS[presetName];
if (!preset) return;
applyTheme(preset);
// Session store theme is legacy - not saving there anymore
};
// Apply a preset menu theme
const applyMenuPreset = async (presetName: string) => {
const preset = MENU_THEME_PRESETS[presetName];
if (!preset) return;
applyMenuTheme(preset);
await sessionStore.setMenuThemeColors(preset);
};
// Reset to default theme
const resetTheme = async () => {
applyTheme(DEFAULT_THEME);
// Session store theme is legacy - not using anymore
};
// Reset to default menu theme
const resetMenuTheme = async () => {
applyMenuTheme(DEFAULT_MENU_THEME);
await sessionStore.resetMenuTheme();
};
// Load theme from preferences or session store
const loadTheme = async () => {
const mode = isDarkMode() ? 'dark' : 'light';
// Check for saved custom themes for both modes
const lightThemeJson = await preferences.getPreference('customTheme-light');
const darkThemeJson = await preferences.getPreference('customTheme-dark');
// Check if we have valid (non-empty) custom themes
const hasLightTheme = lightThemeJson && lightThemeJson.trim() !== '';
const hasDarkTheme = darkThemeJson && darkThemeJson.trim() !== '';
if (hasLightTheme || hasDarkTheme) {
try {
let lightColors = DEFAULT_THEME.light;
let darkColors = DEFAULT_THEME.dark;
if (hasLightTheme) {
lightColors = JSON.parse(lightThemeJson) as ColorSet;
}
if (hasDarkTheme) {
darkColors = JSON.parse(darkThemeJson) as ColorSet;
}
const customTheme: ThemeColors = {
light: lightColors,
dark: darkColors
};
applyTheme(customTheme);
} catch (error) {
// Fall back to session store or default
loadThemeFromSessionStore();
}
} else {
// No custom theme saved, load from session store or default
loadThemeFromSessionStore();
}
// Load menu theme
const savedMenuTheme = sessionStore.menuThemeColors;
if (savedMenuTheme && Object.keys(savedMenuTheme).length > 0) {
applyMenuTheme(savedMenuTheme);
} else {
applyMenuTheme(DEFAULT_MENU_THEME);
}
};
// Helper to load theme from session store (legacy - not used anymore)
const loadThemeFromSessionStore = () => {
// Session store theme storage is legacy and incompatible with new theme structure
// Just apply default theme
applyTheme(DEFAULT_THEME);
};
// Re-apply current theme (useful when toggling dark/light mode)
const reapplyTheme = () => {
applyTheme(colors.value);
};
return {
colors,
menuColors,
setColor,
setMenuColor,
applyPreset,
applyMenuPreset,
resetTheme,
resetMenuTheme,
loadTheme,
applyTheme,
applyMenuTheme,
reapplyTheme,
isDarkMode,
};
};