diff --git a/flutter_client/lib/theme/tokens.dart b/flutter_client/lib/theme/tokens.dart index 7468012d..455e13ca 100644 --- a/flutter_client/lib/theme/tokens.dart +++ b/flutter_client/lib/theme/tokens.dart @@ -17,6 +17,7 @@ class FabledSwordTokens { static const Color error = Color(0xFFC04A1F); static const Color info = Color(0xFF3D5A6E); static const Color accent = Color(0xFF4A6B5C); + static const Color onAction = Color(0xFFE8E4D8); static const double radiusSm = 4; static const double radiusMd = 8; static const double radiusLg = 12; diff --git a/flutter_client/shared/fabledsword.tokens.json b/flutter_client/shared/fabledsword.tokens.json index e8fa04ec..c602dfe7 100644 --- a/flutter_client/shared/fabledsword.tokens.json +++ b/flutter_client/shared/fabledsword.tokens.json @@ -1,31 +1,36 @@ { "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" + "dark": { + "obsidian": "#14171A", + "iron": "#1E2228", + "slate": "#2C313A", + "pewter": "#3F4651", + "parchment": "#E8E4D8", + "vellum": "#C2BFB4", + "ash": "#9C9A92" + }, + "light": { + "obsidian": "#F8F5EE", + "iron": "#ECE6D5", + "slate": "#DCD3BD", + "pewter": "#B8AE94", + "parchment": "#14171A", + "vellum": "#2C313A", + "ash": "#5C6068" + }, + "flat": { + "moss": "#4A5D3F", + "bronze": "#8B7355", + "oxblood": "#6B2118", + "warning": "#8B6F1E", + "error": "#C04A1F", + "info": "#3D5A6E", + "accent": "#4A6B5C", + "on-action": "#E8E4D8" + } }, + "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", diff --git a/flutter_client/tool/gen_tokens.dart b/flutter_client/tool/gen_tokens.dart index 4ec37f7a..9383fa82 100644 --- a/flutter_client/tool/gen_tokens.dart +++ b/flutter_client/tool/gen_tokens.dart @@ -1,15 +1,25 @@ // Reads shared/fabledsword.tokens.json and emits lib/theme/tokens.dart. // Run via `dart run tool/gen_tokens.dart`. The generated file is // committed (CI validates it matches the JSON to catch drift). +// +// Web tokens.json splits colors into `dark`, `light`, and `flat` blocks +// for the SPA's theme toggle. Flutter v1 ships dark-only, so we merge +// dark + flat into a single flat namespace. Light variants will be +// emitted alongside as part of the Flutter theme-toggle parity work. import 'dart:convert'; import 'dart:io'; void main() { final jsonFile = File('shared/fabledsword.tokens.json'); final tokens = jsonDecode(jsonFile.readAsStringSync()) as Map; - final colors = (tokens['colors'] as Map).cast(); - final radii = (tokens['radii'] as Map).cast(); - final fonts = (tokens['fonts'] as Map).cast(); + + final colorsRoot = (tokens['colors'] as Map).cast(); + final dark = (colorsRoot['dark'] as Map).cast(); + final flat = (colorsRoot['flat'] as Map).cast(); + final colors = {...dark, ...flat}; + + final radii = (tokens['radii'] as Map).cast(); + final fonts = (tokens['fonts'] as Map).cast(); final out = StringBuffer() ..writeln('// GENERATED — do not edit. Source: shared/fabledsword.tokens.json') @@ -38,5 +48,11 @@ void main() { stdout.writeln('wrote lib/theme/tokens.dart'); } -String _camel(String s) => s; // tokens are already lowerCamel-friendly -String _pascal(String s) => s[0].toUpperCase() + s.substring(1); +// Hyphens become camelCase boundaries: on-action → onAction. +String _camel(String s) { + final parts = s.split('-'); + return parts.first + + parts.skip(1).map((p) => p[0].toUpperCase() + p.substring(1)).join(); +} + +String _pascal(String s) => s[0].toUpperCase() + s.substring(1);