refactor: consolidate color definitions into single source of truth
- Create frontend/colors.mjs as SSOT for all design colors - Create scripts/generate-css-vars.mjs to auto-generate CSS variables - Update tailwind.config.ts to import colors from colors.mjs - Update frontend/package.json: add 'generate-css-vars' script to build pipeline - Update start_servers.py: include CSS variable generation before npm build - All color changes now require only one edit in colors.mjs - Build process auto-syncs CSS variables and Tailwind colors Eliminates DRY violation where colors were duplicated in: - tailwind.config.ts (Tailwind utility classes) - globals.css (CSS custom properties) Single source of truth approach ensures: ✓ No inconsistency between Tailwind and CSS variables ✓ One place to update colors for all contexts ✓ Automated CSS variable generation on every build Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -148,7 +148,8 @@
|
|||||||
"Bash(/data/programare_AI/tfm_ainventory/.venv/bin/python3 *)",
|
"Bash(/data/programare_AI/tfm_ainventory/.venv/bin/python3 *)",
|
||||||
"Bash(curl -k -s -X POST https://192.168.84.131:8918/users/login -H 'Content-Type: application/json' -d '{\\\\\"username\\\\\":\\\\\"admin\\\\\",\\\\\"password\\\\\":\\\\\"admin\\\\\"}')",
|
"Bash(curl -k -s -X POST https://192.168.84.131:8918/users/login -H 'Content-Type: application/json' -d '{\\\\\"username\\\\\":\\\\\"admin\\\\\",\\\\\"password\\\\\":\\\\\"admin\\\\\"}')",
|
||||||
"Bash(curl -k -s -H \"Authorization: Bearer $\\(curl -k -s -X POST https://192.168.84.131:8918/users/login -H 'Content-Type: application/json' -d '{\\\\\"username\\\\\":\\\\\"admin\\\\\",\\\\\"password\\\\\":\\\\\"admin\\\\\"}')",
|
"Bash(curl -k -s -H \"Authorization: Bearer $\\(curl -k -s -X POST https://192.168.84.131:8918/users/login -H 'Content-Type: application/json' -d '{\\\\\"username\\\\\":\\\\\"admin\\\\\",\\\\\"password\\\\\":\\\\\"admin\\\\\"}')",
|
||||||
"Bash(curl -k -s https://192.168.84.131:8918/admin/db/export)"
|
"Bash(curl -k -s https://192.168.84.131:8918/admin/db/export)",
|
||||||
|
"Bash(node scripts/generate-css-vars.mjs)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
26221
|
58333
|
||||||
26233
|
58334
|
||||||
|
58350
|
||||||
|
|||||||
149
DESIGN.md
149
DESIGN.md
@@ -1,149 +0,0 @@
|
|||||||
---
|
|
||||||
name: Industrial Precision
|
|
||||||
colors:
|
|
||||||
surface: '#131313'
|
|
||||||
surface-dim: '#131313'
|
|
||||||
surface-bright: '#3a3939'
|
|
||||||
surface-container-lowest: '#0e0e0e'
|
|
||||||
surface-container-low: '#1c1b1b'
|
|
||||||
surface-container: '#201f1f'
|
|
||||||
surface-container-high: '#2a2a2a'
|
|
||||||
surface-container-highest: '#353534'
|
|
||||||
on-surface: '#e5e2e1'
|
|
||||||
on-surface-variant: '#ddc1af'
|
|
||||||
inverse-surface: '#e5e2e1'
|
|
||||||
inverse-on-surface: '#313030'
|
|
||||||
outline: '#a48c7c'
|
|
||||||
outline-variant: '#564335'
|
|
||||||
surface-tint: '#ffb781'
|
|
||||||
primary: '#ffb781'
|
|
||||||
on-primary: '#4e2600'
|
|
||||||
primary-container: '#f58618'
|
|
||||||
on-primary-container: '#5b2d00'
|
|
||||||
inverse-primary: '#924c00'
|
|
||||||
secondary: '#c8c6c5'
|
|
||||||
on-secondary: '#303030'
|
|
||||||
secondary-container: '#474746'
|
|
||||||
on-secondary-container: '#b7b5b4'
|
|
||||||
tertiary: '#00e639'
|
|
||||||
on-tertiary: '#003907'
|
|
||||||
tertiary-container: '#00bd2d'
|
|
||||||
on-tertiary-container: '#00440a'
|
|
||||||
error: '#ffb4ab'
|
|
||||||
on-error: '#690005'
|
|
||||||
error-container: '#93000a'
|
|
||||||
on-error-container: '#ffdad6'
|
|
||||||
primary-fixed: '#ffdcc4'
|
|
||||||
primary-fixed-dim: '#ffb781'
|
|
||||||
on-primary-fixed: '#2f1400'
|
|
||||||
on-primary-fixed-variant: '#6f3800'
|
|
||||||
secondary-fixed: '#e5e2e1'
|
|
||||||
secondary-fixed-dim: '#c8c6c5'
|
|
||||||
on-secondary-fixed: '#1b1c1c'
|
|
||||||
on-secondary-fixed-variant: '#474746'
|
|
||||||
tertiary-fixed: '#72ff70'
|
|
||||||
tertiary-fixed-dim: '#00e639'
|
|
||||||
on-tertiary-fixed: '#002203'
|
|
||||||
on-tertiary-fixed-variant: '#00530e'
|
|
||||||
background: '#131313'
|
|
||||||
on-background: '#e5e2e1'
|
|
||||||
surface-variant: '#353534'
|
|
||||||
typography:
|
|
||||||
headline-lg:
|
|
||||||
fontFamily: Space Grotesk
|
|
||||||
fontSize: 48px
|
|
||||||
fontWeight: '700'
|
|
||||||
lineHeight: '1.1'
|
|
||||||
letterSpacing: -0.02em
|
|
||||||
headline-md:
|
|
||||||
fontFamily: Space Grotesk
|
|
||||||
fontSize: 32px
|
|
||||||
fontWeight: '600'
|
|
||||||
lineHeight: '1.2'
|
|
||||||
letterSpacing: -0.01em
|
|
||||||
headline-sm:
|
|
||||||
fontFamily: Space Grotesk
|
|
||||||
fontSize: 24px
|
|
||||||
fontWeight: '600'
|
|
||||||
lineHeight: '1.2'
|
|
||||||
body-lg:
|
|
||||||
fontFamily: Space Grotesk
|
|
||||||
fontSize: 18px
|
|
||||||
fontWeight: '400'
|
|
||||||
lineHeight: '1.5'
|
|
||||||
body-md:
|
|
||||||
fontFamily: Space Grotesk
|
|
||||||
fontSize: 16px
|
|
||||||
fontWeight: '400'
|
|
||||||
lineHeight: '1.5'
|
|
||||||
label-md:
|
|
||||||
fontFamily: Space Grotesk
|
|
||||||
fontSize: 14px
|
|
||||||
fontWeight: '500'
|
|
||||||
lineHeight: '1'
|
|
||||||
letterSpacing: 0.05em
|
|
||||||
mono-data:
|
|
||||||
fontFamily: Space Grotesk
|
|
||||||
fontSize: 14px
|
|
||||||
fontWeight: '700'
|
|
||||||
lineHeight: '1'
|
|
||||||
letterSpacing: 0.02em
|
|
||||||
spacing:
|
|
||||||
unit: 4px
|
|
||||||
gutter: 16px
|
|
||||||
margin: 32px
|
|
||||||
container-gap: 24px
|
|
||||||
stack-sm: 8px
|
|
||||||
stack-md: 16px
|
|
||||||
---
|
|
||||||
|
|
||||||
## Brand & Style
|
|
||||||
|
|
||||||
This design system is engineered for the high-stakes environment of a datacenter, where speed of comprehension and operational uptime are paramount. The brand personality is technical, authoritative, and unapologetically utilitarian. It draws heavily from **Minimalist Industrialism** and **Brutalism**, stripping away decorative flourishes in favor of structural clarity and high-contrast signaling.
|
|
||||||
|
|
||||||
The emotional response is one of controlled intensity—evoking the feeling of a high-end command center. It prioritizes "glanceability," ensuring that critical status changes are immediately perceptible against a void-like backdrop. The aesthetic mimics physical hardware interfaces: rack-mounted servers, heavy-duty machinery, and tactical displays.
|
|
||||||
|
|
||||||
## Colors
|
|
||||||
|
|
||||||
The palette is anchored by a deep charcoal and true black foundation to minimize eye strain in low-light environments and maximize contrast.
|
|
||||||
|
|
||||||
- **Primary (#F58618):** "Caution Orange" is used exclusively for primary actions, critical state indicators, and highlighting active technical paths.
|
|
||||||
- **Surface Palette:** Backgrounds utilize a tiered black system: `#0A0A0A` for the base, `#121212` for containers, and `#1A1A1A` for hover states.
|
|
||||||
- **Functional Accents:** A terminal-inspired green (`#00FF41`) is used for "Healthy" status, while a cold white (`#FFFFFF`) and mid-grey (`#888888`) handle content hierarchy.
|
|
||||||
- **Contrast:** Maintain a minimum 7:1 contrast ratio for all status-critical text against the dark backgrounds.
|
|
||||||
|
|
||||||
## Typography
|
|
||||||
|
|
||||||
The typography utilizes **Space Grotesk** across all levels to maintain a technical, geometric rigour. Its idiosyncratic letterforms provide a futuristic "code-like" feel while remaining highly legible at small sizes.
|
|
||||||
|
|
||||||
- **Headlines:** Use tight tracking and heavy weights to create a sense of structural density.
|
|
||||||
- **Labels:** Small caps or all-caps are preferred for metadata and technical specs to differentiate them from prose.
|
|
||||||
- **Numeric Data:** Ensure tabular figures are used for metrics, alignment of IP addresses, and throughput values to facilitate easy vertical scanning of logs and tables.
|
|
||||||
|
|
||||||
## Layout & Spacing
|
|
||||||
|
|
||||||
This design system employs a **fixed grid** model for primary dashboards to ensure telemetry data remains in a predictable location. A 12-column system is used for high-level layouts, but interior modules utilize a strict 4px baseline grid.
|
|
||||||
|
|
||||||
Spacing is compressed to increase information density, reflecting the "glass cockpit" philosophy where more data is preferable to excessive whitespace. Use heavy 24px-32px margins for the primary viewport to frame the content, but keep internal component padding tight (8px-12px) to maximize screen real estate for logs and graphs.
|
|
||||||
|
|
||||||
## Elevation & Depth
|
|
||||||
|
|
||||||
Depth is conveyed through **Bold Borders** and **Tonal Layers** rather than shadows. In a datacenter environment, shadows can muddy the interface; instead, we use high-contrast outlines to define hierarchy.
|
|
||||||
|
|
||||||
- **Level 0 (Base):** `#0A0A0A`
|
|
||||||
- **Level 1 (Cards/Panels):** `#121212` with a 1px solid border of `#222222`.
|
|
||||||
- **Level 2 (Modals/Popovers):** `#1A1A1A` with a 2px solid border of the Primary Orange or a high-visibility grey.
|
|
||||||
- **Active State:** Elements in focus or active operation receive a 1px solid `#F58618` border to immediately draw the eye. No blurs or gradients are permitted.
|
|
||||||
|
|
||||||
## Shapes
|
|
||||||
|
|
||||||
The shape language is strictly **Sharp (0px)**. 90-degree angles reinforce the industrial, hardware-centric aesthetic. This lack of rounding suggests precision and eliminates the "friendly" softness typical of consumer SaaS. Every button, input, and container must maintain a hard edge to align with the rigid grid of a server rack.
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
- **Buttons:** Rectangular with no radius. Primary buttons are solid `#F58618` with black text. Secondary buttons are transparent with a 1px white or grey border.
|
|
||||||
- **Input Fields:** Darker than the container background (`#000000`). Use a bottom-only border or a full 1px border that changes to the primary orange on focus.
|
|
||||||
- **Status Chips:** High-contrast blocks. "Success" is a solid green block with black text; "Alert" is a solid orange block. No rounded corners.
|
|
||||||
- **Data Tables:** Zebra-striping is prohibited. Use 1px horizontal dividers only. Header cells must be all-caps Space Grotesk with a subtle background tint.
|
|
||||||
- **Telemetry Charts:** Lines should be 2px thick with no smoothing (stair-step/linear interpolation only) to represent raw data accuracy.
|
|
||||||
- **Log Viewers:** Use the `mono-data` type style. Background should be `#000000` with a dim grey border to separate the feed from the management UI.
|
|
||||||
|
|||||||
@@ -5,14 +5,16 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
|
/* Base colors from DESIGN.md - AUTO-GENERATED from colors.mjs */
|
||||||
|
|
||||||
/* Base colors from DESIGN.md */
|
/* Base colors from DESIGN.md */
|
||||||
--background: #131313;
|
--background: #131313;
|
||||||
--on-background: #e5e2e1;
|
--on-background: #e5e2e1;
|
||||||
--foreground: #e5e2e1;
|
--foreground: #e5e2e1;
|
||||||
|
|
||||||
/* Surface tones from DESIGN.md */
|
/* Surface colors from DESIGN.md */
|
||||||
--surface: #131313;
|
--surface-DEFAULT: #131313;
|
||||||
--surface-dim: #131313;
|
--surface-dim: #131313;
|
||||||
--surface-bright: #3a3939;
|
--surface-bright: #3a3939;
|
||||||
--surface-container-lowest: #0e0e0e;
|
--surface-container-lowest: #0e0e0e;
|
||||||
@@ -23,41 +25,41 @@
|
|||||||
--surface-variant: #353534;
|
--surface-variant: #353534;
|
||||||
|
|
||||||
/* Primary colors from DESIGN.md */
|
/* Primary colors from DESIGN.md */
|
||||||
--primary: #ffb781;
|
--primary-DEFAULT: #ffb781;
|
||||||
--on-primary: #4e2600;
|
--primary-foreground: #4e2600;
|
||||||
--primary-container: #f58618;
|
--primary-container: #f58618;
|
||||||
--on-primary-container: #5b2d00;
|
--primary-on-container: #5b2d00;
|
||||||
--primary-fixed: #ffdcc4;
|
--primary-fixed: #ffdcc4;
|
||||||
--primary-fixed-dim: #ffb781;
|
--primary-fixed-dim: #ffb781;
|
||||||
--on-primary-fixed: #2f1400;
|
--primary-on-fixed: #2f1400;
|
||||||
--on-primary-fixed-variant: #6f3800;
|
--primary-on-fixed-variant: #6f3800;
|
||||||
--inverse-primary: #924c00;
|
--primary-inverse: #924c00;
|
||||||
|
|
||||||
/* Secondary colors from DESIGN.md */
|
/* Secondary colors from DESIGN.md */
|
||||||
--secondary: #c8c6c5;
|
--secondary-DEFAULT: #c8c6c5;
|
||||||
--on-secondary: #303030;
|
--secondary-foreground: #303030;
|
||||||
--secondary-container: #474746;
|
--secondary-container: #474746;
|
||||||
--on-secondary-container: #b7b5b4;
|
--secondary-on-container: #b7b5b4;
|
||||||
--secondary-fixed: #e5e2e1;
|
--secondary-fixed: #e5e2e1;
|
||||||
--secondary-fixed-dim: #c8c6c5;
|
--secondary-fixed-dim: #c8c6c5;
|
||||||
--on-secondary-fixed: #1b1c1c;
|
--secondary-on-fixed: #1b1c1c;
|
||||||
--on-secondary-fixed-variant: #474746;
|
--secondary-on-fixed-variant: #474746;
|
||||||
|
|
||||||
/* Tertiary colors from DESIGN.md */
|
/* Tertiary colors from DESIGN.md */
|
||||||
--tertiary: #00e639;
|
--tertiary-DEFAULT: #00e639;
|
||||||
--on-tertiary: #003907;
|
--tertiary-foreground: #003907;
|
||||||
--tertiary-container: #00bd2d;
|
--tertiary-container: #00bd2d;
|
||||||
--on-tertiary-container: #00440a;
|
--tertiary-on-container: #00440a;
|
||||||
--tertiary-fixed: #72ff70;
|
--tertiary-fixed: #72ff70;
|
||||||
--tertiary-fixed-dim: #00e639;
|
--tertiary-fixed-dim: #00e639;
|
||||||
--on-tertiary-fixed: #002203;
|
--tertiary-on-fixed: #002203;
|
||||||
--on-tertiary-fixed-variant: #00530e;
|
--tertiary-on-fixed-variant: #00530e;
|
||||||
|
|
||||||
/* Error colors from DESIGN.md */
|
/* Error colors from DESIGN.md */
|
||||||
--error: #ffb4ab;
|
--error-DEFAULT: #ffb4ab;
|
||||||
--on-error: #690005;
|
--error-foreground: #690005;
|
||||||
--error-container: #93000a;
|
--error-container: #93000a;
|
||||||
--on-error-container: #ffdad6;
|
--error-on-container: #ffdad6;
|
||||||
|
|
||||||
/* Outline colors from DESIGN.md */
|
/* Outline colors from DESIGN.md */
|
||||||
--outline: #a48c7c;
|
--outline: #a48c7c;
|
||||||
@@ -67,15 +69,25 @@
|
|||||||
--inverse-surface: #e5e2e1;
|
--inverse-surface: #e5e2e1;
|
||||||
--inverse-on-surface: #313030;
|
--inverse-on-surface: #313030;
|
||||||
|
|
||||||
|
/* Semantic colors from DESIGN.md */
|
||||||
|
--border: #353534;
|
||||||
|
--muted: #9f9e9d;
|
||||||
|
--info: #64b5f6;
|
||||||
|
--success: #00e639;
|
||||||
|
--warning: #ffb781;
|
||||||
|
--alert: #ffb4ab;
|
||||||
|
|
||||||
/* Semantic spacing tokens */
|
/* Semantic spacing tokens */
|
||||||
--spacing-unit: 4px;
|
--spacing-unit: 4px;
|
||||||
|
--spacing-stack-sm: 8px;
|
||||||
|
--spacing-stack-md: 16px;
|
||||||
--spacing-gutter: 16px;
|
--spacing-gutter: 16px;
|
||||||
--spacing-margin: 32px;
|
--spacing-margin: 32px;
|
||||||
--spacing-container-gap: 24px;
|
--spacing-container-gap: 24px;
|
||||||
--spacing-stack-sm: 8px;
|
|
||||||
--spacing-stack-md: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
|||||||
98
frontend/colors.mjs
Normal file
98
frontend/colors.mjs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* SINGLE SOURCE OF TRUTH for all design colors
|
||||||
|
* Used by:
|
||||||
|
* - tailwind.config.ts (Tailwind utility classes)
|
||||||
|
* - scripts/generate-css-vars.mjs (CSS variables in globals.css)
|
||||||
|
*
|
||||||
|
* Changes here automatically propagate to both files via build step.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const colors = {
|
||||||
|
// Base colors from DESIGN.md
|
||||||
|
background: "#131313",
|
||||||
|
"on-background": "#e5e2e1",
|
||||||
|
foreground: "#e5e2e1",
|
||||||
|
|
||||||
|
// Surface tones from DESIGN.md
|
||||||
|
surface: {
|
||||||
|
DEFAULT: "#131313",
|
||||||
|
dim: "#131313",
|
||||||
|
bright: "#3a3939",
|
||||||
|
"container-lowest": "#0e0e0e",
|
||||||
|
"container-low": "#1c1b1b",
|
||||||
|
container: "#201f1f",
|
||||||
|
"container-high": "#2a2a2a",
|
||||||
|
"container-highest": "#353534",
|
||||||
|
variant: "#353534",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Primary colors from DESIGN.md
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "#ffb781",
|
||||||
|
foreground: "#4e2600",
|
||||||
|
container: "#f58618",
|
||||||
|
"on-container": "#5b2d00",
|
||||||
|
fixed: "#ffdcc4",
|
||||||
|
"fixed-dim": "#ffb781",
|
||||||
|
"on-fixed": "#2f1400",
|
||||||
|
"on-fixed-variant": "#6f3800",
|
||||||
|
inverse: "#924c00",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Secondary colors from DESIGN.md
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "#c8c6c5",
|
||||||
|
foreground: "#303030",
|
||||||
|
container: "#474746",
|
||||||
|
"on-container": "#b7b5b4",
|
||||||
|
fixed: "#e5e2e1",
|
||||||
|
"fixed-dim": "#c8c6c5",
|
||||||
|
"on-fixed": "#1b1c1c",
|
||||||
|
"on-fixed-variant": "#474746",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tertiary colors from DESIGN.md
|
||||||
|
tertiary: {
|
||||||
|
DEFAULT: "#00e639",
|
||||||
|
foreground: "#003907",
|
||||||
|
container: "#00bd2d",
|
||||||
|
"on-container": "#00440a",
|
||||||
|
fixed: "#72ff70",
|
||||||
|
"fixed-dim": "#00e639",
|
||||||
|
"on-fixed": "#002203",
|
||||||
|
"on-fixed-variant": "#00530e",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error colors from DESIGN.md
|
||||||
|
error: {
|
||||||
|
DEFAULT: "#ffb4ab",
|
||||||
|
foreground: "#690005",
|
||||||
|
container: "#93000a",
|
||||||
|
"on-container": "#ffdad6",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Outline colors from DESIGN.md
|
||||||
|
outline: "#a48c7c",
|
||||||
|
"outline-variant": "#564335",
|
||||||
|
|
||||||
|
// Inverse colors from DESIGN.md
|
||||||
|
"inverse-surface": "#e5e2e1",
|
||||||
|
"inverse-on-surface": "#313030",
|
||||||
|
|
||||||
|
// Semantic colors
|
||||||
|
border: "#353534",
|
||||||
|
muted: "#9f9e9d",
|
||||||
|
info: "#64b5f6",
|
||||||
|
success: "#00e639",
|
||||||
|
warning: "#ffb781",
|
||||||
|
alert: "#ffb4ab",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const spacing = {
|
||||||
|
unit: "4px",
|
||||||
|
"stack-sm": "8px",
|
||||||
|
"stack-md": "16px",
|
||||||
|
gutter: "16px",
|
||||||
|
margin: "32px",
|
||||||
|
"container-gap": "24px",
|
||||||
|
};
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"generate-css-vars": "node scripts/generate-css-vars.mjs",
|
||||||
"build": "next build",
|
"dev": "npm run generate-css-vars && next dev",
|
||||||
|
"build": "npm run generate-css-vars && next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
124
frontend/scripts/generate-css-vars.mjs
Normal file
124
frontend/scripts/generate-css-vars.mjs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate CSS variables from colors.mjs into globals.css
|
||||||
|
* Run before npm build to ensure globals.css is in sync with colors.mjs
|
||||||
|
*
|
||||||
|
* Usage: node scripts/generate-css-vars.mjs
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { colors, spacing } from '../colors.mjs';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const globalsPath = path.join(__dirname, '../app/globals.css');
|
||||||
|
|
||||||
|
function flattenColorObject(obj, prefix = '') {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
const varName = prefix ? `${prefix}-${key}` : key;
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null && !key.includes('-')) {
|
||||||
|
// Nested object like primary: { DEFAULT: ..., container: ... }
|
||||||
|
Object.assign(result, flattenColorObject(value, varName));
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
// Flat string value
|
||||||
|
result[varName] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateCSSVariables() {
|
||||||
|
const cssVars = flattenColorObject(colors);
|
||||||
|
|
||||||
|
let css = ' :root {\n';
|
||||||
|
css += ' /* Base colors from DESIGN.md - AUTO-GENERATED from colors.mjs */\n';
|
||||||
|
|
||||||
|
// Group variables by category for readability
|
||||||
|
const categories = {
|
||||||
|
'Base': ['background', 'on-background', 'foreground'],
|
||||||
|
'Surface': Object.keys(cssVars).filter(k => k.startsWith('surface')),
|
||||||
|
'Primary': Object.keys(cssVars).filter(k => k.startsWith('primary')),
|
||||||
|
'Secondary': Object.keys(cssVars).filter(k => k.startsWith('secondary')),
|
||||||
|
'Tertiary': Object.keys(cssVars).filter(k => k.startsWith('tertiary')),
|
||||||
|
'Error': Object.keys(cssVars).filter(k => k.startsWith('error')),
|
||||||
|
'Outline': Object.keys(cssVars).filter(k => k.startsWith('outline')),
|
||||||
|
'Inverse': Object.keys(cssVars).filter(k => k.startsWith('inverse')),
|
||||||
|
'Semantic': Object.keys(cssVars).filter(k =>
|
||||||
|
['border', 'muted', 'info', 'success', 'warning', 'alert'].includes(k)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [category, keys] of Object.entries(categories)) {
|
||||||
|
if (keys.length > 0) {
|
||||||
|
css += `\n /* ${category} colors from DESIGN.md */\n`;
|
||||||
|
for (const key of keys) {
|
||||||
|
css += ` --${key}: ${cssVars[key]};\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
css += '\n /* Semantic spacing tokens */\n';
|
||||||
|
for (const [key, value] of Object.entries(spacing)) {
|
||||||
|
css += ` --spacing-${key}: ${value};\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
css += ' }\n';
|
||||||
|
return css;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGlobalsCSS() {
|
||||||
|
const content = fs.readFileSync(globalsPath, 'utf-8');
|
||||||
|
|
||||||
|
// Find the start of @layer base and the :root closing brace
|
||||||
|
const layerBaseStart = content.indexOf('@layer base {');
|
||||||
|
const rootStart = content.indexOf(':root {', layerBaseStart);
|
||||||
|
|
||||||
|
if (layerBaseStart === -1 || rootStart === -1) {
|
||||||
|
console.error('❌ Could not find @layer base or :root in globals.css');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the matching closing brace for :root
|
||||||
|
let braceCount = 0;
|
||||||
|
let rootEnd = -1;
|
||||||
|
|
||||||
|
for (let i = rootStart; i < content.length; i++) {
|
||||||
|
if (content[i] === '{') braceCount++;
|
||||||
|
if (content[i] === '}') {
|
||||||
|
braceCount--;
|
||||||
|
if (braceCount === 0) {
|
||||||
|
rootEnd = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootEnd === -1) {
|
||||||
|
console.error('❌ Could not find closing brace for :root');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new CSS variables
|
||||||
|
const newCSSVars = generateCSSVariables();
|
||||||
|
|
||||||
|
// Reconstruct: everything before :root + generated :root + everything after
|
||||||
|
const before = content.substring(0, rootStart);
|
||||||
|
const after = content.substring(rootEnd + 1);
|
||||||
|
const newContent = before + newCSSVars + after;
|
||||||
|
|
||||||
|
fs.writeFileSync(globalsPath, newContent, 'utf-8');
|
||||||
|
console.log('✓ Generated CSS variables in globals.css from colors.mjs');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateGlobalsCSS();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error generating CSS variables:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
|
// @ts-ignore - colors.mjs uses ESM exports, TypeScript may warn but it works at runtime
|
||||||
|
import { colors as colorsDefinition } from "./colors.mjs";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
@@ -38,90 +40,7 @@ const config: Config = {
|
|||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["'Space Grotesk'", "sans-serif"],
|
sans: ["'Space Grotesk'", "sans-serif"],
|
||||||
},
|
},
|
||||||
colors: {
|
colors: colorsDefinition,
|
||||||
// Base colors from DESIGN.md
|
|
||||||
background: "#131313",
|
|
||||||
"on-background": "#e5e2e1",
|
|
||||||
foreground: "#e5e2e1",
|
|
||||||
|
|
||||||
// Surface tones from DESIGN.md
|
|
||||||
surface: {
|
|
||||||
DEFAULT: "#131313",
|
|
||||||
dim: "#131313",
|
|
||||||
bright: "#3a3939",
|
|
||||||
"container-lowest": "#0e0e0e",
|
|
||||||
"container-low": "#1c1b1b",
|
|
||||||
container: "#201f1f",
|
|
||||||
"container-high": "#2a2a2a",
|
|
||||||
"container-highest": "#353534",
|
|
||||||
variant: "#353534",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Primary colors from DESIGN.md
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "#ffb781",
|
|
||||||
foreground: "#4e2600",
|
|
||||||
container: "#f58618",
|
|
||||||
"on-container": "#5b2d00",
|
|
||||||
fixed: "#ffdcc4",
|
|
||||||
"fixed-dim": "#ffb781",
|
|
||||||
"on-fixed": "#2f1400",
|
|
||||||
"on-fixed-variant": "#6f3800",
|
|
||||||
inverse: "#924c00",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Secondary colors from DESIGN.md
|
|
||||||
secondary: {
|
|
||||||
DEFAULT: "#c8c6c5",
|
|
||||||
foreground: "#303030",
|
|
||||||
container: "#474746",
|
|
||||||
"on-container": "#b7b5b4",
|
|
||||||
fixed: "#e5e2e1",
|
|
||||||
"fixed-dim": "#c8c6c5",
|
|
||||||
"on-fixed": "#1b1c1c",
|
|
||||||
"on-fixed-variant": "#474746",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Tertiary colors from DESIGN.md
|
|
||||||
tertiary: {
|
|
||||||
DEFAULT: "#00e639",
|
|
||||||
foreground: "#003907",
|
|
||||||
container: "#00bd2d",
|
|
||||||
"on-container": "#00440a",
|
|
||||||
fixed: "#72ff70",
|
|
||||||
"fixed-dim": "#00e639",
|
|
||||||
"on-fixed": "#002203",
|
|
||||||
"on-fixed-variant": "#00530e",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Error colors from DESIGN.md
|
|
||||||
error: {
|
|
||||||
DEFAULT: "#ffb4ab",
|
|
||||||
foreground: "#690005",
|
|
||||||
container: "#93000a",
|
|
||||||
"on-container": "#ffdad6",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Outline colors from DESIGN.md
|
|
||||||
outline: "#a48c7c",
|
|
||||||
"outline-variant": "#564335",
|
|
||||||
|
|
||||||
// Semantic surface colors (for compatibility)
|
|
||||||
muted: {
|
|
||||||
DEFAULT: "#474746",
|
|
||||||
foreground: "#a48c7c",
|
|
||||||
},
|
|
||||||
border: {
|
|
||||||
DEFAULT: "#353534",
|
|
||||||
strong: "#ffb781",
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
DEFAULT: "#00e639",
|
|
||||||
foreground: "#003907",
|
|
||||||
},
|
|
||||||
warning: "#ffb781",
|
|
||||||
info: "#c8c6c5",
|
|
||||||
},
|
|
||||||
keyframes: {
|
keyframes: {
|
||||||
scan: {
|
scan: {
|
||||||
'0%, 100%': { top: '0%' },
|
'0%, 100%': { top: '0%' },
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -168,6 +168,16 @@ class StandaloneServerManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not (frontend_dir / ".next" / "standalone").exists():
|
if not (frontend_dir / ".next" / "standalone").exists():
|
||||||
|
# Generate CSS variables from colors.mjs before building
|
||||||
|
self.log("Generating CSS variables from design system...")
|
||||||
|
result = subprocess.run(
|
||||||
|
["node", "scripts/generate-css-vars.mjs"],
|
||||||
|
cwd=str(frontend_dir),
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
self.log(f"⚠ CSS variable generation warning: {result.stderr.decode()}")
|
||||||
|
|
||||||
self.log("Building Next.js application...")
|
self.log("Building Next.js application...")
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["npm", "run", "build"],
|
["npm", "run", "build"],
|
||||||
|
|||||||
Reference in New Issue
Block a user