Files
rootandClaude Opus 5.5 09a2adb637 feat(web): 设计规范与 uhd_web 同步(截至 2026-10-08)
- 材质统一边光:slab / float / pill / pill-selected / seg-thumb / chrome-side 改为左上亮右下暗的 1px 边光 + 外侧暗线;新增 lens / field / chip 参数与 pg-float-modal
- 主按钮:转动光谱环改为静态左上光谱高光,悬停沿边扫过一小段;次按钮同为 lens 玻璃
- Input / Textarea / SelectTrigger 挂 .field-glass,原生下拉与实体选择器同步;对话框按钮行 gap-3
- 提示条改 pg-float(状态色只在图标、展开不叠放);新增 GlassTooltip 接管 title 提示
- 触屏判断 input-modality.ts、touch / hover 自定义变体;animate-pg-pop 改 backwards;scroll-fade-end
- 控制台外壳:侧栏无硬分隔线、折叠开关进标志行;顶栏去磨砂,右侧控件收进玻璃胶囊
- 登录页居中单卡 auth-card;详情头图「订阅」改回普通主按钮,删除 pill-solid;发现页海报角标改 glass-chip-media
- 引入 simple-liquid-glass(liquid-surface.tsx);更新 docs/design 三份规范与 CHANGELOG

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-08 02:29:19 +08:00

8.4 KiB

设计 token

迁入自 /opt/uhd_web docs/features/(2026-09-27),2026-10-08 与 uhd_web 同步。文中流媒体、播放器、落地页、套餐等段落是 uhd_web 的原始上下文,本项目没有这些页面,只作材质与 token 用法参考;以本目录 README 的约定为准。

样式入口是 app.css。本说明定义颜色类 token 的分层、命名与使用规则,目的是让主站之外的皮肤(如动漫版)只改 token 值就能换皮,组件源码两边保持一致。材质类的说明见界面材质。

分层

层 在哪 谁能引用
原色 @theme 里的 --color-ink-*、--color-bone-*、--color-gold-* 等色阶 只有下一层的 token 定义
语义 token :root(浅色门户)、.dark(深色门户)、:root:has([data-stream-shell])(流媒体壳)三块里的 --content-*、--canvas 等 材质类与组件
材质 pg-surface / pg-float / pg-pill 等 class 与 --pg-* 参数 组件
组件 app/routes、app/components 里的 class 不引用原色、不写字面量

主题包只覆盖语义 token 的值(以及材质参数),不改组件选择器。流媒体壳的 token 块必须放在无层的语义 token 区,放进 @layer base 会被 .dark 的无层规则盖掉。

词汇

token 含义 夜间值
content-strong / content / content-secondary / content-tertiary / content-muted / content-faint 文字层级,随日夜翻转 中性白到灰,逐档变淡
canvas / surface / surface-raised / surface-high 页面底、普通面、抬起的卡、更高一层 中性近黑,逐档抬亮
brand-soft / brand-light / brand / brand-strong / brand-deep 品牌色族。主站无彩:夜间是白、日间是墨(2026-09-26);动漫版换成它的品牌色 白到浅灰
success / warning / destructive(及 -foreground) 状态色,低饱和,只用于状态提示;实底上的文字用对应的 -foreground 绿 / 琥珀 / 红
wash 半透明"洗"层的底色:hover、按压、分隔线。透明度留在调用处,写成 bg-wash/[0.07]、border-wash/10;暗底是白,亮底是黑 白
on-media-*、on-media-wash 压在视频 / 海报上的文字与洗层,不随日夜翻转,画面永远是暗的。播放器目录一律用这一族 与 content 族同值,白
scrim 压暗画面的遮罩底色,透明度留在调用处(bg-scrim/60、from-scrim/75) 黑

bg-wash/[0.07] 经 Tailwind 生成 color-mix(in oklab, var(--wash) 7%, transparent),与原来的 rgba(255,255,255,0.07) 渲染结果一致;去字面量迁移时 143 对旧类到新类在同一元素上原地换类截图,字节级一致(渐变分开两个元素比对会有 Skia 抖动带来的 ±1 噪声,不是颜色差)。

字号

@theme 里的 --text-* 只定字号不定行高,行为等同原来的 text-[Npx];自带行高的 text-xs / text-sm 等 Tailwind 档继续用,两套不混。组件按角色引用,不写像素。

阶 token 值
正文 micro / label / meta / caption / body / control / lead / lead-lg 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17 px
标题 title-xs / title-sm / title / title-lg / headline-xs / headline-sm / headline / headline-lg / display-xs / display-sm / display / display-lg / display-xl 18 / 20 / 22 / 24 / 26 / 28 / 30 / 32 / 34 / 42 / 44 / 52 / 56 px
流式 hero-stream(-sm/-md)、detail-title、notfound-code、pricing-hero、pricing-title、landing-* 各处 clamp 原值,按用途命名

2026-09-12 迁移时零散字号并入最近一档(10.5 → 11、13.5 → 14、0.72rem → 11、1.05rem → 17 等,最大偏差 0.6px,约 120 处),经页面级截图复验;标题阶逐档保留现状值,流式标题各处原值保留,收敛是设计决定另议。

tailwind-merge 不认识的 text-* 会被当成文字颜色,text-control text-muted-foreground 合并时字号会被丢掉。登记表不再手写:gen-design-tokens.mjs 从 app.css 抽出字号、缓动、圆角、时长 token 生成 app/lib/design-tokens.generated.ts,cn() 读它;改了 app.css 跑 deno task gen:tokens,deno task ci 校验生成物是最新的。

圆角

控件 rounded-md、输入与小按钮 rounded-lg、卡片 rounded-xl、面板 rounded-2xl、抽屉与大卡 rounded-3xl、药丸 rounded-full。这些是 Tailwind 名,值由 --radius 派生,主题改一个变量整体缩放。角色型的三个:rounded-tight(徽章、骨架屏等小件,原来的裸 rounded)、rounded-t-drawer(手机端底部抽屉顶角)、rounded-slab(门户大卡)。不写 rounded-[..]。

动效

时长四档加两档特例,缓动五种,组件只用这些名字:

类 值 用途
duration-micro 150ms 按压、hover 这类微反馈
duration-base 200ms 绝大多数颜色 / 透明度过渡
duration-layout 300ms 尺寸、位置变化
duration-slow / duration-hero / duration-cinematic 500 / 700 / 1000ms 入场、hero、装饰性动画
ease-standard cubic-bezier(0.4, 0, 0.2, 1) 默认
ease-spring / ease-spring-soft 回弹 滑块、按压
ease-sheet 抽屉 底部抽屉进出
ease-pop 弹层出现 pg-pop

duration-* 是 @utility,写法与内置的 duration-N 一致(同时设 --tw-duration,tw-animate 的 animate-in 也读它)。app.css 里材质与关键帧动画的时长是逐个校准过的值,不套四档;缓动已统一引用 --ease-*。

材质参数与播放器白天模式

所有材质(播放器的 surface / pill,按钮的 pill-primary / pill-secondary / pill-selected,分段滑块 seg-thumb,门户的 slab / chrome / float,小控件玻璃 lens,输入框 field,小玻璃圆片 chip)都是"固定配方 + --mat-<材质>-<部位> 参数",class 只引用参数,壳与主题只覆盖参数;结构见界面材质。

播放器整体压在画面上,默认只有夜间,但白天模式已经留出位置:主题在播放页作用域(:root:has([data-stream-shell="play"]))覆盖两组东西即可,不需要改播放器组件。

  • on-media-* 与 on-media-wash:控件文字与洗层。做浅色玻璃控件时改成深色文字 + 黑色洗层。
  • --mat-surface-* / --mat-pill-*:材质参数。做浅色玻璃或平面 pop 材质时换底色与线色,blur 设 none 就是平面。

视频面本身永远是黑的,不在主题范围内。

规则

  • 组件里不出现 bone / ink / gold 原色类、white / black 透明度写法和类名里的十六进制颜色。流媒体与播放器连 JS 里的十六进制也不写;测速图表、二维码库这类只接受颜色值的地方用组件内常量表。
  • 组件里不出现 Tailwind 调色板颜色(emerald-*、amber-*、rose-*、blue-* 等):状态用 success / warning / destructive,装饰性图标与强调用 foreground 的透明度。
  • 组件里不出现 text-[Npx] / text-[Nrem] / text-[clamp(...)],字号只从阶梯取。
  • 组件里不出现 duration-N、ease-[...]、裸 rounded、rounded-[...]。
  • 改了 app.css 里的 token 就跑 deno task gen:tokens,让 tailwind-merge 的登记表跟上;CI 会校验。
  • 播放器(app/routes/stream/player/)里压在画面上的东西用 on-media 族;流媒体浏览页用 content / wash 族。分不清就问一句:主题切到浅色时它应不应该变色。
  • 新的语义需求先加 token 再用,不在组件里临时拼色。
  • 按钮 hover 不填实心 primary / foreground;浮层不手写 shadow-2xl,用 pg-float。
  • scripts/check-design-tokens.sh 在 deno task ci 里对整个 app/ 做守卫(落地页组件与 /dev/ui 除外)。确实需要例外时(二维码必须白底、支付品牌色),在该行或上一行写注释 token-allow: 原因,不另开白名单。

迁移状态

  • 已迁移:颜色、字号、圆角、时长与缓动全部 app/(2026-09-27 守卫扩到全站)。
  • 未迁移:落地页组件(app/components/landing)仍固定夜间、有装饰性字面量,落地页日间另做;测速页图表配色是组件内的常量表(已改无彩)。
  • 材质已全部参数化;pg-beam、tab-slider(滑块的位移动效,外观是 seg-thumb)、spd-bg 这类效果不算材质。
  • 三个本来就不存在于色阶里的类(text-bone-600、text-gold-50、to-gold-600)在迁移时删除,它们原本不产生任何样式。