mcpField
V
visual-redesign

visual-redesign

github.com/yu-369设计与 UI99245 次安装12 周前更新
代码仓库

关于 visual-redesign

用于改进现有 React、HTML 和 CSS 界面的视觉设计,同时保留应用行为。它先审查代码,再以 CSS 为主进行调整,避免改动 JavaScript 逻辑或重组可用的 JSX。

主要功能

  1. 01审查文件并区分逻辑与表现
  2. 02保留状态、处理函数和 API 调用
  3. 03定位具体的视觉设计问题
  4. 04提取现有设计令牌与样式
  5. 05避免重组可用的 JSX

适用场景

  1. 01更新现有 React 界面的视觉效果
  2. 02不改业务逻辑地改进页面
  3. 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.tsxUI 组件导航状态(移动端菜单切换)、身份验证状态所有 className 字符串、内联样式、布局中
Hero.tsxUI 组件CTA 点击处理程序、任何分析调用字体排版、颜色、间距、图片、布局低
Features.tsxUI 组件数据数组、映射迭代卡片样式、网格布局、图标低
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 或 whiteindex.css / 内联
次背景#f5f5f5 或 #f8f9faBootstrap 灰色-100
主文本#000000 或 #212529Bootstrap 默认值
次文本#6c757dBootstrap 灰色-600
强调色/主操作#0d6efdBootstrap 主色
强调色悬停状态#0b5ed7Bootstrap 主色悬停状态
危险/错误#dc3545Bootstrap 危险色
成功#198754Bootstrap 成功色
边框颜色#dee2e6Bootstrap 灰色-300
展示字体system-ui 或 Arial浏览器默认值
正文字体与展示字体相同未作区分
等宽字体无缺失
间距基准无体系(临时决定)随机的 px 值
默认圆角4px 或 0.375remBootstrap 默认值
→ 第 2 层:排版
元素当前规范(Slop)
H1font-size: 2rem; font-weight: bold; font-family: inherit
H2font-size: 1.5rem; font-weight: bold
H3font-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 是安全网。只有确认应用稳定后,才能移除它。

→ 高风险文件手术(仪表板、表单、复杂组件)

对于审计表中标记为 高 风险的文件:

  1. 先完整阅读文件——了解每个状态变量、effect 和处理函数
  2. 映射每个 className 和 style 属性——记录哪些属性会在 JS 逻辑中被引用
  3. 只更改 className 字符串值——保留属性本身,只更改其值
  4. 绝不触碰引用状态的内联样式——style={{ display: isOpen ? 'block' : 'none' }} 不可更改
  5. 更改后立即测试——运行应用,触发每次状态变化,提交每个表单,并验证每次 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: #0d6efdBootstrap 默认主蓝色选择符合品牌风格的强调色
box-shadow: 0 2px 4px rgba(0,0,0,0.1)通用默认阴影静止状态使用 box-shadow: 0 1px 3px rgba(0,0,0,0.04),悬停时加深
border-radius: 4pxBootstrap 默认圆角统一圆角风格:8/12/16/9999
border: 1px solid #dee2e6冷调灰色边框border: 1px solid rgba(0,0,0,0.08)——温暖而微妙
transition: all 0.15s ease-in-outBootstrap 默认过渡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)时
  1. 将令牌规范应用为 CSS 自定义属性层(:root 变量)
  2. 使用引用令牌的全局 createGlobalStyle 覆盖 styled-component 样式
  3. 对于组件级覆盖,添加 gold-overrides.ts 文件,其中包含 styled-component 覆盖样式
  4. 切勿直接修改现有 styled-component 定义,而应创建覆盖包装器
当代码使用 Tailwind CSS 时
  1. 在 tailwind.config.js 中使用金色令牌覆盖 Tailwind 的 theme(颜色、字体、圆角、阴影)
  2. 使用 @layer utilities 添加氛围和动效
  3. 系统地替换 Tailwind 颜色类:text-gray-900 → text-[#1a1a1a],或在配置中定义自定义颜色
  4. 将 shadow-sm / shadow-md 替换为配置中的自定义阴影值
  5. 将 rounded / rounded-md 替换为配置中的自定义圆角
  6. 对 Tailwind 来说,这是最安全的做法,因为它在配置层面更改设计系统,而不是在每个组件文件中逐一修改
当代码使用 Material UI / Chakra / Ant Design 时
  1. 覆盖主题提供程序配置——这些库本就支持主题自定义
  2. 重点关注主题对象:颜色、排版、间距、圆角、阴影
  3. 添加组件级 sx 覆盖或 styled() 包装器来实现氛围和动效
  4. 切勿与组件库的结构对抗——应在其主题系统内进行调整
当代码是原生 HTML/CSS(无框架)时
  1. 将 gold.css 作为最后一个样式表添加到 <head> 中
  2. 使用 CSS 优先级覆盖现有样式,而不编辑现有样式
  3. 如果现有样式使用了 !important,你的覆盖样式可能也需要使用 !important(虽然不理想,但有必要)
  4. 在 </body> 之前添加 ScrollReveal 脚本,作为 <script> 引入
当用户说“只要让 hero 看起来更好”

不要升级整个网站。只对 hero 区域应用此流程:

  1. 检查 hero 组件
  2. 提取其当前的设计决策
  3. 为该区域指定金色替代方案
  4. 对该区域的 CSS 进行精细调整
  5. 验证其他部分均未受影响

尊重用户设定的范围。做超出要求的升级既浪费时间,也会带来风险。


核心原则

必须确保产品正常运行。 一个无法正常工作的漂亮应用,比一个能正常工作的丑陋应用更糟糕。每完成一层调整后都要测试。功能始终优先于美观。

只覆盖 CSS,绝不重写 JS。 你的工具包括 className 更改、新 CSS 文件和 data 属性。不要重写组件逻辑、重构状态管理或调整 JSX 层级结构。如果视觉升级需要更改 JS 逻辑,就寻找仅使用 CSS 的替代方案。

gold.css 是一个整体。 只用一个文件,在最后加载,并包含完整的视觉升级。用户只需添加(升级)或移除(还原)一次导入即可。这就是精细调整的理念:干净地开始,干净地退出。

温暖胜过中性。 所有高端设计都采用暖色调——米白背景、暖调近黑色文字、暖灰色边框。冷调中性色(纯灰、纯白、纯黑)给人冷硬、缺乏设计感的印象。见效最快的单项升级,就是调整色温。

间距是秘诀。 影响最显著的单项改动是增加间距——区块内边距、卡片内边距、标题间距、网格间距。间距局促是业余设计的标志;间距宽裕则是高端设计的标志。不确定时,就多留些空间。

展示字体能区分业余与专业。 将标题字体从 Arial/system-ui 换成合适的展示字体(Outfit、Satoshi、Cabinet Grotesk、Clash Display),是影响最大的单项改动。其他所有设计都以此为基础。

评论

支持 Markdown