DESIGN STANDARD · 2026-05-29 · v1

upio.ai 设计系统

2026 起 upio.ai 全站新增页面参照的统一视觉与动效标准,从今天 akke / cloud-pc-progress 一系列页面提炼。每段都是 live demo + 可复制代码片段,全 self-contained、零外部依赖

SCOPE upio.ai 全部新增页面 TONE 深色 · 信息密度高 · 节制的动效 LIVING EXAMPLE akke / cloud-pc-progress
01

SECTION ONE

Design tokens

所有色彩、字体、间距以 CSS variables 承载。新页面把 :root 块整段复制即可。

背景层(3 级深色)

--bg
#0a0d14 · 页面底
--bg-elevated
#131720 · 卡片底
--bg-deep
#1a1f2b · tooltip/inset

文本层(3 级密度)

--text
#e8eaef · 标题/正文
--text-dim
#9ba3b4 · 副文案
--text-muted
#6b7384 · 标签/meta

Accent 调色板(5 色 × 2 强度)

--accent
#8b5cf6 · primary purple
--accent-2
#60a5fa · data blue
--accent-3
#34d399 · success green
--warning
#fbbf24 · amber
--danger
#f87171 · red

Soft 变体(半透明背景)

--accent-soft
rgba(139,92,246,.15)
--accent-2-soft
rgba(96,165,250,.13)
--accent-3-soft
rgba(52,211,153,.14)
--warning-soft
rgba(251,191,36,.14)
--danger-soft
rgba(248,113,113,.14)

语义映射(什么时候用哪种颜色)

--accent (紫) 品牌主色 · 数据强调 · 公告板隐喻 · 链接 hover
--accent-2 (蓝)数据 / 信息 / 缓解中 / 进行中
--accent-3 (绿)成功 / 已完成 / PASS / 健康
--warning (黄)警告 / 监控中 / 部分就绪 / 待激活
--danger (红)错误 / 待修 / 阻塞 / 阻断性失败
:root tokens 代码片段
:root {
  --bg: #0a0d14; --bg-elevated: #131720; --bg-deep: #1a1f2b;
  --border: #232936; --border-strong: #2f3646;
  --text: #e8eaef; --text-dim: #9ba3b4; --text-muted: #6b7384;
  --accent:   #8b5cf6; --accent-soft:   rgba(139, 92, 246, 0.15);
  --accent-2: #60a5fa; --accent-2-soft: rgba(96, 165, 250, 0.13);
  --accent-3: #34d399; --accent-3-soft: rgba(52, 211, 153, 0.14);
  --warning:  #fbbf24; --warning-soft:  rgba(251, 191, 36, 0.14);
  --danger:   #f87171; --danger-soft:   rgba(248, 113, 113, 0.14);
  --font-display: 'Instrument Serif', 'Source Han Serif SC', Georgia, serif;
  --font-ui:      'Inter Tight', 'Noto Sans SC', system-ui, sans-serif;
  --font-mono:    'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;
}
02

SECTION TWO

Typography 三大字体栈

三种字体明确分工:Instrument Serif italic 大标题制造情绪、Inter Tight 正文密度、JetBrains Mono 所有 label/number/code 的"工程语调"。

--font-display · 大标题专用
云电脑子项目进展同步
Instrument Serif italic · 64px · 用于 H1 / .section-head .lede / .section-head .num
--font-ui · 正文 / 副标题
三种字体明确分工,工程语调 + 大标题情绪相互衬托。
Inter Tight 16px · body / subtitle / p / .role .rbody
--font-mono · label / number / code
CHANNEL · 2026-05-28 · WEEK 22 · 7 / 8 · ¥4.90
JetBrains Mono · 10.5–22px · h2 / h4 / .k-label / .k-val / .dial-val / 所有时间戳与数字

字体尺寸阶梯

H1 64px · Instrument Serif italic · 渐变 text-clip
.section-head .lede 30px · Instrument Serif italic · 章节副标题
.section-head .num 80px · Instrument Serif italic · 大章节编号
H3 19px · Inter Tight 600 · 子标题
H2 / H4 11px · JetBrains Mono 700 · UPPERCASE letter-spacing 0.12em · 类目/分隔标签
body 15.5px · Inter Tight 400 · 1.7 line-height
subtitle 17px · Inter Tight 400 · 1.6 line-height
03

SECTION THREE

Layout primitives

Container · 最大宽度 1080px

所有正文页面用 .container { max-width: 1080px; margin: 0 auto; padding: 56px 24px 120px; },避免阅读行长过宽。

Sticky TOC · 顶栏

每个页面顶部固定 .sticky-toc,带 backdrop-filter 模糊背景 + reading-progress 滚动进度条。TOC 链接配章节锚点 + IntersectionObserver 滚动高亮。

Section head · 大编号 + meta

01

SECTION ONE

主题副标题用 italic display
Section head 代码
<div class="section-head" id="案例">
  <span class="num">01</span>
  <div class="meta">
    <h2>SECTION ONE</h2>
    <div class="lede">主题副标题用 italic display</div>
  </div>
</div>

Breadcrumb · 面包屑

04

SECTION FOUR

核心组件

KPI 卡(4 色 × hero/inline 两套用法)

顶部 3px 状态色条 + 大数字 + label + sub + micro。入场 blur-up 错峰 80ms · 数字 count-up · 收尾 text-shadow burst

已完成
2/2
9 分钟连发 · 全 PASS
CROSS-CHECK BY RECEIVER
部分就绪
7/8
仅末段未启动
CRON / SCHEMA / RPC GREEN
设计上限
18
条 / 天 · 45–90 min jitter
OCR ≥ 0.95 GATE
已对外
0
poller 首次抢锁待启动
P0 BLOCKED · 5/29 ACTIVATE
KPI HTML 模板
<div class="kpi-grid">
  <div class="kpi k-green" data-counter-target="2" data-counter-suffix="/2">
    <div class="k-label">已完成</div>
    <div class="k-val" data-counter="2">2/2</div>
    <div class="k-sub">9 分钟连发 · 全 PASS</div>
    <div class="k-micro">CROSS-CHECK BY RECEIVER</div>
  </div>
  <!-- 其他 .k-amber / .k-purple / .k-red 同结构 -->
</div>

Callout × 4(info / warn / success / danger)

info · 蓝色 left-border 3px + soft 蓝底。中性提示、上下文说明。
warn · 黄色 left-border 3px + soft 黄底。需要注意但非阻塞。
success · 绿色 left-border 3px + soft 绿底。已完成 / PASS。
danger · 红色 left-border 3px + soft 红底。阻塞 / 严重失败。
Callout HTML
<div class="callout info"><strong>info ·</strong> 中性提示</div>
<div class="callout warn"><strong>warn ·</strong> 需要注意</div>
<div class="callout success"><strong>success ·</strong> 已完成</div>
<div class="callout danger"><strong>danger ·</strong> 阻塞 / 严重</div>

Dial 卡(环形进度 + glow)

--dial-offset 控制 stroke-dashoffset,0 = 满载 + drop-shadow glow。data-dial-count 触发中心数字与圆环同步 count-up

满载 (offset=0)
2 / 2
drop-shadow glow 自动启用
部分 (offset=33)
7 / 8
无 glow,只填环
阈值/区间
45–90 min
无 count-up(非纯数字)
红色满载
1 阻塞
视觉冲击最强
Dial card 模板
<div class="dial-grid">
  <div class="dial-card green" style="--dial-offset: 0;"
       data-dial-count="2" data-dial-suffix=" / 2">
    <div><svg viewBox="0 0 100 100">
      <circle class="dial-track" cx="50" cy="50" r="42"/>
      <circle class="dial-fill"  cx="50" cy="50" r="42"/>
    </svg></div>
    <div class="dial-meta">
      <div class="dial-name">指标名</div>
      <div class="dial-val">2 / 2</div>
      <div class="dial-design">说明 / 设计上限</div>
    </div>
  </div>
</div>
<!-- --dial-offset 对应 stroke-dasharray=264 的偏移:
     0=100%满 · 33≈88% · 92≈65% · 264=0% -->

Sparkline · 趋势图 + tooltip + 末点 pulse

3 层 SVG:area-fill 渐变(path 下方)+ polyline(数据线)+ data points(dots)。入场 stroke-draw + 7 个点 stagger fade。Hover 显示日期 + 值,竖向 marker 同步。末点是特殊的 +1 预期点,无限脉冲。

DEMO METRIC · TREND
1 → 1 → 2 预期
本周持平 · 下周拐点
1 2 +1
Day 1Day 2Day 3Day 4 Day 5Day 6Day 7下周

试一下:把鼠标移到上面的 sparkline,会出现 tooltip 显示 hovered 数据点的标签 + 值。

Bad Case Matrix · 重要性 × 状态

4 状态色(done/mitigated/watching/todo)做列头小圆点 + 3 重要性级别(critical/important/minor)做左侧 vertical bar accent。空格 opacity 0.25,填充泡是带 ring 的 42×42 圆。唯一阻塞的填充泡加 .urgent 类持续 pulse

DEMO MATRIX

维度
已完成
缓解中
监控中
待处理
关键Critical
3
1
重要Important
2
3
一般Minor
4

Role 卡 · 网格小卡片

① ROLE NAME
用 mono uppercase letterspacing 给名字一种工程语调,body 用 ui 字体保持可读。
② ANOTHER ROLE
2 列网格,移动端自动堆叠。适合用来分发"谁干什么"。

Event timeline · 事件日记

05-22 周四
事件描述里 关键短语用强调,其他用 dim。
05-26 周一
每条 2 列:日期 + 描述。border-left 区分时间线。
下周
future 类换成紫色 border-left + 紫色日期,强调预期。

Tag · 章节标签

DESIGN STANDARD · 2026-05-29 · v1
05

SECTION FIVE

动效模式 8 件套

动效目的是引导注意 + 给静态信息加节奏,不是装饰。每个 pattern 都基于 IntersectionObserver + .in-view class,确保只在用户实际滚到时才触发,并且都带 reduce-motion 兜底。

① 入场 Blur-up(KPI 用)

卡片 opacity: 0 + filter: blur(8px) + translateY(10px) 起步,.in-view 时收回。同组按 :nth-child 错峰 80ms。用于:hero KPI 入场

Blur-up CSS
.kpi {
  opacity: 0; filter: blur(8px); transform: translateY(10px);
  transition: opacity .55s ease-out, filter .55s ease-out,
              transform .55s ease-out, border-color .2s;
}
.kpi.in-view { opacity: 1; filter: blur(0); transform: translateY(0); }
.kpi-grid .kpi:nth-child(2).in-view { transition-delay: 80ms, 80ms, 80ms, 0s; }
.kpi-grid .kpi:nth-child(3).in-view { transition-delay: 160ms, 160ms, 160ms, 0s; }
.kpi-grid .kpi:nth-child(4).in-view { transition-delay: 240ms, 240ms, 240ms, 0s; }

② Number Count-up(统一 helper)

所有数字 0 → target,ease-out cubic 900ms。animateNumber 接受 prefix/suffix/decimals/dur/onDone 选项,整站只有这一个 helper

animateNumber 通用 helper
const animateNumber = (el, target, opts) => {
  opts = opts || {};
  const suffix = opts.suffix || '';
  const prefix = opts.prefix || '';
  const decimals = opts.decimals;
  const onDone = opts.onDone;
  const dur = opts.dur || 900;
  const start = performance.now();
  const isInt = decimals == null ? Number.isInteger(target) : false;
  const fmt = (v) => {
    if (decimals != null) return Number(v).toFixed(decimals);
    return isInt ? Math.round(v).toString() : Number(v).toFixed(1);
  };
  const step = (t) => {
    const p = Math.min((t - start) / dur, 1);
    const eased = 1 - Math.pow(1 - p, 3);
    el.textContent = prefix + fmt(target * eased) + suffix;
    if (p < 1) requestAnimationFrame(step);
    else if (onDone) onDone();
  };
  requestAnimationFrame(step);
};

③ Number Burst(收尾闪光)

count-up 完成后给数字加 0.7s text-shadow 闪一下,再消失。用于:KPI .k-val 数字收尾

numBurst keyframes
.kpi .k-val.burst { animation: numBurst .7s ease-out; }
@keyframes numBurst {
  0%   { text-shadow: 0 0 0 currentColor; }
  35%  { text-shadow: 0 0 18px currentColor, 0 0 6px currentColor; }
  100% { text-shadow: 0 0 0 currentColor; }
}
// JS: animateNumber(el, target, { onDone: () => {
//   el.classList.add('burst');
//   setTimeout(() => el.classList.remove('burst'), 800);
// }});

④ SVG draw-in(flow line / sparkline)

stroke-dasharray = path 长度,stroke-dashoffset 从 length → 0,配 cubic-bezier(.65,0,.35,1) 1.4s。用于:原理图 flow-line、sparkline polyline

SVG stroke-draw
.ps-path {
  stroke-dasharray: 760;  /* ≈ polyline 实际长度 */
  stroke-dashoffset: 760;
}
.primary-sparkline.in-view .ps-path {
  animation: drawSpark 1.4s cubic-bezier(.65,0,.35,1) forwards;
}
@keyframes drawSpark { to { stroke-dashoffset: 0; } }

⑤ Particle flow(SVG animateMotion)

沿 path 跑的发光小圆点,呼应"数据在动"的语义。filter: url(#particleGlow) 给柔光,begin 错开各路径的入场时机。用于:原理图核心通路

Particle SVG 模板
<defs>
  <filter id="particleGlow" x="-200%" y="-200%" width="500%" height="500%">
    <feGaussianBlur stdDeviation="2"/>
    <feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
  </filter>
</defs>
<!-- 沿 line/path 跑 -->
<circle r="3" fill="#34d399" class="particle" filter="url(#particleGlow)">
  <animateMotion path="M 230,354 Q 230,310 230,285"
                 dur="1.8s" repeatCount="indefinite" begin="2.8s"/>
</circle>
<!-- reduce-motion 兜底:CSS 隐藏 -->

⑥ Radar pulse(核心元素强调)

给重要元素加一层同形状外圈,animate 同步驱动 x/y/width/height/stroke-opacity,制造"雷达外扩"。用于:架构图核心节点

Radar pulse SVG
<rect x="100" y="170" width="840" height="110" rx="14"
      fill="none" stroke="#8b5cf6" stroke-width="1.5">
  <animate attributeName="x"      values="100;88"   dur="2.8s" repeatCount="indefinite"/>
  <animate attributeName="y"      values="170;158" dur="2.8s" repeatCount="indefinite"/>
  <animate attributeName="width"  values="840;864" dur="2.8s" repeatCount="indefinite"/>
  <animate attributeName="height" values="110;134" dur="2.8s" repeatCount="indefinite"/>
  <animate attributeName="stroke-opacity" values="0.6;0" dur="2.8s" repeatCount="indefinite"/>
</rect>

⑦ Dial glow(满载强调)

--dial-offset: 0(100% 满)时,stroke 加 drop-shadow 状态色散光。颜色随 currentColor 自动跟随 .green/.red/.amber/.purple。

Dial glow CSS
// JS: dial-card with --dial-offset:0 gets .dial-full class
.dial-card.in-view.dial-full .dial-fill {
  filter: drop-shadow(0 0 5px currentColor);
}
.dial-card.green.in-view.dial-full .dial-fill { filter: drop-shadow(0 0 6px var(--accent-3)); }
.dial-card.red.in-view.dial-full   .dial-fill { filter: drop-shadow(0 0 6px var(--danger)); }

⑧ Urgent pulse(唯一阻塞强调)

给"阻塞" / "P0 待修" 的单一元素加无限循环 box-shadow + filter pulse。规则:每页最多 1 个 urgent,多了视觉就降级了。

bcUrgent keyframes
.bc-bubble.todo.urgent {
  animation: bcUrgent 2.2s ease-in-out infinite;
  animation-delay: 1.2s;
}
@keyframes bcUrgent {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(248, 113, 113, 0.45);
    filter: drop-shadow(0 0 0 transparent);
  }
  50% {
    box-shadow: 0 0 0 10px rgba(248, 113, 113, 0);
    filter: drop-shadow(0 0 8px var(--danger));
  }
}

统一的 IntersectionObserver 模板

in-view 观察器骨架
const target = document.querySelector('.your-component');
if (target && 'IntersectionObserver' in window) {
  const obs = new IntersectionObserver((entries) => {
    entries.forEach(e => {
      if (!e.isIntersecting) return;
      e.target.classList.add('in-view');
      // 触发 count-up / draw / 任何动效
      obs.unobserve(e.target);
    });
  }, { threshold: 0.3 });
  obs.observe(target);
}
06

SECTION SIX

兜底 · 可访问性 · 打印

prefers-reduced-motion

系统级别"减少动画"用户偏好下,所有 transition + animation 一律关闭,动态元素直接显示终态。模板:

reduce-motion 兜底
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
  /* 元素直显终态 */
  .kpi { opacity: 1 !important; filter: none !important; transform: none !important; }
  .dial-card .dial-fill { stroke-dashoffset: var(--dial-offset, 0) !important; }
  .primary-sparkline .ps-path { stroke-dashoffset: 0 !important; }
  .primary-sparkline .pt { opacity: 1 !important; }
  .bc-matrix .bc-cell, .bc-matrix .bc-row-label {
    opacity: 1 !important; transform: none !important;
  }
  /* SVG 动画隐藏 */
  .diagram .particle, .diagram .board-radar { display: none !important; }
  /* CSS 无限循环 pulse 关 */
  .bc-bubble.todo.urgent { animation: none !important; }
}

@media print

白底黑字降级,Georgia serif,干掉 sticky-toc / footer / code-ref。

print 兜底
@media print {
  body { background: white; color: black; font-family: Georgia, serif; }
  .sticky-toc, footer, .breadcrumb, details.code-ref { display: none; }
  .container { max-width: 100%; padding: 0; }
  .kpi, .dial-card, .primary-sparkline, .bc-matrix, .role, .callout {
    background: white; border-color: #ccc; color: black;
  }
  h1 {
    color: black;
    -webkit-text-fill-color: black;
    background: none;
  }
}

语义化 + a11y 基线

SVG 必带 role="img" + aria-label 描述图意,让屏幕阅读器有上下文。
display: none 不能用来藏 reduce-motion 下的关键内容。粒子是装饰可以 display:none;KPI 数字必须直显终态。
color 永远不能是唯一信号。"红/绿/黄" 必须同时配 icon、label、text。Matrix 列头的小圆点 + 文字名就是这个原则。