From 805bf8e2ca967ab1e7ddd04f4fbba7b18bf198ad Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 8 Jul 2026 00:00:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(ds-flow):=20Phase=201.1-1.2=20=E2=80=94=20?= =?UTF-8?q?=E5=8E=9F=E5=9E=8B=E5=8D=95=E6=BA=90=E7=9B=AE=E5=BD=95=20+=20to?= =?UTF-8?q?kens.css=20=E8=BF=81=E4=B8=BA=E7=9C=9F=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - design/prototype/serve.mjs:零依赖热重载预览服务器(照搬 jiu,check-ds 存在性守护,Phase 5 前静默跳过) - design/prototype/tokens.css:token 真源(现结构已满足 ds-flow 的 base :root + [data-theme=dark],字节迁移保证 codegen 零 diff) - design/colors_and_type.css:退化为薄 @import 别名(供历史 preview/*.html 浏览器内引用;codegen 不再读它) - 三个 codegen(Flutter gen_flutter_tokens / website+usercenter build-tokens) + drift 检查全部重指向 prototype/tokens.css - 生成产物 diff 仅头注释源路径行,token 数值零变化(已验证) Co-Authored-By: Claude Opus 4.8 --- ci/check-codegen-drift.sh | 4 +- client/lib/pangolin_tokens.gen.dart | 4 +- design/codegen/gen_flutter_tokens.mjs | 8 +- design/colors_and_type.css | 222 +----------------- design/prototype/serve.mjs | 97 ++++++++ design/prototype/tokens.css | 218 +++++++++++++++++ .../plans/2026-07-07-frontend-ds-refactor.md | 4 +- web/usercenter/public/colors_and_type.css | 2 +- web/usercenter/scripts/build-tokens.mjs | 8 +- web/website/scripts/build-tokens.mjs | 8 +- web/website/src/styles/tokens.gen.css | 2 +- 11 files changed, 341 insertions(+), 236 deletions(-) create mode 100644 design/prototype/serve.mjs create mode 100644 design/prototype/tokens.css diff --git a/ci/check-codegen-drift.sh b/ci/check-codegen-drift.sh index 3b95492..1187f77 100755 --- a/ci/check-codegen-drift.sh +++ b/ci/check-codegen-drift.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # check-codegen-drift.sh — codegen 漂移检查(支柱 2:契约单源 / token 单源)。 # -# design/colors_and_type.css 是唯一 token 真相源,client/lib/pangolin_tokens.gen.dart +# design/prototype/tokens.css 是唯一 token 真相源,client/lib/pangolin_tokens.gen.dart # 由 design/codegen/gen_flutter_tokens.mjs 生成、勿手改(CLAUDE.md 铁律)。本检查重新 # 生成一遍,若生成物与已提交版本不一致 → 漂移:要么改了 CSS 没重生成,要么手改了 # 生成物。两者都会让"单源"失效、契约悄悄分叉。 @@ -22,7 +22,7 @@ echo "→ 重新生成 Flutter token (design/codegen/gen_flutter_tokens.mjs) ... node design/codegen/gen_flutter_tokens.mjs if ! git diff --quiet -- "$GEN"; then - echo "❌ codegen 漂移:$GEN 与 design/colors_and_type.css 不一致。" >&2 + echo "❌ codegen 漂移:$GEN 与 design/prototype/tokens.css 不一致。" >&2 echo " 原因:改了 CSS 真相源却没重生成,或手改了生成物(铁律:勿手改 *.gen.dart)。" >&2 echo " 修复:node design/codegen/gen_flutter_tokens.mjs 后提交生成物。" >&2 echo " ── diff(前 40 行)──" >&2 diff --git a/client/lib/pangolin_tokens.gen.dart b/client/lib/pangolin_tokens.gen.dart index d45df1f..83de116 100644 --- a/client/lib/pangolin_tokens.gen.dart +++ b/client/lib/pangolin_tokens.gen.dart @@ -1,6 +1,6 @@ // pangolin_tokens.gen.dart // AUTO-GENERATED — 勿手改。 -// 源: design/colors_and_type.css +// 源: design/prototype/tokens.css // 生成器: design/codegen/gen_flutter_tokens.mjs // // 包含:PangolinColors · PangolinSpacing · PangolinRadius · PangolinMotion · PangolinShadow @@ -9,7 +9,7 @@ import 'package:flutter/material.dart'; /// ── Primitive color ramps ─────────────────────────────────────────── -/// Auto-generated from design/colors_and_type.css :root color ramps. +/// Auto-generated from design/prototype/tokens.css :root color ramps. class PangolinColors { PangolinColors._(); diff --git a/design/codegen/gen_flutter_tokens.mjs b/design/codegen/gen_flutter_tokens.mjs index f37c3f1..7570159 100644 --- a/design/codegen/gen_flutter_tokens.mjs +++ b/design/codegen/gen_flutter_tokens.mjs @@ -2,7 +2,7 @@ /** * gen_flutter_tokens.mjs — CSS token → Dart codegen * - * 唯一真相源:design/colors_and_type.css + * 唯一真相源:design/prototype/tokens.css * 输出: client/lib/pangolin_tokens.gen.dart * * 生成内容(纯数据层,无 Flutter 实现逻辑): @@ -20,7 +20,7 @@ import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const SRC = resolve(__dirname, '../colors_and_type.css'); +const SRC = resolve(__dirname, '../prototype/tokens.css'); const OUT = resolve(__dirname, '../../client/lib/pangolin_tokens.gen.dart'); if (!existsSync(SRC)) { @@ -125,7 +125,7 @@ const lines = []; lines.push(`// pangolin_tokens.gen.dart`); lines.push(`// AUTO-GENERATED — 勿手改。`); -lines.push(`// 源: design/colors_and_type.css`); +lines.push(`// 源: design/prototype/tokens.css`); lines.push(`// 生成器: design/codegen/gen_flutter_tokens.mjs`); lines.push(`//`); lines.push(`// 包含:PangolinColors · PangolinSpacing · PangolinRadius · PangolinMotion · PangolinShadow`); @@ -136,7 +136,7 @@ lines.push(``); // ── PangolinColors ────────────────────────────────────────────────── lines.push(`/// ── Primitive color ramps ───────────────────────────────────────────`); -lines.push(`/// Auto-generated from design/colors_and_type.css :root color ramps.`); +lines.push(`/// Auto-generated from design/prototype/tokens.css :root color ramps.`); lines.push(`class PangolinColors {`); lines.push(` PangolinColors._();`); lines.push(``); diff --git a/design/colors_and_type.css b/design/colors_and_type.css index 3b1b48b..a82751b 100644 --- a/design/colors_and_type.css +++ b/design/colors_and_type.css @@ -1,218 +1,8 @@ /* ============================================================= - 穿山甲 VPN · Pangolin VPN — Design Tokens - colors_and_type.css - Single source of truth: color ramps, semantic colors (light + dark), - typography, spacing, radii, shadows, motion. + 穿山甲 VPN · Pangolin VPN — Design Tokens (兼容别名) + ⚠️ 真源已迁至 design/prototype/tokens.css(ds-flow 原型单源)。 + 本文件仅为浏览器内 @import 转发,供仍 此路径的历史 preview/*.html 使用。 + codegen(Flutter gen_flutter_tokens.mjs / Web build-tokens.mjs)已改读 prototype/tokens.css。 + 改 token 只改 design/prototype/tokens.css,勿在此加变量(不会被 codegen 解析)。 ============================================================= */ - -/* ---- Webfonts (open-source; documented as the brand's chosen faces) ---- - Sora — display / headings (geometric, friendly) - Manrope — body / UI (humanist geometric) - Noto Sans SC — Chinese (CJK companion) - JetBrains Mono — data readouts (IP, speed, keys) */ -@import url('https://fonts.googleapis.com/css2?family=Sora:wght@500;600;700&family=Manrope:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap'); - -:root { - /* =========================================================== - 1. PRIMITIVE COLOR RAMPS - =========================================================== */ - - /* Clay / Copper — the pangolin-armor primary (warm earth) */ - --clay-50: #FAF3ED; - --clay-100: #F2E2D4; - --clay-200: #E6C7AC; - --clay-300: #D9A982; - --clay-400: #CC8B5C; - --clay-500: #B96A3D; /* ← brand primary */ - --clay-600: #9E5630; - --clay-700: #7E4426; - --clay-800: #5E331D; - --clay-900: #3D2213; - - /* Sand / Taupe — warm neutral ramp */ - --sand-50: #FAF8F4; - --sand-100: #F2EEE7; - --sand-200: #E6DFD3; - --sand-300: #D2C8B8; - --sand-400: #B0A491; - --sand-500: #8C8270; - --sand-600: #6B6253; - --sand-700: #4E4940; - --sand-800: #2E2A24; - --sand-900: #1F1C18; - --sand-950: #14110E; /* warm espresso near-black */ - - /* Semantic hues (earth-tuned) */ - --green-400: #7FB07A; - --green-500: #5B8C5A; /* connected / secure */ - --green-600: #467046; - --amber-400: #E2B05A; - --amber-500: #D69A3C; /* connecting / warning */ - --amber-600: #B47E29; - --red-400: #D4715A; - --red-500: #C0533B; /* error / disconnect */ - --red-600: #9E4230; - - /* =========================================================== - 2. SEMANTIC TOKENS — LIGHT THEME (default) - =========================================================== */ - - /* Backgrounds & surfaces */ - --bg: var(--sand-50); /* app canvas */ - --bg-subtle: var(--sand-100); /* striped / inset regions */ - --surface: #FFFFFF; /* cards, sheets */ - --surface-2: var(--sand-50); /* nested surface */ - --overlay: rgba(31, 28, 24, 0.45); - - /* Foreground / text */ - --fg1: var(--sand-900); /* primary text */ - --fg2: var(--sand-600); /* secondary text */ - --fg3: var(--sand-500); /* tertiary / captions */ - --fg-on-accent: #FFFFFF; /* text on clay fills */ - - /* Brand / accent */ - --accent: var(--clay-500); - --accent-hover: var(--clay-600); - --accent-press: var(--clay-700); - --accent-subtle: var(--clay-50); - --accent-border: var(--clay-200); - - /* Borders & lines */ - --border: var(--sand-200); - --border-strong: var(--sand-300); - --ring: rgba(185, 106, 61, 0.35); /* focus ring (clay) */ - - /* Status */ - --success: var(--green-500); - --success-subtle: #E9F0E6; - --warning: var(--amber-500); - --warning-subtle: #F8EED6; - --danger: var(--red-500); - --danger-subtle: #F6E1DA; - - /* =========================================================== - 3. TYPOGRAPHY - =========================================================== */ - --font-display: 'Sora', 'Noto Sans SC', system-ui, sans-serif; - --font-sans: 'Manrope', 'Noto Sans SC', system-ui, sans-serif; - --font-cjk: 'Noto Sans SC', 'Manrope', system-ui, sans-serif; - --font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace; - - /* Type scale (root 16px) */ - --text-display-xl: 3rem; /* 48 */ - --text-display: 2.25rem; /* 36 */ - --text-h1: 1.875rem; /* 30 */ - --text-h2: 1.5rem; /* 24 */ - --text-h3: 1.25rem; /* 20 */ - --text-body-lg: 1.125rem; /* 18 */ - --text-body: 1rem; /* 16 */ - --text-sm: 0.875rem; /* 14 */ - --text-caption: 0.75rem; /* 12 */ - - --leading-tight: 1.15; - --leading-snug: 1.3; - --leading-normal:1.5; - --leading-relaxed:1.65; - - --tracking-tight: -0.02em; - --tracking-snug: -0.01em; - --tracking-wide: 0.04em; - --tracking-caps: 0.08em; - - /* =========================================================== - 4. SPACING (4px base) - =========================================================== */ - --space-0: 0; - --space-1: 0.25rem; /* 4 */ - --space-2: 0.5rem; /* 8 */ - --space-3: 0.75rem; /* 12 */ - --space-4: 1rem; /* 16 */ - --space-5: 1.25rem; /* 20 */ - --space-6: 1.5rem; /* 24 */ - --space-8: 2rem; /* 32 */ - --space-10: 2.5rem; /* 40 */ - --space-12: 3rem; /* 48 */ - --space-16: 4rem; /* 64 */ - - /* =========================================================== - 5. RADII (generous = friendly) - =========================================================== */ - --radius-sm: 6px; - --radius-md: 10px; - --radius-lg: 14px; - --radius-xl: 20px; - --radius-2xl: 28px; - --radius-full: 999px; - - /* =========================================================== - 6. SHADOWS (warm-tinted, soft) - =========================================================== */ - --shadow-sm: 0 1px 2px rgba(45, 30, 20, 0.06); - --shadow-md: 0 4px 14px rgba(45, 30, 20, 0.08); - --shadow-lg: 0 12px 32px rgba(45, 30, 20, 0.12); - --shadow-xl: 0 24px 60px rgba(45, 30, 20, 0.16); - --shadow-focus: 0 0 0 4px var(--ring); - - /* =========================================================== - 7. MOTION - =========================================================== */ - --ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* @kind other */ - --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* @kind other */ - --dur-fast: 140ms; /* @kind other */ - --dur-base: 220ms; /* @kind other */ - --dur-slow: 360ms; /* @kind other */ -} - -/* =========================================================== - DARK THEME — warm espresso - Apply via [data-theme="dark"] on or any container. - =========================================================== */ -[data-theme="dark"] { - --bg: var(--sand-950); - --bg-subtle: var(--sand-900); - --surface: #221E19; - --surface-2: #2A251F; - --overlay: rgba(0, 0, 0, 0.6); - - --fg1: #F4EFE8; - --fg2: #B6AC9C; - --fg3: #897F6F; - --fg-on-accent: #1F1C18; - - --accent: var(--clay-400); - --accent-hover: var(--clay-300); - --accent-press: var(--clay-500); - --accent-subtle: rgba(204, 139, 92, 0.14); - --accent-border: rgba(204, 139, 92, 0.30); - - --border: rgba(242, 238, 231, 0.10); - --border-strong: rgba(242, 238, 231, 0.18); - --ring: rgba(204, 139, 92, 0.45); - - --success: var(--green-400); - --success-subtle: rgba(127, 176, 122, 0.16); - --warning: var(--amber-400); - --warning-subtle: rgba(226, 176, 90, 0.16); - --danger: var(--red-400); - --danger-subtle: rgba(212, 113, 90, 0.16); - - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); - --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45); - --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5); - --shadow-xl: 0 24px 60px rgba(0, 0, 0, 0.55); -} - -/* =========================================================== - SEMANTIC TYPE CLASSES (use directly in markup) - =========================================================== */ -.t-display-xl { font-family: var(--font-display); font-size: var(--text-display-xl); font-weight: 700; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); } -.t-display { font-family: var(--font-display); font-size: var(--text-display); font-weight: 700; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); } -.t-h1 { font-family: var(--font-display); font-size: var(--text-h1); font-weight: 600; line-height: var(--leading-snug); letter-spacing: var(--tracking-snug); } -.t-h2 { font-family: var(--font-display); font-size: var(--text-h2); font-weight: 600; line-height: var(--leading-snug); letter-spacing: var(--tracking-snug); } -.t-h3 { font-family: var(--font-sans); font-size: var(--text-h3); font-weight: 600; line-height: var(--leading-snug); } -.t-body-lg { font-family: var(--font-sans); font-size: var(--text-body-lg); font-weight: 400; line-height: var(--leading-relaxed); } -.t-body { font-family: var(--font-sans); font-size: var(--text-body); font-weight: 400; line-height: var(--leading-normal); } -.t-sm { font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 400; line-height: var(--leading-normal); } -.t-caption { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 500; line-height: var(--leading-normal); } -.t-overline { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tracking-caps); } -.t-mono { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 400; font-feature-settings: 'tnum' 1; } +@import url('./prototype/tokens.css'); diff --git a/design/prototype/serve.mjs b/design/prototype/serve.mjs new file mode 100644 index 0000000..811eaf4 --- /dev/null +++ b/design/prototype/serve.mjs @@ -0,0 +1,97 @@ +// 零依赖本地热重载静态服务器 — pangolin 原型单源预览 +// 用法: node design/prototype/serve.mjs [port] 默认 5180 +// 评审给 URL,不截图(ds-flow L3)。 +import http from 'node:http'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawn } from 'node:child_process'; + +const ROOT = path.dirname(fileURLToPath(import.meta.url)); +const PORT = Number(process.argv[2]) || 5180; + +const MIME = { + '.html': 'text/html; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.mjs': 'text/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.woff2': 'font/woff2', + '.otf': 'font/otf', + '.ttf': 'font/ttf', +}; + +const clients = new Set(); +const RELOAD_SNIPPET = `\n\n`; + +const server = http.createServer((req, res) => { + if (req.url === '/__reload') { + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }); + res.write('retry: 500\n\n'); + clients.add(res); + req.on('close', () => clients.delete(res)); + return; + } + + let urlPath = decodeURIComponent(req.url.split('?')[0]); + if (urlPath === '/') urlPath = '/index.html'; + const filePath = path.join(ROOT, urlPath); + if (!filePath.startsWith(ROOT)) { res.writeHead(403); res.end('forbidden'); return; } + + fs.readFile(filePath, (err, data) => { + if (err) { res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('

404

'); return; } + const ext = path.extname(filePath).toLowerCase(); + const mime = MIME[ext] || 'application/octet-stream'; + const noCache = { 'Cache-Control': 'no-store, no-cache, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }; + if (ext === '.html') { + const html = data.toString('utf8').replace('', RELOAD_SNIPPET + ''); + res.writeHead(200, { 'Content-Type': mime, ...noCache }); + res.end(html); + } else { + res.writeHead(200, { 'Content-Type': mime, ...noCache }); + res.end(data); + } + }); +}); + +// 设计系统守门:启动 + 每次改动自动跑 check-ds.mjs(Phase 5 建立后生效)。 +const CHECK_DS = path.join(ROOT, 'tools', 'check-ds.mjs'); +function runDsCheck() { + if (!fs.existsSync(CHECK_DS)) return; // Phase 5 前无闸,静默跳过 + const p = spawn(process.execPath, [CHECK_DS], { cwd: ROOT }); + let out = ''; + p.stdout.on('data', d => out += d); + p.stderr.on('data', d => out += d); + p.on('close', code => { + const t = new Date().toLocaleTimeString(); + if (code === 0) console.log(`\x1b[32m[设计系统 ✓ ${t}] 颜色全部走 token、变量均已定义\x1b[0m`); + else console.log(`\x1b[31m[设计系统 ✗ ${t}] 存在违规 —— 运行 node tools/check-ds.mjs 看详情\x1b[0m`); + }); +} + +let debounce; +fs.watch(ROOT, { recursive: true }, (_ev, file) => { + if (file && file.endsWith('serve.mjs')) return; + clearTimeout(debounce); + debounce = setTimeout(() => { + for (const c of clients) c.write('data: reload\n\n'); + if (file && /\.(html|css|js)$/.test(file)) runDsCheck(); + }, 80); +}); + +server.listen(PORT, () => { + console.log(`prototype live at http://localhost:${PORT}/ (watching ${ROOT})`); + runDsCheck(); +}); diff --git a/design/prototype/tokens.css b/design/prototype/tokens.css new file mode 100644 index 0000000..3b1b48b --- /dev/null +++ b/design/prototype/tokens.css @@ -0,0 +1,218 @@ +/* ============================================================= + 穿山甲 VPN · Pangolin VPN — Design Tokens + colors_and_type.css + Single source of truth: color ramps, semantic colors (light + dark), + typography, spacing, radii, shadows, motion. + ============================================================= */ + +/* ---- Webfonts (open-source; documented as the brand's chosen faces) ---- + Sora — display / headings (geometric, friendly) + Manrope — body / UI (humanist geometric) + Noto Sans SC — Chinese (CJK companion) + JetBrains Mono — data readouts (IP, speed, keys) */ +@import url('https://fonts.googleapis.com/css2?family=Sora:wght@500;600;700&family=Manrope:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap'); + +:root { + /* =========================================================== + 1. PRIMITIVE COLOR RAMPS + =========================================================== */ + + /* Clay / Copper — the pangolin-armor primary (warm earth) */ + --clay-50: #FAF3ED; + --clay-100: #F2E2D4; + --clay-200: #E6C7AC; + --clay-300: #D9A982; + --clay-400: #CC8B5C; + --clay-500: #B96A3D; /* ← brand primary */ + --clay-600: #9E5630; + --clay-700: #7E4426; + --clay-800: #5E331D; + --clay-900: #3D2213; + + /* Sand / Taupe — warm neutral ramp */ + --sand-50: #FAF8F4; + --sand-100: #F2EEE7; + --sand-200: #E6DFD3; + --sand-300: #D2C8B8; + --sand-400: #B0A491; + --sand-500: #8C8270; + --sand-600: #6B6253; + --sand-700: #4E4940; + --sand-800: #2E2A24; + --sand-900: #1F1C18; + --sand-950: #14110E; /* warm espresso near-black */ + + /* Semantic hues (earth-tuned) */ + --green-400: #7FB07A; + --green-500: #5B8C5A; /* connected / secure */ + --green-600: #467046; + --amber-400: #E2B05A; + --amber-500: #D69A3C; /* connecting / warning */ + --amber-600: #B47E29; + --red-400: #D4715A; + --red-500: #C0533B; /* error / disconnect */ + --red-600: #9E4230; + + /* =========================================================== + 2. SEMANTIC TOKENS — LIGHT THEME (default) + =========================================================== */ + + /* Backgrounds & surfaces */ + --bg: var(--sand-50); /* app canvas */ + --bg-subtle: var(--sand-100); /* striped / inset regions */ + --surface: #FFFFFF; /* cards, sheets */ + --surface-2: var(--sand-50); /* nested surface */ + --overlay: rgba(31, 28, 24, 0.45); + + /* Foreground / text */ + --fg1: var(--sand-900); /* primary text */ + --fg2: var(--sand-600); /* secondary text */ + --fg3: var(--sand-500); /* tertiary / captions */ + --fg-on-accent: #FFFFFF; /* text on clay fills */ + + /* Brand / accent */ + --accent: var(--clay-500); + --accent-hover: var(--clay-600); + --accent-press: var(--clay-700); + --accent-subtle: var(--clay-50); + --accent-border: var(--clay-200); + + /* Borders & lines */ + --border: var(--sand-200); + --border-strong: var(--sand-300); + --ring: rgba(185, 106, 61, 0.35); /* focus ring (clay) */ + + /* Status */ + --success: var(--green-500); + --success-subtle: #E9F0E6; + --warning: var(--amber-500); + --warning-subtle: #F8EED6; + --danger: var(--red-500); + --danger-subtle: #F6E1DA; + + /* =========================================================== + 3. TYPOGRAPHY + =========================================================== */ + --font-display: 'Sora', 'Noto Sans SC', system-ui, sans-serif; + --font-sans: 'Manrope', 'Noto Sans SC', system-ui, sans-serif; + --font-cjk: 'Noto Sans SC', 'Manrope', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace; + + /* Type scale (root 16px) */ + --text-display-xl: 3rem; /* 48 */ + --text-display: 2.25rem; /* 36 */ + --text-h1: 1.875rem; /* 30 */ + --text-h2: 1.5rem; /* 24 */ + --text-h3: 1.25rem; /* 20 */ + --text-body-lg: 1.125rem; /* 18 */ + --text-body: 1rem; /* 16 */ + --text-sm: 0.875rem; /* 14 */ + --text-caption: 0.75rem; /* 12 */ + + --leading-tight: 1.15; + --leading-snug: 1.3; + --leading-normal:1.5; + --leading-relaxed:1.65; + + --tracking-tight: -0.02em; + --tracking-snug: -0.01em; + --tracking-wide: 0.04em; + --tracking-caps: 0.08em; + + /* =========================================================== + 4. SPACING (4px base) + =========================================================== */ + --space-0: 0; + --space-1: 0.25rem; /* 4 */ + --space-2: 0.5rem; /* 8 */ + --space-3: 0.75rem; /* 12 */ + --space-4: 1rem; /* 16 */ + --space-5: 1.25rem; /* 20 */ + --space-6: 1.5rem; /* 24 */ + --space-8: 2rem; /* 32 */ + --space-10: 2.5rem; /* 40 */ + --space-12: 3rem; /* 48 */ + --space-16: 4rem; /* 64 */ + + /* =========================================================== + 5. RADII (generous = friendly) + =========================================================== */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 20px; + --radius-2xl: 28px; + --radius-full: 999px; + + /* =========================================================== + 6. SHADOWS (warm-tinted, soft) + =========================================================== */ + --shadow-sm: 0 1px 2px rgba(45, 30, 20, 0.06); + --shadow-md: 0 4px 14px rgba(45, 30, 20, 0.08); + --shadow-lg: 0 12px 32px rgba(45, 30, 20, 0.12); + --shadow-xl: 0 24px 60px rgba(45, 30, 20, 0.16); + --shadow-focus: 0 0 0 4px var(--ring); + + /* =========================================================== + 7. MOTION + =========================================================== */ + --ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* @kind other */ + --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* @kind other */ + --dur-fast: 140ms; /* @kind other */ + --dur-base: 220ms; /* @kind other */ + --dur-slow: 360ms; /* @kind other */ +} + +/* =========================================================== + DARK THEME — warm espresso + Apply via [data-theme="dark"] on or any container. + =========================================================== */ +[data-theme="dark"] { + --bg: var(--sand-950); + --bg-subtle: var(--sand-900); + --surface: #221E19; + --surface-2: #2A251F; + --overlay: rgba(0, 0, 0, 0.6); + + --fg1: #F4EFE8; + --fg2: #B6AC9C; + --fg3: #897F6F; + --fg-on-accent: #1F1C18; + + --accent: var(--clay-400); + --accent-hover: var(--clay-300); + --accent-press: var(--clay-500); + --accent-subtle: rgba(204, 139, 92, 0.14); + --accent-border: rgba(204, 139, 92, 0.30); + + --border: rgba(242, 238, 231, 0.10); + --border-strong: rgba(242, 238, 231, 0.18); + --ring: rgba(204, 139, 92, 0.45); + + --success: var(--green-400); + --success-subtle: rgba(127, 176, 122, 0.16); + --warning: var(--amber-400); + --warning-subtle: rgba(226, 176, 90, 0.16); + --danger: var(--red-400); + --danger-subtle: rgba(212, 113, 90, 0.16); + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45); + --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5); + --shadow-xl: 0 24px 60px rgba(0, 0, 0, 0.55); +} + +/* =========================================================== + SEMANTIC TYPE CLASSES (use directly in markup) + =========================================================== */ +.t-display-xl { font-family: var(--font-display); font-size: var(--text-display-xl); font-weight: 700; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); } +.t-display { font-family: var(--font-display); font-size: var(--text-display); font-weight: 700; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); } +.t-h1 { font-family: var(--font-display); font-size: var(--text-h1); font-weight: 600; line-height: var(--leading-snug); letter-spacing: var(--tracking-snug); } +.t-h2 { font-family: var(--font-display); font-size: var(--text-h2); font-weight: 600; line-height: var(--leading-snug); letter-spacing: var(--tracking-snug); } +.t-h3 { font-family: var(--font-sans); font-size: var(--text-h3); font-weight: 600; line-height: var(--leading-snug); } +.t-body-lg { font-family: var(--font-sans); font-size: var(--text-body-lg); font-weight: 400; line-height: var(--leading-relaxed); } +.t-body { font-family: var(--font-sans); font-size: var(--text-body); font-weight: 400; line-height: var(--leading-normal); } +.t-sm { font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 400; line-height: var(--leading-normal); } +.t-caption { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 500; line-height: var(--leading-normal); } +.t-overline { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tracking-caps); } +.t-mono { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 400; font-feature-settings: 'tnum' 1; } diff --git a/docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md b/docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md index f1fd010..b95eda3 100644 --- a/docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md +++ b/docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md @@ -37,8 +37,8 @@ 把散在 `ui_kits/`(6 端 jsx/css 原型)+ `preview/`(20 规格 HTML)+ `_ds_manifest.json`(登记簿) 的东西收敛成 ds-flow 标准三件套。 -- [ ] 1.1 建 `design/prototype/` 目录;`serve.mjs` 照搬 jiu(零依赖热重载,默认端口按 jiu) -- [ ] 1.2 `design/prototype/tokens.css`:从现有 `colors_and_type.css` 迁移/规整为 +- [x] 1.1 建 `design/prototype/` 目录;`serve.mjs` 照搬 jiu(零依赖热重载,默认端口按 jiu) +- [x] 1.2 `design/prototype/tokens.css`:从现有 `colors_and_type.css` 迁移/规整为 「基础 `:root`(主题无关标量:间距/圆角/字号/字体/阴影/动效)+ `[data-theme=dark]` 颜色覆盖块」结构。 **保持数值不变**,只重排为 ds-flow 结构;`colors_and_type.css` 作为兼容别名或迁移为薄封装(不破坏现有 codegen) - [ ] 1.3 `design/prototype/atoms.css`:把按钮/卡片/输入/**语言下拉**/徽章/状态药丸等公用原子类沉淀为 diff --git a/web/usercenter/public/colors_and_type.css b/web/usercenter/public/colors_and_type.css index 3b05fd7..b84aff8 100644 --- a/web/usercenter/public/colors_and_type.css +++ b/web/usercenter/public/colors_and_type.css @@ -1,4 +1,4 @@ -/* AUTO-GENERATED — 勿手改。源: design/colors_and_type.css。生成器: web/usercenter/scripts/build-tokens.mjs。仅移除第三方 Google Fonts @import。 */ +/* AUTO-GENERATED — 勿手改。源: design/prototype/tokens.css。生成器: web/usercenter/scripts/build-tokens.mjs。仅移除第三方 Google Fonts @import。 */ /* ============================================================= 穿山甲 VPN · Pangolin VPN — Design Tokens colors_and_type.css diff --git a/web/usercenter/scripts/build-tokens.mjs b/web/usercenter/scripts/build-tokens.mjs index b4e2add..d98050c 100644 --- a/web/usercenter/scripts/build-tokens.mjs +++ b/web/usercenter/scripts/build-tokens.mjs @@ -2,7 +2,7 @@ /** * build-tokens.mjs — 设计令牌同源生成器(usercenter) * - * 唯一真相来源是仓库根的 `design/colors_and_type.css`。 + * 唯一真相来源是仓库根的 `design/prototype/tokens.css`。 * 本脚本把它原样读入,仅删除 Google Fonts @import 行后写入 * `public/colors_and_type.css`(usercenter 通过 引入此文件)。 * @@ -15,11 +15,11 @@ import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const SRC = resolve(__dirname, '../../../design/colors_and_type.css'); +const SRC = resolve(__dirname, '../../../design/prototype/tokens.css'); const OUT = resolve(__dirname, '../public/colors_and_type.css'); const BANNER = - '/* AUTO-GENERATED — 勿手改。源: design/colors_and_type.css。' + + '/* AUTO-GENERATED — 勿手改。源: design/prototype/tokens.css。' + '生成器: web/usercenter/scripts/build-tokens.mjs。仅移除第三方 Google Fonts @import。 */\n'; if (!existsSync(SRC)) { @@ -39,4 +39,4 @@ const stripped = raw .join('\n'); writeFileSync(OUT, BANNER + stripped, 'utf8'); -console.log('[build-tokens] ✅ 已生成', OUT, `(${stripped.length} 字节,源自 design/colors_and_type.css)`); +console.log('[build-tokens] ✅ 已生成', OUT, `(${stripped.length} 字节,源自 design/prototype/tokens.css)`); diff --git a/web/website/scripts/build-tokens.mjs b/web/website/scripts/build-tokens.mjs index 01f4b8b..5f36f88 100644 --- a/web/website/scripts/build-tokens.mjs +++ b/web/website/scripts/build-tokens.mjs @@ -2,7 +2,7 @@ /** * build-tokens.mjs — 设计令牌同源生成器 * - * 唯一真相来源是仓库根的 `design/colors_and_type.css`(铁律 1:颜色只用语义 token)。 + * 唯一真相来源是仓库根的 `design/prototype/tokens.css`(铁律 1:颜色只用语义 token)。 * 本脚本把它原样读入,仅做一处「必须的」改写后写入 `src/styles/tokens.gen.css`: * * 删除其中加载 Google Fonts 的 `@import url('https://fonts.googleapis.com/...')` 一行。 @@ -20,11 +20,11 @@ import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const SRC = resolve(__dirname, '../../../design/colors_and_type.css'); +const SRC = resolve(__dirname, '../../../design/prototype/tokens.css'); const OUT = resolve(__dirname, '../src/styles/tokens.gen.css'); const BANNER = - '/* AUTO-GENERATED — 勿手改。源: design/colors_and_type.css。' + + '/* AUTO-GENERATED — 勿手改。源: design/prototype/tokens.css。' + '生成器: web/website/scripts/build-tokens.mjs。仅移除第三方 Google Fonts @import。 */\n'; if (!existsSync(SRC)) { @@ -50,4 +50,4 @@ if (/fonts\.(googleapis|gstatic)\.com/i.test(stripped.replace(/^\s*(\/\/|\*|\/\* } writeFileSync(OUT, BANNER + stripped, 'utf8'); -console.log('[build-tokens] 已生成', OUT, `(${stripped.length} 字节,源自 design/colors_and_type.css)`); +console.log('[build-tokens] 已生成', OUT, `(${stripped.length} 字节,源自 design/prototype/tokens.css)`); diff --git a/web/website/src/styles/tokens.gen.css b/web/website/src/styles/tokens.gen.css index 43d106b..f838a1e 100644 --- a/web/website/src/styles/tokens.gen.css +++ b/web/website/src/styles/tokens.gen.css @@ -1,4 +1,4 @@ -/* AUTO-GENERATED — 勿手改。源: design/colors_and_type.css。生成器: web/website/scripts/build-tokens.mjs。仅移除第三方 Google Fonts @import。 */ +/* AUTO-GENERATED — 勿手改。源: design/prototype/tokens.css。生成器: web/website/scripts/build-tokens.mjs。仅移除第三方 Google Fonts @import。 */ /* ============================================================= 穿山甲 VPN · Pangolin VPN — Design Tokens colors_and_type.css