inogen/scripts/update-colors.js

436 lines
12 KiB
JavaScript

#!/usr/bin/env node
/**
* Color Update Utility for Inogen Project
*
* This script helps update all color values across the project
* when new colors are extracted from Figma designs.
*/
const fs = require("fs");
const path = require("path");
// Figma color configuration
// Replace these values with actual colors from Figma
const FIGMA_COLORS = {
// Primary Brand Colors (Teal)
primary: {
50: "#f0fdfa",
100: "#ccfbf1",
200: "#99f6e4",
300: "#5eead4",
400: "#2dd4bf",
500: "#48D1CC", // Main brand color from current design
600: "#40C4C4", // Hover state from current design
700: "#0f766e",
800: "#115e59",
900: "#134e4a",
950: "#042f2e",
},
// Dark Theme Colors
dark: {
50: "#f8fafc",
100: "#f1f5f9",
200: "#e2e8f0",
300: "#cbd5e1",
400: "#94a3b8",
500: "#64748b",
600: "#475569",
700: "#334155",
800: "#1A202C", // Login background from current design
900: "#0f172a",
950: "#020617",
},
// Neutral Colors
neutral: {
50: "#fafafa",
100: "#f5f5f5",
200: "#e5e5e5",
300: "#d4d4d4",
400: "#a3a3a3",
500: "#737373",
600: "#525252",
700: "#404040",
800: "#262626",
900: "#171717",
950: "#0a0a0a",
},
// Status Colors
success: {
50: "#f0fdf4",
100: "#dcfce7",
200: "#bbf7d0",
300: "#86efac",
400: "#4ade80",
500: "#22c55e",
600: "#16a34a",
700: "#15803d",
800: "#166534",
900: "#14532d",
},
error: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
},
warning: {
50: "#fffbeb",
100: "#fef3c7",
200: "#fde68a",
300: "#fcd34d",
400: "#fbbf24",
500: "#f59e0b",
600: "#d97706",
700: "#b45309",
800: "#92400e",
900: "#78350f",
},
info: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a",
},
// Login specific colors
login: {
primary: "#3aea83",
darkStart: "#464861",
darkEnd: "#111628",
},
};
// Semantic color mappings
const SEMANTIC_COLORS = {
light: {
background: "#ffffff",
foreground: "#0a0a0a",
card: "#ffffff",
cardForeground: "#0a0a0a",
popover: "#ffffff",
popoverForeground: "#0a0a0a",
primary: FIGMA_COLORS.primary[500],
primaryForeground: FIGMA_COLORS.dark[800],
secondary: FIGMA_COLORS.neutral[100],
secondaryForeground: FIGMA_COLORS.neutral[900],
muted: FIGMA_COLORS.neutral[100],
mutedForeground: FIGMA_COLORS.neutral[500],
accent: FIGMA_COLORS.neutral[100],
accentForeground: FIGMA_COLORS.neutral[900],
destructive: FIGMA_COLORS.error[500],
destructiveForeground: "#ffffff",
border: FIGMA_COLORS.neutral[200],
input: FIGMA_COLORS.neutral[200],
ring: FIGMA_COLORS.primary[500],
loginPrimary: FIGMA_COLORS.login.primary,
loginDarkStart: FIGMA_COLORS.login.darkStart,
loginDarkEnd: FIGMA_COLORS.login.darkEnd,
},
dark: {
background: FIGMA_COLORS.dark[950],
foreground: FIGMA_COLORS.neutral[50],
card: FIGMA_COLORS.dark[900],
cardForeground: FIGMA_COLORS.neutral[50],
popover: FIGMA_COLORS.dark[900],
popoverForeground: FIGMA_COLORS.neutral[50],
primary: FIGMA_COLORS.primary[500],
primaryForeground: FIGMA_COLORS.dark[800],
secondary: FIGMA_COLORS.dark[800],
secondaryForeground: FIGMA_COLORS.neutral[50],
muted: FIGMA_COLORS.dark[800],
mutedForeground: FIGMA_COLORS.neutral[400],
accent: FIGMA_COLORS.dark[800],
accentForeground: FIGMA_COLORS.neutral[50],
destructive: FIGMA_COLORS.error[500],
destructiveForeground: FIGMA_COLORS.neutral[50],
border: FIGMA_COLORS.dark[800],
input: FIGMA_COLORS.dark[800],
ring: FIGMA_COLORS.primary[400],
loginPrimary: FIGMA_COLORS.login.primary,
loginDarkStart: FIGMA_COLORS.login.darkStart,
loginDarkEnd: FIGMA_COLORS.login.darkEnd,
},
};
/**
* Update CSS custom properties in app.css
*/
function updateAppCSS() {
const cssPath = path.join(__dirname, "../app/app.css");
let cssContent = fs.readFileSync(cssPath, "utf8");
// Generate CSS custom properties
let newProperties = "";
// Add color scales
Object.entries(FIGMA_COLORS).forEach(([colorName, colorScale]) => {
newProperties += `\n /* ${colorName.charAt(0).toUpperCase() + colorName.slice(1)} color scale */\n`;
if (typeof colorScale === "object" && colorScale !== null) {
Object.entries(colorScale).forEach(([shade, value]) => {
newProperties += ` --color-${colorName}-${shade}: ${value};\n`;
});
}
});
// Update semantic colors for light theme
const lightThemeStart = ":root {";
const lightThemeEnd = "}";
let lightThemeContent = `${lightThemeStart}
--radius: 0.5rem;
/* Light theme colors */
--background: ${SEMANTIC_COLORS.light.background};
--foreground: ${SEMANTIC_COLORS.light.foreground};
--card: ${SEMANTIC_COLORS.light.card};
--card-foreground: ${SEMANTIC_COLORS.light.cardForeground};
--popover: ${SEMANTIC_COLORS.light.popover};
--popover-foreground: ${SEMANTIC_COLORS.light.popoverForeground};
--primary: ${SEMANTIC_COLORS.light.primary};
--primary-foreground: ${SEMANTIC_COLORS.light.primaryForeground};
--secondary: ${SEMANTIC_COLORS.light.secondary};
--secondary-foreground: ${SEMANTIC_COLORS.light.secondaryForeground};
--muted: ${SEMANTIC_COLORS.light.muted};
--muted-foreground: ${SEMANTIC_COLORS.light.mutedForeground};
--accent: ${SEMANTIC_COLORS.light.accent};
--accent-foreground: ${SEMANTIC_COLORS.light.accentForeground};
--destructive: ${SEMANTIC_COLORS.light.destructive};
--destructive-foreground: ${SEMANTIC_COLORS.light.destructiveForeground};
--border: ${SEMANTIC_COLORS.light.border};
--input: ${SEMANTIC_COLORS.light.input};
--ring: ${SEMANTIC_COLORS.light.ring};
/* Login specific colors */
--color-login-primary: ${SEMANTIC_COLORS.light.loginPrimary};
--color-login-dark-start: ${SEMANTIC_COLORS.light.loginDarkStart};
--color-login-dark-end: ${SEMANTIC_COLORS.light.loginDarkEnd};
${newProperties}
${lightThemeEnd}`;
// Update dark theme
let darkThemeContent = `.dark {
/* Dark theme colors */
--background: ${SEMANTIC_COLORS.dark.background};
--foreground: ${SEMANTIC_COLORS.dark.foreground};
--card: ${SEMANTIC_COLORS.dark.card};
--card-foreground: ${SEMANTIC_COLORS.dark.cardForeground};
--popover: ${SEMANTIC_COLORS.dark.popover};
--popover-foreground: ${SEMANTIC_COLORS.dark.popoverForeground};
--primary: ${SEMANTIC_COLORS.dark.primary};
--primary-foreground: ${SEMANTIC_COLORS.dark.primaryForeground};
--secondary: ${SEMANTIC_COLORS.dark.secondary};
--secondary-foreground: ${SEMANTIC_COLORS.dark.secondaryForeground};
--muted: ${SEMANTIC_COLORS.dark.muted};
--muted-foreground: ${SEMANTIC_COLORS.dark.mutedForeground};
--accent: ${SEMANTIC_COLORS.dark.accent};
--accent-foreground: ${SEMANTIC_COLORS.dark.accentForeground};
--destructive: ${SEMANTIC_COLORS.dark.destructive};
--destructive-foreground: ${SEMANTIC_COLORS.dark.destructiveForeground};
--border: ${SEMANTIC_COLORS.dark.border};
--input: ${SEMANTIC_COLORS.dark.input};
--ring: ${SEMANTIC_COLORS.dark.ring};
/* Login specific colors */
--color-login-primary: ${SEMANTIC_COLORS.dark.loginPrimary};
--color-login-dark-start: ${SEMANTIC_COLORS.dark.loginDarkStart};
--color-login-dark-end: ${SEMANTIC_COLORS.dark.loginDarkEnd};
}`;
// Replace existing color definitions
cssContent = cssContent.replace(/:root\s*{[^}]*}/s, lightThemeContent);
cssContent = cssContent.replace(/\.dark\s*{[^}]*}/s, darkThemeContent);
fs.writeFileSync(cssPath, cssContent);
console.log("✅ Updated app.css with new colors");
}
/**
* Update TypeScript design tokens
*/
function updateDesignTokens() {
const tokensPath = path.join(__dirname, "../app/lib/design-tokens.ts");
const designTokensContent = `export const colors = {
// Primary Colors
primary: ${JSON.stringify(FIGMA_COLORS.primary, null, 4).replace(/"/g, '"')},
// Secondary Colors (Info/Blue)
secondary: ${JSON.stringify(FIGMA_COLORS.info, null, 4).replace(/"/g, '"')},
// Dark Colors (Brand dark)
dark: ${JSON.stringify(FIGMA_COLORS.dark, null, 4).replace(/"/g, '"')},
// Neutral Colors
neutral: ${JSON.stringify(FIGMA_COLORS.neutral, null, 4).replace(/"/g, '"')},
// Status Colors
success: ${JSON.stringify(FIGMA_COLORS.success, null, 4).replace(/"/g, '"')},
error: ${JSON.stringify(FIGMA_COLORS.error, null, 4).replace(/"/g, '"')},
warning: ${JSON.stringify(FIGMA_COLORS.warning, null, 4).replace(/"/g, '"')},
info: ${JSON.stringify(FIGMA_COLORS.info, null, 4).replace(/"/g, '"')},
};
export const typography = {
fontFamily: {
sans: ["Vazirmatn", "Inter", "ui-sans-serif", "system-ui", "sans-serif"],
mono: ["ui-monospace", "SFMono-Regular", "Consolas", "monospace"],
},
fontSize: {
xs: ["0.75rem", { lineHeight: "1rem" }],
sm: ["0.875rem", { lineHeight: "1.25rem" }],
base: ["1rem", { lineHeight: "1.5rem" }],
lg: ["1.125rem", { lineHeight: "1.75rem" }],
xl: ["1.25rem", { lineHeight: "1.75rem" }],
"2xl": ["1.5rem", { lineHeight: "2rem" }],
"3xl": ["1.875rem", { lineHeight: "2.25rem" }],
"4xl": ["2.25rem", { lineHeight: "2.5rem" }],
"5xl": ["3rem", { lineHeight: "1" }],
"6xl": ["3.75rem", { lineHeight: "1" }],
},
fontWeight: {
thin: "100",
extralight: "200",
light: "300",
normal: "400",
medium: "500",
semibold: "600",
bold: "700",
extrabold: "800",
black: "900",
},
};
export const spacing = {
px: "1px",
0: "0px",
0.5: "0.125rem",
1: "0.25rem",
1.5: "0.375rem",
2: "0.5rem",
2.5: "0.625rem",
3: "0.75rem",
3.5: "0.875rem",
4: "1rem",
5: "1.25rem",
6: "1.5rem",
7: "1.75rem",
8: "2rem",
9: "2.25rem",
10: "2.5rem",
11: "2.75rem",
12: "3rem",
14: "3.5rem",
16: "4rem",
20: "5rem",
24: "6rem",
28: "7rem",
32: "8rem",
36: "9rem",
40: "10rem",
44: "11rem",
48: "12rem",
52: "13rem",
56: "14rem",
60: "15rem",
64: "16rem",
72: "18rem",
80: "20rem",
96: "24rem",
};
export const borderRadius = {
none: "0px",
sm: "0.125rem",
DEFAULT: "0.25rem",
md: "0.375rem",
lg: "0.5rem",
xl: "0.75rem",
"2xl": "1rem",
"3xl": "1.5rem",
full: "9999px",
};
export const shadows = {
sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
DEFAULT: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
"2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)",
inner: "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)",
none: "0 0 #0000",
};
// Theme variants
export const themes = {
light: ${JSON.stringify(SEMANTIC_COLORS.light, null, 4).replace(/"/g, '"')},
dark: ${JSON.stringify(SEMANTIC_COLORS.dark, null, 4).replace(/"/g, '"')},
};
`;
fs.writeFileSync(tokensPath, designTokensContent);
console.log("✅ Updated design-tokens.ts with new colors");
}
/**
* Main execution function
*/
function updateColors() {
console.log("🎨 Updating colors from Figma design...\n");
try {
updateAppCSS();
updateDesignTokens();
console.log("\n✨ Color update completed successfully!");
console.log("\nUpdated files:");
console.log("- app/app.css");
console.log("- app/lib/design-tokens.ts");
console.log("\nNext steps:");
console.log("1. Review the changes");
console.log("2. Test the application");
console.log("3. Verify colors match Figma design");
console.log("4. Update any hardcoded colors in components");
} catch (error) {
console.error("❌ Error updating colors:", error.message);
process.exit(1);
}
}
// Run the script if called directly
if (require.main === module) {
updateColors();
}
module.exports = {
FIGMA_COLORS,
SEMANTIC_COLORS,
updateColors,
};