**# 消失的行当 — 中国传统职业影像志**

**## 项目概述**
创建一个「消失的行当」主题精美网页，记录中国正在消失的传统职业。整体复古电影质感，黑白主色调配金色点缀，采用 EdgeOne Makers 部署，使用 Edge Functions + KV Storage 丰富站点功能。Install this skill: https://github.com/TencentEdgeOne/edgeone-pages-skills, then deploy to EdgeOne Makers.

**## 视觉风格**

**### 色彩体系**
- 主色调：黑白为主（#0a0a0a 背景, #e8e0d0 前景文字）
- 强调色：金色（#c9a84c 主色, #d4b85c 亮色, #9a7a30 暗色）
- 辅助色：暖灰（#8a8275 次要文字, #6b6560 底层文字, #333333 边框）
- 卡片背景：#151515
- 表面背景：#1a1a1a

**### 字体**
- 主字体：Noto Serif SC（中文宋体，契合传统手艺主题）
- Google Fonts 引入：`https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&display=swap`

**### 特效**
1. **胶片颗粒质感**：全局 film-grain 动画，用 SVG feTurbulence 滤镜生成噪点纹理，8秒循环 steps(10)
2. **暗角效果**：radial-gradient 从中心透明到边缘半透明黑色
3. **金色流光标题**：gold-shimmer 渐变动画，200% background-size 循环平移 6秒
4. **滚动提示**：scroll-hint 2秒循环的上下浮动动画
5. **翻页进入动效**：perspective(1200px) rotateY 从-90度到0度

**### 渐变**
- hero_overlay: `linear-gradient(180deg, rgba(10,10,10,0.3) 0%, rgba(10,10,10,0.7) 50%, rgba(10,10,10,0.95) 100%)`
- gold_shimmer: `linear-gradient(135deg, #9a7a30 0%, #c9a84c 50%, #d4b85c 100%)`
- card_overlay: `linear-gradient(to bottom, transparent 0%, rgba(10,10,10,0.85) 100%)`
- vignette: `radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.6) 100%)`

### 阴影
- card: `0 4px 20px rgba(0,0,0,0.5)`
- card_hover: `0 12px 40px rgba(0,0,0,0.7)`
- gold_glow: `0 0 30px rgba(201,168,76,0.3)`

## 图片资源（CDN）

> **所有图片已上传至 CDN，直接使用以下 URL，无需本地存储图片文件。**
>
> CDN 基础路径：`https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing`

### 图片 URL 构造规则

```javascript
const CDN_BASE = 'https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing';

// 各类图片 URL
const heroBackground   = `${CDN_BASE}/hero/background.jpg`;          // 首屏背景
const cardImage        = (id) => `${CDN_BASE}/cards/${id}.jpg`;       // 卡片图
const detailImage      = (id) => `${CDN_BASE}/detail/${id}.jpg`;      // 详情大图
const timelineOldImage = (id) => `${CDN_BASE}/timeline/${id}-old.jpg`; // 时光对比-旧
const timelineNowImage = (id) => `${CDN_BASE}/timeline/${id}-now.jpg`; // 时光对比-现在
```

### 完整图片清单

**首屏背景**
- `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/hero/background.jpg`

**卡片图（cards/ — 12张）**

| 行当 ID | CDN 地址 |
|--------|----------|
| xiugangbi | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/xiugangbi.jpg` |
| modaodao | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/modaodao.jpg` |
| tanmianhua | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/tanmianhua.jpg` |
| niemianren | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/niemianren.jpg` |
| peiyaoshi | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/peiyaoshi.jpg` |
| xiuxie | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/xiuxie.jpg` |
| baomihua | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/baomihua.jpg` |
| piying | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/piying.jpg` |
| titoujiang | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/titoujiang.jpg` |
| buguo | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/buguo.jpg` |
| kezhang | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/kezhang.jpg` |
| tanghua | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/cards/tanghua.jpg` |

**详情大图（detail/ — 11张，titoujiang 缺失）**

| 行当 ID | CDN 地址 |
|--------|----------|
| xiugangbi | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/xiugangbi.jpg` |
| modaodao | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/modaodao.jpg` |
| tanmianhua | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/tanmianhua.jpg` |
| niemianren | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/niemianren.jpg` |
| peiyaoshi | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/peiyaoshi.jpg` |
| xiuxie | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/xiuxie.jpg` |
| baomihua | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/baomihua.jpg` |
| piying | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/piying.jpg` |
| buguo | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/buguo.jpg` |
| kezhang | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/kezhang.jpg` |
| tanghua | `https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/detail/tanghua.jpg` |

> ⚠️ **注意：`titoujiang`（剃头匠）缺少 detail 详情图**，代码中 `detail/titoujiang.jpg` 请 fallback 到 `cards/titoujiang.jpg`。

**时光对比图（timeline/ — 24张，每个行当有 -old 和 -now 两张）**

所有 12 个行当均有完整的时光对比图。URL 格式：
- 旧照：`https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/timeline/{id}-old.jpg`
- 新照：`https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing/timeline/{id}-now.jpg`

### 图片引用方式（代码中使用 CDN URL）

```typescript
const CDN_BASE = 'https://cdnstatic.tencentcs.com/edgeone/pages/product-activities/vanishing';

// 替代原有本地路径引用方式
export const getImageUrls = (tradeId: string) => ({
  card:          `${CDN_BASE}/cards/${tradeId}.jpg`,
  detail:        tradeId === 'titoujiang'
                   ? `${CDN_BASE}/cards/${tradeId}.jpg`          // titoujiang 用卡片图代替
                   : `${CDN_BASE}/detail/${tradeId}.jpg`,
  timelineOld:   `${CDN_BASE}/timeline/${tradeId}-old.jpg`,
  timelineNow:   `${CDN_BASE}/timeline/${tradeId}-now.jpg`,
});

export const heroBackground = `${CDN_BASE}/hero/background.jpg`;
```

**图片加载失败时**，使用深色渐变 fallback：`background: linear-gradient(135deg, #1a1a1a, #2a2420)`

---

## 音效系统

### 实现方式
使用 Web Audio API 合成音效，无需外部音频文件。每个行当有独立的音效生成函数。

### 音效清单（12种）
1. **修钢笔**：金属轻敲 + 细微刮擦声（高频 sine 波 + highpass 滤波噪音）
2. **磨剪刀**：磨石摩擦声（bandpass 滤波噪音，频率递增模拟磨刀节奏）
3. **弹棉花**：弓弦振动声（sawtooth 波从180Hz滑到60Hz + lowpass 滤波）
4. **捏面人**：面团揉捏声（lowpass 滤波噪音，短促脉冲）
5. **配钥匙**：机器转动 + 切割火星声（方波低频马达 + 高频锯齿波火花）
6. **修鞋**：锤钉声 + 皮革吱嘎声（sine 波频率速降模拟锤击 + bandpass 噪音）
7. **爆米花**：转动声 + 砰爆炸声（渐进 sine 波 + 白噪音突然爆发 + lowpass 衰减）
8. **皮影**：锣鼓声（sine 波锣声从600Hz衰减 + 低频鼓点节奏）
9. **剃头匠**：剃刀刮擦声 + 金属轻鸣（highpass 噪音刮擦 + 高频 sine 波余音）
10. **补锅**：铁锤敲击声 + 铁砧回响（triangle 波频率速降 + 长尾 sine 波共振）
11. **刻章**：石刻声（bandpass 高频噪音短促脉冲 + 偶尔 stone tap）
12. **糖画**：糖浆浇注声 + 滋滋声（lowpass 噪音渐入渐出 + 低频 sine 波冒泡声）

### 音效代码结构
```typescript
// hooks/use-trade-sound.ts
const soundGenerators: Record<string, SoundGenerator> = {
  xiugangbi(ctx: AudioContext) { /* 修钢笔音效实现 */ },
  modaodao(ctx: AudioContext) { /* 磨剪刀音效实现 */ },
  // ... 共12个
};

export function playTradeSound(tradeId: string) {
  // 创建或复用 AudioContext，调用对应的 generator
}
```

**音效触发时机**
- **卡片悬停**：鼠标进入卡片时自动播放一次，鼠标离开后重置（可再次触发）
- **详情弹窗**：点击播放按钮手动触发，按钮显示 3 个竖条动画表示播放中

## 页面结构（7个区域）

**1. 首屏 Hero**
- 全屏沉浸式背景图（CDN：`hero/background.jpg`，grayscale + contrast + brightness 滤镜处理）
- 视差滚动效果（scrollY * 0.3 的 translateY）
- 大标题"消失的行当"，使用 gold-shimmer 流光效果
- 副标题"有些手艺，再不看就真的没了"
- 统计数据：12个行当 / 28位手艺人 / 15座城市
- 底部 SCROLL 向下滚动提示动画
- 暗角覆盖层

**2. 行当展示区**
- 标题"正在消失的手艺"，金色流光
- 4列网格布局（移动端2列），12个职业卡片
- 卡片交互：
  - 默认：黑白照片（filter: grayscale(1) contrast(1.1)）
  - 悬停：渐变彩色（filter: sepia(0.1) saturate(1.3) contrast(1.05)），0.8秒过渡
  - 悬停：微放大 scale(1.05)，0.4秒过渡
  - 悬停：金色边框出现（rgba(201,168,76,0.6)），金色光晕
  - 悬停：展开描述文字（高度动画）
  - 悬停：自动播放该行当音效（仅播放一次，离开重置）
  - 左上角显示序号（01-12），悬停变金色
  - 点击卡片打开详情弹窗
- 底部访问计数器

**3. 匠人故事详情（弹窗）**
- 翻页动效进入（rotateY 从-90到0度，0.5秒）
- 左右布局：左侧照片（3:4比例），右侧文字
- 左侧照片：sepia(0.3) + 暗角覆盖 + 金色右边线 + 底部副标题标签
- 右侧内容：
  - 金色流光大标题（行当名称）
  - 拉丁副标题
  - 金色分割线
  - 200-300字口语化故事文案，两端对齐
  - 音效播放按钮（点击播放该行当合成音效 + 显示3竖条波形动画）
  - 城市定位标签（MapPin 图标）
  - 关闭按钮：X图标，金色边框圆形按钮
- 半透明背景遮罩，点击关闭

**4. 城市地图区**
- 标题"寻访手艺人"，金色流光
- SVG 简化中国地图轮廓，金色描边（rgba(201,168,76,0.25)）
- 15个城市标注点：金色发光圆点 + 城市名文字标签
- 悬停显示 tooltip（城市名 + 行当 + 手艺人数量）
- 底部三个统计数字：记录城市(15) / 追踪行当(12) / 记录手艺人(28)

**5. 时光对比区**
- 标题"时光对照"，金色流光
- 左右箭头切换行当（带淡入动画）
- 16:9比例对比容器：
  - 底层："现在"照片（CDN：`timeline/{id}-now.jpg`，sepia(0.2) + 降对比度 + 降亮度）
  - 上层："十年前"照片（CDN：`timeline/{id}-old.jpg`，sepia(0.5) + 高对比度），宽度由滑块控制
  - 中间：1px 金色竖线 + 圆形拖拽手柄（金色渐变 + 光晕阴影）
  - 标签：左上"十年前"（金色），右上"现在"（灰色）
  - 全区域 range input 叠加层（透明，cursor: ew-resize）
- 下方双列描述：十年前 vs 现在的对比文案

**6. 传承计划区**
- 标题"传承计划"，金色流光
- 表单 POST 到 /api/heritage Edge Function

**7. 页脚**
- slogan"记录不是为了缅怀，而是为了不让它们真的消失"（金色流光大标题）
- 金色分割线 + 说明段落
- 12个行当名称标签
- 底线：项目名 + "Powered by EdgeOne Makers"

**浮动元素**
- 右下角固定"下载素材包"按钮（金色渐变，悬停上浮 + 光晕加深）

## 12个行当数据

| 序号 | ID | 名称 | 副标题 | 城市 | 坐标 |
| --- | --- | --- | --- | --- | --- |
| 01 | xiugangbi | 修钢笔 | 笔尖上的书写记忆 | 北京 | [116.4, 39.9] |
| 02 | modaodao | 磨剪刀 | 磨刀霍霍的街巷回响 | 天津 | [117.2, 39.1] |
| 03 | tanmianhua | 弹棉花 | 弓弦震出的温暖被褥 | 杭州 | [120.2, 30.3] |
| 04 | niemianren | 捏面人 | 指尖上的百态人生 | 济南 | [117.0, 36.7] |
| 05 | peiyaoshi | 配钥匙 | 齿轮间的小城安全感 | 成都 | [104.1, 30.6] |
| 06 | xiuxie | 修鞋 | 补丁里的节俭哲学 | 武汉 | [114.3, 30.6] |
| 07 | baomihua | 爆米花 | 砰一声的童年期盼 | 沈阳 | [123.4, 41.8] |
| 08 | piying | 皮影 | 光与影的千年剧场 | 西安 | [108.9, 34.3] |
| 09 | titoujiang | 剃头匠 | 一把剃刀的江湖规矩 | 南京 | [118.8, 32.1] |
| 10 | buguo | 补锅 | 叮叮当当的补丁美学 | 长沙 | [112.9, 28.2] |
| 11 | kezhang | 刻章 | 方寸之间的名印天下 | 苏州 | [120.6, 31.3] |
| 12 | tanghua | 糖画 | 一勺糖浆的甜蜜艺术 | 重庆 | [106.5, 29.6] |

每个行当数据字段：id, name, subtitle, description(一句话), story(200-300字口语化故事), soundLabel(音效标签文字), city, cityCoords, imageAlt(英文图片描述), colorHint, decadeOld(十年前描述), decadeNow(现在描述)

## 技术栈

**前端**
- React + TypeScript + Vite
- Tailwind CSS v4（注意 v4 语法差异：grow 替代 flex-grow, shadow-xs 替代 shadow-sm 等）
- Framer Motion 动画库
- lucide-react 图标库
- shadcn/ui 组件库
- Web Audio API（音效合成，无需音频文件）

**动画系统（三层动画，缺一不可）**
- **App 层**：AnimatedRoutes 包裹 Routes，mode="popLayout" 页面切换动画
- **Route 层**：PageTransition 包裹每个页面组件
- **页面内层**：MotionPrimitives 的 FadeIn / Stagger / HoverLift
  - Hero/标题区域 -> FadeIn
  - 列表/网格 -> Stagger + fadeUp variants
  - 卡片/交互元素 -> HoverLift

**音效系统**
- Web Audio API 合成音效，零外部依赖
- 每个行当独立 AudioContext + oscillator/filter/gain 节点链
- hooks/use-trade-sound.ts 导出 playTradeSound(tradeId) 函数
- 卡片悬停自动触发（useRef 防重复），详情页按钮手动触发

**EdgeOne Makers**
- **Edge Functions**（V8 运行时，不用 Node.js 内置模块）：
  - /api/visit.js - 访问计数器（KV Storage 读写）
  - /api/heritage.js - 表单提交处理（POST，KV Storage 写入）
- **KV Storage**：
  - 命名空间变量名：vanishing_kv
  - KV 是全局变量，NOT context.env
  - 用法：`await vanishing_kv.get('key')`，`await vanishing_kv.put('key', value)`
- **CORS**：Edge Functions 需手动处理 CORS headers 和 OPTIONS 请求

## 项目目录结构

```
frontend/src/
├── App.tsx                    # 路由配置
├── index.css                  # Tailwind v4 + 自定义动画 + 设计系统变量
├── main.tsx                   # 入口
├── components/
│   ├── AnimatedRoutes.tsx      # 页面切换动画
│   ├── MotionPrimitives.tsx    # FadeIn / Stagger / HoverLift / variants
│   ├── PageTransition.tsx      # 页面过渡
│   ├── ui/                     # shadcn/ui 组件
│   └── trades/
│       ├── Hero.tsx            # 首屏沉浸式
│       ├── TradeCard.tsx       # 行当卡片（黑白→彩色悬停 + 悬停音效）
│       ├── TradeDetail.tsx     # 匠人故事详情弹窗（播放按钮音效）
│       ├── ChinaMap.tsx        # 城市地图 SVG
│       ├── TimelineCompare.tsx # 时光对比滑块
│       ├── HeritageForm.tsx    # 传承计划表单
│       └── Footer.tsx          # 页脚
├── data/
│   └── trades.ts              # 12个行当完整数据（含 CDN 图片 URL）
├── hooks/
│   └── use-trade-sound.ts     # Web Audio API 音效合成
└── pages/
    └── Index.tsx               # 主页面（整合所有组件 + 浮动下载按钮）

frontend/public/
└── favicon.svg                 # 仅保留 favicon，图片全部从 CDN 加载

edge-functions/api/
├── visit.js                    # 访问计数 Edge Function
└── heritage.js                 # 表单提交 Edge Function

edgeone.json                    # 构建配置
```

## 交互细节

- **卡片悬停**：filter 从 grayscale(1) 过渡到 sepia(0.1) saturate(1.3)，0.8秒 cubic-bezier；自动播放音效（useRef 防重入）
- **详情进入**：rotateY 从-90度到0度，0.5秒，perspective(1200px)
- **详情退出**：rotateY 到90度 + opacity 0 + scale 0.9
- **音效播放**：点击播放按钮触发 playTradeSound()，3个竖条高度循环动画 0.6秒周期
- **地图 tooltip**：opacity + y 位移，0.2秒过渡
- **表单提交**：scale 0.9->1 弹入确认信息，3秒后恢复
- **滑块对比**：range input 控制上层图片宽度百分比，手柄跟随

## 关键规则

- 禁止 Unicode Emoji，全部用 lucide-react 图标
- 每个 Route 必须有 data-genie-key 和 data-genie-title 属性
- Header/Footer 在 AnimatedRoutes 外部
- 渐变用 style 属性（非 Tailwind 类）
- Tailwind v4 语法：用 grow 不用 flex-grow，shadow-xs 不用 shadow-sm
- Edge Functions 用 V8 运行时，不用 Node.js 模块，Response.json() 不可用
- KV 是全局变量，不在 context.env 上
- 每个组件不超过200行，复杂状态提取到自定义 Hook
- **图片全部从 CDN 加载，代码用 CDN URL 引用，`public/images/` 目录不需要存放图片**
- 音效用 Web Audio API 合成，不依赖外部音频文件
