Files
dudu/ios/dudu/Shared/DuduTheme.swift
T
wangjia 208d0cda12 feat(design): design/ 真相源 + 防漂移静态闸(#25)
真相源整固:
- design/index.html 登记簿:token 色板(var() 实时渲染)/ 组件登记卡 /
  图标与跨端映射 / guidelines / ui_kits 单一入口,check-ds 强制登记
- design/icons.js 图标单源(Lucide 线条)+ icon-map.json 跨端映射表
  (web/iOS/Android 三列,iOS 12 个 SF Symbol 全部收编登记)
- components/core/Icon.jsx 单源图标渲染器;MicBar/RecognitionOverlay 接入
- tokens 新增 --brand-wechat(-press)(微信官方绿)与 --overlay-wave
- fonts.css 弃 Google Fonts CDN,Outfit 可变字体自托管
  (assets/outfit-latin.woff2,40KB;中国网络下 CDN 不可用会卡字体)
- design/serve.mjs 零依赖预览服务器(登记簿与原型评审入口)

codegen 强化(export-tokens.mjs):
- 产物直写消费位置:ios/dudu/Shared/DuduTheme.swift、
  android/.../design/DuduTheme.kt——删除 design-pipeline/generated/
  中间拷贝层(此前 app 内是手工拷贝,--check 守不到真实消费文件已漂移)
- 新增 web/tokens.css 产物(custom props 透传 + Outfit data URI 内嵌)
- tokens 文件遍历排序,产物确定性

存量收编:
- desktop:三个窗口内联 SVG → Icon 组件;tray 裸 rgba → token+opacity
- Android:LoginScreen 微信绿常量 → DuduPalette.brandWechat(Press),
  Color.White → textOnAccent
- 官网 web/index.html:接入 tokens.css,34 处硬编码 hex 全部 var() 化;
  深色板块(metrics/cta/footer)改挂 data-theme="dark" 复用 dark 令牌;
  移除 Google Fonts CDN
- design 包内 8 处裸色:微信绿 token 化、装饰色 ds-ignore 白名单、
  同值色改 var() 引用

防漂移闸(零依赖 Node):
- design-pipeline/check-ds.mjs:真相源自检 7 道(dark⊆light / var 链 /
  裸色∈tokens 值集 / 组件登记 / icons 唯一 / icon-map 双向同集 / 禁 emoji)
- design-pipeline/check-code.mjs:代码侧单源(desktop 禁字面色+内联 path /
  iOS 禁字面色+SF Symbol 同集 / Android 禁 Color(0x / web 禁字面色),
  ds-ignore 行级豁免制
- .githooks/pre-commit(启用:git config core.hooksPath .githooks)
- CI design job 扩展为三道闸全绿
- 反向验证通过:注入裸色/孤儿 token/未登记组件/未登记 symbol 均被拦截

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 00:05:22 +08:00

221 lines
14 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// design/tokens/*.css
// node design-pipeline/export-tokens.mjs
import SwiftUI
public enum DuduTheme {
/// light/dark
static func dynamic(light: Color, dark: Color) -> Color {
Color(UIColor { trait in
trait.userInterfaceStyle == .dark ? UIColor(dark) : UIColor(light)
})
}
// MARK: - Colors
public static let accent = dynamic(
light: Color(.sRGB, red: 0.3098, green: 0.4314, blue: 0.9686, opacity: 1.00),
dark: Color(.sRGB, red: 0.3608, green: 0.4667, blue: 0.9098, opacity: 1.00))
public static let accentHover = dynamic(
light: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00),
dark: Color(.sRGB, red: 0.4314, green: 0.5373, blue: 0.9765, opacity: 1.00))
public static let accentPress = dynamic(
light: Color(.sRGB, red: 0.1843, green: 0.2667, blue: 0.7020, opacity: 1.00),
dark: Color(.sRGB, red: 0.2902, green: 0.3882, blue: 0.8392, opacity: 1.00))
public static let accentSoft = dynamic(
light: Color(.sRGB, red: 0.9333, green: 0.9451, blue: 0.9961, opacity: 1.00),
dark: Color(.sRGB, red: 0.3608, green: 0.4667, blue: 0.9098, opacity: 0.16))
public static let accentSoft2 = dynamic(
light: Color(.sRGB, red: 0.8745, green: 0.8980, blue: 0.9922, opacity: 1.00),
dark: Color(.sRGB, red: 0.3608, green: 0.4667, blue: 0.9098, opacity: 0.26))
public static let accentText = dynamic(
light: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00),
dark: Color(.sRGB, red: 0.4902, green: 0.5843, blue: 0.9686, opacity: 1.00))
public static let amber100 = dynamic(
light: Color(.sRGB, red: 0.9882, green: 0.9373, blue: 0.8431, opacity: 1.00),
dark: Color(.sRGB, red: 0.9882, green: 0.9373, blue: 0.8431, opacity: 1.00))
public static let amber500 = dynamic(
light: Color(.sRGB, red: 0.9098, green: 0.5373, blue: 0.0471, opacity: 1.00),
dark: Color(.sRGB, red: 0.9098, green: 0.5373, blue: 0.0471, opacity: 1.00))
public static let bgApp = dynamic(
light: Color(.sRGB, red: 0.9647, green: 0.9686, blue: 0.9804, opacity: 1.00),
dark: Color(.sRGB, red: 0.0588, green: 0.0667, blue: 0.0863, opacity: 1.00))
public static let blue100 = dynamic(
light: Color(.sRGB, red: 0.8745, green: 0.8980, blue: 0.9922, opacity: 1.00),
dark: Color(.sRGB, red: 0.8745, green: 0.8980, blue: 0.9922, opacity: 1.00))
public static let blue200 = dynamic(
light: Color(.sRGB, red: 0.7686, green: 0.8118, blue: 0.9843, opacity: 1.00),
dark: Color(.sRGB, red: 0.7686, green: 0.8118, blue: 0.9843, opacity: 1.00))
public static let blue300 = dynamic(
light: Color(.sRGB, red: 0.6235, green: 0.6941, blue: 0.9765, opacity: 1.00),
dark: Color(.sRGB, red: 0.6235, green: 0.6941, blue: 0.9765, opacity: 1.00))
public static let blue400 = dynamic(
light: Color(.sRGB, red: 0.4588, green: 0.5647, blue: 0.9725, opacity: 1.00),
dark: Color(.sRGB, red: 0.4588, green: 0.5647, blue: 0.9725, opacity: 1.00))
public static let blue50 = dynamic(
light: Color(.sRGB, red: 0.9333, green: 0.9451, blue: 0.9961, opacity: 1.00),
dark: Color(.sRGB, red: 0.9333, green: 0.9451, blue: 0.9961, opacity: 1.00))
public static let blue500 = dynamic(
light: Color(.sRGB, red: 0.3098, green: 0.4314, blue: 0.9686, opacity: 1.00),
dark: Color(.sRGB, red: 0.3098, green: 0.4314, blue: 0.9686, opacity: 1.00))
public static let blue600 = dynamic(
light: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00),
dark: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00))
public static let blue700 = dynamic(
light: Color(.sRGB, red: 0.1843, green: 0.2667, blue: 0.7020, opacity: 1.00),
dark: Color(.sRGB, red: 0.1843, green: 0.2667, blue: 0.7020, opacity: 1.00))
public static let blue800 = dynamic(
light: Color(.sRGB, red: 0.1451, green: 0.2118, blue: 0.5490, opacity: 1.00),
dark: Color(.sRGB, red: 0.1451, green: 0.2118, blue: 0.5490, opacity: 1.00))
public static let blue900 = dynamic(
light: Color(.sRGB, red: 0.1176, green: 0.1686, blue: 0.4196, opacity: 1.00),
dark: Color(.sRGB, red: 0.1176, green: 0.1686, blue: 0.4196, opacity: 1.00))
public static let border1 = dynamic(
light: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00),
dark: Color(.sRGB, red: 0.1529, green: 0.1725, blue: 0.2196, opacity: 1.00))
public static let border2 = dynamic(
light: Color(.sRGB, red: 0.8235, green: 0.8353, blue: 0.8667, opacity: 1.00),
dark: Color(.sRGB, red: 0.2039, green: 0.2275, blue: 0.2863, opacity: 1.00))
public static let brandWechat = dynamic(
light: Color(.sRGB, red: 0.0275, green: 0.7569, blue: 0.3765, opacity: 1.00),
dark: Color(.sRGB, red: 0.0275, green: 0.7569, blue: 0.3765, opacity: 1.00))
public static let brandWechatPress = dynamic(
light: Color(.sRGB, red: 0.0235, green: 0.6784, blue: 0.3373, opacity: 1.00),
dark: Color(.sRGB, red: 0.0235, green: 0.6784, blue: 0.3373, opacity: 1.00))
public static let danger = dynamic(
light: Color(.sRGB, red: 0.8627, green: 0.1490, blue: 0.1490, opacity: 1.00),
dark: Color(.sRGB, red: 0.9412, green: 0.3569, blue: 0.3569, opacity: 1.00))
public static let dangerSoft = dynamic(
light: Color(.sRGB, red: 0.9961, green: 0.8863, blue: 0.8863, opacity: 1.00),
dark: Color(.sRGB, red: 0.9412, green: 0.3569, blue: 0.3569, opacity: 0.16))
public static let gray0 = dynamic(
light: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00),
dark: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00))
public static let gray100 = dynamic(
light: Color(.sRGB, red: 0.9412, green: 0.9451, blue: 0.9569, opacity: 1.00),
dark: Color(.sRGB, red: 0.9412, green: 0.9451, blue: 0.9569, opacity: 1.00))
public static let gray200 = dynamic(
light: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00),
dark: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00))
public static let gray25 = dynamic(
light: Color(.sRGB, red: 0.9882, green: 0.9882, blue: 0.9922, opacity: 1.00),
dark: Color(.sRGB, red: 0.9882, green: 0.9882, blue: 0.9922, opacity: 1.00))
public static let gray300 = dynamic(
light: Color(.sRGB, red: 0.8235, green: 0.8353, blue: 0.8667, opacity: 1.00),
dark: Color(.sRGB, red: 0.8235, green: 0.8353, blue: 0.8667, opacity: 1.00))
public static let gray400 = dynamic(
light: Color(.sRGB, red: 0.6510, green: 0.6706, blue: 0.7216, opacity: 1.00),
dark: Color(.sRGB, red: 0.6510, green: 0.6706, blue: 0.7216, opacity: 1.00))
public static let gray50 = dynamic(
light: Color(.sRGB, red: 0.9647, green: 0.9686, blue: 0.9804, opacity: 1.00),
dark: Color(.sRGB, red: 0.9647, green: 0.9686, blue: 0.9804, opacity: 1.00))
public static let gray500 = dynamic(
light: Color(.sRGB, red: 0.4784, green: 0.5020, blue: 0.5647, opacity: 1.00),
dark: Color(.sRGB, red: 0.4784, green: 0.5020, blue: 0.5647, opacity: 1.00))
public static let gray600 = dynamic(
light: Color(.sRGB, red: 0.3529, green: 0.3765, blue: 0.4471, opacity: 1.00),
dark: Color(.sRGB, red: 0.3529, green: 0.3765, blue: 0.4471, opacity: 1.00))
public static let gray700 = dynamic(
light: Color(.sRGB, red: 0.2627, green: 0.2863, blue: 0.3412, opacity: 1.00),
dark: Color(.sRGB, red: 0.2627, green: 0.2863, blue: 0.3412, opacity: 1.00))
public static let gray800 = dynamic(
light: Color(.sRGB, red: 0.1686, green: 0.1843, blue: 0.2275, opacity: 1.00),
dark: Color(.sRGB, red: 0.1686, green: 0.1843, blue: 0.2275, opacity: 1.00))
public static let gray900 = dynamic(
light: Color(.sRGB, red: 0.1059, green: 0.1176, blue: 0.1490, opacity: 1.00),
dark: Color(.sRGB, red: 0.1059, green: 0.1176, blue: 0.1490, opacity: 1.00))
public static let gray950 = dynamic(
light: Color(.sRGB, red: 0.0667, green: 0.0745, blue: 0.1020, opacity: 1.00),
dark: Color(.sRGB, red: 0.0667, green: 0.0745, blue: 0.1020, opacity: 1.00))
public static let green100 = dynamic(
light: Color(.sRGB, red: 0.8627, green: 0.9882, blue: 0.9059, opacity: 1.00),
dark: Color(.sRGB, red: 0.8627, green: 0.9882, blue: 0.9059, opacity: 1.00))
public static let green500 = dynamic(
light: Color(.sRGB, red: 0.0863, green: 0.6392, blue: 0.2902, opacity: 1.00),
dark: Color(.sRGB, red: 0.0863, green: 0.6392, blue: 0.2902, opacity: 1.00))
public static let overlayBg = dynamic(
light: Color(.sRGB, red: 0.0941, green: 0.1020, blue: 0.1333, opacity: 0.95),
dark: Color(.sRGB, red: 0.1176, green: 0.1294, blue: 0.1686, opacity: 0.96))
public static let overlayText = dynamic(
light: Color(.sRGB, red: 0.9490, green: 0.9569, blue: 0.9804, opacity: 1.00),
dark: Color(.sRGB, red: 0.9490, green: 0.9569, blue: 0.9804, opacity: 1.00))
public static let overlayText2 = dynamic(
light: Color(.sRGB, red: 0.6039, green: 0.6392, blue: 0.7412, opacity: 1.00),
dark: Color(.sRGB, red: 0.6039, green: 0.6392, blue: 0.7412, opacity: 1.00))
public static let overlayText3 = dynamic(
light: Color(.sRGB, red: 0.3922, green: 0.4314, blue: 0.5490, opacity: 1.00),
dark: Color(.sRGB, red: 0.3922, green: 0.4314, blue: 0.5490, opacity: 1.00))
public static let overlayWave = dynamic(
light: Color(.sRGB, red: 0.4863, green: 0.6078, blue: 1.0000, opacity: 1.00),
dark: Color(.sRGB, red: 0.4863, green: 0.6078, blue: 1.0000, opacity: 1.00))
public static let positive = dynamic(
light: Color(.sRGB, red: 0.0863, green: 0.6392, blue: 0.2902, opacity: 1.00),
dark: Color(.sRGB, red: 0.2039, green: 0.7686, blue: 0.4157, opacity: 1.00))
public static let positiveSoft = dynamic(
light: Color(.sRGB, red: 0.8627, green: 0.9882, blue: 0.9059, opacity: 1.00),
dark: Color(.sRGB, red: 0.2039, green: 0.7686, blue: 0.4157, opacity: 0.16))
public static let red100 = dynamic(
light: Color(.sRGB, red: 0.9961, green: 0.8863, blue: 0.8863, opacity: 1.00),
dark: Color(.sRGB, red: 0.9961, green: 0.8863, blue: 0.8863, opacity: 1.00))
public static let red500 = dynamic(
light: Color(.sRGB, red: 0.8627, green: 0.1490, blue: 0.1490, opacity: 1.00),
dark: Color(.sRGB, red: 0.8627, green: 0.1490, blue: 0.1490, opacity: 1.00))
public static let surface2 = dynamic(
light: Color(.sRGB, red: 0.9412, green: 0.9451, blue: 0.9569, opacity: 1.00),
dark: Color(.sRGB, red: 0.1176, green: 0.1333, blue: 0.1725, opacity: 1.00))
public static let surface3 = dynamic(
light: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00),
dark: Color(.sRGB, red: 0.1490, green: 0.1686, blue: 0.2157, opacity: 1.00))
public static let surfaceCard = dynamic(
light: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00),
dark: Color(.sRGB, red: 0.0902, green: 0.1020, blue: 0.1333, opacity: 1.00))
public static let text1 = dynamic(
light: Color(.sRGB, red: 0.1059, green: 0.1176, blue: 0.1490, opacity: 1.00),
dark: Color(.sRGB, red: 0.9255, green: 0.9333, blue: 0.9569, opacity: 1.00))
public static let text2 = dynamic(
light: Color(.sRGB, red: 0.3529, green: 0.3765, blue: 0.4471, opacity: 1.00),
dark: Color(.sRGB, red: 0.6039, green: 0.6314, blue: 0.6980, opacity: 1.00))
public static let text3 = dynamic(
light: Color(.sRGB, red: 0.6510, green: 0.6706, blue: 0.7216, opacity: 1.00),
dark: Color(.sRGB, red: 0.3725, green: 0.4000, blue: 0.4706, opacity: 1.00))
public static let textOnAccent = dynamic(
light: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00),
dark: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00))
public static let warning = dynamic(
light: Color(.sRGB, red: 0.9098, green: 0.5373, blue: 0.0471, opacity: 1.00),
dark: Color(.sRGB, red: 0.9490, green: 0.6392, blue: 0.2353, opacity: 1.00))
public static let warningSoft = dynamic(
light: Color(.sRGB, red: 0.9882, green: 0.9373, blue: 0.8431, opacity: 1.00),
dark: Color(.sRGB, red: 0.9490, green: 0.6392, blue: 0.2353, opacity: 0.16))
// MARK: - Dimensions (pt)
public static let controlLg: CGFloat = 44
public static let controlMd: CGFloat = 36
public static let controlSm: CGFloat = 28
public static let controlXl: CGFloat = 56
public static let radiusFull: CGFloat = 999
public static let radiusLg: CGFloat = 14
public static let radiusMd: CGFloat = 10
public static let radiusSm: CGFloat = 6
public static let radiusXl: CGFloat = 20
public static let radiusXs: CGFloat = 4
public static let space1: CGFloat = 4
public static let space10: CGFloat = 40
public static let space12: CGFloat = 48
public static let space16: CGFloat = 64
public static let space2: CGFloat = 8
public static let space3: CGFloat = 12
public static let space4: CGFloat = 16
public static let space5: CGFloat = 20
public static let space6: CGFloat = 24
public static let space8: CGFloat = 32
public static let text2xl: CGFloat = 24
public static let text3xl: CGFloat = 32
public static let text4xl: CGFloat = 44
public static let textBase: CGFloat = 14
public static let textLg: CGFloat = 17
public static let textMd: CGFloat = 15
public static let textSm: CGFloat = 13
public static let textXl: CGFloat = 20
public static let textXs: CGFloat = 12
}