refactor(web): extract design tokens to tokens.json source of truth
The Flutter client (#356) needs to consume the same FabledSword tokens the web SPA uses. Move the data into a structured tokens.json file and generate tokens.generated.css from it on every Vite build. The Flutter side gets its own generator pointed at the same JSON. No visual change — generated CSS matches the previous hand-written file. Also installs @types/node devDependency: tsconfig already declared "types": ["node"] but no Node builtins were referenced in TS until now, so the missing types were latent. The new vite.config.ts plugin imports node:child_process, surfacing the gap.
This commit is contained in:
@@ -2,3 +2,4 @@ node_modules/
|
|||||||
.svelte-kit/
|
.svelte-kit/
|
||||||
build/*
|
build/*
|
||||||
!build/index.html
|
!build/index.html
|
||||||
|
src/lib/styles/tokens.generated.css
|
||||||
|
|||||||
Generated
+18
@@ -17,6 +17,7 @@
|
|||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/svelte": "^5.3.1",
|
"@testing-library/svelte": "^5.3.1",
|
||||||
|
"@types/node": "^25.6.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
@@ -1334,6 +1335,16 @@
|
|||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||||
|
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/trusted-types": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
@@ -3764,6 +3775,13 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||||
|
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
|
|||||||
+3
-1
@@ -8,7 +8,8 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"test": "svelte-kit sync && vitest run"
|
"test": "svelte-kit sync && vitest run",
|
||||||
|
"tokens": "node scripts/tokens-to-css.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.6",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/svelte": "^5.3.1",
|
"@testing-library/svelte": "^5.3.1",
|
||||||
|
"@types/node": "^25.6.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Reads ../src/lib/styles/tokens.json and emits tokens.generated.css.
|
||||||
|
// Single source of truth for FabledSword tokens shared with the Flutter
|
||||||
|
// client. Keep output deterministic — Tailwind reads tokens.json directly,
|
||||||
|
// the .css emission is for raw CSS consumers.
|
||||||
|
import { readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const tokensPath = resolve(here, '../src/lib/styles/tokens.json');
|
||||||
|
const outPath = resolve(here, '../src/lib/styles/tokens.generated.css');
|
||||||
|
const tokens = JSON.parse(readFileSync(tokensPath, 'utf8'));
|
||||||
|
|
||||||
|
const lines = ['/* GENERATED — do not edit. Source: tokens.json */', ':root {'];
|
||||||
|
for (const [name, value] of Object.entries(tokens.colors)) {
|
||||||
|
lines.push(` --fs-${name}: ${value};`);
|
||||||
|
}
|
||||||
|
for (const [name, value] of Object.entries(tokens.radii)) {
|
||||||
|
lines.push(` --fs-radius-${name}: ${value};`);
|
||||||
|
}
|
||||||
|
lines.push(` --fs-font-display: ${tokens.fontStacks.display};`);
|
||||||
|
lines.push(` --fs-font-body: ${tokens.fontStacks.body};`);
|
||||||
|
lines.push(` --fs-font-mono: ${tokens.fontStacks.mono};`);
|
||||||
|
lines.push('}', '[data-fs-app="minstrel"] {', ` --fs-accent: ${tokens.colors.accent};`, '}', '');
|
||||||
|
|
||||||
|
writeFileSync(outPath, lines.join('\n'));
|
||||||
|
console.log(`wrote ${outPath}`);
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@import './lib/styles/fabledsword-tokens.css';
|
@import './lib/styles/tokens.generated.css';
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* FabledSword design system tokens
|
|
||||||
*
|
|
||||||
* Shared palette across FabledSword apps (Minstrel, Scribe, Forge, ...).
|
|
||||||
* Per-app accent colour is overridable via the `[data-fs-app="<name>"]`
|
|
||||||
* attribute hook. The `:root` default is forest-teal for Minstrel, the
|
|
||||||
* only consumer of this file today.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
/* Surfaces */
|
|
||||||
--fs-obsidian: #14171A;
|
|
||||||
--fs-iron: #1E2228;
|
|
||||||
--fs-slate: #2C313A;
|
|
||||||
--fs-pewter: #3F4651;
|
|
||||||
|
|
||||||
/* Text */
|
|
||||||
--fs-parchment: #E8E4D8;
|
|
||||||
--fs-vellum: #C2BFB4;
|
|
||||||
--fs-ash: #9C9A92;
|
|
||||||
|
|
||||||
/* Action */
|
|
||||||
--fs-moss: #4A5D3F;
|
|
||||||
--fs-bronze: #8B7355;
|
|
||||||
--fs-oxblood: #6B2118;
|
|
||||||
|
|
||||||
/* Semantic */
|
|
||||||
--fs-warning: #8B6F1E;
|
|
||||||
--fs-error: #C04A1F;
|
|
||||||
--fs-info: #3D5A6E;
|
|
||||||
|
|
||||||
/* Accent (per-app; default = Minstrel forest-teal) */
|
|
||||||
--fs-accent: #4A6B5C;
|
|
||||||
|
|
||||||
/* Radii */
|
|
||||||
--fs-radius-sm: 4px;
|
|
||||||
--fs-radius-md: 8px;
|
|
||||||
--fs-radius-lg: 12px;
|
|
||||||
--fs-radius-xl: 16px;
|
|
||||||
|
|
||||||
/* Fonts */
|
|
||||||
--fs-font-display: 'Fraunces', Georgia, serif;
|
|
||||||
--fs-font-body: 'Inter', system-ui, sans-serif;
|
|
||||||
--fs-font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Per-app accent overrides. Set `data-fs-app="minstrel"` on <html> or any
|
|
||||||
* ancestor to scope; the :root default already matches Minstrel, so this
|
|
||||||
* block is documentation / future override-point for other FS apps. */
|
|
||||||
[data-fs-app="minstrel"] {
|
|
||||||
--fs-accent: #4A6B5C;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"colors": {
|
||||||
|
"obsidian": "#14171A",
|
||||||
|
"iron": "#1E2228",
|
||||||
|
"slate": "#2C313A",
|
||||||
|
"pewter": "#3F4651",
|
||||||
|
"parchment": "#E8E4D8",
|
||||||
|
"vellum": "#C2BFB4",
|
||||||
|
"ash": "#9C9A92",
|
||||||
|
"moss": "#4A5D3F",
|
||||||
|
"bronze": "#8B7355",
|
||||||
|
"oxblood": "#6B2118",
|
||||||
|
"warning": "#8B6F1E",
|
||||||
|
"error": "#C04A1F",
|
||||||
|
"info": "#3D5A6E",
|
||||||
|
"accent": "#4A6B5C"
|
||||||
|
},
|
||||||
|
"radii": {
|
||||||
|
"sm": "4px",
|
||||||
|
"md": "8px",
|
||||||
|
"lg": "12px",
|
||||||
|
"xl": "16px"
|
||||||
|
},
|
||||||
|
"fonts": {
|
||||||
|
"display": "Fraunces",
|
||||||
|
"body": "Inter",
|
||||||
|
"mono": "JetBrains Mono"
|
||||||
|
},
|
||||||
|
"fontStacks": {
|
||||||
|
"display": "'Fraunces', Georgia, serif",
|
||||||
|
"body": "'Inter', system-ui, sans-serif",
|
||||||
|
"mono": "'JetBrains Mono', ui-monospace, monospace"
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-1
@@ -1,8 +1,16 @@
|
|||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
const tokensPlugin = {
|
||||||
|
name: 'minstrel-tokens',
|
||||||
|
buildStart() {
|
||||||
|
execSync('node scripts/tokens-to-css.js', { stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [tokensPlugin, sveltekit()],
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
Reference in New Issue
Block a user