ux-ui-maxpro

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ux-ui-maxpro

ux-ui-maxpro

A playbook for reproducing a reference UI exactly, on this repo's design system, without drifting into approximation or fabricated content. Optimized for the snapcn stack (Next.js App Router + Tailwind v4 + token-driven
app/globals.css
, DESIGN.md rules), but the method is stack-agnostic.
一套用于在当前项目的设计系统上精确复刻参考 UI 的实践手册,避免偏离为近似实现或编造内容。针对 snapcn 技术栈(Next.js App Router + Tailwind v4 + 基于 token 的
app/globals.css
、DESIGN.md 规则)进行了优化,但方法本身与具体技术栈无关。

The one rule

唯一准则

Measure before you type. Never eyeball a reference straight into code. Extract a written spec first; code against the spec. Guessing produces "close-ish", and "close-ish" is exactly what the user rejects when they say "not even a single change".
先测量,再编写代码。 绝不要看着参考图凭感觉直接写代码。先提取一份书面规格,再根据规格编写代码。凭猜测只会得到“比较接近”的结果,而当用户说“不能有任何一处不同”时,“比较接近”恰恰就是他们无法接受的结果。

1. Extract a spec from the reference

1. 从参考设计中提取规格

Read the screenshot(s) like a design system and write down concrete numbers before touching any file:
  • Structure: how many regions (top bar, sidebar, content), and each region's width/height. Note whether the top bar has a border/shadow/blur or is flush with the page background.
  • Grid: column count at the widest breakpoint, gutter size, and how it steps down (4 → 3 → 2 → 1). For masonry, note whether card heights vary and by how much — that varied rhythm is usually the whole point.
  • Cards / tiles: corner radius, surface color, border vs borderless, shadow vs flat, overlay elements (avatars, buttons) with their size and inset from the corner.
  • Type: size, weight, and color for each text role (title, tagline, meta, nav link). Note when two roles sit inline on one baseline (e.g. bold title + gray tagline).
  • Pills / controls: capsule height, horizontal padding, gap, and the active-vs-inactive treatment (a black/white inversion is common).
  • Color: sample the exact surface grays and accents. They are rarely pure
    #fff
    /
    #000
    .
Keep this spec next to you; every class you write should trace back to a measured number.
像分析设计系统一样阅读截图,并在修改任何文件前记录具体数值:
  • 结构:包含多少个区域(顶部栏、侧边栏、内容区),以及每个区域的宽度和高度。记录顶部栏是带有边框、阴影或模糊效果,还是与页面背景无缝衔接。
  • 网格:最宽断点下的列数、栏间距,以及列数如何逐级变化(4 → 3 → 2 → 1)。对于 masonry 布局,记录卡片高度是否有变化以及变化幅度——这种错落的节奏通常正是设计的核心。
  • 卡片 / tile:圆角、表面颜色、使用边框还是无边框、使用阴影还是扁平样式,以及覆盖元素(头像、按钮)的尺寸和距角落的内缩距离。
  • 排版:每种文本角色(标题、标语、元信息、导航链接)的字号、字重和颜色。记录两种文本角色位于同一基线上的情况,例如粗体标题与灰色标语并排显示。
  • Pill / 控件:胶囊高度、水平内边距、间距,以及选中与未选中状态的处理方式(常见做法是黑白反转)。
  • 颜色:采集准确的表面灰阶和强调色。它们很少是纯
    #fff
    /
    #000
    。
将这份规格保留在手边;编写的每个 class 都应能追溯到一个测量值。

2. Map every element to honest data

2. 将每个元素映射到真实数据

For each reference element, decide its real-data equivalent in this project. Never fabricate live-looking numbers (this repo's standing rule): a "63 online" indicator becomes a real derived count (e.g. total components); a "Last updated 11h ago" becomes a real timestamp from an API with a graceful fallback, not a hardcoded string. If there is no honest equivalent, drop the element rather than fake it. Preserve accessibility affordances the reference lacks —
aria-label
/
title
on icon-only links,
aria-pressed
on filter pills.
为每个参考元素确定其在当前项目中的真实数据对应项。绝不编造看似真实的数据(本项目的长期规则):例如,“63 online”指标应替换为真实推导出的数量(如组件总数);“Last updated 11h ago”应替换为 API 返回的真实时间戳,并提供优雅的回退方案,而不是硬编码字符串。如果不存在真实的数据对应项,就删除该元素,而不是伪造。保留参考设计本身可能缺少的可访问性支持——仅含图标的链接应提供
aria-label
/
title
,筛选 pill 应提供
aria-pressed
。

3. Style through tokens, never hex

3. 始终通过 token 设置样式,绝不使用十六进制值

Per DESIGN.md, components must not hardcode colors, radii, or shadows. Any new surface the reference needs (e.g. a gallery card gray) becomes a new semantic token in
app/globals.css
:
  • Add the raw value to both
    :root
    (light) and
    .dark
    (dark) — a dark peer is mandatory, even when the reference is light-only; tune the dark value by eye.
  • Register it under
    @theme inline
    (
    --color-<name>: var(--<name>)
    ) so a
    bg-<name>
    /
    text-<name>
    utility exists.
  • If the exact look requires a documented deviation from DESIGN.md (capsule buttons, blur chips, a borderless card), add a short amendment to DESIGN.md so the next contributor does not "fix" it back.
根据 DESIGN.md,组件不得硬编码颜色、圆角或阴影。参考设计需要的任何新表面样式(例如 gallery card 灰色),都应在
app/globals.css
中定义为一个新的 semantic token:
  • 在
    :root
    (浅色主题)和
    .dark
    (深色主题)中都加入原始值——即使参考设计仅提供浅色主题,也必须配置对应的深色版本;深色值可通过视觉判断进行调整。
  • 在
    @theme inline
    中注册(
    --color-<name>: var(--<name>)
    ),从而生成对应的
    bg-<name>
    /
    text-<name>
    utility。
  • 如果精确还原设计需要偏离 DESIGN.md 中已有规则(胶囊按钮、模糊 chip、无边框卡片等),应在 DESIGN.md 中添加一段简短修订,避免后续贡献者将其“修复”回原样。

4. Masonry recipes

4. Masonry 布局方案

  • CSS columns (
    columns-N
    , children
    break-inside-avoid mb-*
    ) is the default: it packs top-to-bottom per column, SSRs with zero layout shift, and — crucially — keeps every card in one DOM parent, so a stable React
    key
    preserves a card's mounted state across filtering and sorting. Use it unless the design truly needs left-to-right row order.
  • JS balanced columns (measure heights, place into the shortest column) gives row-major order but costs a first-paint measurement pass and remounts children across column parents. Only reach for it when row order is a hard requirement.
  • Manufacturing a varied rhythm: when the source media is uniform (e.g. all 16:9), assign each card a deterministic tile shape by a stable function of its index/slug so the shape never changes when the list is filtered or reordered. Center the real media contained on the tile's surface (never crop unless the user opts into cropping) — compute which dimension binds from the static tile-ratio vs media-ratio comparison.
  • CSS columns(
    columns-N
    ,子项使用
    break-inside-avoid mb-*
    )是默认方案:它按列从上到下填充,支持 SSR 且没有布局偏移;更重要的是,它能让所有卡片保持在同一个 DOM 父节点中,从而通过稳定的 React
    key
    在筛选和排序后保留卡片的挂载状态。除非设计确实需要从左到右的逐行顺序,否则应优先使用。
  • JS 平衡列(测量高度后放入最短的列)可以提供逐行顺序,但需要额外的首屏测量阶段,并且当卡片在不同列的父节点间移动时会发生重新挂载。只有在逐行顺序属于硬性要求时,才应采用此方案。
  • 营造错落的节奏:当源媒体比例统一时(例如全部为 16:9),应通过索引或 slug 的稳定函数为每张卡片分配确定性的 tile 形状,确保列表经过筛选或重排后形状始终不变。将真实媒体以 contained 方式居中显示在 tile 表面(除非用户明确要求裁切,否则绝不能裁切);通过比较静态 tile 比例与媒体比例,确定哪个尺寸起约束作用。

5. Heavy-media grids

5. 重型媒体网格

A grid of 100+ live players/videos cannot mount eagerly. Reuse this repo's load-bearing pattern: lazy-mount each preview via
IntersectionObserver
(
rootMargin: "200px 0px"
), keep a placeholder before mount, and pause playback when the card scrolls offscreen so only a handful render at once. When filtering unmounts cards, their players are freed automatically; keep keys stable so survivors are not needlessly remounted.
包含 100 多个实时播放器或视频的网格不能一次性全部挂载。复用当前项目的核心模式:通过 IntersectionObserver(
rootMargin: "200px 0px"
)延迟挂载每个预览,在挂载前保留 placeholder,并在卡片滚出视口时暂停播放,使同一时间仅渲染少量内容。当筛选操作卸载卡片时,其播放器会自动释放;保持 key 稳定,避免剩余卡片被不必要地重新挂载。

6. Verify by overlay, in both themes

6. 通过叠加图验证,并检查两种主题

Being "done" means it matches — prove it:
  • Run the app and screenshot the built page at the reference's exact breakpoints.
  • Overlay the two at ~50% opacity (or place side by side) and check every measured number: bar height, baseline alignment, pill height and gaps, gutters, card radius, overlay size/inset.
  • Resize through each breakpoint boundary and confirm the column count steps as specified.
  • Toggle light/dark and confirm every new token reads correctly in both.
  • Tab through the keyboard path (pills → controls → cards) and confirm focus rings + ARIA.
  • Open the sibling pages you did not intend to change and confirm they are byte-identical — shared chrome edits leak easily.
Only call it done after the overlay matches and the siblings are untouched.
“完成”意味着完全一致——需要证明这一点:
  • 运行应用,并在参考设计的准确断点处对构建页面截图。
  • 将两张图以约 50% 的不透明度叠加,或并排放置,逐项检查所有测量值:栏高、基线对齐、pill 高度与间距、栏间距、卡片圆角、覆盖元素尺寸与内缩距离。
  • 调整尺寸并逐一跨越每个断点边界,确认列数按照规格变化。
  • 切换浅色与深色主题,确认每个新增 token 在两种主题中都能正确显示。
  • 使用键盘依次访问 pill、控件和卡片,确认焦点环与 ARIA 行为正确。
  • 打开并非计划修改的兄弟页面,确认它们保持字节级一致——共享框架的改动很容易意外影响其他页面。
只有在叠加图完全匹配且兄弟页面未受影响后,才能宣布完成。