网址提交

This commit is contained in:
浅念
2026-08-27 11:54:26 +08:00
commit f4fb024e98
177 changed files with 9100 additions and 0 deletions
@@ -0,0 +1,249 @@
# HESU WordPress 主题设计文档
- 日期:2026-08-15
- 状态:已获用户批准(三轮设计审查修正后)
- 来源:`d:\wp\demojt` 下 10 页静态 HTML 模板 → WordPress 经典主题
## 1. 目标与约束
把现有静态站(HESU,外贸企业站,暗色+橙色主题,英文)1:1 迁移为 WordPress 主题:
- **固定模板**:页面结构锁死,编辑者在后台只能改内容(文字/图片/数字),不能增删或调换板块。
- **公共板块可复用**:抽为原生自定义 Gutenberg 区块(`block.json` + PHP `render.php`),内容存在 `post_content` 区块标记中(非 post meta)。
- **内容后台填充**:区块编辑器可视化编辑;新闻走完整文章系统。
- **不依赖 ACF / 付费插件**:唯一插件依赖 Contact Form 7(联系表单)。
- **严格复刻设计稿**:前端输出 HTML 与静态站逐字节对齐(项目既定原则)。
- 交付位置 `d:\wp\demojt\hesu-theme\hesu\`(zip 后即为标准主题包)。
- 环境:本机无 PHP、无 WP;验证采用"便携 PHP + WordPress + SQLite 插件"一次性测试站(用户已确认)。
## 2. 关键决策记录
| 决策 | 结论 | 备选与理由 |
|---|---|---|
| 主题形态 | 经典主题(PHP 模板)+ 原生自定义区块 | FSE 全区块主题需重写全部 JS 交互(fullpage/pinned/时间线),风险高,弃 |
| 区块实现 | 原生 Gutenberg 区块,无构建工具链(编辑器脚本用原生 `wp.element`,非 JSX) | ACF Pro 区块被用户排除;无 npm 依赖,交付即用 |
| 内容存储 | 区块标记写入 `post_content` | 所见即所得、导入导出原生支持 |
| 结构锁定 | 页面包裹区块 `InnerBlocks templateLock:"all"` + 全部区块实例级 `lock:{move:true,remove:true}` | 见 §5 锁定机制 |
| 新闻 | CPT `news` + 分类法 `news_cat`,Tab 用**分类归档地址**(非自定义 query var) | 原生分页/高亮/canonical 免费获得 |
| 联系表单 | Contact Form 7,seed 时程序化创建默认表单 | 未装 CF7 时显示引导占位 |
| 资产加载 | `has_block()` 判断内容区块条件加载 CSS/JS | 与 slug/模板解耦,编辑者改 slug 不影响 |
| Header/Footer | WP 菜单(自定义渲染)+ Customizer,不做区块 | 见 §6 |
| Seed | `after_switch_theme` 一次性播种(幂等守卫 + 管理员手动重跑按钮) | |
## 3. 主题目录结构
```
wp-content/themes/hesu/
├── style.css # 主题头信息 + 站点基础样式(原 style.css)
├── functions.php # 入口,require inc/ 各模块
├── inc/
│ ├── setup.php # 主题支持、菜单注册、add_post_type_support('page','excerpt')
│ ├── assets.php # has_block() 映射表条件加载 + 编辑器样式
│ ├── blocks.php # 扫描 blocks/ 注册区块
│ ├── cpt-news.php # CPT news + 分类法 news_cat
│ ├── customizer.php # 社交链接、页脚标语、版权、Mega Menu 左侧文案
│ ├── seed.php # 激活播种 + 主题设置页(重新创建预置页面按钮)
│ └── walker-mega.php # Mega Menu 自定义渲染
├── header.php / footer.php
├── page.php # 所有以区块组成的页面共用(唯一渲染入口)
├── archive-news.php # 新闻列表(Tab + 分页)
├── single-news.php # 新闻详情
├── css/ js/ fonts/ image/ # 静态站资产原样迁移(保持相对路径结构)
├── editor-style.css # 编辑器预览样式(含页面级 class 场景)
└── blocks/ # 每区块一个目录:block.json / render.php / index.js
```
无构建步骤;PHP 目标 7.4+,WP 6.0+(用到区块实例锁)。输出一律转义(`esc_html/esc_attr/esc_url`)。
**前端 body class 注入(关键)**:各页面基底 CSS 挂在 `body.is-*` 上(如 `body.is-bp { background:#fff; ... }` 切换浅色主题),page-shell 只在 `<main>` 上输出 class 覆盖不到 body。静态站实际只有 3 种 body 钩子:`is-bp`(4 个业务页**和发展历程页**,main 同为 `bp`)、`is-news`、`is-contact`;首页/全球布局/关于我们三页 `<body>` 无 class。方案:`page.php` 经 `body_class` 过滤器解析当前 `post_content` 中 page-shell 的变体,注入 `is-bp`(含发展历程)/ `is-contact`(main 为 `ct`)/ `is-gp` / `is-au`(后两者 CSS 无对应钩子,仅为语义标记);`archive-news.php` / `single-news.php` 直接注入 `is-news`。编辑器侧则由 page-shell 的容器 class 提供同场景(§10)。
## 4. 区块清单(`hesu/` 命名空间,22 个)
> *勘误(2026-08-17):标题的 22 为早期概数,下表实列 33 个;实现最终为 37 个(补充 brand-card/cap/caps/partner-slide 以对应真实静态 DOM)。以实现与 readme 为准。*
### 通用
| 区块 | 属性/结构 |
|---|---|
| `hesu/hero` | 背景图(ID)、标题、副标题(RichText)、CTA(文字+链接)、SCROLL 指示器开关、对齐变体(左/居中/home/dh/ct);home 变体内置锁定 InnerBlocks 槽位放 stats-bar(DOM 与静态站一致:stats 在 hero section 内) |
| `hesu/stats-bar` → 子块 `hesu/stat` | 子项:图标图(可选)、数字、后缀、标签;输出 `data-count` |
| `hesu/business-intro` | 固定两栏:左(标题+RichText 段落+stats-bar) 右(图片) |
| `hesu/section-heading` | 标题(+副文+可选外框按钮) |
| `hesu/view-more` | 文字+链接 |
| `hesu/page-shell` | 页面级锁定容器:`templateLock:"all"`,class 由 seed 写死(如 `is-bp`/`page-content`),渲染为语义化 `<main>`(home 的 `page-content` 为 div,与静态站一致),无编辑 UI |
### 首页专属
| 区块 | 说明 |
|---|---|
| `hesu/bento-grid` → `hesu/banner-cell` + `hesu/media-card` | 四大业务磁贴,4 卡结构锁死 |
| `hesu/presence` | 标题+副文(含加粗)+按钮+世界地图图 |
| `hesu/hub-carousel` → `hesu/hub-card` | 区域轮播卡(图+区域名+链接) |
| `hesu/partner` | 轮播 slide(图) + 橙色覆盖层(标题+4 特性项:图标+词) |
| `hesu/news-grid` | 动态查询块:自动拉最新 4 篇新闻(图=特色图、分类+年份自动),仅"查看更多"链接可配 |
所有带锚点目标的区块(`gp-screen` 的 `gp-1..gp-5`、产品区 `#products` 等)的 `id` 为可配置属性,seed 沿用静态站原值,保证页脚锚点链接(如 `global-presence#gp-2`)不失效。
### 业务页专属
| 区块 | 说明 |
|---|---|
| `hesu/large-image` | 全宽大图框(bm-stock/gl-port 共用) |
| `hesu/product-cards` → `hesu/product-card` | 产品分类卡:主题内置 SVG 图标集选择(或自定义图)+名称 |
| `hesu/feature-cards` → `hesu/feature-card` | 统一"图标+标题+描述"卡片组,变体 class 区分 as-services / ae-region / as-region / gl-services 四处样式 |
| `hesu/showcase` / `hesu/brand-wall` / `hesu/model-list` | 汽车出口页专属 |
### 特殊页面
| 区块 | 说明 |
|---|---|
| `hesu/timeline` → `hesu/timeline-item` | 发展历程横向时间线(年份/标题/描述) |
| `hesu/gp-shell` → `hesu/gp-screen` | 全球布局 fullpage。容器含 `.gp-pagebg` 背景层;每屏变体(概览/区域,id `gp-1..gp-5` 可配);**概览屏内部含 `gp-nav-list` 链接列表(屏内容的一部分,非独立导航)**——列表项为锁定子区块(文案+`#gp-N` 锚点+`data-i`)。global-presence.js **零适配**原样迁移(它只做 active 高亮,按 `data-i` 匹配) |
| `hesu/au-stage` → `hesu/au-screen-1` / `hesu/au-screen-2` | 关于我们 pinned 视屏:内部结构(背景图/角图/遮罩/标语层/缩放图/大 logo)固定,开放文案+图片编辑。注意 au-screen-1 背景为 style 内联 background-image |
| `hesu/au-gps` | 关于我们底部区,**内部 4 层锁定结构**:① head(标题+文案) ② 4 图一行 ③ 宽幅图 ④ 左右分栏——左侧数据轮播(`au-gps__data-card`: 图+标题+描述 ×3,圆点由 about-us.js 驱动) + 右侧图文覆盖卡(图+overlay 标题+文案) |
| `hesu/contact-form` | CF7 表单下拉选择器+容器样式;CF7 未装显示安装引导占位 |
| `hesu/contact-map` | 总部地图:默认嵌入静态站的 OpenStreetMap iframe(URL 可配置) |
### 编辑器脚本与渲染约定
- 主文案内联 RichText;数字/后缀/链接等用右侧栏 TextControl(控制代码量)。
- **RichText 属性存储(关键陷阱)**:本主题区块全部为动态渲染(render.php、无 save 函数),RichText 属性**禁止用 `source:'html'`**(无保存标记可解析会丢内容),一律普通 string 属性 + `value`/`onChange`。
- **stat-num DOM 微结构(关键)**:main.js 计数器以 `el.firstChild.nodeValue` 写数字——`<span class="stat-num" data-count="20">20<span class="plus">+</span></span>` 中数字文本节点必须是 firstChild,render.php 输出不得多包元素。
- 容器类动态区块 render.php 必须输出 `$content`(InnerBlocks 内容)。
- 不做 inserter `example` 配置(页面锁死,无人手动插入区块)。
- 共享 helper(createElement 包装、公共 Inspector)提为 `blocks/editor/helpers.js` 单 handle。
## 5. 结构锁定机制(核心)
1. **页面级**:每个页面 = `page-shell`(或首页 = `hero(home)` + `page-shell` 两个顶层块,因静态站 DOM 中 hero 在 `page-content` 之外)。顶层块带实例锁 `lock:{move:true,remove:true}`。
2. **容器级**:`page-shell` 内 `InnerBlocks templateLock:"all"`——不能增删/移动子板块,只能进入编辑内容。
3. **编辑器加固**:非管理员隐藏区块插入器与解锁入口(editor settings 过滤)。
4. **残余风险(已向用户说明)**:WP 核心无法阻止管理员手动解锁破坏结构;文档注明"解锁=破坏页面"。
## 6. Header / Footer 内容来源(不用区块)
| 部位 | 配置方式 |
|---|---|
| Logo | 原生 Custom Logo |
| 主导航 | WP 菜单;Mega Menu 用 `wp_get_nav_menu_items()` 自定义渲染:卡片 = 菜单项,卡片图 = 目标页特色图片(无图回退主题占位图,如指向归档/锚点的 News 卡),卡片文案 = 页面摘要(页面启用 excerpt)或菜单项描述 |
| Mega Menu 左侧文案 × 2 组 | Customizer 文本字段 |
| Contact Us 按钮 | 指向联系页 |
| Footer 5 栏 | 1 个 WP 菜单(4 个一级项=4 栏) + 标语走 Customizer |
| 社交图标 4 个 | Customizer(平台+URL) |
| 版权 / Privacy / Terms / Sitemap | Customizer |
| 菜单高亮 | `nav_menu_css_class` 过滤器补 `active` 类(静态 CSS 认 `.active`,WP 原生输出 `.current-menu-item`) |
Mega Menu 卡片恰好 4 张是布局约束,seed 保证初始 4 项;文档说明增删项会改变布局。
## 7. 页面编排(seed 写死顺序)
| 页面 | 组成 |
|---|---|
| 首页 Home | `hero(home,含stats)` + `page-shell(page-content)` → bento-grid → presence → hub-carousel → partner → news-grid |
| 建材出口 | `page-shell(is-bp)` → hero → business-intro(+stats,4 项 data-count) → large-image → product-cards → view-more |
| 汽车出口 | `page-shell(is-bp)` → hero(居中) → business-intro(无 stats,静态站此页没有) → showcase → brand-wall → model-list → feature-cards(region) → view-more |
| 售后改装 | `page-shell(is-bp)` → hero → business-intro(无 stats,静态站此页没有) → feature-cards(services) → feature-cards(region) |
| 国际物流 | `page-shell(is-bp)` → hero → business-intro(+stats,4 项 data-count) → large-image(port) → feature-cards(services) |
| 全球布局 | `page-shell(main 为 gp-main)` → gp-shell(1 概览屏+4 区域屏) |
| 关于我们 | `page-shell(main 为 au-main)` → au-stage(2 pinned 屏) → au-gps |
| 发展历程 | `page-shell(is-bp,静态站此页 body 即 is-bp)` → hero(dh) → timeline |
| 联系我们 | `page-shell(is-contact,main 为 ct)` → hero(ct) → contact-form → contact-map |
| 新闻列表/详情 | PHP 模板渲染,不用区块 |
## 8. 新闻系统
- CPT `news`:支持标题/编辑器/特色图片/摘要;归档 `/news/`(**不建 News 页面,避免 URL 冲突**;导航 News 链接指向 `get_post_type_archive_link('news')`)。
- 分类法 `news_cat`(非层级):seed 建 4 词——Company News / Delivery Cases / Exhibition Info / Global Network(第 4 个供首页卡片 meta 与页脚 News 栏使用;Tab 自动列出全部词,初始即 4 个,较静态站多出 Global Network 一项为有意取舍)。Tab = 全部分类词的**归档地址链接**;分页原生。
- `archive-news.php`:新闻 Hero → Tab → **左文右图行列表(`.nw-row`,与静态站一致,非卡片网格)** → 分页(每页 5 条,与静态 news.js 的 PER_PAGE 一致)。行内 `[data-reveal]`/`.nw-row` 默认 `opacity:0`,reveal 逻辑并入 main.js(见 §10)。**Hero 背景图来源**:Customizer 新增"新闻 Hero 图"设置(theme mod,默认主题资产 `image/carexport/export-bg.jpg`),归档与详情共用。
- `single-news.php`(按 `demo-html/新闻详情1.jpg` 已读图确认):新闻 Hero+面包屑 → 分类标签+日期+标题 → 特色图 → 正文(核心区块编辑器,零自定义区块)→ 上一篇/下一篇 → 返回列表。
- 首页 `news-grid` 自动拉最新 4 篇。
- 静态 `news.js`(207 行,客户端渲染 15 篇硬编码数据 + Tab 过滤 + 分页)服务端化后大部分作废,不迁移;仅 reveal 行为由 main.js 承接。
## 9. Seed 流程
```
守卫(option hesu_seeded) → 图片入库(image/ 内容图经临时文件 + media_handle_sideload)
→ 建 9 个页面(锁定区块标记,引用图片附件 ID;9 页全部设置特色图片,供 Mega Menu 卡片使用)
→ 设 show_on_front/page_on_front → 建 4 个分类词
→ 建 15 篇示例新闻(迁移 news.js 内置的全部 15 篇数据,保证分页可见 3 页)
→ 建主菜单+页脚菜单并绑定卡位 → CF7 已激活时创建默认表单(字段=静态表单)
→ 写入 hesu_seeded
```
- CF7 晚于主题激活的场景:另挂 `activated_plugin` 钩子,检测到 CF7 启用且默认表单缺失时补建;主题设置页"重新创建预置页面"按钮作为最终兜底。
- Mega Menu 卡片无特色图时的占位:复刻静态站 `ph-*` 占位样式(底色 + "Photo" 标签的 div),不使用裸 `<img>`。
- 主题设置页提供管理员"重新创建预置页面"按钮(幂等重跑)。
- 若在已有内容的站点激活:seed 仍会创建预置页面并设置首页,文档说明预期用于全新安装。
## 10. 资产加载
| 层级 | 策略 |
|---|---|
| 全站 | `style.css` + `fonts.css` + `main.js`(footer 加载,与静态站 body 尾一致;扩展:新增对 `[data-reveal]` 与 `.nw-row` 的 IntersectionObserver reveal——业务页 `[data-reveal]` 与新闻行默认 `opacity:0`,无 JS 则内容不可见) |
| 按区块 CSS | `business-intro→business.css`、`hero→business.css`、`product-cards→building-materials.css`、`brand-wall/showcase/model-list→auto-export.css`、`feature-cards→after-sales.css+auto-export.css+global-logistics.css`(交叉装载无害:各文件选择器以自身变体 class 作用域)、`timeline→development-history.css+js`、`gp-shell→global-presence.css+js`、`au-stage→about-us.css+js`、`news-grid→news.css`、`contact-form→contact.css+js` |
| 按区块 JS | 出现任一业务页区块(hero/business-intro/product-cards/brand-wall/showcase/model-list/feature-cards/large-image)→ 加载 4 个业务页 JS(building-materials.js / auto-export.js / after-sales.js / global-logistics.js,共约 6KB)。理由:各文件自带选择器守卫(无目标即 return),其中 data-count 计数(建材/物流两页的 bp-stats)与部分 reveal 由它们驱动,main.js 的 `.stat-num` 计数器不覆盖 `bp-stat__num` 结构 |
| news.js | 不迁移(客户端渲染/过滤/分页已服务端化),reveal 逻辑由 main.js 扩展承接 |
| 模板直载 | archive/single-news 直接 enqueue `news.css` |
| 编辑器 | 编辑器侧加载 `fonts.css` + `style.css` + **全部页面 CSS**(约 130KB,仅后台开销,无法按页条件);page-shell 输出页面级 class(如 `is-bp`)使作用域 CSS 在编辑器 iframe 生效 |
| 行为边界 | 交互(计数/轮播/fullpage)仅在.front 前端运行,编辑器内为静态预览——WP 服务端渲染区块的固有限制 |
### CF7 与静态表单的样式映射
静态表单是自绘结构(`ct-form/ct-field/ct-label/ct-input/ct-selectwrap` + `#ctForm` 客户端校验 + `#ctNote` 成功态);CF7 输出 `form.wpcf7-form` + `span.wpcf7-form-control-wrap` 包裹。约定:
- seed 建表单时为每个字段 shortcode 挂静态 class(如 `[text* your-name class:ct-input]`)。
- `contact.css` 增补桥接规则:以 CF7 实际 DOM(`.wpcf7-form` / `.wpcf7-form-control-wrap` / `.wpcf7-not-valid` / 响应元素 `.wpcf7-response-output`)为目标复刻 ct-* 视觉。CF7 6.x 起状态类(`invalid` / `sent` / `failed` / `aborted` / `unaccepted` / `spam`)挂在 form 元素上(如 `form.wpcf7-form.sent`),响应元素仅保留 `.wpcf7-response-output`,桥接规则按 `form.{invalid,sent,...} .wpcf7-response-output` 匹配;校验与提交反馈全部交给 CF7 原生状态 class,`contact.js` 的表单分支不迁移。
JS 依赖事实:静态站全部 JS 为原生手写(无 GSAP/fullpage 库),**全部原样迁移、零适配**(global-presence.js 的 active 高亮依赖 `gp-nav-list a[data-i]` 标记,由概览屏锁定子区块保证)。main.js 的 network 无限轮播克隆逻辑要求 `.network-card` 为 `.network-track` 直接子元素,hub-carousel 渲染须保持该层级。
## 11. 验证方案(用户已确认搭一次性环境)
1. 下载便携版 PHP(≥7.4,含 pdo_sqlite)解压至项目临时目录(不入库)。
2. WordPress + SQLite drop-in(如 wp-sqlite-db)搭建一次性站点,安装主题与 CF7。
3. 实跑验收清单(§12)。
4. 测完删除临时站点;若用户后续装正式环境可复用清单。
## 12. 验收清单
1. 干净 WP 激活 → 页面/菜单/示例新闻自动生成,首页即成型。
2. 10 个路由与静态站逐页对照:板块顺序、数字滚动、Mega Menu、轮播拖拽、fullpage 翻屏、pinned 视屏、时间线横滑全部工作(含 `.reveal` 滚动动画)。
3. 以"编辑"角色打开任意页面:结构不可增删/移动,文字图片可改;尝试删除被锁区块被拒绝。
4. 新闻:各类别发文 → 归档 Tab 过滤 + 分页 + 首页卡片自动更新。
5. 联系表单:样式按 §10 桥接规则还原静态视觉;测试环境将 CF7 邮件设为 dummy(便携 PHP 无邮件服务),验收标准为**提交成功态/校验态正确展示**,不要求真实发信。
6. 导航高亮、Customizer 各字段生效。
7. `php -l` 全过、输出转义审查、控制台无报错、本地字体正常加载。
8. CF7 缺失时联系页显示引导提示而非报错。
## 13. 设计审查修正记录(三轮)
| # | 级别 | 问题 | 修正 |
|---|---|---|---|
| 1 | 漏洞 | `templateLock` 锁不到顶层区块 | 全部区块实例级 `lock:{move,remove}` |
| 2 | 漏洞 | News 页面与 CPT 归档 `/news/` URL 冲突 | 不建 News 页面 |
| 3 | 漏洞 | 按 slug 条件加载资产脆弱 | 改 `has_block()` |
| 4 | 漏洞 | 页面级 CSS 依赖 body class,编辑器不生效 | page-shell 输出等价 class |
| 5 | 漏洞 | 首页 hero 与 page-content 的 DOM 层级套不进单容器 | 两个顶层锁死块;hero(home) 内嵌 stats 槽位 |
| 6 | 缺口 | Mega Menu News 卡片无特色图/摘要 | 占位图+菜单描述兜底 |
| 7 | 缺口 | 菜单高亮 `.active` vs `.current-menu-item` | nav_menu_css_class 过滤器 |
| 8 | 缺口 | wp_nav_menu 输出不了 mega 结构 | wp_get_nav_menu_items 自定义渲染 |
| 9 | 缺口 | 新闻 Tab 分页保参易错 | 改用分类归档地址,原生分页 |
| 10 | 缺口 | seed 无重跑出口 | 主题设置页按钮 |
| 11 | 缺口 | News 卡用 query_var 方案繁琐 | 改原生归档 URL |
| 12 | 矛盾 | 验收清单需实机 vs 本机无 PHP | 便携 PHP + SQLite 一次性测试站 |
| 13 | 漏洞 | 页面基底 CSS 挂在 `body.is-*`(如 body.is-bp 浅色主题),page-shell 的 main class 覆盖不到 body | `body_class` 过滤器按 page-shell 变体注入前端 body class(spec 审查发现) |
| 14 | 漏洞 | 4 个业务页 JS(data-count 计数/reveal)未纳入资产映射;`[data-reveal]` 默认 opacity:0,JS 缺失内容不可见 | has_block 命中业务区块即加载 4 个业务 JS;main.js 扩展承接 news reveal(spec 审查发现) |
| 15 | 错误 | 汽车出口/售后改装两页静态站没有 stats,编排误写 business-intro(+stats) | 改为无 stats(spec 审查发现) |
| 16 | 错误 | 新闻列表实为左文右图行、每页 5 条、news.js 内置 15 篇,spec 误写 3 列卡片/9 条/4 篇 | 按静态站修正;Tab 有意保留 4 词(spec 审查发现) |
| 17 | 缺口 | CF7 DOM 与静态表单差异未定义、CF7 晚激活无法补建表单、seed 未设页面特色图、锚点 id 保留未覆盖、测试环境无法真实发信 | §10 桥接规则、activated_plugin 补建、seed 设特色图+ph-* 占位、区块 id 可配置、验收措辞调整(spec 审查发现) |
| 18 | 错误 | body 变体名臆造:静态 CSS 只有 is-bp/is-news/is-contact 三种钩子;发展历程页实为 is-bp+main.bp,联系页为 is-contact+main.ct,不存在 is-dh/is-ct | §3/§7 按静态站事实改注 is-bp / is-contact(二审发现) |
| 19 | 错误 | gp"圆点导航由 JS 从 data-nav-title 生成"系错误假设:gp-nav-list 实为概览屏内部链接列表(屏内容),js 仅做 active 高亮,无需任何适配;导航文案与屏标题在静态站本就不一致,不应自动生成 | §4 改为概览屏锁定子区块,global-presence.js 零适配(四轮源码核查发现) |
| 20 | 不足 | au-gps 实为 4 层结构(head/4 图行/宽图/数据轮播+图文卡分栏),spec 仅一句带过 | §4 细化内部结构(四轮源码核查发现) |
| 21 | 缺口 | 动态区块 RichText 用 source:html 会丢内容;stat-num 的 firstChild 文本节点微结构未约定;编辑器 CSS 加载范围未明确;新闻 Hero 图来源未定义;contact-map 实为 OSM iframe | 编辑器约定补两条关键约定、§10 明确全量页面 CSS、§8 Customizer 新闻 Hero 图、contact-map 改 iframe(四轮源码核查发现) |
**内容备注(非缺陷)**:静态站 gp-3(Africa)屏的 lead 文案讲的是 Minsk/Belarus(静态站自身的复制粘贴错误)。迁移按原文保留,seed 后可在后台修正。
## 14. 范围外(明确不做)
- 多语言(结构不阻碍后续接多语言插件,内容在 post_content 中天然可翻译)。
- Classic Editor 插件兼容(文档注明必须使用区块编辑器)。
- 性能优化超出 §10 所列(CDN、缓存策略交由宿主环境)。
## 15. v2 同步记录(2026-08-17)
静态站改版 v2(commit a68ff43)后的主题同步:mega 精简(Business 只留 title/About 空 left 2 卡)、footer 社交 img 化(LinkedIn→Instagram)、hero scroll 全删、bp-stats→bp-caps(含物流双 label)、SVG 图标全面 img 化、as-loc 简化、ae-models 去 tags、ae-map 去 markers、au-gps 重构(img 化+4 数据卡+feature-copy)、au-zoom-mask 新增、ct-map 三层结构、新闻系统重做(4 篇/nd-* 详情页/nw-empty 空态)、news-banner 新图。详见 `plans/2026-08-17-static-v2-sync-report.md` 与验收日志。已接受偏差:footer 按正确闭合(不复刻旧页嵌套错误);"Four Core Businessesk"与"99% Awards Winner"原样复刻。
@@ -0,0 +1,116 @@
# 首页 GLOBAL PRESENCE 地图动画替换 — 设计文档
日期:2026-08-21
状态:已与用户确认(1:1 还原 demo)
## 轮播模式迭代:累积滑窗(2026-08-21 四次补充)
应用户要求,首页地图与 auto-export 地图的轮播从"独占切换"(同一时刻一个)改为**累积滑窗**:
- 每步激活下一个据点,**已激活的保持显示**
- 窗口满后(n-1=4 个),每次新激活时**最旧的一个淡出**(0.45s opacity 过渡)
- 稳态 = 同时显示 4 个:最新的播完整动画序列,最旧的渐隐离场
- 生命周期不变:可见才播 / 完全离开归零 / 每次进入从第一个完整累积
实现:main.js 两个地图块的 `step()`(tick % n 激活,tick ≥ n-1 时 (tick-(n-1)) % n 淡出);CSS 给 `.location`/`.marker`/`.line` 加 opacity 过渡。版本 bump `?v=20260821-6`。
验证:两页 t0→t4 逐步累积至 4 个、t4 时首个淡出、t5 滑窗循环、离场归零、回来重播,全过。
## 丝滑包升级(2026-08-22)
应用户要求做动效升级,四项全部落地(两张地图统一):
1. **弧线引线**:首页 5 条折线 `d` 改为三次贝塞尔曲线(水平出发→自然弯到点位),绘制缓动 `cubic-bezier(.3,.7,.2,1)`
2. **退场反擦除**:入场=animation(.active),退场=反向 animation(.out 类,main.js 的 exit() 添加、650ms 后移除):线倒收回(lineOut dashoffset 0→1)、圆点缩小(dotOut)、标签下滑淡出(labelOut)、光环衰减(haloOut)。⚠️ 不能用 transition 实现退场 — CSS 规范规定 animation 移除时的值跳变不触发 transition
3. **重叠时序**:label .28s@.02s / line .55s@.12s / dot .32s@.5s — 各元素交叉进场
4. **呼吸 + 双涟漪**:dot 落定后 CSS `r` 属性呼吸(breatheR 7.5→8.3px / ae-breatheS 5.5→6.3 / ae-breatheL 6→6.9,`r` 与 transform 不冲突可同元素并存);每个点加第二圈光环/涟漪(halo2 / pulse.p2,延迟错开)
结构变化:子元素自管隐藏基态(group 不再切 opacity);HTML 每点 +1 光晕圆。版本 `?v=20260821-9`。
**验证注意**:Playwright 窗口不可见时 Chrome 冻结动画时钟(currentTime 恒 0)— 动画类验证必须先 `Page.bringToFront`。带前台后实测退场淡出中间态(175ms 时 opacity 0.71)精确符合曲线。
## 目标
把 [index.html](../../../index.html) 首页 GLOBAL PRESENCE 板块的静态占位地图(`image/home/global-map.png`)替换为 [global-map-animation-demo.html](../../../global-map-animation-demo.html) 中的据点轮播动画效果,1:1 还原。
## 效果(以 demo 为准)
- 背景图:`image/home/Frame 119.png`(1219 × 661)
- SVG 覆盖层与背景图共享 `viewBox="0 0 1219 661"` 坐标系,所有点/线/文字用原图坐标
- 5 个据点:DUBAI、ABU DHABI、ETHIOPIA、GHANA、RUSSIA
- 每个据点 = 白色标签 + 橙色(`--orange-700` = `#ff6a00`)折线引线 + 圆点 + 脉冲光环
- **同一时刻只激活一个据点**,每 3 秒切换下一个(循环)
- 动画序列:标签滑入(.35s)→ 引线绘制(.85s 延迟 .18s)→ 圆点弹出(.4s 延迟 1s)→ 光环脉冲(1.4s 无限循环)
- 含大屏首帧防闪烁修复:非 active 线 `stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0`
## 改动文件
| 文件 | 改动 |
|---|---|
| `index.html` | 替换 `.map-wrap` 内部为 `img.map-bg` + `svg.map-overlay`(保留外层 `reveal`、`aria-hidden`、`loading="lazy"`) |
| `css/style.css` | GLOBAL PRESENCE 段合入 demo 样式:`.map-wrap` 改 `aspect-ratio: 1219/661`,新增 `.map-bg`/`.map-overlay`/`.location` 系列 + 4 个 keyframes(颜色用 `var(--orange-700)`);删除废弃 `.map-img`;删除 ≤560px 媒体查询里 `.map-wrap { height: 260px }`(与 aspect-ratio 冲突,比例自适应后手机端高度 ≈200px 与原设计一致) |
| `css/mobile.css` | `.map-wrap` 规则去掉写死 `height: 200px`,保留 margin |
| `js/main.js` | 新增独立 IIFE:查 `.map-overlay`,`.location` 每 3s 轮播 `active`(选择器限定,不影响其他页面) |
## 刻意不做的(YAGNI)
- 不做全部常显模式(用户已确认单点轮播)
- 不移植 demo 里 vestigial 的 `--path-length` JS:CSS 从未读取该变量,`pathLength="1"` 属性已完成路径长度归一化
- 不删旧图 `global-map.png`(可能被 WP 主题或其他地方引用)
- 不加 demo 工具栏按钮(那是调试用的)
## 降级行为
- `prefers-reduced-motion`:站点全局 `animation: none`,当前据点显示标签+圆点(静止),引线保持未绘制 — 可接受
- 无 JS:DUBAI 常显(HTML 里初始 `active`)— 可接受
## 防闪加固(2026-08-21 补充)
用户报告症状:大屏上到达地图时先闪一帧完整线路 → 重置 → 才正常播放。
**根因**(Playwright 取证确认):非 CSS 首帧竞态(demo 的 base-state 修复已移植且有效),而是**状态机时序**——轮播从页面加载就开始空转,加载 6s 后(地图仍在视口外 2380px 处)ABU DHABI 的引线已处于 `fill: both` 保持的完结状态(opacity 1 / dashoffset 0)。用户滚到时第一眼撞见这条"陈旧完结帧"(= 闪一下),3s 后下个 tick 归零重置(= 消失),下一个据点才从头播放(= 正常出现)。屏幕越大板块离首屏越近,越容易触发。
**修复**([js/main.js](../../../js/main.js)):
- init 时先 `show(-1)` 清掉 HTML 里 DUBAI 的 `active`(main.js 是 body 末尾同步脚本,先于首帧执行;无 JS 时保留 DUBAI 静态回退)
- 首次激活用 IntersectionObserver 门控(观察 `.map-wrap`,threshold 0.12,与站点 reveal/count-up 同模式),触发后**双 rAF** 再 `show(0)`——先让不可见 base 状态绘制一帧,动画永远从已沉淀的样式树起步
- 进入视口才开始 3s 轮播;无 IO 的老浏览器直接 begin()
## 防闪加固第二层:re-entry 重启 + 缓存击穿(2026-08-21 二次补充)
用户反馈"还是会出现"。系统排查后确认两条残余路径:
1. **re-entry 陈旧态**:第一层修复只管"首次到达"。轮播一旦启动永不停 → 用户滚走再滚回时,当前据点又处于完结状态 → 闪 → 重置 → 下一个正常播放(症状与首次完全相同)。
2. **旧版 main.js 缓存**:python http.server 等无缓存头环境下,普通 F5 会命中启发式缓存执行旧 JS(Playwright 验证时实测踩中)。
**修复**:
- 轮播改为**可见才播放**的生命周期:IO 持续观察(threshold 0.12),`isIntersecting` → `start()`(每次进入都从 DUBAI 从头播,双 rAF 起步);完全离开 → `stop()`(清定时器 + 归零到不可见 base 态)。首次到达、回访、F5 原位刷新(Chrome 恢复滚动位置)三种场景统一为"从 DUBAI 完整播放"
- `start()` 内 rAF 等待期若已离开视口则放弃启动(守卫 `if (!timer) return`)
- index.html 脚本标签加版本号 `main.js?v=20260821` 击穿缓存(今后 main.js 有实质改动时手动 bump)
**验证**(Playwright):
- 静置顶部不滚动:零激活、所有线 opacity 0
- 滚入 → DUBAI dashoffset 0.93(从 0 画起);3.3s 后 ABU DHABI
- 滚到页脚离开 7s:`active: null`(轮播已停、已归零)
- 滚回:DUBAI dashoffset 重现从 0 画起;3.3s 后 ABU DHABI —— re-entry 不再撞见完结帧
- 原位 reload(Chrome 恢复 scrollY=2309 地图在视口):t=135ms DUBAI 已干净启动,无闪帧;t=10.2s 轮到 GHANA,节奏精准
- 控制台 0 错误 0 警告
**A/B 验证**:
- 修复前:加载 6s 未滚入 → ABU DHABI 激活、线完整(陈旧完结帧)
- 修复后:加载 8s 未滚入 → 零激活、所有线 opacity 0
- 滚入 0.6s → DUBAI 从 dashoffset 0.83 开始绘制;3.1s → ABU DHABI;6.3s → ETHIOPIA,循环正常
- 控制台 0 错误 0 警告
## 相关:bento 业务格渐显重播(2026-08-21 三次补充)
用户要求 FOUR CORE BUSINESSES(STRONG RESOURCES, RELIABLE DELIVERY)板块的图片逐个渐显在**每次进入视口时重播**(原 reveal 一次即 unobserve)。
- index.html:5 个 `.cell.reveal` 追加 `reveal-repeat` 标记类(仅此板块)
- main.js reveal 观察器:带 `reveal-repeat` 的元素不 unobserve,按 `isIntersecting` 切换 `.in` — ≥12% 可见整组阶梯重播,完全离开归零;其余元素保持一次性 reveal
- 脚本版本 bump 至 `?v=20260821-2`
验证:滚入 5/5 in → 滚走 0/5 → 滚回 5/5 重播;标题等一次性 reveal 不受影响;控制台 0 错误。
## 验证
- Playwright 打开 index.html:控制台无报错、3s 轮播切换、桌面/移动端尺寸比例正常