src/lib/theme.ts

function·app·3.2 KB · 119 lignes· Voir l'itinéraire
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} } }
  `;
}