chore(design): 建立 token 单源管线,删除 design/flutter fork

- 新增 design/codegen/gen_flutter_tokens.mjs:
  从 colors_and_type.css codegen 出 client/lib/pangolin_tokens.gen.dart
  覆盖 PangolinColors/Spacing/Radius/Motion/Shadow(纯数值层)
- 重构 client/lib/pangolin_theme.dart:
  删除手抄数值,import+export pangolin_tokens.gen.dart
  保留实现层 PangolinScheme/PangolinText/PangolinTheme/PangolinContext
  19 个引用方零改动,对外 API 完全不变
- 新增 web/usercenter/scripts/build-tokens.mjs:
  仿 website 样板,prebuild/predev 自动从 css 生成 public/colors_and_type.css
  去除 usercenter 手抄副本的漂移风险
- 更新 CLAUDE.md:补 token codegen 使用说明与单源模型
- 更新 design/SKILL.md:移除已删 flutter/ 引用,补 codegen 入口

验证:改 clay-500 → 三端 codegen 同步变化;flutter analyze 无新增 error。
注:design/flutter/ fork 手动删除(git rm 需用户执行)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-15 18:27:22 +08:00
parent 628440b3f9
commit b04cef3cc4
9 changed files with 427 additions and 92 deletions
+13 -85
View File
@@ -1,8 +1,13 @@
// pangolin_theme.dart
// 穿山甲 · Pangolin — Flutter design tokens
// 穿山甲 · Pangolin — Flutter theme implementation
//
// Single Dart source mirroring colors_and_type.css.
// Fonts are loaded at runtime via the google_fonts package (no local ttf needed).
// Token 数值层(颜色/间距/圆角/动效/阴影)由 codegen 自动维护,见 pangolin_tokens.gen.dart。
// 本文件只保留 Flutter 实现层:
// · PangolinScheme — ThemeExtension(语义色映射 + light/dark 实例)
// · PangolinContext — BuildContext 扩展
// · PangolinFonts — 字族标签常量
// · PangolinText — google_fonts 运行时字型
// · PangolinTheme — ThemeData 组装
//
// Usage:
// MaterialApp(
@@ -10,53 +15,14 @@
// darkTheme: PangolinTheme.dark,
// themeMode: ThemeMode.system,
// );
// // tokens: PangolinColors.clay500, PangolinSpacing.x4, PangolinRadius.lg ...
// // tokens: context.pangolin.accent, PangolinSpacing.x4, PangolinRadius.lg ...
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
/// ── Primitive color ramps ───────────────────────────────────────────
class PangolinColors {
PangolinColors._();
// Clay / Copper — pangolin-armor primary (warm earth)
static const clay50 = Color(0xFFFAF3ED);
static const clay100 = Color(0xFFF2E2D4);
static const clay200 = Color(0xFFE6C7AC);
static const clay300 = Color(0xFFD9A982);
static const clay400 = Color(0xFFCC8B5C);
static const clay500 = Color(0xFFB96A3D); // brand primary
static const clay600 = Color(0xFF9E5630);
static const clay700 = Color(0xFF7E4426);
static const clay800 = Color(0xFF5E331D);
static const clay900 = Color(0xFF3D2213);
// Sand / Taupe — warm neutral ramp
static const sand50 = Color(0xFFFAF8F4);
static const sand100 = Color(0xFFF2EEE7);
static const sand200 = Color(0xFFE6DFD3);
static const sand300 = Color(0xFFD2C8B8);
static const sand400 = Color(0xFFB0A491);
static const sand500 = Color(0xFF8C8270);
static const sand600 = Color(0xFF6B6253);
static const sand700 = Color(0xFF4E4940);
static const sand800 = Color(0xFF2E2A24);
static const sand900 = Color(0xFF1F1C18);
static const sand950 = Color(0xFF14110E); // warm espresso near-black
// Semantic hues (earth-tuned)
static const green400 = Color(0xFF7FB07A);
static const green500 = Color(0xFF5B8C5A); // connected / secure
static const green600 = Color(0xFF467046);
static const amber400 = Color(0xFFE2B05A);
static const amber500 = Color(0xFFD69A3C); // connecting / warning
static const amber600 = Color(0xFFB47E29);
static const red400 = Color(0xFFD4715A);
static const red500 = Color(0xFFC0533B); // error / disconnect
static const red600 = Color(0xFF9E4230);
static const white = Color(0xFFFFFFFF);
}
// token 数值层:import 供本文件使用,export 让调用方 import pangolin_theme.dart 时也能访问。
import 'pangolin_tokens.gen.dart';
export 'pangolin_tokens.gen.dart';
/// ── Semantic tokens, resolved per brightness ───────────────────────
@immutable
@@ -177,38 +143,10 @@ extension PangolinContext on BuildContext {
PangolinScheme get pangolin => Theme.of(this).extension<PangolinScheme>()!;
}
/// ── Spacing (4px base) ──────────────────────────────────────────────
class PangolinSpacing {
PangolinSpacing._();
static const double x1 = 4, x2 = 8, x3 = 12, x4 = 16, x5 = 20,
x6 = 24, x8 = 32, x10 = 40, x12 = 48, x16 = 64;
}
/// ── Radii ───────────────────────────────────────────────────────────
class PangolinRadius {
PangolinRadius._();
static const double sm = 6, md = 10, lg = 14, xl = 20, xxl = 28, full = 999;
static const rSm = Radius.circular(sm);
static const rMd = Radius.circular(md);
static const rLg = Radius.circular(lg);
static const rXl = Radius.circular(xl);
static const rXxl = Radius.circular(xxl);
}
/// ── Motion ──────────────────────────────────────────────────────────
class PangolinMotion {
PangolinMotion._();
static const fast = Duration(milliseconds: 140);
static const base = Duration(milliseconds: 220);
static const slow = Duration(milliseconds: 360);
static const easeOut = Cubic(0.22, 1, 0.36, 1);
static const easeInOut = Cubic(0.65, 0, 0.35, 1);
}
/// ── Font families (via google_fonts at runtime) ─────────────────────
class PangolinFonts {
PangolinFonts._();
// These string constants are used as display labels only.
// String constants used as display labels only.
// Actual font loading is done via google_fonts in PangolinText getters.
static const display = 'Sora';
static const sans = 'Manrope';
@@ -248,16 +186,6 @@ class PangolinText {
fontFeatures: const [FontFeature.tabularFigures()]);
}
/// ── Soft warm-tinted shadows ────────────────────────────────────────
class PangolinShadow {
PangolinShadow._();
static const _tint = Color(0xFF2D1E14);
static List<BoxShadow> sm = [BoxShadow(color: _tint.withOpacity(0.06), blurRadius: 2, offset: const Offset(0, 1))];
static List<BoxShadow> md = [BoxShadow(color: _tint.withOpacity(0.08), blurRadius: 14, offset: const Offset(0, 4))];
static List<BoxShadow> lg = [BoxShadow(color: _tint.withOpacity(0.12), blurRadius: 32, offset: const Offset(0, 12))];
static List<BoxShadow> xl = [BoxShadow(color: _tint.withOpacity(0.16), blurRadius: 60, offset: const Offset(0, 24))];
}
/// ── Ready-to-use ThemeData ──────────────────────────────────────────
class PangolinTheme {
PangolinTheme._();
+94
View File
@@ -0,0 +1,94 @@
// pangolin_tokens.gen.dart
// AUTO-GENERATED — 勿手改。
// 源: design/colors_and_type.css
// 生成器: design/codegen/gen_flutter_tokens.mjs
//
// 包含:PangolinColors · PangolinSpacing · PangolinRadius · PangolinMotion · PangolinShadow
// 由 pangolin_theme.dart 导入;业务代码通过 pangolin_theme.dart 的符号访问,无需直接 import 本文件。
import 'package:flutter/material.dart';
/// ── Primitive color ramps ───────────────────────────────────────────
/// Auto-generated from design/colors_and_type.css :root color ramps.
class PangolinColors {
PangolinColors._();
// Clay
static const clay50 = Color(0xFFFAF3ED);
static const clay100 = Color(0xFFF2E2D4);
static const clay200 = Color(0xFFE6C7AC);
static const clay300 = Color(0xFFD9A982);
static const clay400 = Color(0xFFCC8B5C);
static const clay500 = Color(0xFFB96A3D);
static const clay600 = Color(0xFF9E5630);
static const clay700 = Color(0xFF7E4426);
static const clay800 = Color(0xFF5E331D);
static const clay900 = Color(0xFF3D2213);
// Sand
static const sand50 = Color(0xFFFAF8F4);
static const sand100 = Color(0xFFF2EEE7);
static const sand200 = Color(0xFFE6DFD3);
static const sand300 = Color(0xFFD2C8B8);
static const sand400 = Color(0xFFB0A491);
static const sand500 = Color(0xFF8C8270);
static const sand600 = Color(0xFF6B6253);
static const sand700 = Color(0xFF4E4940);
static const sand800 = Color(0xFF2E2A24);
static const sand900 = Color(0xFF1F1C18);
static const sand950 = Color(0xFF14110E);
// Green
static const green400 = Color(0xFF7FB07A);
static const green500 = Color(0xFF5B8C5A);
static const green600 = Color(0xFF467046);
// Amber
static const amber400 = Color(0xFFE2B05A);
static const amber500 = Color(0xFFD69A3C);
static const amber600 = Color(0xFFB47E29);
// Red
static const red400 = Color(0xFFD4715A);
static const red500 = Color(0xFFC0533B);
static const red600 = Color(0xFF9E4230);
static const white = Color(0xFFFFFFFF);
}
/// ── Spacing (4px base) ──────────────────────────────────────────────
class PangolinSpacing {
PangolinSpacing._();
static const double x0 = 0, x1 = 4, x2 = 8, x3 = 12, x4 = 16, x5 = 20, x6 = 24, x8 = 32, x10 = 40, x12 = 48, x16 = 64;
}
/// ── Radii ───────────────────────────────────────────────────────────
class PangolinRadius {
PangolinRadius._();
static const double sm = 6, md = 10, lg = 14, xl = 20, xxl = 28, full = 999;
static const rSm = Radius.circular(sm);
static const rMd = Radius.circular(md);
static const rLg = Radius.circular(lg);
static const rXl = Radius.circular(xl);
static const rXxl = Radius.circular(xxl);
}
/// ── Motion ──────────────────────────────────────────────────────────
class PangolinMotion {
PangolinMotion._();
static const fast = Duration(milliseconds: 140);
static const base = Duration(milliseconds: 220);
static const slow = Duration(milliseconds: 360);
static const easeOut = Cubic(0.22, 1, 0.36, 1);
static const easeInOut = Cubic(0.65, 0, 0.35, 1);
}
/// ── Soft warm-tinted shadows (light mode) ─────────────────────────
class PangolinShadow {
PangolinShadow._();
static const _tint = Color(0xFF2D1E14); // rgba(45, 30, 20) warm shadow tint
static List<BoxShadow> sm = [BoxShadow(color: _tint.withValues(alpha: 0.06), blurRadius: 2, offset: const Offset(0, 1))];
static List<BoxShadow> md = [BoxShadow(color: _tint.withValues(alpha: 0.08), blurRadius: 14, offset: const Offset(0, 4))];
static List<BoxShadow> lg = [BoxShadow(color: _tint.withValues(alpha: 0.12), blurRadius: 32, offset: const Offset(0, 12))];
static List<BoxShadow> xl = [BoxShadow(color: _tint.withValues(alpha: 0.16), blurRadius: 60, offset: const Offset(0, 24))];
}