feat(home): hero 信息层重构 — 声明先行,数据指标下沉为独立成果带
参考 Accenture Radical Relevance 设计语言(方向 A,用户已决策): - hero 内数据条移出,主张(副标题)前置,一屏只负责一句话声明 + 一个行动 - 新增 HeroStatsBand 成果带,滚动触发(Glance 层),承接下沉的指标 - H1 字重 900→700,hero 内 KPI 数字同步收敛,对齐「字重 9→3」克制 - 右侧数据看板保持单电压(仅品牌红),hero 内已无辅助色 验证(evidence-first): - type-check 0 errors / lint 0 errors(127 warnings 均为既有、与本改无关) - jest 13 passed(含新断言:hero 内不含 hero-stats、数据下沉成果带仍渲染) - 视觉回归 22 passed(首页浅/深快照已 update;19 个非首页页零 diff,证明改动仅影响首页) 含 docs/design/hero-redesign-proposal.md(设计提案,未改代码) P0 批次;P1(点阵 token 化 + L2 光标聚光)待续
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
# Hero 重设计方案(参考 Accenture / 埃森哲)
|
||||
|
||||
> 状态:**提案,待决策**(本文件不改动任何代码)
|
||||
> 日期:2026-08-31
|
||||
> 触发:张翔「动态粒子先不着急实现,需要配合重新设计 hero,希望能够参考埃森哲」
|
||||
> 关联:暗黑模式 Phase 2(`refactor/optimize-ui`)、`src/lib/constants/hero-themes.ts`
|
||||
|
||||
---
|
||||
|
||||
## 0. 结论先行
|
||||
|
||||
**不建议照搬埃森哲的「纯黑全出血 hero」。** 埃森哲的 hero 是 `#000000` 纯黑底 + 单一紫电压 —— 这与你已定的「hero 浅色方案,好做暗黑模式」方向正面冲突。
|
||||
|
||||
但埃森哲真正值得迁移的不是「黑」,而是**三条纪律**:
|
||||
|
||||
| 纪律 | 埃森哲 | 迁移到 Novalon 的可行性 |
|
||||
|---|---|---|
|
||||
| **单一电压色** | `#A600FF` 紫,全站只有一个品牌色 | ✅ 直接可迁(我们是 `#C41E3A` 朱砂红) |
|
||||
| **单一签名符号** | `>` 大于号,出现在 logo 旁 / CTA 尾 / 链接末端 | ✅ **已经对齐**(`CTAButton` 已 pill + `ArrowRight`) |
|
||||
| **Glance / Skim / Absorb 三层交互** | 滚动 / 悬浮 / 点击展开 | ⚠️ 我们只有 Glance 的一半(进场动画),**Skim 层缺失** |
|
||||
|
||||
**关键判断**:hero 背景层(点阵 / 粒子)在这套纪律里是**第三优先级**——它是氛围,不是信息。
|
||||
所以正确顺序是:**先重构 hero 的信息层与交互层,再上背景层**。粒子排在最后,且在「光标聚光」验证有效后再决定要不要上。
|
||||
|
||||
**另一个关键判断**:选浅色 hero 不是放弃埃森哲的深色观感,而是**让暗黑模式自动替我们实现它**。
|
||||
hero 底色走 `--color-bg-primary` token → 浅色模式是浅底 editorial 观感,暗黑模式自动变深底 → **一份代码,两种观感**,这正是埃森哲「白色反转带」机制的镜像。
|
||||
|
||||
---
|
||||
|
||||
## 1. 现状诊断
|
||||
|
||||
### 1.1 代码事实
|
||||
|
||||
| 项 | 位置 | 现状 |
|
||||
|---|---|---|
|
||||
| 底色 | `home-content-v15.tsx:160` | `bg-bg-primary` ✅ 已 token 化 |
|
||||
| 点阵 | `home-content-v15.tsx:164-169` | 静态 48px 网格,`rgba(196,30,58,0.4)` **硬编码** + `opacity-[0.03]` |
|
||||
| 标题 | 同文件 `:193` | `font-black`(900)、`text-4xl → 7xl` |
|
||||
| 数据条 | 同文件 `:199-218` | 3 个指标,**排在主张(副标题)之前** |
|
||||
| 副标题 | 同文件 `:221-230` | 排在**数据条之后** |
|
||||
| CTA | 同文件 `:233-250` | `CTAButton`(pill + 箭头)✅ |
|
||||
| 右侧视觉 | 同文件 `:254-261` | `HeroProductVisual` 静态 SVG 看板 |
|
||||
| 纹理配置 | `hero-themes.ts` | 11 主题 × 6 种 texture,**全项目零消费方(死配置)** |
|
||||
| 光标交互 | `hero-section-v2.tsx:128` | 已有 radial-gradient 跟随实现,但**首页未启用** |
|
||||
|
||||
### 1.2 实测数据(脚本计算,非估算)
|
||||
|
||||
**文字 / 图形对比度**
|
||||
|
||||
| 组合 | 实测 | WCAG 判定 |
|
||||
|---|---|---|
|
||||
| `#C41E3A` 品牌红 / 浅底 `#FFFFFF` | **5.84:1** | 正文 AA ✅ |
|
||||
| `#C41E3A` 品牌红 / 深底 `#0A0E14` | **3.31:1** | 大字 AA ✅(**正文不达标**) |
|
||||
| `#E04A68` brand-light / 深底 | **4.93:1** | 正文 AA ✅ |
|
||||
| `#CBD5E1` text-secondary / 深底 | 13.03:1 | 正文 AA ✅ |
|
||||
|
||||
> ⚠️ **修正一条此前的错误印象**:之前记录「品牌红在深底约 2.4:1」是错的,实测 **3.31:1**。
|
||||
> 结论需相应修正:品牌红在深底**可作大字号**(≥24px),但**不可作正文/小字**——小字必须切 `--color-brand-light`。
|
||||
|
||||
**点阵可见度(alpha 合成后与底的对比度)**
|
||||
|
||||
| 底色 | 当前 alpha=0.03 | 0.06 | 0.10 | 0.14 | 0.20 |
|
||||
|---|---|---|---|---|---|
|
||||
| 浅底(brand 红) | **1.05:1** ← 当前值 | 1.10 | 1.18 | **1.26** | 1.40 |
|
||||
| 深底(brand 红) | 1.01 | 1.02 | 1.05 | 1.07 | 1.12 |
|
||||
| 深底(brand-light `#E04A68`) | 1.02 | 1.05 | 1.08 | 1.13 | **1.22** |
|
||||
| 深底(中性浅灰 `#F8FAFC`) | 1.06 | **1.13** | **1.27** | 1.44 | 1.77 |
|
||||
|
||||
**结论(重要)**:
|
||||
|
||||
1. **当前 hero 点阵基本不可见**——浅底 1.05:1,深底 1.01:1。现在这个纹理是「心理安慰」,用户感知不到。要么提 alpha,要么承认无效重做。
|
||||
2. **深底用品牌红做点阵是死路**——alpha 到 0.4 才 1.38:1。因为品牌红本身在深底亮度就低。
|
||||
3. **推荐取值**:
|
||||
- 浅色模式:brand 红 @ **alpha 0.10~0.14**(1.18~1.26:1,可感知不抢戏)
|
||||
- 暗黑模式:中性浅灰 `rgb(var(--color-text-muted-rgb))` @ **alpha 0.06~0.10**(1.13~1.27:1)
|
||||
—— 比 brand-light 更高效(同 alpha 下对比度更高),且不引入第二电压色,符合埃森哲「灰度纪律」
|
||||
|
||||
---
|
||||
|
||||
## 2. 埃森哲设计语言拆解
|
||||
|
||||
来源:Accenture.com 2023-2024 改版(Sana / Sunwha Park 案例 + 公开 DESIGN.md 拆解)
|
||||
|
||||
### 2.1 视觉层
|
||||
|
||||
| 特征 | 埃森哲做法 |
|
||||
|---|---|
|
||||
| 画布 | 近黑 `#0a0a0a` 为默认;**纯黑 `#000000` 用于全出血 hero / 视频背景 / footer** |
|
||||
| 单一电压 | **Accenture Purple `#a600ff`** —— 只承载 `>` 标记、主 CTA pill、链接 hover、focus ring、accent band。无第二品牌色 |
|
||||
| 字体 | Graphik 单一字族;**字重 9 → 3**;display 用 bold(700),正文 regular(400);大标题上限 80px |
|
||||
| CTA | Pill(radius 9999)+ 尾部 `>`;主 CTA 紫色实心,次 CTA 白色描边 |
|
||||
| 灰度纪律 | 紫色之外的**一切**走灰度:近黑底、白→灰墨色、`#2e2e2e` 细 hairline |
|
||||
| 卡片 | hairline 边框 + hover 时**微抬起**,不用重阴影 |
|
||||
| 反转带 | insight / research / case-study 区块**翻转成白底深字**——刻意的「我们开始讲实质内容了」语域切换。紫色在两种底上都通过,品牌连续性不断 |
|
||||
|
||||
### 2.2 交互层(这是最值得抄的部分)
|
||||
|
||||
**Glance / Skim / Absorb**:
|
||||
|
||||
- **Glance(滚动)** —— 首屏即给完整答案,滚动时内容单流逐步展开
|
||||
- **Skim(悬浮)** —— 不点击就能获得更多(hover 展开、预览、微交互)
|
||||
- **Absorb(点击展开)** —— 需要深度时再展开详情
|
||||
|
||||
配套:主页与主要页面是**单一内容流**(single stream of content),一点一点展开,而非一屏塞满所有模块。
|
||||
|
||||
### 2.3 方法论层
|
||||
|
||||
- **Radical Relevance**(北极星):刷新熟悉的、把复杂的变简单、只给受众真正要的
|
||||
- **Create something new, without creating something new**:不新造资产,而是**提升已有资产**——从字体、色彩、被忽视的插画与渐变入手
|
||||
- **Motion choreography**:动效表达「技术与人类巧思交汇处的魔法」,为组件与交互建立**统一编排**(functional button / page transition / kinetic element 都遵循同一套)
|
||||
- 成果:字体权重 9→3、品牌色 60→24、40+ 组件库、52 个市场、8 个月上线、旧站 70% 跳出率 → 改版后显著改善
|
||||
|
||||
---
|
||||
|
||||
## 3. 埃森哲语言 → Novalon 映射
|
||||
|
||||
| 埃森哲要素 | Novalon 现状 | 差距 | 建议动作 |
|
||||
|---|---|---|---|
|
||||
| 深色画布(hero 纯黑) | 浅色 token 底 | **方向性差异**(你已选浅色) | 保留浅色;深色观感由暗黑模式自动提供(见 §5) |
|
||||
| 单一电压色 | 品牌红 + blue/teal/amber/purple/cyan 五色辅助 | 电压分散 | **hero 内只留品牌红一个色**,辅助色退出 hero 区域 |
|
||||
| `>` 签名符号 | `CTAButton` 已 pill + `ArrowRight`(hover translate-x) | ✅ **已对齐** | 保持;可考虑把箭头语义从「装饰」升格为「签名」,全站 CTA 统一 |
|
||||
| 字重 9→3 | 5 个字族(sans/display/mono/brand/serif)+ `font-black`(900) | 字重超发 | **hero 内收到 2 档**:标题 700(去掉 900)+ 正文 400。全站字族精简列为**独立低优先项** |
|
||||
| `#2e2e2e` hairline | 已有 `--color-border-primary: #EEF2F7`(浅)/ `#1E293B`(深) | ✅ 已有细线体系 | 保持 |
|
||||
| 卡片 hairline + hover 微抬 | 部分卡片用重阴影 | 中等差距 | hero 内卡片统一 hairline + hover 微抬 |
|
||||
| Glance(滚动展开) | 有 `ScrollReveal` / `StaggerReveal` + 进场动画 | 部分具备 | 见 §4.3 |
|
||||
| **Skim(悬浮预览)** | **hero 内缺失**(`hero-section-v2` 有 hover 光晕但首页未启用) | **主要缺口** | 见 §4.3 |
|
||||
| Absorb(点击展开) | 无 | 主要缺口 | 见 §4.3 |
|
||||
| 单一内容流渐进展开 | hero 一屏塞 6 类元素(badge/H1/3 数据/副标题/2 CTA/SVG) | **信息密度过高** | 见 §4.2 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Hero 重设计方案
|
||||
|
||||
### 4.1 两个方向
|
||||
|
||||
#### 方向 A:Editorial Hero(**推荐**)—— 对标埃森哲「白色反转带」
|
||||
|
||||
**核心**:把 hero 做成埃森哲的**反转带**——浅底、深字、单一电压、极克制的背景纹理。暗黑模式下自动翻转为深色观感。
|
||||
|
||||
- 底色 `--color-bg-primary`(浅)/ 自动 `#0A0E14`(深)
|
||||
- hero 内**只有品牌红一个彩色**,其余全灰度
|
||||
- 标题 700 字重(去掉 900),单一字族
|
||||
- 背景:静态点阵 + 光标聚光(无粒子,或极少)
|
||||
- CTA:pill + 箭头(已具备)
|
||||
|
||||
**优点**:与暗黑模式战略**完全同向**(零额外维护成本);符合你已定的浅色方向;埃森哲的「编辑/研究区块翻白底」本来就是他们的实质内容语域。
|
||||
**风险**:观感偏「咨询公司」而非「科技公司」,冲击力弱于纯黑 hero。
|
||||
|
||||
#### 方向 B:Full-bleed Dark Hero —— 埃森哲原味(**不推荐**)
|
||||
|
||||
- 纯黑 `#000000` 全出血 + 白字 + 品牌红单电压 + 粒子/渐变背景
|
||||
- **问题 1**:与「浅色 hero 好做暗黑模式」方向冲突,hero 需要**单独维护一套反色逻辑**,退化成两套代码
|
||||
- **问题 2**:暗黑模式下再反色一次,会出现「深色模式里 hero 反而是唯一不变的黑块」的割裂
|
||||
- **问题 3**:成本高(需为 hero 单独建 dark 兼容层 + 视觉回归基线全量更新)
|
||||
|
||||
> **折中**:方向 A 在暗黑模式下**天然获得**方向 B 的观感。这不是妥协,是白赚。
|
||||
|
||||
### 4.2 信息层重构(方向 A 落地)
|
||||
|
||||
**当前顺序**(有问题):
|
||||
|
||||
```
|
||||
badge → H1 → 3 个数据指标 → 副标题(主张)→ CTA →(右侧)静态 SVG 看板
|
||||
↑ 先看到数字,才看到主张 —— 阅读顺序反了
|
||||
```
|
||||
|
||||
**建议顺序**(埃森哲式「声明先行」):
|
||||
|
||||
```
|
||||
eyebrow(slogan badge)
|
||||
↓
|
||||
H1 主张(单一声明,不超过 2 行)
|
||||
↓
|
||||
一句支撑(≤ 2 行,回答「凭什么信你」)
|
||||
↓
|
||||
CTA(主 pill + 次 pill)
|
||||
↓
|
||||
── hero 结束 ──
|
||||
↓
|
||||
成果带 / 信任带(3 个数据指标 + 客户共创状态) ← 数据条下沉为滚动触发的 Glance 层
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
1. **数据条移出 hero**,成为紧随其后的独立带。hero 只负责「一句话说清你是谁 + 一个行动」。这直接对应埃森哲「单一内容流,逐步展开」。
|
||||
2. 右侧 `HeroProductVisual` **保留**(B2B 需要产品感),但从「静态装饰」升级为「渐进展开」(见 Absorb),且不与左栏争主视觉。
|
||||
3. **字重收敛**:H1 由 `font-black`(900) → `font-bold`(700)。埃森哲 80px 大标题就用 700,900 在中文大字号下会糊成一团。
|
||||
4. **辅助色退出 hero**:hero 内不出现 blue/teal/amber/purple/cyan,只留朱砂红。
|
||||
|
||||
### 4.3 交互层设计(Glance / Skim / Absorb 落地)
|
||||
|
||||
| 层 | 现状 | 建议 |
|
||||
|---|---|---|
|
||||
| **Glance**(滚动) | 有进场动画 + `ScrollReveal` | hero 内元素用**统一编排**(stagger 60-80ms,duration 200-300ms,`ease-ink`);数据条下沉后由滚动触发 reveal |
|
||||
| **Skim**(悬浮) | **缺失** | **光标聚光**(见 §5.2):鼠标移动时,点阵在光标周围局部提亮 —— 不点击就获得反馈,且**构成 hero 的「动态感」主来源** |
|
||||
| **Absorb**(点击/展开) | 缺失 | 右侧产品看板:进场时 SVG 逐级描边点亮(stroke-dashoffset 动画),滚动到视口中央时 KPI 数字 count-up;次 CTA「查看产品矩阵」作为深度入口 |
|
||||
|
||||
> **关键**:Skim 层的光标聚光**就是「动态粒子」的低成本替代**。
|
||||
> 它同样提供「hero 是活的」的观感,但零 canvas、零逐帧 JS、零性能风险。
|
||||
> 建议**先上聚光,观察 1-2 周效果,再决定要不要真上粒子**。
|
||||
|
||||
---
|
||||
|
||||
## 5. 背景层分层设计(点阵 / 聚光 / 粒子的定位)
|
||||
|
||||
背景层拆成 4 层,从底到顶:
|
||||
|
||||
### L0 — 底色(已具备)
|
||||
|
||||
`bg-bg-primary` → 浅色 `#FFFFFF` / 暗黑 `#0A0E14`。✅ 无需改动。
|
||||
|
||||
### L1 — 静态点阵(需改造)
|
||||
|
||||
- **现状**:`rgba(196,30,58,0.4)` 硬编码 + `opacity-[0.03]`,48px 网格 → 实测 1.05:1,**基本不可见**
|
||||
- **改造**:
|
||||
1. 颜色改走 token(这是硬编码,暗黑模式下不会反色)
|
||||
2. alpha 提到可感知区间
|
||||
3. 网格 48px → **24px**(埃森哲式细密感,且 24px 与项目 spacing 体系对齐)
|
||||
|
||||
| 模式 | 点阵取色 | alpha | 实测对比度 |
|
||||
|---|---|---|---|
|
||||
| 浅色 | `rgb(var(--color-brand-rgb))` | **0.12** | 1.22:1 |
|
||||
| 暗黑 | `rgb(var(--color-text-muted-rgb))`(中性浅灰) | **0.08** | 1.20:1 |
|
||||
|
||||
实现位置:建议抽成 `globals.css` 的 utility(已有 `.bg-texture-grid` / `.bg-texture-dots` 先例),
|
||||
颜色与 alpha 全部走 CSS 变量 → **暗黑模式零 JS 自动切换**。
|
||||
|
||||
### L2 — 光标聚光(`Skim` 层,**建议优先实现**)
|
||||
|
||||
技术方案(**无 canvas、无逐帧 JS**):
|
||||
|
||||
- 两层同 tiling 的点阵:底层 L1 静态(低 alpha),上层同网格但高 alpha
|
||||
- 上层用 `mask-image: radial-gradient(circle 280px at var(--x) var(--y), black, transparent)`
|
||||
- 鼠标移动时**只写 2 个 CSS 变量** `--x` / `--y`(`pointermove` + `requestAnimationFrame` 节流)
|
||||
- 合成与遮罩全交给 GPU
|
||||
|
||||
成本:一次 paint + 每帧 2 个变量写入。比粒子方案低一个数量级。
|
||||
|
||||
> 项目内已有先例:`hero-section-v2.tsx:128` 的 radial-gradient 跟随,
|
||||
> 以及 `design-system.ts:63` 的 `inkGlow`(`var(--mouse-x)` / `var(--mouse-y)`)。
|
||||
> **这是复用既有资产,正合埃森哲「create something new without creating something new」**。
|
||||
|
||||
### L3 — 真粒子(**暂缓**,按你的指示)
|
||||
|
||||
若后续仍要上,必须满足的硬约束(前置调研结论):
|
||||
|
||||
| 约束 | 要求 |
|
||||
|---|---|
|
||||
| 数量 | ≤ 50 用 CSS keyframes(零运行时);50-300 用 canvas;> 1000 用 shader |
|
||||
| 连线 | **禁用**(500 粒子 = 125k 次距离检查/帧,二次方成本) |
|
||||
| DPR | cap 到 **1.5**(DPR 3 比 1 多 9 倍像素) |
|
||||
| 停循环 | `IntersectionObserver` 离屏即停 + `visibilitychange` 切后台即停 |
|
||||
| 无障碍 | `prefers-reduced-motion: reduce` → 渲染**静态帧**,不做动画 |
|
||||
| 暗黑模式 | 粒子颜色走 token,不可硬编码 |
|
||||
|
||||
### 5.3 hero-themes.ts 的 texture 死配置接线
|
||||
|
||||
`hero-themes.ts` 里 11 个主题各配了 `texture { type, opacity, color }`(6 种类型:`grid` / `data-flow` / `layers` / `shield-hex` / `circuit` / `none`),
|
||||
但**全项目无任何消费方**——Grep `theme.texture` / `texture.type` 零结果,各 hero 组件都是内联硬编码纹理。
|
||||
|
||||
建议:重设计时把 **L1 层接线到 `theme.texture`**:
|
||||
|
||||
```
|
||||
theme.texture.type → 6 个 CSS utility 类(.hero-texture-grid 等)
|
||||
theme.texture.color → 改走 CSS 变量(--hero-texture-color),支持暗黑模式覆盖
|
||||
theme.texture.opacity → CSS 变量 --hero-texture-opacity
|
||||
```
|
||||
|
||||
收益:11 个主题一次性获得纹理差异化 + 暗黑模式自动兼容,且消灭死配置。
|
||||
|
||||
---
|
||||
|
||||
## 6. 暗黑模式兼容性
|
||||
|
||||
| 项 | 处理 | 依据 |
|
||||
|---|---|---|
|
||||
| hero 底色 | 已走 `bg-bg-primary` token ✅ | 自动反色 |
|
||||
| 点阵颜色 | **必须 token 化**(当前硬编码 `rgba(196,30,58,0.4)`) | 硬编码在深底 1.01:1 完全不可见 |
|
||||
| 点阵 alpha | 浅 0.12 / 深 0.08,用 CSS 变量按模式覆盖 | 实测浅 1.22:1 / 深 1.20:1 |
|
||||
| hero 内品牌红大字 | `#C41E3A` **可用**(3.31:1,满足 AA 大字 ≥3:1) | 实测 |
|
||||
| hero 内品牌红小字 / badge 文字 | **必须切 `#E04A68`**(4.93:1,正文 AA) | 实测,3.31:1 不达正文 AA |
|
||||
| 光标聚光颜色 | 深底用中性浅灰而非品牌红(同 alpha 下对比度更高,且不引入第二电压) | 实测 1.27 vs 1.05 |
|
||||
| 动画 | `prefers-reduced-motion` → 静态帧 | 项目已有 `useReducedMotion` hook |
|
||||
|
||||
**待确认的前置问题**:当前 `globals.css` 的 `html[data-theme='dark']` 块**未覆盖 `--color-brand`**,
|
||||
即暗黑模式下品牌红仍是 `#C41E3A`。hero 内小字需显式用 `text-brand-light`,
|
||||
或在 dark 块加一条 `--color-brand: #E04A68` 全局覆盖(影响面大,需单独评估)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 分阶段实施计划(待决策后执行)
|
||||
|
||||
| 阶段 | 内容 | 产出 | 依赖 |
|
||||
|---|---|---|---|
|
||||
| **P0** | 信息层重构:数据条下沉、主张先行、字重 900→700、辅助色退出 hero | 新 hero 结构 + 视觉回归基线更新 | 方向 A/B 决策 |
|
||||
| **P1** | 背景 L1:点阵 token 化(24px / 色+alpha 走变量)+ 接线 `hero-themes.ts` texture | 11 主题纹理差异化 + 暗黑模式自动兼容 | P0 |
|
||||
| **P2** | 背景 L2:光标聚光(mask + CSS 变量,无 canvas)→ Skim 层 | hero「活起来」,零性能风险 | P1 |
|
||||
| **P3** | Absorb 层:右侧看板渐进描边 + KPI count-up | 交互闭环 | P2 |
|
||||
| **P4** | (可选)真粒子:**仅在 P2 验证后仍觉得不够时** | 需满足 §5 L3 全部硬约束 | P2 效果评估 |
|
||||
|
||||
每阶段门禁:`type-check` / `lint` / `jest` / 视觉回归 / computed-style 暗黑模式验证(沿用 Phase 2 节奏)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 待你决策(3 个问题)
|
||||
|
||||
1. **方向**:A(Editorial 浅底,推荐)还是 B(纯黑全出血,不推荐)?
|
||||
2. **信息密度**:数据指标条是否同意**移出 hero**,下沉为滚动触发的成果带?
|
||||
3. **背景层节奏**:是否同意**先上 L1 点阵改造 + L2 光标聚光**,粒子放到最后评估(可能永远不上)?
|
||||
|
||||
---
|
||||
|
||||
## 附录:信源
|
||||
|
||||
| 内容 | 来源 |
|
||||
|---|---|
|
||||
| 埃森哲设计语言(画布/电压/字体/CTA/hairline/反转带) | Accenture DESIGN.md 拆解,webdesignhot.com |
|
||||
| 埃森哲改版方法论(Radical Relevance / Glance-Skim-Absorb / 字重 9→3 / 色 60→24 / motion choreography) | Sana 案例页 sana.works/accenture-com(Sunwha Park,2023-2024) |
|
||||
| 点阵 / 聚光 / 粒子成本模型 | 前置会话技术调研(CSS radial-gradient tiling、mask-image 光标跟随、DPR cap、IntersectionObserver、prefers-reduced-motion) |
|
||||
| 对比度数据 | **本文件 §1.2 实测**(Python 按 WCAG 2.1 相对亮度公式计算,脚本一次性运行,非估算) |
|
||||
| 项目现状代码位置 | `home-content-v15.tsx` / `hero-section-v2.tsx` / `hero-themes.ts` / `globals.css` / `tailwind.config.js` 直接读取 |
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
@@ -106,9 +106,13 @@ describe('HomeContentV15 - 浅色 Hero(暗黑模式 token 友好)', () => {
|
||||
expect(screen.getByTestId('hero-secondary-link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the answer-first stats bar visible', () => {
|
||||
it('keeps hero declaration-first: stats sink out of hero into the results band', () => {
|
||||
render(<HomeContentV15 stats={[{ value: '100%', label: '私有化部署' }]} />);
|
||||
|
||||
const hero = screen.getByTestId('hero-section');
|
||||
// 声明先行:hero 内不再内联数据条(一屏只负责一句话声明 + 一个行动)
|
||||
expect(hero.querySelector('[data-testid="hero-stats"]')).toBeNull();
|
||||
// 数据下沉为独立成果带,仍渲染(Glance 层滚动触发)
|
||||
expect(screen.getByTestId('hero-stats')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -135,10 +135,14 @@ function StyledTitle({ lines }: { lines: (string | undefined)[] }) {
|
||||
|
||||
// ===== 品牌 CTA 按钮已抽离为可复用 UI 组件:@/components/ui/cta-button =====
|
||||
|
||||
// ===== Hero 区:全出血深色画布(对标 Accenture 纯黑 Hero)+ 品牌红单电压 =====
|
||||
function HeroSection({ heroData, stats }: {
|
||||
// ===== Hero 区:浅色 editorial 画布 + 品牌红单电压 =====
|
||||
// 设计方向(2026-08-31 重设计,参考 Accenture Radical Relevance):
|
||||
// - 声明先行:H1 → 主张 → CTA,一屏只负责「一句话说清你是谁 + 一个行动」
|
||||
// - 数据指标下沉为独立成果带(HeroStatsBand),由滚动触发(Glance 层),不再挤在 hero 内
|
||||
// - 单一电压:hero 内只有品牌红一个彩色,其余全走中性 token
|
||||
// - 字重收敛:大标题 700(原 900),对齐 Accenture「字重 9→3」的克制
|
||||
function HeroSection({ heroData }: {
|
||||
heroData?: Record<string, any> | null;
|
||||
stats?: Record<string, any>[];
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const siteConfig = useSiteConfig();
|
||||
@@ -151,9 +155,6 @@ function HeroSection({ heroData, stats }: {
|
||||
const secondaryCtaHref = heroData?.secondaryCtaHref || '/products';
|
||||
const slogan = siteConfig.slogan || COMPANY_INFO.slogan;
|
||||
|
||||
const hasValidStats = stats?.length && stats[0]?.value;
|
||||
const displayStats = hasValidStats ? stats!.slice(0, 3) : [];
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={containerRef}
|
||||
@@ -190,39 +191,17 @@ function HeroSection({ heroData, stats }: {
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.45, delay: 0.06, ease: EASE_OUT }}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-6xl 2xl:text-7xl font-black text-ink leading-[1.05] tracking-tight mb-8 sm:mb-10"
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-6xl 2xl:text-7xl font-bold text-ink leading-[1.05] tracking-tight mb-7 sm:mb-8"
|
||||
>
|
||||
{heading}
|
||||
</motion.h1>
|
||||
|
||||
{/* 数据指标条:答案优先,数据先行(品牌红仅用于第一个数字) */}
|
||||
{displayStats.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35, delay: 0.16, ease: EASE_OUT }}
|
||||
className="flex flex-wrap gap-8 sm:gap-12 md:gap-16 mb-8"
|
||||
data-testid="hero-stats"
|
||||
>
|
||||
{displayStats.map((stat, i) => (
|
||||
<div key={i} className="flex items-baseline gap-2">
|
||||
<span className={cn('text-3xl sm:text-4xl md:text-5xl font-black tabular-nums leading-none', i === 0 ? 'text-brand' : 'text-ink')}>
|
||||
{stat.value}
|
||||
</span>
|
||||
<span className="text-sm text-text-secondary whitespace-nowrap">
|
||||
{stat.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* 副标题 */}
|
||||
{/* 主张:紧随声明之后,回答「凭什么信你」(声明先行,数据下沉至成果带) */}
|
||||
{subheading && (
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35, delay: 0.24, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.35, delay: 0.16, ease: EASE_OUT }}
|
||||
className="text-lg sm:text-xl text-text-secondary max-w-2xl leading-relaxed mb-10"
|
||||
>
|
||||
{subheading}
|
||||
@@ -233,7 +212,7 @@ function HeroSection({ heroData, stats }: {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35, delay: 0.3, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.35, delay: 0.24, ease: EASE_OUT }}
|
||||
className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6"
|
||||
>
|
||||
<CTAButton href={ctaHref} dataTestId="hero-primary-cta">{ctaLabel}</CTAButton>
|
||||
@@ -254,7 +233,7 @@ function HeroSection({ heroData, stats }: {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.45, delay: 0.36, ease: EASE_OUT }}
|
||||
transition={{ duration: 0.45, delay: 0.3, ease: EASE_OUT }}
|
||||
className="relative mt-16 lg:mt-0"
|
||||
>
|
||||
<HeroProductVisual className="max-w-xl w-full lg:ml-auto" />
|
||||
@@ -265,6 +244,42 @@ function HeroSection({ heroData, stats }: {
|
||||
);
|
||||
}
|
||||
|
||||
// ===== Hero 成果带:数据指标下沉为独立带(声明先行,Glance 层滚动触发)=====
|
||||
// 设计意图:hero 一屏只负责「一句话声明 + 一个行动」,硬指标移出后由滚动触发,
|
||||
// 既是 Accenture「single stream 逐步展开」的落地,也避免一屏信息过载。
|
||||
function HeroStatsBand({ stats }: { stats?: Record<string, any>[] }) {
|
||||
const hasValidStats = stats?.length && stats[0]?.value;
|
||||
const displayStats = hasValidStats ? stats!.slice(0, 3) : [];
|
||||
if (displayStats.length === 0) return null;
|
||||
return (
|
||||
<section
|
||||
data-testid="hero-stats"
|
||||
aria-label="关键成果指标"
|
||||
className="relative border-t border-border-primary bg-bg-primary"
|
||||
>
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 py-12 sm:py-16">
|
||||
<ScrollReveal>
|
||||
<div className="flex flex-wrap gap-8 sm:gap-12 md:gap-16">
|
||||
{displayStats.map((stat, i) => (
|
||||
<div key={i} className="flex items-baseline gap-2">
|
||||
<span className={cn(
|
||||
'text-3xl sm:text-4xl md:text-5xl font-bold tabular-nums leading-none',
|
||||
i === 0 ? 'text-brand' : 'text-ink'
|
||||
)}>
|
||||
{stat.value}
|
||||
</span>
|
||||
<span className="text-sm text-text-secondary whitespace-nowrap">
|
||||
{stat.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// ===== 信任/成果区:可验证信任信号 + 首批客户共创标签 =====
|
||||
// 共创计划默认状态条目(如实呈现,无真实案例时不虚构客户)
|
||||
const DEFAULT_EARLY_ACCESS_STATUS = [
|
||||
@@ -968,8 +983,11 @@ export default function HomeContentV15({ services, cases, stats, heroData, news,
|
||||
}) {
|
||||
return (
|
||||
<main>
|
||||
{/* 1. Hero:全出血深色画布 + 品牌红单电压(对标 Accenture 纯黑 Hero) */}
|
||||
<HeroSection heroData={heroData} stats={stats} />
|
||||
{/* 1. Hero:浅色 editorial 画布 + 品牌红单电压(参考 Accenture Radical Relevance) */}
|
||||
<HeroSection heroData={heroData} />
|
||||
|
||||
{/* 1.5 成果带:数据指标下沉,滚动触发(Glance 层,声明先行的承接) */}
|
||||
<HeroStatsBand stats={stats} />
|
||||
|
||||
{/* 2. 信任区:量化数据成果展示 */}
|
||||
<TrustSection pageCopy={pageCopy} />
|
||||
|
||||
@@ -41,7 +41,7 @@ export function HeroProductVisual({ className }: { className?: string }) {
|
||||
<span className="text-[11px] font-medium text-text-muted">{kpi.label}</span>
|
||||
<TrendingUp className="h-3.5 w-3.5 text-brand" />
|
||||
</div>
|
||||
<div className="text-xl font-black tabular-nums leading-none text-ink sm:text-2xl">
|
||||
<div className="text-xl font-bold tabular-nums leading-none text-ink sm:text-2xl">
|
||||
{kpi.value}
|
||||
</div>
|
||||
<TrendBadge trend={kpi.trend} label={kpi.note} />
|
||||
|
||||
Reference in New Issue
Block a user