ux-ui-maxpro
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseux-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 , DESIGN.md rules), but the
method is stack-agnostic.
app/globals.css一套用于在当前项目的设计系统上精确复刻参考 UI 的实践手册,避免偏离为近似实现或编造内容。针对 snapcn 技术栈(Next.js App Router + Tailwind v4 + 基于 token 的 、DESIGN.md 规则)进行了优化,但方法本身与具体技术栈无关。
app/globals.cssThe 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 —
/ on icon-only links, on filter pills.
aria-labeltitlearia-pressed为每个参考元素确定其在当前项目中的真实数据对应项。绝不编造看似真实的数据(本项目的长期规则):例如,“63 online”指标应替换为真实推导出的数量(如组件总数);“Last updated 11h ago”应替换为 API 返回的真实时间戳,并提供优雅的回退方案,而不是硬编码字符串。如果不存在真实的数据对应项,就删除该元素,而不是伪造。保留参考设计本身可能缺少的可访问性支持——仅含图标的链接应提供 /,筛选 pill 应提供 。
aria-labeltitlearia-pressed3. 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 (light) and
:root(dark) — a dark peer is mandatory, even when the reference is light-only; tune the dark value by eye..dark - Register it under (
@theme inline) so a--color-<name>: var(--<name>)/bg-<name>utility exists.text-<name> - 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 灰色),都应在 中定义为一个新的 semantic token:
app/globals.css- 在 (浅色主题)和
:root(深色主题)中都加入原始值——即使参考设计仅提供浅色主题,也必须配置对应的深色版本;深色值可通过视觉判断进行调整。.dark - 在 中注册(
@theme inline),从而生成对应的--color-<name>: var(--<name>)/bg-<name>utility。text-<name> - 如果精确还原设计需要偏离 DESIGN.md 中已有规则(胶囊按钮、模糊 chip、无边框卡片等),应在 DESIGN.md 中添加一段简短修订,避免后续贡献者将其“修复”回原样。
4. Masonry recipes
4. Masonry 布局方案
- CSS columns (, children
columns-N) 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 Reactbreak-inside-avoid mb-*preserves a card's mounted state across filtering and sorting. Use it unless the design truly needs left-to-right row order.key - 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)是默认方案:它按列从上到下填充,支持 SSR 且没有布局偏移;更重要的是,它能让所有卡片保持在同一个 DOM 父节点中,从而通过稳定的 Reactbreak-inside-avoid mb-*在筛选和排序后保留卡片的挂载状态。除非设计确实需要从左到右的逐行顺序,否则应优先使用。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 (), 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.
IntersectionObserverrootMargin: "200px 0px"包含 100 多个实时播放器或视频的网格不能一次性全部挂载。复用当前项目的核心模式:通过 IntersectionObserver()延迟挂载每个预览,在挂载前保留 placeholder,并在卡片滚出视口时暂停播放,使同一时间仅渲染少量内容。当筛选操作卸载卡片时,其播放器会自动释放;保持 key 稳定,避免剩余卡片被不必要地重新挂载。
rootMargin: "200px 0px"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 行为正确。
- 打开并非计划修改的兄弟页面,确认它们保持字节级一致——共享框架的改动很容易意外影响其他页面。
只有在叠加图完全匹配且兄弟页面未受影响后,才能宣布完成。