src/lib/theme.ts
Annotation non disponible
Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.
4 exports
textColorbuildThemegenerateCssVarsThemeColors
Code source· typescript
// Convertit hex en RGB
function hexToRgb(hex: string): [number, number, number] {
const h = hex.replace("#", "");
return [
parseInt(h.slice(0, 2), 16),
parseInt(h.slice(2, 4), 16),
parseInt(h.slice(4, 6), 16),
];
}
// Convertit RGB en hex
function rgbToHex(r: number, g: number, b: number): string {
return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
}
// Calcule la luminance
function luminance(hex: string): number {
const [r, g, b] = hexToRgb(hex);
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
}
// Couleur de texte lisible sur un fond donné
export function textColor(hex: string): string {
return luminance(hex) > 0.5 ? "#111111" : "#ffffff";
}
// Assombrit une couleur d'un facteur (0-1)
function darken(hex: string, factor: number): string {
const [r, g, b] = hexToRgb(hex);
return rgbToHex(r * (1 - factor), g * (1 - factor), b * (1 - factor));
}
// Éclaircit une couleur d'un facteur (0-1)
function lighten(hex: string, factor: number): string {
const [r, g, b] = hexToRgb(hex);
return rgbToHex(r + (255 - r) * factor, g + (255 - g) * factor, b + (255 - b) * factor);
}
export interface ThemeColors {
// Light mode
primary: string;
primaryText: string;
accent: string;
accentText: string;
// Dark mode
darkBg: string;
darkSurface: string;
darkBorder: string;
darkText: string;
darkTextMuted: string;
darkAccent: string;
darkAccentText: string;
}
export function buildTheme(primary: string, accent: string): ThemeColors {
const lum = luminance(primary);
// En mode sombre, on dérive le fond depuis la couleur primary
// Si primary est déjà sombre → on l'utilise directement
// Si primary est clair → on crée une version sombre
const darkBg = lum < 0.15 ? primary : darken(primary, 0.85);
const darkSurface = lighten(darkBg, 0.06);
const darkBorder = lighten(darkBg, 0.12);
const darkText = "#f1f1f1";
const darkTextMuted = "#9ca3af";
return {
primary,
primaryText: textColor(primary),
accent,
accentText: textColor(accent),
darkBg,
darkSurface,
darkBorder,
darkText,
darkTextMuted,
darkAccent: accent,
darkAccentText: textColor(accent),
};
}
export function generateCssVars(theme: ThemeColors, mode: string): string {
const light = `
--c-primary: ${theme.primary};
--c-primary-text: ${theme.primaryText};
--c-accent: ${theme.accent};
--c-accent-text: ${theme.accentText};
--c-bg: #ffffff;
--c-surface: #f9fafb;
--c-border: #e5e7eb;
--c-text: #111111;
--c-text-muted: #6b7280;
`;
const dark = `
--c-primary: ${theme.darkBg};
--c-primary-text: ${theme.darkText};
--c-accent: ${theme.darkAccent};
--c-accent-text: ${theme.darkAccentText};
--c-bg: ${theme.darkBg};
--c-surface: ${theme.darkSurface};
--c-border: ${theme.darkBorder};
--c-text: ${theme.darkText};
--c-text-muted: ${theme.darkTextMuted};
`;
if (mode === "CLAIR") {
return `:root { ${light} }`;
}
if (mode === "SOMBRE") {
return `:root { ${dark} }`;
}
// AUTO
return `
:root { ${light} }
@media (prefers-color-scheme: dark) { :root { ${dark} } }
`;
}
// Convertit hex en RGB
function hexToRgb(hex: string): [number, number, number] {
const h = hex.replace("#", "");
return [
parseInt(h.slice(0, 2), 16),
parseInt(h.slice(2, 4), 16),
parseInt(h.slice(4, 6), 16),
];
}
// Convertit RGB en hex
function rgbToHex(r: number, g: number, b: number): string {
return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
}
// Calcule la luminance
function luminance(hex: string): number {
const [r, g, b] = hexToRgb(hex);
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
}
// Couleur de texte lisible sur un fond donné
export function textColor(hex: string): string {
return luminance(hex) > 0.5 ? "#111111" : "#ffffff";
}
// Assombrit une couleur d'un facteur (0-1)
function darken(hex: string, factor: number): string {
const [r, g, b] = hexToRgb(hex);
return rgbToHex(r * (1 - factor), g * (1 - factor), b * (1 - factor));
}
// Éclaircit une couleur d'un facteur (0-1)
function lighten(hex: string, factor: number): string {
const [r, g, b] = hexToRgb(hex);
return rgbToHex(r + (255 - r) * factor, g + (255 - g) * factor, b + (255 - b) * factor);
}
export interface ThemeColors {
// Light mode
primary: string;
primaryText: string;
accent: string;
accentText: string;
// Dark mode
darkBg: string;
darkSurface: string;
darkBorder: string;
darkText: string;
darkTextMuted: string;
darkAccent: string;
darkAccentText: string;
}
export function buildTheme(primary: string, accent: string): ThemeColors {
const lum = luminance(primary);
// En mode sombre, on dérive le fond depuis la couleur primary
// Si primary est déjà sombre → on l'utilise directement
// Si primary est clair → on crée une version sombre
const darkBg = lum < 0.15 ? primary : darken(primary, 0.85);
const darkSurface = lighten(darkBg, 0.06);
const darkBorder = lighten(darkBg, 0.12);
const darkText = "#f1f1f1";
const darkTextMuted = "#9ca3af";
return {
primary,
primaryText: textColor(primary),
accent,
accentText: textColor(accent),
darkBg,
darkSurface,
darkBorder,
darkText,
darkTextMuted,
darkAccent: accent,
darkAccentText: textColor(accent),
};
}
export function generateCssVars(theme: ThemeColors, mode: string): string {
const light = `
--c-primary: ${theme.primary};
--c-primary-text: ${theme.primaryText};
--c-accent: ${theme.accent};
--c-accent-text: ${theme.accentText};
--c-bg: #ffffff;
--c-surface: #f9fafb;
--c-border: #e5e7eb;
--c-text: #111111;
--c-text-muted: #6b7280;
`;
const dark = `
--c-primary: ${theme.darkBg};
--c-primary-text: ${theme.darkText};
--c-accent: ${theme.darkAccent};
--c-accent-text: ${theme.darkAccentText};
--c-bg: ${theme.darkBg};
--c-surface: ${theme.darkSurface};
--c-border: ${theme.darkBorder};
--c-text: ${theme.darkText};
--c-text-muted: ${theme.darkTextMuted};
`;
if (mode === "CLAIR") {
return `:root { ${light} }`;
}
if (mode === "SOMBRE") {
return `:root { ${dark} }`;
}
// AUTO
return `
:root { ${light} }
@media (prefers-color-scheme: dark) { :root { ${dark} } }
`;
}