关于 visual-redesign
用于改进现有 React、HTML 和 CSS 界面的视觉设计,同时保留应用行为。它先审查代码,再以 CSS 为主进行调整,避免改动 JavaScript 逻辑或重组可用的 JSX。
主要功能
- 01审查文件并区分逻辑与表现
- 02保留状态、处理函数和 API 调用
- 03定位具体的视觉设计问题
- 04提取现有设计令牌与样式
- 05避免重组可用的 JSX
适用场景
- 01更新现有 React 界面的视觉效果
- 02不改业务逻辑地改进页面
- 03设计调整前审查代码库
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yu-369/ vibecurb --skill visual-redesign安装后在 Claude Code 中输入/visual-redesign确认已加载。
详细介绍
视觉重设计:精细美学升级
当用户提供现有的 React/HTML/CSS 代码,并要求优化外观、升级设计、提升美感、打造高级质感、营造 Awwwards 风格,或以其他方式表达“功能没问题,但样子糟透了”时,就会触发此技能。用户的代码是可正常运行的——其中有可用的状态、API 调用、事件处理程序和业务逻辑。你的工作是在不破坏任何现有功能的前提下升级视觉层。你是外科医生,不是拆除队。精准下刀,让患者活着离开。
神圣准则——请先阅读
JavaScript 逻辑神圣不可侵犯。绝不触碰。
这是本技能中每一行内容都必须遵守、不可妥协、不可违背的规则:
SACRED (never modify):
├── useState / useReducer declarations and updates
├── useEffect / useCallback / useMemo bodies
├── API calls (fetch, axios, SWR, React Query)
├── Event handler LOGIC (what happens onClick, not how the button looks)
├── Conditional rendering logic (ternaries, && chains, if blocks)
├── Router/navigation logic
├── Form validation logic
├── Context providers and consumers
├── Custom hook implementations
├── Data transformations (map, filter, reduce on data)
├── Error handling (try/catch, error boundaries)
├── Prop drilling / prop interfaces
└── Third-party library integration logic
SLOP (upgrade aggressively):
├── className strings and CSS classes
├── Inline styles (style={{...}})
├── CSS/SCSS files
├── Tailwind utility classes
├── Bootstrap classes
├── Color values (hex, rgb, hsl)
├── Font families and sizes
├── Spacing values (padding, margin, gap)
├── Border-radius values
├── Shadow values
├── Transition/animation declarations
├── z-index values
├── Layout structure (flex/grid configuration)
└── Wrapper div nesting (for layout, NOT for conditional logic)
⚠ 偏离警告: AI“搞坏应用”的头号方式,是为了让 JSX 看起来更整洁而重构它,却意外移除了条件包装器、移动了 key 属性、更改了 ref 赋值,或重新排列了依赖 DOM 位置的子元素。如果现有结构可用,绝不要出于美观原因重构 JSX。在现有结构上添加 CSS。不要为了迎合自己的 CSS 偏好而改动结构。
→ 灰色地带
有些元素兼具逻辑和样式。处理时务必格外谨慎:
| 元素 | 神圣还是杂项? | 规则 |
|---|---|---|
className={isActive ? 'active' : ''} | 两者皆是——逻辑神圣不可侵犯,类名则可调整 | 保留三元表达式。只更改类名值:className={isActive ? 'nav-link--active' : 'nav-link'} |
style={{ display: isOpen ? 'block' : 'none' }} | 神圣——这是条件可见性逻辑 | 不要替换为 CSS 类。这段内联样式由状态驱动。保留原样,并在此基础上添加样式 |
{items.map((item) => <Card key={item.id} ... />)} | 神圣——map、key 和数据流都属于逻辑 | 设置 Card 组件内部的样式。不要更改 map 结构或 key 赋值 |
ref={containerRef} | 神圣——ref 赋值会驱动 JavaScript 行为 | 绝不要移除、移动或重命名 ref |
aria-* 属性 | 神圣——无障碍属性具有实际功能 | 绝不要移除。可以补充缺失的属性 |
data-* 属性 | 可能神圣——常用于 JavaScript/测试 | 除非确认未使用,否则绝不要移除 |
id 属性 | 可能神圣——可能被 JavaScript 选择器使用 | 除非确认未使用,否则绝不要更改 |
onClick={() => setOpen(!open)} | 神圣——处理程序属于逻辑 | 设置元素样式。不要改动处理程序 |
包装条件内容的 <div> | 神圣——此 div 可能出于渲染原因而存在 | 除非确认纯粹用于呈现,否则不要移除“多余”的包装 div |
灰色地带的黄金法则: 如果不确定某项内容属于逻辑还是样式,就不要动它,在旁边添加样式即可。稍欠优雅但不会破坏应用的 CSS 方案,远胜于引入 bug 的优雅重构。
流程
┌──────────────────────────────────────────────────────────────────────────┐
│ │
│ CODE IN ──→ Phase 1: Audit │
│ (read every file, classify Sacred vs Slop, │
│ identify the aesthetic crimes) │
│ │
│ ──→ Phase 2: Extraction │
│ (extract current design decisions across │
│ 7 layers, build the Slop Sheet) │
│ │
│ ──→ Phase 3: Prescription │
│ (define target aesthetic, map every │
│ slop item to its gold replacement) │
│ │
│ ──→ Phase 4: Surgery │
│ (execute replacements layer by layer: │
│ tokens → typography → color → spacing → │
│ components → atmosphere → motion) │
│ │
│ ──→ Phase 5: Post-Op │
│ (verify nothing broke, visual diff, │
│ responsive check, motion check) │
│ │
│ Each phase has a ✓ Quality Gate. Failing a gate blocks the next. │
│ │
└──────────────────────────────────────────────────────────────────────────┘
阶段 1:审查
在更改任何字符之前,先阅读整个代码库。了解现有内容,并对所有内容进行分类。
→ 阅读每个文件并填写审计表
| 文件 | 类型 | 关键内容 | 不良设计元素 | 风险级别 |
|---|---|---|---|---|
App.tsx | 根组件 | 路由设置、提供程序、全局状态 | 根 className、全局包装器样式 | 低 |
Header.tsx | UI 组件 | 导航状态(移动端菜单切换)、身份验证状态 | 所有 className 字符串、内联样式、布局 | 中 |
Hero.tsx | UI 组件 | CTA 点击处理程序、任何分析调用 | 字体排版、颜色、间距、图片、布局 | 低 |
Features.tsx | UI 组件 | 数据数组、映射迭代 | 卡片样式、网格布局、图标 | 低 |
Dashboard.tsx | 复杂组件 | 所有状态、效果、API 调用、数据转换 | 表格样式、卡片样式、图表包装器样式 | 高 |
Form.tsx | 复杂组件 | 验证、提交、错误处理、引用 | 输入框样式、按钮样式、布局 | 高 |
index.css | 样式表 | 无(但可能包含关键重置样式) | 所有内容 | 低 |
风险级别:
- 低 - 主要是展示层内容。可以放心地大幅调整样式。
- 中 - 逻辑与展示层内容混杂。谨慎调整样式,并在之后测试。
- 高 - 逻辑与展示层内容深度交织。只修改 CSS 类和样式。每次修改都要测试。
→ 找出审美问题
逐一查看 UI,记录所有视觉问题。要具体——“看起来很糟”并不是诊断。
| 问题 | 位置 | 严重程度 | 示例 |
|---|---|---|---|
| 通用字体栈 | 全局/正文 | 严重 | font-family: Arial, sans-serif 或浏览器默认字体 |
| 默认阴影 | 卡片、按钮 | 重大 | box-shadow: 0 2px 4px rgba(0,0,0,0.1)——Bootstrap 默认值 |
| 纯白背景上的纯黑文字 | 所有位置 | 重大 | color: #000; background: #fff——毫无暖意,反差刺眼 |
| 间距不一致 | 区块之间 | 重大 | 一个区块的 margin-top: 20px,下一个区块的 margin-top: 50px |
| Bootstrap 蓝色强调色 | 按钮、链接 | 严重 | #0d6efd——最显眼的“我没做设计”信号 |
| 通用圆角 | 卡片、按钮 | 中等 | 所有地方都是 border-radius: 4px——没有统一的圆角风格 |
| 没有入场动画 | 页面加载 | 中等 | 元素突然出现——静态、缺乏生气 |
| 没有悬停状态 | 按钮、卡片、链接 | 重大 | 交互元素没有任何反馈 |
| 内边距过窄 | 卡片、区块 | 重大 | 卡片只用 padding: 16px,而它需要 32px 才有舒展感 |
| 缺少氛围感 | 背景 | 中等 | 单调的 background: white 或 background: #f5f5f5——没有层次感 |
| 圆角风格混杂 | 各组件之间 | 中等 | 按钮是 rounded-full,卡片却是 rounded-sm,毫无逻辑 |
| 标题沿用正文字体 | H1-H3 | 严重 | 用 Inter/Roboto/Arial,字号为 24px,却冒充展示标题字体 |
| 缺少视觉层级 | 内容区块 | 重大 | 所有内容的字号、字重和颜色都相同 |
| 没有留白体系 | 布局 | 重大 | 随意使用 mt-4、mt-6、mt-3,毫无规律 |
→ 输出审计摘要
用 3-5 行说明你的发现:
"审计摘要:包含 8 个组件的 React SPA。路由、身份验证状态和 3 个 API 调用属于关键内容——都位于 Dashboard.tsx 和 Header.tsx 中。整个视觉层都使用 Bootstrap 5 默认样式:#0d6efd 蓝色强调色、默认阴影、Arial 字体栈、所有元素均为 4px 圆角、没有悬停状态、没有入场动画、卡片内边距只有 16px 且过于局促、纯黑文字配纯白背景。没有设计系统——各组件的间距和颜色都随意设定。预计问题数量:14 个严重问题、23 个重大问题。整体风险:中;Dashboard.tsx 风险高(复杂状态 + 表格渲染)。"
✓ 质量关卡:审计
进入第 2 阶段前,请确认:
- 已阅读并在审计表中对每个文件进行了分类
- 已识别每个文件中的关键元素
- 已为每个文件分配风险等级
- 已列出审美问题,并附上具体示例
- 已撰写审计摘要
- 已了解哪些文件属于高风险(包含大量 JS 逻辑)
- 尚未修改任何代码
第 2 阶段:提取
提取当前在 7 个层面上的设计决策。这将生成「之前」的快照——Slop Sheet。
→ 第 1 层:设计令牌(颜色、字体、间距比例)
| 令牌 | 当前值(Slop) | 来源 |
|---|---|---|
| 主背景 | #ffffff 或 white | index.css / 内联 |
| 次背景 | #f5f5f5 或 #f8f9fa | Bootstrap 灰色-100 |
| 主文本 | #000000 或 #212529 | Bootstrap 默认值 |
| 次文本 | #6c757d | Bootstrap 灰色-600 |
| 强调色/主操作 | #0d6efd | Bootstrap 主色 |
| 强调色悬停状态 | #0b5ed7 | Bootstrap 主色悬停状态 |
| 危险/错误 | #dc3545 | Bootstrap 危险色 |
| 成功 | #198754 | Bootstrap 成功色 |
| 边框颜色 | #dee2e6 | Bootstrap 灰色-300 |
| 展示字体 | system-ui 或 Arial | 浏览器默认值 |
| 正文字体 | 与展示字体相同 | 未作区分 |
| 等宽字体 | 无 | 缺失 |
| 间距基准 | 无体系(临时决定) | 随机的 px 值 |
| 默认圆角 | 4px 或 0.375rem | Bootstrap 默认值 |
→ 第 2 层:排版
| 元素 | 当前规范(Slop) |
|---|---|
| H1 | font-size: 2rem; font-weight: bold; font-family: inherit |
| H2 | font-size: 1.5rem; font-weight: bold |
| H3 | font-size: 1.25rem; font-weight: bold |
| 正文 | font-size: 1rem; line-height: 1.5 |
| 小字/说明文字 | font-size: 0.875rem |
| 按钮文本 | font-size: 1rem; font-weight: 400 |
| 字距 | 未设置(浏览器默认值:normal) |
| 标题行高 | 1.2(Bootstrap 默认值——用于展示字体时过于宽松) |
| 文本换行 | 标题未设置 text-wrap: balance |
| 正文最大宽度 | 无(文本延伸至边缘) |
→ 第 3 层:间距
| 测量项 | 当前值(Slop) |
|---|---|
| 区块内边距 | 不一致:py-3、py-4、py-5、随机的 px 值 |
| 卡片内边距 | p-3(12px)或 p-4(16px)——过于局促 |
| 网格间距 | gap-3(12px)或 gap-4(16px)——过于紧凑 |
| 标题 → 正文间距 | mb-2 或 mb-3——过于紧凑 |
| 正文 → CTA 间距 | mt-3——过于紧凑 |
| 导航栏高度 | py-2(过短且局促)或 Bootstrap 导航栏默认高度 |
| 组件间距 | 无一致体系——每个组件各不相同 |
→ 第 4 层:颜色用法
| 用途 | 当前值(Slop) | 问题 |
|---|---|---|
| 背景 | 纯 #fff 或 #f8f9fa | 单调、冰冷、缺乏暖意 |
| 文本 | 纯 #000 或 #212529 | 生硬,缺乏精致感 |
| 强调色 | Bootstrap #0d6efd | 一眼就让人觉得「未经设计」 |
| 边框 | #dee2e6 | 普通灰色 |
| 阴影 | rgba(0,0,0,0.1) | 默认、缺乏区分度 |
| 悬停状态 | 稍深的色调 | 缺乏个性 |
| 激活状态 | 更深的色调 | 机械,不够自然 |
| 错误 | Bootstrap #dc3545 | 普通红色 |
→ 第 5 层:组件
| 组件 | 当前状态(Slop) |
|---|---|
| 按钮 | Bootstrap .btn.btn-primary——#0d6efd、4px 圆角、通用内边距,没有悬停动态效果 |
| 卡片 | .card——1px solid #dee2e6、4px 圆角、默认阴影或无阴影、内边距局促 |
| 输入框 | Bootstrap 表单控件——#dee2e6 边框,没有聚焦光效,没有浮动标签 |
| 导航 | Bootstrap 导航栏——内容繁杂、局促、使用默认样式 |
| 表格 | Bootstrap .table——条纹行、行距局促、缺乏精致感 |
| 模态框 | Bootstrap 模态框——通用遮罩,没有进入动画 |
| 徽标/胶囊 | Bootstrap .badge——尺寸小、局促、主色为蓝色 |
| 下拉菜单 | Bootstrap 下拉菜单——通用阴影,没有动画 |
→ 第 6 层:氛围
| 属性 | 当前状态(粗制滥造) |
|---|---|
| 背景纹理 | 无——纯色平铺 |
| 环境光晕/渐变 | 无——完全平坦 |
| 颗粒/噪点 | 无 |
| 磨砂玻璃 | 无 |
| 深度系统 | 默认 Bootstrap 阴影或无 |
| 视觉暖度 | 零——冷淡而刻板 |
→ 第 7 层:动效
| 属性 | 当前状态(粗制滥造) |
|---|---|
| 页面进入 | 无——静态挂载,所有内容瞬间出现 |
| 滚动显现 | 无——所有内容立即可见 |
| 悬停过渡 | transition: all 0.15s ease-in-out(Bootstrap 默认值)或无 |
| 页面过渡 | 无——瞬间切换 |
| 微交互 | 无 |
| 加载状态 | 加载指示器或“正在加载……”文本 |
| 缓动曲线 | ease-in-out CSS 关键字或无 |
✓ 质量关卡:提取
进入第 3 阶段前,请确认:
- 已填写全部 7 个提取层,并使用代码库中的实际值
- 值足够具体(精确的十六进制颜色代码、精确的 rem/px 值),而非模糊描述
- 你能看出当前状态与目标品质之间的差距
- 尚未修改任何代码
第 3 阶段:处方
针对第 2 阶段提取的每项粗制滥造之处,开出优质替代方案。这是转型映射图——手术计划。
→ 令牌处方
/* PRESCRIPTION: Design tokens - Slop → Gold
WHY: Tokens are the foundation. Changing these first
means every component that references them upgrades
automatically. This is the highest-leverage change. */
:root {
/* ── Colors ────────────────────────────────────── */
/* Background: #ffffff → warm off-white
WHY: Pure white is harsh and clinical. Off-white
with a warm undertone feels premium and intentional.
The difference is subtle but the eye registers it
as "designed" vs "default." */
--color-bg: #FAFAF9; /* was: #ffffff */
--color-surface: #FFFFFF; /* was: #f8f9fa - cards sit ON the bg */
--color-surface-2: #F5F4F2; /* was: none - for alternating sections */
/* Text: #000000 → warm near-black
WHY: Pure black on warm off-white creates a jarring
temperature clash. Near-black (#1a1a1a) matches the
warmth of the background and reduces eye strain. */
--color-text: #1A1A1A; /* was: #000000 or #212529 */
--color-text-2: #6B7280; /* was: #6c757d - muted, for secondary */
--color-text-3: #9CA3AF; /* was: none - for captions, placeholders */
/* Accent: #0d6efd → considered, non-Bootstrap hue
WHY: Bootstrap blue is the single loudest "I didn't
design this" signal on the web. ANY other considered
hue immediately elevates the design. Pick based on
brand context from the user's brief. */
--color-accent: #____; /* MUST be chosen based on brand context */
--color-accent-hover: #____; /* 10-15% darker or more saturated */
/* Borders and shadows: warm, not gray
WHY: Cool gray borders (#dee2e6) clash with warm
backgrounds. Use warm gray or very low-opacity black. */
--color-border: rgba(0, 0, 0, 0.08); /* was: #dee2e6 */
--color-shadow: rgba(0, 0, 0, 0.04); /* was: rgba(0,0,0,0.1) */
/* ── Typography ────────────────────────────────── */
/* Display font: Arial → a real display font
WHY: Arial/system-ui as a heading font is the
typographic equivalent of serving fine dining
on paper plates. Display fonts have optical
refinements for large sizes that body fonts lack. */
--font-display: 'Outfit', system-ui, sans-serif; /* was: Arial/system-ui */
--font-body: 'Inter', system-ui, sans-serif; /* was: same as display */
--font-mono: 'JetBrains Mono', monospace; /* was: none */
/* ── Spacing ───────────────────────────────────── */
/* Section spacing: random values → consistent scale
WHY: A spacing system creates rhythm. Random spacing
creates visual noise - the eye detects inconsistency
even when the brain can't articulate it. */
--space-section: clamp(5rem, 10vw, 8rem); /* was: random py values */
--space-element: 1.5rem; /* was: 0.75rem–1rem */
--space-component: 2rem; /* was: 1rem–1.5rem */
/* ── Radius ────────────────────────────────────── */
/* Radius: 4px everywhere → considered radius language
WHY: A design system commits to a radius language.
Pick ONE and apply consistently. */
--radius-sm: 8px; /* was: 4px (Bootstrap) - inputs, badges */
--radius-md: 12px; /* was: 4px - cards, containers */
--radius-lg: 16px; /* was: 4px - modals, large cards */
--radius-full: 9999px; /* was: 50% - pills, avatars */
/* ── Easing ────────────────────────────────────── */
/* Easing: ease-in-out → custom curves
WHY: CSS keyword easings are the typographic
equivalent of Comic Sans. Zero character,
zero intentionality. Custom curves give
every animation a deliberate feel. */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-snap: cubic-bezier(0.22, 1, 0.36, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
}
→ 字体排印处方
/* PRESCRIPTION: Typography scale - Slop → Gold
WHY: The heading is the first thing the eye hits.
A display font with tight tracking and compressed
line-height immediately signals "designed." The body
font stays readable with comfortable line-height. */
/* Import the fonts - add to the top of your CSS or <head> */
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
h1, h2, h3, h4 {
font-family: var(--font-display);
letter-spacing: -0.03em; /* was: normal (too loose at display size) */
line-height: 1.1; /* was: 1.2 (Bootstrap) - tighten */
text-wrap: balance; /* prevents ugly orphan lines */
color: var(--color-text);
}
h1 {
font-size: clamp(2.25rem, 5vw, 3.75rem); /* was: 2rem fixed */
font-weight: 700;
letter-spacing: -0.04em;
line-height: 1.05;
}
h2 {
font-size: clamp(1.75rem, 3.5vw, 2.75rem); /* was: 1.5rem fixed */
font-weight: 600;
}
h3 {
font-size: clamp(1.25rem, 2vw, 1.5rem); /* was: 1.25rem fixed */
font-weight: 600;
}
body, p, span, li {
font-family: var(--font-body);
font-size: clamp(0.9375rem, 1.1vw, 1.0625rem); /* was: 1rem fixed */
line-height: 1.65; /* was: 1.5 - slightly more generous */
color: var(--color-text);
}
/* Body text max-width - prevent wall-to-wall text */
p {
max-width: 65ch; /* was: none - text ran edge to edge */
}
/* Muted secondary text */
.text-muted, .text-secondary {
color: var(--color-text-2) !important; /* override Bootstrap's gray */
}
/* Eyebrow / label style - add where appropriate */
.eyebrow {
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--color-text-3);
}
→ 组件处方
/* PRESCRIPTION: Button - Bootstrap → Premium
WHY: The button is the most interactive element on the page.
Its hover feel communicates the entire quality level of the site.
A snappy cubic-bezier with a physical lift-and-shadow creates
a tactile impression that Bootstrap's default cannot achieve. */
/* Strip Bootstrap button defaults */
.btn {
font-family: var(--font-body);
font-size: 0.875rem;
font-weight: 600;
letter-spacing: 0.02em;
padding: 0.75rem 1.75rem; /* was: py-2 px-3 - cramped */
border-radius: var(--radius-full); /* was: 4px - now pill */
border: none;
cursor: pointer;
transition:
transform 0.4s var(--ease-snap),
box-shadow 0.4s var(--ease-snap),
background-color 0.3s var(--ease-out);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px var(--color-shadow);
}
.btn:active {
transform: translateY(0) scale(0.98);
transition-duration: 0.1s;
}
/* Primary button */
.btn-primary {
background: var(--color-accent);
color: #ffffff;
border: none;
box-shadow: 0 1px 3px var(--color-shadow);
}
.btn-primary:hover {
background: var(--color-accent-hover);
}
/* Ghost / outline button */
.btn-outline-primary,
.btn-secondary,
.btn-outline-secondary {
background: transparent;
color: var(--color-text);
border: 1px solid var(--color-border);
}
.btn-outline-primary:hover,
.btn-outline-secondary:hover {
border-color: var(--color-text);
background: rgba(0, 0, 0, 0.02);
}
/* PRESCRIPTION: Card - Bootstrap → Premium */
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md); /* was: 4px */
padding: var(--space-component); /* was: 1rem - cramped */
box-shadow: none; /* was: default Bootstrap shadow */
transition:
transform 0.4s var(--ease-snap),
box-shadow 0.4s var(--ease-snap),
border-color 0.3s var(--ease-snap);
}
.card:hover {
transform: translateY(-4px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.04),
0 2px 8px rgba(0, 0, 0, 0.02);
border-color: rgba(0, 0, 0, 0.12);
}
/* Reset Bootstrap card internals */
.card-body {
padding: 0; /* parent .card already has padding */
}
.card-title {
font-family: var(--font-display);
font-size: 1.125rem;
font-weight: 600;
letter-spacing: -0.01em;
margin-bottom: var(--space-element);
}
/* PRESCRIPTION: Input - Bootstrap → Premium */
.form-control,
input[type="text"],
input[type="email"],
input[type="password"],
textarea,
select {
font-family: var(--font-body);
font-size: 0.9375rem;
padding: 0.75rem 1rem; /* was: py-1.5 px-3 - cramped */
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text);
transition:
border-color 0.3s var(--ease-snap),
box-shadow 0.3s var(--ease-snap);
}
.form-control:focus,
input:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px rgba(var(--color-accent-rgb), 0.15);
}
/* PRESCRIPTION: Navigation - Bootstrap → Premium */
.navbar, nav {
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
background: rgba(250, 250, 249, 0.85); /* semi-transparent for frost */
border-bottom: 1px solid var(--color-border);
padding: 0 clamp(1.5rem, 5vw, 5rem);
height: 64px;
display: flex;
align-items: center;
}
.nav-link, .navbar-nav .nav-link {
font-family: var(--font-body);
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-2);
transition: color 0.3s var(--ease-snap);
position: relative;
}
.nav-link:hover {
color: var(--color-text);
}
/* Sliding underline on nav links */
.nav-link::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 1.5px;
background: var(--color-text);
transform: scaleX(0);
transform-origin: right;
transition: transform 0.3s var(--ease-snap);
}
.nav-link:hover::after {
transform: scaleX(1);
transform-origin: left;
}
/* PRESCRIPTION: Table - Bootstrap → Premium */
.table, table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
}
.table th, table th {
font-family: var(--font-mono);
font-size: 0.6875rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-text-3);
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--color-border);
text-align: left;
}
.table td, table td {
font-size: 0.9375rem;
padding: 1rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
color: var(--color-text);
}
.table tbody tr:hover, table tbody tr:hover {
background: rgba(0, 0, 0, 0.015);
}
/* Kill Bootstrap zebra striping - it looks cheap */
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: transparent;
}
→ 氛围处方
/* PRESCRIPTION: Atmosphere - Flat → Alive
WHY: Flat backgrounds feel like nothing. A subtle
radial gradient, grain texture, or warm tint gives
the background depth without adding visual elements. */
/* Subtle warm radial on the body */
body {
background:
radial-gradient(ellipse at 30% 0%, rgba(250, 235, 215, 0.2) 0%, transparent 50%),
var(--color-bg);
}
/* Noise grain overlay - felt, not seen */
body::after {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
opacity: 0.025;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
}
/* Section dividers - subtle border instead of hard lines */
section + section {
border-top: 1px solid var(--color-border);
}
/* Alternating section backgrounds for rhythm */
section:nth-child(even) {
background-color: var(--color-surface-2);
}
→ 动效处方
/* PRESCRIPTION: Motion - Static → Alive
WHY: Every element that appears without animation
feels like the page is broken. A staggered fade-up
with deblur signals that the page loaded intentionally. */
/* Entry animation keyframe */
@keyframes enter-up {
from {
opacity: 0;
transform: translateY(24px);
filter: blur(6px);
}
to {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
}
/* Apply to major elements - use inline style for stagger:
<h1 class="enter-up" style="--stagger: 0ms">
<p class="enter-up" style="--stagger: 120ms">
<button class="enter-up" style="--stagger: 240ms"> */
.enter-up {
animation: enter-up 0.7s var(--ease-out) both;
animation-delay: var(--stagger, 0ms);
}
/* Scroll reveal - elements below the fold */
[data-reveal] {
opacity: 0;
transform: translateY(30px);
transition:
opacity 0.8s var(--ease-out),
transform 0.8s var(--ease-out);
}
[data-reveal].is-visible {
opacity: 1;
transform: translateY(0);
}
/* Universal interactive transition - replaces Bootstrap's
transition: all 0.15s ease-in-out on EVERY interactive element */
a, button, [role="button"],
input, select, textarea,
.card, .nav-link, .badge {
transition: all 0.3s var(--ease-snap);
}
/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
.enter-up {
animation: none;
opacity: 1;
transform: none;
filter: none;
}
[data-reveal] {
opacity: 1;
transform: none;
transition: none;
}
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
→ 滚动显现 JavaScript(非破坏性)
/* PRESCRIPTION: Scroll reveal - add as a separate script
WHY: This script observes [data-reveal] elements and
adds .is-visible when they enter the viewport. It does
NOT modify any existing JS - it's a new, separate file
that runs independently.
Add to the end of the page or import in main entry. */
class ScrollReveal {
constructor() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
this.observer.unobserve(entry.target);
}
});
},
{ threshold: 0.15, rootMargin: '-50px 0px' }
);
document.querySelectorAll('[data-reveal]').forEach((el) => {
this.observer.observe(el);
});
}
}
// Initialize after DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => new ScrollReveal());
} else {
new ScrollReveal();
}
✓ 质量关卡:处方
进入第 4 阶段前,请确认:
- 已为第 2 阶段中的每项粗制滥造之处开出优质替代方案
- 令牌处方内部一致(暖色背景搭配暖色文字,而非混合不同色温)
- 根据用户的品牌背景选择强调色(而非另一个通用蓝色)
- 字体排印处方为标题使用展示字体,为正文使用正文字体
- 组件处方保持相同的 DOM 结构(仅更改类名)
- 氛围增强效果应当微妙(颗粒不透明度 < 0.05,渐变不透明度 < 0.3)
- 动效增强应当非破坏性(新增 CSS 类和脚本,不修改现有 JS)
- 强调色
#____占位符已填入实际的十六进制颜色值
第 4 阶段:手术
执行处方。这是手术室。请严格按照下方顺序操作——每一层都建立在前一层之上。
→ 手术顺序
Layer 1: Tokens (CSS custom properties - the foundation)
Layer 2: Typography (font imports + heading/body styles)
Layer 3: Color (replace all Bootstrap/generic color values)
Layer 4: Spacing (padding, margins, gaps - breathing room)
Layer 5: Components (buttons, cards, inputs, nav, tables)
Layer 6: Atmosphere (grain, glow, section alternation)
Layer 7: Motion (entry animations, hover states, scroll reveals)
→ 手术规则
| 规则 | 原因 |
|---|---|
| 每次只改一层 | 如果同时更改令牌、字体排印和组件,一旦出错,就无法确定原因 |
| 每层完成后都要测试 | 运行应用。它还能正常工作吗?所有路由都能加载吗?表单能提交吗?API 调用能返回结果吗?如果可以,再继续下一层 |
| 使用 CSS 覆盖,而非替换 | 添加新的样式表(例如 gold.css)来覆盖现有样式。确认覆盖生效前,不要删除现有 CSS 文件 |
| 谨慎更改 className | 如果要用自定义类替换 Bootstrap 类,请在移除前搜索整个代码库中的每个类。用于 JS 逻辑中的类(document.querySelector('.btn-primary'))不可更改 |
| 绝不重写 JSX 结构 | 可以添加或更改 className 和 style 属性,但不得调整子元素顺序、移除包装 div、更改组件层级,或修改非纯视觉属性 |
| 优先新增文件,而非修改文件 | 优先创建 gold.css,并在现有样式表之后导入(以提高覆盖优先级),而不是直接编辑现有样式表。这样可以轻松回滚 |
→ 覆盖策略
最安全的做法是新建一个样式表,并将其加载在所有现有样式表之后:
/* gold.css - loaded LAST in the cascade
WHY: By loading after Bootstrap/existing CSS, our rules
override the defaults without deleting any existing code.
If something breaks, the user can remove this one import
to revert entirely. This is the safest surgery method.
Import in main entry file:
import './gold.css' // AFTER all other CSS imports */
此文件包含第 3 阶段的所有规范——设计令牌、排版、组件、氛围和动效——可以作为一个整体添加或移除。
⚠ 漂移警告: 很容易想要通过删除 Bootstrap 导入或移除旧样式表来“清理”现有 CSS。在用户确认金色覆盖样式正常工作之前,切勿这样做。旧 CSS 是安全网。只有确认应用稳定后,才能移除它。
→ 高风险文件手术(仪表板、表单、复杂组件)
对于审计表中标记为 高 风险的文件:
- 先完整阅读文件——了解每个状态变量、effect 和处理函数
- 映射每个 className 和 style 属性——记录哪些属性会在 JS 逻辑中被引用
- 只更改 className 字符串值——保留属性本身,只更改其值
- 绝不触碰引用状态的内联样式——
style={{ display: isOpen ? 'block' : 'none' }}不可更改 - 更改后立即测试——运行应用,触发每次状态变化,提交每个表单,并验证每次 API 调用
/* EXAMPLE: Safe className surgery on a complex component
BEFORE (Bootstrap):
<div className={`card ${isSelected ? 'border-primary' : ''}`}>
AFTER (Gold):
<div className={`card ${isSelected ? 'card--selected' : ''}`}>
The ternary logic is IDENTICAL. Only the class name value changed.
Then in gold.css:
.card--selected {
border-color: var(--color-accent);
box-shadow: 0 0 0 2px rgba(var(--color-accent-rgb), 0.2);
}
*/
→ 添加 data-reveal 属性(非破坏性)
要添加滚动显示动画,请为现有 JSX 元素添加 data-reveal 属性。这是安全的,因为 data-* 属性不会影响 React 的渲染逻辑:
/* BEFORE: */
<section className="features">
<h2>Features</h2>
{features.map(f => <FeatureCard key={f.id} {...f} />)}
</section>
/* AFTER - added data-reveal, nothing else changed: */
<section className="features" data-reveal>
<h2>Features</h2>
{features.map(f => <FeatureCard key={f.id} {...f} />)}
</section>
/* The data-reveal attribute is inert until the ScrollReveal
script observes it. It does not interfere with React's
reconciliation, event handling, or state management. */
→ 添加入场动画类(非破坏性)
/* BEFORE: */
<h1 className="hero-heading">Build faster.</h1>
<p className="hero-subtext">The platform for modern teams.</p>
<button className="btn btn-primary" onClick={handleSignup}>Get Started</button>
/* AFTER - added enter-up class and stagger variable: */
<h1 className="hero-heading enter-up" style={{ '--stagger': '0ms' } as React.CSSProperties}>Build faster.</h1>
<p className="hero-subtext enter-up" style={{ '--stagger': '120ms' } as React.CSSProperties}>The platform for modern teams.</p>
<button className="btn btn-primary enter-up" style={{ '--stagger': '240ms' } as React.CSSProperties} onClick={handleSignup}>Get Started</button>
/* onClick handler is UNTOUCHED. Only className and style were added.
The style prop uses a CSS custom property for stagger delay.
In TypeScript, cast as React.CSSProperties to avoid type errors. */
✓ 质量关卡:手术
应用完全部 7 层后,请确认:
- 应用运行正常,没有错误(控制台干净)
- 所有路由都能正确加载
- 所有表单都能正确提交和验证
- 所有 API 调用都能返回数据并正确渲染
- 所有状态变化都能正常工作(切换、模态框、下拉菜单、选择)
- 所有事件处理函数都能正确触发(点击、提交、按键)
- 没有 ref 错误或“无法读取 undefined 的属性”错误
- 新 CSS 导入加载在现有样式表之后
- 移除 gold.css 文件即可完全还原
第 5 阶段:术后检查
确认手术成功。逐项完成所有检查。任何一项未通过都需要诊断并修正。
功能检查(完整性至上)
| 检查项 | 通过/未通过 |
|---|---|
| 所有页面/路由均能正常加载 | |
| 所有表单均能正确提交 | |
| 所有 API 调用均能返回并渲染数据 | |
| 所有状态切换均能正常工作(打开/关闭、显示/隐藏、选择/取消选择) | |
| 所有事件处理函数均能触发(onClick、onSubmit、onChange、onKeyDown) | |
| 身份验证流程正常(登录、退出、受保护的路由) | |
| 没有控制台错误 | |
| 没有 TypeScript 错误(如果项目使用 TS) | |
| 没有损坏的 ref 或 undefined 属性错误 | |
| 所有条件渲染均能正常工作(加载状态、错误状态、空状态) |
⚠ 如果任何一项功能检查未通过,请还原最近一次手术改动并进行诊断。在所有功能检查通过之前,切勿进行视觉检查。
视觉升级检查
| 检查项 | 通过/未通过 |
|---|---|
| 全站没有出现 Bootstrap 蓝色(#0d6efd) | |
| 纯白(#fff)背景上没有纯黑(#000)文字 | |
| 标题使用展示字体(而非 Arial/system-ui) | |
| 标题字间距为负值(紧凑,而非宽松) | |
| 标题行高较紧凑(< 1.15) | |
| 正文文本的最大宽度适宜(不铺满整个页面) | |
| 卡片内边距宽裕(不局促于 16px) | |
| 按钮为胶囊形,或采用指定的圆角 | |
| 按钮悬停时会上移并增强阴影 | |
| 按钮按下时有反馈 | |
| 导航栏采用磨砂玻璃效果 | |
| 配色温暖且一致(暖色调中不混入冷灰色) | |
| 阴影柔和且偏暖(而非默认 Bootstrap 样式) | |
| 同类组件的圆角保持一致 |
氛围检查
| 检查项 | 通过/失败 |
|---|---|
| 背景带有微妙的暖意(不是平淡的纯白或灰色) | |
| 存在细腻且微妙的颗粒叠层(能感受到,但看不出来) | |
| 区块交替营造出节奏感(不是所有区块都用相同背景) | |
| 不再有任何平淡、缺乏生气的区块 |
动效检查
| 检查项 | 通过/失败 |
|---|---|
| 首屏元素在页面加载时以交错的淡入、上移和清晰化效果出现 | |
| 滚动到首屏以下的区块时触发显现效果 | |
| 所有按钮都有悬停过渡效果(状态变化不是瞬间发生) | |
| 所有卡片都有悬停上浮效果 | |
| 导航链接有动态下划线 | |
| 输入框聚焦时有边框辉光过渡效果 | |
| 没有动画使用 CSS 关键字缓动(ease、ease-in、ease-out、ease-in-out) | |
遵循 prefers-reduced-motion 设置(减少动态效果时不播放动效) |
响应式检查
| 检查项 | 通过/失败 |
|---|---|
| 布局在 1440px(桌面端)下正常显示 | |
| 布局在 768px(平板)下正常显示 | |
| 布局在 375px(移动端)下正常显示 | |
| 所有视口下都没有水平溢出 | |
| 移动端触控目标至少为 44px | |
| 所有视口下,标题换行不超过 3 行 | |
| 卡片在移动端正确堆叠(单列) |
回滚检查
| 检查项 | 通过/失败 |
|---|---|
| 移除 gold.css 导入后,所有视觉改动都能干净地还原 | |
| 没有删除现有 CSS 文件(它们仍完整保留,可作备用) | |
| 没有改动 JSX 结构(仅添加 className 和 data-*) | |
| 用户可以将整个升级作为一个整体接受或拒绝 |
糟糕设计模式目录——常见模式及其改进方法
常见美学问题的速查表。找到对应模式并应用改进方法。
| 糟糕模式 | 问题所在 | 改进方法 |
|---|---|---|
font-family: Arial, Helvetica, sans-serif | 正文字体充当展示字体 | 为标题导入 Outfit/Satoshi/Cabinet Grotesk |
color: #000; background: #fff | 纯黑配纯白 | color: #1a1a1a; background: #FAFAF9 |
background: #0d6efd | Bootstrap 默认主蓝色 | 选择符合品牌风格的强调色 |
box-shadow: 0 2px 4px rgba(0,0,0,0.1) | 通用默认阴影 | 静止状态使用 box-shadow: 0 1px 3px rgba(0,0,0,0.04),悬停时加深 |
border-radius: 4px | Bootstrap 默认圆角 | 统一圆角风格:8/12/16/9999 |
border: 1px solid #dee2e6 | 冷调灰色边框 | border: 1px solid rgba(0,0,0,0.08)——温暖而微妙 |
transition: all 0.15s ease-in-out | Bootstrap 默认过渡 | transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1) |
卡片上使用 padding: 1rem | 卡片内边距过小,显得局促 | 至少使用 padding: 2rem——卡片需要留白 |
标题与正文间使用 margin-bottom: 0.5rem | 标题间距过小 | 使用 margin-bottom: 1.5rem——给标题留出舒展空间 |
卡片网格中使用 gap: 1rem | 网格间距过小 | 至少使用 gap: 1.5rem——卡片之间需要留出间隔 |
按钮没有 :hover | 按钮死板且没有响应 | translateY(-2px) + shadow expansion + custom easing |
卡片没有 :hover | 卡片静止呆板,缺乏生气 | translateY(-4px) + shadow expansion + border glow |
| 没有入场动画 | 元素立即静态显示 | 为首屏可见元素添加交错的淡入、上移和清晰化效果 |
h1 { font-size: 2rem } | 标题尺寸过小 | font-size: clamp(2.25rem, 5vw, 3.75rem) |
标题使用 line-height: 1.2 | 展示标题的行距过宽 | 使用 line-height: 1.05——紧凑、有结构感 |
| 标题没有字间距 | 字距松散,显得不专业 | 使用 letter-spacing: -0.03em 收紧字距 |
区块内边距使用 py-3 | 区块显得局促 | 使用 py-20 md:py-32——区块是章节,而不是段落 |
| 表格使用斑马纹 | 带有 2010 年代 Bootstrap 风格 | 去掉条纹,改为添加微妙的悬停行高亮 |
.badge { font-size: 0.75em } | 徽标过小且显得局促 | padding: 0.25rem 0.75rem; font-size: 0.75rem; border-radius: 9999px |
输入框没有 ::placeholder 样式 | 占位符使用默认灰色 | color: var(--color-text-3); opacity: 0.7 |
边缘情况
当代码使用 CSS-in-JS(styled-components、Emotion)时
- 将令牌规范应用为 CSS 自定义属性层(
:root变量) - 使用引用令牌的全局
createGlobalStyle覆盖 styled-component 样式 - 对于组件级覆盖,添加
gold-overrides.ts文件,其中包含 styled-component 覆盖样式 - 切勿直接修改现有 styled-component 定义,而应创建覆盖包装器
当代码使用 Tailwind CSS 时
- 在
tailwind.config.js中使用金色令牌覆盖 Tailwind 的theme(颜色、字体、圆角、阴影) - 使用
@layer utilities添加氛围和动效 - 系统地替换 Tailwind 颜色类:
text-gray-900→text-[#1a1a1a],或在配置中定义自定义颜色 - 将
shadow-sm/shadow-md替换为配置中的自定义阴影值 - 将
rounded/rounded-md替换为配置中的自定义圆角 - 对 Tailwind 来说,这是最安全的做法,因为它在配置层面更改设计系统,而不是在每个组件文件中逐一修改
当代码使用 Material UI / Chakra / Ant Design 时
- 覆盖主题提供程序配置——这些库本就支持主题自定义
- 重点关注主题对象:颜色、排版、间距、圆角、阴影
- 添加组件级
sx覆盖或styled()包装器来实现氛围和动效 - 切勿与组件库的结构对抗——应在其主题系统内进行调整
当代码是原生 HTML/CSS(无框架)时
- 将
gold.css作为最后一个样式表添加到<head>中 - 使用 CSS 优先级覆盖现有样式,而不编辑现有样式
- 如果现有样式使用了
!important,你的覆盖样式可能也需要使用!important(虽然不理想,但有必要) - 在
</body>之前添加 ScrollReveal 脚本,作为<script>引入
当用户说“只要让 hero 看起来更好”
不要升级整个网站。只对 hero 区域应用此流程:
- 检查 hero 组件
- 提取其当前的设计决策
- 为该区域指定金色替代方案
- 对该区域的 CSS 进行精细调整
- 验证其他部分均未受影响
尊重用户设定的范围。做超出要求的升级既浪费时间,也会带来风险。
核心原则
必须确保产品正常运行。 一个无法正常工作的漂亮应用,比一个能正常工作的丑陋应用更糟糕。每完成一层调整后都要测试。功能始终优先于美观。
只覆盖 CSS,绝不重写 JS。 你的工具包括
className更改、新 CSS 文件和 data 属性。不要重写组件逻辑、重构状态管理或调整 JSX 层级结构。如果视觉升级需要更改 JS 逻辑,就寻找仅使用 CSS 的替代方案。
gold.css是一个整体。 只用一个文件,在最后加载,并包含完整的视觉升级。用户只需添加(升级)或移除(还原)一次导入即可。这就是精细调整的理念:干净地开始,干净地退出。
温暖胜过中性。 所有高端设计都采用暖色调——米白背景、暖调近黑色文字、暖灰色边框。冷调中性色(纯灰、纯白、纯黑)给人冷硬、缺乏设计感的印象。见效最快的单项升级,就是调整色温。
间距是秘诀。 影响最显著的单项改动是增加间距——区块内边距、卡片内边距、标题间距、网格间距。间距局促是业余设计的标志;间距宽裕则是高端设计的标志。不确定时,就多留些空间。
展示字体能区分业余与专业。 将标题字体从 Arial/system-ui 换成合适的展示字体(Outfit、Satoshi、Cabinet Grotesk、Clash Display),是影响最大的单项改动。其他所有设计都以此为基础。