feat(web/m7-362): tokens.json dark/light/flat split + dual-block CSS generator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
// Reads ../src/lib/styles/tokens.json and emits tokens.generated.css.
|
// Reads ../src/lib/styles/tokens.json and emits tokens.generated.css.
|
||||||
// Single source of truth for FabledSword tokens shared with the Flutter
|
// Single source of truth for FabledSword tokens shared with the Flutter
|
||||||
// client. Keep output deterministic — Tailwind reads tokens.json directly,
|
// client. Keep output deterministic — Tailwind reads tokens.json directly,
|
||||||
// the .css emission is for raw CSS consumers.
|
// the .css emission is for raw CSS consumers and theme switching.
|
||||||
import { readFileSync, writeFileSync } from 'node:fs';
|
import { readFileSync, writeFileSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, resolve } from 'node:path';
|
import { dirname, resolve } from 'node:path';
|
||||||
@@ -10,19 +10,35 @@ import { dirname, resolve } from 'node:path';
|
|||||||
const here = dirname(fileURLToPath(import.meta.url));
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
const tokensPath = resolve(here, '../src/lib/styles/tokens.json');
|
const tokensPath = resolve(here, '../src/lib/styles/tokens.json');
|
||||||
const outPath = resolve(here, '../src/lib/styles/tokens.generated.css');
|
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 {'];
|
export function emit(tokens) {
|
||||||
for (const [name, value] of Object.entries(tokens.colors)) {
|
const lines = ['/* GENERATED — do not edit. Source: tokens.json */', ':root {'];
|
||||||
|
for (const [name, value] of Object.entries(tokens.colors.dark)) {
|
||||||
lines.push(` --fs-${name}: ${value};`);
|
lines.push(` --fs-${name}: ${value};`);
|
||||||
}
|
}
|
||||||
for (const [name, value] of Object.entries(tokens.radii)) {
|
for (const [name, value] of Object.entries(tokens.colors.flat)) {
|
||||||
|
lines.push(` --fs-${name}: ${value};`);
|
||||||
|
}
|
||||||
|
for (const [name, value] of Object.entries(tokens.radii)) {
|
||||||
lines.push(` --fs-radius-${name}: ${value};`);
|
lines.push(` --fs-radius-${name}: ${value};`);
|
||||||
}
|
}
|
||||||
lines.push(` --fs-font-display: ${tokens.fontStacks.display};`);
|
lines.push(` --fs-font-display: ${tokens.fontStacks.display};`);
|
||||||
lines.push(` --fs-font-body: ${tokens.fontStacks.body};`);
|
lines.push(` --fs-font-body: ${tokens.fontStacks.body};`);
|
||||||
lines.push(` --fs-font-mono: ${tokens.fontStacks.mono};`);
|
lines.push(` --fs-font-mono: ${tokens.fontStacks.mono};`);
|
||||||
lines.push('}', '[data-fs-app="minstrel"] {', ` --fs-accent: ${tokens.colors.accent};`, '}', '');
|
lines.push('}');
|
||||||
|
|
||||||
writeFileSync(outPath, lines.join('\n'));
|
lines.push('[data-theme="light"] {');
|
||||||
console.log(`wrote ${outPath}`);
|
for (const [name, value] of Object.entries(tokens.colors.light)) {
|
||||||
|
lines.push(` --fs-${name}: ${value};`);
|
||||||
|
}
|
||||||
|
lines.push('}');
|
||||||
|
|
||||||
|
lines.push('[data-fs-app="minstrel"] {', ` --fs-accent: ${tokens.colors.flat.accent};`, '}', '');
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
|
const tokens = JSON.parse(readFileSync(tokensPath, 'utf8'));
|
||||||
|
writeFileSync(outPath, emit(tokens));
|
||||||
|
console.log(`wrote ${outPath}`);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/* GENERATED — do not edit. Source: tokens.json */
|
||||||
|
:root {
|
||||||
|
--fs-obsidian: #14171A;
|
||||||
|
--fs-iron: #1E2228;
|
||||||
|
--fs-slate: #2C313A;
|
||||||
|
--fs-pewter: #3F4651;
|
||||||
|
--fs-parchment: #E8E4D8;
|
||||||
|
--fs-vellum: #C2BFB4;
|
||||||
|
--fs-ash: #9C9A92;
|
||||||
|
--fs-moss: #4A5D3F;
|
||||||
|
--fs-bronze: #8B7355;
|
||||||
|
--fs-oxblood: #6B2118;
|
||||||
|
--fs-warning: #8B6F1E;
|
||||||
|
--fs-error: #C04A1F;
|
||||||
|
--fs-info: #3D5A6E;
|
||||||
|
--fs-accent: #4A6B5C;
|
||||||
|
--fs-on-action: #E8E4D8;
|
||||||
|
--fs-radius-sm: 4px;
|
||||||
|
--fs-radius-md: 8px;
|
||||||
|
--fs-radius-lg: 12px;
|
||||||
|
--fs-radius-xl: 16px;
|
||||||
|
--fs-font-display: 'Fraunces', Georgia, serif;
|
||||||
|
--fs-font-body: 'Inter', system-ui, sans-serif;
|
||||||
|
--fs-font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||||
|
}
|
||||||
|
[data-theme="light"] {
|
||||||
|
--fs-obsidian: #F8F5EE;
|
||||||
|
--fs-iron: #ECE6D5;
|
||||||
|
--fs-slate: #DCD3BD;
|
||||||
|
--fs-pewter: #B8AE94;
|
||||||
|
--fs-parchment: #14171A;
|
||||||
|
--fs-vellum: #2C313A;
|
||||||
|
--fs-ash: #5C6068;
|
||||||
|
}
|
||||||
|
[data-fs-app="minstrel"] {
|
||||||
|
--fs-accent: #4A6B5C;
|
||||||
|
}
|
||||||
@@ -1,31 +1,36 @@
|
|||||||
{
|
{
|
||||||
"colors": {
|
"colors": {
|
||||||
|
"dark": {
|
||||||
"obsidian": "#14171A",
|
"obsidian": "#14171A",
|
||||||
"iron": "#1E2228",
|
"iron": "#1E2228",
|
||||||
"slate": "#2C313A",
|
"slate": "#2C313A",
|
||||||
"pewter": "#3F4651",
|
"pewter": "#3F4651",
|
||||||
"parchment": "#E8E4D8",
|
"parchment": "#E8E4D8",
|
||||||
"vellum": "#C2BFB4",
|
"vellum": "#C2BFB4",
|
||||||
"ash": "#9C9A92",
|
"ash": "#9C9A92"
|
||||||
|
},
|
||||||
|
"light": {
|
||||||
|
"obsidian": "#F8F5EE",
|
||||||
|
"iron": "#ECE6D5",
|
||||||
|
"slate": "#DCD3BD",
|
||||||
|
"pewter": "#B8AE94",
|
||||||
|
"parchment": "#14171A",
|
||||||
|
"vellum": "#2C313A",
|
||||||
|
"ash": "#5C6068"
|
||||||
|
},
|
||||||
|
"flat": {
|
||||||
"moss": "#4A5D3F",
|
"moss": "#4A5D3F",
|
||||||
"bronze": "#8B7355",
|
"bronze": "#8B7355",
|
||||||
"oxblood": "#6B2118",
|
"oxblood": "#6B2118",
|
||||||
"warning": "#8B6F1E",
|
"warning": "#8B6F1E",
|
||||||
"error": "#C04A1F",
|
"error": "#C04A1F",
|
||||||
"info": "#3D5A6E",
|
"info": "#3D5A6E",
|
||||||
"accent": "#4A6B5C"
|
"accent": "#4A6B5C",
|
||||||
},
|
"on-action": "#E8E4D8"
|
||||||
"radii": {
|
}
|
||||||
"sm": "4px",
|
|
||||||
"md": "8px",
|
|
||||||
"lg": "12px",
|
|
||||||
"xl": "16px"
|
|
||||||
},
|
|
||||||
"fonts": {
|
|
||||||
"display": "Fraunces",
|
|
||||||
"body": "Inter",
|
|
||||||
"mono": "JetBrains Mono"
|
|
||||||
},
|
},
|
||||||
|
"radii": { "sm": "4px", "md": "8px", "lg": "12px", "xl": "16px" },
|
||||||
|
"fonts": { "display": "Fraunces", "body": "Inter", "mono": "JetBrains Mono" },
|
||||||
"fontStacks": {
|
"fontStacks": {
|
||||||
"display": "'Fraunces', Georgia, serif",
|
"display": "'Fraunces', Georgia, serif",
|
||||||
"body": "'Inter', system-ui, sans-serif",
|
"body": "'Inter', system-ui, sans-serif",
|
||||||
|
|||||||
Reference in New Issue
Block a user