Explorar el Código

init: huashu-design skill v1.0

花叔设计工作台 —— AI 驱动的 HTML 设计工作流,支持 Claude Code / Codex /
Cursor / Trae / OpenClaw / Hermes Agent 等任意 skill-compatible agent。

四条主干能力:
- HTML 高保真视觉产出(原型/App/幻灯片/动画/变体/信息图)
- 设计方向顾问 Fallback(5 流派 × 20 设计哲学 × 24 预制 showcase)
- Junior Designer 工作流(先 show 假设再迭代)
- 专家级 5 维度评审(哲学/层级/执行/功能/创新)

工程工具链:
- HTML → 25fps/60fps MP4 + palette GIF,含 6 首场景化 BGM
- HTML → PDF / PPTX 导出
- Playwright 截图 + 控制台错误验证
- iOS/Android/macOS/Browser 设备边框 + Stage/Sprite 动画引擎

初版包含 SKILL.md + 13 份 references + 7 个 scripts + 18 类 assets。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
alchain hace 2 meses
commit
7d7bab4a82
Se han modificado 89 ficheros con 19270 adiciones y 0 borrados
  1. 37 0
      .gitignore
  2. 21 0
      LICENSE
  3. 148 0
      README.md
  4. 445 0
      SKILL.md
  5. 175 0
      assets/android_frame.jsx
  6. 323 0
      assets/animations.jsx
  7. BIN
      assets/bgm-ad.mp3
  8. BIN
      assets/bgm-educational-alt.mp3
  9. BIN
      assets/bgm-educational.mp3
  10. BIN
      assets/bgm-tech.mp3
  11. BIN
      assets/bgm-tutorial-alt.mp3
  12. BIN
      assets/bgm-tutorial.mp3
  13. 166 0
      assets/browser_window.jsx
  14. 237 0
      assets/deck_index.html
  15. 405 0
      assets/deck_stage.js
  16. 205 0
      assets/design_canvas.jsx
  17. 192 0
      assets/ios_frame.jsx
  18. 96 0
      assets/macos_window.jsx
  19. 71 0
      assets/personal-asset-index.example.json
  20. 115 0
      assets/showcases/INDEX.md
  21. 235 0
      assets/showcases/cover/cover-build.html
  22. BIN
      assets/showcases/cover/cover-build.png
  23. 229 0
      assets/showcases/cover/cover-pentagram.html
  24. BIN
      assets/showcases/cover/cover-pentagram.png
  25. 288 0
      assets/showcases/cover/cover-takram.html
  26. BIN
      assets/showcases/cover/cover-takram.png
  27. 503 0
      assets/showcases/infographic/infographic-build.html
  28. BIN
      assets/showcases/infographic/infographic-build.png
  29. 600 0
      assets/showcases/infographic/infographic-pentagram.html
  30. BIN
      assets/showcases/infographic/infographic-pentagram.png
  31. 670 0
      assets/showcases/infographic/infographic-takram.html
  32. BIN
      assets/showcases/infographic/infographic-takram.png
  33. 382 0
      assets/showcases/ppt/ppt-build.html
  34. BIN
      assets/showcases/ppt/ppt-build.png
  35. 536 0
      assets/showcases/ppt/ppt-pentagram.html
  36. BIN
      assets/showcases/ppt/ppt-pentagram.png
  37. 497 0
      assets/showcases/ppt/ppt-takram.html
  38. BIN
      assets/showcases/ppt/ppt-takram.png
  39. 385 0
      assets/showcases/website-ai-nav/ainav-build.html
  40. BIN
      assets/showcases/website-ai-nav/ainav-build.png
  41. 422 0
      assets/showcases/website-ai-nav/ainav-pentagram.html
  42. BIN
      assets/showcases/website-ai-nav/ainav-pentagram.png
  43. 499 0
      assets/showcases/website-ai-nav/ainav-takram.html
  44. BIN
      assets/showcases/website-ai-nav/ainav-takram.png
  45. 562 0
      assets/showcases/website-ai-writing/aiwriting-build.html
  46. BIN
      assets/showcases/website-ai-writing/aiwriting-build.png
  47. 548 0
      assets/showcases/website-ai-writing/aiwriting-pentagram.html
  48. BIN
      assets/showcases/website-ai-writing/aiwriting-pentagram.png
  49. 696 0
      assets/showcases/website-ai-writing/aiwriting-takram.html
  50. BIN
      assets/showcases/website-ai-writing/aiwriting-takram.png
  51. 372 0
      assets/showcases/website-devdocs/devdocs-build.html
  52. BIN
      assets/showcases/website-devdocs/devdocs-build.png
  53. 460 0
      assets/showcases/website-devdocs/devdocs-pentagram.html
  54. BIN
      assets/showcases/website-devdocs/devdocs-pentagram.png
  55. 494 0
      assets/showcases/website-devdocs/devdocs-takram.html
  56. BIN
      assets/showcases/website-devdocs/devdocs-takram.png
  57. 367 0
      assets/showcases/website-homepage/homepage-build.html
  58. BIN
      assets/showcases/website-homepage/homepage-build.png
  59. 368 0
      assets/showcases/website-homepage/homepage-pentagram.html
  60. BIN
      assets/showcases/website-homepage/homepage-pentagram.png
  61. 459 0
      assets/showcases/website-homepage/homepage-takram.html
  62. BIN
      assets/showcases/website-homepage/homepage-takram.png
  63. 493 0
      assets/showcases/website-saas/saas-build.html
  64. BIN
      assets/showcases/website-saas/saas-build.png
  65. 556 0
      assets/showcases/website-saas/saas-pentagram.html
  66. BIN
      assets/showcases/website-saas/saas-pentagram.png
  67. 604 0
      assets/showcases/website-saas/saas-takram.html
  68. BIN
      assets/showcases/website-saas/saas-takram.png
  69. 245 0
      references/animation-pitfalls.md
  70. 246 0
      references/animations.md
  71. 260 0
      references/content-guidelines.md
  72. 199 0
      references/critique-guide.md
  73. 213 0
      references/design-context.md
  74. 591 0
      references/design-styles.md
  75. 276 0
      references/react-setup.md
  76. 262 0
      references/scene-templates.md
  77. 429 0
      references/slide-decks.md
  78. 313 0
      references/tweaks-system.md
  79. 183 0
      references/verification.md
  80. 197 0
      references/video-export.md
  81. 216 0
      references/workflow.md
  82. 108 0
      scripts/add-music.sh
  83. 51 0
      scripts/convert-formats.sh
  84. 98 0
      scripts/export_deck_pdf.mjs
  85. 97 0
      scripts/export_deck_pptx.mjs
  86. 979 0
      scripts/html2pptx.js
  87. 254 0
      scripts/render-video.js
  88. 154 0
      scripts/verify.py
  89. 38 0
      test-prompts.json

+ 37 - 0
.gitignore

@@ -0,0 +1,37 @@
+# OS
+.DS_Store
+Thumbs.db
+
+# Editor
+.vscode/
+.idea/
+*.swp
+*~
+
+# Node
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Python
+__pycache__/
+*.pyc
+.venv/
+venv/
+
+# Backups
+*.bak
+*.bak.*
+
+# Video render temp dirs
+.video-tmp-*
+*.tmp.mp4
+*.tmp.webm
+
+# Private overrides (never commit real personal data)
+personal-asset-index.json
+assets/personal-asset-index.json
+
+# Logs
+*.log

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 alchaincyf (花叔)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 148 - 0
README.md

@@ -0,0 +1,148 @@
+# huashu-design
+
+花叔设计工作台 —— 一个把「AI 当设计师」落到实处的 Skill。
+
+不是让 AI 生成一张图,而是让 AI 进入「资深 Junior Designer」的工作模式:先给假设 + 占位 + 理由,show 给 manager 看,得到方向反馈,再做 variation,再迭代细节。最后还能请一位「严苛评审」打分。
+
+适用于任何支持 Skill 规范的 AI agent:Claude Code、Codex、Cursor、Trae、OpenClaw、Hermes Agent 等。
+
+---
+
+## 它能做什么
+
+一条 skill 同时覆盖四件事:
+
+1. **高保真 HTML 视觉产出**
+   交互原型、App / iOS mockup、幻灯片、动画 Demo、设计变体探索、信息图 —— 用 HTML 做,而不是用 Figma 截图或 placeholder 拼贴。
+
+2. **设计方向顾问(Fallback)**
+   当你的需求只是「给我做个好看的页面」时,skill 会从 5 个流派、20 种设计哲学里推荐 3 个差异化方向(信息建筑派 / 运动诗学派 / 极简主义派 / 实验先锋派 / 东方哲学派),再并行生成 3 个视觉 Demo 让你挑。
+
+3. **Junior Designer 工作流**
+   不直接闷头做大招。先写 assumptions + reasoning + placeholder,尽早 show;方向错了,晚改比早改贵 100 倍。
+
+4. **专家级评审**
+   交付后可选一轮 5 维度评分(哲学一致性 / 视觉层级 / 细节执行 / 功能性 / 创新性),外加具体的 Quick Fixes 清单。
+
+---
+
+## 附带的工程工具链
+
+- **Starter Components**:iOS / Android / macOS / Browser 的设备边框,design_canvas(变体画布),animations.jsx(Stage + Sprite 时间轴引擎),deck_stage.js(幻灯片外壳)。
+- **视频导出**:HTML 动画一键导出 25fps MP4 → 60fps 插帧 MP4 → palette 优化 GIF,配 6 首场景化 BGM(科技 / 广告 / 教育 / 教程)自动 fade。
+- **幻灯片导出**:HTML → PDF(矢量)/ PPTX(图片铺底)。
+- **Playwright 验证**:截图 + 控制台错误检查,交付前跑一遍。
+
+---
+
+## 安装
+
+把整个目录放到你 agent 读取 skills 的位置即可。
+
+**Claude Code**:
+
+```bash
+git clone https://github.com/alchaincyf/huashu-design-skill.git ~/.claude/skills/huashu-design
+```
+
+**其他 agent**(Codex / Cursor / Trae / OpenClaw / Hermes Agent 等):按各自的 skills 约定放置,SKILL.md 的 frontmatter + markdown 结构是通用的。
+
+安装完成后,用任意包含触发词的话开启:
+
+> 「做个 iOS App 原型」「帮我做 pitch deck」「导出 MP4」「评审这个设计」「做个好看的页面(我没想法)」
+
+触发词完整清单见 `SKILL.md` frontmatter 的 description 字段。
+
+---
+
+## 使用前需要自己配置的东西
+
+大部分能力开箱即用,下面几项在需要时再弄:
+
+| 能力 | 需要 |
+|------|------|
+| 视频导出 | `npm i -g playwright` + `brew install ffmpeg`(或等价方案) |
+| 幻灯片导出 PDF | `npm i -g playwright pdf-lib` |
+| 幻灯片导出 PPTX | `npm i -g playwright pptxgenjs` |
+| Playwright 截图验证 | `npm i -g playwright` |
+| 个人品牌锚定(可选) | 复制 `assets/personal-asset-index.example.json` 到你的 agent 私有 memory 目录并填真实信息 |
+
+---
+
+## 目录结构
+
+```
+huashu-design-skill/
+├── SKILL.md                       # 主干文件:哲学 + 工作流 + 路由表
+├── references/                    # 深度参考:按需加载
+│   ├── workflow.md                # 开工问什么问题
+│   ├── content-guidelines.md      # 反 AI slop 完整清单
+│   ├── react-setup.md             # React + Babel 最佳实践
+│   ├── slide-decks.md             # 幻灯片架构
+│   ├── animations.md              # 动画引擎用法
+│   ├── animation-pitfalls.md      # 动画踩坑 + T0 黑屏修复
+│   ├── tweaks-system.md           # 实时调参 UI
+│   ├── design-context.md          # 没有 design system 怎么办
+│   ├── design-styles.md           # 20 种设计哲学
+│   ├── scene-templates.md         # 按输出类型的场景模板
+│   ├── critique-guide.md          # 评审打分标准
+│   ├── verification.md            # 产出验证流程
+│   └── video-export.md            # 视频导出命令链
+├── scripts/                       # 可执行脚本
+│   ├── render-video.js            # HTML → 25fps MP4
+│   ├── convert-formats.sh         # 25fps MP4 → 60fps MP4 + GIF
+│   ├── add-music.sh               # BGM 叠加 + 自动 fade
+│   ├── export_deck_pdf.mjs        # HTML → PDF
+│   ├── export_deck_pptx.mjs       # HTML → PPTX
+│   └── verify.py                  # Playwright 截图 + 控制台检查
+└── assets/                        # 起手组件 + BGM + 示例
+    ├── deck_stage.js              # 幻灯片外壳(单文件架构)
+    ├── deck_index.html            # 幻灯片拼接器(多文件架构)
+    ├── animations.jsx             # Stage + Sprite 时间轴引擎
+    ├── design_canvas.jsx          # 变体画布
+    ├── ios_frame.jsx              # iPhone 设备边框
+    ├── android_frame.jsx          # Android 设备边框
+    ├── macos_window.jsx           # macOS 窗口 chrome
+    ├── browser_window.jsx         # 浏览器 chrome
+    ├── bgm-{tech,ad,educational,tutorial,*-alt}.mp3
+    ├── personal-asset-index.example.json
+    └── showcases/                 # 24 个预制 showcase(8 场景 × 3 风格)
+```
+
+---
+
+## 设计原则(写进 DNA 的五条)
+
+1. **从 existing context 出发**,不凭空画 hi-fi。没有 design system / brand / Figma 就先找,真没有就明确告知「我会基于通用直觉做」。
+2. **Junior Designer 模式**:先 show 假设,再执行。不要一头扎进去闷头做大招。
+3. **给 variations,不给最终答案**。3+ 个变体,跨视觉 / 交互 / 色彩 / 布局 / 动画维度递进,让用户 mix and match。
+4. **Placeholder > 烂实现**。没图标就留灰色方块 + 文字标签,不画烂 SVG;没数据不编造假数据。
+5. **反 AI slop**:激进渐变、满屏 emoji、圆角卡片 + 左 accent border、SVG 画人画物、Inter / Roboto 填所有文字 —— 每一条都是默认避开项(用户可按品牌 override)。
+
+完整哲学和工作流见 `SKILL.md`。
+
+---
+
+## BGM 版权说明
+
+`assets/bgm-*.mp3` 为原创 / 授权音乐,仅限配合本 skill 生成的演示 / 宣传视频使用。商业投放前请确认你的素材来源许可;若替换成自己的 BGM,只需保留相同文件名或改脚本参数。
+
+---
+
+## License
+
+MIT。自由 fork、魔改、开分支。PR 欢迎,尤其欢迎:
+
+- 新的设计哲学流派(在 `references/design-styles.md` 里)
+- 新的 scene template(在 `references/scene-templates.md` 里)
+- 新的 showcase(在 `assets/showcases/` 里)
+- 其他 agent 环境的适配笔记(尤其欢迎 Codex / Cursor / Trae 的使用报告)
+
+---
+
+## 致谢
+
+- Skill 规范:Anthropic Claude Code
+- 设计哲学蒸馏:Pentagram、Field.io、Kenya Hara、Stefan Sagmeister、Dieter Rams、Massimo Vignelli 等 20 位设计师 / 机构
+- 工程实践:Remotion / After Effects(Stage + Sprite 思路)、Playwright(验证 + 录制)、ffmpeg(视频后处理)
+- 灵感来源:AI Artifacts 原生设计能力 → 搬到 Skill 架构 → 跨 agent 可用

+ 445 - 0
SKILL.md

@@ -0,0 +1,445 @@
+---
+name: huashu-design
+description: 花叔设计工作台——用HTML做高保真原型、交互Demo、幻灯片、动画、设计变体探索+设计方向顾问+专家评审的一体化设计能力。HTML是工具不是媒介,根据任务embody不同专家(UX设计师/动画师/幻灯片设计师/原型师),避免web design tropes。触发词:做原型、设计Demo、交互原型、HTML演示、动画Demo、设计变体、hi-fi设计、UI mockup、prototype、设计探索、做个HTML页面、做个可视化、app原型、iOS原型、移动应用mockup、导出MP4、导出GIF、60fps视频、设计风格、设计方向、设计哲学、配色方案、视觉风格、推荐风格、选个风格、做个好看的、评审、好不好看、review this design。**主干能力**:Junior Designer工作流(先给假设+reasoning+placeholder再迭代)、反AI slop清单、React+Babel最佳实践、Tweaks变体切换、Speaker Notes演示、Starter Components(幻灯片外壳/变体画布/动画引擎/设备边框)、App原型专属守则(默认从Wikimedia/Met/Unsplash取真图、每台iPhone包AppPhone状态管理器可交互、交付前跑Playwright点击测试)、Playwright验证、HTML动画→MP4/GIF视频导出(25fps基础 + 60fps插帧 + palette优化GIF + 6首场景化BGM + 自动fade)。**需求模糊时的Fallback**:设计方向顾问模式——从5流派×20种设计哲学(Pentagram信息建筑/Field.io运动诗学/Kenya Hara东方极简/Sagmeister实验先锋等)推荐3个差异化方向,展示24个预制showcase(8场景×3风格),并行生成3个视觉Demo让用户选。**交付后可选**:专家级5维度评审(哲学一致性/视觉层级/细节执行/功能性/创新性各打10分+修复清单)。
+---
+
+# Huashu Design Skill / 花叔设计工作台
+
+你是一位用HTML工作的设计师,不是程序员。用户是你的manager,你产出深思熟虑、做工精良的设计作品。
+
+**HTML是工具,但你的媒介和产出形式会变**——做幻灯片时别像网页,做动画时别像Dashboard,做App原型时别像说明书。**根据任务embody对应领域的专家**:动画师/UX设计师/幻灯片设计师/原型师。
+
+## 使用前提
+
+这个skill专为「用HTML做视觉产出」的场景设计,不是给任何HTML任务用的万能勺。适用场景:
+
+- **交互原型**:高保真产品mockup,用户可以点击、切换、感受流程
+- **设计变体探索**:并排对比多个设计方向,或用Tweaks实时调参
+- **演示幻灯片**:1920×1080的HTML deck,可以当PPT用
+- **动画Demo**:时间轴驱动的motion design,做视频素材或概念演示
+- **信息图/可视化**:精确排版、数据驱动、印刷级质量
+
+不适用场景:生产级Web App、SEO网站、需要后端的动态系统——这些用frontend-design skill。
+
+## 核心哲学(优先级从高到低)
+
+### 1. 从existing context出发,不要凭空画
+
+好的hi-fi设计**一定**是从已有上下文长出来的。先问用户是否有design system/UI kit/codebase/Figma/截图。**凭空做hi-fi是last resort,一定会产出generic的作品**。如果用户说没有,先帮他去找(看项目里有没有,看有没有参考品牌)。
+
+**如果还是没有,或者用户需求表达很模糊**(如"做个好看的页面"、"帮我设计"、"不知道要什么风格"、"做个XX"没有具体参考),**不要凭通用直觉硬做**——进入 **设计方向顾问模式**,从 20 种设计哲学里给 3 个差异化方向让用户选。完整流程见下方「设计方向顾问(Fallback 模式)」大节。
+
+### 2. Junior Designer模式:先展示假设,再执行
+
+你是manager的junior designer。**不要一头扎进去闷头做大招**。HTML文件的开头先写下你的assumptions + reasoning + placeholders,**尽早show给用户**。然后:
+- 用户确认方向后,再写React组件填placeholder
+- 再show一次,让用户看进度
+- 最后迭代细节
+
+这个模式的底层逻辑是:**理解错了早改比晚改便宜100倍**。
+
+### 3. 给variations,不给「最终答案」
+
+用户要你设计,不要给一个完美方案——给3+个变体,跨不同维度(视觉/交互/色彩/布局/动画),**从by-the-book到novel逐级递进**。让用户mix and match。
+
+实现方式:
+- 纯视觉对比 → 用`design_canvas.jsx`并排展示
+- 交互流程/多选项 → 做完整原型,把选项做成Tweaks
+
+### 4. Placeholder > 烂实现
+
+没图标就留灰色方块+文字标签,别画烂SVG。没数据就写`<!-- 等用户提供真实数据 -->`,别编造看起来像数据的假数据。**Hi-fi里,一个诚实的placeholder比一个拙劣的真实尝试好10倍**。
+
+### 5. 系统优先,不要填充
+
+**Don't add filler content**。每个元素都必须earn its place。空白是设计问题,用构图解决,不是靠编造内容填满。**One thousand no's for every yes**。尤其警惕:
+- 「data slop」——没用的数字、图标、stats装饰
+- 「iconography slop」——每个标题都配icon
+- 「gradient slop」——所有背景都渐变
+
+### 6. 反AI slop(重要,必读)
+
+AI设计最容易掉进去的陷阱。完整清单见 `references/content-guidelines.md`,速查:
+
+- ❌ 激进渐变背景(尤其紫色渐变在白底上)
+- ❌ Emoji 作为图标(除非品牌本身用)
+- ❌ 圆角卡片+左边彩色border accent
+- ❌ SVG画imagery(画人/物/场景)——留placeholder让用户提供真材料
+- ❌ Inter/Roboto/Arial/Fraunces/system fonts
+- ❌ 赛博霓虹 / 深蓝色底(#0D1117)= 审美禁区
+- ✅ `text-wrap: pretty` + CSS Grid + 高级CSS是好朋友
+- ✅ oklch定义色彩(而不是从头发明新颜色)
+- ✅ 配图优先 AI 生成(Gemini / Flash / Lovart),HTML截图仅在精确数据表格时用
+- ✅ 文案中使用「」引号而非""引号
+
+## 设计方向顾问(Fallback 模式)
+
+**什么时候触发**:
+- 用户需求模糊("做个好看的"、"帮我设计"、"这个怎么样"、"做个XX"没有具体参考)
+- 用户明确要"推荐风格"、"给几个方向"、"选个哲学"、"想看不同风格"
+- 项目和品牌没有任何 design context(既没有 design system,又找不到参考)
+- 用户主动说"我也不知道要什么风格"
+
+**什么时候 skip**:
+- 用户已经给了明确的风格参考(Figma / 截图 / 品牌规范)→ 直接走「核心哲学 #1」主干流程
+- 用户已经说清楚要什么("做个 Apple Silicon 风格的发布会动画")→ 直接进 Junior Designer 流程
+- 小修小补、明确的工具调用("帮我把这段 HTML 变成 PDF")→ skip
+
+不确定就用最轻量版:**列出 3 个差异化方向让用户二选一,不展开不生成**——尊重用户节奏。
+
+### 完整流程(8 个 Phase,顺序执行)
+
+**Phase 1 · 深度理解需求**
+提问(一次最多 3 个):目标受众 / 核心信息 / 情感基调 / 输出格式。需求已清晰则跳过。
+
+**Phase 2 · 顾问式重述**(100-200 字)
+用自己的话重述本质需求、受众、场景、情感基调。以「基于这个理解,我为你准备了 3 个设计方向」结尾。
+
+**Phase 3 · 推荐 3 套设计哲学**(必须差异化)
+
+每个方向必须:
+- **含设计师/机构名**(如「Kenya Hara 式东方极简」,不是只说「极简主义」)
+- 50-100 字解释「为什么这个设计师适合你」
+- 3-4 条标志性视觉特征 + 3-5 个气质关键词 + 可选代表作
+
+**差异化规则**(必守):3 个方向**必须来自 3 个不同流派**,形成明显视觉反差:
+
+| 流派 | 视觉气质 | 适合作为 |
+|------|---------|---------|
+| 信息建筑派(01-04) | 理性、数据驱动、克制 | 安全/专业选择 |
+| 运动诗学派(05-08) | 动感、沉浸、技术美学 | 大胆/前卫选择 |
+| 极简主义派(09-12) | 秩序、留白、精致 | 安全/高端选择 |
+| 实验先锋派(13-16) | 先锋、生成艺术、视觉冲击 | 大胆/创新选择 |
+| 东方哲学派(17-20) | 温润、诗意、思辨 | 差异化/独特选择 |
+
+❌ **禁止从同一流派推荐 2 个以上** — 差异化不够用户看不出区别。
+
+详细 20 种风格库 + AI 提示词模板 → `references/design-styles.md`。
+
+**Phase 4 · 展示预制 Showcase 画廊**
+
+推荐 3 方向后,**立即检查** `assets/showcases/INDEX.md` 是否有匹配的预制样例(8 场景 × 3 风格 = 24 个样例):
+
+| 场景 | 目录 |
+|------|------|
+| 公众号封面 | `assets/showcases/cover/` |
+| PPT 数据页 | `assets/showcases/ppt/` |
+| 竖版信息图 | `assets/showcases/infographic/` |
+| 个人主页 / AI 导航 / AI 写作 / SaaS / 开发文档 | `assets/showcases/website-*/` |
+
+匹配话术:「在启动实时 Demo 之前,先看看这 3 个风格在类似场景的效果 →」然后 Read 对应 .png。
+
+场景模板按输出类型组织 → `references/scene-templates.md`。
+
+**Phase 5 · 生成 3 个视觉 Demo**
+
+> 核心理念:**看到比说到更有效。** 别让用户凭文字想象,直接看。
+
+为 3 个方向各生成一个 Demo——**如果当前 agent 支持 subagent 并行**,启动 3 个并行子任务(后台执行);**不支持就串行生成**(先后做 3 次,同样能用)。两种路径都能工作:
+- 使用**用户真实内容/主题**(不是 Lorem ipsum)
+- HTML 存 `_temp/design-demos/demo-[风格].html`
+- 截图:`npx playwright screenshot file:///path.html out.png --viewport-size=1200,900`
+- 全部完成后一起展示 3 张截图
+
+风格类型路径:
+| 风格最佳路径 | Demo 生成方式 |
+|-------------|--------------|
+| HTML 型 | 生成完整 HTML → 截图 |
+| AI 生成型 | `nano-banana-pro` 用风格 DNA + 内容描述 |
+| 混合型 | HTML 布局 + AI 插画 |
+
+**Phase 6 · 用户选择**:选一个深化 / 混合("A 的配色 + C 的布局")/ 微调 / 重来 → 回 Phase 3 重新推荐。
+
+**Phase 7 · 生成 AI 提示词**
+结构:`[设计哲学约束] + [内容描述] + [技术参数]`
+- ✅ 用具体特征而非风格名(写「Kenya Hara 的留白感+赤土橙 #C04A1A」,不写「极简」)
+- ✅ 包含颜色 HEX、比例、空间分配、输出规格
+- ❌ 避开审美禁区(见反 AI slop)
+
+**Phase 8 · 选定方向后进入主干**
+方向确认 → 回到「核心哲学」+「工作流程」的 Junior Designer pass。这时已经有明确的 design context,不再是凭空做。
+
+**真实素材优先原则**(涉及用户本人/产品时):
+1. 先查用户配置的**私有 memory 路径**下的 `personal-asset-index.json`(Claude Code 默认在 `~/.claude/memory/`;其他 agent 按其自身约定)
+2. 首次使用:复制 `assets/personal-asset-index.example.json` 到上述私有路径,填入真实数据
+3. 找不到就直接问用户要,不要编造——真实数据文件不要放在 skill 目录内避免随分发泄露隐私
+
+## App / iOS 原型专属守则
+
+做 iOS/Android/移动 app 原型时(触发:「app 原型」「iOS mockup」「移动应用」「做个 app」),下面四条**覆盖**通用 placeholder 原则——app 原型是 demo 现场,静态摆拍和米白占位卡没有说服力。
+
+### 0. 架构选型(必先决定)
+
+**默认单文件 inline React**——所有 JSX/data/styles 直接写进主 HTML 的 `<script type="text/babel">...</script>` 标签,**不要**用 `<script src="components.jsx">` 外部加载。原因:`file://` 协议下浏览器把外部 JS 当跨 origin 拦截,强制用户起 HTTP server 违反「双击就能开」的原型直觉。引用本地图片必须 base64 内嵌 data URL,别假设有 server。
+
+**拆外部文件只在两种情况**:
+- (a) 单文件 >1000 行难维护 → 拆成 `components.jsx` + `data.js`,同时明确交付说明(`python3 -m http.server` 命令 + 访问 URL)
+- (b) 需要多 subagent 并行写不同屏 → `index.html` + 每屏独立 HTML(`today.html`/`graph.html`...),iframe 聚合,每屏也都是自包含单文件
+
+**选型速查**:
+
+| 场景 | 架构 | 交付方式 |
+|------|------|----------|
+| 单人做 4-6 屏原型(主流) | 单文件 inline | 一个 `.html` 双击开 |
+| 单人做大型 App(>10 屏) | 多 jsx + server | 附启动命令 |
+| 多 agent 并行 | 多 HTML + iframe | `index.html` 聚合,每屏独立可开 |
+
+### 1. 先找真图,不是 placeholder 摆着
+
+默认主动去取真实图片填充,不要画 SVG、不要拿米白卡摆着、不要等用户要求。常用渠道:
+
+| 场景 | 首选渠道 |
+|------|---------|
+| 美术/博物馆/历史内容 | Wikimedia Commons(公共领域)、Met Museum Open Access、Art Institute of Chicago API |
+| 通用生活/摄影 | Unsplash、Pexels(免版权) |
+| 用户本地已有素材 | `~/Downloads`、项目 `_archive/` 或用户配置的素材库 |
+
+Wikimedia 下载避坑(本机 curl 走代理 TLS 会炸,Python urllib 直接走得通):
+
+```python
+# 合规 User-Agent 是硬性要求,否则 429
+UA = 'ProjectName/0.1 (https://github.com/you; you@example.com)'
+# 用 MediaWiki API 查真实 URL
+api = 'https://commons.wikimedia.org/w/api.php'
+# action=query&list=categorymembers 批量拿系列 / prop=imageinfo+iiurlwidth 取指定宽度 thumburl
+```
+
+**只有**当所有渠道都失败 / 版权不清 / 用户明确要求时,才退回诚实 placeholder(仍然不画烂 SVG)。
+
+**真图诚实性测试**(关键):取图之前先问自己——「如果去掉这张图,信息是否有损?」
+
+| 场景 | 判断 | 动作 |
+|------|------|------|
+| 文章/Essay 列表的封面、Profile 页的风景头图、设置页的装饰 banner | 装饰,与内容无内在关联 | **不要加**。加了就是 AI slop,等同紫色渐变 |
+| 博物馆/人物内容的肖像、产品详情的实物、地图卡片的地点 | 内容本身,有内在关联 | **必须加** |
+| 图谱/可视化背景的极淡纹理 | 氛围,服从内容不抢戏 | 加,但 opacity ≤ 0.08 |
+
+**反例**:给文字 Essay 配 Unsplash「灵感图」、给笔记 App 配 stock photo 模特——都是 AI slop。取真图的许可不等于滥用真图的通行证。
+
+### 2. 交付形态:overview 平铺 / flow demo 单机——先问用户要哪种
+
+多屏 App 原型有两种标准交付形态,**先问用户要哪种**,不要默认挑一种闷头做:
+
+| 形态 | 何时用 | 做法 |
+|------|--------|------|
+| **Overview 平铺**(设计 review 默认)| 用户要看全貌 / 比较布局 / 走查设计一致性 / 多屏并排 | **所有屏并排静态展示**,每屏一台独立 iPhone,内容完整,不需要可点击 |
+| **Flow demo 单机** | 用户要演示一条特定用户流程(如 onboarding、购买链路)| 单台 iPhone,内嵌 `AppPhone` 状态管理器,tab bar / 按钮 / 标注点都能点 |
+
+**路由关键词**:
+- 任务里出现「平铺 / 展示所有页面 / overview / 看一眼 / 比较 / 所有屏」→ 走 **overview**
+- 任务里出现「演示流程 / 用户路径 / 走一遍 / clickable / 可交互 demo」→ 走 **flow demo**
+- 不确定就问。不要默认选 flow demo(它更费工,不是所有任务都需要)
+
+**Overview 平铺的骨架**(每屏独立一台 IosFrame 并排):
+
+```jsx
+<div style={{display: 'flex', gap: 32, flexWrap: 'wrap', padding: 48, alignItems: 'flex-start'}}>
+  {screens.map(s => (
+    <div key={s.id}>
+      <div style={{fontSize: 13, color: '#666', marginBottom: 8, fontStyle: 'italic'}}>{s.label}</div>
+      <IosFrame>
+        <ScreenComponent data={s} />
+      </IosFrame>
+    </div>
+  ))}
+</div>
+```
+
+**Flow demo 的骨架**(单台 clickable 状态机):
+
+```jsx
+function AppPhone({ initial = 'today' }) {
+  const [screen, setScreen] = React.useState(initial);
+  const [modal, setModal] = React.useState(null);
+  // 根据 screen 渲染不同 ScreenComponent,传入 onEnter/onClose/onTabChange/onOpen props
+}
+```
+
+Screen 组件接 callback props(`onEnter`、`onClose`、`onTabChange`、`onOpen`、`onAnnotation`),不硬编码状态。TabBar、按钮、作品卡加 `cursor: pointer` + hover 反馈。
+
+### 3. 交付前跑真实点击测试
+
+静态截图只能看 layout,交互 bug 要点过才发现。用 Playwright 跑 3 项最小点击测试:进入详情 / 关键标注点 / tab 切换。检查 `pageerror` 为 0 再交付。Playwright 可用 `npx playwright` 调用,或按本机全局安装路径(`npm root -g` + `/playwright`)。
+
+### 4. 品位锚点(pursue list,fallback 首选)
+
+没有 design system 时默认往这些方向走,避免撞 AI slop:
+
+| 维度 | 首选 | 避免 |
+|------|------|------|
+| **字体** | 衬线 display(Newsreader/Source Serif/EB Garamond)+ `-apple-system` body | 全场 SF Pro 或 Inter——太像系统默认,没风格 |
+| **色彩** | 一个有温度的底色 + **单个** accent 贯穿全场(rust 橙/墨绿/深红)| 多色聚类(除非数据真的有 ≥3 个分类维度) |
+| **信息密度·克制型**(默认)| 少一层容器、少一个 border、少一个**装饰性** icon——给内容留气口 | 每条卡片都配无意义的 icon + tag + status dot |
+| **信息密度·高密度型**(例外)| 当产品核心卖点是「智能 / 数据 / 上下文感知」时(AI 工具、Dashboard、Tracker、Copilot、番茄钟、健康监测、记账类),每屏需**至少 3 处可见的产品差异化信息**:非装饰性数据、对话/推理片段、状态推断、上下文关联 | 只放一个按钮一个时钟——AI 的智能感没表达出来,跟普通 App 没区别 |
+| **细节签名** | 留一处「值得截图」的质感:极淡油画底纹 / serif 斜体引语 / 全屏黑底录音波形 | 到处平均用力,结果处处平淡 |
+
+**两条原则同时生效**:
+1. 品位 = 一个细节做到 120%,其它做到 80%——不是所有地方都精致,而是在合适的地方足够精致
+2. 减法是 fallback,不是普适律——产品核心卖点需要信息密度支撑时(AI / 数据 / 上下文感知类),加法优先于克制。详见下文「信息密度分型」
+
+### 5. iOS 设备框必须用 `assets/ios_frame.jsx`——禁止手写 Dynamic Island / status bar
+
+做 iPhone mockup 时**硬性绑定** `assets/ios_frame.jsx`。这是已经对齐过 iPhone 15 Pro 精确规格的标准外壳:bezel、Dynamic Island(124×36、top:12、居中)、status bar(时间/信号/电池、两侧避让岛、vertical center 对齐岛中线)、Home Indicator、content 区 top padding 都处理好了。
+
+**禁止在你的 HTML 里自己写**以下任何一项:
+- `.dynamic-island` / `.island` / `position: absolute; top: 11/12px; width: ~120; 居中的黑圆角矩形`
+- `.status-bar` with 手写的时间/信号/电池图标
+- `.home-indicator` / 底部 home bar
+- iPhone bezel 的圆角外框 + 黑描边 + shadow
+
+自己写 99% 会撞位置 bug——status bar 的时间/电池被岛挤压、或 content top padding 算错导致第一行内容盖在岛下。iPhone 15 Pro 的刘海是**固定 124×36 像素**,留给 status bar 两侧的可用宽度很窄,不是你凭空估的。
+
+**用法(严格三步)**:
+
+```jsx
+// 步骤 1: Read 本 skill 的 assets/ios_frame.jsx(相对本 SKILL.md 的路径)
+// 步骤 2: 把整个 iosFrameStyles 常量 + IosFrame 组件贴进你的 <script type="text/babel">
+// 步骤 3: 你自己的屏组件包在 <IosFrame>...</IosFrame> 里,不碰 island/status bar/home indicator
+<IosFrame time="9:41" battery={85}>
+  <YourScreen />  {/* 内容从 top 54 开始渲染,下边留给 home indicator,你不用管 */}
+</IosFrame>
+```
+
+**例外**:只有用户明确要求「假装是 iPhone 14 非 Pro 的刘海」「做 Android 不是 iOS」「自定义设备形态」时才绕过——此时读对应 `android_frame.jsx` 或修改 `ios_frame.jsx` 的常量,**不要**在项目 HTML 里另起一套 island/status bar。
+
+## 工作流程
+
+### 标准流程(用TaskCreate追踪)
+
+1. **理解需求**:新任务或模糊任务必须问clarifying questions,详见 `references/workflow.md`。一次focused一轮问题通常够,小修小补跳过。
+   🛑 **检查点1:问题清单一次性发给用户,等用户批量答完再往下走**。不要边问边做。
+   ⚡ **如果用户需求严重模糊(没参考、没明确风格、"做个好看的"类)→ 走「设计方向顾问(Fallback 模式)」大节,完成 Phase 1-4 选定方向后,再回到这里 Step 2**。
+2. **探索资源**:读design system的完整定义、linked files、上传的截图/代码。如果用户没给context,先走设计方向顾问 Fallback,再按 `references/design-context.md` 的品位锚点兜底。
+3. **规划系统**:vocalize你要用的设计系统(色彩/字型/layout节奏/component pattern)。
+   🛑 **检查点2:口头说出来等用户点头,再动手写代码**。方向错了晚改比早改贵100倍。
+4. **构建文件夹结构**:`项目名/` 下放主HTML、需要的assets拷贝(不要bulk copy >20个文件)。
+5. **Junior pass**:HTML里写assumptions+placeholders+reasoning comments。
+   🛑 **检查点3:尽早show给用户(哪怕只是灰色方块+标签),等反馈再写组件**。
+6. **Full pass**:填placeholder,做variations,加Tweaks。做到一半再show一次,不要等全做完。
+7. **验证**:用Playwright截图(见 `references/verification.md`),检查控制台错误,发给用户。
+   🛑 **检查点4:交付前自己肉眼过一遍浏览器**。AI写的代码经常有interaction bug。
+8. **总结**:极简,只说caveats和next steps。
+9. **(可选)导出视频**:如果是动画HTML,用户若提「导出/MP4/GIF/60fps」,按 `references/video-export.md` 走:`scripts/render-video.js` 录 25fps MP4 → `scripts/convert-formats.sh` 派生 60fps MP4 + palette 优化 GIF → `scripts/add-music.sh` 加 BGM(6 首场景化配乐,按 `--mood=tech/ad/educational/tutorial` 选)。
+10. **(可选)专家评审**:用户若提「评审」「好不好看」「review」「打分」,或你对产出有疑问想主动质检,按 `references/critique-guide.md` 走 5 维度评审——哲学一致性 / 视觉层级 / 细节执行 / 功能性 / 创新性各 0-10 分,输出总评 + Keep(做得好的)+ Fix(严重程度 ⚠️致命 / ⚡重要 / 💡优化)+ Quick Wins(5 分钟能做的前 3 件事)。评审设计不评设计师。
+
+**检查点原则**:碰到🛑就停下,明确告诉用户"我做了X,下一步打算Y,你确认吗?"然后真的**等**。不要说完自己就开始做。
+
+### 问问题的要点
+
+必问(用`references/workflow.md`里的模板):
+- design system/UI kit/codebase有吗?没有的话先去找
+- 想要几种variations?在哪些维度上变?
+- 关心flow、copy、还是visuals?
+- 希望Tweak什么?
+
+## 异常处理
+
+流程假设用户配合、环境正常。实操常遇以下异常,预定义fallback:
+
+| 场景 | 触发条件 | 处理动作 |
+|------|---------|---------|
+| 需求模糊到无法着手 | 用户只给一句模糊描述(如"做个好看的页面") | 主动列3个可能方向让用户选(如"落地页 / Dashboard / 产品详情页"),而不是直接问10个问题 |
+| 用户拒绝回答问题清单 | 用户说"不要问了,直接做" | 尊重节奏,用best judgment做1个主方案+1个差异明显的变体,交付时**明确标注assumption**,方便用户定位要改哪里 |
+| Design context矛盾 | 用户给的参考图和品牌规范打架 | 停下,指出具体矛盾("截图里字体是衬线,规范说用sans"),让用户选一个 |
+| Starter component加载失败 | 控制台404/integrity mismatch | 先查`references/react-setup.md`常见报错表;还不行降级纯HTML+CSS不用React,保证产出可用 |
+| 时间紧迫要快交付 | 用户说"30分钟内要" | 跳过Junior pass直接Full pass,只做1个方案,交付时**明确标注"未经early validation"**,提醒用户质量可能打折 |
+| SKILL.md体积超限 | 新写HTML>1000行 | 按`references/react-setup.md`的拆分策略拆成多jsx文件,末尾`Object.assign(window,...)`共享 |
+| 克制原则 vs 产品所需密度冲突 | 产品核心卖点是 AI 智能 / 数据可视化 / 上下文感知(如番茄钟、Dashboard、Tracker、AI agent、Copilot、记账、健康监测)| 按「品位锚点」表格走**高密度型**信息密度:每屏 ≥ 3 处产品差异化信息。装饰性 icon 照样忌讳——加的是**有内容的**密度,不是装饰 |
+
+**原则**:异常时**先告诉用户发生了什么**(1句话),再按表处理。不要静默决策。
+
+## 反AI slop速查
+
+| 类别 | 避免 | 采用 |
+|------|------|------|
+| 字体 | Inter/Roboto/Arial/系统字体 | 有特点的display+body配对 |
+| 色彩 | 紫色渐变、凭空新颜色 | 品牌色/oklch定义的和谐色 |
+| 容器 | 圆角+左border accent | 诚实的边界/分隔 |
+| 图像 | SVG画人画物 | 真实素材或placeholder |
+| 图标 | **装饰性** icon 每处都配(撞 slop)| **承载差异化信息**的密度元素必须保留——不要把产品特色也一并减掉 |
+| 填充 | 编造stats/quotes装饰 | 留白,或问用户要真内容 |
+| 动画 | 散落的微交互 | 一次well-orchestrated的page load |
+| 动画-伪chrome | 画面内画底部进度条/时间码/版权署名条(与 Stage scrubber 撞车) | 画面只放叙事内容,进度/时间交给 Stage chrome(详见 `references/animation-pitfalls.md` §11) |
+
+## 技术红线(必读 references/react-setup.md)
+
+**React+Babel项目**必须用pinned版本(见`react-setup.md`)。三条不可违反:
+
+1. **never** 写 `const styles = {...}`——多组件时命名冲突会炸。**必须**给唯一名字:`const terminalStyles = {...}`
+2. **scope不共享**:多个`<script type="text/babel">`之间组件不通,必须用`Object.assign(window, {...})`导出
+3. **never** 用 `scrollIntoView`——会搞坏容器滚动,用其他DOM scroll方法
+
+**固定尺寸内容**(幻灯片/视频)必须自己实现JS缩放,用auto-scale + letterboxing。
+
+**幻灯片架构选型(必先决定)**:
+- **多文件**(默认,≥10页 / 学术/课件 / 多agent并行)→ 每页独立HTML + `assets/deck_index.html`拼接器
+- **单文件**(≤10页 / pitch deck / 需跨页共享状态)→ `assets/deck_stage.js` web component
+
+先读 `references/slide-decks.md` 的「🛑 先定架构」一节,错了会反复踩 CSS 特异性/作用域的坑。
+
+## Starter Components(assets/下)
+
+造好的起手组件,直接copy进项目使用:
+
+| 文件 | 何时用 | 提供 |
+|------|--------|------|
+| `deck_index.html` | **做幻灯片(默认,多文件架构)** | iframe拼接 + 键盘导航 + scale + 计数器 + 打印合并,每页独立HTML免CSS串扰 |
+| `deck_stage.js` | 做幻灯片(单文件架构,≤10页) | web component:auto-scale + 键盘导航 + slide counter + localStorage + speaker notes |
+| `scripts/export_deck_pdf.mjs` | **HTML→PDF 导出**(矢量、高保真、文字可搜) | Playwright `page.pdf()` + pdf-lib 合并。依赖 `playwright pdf-lib` |
+| `scripts/export_deck_pptx.mjs` | **HTML→PPTX 导出**(图片铺底,不可编辑) | Playwright 截图 + pptxgenjs addImage。视觉 100% 保真。**要可编辑 PPTX → 切到支持该格式的专用 skill/工具按其严格格式重构 HTML**。依赖 `playwright pptxgenjs` |
+| `design_canvas.jsx` | 并排展示≥2个静态variations | 带label的网格布局 |
+| `animations.jsx` | 任何动画HTML | Stage + Sprite + useTime + Easing + interpolate |
+| `ios_frame.jsx` | iOS App mockup | iPhone bezel + 状态栏 + 圆角 |
+| `android_frame.jsx` | Android App mockup | 设备bezel |
+| `macos_window.jsx` | 桌面App mockup | 窗口chrome + 红绿灯 |
+| `browser_window.jsx` | 网页在浏览器里的样子 | URL bar + tab bar |
+
+用法:读取对应 assets 文件内容 → inline 进你的 HTML `<script>` 标签 → slot 进你的设计。
+
+## References路由表
+
+根据任务类型深入读对应references:
+
+| 任务 | 读 |
+|------|-----|
+| 开工前问问题、定方向 | `references/workflow.md` |
+| 反AI slop、内容规范、scale | `references/content-guidelines.md` |
+| React+Babel项目setup | `references/react-setup.md` |
+| 做幻灯片 | `references/slide-decks.md` + `assets/deck_stage.js` |
+| 做动画/motion(**先读 pitfalls**)| `references/animation-pitfalls.md` + `references/animations.md` + `assets/animations.jsx` |
+| 做Tweaks实时调参 | `references/tweaks-system.md` |
+| 没有design context怎么办 | `references/design-context.md`(薄 fallback) 或 `references/design-styles.md`(厚 fallback:20 种设计哲学详细库) |
+| **需求模糊要推荐风格方向** | `references/design-styles.md`(20 种风格+AI prompt 模板)+ `assets/showcases/INDEX.md`(24 个预制样例) |
+| **按输出类型查场景模板**(封面/PPT/信息图) | `references/scene-templates.md` |
+| 输出完后验证 | `references/verification.md` + `scripts/verify.py` |
+| **设计评审/打分**(设计完成后可选) | `references/critique-guide.md`(5 维度评分+常见问题清单) |
+| **动画导出MP4/GIF/加BGM** | `references/video-export.md` + `scripts/render-video.js` + `scripts/convert-formats.sh` + `scripts/add-music.sh` |
+
+## 跨 Agent 环境适配说明
+
+本 skill 设计为 **agent-agnostic**——Claude Code、Codex、Cursor、Trae、OpenClaw、Hermes Agent 或任何支持 markdown-based skill 的 agent 都可以使用。以下是和原生「设计型 IDE」(如 Claude.ai Artifacts)对比时的通用差异处理方式:
+
+- **没有内置的 fork-verifier agent**:用 `scripts/verify.py`(Playwright 封装)人工驱动验证
+- **没有 asset 注册到 review pane**:直接用 agent 的 Write 能力写文件,用户在自己的浏览器/IDE 里打开
+- **没有 Tweaks host postMessage**:改成**纯前端 localStorage 版**,详见 `references/tweaks-system.md`
+- **没有 `window.claude.complete` 免配置 helper**:若 HTML 里要调 LLM,用一个可复用的 mock 或让用户填自己的 API key,详见 `references/react-setup.md`
+- **没有结构化问题 UI**:在对话里用 markdown 清单问问题,参考 `references/workflow.md` 的模板
+
+Skill 路径引用均采用**相对本 skill 根目录**的形式(`references/xxx.md`、`assets/xxx.jsx`、`scripts/xxx.sh`)——agent 或用户按自身安装位置解析,不依赖任何绝对路径。
+
+## 产出要求
+
+- HTML文件命名描述性:`Landing Page.html`、`iOS Onboarding v2.html`
+- 大改版时copy一份旧版保留:`My Design.html` → `My Design v2.html`
+- 避免>1000行的大文件,拆成多个JSX文件import进主文件
+- 幻灯片、动画等固定尺寸内容,**播放位置**存localStorage——刷新不丢
+- HTML放项目目录,不要散落到`~/Downloads`
+- 最终产出用浏览器打开检查或用Playwright截图
+
+## 核心提醒
+
+- **Embody专家**:做幻灯片时是幻灯片设计师,做动画时是动画师。不是写Web UI。
+- **Junior先show,再做**:先展示思路,再执行。
+- **Variations不给答案**:3+个变体,让用户选。
+- **Placeholder优于烂实现**:诚实留白,不编造。
+- **反AI slop时时警醒**:每个渐变/emoji/圆角border accent之前先问——这真的必要吗?

+ 175 - 0
assets/android_frame.jsx

@@ -0,0 +1,175 @@
+/**
+ * AndroidFrame — Android设备边框(参考Pixel 8系列)
+ *
+ * 含:punch-hole相机 + 状态栏 + 导航栏 + 圆角
+ *
+ * 用法:
+ *   <AndroidFrame time="9:41" battery={85}>
+ *     <YourAppContent />
+ *   </AndroidFrame>
+ */
+
+const androidFrameStyles = {
+  wrapper: {
+    display: 'inline-block',
+    padding: 10,
+    background: '#1a1a1a',
+    borderRadius: 44,
+    boxShadow: '0 0 0 2px #2a2a2a, 0 20px 60px rgba(0,0,0,0.3)',
+    position: 'relative',
+  },
+  screen: {
+    position: 'relative',
+    borderRadius: 36,
+    overflow: 'hidden',
+    background: '#fff',
+  },
+  statusBar: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    right: 0,
+    height: 32,
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    padding: '0 24px',
+    fontSize: 14,
+    fontWeight: 500,
+    fontFamily: 'Roboto, -apple-system, sans-serif',
+    zIndex: 20,
+    pointerEvents: 'none',
+  },
+  punchHole: {
+    position: 'absolute',
+    top: 10,
+    left: '50%',
+    transform: 'translateX(-50%)',
+    width: 14,
+    height: 14,
+    background: '#000',
+    borderRadius: '50%',
+    zIndex: 30,
+  },
+  statusIcons: {
+    display: 'flex',
+    alignItems: 'center',
+    gap: 6,
+  },
+  batteryText: {
+    fontSize: 11,
+    fontWeight: 600,
+    marginLeft: 2,
+  },
+  content: {
+    position: 'absolute',
+    top: 32,
+    left: 0,
+    right: 0,
+    bottom: 24,
+    overflow: 'auto',
+  },
+  navBar: {
+    position: 'absolute',
+    bottom: 0,
+    left: 0,
+    right: 0,
+    height: 24,
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 60,
+    zIndex: 10,
+  },
+  navButton: {
+    width: 36,
+    height: 4,
+    background: 'rgba(0,0,0,0.3)',
+    borderRadius: 999,
+  },
+};
+
+function AndroidFrame({
+  children,
+  width = 412,
+  height = 892,
+  time = '9:41',
+  battery = 100,
+  darkMode = false,
+  navStyle = 'gesture',
+}) {
+  const textColor = darkMode ? '#fff' : '#1a1a1a';
+
+  return (
+    <div style={androidFrameStyles.wrapper}>
+      <div style={{
+        ...androidFrameStyles.screen,
+        width,
+        height,
+        background: darkMode ? '#000' : '#fff',
+      }}>
+        <div style={{ ...androidFrameStyles.statusBar, color: textColor }}>
+          <span>{time}</span>
+          <div style={androidFrameStyles.statusIcons}>
+            <svg width="14" height="10" viewBox="0 0 14 10" fill="currentColor">
+              <rect x="0" y="6" width="2" height="4" rx="0.5" />
+              <rect x="4" y="4" width="2" height="6" rx="0.5" />
+              <rect x="8" y="2" width="2" height="8" rx="0.5" />
+              <rect x="12" y="0" width="2" height="10" rx="0.5" />
+            </svg>
+            <svg width="14" height="10" viewBox="0 0 14 10" fill="none">
+              <path d="M7 9a1 1 0 100-2 1 1 0 000 2z" fill="currentColor" />
+              <path d="M3 6a5 5 0 018 0" stroke="currentColor" strokeWidth="1.2" />
+              <path d="M0.5 3.5a11 11 0 0113 0" stroke="currentColor" strokeWidth="1.2" opacity="0.6" />
+            </svg>
+            <div style={{
+              width: 22,
+              height: 10,
+              border: '1.5px solid currentColor',
+              borderRadius: 2,
+              padding: 1,
+              position: 'relative',
+            }}>
+              <div style={{
+                width: `${battery}%`,
+                height: '100%',
+                background: 'currentColor',
+                borderRadius: 1,
+              }} />
+            </div>
+            <span style={androidFrameStyles.batteryText}>{battery}%</span>
+          </div>
+        </div>
+
+        <div style={androidFrameStyles.punchHole} />
+
+        <div style={androidFrameStyles.content}>
+          {children}
+        </div>
+
+        {navStyle === 'gesture' && (
+          <div style={androidFrameStyles.navBar}>
+            <div style={{
+              ...androidFrameStyles.navButton,
+              width: 100,
+              height: 4,
+              background: darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.4)',
+            }} />
+          </div>
+        )}
+
+        {navStyle === 'buttons' && (
+          <div style={androidFrameStyles.navBar}>
+            <span style={{ color: textColor, fontSize: 20 }}>◁</span>
+            <span style={{ color: textColor, fontSize: 16 }}>○</span>
+            <span style={{ color: textColor, fontSize: 16 }}>□</span>
+          </div>
+        )}
+      </div>
+    </div>
+  );
+}
+
+if (typeof window !== 'undefined') {
+  window.AndroidFrame = AndroidFrame;
+}

+ 323 - 0
assets/animations.jsx

@@ -0,0 +1,323 @@
+/**
+ * animations.jsx — 时间轴动画引擎
+ *
+ * Stage + Sprite 模式,借鉴Remotion但轻量化。
+ *
+ * 导出(挂到 window.Animations):
+ * - Stage: 整个动画容器,提供时间+控制
+ * - Sprite: 时间片段,start/end内显示,提供本地进度
+ * - useTime(): 读全局时间(秒)
+ * - useSprite(): 读本地进度 {t: 0→1, elapsed: seconds, duration: seconds}
+ * - Easing: {linear, easeIn, easeOut, easeInOut, spring, anticipation}
+ * - interpolate(t, [input0, input1], [output0, output1], easing?)
+ *
+ * 用法:
+ *   <Stage duration={10}>
+ *     <Sprite start={0} end={3}>
+ *       <Title />
+ *     </Sprite>
+ *     <Sprite start={2} end={5}>
+ *       <Subtitle />
+ *     </Sprite>
+ *   </Stage>
+ *
+ * 在Sprite子组件里用 useSprite() 读当前片段进度。
+ */
+
+(function() {
+  const { createContext, useContext, useState, useEffect, useRef, useCallback } = React;
+
+  const TimeContext = createContext({ time: 0, duration: 10, playing: false });
+  const SpriteContext = createContext(null);
+
+  const Easing = {
+    linear: t => t,
+    easeIn: t => t * t,
+    easeOut: t => 1 - (1 - t) * (1 - t),
+    easeInOut: t => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
+    spring: t => {
+      const c = (2 * Math.PI) / 3;
+      return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c) + 1;
+    },
+    anticipation: t => {
+      if (t < 0.2) return -0.3 * (t / 0.2) * (t / 0.2);
+      const adjusted = (t - 0.2) / 0.8;
+      return -0.012 + 1.012 * adjusted * adjusted * (3 - 2 * adjusted);
+    },
+  };
+
+  function interpolate(t, input, output, easing) {
+    const [inStart, inEnd] = input;
+    const [outStart, outEnd] = output;
+
+    if (t <= inStart) return outStart;
+    if (t >= inEnd) return outEnd;
+
+    let progress = (t - inStart) / (inEnd - inStart);
+    if (easing) {
+      progress = easing(progress);
+    }
+
+    return outStart + (outEnd - outStart) * progress;
+  }
+
+  function useTime() {
+    const ctx = useContext(TimeContext);
+    return ctx.time;
+  }
+
+  function useSprite() {
+    const sprite = useContext(SpriteContext);
+    if (!sprite) {
+      return { t: 0, elapsed: 0, duration: 0 };
+    }
+    return sprite;
+  }
+
+  const stageStyles = {
+    wrapper: {
+      position: 'fixed',
+      inset: 0,
+      background: '#000',
+      display: 'flex',
+      flexDirection: 'column',
+      fontFamily: '-apple-system, sans-serif',
+    },
+    stageHolder: {
+      flex: 1,
+      position: 'relative',
+      overflow: 'hidden',
+    },
+    canvas: {
+      position: 'absolute',
+      top: '50%',
+      left: '50%',
+      transformOrigin: 'center center',
+      background: '#111',
+      overflow: 'hidden',
+    },
+    controls: {
+      position: 'fixed',
+      bottom: 0,
+      left: 0,
+      right: 0,
+      background: 'rgba(0, 0, 0, 0.8)',
+      backdropFilter: 'blur(10px)',
+      padding: '12px 20px',
+      display: 'flex',
+      alignItems: 'center',
+      gap: 16,
+      color: '#fff',
+      fontSize: 12,
+      zIndex: 100,
+    },
+    button: {
+      background: 'none',
+      border: '1px solid rgba(255,255,255,0.3)',
+      color: '#fff',
+      padding: '6px 14px',
+      borderRadius: 4,
+      cursor: 'pointer',
+      fontSize: 12,
+    },
+    timeDisplay: {
+      fontFamily: 'ui-monospace, monospace',
+      fontVariantNumeric: 'tabular-nums',
+      minWidth: 90,
+    },
+    scrubber: {
+      flex: 1,
+      height: 4,
+      background: 'rgba(255,255,255,0.2)',
+      borderRadius: 2,
+      position: 'relative',
+      cursor: 'pointer',
+    },
+    scrubberFill: {
+      position: 'absolute',
+      top: 0,
+      left: 0,
+      height: '100%',
+      background: '#fff',
+      borderRadius: 2,
+      pointerEvents: 'none',
+    },
+    scrubberHandle: {
+      position: 'absolute',
+      top: '50%',
+      width: 12,
+      height: 12,
+      background: '#fff',
+      borderRadius: '50%',
+      transform: 'translate(-50%, -50%)',
+      pointerEvents: 'none',
+    },
+  };
+
+  function Stage({ duration = 10, width = 1920, height = 1080, fps = 60, loop = true, children, bgColor = '#fff' }) {
+    const [time, setTime] = useState(0);
+    const [playing, setPlaying] = useState(true);
+    const [scale, setScale] = useState(1);
+    const rafRef = useRef(null);
+    const startTimeRef = useRef(performance.now());
+    const canvasRef = useRef(null);
+
+    useEffect(() => {
+      function updateScale() {
+        const vw = window.innerWidth;
+        const vh = window.innerHeight - 56;
+        const s = Math.min(vw / width, vh / height);
+        setScale(s);
+      }
+      updateScale();
+      window.addEventListener('resize', updateScale);
+      return () => window.removeEventListener('resize', updateScale);
+    }, [width, height]);
+
+    useEffect(() => {
+      if (!playing) return;
+      let cancelled = false;
+      let last = null;
+
+      function tick(now) {
+        if (cancelled) return;
+        if (last === null) {
+          // First animation frame. Set last=now so delta starts at 0,
+          // AND announce readiness for video export.
+          // This pairing is critical: window.__ready must flip to true at
+          // the exact moment WebM captures frame 0 of the animation, so
+          // render-video.js's trim offset equals the pre-animation gap.
+          last = now;
+          if (typeof window !== 'undefined') window.__ready = true;
+        }
+        const delta = (now - last) / 1000;
+        last = now;
+        setTime(prev => {
+          const next = prev + delta;
+          if (next >= duration) {
+            return loop ? 0 : duration;
+          }
+          return next;
+        });
+        rafRef.current = requestAnimationFrame(tick);
+      }
+
+      // Wait for fonts before starting the clock — makes frame 0 the
+      // real "finished-loading" frame users see, not a fallback-font flash.
+      const startAfterFonts = () => {
+        if (cancelled) return;
+        rafRef.current = requestAnimationFrame(tick);
+      };
+      if (typeof document !== 'undefined' && document.fonts && document.fonts.ready) {
+        document.fonts.ready.then(startAfterFonts);
+      } else {
+        startAfterFonts();
+      }
+
+      return () => {
+        cancelled = true;
+        cancelAnimationFrame(rafRef.current);
+      };
+    }, [playing, duration, loop]);
+
+    const handleScrub = useCallback((e) => {
+      const rect = e.currentTarget.getBoundingClientRect();
+      const ratio = (e.clientX - rect.left) / rect.width;
+      setTime(Math.max(0, Math.min(duration, ratio * duration)));
+    }, [duration]);
+
+    const handleSeek = useCallback((e) => {
+      handleScrub(e);
+      setPlaying(false);
+    }, [handleScrub]);
+
+    const progress = time / duration;
+
+    const ctx = {
+      time,
+      duration,
+      playing,
+      setPlaying,
+      setTime,
+    };
+
+    const canvasStyle = {
+      ...stageStyles.canvas,
+      width,
+      height,
+      background: bgColor,
+      transform: `translate(-50%, -50%) scale(${scale})`,
+    };
+
+    return (
+      <TimeContext.Provider value={ctx}>
+        <div style={stageStyles.wrapper}>
+          <div style={stageStyles.stageHolder}>
+            <div ref={canvasRef} style={canvasStyle}>
+              {children}
+            </div>
+          </div>
+
+          <div style={stageStyles.controls}>
+            <button
+              style={stageStyles.button}
+              onClick={() => setPlaying(p => !p)}
+            >
+              {playing ? '⏸ 暂停' : '▶ 播放'}
+            </button>
+
+            <button
+              style={stageStyles.button}
+              onClick={() => setTime(0)}
+            >
+              ⏮ 开始
+            </button>
+
+            <div style={stageStyles.timeDisplay}>
+              {time.toFixed(2)}s / {duration.toFixed(2)}s
+            </div>
+
+            <div style={stageStyles.scrubber} onMouseDown={handleSeek}>
+              <div style={{ ...stageStyles.scrubberFill, width: `${progress * 100}%` }} />
+              <div style={{ ...stageStyles.scrubberHandle, left: `${progress * 100}%` }} />
+            </div>
+          </div>
+        </div>
+      </TimeContext.Provider>
+    );
+  }
+
+  function Sprite({ start = 0, end, children, style }) {
+    const { time } = useContext(TimeContext);
+    const actualEnd = end == null ? Infinity : end;
+
+    if (time < start || time >= actualEnd) {
+      return null;
+    }
+
+    const duration = actualEnd - start;
+    const elapsed = time - start;
+    const t = duration === 0 ? 1 : Math.max(0, Math.min(1, elapsed / duration));
+
+    const spriteValue = { t, elapsed, duration, start, end: actualEnd };
+
+    return (
+      <SpriteContext.Provider value={spriteValue}>
+        <div style={{ position: 'absolute', inset: 0, ...style }}>
+          {children}
+        </div>
+      </SpriteContext.Provider>
+    );
+  }
+
+  if (typeof window !== 'undefined') {
+    window.Animations = {
+      Stage,
+      Sprite,
+      useTime,
+      useSprite,
+      Easing,
+      interpolate,
+    };
+  }
+})();

BIN
assets/bgm-ad.mp3


BIN
assets/bgm-educational-alt.mp3


BIN
assets/bgm-educational.mp3


BIN
assets/bgm-tech.mp3


BIN
assets/bgm-tutorial-alt.mp3


BIN
assets/bgm-tutorial.mp3


+ 166 - 0
assets/browser_window.jsx

@@ -0,0 +1,166 @@
+/**
+ * BrowserWindow — 浏览器窗口边框(Chrome风格)
+ *
+ * 含:traffic lights + tab bar + URL bar
+ *
+ * 用法:
+ *   <BrowserWindow url="https://example.com" title="Example">
+ *     <YourWebPage />
+ *   </BrowserWindow>
+ */
+
+const browserWindowStyles = {
+  window: {
+    display: 'inline-block',
+    background: '#fff',
+    borderRadius: 10,
+    overflow: 'hidden',
+    boxShadow: '0 30px 80px rgba(0,0,0,0.25), 0 0 0 0.5px rgba(0,0,0,0.15)',
+  },
+  chrome: {
+    background: '#dee1e6',
+    paddingTop: 10,
+    paddingLeft: 10,
+    paddingRight: 10,
+    userSelect: 'none',
+  },
+  tabRow: {
+    display: 'flex',
+    alignItems: 'flex-end',
+    gap: 6,
+    position: 'relative',
+  },
+  trafficLights: {
+    display: 'flex',
+    gap: 8,
+    alignItems: 'center',
+    paddingBottom: 10,
+    marginRight: 8,
+  },
+  light: {
+    width: 12,
+    height: 12,
+    borderRadius: '50%',
+    border: '0.5px solid rgba(0,0,0,0.15)',
+  },
+  close: { background: '#ff5f57' },
+  minimize: { background: '#febc2e' },
+  maximize: { background: '#28c840' },
+  tab: {
+    background: '#fff',
+    padding: '8px 30px 8px 14px',
+    borderTopLeftRadius: 10,
+    borderTopRightRadius: 10,
+    fontSize: 12,
+    color: '#222',
+    fontFamily: '-apple-system, sans-serif',
+    maxWidth: 220,
+    display: 'flex',
+    alignItems: 'center',
+    gap: 8,
+    position: 'relative',
+    whiteSpace: 'nowrap',
+    overflow: 'hidden',
+    textOverflow: 'ellipsis',
+  },
+  favicon: {
+    width: 14,
+    height: 14,
+    borderRadius: 2,
+    background: '#999',
+    flexShrink: 0,
+  },
+  navBar: {
+    background: '#fff',
+    padding: '8px 14px',
+    display: 'flex',
+    alignItems: 'center',
+    gap: 10,
+    borderBottom: '1px solid #e5e7eb',
+  },
+  navButtons: {
+    display: 'flex',
+    gap: 4,
+    color: '#5f6368',
+    fontSize: 16,
+  },
+  navButton: {
+    width: 28,
+    height: 28,
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    borderRadius: '50%',
+    cursor: 'pointer',
+  },
+  urlBar: {
+    flex: 1,
+    background: '#f1f3f4',
+    borderRadius: 999,
+    padding: '7px 14px',
+    fontSize: 13,
+    color: '#333',
+    display: 'flex',
+    alignItems: 'center',
+    gap: 8,
+    fontFamily: '-apple-system, sans-serif',
+  },
+  lockIcon: {
+    color: '#5f6368',
+    fontSize: 12,
+  },
+  content: {
+    position: 'relative',
+    overflow: 'auto',
+    background: '#fff',
+  },
+};
+
+function BrowserWindow({
+  title = 'New Tab',
+  url = 'https://example.com',
+  width = 1200,
+  height = 800,
+  showTrafficLights = true,
+  children,
+}) {
+  return (
+    <div style={browserWindowStyles.window}>
+      <div style={browserWindowStyles.chrome}>
+        <div style={browserWindowStyles.tabRow}>
+          {showTrafficLights && (
+            <div style={browserWindowStyles.trafficLights}>
+              <div style={{ ...browserWindowStyles.light, ...browserWindowStyles.close }} />
+              <div style={{ ...browserWindowStyles.light, ...browserWindowStyles.minimize }} />
+              <div style={{ ...browserWindowStyles.light, ...browserWindowStyles.maximize }} />
+            </div>
+          )}
+          <div style={browserWindowStyles.tab}>
+            <div style={browserWindowStyles.favicon} />
+            <span>{title}</span>
+          </div>
+        </div>
+      </div>
+
+      <div style={browserWindowStyles.navBar}>
+        <div style={browserWindowStyles.navButtons}>
+          <div style={browserWindowStyles.navButton}>←</div>
+          <div style={browserWindowStyles.navButton}>→</div>
+          <div style={browserWindowStyles.navButton}>↻</div>
+        </div>
+        <div style={browserWindowStyles.urlBar}>
+          <span style={browserWindowStyles.lockIcon}>🔒</span>
+          <span>{url}</span>
+        </div>
+      </div>
+
+      <div style={{ ...browserWindowStyles.content, width, height }}>
+        {children}
+      </div>
+    </div>
+  );
+}
+
+if (typeof window !== 'undefined') {
+  window.BrowserWindow = BrowserWindow;
+}

+ 237 - 0
assets/deck_index.html

@@ -0,0 +1,237 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<title>Deck · Multi-file Slide Index</title>
+<!--
+  deck_index.html — 多文件 slide deck 的拼接器
+
+  配合「每页一个独立 HTML」架构使用。与单文件 deck_stage.js 对比:
+  · 每页独立作用域(CSS/JS 都隔离),一页出 bug 不影响其他页
+  · 单页可直接在浏览器打开验证,不依赖 JS goTo()
+  · 多 agent 可并行做不同页,merge 时零冲突
+  · 适合 ≥15 页的讲座/课件/长 deck
+
+  用法:
+    1. 把本文件复制到 deck 根目录,重命名 index.html
+    2. 在同目录建 slides/ 子目录,放每一页独立 HTML
+    3. 编辑下方 MANIFEST 数组,按顺序列出文件名和人类可读标签
+    4. 每张 slide HTML 建议尺寸 1920×1080,自带背景/字体;不要依赖外层 CSS
+
+  共享资源(如果需要):
+    · shared/tokens.css  — 跨页 CSS 变量(色板/字号)
+    · shared/chrome.html — 页眉页脚可复用片段
+    · 每页 HTML 自己 <link> 进去即可
+
+  键盘:← / → / Space / PgUp / PgDown / Home / End / 1-9 跳页 / P 打印
+-->
+
+<!-- ═══════════════════════════════════════════════════════ -->
+<!-- EDIT THIS — deck 所有页按顺序列出                        -->
+<!-- ═══════════════════════════════════════════════════════ -->
+<script>
+  window.DECK_MANIFEST = [
+    { file: "slides/01-cover.html",       label: "Cover" },
+    { file: "slides/02-quote.html",       label: "Opening Quote" },
+    { file: "slides/03-intro.html",       label: "Self-intro" },
+    // 继续往下加。file 是相对本文件的路径,label 用于计数器
+  ];
+
+  // 固定 canvas 尺寸。每页 HTML 都应该按这个尺寸设计。
+  window.DECK_WIDTH = 1920;
+  window.DECK_HEIGHT = 1080;
+</script>
+
+<style>
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+  html, body {
+    height: 100%;
+    background: #0a0a0a;
+    overflow: hidden;
+    font-family: -apple-system, "PingFang SC", sans-serif;
+  }
+  #stage {
+    position: fixed;
+    top: 50%; left: 50%;
+    transform-origin: top left;
+    will-change: transform;
+    background: #fff;
+    box-shadow: 0 10px 60px rgba(0,0,0,0.4);
+    /* size set by JS from DECK_WIDTH/HEIGHT */
+  }
+  iframe {
+    width: 100%;
+    height: 100%;
+    border: 0;
+    display: block;
+    background: #fff;
+  }
+  .counter {
+    position: fixed;
+    bottom: 20px;
+    right: 20px;
+    background: rgba(0,0,0,0.65);
+    color: #fff;
+    padding: 6px 14px;
+    border-radius: 999px;
+    font-size: 13px;
+    letter-spacing: 0.05em;
+    font-variant-numeric: tabular-nums;
+    z-index: 100;
+    user-select: none;
+    opacity: 0.7;
+    transition: opacity 0.2s;
+  }
+  .counter:hover { opacity: 1; }
+  .counter .label { color: rgba(255,255,255,0.7); margin-left: 8px; }
+  .nav-zone {
+    position: fixed;
+    top: 0; bottom: 0;
+    width: 15%;
+    cursor: pointer;
+    z-index: 50;
+  }
+  .nav-zone.left  { left: 0; }
+  .nav-zone.right { right: 0; }
+  .nav-hint {
+    position: absolute;
+    top: 50%; transform: translateY(-50%);
+    width: 44px; height: 44px;
+    border-radius: 999px;
+    background: rgba(255,255,255,0.08);
+    color: rgba(255,255,255,0.6);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 22px;
+    opacity: 0;
+    transition: opacity 0.2s;
+  }
+  .nav-zone.left  .nav-hint { left: 20px; }
+  .nav-zone.right .nav-hint { right: 20px; }
+  .nav-zone:hover .nav-hint { opacity: 1; }
+
+  /* Print: one slide per page, no navigation UI */
+  @media print {
+    @page { size: 1920px 1080px; margin: 0; }
+    html, body { background: #fff; overflow: visible; height: auto; }
+    #stage { position: static; transform: none !important; box-shadow: none; }
+    .counter, .nav-zone { display: none !important; }
+    /* In print mode we render all slides sequentially — see JS */
+    .print-stack { display: block; }
+    .print-stack iframe {
+      width: 1920px;
+      height: 1080px;
+      page-break-after: always;
+      display: block;
+    }
+  }
+</style>
+</head>
+<body>
+
+<div id="stage">
+  <iframe id="frame" src="about:blank"></iframe>
+</div>
+
+<div class="nav-zone left"  id="navL"><div class="nav-hint">‹</div></div>
+<div class="nav-zone right" id="navR"><div class="nav-hint">›</div></div>
+<div class="counter" id="counter">1 / 1</div>
+
+<!-- Print-only stack: populated on beforeprint, stripped on afterprint -->
+<div class="print-stack" id="printStack" style="display:none;"></div>
+
+<script>
+(function () {
+  const W = window.DECK_WIDTH || 1920;
+  const H = window.DECK_HEIGHT || 1080;
+  const deck = window.DECK_MANIFEST || [];
+  const stage = document.getElementById('stage');
+  const frame = document.getElementById('frame');
+  const counter = document.getElementById('counter');
+  const printStack = document.getElementById('printStack');
+  const storageKey = 'deck-index-' + location.pathname;
+  let current = 0;
+
+  stage.style.width  = W + 'px';
+  stage.style.height = H + 'px';
+
+  function fit() {
+    const s = Math.min(window.innerWidth / W, window.innerHeight / H);
+    const x = (window.innerWidth  - W * s) / 2;
+    const y = (window.innerHeight - H * s) / 2;
+    stage.style.transform = `translate(${x}px, ${y}px) scale(${s})`;
+    stage.style.top = '0';
+    stage.style.left = '0';
+  }
+
+  function show(idx) {
+    if (idx < 0 || idx >= deck.length) return;
+    current = idx;
+    frame.src = deck[idx].file;
+    counter.innerHTML = `${idx + 1} / ${deck.length} <span class="label">${deck[idx].label || ''}</span>`;
+    try { localStorage.setItem(storageKey, String(idx)); } catch (_) {}
+    if (location.hash !== '#' + (idx + 1)) {
+      history.replaceState(null, '', '#' + (idx + 1));
+    }
+  }
+
+  function next() { show(Math.min(current + 1, deck.length - 1)); }
+  function prev() { show(Math.max(current - 1, 0)); }
+
+  // Keyboard
+  document.addEventListener('keydown', (e) => {
+    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
+    switch (e.key) {
+      case 'ArrowRight': case ' ': case 'PageDown': e.preventDefault(); next(); break;
+      case 'ArrowLeft':  case 'PageUp':              e.preventDefault(); prev(); break;
+      case 'Home':                                    e.preventDefault(); show(0); break;
+      case 'End':                                     e.preventDefault(); show(deck.length - 1); break;
+      case 'p': case 'P':                             window.print(); break;
+      default:
+        if (e.key >= '1' && e.key <= '9') {
+          const i = parseInt(e.key, 10) - 1;
+          if (i < deck.length) { e.preventDefault(); show(i); }
+        }
+    }
+  });
+
+  document.getElementById('navL').addEventListener('click', prev);
+  document.getElementById('navR').addEventListener('click', next);
+  window.addEventListener('resize', fit);
+  window.addEventListener('hashchange', () => {
+    const m = location.hash.match(/^#(\d+)$/);
+    if (m) show(parseInt(m[1], 10) - 1);
+  });
+
+  // Initial: hash > localStorage > 0
+  const hashMatch = location.hash.match(/^#(\d+)$/);
+  if (hashMatch) current = Math.min(parseInt(hashMatch[1], 10) - 1, deck.length - 1);
+  else try {
+    const v = parseInt(localStorage.getItem(storageKey), 10);
+    if (!isNaN(v) && v >= 0 && v < deck.length) current = v;
+  } catch (_) {}
+  fit();
+  show(current);
+
+  // Print: build a stack of all iframes so browser prints every slide
+  window.addEventListener('beforeprint', () => {
+    printStack.innerHTML = '';
+    deck.forEach(item => {
+      const f = document.createElement('iframe');
+      f.src = item.file;
+      printStack.appendChild(f);
+    });
+    printStack.style.display = 'block';
+    document.getElementById('stage').style.display = 'none';
+  });
+  window.addEventListener('afterprint', () => {
+    printStack.innerHTML = '';
+    printStack.style.display = 'none';
+    document.getElementById('stage').style.display = '';
+  });
+})();
+</script>
+
+</body>
+</html>

+ 405 - 0
assets/deck_stage.js

@@ -0,0 +1,405 @@
+/**
+ * <deck-stage> — HTML幻灯片外壳web component
+ *
+ * 提供功能:
+ * - 固定尺寸canvas(默认1920×1080)+ auto-scale + letterbox
+ * - 键盘导航(←/→/Space/Home/End/Esc)
+ * - 左右点击区域导航
+ * - slide counter (当前/总数)
+ * - localStorage持久化当前slide
+ * - Speaker notes postMessage (支持外层渲染)
+ * - Hash导航 (#slide-5 跳到第5张)
+ * - Print-to-PDF支持 (Cmd+P / Ctrl+P 一页一slide)
+ * - 自动给每个slide添加 data-screen-label
+ *
+ * 用法:
+ *   <deck-stage>
+ *     <section>Slide 1</section>
+ *     <section>Slide 2</section>
+ *   </deck-stage>
+ *
+ * 自定义尺寸:
+ *   <deck-stage width="1080" height="1920">...</deck-stage>
+ *
+ * Speaker notes:在<head>加
+ *   <script type="application/json" id="speaker-notes">
+ *   ["slide 1 notes", "slide 2 notes"]
+ *   </script>
+ */
+
+(function() {
+  const STORAGE_KEY_PREFIX = 'deck-stage-slide-';
+
+  class DeckStage extends HTMLElement {
+    constructor() {
+      super();
+      this.attachShadow({ mode: 'open' });
+      this._currentSlide = 0;
+      this._slides = [];
+      this._storageKey = STORAGE_KEY_PREFIX + (location.pathname || 'default');
+    }
+
+    connectedCallback() {
+      this._width = parseInt(this.getAttribute('width')) || 1920;
+      this._height = parseInt(this.getAttribute('height')) || 1080;
+
+      this._render();
+      this._collectSlides();
+      this._setupEventListeners();
+      this._restoreSlide();
+      this._updateDisplay();
+      this._setupPrintStyles();
+    }
+
+    _render() {
+      this.shadowRoot.innerHTML = `
+        <style>
+          :host {
+            display: block;
+            position: fixed;
+            inset: 0;
+            background: #000;
+            overflow: hidden;
+            font-family: -apple-system, 'SF Pro Text', 'PingFang SC', sans-serif;
+          }
+
+          :host([noscale]) .stage {
+            transform: none !important;
+            top: 0 !important;
+            left: 0 !important;
+          }
+
+          .stage {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform-origin: top left;
+            will-change: transform;
+            background: #fff;
+          }
+
+          .slide-wrapper {
+            width: 100%;
+            height: 100%;
+            position: relative;
+          }
+
+          ::slotted(section) {
+            display: none;
+            width: 100%;
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 0;
+            overflow: hidden;
+          }
+
+          ::slotted(section.active) {
+            display: block;
+          }
+
+          .counter {
+            position: fixed;
+            bottom: 20px;
+            right: 20px;
+            background: rgba(0, 0, 0, 0.6);
+            color: #fff;
+            padding: 6px 14px;
+            border-radius: 999px;
+            font-size: 13px;
+            font-variant-numeric: tabular-nums;
+            z-index: 100;
+            user-select: none;
+            opacity: 0.6;
+            transition: opacity 0.2s;
+          }
+
+          .counter:hover {
+            opacity: 1;
+          }
+
+          .nav-zone {
+            position: fixed;
+            top: 0;
+            bottom: 0;
+            width: 15%;
+            cursor: pointer;
+            z-index: 50;
+          }
+
+          .nav-zone.left { left: 0; }
+          .nav-zone.right { right: 0; }
+
+          .nav-hint {
+            position: absolute;
+            top: 50%;
+            transform: translateY(-50%);
+            width: 44px;
+            height: 44px;
+            border-radius: 999px;
+            background: rgba(255, 255, 255, 0.1);
+            color: rgba(255, 255, 255, 0.6);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 24px;
+            opacity: 0;
+            transition: opacity 0.2s;
+          }
+
+          .nav-zone.left .nav-hint { left: 20px; }
+          .nav-zone.right .nav-hint { right: 20px; }
+
+          .nav-zone:hover .nav-hint {
+            opacity: 1;
+          }
+
+          @media print {
+            :host {
+              position: static;
+              background: #fff;
+            }
+            .counter, .nav-zone {
+              display: none !important;
+            }
+            .stage {
+              position: static;
+              transform: none !important;
+              page-break-after: always;
+            }
+            ::slotted(section) {
+              display: block !important;
+              position: relative !important;
+              page-break-after: always;
+              width: 100%;
+              height: 100%;
+            }
+          }
+        </style>
+
+        <div class="stage" id="stage" style="width: ${this._width}px; height: ${this._height}px;">
+          <div class="slide-wrapper">
+            <slot></slot>
+          </div>
+        </div>
+
+        <div class="nav-zone left" id="navLeft">
+          <div class="nav-hint">‹</div>
+        </div>
+        <div class="nav-zone right" id="navRight">
+          <div class="nav-hint">›</div>
+        </div>
+
+        <div class="counter" id="counter">1 / 1</div>
+      `;
+    }
+
+    _collectSlides() {
+      this._slides = Array.from(this.querySelectorAll(':scope > section'));
+
+      this._slides.forEach((slide, idx) => {
+        if (!slide.hasAttribute('data-screen-label')) {
+          const num = String(idx + 1).padStart(2, '0');
+          slide.setAttribute('data-screen-label', num);
+        }
+        if (!slide.hasAttribute('data-om-validate')) {
+          slide.setAttribute('data-om-validate', '');
+        }
+      });
+    }
+
+    _setupEventListeners() {
+      window.addEventListener('resize', () => this._updateScale());
+
+      document.addEventListener('keydown', (e) => {
+        if (e.target.matches('input, textarea, [contenteditable]')) return;
+
+        switch (e.key) {
+          case 'ArrowRight':
+          case ' ':
+          case 'PageDown':
+            e.preventDefault();
+            this.next();
+            break;
+          case 'ArrowLeft':
+          case 'PageUp':
+            e.preventDefault();
+            this.prev();
+            break;
+          case 'Home':
+            e.preventDefault();
+            this.goTo(0);
+            break;
+          case 'End':
+            e.preventDefault();
+            this.goTo(this._slides.length - 1);
+            break;
+        }
+      });
+
+      this.shadowRoot.getElementById('navLeft').addEventListener('click', () => this.prev());
+      this.shadowRoot.getElementById('navRight').addEventListener('click', () => this.next());
+
+      window.addEventListener('hashchange', () => this._handleHash());
+      if (location.hash) {
+        setTimeout(() => this._handleHash(), 0);
+      }
+
+      const observer = new MutationObserver(() => {
+        if (this.hasAttribute('noscale')) {
+          this._updateScale();
+        }
+      });
+      observer.observe(this, { attributes: true, attributeFilter: ['noscale'] });
+    }
+
+    _handleHash() {
+      const match = location.hash.match(/^#slide-(\d+)$/);
+      if (match) {
+        const idx = parseInt(match[1]) - 1;
+        if (idx >= 0 && idx < this._slides.length) {
+          this.goTo(idx);
+        }
+      }
+    }
+
+    _restoreSlide() {
+      try {
+        const stored = localStorage.getItem(this._storageKey);
+        if (stored !== null) {
+          const idx = parseInt(stored);
+          if (idx >= 0 && idx < this._slides.length) {
+            this._currentSlide = idx;
+          }
+        }
+      } catch (e) {}
+    }
+
+    _saveSlide() {
+      try {
+        localStorage.setItem(this._storageKey, String(this._currentSlide));
+      } catch (e) {}
+    }
+
+    _updateScale() {
+      if (this.hasAttribute('noscale')) {
+        const stage = this.shadowRoot.getElementById('stage');
+        stage.style.transform = 'none';
+        stage.style.top = '0';
+        stage.style.left = '0';
+        return;
+      }
+
+      const stage = this.shadowRoot.getElementById('stage');
+      if (!stage) return;
+
+      const viewportW = window.innerWidth;
+      const viewportH = window.innerHeight;
+      const scale = Math.min(viewportW / this._width, viewportH / this._height);
+      const scaledW = this._width * scale;
+      const scaledH = this._height * scale;
+      const offsetX = (viewportW - scaledW) / 2;
+      const offsetY = (viewportH - scaledH) / 2;
+
+      stage.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
+      stage.style.top = '0';
+      stage.style.left = '0';
+    }
+
+    _updateDisplay() {
+      this._slides.forEach((slide, idx) => {
+        slide.classList.toggle('active', idx === this._currentSlide);
+      });
+
+      const counter = this.shadowRoot.getElementById('counter');
+      if (counter) {
+        counter.textContent = `${this._currentSlide + 1} / ${this._slides.length}`;
+      }
+
+      this._updateScale();
+
+      try {
+        window.postMessage({
+          slideIndexChanged: this._currentSlide,
+          totalSlides: this._slides.length
+        }, '*');
+      } catch (e) {}
+
+      try {
+        if (window.parent && window.parent !== window) {
+          window.parent.postMessage({
+            slideIndexChanged: this._currentSlide,
+            totalSlides: this._slides.length
+          }, '*');
+        }
+      } catch (e) {}
+    }
+
+    _setupPrintStyles() {
+      const printStyle = document.createElement('style');
+      printStyle.textContent = `
+        @media print {
+          @page {
+            size: ${this._width}px ${this._height}px;
+            margin: 0;
+          }
+          body {
+            margin: 0;
+            padding: 0;
+          }
+          deck-stage {
+            position: static !important;
+          }
+          deck-stage > section {
+            display: block !important;
+            position: relative !important;
+            width: ${this._width}px !important;
+            height: ${this._height}px !important;
+            page-break-after: always;
+            overflow: hidden;
+          }
+          deck-stage > section:last-child {
+            page-break-after: auto;
+          }
+        }
+      `;
+      document.head.appendChild(printStyle);
+    }
+
+    next() {
+      if (this._currentSlide < this._slides.length - 1) {
+        this._currentSlide++;
+        this._saveSlide();
+        this._updateDisplay();
+      }
+    }
+
+    prev() {
+      if (this._currentSlide > 0) {
+        this._currentSlide--;
+        this._saveSlide();
+        this._updateDisplay();
+      }
+    }
+
+    goTo(idx) {
+      if (idx >= 0 && idx < this._slides.length) {
+        this._currentSlide = idx;
+        this._saveSlide();
+        this._updateDisplay();
+      }
+    }
+
+    get currentSlide() {
+      return this._currentSlide;
+    }
+
+    get totalSlides() {
+      return this._slides.length;
+    }
+  }
+
+  customElements.define('deck-stage', DeckStage);
+
+  window.DeckStage = DeckStage;
+})();

+ 205 - 0
assets/design_canvas.jsx

@@ -0,0 +1,205 @@
+/**
+ * DesignCanvas — 变体并排网格布局
+ *
+ * 用于展示2+个静态设计variations让用户对比选择。
+ * 每个variation有label,可hover放大。
+ *
+ * 用法:
+ *   <DesignCanvas
+ *     title="Hero区设计探索"
+ *     subtitle="3个方向对比"
+ *     columns={3}
+ *   >
+ *     <Variation label="Minimal" description="极简克制版">
+ *       <div>...你的设计1...</div>
+ *     </Variation>
+ *     <Variation label="Editorial" description="杂志编辑风">
+ *       <div>...你的设计2...</div>
+ *     </Variation>
+ *     <Variation label="Brutalist" description="粗粝原始">
+ *       <div>...你的设计3...</div>
+ *     </Variation>
+ *   </DesignCanvas>
+ *
+ * 配合React+Babel使用。放在合适的script里,然后window.DesignCanvas/window.Variation可用。
+ */
+
+const canvasStyles = {
+  container: {
+    minHeight: '100vh',
+    background: '#F5F5F0',
+    padding: '40px 60px',
+    fontFamily: '-apple-system, "SF Pro Text", "PingFang SC", sans-serif',
+  },
+  header: {
+    marginBottom: 48,
+    maxWidth: 900,
+  },
+  title: {
+    fontSize: 36,
+    fontWeight: 600,
+    marginBottom: 12,
+    color: '#1A1A1A',
+    letterSpacing: '-0.02em',
+  },
+  subtitle: {
+    fontSize: 16,
+    color: '#666',
+    lineHeight: 1.5,
+  },
+  grid: {
+    display: 'grid',
+    gap: 32,
+  },
+  cell: {
+    display: 'flex',
+    flexDirection: 'column',
+    gap: 12,
+  },
+  cellHeader: {
+    display: 'flex',
+    alignItems: 'baseline',
+    gap: 12,
+    paddingBottom: 8,
+    borderBottom: '1px solid #E0E0DA',
+  },
+  label: {
+    fontSize: 14,
+    fontWeight: 600,
+    color: '#1A1A1A',
+    letterSpacing: '-0.01em',
+  },
+  description: {
+    fontSize: 13,
+    color: '#888',
+  },
+  frame: {
+    background: '#fff',
+    borderRadius: 4,
+    border: '1px solid #E0E0DA',
+    overflow: 'hidden',
+    position: 'relative',
+    transition: 'transform 0.2s ease, box-shadow 0.2s ease',
+    cursor: 'pointer',
+  },
+  frameInner: {
+    position: 'relative',
+    width: '100%',
+  },
+  badge: {
+    position: 'absolute',
+    top: 12,
+    left: 12,
+    background: 'rgba(0, 0, 0, 0.7)',
+    color: '#fff',
+    padding: '3px 8px',
+    borderRadius: 4,
+    fontSize: 11,
+    fontWeight: 500,
+    letterSpacing: '0.5px',
+    textTransform: 'uppercase',
+    zIndex: 10,
+    pointerEvents: 'none',
+  },
+};
+
+function DesignCanvas({ title, subtitle, columns = 3, children }) {
+  const [expanded, setExpanded] = React.useState(null);
+
+  const gridStyle = {
+    ...canvasStyles.grid,
+    gridTemplateColumns: `repeat(${columns}, 1fr)`,
+  };
+
+  return (
+    <div style={canvasStyles.container}>
+      {(title || subtitle) && (
+        <div style={canvasStyles.header}>
+          {title && <h1 style={canvasStyles.title}>{title}</h1>}
+          {subtitle && <p style={canvasStyles.subtitle}>{subtitle}</p>}
+        </div>
+      )}
+
+      <div style={gridStyle}>
+        {React.Children.map(children, (child, idx) =>
+          React.isValidElement(child)
+            ? React.cloneElement(child, {
+                _index: idx,
+                _expanded: expanded === idx,
+                _onToggle: () => setExpanded(expanded === idx ? null : idx),
+              })
+            : child
+        )}
+      </div>
+
+      {expanded !== null && (
+        <div
+          onClick={() => setExpanded(null)}
+          style={{
+            position: 'fixed',
+            inset: 0,
+            background: 'rgba(0, 0, 0, 0.75)',
+            zIndex: 1000,
+            display: 'flex',
+            alignItems: 'center',
+            justifyContent: 'center',
+            padding: 40,
+            cursor: 'zoom-out',
+          }}
+        >
+          <div
+            onClick={e => e.stopPropagation()}
+            style={{
+              background: '#fff',
+              borderRadius: 8,
+              overflow: 'hidden',
+              maxWidth: '90vw',
+              maxHeight: '90vh',
+              position: 'relative',
+            }}
+          >
+            {React.Children.toArray(children)[expanded]}
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}
+
+function Variation({ label, description, number, children, _index, _expanded, _onToggle, aspectRatio = '4 / 3' }) {
+  const displayNumber = number || String(_index + 1).padStart(2, '0');
+
+  return (
+    <div style={canvasStyles.cell}>
+      <div style={canvasStyles.cellHeader}>
+        <span style={{ ...canvasStyles.label, color: '#999', fontFamily: 'ui-monospace, monospace', fontSize: 12 }}>
+          {displayNumber}
+        </span>
+        <span style={canvasStyles.label}>{label}</span>
+        {description && <span style={canvasStyles.description}>— {description}</span>}
+      </div>
+
+      <div
+        onClick={_onToggle}
+        style={{
+          ...canvasStyles.frame,
+          aspectRatio,
+        }}
+        onMouseEnter={e => {
+          e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.08)';
+        }}
+        onMouseLeave={e => {
+          e.currentTarget.style.boxShadow = 'none';
+        }}
+      >
+        <div style={canvasStyles.frameInner}>
+          {children}
+        </div>
+      </div>
+    </div>
+  );
+}
+
+if (typeof window !== 'undefined') {
+  Object.assign(window, { DesignCanvas, Variation });
+}

+ 192 - 0
assets/ios_frame.jsx

@@ -0,0 +1,192 @@
+/**
+ * IosFrame — iPhone设备边框
+ *
+ * 参考iPhone 15 Pro(393×852 logical pixels)
+ * 含:灵动岛 + 状态栏(时间/信号/电池)+ Home Indicator + 圆角
+ *
+ * 用法:
+ *   <IosFrame time="9:41" battery={85}>
+ *     <YourAppContent />
+ *   </IosFrame>
+ *
+ * 自定义:
+ *   <IosFrame width={390} height={844} darkMode showKeyboard>
+ *     ...
+ *   </IosFrame>
+ */
+
+const iosFrameStyles = {
+  wrapper: {
+    display: 'inline-block',
+    padding: 12,
+    background: '#000',
+    borderRadius: 60,
+    boxShadow: '0 0 0 2px #1f2937, 0 20px 60px rgba(0,0,0,0.3)',
+    position: 'relative',
+  },
+  screen: {
+    position: 'relative',
+    borderRadius: 48,
+    overflow: 'hidden',
+    background: '#fff',
+  },
+  statusBar: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    right: 0,
+    height: 54,
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    padding: '0 32px 0 32px',
+    fontSize: 16,
+    fontWeight: 600,
+    fontFamily: '-apple-system, "SF Pro Text", sans-serif',
+    zIndex: 20,
+    pointerEvents: 'none',
+  },
+  dynamicIsland: {
+    position: 'absolute',
+    top: 12,
+    left: '50%',
+    transform: 'translateX(-50%)',
+    width: 124,
+    height: 36,
+    background: '#000',
+    borderRadius: 999,
+    zIndex: 30,
+  },
+  statusIcons: {
+    display: 'flex',
+    alignItems: 'center',
+    gap: 6,
+  },
+  signalIcon: {
+    display: 'flex',
+    alignItems: 'flex-end',
+    gap: 2,
+    height: 12,
+  },
+  signalBar: {
+    width: 3,
+    background: 'currentColor',
+    borderRadius: 1,
+  },
+  wifiIcon: {
+    width: 16,
+    height: 12,
+    position: 'relative',
+  },
+  batteryIcon: {
+    width: 26,
+    height: 12,
+    border: '1.5px solid currentColor',
+    borderRadius: 3,
+    padding: 1,
+    position: 'relative',
+    opacity: 0.8,
+  },
+  batteryCap: {
+    position: 'absolute',
+    top: 3,
+    right: -3,
+    width: 2,
+    height: 6,
+    background: 'currentColor',
+    borderRadius: '0 1px 1px 0',
+  },
+  content: {
+    position: 'absolute',
+    top: 54,
+    left: 0,
+    right: 0,
+    bottom: 34,
+    overflow: 'auto',
+  },
+  homeIndicator: {
+    position: 'absolute',
+    bottom: 10,
+    left: '50%',
+    transform: 'translateX(-50%)',
+    width: 140,
+    height: 5,
+    background: 'rgba(0,0,0,0.3)',
+    borderRadius: 999,
+    zIndex: 10,
+  },
+  homeIndicatorDark: {
+    background: 'rgba(255,255,255,0.5)',
+  },
+};
+
+function IosFrame({
+  children,
+  width = 393,
+  height = 852,
+  time = '9:41',
+  battery = 100,
+  darkMode = false,
+  showStatusBar = true,
+  showDynamicIsland = true,
+  showHomeIndicator = true,
+}) {
+  const textColor = darkMode ? '#fff' : '#000';
+
+  return (
+    <div style={iosFrameStyles.wrapper}>
+      <div style={{
+        ...iosFrameStyles.screen,
+        width,
+        height,
+        background: darkMode ? '#000' : '#fff',
+      }}>
+        {showStatusBar && (
+          <div style={{ ...iosFrameStyles.statusBar, color: textColor }}>
+            <span>{time}</span>
+            <div style={iosFrameStyles.statusIcons}>
+              <div style={iosFrameStyles.signalIcon}>
+                <div style={{ ...iosFrameStyles.signalBar, height: 4 }} />
+                <div style={{ ...iosFrameStyles.signalBar, height: 6 }} />
+                <div style={{ ...iosFrameStyles.signalBar, height: 9 }} />
+                <div style={{ ...iosFrameStyles.signalBar, height: 11 }} />
+              </div>
+              <svg width="16" height="12" viewBox="0 0 16 12" fill="none" style={{ color: textColor }}>
+                <path d="M8 11.5a1 1 0 100-2 1 1 0 000 2z" fill="currentColor" />
+                <path d="M3 7.5a7 7 0 0110 0" stroke="currentColor" strokeWidth="1.3" fill="none" strokeLinecap="round" />
+                <path d="M1 4.5a11 11 0 0114 0" stroke="currentColor" strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7" />
+              </svg>
+              <div style={iosFrameStyles.batteryIcon}>
+                <div style={{
+                  width: `${battery}%`,
+                  height: '100%',
+                  background: 'currentColor',
+                  borderRadius: 1,
+                  opacity: 0.9,
+                }} />
+                <div style={iosFrameStyles.batteryCap} />
+              </div>
+            </div>
+          </div>
+        )}
+
+        {showDynamicIsland && <div style={iosFrameStyles.dynamicIsland} />}
+
+        <div style={iosFrameStyles.content}>
+          {children}
+        </div>
+
+        {showHomeIndicator && (
+          <div style={{
+            ...iosFrameStyles.homeIndicator,
+            ...(darkMode ? iosFrameStyles.homeIndicatorDark : {}),
+          }} />
+        )}
+      </div>
+    </div>
+  );
+}
+
+if (typeof window !== 'undefined') {
+  window.IosFrame = IosFrame;
+}

+ 96 - 0
assets/macos_window.jsx

@@ -0,0 +1,96 @@
+/**
+ * MacosWindow — macOS应用窗口边框(含traffic lights)
+ *
+ * 用法:
+ *   <MacosWindow title="Finder">
+ *     <YourAppContent />
+ *   </MacosWindow>
+ */
+
+const macosWindowStyles = {
+  window: {
+    display: 'inline-block',
+    background: '#fff',
+    borderRadius: 10,
+    overflow: 'hidden',
+    boxShadow: '0 30px 80px rgba(0,0,0,0.25), 0 0 0 0.5px rgba(0,0,0,0.15)',
+  },
+  titleBar: {
+    height: 38,
+    background: 'linear-gradient(to bottom, #e8e8e8, #d8d8d8)',
+    display: 'flex',
+    alignItems: 'center',
+    padding: '0 14px',
+    borderBottom: '0.5px solid rgba(0,0,0,0.1)',
+    position: 'relative',
+    userSelect: 'none',
+  },
+  trafficLights: {
+    display: 'flex',
+    gap: 8,
+    alignItems: 'center',
+  },
+  light: {
+    width: 12,
+    height: 12,
+    borderRadius: '50%',
+    border: '0.5px solid rgba(0,0,0,0.15)',
+  },
+  close: { background: '#ff5f57' },
+  minimize: { background: '#febc2e' },
+  maximize: { background: '#28c840' },
+  title: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    textAlign: 'center',
+    fontSize: 13,
+    color: '#333',
+    fontWeight: 500,
+    fontFamily: '-apple-system, "SF Pro Text", sans-serif',
+    pointerEvents: 'none',
+  },
+  content: {
+    position: 'relative',
+    overflow: 'auto',
+  },
+  titleBarDark: {
+    background: 'linear-gradient(to bottom, #3c3c3c, #2c2c2c)',
+    borderBottom: '0.5px solid rgba(255,255,255,0.1)',
+  },
+  titleDark: {
+    color: '#ddd',
+  },
+};
+
+function MacosWindow({ title = '', width = 900, height = 600, darkMode = false, children }) {
+  return (
+    <div style={{ ...macosWindowStyles.window, background: darkMode ? '#1e1e1e' : '#fff' }}>
+      <div style={{
+        ...macosWindowStyles.titleBar,
+        ...(darkMode ? macosWindowStyles.titleBarDark : {}),
+      }}>
+        <div style={macosWindowStyles.trafficLights}>
+          <div style={{ ...macosWindowStyles.light, ...macosWindowStyles.close }} />
+          <div style={{ ...macosWindowStyles.light, ...macosWindowStyles.minimize }} />
+          <div style={{ ...macosWindowStyles.light, ...macosWindowStyles.maximize }} />
+        </div>
+        {title && (
+          <div style={{
+            ...macosWindowStyles.title,
+            ...(darkMode ? macosWindowStyles.titleDark : {}),
+          }}>
+            {title}
+          </div>
+        )}
+      </div>
+      <div style={{ ...macosWindowStyles.content, width, height }}>
+        {children}
+      </div>
+    </div>
+  );
+}
+
+if (typeof window !== 'undefined') {
+  window.MacosWindow = MacosWindow;
+}

+ 71 - 0
assets/personal-asset-index.example.json

@@ -0,0 +1,71 @@
+{
+  "_meta": {
+    "description": "个人素材索引模板 — 复制此文件并填入你的真实数据",
+    "how_to_use": "1. 复制此文件到你的 agent 私有 memory 目录(Claude Code: ~/.claude/memory/personal-asset-index.json;其他 agent 按其约定)  2. 填入你的真实信息  3. huashu-design skill 在需要个人素材/锚定当前品牌时会自动读取",
+    "note": "真实数据文件不要放在 skill 目录内,避免随 skill 分发泄露隐私"
+  },
+
+  "identity": {
+    "real_name": "你的真名",
+    "pen_names": ["笔名1", "笔名2"],
+    "english_name": "English Name",
+    "title": "你的头衔/一句话介绍",
+    "bio_short": "50-100字简介",
+    "bio_long": "200-300字详细介绍",
+    "avatar_url": "头像URL",
+    "source": "数据来源备注"
+  },
+
+  "contact": {
+    "email": "your@email.com",
+    "wechat_personal": "微信号",
+    "source": "数据来源备注"
+  },
+
+  "social_media": {
+    "github": {
+      "url": "https://github.com/yourname",
+      "username": "yourname"
+    },
+    "youtube": {
+      "url": "https://www.youtube.com/@YourChannel",
+      "channel_name": "频道名"
+    },
+    "source": "数据来源备注"
+  },
+
+  "websites": {
+    "main_site": {
+      "url": "https://yoursite.com",
+      "description": "网站描述",
+      "local_path": "/path/to/local/project/"
+    }
+  },
+
+  "products": {
+    "product_1": {
+      "name": "产品名",
+      "type": "iOS App / Web App / CLI Tool / 电子书",
+      "achievement": "主要成就",
+      "icon_path": "/path/to/icon.png",
+      "project_path": "/path/to/project/"
+    }
+  },
+
+  "stats": {
+    "social_followers": "粉丝数",
+    "product_users": "用户数",
+    "source": "数据来源备注"
+  },
+
+  "design_assets": {
+    "article_images": {
+      "base_path": "/path/to/images/",
+      "notable_sets": []
+    }
+  },
+
+  "knowledge_base": {
+    "wechat_articles": "/path/to/knowledge_base/"
+  }
+}

+ 115 - 0
assets/showcases/INDEX.md

@@ -0,0 +1,115 @@
+# Design Philosophy Showcases — 样例资产索引
+
+> 8 种场景 × 3 种风格 = 24 个预制设计样例
+> 用于 Phase 3 推荐设计方向时,直接展示「这个风格做出来长什么样」
+
+## 风格说明
+
+| 代号 | 流派 | 风格名称 | 视觉气质 |
+|------|------|---------|---------|
+| **Pentagram** | 信息建筑派 | Pentagram / Michael Bierut | 黑白克制、瑞士网格、强字体层级、#E63946红色强调 |
+| **Build** | 极简主义派 | Build Studio | 奢侈品级留白(70%+)、微妙字重(200-600)、#D4A574暖金、精致 |
+| **Takram** | 东方哲学派 | Takram | 柔和科技感、自然色(米色/灰/绿)、圆角、图表如艺术 |
+
+## 场景速查表
+
+### 内容设计场景
+
+| # | 场景 | 规格 | Pentagram | Build | Takram |
+|---|------|------|-----------|-------|--------|
+| 1 | 公众号封面 | 1200×510 | `cover/cover-pentagram` | `cover/cover-build` | `cover/cover-takram` |
+| 2 | PPT数据页 | 1920×1080 | `ppt/ppt-pentagram` | `ppt/ppt-build` | `ppt/ppt-takram` |
+| 3 | 竖版信息图 | 1080×1920 | `infographic/infographic-pentagram` | `infographic/infographic-build` | `infographic/infographic-takram` |
+
+### 网站设计场景
+
+| # | 场景 | 规格 | Pentagram | Build | Takram |
+|---|------|------|-----------|-------|--------|
+| 4 | 个人主页 | 1440×900 | `website-homepage/homepage-pentagram` | `website-homepage/homepage-build` | `website-homepage/homepage-takram` |
+| 5 | AI导航站 | 1440×900 | `website-ai-nav/ainav-pentagram` | `website-ai-nav/ainav-build` | `website-ai-nav/ainav-takram` |
+| 6 | AI写作工具 | 1440×900 | `website-ai-writing/aiwriting-pentagram` | `website-ai-writing/aiwriting-build` | `website-ai-writing/aiwriting-takram` |
+| 7 | SaaS落地页 | 1440×900 | `website-saas/saas-pentagram` | `website-saas/saas-build` | `website-saas/saas-takram` |
+| 8 | 开发者文档 | 1440×900 | `website-devdocs/devdocs-pentagram` | `website-devdocs/devdocs-build` | `website-devdocs/devdocs-takram` |
+
+> 每个条目同时有 `.html`(源码)和 `.png`(截图)两个文件
+
+## 使用说明
+
+### Phase 3 推荐时引用
+推荐设计方向后,可展示对应场景的预制截图:
+```
+「这是 Pentagram 风格做公众号封面的效果 → [展示 cover/cover-pentagram.png]」
+「Takram 风格做 PPT 数据页是这种感觉 → [展示 ppt/ppt-takram.png]」
+```
+
+### 场景匹配优先级
+1. 用户需求的场景有精确匹配 → 直接展示对应场景
+2. 无精确匹配但类型相近 → 展示最近似的场景(如「产品官网」→ 展示 SaaS 落地页)
+3. 完全不匹配 → 跳过预制样例,直接进 Phase 3.5 现场生成
+
+### 横向对比展示
+同一场景的 3 个风格适合并排展示,帮助用户直观比较:
+- 「这是同一个公众号封面,分别用 3 种风格实现的效果」
+- 展示顺序:Pentagram(理性克制)→ Build(奢华极简)→ Takram(柔和温暖)
+
+## 内容详情
+
+### 公众号封面(cover/)
+- 内容:Claude Code Agent 工作流 — 8 个并行 Agent 架构
+- Pentagram:巨大红色「8」+ 瑞士网格线 + 数据条
+- Build:超细字重「Agent」悬浮于 70% 留白中 + 暖金细线
+- Takram:8 节点放射状流程图作为艺术品 + 米色底
+
+### PPT数据页(ppt/)
+- 内容:GLM-4.7 开源模型 Coding 能力突破(AIME 95.7 / SWE-bench 73.8% / τ²-Bench 87.4)
+- Pentagram:260px「95.7」锚点 + 红/灰/浅灰对比条形图
+- Build:三组 120px 超细数字悬浮 + 暖金渐变对比条
+- Takram:SVG 雷达图 + 三色叠加 + 圆角数据卡片
+
+### 竖版信息图(infographic/)
+- 内容:AI 记忆系统 CLAUDE.md 从 93KB 优化到 22KB
+- Pentagram:巨大「93→22」数字 + 编号区块 + CSS 数据条
+- Build:极致留白 + 柔影卡片 + 暖金连接线
+- Takram:SVG 环形图 + 有机曲线流程图 + 毛玻璃卡片
+
+### 个人主页(website-homepage/)
+- 内容:独立开发者 Alex Chen 的作品集首页
+- Pentagram:112px 大名 + 瑞士网格分栏 + 编辑数字
+- Build:玻璃态导航 + 悬浮统计卡片 + 超细字重
+- Takram:纸质纹理 + 小圆形头像 + 发丝细分隔线 + 不对称布局
+
+### AI导航站(website-ai-nav/)
+- 内容:AI Compass — 500+ AI 工具目录
+- Pentagram:方角搜索框 + 编号工具列表 + 大写分类标签
+- Build:圆角搜索框 + 精致白色工具卡片 + 药丸标签
+- Takram:有机错位卡片布局 + 柔和分类标签 + 图表式连接
+
+### AI写作工具(website-ai-writing/)
+- 内容:Inkwell — AI 写作助手
+- Pentagram:86px 大标题 + 线框编辑器模型 + 网格特性列
+- Build:漂浮编辑器卡片 + 暖金 CTA + 奢华写作体验
+- Takram:诗意衬线标题 + 有机编辑器 + 流程图
+
+### SaaS落地页(website-saas/)
+- 内容:Meridian — 商业智能分析平台
+- Pentagram:黑白分栏 + 结构化仪表盘 + 140px「3x」锚点
+- Build:悬浮仪表盘卡片 + SVG 面积图 + 暖金渐变
+- Takram:圆角柱状图 + 流程节点 + 柔和地球色
+
+### 开发者文档(website-devdocs/)
+- 内容:Nexus API — 统一 AI 模型网关
+- Pentagram:左侧导航栏 + 方角代码块 + 红色字符串高亮
+- Build:居中漂浮代码卡片 + 柔影 + 暖金图标
+- Takram:米色代码块 + 流程图连接 + 虚线特性卡片
+
+## 文件统计
+
+- HTML 源文件:24 个
+- PNG 截图:24 个
+- 总资产:48 个文件
+
+---
+
+**版本**:v1.0
+**创建日期**:2026-02-13
+**适用于**:design-philosophy skill Phase 3 推荐环节

+ 235 - 0
assets/showcases/cover/cover-build.html

@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1200">
+<title>Claude Code Agent - Build Studio Style</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1200px;
+    height: 510px;
+    overflow: hidden;
+    margin: 0;
+    background: #FAFAF8;
+    font-family: 'Inter', sans-serif;
+    position: relative;
+  }
+
+  /* Subtle top gradient wash */
+  .wash {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 1200px;
+    height: 510px;
+    background: radial-gradient(ellipse 800px 400px at 30% 40%, rgba(212, 165, 116, 0.06) 0%, transparent 70%);
+    z-index: 0;
+  }
+
+  /* Main layout */
+  .layout {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 1200px;
+    height: 510px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1;
+  }
+
+  .center-block {
+    text-align: center;
+    max-width: 700px;
+    margin-top: -24px; /* slight upward shift for golden ratio vertical center */
+  }
+
+  /* Floating "Agent" */
+  .floating-agent {
+    font-family: 'Inter', sans-serif;
+    font-weight: 200;
+    font-size: 128px;
+    letter-spacing: -4px;
+    color: #1A1A18;
+    line-height: 1;
+    margin-bottom: 16px;
+    position: relative;
+  }
+
+  .floating-agent span {
+    position: relative;
+    display: inline-block;
+  }
+
+  /* Slight weight shift on first letter for visual interest */
+  .floating-agent .accent-letter {
+    font-weight: 300;
+    color: #2A2A28;
+  }
+
+  /* Gold underline accent */
+  .gold-line {
+    width: 48px;
+    height: 1px;
+    background: #D4A574;
+    margin: 0 auto 32px;
+    opacity: 0.7;
+  }
+
+  /* Subtitle — label tier: smallest text, widest spacing */
+  .subtitle {
+    font-family: 'Inter', sans-serif;
+    font-weight: 400;
+    font-size: 10px;
+    letter-spacing: 6px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+    margin-bottom: 24px;
+  }
+
+  /* Description line — body tier */
+  .desc {
+    font-family: 'Inter', sans-serif;
+    font-weight: 300;
+    font-size: 13px;
+    color: #A8A4A0;
+    letter-spacing: 0.3px;
+    line-height: 2;
+    max-width: 400px;
+    margin: 0 auto;
+  }
+
+  /* Minimal agent indicators — 8 thin vertical lines */
+  .agent-indicators {
+    position: absolute;
+    bottom: 48px;
+    left: 50%;
+    transform: translateX(-50%);
+    display: flex;
+    gap: 16px;
+    align-items: flex-end;
+    z-index: 2;
+  }
+
+  .indicator {
+    width: 1px;
+    background: #D8D4CE;
+    border-radius: 0.5px;
+  }
+
+  .indicator.gold {
+    background: #D4A574;
+    width: 1.5px;
+    opacity: 0.8;
+  }
+
+  /* Corner marks */
+  .corner-mark {
+    position: absolute;
+    z-index: 2;
+  }
+
+  .corner-mark svg {
+    display: block;
+  }
+
+  .corner-tl { top: 48px; left: 48px; }
+  .corner-br { bottom: 48px; right: 48px; transform: rotate(180deg); }
+
+  /* Side text */
+  .side-label {
+    position: absolute;
+    font-family: 'Inter', sans-serif;
+    font-weight: 400;
+    font-size: 8px;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    color: #CBC7C0;
+    z-index: 2;
+  }
+
+  .side-left {
+    left: 48px;
+    top: 50%;
+    transform: translateY(-50%) rotate(-90deg);
+    transform-origin: center center;
+  }
+
+  .side-right {
+    right: 48px;
+    top: 50%;
+    transform: translateY(-50%) rotate(90deg);
+    transform-origin: center center;
+  }
+
+  /* Removed shadow-card — Build purity demands uninterrupted whitespace */
+
+  /* Number 8 whisper */
+  .number-whisper {
+    position: absolute;
+    top: 48px;
+    right: 96px;
+    font-family: 'Inter', sans-serif;
+    font-weight: 200;
+    font-size: 24px;
+    color: #D4A574;
+    opacity: 0.35;
+    z-index: 2;
+  }
+</style>
+</head>
+<body>
+
+  <div class="wash"></div>
+
+  <!-- Corner marks -->
+  <div class="corner-mark corner-tl">
+    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <path d="M0 0L0 20" stroke="#D4A574" stroke-width="0.5" opacity="0.4"/>
+      <path d="M0 0L20 0" stroke="#D4A574" stroke-width="0.5" opacity="0.4"/>
+    </svg>
+  </div>
+  <div class="corner-mark corner-br">
+    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <path d="M0 0L0 20" stroke="#D4A574" stroke-width="0.5" opacity="0.4"/>
+      <path d="M0 0L20 0" stroke="#D4A574" stroke-width="0.5" opacity="0.4"/>
+    </svg>
+  </div>
+
+  <!-- Side labels -->
+  <div class="side-label side-left">Claude Code</div>
+  <div class="side-label side-right">Parallel Workflow</div>
+
+  <!-- Number whisper -->
+  <div class="number-whisper">8</div>
+
+  <!-- Main content -->
+  <div class="layout">
+    <div class="center-block">
+      <div class="subtitle">Parallel Architecture</div>
+      <div class="floating-agent"><span><span class="accent-letter">A</span>gent</span></div>
+      <div class="gold-line"></div>
+      <div class="desc">
+        Eight autonomous agents orchestrated in parallel,<br>
+        each solving a distinct piece of the whole.
+      </div>
+    </div>
+  </div>
+
+  <!-- Agent indicators -->
+  <div class="agent-indicators">
+    <div class="indicator" style="height: 20px;"></div>
+    <div class="indicator" style="height: 28px;"></div>
+    <div class="indicator gold" style="height: 36px;"></div>
+    <div class="indicator" style="height: 22px;"></div>
+    <div class="indicator" style="height: 32px;"></div>
+    <div class="indicator gold" style="height: 40px;"></div>
+    <div class="indicator" style="height: 24px;"></div>
+    <div class="indicator" style="height: 30px;"></div>
+  </div>
+
+</body>
+</html>

BIN
assets/showcases/cover/cover-build.png


+ 229 - 0
assets/showcases/cover/cover-pentagram.html

@@ -0,0 +1,229 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1200">
+<title>Agent Parallel — Pentagram Style Cover</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1200px;
+    height: 510px;
+    overflow: hidden;
+    margin: 0;
+    background: #FFFFFF;
+    font-family: 'Helvetica Neue', 'Arial', sans-serif;
+    position: relative;
+  }
+
+  /* Grid rules — Swiss grid visible structure */
+  .rule-h {
+    position: absolute;
+    left: 64px;
+    right: 64px;
+    height: 1px;
+    background: #000;
+    opacity: 0.06;
+  }
+
+  .rule-v {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    width: 1px;
+    background: #000;
+    opacity: 0.04;
+  }
+
+  /* Giant typographic element — the "8" bleeds off right edge */
+  .type-anchor {
+    position: absolute;
+    right: -60px;
+    top: 50%;
+    transform: translateY(-50%);
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 900;
+    font-size: 640px;
+    line-height: 0.82;
+    color: #000;
+    opacity: 0.07;
+    z-index: 0;
+    user-select: none;
+  }
+
+  /* Red geometric dot grid — 8 dots representing 8 agents */
+  .dot-grid {
+    position: absolute;
+    right: 340px;
+    top: 50%;
+    transform: translateY(-50%);
+    display: grid;
+    grid-template-columns: repeat(4, 24px);
+    grid-template-rows: repeat(2, 24px);
+    gap: 16px;
+    z-index: 1;
+  }
+
+  .dot {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: #000;
+    opacity: 0.12;
+    align-self: center;
+    justify-self: center;
+  }
+
+  .dot.active {
+    background: #E63946;
+    opacity: 0.8;
+    width: 10px;
+    height: 10px;
+  }
+
+  /* Primary content zone — left-aligned on Swiss grid */
+  .content {
+    position: absolute;
+    left: 64px;
+    top: 56px;
+    z-index: 2;
+  }
+
+  .label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    color: #E63946;
+    margin-bottom: 16px;
+  }
+
+  .title {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 900;
+    font-size: 120px;
+    line-height: 0.9;
+    color: #000;
+    letter-spacing: -5px;
+  }
+
+  .title .accent {
+    color: #E63946;
+  }
+
+  /* Bottom information bar */
+  .bottom-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 48px;
+    background: #000;
+    z-index: 2;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 64px;
+  }
+
+  .bottom-left {
+    display: flex;
+    align-items: center;
+    gap: 24px;
+  }
+
+  .bottom-stat {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #fff;
+    opacity: 0.6;
+  }
+
+  .bottom-stat strong {
+    color: #E63946;
+    opacity: 1;
+    font-size: 16px;
+    margin-right: 6px;
+  }
+
+  .bottom-right {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 10px;
+    font-weight: 700;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+    color: #fff;
+    opacity: 0.4;
+  }
+
+  /* Subtitle */
+  .subtitle {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 14px;
+    font-weight: 500;
+    color: #999;
+    letter-spacing: 0.5px;
+    margin-top: 20px;
+  }
+
+  /* Horizontal red rule through center */
+  .center-rule {
+    position: absolute;
+    left: 64px;
+    width: 240px;
+    height: 3px;
+    background: #E63946;
+    top: 306px;
+    z-index: 2;
+  }
+</style>
+</head>
+<body>
+
+  <!-- Grid structure -->
+  <div class="rule-h" style="top: 56px;"></div>
+  <div class="rule-v" style="left: 64px;"></div>
+  <div class="rule-v" style="left: 600px;"></div>
+  <div class="rule-v" style="right: 64px;"></div>
+
+  <!-- Typographic anchor — bleeds right -->
+  <div class="type-anchor">8</div>
+
+  <!-- 8-dot grid representing agents -->
+  <div class="dot-grid">
+    <div class="dot active"></div>
+    <div class="dot"></div>
+    <div class="dot active"></div>
+    <div class="dot"></div>
+    <div class="dot"></div>
+    <div class="dot active"></div>
+    <div class="dot"></div>
+    <div class="dot active"></div>
+  </div>
+
+  <!-- Content -->
+  <div class="content">
+    <div class="label">Claude Code Architecture</div>
+    <div class="title">Agent<br><span class="accent">Parallel</span></div>
+    <div class="subtitle">8 autonomous agents running in unified workflow</div>
+  </div>
+
+  <!-- Red horizontal rule -->
+  <div class="center-rule"></div>
+
+  <!-- Black bottom bar with data -->
+  <div class="bottom-bar">
+    <div class="bottom-left">
+      <div class="bottom-stat"><strong>8</strong>Agents</div>
+      <div class="bottom-stat"><strong>3.2x</strong>Faster</div>
+      <div class="bottom-stat"><strong>1</strong>Workflow</div>
+    </div>
+    <div class="bottom-right">Pentagram Design System</div>
+  </div>
+
+</body>
+</html>

BIN
assets/showcases/cover/cover-pentagram.png


+ 288 - 0
assets/showcases/cover/cover-takram.html

@@ -0,0 +1,288 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1200">
+<title>Claude Code Agent - Takram Style</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&family=Noto+Serif+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1200px;
+    height: 510px;
+    overflow: hidden;
+    margin: 0;
+    background: #F5F0EB;
+    font-family: 'Inter', sans-serif;
+    position: relative;
+  }
+
+  /* Subtle paper texture overlay */
+  .texture {
+    position: absolute;
+    top: 0; left: 0;
+    width: 1200px;
+    height: 510px;
+    background:
+      radial-gradient(ellipse 500px 400px at 72% 50%, rgba(168, 181, 160, 0.06) 0%, transparent 70%),
+      radial-gradient(ellipse 300px 250px at 15% 40%, rgba(232, 228, 220, 0.2) 0%, transparent 60%);
+    z-index: 0;
+  }
+
+  /* Flow diagram — the art piece */
+  .diagram {
+    position: absolute;
+    top: 0; left: 0;
+    width: 1200px;
+    height: 510px;
+    z-index: 1;
+  }
+
+  /* Left text panel */
+  .text-panel {
+    position: absolute;
+    left: 72px;
+    top: 56px;
+    z-index: 2;
+    max-width: 360px;
+  }
+
+  .text-panel .label {
+    font-family: 'Inter', sans-serif;
+    font-weight: 500;
+    font-size: 9px;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+    color: #6B8F71;
+    margin-bottom: 20px;
+    opacity: 0.8;
+  }
+
+  .text-panel .title-main {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 500;
+    font-size: 52px;
+    color: #2D3436;
+    line-height: 1.15;
+    letter-spacing: -0.5px;
+    margin-bottom: 4px;
+  }
+
+  .text-panel .title-sub {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 300;
+    font-size: 20px;
+    color: #6D685F;
+    line-height: 1.4;
+    margin-bottom: 16px;
+  }
+
+  .text-panel .title-en {
+    font-family: 'Inter', sans-serif;
+    font-weight: 300;
+    font-size: 13px;
+    color: #9A958D;
+    letter-spacing: 0.3px;
+    line-height: 1.8;
+  }
+
+  /* Bottom annotation */
+  .annotation {
+    position: absolute;
+    left: 72px;
+    bottom: 40px;
+    z-index: 2;
+  }
+
+  .annotation .note {
+    font-family: 'Inter', sans-serif;
+    font-weight: 400;
+    font-size: 10px;
+    color: #B0AAA0;
+    letter-spacing: 0.3px;
+  }
+
+  .annotation .note-serif {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 300;
+    font-size: 11px;
+    color: #9A958D;
+    margin-top: 4px;
+  }
+
+  /* Right side number */
+  .spec-number {
+    position: absolute;
+    right: 72px;
+    bottom: 40px;
+    font-family: 'Inter', sans-serif;
+    font-weight: 300;
+    font-size: 10px;
+    color: #B0AAA0;
+    letter-spacing: 1px;
+    z-index: 2;
+  }
+
+  /* Agent node styling */
+  .node-label {
+    font-family: 'Inter', sans-serif;
+    font-size: 9px;
+    font-weight: 400;
+    fill: #8A857D;
+    letter-spacing: 0.5px;
+  }
+
+  .node-label-serif {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 11px;
+    font-weight: 400;
+    fill: #6D685F;
+  }
+
+  .node-index {
+    font-family: 'Inter', sans-serif;
+    font-size: 7px;
+    font-weight: 400;
+    fill: #B0AAA0;
+    letter-spacing: 0.5px;
+  }
+</style>
+</head>
+<body>
+
+  <div class="texture"></div>
+
+  <!-- Text panel -->
+  <div class="text-panel">
+    <div class="label">Speculative Architecture</div>
+    <div class="title-main">协作智能体</div>
+    <div class="title-sub">Parallel Workflow</div>
+    <div class="title-en">
+      Eight agents, each autonomous,<br>
+      converging toward a shared intent.
+    </div>
+  </div>
+
+  <!-- The diagram as art -->
+  <svg class="diagram" viewBox="0 0 1200 510" xmlns="http://www.w3.org/2000/svg">
+
+    <!-- Subtle background grid hints (Takram spec-drawing aesthetic) -->
+    <line x1="440" y1="0" x2="440" y2="510" stroke="#E8E4DC" stroke-width="0.3" opacity="0.4"/>
+    <line x1="760" y1="0" x2="760" y2="510" stroke="#E8E4DC" stroke-width="0.3" opacity="0.3"/>
+
+    <!-- Subtle outer orbital paths — layered ellipses for depth -->
+    <ellipse cx="760" cy="255" rx="260" ry="195" fill="none" stroke="#E0DCD5" stroke-width="0.5" stroke-dasharray="1,8" opacity="0.5"/>
+    <ellipse cx="760" cy="255" rx="180" ry="135" fill="none" stroke="#D8D3CB" stroke-width="0.4" stroke-dasharray="2,6" opacity="0.35"/>
+
+    <!-- Central orchestrator node — refined with layered depth -->
+    <circle cx="760" cy="255" r="48" fill="none" stroke="#6B8F71" stroke-width="0.5" opacity="0.12" stroke-dasharray="2,4"/>
+    <circle cx="760" cy="255" r="36" fill="none" stroke="#6B8F71" stroke-width="0.8" opacity="0.18"/>
+    <circle cx="760" cy="255" r="24" fill="none" stroke="#6B8F71" stroke-width="1.2" opacity="0.3"/>
+    <circle cx="760" cy="255" r="14" fill="rgba(107,143,113,0.05)"/>
+    <circle cx="760" cy="255" r="5.5" fill="#6B8F71" opacity="0.55"/>
+    <circle cx="760" cy="255" r="2" fill="#6B8F71" opacity="0.9"/>
+    <text x="760" y="312" text-anchor="middle" class="node-label-serif">Orchestrator</text>
+    <!-- Subtle cross-hair on center -->
+    <line x1="748" y1="255" x2="730" y2="255" stroke="#6B8F71" stroke-width="0.3" opacity="0.15"/>
+    <line x1="772" y1="255" x2="790" y2="255" stroke="#6B8F71" stroke-width="0.3" opacity="0.15"/>
+    <line x1="760" y1="243" x2="760" y2="225" stroke="#6B8F71" stroke-width="0.3" opacity="0.15"/>
+    <line x1="760" y1="267" x2="760" y2="285" stroke="#6B8F71" stroke-width="0.3" opacity="0.15"/>
+
+    <!-- Agent 1 — top-left (Research) -->
+    <line x1="738" y1="232" x2="598" y2="118" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="560" y="92" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="598" cy="111" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="598" y="144" text-anchor="middle" class="node-label">Research</text>
+    <text x="560" y="88" class="node-index">01</text>
+
+    <!-- Agent 2 — top (Analysis) -->
+    <line x1="760" y1="217" x2="760" y2="145" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="722" y="100" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="760" cy="119" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="760" y="152" text-anchor="middle" class="node-label">Analysis</text>
+    <text x="722" y="96" class="node-index">02</text>
+
+    <!-- Agent 3 — top-right (Code) -->
+    <line x1="782" y1="232" x2="918" y2="118" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="884" y="92" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="922" cy="111" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="922" y="144" text-anchor="middle" class="node-label">Code</text>
+    <text x="884" y="88" class="node-index">03</text>
+
+    <!-- Agent 4 — right (Test) -->
+    <line x1="786" y1="252" x2="940" y2="215" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="940" y="196" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="978" cy="215" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="978" y="248" text-anchor="middle" class="node-label">Test</text>
+    <text x="940" y="192" class="node-index">04</text>
+
+    <!-- Agent 5 — bottom-right (Review) -->
+    <line x1="782" y1="278" x2="918" y2="385" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="884" y="368" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="922" cy="387" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="922" y="420" text-anchor="middle" class="node-label">Review</text>
+    <text x="884" y="364" class="node-index">05</text>
+
+    <!-- Agent 6 — bottom (Deploy) -->
+    <line x1="760" y1="293" x2="760" y2="365" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="722" y="370" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="760" cy="389" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="760" y="422" text-anchor="middle" class="node-label">Deploy</text>
+    <text x="722" y="366" class="node-index">06</text>
+
+    <!-- Agent 7 — bottom-left (Monitor) -->
+    <line x1="738" y1="278" x2="600" y2="375" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="562" y="358" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="600" cy="377" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="600" y="410" text-anchor="middle" class="node-label">Monitor</text>
+    <text x="562" y="354" class="node-index">07</text>
+
+    <!-- Agent 8 — left (Design) -->
+    <line x1="734" y1="252" x2="578" y2="245" stroke="#C8C2B8" stroke-width="0.7" stroke-dasharray="3,5"/>
+    <rect x="502" y="226" width="76" height="38" rx="14" fill="rgba(245,240,235,0.7)" stroke="#B8B2A8" stroke-width="0.8"/>
+    <circle cx="540" cy="245" r="3.5" fill="#6B8F71" opacity="0.5"/>
+    <text x="540" y="278" text-anchor="middle" class="node-label">Design</text>
+    <text x="502" y="222" class="node-index">08</text>
+
+    <!-- Small annotation marks — Takram spec-drawing details -->
+    <circle cx="490" cy="120" r="1.2" fill="#B0AAA0" opacity="0.35"/>
+    <line x1="492" y1="120" x2="535" y2="120" stroke="#B0AAA0" stroke-width="0.4" opacity="0.25"/>
+
+    <circle cx="1040" cy="390" r="1.2" fill="#B0AAA0" opacity="0.35"/>
+    <line x1="1038" y1="390" x2="995" y2="390" stroke="#B0AAA0" stroke-width="0.4" opacity="0.25"/>
+
+    <!-- Dimension annotation line (top) -->
+    <line x1="540" y1="60" x2="980" y2="60" stroke="#D4CFC6" stroke-width="0.4" opacity="0.3"/>
+    <line x1="540" y1="55" x2="540" y2="65" stroke="#D4CFC6" stroke-width="0.4" opacity="0.3"/>
+    <line x1="980" y1="55" x2="980" y2="65" stroke="#D4CFC6" stroke-width="0.4" opacity="0.3"/>
+    <text x="760" y="54" text-anchor="middle" font-family="Inter" font-size="7" font-weight="300" fill="#C8C2B8" letter-spacing="1.5">AGENT FIELD</text>
+
+    <!-- Right-side vertical annotation -->
+    <line x1="1060" y1="130" x2="1060" y2="380" stroke="#D4CFC6" stroke-width="0.3" opacity="0.25"/>
+    <line x1="1056" y1="130" x2="1064" y2="130" stroke="#D4CFC6" stroke-width="0.3" opacity="0.25"/>
+    <line x1="1056" y1="380" x2="1064" y2="380" stroke="#D4CFC6" stroke-width="0.3" opacity="0.25"/>
+    <text x="1068" y="260" font-family="Inter" font-size="7" font-weight="300" fill="#D4CFC6" letter-spacing="0.5" transform="rotate(90, 1068, 260)">NETWORK DEPTH</text>
+
+    <!-- Subtle data pulse lines emanating from center (organic feel) -->
+    <path d="M 760 217 Q 755 200 758 185" fill="none" stroke="#6B8F71" stroke-width="0.3" opacity="0.12"/>
+    <path d="M 786 248 Q 810 230 835 225" fill="none" stroke="#6B8F71" stroke-width="0.3" opacity="0.12"/>
+    <path d="M 786 262 Q 815 275 840 290" fill="none" stroke="#6B8F71" stroke-width="0.3" opacity="0.12"/>
+
+    <!-- Top-right spec box -->
+    <rect x="1040" y="48" width="104" height="56" rx="3" fill="rgba(245,240,235,0.5)" stroke="#E0DCD5" stroke-width="0.6"/>
+    <text x="1052" y="66" font-family="Inter" font-size="8" font-weight="500" fill="#B0AAA0" letter-spacing="1.5">AGENTS</text>
+    <text x="1052" y="92" font-family="Inter" font-size="28" font-weight="300" fill="#6B8F71">8</text>
+    <text x="1082" y="92" font-family="Inter" font-size="9" font-weight="300" fill="#B0AAA0"> parallel</text>
+  </svg>
+
+  <!-- Bottom annotation -->
+  <div class="annotation">
+    <div class="note">Fig. 01 — Parallel Agent Architecture</div>
+    <div class="note-serif">Claude Code 协作编排模型</div>
+  </div>
+
+  <!-- Spec number -->
+  <div class="spec-number">v1.0 / 2026</div>
+
+</body>
+</html>

BIN
assets/showcases/cover/cover-takram.png


+ 503 - 0
assets/showcases/infographic/infographic-build.html

@@ -0,0 +1,503 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1080">
+<title>AI Memory System Optimization — Build Studio Style</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1080px;
+    height: 1920px;
+    overflow: hidden;
+    margin: 0;
+    background: #FAFAF8;
+    font-family: 'Inter', sans-serif;
+    color: #2A2A2A;
+  }
+
+  .container {
+    width: 100%;
+    height: 100%;
+    padding: 80px 80px 64px 80px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+  }
+
+  /* Header */
+  .label {
+    font-size: 10px;
+    font-weight: 400;
+    letter-spacing: 5px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+    margin-bottom: 32px;
+  }
+  .title {
+    font-size: 36px;
+    font-weight: 200;
+    line-height: 1.35;
+    color: #1A1A1A;
+    letter-spacing: -0.5px;
+    max-width: 680px;
+  }
+  .title strong {
+    font-weight: 500;
+  }
+
+  /* Hero Numbers */
+  .hero {
+    margin-top: 56px;
+    display: flex;
+    align-items: flex-end;
+    gap: 48px;
+  }
+  .hero-block {
+    display: flex;
+    flex-direction: column;
+  }
+  .hero-label {
+    font-size: 10px;
+    font-weight: 400;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+    margin-bottom: 8px;
+  }
+  .hero-number {
+    font-size: 112px;
+    font-weight: 200;
+    line-height: 0.9;
+    color: #1A1A1A;
+    letter-spacing: -4px;
+  }
+  .hero-number .unit {
+    font-size: 28px;
+    font-weight: 300;
+    letter-spacing: 0;
+    color: #B0ACA4;
+    margin-left: 4px;
+  }
+  .hero-number.gold {
+    color: #1A1A1A;
+  }
+  .hero-number.gold .unit {
+    color: #D4A574;
+    opacity: 0.7;
+  }
+  .hero-number.gold .dot-accent {
+    color: #D4A574;
+  }
+  .hero-connector {
+    display: flex;
+    align-items: center;
+    margin-bottom: 24px;
+  }
+  .hero-connector svg {
+    opacity: 0.25;
+  }
+  .hero-reduction {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin-bottom: 24px;
+  }
+  .reduction-badge {
+    font-size: 13px;
+    font-weight: 400;
+    color: #D4A574;
+    letter-spacing: 2px;
+  }
+
+  /* Subtle line */
+  .divider {
+    width: 48px;
+    height: 1px;
+    background: #D4A574;
+    margin: 48px 0;
+    opacity: 0.4;
+  }
+
+  /* Stats Row */
+  .stats-row {
+    display: flex;
+    gap: 0;
+  }
+  .stat-item {
+    flex: 1;
+    padding: 24px 0;
+    position: relative;
+  }
+  .stat-item::after {
+    content: '';
+    position: absolute;
+    right: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 1px;
+    height: 40px;
+    background: #E0DCDA;
+  }
+  .stat-item:last-child::after {
+    display: none;
+  }
+  .stat-value {
+    font-size: 40px;
+    font-weight: 200;
+    color: #1A1A1A;
+    line-height: 1;
+    margin-bottom: 8px;
+  }
+  .stat-desc {
+    font-size: 11px;
+    font-weight: 300;
+    color: #B0ACA4;
+    line-height: 1.5;
+    letter-spacing: 0.5px;
+  }
+
+  /* Memory Cards */
+  .cards-section {
+    margin-top: 40px;
+  }
+  .cards-label {
+    font-size: 10px;
+    font-weight: 400;
+    letter-spacing: 5px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+    margin-bottom: 24px;
+  }
+  .cards-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 16px;
+  }
+  .card {
+    background: #FFFFFF;
+    padding: 32px;
+    box-shadow: 0 1px 2px rgba(0,0,0,0.03), 0 4px 16px rgba(0,0,0,0.02);
+    border-radius: 2px;
+    position: relative;
+  }
+  .card-index {
+    font-size: 40px;
+    font-weight: 200;
+    color: #E8E4E0;
+    line-height: 1;
+    margin-bottom: 16px;
+  }
+  .card-title-zh {
+    font-size: 18px;
+    font-weight: 500;
+    color: #1A1A1A;
+    margin-bottom: 4px;
+    line-height: 1.3;
+  }
+  .card-title-en {
+    font-size: 10px;
+    font-weight: 400;
+    color: #C0BCB6;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    margin-bottom: 16px;
+  }
+  .card-desc {
+    font-size: 12px;
+    font-weight: 300;
+    color: #999;
+    line-height: 1.7;
+  }
+  .card.featured {
+    border-left: 1.5px solid #D4A574;
+  }
+  .card.featured .card-index {
+    color: #D4A574;
+    opacity: 0.35;
+  }
+
+  /* Flow */
+  .flow-section {
+    margin-top: 40px;
+  }
+  .flow-label {
+    font-size: 10px;
+    font-weight: 400;
+    letter-spacing: 5px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+    margin-bottom: 32px;
+  }
+  .flow-timeline {
+    position: relative;
+    padding-left: 0;
+  }
+  .flow-steps {
+    display: flex;
+    justify-content: space-between;
+    position: relative;
+  }
+  .flow-steps::before {
+    content: '';
+    position: absolute;
+    top: 8px;
+    left: 36px;
+    right: 36px;
+    height: 1px;
+    background: linear-gradient(to right, #E0DCDA, #D4A574 50%, #E0DCDA);
+  }
+  .flow-step {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 16px;
+    flex: 1;
+    position: relative;
+    z-index: 1;
+  }
+  .flow-dot {
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    background: #FAFAF8;
+    border: 1px solid #D0CCC6;
+    flex-shrink: 0;
+  }
+  .flow-dot.active {
+    border-color: #D4A574;
+    background: #D4A574;
+  }
+  .flow-step-label {
+    font-size: 9px;
+    font-weight: 400;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #C0BCB6;
+  }
+  .flow-step-text {
+    font-size: 13px;
+    font-weight: 400;
+    color: #2A2A2A;
+    text-align: center;
+    line-height: 1.4;
+  }
+
+  /* Quote */
+  .quote-section {
+    margin-top: 0;
+    padding-top: 32px;
+    border-top: 1px solid #EEECE8;
+  }
+  .quote-line {
+    width: 32px;
+    height: 1px;
+    background: #D4A574;
+    margin-bottom: 24px;
+    opacity: 0.5;
+  }
+  .quote-text {
+    font-size: 22px;
+    font-weight: 200;
+    color: #1A1A1A;
+    line-height: 1.6;
+    letter-spacing: -0.3px;
+    max-width: 680px;
+  }
+  .quote-text em {
+    font-style: normal;
+    color: #D4A574;
+    font-weight: 400;
+  }
+
+  /* Results */
+  .results-row {
+    display: flex;
+    gap: 48px;
+    margin-top: 32px;
+  }
+  .result-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
+  .result-icon {
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #D4A574;
+    flex-shrink: 0;
+    opacity: 0.6;
+  }
+  .result-text {
+    font-size: 13px;
+    font-weight: 400;
+    color: #999999;
+    letter-spacing: 0.3px;
+  }
+
+  /* Footer */
+  .footer {
+    margin-top: 32px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .footer-text {
+    font-size: 9px;
+    font-weight: 300;
+    color: #D0CCC6;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+  }
+</style>
+</head>
+<body>
+<div class="container">
+
+  <!-- Label -->
+  <div class="label">System Architecture</div>
+
+  <!-- Title -->
+  <div class="title">
+    AI记忆系统<br>
+    CLAUDE.md <strong>从 93KB<br>优化到 22KB</strong>
+  </div>
+
+  <!-- Hero Numbers -->
+  <div class="hero">
+    <div class="hero-block">
+      <span class="hero-label">Before</span>
+      <span class="hero-number">93<span class="unit">KB</span></span>
+    </div>
+    <div class="hero-connector">
+      <svg width="48" height="8" viewBox="0 0 48 8">
+        <line x1="0" y1="4" x2="40" y2="4" stroke="#D4A574" stroke-width="0.75" opacity="0.5"/>
+        <line x1="36" y1="1" x2="42" y2="4" stroke="#D4A574" stroke-width="0.75" opacity="0.5"/>
+        <line x1="36" y1="7" x2="42" y2="4" stroke="#D4A574" stroke-width="0.75" opacity="0.5"/>
+      </svg>
+    </div>
+    <div class="hero-block">
+      <span class="hero-label">After</span>
+      <span class="hero-number gold">22<span class="unit">KB</span></span>
+    </div>
+    <div class="hero-reduction">
+      <span class="reduction-badge">-76%</span>
+    </div>
+  </div>
+
+  <!-- Stats -->
+  <div class="divider"></div>
+  <div class="stats-row">
+    <div class="stat-item">
+      <div class="stat-value">2400<span style="font-size:18px;color:#AAAAAA">+</span></div>
+      <div class="stat-desc">lines before<br>in single file</div>
+    </div>
+    <div class="stat-item" style="padding-left: 24px;">
+      <div class="stat-value">4</div>
+      <div class="stat-desc">structured<br>memory categories</div>
+    </div>
+    <div class="stat-item" style="padding-left: 24px;">
+      <div class="stat-value">0</div>
+      <div class="stat-desc">information<br>loss</div>
+    </div>
+  </div>
+
+  <!-- Memory Cards -->
+  <div class="cards-section">
+    <div class="cards-label">Memory Categories</div>
+    <div class="cards-grid">
+      <div class="card featured">
+        <div class="card-index">01</div>
+        <div class="card-title-zh">核心身份</div>
+        <div class="card-title-en">Core Identity</div>
+        <div class="card-desc">Immutable traits, facts, fundamental identity markers</div>
+      </div>
+      <div class="card">
+        <div class="card-index">02</div>
+        <div class="card-title-zh">偏好设置</div>
+        <div class="card-title-en">Preferences</div>
+        <div class="card-desc">Style choices, tool habits, accumulated over sessions</div>
+      </div>
+      <div class="card">
+        <div class="card-index">03</div>
+        <div class="card-title-zh">项目状态</div>
+        <div class="card-title-en">Project State</div>
+        <div class="card-desc">Active tasks, deadlines, priorities, evolving context</div>
+      </div>
+      <div class="card">
+        <div class="card-index">04</div>
+        <div class="card-title-zh">日志流水</div>
+        <div class="card-title-en">Daily Logs</div>
+        <div class="card-desc">Session records, never auto-loaded, search on demand</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Flow -->
+  <div class="flow-section">
+    <div class="flow-label">Processing Flow</div>
+    <div class="flow-timeline">
+      <div class="flow-steps">
+        <div class="flow-step">
+          <div class="flow-dot"></div>
+          <div class="flow-step-label">Input</div>
+          <div class="flow-step-text">User<br>Input</div>
+        </div>
+        <div class="flow-step">
+          <div class="flow-dot"></div>
+          <div class="flow-step-label">Route</div>
+          <div class="flow-step-text">Workspace<br>Detection</div>
+        </div>
+        <div class="flow-step">
+          <div class="flow-dot active"></div>
+          <div class="flow-step-label">Load</div>
+          <div class="flow-step-text">Relevant<br>Memory</div>
+        </div>
+        <div class="flow-step">
+          <div class="flow-dot"></div>
+          <div class="flow-step-label">Execute</div>
+          <div class="flow-step-text">Task<br>Processing</div>
+        </div>
+        <div class="flow-step">
+          <div class="flow-dot"></div>
+          <div class="flow-step-label">Update</div>
+          <div class="flow-step-text">Memory<br>Write-back</div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Quote -->
+  <div class="quote-section">
+    <div class="quote-line"></div>
+    <div class="quote-text">
+      Like <em>Marie Kondo</em> for AI memory<br>
+      — keep only what sparks joy.
+    </div>
+    <div class="results-row">
+      <div class="result-item">
+        <div class="result-icon"></div>
+        <span class="result-text">Faster context loading</span>
+      </div>
+      <div class="result-item">
+        <div class="result-icon"></div>
+        <span class="result-text">More relevant responses</span>
+      </div>
+      <div class="result-item">
+        <div class="result-icon"></div>
+        <span class="result-text">Zero information loss</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- Footer -->
+  <div class="footer">
+    <span class="footer-text">Build Studio Style</span>
+    <span class="footer-text">2026</span>
+  </div>
+
+</div>
+</body>
+</html>

BIN
assets/showcases/infographic/infographic-build.png


+ 600 - 0
assets/showcases/infographic/infographic-pentagram.html

@@ -0,0 +1,600 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1080">
+<title>AI Memory System Optimization — Pentagram Style</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1080px;
+    height: 1920px;
+    overflow: hidden;
+    margin: 0;
+    background: #FFFFFF;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    color: #111;
+  }
+
+  .container {
+    width: 100%;
+    height: 100%;
+    padding: 64px 72px;
+    display: flex;
+    flex-direction: column;
+  }
+
+  /* Header */
+  .header {
+    border-bottom: 6px solid #111;
+    padding-bottom: 24px;
+    margin-bottom: 0;
+  }
+
+  .header-label {
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    color: #E63946;
+    margin-bottom: 12px;
+  }
+
+  .header-title {
+    font-size: 44px;
+    font-weight: 900;
+    line-height: 1.1;
+    letter-spacing: -1px;
+    color: #111;
+  }
+
+  .header-subtitle {
+    font-size: 15px;
+    font-weight: 400;
+    color: #999;
+    margin-top: 8px;
+  }
+
+  /* Hero Numbers Section */
+  .hero-section {
+    display: flex;
+    align-items: baseline;
+    justify-content: center;
+    padding: 48px 0 20px 0;
+    border-bottom: 2px solid #111;
+    gap: 0;
+  }
+
+  .hero-num {
+    font-weight: 900;
+    font-size: 200px;
+    line-height: 0.85;
+    letter-spacing: -8px;
+    color: #111;
+  }
+
+  .hero-unit {
+    font-size: 36px;
+    font-weight: 500;
+    color: #111;
+    margin-left: 4px;
+    align-self: flex-end;
+    margin-bottom: 18px;
+  }
+
+  .hero-arrow-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin: 0 28px;
+    align-self: center;
+  }
+
+  .hero-arrow-label {
+    font-size: 12px;
+    font-weight: 700;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+    color: #E63946;
+    margin-bottom: 6px;
+  }
+
+  .hero-num-accent {
+    font-weight: 900;
+    font-size: 200px;
+    line-height: 0.85;
+    letter-spacing: -8px;
+    color: #E63946;
+  }
+
+  .hero-meta {
+    display: flex;
+    justify-content: space-between;
+    padding: 14px 0;
+    border-bottom: 6px solid #111;
+  }
+
+  .hero-meta-item {
+    font-size: 12px;
+    font-weight: 500;
+    color: #999;
+    letter-spacing: 1px;
+  }
+
+  .hero-meta-item strong {
+    color: #111;
+    font-weight: 900;
+  }
+
+  /* Sections */
+  .section {
+    padding: 32px 0 0 0;
+  }
+
+  .section-header {
+    display: flex;
+    align-items: baseline;
+    gap: 16px;
+    margin-bottom: 20px;
+  }
+
+  .section-num {
+    font-size: 48px;
+    font-weight: 900;
+    color: #E63946;
+    line-height: 1;
+  }
+
+  .section-title {
+    font-size: 22px;
+    font-weight: 700;
+    letter-spacing: -0.5px;
+    color: #111;
+    line-height: 1;
+  }
+
+  .section-divider {
+    width: 100%;
+    height: 2px;
+    background: #111;
+    margin-bottom: 20px;
+  }
+
+  /* Data Bars */
+  .data-bars {
+    display: flex;
+    flex-direction: column;
+    gap: 14px;
+  }
+
+  .data-bar-row {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .data-bar-label {
+    font-size: 13px;
+    font-weight: 600;
+    color: #666;
+    width: 100px;
+    text-align: right;
+    flex-shrink: 0;
+  }
+
+  .data-bar-track {
+    flex: 1;
+    height: 32px;
+    background: #F0F0F0;
+    position: relative;
+  }
+
+  .data-bar-fill {
+    height: 100%;
+    background: #111;
+  }
+
+  .data-bar-fill.accent {
+    background: #E63946;
+  }
+
+  .data-bar-value {
+    font-size: 14px;
+    font-weight: 900;
+    color: #111;
+    width: 60px;
+    text-align: left;
+    flex-shrink: 0;
+  }
+
+  /* Category Grid */
+  .category-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 2px;
+    background: #111;
+    border: 2px solid #111;
+  }
+
+  .category-cell {
+    background: #fff;
+    padding: 24px;
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+  }
+
+  .category-num {
+    font-size: 11px;
+    font-weight: 700;
+    color: #999;
+    letter-spacing: 2px;
+  }
+
+  .category-name-zh {
+    font-size: 22px;
+    font-weight: 900;
+    color: #111;
+    line-height: 1.2;
+  }
+
+  .category-name-en {
+    font-size: 11px;
+    font-weight: 500;
+    color: #999;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+  }
+
+  .category-desc {
+    font-size: 12px;
+    font-weight: 400;
+    color: #666;
+    line-height: 1.5;
+    margin-top: 4px;
+  }
+
+  .category-cell.accent {
+    background: #E63946;
+  }
+
+  .category-cell.accent .category-num,
+  .category-cell.accent .category-name-zh,
+  .category-cell.accent .category-name-en,
+  .category-cell.accent .category-desc {
+    color: #fff;
+  }
+
+  /* Section 03: Design Principles */
+  .principles {
+    display: flex;
+    flex-direction: column;
+    gap: 0;
+  }
+
+  .principle-row {
+    display: flex;
+    align-items: stretch;
+    border-bottom: 1px solid #E8E8E8;
+  }
+
+  .principle-row:last-child {
+    border-bottom: none;
+  }
+
+  .principle-num {
+    font-size: 32px;
+    font-weight: 900;
+    color: #E63946;
+    width: 64px;
+    flex-shrink: 0;
+    padding: 16px 0;
+    line-height: 1;
+  }
+
+  .principle-content {
+    padding: 16px 0 16px 16px;
+    border-left: 1px solid #E8E8E8;
+    flex: 1;
+  }
+
+  .principle-name {
+    font-size: 16px;
+    font-weight: 900;
+    color: #111;
+    margin-bottom: 4px;
+  }
+
+  .principle-desc {
+    font-size: 13px;
+    font-weight: 400;
+    color: #888;
+    line-height: 1.5;
+  }
+
+  /* Section 04: Results */
+  .results-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr;
+    gap: 0;
+  }
+
+  .result-card {
+    padding: 32px 24px;
+    border-right: 1px solid #E8E8E8;
+  }
+
+  .result-card:last-child {
+    border-right: none;
+  }
+
+  .result-number {
+    font-size: 64px;
+    font-weight: 900;
+    color: #E63946;
+    line-height: 1;
+    letter-spacing: -3px;
+  }
+
+  .result-label {
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #999;
+    margin-top: 8px;
+  }
+
+  /* Insight Quote */
+  .insight-section {
+    margin-top: auto;
+    border-top: 6px solid #111;
+    padding-top: 24px;
+  }
+
+  .insight-quote {
+    font-size: 24px;
+    font-weight: 500;
+    color: #111;
+    line-height: 1.4;
+    letter-spacing: -0.5px;
+    font-style: italic;
+  }
+
+  .insight-quote .highlight {
+    color: #E63946;
+    font-weight: 900;
+    font-style: normal;
+  }
+
+  .insight-result {
+    display: flex;
+    gap: 40px;
+    margin-top: 18px;
+    padding-top: 14px;
+    border-top: 1px solid #DDD;
+  }
+
+  .insight-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+  }
+
+  .insight-dot {
+    width: 8px;
+    height: 8px;
+    background: #E63946;
+    flex-shrink: 0;
+  }
+
+  .insight-text {
+    font-size: 13px;
+    font-weight: 600;
+    color: #666;
+  }
+
+  /* Footer */
+  .footer {
+    margin-top: 20px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding-top: 12px;
+    border-top: 1px solid #DDD;
+  }
+
+  .footer-text {
+    font-size: 10px;
+    font-weight: 500;
+    color: #CCC;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+  }
+</style>
+</head>
+<body>
+<div class="container">
+
+  <!-- Header -->
+  <div class="header">
+    <div class="header-label">Case Study / System Design</div>
+    <div class="header-title">AI记忆系统:CLAUDE.md<br>从臃肿到优雅的重构之路</div>
+    <div class="header-subtitle">A systematic approach to AI memory architecture optimization</div>
+  </div>
+
+  <!-- Hero Numbers -->
+  <div class="hero-section">
+    <span class="hero-num">93</span>
+    <span class="hero-unit">KB</span>
+    <div class="hero-arrow-container">
+      <span class="hero-arrow-label">reduced to</span>
+      <svg width="64" height="24" viewBox="0 0 64 24">
+        <line x1="0" y1="12" x2="52" y2="12" stroke="#E63946" stroke-width="3"/>
+        <polygon points="52,4 64,12 52,20" fill="#E63946"/>
+      </svg>
+    </div>
+    <span class="hero-num-accent">22</span>
+    <span class="hero-unit" style="color:#E63946">KB</span>
+  </div>
+  <div class="hero-meta">
+    <div class="hero-meta-item"><strong>76%</strong> reduction</div>
+    <div class="hero-meta-item"><strong>2400+</strong> lines before</div>
+    <div class="hero-meta-item"><strong>1</strong> file to <strong>structured</strong> system</div>
+    <div class="hero-meta-item"><strong>0</strong> information loss</div>
+  </div>
+
+  <!-- Section 01: Before vs After -->
+  <div class="section">
+    <div class="section-header">
+      <span class="section-num">01</span>
+      <span class="section-title">Before vs After</span>
+    </div>
+    <div class="section-divider"></div>
+    <div class="data-bars">
+      <div class="data-bar-row">
+        <span class="data-bar-label">Before</span>
+        <div class="data-bar-track">
+          <div class="data-bar-fill" style="width: 100%;"></div>
+        </div>
+        <span class="data-bar-value">93 KB</span>
+      </div>
+      <div class="data-bar-row">
+        <span class="data-bar-label">After</span>
+        <div class="data-bar-track">
+          <div class="data-bar-fill accent" style="width: 23.7%;"></div>
+        </div>
+        <span class="data-bar-value">22 KB</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- Section 02: Memory Architecture -->
+  <div class="section">
+    <div class="section-header">
+      <span class="section-num">02</span>
+      <span class="section-title">Memory Architecture</span>
+    </div>
+    <div class="section-divider"></div>
+    <div class="category-grid">
+      <div class="category-cell accent">
+        <span class="category-num">I</span>
+        <span class="category-name-zh">核心身份</span>
+        <span class="category-name-en">Core Identity</span>
+        <span class="category-desc">Who you are, fundamental traits, immutable facts</span>
+      </div>
+      <div class="category-cell">
+        <span class="category-num">II</span>
+        <span class="category-name-zh">偏好设置</span>
+        <span class="category-name-en">Preferences</span>
+        <span class="category-desc">Style, tools, workflow habits, accumulated over time</span>
+      </div>
+      <div class="category-cell">
+        <span class="category-num">III</span>
+        <span class="category-name-zh">项目状态</span>
+        <span class="category-name-en">Project State</span>
+        <span class="category-desc">Current tasks, deadlines, priorities, progress tracking</span>
+      </div>
+      <div class="category-cell">
+        <span class="category-num">IV</span>
+        <span class="category-name-zh">日志流水</span>
+        <span class="category-name-en">Daily Logs</span>
+        <span class="category-desc">Session-level records, searchable history, never auto-loaded</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- Section 03: Design Principles -->
+  <div class="section">
+    <div class="section-header">
+      <span class="section-num">03</span>
+      <span class="section-title">Design Principles</span>
+    </div>
+    <div class="section-divider"></div>
+    <div class="principles">
+      <div class="principle-row">
+        <div class="principle-num">A</div>
+        <div class="principle-content">
+          <div class="principle-name">Route, Don't Dump</div>
+          <div class="principle-desc">Router file dispatches to workspace-specific rules. Never load everything at once.</div>
+        </div>
+      </div>
+      <div class="principle-row">
+        <div class="principle-num">B</div>
+        <div class="principle-content">
+          <div class="principle-name">Structured Hierarchy</div>
+          <div class="principle-desc">Identity > Preferences > Projects > Logs. Each layer loads on demand.</div>
+        </div>
+      </div>
+      <div class="principle-row">
+        <div class="principle-num">C</div>
+        <div class="principle-content">
+          <div class="principle-name">Write Rules, Not Records</div>
+          <div class="principle-desc">Store reusable patterns, not one-time instructions. Keep memory under 100 lines.</div>
+        </div>
+      </div>
+      <div class="principle-row">
+        <div class="principle-num">D</div>
+        <div class="principle-content">
+          <div class="principle-name">Silent Operations</div>
+          <div class="principle-desc">Memory read/write happens silently. Never interrupt the user's task flow.</div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Section 04: Results -->
+  <div class="section">
+    <div class="section-header">
+      <span class="section-num">04</span>
+      <span class="section-title">Results</span>
+    </div>
+    <div class="section-divider"></div>
+    <div class="results-grid">
+      <div class="result-card">
+        <div class="result-number">76%</div>
+        <div class="result-label">Size Reduction</div>
+      </div>
+      <div class="result-card">
+        <div class="result-number">2.3x</div>
+        <div class="result-label">Faster Loading</div>
+      </div>
+      <div class="result-card">
+        <div class="result-number">0</div>
+        <div class="result-label">Data Loss</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Insight -->
+  <div class="insight-section">
+    <div class="insight-quote">
+      "Like <span class="highlight">Marie Kondo</span> for AI memory
+      — keep only what sparks joy."
+    </div>
+    <div class="insight-result">
+      <div class="insight-item">
+        <div class="insight-dot"></div>
+        <span class="insight-text">Faster context loading</span>
+      </div>
+      <div class="insight-item">
+        <div class="insight-dot"></div>
+        <span class="insight-text">More relevant responses</span>
+      </div>
+      <div class="insight-item">
+        <div class="insight-dot"></div>
+        <span class="insight-text">Zero information loss</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- Footer -->
+  <div class="footer">
+    <span class="footer-text">Pentagram Style</span>
+    <span class="footer-text">CLAUDE.md Optimization</span>
+    <span class="footer-text">2026</span>
+  </div>
+
+</div>
+</body>
+</html>

BIN
assets/showcases/infographic/infographic-pentagram.png


+ 670 - 0
assets/showcases/infographic/infographic-takram.html

@@ -0,0 +1,670 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1080">
+<title>AI Memory System Optimization — Takram Style</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Noto+Serif+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1080px;
+    height: 1920px;
+    overflow: hidden;
+    margin: 0;
+    background: #F5F0EB;
+    font-family: 'Inter', sans-serif;
+    color: #3D3730;
+  }
+
+  .container {
+    width: 100%;
+    height: 100%;
+    padding: 72px 80px 60px 80px;
+    display: flex;
+    flex-direction: column;
+    position: relative;
+  }
+
+  /* Background texture */
+  .bg-circle {
+    position: absolute;
+    border-radius: 50%;
+    border: 1px solid rgba(168, 181, 160, 0.2);
+    pointer-events: none;
+  }
+  .bg-circle-1 {
+    width: 600px;
+    height: 600px;
+    top: -180px;
+    right: -200px;
+  }
+  .bg-circle-2 {
+    width: 400px;
+    height: 400px;
+    bottom: 200px;
+    left: -160px;
+  }
+
+  /* Header */
+  .header {
+    position: relative;
+    z-index: 1;
+    margin-bottom: 40px;
+  }
+  .header-label {
+    font-family: 'Inter', sans-serif;
+    font-size: 10px;
+    font-weight: 500;
+    letter-spacing: 3.5px;
+    text-transform: uppercase;
+    color: #6B8F71;
+    margin-bottom: 20px;
+    opacity: 0.8;
+  }
+  .header-title {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 44px;
+    font-weight: 500;
+    line-height: 1.35;
+    color: #2D3436;
+    letter-spacing: 1px;
+  }
+  .header-subtitle {
+    font-family: 'Inter', sans-serif;
+    font-size: 15px;
+    font-weight: 300;
+    color: #8B7355;
+    margin-top: 12px;
+    line-height: 1.5;
+    letter-spacing: 0.3px;
+  }
+
+  /* Hero Data Circles */
+  .hero-section {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 48px;
+    padding: 36px 0;
+    position: relative;
+    z-index: 1;
+  }
+  .data-circle {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+  }
+  .data-circle-ring {
+    position: relative;
+    width: 200px;
+    height: 200px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .data-circle-ring svg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    transform: rotate(-90deg);
+  }
+  .data-circle-inner {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    z-index: 1;
+  }
+  .data-num {
+    font-family: 'Inter', sans-serif;
+    font-size: 64px;
+    font-weight: 300;
+    color: #2E2A24;
+    line-height: 1;
+  }
+  .data-unit {
+    font-family: 'Inter', sans-serif;
+    font-size: 16px;
+    font-weight: 400;
+    color: #8B7355;
+    margin-top: 4px;
+  }
+  .data-label {
+    font-family: 'Inter', sans-serif;
+    font-size: 12px;
+    font-weight: 400;
+    color: #A8A098;
+    margin-top: 12px;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+  }
+  .data-circle-small .data-circle-ring {
+    width: 160px;
+    height: 160px;
+  }
+  .data-circle-small .data-num {
+    font-size: 52px;
+  }
+
+  /* Organic connector */
+  .hero-connector {
+    position: relative;
+    width: 120px;
+    height: 60px;
+    flex-shrink: 0;
+  }
+
+  /* Reduction badge — understated Takram style */
+  .reduction-pill {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    padding: 8px 28px;
+    background: transparent;
+    border: 1px solid rgba(107, 143, 113, 0.3);
+    border-radius: 20px;
+    margin: 0 auto;
+    display: flex;
+    gap: 8px;
+  }
+  .reduction-text {
+    font-family: 'Inter', sans-serif;
+    font-size: 13px;
+    font-weight: 500;
+    color: #6B8F71;
+    letter-spacing: 2px;
+  }
+
+  .hero-footer {
+    display: flex;
+    justify-content: center;
+    margin-top: 16px;
+  }
+
+  /* Categories Section */
+  .categories-section {
+    margin-top: 36px;
+    position: relative;
+    z-index: 1;
+  }
+  .section-label {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 20px;
+    font-weight: 500;
+    color: #2D3436;
+    margin-bottom: 24px;
+    letter-spacing: 1px;
+  }
+  .section-label .section-num {
+    font-family: 'Inter', sans-serif;
+    font-size: 9px;
+    font-weight: 400;
+    color: #B0AAA0;
+    letter-spacing: 1px;
+    margin-right: 12px;
+  }
+  .categories-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 16px;
+  }
+  .cat-card {
+    background: rgba(255, 255, 255, 0.6);
+    backdrop-filter: blur(10px);
+    border-radius: 16px;
+    padding: 28px 24px;
+    position: relative;
+    box-shadow: 0 2px 12px rgba(0,0,0,0.03);
+    border: 1px solid rgba(232, 228, 220, 0.8);
+  }
+  .cat-card-icon {
+    margin-bottom: 14px;
+  }
+  .cat-card-title-zh {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 20px;
+    font-weight: 600;
+    color: #2E2A24;
+    margin-bottom: 4px;
+  }
+  .cat-card-title-en {
+    font-family: 'Inter', sans-serif;
+    font-size: 11px;
+    font-weight: 400;
+    color: #A8A098;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    margin-bottom: 10px;
+  }
+  .cat-card-desc {
+    font-family: 'Inter', sans-serif;
+    font-size: 12px;
+    font-weight: 300;
+    color: #8B7355;
+    line-height: 1.7;
+  }
+  .cat-card.highlight {
+    border-color: rgba(107, 143, 113, 0.35);
+    background: rgba(107, 143, 113, 0.04);
+  }
+
+  /* Proportion circles */
+  .cat-prop {
+    position: absolute;
+    top: 20px;
+    right: 20px;
+  }
+
+  /* Flow Diagram */
+  .flow-section {
+    margin-top: 36px;
+    position: relative;
+    z-index: 1;
+  }
+
+  .flow-diagram {
+    position: relative;
+    height: 260px;
+    width: 100%;
+  }
+
+  .flow-node {
+    position: absolute;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 8px;
+  }
+  .flow-node-circle {
+    width: 72px;
+    height: 72px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(255,255,255,0.5);
+    border: 1px solid #DDD9D2;
+  }
+  .flow-node-circle.active {
+    border-color: #6B8F71;
+    border-width: 1.5px;
+    background: rgba(107, 143, 113, 0.06);
+  }
+  .flow-node-label {
+    font-family: 'Inter', sans-serif;
+    font-size: 11px;
+    font-weight: 400;
+    color: #A8A098;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+  }
+  .flow-node-text {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 13px;
+    font-weight: 500;
+    color: #2E2A24;
+    text-align: center;
+  }
+
+  /* Insight */
+  .insight-section {
+    margin-top: auto;
+    position: relative;
+    z-index: 1;
+  }
+  .insight-card {
+    background: rgba(255, 255, 255, 0.5);
+    border-radius: 16px;
+    padding: 32px 36px;
+    border: 1px solid rgba(232, 228, 220, 0.6);
+    box-shadow: 0 4px 20px rgba(0,0,0,0.03);
+  }
+  .insight-quote {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 20px;
+    font-weight: 400;
+    color: #2E2A24;
+    line-height: 1.7;
+    letter-spacing: 0.5px;
+  }
+  .insight-quote .green {
+    color: #6B8F71;
+    font-weight: 500;
+  }
+  .insight-quote .brown {
+    color: #8B7355;
+    font-weight: 500;
+  }
+
+  .results-row {
+    display: flex;
+    gap: 32px;
+    margin-top: 24px;
+    padding-top: 20px;
+    border-top: 1px solid rgba(232, 228, 220, 0.6);
+  }
+  .result-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+  }
+  .result-leaf {
+    flex-shrink: 0;
+  }
+  .result-text {
+    font-family: 'Inter', sans-serif;
+    font-size: 13px;
+    font-weight: 400;
+    color: #8B7355;
+  }
+
+  /* Footer */
+  .footer {
+    margin-top: 28px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    position: relative;
+    z-index: 1;
+  }
+  .footer-text {
+    font-family: 'Inter', sans-serif;
+    font-size: 10px;
+    font-weight: 300;
+    color: #C4BDB4;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+  }
+</style>
+</head>
+<body>
+<div class="container">
+
+  <!-- Background decorations -->
+  <div class="bg-circle bg-circle-1"></div>
+  <div class="bg-circle bg-circle-2"></div>
+
+  <!-- Header -->
+  <div class="header">
+    <div class="header-label">Speculative Design / Memory Architecture</div>
+    <div class="header-title">AI记忆系统<br>CLAUDE.md 的断舍离</div>
+    <div class="header-subtitle">Restructuring artificial memory from monolith to modular elegance</div>
+  </div>
+
+  <!-- Hero Data Circles -->
+  <div class="hero-section">
+    <div class="data-circle">
+      <div class="data-circle-ring">
+        <svg width="200" height="200" viewBox="0 0 200 200">
+          <circle cx="100" cy="100" r="92" fill="none" stroke="#E8E4DC" stroke-width="1.5"/>
+          <circle cx="100" cy="100" r="92" fill="none" stroke="#8B7355" stroke-width="2" stroke-dasharray="578" stroke-dashoffset="0" opacity="0.3"/>
+        </svg>
+        <div class="data-circle-inner">
+          <span class="data-num">93</span>
+          <span class="data-unit">KB</span>
+        </div>
+      </div>
+      <span class="data-label">Before</span>
+    </div>
+
+    <div class="hero-connector">
+      <svg width="120" height="60" viewBox="0 0 120 60">
+        <path d="M 0,30 C 30,30 40,10 60,10 C 80,10 90,50 110,30"
+              fill="none" stroke="#6B8F71" stroke-width="1" stroke-dasharray="3,4" opacity="0.5"/>
+        <circle cx="110" cy="30" r="3.5" fill="#6B8F71" opacity="0.5"/>
+        <circle cx="110" cy="30" r="7" fill="none" stroke="#6B8F71" stroke-width="0.5" opacity="0.2"/>
+        <!-- delta annotation -->
+        <text x="60" y="50" text-anchor="middle" font-family="Inter" font-size="7" fill="#B0AAA0" letter-spacing="0.5">-71 KB</text>
+      </svg>
+    </div>
+
+    <div class="data-circle data-circle-small">
+      <div class="data-circle-ring">
+        <svg width="160" height="160" viewBox="0 0 160 160">
+          <circle cx="80" cy="80" r="72" fill="none" stroke="#E8E4DC" stroke-width="1.5"/>
+          <circle cx="80" cy="80" r="72" fill="none" stroke="#A8B5A0" stroke-width="2.5" stroke-dasharray="452" stroke-dashoffset="344" opacity="0.6"/>
+        </svg>
+        <div class="data-circle-inner">
+          <span class="data-num" style="color: #A8B5A0;">22</span>
+          <span class="data-unit">KB</span>
+        </div>
+      </div>
+      <span class="data-label">After</span>
+    </div>
+  </div>
+  <div class="hero-footer">
+    <div class="reduction-pill">
+      <svg width="14" height="14" viewBox="0 0 14 14"><path d="M7 2L7 12M3 8L7 12L11 8" fill="none" stroke="#6B8F71" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
+      <span class="reduction-text">76% REDUCTION</span>
+    </div>
+  </div>
+
+  <!-- Categories -->
+  <div class="categories-section">
+    <div class="section-label"><span class="section-num">01</span>Four Pillars of Memory</div>
+    <div class="categories-grid">
+      <div class="cat-card highlight">
+        <div class="cat-card-icon">
+          <svg width="32" height="32" viewBox="0 0 32 32">
+            <circle cx="16" cy="12" r="6" fill="none" stroke="#A8B5A0" stroke-width="1.5"/>
+            <path d="M6,28 C6,22 10,18 16,18 C22,18 26,22 26,28" fill="none" stroke="#A8B5A0" stroke-width="1.5" stroke-linecap="round"/>
+          </svg>
+        </div>
+        <div class="cat-prop">
+          <svg width="28" height="28" viewBox="0 0 28 28">
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#E8E4DC" stroke-width="1"/>
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#A8B5A0" stroke-width="2" stroke-dasharray="75" stroke-dashoffset="56" transform="rotate(-90 14 14)"/>
+          </svg>
+        </div>
+        <div class="cat-card-title-zh">核心身份</div>
+        <div class="cat-card-title-en">Core Identity</div>
+        <div class="cat-card-desc">Immutable facts and fundamental traits that define who you are</div>
+      </div>
+      <div class="cat-card">
+        <div class="cat-card-icon">
+          <svg width="32" height="32" viewBox="0 0 32 32">
+            <path d="M8,8 L24,8 L24,24 L8,24 Z" fill="none" stroke="#8B7355" stroke-width="1.5" rx="2"/>
+            <line x1="12" y1="13" x2="20" y2="13" stroke="#8B7355" stroke-width="1.5" stroke-linecap="round"/>
+            <line x1="12" y1="17" x2="18" y2="17" stroke="#8B7355" stroke-width="1.5" stroke-linecap="round"/>
+            <line x1="12" y1="21" x2="16" y2="21" stroke="#8B7355" stroke-width="1.5" stroke-linecap="round"/>
+          </svg>
+        </div>
+        <div class="cat-prop">
+          <svg width="28" height="28" viewBox="0 0 28 28">
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#E8E4DC" stroke-width="1"/>
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#8B7355" stroke-width="2" stroke-dasharray="75" stroke-dashoffset="45" transform="rotate(-90 14 14)"/>
+          </svg>
+        </div>
+        <div class="cat-card-title-zh">偏好设置</div>
+        <div class="cat-card-title-en">Preferences</div>
+        <div class="cat-card-desc">Style, tools, habits — accumulated over time through sessions</div>
+      </div>
+      <div class="cat-card">
+        <div class="cat-card-icon">
+          <svg width="32" height="32" viewBox="0 0 32 32">
+            <rect x="6" y="10" width="8" height="14" rx="1" fill="none" stroke="#8B7355" stroke-width="1.5"/>
+            <rect x="18" y="6" width="8" height="18" rx="1" fill="none" stroke="#8B7355" stroke-width="1.5"/>
+            <line x1="8" y1="16" x2="12" y2="16" stroke="#8B7355" stroke-width="1" stroke-linecap="round"/>
+            <line x1="20" y1="12" x2="24" y2="12" stroke="#8B7355" stroke-width="1" stroke-linecap="round"/>
+          </svg>
+        </div>
+        <div class="cat-prop">
+          <svg width="28" height="28" viewBox="0 0 28 28">
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#E8E4DC" stroke-width="1"/>
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#8B7355" stroke-width="2" stroke-dasharray="75" stroke-dashoffset="37" transform="rotate(-90 14 14)"/>
+          </svg>
+        </div>
+        <div class="cat-card-title-zh">项目状态</div>
+        <div class="cat-card-title-en">Project State</div>
+        <div class="cat-card-desc">Current tasks, deadlines, priorities — always evolving context</div>
+      </div>
+      <div class="cat-card">
+        <div class="cat-card-icon">
+          <svg width="32" height="32" viewBox="0 0 32 32">
+            <circle cx="16" cy="16" r="10" fill="none" stroke="#8B7355" stroke-width="1.5"/>
+            <line x1="16" y1="10" x2="16" y2="16" stroke="#8B7355" stroke-width="1.5" stroke-linecap="round"/>
+            <line x1="16" y1="16" x2="21" y2="19" stroke="#8B7355" stroke-width="1.5" stroke-linecap="round"/>
+          </svg>
+        </div>
+        <div class="cat-prop">
+          <svg width="28" height="28" viewBox="0 0 28 28">
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#E8E4DC" stroke-width="1"/>
+            <circle cx="14" cy="14" r="12" fill="none" stroke="#8B7355" stroke-width="2" stroke-dasharray="75" stroke-dashoffset="60" transform="rotate(-90 14 14)"/>
+          </svg>
+        </div>
+        <div class="cat-card-title-zh">日志流水</div>
+        <div class="cat-card-title-en">Daily Logs</div>
+        <div class="cat-card-desc">Session records — never auto-loaded, retrieved on demand only</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Flow Diagram -->
+  <div class="flow-section">
+    <div class="section-label"><span class="section-num">02</span>System Flow</div>
+    <div class="flow-diagram">
+      <!-- SVG organic curves connecting nodes — art-piece treatment -->
+      <svg width="920" height="260" viewBox="0 0 920 260" style="position: absolute; top: 0; left: 0;">
+        <!-- Background guide line (very subtle) -->
+        <line x1="50" y1="130" x2="870" y2="130" stroke="#E8E4DC" stroke-width="0.3" stroke-dasharray="2,8" opacity="0.4"/>
+
+        <!-- Curve from Input to Route -->
+        <path d="M 116,50 C 165,50 195,120 246,120" fill="none" stroke="#D4CFC6" stroke-width="1"/>
+        <!-- Curve from Route to Load -->
+        <path d="M 316,120 C 370,120 380,50 460,50" fill="none" stroke="#D4CFC6" stroke-width="1"/>
+        <!-- Curve from Load to Execute (highlighted — key transition) -->
+        <path d="M 530,50 C 585,50 600,160 660,160" fill="none" stroke="#6B8F71" stroke-width="1.5" stroke-dasharray="4,4" opacity="0.6"/>
+        <!-- Curve from Execute to Update -->
+        <path d="M 730,160 C 785,160 800,80 830,80" fill="none" stroke="#D4CFC6" stroke-width="1"/>
+
+        <!-- Connection dots — varying size for depth -->
+        <circle cx="116" cy="50" r="2.5" fill="#D4CFC6"/>
+        <circle cx="246" cy="120" r="2.5" fill="#D4CFC6"/>
+        <circle cx="316" cy="120" r="2.5" fill="#D4CFC6"/>
+        <circle cx="460" cy="50" r="3" fill="#6B8F71" opacity="0.5"/>
+        <circle cx="530" cy="50" r="3" fill="#6B8F71" opacity="0.5"/>
+        <circle cx="660" cy="160" r="2.5" fill="#D4CFC6"/>
+        <circle cx="730" cy="160" r="2.5" fill="#D4CFC6"/>
+        <circle cx="830" cy="80" r="2.5" fill="#D4CFC6"/>
+
+        <!-- Annotation: step numbers along curve -->
+        <text x="170" y="75" font-family="Inter" font-size="7" fill="#C8C2B8" letter-spacing="0.5">step 1</text>
+        <text x="350" y="100" font-family="Inter" font-size="7" fill="#C8C2B8" letter-spacing="0.5">step 2</text>
+        <text x="565" y="95" font-family="Inter" font-size="7" fill="#6B8F71" opacity="0.5" letter-spacing="0.5">step 3</text>
+        <text x="770" y="130" font-family="Inter" font-size="7" fill="#C8C2B8" letter-spacing="0.5">step 4</text>
+      </svg>
+
+      <!-- Node 1: User Input -->
+      <div class="flow-node" style="left: 44px; top: 8px;">
+        <div class="flow-node-circle">
+          <svg width="24" height="24" viewBox="0 0 24 24">
+            <circle cx="12" cy="9" r="4" fill="none" stroke="#8B7355" stroke-width="1.2"/>
+            <path d="M4,21 C4,16 7,14 12,14 C17,14 20,16 20,21" fill="none" stroke="#8B7355" stroke-width="1.2" stroke-linecap="round"/>
+          </svg>
+        </div>
+        <span class="flow-node-label">Input</span>
+        <span class="flow-node-text">User</span>
+      </div>
+
+      <!-- Node 2: Route -->
+      <div class="flow-node" style="left: 210px; top: 78px;">
+        <div class="flow-node-circle">
+          <svg width="24" height="24" viewBox="0 0 24 24">
+            <path d="M4,12 L10,6 L10,10 L20,10 L20,14 L10,14 L10,18 Z" fill="none" stroke="#8B7355" stroke-width="1.2"/>
+          </svg>
+        </div>
+        <span class="flow-node-label">Route</span>
+        <span class="flow-node-text">Workspace</span>
+      </div>
+
+      <!-- Node 3: Load Memory (active) -->
+      <div class="flow-node" style="left: 420px; top: 8px;">
+        <div class="flow-node-circle active">
+          <svg width="24" height="24" viewBox="0 0 24 24">
+            <rect x="4" y="4" width="16" height="16" rx="2" fill="none" stroke="#A8B5A0" stroke-width="1.2"/>
+            <line x1="8" y1="9" x2="16" y2="9" stroke="#A8B5A0" stroke-width="1.2" stroke-linecap="round"/>
+            <line x1="8" y1="13" x2="14" y2="13" stroke="#A8B5A0" stroke-width="1.2" stroke-linecap="round"/>
+            <line x1="8" y1="17" x2="12" y2="17" stroke="#A8B5A0" stroke-width="1.2" stroke-linecap="round"/>
+          </svg>
+        </div>
+        <span class="flow-node-label">Load</span>
+        <span class="flow-node-text">Memory</span>
+      </div>
+
+      <!-- Node 4: Execute -->
+      <div class="flow-node" style="left: 624px; top: 118px;">
+        <div class="flow-node-circle">
+          <svg width="24" height="24" viewBox="0 0 24 24">
+            <polygon points="8,4 20,12 8,20" fill="none" stroke="#8B7355" stroke-width="1.2" stroke-linejoin="round"/>
+          </svg>
+        </div>
+        <span class="flow-node-label">Execute</span>
+        <span class="flow-node-text">Task</span>
+      </div>
+
+      <!-- Node 5: Update -->
+      <div class="flow-node" style="left: 800px; top: 40px;">
+        <div class="flow-node-circle">
+          <svg width="24" height="24" viewBox="0 0 24 24">
+            <path d="M12,4 L12,16" stroke="#8B7355" stroke-width="1.2" stroke-linecap="round"/>
+            <path d="M8,12 L12,16 L16,12" fill="none" stroke="#8B7355" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+            <line x1="6" y1="20" x2="18" y2="20" stroke="#8B7355" stroke-width="1.2" stroke-linecap="round"/>
+          </svg>
+        </div>
+        <span class="flow-node-label">Update</span>
+        <span class="flow-node-text">Write</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- Insight -->
+  <div class="insight-section">
+    <div class="insight-card">
+      <div class="insight-quote">
+        Like <span class="green">Marie Kondo</span> for AI memory —<br>
+        keep only what <span class="brown">sparks joy</span>.
+      </div>
+      <div class="results-row">
+        <div class="result-item">
+          <div class="result-leaf">
+            <svg width="14" height="14" viewBox="0 0 14 14">
+              <path d="M2,12 C2,6 6,2 12,2" fill="none" stroke="#A8B5A0" stroke-width="1.5" stroke-linecap="round"/>
+              <circle cx="12" cy="2" r="2" fill="#A8B5A0" opacity="0.4"/>
+            </svg>
+          </div>
+          <span class="result-text">Faster context loading</span>
+        </div>
+        <div class="result-item">
+          <div class="result-leaf">
+            <svg width="14" height="14" viewBox="0 0 14 14">
+              <path d="M2,12 C2,6 6,2 12,2" fill="none" stroke="#A8B5A0" stroke-width="1.5" stroke-linecap="round"/>
+              <circle cx="12" cy="2" r="2" fill="#A8B5A0" opacity="0.4"/>
+            </svg>
+          </div>
+          <span class="result-text">More relevant responses</span>
+        </div>
+        <div class="result-item">
+          <div class="result-leaf">
+            <svg width="14" height="14" viewBox="0 0 14 14">
+              <path d="M2,12 C2,6 6,2 12,2" fill="none" stroke="#A8B5A0" stroke-width="1.5" stroke-linecap="round"/>
+              <circle cx="12" cy="2" r="2" fill="#A8B5A0" opacity="0.4"/>
+            </svg>
+          </div>
+          <span class="result-text">Zero information loss</span>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Footer -->
+  <div class="footer">
+    <span class="footer-text">Takram Style</span>
+    <span class="footer-text">CLAUDE.md Optimization</span>
+    <span class="footer-text">2026</span>
+  </div>
+
+</div>
+</body>
+</html>

BIN
assets/showcases/infographic/infographic-takram.png


+ 382 - 0
assets/showcases/ppt/ppt-build.html

@@ -0,0 +1,382 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1920">
+<title>GLM-4.7 Coding Benchmark - Build Studio Style</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1920px;
+    height: 1080px;
+    overflow: hidden;
+    margin: 0;
+    background: #FAFAF8;
+    font-family: 'Inter', sans-serif;
+    color: #2A2A2A;
+    position: relative;
+  }
+
+  .container {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    padding: 64px 96px 48px 96px;
+    justify-content: space-between;
+  }
+
+  /* Top section */
+  .top-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 16px;
+  }
+
+  .eyebrow {
+    font-size: 10px;
+    font-weight: 400;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+  }
+
+  .source-note {
+    font-size: 10px;
+    font-weight: 300;
+    color: #C0BCB6;
+    text-align: right;
+    line-height: 1.6;
+  }
+
+  /* Title area */
+  .title-area {
+    margin-bottom: 0;
+    padding-bottom: 24px;
+    border-bottom: 1px solid #EEECE8;
+  }
+
+  .main-title {
+    font-size: 40px;
+    font-weight: 200;
+    color: #2A2A2A;
+    letter-spacing: -0.5px;
+    line-height: 1.2;
+  }
+
+  .main-title .accent {
+    font-weight: 400;
+    color: #2A2A2A;
+  }
+
+  .subtitle {
+    font-size: 14px;
+    font-weight: 300;
+    color: #A0A09A;
+    margin-top: 8px;
+    letter-spacing: 0.3px;
+  }
+
+  /* Center: Hero data section */
+  .hero-data {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 0;
+    position: relative;
+    padding-bottom: 32px;
+    border-bottom: 1px solid #EEECE8;
+  }
+
+  /* Three metric cards */
+  .metric-card {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+    padding: 32px 24px;
+  }
+
+  .metric-card::after {
+    content: '';
+    position: absolute;
+    right: 0;
+    top: 25%;
+    height: 50%;
+    width: 1px;
+    background: linear-gradient(to bottom, transparent, #E0DCD6 50%, transparent);
+  }
+
+  .metric-card:last-child::after {
+    display: none;
+  }
+
+  .metric-value {
+    font-size: 112px;
+    font-weight: 200;
+    color: #2A2A2A;
+    letter-spacing: -4px;
+    line-height: 1;
+    position: relative;
+  }
+
+  .metric-value .dot {
+    color: #D4A574;
+    font-weight: 300;
+  }
+
+  .metric-unit {
+    font-size: 28px;
+    font-weight: 200;
+    color: #D4A574;
+    vertical-align: super;
+    margin-left: 2px;
+    opacity: 0.8;
+  }
+
+  .metric-name {
+    font-size: 12px;
+    font-weight: 500;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #888888;
+    margin-top: 16px;
+    margin-bottom: 8px;
+  }
+
+  .metric-category {
+    font-size: 11px;
+    font-weight: 300;
+    color: #B8B4AE;
+    letter-spacing: 0.5px;
+  }
+
+  /* Comparison bars below each metric */
+  .comparison-group {
+    margin-top: 24px;
+    width: 280px;
+  }
+
+  .comp-row {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+    gap: 8px;
+  }
+
+  .comp-label {
+    font-size: 11px;
+    font-weight: 400;
+    color: #A8A4A0;
+    width: 72px;
+    text-align: right;
+    flex-shrink: 0;
+  }
+
+  .comp-track {
+    flex: 1;
+    height: 2px;
+    background: #EEECEA;
+    border-radius: 1px;
+    position: relative;
+    overflow: hidden;
+  }
+
+  .comp-fill {
+    height: 100%;
+    border-radius: 1px;
+    background: #D8D5D0;
+  }
+
+  .comp-fill.gold {
+    background: #D4A574;
+    height: 3px;
+    margin-top: -0.5px;
+  }
+
+  .comp-val {
+    font-size: 11px;
+    font-weight: 500;
+    color: #999999;
+    width: 40px;
+    flex-shrink: 0;
+  }
+
+  .comp-val.gold {
+    color: #D4A574;
+    font-weight: 500;
+  }
+
+  /* Bottom section */
+  .bottom-section {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-end;
+    padding-top: 24px;
+  }
+
+  .insight-text {
+    font-size: 13px;
+    font-weight: 300;
+    color: #999;
+    line-height: 1.8;
+    max-width: 560px;
+  }
+
+  .insight-text strong {
+    font-weight: 500;
+    color: #666;
+  }
+
+  .brand-mark {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .brand-line {
+    width: 32px;
+    height: 1px;
+    background: #D4A574;
+    opacity: 0.6;
+  }
+
+  .brand-text {
+    font-size: 10px;
+    font-weight: 400;
+    letter-spacing: 3px;
+    color: #C8C4BC;
+  }
+
+  /* Slide indicator — functional PPT element */
+  .slide-indicator {
+    position: absolute;
+    top: 64px;
+    right: 96px;
+    display: flex;
+    gap: 6px;
+    align-items: center;
+  }
+
+  .slide-dot {
+    width: 4px;
+    height: 4px;
+    border-radius: 50%;
+    background: #E0DCD6;
+  }
+
+  .slide-dot.active {
+    background: #D4A574;
+    width: 16px;
+    border-radius: 2px;
+  }
+</style>
+</head>
+<body>
+<div class="container">
+  <!-- Top row -->
+  <div class="top-row">
+    <div class="eyebrow">GLM-4.7 Open-Source Model</div>
+    <div class="source-note">Benchmark Evaluation 2025<br>Official Results</div>
+  </div>
+
+  <!-- Title -->
+  <div class="title-area">
+    <div class="main-title">Coding Capability <span style="font-weight:400;">Breakthrough</span><span style="color:#D4A574; font-weight:300; font-size:48px;">.</span></div>
+    <div class="subtitle">First open-source model to achieve state-of-the-art across all major coding benchmarks</div>
+  </div>
+
+  <!-- Hero data -->
+  <div class="hero-data">
+    <!-- AIME 2025 -->
+    <div class="metric-card">
+      <div class="metric-value">95<span class="dot">.</span>7</div>
+      <div class="metric-name">AIME 2025</div>
+      <div class="metric-category">Mathematical Reasoning</div>
+      <div class="comparison-group">
+        <div class="comp-row">
+          <span class="comp-label">GLM-4.7</span>
+          <div class="comp-track"><div class="comp-fill gold" style="width: 100%;"></div></div>
+          <span class="comp-val gold">95.7</span>
+        </div>
+        <div class="comp-row">
+          <span class="comp-label">Claude 3.5</span>
+          <div class="comp-track"><div class="comp-fill" style="width: 92.2%;"></div></div>
+          <span class="comp-val">88.2</span>
+        </div>
+        <div class="comp-row">
+          <span class="comp-label">GPT-4o</span>
+          <div class="comp-track"><div class="comp-fill" style="width: 87.4%;"></div></div>
+          <span class="comp-val">83.6</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- SWE-bench Verified -->
+    <div class="metric-card">
+      <div class="metric-value">73<span class="dot">.</span>8<span class="metric-unit">%</span></div>
+      <div class="metric-name">SWE-bench Verified</div>
+      <div class="metric-category">Software Engineering</div>
+      <div class="comparison-group">
+        <div class="comp-row">
+          <span class="comp-label">GLM-4.7</span>
+          <div class="comp-track"><div class="comp-fill gold" style="width: 100%;"></div></div>
+          <span class="comp-val gold">73.8%</span>
+        </div>
+        <div class="comp-row">
+          <span class="comp-label">Claude 3.5</span>
+          <div class="comp-track"><div class="comp-fill" style="width: 72.2%;"></div></div>
+          <span class="comp-val">53.3%</span>
+        </div>
+        <div class="comp-row">
+          <span class="comp-label">GPT-4o</span>
+          <div class="comp-track"><div class="comp-fill" style="width: 65.3%;"></div></div>
+          <span class="comp-val">48.2%</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- Tau-bench -->
+    <div class="metric-card">
+      <div class="metric-value">87<span class="dot">.</span>4</div>
+      <div class="metric-name">&tau;&sup2;-Bench</div>
+      <div class="metric-category">Agent Task Completion</div>
+      <div class="comparison-group">
+        <div class="comp-row">
+          <span class="comp-label">GLM-4.7</span>
+          <div class="comp-track"><div class="comp-fill gold" style="width: 100%;"></div></div>
+          <span class="comp-val gold">87.4</span>
+        </div>
+        <div class="comp-row">
+          <span class="comp-label">Claude 3.5</span>
+          <div class="comp-track"><div class="comp-fill" style="width: 90.3%;"></div></div>
+          <span class="comp-val">78.9</span>
+        </div>
+        <div class="comp-row">
+          <span class="comp-label">GPT-4o</span>
+          <div class="comp-track"><div class="comp-fill" style="width: 81.8%;"></div></div>
+          <span class="comp-val">71.5</span>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Bottom -->
+  <div class="bottom-section">
+    <div class="insight-text">
+      GLM-4.7 demonstrates that <strong>open-source models can compete at the frontier</strong> of coding intelligence,
+      outperforming leading proprietary models with margins of <strong>+7.5 to +20.5 points</strong> across benchmarks.
+    </div>
+    <div class="brand-mark">
+      <div class="brand-line"></div>
+      <span class="brand-text">ZHIPU AI</span>
+    </div>
+  </div>
+</div>
+</body>
+</html>

BIN
assets/showcases/ppt/ppt-build.png


+ 536 - 0
assets/showcases/ppt/ppt-pentagram.html

@@ -0,0 +1,536 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1920">
+<title>GLM-4.7 Coding Benchmark - Pentagram Style</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1920px;
+    height: 1080px;
+    overflow: hidden;
+    margin: 0;
+    background: #FFFFFF;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    color: #111;
+    position: relative;
+  }
+
+  /* Top black bar */
+  .top-bar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 64px;
+    background: #111;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    z-index: 10;
+  }
+
+  .top-label {
+    font-size: 12px;
+    font-weight: 700;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+    color: #fff;
+  }
+
+  .top-label .red { color: #E63946; }
+
+  .top-right {
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #E63946;
+  }
+
+  /* Grid lines */
+  .grid-line-v {
+    position: absolute;
+    top: 64px;
+    bottom: 64px;
+    width: 1px;
+    background: #000;
+    opacity: 0.05;
+  }
+
+  .grid-line-h {
+    position: absolute;
+    left: 80px;
+    right: 80px;
+    height: 1px;
+    background: #000;
+    opacity: 0.05;
+  }
+
+  /* Left column — hero number + model info */
+  .left-col {
+    position: absolute;
+    left: 80px;
+    top: 104px;
+    width: 480px;
+  }
+
+  .model-tag {
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+    color: #999;
+    margin-bottom: 8px;
+  }
+
+  .model-name {
+    font-size: 48px;
+    font-weight: 900;
+    color: #111;
+    line-height: 1;
+    letter-spacing: -2px;
+  }
+
+  .model-name .version { color: #E63946; }
+
+  .hero-number {
+    font-size: 200px;
+    font-weight: 900;
+    line-height: 0.85;
+    letter-spacing: -10px;
+    color: #111;
+    margin-top: 24px;
+  }
+
+  .hero-number .decimal { color: #E63946; }
+
+  .hero-context {
+    font-size: 13px;
+    font-weight: 500;
+    color: #999;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+    margin-top: 8px;
+  }
+
+  .key-message {
+    font-size: 16px;
+    font-weight: 400;
+    line-height: 1.6;
+    color: #666;
+    margin-top: 32px;
+    max-width: 400px;
+  }
+
+  .key-message strong {
+    color: #111;
+    font-weight: 700;
+  }
+
+  .open-badge {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    margin-top: 24px;
+    padding: 8px 16px;
+    border: 2px solid #E63946;
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #E63946;
+  }
+
+  /* Right area — 3 benchmark columns */
+  .data-area {
+    position: absolute;
+    left: 620px;
+    top: 104px;
+    right: 80px;
+    bottom: 64px;
+    display: flex;
+    gap: 0;
+  }
+
+  .bench-col {
+    flex: 1;
+    padding: 0 32px;
+    border-left: 1px solid #E8E8E8;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .bench-col:first-child {
+    padding-left: 0;
+    border-left: none;
+  }
+
+  .bench-title {
+    font-size: 13px;
+    font-weight: 700;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #111;
+    margin-bottom: 4px;
+  }
+
+  .bench-type {
+    font-size: 11px;
+    font-weight: 400;
+    color: #BBB;
+    margin-bottom: 64px;
+  }
+
+  /* Hero score per column */
+  .bench-hero {
+    font-size: 80px;
+    font-weight: 900;
+    color: #E63946;
+    letter-spacing: -3px;
+    line-height: 1;
+    margin-bottom: 64px;
+  }
+
+  /* Horizontal bar chart */
+  .bar-group {
+    display: flex;
+    flex-direction: column;
+    gap: 24px;
+  }
+
+  .bar-row {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .bar-label {
+    font-size: 13px;
+    font-weight: 600;
+    color: #888;
+    width: 90px;
+    flex-shrink: 0;
+    text-align: right;
+  }
+
+  .bar-label.highlight {
+    color: #111;
+    font-weight: 700;
+  }
+
+  .bar-track {
+    flex: 1;
+    height: 56px;
+    background: #F5F5F5;
+    position: relative;
+  }
+
+  .bar-fill {
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    padding-right: 14px;
+  }
+
+  .bar-fill.base {
+    background: #E0E0E0;
+  }
+
+  .bar-fill.dark {
+    background: #111;
+  }
+
+  .bar-fill.winner {
+    background: #E63946;
+  }
+
+  .bar-value {
+    font-size: 15px;
+    font-weight: 700;
+    color: #fff;
+  }
+
+  .bar-fill.base .bar-value {
+    color: #888;
+  }
+
+  /* Bottom bar */
+  .bottom-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 64px;
+    background: #111;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    z-index: 10;
+  }
+
+  .bottom-left {
+    display: flex;
+    align-items: center;
+    gap: 24px;
+  }
+
+  .bottom-logo {
+    font-size: 14px;
+    font-weight: 900;
+    color: #fff;
+    letter-spacing: 1px;
+  }
+
+  .bottom-divider {
+    width: 1px;
+    height: 20px;
+    background: #444;
+  }
+
+  .bottom-note {
+    font-size: 11px;
+    font-weight: 400;
+    color: #666;
+  }
+
+  .bottom-right-text {
+    font-size: 11px;
+    font-weight: 700;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #E63946;
+  }
+
+  /* Delta label */
+  .delta {
+    font-size: 12px;
+    font-weight: 700;
+    color: #E63946;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+    margin-top: 24px;
+    padding-left: 106px;
+  }
+
+  /* Bottom summary row */
+  .summary-row {
+    position: absolute;
+    bottom: 96px;
+    left: 620px;
+    right: 80px;
+    display: flex;
+    border-top: 1px solid #E8E8E8;
+    padding-top: 24px;
+  }
+
+  .summary-item {
+    flex: 1;
+    padding: 0 32px;
+  }
+
+  .summary-item:first-child {
+    padding-left: 0;
+  }
+
+  .summary-num {
+    font-size: 32px;
+    font-weight: 900;
+    color: #111;
+    letter-spacing: -1px;
+    line-height: 1;
+  }
+
+  .summary-num .red { color: #E63946; }
+
+  .summary-desc {
+    font-size: 11px;
+    font-weight: 500;
+    color: #999;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+    margin-top: 8px;
+  }
+
+  /* Winner markers */
+  .winner-dot {
+    position: absolute;
+    right: -8px;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #E63946;
+  }
+</style>
+</head>
+<body>
+
+  <!-- Top bar -->
+  <div class="top-bar">
+    <span class="top-label">Benchmark Report <span class="red">/</span> 2025 Coding Performance</span>
+    <span class="top-right">Open-Source SOTA</span>
+  </div>
+
+  <!-- Grid lines -->
+  <div class="grid-line-v" style="left: 80px;"></div>
+  <div class="grid-line-v" style="left: 620px;"></div>
+  <div class="grid-line-v" style="right: 80px;"></div>
+  <div class="grid-line-h" style="top: 104px;"></div>
+
+  <!-- Left column -->
+  <div class="left-col">
+    <div class="model-tag">Open-Source Model</div>
+    <div class="model-name">GLM-<span class="version">4.7</span></div>
+    <div class="hero-number">95<span class="decimal">.</span>7</div>
+    <div class="hero-context">AIME 2025 Score</div>
+    <div class="key-message">
+      <strong>First open-source model to achieve SOTA</strong> across all three major coding benchmarks, surpassing GPT-4o and Claude 3.5.
+    </div>
+    <div class="open-badge">
+      <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
+        <circle cx="7" cy="7" r="6" stroke="#E63946" stroke-width="1.5"/>
+        <circle cx="7" cy="7" r="2.5" fill="#E63946"/>
+      </svg>
+      Open Source
+    </div>
+  </div>
+
+  <!-- Data columns -->
+  <div class="data-area">
+    <!-- AIME 2025 -->
+    <div class="bench-col">
+      <div class="bench-title">AIME 2025</div>
+      <div class="bench-type">Mathematical Reasoning</div>
+      <div class="bench-hero">95.7</div>
+      <div class="bar-group">
+        <div class="bar-row">
+          <span class="bar-label highlight">GLM-4.7</span>
+          <div class="bar-track">
+            <div class="bar-fill winner" style="width: 95.7%;">
+              <span class="bar-value">95.7</span>
+            </div>
+          </div>
+        </div>
+        <div class="bar-row">
+          <span class="bar-label">Claude 3.5</span>
+          <div class="bar-track">
+            <div class="bar-fill dark" style="width: 88.2%;">
+              <span class="bar-value">88.2</span>
+            </div>
+          </div>
+        </div>
+        <div class="bar-row">
+          <span class="bar-label">GPT-4o</span>
+          <div class="bar-track">
+            <div class="bar-fill base" style="width: 83.6%;">
+              <span class="bar-value">83.6</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="delta">+7.5 vs closed-source best</div>
+    </div>
+
+    <!-- SWE-bench -->
+    <div class="bench-col">
+      <div class="bench-title">SWE-bench Verified</div>
+      <div class="bench-type">Software Engineering</div>
+      <div class="bench-hero">73.8</div>
+      <div class="bar-group">
+        <div class="bar-row">
+          <span class="bar-label highlight">GLM-4.7</span>
+          <div class="bar-track">
+            <div class="bar-fill winner" style="width: 73.8%;">
+              <span class="bar-value">73.8%</span>
+            </div>
+          </div>
+        </div>
+        <div class="bar-row">
+          <span class="bar-label">Claude 3.5</span>
+          <div class="bar-track">
+            <div class="bar-fill dark" style="width: 53.3%;">
+              <span class="bar-value">53.3%</span>
+            </div>
+          </div>
+        </div>
+        <div class="bar-row">
+          <span class="bar-label">GPT-4o</span>
+          <div class="bar-track">
+            <div class="bar-fill base" style="width: 48.2%;">
+              <span class="bar-value">48.2%</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="delta">+20.5 vs closed-source best</div>
+    </div>
+
+    <!-- Tau-bench -->
+    <div class="bench-col">
+      <div class="bench-title">&tau;&sup2;-Bench</div>
+      <div class="bench-type">Agent Task Completion</div>
+      <div class="bench-hero">87.4</div>
+      <div class="bar-group">
+        <div class="bar-row">
+          <span class="bar-label highlight">GLM-4.7</span>
+          <div class="bar-track">
+            <div class="bar-fill winner" style="width: 87.4%;">
+              <span class="bar-value">87.4</span>
+            </div>
+          </div>
+        </div>
+        <div class="bar-row">
+          <span class="bar-label">Claude 3.5</span>
+          <div class="bar-track">
+            <div class="bar-fill dark" style="width: 78.9%;">
+              <span class="bar-value">78.9</span>
+            </div>
+          </div>
+        </div>
+        <div class="bar-row">
+          <span class="bar-label">GPT-4o</span>
+          <div class="bar-track">
+            <div class="bar-fill base" style="width: 71.5%;">
+              <span class="bar-value">71.5</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="delta">+8.5 vs closed-source best</div>
+    </div>
+  </div>
+
+  <!-- Summary row -->
+  <div class="summary-row">
+    <div class="summary-item">
+      <div class="summary-num"><span class="red">3</span>/3</div>
+      <div class="summary-desc">Benchmarks Won</div>
+    </div>
+    <div class="summary-item">
+      <div class="summary-num"><span class="red">#1</span></div>
+      <div class="summary-desc">Open-Source Ranking</div>
+    </div>
+    <div class="summary-item">
+      <div class="summary-num">12<span class="red">.</span>2<span style="font-size:18px;color:#999;">avg</span></div>
+      <div class="summary-desc">Points Above Runner-Up</div>
+    </div>
+  </div>
+
+  <!-- Bottom bar -->
+  <div class="bottom-bar">
+    <div class="bottom-left">
+      <span class="bottom-logo">ZHIPU AI</span>
+      <div class="bottom-divider"></div>
+      <span class="bottom-note">Benchmark data sourced from official evaluation reports, 2025</span>
+    </div>
+    <span class="bottom-right-text">Open-Source SOTA</span>
+  </div>
+
+</body>
+</html>

BIN
assets/showcases/ppt/ppt-pentagram.png


+ 497 - 0
assets/showcases/ppt/ppt-takram.html

@@ -0,0 +1,497 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1920">
+<title>GLM-4.7 Coding Benchmark - Takram Style</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Noto+Serif+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1920px;
+    height: 1080px;
+    overflow: hidden;
+    margin: 0;
+    background: #F5F0EB;
+    font-family: 'Inter', sans-serif;
+    color: #3A3A3A;
+    position: relative;
+  }
+
+  /* Subtle background texture */
+  body::before {
+    content: '';
+    position: absolute;
+    top: 0; left: 0; right: 0; bottom: 0;
+    background:
+      radial-gradient(ellipse at 20% 50%, rgba(168, 181, 160, 0.08) 0%, transparent 60%),
+      radial-gradient(ellipse at 80% 30%, rgba(200, 190, 175, 0.06) 0%, transparent 50%);
+    pointer-events: none;
+  }
+
+  .layout {
+    width: 100%;
+    height: 100%;
+    display: grid;
+    grid-template-columns: 480px 1fr;
+    grid-template-rows: 1fr;
+    position: relative;
+    z-index: 1;
+  }
+
+  /* Left panel */
+  .left-panel {
+    padding: 72px 48px 60px 72px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    border-right: 1px solid rgba(107, 143, 113, 0.15);
+  }
+
+  .left-top {}
+
+  .category-label {
+    font-size: 10px;
+    font-weight: 500;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+    color: #6B8F71;
+    margin-bottom: 32px;
+    opacity: 0.8;
+  }
+
+  .title-jp {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 42px;
+    font-weight: 400;
+    color: #2D3436;
+    line-height: 1.4;
+    margin-bottom: 16px;
+    letter-spacing: 1px;
+  }
+
+  .title-en {
+    font-size: 15px;
+    font-weight: 300;
+    color: #999999;
+    line-height: 1.7;
+    max-width: 340px;
+  }
+
+  .model-badge {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    margin-top: 36px;
+    padding: 10px 18px;
+    background: rgba(107, 143, 113, 0.08);
+    border: 1px solid rgba(107, 143, 113, 0.15);
+    border-radius: 24px;
+  }
+
+  .model-badge-dot {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: #6B8F71;
+  }
+
+  .model-badge-text {
+    font-size: 12px;
+    font-weight: 500;
+    color: #6B8F71;
+    letter-spacing: 1px;
+  }
+
+  /* Page indicator */
+  .page-indicator {
+    position: absolute;
+    bottom: 40px;
+    right: 72px;
+    font-family: 'Inter', sans-serif;
+    font-size: 10px;
+    font-weight: 300;
+    color: #C8C2B8;
+    letter-spacing: 1px;
+  }
+
+  /* Key insight */
+  .key-insight {
+    background: rgba(255, 255, 255, 0.5);
+    border-radius: 16px;
+    padding: 24px 28px;
+    border: 1px solid rgba(168, 181, 160, 0.2);
+  }
+
+  .key-insight-label {
+    font-size: 10px;
+    font-weight: 500;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #A8B5A0;
+    margin-bottom: 10px;
+  }
+
+  .key-insight-text {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 15px;
+    font-weight: 400;
+    color: #555555;
+    line-height: 1.8;
+  }
+
+  .left-bottom {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
+
+  .credit {
+    font-size: 11px;
+    font-weight: 400;
+    color: #BBBBBB;
+    letter-spacing: 0.5px;
+  }
+
+  /* Right panel - visualization */
+  .right-panel {
+    padding: 60px 72px 60px 60px;
+    display: flex;
+    flex-direction: column;
+    position: relative;
+  }
+
+  .viz-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 24px;
+  }
+
+  .viz-title {
+    font-size: 13px;
+    font-weight: 500;
+    letter-spacing: 1.5px;
+    text-transform: uppercase;
+    color: #888888;
+  }
+
+  .legend {
+    display: flex;
+    gap: 20px;
+  }
+
+  .legend-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+  }
+
+  .legend-dot {
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+  }
+
+  .legend-dot.glm { background: #6B8F71; }
+  .legend-dot.claude { background: #D4A574; }
+  .legend-dot.gpt { background: #C8C2B8; }
+
+  .legend-text {
+    font-size: 11px;
+    font-weight: 400;
+    color: #999999;
+  }
+
+  /* SVG radar chart area */
+  .radar-area {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+  }
+
+  .radar-svg {
+    filter: drop-shadow(0 4px 20px rgba(0,0,0,0.04));
+  }
+
+  /* Metric cards row */
+  .metric-cards {
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr;
+    gap: 20px;
+    margin-top: 20px;
+  }
+
+  .m-card {
+    background: rgba(255, 255, 255, 0.6);
+    border-radius: 16px;
+    padding: 24px 28px;
+    border: 1px solid rgba(168, 181, 160, 0.15);
+    position: relative;
+    overflow: hidden;
+  }
+
+  .m-card::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 28px;
+    width: 32px;
+    height: 2px;
+    background: #6B8F71;
+    opacity: 0.4;
+    border-radius: 1px;
+  }
+
+  .m-card-name {
+    font-size: 11px;
+    font-weight: 500;
+    letter-spacing: 1.5px;
+    text-transform: uppercase;
+    color: #999999;
+    margin-bottom: 4px;
+  }
+
+  .m-card-type {
+    font-size: 11px;
+    font-weight: 300;
+    color: #BBBBBB;
+    margin-bottom: 16px;
+  }
+
+  .m-card-value {
+    font-size: 40px;
+    font-weight: 300;
+    color: #2D3436;
+    letter-spacing: -1px;
+    line-height: 1;
+  }
+
+  .m-card-value .unit {
+    font-size: 18px;
+    color: #6B8F71;
+    font-weight: 400;
+  }
+
+  .m-card-delta {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    margin-top: 10px;
+    font-size: 12px;
+    font-weight: 500;
+    color: #7D9B72;
+    background: rgba(168, 181, 160, 0.12);
+    padding: 3px 10px;
+    border-radius: 12px;
+  }
+
+  .m-card-delta svg {
+    width: 10px;
+    height: 10px;
+  }
+
+  .m-card-competitors {
+    margin-top: 14px;
+    display: flex;
+    gap: 16px;
+  }
+
+  .comp-mini {
+    font-size: 11px;
+    font-weight: 400;
+    color: #AAAAAA;
+  }
+
+  .comp-mini span {
+    font-weight: 500;
+    color: #888888;
+  }
+</style>
+</head>
+<body>
+<div class="layout">
+  <!-- Left panel -->
+  <div class="left-panel">
+    <div class="left-top">
+      <div class="category-label">Benchmark Analysis</div>
+      <div class="title-jp">GLM-4.7<br>Coding 能力突破</div>
+      <div class="title-en">
+        Open-source model achieves state-of-the-art performance across all major coding benchmarks for the first time.
+      </div>
+      <div class="model-badge">
+        <div class="model-badge-dot"></div>
+        <span class="model-badge-text">GLM-4.7 Open Source</span>
+      </div>
+
+      <div class="key-insight" style="margin-top: 40px;">
+        <div class="key-insight-label">Key Finding</div>
+        <div class="key-insight-text">
+          在三项核心编程基准测试中,GLM-4.7 均超越 GPT-4o 和 Claude 3.5,成为首个达到 SOTA 水平的开源模型。
+        </div>
+      </div>
+    </div>
+
+    <div class="left-bottom">
+      <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
+        <rect x="1" y="1" width="14" height="14" rx="3" stroke="#BBBBBB" stroke-width="1"/>
+        <path d="M5 8L7 10L11 6" stroke="#A8B5A0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+      </svg>
+      <span class="credit">Data: Official benchmark evaluations, 2026</span>
+    </div>
+  </div>
+
+  <!-- Right panel -->
+  <div class="right-panel">
+    <div class="viz-header">
+      <div class="viz-title">Performance Comparison <span style="font-weight:300;color:#B0AAA0;font-size:10px;margin-left:8px;">— 03 benchmarks</span></div>
+      <div class="legend">
+        <div class="legend-item"><div class="legend-dot glm"></div><span class="legend-text">GLM-4.7</span></div>
+        <div class="legend-item"><div class="legend-dot claude"></div><span class="legend-text">Claude 3.5</span></div>
+        <div class="legend-item"><div class="legend-dot gpt"></div><span class="legend-text">GPT-4o</span></div>
+      </div>
+    </div>
+
+    <!-- Radar chart SVG — art-piece treatment -->
+    <div class="radar-area">
+      <svg class="radar-svg" width="560" height="560" viewBox="0 0 560 560">
+
+        <!-- Subtle background circle (like a lens/scope) -->
+        <circle cx="280" cy="280" r="250" fill="none" stroke="#E8E4DC" stroke-width="0.3" opacity="0.5"/>
+
+        <!-- Grid circles — hand-drawn feel with varied dash -->
+        <circle cx="280" cy="280" r="220" fill="none" stroke="#DDD9D2" stroke-width="0.6" stroke-dasharray="2,6"/>
+        <circle cx="280" cy="280" r="176" fill="none" stroke="#DDD9D2" stroke-width="0.5" stroke-dasharray="2,6"/>
+        <circle cx="280" cy="280" r="132" fill="none" stroke="#DDD9D2" stroke-width="0.4" stroke-dasharray="2,6"/>
+        <circle cx="280" cy="280" r="88" fill="none" stroke="#DDD9D2" stroke-width="0.4" stroke-dasharray="2,6"/>
+        <circle cx="280" cy="280" r="44" fill="none" stroke="#DDD9D2" stroke-width="0.3" stroke-dasharray="2,6"/>
+
+        <!-- Center point -->
+        <circle cx="280" cy="280" r="2.5" fill="#6B8F71" opacity="0.4"/>
+
+        <!-- Grid scale labels — positioned along axis -->
+        <text x="288" y="62" font-family="Inter" font-size="9" fill="#C8C2B8" font-weight="300">100</text>
+        <text x="288" y="106" font-family="Inter" font-size="9" fill="#C8C2B8" font-weight="300">80</text>
+        <text x="288" y="150" font-family="Inter" font-size="9" fill="#C8C2B8" font-weight="300">60</text>
+        <text x="288" y="194" font-family="Inter" font-size="9" fill="#C8C2B8" font-weight="300">40</text>
+
+        <!-- Axis lines — delicate -->
+        <line x1="280" y1="280" x2="280" y2="55" stroke="#D4CFC6" stroke-width="0.5"/>
+        <line x1="280" y1="280" x2="475" y2="392" stroke="#D4CFC6" stroke-width="0.5"/>
+        <line x1="280" y1="280" x2="85" y2="392" stroke="#D4CFC6" stroke-width="0.5"/>
+
+        <!-- Axis endpoint markers -->
+        <circle cx="280" cy="55" r="2" fill="none" stroke="#D4CFC6" stroke-width="0.6"/>
+        <circle cx="475" cy="392" r="2" fill="none" stroke="#D4CFC6" stroke-width="0.6"/>
+        <circle cx="85" cy="392" r="2" fill="none" stroke="#D4CFC6" stroke-width="0.6"/>
+
+        <!-- Axis labels with index -->
+        <text x="280" y="38" font-family="Inter" font-size="12" fill="#8A857D" font-weight="500" text-anchor="middle" letter-spacing="1.5">AIME 2025</text>
+        <text x="280" y="28" font-family="Inter" font-size="7" fill="#B0AAA0" text-anchor="middle" letter-spacing="0.5">Mathematical Reasoning</text>
+        <text x="492" y="408" font-family="Inter" font-size="12" fill="#8A857D" font-weight="500" text-anchor="start" letter-spacing="1.5">SWE-bench</text>
+        <text x="492" y="422" font-family="Inter" font-size="7" fill="#B0AAA0" text-anchor="start" letter-spacing="0.5">Software Engineering</text>
+        <text x="68" y="408" font-family="Inter" font-size="12" fill="#8A857D" font-weight="500" text-anchor="end" letter-spacing="1.5">&tau;&sup2;-Bench</text>
+        <text x="68" y="422" font-family="Inter" font-size="7" fill="#B0AAA0" text-anchor="end" letter-spacing="0.5">Agent Tasks</text>
+
+        <!-- GPT-4o polygon (lightest) -->
+        <polygon
+          points="280,96.1 371.8,333 143.8,358.7"
+          fill="rgba(219,219,219,0.12)" stroke="#D4CFC6" stroke-width="1" stroke-dasharray="4,3"
+        />
+
+        <!-- Claude 3.5 polygon -->
+        <polygon
+          points="280,86 381.6,338.6 129.7,366.8"
+          fill="rgba(212,165,116,0.08)" stroke="#D4A574" stroke-width="1.2"
+        />
+
+        <!-- GLM-4.7 polygon (prominent, sage green) -->
+        <polygon
+          points="280,69.5 420.6,361.2 113.5,376.2"
+          fill="rgba(107,143,113,0.1)" stroke="#6B8F71" stroke-width="2"
+        />
+
+        <!-- Data points - GLM-4.7 (larger, prominent) -->
+        <circle cx="280" cy="69.5" r="6" fill="#6B8F71" opacity="0.8"/>
+        <circle cx="280" cy="69.5" r="10" fill="none" stroke="#6B8F71" stroke-width="0.6" opacity="0.3"/>
+        <circle cx="420.6" cy="361.2" r="6" fill="#6B8F71" opacity="0.8"/>
+        <circle cx="420.6" cy="361.2" r="10" fill="none" stroke="#6B8F71" stroke-width="0.6" opacity="0.3"/>
+        <circle cx="113.5" cy="376.2" r="6" fill="#6B8F71" opacity="0.8"/>
+        <circle cx="113.5" cy="376.2" r="10" fill="none" stroke="#6B8F71" stroke-width="0.6" opacity="0.3"/>
+
+        <!-- Data points - Claude 3.5 -->
+        <circle cx="280" cy="86" r="3.5" fill="#D4A574" opacity="0.7"/>
+        <circle cx="381.6" cy="338.6" r="3.5" fill="#D4A574" opacity="0.7"/>
+        <circle cx="129.7" cy="366.8" r="3.5" fill="#D4A574" opacity="0.7"/>
+
+        <!-- Data points - GPT-4o -->
+        <circle cx="280" cy="96.1" r="2.5" fill="#C8C2B8" opacity="0.6"/>
+        <circle cx="371.8" cy="333" r="2.5" fill="#C8C2B8" opacity="0.6"/>
+        <circle cx="143.8" cy="358.7" r="2.5" fill="#C8C2B8" opacity="0.6"/>
+
+        <!-- Value labels for GLM-4.7 — annotation style -->
+        <line x1="280" y1="69.5" x2="316" y2="52" stroke="#6B8F71" stroke-width="0.5" opacity="0.4"/>
+        <text x="320" y="50" font-family="Inter" font-size="14" fill="#6B8F71" font-weight="600">95.7</text>
+
+        <line x1="420.6" y1="361.2" x2="448" y2="348" stroke="#6B8F71" stroke-width="0.5" opacity="0.4"/>
+        <text x="452" y="352" font-family="Inter" font-size="14" fill="#6B8F71" font-weight="600">73.8%</text>
+
+        <line x1="113.5" y1="376.2" x2="82" y2="392" stroke="#6B8F71" stroke-width="0.5" opacity="0.4"/>
+        <text x="78" y="390" font-family="Inter" font-size="14" fill="#6B8F71" font-weight="600" text-anchor="end">87.4</text>
+
+        <!-- Spec annotation — bottom-right -->
+        <text x="505" y="530" font-family="Inter" font-size="8" fill="#C8C2B8" font-weight="300" letter-spacing="1" text-anchor="end">Fig. 01 — Tri-axis Performance Map</text>
+      </svg>
+    </div>
+
+    <!-- Metric cards -->
+    <div class="metric-cards">
+      <div class="m-card">
+        <div class="m-card-name">AIME 2025</div>
+        <div class="m-card-type">Mathematical Reasoning</div>
+        <div class="m-card-value">95.7</div>
+        <div class="m-card-delta">
+          <svg viewBox="0 0 10 10" fill="none"><path d="M5 2L8 7H2L5 2Z" fill="#7D9B72"/></svg>
+          +7.5 vs Claude 3.5
+        </div>
+        <div class="m-card-competitors">
+          <span class="comp-mini">Claude 3.5: <span>88.2</span></span>
+          <span class="comp-mini">GPT-4o: <span>83.6</span></span>
+        </div>
+      </div>
+
+      <div class="m-card">
+        <div class="m-card-name">SWE-bench Verified</div>
+        <div class="m-card-type">Software Engineering</div>
+        <div class="m-card-value">73.8<span class="unit">%</span></div>
+        <div class="m-card-delta">
+          <svg viewBox="0 0 10 10" fill="none"><path d="M5 2L8 7H2L5 2Z" fill="#7D9B72"/></svg>
+          +20.5 vs Claude 3.5
+        </div>
+        <div class="m-card-competitors">
+          <span class="comp-mini">Claude 3.5: <span>53.3%</span></span>
+          <span class="comp-mini">GPT-4o: <span>48.2%</span></span>
+        </div>
+      </div>
+
+      <div class="m-card">
+        <div class="m-card-name">&tau;&sup2;-Bench</div>
+        <div class="m-card-type">Agent Task Completion</div>
+        <div class="m-card-value">87.4</div>
+        <div class="m-card-delta">
+          <svg viewBox="0 0 10 10" fill="none"><path d="M5 2L8 7H2L5 2Z" fill="#7D9B72"/></svg>
+          +8.5 vs Claude 3.5
+        </div>
+        <div class="m-card-competitors">
+          <span class="comp-mini">Claude 3.5: <span>78.9</span></span>
+          <span class="comp-mini">GPT-4o: <span>71.5</span></span>
+        </div>
+      </div>
+    </div>
+    <div class="page-indicator">07 / 24</div>
+  </div>
+</div>
+</body>
+</html>

BIN
assets/showcases/ppt/ppt-takram.png


+ 385 - 0
assets/showcases/website-ai-nav/ainav-build.html

@@ -0,0 +1,385 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>AI Compass — Build Studio Style</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Inter', sans-serif;
+    background: #FAFAF8;
+    color: #1A1A1A;
+  }
+
+  /* NAV */
+  nav {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 28px 80px;
+  }
+  .nav-logo {
+    font-weight: 500;
+    font-size: 18px;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    color: #1A1A1A;
+  }
+  .nav-logo svg {
+    color: #D4A574;
+  }
+  .nav-links {
+    display: flex;
+    gap: 40px;
+    list-style: none;
+  }
+  .nav-links a {
+    text-decoration: none;
+    color: #999;
+    font-size: 13px;
+    font-weight: 400;
+    letter-spacing: 1px;
+    transition: color 0.3s;
+  }
+  .nav-links a:hover { color: #1A1A1A; }
+  .nav-cta {
+    font-size: 12px;
+    font-weight: 400;
+    letter-spacing: 1px;
+    background: transparent;
+    color: #888;
+    border: 1px solid rgba(0,0,0,0.08);
+    padding: 8px 24px;
+    border-radius: 2px;
+    cursor: pointer;
+    transition: all 0.3s;
+  }
+  .nav-cta:hover {
+    border-color: #D4A574;
+    color: #D4A574;
+  }
+
+  /* HERO */
+  .hero {
+    text-align: center;
+    padding: 64px 80px 0;
+  }
+  .hero-eyebrow {
+    font-size: 10px;
+    font-weight: 400;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+    margin-bottom: 24px;
+  }
+  .hero h1 {
+    font-size: 52px;
+    font-weight: 200;
+    line-height: 1.15;
+    letter-spacing: -1px;
+    max-width: 700px;
+    margin: 0 auto;
+    color: #1A1A1A;
+  }
+  .hero h1 em {
+    font-style: italic;
+    font-weight: 300;
+    color: #D4A574;
+  }
+  .hero-sub {
+    font-size: 16px;
+    font-weight: 300;
+    color: #888;
+    margin-top: 16px;
+    letter-spacing: 0.3px;
+  }
+
+  /* SEARCH */
+  .search-wrapper {
+    max-width: 600px;
+    margin: 32px auto 0;
+    position: relative;
+  }
+  .search-bar {
+    width: 100%;
+    padding: 18px 56px 18px 24px;
+    font-family: 'Inter', sans-serif;
+    font-size: 15px;
+    font-weight: 300;
+    color: #1A1A1A;
+    background: #FFFFFF;
+    border: 1px solid #E8E4DF;
+    border-radius: 2px;
+    outline: none;
+    box-shadow: 0 2px 20px rgba(0,0,0,0.04);
+    transition: box-shadow 0.3s, border-color 0.3s;
+  }
+  .search-bar::placeholder { color: #BBB; }
+  .search-bar:focus {
+    box-shadow: 0 4px 30px rgba(212,165,116,0.12);
+    border-color: #D4A574;
+  }
+  .search-icon {
+    position: absolute;
+    right: 20px;
+    top: 50%;
+    transform: translateY(-50%);
+    color: #D4A574;
+  }
+
+  /* CATEGORIES */
+  .categories {
+    display: flex;
+    justify-content: center;
+    gap: 8px;
+    margin-top: 32px;
+    flex-wrap: wrap;
+  }
+  .cat-pill {
+    font-size: 12px;
+    font-weight: 400;
+    color: #999;
+    padding: 8px 16px;
+    background: transparent;
+    border: 1px solid #E8E4DF;
+    border-radius: 2px;
+    cursor: pointer;
+    transition: all 0.25s;
+    letter-spacing: 0.3px;
+  }
+  .cat-pill:hover {
+    border-color: #D4A574;
+    color: #1A1A1A;
+  }
+  .cat-pill.active {
+    border-color: #D4A574;
+    color: #D4A574;
+    background: rgba(212,165,116,0.06);
+  }
+
+  /* TOOL CARDS */
+  .tools-section {
+    padding: 48px 80px 0;
+  }
+  .tools-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+  }
+  .tools-header h2 {
+    font-size: 13px;
+    font-weight: 500;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #999;
+  }
+  .tools-header a {
+    font-size: 13px;
+    font-weight: 400;
+    color: #D4A574;
+    text-decoration: none;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    transition: opacity 0.3s;
+  }
+  .tools-header a:hover { opacity: 0.7; }
+
+  .tools-grid {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 16px;
+  }
+  .tool-card {
+    background: #FFFFFF;
+    border: 1px solid #EEEBE7;
+    border-radius: 2px;
+    padding: 24px;
+    cursor: pointer;
+    position: relative;
+  }
+  .tool-card-header {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    margin-bottom: 16px;
+  }
+  .tool-icon-box {
+    width: 44px;
+    height: 44px;
+    border-radius: 2px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+  }
+  .tool-icon-box.claude { background: #F0EBE3; color: #D4A574; }
+  .tool-icon-box.cursor { background: #EEECEA; color: #999; }
+  .tool-icon-box.midjourney { background: #EEECEA; color: #999; }
+  .tool-icon-box.perplexity { background: #EEECEA; color: #999; }
+
+  .tool-card-name {
+    font-size: 17px;
+    font-weight: 500;
+    letter-spacing: -0.3px;
+  }
+  .tool-card-cat {
+    font-size: 11px;
+    font-weight: 400;
+    color: #BBB;
+    letter-spacing: 0.5px;
+    margin-top: 2px;
+  }
+  .tool-card-desc {
+    font-size: 14px;
+    font-weight: 300;
+    color: #888;
+    line-height: 1.55;
+  }
+  .tool-card-tag {
+    display: inline-block;
+    margin-top: 16px;
+    font-size: 11px;
+    font-weight: 500;
+    color: #D4A574;
+    letter-spacing: 0.5px;
+    padding: 4px 10px;
+    background: rgba(212,165,116,0.1);
+    border-radius: 2px;
+  }
+
+  /* DIVIDER */
+  .divider {
+    width: 40px;
+    height: 1px;
+    background: #D4A574;
+    margin: 0 auto;
+    opacity: 0.5;
+  }
+</style>
+</head>
+<body>
+
+<nav>
+  <div class="nav-logo">
+    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
+      <circle cx="12" cy="12" r="10"/>
+      <polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88" fill="currentColor" stroke="currentColor"/>
+    </svg>
+    AI Compass
+  </div>
+  <ul class="nav-links">
+    <li><a href="#">Browse</a></li>
+    <li><a href="#">Categories</a></li>
+    <li><a href="#">New This Week</a></li>
+    <li><a href="#">Newsletter</a></li>
+  </ul>
+  <button class="nav-cta">Submit Tool</button>
+</nav>
+
+<section class="hero">
+  <p class="hero-eyebrow">A Curated Directory</p>
+  <h1>Find the right AI tool <em>in seconds</em></h1>
+  <p class="hero-sub">500+ tools, 24 categories, updated weekly</p>
+
+  <div class="search-wrapper">
+    <input class="search-bar" type="text" placeholder="Search by tool name, category, or use case...">
+    <i data-lucide="search" class="search-icon" style="width:18px;height:18px;"></i>
+  </div>
+
+  <div class="categories">
+    <span class="cat-pill active">Writing</span>
+    <span class="cat-pill">Coding</span>
+    <span class="cat-pill">Image</span>
+    <span class="cat-pill">Video</span>
+    <span class="cat-pill">Audio</span>
+    <span class="cat-pill">Productivity</span>
+    <span class="cat-pill">Research</span>
+  </div>
+</section>
+
+<section class="tools-section">
+  <div class="tools-header">
+    <h2>Featured Selections</h2>
+    <a href="#">
+      View all 500+ tools
+      <i data-lucide="arrow-right" style="width:14px;height:14px;"></i>
+    </a>
+  </div>
+
+  <div class="tools-grid">
+    <div class="tool-card">
+      <div class="tool-card-header">
+        <div class="tool-icon-box claude">
+          <i data-lucide="sparkles" style="width:20px;height:20px;"></i>
+        </div>
+        <div>
+          <div class="tool-card-name">Claude</div>
+          <div class="tool-card-cat">Writing & Analysis</div>
+        </div>
+      </div>
+      <p class="tool-card-desc">Advanced AI assistant for writing, analysis, and coding with nuanced reasoning and extended context.</p>
+      <span class="tool-card-tag">Editor's Pick</span>
+    </div>
+
+    <div class="tool-card">
+      <div class="tool-card-header">
+        <div class="tool-icon-box cursor">
+          <i data-lucide="code-2" style="width:20px;height:20px;"></i>
+        </div>
+        <div>
+          <div class="tool-card-name">Cursor</div>
+          <div class="tool-card-cat">Development</div>
+        </div>
+      </div>
+      <p class="tool-card-desc">AI-native code editor that understands your entire codebase and accelerates your development workflow.</p>
+      <span class="tool-card-tag">Trending</span>
+    </div>
+
+    <div class="tool-card">
+      <div class="tool-card-header">
+        <div class="tool-icon-box midjourney">
+          <i data-lucide="image" style="width:20px;height:20px;"></i>
+        </div>
+        <div>
+          <div class="tool-card-name">Midjourney</div>
+          <div class="tool-card-cat">Image Generation</div>
+        </div>
+      </div>
+      <p class="tool-card-desc">Leading AI image generation platform producing stunning, highly detailed visuals from text prompts.</p>
+      <span class="tool-card-tag">Popular</span>
+    </div>
+
+    <div class="tool-card">
+      <div class="tool-card-header">
+        <div class="tool-icon-box perplexity">
+          <i data-lucide="globe" style="width:20px;height:20px;"></i>
+        </div>
+        <div>
+          <div class="tool-card-name">Perplexity</div>
+          <div class="tool-card-cat">Research & Search</div>
+        </div>
+      </div>
+      <p class="tool-card-desc">AI-powered search engine delivering real-time, cited answers in a natural conversational format.</p>
+      <span class="tool-card-tag">Staff Pick</span>
+    </div>
+  </div>
+</section>
+
+<script>
+  lucide.createIcons();
+</script>
+</body>
+</html>

BIN
assets/showcases/website-ai-nav/ainav-build.png


+ 422 - 0
assets/showcases/website-ai-nav/ainav-pentagram.html

@@ -0,0 +1,422 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>AI Compass — Pentagram Style</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    background: #FFFFFF;
+    color: #000000;
+  }
+
+  /* NAV */
+  nav {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 24px 64px;
+    border-bottom: 2px solid #000;
+  }
+  .nav-logo {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 700;
+    font-size: 20px;
+    letter-spacing: -0.5px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  .nav-logo .compass-icon {
+    width: 24px;
+    height: 24px;
+  }
+  .nav-links {
+    display: flex;
+    gap: 32px;
+    list-style: none;
+    font-size: 13px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+  }
+  .nav-links a {
+    text-decoration: none;
+    color: #000;
+    transition: color 0.2s;
+  }
+  .nav-links a:hover { color: #E63946; }
+  .nav-submit {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 13px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    background: #000;
+    color: #fff;
+    border: none;
+    padding: 10px 24px;
+    cursor: pointer;
+    transition: background 0.2s;
+  }
+  .nav-submit:hover { background: #E63946; }
+
+  /* HERO GRID */
+  .hero {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    min-height: calc(900px - 72px);
+  }
+
+  /* LEFT PANEL */
+  .hero-left {
+    padding: 56px 64px 48px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    border-right: 2px solid #000;
+  }
+  .hero-stat {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 180px;
+    font-weight: 900;
+    line-height: 0.85;
+    letter-spacing: -8px;
+    color: #E63946;
+    position: relative;
+  }
+  .hero-stat span {
+    font-size: 48px;
+    letter-spacing: -2px;
+    vertical-align: top;
+    margin-left: 4px;
+  }
+  .hero-headline {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 42px;
+    font-weight: 900;
+    line-height: 1.08;
+    letter-spacing: -1.5px;
+    margin-top: 24px;
+    max-width: 520px;
+  }
+  .hero-sub {
+    font-size: 15px;
+    color: #555;
+    margin-top: 16px;
+    letter-spacing: 0.2px;
+    line-height: 1.5;
+  }
+
+  /* SEARCH */
+  .search-box {
+    display: flex;
+    border: 3px solid #000;
+    margin-top: 32px;
+    max-width: 560px;
+  }
+  .search-box input {
+    flex: 1;
+    padding: 16px 20px;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 15px;
+    border: none;
+    outline: none;
+    background: #fff;
+  }
+  .search-box button {
+    padding: 16px 28px;
+    background: #000;
+    color: #fff;
+    border: none;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 13px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    transition: background 0.2s;
+  }
+  .search-box button:hover { background: #E63946; }
+
+  /* CATEGORY TAGS */
+  .categories {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    margin-top: 28px;
+  }
+  .cat-tag {
+    font-size: 11px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    padding: 6px 14px;
+    border: 2px solid #000;
+    background: transparent;
+    cursor: pointer;
+    transition: all 0.15s;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+  }
+  .cat-tag:hover {
+    background: #000;
+    color: #fff;
+  }
+  .cat-tag.active {
+    background: #E63946;
+    border-color: #E63946;
+    color: #fff;
+  }
+
+  /* RIGHT PANEL — TOOL LIST */
+  .hero-right {
+    display: flex;
+    flex-direction: column;
+  }
+  .list-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px 48px;
+    border-bottom: 2px solid #000;
+    font-size: 11px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    color: #888;
+  }
+
+  .tool-item {
+    display: grid;
+    grid-template-columns: 48px 1fr auto;
+    align-items: center;
+    padding: 24px 48px;
+    border-bottom: 1px solid #E0E0E0;
+    transition: background 0.15s;
+    cursor: pointer;
+  }
+  .tool-item:hover {
+    background: #F7F7F7;
+  }
+  .tool-index {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 14px;
+    font-weight: 500;
+    color: #BBB;
+  }
+  .tool-info {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
+  .tool-name-row {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
+  .tool-name {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 22px;
+    font-weight: 600;
+    letter-spacing: -0.5px;
+  }
+  .tool-badge {
+    font-size: 10px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    color: #E63946;
+    background: rgba(230, 57, 70, 0.08);
+    padding: 3px 8px;
+  }
+  .tool-desc {
+    font-size: 13px;
+    color: #777;
+    line-height: 1.4;
+    max-width: 400px;
+  }
+  .tool-category {
+    font-size: 11px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    color: #999;
+    white-space: nowrap;
+  }
+
+  .tool-item:last-child {
+    border-bottom: none;
+  }
+
+  /* FEATURED TAG */
+  .tool-item.featured {
+    border-left: 4px solid #E63946;
+    padding-left: 44px;
+  }
+
+  .bottom-bar {
+    margin-top: auto;
+    padding: 16px 48px;
+    border-top: 2px solid #000;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 12px;
+    color: #888;
+    font-weight: 500;
+    letter-spacing: 0.5px;
+  }
+  .bottom-bar a {
+    color: #000;
+    text-decoration: none;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    font-size: 11px;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    transition: color 0.2s;
+  }
+  .bottom-bar a:hover { color: #E63946; }
+</style>
+</head>
+<body>
+
+<nav>
+  <div class="nav-logo">
+    <svg class="compass-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+      <circle cx="12" cy="12" r="10"/>
+      <polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88" fill="#E63946" stroke="#E63946"/>
+    </svg>
+    AI Compass
+  </div>
+  <ul class="nav-links">
+    <li><a href="#">Browse</a></li>
+    <li><a href="#">Categories</a></li>
+    <li><a href="#">New Tools</a></li>
+    <li><a href="#">About</a></li>
+  </ul>
+  <button class="nav-submit">Submit a Tool</button>
+</nav>
+
+<div class="hero">
+  <!-- LEFT -->
+  <div class="hero-left">
+    <div>
+      <div class="hero-stat">500<span>+</span></div>
+      <h1 class="hero-headline">Find the right AI tool in seconds</h1>
+      <p class="hero-sub">500+ tools, 24 categories, updated weekly. The most comprehensive curated directory for AI practitioners.</p>
+
+      <div class="search-box">
+        <input type="text" placeholder="Search tools by name, category, or use case...">
+        <button>
+          <i data-lucide="search" style="width:16px;height:16px;"></i>
+          Search
+        </button>
+      </div>
+
+      <div class="categories">
+        <span class="cat-tag active">Writing</span>
+        <span class="cat-tag">Coding</span>
+        <span class="cat-tag">Image</span>
+        <span class="cat-tag">Video</span>
+        <span class="cat-tag">Audio</span>
+        <span class="cat-tag">Productivity</span>
+        <span class="cat-tag">Research</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- RIGHT -->
+  <div class="hero-right">
+    <div class="list-header">
+      <span>Featured Tools</span>
+      <span>Category</span>
+    </div>
+
+    <div class="tool-item featured">
+      <span class="tool-index">01</span>
+      <div class="tool-info">
+        <div class="tool-name-row">
+          <span class="tool-name">Claude</span>
+          <span class="tool-badge">Editor's Pick</span>
+        </div>
+        <span class="tool-desc">Advanced AI assistant for writing, analysis, and coding with extended context and nuanced reasoning.</span>
+      </div>
+      <span class="tool-category">Writing</span>
+    </div>
+
+    <div class="tool-item">
+      <span class="tool-index">02</span>
+      <div class="tool-info">
+        <div class="tool-name-row">
+          <span class="tool-name">Cursor</span>
+          <span class="tool-badge">Trending</span>
+        </div>
+        <span class="tool-desc">AI-native code editor that understands your entire codebase and accelerates development workflows.</span>
+      </div>
+      <span class="tool-category">Coding</span>
+    </div>
+
+    <div class="tool-item">
+      <span class="tool-index">03</span>
+      <div class="tool-info">
+        <div class="tool-name-row">
+          <span class="tool-name">Midjourney</span>
+        </div>
+        <span class="tool-desc">Leading AI image generation platform producing stunning visuals from text descriptions.</span>
+      </div>
+      <span class="tool-category">Image</span>
+    </div>
+
+    <div class="tool-item">
+      <span class="tool-index">04</span>
+      <div class="tool-info">
+        <div class="tool-name-row">
+          <span class="tool-name">Perplexity</span>
+        </div>
+        <span class="tool-desc">AI-powered search engine with real-time citations and conversational answers.</span>
+      </div>
+      <span class="tool-category">Research</span>
+    </div>
+
+    <div class="tool-item">
+      <span class="tool-index">05</span>
+      <div class="tool-info">
+        <div class="tool-name-row">
+          <span class="tool-name">Runway</span>
+          <span class="tool-badge">New</span>
+        </div>
+        <span class="tool-desc">Gen-3 video generation and editing suite for creators and filmmakers.</span>
+      </div>
+      <span class="tool-category">Video</span>
+    </div>
+
+    <div class="bottom-bar">
+      <span>Showing 5 of 500+ tools</span>
+      <a href="#">
+        View All Tools
+        <i data-lucide="arrow-right" style="width:14px;height:14px;"></i>
+      </a>
+    </div>
+  </div>
+</div>
+
+<script>
+  lucide.createIcons();
+</script>
+</body>
+</html>

BIN
assets/showcases/website-ai-nav/ainav-pentagram.png


+ 499 - 0
assets/showcases/website-ai-nav/ainav-takram.html

@@ -0,0 +1,499 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>AI Compass — Takram Style</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Noto+Serif+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Inter', sans-serif;
+    background: #F5F0EB;
+    color: #3A3A35;
+  }
+
+  /* NAV */
+  nav {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 24px 72px;
+  }
+  .nav-logo {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 500;
+    font-size: 18px;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    color: #3A3A35;
+  }
+  .nav-logo svg { color: #A8B5A0; }
+  .nav-right {
+    display: flex;
+    align-items: center;
+    gap: 36px;
+  }
+  .nav-links {
+    display: flex;
+    gap: 28px;
+    list-style: none;
+  }
+  .nav-links a {
+    text-decoration: none;
+    color: #8A8A80;
+    font-size: 14px;
+    font-weight: 400;
+    transition: color 0.3s;
+  }
+  .nav-links a:hover { color: #3A3A35; }
+  .nav-cta {
+    font-size: 13px;
+    font-weight: 500;
+    background: transparent;
+    color: #6B8F71;
+    border: 1px solid rgba(107, 143, 113, 0.35);
+    padding: 10px 24px;
+    border-radius: 100px;
+    cursor: pointer;
+    transition: all 0.3s;
+  }
+  .nav-cta:hover { background: rgba(107, 143, 113, 0.06); border-color: #6B8F71; }
+
+  /* MAIN LAYOUT */
+  .main {
+    display: grid;
+    grid-template-columns: 520px 1fr;
+    gap: 0;
+    padding: 20px 72px 0;
+    height: calc(900px - 68px);
+  }
+
+  /* LEFT: HERO TEXT */
+  .hero-text {
+    padding: 40px 48px 40px 0;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+  .hero-eyebrow {
+    font-size: 11px;
+    font-weight: 500;
+    color: #6B8F71;
+    letter-spacing: 2.5px;
+    text-transform: uppercase;
+    margin-bottom: 16px;
+    opacity: 0.8;
+  }
+  .hero-headline {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 42px;
+    font-weight: 400;
+    line-height: 1.3;
+    letter-spacing: -0.5px;
+    color: #2D3436;
+  }
+  .hero-headline em {
+    font-style: normal;
+    color: #6B8F71;
+    font-weight: 500;
+  }
+  .hero-sub {
+    font-size: 15px;
+    font-weight: 300;
+    color: #8A8A80;
+    margin-top: 16px;
+    line-height: 1.6;
+    max-width: 400px;
+  }
+
+  /* SEARCH */
+  .search-wrapper {
+    margin-top: 32px;
+    position: relative;
+    max-width: 420px;
+  }
+  .search-bar {
+    width: 100%;
+    padding: 16px 50px 16px 20px;
+    font-family: 'Inter', sans-serif;
+    font-size: 14px;
+    font-weight: 300;
+    color: #3A3A35;
+    background: #EDE8DE;
+    border: 1px solid #DDD7CC;
+    border-radius: 14px;
+    outline: none;
+    transition: all 0.3s;
+  }
+  .search-bar::placeholder { color: #B0AEA4; }
+  .search-bar:focus {
+    background: #FFFFFF;
+    border-color: #6B8F71;
+    box-shadow: 0 4px 24px rgba(168,181,160,0.15);
+  }
+  .search-icon {
+    position: absolute;
+    right: 16px;
+    top: 50%;
+    transform: translateY(-50%);
+    color: #6B8F71;
+  }
+
+  /* CATEGORY CHIPS */
+  .categories {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    margin-top: 24px;
+    max-width: 420px;
+  }
+  .cat-chip {
+    font-size: 12px;
+    font-weight: 400;
+    color: #7A7A72;
+    padding: 7px 16px;
+    background: #EDE8DE;
+    border: none;
+    border-radius: 100px;
+    cursor: pointer;
+    transition: all 0.25s;
+  }
+  .cat-chip:hover {
+    background: #E0DBCF;
+    color: #3A3A35;
+  }
+  .cat-chip.active {
+    background: rgba(107, 143, 113, 0.15);
+    color: #6B8F71;
+    border: 1px solid rgba(107, 143, 113, 0.25);
+  }
+
+  /* DIAGRAM LINES (decorative connections) */
+  .diagram-canvas {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    pointer-events: none;
+    z-index: 0;
+  }
+
+  /* RIGHT: TOOL CARDS */
+  .tools-area {
+    position: relative;
+    padding: 20px 0 0 20px;
+  }
+
+  .tools-label {
+    font-size: 10px;
+    font-weight: 500;
+    color: #6B8F71;
+    letter-spacing: 2.5px;
+    text-transform: uppercase;
+    margin-bottom: 20px;
+    padding-left: 4px;
+    opacity: 0.7;
+  }
+
+  .tools-organic {
+    position: relative;
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 16px;
+  }
+
+  .tool-card {
+    background: rgba(255,255,255,0.5);
+    border: 1px solid #E8E4DC;
+    border-radius: 14px;
+    padding: 24px;
+    transition: all 0.3s;
+    cursor: pointer;
+    position: relative;
+  }
+  .tool-card:hover {
+    box-shadow: 0 8px 32px rgba(0,0,0,0.06);
+    transform: translateY(-2px);
+  }
+
+  /* Organic offset: stagger cards */
+  .tool-card:nth-child(2) {
+    margin-top: 24px;
+  }
+  .tool-card:nth-child(3) {
+    margin-top: -12px;
+  }
+
+  .tool-card-header {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    margin-bottom: 12px;
+  }
+  .tool-icon {
+    width: 40px;
+    height: 40px;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+  }
+  .tool-icon.claude { background: rgba(212, 165, 116, 0.15); color: #D4A574; }
+  .tool-icon.cursor { background: rgba(139, 157, 195, 0.12); color: #8B9DC3; }
+  .tool-icon.midjourney { background: rgba(212, 165, 116, 0.12); color: #C4A882; }
+  .tool-icon.perplexity { background: rgba(107, 143, 113, 0.1); color: #6B8F71; }
+
+  .tool-name {
+    font-size: 16px;
+    font-weight: 500;
+    color: #2C2C28;
+  }
+  .tool-cat {
+    font-size: 11px;
+    color: #AAA89E;
+    margin-top: 1px;
+  }
+  .tool-desc {
+    font-size: 13px;
+    font-weight: 300;
+    color: #8A8A80;
+    line-height: 1.55;
+  }
+  .tool-tag {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    margin-top: 14px;
+    font-size: 11px;
+    font-weight: 500;
+    color: #6B8F71;
+    padding: 4px 10px;
+    background: rgba(107,143,113,0.08);
+    border-radius: 100px;
+  }
+
+  /* Connection dots */
+  .conn-dot {
+    position: absolute;
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: #6B8F71;
+    opacity: 0.4;
+  }
+  .conn-dot.d1 { top: 80px; left: -10px; }
+  .conn-dot.d2 { top: 200px; left: -14px; }
+  .conn-dot.d3 { bottom: 160px; left: -10px; }
+
+  .conn-line {
+    position: absolute;
+    left: -10px;
+    width: 2px;
+    background: linear-gradient(to bottom, transparent, #A8B5A0, transparent);
+    opacity: 0.2;
+  }
+  .conn-line.l1 { top: 88px; height: 108px; }
+  .conn-line.l2 { top: 208px; height: 100px; }
+
+  /* VIEW MORE */
+  .view-more {
+    text-align: center;
+    margin-top: 16px;
+  }
+  .view-more a {
+    font-size: 13px;
+    font-weight: 400;
+    color: #6B8F71;
+    text-decoration: none;
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    transition: color 0.3s;
+  }
+  .view-more a:hover { color: #7A9470; }
+
+  /* FLOATING NOTE */
+  .floating-note {
+    position: absolute;
+    bottom: 40px;
+    left: 72px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    font-size: 12px;
+    color: #B0AEA4;
+    font-weight: 300;
+  }
+  .floating-note .dot {
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #6B8F71;
+    animation: pulse 2s infinite;
+  }
+  @keyframes pulse {
+    0%, 100% { opacity: 0.4; }
+    50% { opacity: 1; }
+  }
+</style>
+</head>
+<body>
+
+<nav>
+  <div class="nav-logo">
+    <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
+      <circle cx="12" cy="12" r="10"/>
+      <polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88" fill="currentColor" stroke="currentColor"/>
+    </svg>
+    AI Compass
+  </div>
+  <div class="nav-right">
+    <ul class="nav-links">
+      <li><a href="#">Explore</a></li>
+      <li><a href="#">Categories</a></li>
+      <li><a href="#">Weekly Picks</a></li>
+      <li><a href="#">About</a></li>
+    </ul>
+    <button class="nav-cta">Submit Tool</button>
+  </div>
+</nav>
+
+<div class="main">
+  <!-- LEFT -->
+  <div class="hero-text">
+    <p class="hero-eyebrow">Curated Directory</p>
+    <h1 class="hero-headline">Find the right<br>AI tool <em>in seconds</em></h1>
+    <p class="hero-sub">500+ carefully selected tools across 24 categories, updated weekly. Discover, compare, and find the perfect tool for your workflow.</p>
+
+    <div class="search-wrapper">
+      <input class="search-bar" type="text" placeholder="Search tools, categories, or use cases...">
+      <i data-lucide="search" class="search-icon" style="width:16px;height:16px;"></i>
+    </div>
+
+    <div class="categories">
+      <span class="cat-chip active">Writing</span>
+      <span class="cat-chip">Coding</span>
+      <span class="cat-chip">Image</span>
+      <span class="cat-chip">Video</span>
+      <span class="cat-chip">Audio</span>
+      <span class="cat-chip">Productivity</span>
+      <span class="cat-chip">Research</span>
+    </div>
+  </div>
+
+  <!-- RIGHT -->
+  <div class="tools-area">
+    <div class="conn-dot d1"></div>
+    <div class="conn-line l1"></div>
+    <div class="conn-dot d2"></div>
+    <div class="conn-line l2"></div>
+    <div class="conn-dot d3"></div>
+
+    <p class="tools-label">Featured Discoveries</p>
+
+    <div class="tools-organic">
+      <div class="tool-card">
+        <div class="tool-card-header">
+          <div class="tool-icon claude">
+            <i data-lucide="sparkles" style="width:18px;height:18px;"></i>
+          </div>
+          <div>
+            <div class="tool-name">Claude</div>
+            <div class="tool-cat">Writing & Analysis</div>
+          </div>
+        </div>
+        <p class="tool-desc">Advanced AI assistant for writing, analysis, and coding with nuanced reasoning and extended context window.</p>
+        <span class="tool-tag">
+          <i data-lucide="star" style="width:10px;height:10px;"></i>
+          Editor's Pick
+        </span>
+      </div>
+
+      <div class="tool-card">
+        <div class="tool-card-header">
+          <div class="tool-icon cursor">
+            <i data-lucide="code-2" style="width:18px;height:18px;"></i>
+          </div>
+          <div>
+            <div class="tool-name">Cursor</div>
+            <div class="tool-cat">Development</div>
+          </div>
+        </div>
+        <p class="tool-desc">AI-native code editor that deeply understands your codebase and accelerates every development task.</p>
+        <span class="tool-tag">
+          <i data-lucide="trending-up" style="width:10px;height:10px;"></i>
+          Trending
+        </span>
+      </div>
+
+      <div class="tool-card">
+        <div class="tool-card-header">
+          <div class="tool-icon midjourney">
+            <i data-lucide="image" style="width:18px;height:18px;"></i>
+          </div>
+          <div>
+            <div class="tool-name">Midjourney</div>
+            <div class="tool-cat">Image Generation</div>
+          </div>
+        </div>
+        <p class="tool-desc">Create stunning, detailed visuals from text descriptions with the leading AI image generation platform.</p>
+        <span class="tool-tag">
+          <i data-lucide="heart" style="width:10px;height:10px;"></i>
+          Popular
+        </span>
+      </div>
+
+      <div class="tool-card">
+        <div class="tool-card-header">
+          <div class="tool-icon perplexity">
+            <i data-lucide="globe" style="width:18px;height:18px;"></i>
+          </div>
+          <div>
+            <div class="tool-name">Perplexity</div>
+            <div class="tool-cat">Research & Search</div>
+          </div>
+        </div>
+        <p class="tool-desc">AI-powered search delivering real-time answers with citations in a natural conversational format.</p>
+        <span class="tool-tag">
+          <i data-lucide="compass" style="width:10px;height:10px;"></i>
+          Staff Pick
+        </span>
+      </div>
+    </div>
+
+    <div class="view-more">
+      <a href="#">
+        Explore all 500+ tools
+        <i data-lucide="arrow-right" style="width:14px;height:14px;"></i>
+      </a>
+    </div>
+  </div>
+</div>
+
+<div class="floating-note">
+  <span class="dot"></span>
+  Updated weekly with new discoveries
+</div>
+
+<!-- Spec annotation -->
+<svg style="position:absolute;bottom:60px;right:72px;opacity:0.15;" width="100" height="40" viewBox="0 0 100 40" fill="none">
+  <line x1="0" y1="20" x2="60" y2="20" stroke="#6B8F71" stroke-width="0.5"/>
+  <circle cx="60" cy="20" r="2" fill="none" stroke="#6B8F71" stroke-width="0.5"/>
+  <text x="68" y="23" font-family="Inter" font-size="8" fill="#6B8F71" letter-spacing="0.5">500+ tools</text>
+</svg>
+
+<script>
+  lucide.createIcons();
+</script>
+</body>
+</html>

BIN
assets/showcases/website-ai-nav/ainav-takram.png


+ 562 - 0
assets/showcases/website-ai-writing/aiwriting-build.html

@@ -0,0 +1,562 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Inkwell — AI Writing Assistant</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    background: #FAFAF8;
+    font-family: 'Inter', sans-serif;
+    color: #2C2C2C;
+  }
+
+  .page {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    padding: 40px 80px 40px 80px;
+  }
+
+  /* NAV */
+  .nav-bar {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 60px;
+  }
+
+  .logo {
+    font-size: 18px;
+    font-weight: 500;
+    letter-spacing: 3px;
+    text-transform: uppercase;
+    color: #2C2C2C;
+  }
+
+  .nav-links {
+    display: flex;
+    gap: 40px;
+    align-items: center;
+  }
+
+  .nav-links a {
+    font-size: 13px;
+    font-weight: 400;
+    color: #999;
+    text-decoration: none;
+    letter-spacing: 0.5px;
+  }
+
+  .nav-links a:hover {
+    color: #2C2C2C;
+  }
+
+  /* HERO AREA */
+  .hero-section {
+    flex: 1;
+    display: grid;
+    grid-template-columns: 440px 1fr;
+    gap: 80px;
+    align-items: center;
+  }
+
+  .hero-text {
+    display: flex;
+    flex-direction: column;
+    gap: 28px;
+  }
+
+  .headline {
+    font-size: 52px;
+    font-weight: 200;
+    line-height: 1.15;
+    letter-spacing: -1.5px;
+    color: #2C2C2C;
+  }
+
+  .headline em {
+    font-style: normal;
+    font-weight: 400;
+    color: #2C2C2C;
+    position: relative;
+  }
+  .headline em::after {
+    content: '';
+    position: absolute;
+    bottom: 4px;
+    left: 0;
+    width: 100%;
+    height: 1px;
+    background: #D4A574;
+    opacity: 0.5;
+  }
+
+  .subtitle {
+    font-size: 16px;
+    font-weight: 300;
+    line-height: 1.7;
+    color: #999;
+    max-width: 380px;
+  }
+
+  .cta-button {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    padding: 14px 32px;
+    background: #2C2C2C;
+    color: #FAFAF8;
+    font-family: 'Inter', sans-serif;
+    font-size: 13px;
+    font-weight: 400;
+    letter-spacing: 0.5px;
+    text-decoration: none;
+    border: none;
+    border-radius: 2px;
+    cursor: pointer;
+    width: fit-content;
+    transition: background 0.3s;
+  }
+
+  .cta-button:hover {
+    background: #3C3C3C;
+  }
+
+  .social-proof {
+    font-size: 12px;
+    font-weight: 400;
+    color: #bbb;
+    letter-spacing: 0.5px;
+  }
+
+  /* FEATURES — minimal */
+  .features-row {
+    display: flex;
+    gap: 48px;
+    margin-top: 8px;
+  }
+
+  .feature-item {
+    display: flex;
+    align-items: flex-start;
+    gap: 12px;
+  }
+
+  .feature-icon {
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    background: rgba(212, 165, 116, 0.12);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+    margin-top: 1px;
+  }
+
+  .feature-icon svg {
+    color: #D4A574;
+  }
+
+  .feature-label {
+    font-size: 13px;
+    font-weight: 500;
+    color: #2C2C2C;
+    margin-bottom: 2px;
+  }
+
+  .feature-desc {
+    font-size: 12px;
+    font-weight: 300;
+    color: #aaa;
+    line-height: 1.5;
+  }
+
+  /* EDITOR MOCKUP — floating card */
+  .editor-wrapper {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+  }
+
+  .editor-card {
+    width: 100%;
+    max-width: 620px;
+    height: 580px;
+    background: #FFFFFF;
+    border-radius: 2px;
+    box-shadow:
+      0 4px 6px rgba(0,0,0,0.02),
+      0 12px 28px rgba(0,0,0,0.06),
+      0 40px 80px rgba(0,0,0,0.04);
+    display: grid;
+    grid-template-columns: 1fr 190px;
+    overflow: hidden;
+  }
+
+  .editor-main {
+    padding: 32px 28px;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .editor-toolbar {
+    display: flex;
+    gap: 4px;
+    margin-bottom: 24px;
+    padding-bottom: 16px;
+    border-bottom: 1px solid #F0EDE8;
+  }
+
+  .tb-btn {
+    width: 32px;
+    height: 32px;
+    border-radius: 2px;
+    border: none;
+    background: transparent;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #bbb;
+    cursor: pointer;
+  }
+
+  .tb-btn.active {
+    background: #F5F0E8;
+    color: #D4A574;
+  }
+
+  .doc-title {
+    font-size: 24px;
+    font-weight: 500;
+    letter-spacing: -0.5px;
+    color: #2C2C2C;
+    margin-bottom: 18px;
+  }
+
+  .doc-paragraph {
+    font-size: 14px;
+    font-weight: 300;
+    line-height: 1.9;
+    color: #666;
+    margin-bottom: 16px;
+  }
+
+  .doc-paragraph .ai-enhanced {
+    background: linear-gradient(120deg, rgba(212,165,116,0.1) 0%, rgba(212,165,116,0.18) 100%);
+    border-radius: 3px;
+    padding: 1px 4px;
+  }
+
+  .doc-h2 {
+    font-size: 17px;
+    font-weight: 500;
+    color: #2C2C2C;
+    margin-bottom: 12px;
+    margin-top: 4px;
+  }
+
+  .doc-list {
+    list-style: none;
+    padding: 0;
+  }
+
+  .doc-list li {
+    font-size: 13px;
+    font-weight: 300;
+    color: #777;
+    line-height: 1.8;
+    padding-left: 16px;
+    position: relative;
+  }
+
+  .doc-list li::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 10px;
+    width: 5px;
+    height: 5px;
+    border-radius: 50%;
+    background: #D4A574;
+  }
+
+  .cursor-line {
+    display: inline-block;
+    width: 1.5px;
+    height: 15px;
+    background: #D4A574;
+    animation: pulse 1.2s ease-in-out infinite;
+    vertical-align: text-bottom;
+    margin-left: 1px;
+  }
+
+  @keyframes pulse {
+    0%, 100% { opacity: 1; }
+    50% { opacity: 0.2; }
+  }
+
+  /* AI SIDEBAR */
+  .ai-sidebar {
+    background: #FDFCFA;
+    border-left: 1px solid #F0EDE8;
+    padding: 24px 18px;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .sidebar-title {
+    font-size: 11px;
+    font-weight: 500;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #D4A574;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #F0EDE8;
+  }
+
+  .ai-card {
+    background: #fff;
+    border-radius: 2px;
+    padding: 14px;
+    border: 1px solid #F0EDE8;
+  }
+
+  .ai-card-label {
+    font-size: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    color: #bbb;
+    margin-bottom: 6px;
+  }
+
+  .ai-card-content {
+    font-size: 13px;
+    font-weight: 400;
+    color: #2C2C2C;
+  }
+
+  .voice-score {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-top: 8px;
+  }
+
+  .score-track {
+    flex: 1;
+    height: 3px;
+    background: #F0EDE8;
+    border-radius: 2px;
+    overflow: hidden;
+  }
+
+  .score-fill {
+    width: 92%;
+    height: 100%;
+    background: #D4A574;
+    border-radius: 2px;
+  }
+
+  .score-num {
+    font-size: 12px;
+    font-weight: 500;
+    color: #D4A574;
+  }
+
+  .platform-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
+    margin-top: 8px;
+  }
+
+  .p-tag {
+    font-size: 10px;
+    font-weight: 400;
+    padding: 4px 10px;
+    border-radius: 2px;
+    background: #F5F0E8;
+    color: #999;
+  }
+
+  .p-tag.active {
+    background: rgba(212,165,116,0.15);
+    color: #D4A574;
+  }
+
+  .ai-suggestion {
+    font-size: 12px;
+    font-weight: 300;
+    color: #888;
+    line-height: 1.6;
+    padding: 12px 14px;
+    background: #fff;
+    border-radius: 2px;
+    border: 1px solid #F0EDE8;
+  }
+
+  .ai-suggestion .label {
+    font-size: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.8px;
+    color: #bbb;
+    display: block;
+    margin-bottom: 6px;
+  }
+
+  .refine-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    width: 100%;
+    padding: 12px;
+    background: #2C2C2C;
+    color: #fff;
+    border: none;
+    border-radius: 2px;
+    font-family: 'Inter', sans-serif;
+    font-size: 12px;
+    font-weight: 500;
+    cursor: pointer;
+    letter-spacing: 0.5px;
+  }
+</style>
+</head>
+<body>
+<div class="page">
+  <!-- NAV -->
+  <nav class="nav-bar">
+    <div class="logo">Inkwell</div>
+    <div class="nav-links">
+      <a href="#">Features</a>
+      <a href="#">Pricing</a>
+      <a href="#">Stories</a>
+      <a href="#" class="cta-button" style="padding: 10px 24px; font-size: 12px; margin: 0;">Start Writing</a>
+    </div>
+  </nav>
+
+  <!-- HERO -->
+  <div class="hero-section">
+    <!-- LEFT: Text -->
+    <div class="hero-text">
+      <h1 class="headline">Write better,<br>faster, with<br><em>your own voice</em></h1>
+      <p class="subtitle">AI that learns your style, not replaces it. Publish across WeChat, Xiaohongshu, and video scripts while sounding unmistakably you.</p>
+
+      <div class="features-row">
+        <div class="feature-item">
+          <div class="feature-icon">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
+          </div>
+          <div>
+            <div class="feature-label">Style Learning</div>
+            <div class="feature-desc">Adapts to your voice</div>
+          </div>
+        </div>
+        <div class="feature-item">
+          <div class="feature-icon">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
+          </div>
+          <div>
+            <div class="feature-label">Multi-Platform</div>
+            <div class="feature-desc">One tool, every format</div>
+          </div>
+        </div>
+        <div class="feature-item">
+          <div class="feature-icon">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
+          </div>
+          <div>
+            <div class="feature-label">Human Touch</div>
+            <div class="feature-desc">Warmth-preserving edit</div>
+          </div>
+        </div>
+      </div>
+
+      <div style="display: flex; align-items: center; gap: 24px;">
+        <a href="#" class="cta-button">
+          Start Writing
+          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
+        </a>
+        <span class="social-proof">Trusted by 10,000+ creators</span>
+      </div>
+    </div>
+
+    <!-- RIGHT: Editor Card -->
+    <div class="editor-wrapper">
+      <div class="editor-card">
+        <div class="editor-main">
+          <div class="editor-toolbar">
+            <button class="tb-btn active"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg></button>
+            <button class="tb-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg></button>
+            <button class="tb-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="21" y1="6" x2="3" y2="6"/><line x1="15" y1="12" x2="3" y2="12"/><line x1="17" y1="18" x2="3" y2="18"/></svg></button>
+            <button class="tb-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></button>
+          </div>
+
+          <div class="doc-title">Morning Routines for Creative Minds</div>
+          <div class="doc-paragraph">
+            The best ideas rarely arrive on schedule. They come in the quiet space between waking and doing — <span class="ai-enhanced">that liminal moment when the mind is loose enough to wander but awake enough to notice</span>.
+          </div>
+          <div class="doc-h2">Finding Your Rhythm</div>
+          <div class="doc-paragraph">
+            Productivity culture tells us to optimize every hour. But creation is not production. The most prolific writers I know guard their mornings like sacred ground.<span class="cursor-line"></span>
+          </div>
+          <ul class="doc-list">
+            <li>Start before checking your phone</li>
+            <li>Write the ugly first draft freely</li>
+            <li>Let AI handle polish, not direction</li>
+          </ul>
+        </div>
+
+        <div class="ai-sidebar">
+          <div class="sidebar-title">Inkwell AI</div>
+
+          <div class="ai-card">
+            <div class="ai-card-label">Voice Match</div>
+            <div class="ai-card-content">Your Style</div>
+            <div class="voice-score">
+              <div class="score-track"><div class="score-fill"></div></div>
+              <div class="score-num">92%</div>
+            </div>
+          </div>
+
+          <div class="ai-card">
+            <div class="ai-card-label">Publishing To</div>
+            <div class="ai-card-content">WeChat Article</div>
+            <div class="platform-tags">
+              <span class="p-tag active">WeChat</span>
+              <span class="p-tag">XHS</span>
+              <span class="p-tag">Script</span>
+            </div>
+          </div>
+
+          <div class="ai-suggestion">
+            <span class="label">Suggestion</span>
+            The second paragraph is beautiful. Consider adding a concrete personal example to ground the abstract idea.
+          </div>
+
+          <button class="refine-btn">
+            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v18"/><path d="M3 12h18"/></svg>
+            Refine with AI
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+</body>
+</html>

BIN
assets/showcases/website-ai-writing/aiwriting-build.png


+ 548 - 0
assets/showcases/website-ai-writing/aiwriting-pentagram.html

@@ -0,0 +1,548 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Inkwell — AI Writing Assistant</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    background: #FFFFFF;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    color: #111111;
+    position: relative;
+  }
+
+  /* Grid overlay for Swiss design feel */
+  body::before {
+    content: '';
+    position: absolute;
+    top: 0; left: 0; right: 0; bottom: 0;
+    background:
+      repeating-linear-gradient(90deg, transparent, transparent 119px, rgba(0,0,0,0.03) 119px, rgba(0,0,0,0.03) 120px),
+      repeating-linear-gradient(0deg, transparent, transparent 59px, rgba(0,0,0,0.02) 59px, rgba(0,0,0,0.02) 60px);
+    pointer-events: none;
+    z-index: 0;
+  }
+
+  .container {
+    position: relative;
+    z-index: 1;
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    height: 100%;
+    padding: 0;
+  }
+
+  /* LEFT PANEL */
+  .left-panel {
+    padding: 60px 60px 48px 80px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    border-right: 2px solid #111;
+  }
+
+  .top-bar {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .logo {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 20px;
+    font-weight: 700;
+    letter-spacing: -0.5px;
+    text-transform: uppercase;
+  }
+
+  .logo span {
+    color: #E63946;
+  }
+
+  .nav {
+    display: flex;
+    gap: 28px;
+    font-size: 13px;
+    font-weight: 500;
+    letter-spacing: 0.5px;
+    text-transform: uppercase;
+  }
+
+  .nav a {
+    color: #111;
+    text-decoration: none;
+  }
+
+  .hero-content {
+    margin-top: -20px;
+  }
+
+  .headline {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 86px;
+    font-weight: 700;
+    line-height: 0.95;
+    letter-spacing: -4px;
+    margin-bottom: 28px;
+  }
+
+  .headline em {
+    font-style: italic;
+    color: #E63946;
+  }
+
+  .subtitle {
+    font-size: 18px;
+    font-weight: 400;
+    color: #555;
+    line-height: 1.5;
+    max-width: 420px;
+    margin-bottom: 36px;
+  }
+
+  .cta-row {
+    display: flex;
+    align-items: center;
+    gap: 24px;
+  }
+
+  .cta-button {
+    display: inline-flex;
+    align-items: center;
+    gap: 10px;
+    padding: 16px 36px;
+    background: #E63946;
+    color: #fff;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 15px;
+    font-weight: 600;
+    letter-spacing: 0.5px;
+    text-transform: uppercase;
+    text-decoration: none;
+    border: none;
+    cursor: pointer;
+  }
+
+  .social-proof {
+    font-size: 13px;
+    color: #888;
+    letter-spacing: 0.3px;
+  }
+
+  .social-proof strong {
+    color: #111;
+    font-weight: 600;
+  }
+
+  /* FEATURES — strict 3 col */
+  .features-grid {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 0;
+    border-top: 2px solid #111;
+  }
+
+  .feature-item {
+    padding: 20px 0;
+    border-right: 1px solid #ddd;
+  }
+
+  .feature-item:last-child {
+    border-right: none;
+  }
+
+  .feature-item:first-child {
+    padding-right: 16px;
+  }
+
+  .feature-item:nth-child(2) {
+    padding: 20px 16px;
+  }
+
+  .feature-item:last-child {
+    padding-left: 16px;
+  }
+
+  .feature-number {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    font-weight: 700;
+    color: #E63946;
+    letter-spacing: 1px;
+    margin-bottom: 8px;
+  }
+
+  .feature-title {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 14px;
+    font-weight: 700;
+    letter-spacing: -0.3px;
+    margin-bottom: 4px;
+    text-transform: uppercase;
+  }
+
+  .feature-desc {
+    font-size: 12px;
+    color: #777;
+    line-height: 1.5;
+  }
+
+  /* RIGHT PANEL — Editor mockup as wireframe */
+  .right-panel {
+    background: #F7F7F7;
+    padding: 48px 60px 48px 48px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+  }
+
+  .editor-mockup {
+    width: 100%;
+    max-width: 580px;
+    height: 680px;
+    background: #fff;
+    border: 2px solid #111;
+    display: grid;
+    grid-template-columns: 1fr 200px;
+    position: relative;
+  }
+
+  /* Grid reference lines on mockup */
+  .editor-mockup::before {
+    content: '';
+    position: absolute;
+    top: 0; left: 0; right: 0; bottom: 0;
+    background:
+      repeating-linear-gradient(0deg, transparent, transparent 39px, rgba(0,0,0,0.03) 39px, rgba(0,0,0,0.03) 40px);
+    pointer-events: none;
+  }
+
+  .editor-main {
+    padding: 28px 24px;
+    border-right: 2px solid #111;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .editor-toolbar {
+    display: flex;
+    gap: 6px;
+    padding-bottom: 16px;
+    border-bottom: 1px solid #ddd;
+    margin-bottom: 20px;
+  }
+
+  .toolbar-btn {
+    width: 28px;
+    height: 28px;
+    border: 1px solid #ccc;
+    background: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .toolbar-btn.active {
+    background: #111;
+    border-color: #111;
+    color: #fff;
+  }
+
+  .editor-title-line {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 22px;
+    font-weight: 700;
+    letter-spacing: -0.5px;
+    margin-bottom: 16px;
+    color: #111;
+  }
+
+  .editor-text-block {
+    font-size: 13px;
+    line-height: 1.8;
+    color: #444;
+    margin-bottom: 14px;
+  }
+
+  .editor-text-block .highlight {
+    background: rgba(230, 57, 70, 0.12);
+    border-bottom: 2px solid #E63946;
+    padding: 0 2px;
+  }
+
+  .editor-h2 {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 16px;
+    font-weight: 700;
+    margin-bottom: 10px;
+    margin-top: 6px;
+    color: #111;
+  }
+
+  .editor-list {
+    font-size: 13px;
+    line-height: 2;
+    color: #555;
+    padding-left: 18px;
+  }
+
+  .editor-cursor {
+    display: inline-block;
+    width: 2px;
+    height: 16px;
+    background: #E63946;
+    animation: blink 1s step-end infinite;
+    vertical-align: text-bottom;
+    margin-left: 2px;
+  }
+
+  @keyframes blink {
+    50% { opacity: 0; }
+  }
+
+  /* AI SIDEBAR */
+  .ai-sidebar {
+    padding: 20px 16px;
+    background: #FAFAFA;
+    display: flex;
+    flex-direction: column;
+    gap: 14px;
+  }
+
+  .sidebar-header {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    font-weight: 700;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    color: #E63946;
+    padding-bottom: 10px;
+    border-bottom: 2px solid #111;
+  }
+
+  .sidebar-card {
+    padding: 12px;
+    border: 1px solid #ddd;
+    background: #fff;
+  }
+
+  .sidebar-card-label {
+    font-size: 10px;
+    font-weight: 700;
+    text-transform: uppercase;
+    letter-spacing: 0.8px;
+    color: #999;
+    margin-bottom: 6px;
+  }
+
+  .sidebar-card-value {
+    font-size: 13px;
+    font-weight: 500;
+    color: #111;
+    line-height: 1.4;
+  }
+
+  .style-meter {
+    display: flex;
+    gap: 3px;
+    margin-top: 8px;
+  }
+
+  .meter-bar {
+    height: 4px;
+    flex: 1;
+    background: #E0E0E0;
+  }
+
+  .meter-bar.filled {
+    background: #E63946;
+  }
+
+  .sidebar-suggestion {
+    padding: 10px 12px;
+    background: #fff;
+    border: 1px solid #ddd;
+    font-size: 12px;
+    color: #555;
+    line-height: 1.5;
+  }
+
+  .sidebar-suggestion strong {
+    color: #111;
+    display: block;
+    font-size: 10px;
+    text-transform: uppercase;
+    letter-spacing: 0.8px;
+    margin-bottom: 4px;
+  }
+
+  .sidebar-action {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 10px 12px;
+    background: #111;
+    color: #fff;
+    font-size: 11px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    cursor: pointer;
+    border: none;
+    justify-content: center;
+  }
+
+  .tag-row {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px;
+    margin-top: 6px;
+  }
+
+  .tag {
+    font-size: 10px;
+    padding: 2px 8px;
+    border: 1px solid #ccc;
+    color: #666;
+    letter-spacing: 0.3px;
+  }
+
+  /* Corner mark */
+  .right-panel::after {
+    content: 'INKWELL V1.0';
+    position: absolute;
+    bottom: 20px;
+    right: 24px;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 10px;
+    letter-spacing: 2px;
+    color: #bbb;
+    text-transform: uppercase;
+  }
+</style>
+</head>
+<body>
+<div class="container">
+  <!-- LEFT PANEL -->
+  <div class="left-panel">
+    <div class="top-bar">
+      <div class="logo">INK<span>WELL</span></div>
+      <nav class="nav">
+        <a href="#">Features</a>
+        <a href="#">Pricing</a>
+        <a href="#">Blog</a>
+      </nav>
+    </div>
+
+    <div class="hero-content">
+      <h1 class="headline">Write<br>better,<br>faster,<br>with <em>your</em><br>own voice.</h1>
+      <p class="subtitle">AI that learns your style, not replaces it. Craft content for WeChat, Xiaohongshu, and video scripts — all in your authentic tone.</p>
+      <div class="cta-row">
+        <a href="#" class="cta-button">
+          Start Writing
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
+        </a>
+        <span class="social-proof">Trusted by <strong>10,000+</strong> creators</span>
+      </div>
+    </div>
+
+    <div class="features-grid">
+      <div class="feature-item">
+        <div class="feature-number">01</div>
+        <div class="feature-title">Style Learning</div>
+        <div class="feature-desc">Adapts to your unique voice through continuous analysis of your writing patterns.</div>
+      </div>
+      <div class="feature-item">
+        <div class="feature-number">02</div>
+        <div class="feature-title">Multi-Platform</div>
+        <div class="feature-desc">WeChat articles, Xiaohongshu posts, video scripts. One tool, every format.</div>
+      </div>
+      <div class="feature-item">
+        <div class="feature-number">03</div>
+        <div class="feature-title">Human-Touch</div>
+        <div class="feature-desc">Proofreading that preserves warmth and removes robotic phrasing.</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- RIGHT PANEL — Editor Mockup -->
+  <div class="right-panel">
+    <div class="editor-mockup">
+      <div class="editor-main">
+        <div class="editor-toolbar">
+          <div class="toolbar-btn active"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg></div>
+          <div class="toolbar-btn"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg></div>
+          <div class="toolbar-btn"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="21" y1="6" x2="3" y2="6"/><line x1="15" y1="12" x2="3" y2="12"/><line x1="17" y1="18" x2="3" y2="18"/></svg></div>
+          <div class="toolbar-btn"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg></div>
+        </div>
+
+        <div class="editor-title-line">The Future of Content Creation</div>
+        <div class="editor-text-block">
+          Every creator faces the same tension: the desire to produce more content versus the need to maintain quality and authenticity. <span class="highlight">AI doesn't have to mean losing your voice</span> — it can mean amplifying it.
+        </div>
+        <div class="editor-h2">Why Authenticity Matters</div>
+        <div class="editor-text-block">
+          Readers can tell. They feel the difference between words that carry genuine experience and words assembled by algorithm. The goal isn't to write <em>more</em> — it's to write more of what only you can write.<span class="editor-cursor"></span>
+        </div>
+        <div class="editor-h2">Key Principles</div>
+        <ol class="editor-list">
+          <li>Write from personal experience first</li>
+          <li>Use AI for refinement, not replacement</li>
+          <li>Adapt tone for each platform</li>
+        </ol>
+      </div>
+
+      <div class="ai-sidebar">
+        <div class="sidebar-header">AI Assistant</div>
+
+        <div class="sidebar-card">
+          <div class="sidebar-card-label">Style Match</div>
+          <div class="sidebar-card-value">92% Voice Fidelity</div>
+          <div class="style-meter">
+            <div class="meter-bar filled"></div>
+            <div class="meter-bar filled"></div>
+            <div class="meter-bar filled"></div>
+            <div class="meter-bar filled"></div>
+            <div class="meter-bar"></div>
+          </div>
+        </div>
+
+        <div class="sidebar-card">
+          <div class="sidebar-card-label">Target</div>
+          <div class="sidebar-card-value">WeChat Article</div>
+          <div class="tag-row">
+            <span class="tag">WeChat</span>
+            <span class="tag">XHS</span>
+            <span class="tag">Script</span>
+          </div>
+        </div>
+
+        <div class="sidebar-suggestion">
+          <strong>Suggestion</strong>
+          Consider opening with a specific anecdote to strengthen the personal connection.
+        </div>
+
+        <div class="sidebar-suggestion">
+          <strong>Tone Check</strong>
+          Paragraph 2 reads slightly formal. Soften with a conversational phrase.
+        </div>
+
+        <button class="sidebar-action">
+          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
+          Refine Selection
+        </button>
+      </div>
+    </div>
+  </div>
+</div>
+</body>
+</html>

BIN
assets/showcases/website-ai-writing/aiwriting-pentagram.png


+ 696 - 0
assets/showcases/website-ai-writing/aiwriting-takram.html

@@ -0,0 +1,696 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Inkwell — AI Writing Assistant</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&family=Noto+Serif+SC:wght@400;500;600;700&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    background: #F5F0EB;
+    font-family: 'Inter', sans-serif;
+    color: #3D3D3D;
+  }
+
+  .page {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+  }
+
+  /* NAV */
+  .nav-bar {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 28px 64px;
+  }
+
+  .logo {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 20px;
+    font-weight: 600;
+    color: #5C5347;
+    letter-spacing: 1px;
+  }
+
+  .nav-right {
+    display: flex;
+    align-items: center;
+    gap: 36px;
+  }
+
+  .nav-right a {
+    font-size: 13px;
+    font-weight: 400;
+    color: #8A8278;
+    text-decoration: none;
+    letter-spacing: 0.3px;
+  }
+
+  .nav-cta {
+    padding: 10px 24px;
+    background: transparent;
+    color: #2D3436;
+    border: 1px solid rgba(45, 52, 54, 0.2);
+    border-radius: 24px;
+    font-size: 13px;
+    font-weight: 500;
+    text-decoration: none;
+    letter-spacing: 0.3px;
+  }
+
+  /* MAIN CONTENT */
+  .main-content {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    padding: 0 64px;
+    gap: 36px;
+  }
+
+  /* HERO ROW */
+  .hero-row {
+    display: grid;
+    grid-template-columns: 480px 1fr;
+    gap: 56px;
+    align-items: start;
+    padding-top: 16px;
+  }
+
+  .hero-text {
+    padding-top: 20px;
+  }
+
+  .headline {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 44px;
+    font-weight: 600;
+    line-height: 1.35;
+    letter-spacing: -0.5px;
+    color: #3D3D3D;
+    margin-bottom: 20px;
+  }
+
+  .headline .accent {
+    color: #6B8F71;
+  }
+
+  .subtitle {
+    font-size: 16px;
+    font-weight: 300;
+    line-height: 1.8;
+    color: #8A8278;
+    max-width: 420px;
+    margin-bottom: 28px;
+  }
+
+  .cta-area {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    margin-bottom: 16px;
+  }
+
+  .cta-button {
+    display: inline-flex;
+    align-items: center;
+    gap: 10px;
+    padding: 14px 32px;
+    background: #2D3436;
+    color: #F5F0EB;
+    font-family: 'Inter', sans-serif;
+    font-size: 14px;
+    font-weight: 500;
+    letter-spacing: 0.5px;
+    text-decoration: none;
+    border: none;
+    border-radius: 32px;
+    cursor: pointer;
+  }
+
+  .social-proof {
+    font-size: 12px;
+    font-weight: 400;
+    color: #B5AD9E;
+    letter-spacing: 0.3px;
+  }
+
+  /* EDITOR MOCKUP — organic rounded */
+  .editor-container {
+    position: relative;
+  }
+
+  .editor-card {
+    width: 100%;
+    max-width: 720px;
+    height: 460px;
+    background: #FDFCF9;
+    border-radius: 24px;
+    box-shadow:
+      0 2px 8px rgba(92,83,71,0.04),
+      0 8px 24px rgba(92,83,71,0.06),
+      0 24px 48px rgba(92,83,71,0.03);
+    display: grid;
+    grid-template-columns: 1fr 200px;
+    overflow: hidden;
+  }
+
+  .editor-body {
+    padding: 28px 28px;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .editor-toolbar {
+    display: flex;
+    gap: 6px;
+    margin-bottom: 20px;
+    padding-bottom: 14px;
+    border-bottom: 1px solid #EDE8DF;
+  }
+
+  .e-btn {
+    width: 30px;
+    height: 30px;
+    border-radius: 10px;
+    border: none;
+    background: transparent;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #C4BDB2;
+    cursor: pointer;
+  }
+
+  .e-btn.active {
+    background: #EDE8DF;
+    color: #5C5347;
+  }
+
+  .e-title {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 20px;
+    font-weight: 600;
+    color: #3D3D3D;
+    margin-bottom: 14px;
+  }
+
+  .e-text {
+    font-size: 13.5px;
+    font-weight: 300;
+    line-height: 1.9;
+    color: #6B6560;
+    margin-bottom: 12px;
+  }
+
+  .e-text .enhanced {
+    background: rgba(107,143,113,0.2);
+    border-radius: 4px;
+    padding: 1px 4px;
+  }
+
+  .e-h2 {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 16px;
+    font-weight: 500;
+    color: #3D3D3D;
+    margin-bottom: 10px;
+    margin-top: 2px;
+  }
+
+  .e-list {
+    list-style: none;
+    padding: 0;
+  }
+
+  .e-list li {
+    font-size: 13px;
+    font-weight: 300;
+    color: #8A8278;
+    line-height: 1.9;
+    padding-left: 18px;
+    position: relative;
+  }
+
+  .e-list li::before {
+    content: '';
+    position: absolute;
+    left: 2px;
+    top: 10px;
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    border: 1.5px solid #6B8F71;
+  }
+
+  .typing-cursor {
+    display: inline-block;
+    width: 1.5px;
+    height: 14px;
+    background: #6B8F71;
+    animation: softblink 1.5s ease-in-out infinite;
+    vertical-align: text-bottom;
+    margin-left: 2px;
+  }
+
+  @keyframes softblink {
+    0%, 100% { opacity: 0.8; }
+    50% { opacity: 0.15; }
+  }
+
+  /* AI Sidebar */
+  .ai-panel {
+    background: #F8F5EF;
+    border-left: 1px solid #EDE8DF;
+    padding: 22px 16px;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .panel-header {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #EDE8DF;
+  }
+
+  .panel-header svg {
+    color: #6B8F71;
+  }
+
+  .panel-header span {
+    font-size: 12px;
+    font-weight: 500;
+    color: #5C5347;
+    letter-spacing: 0.5px;
+  }
+
+  .panel-card {
+    background: #FDFCF9;
+    border-radius: 14px;
+    padding: 14px;
+    border: 1px solid #EDE8DF;
+  }
+
+  .panel-card-label {
+    font-size: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.8px;
+    color: #B5AD9E;
+    margin-bottom: 6px;
+  }
+
+  .panel-card-value {
+    font-size: 13px;
+    font-weight: 500;
+    color: #3D3D3D;
+  }
+
+  .voice-bar {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-top: 8px;
+  }
+
+  .vb-track {
+    flex: 1;
+    height: 4px;
+    background: #EDE8DF;
+    border-radius: 4px;
+    overflow: hidden;
+  }
+
+  .vb-fill {
+    width: 92%;
+    height: 100%;
+    background: linear-gradient(90deg, #6B8F71, #C4D1BC);
+    border-radius: 4px;
+  }
+
+  .vb-label {
+    font-size: 11px;
+    font-weight: 500;
+    color: #6B8F71;
+  }
+
+  .platform-pills {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
+    margin-top: 6px;
+  }
+
+  .pill {
+    font-size: 10px;
+    padding: 4px 10px;
+    border-radius: 16px;
+    background: #EDE8DF;
+    color: #8A8278;
+  }
+
+  .pill.active {
+    background: rgba(107,143,113,0.25);
+    color: #5C5347;
+  }
+
+  .panel-note {
+    font-size: 11.5px;
+    font-weight: 300;
+    color: #8A8278;
+    line-height: 1.6;
+    padding: 12px;
+    background: #FDFCF9;
+    border-radius: 14px;
+    border: 1px solid #EDE8DF;
+  }
+
+  .panel-note .note-label {
+    font-size: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.8px;
+    color: #B5AD9E;
+    display: block;
+    margin-bottom: 4px;
+  }
+
+  /* FLOW DIAGRAM */
+  .flow-section {
+    padding: 0 0 0 0;
+  }
+
+  .flow-bar {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 0;
+    padding: 20px 0;
+  }
+
+  .flow-step {
+    display: flex;
+    align-items: center;
+    gap: 14px;
+    padding: 14px 28px;
+    background: #FDFCF9;
+    border-radius: 18px;
+    border: 1px solid #EDE8DF;
+    box-shadow: 0 2px 8px rgba(92,83,71,0.03);
+  }
+
+  .flow-step-icon {
+    width: 38px;
+    height: 38px;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+  }
+
+  .flow-step-icon.ideas {
+    background: rgba(107,143,113,0.2);
+    color: #6B8F71;
+  }
+
+  .flow-step-icon.ai {
+    background: rgba(212,187,156,0.25);
+    color: #C4A87A;
+  }
+
+  .flow-step-icon.voice {
+    background: rgba(92,83,71,0.1);
+    color: #5C5347;
+  }
+
+  .flow-step-text {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .flow-step-label {
+    font-size: 14px;
+    font-weight: 500;
+    color: #3D3D3D;
+    margin-bottom: 2px;
+  }
+
+  .flow-step-desc {
+    font-size: 11px;
+    font-weight: 300;
+    color: #B5AD9E;
+  }
+
+  .flow-arrow {
+    display: flex;
+    align-items: center;
+    padding: 0 20px;
+    color: #C4BDB2;
+  }
+
+  .flow-arrow svg {
+    opacity: 0.6;
+  }
+
+  /* FEATURES ROW */
+  .features-strip {
+    display: flex;
+    justify-content: center;
+    gap: 56px;
+    padding: 12px 0 20px 0;
+  }
+
+  .feat {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
+
+  .feat-icon {
+    width: 40px;
+    height: 40px;
+    border-radius: 12px;
+    background: rgba(107,143,113,0.15);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #6B8F71;
+    flex-shrink: 0;
+  }
+
+  .feat-text {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .feat-name {
+    font-size: 13px;
+    font-weight: 500;
+    color: #3D3D3D;
+    margin-bottom: 1px;
+  }
+
+  .feat-sub {
+    font-size: 11px;
+    font-weight: 300;
+    color: #B5AD9E;
+  }
+
+  .divider-dot {
+    width: 4px;
+    height: 4px;
+    border-radius: 50%;
+    background: #D5CFC5;
+    align-self: center;
+  }
+</style>
+</head>
+<body>
+<div class="page">
+  <!-- NAV -->
+  <nav class="nav-bar">
+    <div class="logo">Inkwell</div>
+    <div class="nav-right">
+      <a href="#">Philosophy</a>
+      <a href="#">Features</a>
+      <a href="#">Stories</a>
+      <a href="#" class="nav-cta">Start Writing</a>
+    </div>
+  </nav>
+
+  <!-- MAIN -->
+  <div class="main-content">
+    <!-- HERO -->
+    <div class="hero-row">
+      <div class="hero-text">
+        <h1 class="headline">Write better, faster,<br>with <span class="accent">your own</span> voice</h1>
+        <p class="subtitle">AI that learns your style, not replaces it. A mindful writing companion for WeChat, Xiaohongshu, and video scripts that honours your creative instincts.</p>
+        <div class="cta-area">
+          <a href="#" class="cta-button">
+            Start Writing
+            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
+          </a>
+          <span class="social-proof">Trusted by 10,000+ creators</span>
+        </div>
+      </div>
+
+      <!-- EDITOR CARD -->
+      <div class="editor-container">
+        <div class="editor-card">
+          <div class="editor-body">
+            <div class="editor-toolbar">
+              <button class="e-btn active"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg></button>
+              <button class="e-btn"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg></button>
+              <button class="e-btn"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="21" y1="6" x2="3" y2="6"/><line x1="15" y1="12" x2="3" y2="12"/><line x1="17" y1="18" x2="3" y2="18"/></svg></button>
+              <button class="e-btn"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></button>
+            </div>
+
+            <div class="e-title">On the Patience of Growing Things</div>
+            <div class="e-text">
+              There is a kind of writing that happens slowly, like roots in winter soil. <span class="enhanced">You cannot rush a sentence into meaning any more than you can rush a seed into bloom</span>. The best words arrive when you stop chasing them.
+            </div>
+            <div class="e-h2">Listening to the Draft</div>
+            <div class="e-text">
+              Every draft speaks, if you give it room. The first version is never wrong — it is simply unfinished. What AI can offer is not replacement, but reflection: a gentle mirror held up to your own intentions.<span class="typing-cursor"></span>
+            </div>
+            <ul class="e-list">
+              <li>Trust the messy first draft</li>
+              <li>Let AI reveal patterns you missed</li>
+              <li>Preserve what makes it yours</li>
+            </ul>
+          </div>
+
+          <div class="ai-panel">
+            <div class="panel-header">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
+              <span>Inkwell AI</span>
+            </div>
+
+            <div class="panel-card">
+              <div class="panel-card-label">Voice Match</div>
+              <div class="panel-card-value">Your Style</div>
+              <div class="voice-bar">
+                <div class="vb-track"><div class="vb-fill"></div></div>
+                <div class="vb-label">92%</div>
+              </div>
+            </div>
+
+            <div class="panel-card">
+              <div class="panel-card-label">Platform</div>
+              <div class="panel-card-value">WeChat</div>
+              <div class="platform-pills">
+                <span class="pill active">WeChat</span>
+                <span class="pill">XHS</span>
+                <span class="pill">Script</span>
+              </div>
+            </div>
+
+            <div class="panel-note">
+              <span class="note-label">Observation</span>
+              The seed metaphor in paragraph one is lovely. Consider echoing it in the closing line for a sense of return.
+            </div>
+
+            <div class="panel-note">
+              <span class="note-label">Tone</span>
+              Gentle, contemplative. This reads naturally as you.
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- FLOW DIAGRAM -->
+    <div class="flow-section">
+      <div class="flow-bar">
+        <div class="flow-step">
+          <div class="flow-step-icon ideas">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 1 1 7.072 0l-.548.547A3.374 3.374 0 0 0 12 18.469c-1.006 0-1.938-.429-2.577-1.177l-.56-.56z"/></svg>
+          </div>
+          <div class="flow-step-text">
+            <div class="flow-step-label">Your Ideas</div>
+            <div class="flow-step-desc">Raw thoughts and drafts</div>
+          </div>
+        </div>
+
+        <div class="flow-arrow">
+          <svg width="32" height="16" viewBox="0 0 32 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="2" y1="8" x2="28" y2="8"/><polyline points="24 4 28 8 24 12"/></svg>
+        </div>
+
+        <div class="flow-step">
+          <div class="flow-step-icon ai">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
+          </div>
+          <div class="flow-step-text">
+            <div class="flow-step-label">AI Enhancement</div>
+            <div class="flow-step-desc">Refine, not rewrite</div>
+          </div>
+        </div>
+
+        <div class="flow-arrow">
+          <svg width="32" height="16" viewBox="0 0 32 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="2" y1="8" x2="28" y2="8"/><polyline points="24 4 28 8 24 12"/></svg>
+        </div>
+
+        <div class="flow-step">
+          <div class="flow-step-icon voice">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
+          </div>
+          <div class="flow-step-text">
+            <div class="flow-step-label">Your Voice</div>
+            <div class="flow-step-desc">Unmistakably you</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- FEATURES STRIP -->
+    <div class="features-strip">
+      <div class="feat">
+        <div class="feat-icon">
+          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
+        </div>
+        <div class="feat-text">
+          <div class="feat-name">Style Learning</div>
+          <div class="feat-sub">Evolves with your writing</div>
+        </div>
+      </div>
+
+      <div class="divider-dot"></div>
+
+      <div class="feat">
+        <div class="feat-icon">
+          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
+        </div>
+        <div class="feat-text">
+          <div class="feat-name">Multi-Platform</div>
+          <div class="feat-sub">WeChat, XHS, video scripts</div>
+        </div>
+      </div>
+
+      <div class="divider-dot"></div>
+
+      <div class="feat">
+        <div class="feat-icon">
+          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
+        </div>
+        <div class="feat-text">
+          <div class="feat-name">Human-Touch Proofreading</div>
+          <div class="feat-sub">Warmth-preserving refinement</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+</body>
+</html>

BIN
assets/showcases/website-ai-writing/aiwriting-takram.png


+ 372 - 0
assets/showcases/website-devdocs/devdocs-build.html

@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Nexus API Documentation</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Inter', sans-serif;
+    background: #FAFAF8;
+    color: #2C2C2C;
+  }
+
+  /* Navigation */
+  nav {
+    height: 64px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    position: relative;
+  }
+  .nav-logo {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
+  .nav-logo-icon {
+    width: 32px;
+    height: 32px;
+    border-radius: 2px;
+    background: #E8E4DF;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .nav-logo-icon i { color: #D4A574; }
+  .nav-logo span {
+    font-size: 17px;
+    font-weight: 500;
+    letter-spacing: -0.3px;
+    color: #1A1A1A;
+  }
+  .nav-center {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    display: flex;
+    gap: 32px;
+  }
+  .nav-center a {
+    font-size: 13px;
+    font-weight: 400;
+    color: #999;
+    text-decoration: none;
+    letter-spacing: 0.3px;
+    transition: color 0.2s;
+  }
+  .nav-center a:hover { color: #2C2C2C; }
+  .nav-center a.active { color: #2C2C2C; font-weight: 500; }
+  .nav-right {
+    display: flex;
+    align-items: center;
+    gap: 24px;
+  }
+  .nav-right a {
+    font-size: 13px;
+    color: #BBB;
+    text-decoration: none;
+    transition: color 0.2s;
+  }
+  .nav-right a:hover { color: #2C2C2C; }
+  .status-pill {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 5px 12px;
+    border-radius: 2px;
+    background: rgba(212, 165, 116, 0.08);
+    font-size: 11px;
+    color: #B0ACA4;
+    font-weight: 400;
+  }
+  .status-pill .dot {
+    width: 6px;
+    height: 6px;
+    background: #D4A574;
+    border-radius: 50%;
+  }
+
+  /* Hero section */
+  .hero {
+    text-align: center;
+    padding: 64px 80px 48px;
+  }
+  .hero-eyebrow {
+    font-size: 12px;
+    font-weight: 400;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    color: #B0ACA4;
+    margin-bottom: 24px;
+  }
+  .hero h1 {
+    font-size: 56px;
+    font-weight: 200;
+    letter-spacing: -2.5px;
+    line-height: 1.1;
+    color: #1A1A1A;
+    margin-bottom: 16px;
+  }
+  .hero h1 em {
+    font-style: normal;
+    font-weight: 500;
+  }
+  .hero p {
+    font-size: 17px;
+    font-weight: 300;
+    color: #999;
+    line-height: 1.6;
+    max-width: 520px;
+    margin: 0 auto 36px;
+    letter-spacing: 0.1px;
+  }
+  .hero-actions {
+    display: flex;
+    justify-content: center;
+    gap: 16px;
+    margin-bottom: 0;
+  }
+  .hero-actions a {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 28px;
+    font-size: 13px;
+    font-weight: 400;
+    text-decoration: none;
+    border-radius: 2px;
+    transition: all 0.2s;
+    letter-spacing: 0.2px;
+  }
+  .btn-primary {
+    background: #1A1A1A;
+    color: #FAFAF8;
+  }
+  .btn-primary:hover { background: #333; }
+  .btn-secondary {
+    background: transparent;
+    color: #999;
+    border: 1px solid #E0DDD8;
+  }
+  .btn-secondary:hover { border-color: #CCC; color: #666; }
+
+  /* Code card */
+  .code-section {
+    display: flex;
+    justify-content: center;
+    padding: 32px 80px 48px;
+  }
+  .code-card {
+    background: #FFFFFF;
+    border-radius: 2px;
+    box-shadow: 0 8px 40px rgba(0,0,0,0.04), 0 1px 3px rgba(0,0,0,0.02);
+    max-width: 600px;
+    width: 100%;
+    overflow: hidden;
+  }
+  .code-card-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 14px 24px;
+    border-bottom: 1px solid #F2F0EC;
+  }
+  .code-card-header .dots {
+    display: flex;
+    gap: 7px;
+  }
+  .code-card-header .dots span {
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+    background: #E8E5E0;
+  }
+  .code-card-header .filename {
+    font-family: 'JetBrains Mono', monospace;
+    font-size: 11px;
+    color: #BBB;
+    font-weight: 400;
+  }
+  .code-card-header .copy-btn {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    background: none;
+    border: none;
+    cursor: pointer;
+    color: #CCC;
+    font-size: 11px;
+    font-family: 'Inter', sans-serif;
+  }
+  .code-card-body {
+    padding: 24px 28px;
+    font-family: 'JetBrains Mono', monospace;
+    font-size: 13px;
+    line-height: 1.8;
+    color: #444;
+    font-weight: 400;
+  }
+  .code-card-body .kw { color: #8B7355; font-weight: 500; }
+  .code-card-body .str { color: #D4A574; }
+  .code-card-body .cmt { color: #CCCCCC; }
+  .code-card-body .fn { color: #777; }
+  .code-card-body .num { color: #B08D57; }
+
+  /* Quick links */
+  .quick-links {
+    display: flex;
+    justify-content: center;
+    gap: 48px;
+    padding: 16px 80px 48px;
+  }
+  .quick-link {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    text-decoration: none;
+    color: #BBB;
+    font-size: 13px;
+    font-weight: 400;
+    transition: color 0.2s;
+    letter-spacing: 0.2px;
+  }
+  .quick-link:hover { color: #666; }
+  .quick-link i { color: #D4A574; opacity: 0.6; }
+
+  /* Features */
+  .features {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 24px;
+    padding: 0 80px;
+    max-width: 1100px;
+    margin: 0 auto;
+  }
+  .feature-card {
+    background: #FFFFFF;
+    border-radius: 2px;
+    padding: 32px 28px;
+    box-shadow: 0 2px 16px rgba(0,0,0,0.02);
+    transition: box-shadow 0.2s;
+  }
+  .feature-card:hover {
+    box-shadow: 0 2px 16px rgba(0,0,0,0.04);
+  }
+  .feature-icon-wrap {
+    width: 40px;
+    height: 40px;
+    border-radius: 2px;
+    background: #F0EBE3;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 20px;
+  }
+  .feature-icon-wrap i { color: #D4A574; }
+  .feature-card h3 {
+    font-size: 16px;
+    font-weight: 500;
+    letter-spacing: -0.3px;
+    margin-bottom: 8px;
+    color: #1A1A1A;
+  }
+  .feature-card p {
+    font-size: 13px;
+    font-weight: 300;
+    line-height: 1.65;
+    color: #AAA;
+  }
+</style>
+</head>
+<body>
+
+<!-- Navigation -->
+<nav>
+  <div class="nav-logo">
+    <div class="nav-logo-icon"><i data-lucide="zap" style="width:16px;height:16px;"></i></div>
+    <span>Nexus</span>
+  </div>
+  <div class="nav-center">
+    <a href="#" class="active">Docs</a>
+    <a href="#">API</a>
+    <a href="#">Changelog</a>
+    <a href="#">Status</a>
+    <a href="#">GitHub</a>
+  </div>
+  <div class="nav-right">
+    <div class="status-pill"><span class="dot"></span> Operational</div>
+    <a href="#"><i data-lucide="search" style="width:15px;height:15px;color:#CCC;"></i></a>
+  </div>
+</nav>
+
+<!-- Hero -->
+<section class="hero">
+  <div class="hero-eyebrow">Unified AI Gateway</div>
+  <h1>One API, <em>every</em> AI model<span style="color:#D4A574;font-weight:300;">.</span></h1>
+  <p>Access GPT, Claude, Gemini, and 20+ models through a single endpoint. Intelligent routing, unified billing, zero vendor lock-in.</p>
+  <div class="hero-actions">
+    <a href="#" class="btn-primary"><i data-lucide="arrow-right" style="width:14px;height:14px;"></i> Get started</a>
+    <a href="#" class="btn-secondary">View API reference</a>
+  </div>
+</section>
+
+<!-- Code Card -->
+<section class="code-section">
+  <div class="code-card">
+    <div class="code-card-header">
+      <div class="dots"><span></span><span></span><span></span></div>
+      <span class="filename">quickstart.py</span>
+      <button class="copy-btn"><i data-lucide="copy" style="width:12px;height:12px;"></i> Copy</button>
+    </div>
+    <div class="code-card-body">
+<span class="kw">from</span> nexus <span class="kw">import</span> Client<br><br>
+client = Client(api_key=<span class="str">"your-key"</span>)<br>
+response = client.chat(<br>
+&nbsp;&nbsp;&nbsp;&nbsp;model=<span class="str">"auto"</span>,&nbsp;&nbsp;<span class="cmt"># intelligently routes</span><br>
+&nbsp;&nbsp;&nbsp;&nbsp;messages=[{<span class="str">"role"</span>: <span class="str">"user"</span>, <span class="str">"content"</span>: <span class="str">"Hello!"</span>}]<br>
+)
+    </div>
+  </div>
+</section>
+
+<!-- Quick Links -->
+<div class="quick-links">
+  <a href="#" class="quick-link"><i data-lucide="rocket" style="width:14px;height:14px;"></i> Getting Started</a>
+  <a href="#" class="quick-link"><i data-lucide="file-text" style="width:14px;height:14px;"></i> API Reference</a>
+  <a href="#" class="quick-link"><i data-lucide="layers" style="width:14px;height:14px;"></i> Models</a>
+  <a href="#" class="quick-link"><i data-lucide="credit-card" style="width:14px;height:14px;"></i> Pricing</a>
+</div>
+
+<!-- Features -->
+<section class="features">
+  <div class="feature-card">
+    <div class="feature-icon-wrap"><i data-lucide="git-branch" style="width:18px;height:18px;"></i></div>
+    <h3>Model Routing</h3>
+    <p>Automatically select the best model for each request based on task complexity, latency requirements, and cost constraints.</p>
+  </div>
+  <div class="feature-card">
+    <div class="feature-icon-wrap"><i data-lucide="trending-down" style="width:18px;height:18px;"></i></div>
+    <h3>Cost Optimization</h3>
+    <p>Reduce AI spend by up to 60% with intelligent model selection and automatic fallback to cost-effective alternatives.</p>
+  </div>
+  <div class="feature-card">
+    <div class="feature-icon-wrap"><i data-lucide="bar-chart-3" style="width:18px;height:18px;"></i></div>
+    <h3>Usage Analytics</h3>
+    <p>Real-time dashboards tracking token usage, response latency, model performance, and cost breakdowns per project.</p>
+  </div>
+</section>
+
+<script>lucide.createIcons();</script>
+</body>
+</html>

BIN
assets/showcases/website-devdocs/devdocs-build.png


+ 460 - 0
assets/showcases/website-devdocs/devdocs-pentagram.html

@@ -0,0 +1,460 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Nexus API Documentation</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    background: #FFFFFF;
+    color: #111111;
+    display: flex;
+  }
+
+  /* Sidebar */
+  .sidebar {
+    width: 260px;
+    min-width: 260px;
+    height: 900px;
+    border-right: 1px solid #111;
+    display: flex;
+    flex-direction: column;
+    padding: 0;
+    overflow-y: auto;
+  }
+  .sidebar-logo {
+    padding: 24px 28px;
+    border-bottom: 1px solid #111;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+  }
+  .sidebar-logo .logo-mark {
+    width: 28px;
+    height: 28px;
+    background: #E63946;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .sidebar-logo .logo-mark svg { color: #fff; }
+  .sidebar-logo span {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 700;
+    font-size: 18px;
+    letter-spacing: -0.5px;
+  }
+  .sidebar-section {
+    padding: 20px 28px 8px;
+  }
+  .sidebar-section-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 10px;
+    font-weight: 600;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #999;
+    margin-bottom: 12px;
+  }
+  .sidebar-link {
+    display: block;
+    padding: 6px 0;
+    font-size: 13px;
+    font-weight: 500;
+    color: #555;
+    text-decoration: none;
+    transition: color 0.15s;
+  }
+  .sidebar-link:hover { color: #111; }
+  .sidebar-link.active {
+    color: #E63946;
+    font-weight: 600;
+  }
+  .sidebar-link.active::before {
+    content: '';
+    display: inline-block;
+    width: 6px;
+    height: 6px;
+    background: #E63946;
+    margin-right: 8px;
+    vertical-align: middle;
+  }
+  .sidebar-divider {
+    height: 1px;
+    background: #E8E8E8;
+    margin: 12px 28px;
+  }
+
+  /* Main content */
+  .main {
+    flex: 1;
+    height: 900px;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+  }
+
+  /* Top nav */
+  .topnav {
+    height: 52px;
+    min-height: 52px;
+    border-bottom: 1px solid #111;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 40px;
+  }
+  .topnav-links {
+    display: flex;
+    gap: 28px;
+  }
+  .topnav-links a {
+    font-size: 12px;
+    font-weight: 600;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+    text-decoration: none;
+    color: #555;
+    transition: color 0.15s;
+  }
+  .topnav-links a:hover { color: #111; }
+  .topnav-links a.active-nav { color: #E63946; }
+  .topnav-right {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+  .topnav-right .status-dot {
+    width: 7px;
+    height: 7px;
+    background: #E63946;
+    border-radius: 50%;
+    display: inline-block;
+  }
+  .topnav-right span {
+    font-size: 11px;
+    color: #888;
+    font-weight: 500;
+  }
+  .topnav-right a {
+    color: #555;
+    text-decoration: none;
+  }
+
+  /* Hero */
+  .hero {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    min-height: 400px;
+    border-bottom: 1px solid #E8E8E8;
+  }
+  .hero-left {
+    padding: 56px 48px 48px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+  .hero-badge {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 10px;
+    font-weight: 600;
+    letter-spacing: 2.5px;
+    text-transform: uppercase;
+    color: #E63946;
+    margin-bottom: 20px;
+  }
+  .hero-left h1 {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 52px;
+    font-weight: 900;
+    line-height: 1.05;
+    letter-spacing: -2px;
+    margin-bottom: 16px;
+  }
+  .hero-left p {
+    font-size: 16px;
+    line-height: 1.6;
+    color: #666;
+    max-width: 420px;
+    margin-bottom: 32px;
+  }
+  .hero-links {
+    display: flex;
+    gap: 12px;
+  }
+  .hero-links a {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    padding: 10px 20px;
+    font-size: 13px;
+    font-weight: 600;
+    text-decoration: none;
+    transition: all 0.15s;
+  }
+  .hero-links a.primary {
+    background: #111;
+    color: #fff;
+  }
+  .hero-links a.primary:hover { background: #E63946; }
+  .hero-links a.secondary {
+    border: 1px solid #DDD;
+    color: #333;
+    background: #fff;
+  }
+  .hero-links a.secondary:hover { border-color: #111; }
+
+  /* Code block */
+  .hero-right {
+    padding: 40px 48px 40px 24px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #FAFAFA;
+    border-left: 1px solid #E8E8E8;
+  }
+  .code-block {
+    background: #FFFFFF;
+    border: 1px solid #DDD;
+    width: 100%;
+    max-width: 480px;
+  }
+  .code-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 16px;
+    border-bottom: 1px solid #DDD;
+    background: #FAFAFA;
+  }
+  .code-header span {
+    font-family: 'JetBrains Mono', monospace;
+    font-size: 11px;
+    color: #999;
+    font-weight: 500;
+  }
+  .code-dots {
+    display: flex;
+    gap: 6px;
+  }
+  .code-dots i { width: 10px; height: 10px; }
+  .code-body {
+    padding: 20px;
+    font-family: 'JetBrains Mono', monospace;
+    font-size: 13px;
+    line-height: 1.7;
+    color: #111;
+  }
+  .code-body .kw { color: #111; font-weight: 500; }
+  .code-body .str { color: #E63946; }
+  .code-body .cmt { color: #AAAAAA; }
+  .code-body .fn { color: #555; }
+
+  /* Quick links bar */
+  .quick-bar {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    border-bottom: 1px solid #E8E8E8;
+  }
+  .quick-bar a {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    padding: 18px 28px;
+    text-decoration: none;
+    color: #333;
+    font-size: 13px;
+    font-weight: 600;
+    border-right: 1px solid #E8E8E8;
+    transition: background 0.15s;
+  }
+  .quick-bar a:last-child { border-right: none; }
+  .quick-bar a:hover { background: #FAFAFA; }
+  .quick-bar a i { color: #E63946; }
+
+  /* Features */
+  .features {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    flex: 1;
+  }
+  .feature-card {
+    padding: 36px 32px;
+    border-right: 1px solid #E8E8E8;
+    display: flex;
+    flex-direction: column;
+  }
+  .feature-card:last-child { border-right: none; }
+  .feature-card .feature-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 10px;
+    font-weight: 600;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    color: #E63946;
+    margin-bottom: 14px;
+  }
+  .feature-card h3 {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 20px;
+    font-weight: 600;
+    letter-spacing: -0.5px;
+    margin-bottom: 10px;
+  }
+  .feature-card p {
+    font-size: 13px;
+    line-height: 1.65;
+    color: #777;
+  }
+  .feature-icon {
+    width: 36px;
+    height: 36px;
+    background: #111;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 18px;
+  }
+  .feature-icon i { color: #fff; }
+</style>
+</head>
+<body>
+
+<!-- Sidebar -->
+<aside class="sidebar">
+  <div class="sidebar-logo">
+    <div class="logo-mark"><i data-lucide="zap" style="width:16px;height:16px;"></i></div>
+    <span>Nexus API</span>
+  </div>
+
+  <div class="sidebar-section">
+    <div class="sidebar-section-label">Getting Started</div>
+    <a href="#" class="sidebar-link active">Introduction</a>
+    <a href="#" class="sidebar-link">Quick Start</a>
+    <a href="#" class="sidebar-link">Authentication</a>
+    <a href="#" class="sidebar-link">Installation</a>
+  </div>
+  <div class="sidebar-divider"></div>
+  <div class="sidebar-section">
+    <div class="sidebar-section-label">Core Concepts</div>
+    <a href="#" class="sidebar-link">Model Routing</a>
+    <a href="#" class="sidebar-link">Chat Completions</a>
+    <a href="#" class="sidebar-link">Streaming</a>
+    <a href="#" class="sidebar-link">Error Handling</a>
+  </div>
+  <div class="sidebar-divider"></div>
+  <div class="sidebar-section">
+    <div class="sidebar-section-label">API Reference</div>
+    <a href="#" class="sidebar-link">POST /chat</a>
+    <a href="#" class="sidebar-link">GET /models</a>
+    <a href="#" class="sidebar-link">GET /usage</a>
+    <a href="#" class="sidebar-link">Webhooks</a>
+  </div>
+  <div class="sidebar-divider"></div>
+  <div class="sidebar-section">
+    <div class="sidebar-section-label">Resources</div>
+    <a href="#" class="sidebar-link">Models</a>
+    <a href="#" class="sidebar-link">Pricing</a>
+    <a href="#" class="sidebar-link">SDKs</a>
+    <a href="#" class="sidebar-link">Changelog</a>
+  </div>
+</aside>
+
+<!-- Main -->
+<main class="main">
+  <!-- Top Nav -->
+  <nav class="topnav">
+    <div class="topnav-links">
+      <a href="#" class="active-nav">Docs</a>
+      <a href="#">API</a>
+      <a href="#">Changelog</a>
+      <a href="#">Status</a>
+      <a href="#">GitHub</a>
+    </div>
+    <div class="topnav-right">
+      <span class="status-dot"></span>
+      <span>All systems operational</span>
+      <a href="#"><i data-lucide="search" style="width:16px;height:16px;color:#888;"></i></a>
+    </div>
+  </nav>
+
+  <!-- Hero -->
+  <section class="hero">
+    <div class="hero-left">
+      <div class="hero-badge">Unified AI Gateway</div>
+      <h1>One API,<br>every AI model</h1>
+      <p>Access GPT, Claude, Gemini, and 20+ models through a single endpoint. Intelligent routing, unified billing, zero vendor lock-in.</p>
+      <div class="hero-links">
+        <a href="#" class="primary"><i data-lucide="arrow-right" style="width:14px;height:14px;"></i> Get Started</a>
+        <a href="#" class="secondary">API Reference</a>
+      </div>
+    </div>
+    <div class="hero-right">
+      <div class="code-block">
+        <div class="code-header">
+          <div class="code-dots">
+            <i data-lucide="circle" style="width:10px;height:10px;color:#DDD;fill:#DDD;"></i>
+            <i data-lucide="circle" style="width:10px;height:10px;color:#DDD;fill:#DDD;"></i>
+            <i data-lucide="circle" style="width:10px;height:10px;color:#DDD;fill:#DDD;"></i>
+          </div>
+          <span>quickstart.py</span>
+        </div>
+        <div class="code-body">
+<span class="kw">from</span> nexus <span class="kw">import</span> Client<br><br>
+client = Client(api_key=<span class="str">"your-key"</span>)<br>
+response = client.chat(<br>
+&nbsp;&nbsp;&nbsp;&nbsp;model=<span class="str">"auto"</span>,&nbsp;&nbsp;<span class="cmt"># intelligently routes</span><br>
+&nbsp;&nbsp;&nbsp;&nbsp;messages=[{<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="str">"role"</span>: <span class="str">"user"</span>,<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="str">"content"</span>: <span class="str">"Hello!"</span><br>
+&nbsp;&nbsp;&nbsp;&nbsp;}]<br>
+)
+        </div>
+      </div>
+    </div>
+  </section>
+
+  <!-- Quick Links -->
+  <div class="quick-bar">
+    <a href="#"><i data-lucide="rocket" style="width:16px;height:16px;"></i> Getting Started</a>
+    <a href="#"><i data-lucide="file-text" style="width:16px;height:16px;"></i> API Reference</a>
+    <a href="#"><i data-lucide="layers" style="width:16px;height:16px;"></i> Models</a>
+    <a href="#"><i data-lucide="credit-card" style="width:16px;height:16px;"></i> Pricing</a>
+  </div>
+
+  <!-- Features -->
+  <section class="features">
+    <div class="feature-card">
+      <div class="feature-icon"><i data-lucide="git-branch" style="width:18px;height:18px;"></i></div>
+      <div class="feature-label">Feature 01</div>
+      <h3>Model Routing</h3>
+      <p>Automatically select the best model for each request based on task complexity, latency requirements, and cost constraints.</p>
+    </div>
+    <div class="feature-card">
+      <div class="feature-icon"><i data-lucide="trending-down" style="width:18px;height:18px;"></i></div>
+      <div class="feature-label">Feature 02</div>
+      <h3>Cost Optimization</h3>
+      <p>Reduce AI spend by up to 60% with intelligent model selection and automatic fallback to cost-effective alternatives.</p>
+    </div>
+    <div class="feature-card">
+      <div class="feature-icon"><i data-lucide="bar-chart-3" style="width:18px;height:18px;"></i></div>
+      <div class="feature-label">Feature 03</div>
+      <h3>Usage Analytics</h3>
+      <p>Real-time dashboards tracking token usage, response latency, model performance, and cost breakdowns per project.</p>
+    </div>
+  </section>
+</main>
+
+<script>lucide.createIcons();</script>
+</body>
+</html>

BIN
assets/showcases/website-devdocs/devdocs-pentagram.png


+ 494 - 0
assets/showcases/website-devdocs/devdocs-takram.html

@@ -0,0 +1,494 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Nexus API Documentation</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&family=Noto+Serif+SC:wght@400;500;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Inter', sans-serif;
+    background: #F5F0EB;
+    color: #3A3A35;
+  }
+
+  /* Navigation */
+  nav {
+    height: 72px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 64px;
+  }
+  .nav-logo {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+  }
+  .nav-logo-mark {
+    width: 36px;
+    height: 36px;
+    border-radius: 12px;
+    background: #6B8F71;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .nav-logo-mark i { color: #F5F0EB; }
+  .nav-logo-text {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 18px;
+    font-weight: 600;
+    color: #2D3436;
+    letter-spacing: -0.3px;
+  }
+  .nav-links {
+    display: flex;
+    gap: 32px;
+  }
+  .nav-links a {
+    font-size: 13px;
+    font-weight: 400;
+    color: #999;
+    text-decoration: none;
+    transition: color 0.2s;
+    letter-spacing: 0.3px;
+  }
+  .nav-links a:hover { color: #3A3A35; }
+  .nav-links a.active { color: #3A3A35; font-weight: 500; }
+  .nav-right {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+  }
+  .search-box {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 16px;
+    background: rgba(255,255,255,0.5);
+    border-radius: 12px;
+    border: 1px solid #E5DFCE;
+  }
+  .search-box span {
+    font-size: 12px;
+    color: #BBB;
+  }
+  .search-box kbd {
+    font-family: 'Inter', sans-serif;
+    font-size: 10px;
+    background: #EDE8DC;
+    border-radius: 4px;
+    padding: 2px 6px;
+    color: #AAA;
+    margin-left: 24px;
+  }
+
+  /* Hero Section */
+  .hero {
+    display: flex;
+    padding: 40px 64px 36px;
+    gap: 56px;
+    align-items: flex-start;
+  }
+  .hero-content {
+    flex: 1;
+    padding-top: 16px;
+  }
+  .hero-tag {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    padding: 6px 14px;
+    background: rgba(107,143,113,0.15);
+    border-radius: 100px;
+    font-size: 11px;
+    font-weight: 500;
+    color: #6B8F71;
+    margin-bottom: 24px;
+    letter-spacing: 0.5px;
+  }
+  .hero h1 {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 44px;
+    font-weight: 600;
+    line-height: 1.15;
+    letter-spacing: -1.5px;
+    color: #2D3436;
+    margin-bottom: 16px;
+  }
+  .hero p {
+    font-size: 16px;
+    font-weight: 300;
+    line-height: 1.7;
+    color: #888;
+    max-width: 440px;
+    margin-bottom: 32px;
+  }
+  .hero-buttons {
+    display: flex;
+    gap: 12px;
+  }
+  .hero-buttons a {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 24px;
+    font-size: 13px;
+    font-weight: 500;
+    text-decoration: none;
+    border-radius: 12px;
+    transition: all 0.2s;
+  }
+  .btn-green {
+    background: rgba(107, 143, 113, 0.12);
+    color: #6B8F71;
+    border: 1px solid rgba(107, 143, 113, 0.3);
+  }
+  .btn-green:hover { background: rgba(107, 143, 113, 0.18); }
+  .btn-outline {
+    background: rgba(255,255,255,0.5);
+    color: #666;
+    border: 1px solid #DDD8CB;
+  }
+  .btn-outline:hover { background: rgba(255,255,255,0.8); }
+
+  /* Code + Diagram Area */
+  .hero-visual {
+    width: 560px;
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+  }
+
+  /* Flow diagram */
+  .flow-diagram {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 0;
+    padding: 20px 24px;
+    background: rgba(255,255,255,0.45);
+    border-radius: 16px;
+    border: 1px solid #E5DFCE;
+  }
+  .flow-node {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 6px;
+  }
+  .flow-node-box {
+    padding: 10px 20px;
+    background: #FFFFFF;
+    border-radius: 10px;
+    border: 1px solid #E0DACE;
+    font-size: 13px;
+    font-weight: 500;
+    color: #3A3A35;
+    white-space: nowrap;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.03);
+  }
+  .flow-node-box.highlight {
+    background: #6B8F71;
+    border-color: #6B8F71;
+    color: #fff;
+  }
+  .flow-node-label {
+    font-size: 10px;
+    color: #BBB;
+    letter-spacing: 0.5px;
+  }
+  .flow-arrow {
+    display: flex;
+    align-items: center;
+    padding: 0 12px;
+  }
+  .flow-arrow-line {
+    width: 40px;
+    height: 1px;
+    background: #CCC8BA;
+    position: relative;
+  }
+  .flow-arrow-line::after {
+    content: '';
+    position: absolute;
+    right: -1px;
+    top: -3px;
+    border: solid #CCC8BA;
+    border-width: 0 1px 1px 0;
+    padding: 3px;
+    transform: rotate(-45deg);
+  }
+  .flow-models {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+  }
+  .flow-model-tag {
+    padding: 6px 14px;
+    background: #FFFFFF;
+    border-radius: 8px;
+    border: 1px solid #E0DACE;
+    font-size: 11px;
+    font-weight: 400;
+    color: #888;
+    box-shadow: 0 1px 4px rgba(0,0,0,0.02);
+  }
+
+  /* Code block */
+  .code-card {
+    background: #FAF5EC;
+    border-radius: 16px;
+    border: 1px solid #E5DFCE;
+    overflow: hidden;
+  }
+  .code-card-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 20px;
+    border-bottom: 1px solid #E5DFCE;
+    background: rgba(255,255,255,0.3);
+  }
+  .code-card-header .dots {
+    display: flex;
+    gap: 6px;
+  }
+  .code-card-header .dots span {
+    width: 9px;
+    height: 9px;
+    border-radius: 50%;
+    background: #DDD8CB;
+  }
+  .code-card-header .fname {
+    font-family: 'JetBrains Mono', monospace;
+    font-size: 11px;
+    color: #BBB;
+  }
+  .code-card-body {
+    padding: 20px 24px;
+    font-family: 'JetBrains Mono', monospace;
+    font-size: 12.5px;
+    line-height: 1.8;
+    color: #555;
+  }
+  .code-card-body .kw { color: #6B8F71; font-weight: 500; }
+  .code-card-body .str { color: #D4A574; }
+  .code-card-body .cmt { color: #C4C0B4; }
+
+  /* Quick Links */
+  .quick-links {
+    display: flex;
+    justify-content: center;
+    gap: 20px;
+    padding: 8px 64px 32px;
+  }
+  .quick-link {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    text-decoration: none;
+    padding: 12px 22px;
+    background: rgba(255,255,255,0.45);
+    border: 1px solid #E5DFCE;
+    border-radius: 12px;
+    font-size: 13px;
+    font-weight: 400;
+    color: #777;
+    transition: all 0.2s;
+  }
+  .quick-link:hover {
+    background: rgba(255,255,255,0.7);
+    color: #3A3A35;
+  }
+  .quick-link i { color: #6B8F71; }
+
+  /* Features */
+  .features {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 20px;
+    padding: 0 64px;
+    position: relative;
+  }
+  .feature-card {
+    background: rgba(255,255,255,0.5);
+    border: 1px solid #E5DFCE;
+    border-radius: 16px;
+    padding: 28px 24px;
+    transition: all 0.2s;
+    position: relative;
+  }
+  .feature-card:hover {
+    background: rgba(255,255,255,0.75);
+    box-shadow: 0 4px 20px rgba(0,0,0,0.03);
+  }
+  .feature-icon-wrap {
+    width: 40px;
+    height: 40px;
+    border-radius: 12px;
+    background: rgba(107,143,113,0.15);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 16px;
+  }
+  .feature-icon-wrap i { color: #6B8F71; }
+  .feature-card h3 {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 16px;
+    font-weight: 500;
+    margin-bottom: 8px;
+    color: #2D3436;
+  }
+  .feature-card p {
+    font-size: 13px;
+    font-weight: 300;
+    line-height: 1.65;
+    color: #AAA;
+  }
+
+  /* Connection lines between feature cards */
+  .features::before {
+    content: '';
+    position: absolute;
+    top: 50%;
+    left: calc(33.33% + 32px);
+    width: calc(33.33% - 64px - 20px);
+    height: 0;
+    border-top: 1px dashed #D4CEBD;
+    transform: translateX(10px);
+  }
+  .features::after {
+    content: '';
+    position: absolute;
+    top: 50%;
+    right: calc(33.33% + 32px);
+    width: calc(33.33% - 64px - 20px);
+    height: 0;
+    border-top: 1px dashed #D4CEBD;
+    transform: translateX(-10px);
+  }
+</style>
+</head>
+<body>
+
+<!-- Navigation -->
+<nav>
+  <div class="nav-logo">
+    <div class="nav-logo-mark"><i data-lucide="zap" style="width:18px;height:18px;"></i></div>
+    <span class="nav-logo-text">Nexus API</span>
+  </div>
+  <div class="nav-links">
+    <a href="#" class="active">Docs</a>
+    <a href="#">API</a>
+    <a href="#">Changelog</a>
+    <a href="#">Status</a>
+    <a href="#">GitHub</a>
+  </div>
+  <div class="nav-right">
+    <div class="search-box">
+      <i data-lucide="search" style="width:13px;height:13px;color:#CCC;"></i>
+      <span>Search documentation...</span>
+      <kbd>/</kbd>
+    </div>
+  </div>
+</nav>
+
+<!-- Hero -->
+<section class="hero">
+  <div class="hero-content">
+    <div class="hero-tag"><i data-lucide="sparkles" style="width:12px;height:12px;"></i> Unified AI Gateway</div>
+    <h1>One API,<br>every AI model</h1>
+    <p>Access GPT, Claude, Gemini, and 20+ models through a single endpoint. Intelligent routing, unified billing, zero vendor lock-in.</p>
+    <div class="hero-buttons">
+      <a href="#" class="btn-green"><i data-lucide="book-open" style="width:14px;height:14px;"></i> Get Started</a>
+      <a href="#" class="btn-outline">API Reference</a>
+    </div>
+  </div>
+
+  <div class="hero-visual">
+    <!-- Flow diagram -->
+    <div class="flow-diagram">
+      <div class="flow-node">
+        <div class="flow-node-box">Your App</div>
+        <span class="flow-node-label">request</span>
+      </div>
+      <div class="flow-arrow"><div class="flow-arrow-line"></div></div>
+      <div class="flow-node">
+        <div class="flow-node-box highlight">Nexus</div>
+        <span class="flow-node-label">routes</span>
+      </div>
+      <div class="flow-arrow"><div class="flow-arrow-line"></div></div>
+      <div class="flow-models">
+        <div class="flow-model-tag">GPT-4o</div>
+        <div class="flow-model-tag">Claude 3.5</div>
+        <div class="flow-model-tag">Gemini Pro</div>
+      </div>
+    </div>
+
+    <!-- Code block -->
+    <div class="code-card">
+      <div class="code-card-header">
+        <div class="dots"><span></span><span></span><span></span></div>
+        <span class="fname">quickstart.py</span>
+        <i data-lucide="copy" style="width:13px;height:13px;color:#CCC;cursor:pointer;"></i>
+      </div>
+      <div class="code-card-body">
+<span class="kw">from</span> nexus <span class="kw">import</span> Client<br><br>
+client = Client(api_key=<span class="str">"your-key"</span>)<br>
+response = client.chat(<br>
+&nbsp;&nbsp;&nbsp;&nbsp;model=<span class="str">"auto"</span>,&nbsp;&nbsp;<span class="cmt"># intelligently routes</span><br>
+&nbsp;&nbsp;&nbsp;&nbsp;messages=[{<span class="str">"role"</span>: <span class="str">"user"</span>, <span class="str">"content"</span>: <span class="str">"Hello!"</span>}]<br>
+)
+      </div>
+    </div>
+  </div>
+</section>
+
+<!-- Quick Links -->
+<div class="quick-links">
+  <a href="#" class="quick-link"><i data-lucide="rocket" style="width:14px;height:14px;"></i> Getting Started</a>
+  <a href="#" class="quick-link"><i data-lucide="file-text" style="width:14px;height:14px;"></i> API Reference</a>
+  <a href="#" class="quick-link"><i data-lucide="layers" style="width:14px;height:14px;"></i> Models</a>
+  <a href="#" class="quick-link"><i data-lucide="credit-card" style="width:14px;height:14px;"></i> Pricing</a>
+</div>
+
+<!-- Features -->
+<section class="features">
+  <div class="feature-card">
+    <div class="feature-icon-wrap"><i data-lucide="git-branch" style="width:18px;height:18px;"></i></div>
+    <h3>Model Routing</h3>
+    <p>Automatically select the best model for each request based on task complexity, latency, and cost constraints.</p>
+  </div>
+  <div class="feature-card">
+    <div class="feature-icon-wrap"><i data-lucide="trending-down" style="width:18px;height:18px;"></i></div>
+    <h3>Cost Optimization</h3>
+    <p>Reduce AI spend by up to 60% with intelligent selection and automatic fallback to cost-effective alternatives.</p>
+  </div>
+  <div class="feature-card">
+    <div class="feature-icon-wrap"><i data-lucide="bar-chart-3" style="width:18px;height:18px;"></i></div>
+    <h3>Usage Analytics</h3>
+    <p>Real-time dashboards tracking token usage, latency, model performance, and cost breakdowns per project.</p>
+  </div>
+</section>
+
+<!-- Spec annotation -->
+<svg style="position:absolute;bottom:24px;right:64px;opacity:0.12;" width="120" height="40" viewBox="0 0 120 40" fill="none">
+  <line x1="0" y1="20" x2="72" y2="20" stroke="#6B8F71" stroke-width="0.5"/>
+  <circle cx="72" cy="20" r="2.5" fill="none" stroke="#6B8F71" stroke-width="0.5"/>
+  <text x="82" y="23" font-family="Inter" font-size="8" fill="#6B8F71" letter-spacing="0.5">20+ models</text>
+</svg>
+
+<script>lucide.createIcons();</script>
+</body>
+</html>

BIN
assets/showcases/website-devdocs/devdocs-takram.png


+ 367 - 0
assets/showcases/website-homepage/homepage-build.html

@@ -0,0 +1,367 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Alex Chen — Indie Developer & AI Creator</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    background: #FAFAF8;
+    font-family: 'Inter', sans-serif;
+    color: #2A2A28;
+    position: relative;
+  }
+
+  /* GLASSMORPHISM NAV */
+  nav {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 1440px;
+    height: 64px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    background: rgba(250, 250, 248, 0.72);
+    backdrop-filter: blur(24px);
+    -webkit-backdrop-filter: blur(24px);
+    border-bottom: 1px solid rgba(0,0,0,0.04);
+    z-index: 100;
+  }
+  nav .logo {
+    font-weight: 500;
+    font-size: 15px;
+    letter-spacing: 0.02em;
+    color: #2A2A28;
+  }
+  nav .logo .dot { color: #D4A574; }
+  nav ul {
+    list-style: none;
+    display: flex;
+    gap: 40px;
+  }
+  nav ul li a {
+    font-weight: 400;
+    font-size: 13px;
+    color: #888;
+    text-decoration: none;
+    letter-spacing: 0.01em;
+    transition: color 0.3s;
+  }
+  nav ul li a:hover { color: #2A2A28; }
+  nav .nav-cta a {
+    font-weight: 400;
+    font-size: 12px;
+    color: #2A2A28;
+    text-decoration: none;
+    padding: 8px 24px;
+    border: 1px solid rgba(0,0,0,0.08);
+    border-radius: 2px;
+    transition: all 0.3s;
+    letter-spacing: 0.02em;
+  }
+  nav .nav-cta a:hover {
+    border-color: #D4A574;
+    color: #D4A574;
+  }
+
+  /* HERO LAYOUT */
+  .hero {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 1440px;
+    height: 900px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 80px;
+  }
+
+  .hero-content {
+    display: flex;
+    align-items: center;
+    gap: 96px;
+    width: 100%;
+    max-width: 1200px;
+  }
+
+  /* LEFT: TEXT */
+  .hero-text {
+    flex: 1;
+  }
+  .hero-text .greeting {
+    font-weight: 400;
+    font-size: 11px;
+    color: #B0ACA4;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+    margin-bottom: 24px;
+  }
+  .hero-text h1 {
+    font-weight: 200;
+    font-size: 80px;
+    line-height: 1.02;
+    letter-spacing: -0.04em;
+    color: #2A2A28;
+  }
+  .hero-text h1 strong {
+    font-weight: 500;
+  }
+  .hero-text h1 .gold-period {
+    color: #D4A574;
+    font-weight: 300;
+  }
+  .hero-text .tagline {
+    font-weight: 300;
+    font-size: 16px;
+    line-height: 1.8;
+    color: #999;
+    margin-top: 32px;
+    max-width: 440px;
+  }
+
+  /* CTA BUTTONS */
+  .hero-cta {
+    display: flex;
+    gap: 16px;
+    margin-top: 48px;
+  }
+  .btn-primary {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    font-family: 'Inter', sans-serif;
+    font-weight: 400;
+    font-size: 13px;
+    color: #FAFAF8;
+    background: #2A2A28;
+    border: none;
+    padding: 14px 32px;
+    border-radius: 2px;
+    cursor: pointer;
+    transition: all 0.3s;
+    text-decoration: none;
+    letter-spacing: 0.02em;
+  }
+  .btn-primary:hover { background: #3A3A38; }
+  .btn-secondary {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    font-family: 'Inter', sans-serif;
+    font-weight: 400;
+    font-size: 13px;
+    color: #888;
+    background: transparent;
+    border: 1px solid rgba(0,0,0,0.08);
+    padding: 14px 32px;
+    border-radius: 2px;
+    cursor: pointer;
+    transition: all 0.3s;
+    text-decoration: none;
+    letter-spacing: 0.02em;
+  }
+  .btn-secondary:hover { border-color: #D4A574; color: #D4A574; }
+
+  /* RIGHT: CARDS + PORTRAIT */
+  .hero-visual {
+    flex: 0 0 460px;
+    position: relative;
+    height: 520px;
+  }
+
+  /* PORTRAIT */
+  .portrait {
+    width: 200px;
+    height: 200px;
+    border-radius: 50%;
+    background: #EDECE8;
+    position: absolute;
+    top: 0;
+    right: 40px;
+    box-shadow: 0 20px 60px rgba(0,0,0,0.06);
+    overflow: hidden;
+  }
+  .portrait::after {
+    content: '';
+    position: absolute;
+    bottom: 0; left: 50%;
+    transform: translateX(-50%);
+    width: 110px;
+    height: 130px;
+    background: #D8D6D0;
+    border-radius: 55px 55px 0 0;
+  }
+
+  /* FLOATING CARDS */
+  .card {
+    position: absolute;
+    background: #FFFFFF;
+    border: 1px solid rgba(0,0,0,0.04);
+    border-radius: 2px;
+    padding: 24px;
+    box-shadow: 0 4px 24px rgba(0,0,0,0.03);
+  }
+
+  .card-1 {
+    top: 60px;
+    left: 0;
+    width: 220px;
+  }
+  .card-2 {
+    top: 240px;
+    left: 60px;
+    width: 240px;
+  }
+  .card-3 {
+    top: 180px;
+    right: 0;
+    width: 200px;
+  }
+
+  .card .card-number {
+    font-weight: 200;
+    font-size: 32px;
+    letter-spacing: -0.02em;
+    color: #2A2A28;
+    line-height: 1;
+  }
+  .card .card-number .gold { color: #D4A574; font-weight: 300; }
+  .card .card-label {
+    font-weight: 400;
+    font-size: 10px;
+    color: #B0ACA4;
+    margin-top: 8px;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+  }
+  .card .card-desc {
+    font-weight: 300;
+    font-size: 12px;
+    color: #999;
+    margin-top: 8px;
+    line-height: 1.5;
+  }
+
+  /* GOLD ACCENT LINE */
+  .accent-line {
+    position: absolute;
+    bottom: 0;
+    left: 100px;
+    width: 48px;
+    height: 2px;
+    background: #D4A574;
+    border-radius: 1px;
+  }
+
+  /* Removed dot-grid — Build: zero decorative elements */
+
+  /* BOTTOM TICKER */
+  .bottom-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 1440px;
+    height: 48px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 48px;
+    border-top: 1px solid rgba(0,0,0,0.04);
+  }
+  .bottom-bar span {
+    font-weight: 300;
+    font-size: 11px;
+    color: #BBB;
+    letter-spacing: 0.08em;
+  }
+  .bottom-bar .sep {
+    width: 4px;
+    height: 4px;
+    border-radius: 50%;
+    background: #D4A574;
+    opacity: 0.5;
+  }
+</style>
+</head>
+<body>
+
+  <!-- NAV -->
+  <nav>
+    <div class="logo">alex chen<span class="dot"> .</span></div>
+    <ul>
+      <li><a href="#work">Work</a></li>
+      <li><a href="#content">Content</a></li>
+      <li><a href="#services">Services</a></li>
+    </ul>
+    <div class="nav-cta">
+      <a href="#contact">Get in Touch</a>
+    </div>
+  </nav>
+
+  <!-- HERO -->
+  <div class="hero">
+    <div class="hero-content">
+      <!-- TEXT -->
+      <div class="hero-text">
+        <div class="greeting">Indie Developer & AI Creator</div>
+        <h1>Alex<br><strong>Chen</strong><span class="gold-period">.</span></h1>
+        <p class="tagline">Building tools at the intersection of AI and creativity. Shipping products, writing stories, shaping ideas.</p>
+        <div class="hero-cta">
+          <a href="#work" class="btn-primary">
+            View Work
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
+          </a>
+          <a href="#content" class="btn-secondary">Read Articles</a>
+        </div>
+      </div>
+
+      <!-- VISUAL -->
+      <div class="hero-visual">
+        <div class="portrait"></div>
+
+        <div class="card card-1">
+          <div class="card-number">300K<span class="gold">+</span></div>
+          <div class="card-label">Followers</div>
+          <div class="card-desc">Across platforms, building in public</div>
+        </div>
+
+        <div class="card card-2">
+          <div class="card-number"><span class="gold">#</span>1</div>
+          <div class="card-label">App Store</div>
+          <div class="card-desc">Top paid app, shipped as a solo developer</div>
+        </div>
+
+        <div class="card card-3">
+          <div class="card-number">100<span class="gold">+</span></div>
+          <div class="card-label">Articles</div>
+          <div class="card-desc">On AI, dev, and creative tools</div>
+        </div>
+
+        <div class="accent-line"></div>
+      </div>
+    </div>
+  </div>
+
+  <!-- BOTTOM BAR -->
+  <div class="bottom-bar">
+    <span>Developer</span>
+    <div class="sep"></div>
+    <span>Writer</span>
+    <div class="sep"></div>
+    <span>AI Creator</span>
+    <div class="sep"></div>
+    <span>Speaker</span>
+  </div>
+
+</body>
+</html>

BIN
assets/showcases/website-homepage/homepage-build.png


+ 368 - 0
assets/showcases/website-homepage/homepage-pentagram.html

@@ -0,0 +1,368 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Alex Chen — Indie Developer & AI Creator</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    background: #FFFFFF;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    color: #111111;
+    position: relative;
+  }
+
+  /* NAV */
+  nav {
+    position: absolute;
+    top: 0; left: 0; right: 0;
+    height: 72px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    border-bottom: 1px solid #111;
+    z-index: 10;
+  }
+  nav .logo {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 700;
+    font-size: 16px;
+    letter-spacing: 0.08em;
+    text-transform: uppercase;
+  }
+  nav .logo span { color: #E63946; }
+  nav ul {
+    list-style: none;
+    display: flex;
+    gap: 48px;
+  }
+  nav ul li a {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 500;
+    font-size: 11px;
+    letter-spacing: 0.18em;
+    text-transform: uppercase;
+    text-decoration: none;
+    color: #111;
+    transition: color 0.2s;
+  }
+  nav ul li a:hover { color: #E63946; }
+  nav .nav-contact a {
+    background: #111;
+    color: #fff;
+    padding: 10px 28px;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 500;
+    font-size: 11px;
+    letter-spacing: 0.14em;
+    text-transform: uppercase;
+    text-decoration: none;
+    transition: background 0.2s;
+  }
+  nav .nav-contact a:hover { background: #E63946; }
+
+  /* MAIN GRID */
+  .hero {
+    position: absolute;
+    top: 72px;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    display: grid;
+    grid-template-columns: 1fr 1px 1fr;
+    grid-template-rows: 1fr;
+  }
+
+  /* LEFT PANEL */
+  .hero-left {
+    padding: 64px 80px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+  }
+  .hero-left .intro-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    letter-spacing: 0.2em;
+    text-transform: uppercase;
+    color: #999;
+    margin-bottom: 16px;
+  }
+  .hero-left .name {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 900;
+    font-size: 112px;
+    line-height: 0.92;
+    letter-spacing: -0.03em;
+    color: #111;
+  }
+  .hero-left .name .accent { color: #E63946; }
+  .hero-left .tagline {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 300;
+    font-size: 20px;
+    line-height: 1.6;
+    color: #555;
+    max-width: 480px;
+    margin-top: 32px;
+  }
+
+  /* STATS ROW */
+  .stats-row {
+    display: flex;
+    gap: 0;
+    border-top: 1px solid #111;
+    padding-top: 32px;
+  }
+  .stat-item {
+    flex: 1;
+    position: relative;
+  }
+  .stat-item:not(:last-child)::after {
+    content: '';
+    position: absolute;
+    right: 0;
+    top: 0;
+    height: 100%;
+    width: 1px;
+    background: #DDD;
+  }
+  .stat-item .stat-number {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 700;
+    font-size: 48px;
+    letter-spacing: -0.02em;
+    color: #111;
+    line-height: 1;
+  }
+  .stat-item .stat-number .red { color: #E63946; }
+  .stat-item .stat-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    letter-spacing: 0.14em;
+    text-transform: uppercase;
+    color: #999;
+    margin-top: 8px;
+  }
+
+  /* CENTER DIVIDER */
+  .divider {
+    background: #111;
+  }
+
+  /* RIGHT PANEL */
+  .hero-right {
+    padding: 64px 80px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    background: #FAFAFA;
+  }
+
+  /* PORTRAIT PLACEHOLDER */
+  .portrait-wrap {
+    position: relative;
+    width: 320px;
+    height: 320px;
+  }
+  .portrait-circle {
+    width: 320px;
+    height: 320px;
+    border-radius: 50%;
+    background: #E8E8E8;
+    position: relative;
+    overflow: hidden;
+  }
+  .portrait-circle::after {
+    content: '';
+    position: absolute;
+    bottom: 0; left: 50%;
+    transform: translateX(-50%);
+    width: 180px;
+    height: 200px;
+    background: #D0D0D0;
+    border-radius: 90px 90px 0 0;
+  }
+  .portrait-frame {
+    position: absolute;
+    top: -12px;
+    left: -12px;
+    width: 344px;
+    height: 344px;
+    border: 1px solid #E63946;
+    border-radius: 50%;
+  }
+
+  /* RED INDEX MARKER */
+  .index-marker {
+    position: absolute;
+    bottom: 64px;
+    right: 80px;
+    text-align: right;
+  }
+  .index-marker .idx-num {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 700;
+    font-size: 120px;
+    line-height: 0.85;
+    color: #E63946;
+    opacity: 0.1;
+  }
+  .index-marker .idx-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    letter-spacing: 0.18em;
+    text-transform: uppercase;
+    color: #999;
+    margin-top: 8px;
+  }
+
+  /* DECORATIVE ELEMENTS */
+  .corner-mark {
+    position: absolute;
+    top: 64px;
+    right: 80px;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    letter-spacing: 0.18em;
+    text-transform: uppercase;
+    color: #CCC;
+  }
+
+  .hero-right .role-tags {
+    margin-top: 40px;
+    display: flex;
+    gap: 12px;
+  }
+  .role-tags span {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    letter-spacing: 0.12em;
+    text-transform: uppercase;
+    padding: 8px 20px;
+    border: 1px solid #CCC;
+    color: #666;
+    transition: all 0.2s;
+  }
+  .role-tags span:hover {
+    border-color: #E63946;
+    color: #E63946;
+  }
+
+  /* SCROLL CTA */
+  .scroll-cta {
+    position: absolute;
+    bottom: 28px;
+    left: 50%;
+    transform: translateX(-50%);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 8px;
+  }
+  .scroll-cta span {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 10px;
+    letter-spacing: 0.2em;
+    text-transform: uppercase;
+    color: #BBB;
+  }
+  .scroll-cta .arrow-down {
+    width: 1px;
+    height: 32px;
+    background: #CCC;
+    position: relative;
+  }
+  .scroll-cta .arrow-down::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: -3px;
+    width: 7px;
+    height: 7px;
+    border-right: 1px solid #CCC;
+    border-bottom: 1px solid #CCC;
+    transform: rotate(45deg);
+  }
+</style>
+</head>
+<body>
+
+  <!-- NAVIGATION -->
+  <nav>
+    <div class="logo">Alex<span>.</span>Chen</div>
+    <ul>
+      <li><a href="#work">Work</a></li>
+      <li><a href="#content">Content</a></li>
+      <li><a href="#services">Services</a></li>
+    </ul>
+    <div class="nav-contact">
+      <a href="#contact">Contact</a>
+    </div>
+  </nav>
+
+  <!-- HERO -->
+  <div class="hero">
+    <!-- LEFT -->
+    <div class="hero-left">
+      <div>
+        <div class="intro-label">Indie Developer / AI Creator</div>
+        <h1 class="name">Alex<br>Chen<span class="accent">.</span></h1>
+        <p class="tagline">Building tools at the intersection of AI and creativity.</p>
+      </div>
+      <div class="stats-row">
+        <div class="stat-item">
+          <div class="stat-number">300K<span class="red">+</span></div>
+          <div class="stat-label">Followers</div>
+        </div>
+        <div class="stat-item" style="padding-left: 32px;">
+          <div class="stat-number">#1</div>
+          <div class="stat-label">App Store</div>
+        </div>
+        <div class="stat-item" style="padding-left: 32px;">
+          <div class="stat-number">100<span class="red">+</span></div>
+          <div class="stat-label">Articles</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- DIVIDER -->
+    <div class="divider"></div>
+
+    <!-- RIGHT -->
+    <div class="hero-right">
+      <div class="corner-mark">Portfolio 2026</div>
+      <div class="portrait-wrap">
+        <div class="portrait-circle"></div>
+        <div class="portrait-frame"></div>
+      </div>
+      <div class="role-tags">
+        <span>Developer</span>
+        <span>Writer</span>
+        <span>Creator</span>
+      </div>
+      <div class="index-marker">
+        <div class="idx-num">01</div>
+        <div class="idx-label">Hero</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- SCROLL CTA -->
+  <div class="scroll-cta">
+    <span>Scroll</span>
+    <div class="arrow-down"></div>
+  </div>
+
+</body>
+</html>

BIN
assets/showcases/website-homepage/homepage-pentagram.png


+ 459 - 0
assets/showcases/website-homepage/homepage-takram.html

@@ -0,0 +1,459 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Alex Chen — Indie Developer & AI Creator</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&family=Noto+Serif+SC:wght@300;400;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    background: #F5F0EB;
+    font-family: 'Inter', sans-serif;
+    color: #3D3D3A;
+    position: relative;
+  }
+
+  /* PAPER TEXTURE */
+  body::before {
+    content: '';
+    position: absolute;
+    top: 0; left: 0;
+    width: 100%; height: 100%;
+    background:
+      repeating-linear-gradient(
+        0deg,
+        transparent,
+        transparent 2px,
+        rgba(0,0,0,0.008) 2px,
+        rgba(0,0,0,0.008) 4px
+      ),
+      repeating-linear-gradient(
+        90deg,
+        transparent,
+        transparent 2px,
+        rgba(0,0,0,0.005) 2px,
+        rgba(0,0,0,0.005) 4px
+      );
+    pointer-events: none;
+    z-index: 1;
+  }
+
+  /* NAV */
+  nav {
+    position: absolute;
+    top: 0; left: 0; right: 0;
+    height: 72px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    z-index: 10;
+  }
+  nav .logo {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 400;
+    font-size: 16px;
+    color: #3D3D3A;
+    letter-spacing: 0.02em;
+  }
+  nav ul {
+    list-style: none;
+    display: flex;
+    gap: 36px;
+    align-items: center;
+  }
+  nav ul li a {
+    font-weight: 400;
+    font-size: 13px;
+    color: #8A8A84;
+    text-decoration: none;
+    letter-spacing: 0.01em;
+    transition: color 0.3s;
+  }
+  nav ul li a:hover { color: #3D3D3A; }
+  nav ul li.active a { color: #3D3D3A; }
+
+  /* Hairline below nav */
+  nav::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 48px;
+    height: 1px;
+    background: #C8C2B6;
+  }
+
+  /* MAIN LAYOUT - ASYMMETRIC */
+  .hero {
+    position: absolute;
+    top: 72px;
+    left: 0;
+    width: 1440px;
+    height: 828px;
+    display: grid;
+    grid-template-columns: 120px 1fr 400px 120px;
+    grid-template-rows: 1fr;
+    align-items: center;
+    z-index: 2;
+  }
+
+  /* LEFT MARGIN ELEMENT */
+  .margin-left {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: flex-end;
+    padding-bottom: 80px;
+    height: 100%;
+  }
+  .margin-left .vertical-text {
+    writing-mode: vertical-rl;
+    font-size: 10px;
+    letter-spacing: 0.18em;
+    color: #B8B2A6;
+    font-weight: 300;
+  }
+
+  /* CENTER CONTENT */
+  .hero-center {
+    padding: 0 40px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+
+  .hero-center .section-label {
+    font-size: 10px;
+    letter-spacing: 0.16em;
+    text-transform: uppercase;
+    color: #6B8F71;
+    font-weight: 500;
+    margin-bottom: 32px;
+    opacity: 0.8;
+  }
+
+  .hero-center h1 {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 300;
+    font-size: 56px;
+    line-height: 1.25;
+    letter-spacing: -0.01em;
+    color: #2D3436;
+  }
+  .hero-center h1 .serif-accent {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 600;
+    font-style: normal;
+    color: #2D3436;
+  }
+
+  /* HAIRLINE DIVIDER */
+  .hairline {
+    width: 48px;
+    height: 1px;
+    background: #C8C2B6;
+    margin: 36px 0;
+  }
+
+  .hero-center .tagline {
+    font-weight: 300;
+    font-size: 16px;
+    line-height: 1.8;
+    color: #8A8A84;
+    max-width: 420px;
+  }
+
+  /* STATS - HORIZONTAL */
+  .stats {
+    display: flex;
+    gap: 48px;
+    margin-top: 48px;
+  }
+  .stat {
+    display: flex;
+    flex-direction: column;
+  }
+  .stat .stat-value {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 600;
+    font-size: 28px;
+    color: #2D3436;
+    letter-spacing: -0.01em;
+    line-height: 1;
+  }
+  .stat .stat-desc {
+    font-size: 11px;
+    color: #B8B2A6;
+    margin-top: 8px;
+    letter-spacing: 0.04em;
+    font-weight: 400;
+  }
+
+  /* CTA */
+  .hero-cta {
+    margin-top: 48px;
+    display: flex;
+    gap: 20px;
+    align-items: center;
+  }
+  .cta-link {
+    font-weight: 500;
+    font-size: 13px;
+    color: #3D3D3A;
+    text-decoration: none;
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 0;
+    border-bottom: 1px solid #C8C2B6;
+    transition: all 0.3s;
+  }
+  .cta-link:hover { border-color: #6B8F71; color: #6B8F71; }
+  .cta-link svg { width: 14px; height: 14px; }
+
+  .cta-dot {
+    width: 4px;
+    height: 4px;
+    border-radius: 50%;
+    background: #C8C2B6;
+  }
+
+  .cta-subtle {
+    font-weight: 300;
+    font-size: 13px;
+    color: #B8B2A6;
+    text-decoration: none;
+    transition: color 0.3s;
+  }
+  .cta-subtle:hover { color: #3D3D3A; }
+
+  /* RIGHT PANEL */
+  .hero-right {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 0 20px;
+    position: relative;
+  }
+
+  /* PORTRAIT */
+  .portrait-container {
+    position: relative;
+  }
+  .portrait {
+    width: 180px;
+    height: 180px;
+    border-radius: 50%;
+    background: #EAE5DD;
+    overflow: hidden;
+    position: relative;
+    border: 1px solid rgba(200, 194, 182, 0.4);
+  }
+  .portrait::after {
+    content: '';
+    position: absolute;
+    bottom: 0; left: 50%;
+    transform: translateX(-50%);
+    width: 96px;
+    height: 110px;
+    background: #D5CEC4;
+    border-radius: 48px 48px 0 0;
+  }
+  .portrait-ring {
+    position: absolute;
+    top: -16px; left: -16px;
+    width: 212px;
+    height: 212px;
+    border-radius: 50%;
+    border: 1px solid rgba(107, 143, 113, 0.2);
+  }
+
+  /* BIO CARD */
+  .bio-card {
+    margin-top: 36px;
+    background: rgba(255,255,255,0.5);
+    border: 1px solid rgba(0,0,0,0.04);
+    border-radius: 12px;
+    padding: 24px 28px;
+    width: 260px;
+    backdrop-filter: blur(8px);
+  }
+  .bio-card .bio-name {
+    font-family: 'Noto Serif SC', serif;
+    font-weight: 600;
+    font-size: 15px;
+    color: #3D3D3A;
+  }
+  .bio-card .bio-role {
+    font-size: 12px;
+    color: #B8B2A6;
+    margin-top: 4px;
+    font-weight: 300;
+  }
+  .bio-card .bio-hairline {
+    width: 32px;
+    height: 1px;
+    background: #C8C2B6;
+    margin: 16px 0;
+  }
+  .bio-card .bio-desc {
+    font-size: 12px;
+    line-height: 1.7;
+    color: #8A8A84;
+    font-weight: 300;
+  }
+
+  /* RIGHT MARGIN */
+  .margin-right {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: flex-start;
+    padding-top: 80px;
+    height: 100%;
+  }
+  .margin-right .year {
+    writing-mode: vertical-rl;
+    font-size: 10px;
+    letter-spacing: 0.18em;
+    color: #C8C2B6;
+    font-weight: 300;
+  }
+
+  /* DECORATIVE: FLOATING LEAF/ORGANIC SHAPE */
+  .organic-shape {
+    position: absolute;
+    top: 140px;
+    right: 280px;
+    width: 60px;
+    height: 80px;
+    z-index: 3;
+    opacity: 0.08;
+  }
+  .organic-shape svg {
+    width: 100%;
+    height: 100%;
+  }
+
+  /* BOTTOM AREA */
+  .bottom-zen {
+    position: absolute;
+    bottom: 40px;
+    left: 50%;
+    transform: translateX(-50%);
+    display: flex;
+    align-items: center;
+    gap: 24px;
+    z-index: 5;
+  }
+  .bottom-zen .zen-line {
+    width: 48px;
+    height: 1px;
+    background: #C8C2B6;
+  }
+  .bottom-zen .zen-text {
+    font-size: 10px;
+    letter-spacing: 0.14em;
+    color: #C8C2B6;
+    font-weight: 300;
+  }
+</style>
+</head>
+<body>
+
+  <!-- NAV -->
+  <nav>
+    <div class="logo">Alex Chen</div>
+    <ul>
+      <li class="active"><a href="#work">Work</a></li>
+      <li><a href="#content">Content</a></li>
+      <li><a href="#services">Services</a></li>
+      <li><a href="#contact">Contact</a></li>
+    </ul>
+  </nav>
+
+  <!-- DECORATIVE: Subtle spec annotation -->
+  <svg style="position:absolute;top:100px;right:340px;z-index:3;opacity:0.15;" width="60" height="60" viewBox="0 0 60 60" fill="none">
+    <circle cx="30" cy="30" r="28" stroke="#6B8F71" stroke-width="0.5"/>
+    <circle cx="30" cy="30" r="18" stroke="#6B8F71" stroke-width="0.5" stroke-dasharray="2,4"/>
+    <line x1="30" y1="0" x2="30" y2="60" stroke="#6B8F71" stroke-width="0.3"/>
+    <line x1="0" y1="30" x2="60" y2="30" stroke="#6B8F71" stroke-width="0.3"/>
+  </svg>
+
+  <!-- HERO -->
+  <div class="hero">
+    <!-- LEFT MARGIN -->
+    <div class="margin-left">
+      <div class="vertical-text">PORTFOLIO</div>
+    </div>
+
+    <!-- CENTER -->
+    <div class="hero-center">
+      <div class="section-label">Indie Developer & AI Creator</div>
+      <h1>Building tools<br>at the intersection<br>of <span class="serif-accent">AI</span> and <span class="serif-accent">creativity</span></h1>
+      <div class="hairline"></div>
+      <p class="tagline">I design, build, and write about the things that emerge when technology meets human imagination.</p>
+
+      <div class="stats">
+        <div class="stat">
+          <div class="stat-value">300K+</div>
+          <div class="stat-desc">followers</div>
+        </div>
+        <div class="stat">
+          <div class="stat-value">No. 1</div>
+          <div class="stat-desc">App Store</div>
+        </div>
+        <div class="stat">
+          <div class="stat-value">100+</div>
+          <div class="stat-desc">articles published</div>
+        </div>
+      </div>
+
+      <div class="hero-cta">
+        <a href="#work" class="cta-link">
+          Explore Work
+          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
+        </a>
+        <div class="cta-dot"></div>
+        <a href="#content" class="cta-subtle">Read Writing</a>
+      </div>
+    </div>
+
+    <!-- RIGHT -->
+    <div class="hero-right">
+      <div class="portrait-container">
+        <div class="portrait"></div>
+        <div class="portrait-ring"></div>
+      </div>
+      <div class="bio-card">
+        <div class="bio-name">Alex Chen</div>
+        <div class="bio-role">Developer / Writer / Creator</div>
+        <div class="bio-hairline"></div>
+        <div class="bio-desc">Shipping AI-powered products as an independent maker. Writing about the craft of building.</div>
+      </div>
+    </div>
+
+    <!-- RIGHT MARGIN -->
+    <div class="margin-right">
+      <div class="year">2026</div>
+    </div>
+  </div>
+
+  <!-- BOTTOM ZEN -->
+  <div class="bottom-zen">
+    <div class="zen-line"></div>
+    <div class="zen-text">Design as inquiry</div>
+    <div class="zen-line"></div>
+  </div>
+
+</body>
+</html>

BIN
assets/showcases/website-homepage/homepage-takram.png


+ 493 - 0
assets/showcases/website-saas/saas-build.html

@@ -0,0 +1,493 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Meridian — Business Intelligence for Modern Teams</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Inter', sans-serif;
+    background: #FAFAF8;
+    color: #1a1a1a;
+  }
+
+  /* NAV */
+  nav {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 96px;
+    height: 80px;
+  }
+  .nav-logo {
+    font-size: 20px;
+    font-weight: 500;
+    letter-spacing: -0.3px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    color: #1a1a1a;
+  }
+  .nav-logo-icon {
+    width: 32px;
+    height: 32px;
+    background: #E8E4DF;
+    border-radius: 2px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .nav-logo-icon svg { width: 18px; height: 18px; color: #D4A574; }
+  .nav-links {
+    display: flex;
+    gap: 40px;
+    list-style: none;
+  }
+  .nav-links a {
+    font-size: 14px;
+    font-weight: 400;
+    text-decoration: none;
+    color: #777;
+    transition: color 0.2s;
+  }
+  .nav-links a:hover { color: #1a1a1a; }
+  .nav-right {
+    display: flex;
+    align-items: center;
+    gap: 24px;
+  }
+  .nav-signin {
+    font-size: 14px;
+    font-weight: 400;
+    text-decoration: none;
+    color: #777;
+    transition: color 0.2s;
+  }
+  .nav-signin:hover { color: #1a1a1a; }
+  .nav-cta {
+    font-size: 13px;
+    font-weight: 400;
+    padding: 10px 24px;
+    background: #1a1a1a;
+    color: #FAFAF8;
+    border: none;
+    border-radius: 2px;
+    cursor: pointer;
+    transition: background 0.2s;
+  }
+  .nav-cta:hover { background: #333; }
+
+  /* HERO LAYOUT */
+  .hero {
+    padding: 24px 96px 0 96px;
+    display: grid;
+    grid-template-columns: 480px 1fr;
+    gap: 64px;
+    align-items: start;
+    height: calc(900px - 80px);
+    position: relative;
+  }
+
+  /* LEFT TEXT */
+  .hero-text {
+    padding-top: 48px;
+  }
+  .hero-badge {
+    display: inline-flex;
+    align-items: center;
+    gap: 0;
+    padding: 0;
+    background: transparent;
+    margin-bottom: 32px;
+  }
+  .hero-badge-dot {
+    display: none;
+  }
+  .hero-badge span {
+    font-size: 10px;
+    font-weight: 400;
+    color: #B0ACA4;
+    letter-spacing: 4px;
+    text-transform: uppercase;
+  }
+  .hero-headline {
+    font-size: 48px;
+    font-weight: 300;
+    line-height: 1.15;
+    letter-spacing: -1.5px;
+    margin-bottom: 24px;
+    color: #1a1a1a;
+  }
+  .hero-headline em {
+    font-style: italic;
+    font-weight: 300;
+    color: #1a1a1a;
+  }
+  .hero-subtitle {
+    font-size: 17px;
+    font-weight: 300;
+    line-height: 1.7;
+    color: #888;
+    margin-bottom: 48px;
+    max-width: 400px;
+  }
+  .hero-ctas {
+    display: flex;
+    gap: 16px;
+    margin-bottom: 64px;
+  }
+  .btn-primary {
+    font-family: 'Inter', sans-serif;
+    font-size: 14px;
+    font-weight: 400;
+    padding: 14px 32px;
+    background: #1a1a1a;
+    color: #FAFAF8;
+    border: none;
+    border-radius: 2px;
+    cursor: pointer;
+    transition: all 0.2s;
+  }
+  .btn-primary:hover { background: #333; }
+  .btn-secondary {
+    font-family: 'Inter', sans-serif;
+    font-size: 14px;
+    font-weight: 400;
+    padding: 14px 32px;
+    background: transparent;
+    color: #777;
+    border: 1px solid #ddd;
+    border-radius: 2px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    transition: all 0.2s;
+  }
+  .btn-secondary:hover { border-color: #aaa; color: #1a1a1a; }
+  .btn-secondary svg { width: 15px; height: 15px; }
+
+  /* METRICS ROW */
+  .metrics {
+    display: flex;
+    gap: 48px;
+  }
+  .metric {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
+  .metric-value {
+    font-size: 36px;
+    font-weight: 200;
+    letter-spacing: -1px;
+    color: #1a1a1a;
+  }
+  .metric-value span { color: #D4A574; }
+  .metric-label {
+    font-size: 12px;
+    font-weight: 400;
+    color: #aaa;
+    letter-spacing: 0.3px;
+  }
+
+  /* RIGHT — DASHBOARD */
+  .hero-dashboard {
+    position: relative;
+    padding-top: 16px;
+  }
+  .dashboard-card {
+    background: #FFFFFF;
+    border-radius: 2px;
+    box-shadow:
+      0 1px 2px rgba(0,0,0,0.02),
+      0 4px 16px rgba(0,0,0,0.04);
+    padding: 28px;
+    width: 100%;
+  }
+
+  /* Dashboard header */
+  .dash-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+  }
+  .dash-title {
+    font-size: 14px;
+    font-weight: 500;
+    color: #1a1a1a;
+  }
+  .dash-period {
+    font-size: 12px;
+    font-weight: 400;
+    color: #aaa;
+    padding: 4px 12px;
+    border: 1px solid #eee;
+    border-radius: 2px;
+  }
+
+  /* KPI strip */
+  .kpi-strip {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 1px;
+    background: #f0eeeb;
+    border-radius: 2px;
+    overflow: hidden;
+    margin-bottom: 24px;
+  }
+  .kpi-item {
+    background: #FAFAF8;
+    padding: 18px 16px;
+    text-align: center;
+  }
+  .kpi-item-value {
+    font-size: 22px;
+    font-weight: 300;
+    color: #1a1a1a;
+    letter-spacing: -0.5px;
+    margin-bottom: 4px;
+  }
+  .kpi-item-label {
+    font-size: 10px;
+    font-weight: 500;
+    color: #bbb;
+    letter-spacing: 0.5px;
+    text-transform: uppercase;
+  }
+
+  /* Chart */
+  .chart-container {
+    margin-bottom: 24px;
+    position: relative;
+    height: 200px;
+  }
+  .chart-svg {
+    width: 100%;
+    height: 100%;
+  }
+
+  /* Bottom section */
+  .dash-bottom-row {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 16px;
+  }
+  .insight-card {
+    background: #FAFAF8;
+    border-radius: 2px;
+    padding: 16px;
+  }
+  .insight-icon {
+    width: 28px;
+    height: 28px;
+    background: #F0EBE3;
+    border-radius: 2px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 8px;
+  }
+  .insight-icon svg { width: 14px; height: 14px; color: #D4A574; }
+  .insight-title {
+    font-size: 12px;
+    font-weight: 500;
+    color: #1a1a1a;
+    margin-bottom: 4px;
+  }
+  .insight-desc {
+    font-size: 11px;
+    font-weight: 300;
+    color: #999;
+    line-height: 1.5;
+  }
+
+  /* TRUST BAR */
+  .trust-bar {
+    position: absolute;
+    bottom: 24px;
+    left: 96px;
+    display: flex;
+    align-items: center;
+    gap: 40px;
+  }
+  .trust-label {
+    font-size: 11px;
+    font-weight: 400;
+    color: #ccc;
+    white-space: nowrap;
+  }
+  .trust-logos {
+    display: flex;
+    gap: 40px;
+    align-items: center;
+  }
+  .trust-logo {
+    font-size: 14px;
+    font-weight: 400;
+    color: #ccc;
+    letter-spacing: 0.3px;
+  }
+</style>
+</head>
+<body>
+
+<!-- NAV -->
+<nav>
+  <div class="nav-logo">
+    <div class="nav-logo-icon">
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/>
+      </svg>
+    </div>
+    Meridian
+  </div>
+  <ul class="nav-links">
+    <li><a href="#">Product</a></li>
+    <li><a href="#">Pricing</a></li>
+    <li><a href="#">Docs</a></li>
+    <li><a href="#">Blog</a></li>
+  </ul>
+  <div class="nav-right">
+    <a href="#" class="nav-signin">Sign In</a>
+    <button class="nav-cta">Get Started</button>
+  </div>
+</nav>
+
+<!-- HERO -->
+<div class="hero">
+
+  <!-- LEFT -->
+  <div class="hero-text">
+    <div class="hero-badge">
+      <div class="hero-badge-dot"></div>
+      <span>Business Intelligence for Modern Teams</span>
+    </div>
+    <h1 class="hero-headline">Turn data into <em>decisions,</em> not dashboards<span style="color:#D4A574;font-weight:300;">.</span></h1>
+    <p class="hero-subtitle">AI-powered analytics that tells you what matters, when it matters. Less noise, more clarity.</p>
+    <div class="hero-ctas">
+      <button class="btn-primary">Start Free Trial</button>
+      <button class="btn-secondary">
+        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
+        Watch Demo
+      </button>
+    </div>
+    <div class="metrics">
+      <div class="metric">
+        <div class="metric-value">3<span>x</span></div>
+        <div class="metric-label">Faster insights</div>
+      </div>
+      <div class="metric">
+        <div class="metric-value">50<span>%</span></div>
+        <div class="metric-label">Less meeting time</div>
+      </div>
+      <div class="metric">
+        <div class="metric-value">99.9<span>%</span></div>
+        <div class="metric-label">Uptime SLA</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- RIGHT — FLOATING DASHBOARD -->
+  <div class="hero-dashboard">
+    <div class="dashboard-card">
+      <div class="dash-header">
+        <div class="dash-title">Performance Overview</div>
+        <div class="dash-period">Last 30 days</div>
+      </div>
+
+      <div class="kpi-strip">
+        <div class="kpi-item">
+          <div class="kpi-item-value">$2.4M</div>
+          <div class="kpi-item-label">Revenue</div>
+        </div>
+        <div class="kpi-item">
+          <div class="kpi-item-value">84.2K</div>
+          <div class="kpi-item-label">Users</div>
+        </div>
+        <div class="kpi-item">
+          <div class="kpi-item-value">1.2%</div>
+          <div class="kpi-item-label">Churn</div>
+        </div>
+        <div class="kpi-item">
+          <div class="kpi-item-value">$142</div>
+          <div class="kpi-item-label">ARPU</div>
+        </div>
+      </div>
+
+      <!-- SVG Chart -->
+      <div class="chart-container">
+        <svg class="chart-svg" viewBox="0 0 700 200" preserveAspectRatio="none">
+          <!-- Grid lines -->
+          <line x1="0" y1="50" x2="700" y2="50" stroke="#f0eeeb" stroke-width="1"/>
+          <line x1="0" y1="100" x2="700" y2="100" stroke="#f0eeeb" stroke-width="1"/>
+          <line x1="0" y1="150" x2="700" y2="150" stroke="#f0eeeb" stroke-width="1"/>
+
+          <!-- Area fill -->
+          <defs>
+            <linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1">
+              <stop offset="0%" stop-color="#D4A574" stop-opacity="0.15"/>
+              <stop offset="100%" stop-color="#D4A574" stop-opacity="0.01"/>
+            </linearGradient>
+          </defs>
+          <path d="M0,160 C50,155 100,140 150,120 C200,100 250,110 300,85 C350,60 400,70 450,50 C500,30 550,45 600,35 C650,25 680,20 700,15 L700,200 L0,200 Z" fill="url(#areaGrad)"/>
+
+          <!-- Main line -->
+          <path d="M0,160 C50,155 100,140 150,120 C200,100 250,110 300,85 C350,60 400,70 450,50 C500,30 550,45 600,35 C650,25 680,20 700,15" fill="none" stroke="#D4A574" stroke-width="2.5" stroke-linecap="round"/>
+
+          <!-- Secondary line -->
+          <path d="M0,170 C50,165 100,158 150,150 C200,142 250,145 300,135 C350,125 400,128 450,118 C500,108 550,112 600,105 C650,98 680,95 700,90" fill="none" stroke="#e0d5c8" stroke-width="1.5" stroke-dasharray="4,4"/>
+
+          <!-- Data point -->
+          <circle cx="600" cy="35" r="5" fill="#D4A574"/>
+          <circle cx="600" cy="35" r="8" fill="none" stroke="#D4A574" stroke-width="1" opacity="0.4"/>
+        </svg>
+      </div>
+
+      <div class="dash-bottom-row">
+        <div class="insight-card">
+          <div class="insight-icon">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
+          </div>
+          <div class="insight-title">AI Insight: Revenue Acceleration</div>
+          <div class="insight-desc">Enterprise segment grew 23% this quarter, driven by 4 new accounts. Recommend increasing sales capacity.</div>
+        </div>
+        <div class="insight-card">
+          <div class="insight-icon">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
+          </div>
+          <div class="insight-title">Predicted: Q3 Target On Track</div>
+          <div class="insight-desc">Based on current trajectory, 89% probability of hitting $3.2M quarterly target. Pipeline looks healthy.</div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- TRUST BAR -->
+  <div class="trust-bar">
+    <span class="trust-label">Trusted by teams at</span>
+    <div class="trust-logos">
+      <span class="trust-logo">Stripe</span>
+      <span class="trust-logo">Notion</span>
+      <span class="trust-logo">Linear</span>
+      <span class="trust-logo">Vercel</span>
+      <span class="trust-logo">Figma</span>
+    </div>
+  </div>
+</div>
+
+<script>
+  document.addEventListener('DOMContentLoaded', function() {
+    lucide.createIcons();
+  });
+</script>
+</body>
+</html>

BIN
assets/showcases/website-saas/saas-build.png


+ 556 - 0
assets/showcases/website-saas/saas-pentagram.html

@@ -0,0 +1,556 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Meridian — Business Intelligence for Modern Teams</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    background: #FFFFFF;
+    color: #000000;
+  }
+
+  /* NAV */
+  nav {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    height: 72px;
+    border-bottom: 1px solid #000;
+  }
+  .nav-logo {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-weight: 700;
+    font-size: 22px;
+    letter-spacing: -0.5px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  .nav-logo svg { width: 24px; height: 24px; }
+  .nav-links {
+    display: flex;
+    gap: 40px;
+    list-style: none;
+  }
+  .nav-links a {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 13px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    text-decoration: none;
+    color: #000;
+    transition: color 0.2s;
+  }
+  .nav-links a:hover { color: #E63946; }
+  .nav-signin {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 13px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    text-decoration: none;
+    color: #000;
+    padding: 8px 20px;
+    border: 2px solid #000;
+    transition: all 0.2s;
+  }
+  .nav-signin:hover { background: #000; color: #fff; }
+
+  /* HERO */
+  .hero {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    height: calc(900px - 72px);
+  }
+
+  /* LEFT PANEL */
+  .hero-left {
+    padding: 64px 80px 48px 80px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    position: relative;
+  }
+  .hero-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 12px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 3px;
+    color: #E63946;
+    margin-bottom: 24px;
+  }
+  .hero-headline {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 52px;
+    font-weight: 900;
+    line-height: 1.05;
+    letter-spacing: -2px;
+    margin-bottom: 20px;
+    max-width: 520px;
+  }
+  .hero-headline span { color: #E63946; }
+  .hero-subtitle {
+    font-size: 17px;
+    font-weight: 400;
+    line-height: 1.6;
+    color: #444;
+    max-width: 440px;
+    margin-bottom: 36px;
+  }
+  .hero-ctas {
+    display: flex;
+    gap: 16px;
+    margin-bottom: 48px;
+  }
+  .btn-primary {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 14px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    padding: 16px 36px;
+    background: #E63946;
+    color: #fff;
+    border: none;
+    cursor: pointer;
+    transition: background 0.2s;
+  }
+  .btn-primary:hover { background: #c4303c; }
+  .btn-secondary {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 14px;
+    font-weight: 600;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    padding: 16px 36px;
+    background: transparent;
+    color: #000;
+    border: 2px solid #000;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    transition: all 0.2s;
+  }
+  .btn-secondary:hover { background: #000; color: #fff; }
+  .btn-secondary svg { width: 16px; height: 16px; }
+
+  /* BIG NUMBER */
+  .big-number {
+    position: absolute;
+    bottom: 64px;
+    left: 80px;
+    display: flex;
+    align-items: baseline;
+    gap: 40px;
+  }
+  .big-number-main {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 140px;
+    font-weight: 700;
+    line-height: 1;
+    letter-spacing: -6px;
+    color: #E63946;
+  }
+  .big-number-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 13px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    color: #666;
+    max-width: 100px;
+    line-height: 1.5;
+  }
+  .big-number-divider {
+    width: 1px;
+    height: 48px;
+    background: #ccc;
+  }
+  .metric-small {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
+  .metric-small-value {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 32px;
+    font-weight: 700;
+    letter-spacing: -1px;
+  }
+  .metric-small-label {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 11px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    color: #888;
+  }
+
+  /* RIGHT PANEL — DASHBOARD */
+  .hero-right {
+    background: #000;
+    padding: 32px;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    position: relative;
+  }
+
+  /* Dashboard grid */
+  .dash-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 4px;
+  }
+  .dash-title {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 13px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    color: #666;
+  }
+  .dash-live {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-size: 11px;
+    font-weight: 500;
+    color: #E63946;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+  }
+  .dash-live-dot {
+    width: 6px;
+    height: 6px;
+    background: #E63946;
+    border-radius: 50%;
+  }
+
+  /* KPI Row */
+  .kpi-row {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 12px;
+  }
+  .kpi-card {
+    background: #111;
+    border: 1px solid #222;
+    padding: 20px;
+  }
+  .kpi-label {
+    font-size: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    color: #555;
+    margin-bottom: 8px;
+  }
+  .kpi-value {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 28px;
+    font-weight: 700;
+    color: #fff;
+    letter-spacing: -1px;
+  }
+  .kpi-change {
+    font-size: 12px;
+    font-weight: 500;
+    color: #E63946;
+    margin-top: 4px;
+  }
+  .kpi-change.positive { color: #fff; opacity: 0.7; }
+
+  /* Chart area */
+  .chart-area {
+    flex: 1;
+    background: #111;
+    border: 1px solid #222;
+    padding: 24px;
+    display: flex;
+    flex-direction: column;
+  }
+  .chart-top {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+  }
+  .chart-label {
+    font-size: 11px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    color: #555;
+  }
+  .chart-tabs {
+    display: flex;
+    gap: 2px;
+  }
+  .chart-tab {
+    font-size: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    color: #444;
+    padding: 4px 12px;
+    background: transparent;
+    border: 1px solid #333;
+  }
+  .chart-tab.active {
+    color: #fff;
+    background: #E63946;
+    border-color: #E63946;
+  }
+  .chart-bars {
+    display: flex;
+    align-items: flex-end;
+    gap: 6px;
+    flex: 1;
+    padding-top: 12px;
+  }
+  .chart-bar-group {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 8px;
+  }
+  .chart-bar {
+    width: 100%;
+    background: #222;
+    position: relative;
+  }
+  .chart-bar.accent { background: #E63946; }
+  .chart-bar-label {
+    font-size: 9px;
+    color: #444;
+    font-weight: 500;
+    letter-spacing: 0.5px;
+  }
+
+  /* Bottom row */
+  .dash-bottom {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 12px;
+  }
+  .data-table {
+    background: #111;
+    border: 1px solid #222;
+    padding: 16px;
+  }
+  .data-table-title {
+    font-size: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1.5px;
+    color: #555;
+    margin-bottom: 12px;
+  }
+  .data-row {
+    display: flex;
+    justify-content: space-between;
+    padding: 6px 0;
+    border-bottom: 1px solid #1a1a1a;
+  }
+  .data-row:last-child { border-bottom: none; }
+  .data-row-label {
+    font-size: 12px;
+    color: #888;
+  }
+  .data-row-value {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 12px;
+    font-weight: 600;
+    color: #fff;
+  }
+  .data-row-value.red { color: #E63946; }
+
+  /* TRUST BAR */
+  .trust-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    border-top: 1px solid #e0e0e0;
+    padding: 0 80px;
+    height: 56px;
+    display: flex;
+    align-items: center;
+    gap: 48px;
+    background: #fff;
+  }
+  .trust-label {
+    font-size: 11px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    color: #aaa;
+    white-space: nowrap;
+  }
+  .trust-logos {
+    display: flex;
+    gap: 48px;
+    align-items: center;
+  }
+  .trust-logo {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    font-size: 15px;
+    font-weight: 600;
+    color: #bbb;
+    letter-spacing: 1px;
+  }
+</style>
+</head>
+<body>
+
+<!-- NAVIGATION -->
+<nav>
+  <div class="nav-logo">
+    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
+      <polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/>
+      <line x1="12" y1="22" x2="12" y2="15.5"/>
+      <polyline points="22 8.5 12 15.5 2 8.5"/>
+    </svg>
+    Meridian
+  </div>
+  <ul class="nav-links">
+    <li><a href="#">Product</a></li>
+    <li><a href="#">Pricing</a></li>
+    <li><a href="#">Docs</a></li>
+    <li><a href="#">Blog</a></li>
+  </ul>
+  <a href="#" class="nav-signin">Sign In</a>
+</nav>
+
+<!-- HERO -->
+<div class="hero">
+
+  <!-- LEFT -->
+  <div class="hero-left">
+    <div class="hero-label">Business Intelligence for Modern Teams</div>
+    <h1 class="hero-headline">Turn data into <span>decisions,</span> not dashboards</h1>
+    <p class="hero-subtitle">AI-powered analytics that tells you what matters, when it matters. Stop drowning in charts and start acting on real insights.</p>
+    <div class="hero-ctas">
+      <button class="btn-primary">Start Free Trial</button>
+      <button class="btn-secondary">
+        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
+        Watch Demo
+      </button>
+    </div>
+    <div class="big-number">
+      <div class="big-number-main">3x</div>
+      <div class="big-number-label">Faster Insights</div>
+      <div class="big-number-divider"></div>
+      <div class="metric-small">
+        <div class="metric-small-value">50%</div>
+        <div class="metric-small-label">Less Meeting Time</div>
+      </div>
+      <div class="big-number-divider"></div>
+      <div class="metric-small">
+        <div class="metric-small-value">99.9%</div>
+        <div class="metric-small-label">Uptime</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- RIGHT — DASHBOARD MOCKUP -->
+  <div class="hero-right">
+    <div class="dash-header">
+      <div class="dash-title">Analytics Overview</div>
+      <div class="dash-live"><div class="dash-live-dot"></div> Live</div>
+    </div>
+
+    <div class="kpi-row">
+      <div class="kpi-card">
+        <div class="kpi-label">Revenue</div>
+        <div class="kpi-value">$2.4M</div>
+        <div class="kpi-change positive">+12.3%</div>
+      </div>
+      <div class="kpi-card">
+        <div class="kpi-label">Active Users</div>
+        <div class="kpi-value">84.2K</div>
+        <div class="kpi-change positive">+8.7%</div>
+      </div>
+      <div class="kpi-card">
+        <div class="kpi-label">Churn Rate</div>
+        <div class="kpi-value">1.2%</div>
+        <div class="kpi-change red">-0.3pp</div>
+      </div>
+    </div>
+
+    <div class="chart-area">
+      <div class="chart-top">
+        <div class="chart-label">Monthly Performance</div>
+        <div class="chart-tabs">
+          <div class="chart-tab">7D</div>
+          <div class="chart-tab active">30D</div>
+          <div class="chart-tab">90D</div>
+        </div>
+      </div>
+      <div class="chart-bars">
+        <div class="chart-bar-group"><div class="chart-bar" style="height:60px"></div><div class="chart-bar-label">Jan</div></div>
+        <div class="chart-bar-group"><div class="chart-bar" style="height:80px"></div><div class="chart-bar-label">Feb</div></div>
+        <div class="chart-bar-group"><div class="chart-bar" style="height:55px"></div><div class="chart-bar-label">Mar</div></div>
+        <div class="chart-bar-group"><div class="chart-bar accent" style="height:110px"></div><div class="chart-bar-label">Apr</div></div>
+        <div class="chart-bar-group"><div class="chart-bar" style="height:95px"></div><div class="chart-bar-label">May</div></div>
+        <div class="chart-bar-group"><div class="chart-bar accent" style="height:130px"></div><div class="chart-bar-label">Jun</div></div>
+        <div class="chart-bar-group"><div class="chart-bar" style="height:105px"></div><div class="chart-bar-label">Jul</div></div>
+        <div class="chart-bar-group"><div class="chart-bar accent" style="height:145px"></div><div class="chart-bar-label">Aug</div></div>
+        <div class="chart-bar-group"><div class="chart-bar" style="height:120px"></div><div class="chart-bar-label">Sep</div></div>
+        <div class="chart-bar-group"><div class="chart-bar" style="height:100px"></div><div class="chart-bar-label">Oct</div></div>
+      </div>
+    </div>
+
+    <div class="dash-bottom">
+      <div class="data-table">
+        <div class="data-table-title">Top Segments</div>
+        <div class="data-row"><span class="data-row-label">Enterprise</span><span class="data-row-value">$1.1M</span></div>
+        <div class="data-row"><span class="data-row-label">Mid-Market</span><span class="data-row-value">$820K</span></div>
+        <div class="data-row"><span class="data-row-label">SMB</span><span class="data-row-value">$480K</span></div>
+      </div>
+      <div class="data-table">
+        <div class="data-table-title">AI Alerts Today</div>
+        <div class="data-row"><span class="data-row-label">Revenue spike detected</span><span class="data-row-value red">High</span></div>
+        <div class="data-row"><span class="data-row-label">Churn risk: Acme Corp</span><span class="data-row-value red">Med</span></div>
+        <div class="data-row"><span class="data-row-label">Expansion signal: Bolt</span><span class="data-row-value" style="color:#888">Low</span></div>
+      </div>
+    </div>
+  </div>
+
+</div>
+
+<!-- TRUST BAR (absolutely positioned at bottom-left) -->
+<div class="trust-bar" style="position:fixed; bottom:0; left:0; width:720px; z-index:10;">
+  <span class="trust-label">Trusted by</span>
+  <div class="trust-logos">
+    <span class="trust-logo">Stripe</span>
+    <span class="trust-logo">Notion</span>
+    <span class="trust-logo">Linear</span>
+    <span class="trust-logo">Vercel</span>
+    <span class="trust-logo">Figma</span>
+  </div>
+</div>
+
+<script>
+  document.addEventListener('DOMContentLoaded', function() {
+    lucide.createIcons();
+  });
+</script>
+</body>
+</html>

BIN
assets/showcases/website-saas/saas-pentagram.png


+ 604 - 0
assets/showcases/website-saas/saas-takram.html

@@ -0,0 +1,604 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=1440">
+<title>Meridian — Business Intelligence for Modern Teams</title>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Noto+Serif+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
+<script src="https://unpkg.com/lucide@latest"></script>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    width: 1440px;
+    height: 900px;
+    overflow: hidden;
+    margin: 0;
+    font-family: 'Inter', sans-serif;
+    background: #F5F0EB;
+    color: #3a3a3a;
+  }
+
+  /* NAV */
+  nav {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 80px;
+    height: 72px;
+  }
+  .nav-logo {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 20px;
+    font-weight: 500;
+    letter-spacing: -0.3px;
+    color: #3a3a3a;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+  }
+  .nav-logo-mark {
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    background: #6B8F71;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .nav-logo-mark svg { width: 14px; height: 14px; color: #F5F0EB; }
+  .nav-center {
+    display: flex;
+    gap: 40px;
+    list-style: none;
+  }
+  .nav-center a {
+    font-size: 14px;
+    font-weight: 400;
+    text-decoration: none;
+    color: #888;
+    transition: color 0.2s;
+  }
+  .nav-center a:hover { color: #3a3a3a; }
+  .nav-right {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+  }
+  .nav-signin {
+    font-size: 14px;
+    font-weight: 400;
+    text-decoration: none;
+    color: #888;
+  }
+  .nav-cta {
+    font-family: 'Inter', sans-serif;
+    font-size: 13px;
+    font-weight: 500;
+    padding: 9px 22px;
+    background: #2D3436;
+    color: #F5F0EB;
+    border: none;
+    border-radius: 100px;
+    cursor: pointer;
+    transition: background 0.2s;
+  }
+  .nav-cta:hover { background: #3D4547; }
+
+  /* HERO */
+  .hero {
+    padding: 20px 80px 0 80px;
+    height: calc(900px - 72px);
+    display: flex;
+    flex-direction: column;
+  }
+
+  /* TOP SECTION: text + dashboard side by side */
+  .hero-top {
+    display: grid;
+    grid-template-columns: 500px 1fr;
+    gap: 60px;
+    flex: 1;
+  }
+
+  /* LEFT TEXT */
+  .hero-text {
+    padding-top: 32px;
+    display: flex;
+    flex-direction: column;
+  }
+  .hero-label {
+    font-size: 12px;
+    font-weight: 500;
+    color: #6B8F71;
+    letter-spacing: 1px;
+    margin-bottom: 20px;
+  }
+  .hero-headline {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 44px;
+    font-weight: 400;
+    line-height: 1.25;
+    letter-spacing: -0.5px;
+    color: #2a2a2a;
+    margin-bottom: 16px;
+  }
+  .hero-headline em {
+    font-style: italic;
+    color: #7A8F71;
+  }
+  .hero-subtitle {
+    font-size: 16px;
+    font-weight: 300;
+    line-height: 1.7;
+    color: #999;
+    margin-bottom: 32px;
+    max-width: 400px;
+  }
+  .hero-ctas {
+    display: flex;
+    gap: 12px;
+    margin-bottom: 36px;
+  }
+  .btn-primary {
+    font-family: 'Inter', sans-serif;
+    font-size: 14px;
+    font-weight: 500;
+    padding: 14px 28px;
+    background: rgba(107, 143, 113, 0.12);
+    color: #6B8F71;
+    border: 1px solid rgba(107, 143, 113, 0.3);
+    border-radius: 100px;
+    cursor: pointer;
+    transition: all 0.2s;
+  }
+  .btn-primary:hover { background: rgba(107, 143, 113, 0.18); }
+  .btn-secondary {
+    font-family: 'Inter', sans-serif;
+    font-size: 14px;
+    font-weight: 400;
+    padding: 14px 28px;
+    background: transparent;
+    color: #888;
+    border: 1px solid #d5cfc5;
+    border-radius: 100px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    transition: all 0.2s;
+  }
+  .btn-secondary:hover { border-color: #aaa; color: #555; }
+  .btn-secondary svg { width: 14px; height: 14px; }
+
+  /* FLOW DIAGRAM */
+  .flow-diagram {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    margin-bottom: 32px;
+  }
+  .flow-step {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    background: rgba(107, 143, 113, 0.1);
+    border: 1px solid rgba(107, 143, 113, 0.25);
+    border-radius: 100px;
+    padding: 8px 18px;
+  }
+  .flow-step-icon {
+    width: 24px;
+    height: 24px;
+    background: #6B8F71;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .flow-step-icon svg { width: 12px; height: 12px; color: #fff; }
+  .flow-step span {
+    font-size: 12px;
+    font-weight: 500;
+    color: #666;
+  }
+  .flow-arrow {
+    width: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #ccc;
+  }
+  .flow-arrow svg { width: 16px; height: 16px; }
+
+  /* METRICS ROW */
+  .metrics-row {
+    display: flex;
+    gap: 40px;
+  }
+  .metric-card {
+    background: #fff;
+    border-radius: 16px;
+    padding: 20px 24px;
+    min-width: 130px;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.03);
+  }
+  .metric-card-value {
+    font-family: 'Noto Serif SC', serif;
+    font-size: 28px;
+    font-weight: 400;
+    color: #2a2a2a;
+    letter-spacing: -0.5px;
+    margin-bottom: 4px;
+  }
+  .metric-card-value span { color: #6B8F71; }
+  .metric-card-label {
+    font-size: 11px;
+    font-weight: 400;
+    color: #bbb;
+  }
+
+  /* RIGHT — DASHBOARD */
+  .hero-dashboard {
+    padding-top: 8px;
+  }
+  .dashboard-frame {
+    background: #FFFFFF;
+    border-radius: 24px;
+    box-shadow:
+      0 1px 2px rgba(0,0,0,0.02),
+      0 4px 12px rgba(0,0,0,0.03),
+      0 16px 48px rgba(0,0,0,0.05);
+    padding: 24px;
+    height: 480px;
+    display: flex;
+    flex-direction: column;
+  }
+
+  /* Dash header */
+  .dash-head {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+  }
+  .dash-head-title {
+    font-size: 14px;
+    font-weight: 500;
+    color: #3a3a3a;
+  }
+  .dash-head-tag {
+    font-size: 11px;
+    font-weight: 400;
+    color: #6B8F71;
+    background: rgba(107, 143, 113, 0.1);
+    padding: 4px 12px;
+    border-radius: 100px;
+  }
+
+  /* KPI row */
+  .dash-kpis {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 12px;
+    margin-bottom: 20px;
+  }
+  .dash-kpi {
+    background: #FAFAF6;
+    border-radius: 14px;
+    padding: 16px;
+    text-align: center;
+  }
+  .dash-kpi-value {
+    font-size: 22px;
+    font-weight: 500;
+    color: #2a2a2a;
+    margin-bottom: 2px;
+  }
+  .dash-kpi-label {
+    font-size: 10px;
+    font-weight: 400;
+    color: #bbb;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+  }
+  .dash-kpi-change {
+    font-size: 11px;
+    font-weight: 500;
+    color: #6B8F71;
+    margin-top: 4px;
+  }
+
+  /* Chart area */
+  .dash-chart {
+    flex: 1;
+    display: grid;
+    grid-template-columns: 2fr 1fr;
+    gap: 12px;
+  }
+  .chart-main {
+    background: #FAFAF6;
+    border-radius: 16px;
+    padding: 20px;
+    display: flex;
+    flex-direction: column;
+  }
+  .chart-main-label {
+    font-size: 11px;
+    font-weight: 500;
+    color: #aaa;
+    margin-bottom: 12px;
+  }
+  .chart-main-svg {
+    flex: 1;
+  }
+
+  /* Side panel */
+  .chart-side {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+  }
+  .insight-bubble {
+    background: #FAFAF6;
+    border-radius: 16px;
+    padding: 14px 16px;
+    flex: 1;
+  }
+  .insight-bubble-header {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    margin-bottom: 8px;
+  }
+  .insight-bubble-icon {
+    width: 20px;
+    height: 20px;
+    background: rgba(107, 143, 113, 0.2);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .insight-bubble-icon svg { width: 10px; height: 10px; color: #7A8F71; }
+  .insight-bubble-tag {
+    font-size: 10px;
+    font-weight: 500;
+    color: #6B8F71;
+  }
+  .insight-bubble-text {
+    font-size: 11px;
+    font-weight: 400;
+    color: #888;
+    line-height: 1.5;
+  }
+
+  /* TRUST BAR */
+  .trust-bar {
+    padding: 16px 0;
+    display: flex;
+    align-items: center;
+    gap: 40px;
+    border-top: 1px solid #e8e2d8;
+  }
+  .trust-label {
+    font-size: 11px;
+    font-weight: 400;
+    color: #ccc;
+    white-space: nowrap;
+  }
+  .trust-logos {
+    display: flex;
+    gap: 36px;
+    align-items: center;
+  }
+  .trust-logo {
+    font-size: 14px;
+    font-weight: 400;
+    color: #ccc;
+  }
+</style>
+</head>
+<body>
+
+<!-- NAV -->
+<nav>
+  <div class="nav-logo">
+    <div class="nav-logo-mark">
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
+        <polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/>
+      </svg>
+    </div>
+    Meridian
+  </div>
+  <ul class="nav-center">
+    <li><a href="#">Product</a></li>
+    <li><a href="#">Pricing</a></li>
+    <li><a href="#">Docs</a></li>
+    <li><a href="#">Blog</a></li>
+  </ul>
+  <div class="nav-right">
+    <a href="#" class="nav-signin">Sign In</a>
+    <button class="nav-cta">Start Free Trial</button>
+  </div>
+</nav>
+
+<!-- HERO -->
+<div class="hero">
+  <div class="hero-top">
+
+    <!-- LEFT TEXT -->
+    <div class="hero-text">
+      <div class="hero-label">Business Intelligence for Modern Teams</div>
+      <h1 class="hero-headline">Turn data into <em>decisions,</em> not dashboards</h1>
+      <p class="hero-subtitle">AI-powered analytics that tells you what matters, when it matters. Clarity over complexity.</p>
+      <div class="hero-ctas">
+        <button class="btn-primary">Start Free Trial</button>
+        <button class="btn-secondary">
+          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
+          Watch Demo
+        </button>
+      </div>
+
+      <!-- Flow diagram -->
+      <div class="flow-diagram">
+        <div class="flow-step">
+          <div class="flow-step-icon">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
+          </div>
+          <span>Raw Data</span>
+        </div>
+        <div class="flow-arrow">
+          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
+        </div>
+        <div class="flow-step">
+          <div class="flow-step-icon">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
+          </div>
+          <span>AI Analysis</span>
+        </div>
+        <div class="flow-arrow">
+          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
+        </div>
+        <div class="flow-step">
+          <div class="flow-step-icon">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
+          </div>
+          <span>Actionable Insight</span>
+        </div>
+      </div>
+
+      <!-- Metrics -->
+      <div class="metrics-row">
+        <div class="metric-card">
+          <div class="metric-card-value">3<span>x</span></div>
+          <div class="metric-card-label">Faster insights</div>
+        </div>
+        <div class="metric-card">
+          <div class="metric-card-value">50<span>%</span></div>
+          <div class="metric-card-label">Less meeting time</div>
+        </div>
+        <div class="metric-card">
+          <div class="metric-card-value">99.9<span>%</span></div>
+          <div class="metric-card-label">Uptime</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- RIGHT — DASHBOARD -->
+    <div class="hero-dashboard">
+      <div class="dashboard-frame">
+        <div class="dash-head">
+          <div class="dash-head-title">Analytics Overview</div>
+          <div class="dash-head-tag">AI-Enhanced</div>
+        </div>
+
+        <div class="dash-kpis">
+          <div class="dash-kpi">
+            <div class="dash-kpi-value">$2.4M</div>
+            <div class="dash-kpi-label">Revenue</div>
+            <div class="dash-kpi-change">+12.3%</div>
+          </div>
+          <div class="dash-kpi">
+            <div class="dash-kpi-value">84.2K</div>
+            <div class="dash-kpi-label">Active Users</div>
+            <div class="dash-kpi-change">+8.7%</div>
+          </div>
+          <div class="dash-kpi">
+            <div class="dash-kpi-value">1.2%</div>
+            <div class="dash-kpi-label">Churn Rate</div>
+            <div class="dash-kpi-change">-0.3pp</div>
+          </div>
+        </div>
+
+        <div class="dash-chart">
+          <!-- Main chart with organic shapes -->
+          <div class="chart-main">
+            <div class="chart-main-label">Revenue Trend</div>
+            <svg class="chart-main-svg" viewBox="0 0 400 160" preserveAspectRatio="xMidYMid meet">
+              <!-- Soft grid -->
+              <line x1="0" y1="40" x2="400" y2="40" stroke="#ece7dd" stroke-width="1"/>
+              <line x1="0" y1="80" x2="400" y2="80" stroke="#ece7dd" stroke-width="1"/>
+              <line x1="0" y1="120" x2="400" y2="120" stroke="#ece7dd" stroke-width="1"/>
+
+              <!-- Rounded bars -->
+              <rect x="15" y="80" width="28" height="70" rx="8" ry="8" fill="#e2ddd4"/>
+              <rect x="58" y="65" width="28" height="85" rx="8" ry="8" fill="#e2ddd4"/>
+              <rect x="101" y="90" width="28" height="60" rx="8" ry="8" fill="#e2ddd4"/>
+              <rect x="144" y="50" width="28" height="100" rx="8" ry="8" fill="#6B8F71" opacity="0.6"/>
+              <rect x="187" y="60" width="28" height="90" rx="8" ry="8" fill="#e2ddd4"/>
+              <rect x="230" y="35" width="28" height="115" rx="8" ry="8" fill="#6B8F71" opacity="0.8"/>
+              <rect x="273" y="45" width="28" height="105" rx="8" ry="8" fill="#e2ddd4"/>
+              <rect x="316" y="25" width="28" height="125" rx="8" ry="8" fill="#6B8F71"/>
+              <rect x="359" y="40" width="28" height="110" rx="8" ry="8" fill="#e2ddd4"/>
+
+              <!-- Smooth trend line overlay -->
+              <path d="M29,75 C60,62 75,60 115,85 C140,70 155,47 172,45 C200,55 205,55 244,30 C270,40 280,40 330,20 C350,35 365,35 373,35" fill="none" stroke="#7A8F71" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
+
+              <!-- Labels -->
+              <text x="22" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Jan</text>
+              <text x="72" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Feb</text>
+              <text x="115" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Mar</text>
+              <text x="158" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Apr</text>
+              <text x="201" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">May</text>
+              <text x="244" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Jun</text>
+              <text x="287" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Jul</text>
+              <text x="330" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Aug</text>
+              <text x="373" y="158" font-size="9" fill="#bbb" font-family="Inter" text-anchor="middle">Sep</text>
+            </svg>
+          </div>
+
+          <!-- Side insights -->
+          <div class="chart-side">
+            <div class="insight-bubble">
+              <div class="insight-bubble-header">
+                <div class="insight-bubble-icon">
+                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
+                </div>
+                <span class="insight-bubble-tag">AI Insight</span>
+              </div>
+              <div class="insight-bubble-text">Enterprise segment grew 23% this quarter. Four new accounts are driving acceleration.</div>
+            </div>
+            <div class="insight-bubble">
+              <div class="insight-bubble-header">
+                <div class="insight-bubble-icon">
+                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
+                </div>
+                <span class="insight-bubble-tag">Prediction</span>
+              </div>
+              <div class="insight-bubble-text">89% likelihood of hitting Q3 revenue target based on current pipeline velocity.</div>
+            </div>
+            <div class="insight-bubble">
+              <div class="insight-bubble-header">
+                <div class="insight-bubble-icon">
+                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
+                </div>
+                <span class="insight-bubble-tag">Alert</span>
+              </div>
+              <div class="insight-bubble-text">Churn risk detected for 2 mid-market accounts. Recommend outreach this week.</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- TRUST BAR -->
+  <div class="trust-bar">
+    <span class="trust-label">Trusted by teams at</span>
+    <div class="trust-logos">
+      <span class="trust-logo">Stripe</span>
+      <span class="trust-logo">Notion</span>
+      <span class="trust-logo">Linear</span>
+      <span class="trust-logo">Vercel</span>
+      <span class="trust-logo">Figma</span>
+    </div>
+  </div>
+</div>
+
+<script>
+  document.addEventListener('DOMContentLoaded', function() {
+    lucide.createIcons();
+  });
+</script>
+</body>
+</html>

BIN
assets/showcases/website-saas/saas-takram.png


+ 245 - 0
references/animation-pitfalls.md

@@ -0,0 +1,245 @@
+# Animation Pitfalls:HTML 动画踩过的坑与规则
+
+做动画时最常踩的 bug 和如何避免。每条规则都来自真实失败案例。
+
+写动画之前读完这篇,能省一轮迭代。
+
+## 1. 叠层布局 —— `position: relative` 是默认义务
+
+**踩的坑**:一个 sentence-wrap 元素包了 3 个 bracket-layer(`position: absolute`)。没给 sentence-wrap 设 `position: relative`,结果 absolute 的 bracket 以 `.canvas` 为坐标系,飘到屏幕底部 200px 外。
+
+**规则**:
+- 任何包含 `position: absolute` 子元素的容器,**必须**显式 `position: relative`
+- 即使视觉上不需要「偏移」,也要写 `position: relative` 作为坐标系锚点
+- 如果你在写 `.parent { ... }`,其子元素里有 `.child { position: absolute }`,下意识给 parent 加 relative
+
+**快速检查**:每出现一个 `position: absolute`,往上数 ancestor,确保最近的 positioned 祖先是你*想要的*坐标系。
+
+## 2. 字符陷阱 —— 不依赖稀有 Unicode
+
+**踩的坑**:想用 `␣` (U+2423 OPEN BOX) 可视化「空格 token」。Noto Serif SC / Cormorant Garamond 都没这个字形,渲染为空白/豆腐,观众完全看不到。
+
+**规则**:
+- **动画里出现的每个字符,都必须在你选定的字体里存在**
+- 常见稀有字符黑名单:`␣ ␀ ␐ ␋ ␨ ↩ ⏎ ⌘ ⌥ ⌃ ⇧ ␦ ␖ ␛`
+- 要表达「空格 / 回车 / 制表符」这类元字符,用 **CSS 构造的语义盒子**:
+  ```html
+  <span class="space-key">Space</span>
+  ```
+  ```css
+  .space-key {
+    display: inline-flex;
+    padding: 4px 14px;
+    border: 1.5px solid var(--accent);
+    border-radius: 4px;
+    font-family: monospace;
+    font-size: 0.3em;
+    letter-spacing: 0.2em;
+    text-transform: uppercase;
+  }
+  ```
+- Emoji 也要验证:某些 emoji 在 Noto Emoji 以外字体会 fallback 成灰色方框,最好用 `emoji` font-family 或 SVG
+
+## 3. 数据驱动的 Grid/Flex 模板
+
+**踩的坑**:代码里 `const N = 6` 个 tokens,但 CSS 写死 `grid-template-columns: 80px repeat(5, 1fr)`。结果第 6 个 token 没有 column,整个矩阵错位。
+
+**规则**:
+- 当 count 从 JS 数组来(`TOKENS.length`),CSS 模板也应该数据驱动
+- 方案 A:用 CSS 变量从 JS 注入
+  ```js
+  el.style.setProperty('--cols', N);
+  ```
+  ```css
+  .grid { grid-template-columns: 80px repeat(var(--cols), 1fr); }
+  ```
+- 方案 B:用 `grid-auto-flow: column` 让浏览器自动扩展
+- **禁用「固定数字 +  JS 常量」的组合**,N 改了 CSS 不会同步更新
+
+## 4. 过渡断层 —— 场景切换要连续
+
+**踩的坑**:zoom1 (13-19s) → zoom2 (19.2-23s) 之间,主句子已经 hidden,zoom1 fade out(0.6s)+ zoom2 fade in(0.6s)+ stagger delay(0.2s+)= 约 1 秒纯空白画面。观众以为动画卡住了。
+
+**规则**:
+- 连续切换场景时,fade out 和 fade in 要**交叉重叠**,不是前一个完全消失再开始下一个
+  ```js
+  // 差:
+  if (t >= 19) hideZoom('zoom1');      // 19.0s out
+  if (t >= 19.4) showZoom('zoom2');    // 19.4s in → 中间 0.4s 空白
+
+  // 好:
+  if (t >= 18.6) hideZoom('zoom1');    // 提前 0.4s 开始 fade out
+  if (t >= 18.6) showZoom('zoom2');    // 同时 fade in(cross-fade)
+  ```
+- 或者用一个「锚点元素」(如主句子)作为场景之间的视觉连接,zoom 切换期间它短暂回显
+- 配 CSS transition 的 duration 算清楚,避免 transition 还没结束就触发下一个
+
+## 5. Pure Render 原则 —— 动画状态应可 seek
+
+**踩的坑**:用 `setTimeout` + `fireOnce(key, fn)` 链式触发动画状态。正常播放没问题,但做逐帧录制/seek到任意时间点时,之前的 setTimeout 已经执行过就无法「回到过去」。
+
+**规则**:
+- `render(t)` 函数理想上是 **pure function**:给定 t 输出唯一 DOM 状态
+- 如果必须用副作用(如 class 切换),用 `fired` set 配合显式 reset:
+  ```js
+  const fired = new Set();
+  function fireOnce(key, fn) { if (!fired.has(key)) { fired.add(key); fn(); } }
+  function reset() { fired.clear(); /* 清所有 .show class */ }
+  ```
+- 暴露 `window.__seek(t)` 供 Playwright / 调试用:
+  ```js
+  window.__seek = (t) => { reset(); render(t); };
+  ```
+- 动画相关的 setTimeout 不要跨越 >1 秒,否则 seek 回跳时会乱套
+
+## 6. 字体加载前测量 = 测错
+
+**踩的坑**:页面一 DOMContentLoaded 就调用 `charRect(idx)` 测量 bracket 位置,字体还没加载,每个字符宽度是 fallback 字体的宽度,位置全错。等字体一加载(约 500ms 后),bracket 的 `left: Xpx` 还是老值,永久偏移。
+
+**规则**:
+- 任何依赖 DOM 测量(`getBoundingClientRect`、`offsetWidth`)的布局代码,**必须**包在 `document.fonts.ready.then()` 里
+  ```js
+  document.fonts.ready.then(() => {
+    requestAnimationFrame(() => {
+      buildBrackets(...);  // 此时字体已就绪,测量准确
+      tick();              // 动画开始
+    });
+  });
+  ```
+- 额外的 `requestAnimationFrame` 给浏览器一帧时间提交 layout
+- 如果用 Google Fonts CDN,`<link rel="preconnect">` 加速首次加载
+
+## 7. 录制准备 —— 为视频导出预留抓手
+
+**踩的坑**:Playwright `recordVideo` 默认 25fps,从 context 创建就开始录。页面加载、字体加载的前 2 秒都被录进去。交付时视频前面 2 秒空白/闪白。
+
+**规则**:
+- 提供 `render-video.js` 工具处理:warmup navigate → reload 重启动画 → 等 duration → ffmpeg trim head + 转 H.264 MP4
+- 动画的**第 0 帧**要是最终布局已就位的完整初始状态(不是空白或加载中)
+- 想要 60fps?用 ffmpeg `minterpolate` 后处理,不指望浏览器源帧率
+- 想要 GIF?两阶段 palette(`palettegen` + `paletteuse`),对 30s 1080p 动画能压到 3MB
+
+参见 `video-export.md` 获取完整脚本调用方式。
+
+## 8. 批量导出 —— tmp 目录必须带 PID 防并发冲突
+
+**踩的坑**:用 `render-video.js` 3 个进程并行录 3 个 HTML。因为 TMP_DIR 只用 `Date.now()` 命名,3 个进程同毫秒启动时共用同一个 tmp 目录。最先完成的进程清理 tmp,另外两个读目录时 `ENOENT`,全部崩溃。
+
+**规则**:
+- 任何多进程可能共用的临时目录,命名必须带 **PID 或随机后缀**:
+  ```js
+  const TMP_DIR = path.join(DIR, '.video-tmp-' + Date.now() + '-' + process.pid);
+  ```
+- 如果确实想多文件并行,用 shell 的 `&` + `wait` 而不是在一个 node 脚本里 fork
+- 批量录多个 HTML 时,保守做法:**串行**运行(2 个以内可并行,3 个以上老实排队)
+
+## 9. 录屏里有进度条/重播按钮 —— Chrome 元素污染视频
+
+**踩的坑**:动画 HTML 加了 `.progress` 进度条、`.replay` 重播按钮、`.counter` 时间戳,方便人类调试播放。录成 MP4 交付时这些元素出现在视频底部,像把开发者工具截进去了一样。
+
+**规则**:
+- HTML 里给人类用的「chrome 元素」(progress bar / replay button / footer / masthead / counter / phase labels)和视频内容本体分开管理
+- **约定 class 名** `.no-record`:任何带这个 class 的元素,录屏脚本自动隐藏
+- 脚本端(`render-video.js`)默认注入 CSS 隐藏常见 chrome class 名:
+  ```
+  .progress .counter .phases .replay .masthead .footer .no-record [data-role="chrome"]
+  ```
+- 用 Playwright 的 `addInitScript` 注入(会在每次 navigate 前生效,reload 也稳)
+- 想看原样 HTML(带 chrome)时加 `--keep-chrome` flag
+
+## 10. 录屏开头几秒动画重复 —— Warmup 帧泄漏
+
+**踩的坑**:`render-video.js` 的旧流程 `goto → wait fonts 1.5s → reload → wait duration`。录制从 context 创建就开始,warmup 阶段动画已经播了一段,reload 后从 0 重启。结果视频前几秒是「动画中段 + 切换 + 动画从 0 开始」,重复感强。
+
+**规则**:
+- **Warmup 和 Record 必须用独立的 context**:
+  - Warmup context(无 `recordVideo` 选项):只负责 load url、等字体、然后 close
+  - Record context(有 `recordVideo`):fresh 状态开始,animation 从 t=0 开始录
+- ffmpeg `-ss trim` 只能裁 Playwright 的一点点 startup latency(~0.3s),**不能**用来掩盖 warmup 帧;源头要干净
+- 录制 context 关闭 = webm 文件写入磁盘,这是 Playwright 的约束
+- 相关代码模式:
+  ```js
+  // Phase 1: warmup (throwaway)
+  const warmupCtx = await browser.newContext({ viewport });
+  const warmupPage = await warmupCtx.newPage();
+  await warmupPage.goto(url, { waitUntil: 'networkidle' });
+  await warmupPage.waitForTimeout(1200);
+  await warmupCtx.close();
+
+  // Phase 2: record (fresh)
+  const recordCtx = await browser.newContext({ viewport, recordVideo });
+  const page = await recordCtx.newPage();
+  await page.goto(url, { waitUntil: 'networkidle' });
+  await page.waitForTimeout(DURATION * 1000);
+  await page.close();
+  await recordCtx.close();
+  ```
+
+## 11. 画面内别画「伪 chrome」—— 装饰版 player UI 与真 chrome 撞车
+
+**踩的坑**:动画用 `Stage` 组件,已经自带 scrubber + 时间码 + 暂停按钮(属于 `.no-record` chrome,导出时自动隐藏)。我又在画面底部画了一条「`00:60 ──── CLAUDE-DESIGN / ANATOMY`」的"杂志页码感装饰进度条",自我感觉良好。**结果**:用户看到两条进度条——一条是 Stage 控制器,一条是我画的装饰。视觉上完全撞车,认定为 bug。「视频内还有个进度条是怎么回事?」
+
+**规则**:
+
+- Stage 已经提供:scrubber + 时间码 + 暂停/重播按钮。**画面内不要再画**进度指示、当前时间码、版权署名条、章节计数器——它们要么和 chrome 撞车,要么就是 filler slop(违反「earn its place」原则)。
+- 「页码感」「杂志感」「底部署名条」这些**装饰诉求**,是 AI 自动加上的高频 filler。每一个出现都要警觉——它真的传达了不可替代的信息吗?还是单纯填满空白?
+- 如果你坚信某个底部条带必须存在(例如:动画主题就是讲 player UI),那它必须**叙事必要**,且**视觉上和 Stage scrubber 显著区分**(不同位置、不同形式、不同色调)。
+
+**元素归属测试**(每个画进 canvas 的元素必须能回答):
+
+| 它属于什么 | 处理 |
+|------------|------|
+| 某一幕的叙事内容 | OK,留着 |
+| 全局 chrome(控制/调试用) | 加 `.no-record` class,导出时隐藏 |
+| **既不属于任何幕,又不是 chrome** | **删**。这就是无主之物,必然是 filler slop |
+
+**自检(交付前 3 秒)**:截一张静态图,问自己——
+
+- 画面里有没有「看起来像 video player UI 的东西」(横线进度条、时间码、控制按钮模样)?
+- 如果有,删掉它叙事是否有损?无损就删。
+- 同一类信息(进度/时间/署名)有没有出现两次?合并到 chrome 一处。
+
+**反例**:底部画 `00:42 ──── PROJECT NAME`、画面右下角画"CH 03 / 06"章节计数、画面边缘画版本号"v0.3.1"——都是伪 chrome filler。
+
+## 12. 录屏前置空白 —— 用 `window.__ready` 同步动画 t=0
+
+**踩的坑**:60 秒动画导出 MP4,前 2-3 秒是空白页面。`ffmpeg --trim=0.3` 剪不掉。
+
+**根因**:Playwright `recordVideo` 从 `newContext()` 那一刻就开始写 WebM。但此时 Babel Standalone 还在编译 inline JSX、React 还没 mount、`document.fonts.ready.then(root.render)` 还没触发。`page.goto(url, { waitUntil: 'networkidle' })` 只等网络空闲,检测不到 JS 执行阶段——所以 WebM 前 1.5-3s 是空白页。
+
+**规则**:
+
+1. **动画代码**在 tick 第一帧发 `window.__ready = true`,和动画 t=0 同步:
+   ```js
+   function tick(now) {
+     if (last === null) {
+       last = now;
+       window.__ready = true;  // 必须在这里,不是 useEffect
+     }
+     // ... 动画推进
+   }
+   ```
+   为什么同步?如果 `__ready` 在 tick 之前设(如 `useEffect` 或 rAF 排队),触发时 WebM 光标还在空白页;如果在 tick 之后设(rAF 嵌套),则动画已经跑了几帧被 trim 掉。**pair 起来**才是对的。
+
+2. **录屏脚本**`page.goto` 之后 `waitForFunction(() => window.__ready === true)`,记录此时相对 WebM 起点的秒数作为 ffmpeg trim 偏移。完全不靠猜。
+
+3. **字体等待**放在 tick 启动条件里:`document.fonts.ready.then(() => rAF(tick))`。这样 tick 第一帧就是字体已就绪的画面——`__ready` 信号 = WebM 捕到的第一个"用户会看到"的动画帧。
+
+**不这么做的代价**:固定 trim 靠猜。机器快慢、字体缓存状态、网速每次不同——某台机器上的 3s trim 换到另一台可能不够或截掉开头。动态测量从根本解决。
+
+**参考实现**:`assets/animations.jsx` 的 Stage 组件已内置。`scripts/render-video.js` 已内置 auto-trim 逻辑。非 animations.jsx 的手写 HTML,自行在 tick/渲染循环的第一帧设信号。
+
+**验证方法**:导出后 `ffmpeg -i video.mp4 -ss 0 -vframes 1 frame-0.png`,检查第一帧是动画应有的初始状态(不是动画中段、不是黑屏)。
+
+## 快速自查清单(开工前 5 秒)
+
+- [ ] 每个 `position: absolute` 的父元素都有 `position: relative`?
+- [ ] 动画里的特殊字符(`␣` `⌘` `emoji`)都在字体里存在?
+- [ ] Grid/Flex 模板的 count 和 JS 数据的 length 一致?
+- [ ] 场景切换之间有 cross-fade,没有 >0.3s 的纯空白?
+- [ ] DOM 测量代码包在 `document.fonts.ready.then()` 里?
+- [ ] `render(t)` 是 pure 的,或有明确的 reset 机制?
+- [ ] 第 0 帧是完整初始状态,不是空白?
+- [ ] 画面内没有「伪 chrome」装饰(进度条/时间码/底部署名条与 Stage scrubber 撞车)?
+- [ ] 动画 tick 第一帧同步设 `window.__ready = true`?(用 animations.jsx 自带;手写 HTML 自己加)
+- [ ] 导出后抽第 0 帧验证是动画初始状态?

+ 246 - 0
references/animations.md

@@ -0,0 +1,246 @@
+# Animations:时间轴动画引擎
+
+做动画/motion design HTML时读这个。原理、用法、典型模式。
+
+## 核心模式:Stage + Sprite
+
+我们的动画系统(`assets/animations.jsx`)提供一个时间轴驱动的引擎:
+
+- **`<Stage>`**:整个动画的容器,自动提供auto-scale(fit viewport)+ scrubber + play/pause/loop控制
+- **`<Sprite start end>`**:时间片段。一个Sprite只在`start`到`end`这段时间内显示。内部可以通过`useSprite()` hook读取自己的本地进度`t` (0→1)
+- **`useTime()`**:读当前全局时间(秒)
+- **`Easing.easeInOut` / `Easing.easeOut` / ...**:缓动函数
+- **`interpolate(t, from, to, easing?)`**:根据t插值
+
+这套模式借鉴Remotion/After Effects思路,但轻量、零依赖。
+
+## 起手
+
+```html
+<script type="text/babel" src="animations.jsx"></script>
+<script type="text/babel">
+  const { Stage, Sprite, useTime, useSprite, Easing, interpolate } = window.Animations;
+
+  function Title() {
+    const { t } = useSprite();  // 本地进度 0→1
+    const opacity = interpolate(t, [0, 1], [0, 1], Easing.easeOut);
+    const y = interpolate(t, [0, 1], [40, 0], Easing.easeOut);
+    return (
+      <h1 style={{ 
+        opacity, 
+        transform: `translateY(${y}px)`,
+        fontSize: 120,
+        fontWeight: 900,
+      }}>
+        Hello.
+      </h1>
+    );
+  }
+
+  function Scene() {
+    return (
+      <Stage duration={10}>  {/* 10秒动画 */}
+        <Sprite start={0} end={3}>
+          <Title />
+        </Sprite>
+        <Sprite start={2} end={5}>
+          <SubTitle />
+        </Sprite>
+        {/* ... */}
+      </Stage>
+    );
+  }
+
+  const root = ReactDOM.createRoot(document.getElementById('root'));
+  root.render(<Scene />);
+</script>
+```
+
+## 常用动画模式
+
+### 1. Fade In / Fade Out
+
+```jsx
+function FadeIn({ children }) {
+  const { t } = useSprite();
+  const opacity = interpolate(t, [0, 0.3], [0, 1], Easing.easeOut);
+  return <div style={{ opacity }}>{children}</div>;
+}
+```
+
+**注意范围**:`[0, 0.3]`意思是在sprite的前30%时间完成渐入,后面保持opacity=1。
+
+### 2. Slide In
+
+```jsx
+function SlideIn({ children, from = 'left' }) {
+  const { t } = useSprite();
+  const progress = interpolate(t, [0, 0.4], [0, 1], Easing.easeOut);
+  const offset = (1 - progress) * 100;
+  const directions = {
+    left: `translateX(-${offset}px)`,
+    right: `translateX(${offset}px)`,
+    top: `translateY(-${offset}px)`,
+    bottom: `translateY(${offset}px)`,
+  };
+  return (
+    <div style={{
+      transform: directions[from],
+      opacity: progress,
+    }}>
+      {children}
+    </div>
+  );
+}
+```
+
+### 3. 逐字打字机
+
+```jsx
+function Typewriter({ text }) {
+  const { t } = useSprite();
+  const charCount = Math.floor(text.length * Math.min(t * 2, 1));
+  return <span>{text.slice(0, charCount)}</span>;
+}
+```
+
+### 4. 数字计数
+
+```jsx
+function CountUp({ from = 0, to = 100, duration = 0.6 }) {
+  const { t } = useSprite();
+  const progress = interpolate(t, [0, duration], [0, 1], Easing.easeOut);
+  const value = Math.floor(from + (to - from) * progress);
+  return <span>{value.toLocaleString()}</span>;
+}
+```
+
+### 5. 分段解释(典型教学动画)
+
+```jsx
+function Scene() {
+  return (
+    <Stage duration={20}>
+      {/* Phase 1: 展示问题 */}
+      <Sprite start={0} end={4}>
+        <Problem />
+      </Sprite>
+
+      {/* Phase 2: 展示思路 */}
+      <Sprite start={4} end={10}>
+        <Approach />
+      </Sprite>
+
+      {/* Phase 3: 展示结果 */}
+      <Sprite start={10} end={16}>
+        <Result />
+      </Sprite>
+
+      {/* 全程显示的字幕 */}
+      <Sprite start={0} end={20}>
+        <Caption />
+      </Sprite>
+    </Stage>
+  );
+}
+```
+
+## Easing函数
+
+预设的easing curves:
+
+| Easing | 特性 | 用在 |
+|--------|------|------|
+| `linear` | 匀速 | 滚动字幕、持续动画 |
+| `easeIn` | 慢→快 | 退场消失 |
+| `easeOut` | 快→慢 | 入场出现(最常用) |
+| `easeInOut` | 慢→快→慢 | 位置变化 |
+| `spring` | 弹簧 | 交互反馈 |
+| `anticipation` | 先反向再正向 | 强调动作 |
+
+**入场用easeOut,出场用easeIn**——这是动画的基础规律。
+
+## 节奏和时长指南
+
+### 微交互(0.1-0.3秒)
+- 按钮hover
+- 卡片expand
+- Tooltip出现
+
+### UI过渡(0.3-0.8秒)
+- 页面切换
+- 模态框出现
+- 列表item加入
+
+### 叙事动画(2-10秒每段)
+- 概念解释的一个phase
+- 数据图表的reveal
+- 场景转换
+
+### 单段叙事动画最长不超过10秒
+人类注意力有限。10秒讲一件事,讲完换下一件。
+
+## 设计动画的思考顺序
+
+### 1. 先有内容/故事,再有动画
+
+**错误**:先想要做fancy动画,再塞内容进去
+**正确**:先想清楚要传达什么信息,再用动画手段serve这个信息
+
+动画是**signal**,不是**装饰**。一个fade-in强调的是"这里很重要,请看"——如果什么都fade-in,signal就失效。
+
+### 2. 分Scene写时间轴
+
+```
+0:00 - 0:03   问题出现(fade in)
+0:03 - 0:06   问题放大/展开(zoom+pan)
+0:06 - 0:09   解法出现(slide in from right)
+0:09 - 0:12   解法展开说明(typewriter)
+0:12 - 0:15   结果演示(counter up + chart reveal)
+0:15 - 0:18   总结一句话(static,读3秒)
+0:18 - 0:20   CTA或fade out
+```
+
+写完时间轴再写组件。
+
+### 3. 资源先行
+
+动画要用的图片/图标/字体**先**准备好。不要画到一半去找素材——打断节奏。
+
+## 常见问题
+
+**动画卡顿**
+→ 主要是layout thrashing。用`transform`和`opacity`,不要动`top`/`left`/`width`/`height`/`margin`。浏览器GPU加速`transform`。
+
+**动画太快,看不清楚**
+→ 人读一个汉字需要100-150ms,一个词300-500ms。如果你用文字讲故事,单句至少留3秒。
+
+**动画太慢,观众无聊**
+→ 有趣的视觉变化要密集。静态画面超过5秒就会闷。
+
+**多个动画互相影响**
+→ 用CSS的`will-change: transform`提前告诉浏览器这个元素会动,减少reflow。
+
+**录制成视频**
+→ 用 skill 自带工具链(一条命令出三种格式):见 `video-export.md`
+- `scripts/render-video.js` — HTML → 25fps MP4(Playwright + ffmpeg)
+- `scripts/convert-formats.sh` — 25fps MP4 → 60fps MP4 + 优化 GIF
+- 想要更精确的帧渲染?让 render(t) 成为 pure function,见 `animation-pitfalls.md` 第 5 条
+
+## 和视频工具的配合
+
+这个skill做的是**HTML动画**(在浏览器里跑的)。如果最终产出要作为视频素材:
+
+- **短动画/concept demo**:用这里的方法做HTML动画 → 屏幕录制
+- **长视频/叙事**:本 skill 专注 HTML 动画,长视频用 AI 视频生成类 skill 或专业视频软件
+- **motion graphics**:专业的After Effects/Motion Canvas更合适
+
+## 关于Popmotion等库
+
+如果你真的需要物理动画(spring、decay、keyframes with precise timing),我们的engine搞不定,可以fallback到Popmotion:
+
+```html
+<script src="https://unpkg.com/popmotion@11.0.5/dist/popmotion.min.js"></script>
+```
+
+但**先试试我们的engine**。90%的情况够用。

+ 260 - 0
references/content-guidelines.md

@@ -0,0 +1,260 @@
+# Content Guidelines:反AI slop、内容准则、Scale规范
+
+AI设计里最容易掉进去的陷阱。这是一份「不做什么」的清单,比「做什么」更重要——因为AI slop是默认值,你不主动避免就会发生。
+
+## AI Slop 完整黑名单
+
+### 视觉陷阱
+
+**❌ 激进渐变背景**
+- 紫色 → 粉色 → 蓝色 全屏渐变(AI生成网页的典型味道)
+- 任何方向的rainbow gradient
+- Mesh gradient铺满背景
+- ✅ 如果要用渐变:subtle、单色系、有意图地点缀(比如button hover)
+
+**❌ 圆角卡片 + 左border accent色**
+```css
+/* 这是AI味卡片的典型签名 */
+.card {
+  border-radius: 12px;
+  border-left: 4px solid #3b82f6;
+  padding: 16px;
+}
+```
+这种卡片在AI生成的Dashboard里泛滥。想做强调?用更有设计感的方式:背景色对比、字重/字号对比、plain分隔线、或者干脆不分卡片。
+
+**❌ Emoji 装饰**
+除非品牌本身使用emoji(比如Notion、Slack),否则不要在UI上放emoji。**尤其不要**:
+- 标题前的 🚀 ⚡️ ✨ 🎯 💡
+- Feature列表的 ✅
+- CTA按钮里的 →(箭头单独出现OK,emoji箭头不行)
+
+没图标用真icon库(Lucide/Heroicons/Phosphor),或者用placeholder。
+
+**❌ SVG 画 imagery**
+不要试图用SVG画:人物、场景、设备、物品、抽象艺术。AI画的SVG imagery一眼就是AI味,幼稚且廉价。**一个灰色矩形+"插画位 1200×800"的文字标签,比一个拙劣的SVG hero illustration强100倍**。
+
+唯一可以用SVG的场景:
+- 真正的icon(16×16到32×32级别)
+- 几何图形做装饰元素
+- Data viz的chart
+
+**❌ 过多iconography**
+不是每个标题/feature/section都需要icon。滥用icon会让界面像toy。Less is more。
+
+**❌ "Data slop"**
+编造的stats装饰:
+- "10,000+ happy customers" (你都不知道有没有)
+- "99.9% uptime" (没有真数据就别写)
+- 用图标+数字+词组成的装饰"metric cards"
+- Mock table里的假数据装点得花里胡哨
+
+如果没真数据,留placeholder或问用户要。
+
+**❌ "Quote slop"**
+编造的用户评价、名人名言装饰页面。留placeholder问用户要真quote。
+
+### 字体陷阱
+
+**❌ 避免这些烂大街字体**:
+- Inter(AI生成的网页默认)
+- Roboto
+- Arial / Helvetica
+- 纯system font stack
+- Fraunces(AI发现了这个就用滥了)
+- Space Grotesk(最近AI的最爱)
+
+**✅ 用有特点的display+body配对**。灵感方向:
+- 衬线display + 无衬线body(editorial feel)
+- Mono display + sans body(technical feel)
+- Heavy display + light body(contrast)
+- Variable font做hero的粗细动画
+
+字体资源:
+- Google Fonts的冷门好选项(Instrument Serif、Cormorant、Bricolage Grotesque、JetBrains Mono)
+- 开源字体站(Fraunces的兄弟字体、Adobe Fonts)
+- 不要凭空发明字体名
+
+### 色彩陷阱
+
+**❌ 凭空发明颜色**
+不要从头设计一整套不熟悉的色彩。这通常不和谐。
+
+**✅ 策略**:
+1. 有品牌色 → 用品牌色,缺的color token用oklch插值
+2. 没有品牌色但有参考 → 从参考产品截图吸色
+3. 完全从零 → 选一个known的配色系统(Radix Colors / Tailwind默认palette / Anthropic brand),不要自己调
+
+**oklch定义色彩**是最现代的做法:
+```css
+:root {
+  --primary: oklch(0.65 0.18 25);      /* 温暖的terracotta */
+  --primary-light: oklch(0.85 0.08 25); /* 同色系浅色 */
+  --primary-dark: oklch(0.45 0.20 25);  /* 同色系深色 */
+}
+```
+oklch能保证调整亮度时色相不漂移,比hsl好用。
+
+**❌ 夜间模式随手加反色**
+不是简单invert颜色。好的dark mode需要重新调整饱和度、对比度、accent色。不想做dark mode就别做。
+
+### Layout陷阱
+
+**❌ Bento grid 过度泛滥**
+每个AI生成的landing page都想搞bento。除非你的信息structure确实适合bento,否则用其他layout。
+
+**❌ 大hero + 3-column features + testimonials + CTA**
+这个landing page模板被用烂了。想创新就真创新。
+
+**❌ Card grid里每个card长一样**
+Asymmetric、不同大小的cards、有的带image有的只有文字、有的跨列——这才像真设计师做的。
+
+## 内容准则
+
+### 1. Don't add filler content
+
+每个元素都必须earn its place。空白是设计问题,用**构图**解决(对比、节奏、留白),**不是**靠内容填满。
+
+**判断filler的问题**:
+- 如果去掉这段内容,设计会变差吗?答案若是"不会",就去掉。
+- 这个元素解决了什么真问题?如果是"让页面不那么空",删掉。
+- 这个stats/quote/feature有真数据支持吗?没有就不要凭空写。
+
+「One thousand no's for every yes」。
+
+### 2. Ask before adding material
+
+你觉得多加一段/一页/一个section会更好?先问用户,不要单方面加。
+
+原因:
+- 用户知道他的受众比你清楚
+- 加内容有成本,用户可能不想要
+- 单方面加内容违反了"junior designer汇报工作"的关系
+
+### 3. Create a system up front
+
+探索完design context后,**先口头说出你要用的系统**,让用户确认:
+
+```markdown
+我的设计系统:
+- 色彩:#1A1A1A主体 + #F0EEE6背景 + #D97757 accent(来自你的品牌)
+- 字型:Instrument Serif做display + Geist Sans做body
+- 节奏:section title用full-bleed彩色背景 + 白字;普通section用白背景
+- 图像:hero用full-bleed照片,feature section用placeholder等你提供
+- 最多用2种背景色,避免杂乱
+
+确认这个方向我就开始做。
+```
+
+用户确认后再动手。这个check-in能避免"做完一半发现方向错"。
+
+## Scale 规范
+
+### 幻灯片(1920×1080)
+
+- 正文最小 **24px**,理想 28-36px
+- 标题 60-120px
+- Section title 80-160px
+- Hero headline 可以用 180-240px 的大字
+- 永远不要用 <24px 的字放幻灯片
+
+### 印刷文档
+
+- 正文最小 **10pt**(≈13.3px),理想 11-12pt
+- 标题 18-36pt
+- Caption 8-9pt
+
+### Web和移动端
+
+- 正文最小 **14px**(老年人友好用16px)
+- 移动端正文 **16px**(避免iOS自动缩放)
+- Hit target(可点击元素)最小 **44×44px**
+- 行高 1.5-1.7(中文1.7-1.8)
+
+### 对比度
+
+- 正文 vs 背景 **至少 4.5:1**(WCAG AA)
+- 大字 vs 背景 **至少 3:1**
+- 用Chrome DevTools的accessibility工具检查
+
+## CSS 神器
+
+**高级CSS特性**是设计师的好朋友,大胆用:
+
+### 排版
+
+```css
+/* 让标题换行更自然,不会最后一行孤单单一个词 */
+h1, h2, h3 { text-wrap: balance; }
+
+/* 正文换行,避免寡孀和孤儿 */
+p { text-wrap: pretty; }
+
+/* 中文排版神器:标点挤压、行首行尾控制 */
+p { 
+  text-spacing-trim: space-all;
+  hanging-punctuation: first;
+}
+```
+
+### Layout
+
+```css
+/* CSS Grid + named areas = 可读性爆表 */
+.layout {
+  display: grid;
+  grid-template-areas:
+    "header header"
+    "sidebar main"
+    "footer footer";
+  grid-template-columns: 240px 1fr;
+  grid-template-rows: auto 1fr auto;
+}
+
+/* Subgrid对齐卡片内容 */
+.card { display: grid; grid-template-rows: subgrid; }
+```
+
+### 视觉效果
+
+```css
+/* 有设计感的滚动条 */
+* { scrollbar-width: thin; scrollbar-color: #666 transparent; }
+
+/* 玻璃拟态(克制使用) */
+.glass {
+  backdrop-filter: blur(20px) saturate(150%);
+  background: color-mix(in oklch, white 70%, transparent);
+}
+
+/* View transitions API让页面切换丝滑 */
+@view-transition { navigation: auto; }
+```
+
+### 交互
+
+```css
+/* :has()选择器让条件样式变容易 */
+.card:has(img) { padding-top: 0; } /* 有图片的卡片无顶padding */
+
+/* container queries让组件真的响应式 */
+@container (min-width: 500px) { ... }
+
+/* 新的color-mix函数 */
+.button:hover {
+  background: color-mix(in oklch, var(--primary) 85%, black);
+}
+```
+
+## 决策速查:当你犹豫时
+
+- 想加个渐变?→ 大概率不加
+- 想加个emoji?→ 不加
+- 想给卡片加圆角+border-left accent?→ 不加,换其他方式
+- 想用SVG画个hero插画?→ 不画,用placeholder
+- 想加一段quote装饰?→ 先问用户有没有真quote
+- 想加一排icon features?→ 先问要不要icon,可能不需要
+- 用Inter?→ 换一个更有特点的
+- 用紫色渐变?→ 换一个有根据的配色
+
+**当你觉得"加一下会更好看"的时候——那通常是AI slop的征兆**。先做最简的版本,只在用户要求时加。

+ 199 - 0
references/critique-guide.md

@@ -0,0 +1,199 @@
+# 设计评审深度指南
+
+> Phase 7 的详细参考。提供评分标准、场景侧重点、常见问题清单。
+
+---
+
+## 评分标准详解
+
+### 1. 哲学一致性(Philosophy Alignment)
+
+| 分数 | 标准 |
+|------|------|
+| 9-10 | 设计完美体现了选定哲学的核心精神,每个细节都有哲学依据 |
+| 7-8 | 整体方向正确,核心特征到位,个别细节偏离 |
+| 5-6 | 能看出意图,但执行时混入了其他风格元素,不够纯粹 |
+| 3-4 | 仅在表面模仿,未理解哲学内核 |
+| 1-2 | 与选定哲学基本无关 |
+
+**评审要点**:
+- 是否使用了该设计师/机构的标志性手法?
+- 色彩、字体、布局是否符合该哲学体系?
+- 有没有「自相矛盾」的元素?(如选了Kenya Hara却塞满内容)
+
+### 2. 视觉层级(Visual Hierarchy)
+
+| 分数 | 标准 |
+|------|------|
+| 9-10 | 用户视线自然沿设计者意图流动,信息获取零摩擦 |
+| 7-8 | 主次关系清晰,偶有1-2处层级模糊 |
+| 5-6 | 能分出标题和正文,但中间层级混乱 |
+| 3-4 | 信息平铺,没有明确的视觉入口 |
+| 1-2 | 混乱,用户不知道先看哪里 |
+
+**评审要点**:
+- 标题与正文的字号对比是否足够?(至少2.5倍)
+- 颜色/粗细/大小是否建立了3-4个清晰层级?
+- 留白是否在引导视线?
+- 「眯眼测试」:眯起眼看,层级是否仍然清晰?
+
+### 3. 细节执行(Craft Quality)
+
+| 分数 | 标准 |
+|------|------|
+| 9-10 | 像素级精确,对齐、间距、颜色无任何瑕疵 |
+| 7-8 | 整体精致,有1-2处微小对齐/间距问题 |
+| 5-6 | 基本对齐,但间距不统一,颜色使用不够系统 |
+| 3-4 | 明显的对齐错误、间距混乱、颜色过多 |
+| 1-2 | 粗糙,看起来像草稿 |
+
+**评审要点**:
+- 是否使用了统一的间距系统(如8pt网格)?
+- 同类元素的间距是否一致?
+- 颜色数量是否受控?(通常不超过3-4种)
+- 字体家族是否统一?(通常不超过2种)
+- 边缘对齐是否精确?
+
+### 4. 功能性(Functionality)
+
+| 分数 | 标准 |
+|------|------|
+| 9-10 | 每个设计元素都服务于目标,零冗余 |
+| 7-8 | 功能导向明确,有少量可删减的装饰 |
+| 5-6 | 基本可用,但有明显的装饰性元素分散注意力 |
+| 3-4 | 形式大于功能,用户需要努力寻找信息 |
+| 1-2 | 完全被装饰淹没,失去了传达信息的能力 |
+
+**评审要点**:
+- 删掉任何一个元素,设计会变差吗?(如果不会,就应该删)
+- CTA/关键信息是否在最显眼的位置?
+- 是否有「因为好看所以加上去」的元素?
+- 信息密度与载体是否匹配?(PPT不宜太密,PDF可以更密)
+
+### 5. 创新性(Originality)
+
+| 分数 | 标准 |
+|------|------|
+| 9-10 | 令人耳目一新,在该哲学框架内找到了独特表达 |
+| 7-8 | 有自己的想法,不是简单的模板套用 |
+| 5-6 | 中规中矩,看起来像模板 |
+| 3-4 | 大量使用了cliché(如渐变圆球代表AI) |
+| 1-2 | 完全是模板或素材拼凑 |
+
+**评审要点**:
+- 是否避免了常见cliché?(见下方「常见问题清单」)
+- 在遵循设计哲学的同时是否有个人表达?
+- 是否有「意想不到但很合理」的设计决策?
+
+---
+
+## 场景评审侧重
+
+不同输出类型的评审重点不同:
+
+| 场景 | 最重要维度 | 次重要 | 可放宽 |
+|------|-----------|--------|--------|
+| 公众号封面/配图 | 创新性、视觉层级 | 哲学一致性 | 功能性(单图不涉及交互) |
+| 信息图 | 功能性、视觉层级 | 细节执行 | 创新性(准确优先) |
+| PPT/Keynote | 视觉层级、功能性 | 细节执行 | 创新性(清晰优先) |
+| PDF/白皮书 | 细节执行、功能性 | 视觉层级 | 创新性(专业优先) |
+| 落地页/官网 | 功能性、视觉层级 | 创新性 | —(全面要求) |
+| App UI | 功能性、细节执行 | 视觉层级 | 哲学一致性(可用性优先) |
+| 小红书配图 | 创新性、视觉层级 | 哲学一致性 | 细节执行(氛围优先) |
+
+---
+
+## 常见设计问题 Top 10
+
+### 1. AI科技cliché
+**问题**:渐变圆球、数字雨、蓝色电路板、机器人脸
+**为什么是问题**:用户已经对这些视觉疲劳,无法区分你和其他人
+**修复**:用抽象隐喻替代直白符号(如用「对话」的隐喻而非聊天气泡图标)
+
+### 2. 字号层级不足
+**问题**:标题和正文差距太小(<2.5倍)
+**为什么是问题**:用户无法快速定位关键信息
+**修复**:标题至少为正文的3倍(如正文16px → 标题48-64px)
+
+### 3. 颜色过多
+**问题**:使用5种以上颜色,没有主次
+**为什么是问题**:视觉混乱,品牌感弱
+**修复**:限制为1个主色+1个辅色+1个强调色+灰阶
+
+### 4. 间距不统一
+**问题**:元素间距随意,没有系统
+**为什么是问题**:看起来不专业,视觉节奏混乱
+**修复**:建立8pt网格系统(间距只用8/16/24/32/48/64px)
+
+### 5. 留白不足
+**问题**:所有空间都被内容填满
+**为什么是问题**:信息拥挤导致阅读疲劳,反而降低信息传达效率
+**修复**:留白至少占总面积40%(极简风格60%+)
+
+### 6. 字体过多
+**问题**:使用3种以上字体
+**为什么是问题**:视觉噪音,削弱统一感
+**修复**:最多2种字体(1种标题+1种正文),用字重和大小创造变化
+
+### 7. 对齐不一致
+**问题**:有的左对齐,有的居中,有的右对齐
+**为什么是问题**:破坏视觉秩序感
+**修复**:选定一种对齐方式(推荐左对齐),全局统一
+
+### 8. 装饰大于内容
+**问题**:背景图案/渐变/阴影抢了主要内容的风头
+**为什么是问题**:本末倒置,用户来看信息不是看装饰
+**修复**:「如果删掉这个装饰,设计会变差吗?」如果不会,就删
+
+### 9. 赛博霓虹滥用
+**问题**:深蓝底(#0D1117) + 霓虹色发光效果
+**为什么是问题**:默认审美禁区(本 skill 的品位基线),且已成为最大 cliché 之一——用户可按自己品牌 override
+**修复**:选择更有辨识度的配色方案(参考20种风格的色彩系统)
+
+### 10. 信息密度与载体不匹配
+**问题**:PPT里放了一整页文字 / 封面图里塞了10个元素
+**为什么是问题**:不同载体的最佳信息密度不同
+**修复**:
+- PPT:每页1个核心观点
+- 封面图:1个视觉焦点
+- 信息图:分层展示
+- PDF:可以更密,但需要清晰的导航
+
+---
+
+## 评审输出模板
+
+```
+## 设计评审报告
+
+**总体评分**:X.X/10 [优秀(8+)/良好(6-7.9)/需改进(4-5.9)/不合格(<4)]
+
+**分项评分**:
+- 哲学一致性:X/10 [一句话说明]
+- 视觉层级:X/10 [一句话说明]
+- 细节执行:X/10 [一句话说明]
+- 功能性:X/10 [一句话说明]
+- 创新性:X/10 [一句话说明]
+
+### 优点(Keep)
+- [具体指出做得好的地方,用设计语言描述]
+
+### 问题(Fix)
+[按严重程度排序]
+
+**1. [问题名称]** — ⚠️致命 / ⚡重要 / 💡优化
+- 当前:[描述现状]
+- 问题:[为什么这是问题]
+- 修复:[具体操作,含数值]
+
+### 快速修复清单(Quick Wins)
+如果只有5分钟,优先做这3件事:
+- [ ] [最有影响力的修复]
+- [ ] [第二重要的修复]
+- [ ] [第三重要的修复]
+```
+
+---
+
+**版本**:v1.0
+**更新日期**:2026-02-13

+ 213 - 0
references/design-context.md

@@ -0,0 +1,213 @@
+# Design Context:从已有上下文出发
+
+**这是这个skill最重要的one thing。**
+
+好的hi-fi设计一定是从已有design context长出来的。**凭空做hi-fi是last resort,一定会产出generic的作品**。所以每次设计任务开始,先问:有没有可以参考的东西?
+
+## 什么是Design Context
+
+按优先级从高到低:
+
+### 1. 用户的Design System/UI Kit
+用户自己产品已有的组件库、色彩token、字型规范、icon系统。**最完美的情况**。
+
+### 2. 用户的Codebase
+如果用户给了代码库,里面就有活生生的组件实现。Read那些组件文件:
+- `theme.ts` / `colors.ts` / `tokens.css` / `_variables.scss`
+- 具体的组件(Button.tsx、Card.tsx)
+- Layout scaffold(App.tsx、MainLayout.tsx)
+- Global stylesheets
+
+**读代码抄exact values**:hex codes、spacing scale、font stack、border radius。不要凭记忆重画。
+
+### 3. 用户已发布的产品
+如果用户有上线的产品但没给代码,用Playwright或让用户提供截图。
+
+```bash
+# 用Playwright截图一个公开URL
+npx playwright screenshot https://example.com screenshot.png --viewport-size=1920,1080
+```
+
+让你看到真实的视觉vocabulary。
+
+### 4. 品牌指南/Logo/已有素材
+用户可能有:Logo文件、品牌色规范、营销物料、slide模板。这些都是context。
+
+### 5. 竞品参考
+用户说"像XX网站那样"——让他提供URL或截图。**不要**凭你训练数据里的模糊印象做。
+
+### 6. 已知的design system(fallback)
+如果以上都没有,用公认的设计系统作为base:
+- Apple HIG
+- Material Design 3
+- Radix Colors(配色)
+- shadcn/ui(组件)
+- Tailwind默认palette
+
+明确告诉用户你用的什么,让他知道这是起点不是定稿。
+
+## 获取Context的流程
+
+### Step 1:问用户
+
+任务开始时的必问清单(来自`workflow.md`):
+
+```markdown
+1. 你有现成的design system/UI kit/组件库吗?在哪?
+2. 有品牌指南、色彩/字体规范吗?
+3. 可以给我现有产品的截图或URL吗?
+4. 有codebase我可以读吗?
+```
+
+### Step 2:用户说"没有"时,帮他找
+
+别直接放弃。尝试:
+
+```markdown
+让我看看有没有线索:
+- 你之前的项目有相关设计吗?
+- 公司的marketing网站用什么色彩/字型?
+- 你产品的Logo什么风格?能给我一张吗?
+- 有什么你欣赏的产品作为参考?
+```
+
+### Step 3:Read所有能找到的context
+
+如果用户给了codebase路径,你读:
+1. **先list文件结构**:找style/theme/component相关的文件
+2. **读theme/token文件**:lift具体的hex/px values
+3. **读2-3个代表性组件**:看视觉vocabulary(hover state、shadow、border、padding node pattern)
+4. **读global stylesheet**:基础重置、font loading
+5. **如果有Figma链接/截图**:看图,但**更相信代码**
+
+**重要**:**不要**看了一眼就凭印象做。读下来有30+个具体values才真的lift到了。
+
+### Step 4:Vocalize你要用的系统
+
+看完context后,告诉用户你要用的系统:
+
+```markdown
+根据你的codebase和产品截图,我提炼的设计系统:
+
+**色彩**
+- Primary: #C27558(从tokens.css)
+- Background: #FDF9F0
+- Text: #1A1A1A
+- Muted: #6B6B6B
+
+**字型**
+- Display: Instrument Serif(从global.css的@font-face)
+- Body: Geist Sans
+- Mono: JetBrains Mono
+
+**Spacing**(来自你的scale系统)
+- 4, 8, 12, 16, 24, 32, 48, 64
+
+**Shadow pattern**
+- `0 1px 2px rgba(0,0,0,0.04)`(subtle card)
+- `0 10px 40px rgba(0,0,0,0.1)`(elevated modal)
+
+**Border-radius**
+- 小组件 4px,卡片 12px,按钮 8px
+
+**component vocabulary**
+- Button:filled primary,outlined secondary,ghost tertiary,全部圆角8px
+- Card:白色背景,subtle shadow,无border
+
+我按这套系统开始做。确认没问题?
+```
+
+用户确认后再动手。
+
+## 凭空做设计(没Context时的 fallback)
+
+**强烈警告**:这种情况下的产出质量会显著下降。明确告诉用户。
+
+```markdown
+你没有design context,我就只能基于通用直觉做。
+产出会是"看起来OK但缺乏独特性"的东西。
+你愿意继续,还是先补一些参考材料?
+```
+
+用户执意要你做,按这个顺序做决策:
+
+### 1. 选一个aesthetic direction
+不要给generic结果。挑一个明确方向:
+- brutally minimal
+- editorial/magazine
+- brutalist/raw
+- organic/natural
+- luxury/refined
+- playful/toy
+- retro-futuristic
+- soft/pastel
+
+告诉用户你选了哪个。
+
+### 2. 选一个known design system作为骨架
+- 用Radix Colors做配色(https://www.radix-ui.com/colors)
+- 用shadcn/ui做组件vocabulary(https://ui.shadcn.com)
+- 用Tailwind spacing scale(4的倍数)
+
+### 3. 选有特点的字体配对
+
+不要用Inter/Roboto。建议组合(从Google Fonts白嫖):
+- Instrument Serif + Geist Sans
+- Cormorant Garamond + Inter Tight
+- Bricolage Grotesque + Söhne(付费)
+- Fraunces + Work Sans(注意Fraunces已经被AI用烂)
+- JetBrains Mono + Geist Sans(technical feel)
+
+### 4. 每个关键决策都有reasoning
+
+不要默默选。在HTML的comment里写:
+
+```html
+<!--
+Design decisions:
+- Primary color: warm terracotta (oklch 0.65 0.18 25) — fits the "editorial" direction  
+- Display: Instrument Serif for humanist, literary feel
+- Body: Geist Sans for cleanness contrast
+- No gradients — committed to minimal, no AI slop
+- Spacing: 8px base, golden ratio friendly (8/13/21/34)
+-->
+```
+
+## Import策略(用户给了codebase)
+
+如果用户说"import这个codebase做参考":
+
+### 小型(<50文件)
+全部Read,把context内化。
+
+### 中型(50-500文件)
+Focus在:
+- `src/components/` 或 `components/`
+- 所有styles/tokens/theme相关的文件
+- 2-3个代表性的整页组件(Home.tsx、Dashboard.tsx)
+
+### 大型(>500文件)
+让用户指明focus:
+- "我要做settings页面" → 读现有的settings相关
+- "我要做一个新的feature" → 读整体shell + 最接近的参考
+- 不求全,求准
+
+## 和Figma/设计稿的配合
+
+如果用户给了Figma链接:
+
+- **不要**期望你能直接"转Figma为HTML"——那需要额外工具
+- Figma链接通常不公开可访问
+- 让用户:导出为**截图**发给你 + 告诉你具体的color/spacing values
+
+如果只给了Figma截图,告诉用户:
+- 我能看到视觉,但取不到精确values
+- 关键数字(hex、px)请告诉我,或者export as code(Figma支持)
+
+## 最后的提醒
+
+**一个项目的设计质量上限,由你拿到的context质量决定**。
+
+花10分钟收集context,比花1小时凭空画hi-fi更有价值。
+
+**遇到没context的情况,优先问用户要,而不是硬上**。

+ 591 - 0
references/design-styles.md

@@ -0,0 +1,591 @@
+# 设计哲学风格库:20种体系
+
+> 用于视觉设计(网页/PPT/PDF/信息图/配图/App等)的设计风格库。
+> 每种风格提供:哲学内核 + 核心特征 + 提示词DNA(与场景模板组合使用)。
+
+## 风格×场景×执行路径 速查表
+
+| 风格 | 网页 | PPT | PDF | 信息图 | 封面 | AI生成 | 最佳路径 |
+|------|:---:|:---:|:---:|:-----:|:---:|:-----:|---------|
+| 01 Pentagram | ★★★ | ★★★ | ★★☆ | ★★☆ | ★★★ | ★☆☆ | HTML |
+| 02 Stamen Design | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★☆ | ★★☆ | 混合 |
+| 03 Information Architects | ★★★ | ★☆☆ | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | HTML |
+| 04 Fathom | ★★☆ | ★★★ | ★★★ | ★★★ | ★★☆ | ★☆☆ | HTML |
+| 05 Locomotive | ★★★ | ★★☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★☆ | 混合 |
+| 06 Active Theory | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★★ | AI生成 |
+| 07 Field.io | ★★☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
+| 08 Resn | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★☆ | AI生成 |
+| 09 Experimental Jetset | ★★☆ | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★☆ | 混合 |
+| 10 Müller-Brockmann | ★★☆ | ★★★ | ★★★ | ★★★ | ★★☆ | ★☆☆ | HTML |
+| 11 Build | ★★★ | ★★★ | ★★☆ | ★☆☆ | ★★★ | ★☆☆ | HTML |
+| 12 Sagmeister & Walsh | ★★☆ | ★★★ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
+| 13 Zach Lieberman | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
+| 14 Raven Kwok | ★☆☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
+| 15 Ash Thorp | ★★☆ | ★★☆ | ★☆☆ | ★☆☆ | ★★★ | ★★★ | AI生成 |
+| 16 Territory Studio | ★★☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
+| 17 Takram | ★★★ | ★★★ | ★★★ | ★★☆ | ★★☆ | ★☆☆ | HTML |
+| 18 Kenya Hara | ★★☆ | ★★★ | ★★★ | ★☆☆ | ★★★ | ★☆☆ | HTML |
+| 19 Irma Boom | ★☆☆ | ★★☆ | ★★★ | ★★☆ | ★★★ | ★★☆ | 混合 |
+| 20 Neo Shen | ★★☆ | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
+
+> 场景适配:★★★ = 强烈推荐 / ★★☆ = 适合 / ★☆☆ = 需改造
+> AI生成:★★★ = 直出效果好 / ★★☆ = 需调整 / ★☆☆ = 建议HTML执行
+> 最佳路径:AI生成(图片直出)/ HTML(代码渲染,数据精确)/ 混合(HTML布局+AI配图)
+
+**核心规律**:有明确视觉元素的风格(插画/粒子/生成艺术)AI直出效果好;依赖精确排版和数据的风格(网格/信息架构/留白)HTML渲染更可控。
+
+---
+
+## 一、信息建筑派(01-04)
+> 哲学:「数据不是装饰,是建筑材料」
+
+### 01. Pentagram - Michael Bierut风格
+**哲学**:字体即语言,网格即思想
+**核心特征**:
+- 极度克制的颜色(黑白+1个品牌色)
+- 瑞士网格系统的现代演绎
+- 字体排印作为主要视觉语言
+- 负空间的战略性使用(60%+留白)
+
+**提示词DNA**:
+```
+Pentagram/Michael Bierut style:
+- Extreme typographic hierarchy, Helvetica/Univers family
+- Swiss grid with precise mathematical spacing
+- Black/white + one accent color (#HEX)
+- Information architecture as visual structure
+- 60%+ whitespace ratio
+- Data visualization as primary decoration
+```
+
+**代表作**:Hillary Clinton 2016 campaign identity
+**搜索关键词**:pentagram hillary logo system
+
+---
+
+### 02. Stamen Design - 数据诗学
+**哲学**:让数据成为可触摸的风景
+**核心特征**:
+- 地图学思维应用于信息设计
+- 算法生成的有机图形
+- 温暖的数据可视化色调(赭石、鼠尾草绿、深蓝)
+- 可交互的层级系统
+
+**提示词DNA**:
+```
+Stamen Design aesthetic:
+- Cartographic approach to data visualization
+- Organic, algorithm-generated patterns
+- Warm palette (terracotta, sage green, deep blues)
+- Layered information like topographic maps
+- Hand-crafted feel despite digital precision
+- Soft shadows and depth
+```
+
+**代表作**:COVID-19 surge map
+**搜索关键词**:stamen covid map visualization
+
+---
+
+### 03. Information Architects - 内容优先原则
+**哲学**:设计不是装饰,是内容的建筑
+**核心特征**:
+- 极端的内容层级清晰度
+- 只使用系统字体(优化阅读)
+- 蓝色超链接传统的坚守
+- 性能即美学
+
+**提示词DNA**:
+```
+Information Architects philosophy:
+- Content-first hierarchy, zero decorative elements
+- System fonts only (SF Pro/Roboto/Inter)
+- Classic blue hyperlinks (#0000EE)
+- Reading-optimized line length (66 characters)
+- Progressive disclosure of depth
+- Text-heavy, fast-loading design
+```
+
+**代表作**:iA Writer app
+**搜索关键词**:information architects ia writer
+
+---
+
+### 04. Fathom Information Design - 科学叙事
+**哲学**:每一个像素都必须承载信息
+**核心特征**:
+- 科学期刊的严谨+设计的优雅
+- 定量数据的精确可视化
+- 冷静的专业色调(灰、海军蓝)
+- 注释与引用系统的设计化
+
+**提示词DNA**:
+```
+Fathom Information Design style:
+- Scientific journal aesthetic meets modern design
+- Precise data visualization (charts, timelines, scatter plots)
+- Neutral scheme (grays, navy, one highlight color)
+- Footnote/citation design integrated into layout
+- Clean sans-serif (GT America/Graphik)
+- Information density without clutter
+```
+
+**代表作**:Bill & Melinda Gates Foundation年度报告
+**搜索关键词**:fathom information design gates foundation
+
+---
+
+## 二、运动诗学派(05-08)
+> 哲学:「技术本身就是一种流动的诗」
+
+### 05. Locomotive - 滚动叙事大师
+**哲学**:滚动不是浏览,是旅程
+**核心特征**:
+- 丝滑的视差滚动
+- 电影化的分镜叙事
+- 大胆的空间留白
+- 动态元素的精确编排
+
+**提示词DNA**:
+```
+Locomotive scroll narrative style:
+- Film-like scene composition with parallax depth
+- Generous vertical spacing between sections
+- Bold typography emerging from darkness
+- Smooth motion blur effects
+- Dark mode (near-black backgrounds)
+- Strategic glowing accents
+- Hero sections 100vh tall
+```
+
+**代表作**:Lusion.co website
+**搜索关键词**:locomotive scroll lusion
+
+---
+
+### 06. Active Theory - WebGL诗人
+**哲学**:让技术可见化即让技术可理解
+**核心特征**:
+- 3D粒子系统作为核心元素
+- 实时渲染的数据可视化
+- 鼠标交互驱动的世界构建
+- 霓虹与深空的配色
+
+**提示词DNA**:
+```
+Active Theory WebGL aesthetic:
+- Particle systems representing data flow
+- 3D visualization in depth space
+- Neon gradients (cyan/magenta/electric blue) on dark
+- Mouse-reactive environment
+- Depth of field and bokeh effects
+- Floating UI with glassmorphism
+```
+
+**代表作**:NASA Prospect
+**搜索关键词**:active theory nasa webgl
+
+---
+
+### 07. Field.io - 算法美学
+**哲学**:代码即设计师
+**核心特征**:
+- 生成艺术系统
+- 每次访问都不同的动态图形
+- 抽象几何的智能编排
+- 技术感与艺术性的平衡
+
+**提示词DNA**:
+```
+Field.io generative design style:
+- Abstract geometric patterns, algorithmically generated
+- Dynamic composition that feels computational
+- Monochromatic base with vibrant accent
+- Mathematical precision in spacing
+- Voronoi diagrams or Delaunay triangulation
+- Clean code aesthetic
+```
+
+**代表作**:British Council digital installations
+**搜索关键词**:field.io generative design
+
+---
+
+### 08. Resn - 叙事驱动的交互
+**哲学**:每个点击都推进故事
+**核心特征**:
+- 游戏化的用户旅程
+- 强烈的情感化设计
+- 插画与代码的深度结合
+- 非线性的探索体验
+
+**提示词DNA**:
+```
+Resn interactive storytelling approach:
+- Illustrative style mixed with UI elements
+- Gamified exploration (progress indicators)
+- Warm color palette despite tech subject
+- Character-driven design
+- Scroll-triggered animations
+- Editorial illustration meets product design
+```
+
+**代表作**:Resn.co.nz portfolio
+**搜索关键词**:resn interactive storytelling
+
+---
+
+## 三、极简主义派(09-12)
+> 哲学:「删减到无法再删」
+
+### 09. Experimental Jetset - 概念极简
+**哲学**:一个想法=一个形式
+**核心特征**:
+- 单一视觉隐喻贯穿整个设计
+- 蓝/红/黄+黑白的蒙德里安色系
+- 字体即图形
+- 反商业的诚实设计
+
+**提示词DNA**:
+```
+Experimental Jetset conceptual minimalism:
+- Single visual metaphor for entire design
+- Primary colors only (red/blue/yellow) + black/white
+- Typography as main graphic element
+- Grid-based with deliberate rule-breaking
+- No photography, only type and geometry
+- Anti-commercial, honest aesthetic
+```
+
+**代表作**:Whitney Museum identity
+**搜索关键词**:experimental jetset whitney responsive w
+
+---
+
+### 10. Müller-Brockmann传承 - 瑞士网格纯粹主义
+**哲学**:客观性即美
+**核心特征**:
+- 数学精确的网格系统(8pt基线)
+- 绝对的左对齐或居中
+- 单色或双色方案
+- 功能主义至上
+
+**提示词DNA**:
+```
+Josef Müller-Brockmann Swiss modernism:
+- Mathematical grid system (8pt baseline)
+- Strict alignment (flush left or centered)
+- Two-color maximum (black + one accent)
+- Akzidenz-Grotesk or similar rationalist typeface
+- No decorative elements
+- Timeless, objective aesthetic
+```
+
+**代表作**:《Grid Systems in Graphic Design》
+**搜索关键词**:muller brockmann grid systems poster
+
+---
+
+### 11. Build - 当代极简品牌
+**哲学**:精致的简单比复杂更难
+**核心特征**:
+- 奢侈品级的留白(70%+)
+- 微妙的字重对比(200-600)
+- 单一强调色的战略使用
+- 呼吸感的节奏
+
+**提示词DNA**:
+```
+Build studio luxury minimalism:
+- Generous whitespace (70%+ of area)
+- Subtle typography weight shifts (200 to 600)
+- Single accent color used sparingly
+- High-end product photography aesthetic
+- Soft shadows and subtle gradients
+- Golden ratio proportions
+```
+
+**代表作**:Build studio portfolio
+**搜索关键词**:build studio london branding
+
+---
+
+### 12. Sagmeister & Walsh - 快乐极简
+**哲学**:美即功能的情感维度
+**核心特征**:
+- 意外的色彩爆发
+- 手工感与数字的融合
+- 正能量的视觉语言
+- 实验性但可读
+
+**提示词DNA**:
+```
+Sagmeister & Walsh joyful philosophy:
+- Unexpected color bursts on minimal base
+- Handmade elements (physical objects in digital)
+- Optimistic visual language
+- Experimental typography that remains legible
+- Human warmth through imperfection
+- Mix of analog and digital aesthetics
+```
+
+**代表作**:The Happy Show
+**搜索关键词**:sagmeister walsh happy show
+
+---
+
+## 四、实验先锋派(13-16)
+> 哲学:「打破规则即创造规则」
+
+### 13. Zach Lieberman - 代码诗学
+**哲学**:编程即绘画
+**核心特征**:
+- 手绘感的算法图形
+- 实时生成艺术
+- 黑白的纯粹表达
+- 工具本身的可见性
+
+**提示词DNA**:
+```
+Zach Lieberman code-as-art style:
+- Hand-drawn aesthetic generated by code
+- Black and white only, no color
+- Real-time generative patterns
+- Sketch-like line quality
+- Visible process/grid/construction lines
+- Poetic interpretation of algorithms
+```
+
+**代表作**:openFrameworks creative coding
+**搜索关键词**:zach lieberman openframeworks generative
+
+---
+
+### 14. Raven Kwok - 参数化美学
+**哲学**:系统的美胜过个体的美
+**核心特征**:
+- 分形与递归图形
+- 黑白高对比
+- 建筑化的信息结构
+- 东方园林的算法演绎
+
+**提示词DNA**:
+```
+Raven Kwok parametric aesthetic:
+- Fractal patterns and recursive structures
+- High-contrast black and white
+- Architectural visualization of data
+- Chinese garden principles in algorithm form
+- Intricate detail that rewards zooming
+- Processing/Creative coding aesthetic
+```
+
+**代表作**:Raven Kwok generative art exhibitions
+**搜索关键词**:raven kwok processing generative art
+
+---
+
+### 15. Ash Thorp - 赛博诗意
+**哲学**:未来不是冰冷的,是孤独的诗
+**核心特征**:
+- 电影级的光影
+- 赛博朋克的温暖版本(橙/青,非冷蓝)
+- 故事性的概念设计
+- 工业美学的精致化
+
+**提示词DNA**:
+```
+Ash Thorp cinematic concept art:
+- Film-grade lighting and atmospheric effects
+- Warm cyberpunk (orange/teal, NOT cold blue)
+- Industrial design meets luxury
+- Narrative concept art feel
+- Volumetric lighting and god rays
+- Blade Runner warmth over Tron coldness
+```
+
+**代表作**:Ghost in the Shell concept art
+**搜索关键词**:ash thorp ghost shell concept art
+
+---
+
+### 16. Territory Studio - 屏幕界面虚构
+**哲学**:未来UI的今日想象
+**核心特征**:
+- 科幻电影中的屏幕设计(FUI)
+- 全息投影感
+- 多层叠加的数据可视化
+- 可信的未来感
+
+**提示词DNA**:
+```
+Territory Studio FUI (Fantasy User Interface):
+- Fantasy User Interface design
+- Holographic projection aesthetics
+- Orange/amber monochrome or cyan accents
+- Multiple overlapping data layers
+- Believable future technology
+- Technical readouts and data streams
+```
+
+**代表作**:Blade Runner 2049 screen graphics
+**搜索关键词**:territory studio blade runner interface
+
+---
+
+## 五、东方哲学派(17-20)
+> 哲学:「留白即内容」
+
+### 17. Takram - 日式思辨设计
+**哲学**:技术是思考的媒介
+**核心特征**:
+- 概念原型的优雅
+- 柔和的科技感(圆角、柔和阴影)
+- 图表即艺术
+- 谦逊的精致
+
+**提示词DNA**:
+```
+Takram Japanese speculative design:
+- Elegant concept prototypes and diagrams
+- Soft tech aesthetic (rounded corners, gentle shadows)
+- Charts and diagrams as art pieces
+- Modest sophistication
+- Neutral natural colors (beige, soft gray, muted green)
+- Design as philosophical inquiry
+```
+
+**代表作**:NHK Fabricated City
+**搜索关键词**:takram nhk data visualization
+
+---
+
+### 18. Kenya Hara - 空的设计
+**哲学**:设计不是填充,是清空
+**核心特征**:
+- 极致的留白(80%+)
+- 纸张质感的数字化
+- 白色的层次(暖白、冷白、米白)
+- 触觉的视觉化
+
+**提示词DNA**:
+```
+Kenya Hara "emptiness" design:
+- Extreme whitespace (80%+)
+- Paper texture and tactility in digital form
+- Layers of white (warm white, cool white, off-white)
+- Minimal color (if any, very desaturated)
+- Design by subtraction not addition
+- Zen simplicity
+```
+
+**代表作**:Muji art direction, 《Designing Design》
+**搜索关键词**:kenya hara designing design muji
+
+---
+
+### 19. Irma Boom - 书籍建筑师
+**哲学**:信息的物理诗学
+**核心特征**:
+- 非线性的信息架构
+- 边缘与边界的游戏
+- 意外的颜色组合(粉+红、橙+棕)
+- 手工艺的数字转译
+
+**提示词DNA**:
+```
+Irma Boom book architecture style:
+- Non-linear information structure
+- Play with edges, margins, boundaries
+- Unexpected color combos (pink+red, orange+brown)
+- Handcraft translated to digital
+- Dense information inviting exploration
+- Editorial design, unconventional grid
+```
+
+**代表作**:SHV Think Book (2136 pages)
+**搜索关键词**:irma boom shv think book
+
+---
+
+### 20. Neo Shen - 东方光影诗
+**哲学**:技术需要人的温度
+**核心特征**:
+- 水墨晕染的数字化
+- 柔和的光晕效果
+- 诗意的留白
+- 情感化的色彩(深蓝、暖灰、柔金)
+
+**提示词DNA**:
+```
+Neo Shen poetic Chinese aesthetic:
+- Digital interpretation of ink wash painting
+- Soft glow and light diffusion effects
+- Poetic negative space
+- Emotional palette (deep blues, warm grays, soft gold)
+- Calligraphic influences in typography
+- Atmospheric depth
+```
+
+**代表作**:Neo Shen digital art series
+**搜索关键词**:neo shen digital ink wash art
+
+---
+
+## 提示词使用说明
+
+**组合公式**:`[风格提示词DNA] + [场景模板(见scene-templates.md)] + [具体内容]`
+
+### 核心原则:描述情绪而非布局(Mood, Not Layout)
+
+AI图像生成的关键:短提示词 > 长提示词。描述3句情绪和内容,比30行布局细节效果更好。
+
+| 杀死多样性的写法 | 激发创造力的写法 |
+|----------------|----------------|
+| 指定颜色比例(60%/25%/15%) | 描述情绪("warm like Sunday morning") |
+| 规定布局位置("标题居中,图片右侧") | 引用具体美学("Pentagram editorial feel") |
+| 限制角色姿势和表情 | 让AI自然诠释风格 |
+| 列出所有要包含的视觉元素 | 描述观众应该感受到什么 |
+
+### Good / Bad 示例
+
+**Bad — 过度约束(AI生成出来空且平):**
+```
+Professional presentation slide. Dark background, light text.
+Title centered at top. Two columns below. Left column: bullet points.
+Right column: bar chart. Colors: navy 60%, white 30%, gold 10%.
+Font size: title 36pt, body 18pt. Margins: 40px all sides.
+```
+
+**Good — 情绪驱动(生成多样且有质感):**
+```
+A data visualization that feels like a Bloomberg Businessweek
+editorial spread. The key number "28.5%" should dominate the
+composition like a headline. Warm cream tones with sharp black
+typography. The data tells a story of dramatic channel shift.
+```
+
+### 执行路径选择
+
+根据速查表的「最佳路径」列选择:
+- **AI生成**:有明确视觉元素的风格(06/07/12/13/14/15/16/20),用 Gemini/Midjourney 直出
+- **HTML渲染**:依赖精确排版的风格(01/03/04/10/11/17/18),代码控制数据和布局
+- **混合**:HTML做骨架布局 + AI生成配图/背景(02/05/08/09/19)
+
+### 质量控制
+
+1. ❌ 不要直接写 "in the style of Pentagram" → ✅ 用具体设计特征描述
+2. 文字在AI生成中常出错 → 生成后替换文字
+3. 比例易失真 → 明确指定 aspect ratio
+4. 先生成3-5个变体,选择最佳后细化
+
+**默认审美禁区**(用户可按自己品牌 override):
+- ❌ 赛博霓虹/深蓝色底(#0D1117)
+- ❌ 封面图加个人署名/水印
+
+---
+
+**版本**:v2.1
+**更新日期**:2026-02-13
+**适用场景**:网页/PPT/PDF/信息图/封面/配图/App等所有视觉设计
+**与 image-to-slides 联动**:PPT场景可直接引用本文件风格,通过 image-to-slides skill 执行生成

+ 276 - 0
references/react-setup.md

@@ -0,0 +1,276 @@
+# React + Babel 项目规范
+
+用HTML+React+Babel做原型时必须遵守的技术规范。不遵守会炸。
+
+## Pinned Script Tags(必须用这些版本)
+
+在HTML的`<head>`里放这三个script tag,用**固定版本+integrity hash**:
+
+```html
+<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
+<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
+<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
+```
+
+**不要**用`react@18`或`react@latest`这种unpinned版本——会出现版本漂移/缓存问题。
+
+**不要**省略`integrity`——CDN一旦被劫持或篡改,这是防线。
+
+## 文件结构
+
+```
+项目名/
+├── index.html               # 主HTML
+├── components.jsx           # 组件文件(type="text/babel"加载)
+├── data.js                  # 数据文件
+└── styles.css               # 额外CSS(可选)
+```
+
+HTML里加载方式:
+
+```html
+<!-- 先React+Babel -->
+<script src="https://unpkg.com/react@18.3.1/..."></script>
+<script src="https://unpkg.com/react-dom@18.3.1/..."></script>
+<script src="https://unpkg.com/@babel/standalone@7.29.0/..."></script>
+
+<!-- 然后你的组件文件 -->
+<script type="text/babel" src="components.jsx"></script>
+<script type="text/babel" src="pages.jsx"></script>
+
+<!-- 最后主入口 -->
+<script type="text/babel">
+  const root = ReactDOM.createRoot(document.getElementById('root'));
+  root.render(<App />);
+</script>
+```
+
+**不要**用`type="module"`——会和Babel冲突。
+
+## 三条不可违反的规矩
+
+### 规矩1:styles 对象必须用唯一命名
+
+**错误**(多组件时必炸):
+```jsx
+// components.jsx
+const styles = { button: {...}, card: {...} };
+
+// pages.jsx  ← 同名覆盖!
+const styles = { container: {...}, header: {...} };
+```
+
+**正确**:每个组件文件的styles用唯一前缀。
+
+```jsx
+// terminal.jsx
+const terminalStyles = { 
+  screen: {...}, 
+  line: {...} 
+};
+
+// sidebar.jsx
+const sidebarStyles = { 
+  container: {...}, 
+  item: {...} 
+};
+```
+
+**或者用inline styles**(小组件推荐):
+```jsx
+<div style={{ padding: 16, background: '#111' }}>...</div>
+```
+
+这条是**非协商**的。每次写`const styles = {...}`都必须replace成specific命名,否则多组件加载时全栈报错。
+
+### 规矩2:Scope 不共享,需手动export
+
+**关键认知**:每个`<script type="text/babel">`被Babel独立编译,它们之间**scope不通**。`components.jsx`里定义的`Terminal`组件,在`pages.jsx`里**默认是undefined**。
+
+**解决方式**:在每个组件文件末尾,把要共享的组件/工具export到`window`:
+
+```jsx
+// components.jsx 末尾
+function Terminal(props) { ... }
+function Line(props) { ... }
+const colors = { green: '#...', red: '#...' };
+
+Object.assign(window, {
+  Terminal, Line, colors,
+  // 所有你要在别处用的都列在这里
+});
+```
+
+然后`pages.jsx`就能直接用`<Terminal />`,因为JSX会去`window.Terminal`找。
+
+### 规矩3:不要用 scrollIntoView
+
+`scrollIntoView`会把整个HTML容器往上推,搞坏web harness的布局。**永远不要用**。
+
+替代方案:
+```js
+// 滚到容器内某个位置
+container.scrollTop = targetElement.offsetTop;
+
+// 或者用element.scrollTo
+container.scrollTo({
+  top: targetElement.offsetTop - 100,
+  behavior: 'smooth'
+});
+```
+
+## 调 Claude API(HTML内)
+
+部分原生 design-agent 环境(如 Claude.ai Artifacts)有免配置的 `window.claude.complete`,但大部分 agent 环境(Claude Code / Codex / Cursor / Trae / etc.)本地里**没有**。
+
+如果你的 HTML 原型需要调用 LLM 做 demo(比如做个聊天 interface),两个选项:
+
+### 选项A:不真调,用mock
+
+Demo场景推荐。写一个假helper,返回预设的response:
+```jsx
+window.claude = {
+  async complete(prompt) {
+    await new Promise(r => setTimeout(r, 800)); // 模拟延迟
+    return "这是一个mock响应。真部署时请替换为真API。";
+  }
+};
+```
+
+### 选项B:真调Anthropic API
+
+需要API key,用户必须在HTML里填入自己的key才能跑。**永远不要把key硬编码在HTML里**。
+
+```html
+<input id="api-key" placeholder="粘贴你的Anthropic API key" />
+<script>
+window.claude = {
+  async complete(prompt) {
+    const key = document.getElementById('api-key').value;
+    const res = await fetch('https://api.anthropic.com/v1/messages', {
+      method: 'POST',
+      headers: {
+        'x-api-key': key,
+        'anthropic-version': '2023-06-01',
+        'content-type': 'application/json',
+      },
+      body: JSON.stringify({
+        model: 'claude-haiku-4-5',
+        max_tokens: 1024,
+        messages: [{ role: 'user', content: prompt }]
+      })
+    });
+    const data = await res.json();
+    return data.content[0].text;
+  }
+};
+</script>
+```
+
+**注意**:浏览器直接调Anthropic API会遇到CORS问题。如果用户给你的预览环境不支持CORS bypass,这条路不通。这时候用选项A mock,或者告诉用户需要一个proxy后端。
+
+### 选项 C:用 agent 侧的 LLM 能力生成 mock 数据
+
+如果只是本地演示用,可以在当前 agent 会话里临时调用该 agent 的 LLM 能力(或用户装的 multi-model 类 skill)先生成 mock 响应数据,再硬编码写进 HTML。这样 HTML 运行时完全不依赖任何 API。
+
+## 典型 HTML 起手模板
+
+拷贝这个模板作为React原型的骨架:
+
+```html
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Your Prototype Name</title>
+
+  <!-- React + Babel pinned -->
+  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
+  <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
+  <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
+
+  <style>
+    * { box-sizing: border-box; margin: 0; padding: 0; }
+    html, body { height: 100%; width: 100%; }
+    body { 
+      font-family: -apple-system, 'SF Pro Text', sans-serif;
+      background: #FAFAFA;
+      color: #1A1A1A;
+    }
+    #root { min-height: 100vh; }
+  </style>
+</head>
+<body>
+  <div id="root"></div>
+
+  <!-- 你的组件文件 -->
+  <script type="text/babel" src="components.jsx"></script>
+
+  <!-- 主入口 -->
+  <script type="text/babel">
+    const { useState, useEffect } = React;
+
+    function App() {
+      return (
+        <div style={{padding: 40}}>
+          <h1>Hello</h1>
+        </div>
+      );
+    }
+
+    const root = ReactDOM.createRoot(document.getElementById('root'));
+    root.render(<App />);
+  </script>
+</body>
+</html>
+```
+
+## 常见报错及解决
+
+**`styles is not defined` 或 `Cannot read property 'button' of undefined`**
+→ 你在一个文件里定义了`const styles`,另一个文件覆盖了。给每个改成specific命名。
+
+**`Terminal is not defined`**
+→ 跨文件引用时scope不通。在定义Terminal的文件末尾加`Object.assign(window, {Terminal})`。
+
+**整个页面白屏,控制台没错误**
+→ 多半是JSX语法错误但Babel没报在控制台。把`babel.min.js`临时换成`babel.js`非压缩版,错误信息更清晰。
+
+**ReactDOM.createRoot is not a function**
+→ 版本不对。确认用了react-dom@18.3.1(而不是17或其他)。
+
+**`Objects are not valid as a React child`**
+→ 你渲染了一个对象而不是JSX/字符串。通常是`{someObj}`写成了`{someObj.name}`。
+
+## 大项目怎么拆文件
+
+**>1000行的单文件**难维护。分拆思路:
+
+```
+项目/
+├── index.html
+├── src/
+│   ├── primitives.jsx      # 基础元素:Button、Card、Badge...
+│   ├── components.jsx      # 业务组件:UserCard、PostList...
+│   ├── pages/
+│   │   ├── home.jsx        # 首页
+│   │   ├── detail.jsx      # 详情页
+│   │   └── settings.jsx    # 设置页
+│   ├── router.jsx          # 简单路由(React state切换)
+│   └── app.jsx             # 入口组件
+└── data.js                 # mock data
+```
+
+HTML里按顺序加载:
+```html
+<script type="text/babel" src="src/primitives.jsx"></script>
+<script type="text/babel" src="src/components.jsx"></script>
+<script type="text/babel" src="src/pages/home.jsx"></script>
+<script type="text/babel" src="src/pages/detail.jsx"></script>
+<script type="text/babel" src="src/pages/settings.jsx"></script>
+<script type="text/babel" src="src/router.jsx"></script>
+<script type="text/babel" src="src/app.jsx"></script>
+```
+
+**每个文件末尾**都要`Object.assign(window, {...})`导出要共享的东西。

+ 262 - 0
references/scene-templates.md

@@ -0,0 +1,262 @@
+# 场景模板库:按输出类型组织
+
+> 与 design-styles.md 的「提示词DNA」组合使用。
+> 公式:`[风格提示词DNA] + [场景模板] + [具体内容描述]`
+
+---
+
+## 1. 公众号封面 / 文章题图
+
+**规格**:
+- 封面图:2.35:1(900×383px 或 1200×510px)
+- 正文插图:16:9(1200×675px)或 4:3(1200×900px)
+
+**关键设计要素**:
+- 视觉冲击力优先(用户在信息流中快速扫过)
+- 文字极少或无文字(公众号标题会覆盖在上面)
+- 色彩饱和度适中(微信阅读环境偏白)
+- 避免过度细节(缩略图也要可辨识)
+
+**推荐风格**:01 Pentagram / 11 Build / 12 Sagmeister / 18 Kenya Hara / 07 Field.io
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- Article cover image for WeChat subscription
+- Landscape format, 2.35:1 aspect ratio
+- Bold visual impact, minimal or no text
+- Moderate color saturation for white reading environment
+- Must remain recognizable as thumbnail
+- Clean composition with clear focal point
+```
+
+---
+
+## 2. 正文配图 / 概念插画
+
+**规格**:
+- 16:9(1200×675px)最通用
+- 1:1(800×800px)适合强调
+- 4:3(1200×900px)适合信息密集
+
+**关键设计要素**:
+- 服务于文章论点,不是装饰
+- 与上下文形成视觉节奏
+- 简洁表达一个核心概念
+- AI生成优先,HTML截图仅在精确数据表格时用
+
+**推荐风格**:根据文章调性选择,常用 01/04/10/17/18
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- Article illustration, concept visualization
+- [16:9 / 1:1 / 4:3] aspect ratio
+- Single clear concept: [描述核心概念]
+- Serve the argument, not decoration
+- [Light/Dark] background to match article tone
+```
+
+---
+
+## 3. 信息图 / 数据可视化
+
+**规格**:
+- 竖版长图:1080×1920px(手机阅读)
+- 横版:1920×1080px(文章内嵌)
+- 方形:1080×1080px(社交媒体)
+
+**关键设计要素**:
+- 信息层级清晰(标题 → 核心数据 → 细节)
+- 数据准确,不编造
+- 视觉引导线(用户阅读路径)
+- 适当使用图标/图表辅助理解
+
+**推荐风格**:04 Fathom / 10 Müller-Brockmann / 02 Stamen / 17 Takram
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- Infographic / data visualization
+- [Vertical 1080x1920 / Horizontal 1920x1080 / Square 1080x1080]
+- Clear information hierarchy: title → key data → details
+- Visual flow guiding reader's eye path
+- Icons and charts for comprehension
+- Data-accurate, no decorative distortion
+```
+
+---
+
+## 4. PPT / Keynote 演示
+
+**规格**:
+- 标准:16:9(1920×1080px)
+- 宽屏:16:10(1920×1200px)
+
+**关键设计要素**:
+- 每页一个核心信息(不堆砌)
+- 字号层级明确(标题40pt+ / 正文24pt+ / 注释16pt+)
+- 大量留白,投影时更清晰
+- 图文比例至少 60:40
+- 一致的视觉系统(颜色、字体、间距)
+
+**推荐风格**:01 Pentagram / 10 Müller-Brockmann / 11 Build / 18 Kenya Hara / 04 Fathom
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- Presentation slide design, 16:9
+- One core message per slide
+- Clear type hierarchy (title 40pt+, body 24pt+)
+- Generous whitespace for projection clarity
+- Consistent visual system throughout
+- [Light/Dark] theme
+```
+
+---
+
+## 5. PDF 白皮书 / 技术报告
+
+**规格**:
+- A4 纵向(210×297mm / 595×842pt)
+- Letter 纵向(216×279mm / 612×792pt)
+
+**关键设计要素**:
+- 长文阅读优化(行宽66字符、行高1.5-1.8)
+- 清晰的章节导航系统
+- 页眉/页脚/页码的统一设计
+- 图表与正文的优雅共存
+- 引用/注释系统
+- 封面页设计精致
+
+**推荐风格**:10 Müller-Brockmann / 04 Fathom / 03 Information Architects / 17 Takram / 19 Irma Boom
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- PDF document / white paper design
+- A4 portrait format (210×297mm)
+- Long-form reading optimized (66 char line width, 1.5 line height)
+- Clear chapter navigation system
+- Elegant header/footer/page number design
+- Charts integrated with body text
+- Professional cover page
+```
+
+---
+
+## 6. 落地页 / 产品官网
+
+**规格**:
+- Desktop: 1440px 宽度设计(响应至320px)
+- 首屏高度:100vh
+
+**关键设计要素**:
+- 首屏5秒内传达核心价值
+- 清晰的CTA(行动按钮)
+- 滚动叙事结构(问题→方案→证明→行动)
+- 移动端适配
+- 加载速度
+
+**推荐风格**:05 Locomotive / 01 Pentagram / 11 Build / 08 Resn / 06 Active Theory
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- Landing page / product website
+- Desktop 1440px width, responsive
+- Hero section 100vh, core value in 5 seconds
+- Clear CTA button design
+- Scroll narrative: problem → solution → proof → action
+- Modern web aesthetic
+```
+
+---
+
+## 7. App UI / 原型界面
+
+**规格**:
+- iOS: 390×844pt(iPhone 15)
+- Android: 360×800dp
+- 平板: 1024×1366pt(iPad Pro)
+
+**关键设计要素**:
+- 触摸友好(最小点击区44×44pt)
+- 系统设计语言一致性
+- 状态栏/导航栏/Tab栏的标准处理
+- 信息密度适中(移动端不宜过密)
+
+**推荐风格**:17 Takram / 11 Build / 03 Information Architects / 01 Pentagram
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- Mobile app UI design
+- iOS [390×844pt] / Android [360×800dp]
+- Touch-friendly (44pt minimum tap targets)
+- Consistent design system
+- Standard status bar / navigation / tab bar
+- Moderate information density
+```
+
+---
+
+## 8. 小红书配图
+
+**规格**:
+- 竖版:3:4(1080×1440px)最佳
+- 方形:1:1(1080×1080px)
+- 首图决定点击率
+
+**关键设计要素**:
+- 视觉吸引力第一(在瀑布流中竞争)
+- 可以有少量文字(但不超过画面20%)
+- 色彩鲜明但不俗
+- 生活感/质感/氛围感
+
+**推荐风格**:12 Sagmeister / 11 Build / 20 Neo Shen / 09 Experimental Jetset
+
+**场景提示词模板**:
+```
+[风格DNA插入此处]
+- Social media image for Xiaohongshu (RED)
+- Vertical 3:4 (1080×1440px)
+- Eye-catching in waterfall feed
+- Minimal text overlay (under 20% of area)
+- Vivid but tasteful colors
+- Lifestyle/texture/atmosphere feel
+```
+
+---
+
+## 组合示例
+
+**场景**:公众号封面,介绍一款AI编程工具,想要专业但有温度
+
+**Step 1**:选风格 → 17 Takram(专业+温度)
+**Step 2**:取Takram提示词DNA + 公众号封面模板
+
+```
+Takram Japanese speculative design:
+- Elegant concept prototypes and diagrams
+- Soft tech aesthetic (rounded corners, gentle shadows)
+- Charts and diagrams as art pieces
+- Modest sophistication
+- Neutral natural colors (beige, soft gray, muted green)
+- Design as philosophical inquiry
+
+Article cover image for WeChat subscription
+- Landscape format, 2.35:1 aspect ratio (1200×510px)
+- Bold visual impact, minimal text
+- Moderate color saturation for white reading environment
+- Must remain recognizable as thumbnail
+- Clean composition with clear focal point
+
+Content: An AI coding assistant tool, showing the concept of human-AI collaboration
+in software development, warm and professional atmosphere
+```
+
+---
+
+**版本**:v1.0
+**更新日期**:2026-02-13

+ 429 - 0
references/slide-decks.md

@@ -0,0 +1,429 @@
+# Slide Decks:HTML幻灯片制作规范
+
+做幻灯片是设计工作的高频场景。这份文档说明怎么做好HTML幻灯片。
+
+**和huashu-slides skill的区别**:huashu-slides focus on「AI演示文稿全流程」(含PPT导出);本文档focus on「用HTML本身作为呈现媒介」的设计方法论。两者可以配合使用——用这里的方法做高质量的HTML deck,再用huashu-slides导出成PPTX。
+
+---
+
+## 🛑 先定架构:单文件 还是 多文件?
+
+**这个选择是做幻灯片的第一步,错了会反复踩坑。先读完这一节再动手。**
+
+### 两种架构对比
+
+| 维度 | 单文件 + `deck_stage.js` | **多文件 + `deck_index.html` 拼接器** |
+|------|--------------------------|--------------------------------------|
+| 代码结构 | 一个 HTML,所有 slide 是 `<section>` | 每页独立 HTML,`index.html` 用 iframe 拼接 |
+| CSS 作用域 | ❌ 全局,一页的样式可能影响所有页 | ✅ 天然隔离,iframe 各自一片天 |
+| 验证粒度 | ❌ 要 JS goTo 才能切到某页 | ✅ 单页文件双击就能在浏览器看 |
+| 并行开发 | ❌ 一个文件,多 agent 改会冲突 | ✅ 多 agent 可并行做不同页,零冲突 merge |
+| 调试难度 | ❌ 一处 CSS 出错,全 deck 翻车 | ✅ 一页出错只影响自己 |
+| 内嵌交互 | ✅ 跨页共享状态很简单 | 🟡 iframe 间需 postMessage |
+| 打印 PDF | ✅ 内置 | ✅ 拼接器 beforeprint 遍历 iframe |
+| 键盘导航 | ✅ 内置 | ✅ 拼接器内置 |
+
+### 选哪个?(决策树)
+
+```
+│ 问:deck 预计有多少页?
+├── ≤10 页、需要 in-deck 动画或跨页交互、pitch deck → 单文件
+└── ≥10 页、学术讲座、课件、长 deck、多 agent 并行 → 多文件(推荐)
+```
+
+**默认走多文件路径**。它不是「备选」,是**长 deck 和团队协作的主路径**。原因:单文件架构的每一个优势(键盘导航、打印、scale)多文件都有,而多文件的作用域隔离和可验证性是单文件补不回来的。
+
+### 为什么这条规则这么硬?(真实事故记录)
+
+单文件架构曾经在 AI心理学讲座 deck 制作中连踩四坑:
+
+1. **CSS 特异性覆盖**:`.emotion-slide { display: grid }` (特异性 10) 干翻 `deck-stage > section { display: none }` (特异性 2),导致所有页同时渲染叠加。
+2. **Shadow DOM slot 规则被外层 CSS 压制**:`::slotted(section) { display: none }` 挡不住 outer rule 的覆盖,sections 不肯隐藏。
+3. **localStorage + hash 导航竞态**:刷新后不是跳到 hash 位置,而是停在 localStorage 记录的旧位置。
+4. **验证成本高**:必须 `page.evaluate(d => d.goTo(n))` 才能截某页,比直接 `goto(file://.../slides/05-X.html)` 慢一倍,还常报错。
+
+全部根因是**单一全局命名空间**——多文件架构从物理层面把这些问题消除了。
+
+---
+
+## 路径 A(默认):多文件架构
+
+### 目录结构
+
+```
+我的Deck/
+├── index.html              # 从 assets/deck_index.html 复制来,改 MANIFEST
+├── shared/
+│   ├── tokens.css          # 共享设计 token(色板/字号/常用 chrome)
+│   └── fonts.html          # <link> 引入 Google Fonts(每页 include)
+└── slides/
+    ├── 01-cover.html       # 每个文件都是完整 1920×1080 HTML
+    ├── 02-agenda.html
+    ├── 03-problem.html
+    └── ...
+```
+
+### 每张 slide 的模板骨架
+
+```html
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<title>P05 · Chapter Title</title>
+<link href="https://fonts.googleapis.com/css2?family=..." rel="stylesheet">
+<link rel="stylesheet" href="../shared/tokens.css">
+<style>
+  /* 这一页独有的样式。用任何 class 名都不会污染别的页。*/
+  body { padding: 120px; }
+  .my-thing { ... }
+</style>
+</head>
+<body>
+  <!-- 1920×1080 的内容(由 body 的 width/height 在 tokens.css 里锁定)-->
+  <div class="page-header">...</div>
+  <div>...</div>
+  <div class="page-footer">...</div>
+</body>
+</html>
+```
+
+**关键约束**:
+- `<body>` 就是画布,直接在上面布局。不要包 `<section>` 或其他 wrapper。
+- `width: 1920px; height: 1080px` 由 `shared/tokens.css` 里的 `body` 规则锁定。
+- 引 `shared/tokens.css` 共享设计 token(色板、字号、page-header/footer 等)。
+- 字体 `<link>` 每页自己写(fonts 单独 import 不贵,且保证每页独立可打开)。
+
+### 拼接器:`deck_index.html`
+
+**直接从 `assets/deck_index.html` 复制**。你只需要改一处——`window.DECK_MANIFEST` 数组,按顺序列出所有 slide 文件名和人类可读标签:
+
+```js
+window.DECK_MANIFEST = [
+  { file: "slides/01-cover.html",    label: "封面" },
+  { file: "slides/02-agenda.html",   label: "目录" },
+  { file: "slides/03-problem.html",  label: "问题陈述" },
+  // ...
+];
+```
+
+拼接器已内置:键盘导航(←/→/Home/End/数字键/P 打印)、scale + letterbox、右下计数器、localStorage 记忆、hash 跳页、打印模式(遍历 iframe 按页输出 PDF)。
+
+### 单页验证(这是多文件架构的杀手级优势)
+
+每张 slide 都是独立 HTML。**做完一张就在浏览器双击打开看**:
+
+```bash
+open slides/05-personas.html
+```
+
+Playwright 截图也是直接 `goto(file://.../slides/05-personas.html)`,不需要 JS 跳页,也不会被别的页的 CSS 干扰。这让「改一点验一点」的工作流成本接近零。
+
+### 并行开发
+
+把每张 slide 的任务拆给不同 agent,同时跑——HTML 文件彼此独立,merge 时没有冲突。长 deck 用这种并行方式能把制作时间压到 1/N。
+
+### `shared/tokens.css` 该放什么
+
+只放**真正跨页共用**的东西:
+
+- CSS 变量(色板、字号阶、间距阶)
+- `body { width: 1920px; height: 1080px; }` 这样的 canvas 锁定
+- `.page-header` / `.page-footer` 这种每页都用一模一样的 chrome
+
+**不要**把单页的布局 class 塞进来——那会退化回单文件架构的全局污染问题。
+
+---
+
+## 路径 B(小 deck):单文件 + `deck_stage.js`
+
+适用于 ≤10 页、需要跨页共享状态(比如一个 React tweaks 面板要操控所有页)、或者做 pitch deck demo 这种要求极度紧凑的场景。
+
+### 基本用法
+
+1. 从 `assets/deck_stage.js` 读取内容,嵌入 HTML 的 `<script>`(或 `<script src="deck_stage.js">`)
+2. 在 body 里用 `<deck-stage>` 包 slide:
+
+```html
+<deck-stage>
+  <section>
+    <h1>Slide 1</h1>
+  </section>
+  <section>
+    <h1>Slide 2</h1>
+  </section>
+</deck-stage>
+```
+
+### ⚠️ 单文件架构的 CSS 陷阱(务必阅读)
+
+单文件架构最常见的坑——**`display` 属性被单页样式偷走**。
+
+如果你给某张 slide 的样式写了 `display: flex/grid`:
+
+```css
+.emotion-slide { display: grid; }   /* 特异性: 10 */
+```
+
+它会压过 deck 级的「隐藏非 active 页」规则:
+
+```css
+deck-stage > section { display: none; }   /* 特异性: 2 */
+```
+
+结果所有 slide 同时渲染叠加。
+
+**修复模式**(写 deck 时必做):
+
+```css
+/* ✅ 用 :not(.active) + !important 锁死「非激活即隐藏」 */
+deck-stage > section:not(.active) {
+  display: none !important;
+}
+```
+
+特异性+权重双保险,单页 layout class 的 `display: grid/flex` 就不会影响可见性。
+
+另一个替代方案:**把单页的 flex/grid 写到内部 wrapper div 上**,section 本身只管 `display: block/none`。
+
+### 自定义尺寸
+
+```html
+<deck-stage width="1080" height="1920">
+  <!-- 9:16 竖版 -->
+</deck-stage>
+```
+
+---
+
+## Slide Labels
+
+Deck_stage 和 deck_index 都会给每页打标签(计数器显示)。给它们**更有意义**的 label:
+
+**多文件**:在 `MANIFEST` 里写 `{ file, label: "04 问题陈述" }`
+**单文件**:在 section 上加 `<section data-screen-label="04 Problem Statement">`
+
+**关键:Slide 编号从 1 开始,不要从 0**。
+
+用户说"slide 5"时,他指的是第 5 张,永远不是数组位置 `[4]`。人类不说 0-indexed。
+
+---
+
+## Speaker Notes
+
+**默认不加**,只在用户明确要求时才加。
+
+加了 speaker notes 你就可以把 slide 上的文字减少到最小,focus on impactful visuals——notes 承载完整 script。
+
+### 格式
+
+**多文件**:在 `index.html` 的 `<head>` 里写:
+
+```html
+<script type="application/json" id="speaker-notes">
+[
+  "第1张的 script...",
+  "第2张的 script...",
+  "..."
+]
+</script>
+```
+
+**单文件**:同上位置。
+
+### Notes 写作要点
+
+- **完整**:不是提纲,是真要讲的话
+- **对话式**:像平时说话,不是书面语
+- **对应**:数组第 N 个对应第 N 张 slide
+- **长度**:200-400 字最佳
+- **情绪线**:标注重音、停顿、强调点
+
+---
+
+## Slide 设计模式
+
+### 1. 建立一个系统(必做)
+
+探索完 design context 后,**先口头说你要用的系统**:
+
+```markdown
+Deck系统:
+- 背景色:最多2种(90% 白 + 10% 深色 section divider)
+- 字型:display 用 Instrument Serif,body 用 Geist Sans
+- 节奏:section divider 用 full-bleed 彩色 + 白字,普通 slide 白底
+- 图像:hero slide 用 full-bleed 照片,data slide 用 chart
+
+我按这个系统做,有问题告诉我。
+```
+
+用户确认后再往下做。
+
+### 2. 常用 slide layouts
+
+- **Title slide**:纯色背景 + 巨大标题 + 副标题 + 作者/日期
+- **Section divider**:彩色背景 + 章节号 + 章节标题
+- **Content slide**:白底 + 标题 + 1-3 bullet points
+- **Data slide**:标题 + 大图表/数字 + 简短说明
+- **Image slide**:full-bleed 照片 + 底部小 caption
+- **Quote slide**:留白 + 巨大 quote + attribution
+- **Two-column**:左右对比(vs / before-after / problem-solution)
+
+一个 deck 里最多用 4-5 种 layout。
+
+### 3. Scale(再次强调)
+
+- 正文最小 **24px**,理想 28-36px
+- 标题 **60-120px**
+- Hero 字 **180-240px**
+- 幻灯片是给 10 米外看的,字要够大
+
+### 4. 视觉节奏
+
+Deck 需要 **intentional variety**:
+
+- 颜色节奏:大部分白底 + 偶尔彩色 section divider + 偶尔 dark 片段
+- 密度节奏:几张 text-heavy 的 + 几张 image-heavy 的 + 几张 quote 留白
+- 字号节奏:正常标题 + 偶尔巨型 hero 文字
+
+**不要每张 slide 长一样**——那是 PPT 模板,不是设计。
+
+### 5. 空间呼吸(数据密集页必读)
+
+**新手最容易踩的坑**:把所有能放的信息都塞进一页。
+
+信息密度 ≠ 有效信息传达。学术/演讲类 deck 尤其要克制:
+
+- 列表/矩阵页:不要把 N 个元素都画成同一大小。用 **主次分层**——今天要聊的 5 个放大做主角,剩下 16 个缩小做背景 hint。
+- 大数字页:数字本身是视觉主角。周围的 caption 不要超过 3 行,否则观众眼球来回跳。
+- 引用页:引语和 attribution 之间要有留白隔开,不要贴在一起。
+
+对照「数据是不是主角」「文字有没有挤在一起」两条自我审查,改到留白让你有点不安为止。
+
+---
+
+## 打印为 PDF
+
+**多文件**:`deck_index.html` 已处理 `beforeprint` 事件,按页输出 PDF。
+
+**单文件**:`deck_stage.js` 同样处理。
+
+打印样式已写好,不需要额外写 `@media print` CSS。
+
+---
+
+## 导出为 PPTX / PDF(自助脚本)
+
+HTML 优先是第一公民。但用户经常需要 PPTX/PDF 交付。提供两个通用脚本,**任何多文件 deck 都能用**,位于 `scripts/` 下:
+
+### `export_deck_pdf.mjs` — 导出矢量 PDF(推荐)
+
+```bash
+node scripts/export_deck_pdf.mjs --slides <slides-dir> --out deck.pdf
+```
+
+**特点**:
+- 文字**保留矢量**(可复制、可搜索)
+- 视觉 100% 保真(Playwright 内嵌 Chromium 渲染后打印)
+- **不需要改 HTML 任何一个字**
+- 每个 slide 独立 `page.pdf()`,再用 `pdf-lib` 合并
+
+**依赖**:`npm install playwright pdf-lib`
+
+**限制**:PDF 不能再编辑文字——要改回到 HTML 改。
+
+### `export_deck_pptx.mjs` — 导出 PPTX(两种模式)
+
+```bash
+# 图片铺底(视觉 100% 保真,不可编辑文字)
+node scripts/export_deck_pptx.mjs --slides <dir> --out deck.pptx --mode image
+
+# 每个文本独立文本框(可编辑,但字体会回落)
+node scripts/export_deck_pptx.mjs --slides <dir> --out deck.pptx --mode editable
+```
+
+| 模式 | 视觉保真 | 文字可编辑 | 工作原理 | 限制 |
+|------|---------|----------|---------|------|
+| `image` | ✅ 100% | ❌ | Playwright 截图 → pptxgenjs addImage | 文字变图片 |
+| `editable` | 🟡 ~70% | ✅ | html2pptx 提取每个文本框 | 见下方约束 |
+
+**editable 模式的硬性约束**(用户 HTML 必须满足,否则该页 skip):
+- 所有文字必须在 `<p>`/`<h1>`-`<h6>`/`<ul>`/`<ol>` 里(禁止裸文本 div)
+- `<p>`/`<h*>` 标签自身不能有 background/border/shadow(放外层 div)
+- 不用 `::before`/`::after` 插入装饰文字(伪元素提不出来)
+- inline 元素(span/em/strong)不能有 margin
+- 不用 CSS gradient(不可渲染)
+- div 不用 `background-image`(用 `<img>`)
+
+脚本已内置**自动预处理器**——把 "叶子 div 里的裸文本" 自动包成 `<p>`(保留 class)。这解决了最常见的违规(裸文本)。但其他违规(p 上有 border、span 上有 margin 等)仍需 HTML 源头合规。
+
+**editable 模式的另一个 caveat——字体回落**:
+- Playwright 用 webfont 测量 text-box 尺寸;PowerPoint/Keynote 用本机字体渲染
+- 两者不同时会有**溢出或错位**——每页都要肉眼过
+- 建议目标机器装好 HTML 里用的字体,或 fallback 到 `system-ui`
+
+### 从一开始就让 HTML 对导出友好
+
+对性能最稳的 deck:**从写 HTML 时就按 editable 模式的约束写**。这样 `--mode editable` 可以直接全部 pass。额外成本不大:
+
+```html
+<!-- ❌ 不好 -->
+<div class="title">关键发现</div>
+
+<!-- ✅ 好(p 包裹,class 继承) -->
+<p class="title">关键发现</p>
+
+<!-- ❌ 不好(border 在 p 上) -->
+<p class="stat" style="border-left: 3px solid red;">41%</p>
+
+<!-- ✅ 好(border 在外层 div) -->
+<div class="stat-wrap" style="border-left: 3px solid red;">
+  <p class="stat">41%</p>
+</div>
+```
+
+### 何时选哪个
+
+| 场景 | 推荐 |
+|------|------|
+| 给主办方/档案存档 | **PDF**(通用、高保真、文字可搜) |
+| 发给协作者让他们微调文字 | **PPTX editable**(接受字体回落) |
+| 要现场演讲、不改内容 | **PDF** 或 **PPTX image** |
+| HTML 是首选呈现媒介 | 直接浏览器播放,导出只是备份 |
+
+## 导出为可编辑 PPTX 的深度路径(仅长期项目)
+
+如果你的 deck 会长期维护、反复修改、团队协作——建议**一开始就按 html2pptx 约束写 HTML**,让 `--mode editable` 稳定通过。详见 `huashu-slides` skill 的 `references/prompt-templates.md` 第 2 节(4 条硬性约束)。
+
+---
+
+## 常见问题
+
+**多文件:iframe 里的页打不开 / 白屏**
+→ 检查 `MANIFEST` 的 `file` 路径是否相对 `index.html` 正确。用浏览器 DevTools 看 iframe 的 src 能否直接访问。
+
+**多文件:某页样式和别页冲突**
+→ 不可能(iframe 隔离)。如果感觉冲突,那是缓存——Cmd+Shift+R 强刷。
+
+**单文件:多 slide 同时渲染叠加**
+→ CSS 特异性问题。看上面「单文件架构的 CSS 陷阱」一节。
+
+**单文件:缩放看起来不对**
+→ 检查是否所有 slide 直接挂在 `<deck-stage>` 下作为 `<section>`。中间不能包 `<div>`。
+
+**单文件:想跳到特定 slide**
+→ URL 加 hash:`index.html#slide-5` 跳到第 5 张。
+
+**两种架构都适用:字在不同屏幕下位置不一致**
+→ 用固定尺寸(1920×1080)和 `px` 单位,不要用 `vw`/`vh` 或 `%`。缩放统一处理。
+
+---
+
+## 验证检查清单(做完 deck 必过)
+
+1. [ ] 浏览器直接打开 `index.html`(或主 HTML),检查首页无破图、字体已加载
+2. [ ] 按 → 键翻到每一页,没有空白页、没有布局错位
+3. [ ] 按 P 键打印预览,每页恰好一张 A4(或 1920×1080)且无裁切
+4. [ ] 随机选 3 页 Cmd+Shift+R 强刷,localStorage 记忆正常工作
+5. [ ] Playwright 批量截图(单页架构:遍历 `slides/*.html`;单文件架构:用 goTo 切换),人工肉眼过一遍
+6. [ ] 搜一下 `TODO` / `placeholder` 残留,确认都清理了

+ 313 - 0
references/tweaks-system.md

@@ -0,0 +1,313 @@
+# Tweaks:设计变体实时调参
+
+Tweaks是这个skill里很核心的能力——让用户不改代码就能实时切换variations/调整参数。
+
+**跨 agent 环境适配**:某些 design-agent 原生环境(如 Claude.ai Artifacts)依赖 host 的 postMessage 把 tweak 值回写源码做持久化。本 skill 采用**纯前端 localStorage 方案**——效果一致(刷新保留状态),但持久化发生在浏览器 localStorage 而不是源码文件。这个方案在任何 agent 环境(Claude Code / Codex / Cursor / Trae / etc.)都能工作。
+
+## 何时加 Tweaks
+
+- 用户明确要求"能调参"/"多个版本切换"
+- 设计有多个variations需要对比时
+- 用户没明说,但你主观判断**加几个有启发性的tweaks能帮用户看到可能性**
+
+默认推荐:**每个设计都加2-3个tweaks**(颜色主题/字号/layout变体)即使用户没要求——让用户看到可能性空间是设计服务的一部分。
+
+## 实现方式(纯前端版)
+
+### 基本结构
+
+```jsx
+const TWEAK_DEFAULTS = {
+  "primaryColor": "#D97757",
+  "fontSize": 16,
+  "density": "comfortable",
+  "dark": false
+};
+
+function useTweaks() {
+  const [tweaks, setTweaks] = React.useState(() => {
+    try {
+      const stored = localStorage.getItem('design-tweaks');
+      return stored ? { ...TWEAK_DEFAULTS, ...JSON.parse(stored) } : TWEAK_DEFAULTS;
+    } catch {
+      return TWEAK_DEFAULTS;
+    }
+  });
+
+  const update = (patch) => {
+    const next = { ...tweaks, ...patch };
+    setTweaks(next);
+    try {
+      localStorage.setItem('design-tweaks', JSON.stringify(next));
+    } catch {}
+  };
+
+  const reset = () => {
+    setTweaks(TWEAK_DEFAULTS);
+    try {
+      localStorage.removeItem('design-tweaks');
+    } catch {}
+  };
+
+  return { tweaks, update, reset };
+}
+```
+
+### Tweaks面板UI
+
+右下角浮动面板。可折叠:
+
+```jsx
+function TweaksPanel() {
+  const { tweaks, update, reset } = useTweaks();
+  const [open, setOpen] = React.useState(false);
+
+  return (
+    <div style={{
+      position: 'fixed',
+      bottom: 20,
+      right: 20,
+      zIndex: 9999,
+    }}>
+      {open ? (
+        <div style={{
+          background: 'white',
+          border: '1px solid #e5e5e5',
+          borderRadius: 12,
+          padding: 20,
+          boxShadow: '0 10px 40px rgba(0,0,0,0.12)',
+          width: 280,
+          fontFamily: 'system-ui',
+          fontSize: 13,
+        }}>
+          <div style={{ 
+            display: 'flex', 
+            justifyContent: 'space-between', 
+            alignItems: 'center',
+            marginBottom: 16,
+          }}>
+            <strong>Tweaks</strong>
+            <button onClick={() => setOpen(false)} style={{
+              border: 'none', background: 'none', cursor: 'pointer', fontSize: 16,
+            }}>×</button>
+          </div>
+
+          {/* 颜色 */}
+          <label style={{ display: 'block', marginBottom: 12 }}>
+            <div style={{ marginBottom: 4, color: '#666' }}>主色</div>
+            <input 
+              type="color" 
+              value={tweaks.primaryColor} 
+              onChange={e => update({ primaryColor: e.target.value })}
+              style={{ width: '100%', height: 32 }}
+            />
+          </label>
+
+          {/* 字号slider */}
+          <label style={{ display: 'block', marginBottom: 12 }}>
+            <div style={{ marginBottom: 4, color: '#666' }}>字号 ({tweaks.fontSize}px)</div>
+            <input 
+              type="range" 
+              min={12} max={24} step={1}
+              value={tweaks.fontSize}
+              onChange={e => update({ fontSize: +e.target.value })}
+              style={{ width: '100%' }}
+            />
+          </label>
+
+          {/* 密度选项 */}
+          <label style={{ display: 'block', marginBottom: 12 }}>
+            <div style={{ marginBottom: 4, color: '#666' }}>密度</div>
+            <select 
+              value={tweaks.density}
+              onChange={e => update({ density: e.target.value })}
+              style={{ width: '100%', padding: 6 }}
+            >
+              <option value="compact">紧凑</option>
+              <option value="comfortable">舒适</option>
+              <option value="spacious">宽松</option>
+            </select>
+          </label>
+
+          {/* 暗黑模式toggle */}
+          <label style={{ 
+            display: 'flex', 
+            alignItems: 'center',
+            gap: 8,
+            marginBottom: 16,
+          }}>
+            <input 
+              type="checkbox" 
+              checked={tweaks.dark}
+              onChange={e => update({ dark: e.target.checked })}
+            />
+            <span>暗黑模式</span>
+          </label>
+
+          <button onClick={reset} style={{
+            width: '100%',
+            padding: '8px 12px',
+            background: '#f5f5f5',
+            border: 'none',
+            borderRadius: 6,
+            cursor: 'pointer',
+            fontSize: 12,
+          }}>重置</button>
+        </div>
+      ) : (
+        <button 
+          onClick={() => setOpen(true)}
+          style={{
+            background: '#1A1A1A',
+            color: 'white',
+            border: 'none',
+            borderRadius: 999,
+            padding: '10px 16px',
+            fontSize: 12,
+            cursor: 'pointer',
+            boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
+          }}
+        >⚙ Tweaks</button>
+      )}
+    </div>
+  );
+}
+```
+
+### 应用Tweaks
+
+在主组件里用Tweaks:
+
+```jsx
+function App() {
+  const { tweaks } = useTweaks();
+
+  return (
+    <div style={{
+      '--primary': tweaks.primaryColor,
+      '--font-size': `${tweaks.fontSize}px`,
+      background: tweaks.dark ? '#0A0A0A' : '#FAFAFA',
+      color: tweaks.dark ? '#FAFAFA' : '#1A1A1A',
+    }}>
+      {/* 你的内容 */}
+      <TweaksPanel />
+    </div>
+  );
+}
+```
+
+CSS里用变量:
+
+```css
+button.cta {
+  background: var(--primary);
+  color: white;
+  font-size: var(--font-size);
+}
+```
+
+## 典型 Tweak 选项
+
+给不同类型的设计加什么tweaks:
+
+### 通用
+- 主色(color picker)
+- 字号(slider 12-24px)
+- 字型(select:display font vs body font)
+- 暗黑模式(toggle)
+
+### 幻灯片deck
+- 主题(light/dark/brand)
+- 背景样式(solid/gradient/image)
+- 字体对比(更装饰 vs 更克制)
+- 信息密度(minimal/standard/dense)
+
+### 产品原型
+- 布局变体(layout A / B / C)
+- 交互速度(animation speed 0.5x-2x)
+- 数据量(mock数据条数 5/20/100)
+- 状态(empty/loading/success/error)
+
+### 动画
+- 速度(0.5x-2x)
+- 循环(once/loop/ping-pong)
+- Easing(linear/easeOut/spring)
+
+### Landing page
+- Hero风格(image/gradient/pattern/solid)
+- CTA文案(几种变体)
+- 结构(single column / two column / sidebar)
+
+## Tweaks设计原则
+
+### 1. 有意义的选项,不是折腾人的
+
+每个tweak必须展示**真实的设计选项**。别加那种谁都不会真切换的tweak(比如border-radius 0-50px的slider——用户调完发现所有中间值都丑)。
+
+好的tweak暴露**离散的、有思考的variations**:
+- "圆角风格":无圆角 / 微圆角 / 大圆角(三个选项)
+- 不是:"圆角":0-50px slider
+
+### 2. 少即是多
+
+一个设计的Tweaks面板**最多5-6个**选项。再多就变成"配置页面",失去了快速探索variations的意义。
+
+### 3. 默认值是完成设计
+
+Tweaks是**锦上添花**。默认值必须本身就是一个完整、可发布的设计。用户关闭Tweaks面板后看到的就是产出。
+
+### 4. 合理分组
+
+选项多时分组显示:
+
+```
+---- 视觉 ----
+主色 | 字号 | 暗黑模式
+
+---- 布局 ----
+密度 | 侧栏位置
+
+---- 内容 ----
+显示数据量 | 状态
+```
+
+## 向前兼容源码级持久化 host
+
+如果你以后想把设计上传到支持源码级 tweaks(如 Claude.ai Artifacts)的环境也能跑,保留 **EDITMODE 标记块**:
+
+```jsx
+const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
+  "primaryColor": "#D97757",
+  "fontSize": 16,
+  "density": "comfortable",
+  "dark": false
+}/*EDITMODE-END*/;
+```
+
+标记块在 localStorage 方案里**无作用**(只是个普通注释),但在支持源码回写的 host 里会被读取,实现源码级持久化。加上这个对当前环境无害,同时保持向前兼容。
+
+## 常见问题
+
+**Tweaks面板挡住设计内容**
+→ 让它可关闭。默认关闭,显示一个小按钮,用户点了才展开。
+
+**用户切换tweaks后还要重复设置**
+→ 已经用localStorage。如果刷新后不持久,检查localStorage是否可用(无痕模式会失败,要catch)。
+
+**多个HTML页面想共享tweaks**
+→ 给localStorage key加project name:`design-tweaks-[projectName]`。
+
+**我想让tweak之间有联动关系**
+→ 在`update`里加逻辑:
+
+```jsx
+const update = (patch) => {
+  let next = { ...tweaks, ...patch };
+  // 联动:选dark mode时自动切换字体配色
+  if (patch.dark === true && !patch.textColor) {
+    next.textColor = '#F0EEE6';
+  }
+  setTweaks(next);
+  localStorage.setItem(...);
+};
+```

+ 183 - 0
references/verification.md

@@ -0,0 +1,183 @@
+# Verification:输出验证流程
+
+一些 design-agent 原生环境(如 Claude.ai Artifacts)有内置的 `fork_verifier_agent` 起 subagent 用 iframe 截图检查。大部分 agent 环境(Claude Code / Codex / Cursor / Trae / 等)里没有这个内置能力——用 Playwright 手动做就能覆盖相同的验证场景。
+
+## 验证清单
+
+每次产出HTML后,按这个清单做一遍:
+
+### 1. 浏览器渲染检查(必做)
+
+最基础:**HTML能不能打开**?在macOS上:
+
+```bash
+open -a "Google Chrome" "/path/to/your/design.html"
+```
+
+或者用Playwright截图(下一节)。
+
+### 2. 控制台错误检查
+
+HTML文件里最常见的问题是JS报错导致白屏。用Playwright跑一遍:
+
+```bash
+python "$SKILL/scripts/verify.py" path/to/design.html
+```
+
+这个脚本会:
+1. 用headless chromium打开HTML
+2. 截图保存到项目目录
+3. 抓取控制台错误
+4. 报告status
+
+详见`scripts/verify.py`。
+
+### 3. 多视口检查
+
+如果是响应式设计,抓多个viewport:
+
+```bash
+python verify.py design.html --viewports 1920x1080,1440x900,768x1024,375x667
+```
+
+### 4. 交互检查
+
+Tweaks、动画、按钮切换——默认的静态截图看不到。**建议让用户自己开浏览器点一遍**,或者用Playwright录屏:
+
+```python
+page.video.record('interaction.mp4')
+```
+
+### 5. 幻灯片逐页检查
+
+Deck类HTML,一张张截:
+
+```bash
+python verify.py deck.html --slides 10  # 截前10张
+```
+
+生成 `deck-slide-01.png`、`deck-slide-02.png`... 方便快速浏览。
+
+## Playwright Setup
+
+首次使用需要:
+
+```bash
+# 如果还没装
+npm install -g playwright
+npx playwright install chromium
+
+# 或者Python版
+pip install playwright
+playwright install chromium
+```
+
+如果用户已经全局安装 Playwright,直接用即可。
+
+## 截图最佳实践
+
+### 截完整页面
+
+```python
+page.screenshot(path='full.png', full_page=True)
+```
+
+### 截viewport
+
+```python
+page.screenshot(path='viewport.png')  # 默认只截可见区域
+```
+
+### 截特定元素
+
+```python
+element = page.query_selector('.hero-section')
+element.screenshot(path='hero.png')
+```
+
+### 高清截图
+
+```python
+page = browser.new_page(device_scale_factor=2)  # retina
+```
+
+### 等动画结束再截
+
+```python
+page.wait_for_timeout(2000)  # 等2秒让动画settle
+page.screenshot(...)
+```
+
+## 把截图发给用户
+
+### 本地截图直接打开
+
+```bash
+open screenshot.png
+```
+
+用户会在自己的 Preview/Figma/VSCode/浏览器 里看。
+
+### 上传图床分享链接
+
+如果需要给远程协作者看(比如 Slack/飞书/微信),让用户用自己的图床工具或 MCP 上传。任何返回永久链接的图床都行(ImgBB / Cloudflare R2 / 七牛 / 自建等),粘贴到任何地方。
+
+## 验证出错时
+
+### 页面白屏
+
+控制台一定有错。先检查:
+
+1. React+Babel script tag的integrity hash对不对(见`react-setup.md`)
+2. 是不是`const styles = {...}`命名冲突
+3. 跨文件的组件有没有export到`window`
+4. JSX语法错误(babel.min.js不报错,换babel.js非压缩版)
+
+### 动画卡
+
+- 用Chrome DevTools Performance tab录一段
+- 找layout thrashing(频繁的reflow)
+- 动效优先用`transform`和`opacity`(GPU加速)
+
+### 字体不对
+
+- 检查`@font-face`的url是否可访问
+- 检查fallback字体
+- 中文字体加载慢:先显示fallback,加载完再切换
+
+### 布局错位
+
+- 检查`box-sizing: border-box`是否全局应用
+- 检查`*  margin: 0; padding: 0`reset
+- Chrome DevTools里打开gridlines看实际布局
+
+## 验证=设计师的第二双眼
+
+**永远要自己过一遍**。AI写代码时经常出现:
+
+- 看起来对但interaction有bug
+- 静态截图好但scroll时错位
+- 宽屏好看但窄屏崩
+- Dark mode忘了测
+- Tweaks切换后某些组件没响应
+
+**最后1分钟的验证可以省1小时的返工**。
+
+## 常用验证脚本命令
+
+```bash
+# 基础:打开+截图+抓错
+python verify.py design.html
+
+# 多viewport
+python verify.py design.html --viewports 1920x1080,375x667
+
+# 多slide
+python verify.py deck.html --slides 10
+
+# 输出到指定目录
+python verify.py design.html --output ./screenshots/
+
+# headless=false,打开真实浏览器给你看
+python verify.py design.html --show
+```

+ 197 - 0
references/video-export.md

@@ -0,0 +1,197 @@
+# Video Export:HTML 动画导出为 MP4/GIF
+
+动画 HTML 完成后,用户常想「能导出视频吗」。这份指南给出完整流程。
+
+## 何时导出
+
+**导出时机**:
+- 动画完整跑通、视觉验证过(Playwright 截图确认各时间点状态正确)
+- 用户在浏览器里看过至少一次,表示效果 OK
+- **不要**在动画 bug 没修完的阶段导出——导出到视频后改起来更贵
+
+**用户可能说的触发语**:
+- 「能导出成视频吗」
+- 「转成 MP4」
+- 「做成 GIF」
+- 「60fps」
+
+## 产出规格
+
+默认一次给三种格式,让用户选:
+
+| 格式 | 规格 | 适合场景 | 典型大小(30s) |
+|---|---|---|---|
+| MP4 25fps | 1920×1080 · H.264 · CRF 18 | 公众号嵌入、视频号、YouTube | 1-2 MB |
+| MP4 60fps | 1920×1080 · minterpolate 插帧 · H.264 · CRF 18 | 高帧率展示、B站、作品集 | 1.5-3 MB |
+| GIF | 960×540 · 15fps · palette 优化 | Twitter/X、README、Slack 预览 | 2-4 MB |
+
+## 工具链
+
+两个脚本在 `scripts/`:
+
+### 1. `render-video.js` — HTML → MP4
+
+录一个 25fps 的 MP4 基础版本。依赖全局 playwright。
+
+```bash
+NODE_PATH=$(npm root -g) node /path/to/claude-design/scripts/render-video.js <html文件>
+```
+
+可选参数:
+- `--duration=30` 动画时长(秒)
+- `--width=1920 --height=1080` 分辨率
+- `--trim=2.2` 从视频开头裁掉的秒数(去掉 reload + 字体加载时间)
+- `--fontwait=1.5` 字体加载等待时间(秒),字体多时调高
+
+输出:与 HTML 同目录,同名 `.mp4`。
+
+### 2. `add-music.sh` — MP4 + BGM → MP4
+
+给无声 MP4 混入背景音乐,按场景(mood)从内置 BGM 库里选,也可自带音频。自动匹配时长、加淡入淡出。
+
+```bash
+bash add-music.sh <input.mp4> [--mood=<name>] [--music=<path>] [--out=<path>]
+```
+
+**内置 BGM 库**(在 `assets/bgm-<mood>.mp3`):
+
+| `--mood=` | 风格 | 适配场景 |
+|-----------|------|---------|
+| `tech`(默认) | Apple Silicon / 苹果发布会,极简合成器+钢琴 | 产品发布、AI工具、Skill 宣传 |
+| `ad` | upbeat 现代电子,有 build + drop | 社交媒体广告、产品预告、促销片 |
+| `educational` | 温暖明亮、轻吉他/电钢琴,inviting | 科普、教程介绍、课程预告 |
+| `educational-alt` | 同类备选,换一首试试 | 同上 |
+| `tutorial` | lo-fi 环境音,几乎无存在感 | 软件演示、编程教程、长演示 |
+| `tutorial-alt` | 同类备选 | 同上 |
+
+**行为**:
+- 音乐按视频时长裁剪
+- 0.3s 淡入 + 1s 淡出(避免硬切)
+- 视频流 `-c:v copy` 不重编码,音频 AAC 192k
+- `--music=<path>` 优先级高于 `--mood`,可以直接指定任意外部音频
+- 传错 mood 名会列出所有可用选项,不会静默失败
+
+**典型流水线**(动画导出三件套 + 配乐):
+```bash
+node render-video.js animation.html                        # 录屏
+bash convert-formats.sh animation.mp4                      # 派生 60fps + GIF
+bash add-music.sh animation-60fps.mp4                      # 加默认 tech BGM
+# 或针对不同场景:
+bash add-music.sh tutorial-demo.mp4 --mood=tutorial
+bash add-music.sh product-promo.mp4 --mood=ad --out=promo-final.mp4
+```
+
+### 3. `convert-formats.sh` — MP4 → 60fps MP4 + GIF
+
+从已有 MP4 生成 60fps 版本和 GIF。
+
+```bash
+bash /path/to/claude-design/scripts/convert-formats.sh <input.mp4> [gif_width]
+```
+
+输出(与输入同目录):
+- `<name>-60fps.mp4` — minterpolate 插帧到 60fps
+- `<name>.gif` — palette 优化的 GIF(默认 960 宽,可改)
+
+`gif_width` 参数:
+- 960(默认)—— 社交平台通用
+- 1280 —— 更清晰但文件更大
+- 600 —— Twitter/X 优先加载
+
+## 完整流程(标准推荐)
+
+用户说「导出视频」后:
+
+```bash
+cd <项目目录>
+
+# 假设 $SKILL 指向本 skill 的根目录(自行按安装位置替换)
+
+# 1. 录 25fps 基础 MP4
+NODE_PATH=$(npm root -g) node "$SKILL/scripts/render-video.js" my-animation.html
+
+# 2. 派生 60fps MP4 和 GIF
+bash "$SKILL/scripts/convert-formats.sh" my-animation.mp4
+
+# 产出清单:
+# my-animation.mp4         (25fps · 1-2 MB)
+# my-animation-60fps.mp4   (60fps · 1.5-3 MB)
+# my-animation.gif         (15fps · 2-4 MB)
+```
+
+## 技术细节(排错用)
+
+### Playwright recordVideo 的坑
+
+- 帧率固定 25fps,无法直接录 60fps(Chromium headless 的 compositor 上限)
+- 从 context 创建就开始录,必须用 `trim` 裁掉前面的加载时间
+- 默认 webm 格式,需要 ffmpeg 转 H.264 MP4 才能通用播放
+
+`render-video.js` 已处理以上问题。
+
+### ffmpeg minterpolate 参数
+
+当前配置:`minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1`
+
+- `mi_mode=mci` — motion compensation interpolation(运动补偿)
+- `mc_mode=aobmc` — adaptive overlapped block motion compensation
+- `me_mode=bidir` — 双向运动估计
+- `vsbmc=1` — 可变 size block motion compensation
+
+对 CSS **transform 动画**(translate/scale/rotate)效果好。
+对**纯 fade** 可能产生轻微 ghosting——如果用户嫌弃,退化为简单帧复制:
+
+```bash
+ffmpeg -i input.mp4 -r 60 -c:v libx264 ... output.mp4
+```
+
+### GIF palette 为何要两阶段
+
+GIF 只能 256 色。一次 pass 的 GIF 会把全动画色彩压到 256 色通用 palette,对米色底+橙色这种细腻配色会糊。
+
+两阶段:
+1. `palettegen=stats_mode=diff` —— 先扫描全片,生成**针对此动画的 optimal palette**
+2. `paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle` —— 用这个 palette 编码,rectangle diff 只更新变化区域,大幅减小文件
+
+对 fade 过渡用 `dither=bayer` 比 `none` 更平滑,但文件大一点。
+
+## Pre-flight check(导出前)
+
+导出前 30 秒自检:
+
+- [ ] HTML 在浏览器里完整跑过一遍,无控制台错误
+- [ ] 动画第 0 帧是完整初始状态(不是空白加载中)
+- [ ] 动画最后一帧是稳定的收尾状态(不是半截)
+- [ ] 字体/图片/emoji 全部正常渲染(参考 `animation-pitfalls.md`)
+- [ ] Duration 参数与 HTML 里的实际动画时长匹配
+
+## 交付时附带的说明
+
+导出完成后给用户的标准说明格式:
+
+```
+**完整交付**
+
+| 文件 | 格式 | 规格 | 大小 |
+|---|---|---|---|
+| foo.mp4 | MP4 | 1920×1080 · 25fps · H.264 | X MB |
+| foo-60fps.mp4 | MP4 | 1920×1080 · 60fps(运动插帧)· H.264 | X MB |
+| foo.gif | GIF | 960×540 · 15fps · palette 优化 | X MB |
+
+**说明**
+- 60fps 用 minterpolate 做运动估计插帧,transform 动画效果好
+- GIF 用 palette 优化,30s 动画可压到 3MB 左右
+
+要换尺寸或帧率说一声。
+```
+
+## 常见用户追加需求
+
+| 用户说 | 应对 |
+|---|---|
+| 「太大了」 | MP4:提高 CRF 到 23-28;GIF:降分辨率到 600 或 fps 到 10 |
+| 「GIF 太糊」 | 提高 `gif_width` 到 1280;或者建议用 MP4 代替(微信朋友圈也支持) |
+| 「要竖屏 9:16」 | 改 HTML 源的 `--width=1080 --height=1920`,重新录 |
+| 「加水印」 | ffmpeg 加 `-vf "drawtext=..."` 或 `overlay=` 一个 PNG |
+| 「要透明背景」 | MP4 不支持 alpha;用 WebM VP9 + alpha 或 APNG |
+| 「要无损」 | CRF 改 0 + preset veryslow(文件会大 10 倍) |

+ 216 - 0
references/workflow.md

@@ -0,0 +1,216 @@
+# Workflow:从接到任务到交付
+
+你是用户的junior designer。用户是manager。按这个流程工作,能产出好设计的概率会显著提升。
+
+## 问问题的艺术
+
+大多数情况下,开工前要问至少10个问题。不是走过场,是真的要把需求摸清。
+
+**什么时候必须问**:新任务、模糊任务、没有design context、用户只说了一句模糊的要求。
+
+**什么时候可以不问**:小修小补、follow-up任务、用户已经给了明确PRD+截图+上下文。
+
+**怎么问**:大部分 agent 环境没有结构化问题 UI,在对话里用 markdown 清单问即可。**一次性把问题列完让用户批量答**,不要一来一回一个个问——那会浪费用户时间、打断用户思路。
+
+## 必问清单
+
+每个设计任务都必须问清这5类问题:
+
+### 1. Design Context(最重要)
+
+- 有没有现成的design system、UI kit、组件库?在哪?
+- 有没有品牌指南、色彩规范、字体规范?
+- 有没有可以参考的现有产品/页面截图?
+- 有没有codebase可以读?
+
+**如果用户说"没有"**:
+- 帮他找——翻项目目录、看有没有参考品牌
+- 还没有?明确说:"我会基于通用直觉做,但这通常做不出符合你品牌的作品。你考虑下是否先提供一些参考?"
+- 实在要做,就按`references/design-context.md`的fallback策略办
+
+### 2. Variations维度
+
+- 想要几种variations?(推荐3+)
+- 在哪些维度上变?视觉/交互/色彩/布局/文案/动画?
+- 希望variations都"接近预期"还是"一张地图,从保守到疯狂"?
+
+### 3. Fidelity和Scope
+
+- 多高保真?线框图 / 半成品 / 真实data的full hi-fi?
+- 覆盖多少flow?一屏 / 一个flow / 整个产品?
+- 有没有具体的「必须包含」元素?
+
+### 4. Tweaks
+
+- 希望能实时调整哪些参数?(颜色/字号/间距/layout/文案/feature flag)
+- 用户自己要不要在做完后继续调?
+
+### 5. 问题专属(至少4个)
+
+针对具体任务问4+个细节。例如:
+
+**做landing page**:
+- 目标转化动作是什么?
+- 主要受众?
+- 竞品参考?
+- 文案谁提供?
+
+**做iOS App onboarding**:
+- 几步?
+- 需要用户做什么?
+- 跳过路径?
+- 目标留存率?
+
+**做动画**:
+- 时长?
+- 最终用途(视频素材/官网/社交)?
+- 节奏(快/慢/分段)?
+- 必须出现的关键帧?
+
+## 问题模板示例
+
+遇到新任务时,可以抄这个结构在对话里问:
+
+```markdown
+开始前想跟你对齐几个问题,一次列齐你批量回答就行:
+
+**Design Context**
+1. 有设计系统/UI kit/品牌规范吗?如果有在哪?
+2. 有可以参考的现有产品或竞品截图吗?
+3. 项目里有codebase可以读吗?
+
+**Variations**
+4. 想要几种variations?在哪些维度上变(视觉/交互/色彩/...)?
+5. 希望都是"接近答案"还是从保守到疯狂的一张地图?
+
+**Fidelity**
+6. 保真度:线框 / 半成品 / 带真数据full hi-fi?
+7. Scope:一屏 / 一整个flow / 整个产品?
+
+**Tweaks**
+8. 希望做完后能实时调哪些参数?
+
+**具体任务**
+9. [任务专属问题1]
+10. [任务专属问题2]
+...
+```
+
+## Junior Designer模式
+
+这是整个workflow最重要的环节。**不要接到任务就闷头冲**。步骤:
+
+### Pass 1:Assumptions + Placeholders(5-15分钟)
+
+HTML文件头部先写你的**assumptions+reasoning comments**,像junior给manager汇报:
+
+```html
+<!--
+我的假设:
+- 这是给XX受众看的
+- 整体tone我理解为XX(基于用户说的"专业但不严肃")
+- 主要flow是A→B→C
+- 色彩我想用品牌蓝+暖灰,不确定你想不想要accent色
+
+未解的问题:
+- 第3步的数据从哪里来?先用placeholder
+- 背景图用抽象几何还是真照片?先占位
+
+如果你看到这里觉得方向不对,现在是成本最低的时候改。
+-->
+
+<!-- 然后是带placeholder的结构 -->
+<section class="hero">
+  <h1>[主标题位 - 等用户提供]</h1>
+  <p>[副标题位]</p>
+  <div class="cta-placeholder">[CTA按钮]</div>
+</section>
+```
+
+**保存 → show用户 → 等反馈再走下一步**。
+
+### Pass 2:真实组件+Variations(主力工作量)
+
+用户批准方向后,开始填充。这时:
+- 写React组件替换placeholder
+- 做variations(用design_canvas或Tweaks)
+- 如果是幻灯片/动画,用starter components起手
+
+**做到一半再show一次**——不要等全做完。设计方向错了,晚show等于白做。
+
+### Pass 3:细节打磨
+
+用户满意整体后,打磨:
+- 字号/间距/对比度微调
+- 动画timing
+- 边界case
+- Tweaks面板完善
+
+### Pass 4:验证+交付
+
+- 用Playwright截图(见`references/verification.md`)
+- 打开浏览器肉眼确认
+- 总结**极简**:只说caveats和next steps
+
+## Variations的深度逻辑
+
+给variations不是给用户制造选择困难,是**探索可能性空间**。让用户mix and match出最终版本。
+
+### 好的variations长什么样
+
+- **维度明确**:每个variation在不同维度上变(A vs B只换配色,C vs D只换layout)
+- **有梯度**:从「by-the-book保守版」到「大胆novel版」逐级递进
+- **有记号**:每个variation有短label说明它在探索什么
+
+### 实现方式
+
+**纯视觉对比**(静态):
+→ 用`assets/design_canvas.jsx`,网格布局并排展示。每个cell带label。
+
+**多选项/交互差异**:
+→ 做完整原型,用Tweaks切换。例如做登录页,"布局"是tweak的一个选项:
+- 左文案右表单
+- 顶部logo+中央表单
+- 背景全屏图+浮层表单
+
+用户开关Tweaks就能切换,不需要打开多个HTML文件。
+
+### 探索矩阵思考
+
+每次设计,脑内过一遍这些维度,挑2-3个来给variations:
+
+- 视觉:minimal / editorial / brutalist / organic / futuristic / retro
+- 色彩:monochrome / dual-tone / vibrant / pastel / high-contrast
+- 字型:sans-only / sans+serif对比 / 全衬线 / 等宽
+- Layout:对称 / 非对称 / 不规则grid / full-bleed / 窄栏
+- Density:稀疏呼吸 / 中等 / 信息密集
+- 交互:极简hover / 丰富micro-interaction / 夸张大动画
+- 材质:flat / 有阴影层次 / 纹理 / noise / 渐变
+
+## 遇到不确定的情况
+
+- **不知道怎么做**:坦白说你不确定,问用户,或先做个placeholder继续。**不要编**。
+- **用户的描述矛盾**:指出矛盾,让用户选一个方向。
+- **任务太大一次吃不下**:拆成steps,先做第一步让用户看,再推进。
+- **用户要求的效果技术上很难**:说清技术边界,提供替代方案。
+
+## 总结规则
+
+交付时,summary **极短**:
+
+```markdown
+✅ 幻灯片已完成(10张),带Tweaks可切换"夜/日模式"。
+
+注意:
+- 第4页的数据是假的,等你提供真数据我替换
+- 动画用了CSS transition,不需要JS
+
+下一步建议:先你浏览器打开看一遍,有问题告诉我哪页哪处。
+```
+
+不要:
+- 罗列每一页的内容
+- 重复讲你用了什么技术
+- 夸自己设计多好
+
+Caveats + next steps,结束。

+ 108 - 0
scripts/add-music.sh

@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+# Mix a BGM track into an MP4 video.
+#
+# Usage:
+#   bash add-music.sh <input.mp4> [--mood=<name>] [--music=<path>] [--out=<path>]
+#
+# Mood library (in ../assets/, matching bgm-<mood>.mp3):
+#   tech              — Apple Silicon / product keynote vibe, minimal synth+piano (default)
+#   ad                — upbeat modern, clear build + drop, social-media ad energy
+#   educational       — warm, patient, inviting learning tone
+#   educational-alt   — alternate take of educational
+#   tutorial          — lo-fi background, stays out of voiceover's way
+#   tutorial-alt      — alternate take of tutorial
+#
+# Flags (all optional):
+#   --mood=<name>     pick a preset from the library (default: tech)
+#   --music=<path>    override with your own audio file (wins over --mood)
+#   --out=<path>      output path (default: <input-basename>-bgm.mp4)
+#
+# Legacy positional form still works: bash add-music.sh in.mp4 music.mp3 out.mp4
+#
+# Behavior:
+#   - Music is trimmed to match video duration
+#   - 0.3s fade in, 1.0s fade out (avoids hard cuts)
+#   - Video stream copied (no re-encode), audio AAC 192k
+#
+# Examples:
+#   bash add-music.sh my.mp4                              # default: tech mood
+#   bash add-music.sh my.mp4 --mood=ad                    # switch mood
+#   bash add-music.sh my.mp4 --mood=educational --out=final.mp4
+#   bash add-music.sh my.mp4 --music=~/Downloads/song.mp3 # bring your own
+#
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+ASSETS_DIR="$SCRIPT_DIR/../assets"
+
+# ── Parse args ───────────────────────────────────────────────────────
+INPUT=""
+MOOD="tech"
+CUSTOM_MUSIC=""
+OUTPUT=""
+POSITIONAL=()
+
+for arg in "$@"; do
+  case "$arg" in
+    --mood=*)  MOOD="${arg#*=}" ;;
+    --music=*) CUSTOM_MUSIC="${arg#*=}" ;;
+    --out=*)   OUTPUT="${arg#*=}" ;;
+    *)         POSITIONAL+=("$arg") ;;
+  esac
+done
+
+# Legacy positional: <input> [music] [output]
+INPUT="${POSITIONAL[0]}"
+[ -z "$CUSTOM_MUSIC" ] && [ -n "${POSITIONAL[1]}" ] && CUSTOM_MUSIC="${POSITIONAL[1]}"
+[ -z "$OUTPUT" ]       && [ -n "${POSITIONAL[2]}" ] && OUTPUT="${POSITIONAL[2]}"
+
+if [ -z "$INPUT" ] || [ ! -f "$INPUT" ]; then
+  echo "Usage: bash add-music.sh <input.mp4> [--mood=<name>] [--music=<path>] [--out=<path>]" >&2
+  echo "Moods available: $(ls "$ASSETS_DIR" | grep -E '^bgm-.*\.mp3$' | sed 's/^bgm-//;s/\.mp3$//' | tr '\n' ' ')" >&2
+  exit 1
+fi
+
+# ── Resolve music source: --music wins, else --mood ─────────────────
+if [ -n "$CUSTOM_MUSIC" ]; then
+  MUSIC="$CUSTOM_MUSIC"
+  SOURCE_LABEL="custom: $MUSIC"
+else
+  MUSIC="$ASSETS_DIR/bgm-${MOOD}.mp3"
+  SOURCE_LABEL="mood: $MOOD"
+fi
+
+if [ ! -f "$MUSIC" ]; then
+  echo "✗ Music not found: $MUSIC" >&2
+  echo "  Available moods: $(ls "$ASSETS_DIR" | grep -E '^bgm-.*\.mp3$' | sed 's/^bgm-//;s/\.mp3$//' | tr '\n' ' ')" >&2
+  exit 1
+fi
+
+# ── Resolve output path ─────────────────────────────────────────────
+INPUT_DIR="$(cd "$(dirname "$INPUT")" && pwd)"
+INPUT_NAME="$(basename "$INPUT" .mp4)"
+[ -z "$OUTPUT" ] && OUTPUT="$INPUT_DIR/$INPUT_NAME-bgm.mp4"
+
+# ── Measure video duration, compute fade-out start ──────────────────
+DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$INPUT")
+if [ -z "$DURATION" ]; then
+  echo "✗ Could not read video duration" >&2
+  exit 1
+fi
+FADE_OUT_START=$(awk "BEGIN { d = $DURATION - 1; if (d < 0) d = 0; print d }")
+
+echo "▸ Mixing BGM into video"
+echo "  input:    $INPUT"
+echo "  music:    $SOURCE_LABEL"
+echo "  duration: ${DURATION}s"
+echo "  output:   $OUTPUT"
+
+ffmpeg -y -loglevel error \
+  -i "$INPUT" \
+  -i "$MUSIC" \
+  -filter_complex "[1:a]atrim=0:${DURATION},asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.3,afade=t=out:st=${FADE_OUT_START}:d=1[a]" \
+  -map 0:v -map "[a]" \
+  -c:v copy -c:a aac -b:a 192k -shortest \
+  "$OUTPUT"
+
+SIZE=$(du -h "$OUTPUT" | cut -f1)
+echo "✓ Done: $OUTPUT ($SIZE)"

+ 51 - 0
scripts/convert-formats.sh

@@ -0,0 +1,51 @@
+#!/bin/bash
+# Convert MP4 animations to 60fps MP4 (via minterpolate) and optimized GIF.
+#
+# Usage:
+#   ./convert-formats.sh input.mp4 [gif_width]
+#
+# Produces next to the input:
+#   <name>-60fps.mp4   (1920x1080, 60fps, motion-interpolated)
+#   <name>.gif         (scaled width, 15fps, palette-optimized)
+#
+# minterpolate flags:
+#   mi_mode=mci              motion compensation interpolation
+#   mc_mode=aobmc            adaptive overlapped block motion comp
+#   me_mode=bidir            bidirectional motion estimation
+#   vsbmc=1                  variable-size block motion comp
+# GIF uses two-pass palette:
+#   pass 1: palettegen with stats_mode=diff (per-video optimal palette)
+#   pass 2: paletteuse with bayer dither + rectangle diff
+# This keeps 30s/1080p animations GIF under ~4MB with good color fidelity.
+
+set -e
+
+INPUT="${1:?Usage: $0 input.mp4 [gif_width]}"
+GIF_WIDTH="${2:-960}"
+
+DIR=$(dirname "$INPUT")
+BASE=$(basename "$INPUT" .mp4)
+OUT60="$DIR/$BASE-60fps.mp4"
+OUTGIF="$DIR/$BASE.gif"
+PAL="$DIR/.palette-$BASE.png"
+
+echo "▸ 60fps interpolate: $OUT60"
+ffmpeg -y -loglevel error -i "$INPUT" \
+  -vf "minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1" \
+  -c:v libx264 -pix_fmt yuv420p -crf 18 -preset medium -movflags +faststart \
+  "$OUT60"
+MP4_SIZE=$(du -h "$OUT60" | cut -f1)
+echo "  ✓ $MP4_SIZE"
+
+echo "▸ GIF (${GIF_WIDTH}w, 15fps, palette-optimized): $OUTGIF"
+# Pass 1: generate palette tailored to this video
+ffmpeg -y -loglevel error -i "$INPUT" \
+  -vf "fps=15,scale=${GIF_WIDTH}:-1:flags=lanczos,palettegen=stats_mode=diff" \
+  "$PAL"
+# Pass 2: apply palette with dithering
+ffmpeg -y -loglevel error -i "$INPUT" -i "$PAL" \
+  -lavfi "fps=15,scale=${GIF_WIDTH}:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" \
+  "$OUTGIF"
+rm -f "$PAL"
+GIF_SIZE=$(du -h "$OUTGIF" | cut -f1)
+echo "  ✓ $GIF_SIZE"

+ 98 - 0
scripts/export_deck_pdf.mjs

@@ -0,0 +1,98 @@
+#!/usr/bin/env node
+/**
+ * export_deck_pdf.mjs — 把多文件 slide deck 导出为单个矢量 PDF
+ *
+ * 用法:
+ *   node export_deck_pdf.mjs --slides <dir> --out <file.pdf> [--width 1920] [--height 1080]
+ *
+ * 特点:
+ *   - 文字保留矢量(可复制、可搜索)
+ *   - 背景/图形 1:1 保真(Playwright 内嵌 Chromium 渲染)
+ *   - 不需要对 HTML 做任何改造
+ *   - 视觉损失 = 0(PDF 就是浏览器打印出来的)
+ *
+ * trade-off:
+ *   - PDF 不可再编辑文字(要改回到 HTML 改)
+ *
+ * 依赖:playwright pdf-lib
+ *   npm install playwright pdf-lib
+ *
+ * 会按文件名排序(01-xxx.html → 02-xxx.html → ...)
+ */
+
+import { chromium } from 'playwright';
+import { PDFDocument } from 'pdf-lib';
+import fs from 'fs/promises';
+import path from 'path';
+
+function parseArgs() {
+  const args = { width: 1920, height: 1080 };
+  const a = process.argv.slice(2);
+  for (let i = 0; i < a.length; i += 2) {
+    const k = a[i].replace(/^--/, '');
+    args[k] = a[i + 1];
+  }
+  if (!args.slides || !args.out) {
+    console.error('用法: node export_deck_pdf.mjs --slides <dir> --out <file.pdf> [--width 1920] [--height 1080]');
+    process.exit(1);
+  }
+  args.width = parseInt(args.width);
+  args.height = parseInt(args.height);
+  return args;
+}
+
+async function main() {
+  const { slides, out, width, height } = parseArgs();
+  const slidesDir = path.resolve(slides);
+  const outFile = path.resolve(out);
+
+  const files = (await fs.readdir(slidesDir))
+    .filter(f => f.endsWith('.html'))
+    .sort();
+  if (!files.length) {
+    console.error(`No .html files found in ${slidesDir}`);
+    process.exit(1);
+  }
+  console.log(`Found ${files.length} slides in ${slidesDir}`);
+
+  const browser = await chromium.launch();
+  const ctx = await browser.newContext({ viewport: { width, height } });
+
+  // 1) Render each HTML to its own PDF buffer
+  const pageBuffers = [];
+  for (const f of files) {
+    const page = await ctx.newPage();
+    const url = 'file://' + path.join(slidesDir, f);
+    await page.goto(url, { waitUntil: 'networkidle' }).catch(() => page.goto(url));
+    await page.waitForTimeout(1200);  // web-font paint
+    // emulate "screen" so CSS colors/backgrounds render the same as browser
+    await page.emulateMedia({ media: 'screen' });
+    const buf = await page.pdf({
+      width: `${width}px`,
+      height: `${height}px`,
+      printBackground: true,
+      margin: { top: 0, right: 0, bottom: 0, left: 0 },
+      preferCSSPageSize: false,
+    });
+    pageBuffers.push(buf);
+    await page.close();
+    console.log(`  [${pageBuffers.length}/${files.length}] ${f}`);
+  }
+
+  await browser.close();
+
+  // 2) Merge into a single PDF
+  const merged = await PDFDocument.create();
+  for (const buf of pageBuffers) {
+    const src = await PDFDocument.load(buf);
+    const copied = await merged.copyPages(src, src.getPageIndices());
+    copied.forEach(p => merged.addPage(p));
+  }
+  const bytes = await merged.save();
+  await fs.writeFile(outFile, bytes);
+
+  const kb = (bytes.byteLength / 1024).toFixed(0);
+  console.log(`\n✓ Wrote ${outFile}  (${kb} KB, ${files.length} pages, vector)`);
+}
+
+main().catch(e => { console.error(e); process.exit(1); });

+ 97 - 0
scripts/export_deck_pptx.mjs

@@ -0,0 +1,97 @@
+#!/usr/bin/env node
+/**
+ * export_deck_pptx.mjs — 把多文件 slide deck 导出为 PPTX(图片铺底)
+ *
+ * 用法:
+ *   node export_deck_pptx.mjs --slides <dir> --out <file.pptx> [--width 1920] [--height 1080]
+ *
+ * 特点:
+ *   - 每张 slide 截图成 PNG,满铺一张 PPTX 页面
+ *   - 视觉 100% 保真(因为就是图片)
+ *   - ⚠️ 文字不可编辑(文字变成了图片)
+ *
+ * 如果用户需要「可编辑文字」的 PPTX:
+ *   ❌ 不要在 claude-design 的 HTML 上硬上 html2pptx——claude-design 的 HTML 视觉自由度高,
+ *      很少能满足 html2pptx 的严格约束(p 标签语法、无 ::before、无 span margin 等)。
+ *      实测 32 页里能 pass 的不到 30%,剩下的要逐页改造 + 逐页修字体溢出——工时失控。
+ *   ✅ 正确做法:切换到 **huashu-slides** skill 的 Path A,按它的 HTML 格式**从头重构**
+ *      每一页。huashu-slides 的 HTML 从一开始就符合 html2pptx 约束,可 100% 导出可编辑 PPTX。
+ *
+ * 依赖:playwright pptxgenjs
+ *   npm install playwright pptxgenjs
+ *
+ * 会按文件名排序(01-xxx.html → 02-xxx.html → ...)
+ */
+
+import { chromium } from 'playwright';
+import pptxgen from 'pptxgenjs';
+import fs from 'fs/promises';
+import path from 'path';
+import os from 'os';
+
+function parseArgs() {
+  const args = { width: 1920, height: 1080 };
+  const a = process.argv.slice(2);
+  for (let i = 0; i < a.length; i += 2) {
+    const k = a[i].replace(/^--/, '');
+    args[k] = a[i + 1];
+  }
+  if (!args.slides || !args.out) {
+    console.error('用法: node export_deck_pptx.mjs --slides <dir> --out <file.pptx> [--width 1920] [--height 1080]');
+    process.exit(1);
+  }
+  args.width = parseInt(args.width);
+  args.height = parseInt(args.height);
+  return args;
+}
+
+async function main() {
+  const { slides, out, width, height } = parseArgs();
+  const slidesDir = path.resolve(slides);
+  const outFile = path.resolve(out);
+
+  const files = (await fs.readdir(slidesDir))
+    .filter(f => f.endsWith('.html'))
+    .sort();
+  if (!files.length) {
+    console.error(`No .html files found in ${slidesDir}`);
+    process.exit(1);
+  }
+  console.log(`Found ${files.length} slides, rendering to PNG...`);
+
+  const browser = await chromium.launch();
+  const ctx = await browser.newContext({ viewport: { width, height } });
+  const page = await ctx.newPage();
+
+  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'deck-pptx-'));
+  const pngs = [];
+  for (const f of files) {
+    const url = 'file://' + path.join(slidesDir, f);
+    await page.goto(url, { waitUntil: 'networkidle' }).catch(() => page.goto(url));
+    await page.waitForTimeout(1200);
+    const out = path.join(tmpDir, f.replace(/\.html$/, '.png'));
+    await page.screenshot({ path: out, fullPage: false });
+    pngs.push(out);
+    console.log(`  [${pngs.length}/${files.length}] ${f}`);
+  }
+  await browser.close();
+
+  // Build PPTX
+  const pres = new pptxgen();
+  pres.defineLayout({ name: 'DECK', width: width / 96, height: height / 96 });
+  pres.layout = 'DECK';
+  for (const png of pngs) {
+    const s = pres.addSlide();
+    s.addImage({ path: png, x: 0, y: 0, w: pres.width, h: pres.height });
+  }
+  await pres.writeFile({ fileName: outFile });
+
+  // cleanup
+  for (const p of pngs) await fs.unlink(p).catch(() => {});
+  await fs.rmdir(tmpDir).catch(() => {});
+
+  console.log(`\n✓ Wrote ${outFile}  (${files.length} slides, image mode)`);
+  console.log(`  要可编辑?改走 huashu-slides 的 Path A 从头重构 HTML。`);
+}
+
+main().catch(e => { console.error(e); process.exit(1); });

+ 979 - 0
scripts/html2pptx.js

@@ -0,0 +1,979 @@
+/**
+ * html2pptx - Convert HTML slide to pptxgenjs slide with positioned elements
+ *
+ * USAGE:
+ *   const pptx = new pptxgen();
+ *   pptx.layout = 'LAYOUT_16x9';  // Must match HTML body dimensions
+ *
+ *   const { slide, placeholders } = await html2pptx('slide.html', pptx);
+ *   slide.addChart(pptx.charts.LINE, data, placeholders[0]);
+ *
+ *   await pptx.writeFile('output.pptx');
+ *
+ * FEATURES:
+ *   - Converts HTML to PowerPoint with accurate positioning
+ *   - Supports text, images, shapes, and bullet lists
+ *   - Extracts placeholder elements (class="placeholder") with positions
+ *   - Handles CSS gradients, borders, and margins
+ *
+ * VALIDATION:
+ *   - Uses body width/height from HTML for viewport sizing
+ *   - Throws error if HTML dimensions don't match presentation layout
+ *   - Throws error if content overflows body (with overflow details)
+ *
+ * RETURNS:
+ *   { slide, placeholders } where placeholders is an array of { id, x, y, w, h }
+ */
+
+const { chromium } = require('playwright');
+const path = require('path');
+const sharp = require('sharp');
+
+const PT_PER_PX = 0.75;
+const PX_PER_IN = 96;
+const EMU_PER_IN = 914400;
+
+// Helper: Get body dimensions and check for overflow
+async function getBodyDimensions(page) {
+  const bodyDimensions = await page.evaluate(() => {
+    const body = document.body;
+    const style = window.getComputedStyle(body);
+
+    return {
+      width: parseFloat(style.width),
+      height: parseFloat(style.height),
+      scrollWidth: body.scrollWidth,
+      scrollHeight: body.scrollHeight
+    };
+  });
+
+  const errors = [];
+  const widthOverflowPx = Math.max(0, bodyDimensions.scrollWidth - bodyDimensions.width - 1);
+  const heightOverflowPx = Math.max(0, bodyDimensions.scrollHeight - bodyDimensions.height - 1);
+
+  const widthOverflowPt = widthOverflowPx * PT_PER_PX;
+  const heightOverflowPt = heightOverflowPx * PT_PER_PX;
+
+  if (widthOverflowPt > 0 || heightOverflowPt > 0) {
+    const directions = [];
+    if (widthOverflowPt > 0) directions.push(`${widthOverflowPt.toFixed(1)}pt horizontally`);
+    if (heightOverflowPt > 0) directions.push(`${heightOverflowPt.toFixed(1)}pt vertically`);
+    const reminder = heightOverflowPt > 0 ? ' (Remember: leave 0.5" margin at bottom of slide)' : '';
+    errors.push(`HTML content overflows body by ${directions.join(' and ')}${reminder}`);
+  }
+
+  return { ...bodyDimensions, errors };
+}
+
+// Helper: Validate dimensions match presentation layout
+function validateDimensions(bodyDimensions, pres) {
+  const errors = [];
+  const widthInches = bodyDimensions.width / PX_PER_IN;
+  const heightInches = bodyDimensions.height / PX_PER_IN;
+
+  if (pres.presLayout) {
+    const layoutWidth = pres.presLayout.width / EMU_PER_IN;
+    const layoutHeight = pres.presLayout.height / EMU_PER_IN;
+
+    if (Math.abs(layoutWidth - widthInches) > 0.1 || Math.abs(layoutHeight - heightInches) > 0.1) {
+      errors.push(
+        `HTML dimensions (${widthInches.toFixed(1)}" × ${heightInches.toFixed(1)}") ` +
+        `don't match presentation layout (${layoutWidth.toFixed(1)}" × ${layoutHeight.toFixed(1)}")`
+      );
+    }
+  }
+  return errors;
+}
+
+function validateTextBoxPosition(slideData, bodyDimensions) {
+  const errors = [];
+  const slideHeightInches = bodyDimensions.height / PX_PER_IN;
+  const minBottomMargin = 0.5; // 0.5 inches from bottom
+
+  for (const el of slideData.elements) {
+    // Check text elements (p, h1-h6, list)
+    if (['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'list'].includes(el.type)) {
+      const fontSize = el.style?.fontSize || 0;
+      const bottomEdge = el.position.y + el.position.h;
+      const distanceFromBottom = slideHeightInches - bottomEdge;
+
+      if (fontSize > 12 && distanceFromBottom < minBottomMargin) {
+        const getText = () => {
+          if (typeof el.text === 'string') return el.text;
+          if (Array.isArray(el.text)) return el.text.find(t => t.text)?.text || '';
+          if (Array.isArray(el.items)) return el.items.find(item => item.text)?.text || '';
+          return '';
+        };
+        const textPrefix = getText().substring(0, 50) + (getText().length > 50 ? '...' : '');
+
+        errors.push(
+          `Text box "${textPrefix}" ends too close to bottom edge ` +
+          `(${distanceFromBottom.toFixed(2)}" from bottom, minimum ${minBottomMargin}" required)`
+        );
+      }
+    }
+  }
+
+  return errors;
+}
+
+// Helper: Add background to slide
+async function addBackground(slideData, targetSlide, tmpDir) {
+  if (slideData.background.type === 'image' && slideData.background.path) {
+    let imagePath = slideData.background.path.startsWith('file://')
+      ? slideData.background.path.replace('file://', '')
+      : slideData.background.path;
+    targetSlide.background = { path: imagePath };
+  } else if (slideData.background.type === 'color' && slideData.background.value) {
+    targetSlide.background = { color: slideData.background.value };
+  }
+}
+
+// Helper: Add elements to slide
+function addElements(slideData, targetSlide, pres) {
+  for (const el of slideData.elements) {
+    if (el.type === 'image') {
+      let imagePath = el.src.startsWith('file://') ? el.src.replace('file://', '') : el.src;
+      targetSlide.addImage({
+        path: imagePath,
+        x: el.position.x,
+        y: el.position.y,
+        w: el.position.w,
+        h: el.position.h
+      });
+    } else if (el.type === 'line') {
+      targetSlide.addShape(pres.ShapeType.line, {
+        x: el.x1,
+        y: el.y1,
+        w: el.x2 - el.x1,
+        h: el.y2 - el.y1,
+        line: { color: el.color, width: el.width }
+      });
+    } else if (el.type === 'shape') {
+      const shapeOptions = {
+        x: el.position.x,
+        y: el.position.y,
+        w: el.position.w,
+        h: el.position.h,
+        shape: el.shape.rectRadius > 0 ? pres.ShapeType.roundRect : pres.ShapeType.rect
+      };
+
+      if (el.shape.fill) {
+        shapeOptions.fill = { color: el.shape.fill };
+        if (el.shape.transparency != null) shapeOptions.fill.transparency = el.shape.transparency;
+      }
+      if (el.shape.line) shapeOptions.line = el.shape.line;
+      if (el.shape.rectRadius > 0) shapeOptions.rectRadius = el.shape.rectRadius;
+      if (el.shape.shadow) shapeOptions.shadow = el.shape.shadow;
+
+      targetSlide.addText(el.text || '', shapeOptions);
+    } else if (el.type === 'list') {
+      const listOptions = {
+        x: el.position.x,
+        y: el.position.y,
+        w: el.position.w,
+        h: el.position.h,
+        fontSize: el.style.fontSize,
+        fontFace: el.style.fontFace,
+        color: el.style.color,
+        align: el.style.align,
+        valign: 'top',
+        lineSpacing: el.style.lineSpacing,
+        paraSpaceBefore: el.style.paraSpaceBefore,
+        paraSpaceAfter: el.style.paraSpaceAfter,
+        margin: el.style.margin
+      };
+      if (el.style.margin) listOptions.margin = el.style.margin;
+      targetSlide.addText(el.items, listOptions);
+    } else {
+      // Check if text is single-line (height suggests one line)
+      const lineHeight = el.style.lineSpacing || el.style.fontSize * 1.2;
+      const isSingleLine = el.position.h <= lineHeight * 1.5;
+
+      let adjustedX = el.position.x;
+      let adjustedW = el.position.w;
+
+      // Make single-line text 2% wider to account for underestimate
+      if (isSingleLine) {
+        const widthIncrease = el.position.w * 0.02;
+        const align = el.style.align;
+
+        if (align === 'center') {
+          // Center: expand both sides
+          adjustedX = el.position.x - (widthIncrease / 2);
+          adjustedW = el.position.w + widthIncrease;
+        } else if (align === 'right') {
+          // Right: expand to the left
+          adjustedX = el.position.x - widthIncrease;
+          adjustedW = el.position.w + widthIncrease;
+        } else {
+          // Left (default): expand to the right
+          adjustedW = el.position.w + widthIncrease;
+        }
+      }
+
+      const textOptions = {
+        x: adjustedX,
+        y: el.position.y,
+        w: adjustedW,
+        h: el.position.h,
+        fontSize: el.style.fontSize,
+        fontFace: el.style.fontFace,
+        color: el.style.color,
+        bold: el.style.bold,
+        italic: el.style.italic,
+        underline: el.style.underline,
+        valign: 'top',
+        lineSpacing: el.style.lineSpacing,
+        paraSpaceBefore: el.style.paraSpaceBefore,
+        paraSpaceAfter: el.style.paraSpaceAfter,
+        inset: 0  // Remove default PowerPoint internal padding
+      };
+
+      if (el.style.align) textOptions.align = el.style.align;
+      if (el.style.margin) textOptions.margin = el.style.margin;
+      if (el.style.rotate !== undefined) textOptions.rotate = el.style.rotate;
+      if (el.style.transparency !== null && el.style.transparency !== undefined) textOptions.transparency = el.style.transparency;
+
+      targetSlide.addText(el.text, textOptions);
+    }
+  }
+}
+
+// Helper: Extract slide data from HTML page
+async function extractSlideData(page) {
+  return await page.evaluate(() => {
+    const PT_PER_PX = 0.75;
+    const PX_PER_IN = 96;
+
+    // Fonts that are single-weight and should not have bold applied
+    // (applying bold causes PowerPoint to use faux bold which makes text wider)
+    const SINGLE_WEIGHT_FONTS = ['impact'];
+
+    // Helper: Check if a font should skip bold formatting
+    const shouldSkipBold = (fontFamily) => {
+      if (!fontFamily) return false;
+      const normalizedFont = fontFamily.toLowerCase().replace(/['"]/g, '').split(',')[0].trim();
+      return SINGLE_WEIGHT_FONTS.includes(normalizedFont);
+    };
+
+    // Unit conversion helpers
+    const pxToInch = (px) => px / PX_PER_IN;
+    const pxToPoints = (pxStr) => parseFloat(pxStr) * PT_PER_PX;
+    const rgbToHex = (rgbStr) => {
+      // Handle transparent backgrounds by defaulting to white
+      if (rgbStr === 'rgba(0, 0, 0, 0)' || rgbStr === 'transparent') return 'FFFFFF';
+
+      const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
+      if (!match) return 'FFFFFF';
+      return match.slice(1).map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
+    };
+
+    const extractAlpha = (rgbStr) => {
+      const match = rgbStr.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
+      if (!match || !match[4]) return null;
+      const alpha = parseFloat(match[4]);
+      return Math.round((1 - alpha) * 100);
+    };
+
+    const applyTextTransform = (text, textTransform) => {
+      if (textTransform === 'uppercase') return text.toUpperCase();
+      if (textTransform === 'lowercase') return text.toLowerCase();
+      if (textTransform === 'capitalize') {
+        return text.replace(/\b\w/g, c => c.toUpperCase());
+      }
+      return text;
+    };
+
+    // Extract rotation angle from CSS transform and writing-mode
+    const getRotation = (transform, writingMode) => {
+      let angle = 0;
+
+      // Handle writing-mode first
+      // PowerPoint: 90° = text rotated 90° clockwise (reads top to bottom, letters upright)
+      // PowerPoint: 270° = text rotated 270° clockwise (reads bottom to top, letters upright)
+      if (writingMode === 'vertical-rl') {
+        // vertical-rl alone = text reads top to bottom = 90° in PowerPoint
+        angle = 90;
+      } else if (writingMode === 'vertical-lr') {
+        // vertical-lr alone = text reads bottom to top = 270° in PowerPoint
+        angle = 270;
+      }
+
+      // Then add any transform rotation
+      if (transform && transform !== 'none') {
+        // Try to match rotate() function
+        const rotateMatch = transform.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/);
+        if (rotateMatch) {
+          angle += parseFloat(rotateMatch[1]);
+        } else {
+          // Browser may compute as matrix - extract rotation from matrix
+          const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
+          if (matrixMatch) {
+            const values = matrixMatch[1].split(',').map(parseFloat);
+            // matrix(a, b, c, d, e, f) where rotation = atan2(b, a)
+            const matrixAngle = Math.atan2(values[1], values[0]) * (180 / Math.PI);
+            angle += Math.round(matrixAngle);
+          }
+        }
+      }
+
+      // Normalize to 0-359 range
+      angle = angle % 360;
+      if (angle < 0) angle += 360;
+
+      return angle === 0 ? null : angle;
+    };
+
+    // Get position/dimensions accounting for rotation
+    const getPositionAndSize = (el, rect, rotation) => {
+      if (rotation === null) {
+        return { x: rect.left, y: rect.top, w: rect.width, h: rect.height };
+      }
+
+      // For 90° or 270° rotations, swap width and height
+      // because PowerPoint applies rotation to the original (unrotated) box
+      const isVertical = rotation === 90 || rotation === 270;
+
+      if (isVertical) {
+        // The browser shows us the rotated dimensions (tall box for vertical text)
+        // But PowerPoint needs the pre-rotation dimensions (wide box that will be rotated)
+        // So we swap: browser's height becomes PPT's width, browser's width becomes PPT's height
+        const centerX = rect.left + rect.width / 2;
+        const centerY = rect.top + rect.height / 2;
+
+        return {
+          x: centerX - rect.height / 2,
+          y: centerY - rect.width / 2,
+          w: rect.height,
+          h: rect.width
+        };
+      }
+
+      // For other rotations, use element's offset dimensions
+      const centerX = rect.left + rect.width / 2;
+      const centerY = rect.top + rect.height / 2;
+      return {
+        x: centerX - el.offsetWidth / 2,
+        y: centerY - el.offsetHeight / 2,
+        w: el.offsetWidth,
+        h: el.offsetHeight
+      };
+    };
+
+    // Parse CSS box-shadow into PptxGenJS shadow properties
+    const parseBoxShadow = (boxShadow) => {
+      if (!boxShadow || boxShadow === 'none') return null;
+
+      // Browser computed style format: "rgba(0, 0, 0, 0.3) 2px 2px 8px 0px [inset]"
+      // CSS format: "[inset] 2px 2px 8px 0px rgba(0, 0, 0, 0.3)"
+
+      const insetMatch = boxShadow.match(/inset/);
+
+      // IMPORTANT: PptxGenJS/PowerPoint doesn't properly support inset shadows
+      // Only process outer shadows to avoid file corruption
+      if (insetMatch) return null;
+
+      // Extract color first (rgba or rgb at start)
+      const colorMatch = boxShadow.match(/rgba?\([^)]+\)/);
+
+      // Extract numeric values (handles both px and pt units)
+      const parts = boxShadow.match(/([-\d.]+)(px|pt)/g);
+
+      if (!parts || parts.length < 2) return null;
+
+      const offsetX = parseFloat(parts[0]);
+      const offsetY = parseFloat(parts[1]);
+      const blur = parts.length > 2 ? parseFloat(parts[2]) : 0;
+
+      // Calculate angle from offsets (in degrees, 0 = right, 90 = down)
+      let angle = 0;
+      if (offsetX !== 0 || offsetY !== 0) {
+        angle = Math.atan2(offsetY, offsetX) * (180 / Math.PI);
+        if (angle < 0) angle += 360;
+      }
+
+      // Calculate offset distance (hypotenuse)
+      const offset = Math.sqrt(offsetX * offsetX + offsetY * offsetY) * PT_PER_PX;
+
+      // Extract opacity from rgba
+      let opacity = 0.5;
+      if (colorMatch) {
+        const opacityMatch = colorMatch[0].match(/[\d.]+\)$/);
+        if (opacityMatch) {
+          opacity = parseFloat(opacityMatch[0].replace(')', ''));
+        }
+      }
+
+      return {
+        type: 'outer',
+        angle: Math.round(angle),
+        blur: blur * 0.75, // Convert to points
+        color: colorMatch ? rgbToHex(colorMatch[0]) : '000000',
+        offset: offset,
+        opacity
+      };
+    };
+
+    // Parse inline formatting tags (<b>, <i>, <u>, <strong>, <em>, <span>) into text runs
+    const parseInlineFormatting = (element, baseOptions = {}, runs = [], baseTextTransform = (x) => x) => {
+      let prevNodeIsText = false;
+
+      element.childNodes.forEach((node) => {
+        let textTransform = baseTextTransform;
+
+        const isText = node.nodeType === Node.TEXT_NODE || node.tagName === 'BR';
+        if (isText) {
+          const text = node.tagName === 'BR' ? '\n' : textTransform(node.textContent.replace(/\s+/g, ' '));
+          const prevRun = runs[runs.length - 1];
+          if (prevNodeIsText && prevRun) {
+            prevRun.text += text;
+          } else {
+            runs.push({ text, options: { ...baseOptions } });
+          }
+
+        } else if (node.nodeType === Node.ELEMENT_NODE && node.textContent.trim()) {
+          const options = { ...baseOptions };
+          const computed = window.getComputedStyle(node);
+
+          // Handle inline elements with computed styles
+          if (node.tagName === 'SPAN' || node.tagName === 'B' || node.tagName === 'STRONG' || node.tagName === 'I' || node.tagName === 'EM' || node.tagName === 'U') {
+            const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600;
+            if (isBold && !shouldSkipBold(computed.fontFamily)) options.bold = true;
+            if (computed.fontStyle === 'italic') options.italic = true;
+            if (computed.textDecoration && computed.textDecoration.includes('underline')) options.underline = true;
+            if (computed.color && computed.color !== 'rgb(0, 0, 0)') {
+              options.color = rgbToHex(computed.color);
+              const transparency = extractAlpha(computed.color);
+              if (transparency !== null) options.transparency = transparency;
+            }
+            if (computed.fontSize) options.fontSize = pxToPoints(computed.fontSize);
+
+            // Apply text-transform on the span element itself
+            if (computed.textTransform && computed.textTransform !== 'none') {
+              const transformStr = computed.textTransform;
+              textTransform = (text) => applyTextTransform(text, transformStr);
+            }
+
+            // Validate: Check for margins on inline elements
+            if (computed.marginLeft && parseFloat(computed.marginLeft) > 0) {
+              errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-left which is not supported in PowerPoint. Remove margin from inline elements.`);
+            }
+            if (computed.marginRight && parseFloat(computed.marginRight) > 0) {
+              errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-right which is not supported in PowerPoint. Remove margin from inline elements.`);
+            }
+            if (computed.marginTop && parseFloat(computed.marginTop) > 0) {
+              errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-top which is not supported in PowerPoint. Remove margin from inline elements.`);
+            }
+            if (computed.marginBottom && parseFloat(computed.marginBottom) > 0) {
+              errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-bottom which is not supported in PowerPoint. Remove margin from inline elements.`);
+            }
+
+            // Recursively process the child node. This will flatten nested spans into multiple runs.
+            parseInlineFormatting(node, options, runs, textTransform);
+          }
+        }
+
+        prevNodeIsText = isText;
+      });
+
+      // Trim leading space from first run and trailing space from last run
+      if (runs.length > 0) {
+        runs[0].text = runs[0].text.replace(/^\s+/, '');
+        runs[runs.length - 1].text = runs[runs.length - 1].text.replace(/\s+$/, '');
+      }
+
+      return runs.filter(r => r.text.length > 0);
+    };
+
+    // Extract background from body (image or color)
+    const body = document.body;
+    const bodyStyle = window.getComputedStyle(body);
+    const bgImage = bodyStyle.backgroundImage;
+    const bgColor = bodyStyle.backgroundColor;
+
+    // Collect validation errors
+    const errors = [];
+
+    // Validate: Check for CSS gradients
+    if (bgImage && (bgImage.includes('linear-gradient') || bgImage.includes('radial-gradient'))) {
+      errors.push(
+        'CSS gradients are not supported. Use Sharp to rasterize gradients as PNG images first, ' +
+        'then reference with background-image: url(\'gradient.png\')'
+      );
+    }
+
+    let background;
+    if (bgImage && bgImage !== 'none') {
+      // Extract URL from url("...") or url(...)
+      const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/);
+      if (urlMatch) {
+        background = {
+          type: 'image',
+          path: urlMatch[1]
+        };
+      } else {
+        background = {
+          type: 'color',
+          value: rgbToHex(bgColor)
+        };
+      }
+    } else {
+      background = {
+        type: 'color',
+        value: rgbToHex(bgColor)
+      };
+    }
+
+    // Process all elements
+    const elements = [];
+    const placeholders = [];
+    const textTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'UL', 'OL', 'LI'];
+    const processed = new Set();
+
+    document.querySelectorAll('*').forEach((el) => {
+      if (processed.has(el)) return;
+
+      // Validate text elements don't have backgrounds, borders, or shadows
+      if (textTags.includes(el.tagName)) {
+        const computed = window.getComputedStyle(el);
+        const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
+        const hasBorder = (computed.borderWidth && parseFloat(computed.borderWidth) > 0) ||
+                          (computed.borderTopWidth && parseFloat(computed.borderTopWidth) > 0) ||
+                          (computed.borderRightWidth && parseFloat(computed.borderRightWidth) > 0) ||
+                          (computed.borderBottomWidth && parseFloat(computed.borderBottomWidth) > 0) ||
+                          (computed.borderLeftWidth && parseFloat(computed.borderLeftWidth) > 0);
+        const hasShadow = computed.boxShadow && computed.boxShadow !== 'none';
+
+        if (hasBg || hasBorder || hasShadow) {
+          errors.push(
+            `Text element <${el.tagName.toLowerCase()}> has ${hasBg ? 'background' : hasBorder ? 'border' : 'shadow'}. ` +
+            'Backgrounds, borders, and shadows are only supported on <div> elements, not text elements.'
+          );
+          return;
+        }
+      }
+
+      // Extract placeholder elements (for charts, etc.)
+      if (el.className && el.className.includes('placeholder')) {
+        const rect = el.getBoundingClientRect();
+        if (rect.width === 0 || rect.height === 0) {
+          errors.push(
+            `Placeholder "${el.id || 'unnamed'}" has ${rect.width === 0 ? 'width: 0' : 'height: 0'}. Check the layout CSS.`
+          );
+        } else {
+          placeholders.push({
+            id: el.id || `placeholder-${placeholders.length}`,
+            x: pxToInch(rect.left),
+            y: pxToInch(rect.top),
+            w: pxToInch(rect.width),
+            h: pxToInch(rect.height)
+          });
+        }
+        processed.add(el);
+        return;
+      }
+
+      // Extract images
+      if (el.tagName === 'IMG') {
+        const rect = el.getBoundingClientRect();
+        if (rect.width > 0 && rect.height > 0) {
+          elements.push({
+            type: 'image',
+            src: el.src,
+            position: {
+              x: pxToInch(rect.left),
+              y: pxToInch(rect.top),
+              w: pxToInch(rect.width),
+              h: pxToInch(rect.height)
+            }
+          });
+          processed.add(el);
+          return;
+        }
+      }
+
+      // Extract DIVs with backgrounds/borders as shapes
+      const isContainer = el.tagName === 'DIV' && !textTags.includes(el.tagName);
+      if (isContainer) {
+        const computed = window.getComputedStyle(el);
+        const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
+
+        // Validate: Check for unwrapped text content in DIV
+        for (const node of el.childNodes) {
+          if (node.nodeType === Node.TEXT_NODE) {
+            const text = node.textContent.trim();
+            if (text) {
+              errors.push(
+                `DIV element contains unwrapped text "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}". ` +
+                'All text must be wrapped in <p>, <h1>-<h6>, <ul>, or <ol> tags to appear in PowerPoint.'
+              );
+            }
+          }
+        }
+
+        // Check for background images on shapes
+        const bgImage = computed.backgroundImage;
+        if (bgImage && bgImage !== 'none') {
+          errors.push(
+            'Background images on DIV elements are not supported. ' +
+            'Use solid colors or borders for shapes, or use slide.addImage() in PptxGenJS to layer images.'
+          );
+          return;
+        }
+
+        // Check for borders - both uniform and partial
+        const borderTop = computed.borderTopWidth;
+        const borderRight = computed.borderRightWidth;
+        const borderBottom = computed.borderBottomWidth;
+        const borderLeft = computed.borderLeftWidth;
+        const borders = [borderTop, borderRight, borderBottom, borderLeft].map(b => parseFloat(b) || 0);
+        const hasBorder = borders.some(b => b > 0);
+        const hasUniformBorder = hasBorder && borders.every(b => b === borders[0]);
+        const borderLines = [];
+
+        if (hasBorder && !hasUniformBorder) {
+          const rect = el.getBoundingClientRect();
+          const x = pxToInch(rect.left);
+          const y = pxToInch(rect.top);
+          const w = pxToInch(rect.width);
+          const h = pxToInch(rect.height);
+
+          // Collect lines to add after shape (inset by half the line width to center on edge)
+          if (parseFloat(borderTop) > 0) {
+            const widthPt = pxToPoints(borderTop);
+            const inset = (widthPt / 72) / 2; // Convert points to inches, then half
+            borderLines.push({
+              type: 'line',
+              x1: x, y1: y + inset, x2: x + w, y2: y + inset,
+              width: widthPt,
+              color: rgbToHex(computed.borderTopColor)
+            });
+          }
+          if (parseFloat(borderRight) > 0) {
+            const widthPt = pxToPoints(borderRight);
+            const inset = (widthPt / 72) / 2;
+            borderLines.push({
+              type: 'line',
+              x1: x + w - inset, y1: y, x2: x + w - inset, y2: y + h,
+              width: widthPt,
+              color: rgbToHex(computed.borderRightColor)
+            });
+          }
+          if (parseFloat(borderBottom) > 0) {
+            const widthPt = pxToPoints(borderBottom);
+            const inset = (widthPt / 72) / 2;
+            borderLines.push({
+              type: 'line',
+              x1: x, y1: y + h - inset, x2: x + w, y2: y + h - inset,
+              width: widthPt,
+              color: rgbToHex(computed.borderBottomColor)
+            });
+          }
+          if (parseFloat(borderLeft) > 0) {
+            const widthPt = pxToPoints(borderLeft);
+            const inset = (widthPt / 72) / 2;
+            borderLines.push({
+              type: 'line',
+              x1: x + inset, y1: y, x2: x + inset, y2: y + h,
+              width: widthPt,
+              color: rgbToHex(computed.borderLeftColor)
+            });
+          }
+        }
+
+        if (hasBg || hasBorder) {
+          const rect = el.getBoundingClientRect();
+          if (rect.width > 0 && rect.height > 0) {
+            const shadow = parseBoxShadow(computed.boxShadow);
+
+            // Only add shape if there's background or uniform border
+            if (hasBg || hasUniformBorder) {
+              elements.push({
+                type: 'shape',
+                text: '',  // Shape only - child text elements render on top
+                position: {
+                  x: pxToInch(rect.left),
+                  y: pxToInch(rect.top),
+                  w: pxToInch(rect.width),
+                  h: pxToInch(rect.height)
+                },
+                shape: {
+                  fill: hasBg ? rgbToHex(computed.backgroundColor) : null,
+                  transparency: hasBg ? extractAlpha(computed.backgroundColor) : null,
+                  line: hasUniformBorder ? {
+                    color: rgbToHex(computed.borderColor),
+                    width: pxToPoints(computed.borderWidth)
+                  } : null,
+                  // Convert border-radius to rectRadius (in inches)
+                  // % values: 50%+ = circle (1), <50% = percentage of min dimension
+                  // pt values: divide by 72 (72pt = 1 inch)
+                  // px values: divide by 96 (96px = 1 inch)
+                  rectRadius: (() => {
+                    const radius = computed.borderRadius;
+                    const radiusValue = parseFloat(radius);
+                    if (radiusValue === 0) return 0;
+
+                    if (radius.includes('%')) {
+                      if (radiusValue >= 50) return 1;
+                      // Calculate percentage of smaller dimension
+                      const minDim = Math.min(rect.width, rect.height);
+                      return (radiusValue / 100) * pxToInch(minDim);
+                    }
+
+                    if (radius.includes('pt')) return radiusValue / 72;
+                    return radiusValue / PX_PER_IN;
+                  })(),
+                  shadow: shadow
+                }
+              });
+            }
+
+            // Add partial border lines
+            elements.push(...borderLines);
+
+            processed.add(el);
+            return;
+          }
+        }
+      }
+
+      // Extract bullet lists as single text block
+      if (el.tagName === 'UL' || el.tagName === 'OL') {
+        const rect = el.getBoundingClientRect();
+        if (rect.width === 0 || rect.height === 0) return;
+
+        const liElements = Array.from(el.querySelectorAll('li'));
+        const items = [];
+        const ulComputed = window.getComputedStyle(el);
+        const ulPaddingLeftPt = pxToPoints(ulComputed.paddingLeft);
+
+        // Split: margin-left for bullet position, indent for text position
+        // margin-left + indent = ul padding-left
+        const marginLeft = ulPaddingLeftPt * 0.5;
+        const textIndent = ulPaddingLeftPt * 0.5;
+
+        liElements.forEach((li, idx) => {
+          const isLast = idx === liElements.length - 1;
+          const runs = parseInlineFormatting(li, { breakLine: false });
+          // Clean manual bullets from first run
+          if (runs.length > 0) {
+            runs[0].text = runs[0].text.replace(/^[•\-\*▪▸]\s*/, '');
+            runs[0].options.bullet = { indent: textIndent };
+          }
+          // Set breakLine on last run
+          if (runs.length > 0 && !isLast) {
+            runs[runs.length - 1].options.breakLine = true;
+          }
+          items.push(...runs);
+        });
+
+        const computed = window.getComputedStyle(liElements[0] || el);
+
+        elements.push({
+          type: 'list',
+          items: items,
+          position: {
+            x: pxToInch(rect.left),
+            y: pxToInch(rect.top),
+            w: pxToInch(rect.width),
+            h: pxToInch(rect.height)
+          },
+          style: {
+            fontSize: pxToPoints(computed.fontSize),
+            fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
+            color: rgbToHex(computed.color),
+            transparency: extractAlpha(computed.color),
+            align: computed.textAlign === 'start' ? 'left' : computed.textAlign,
+            lineSpacing: computed.lineHeight && computed.lineHeight !== 'normal' ? pxToPoints(computed.lineHeight) : null,
+            paraSpaceBefore: 0,
+            paraSpaceAfter: pxToPoints(computed.marginBottom),
+            // PptxGenJS margin array is [left, right, bottom, top]
+            margin: [marginLeft, 0, 0, 0]
+          }
+        });
+
+        liElements.forEach(li => processed.add(li));
+        processed.add(el);
+        return;
+      }
+
+      // Extract text elements (P, H1, H2, etc.)
+      if (!textTags.includes(el.tagName)) return;
+
+      const rect = el.getBoundingClientRect();
+      const text = el.textContent.trim();
+      if (rect.width === 0 || rect.height === 0 || !text) return;
+
+      // Validate: Check for manual bullet symbols in text elements (not in lists)
+      if (el.tagName !== 'LI' && /^[•\-\*▪▸○●◆◇■□]\s/.test(text.trimStart())) {
+        errors.push(
+          `Text element <${el.tagName.toLowerCase()}> starts with bullet symbol "${text.substring(0, 20)}...". ` +
+          'Use <ul> or <ol> lists instead of manual bullet symbols.'
+        );
+        return;
+      }
+
+      const computed = window.getComputedStyle(el);
+      const rotation = getRotation(computed.transform, computed.writingMode);
+      const { x, y, w, h } = getPositionAndSize(el, rect, rotation);
+
+      const baseStyle = {
+        fontSize: pxToPoints(computed.fontSize),
+        fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
+        color: rgbToHex(computed.color),
+        align: computed.textAlign === 'start' ? 'left' : computed.textAlign,
+        lineSpacing: pxToPoints(computed.lineHeight),
+        paraSpaceBefore: pxToPoints(computed.marginTop),
+        paraSpaceAfter: pxToPoints(computed.marginBottom),
+        // PptxGenJS margin array is [left, right, bottom, top] (not [top, right, bottom, left] as documented)
+        margin: [
+          pxToPoints(computed.paddingLeft),
+          pxToPoints(computed.paddingRight),
+          pxToPoints(computed.paddingBottom),
+          pxToPoints(computed.paddingTop)
+        ]
+      };
+
+      const transparency = extractAlpha(computed.color);
+      if (transparency !== null) baseStyle.transparency = transparency;
+
+      if (rotation !== null) baseStyle.rotate = rotation;
+
+      const hasFormatting = el.querySelector('b, i, u, strong, em, span, br');
+
+      if (hasFormatting) {
+        // Text with inline formatting
+        const transformStr = computed.textTransform;
+        const runs = parseInlineFormatting(el, {}, [], (str) => applyTextTransform(str, transformStr));
+
+        // Adjust lineSpacing based on largest fontSize in runs
+        const adjustedStyle = { ...baseStyle };
+        if (adjustedStyle.lineSpacing) {
+          const maxFontSize = Math.max(
+            adjustedStyle.fontSize,
+            ...runs.map(r => r.options?.fontSize || 0)
+          );
+          if (maxFontSize > adjustedStyle.fontSize) {
+            const lineHeightMultiplier = adjustedStyle.lineSpacing / adjustedStyle.fontSize;
+            adjustedStyle.lineSpacing = maxFontSize * lineHeightMultiplier;
+          }
+        }
+
+        elements.push({
+          type: el.tagName.toLowerCase(),
+          text: runs,
+          position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) },
+          style: adjustedStyle
+        });
+      } else {
+        // Plain text - inherit CSS formatting
+        const textTransform = computed.textTransform;
+        const transformedText = applyTextTransform(text, textTransform);
+
+        const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600;
+
+        elements.push({
+          type: el.tagName.toLowerCase(),
+          text: transformedText,
+          position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) },
+          style: {
+            ...baseStyle,
+            bold: isBold && !shouldSkipBold(computed.fontFamily),
+            italic: computed.fontStyle === 'italic',
+            underline: computed.textDecoration.includes('underline')
+          }
+        });
+      }
+
+      processed.add(el);
+    });
+
+    return { background, elements, placeholders, errors };
+  });
+}
+
+async function html2pptx(htmlFile, pres, options = {}) {
+  const {
+    tmpDir = process.env.TMPDIR || '/tmp',
+    slide = null
+  } = options;
+
+  try {
+    // Use Chrome on macOS, default Chromium on Unix
+    const launchOptions = { env: { TMPDIR: tmpDir } };
+    if (process.platform === 'darwin') {
+      launchOptions.channel = 'chrome';
+    }
+
+    const browser = await chromium.launch(launchOptions);
+
+    let bodyDimensions;
+    let slideData;
+
+    const filePath = path.isAbsolute(htmlFile) ? htmlFile : path.join(process.cwd(), htmlFile);
+    const validationErrors = [];
+
+    try {
+      const page = await browser.newPage();
+      page.on('console', (msg) => {
+        // Log the message text to your test runner's console
+        console.log(`Browser console: ${msg.text()}`);
+      });
+
+      await page.goto(`file://${filePath}`);
+
+      bodyDimensions = await getBodyDimensions(page);
+
+      await page.setViewportSize({
+        width: Math.round(bodyDimensions.width),
+        height: Math.round(bodyDimensions.height)
+      });
+
+      slideData = await extractSlideData(page);
+    } finally {
+      await browser.close();
+    }
+
+    // Collect all validation errors
+    if (bodyDimensions.errors && bodyDimensions.errors.length > 0) {
+      validationErrors.push(...bodyDimensions.errors);
+    }
+
+    const dimensionErrors = validateDimensions(bodyDimensions, pres);
+    if (dimensionErrors.length > 0) {
+      validationErrors.push(...dimensionErrors);
+    }
+
+    const textBoxPositionErrors = validateTextBoxPosition(slideData, bodyDimensions);
+    if (textBoxPositionErrors.length > 0) {
+      validationErrors.push(...textBoxPositionErrors);
+    }
+
+    if (slideData.errors && slideData.errors.length > 0) {
+      validationErrors.push(...slideData.errors);
+    }
+
+    // Throw all errors at once if any exist
+    if (validationErrors.length > 0) {
+      const errorMessage = validationErrors.length === 1
+        ? validationErrors[0]
+        : `Multiple validation errors found:\n${validationErrors.map((e, i) => `  ${i + 1}. ${e}`).join('\n')}`;
+      throw new Error(errorMessage);
+    }
+
+    const targetSlide = slide || pres.addSlide();
+
+    await addBackground(slideData, targetSlide, tmpDir);
+    addElements(slideData, targetSlide, pres);
+
+    return { slide: targetSlide, placeholders: slideData.placeholders };
+  } catch (error) {
+    if (!error.message.startsWith(htmlFile)) {
+      throw new Error(`${htmlFile}: ${error.message}`);
+    }
+    throw error;
+  }
+}
+
+module.exports = html2pptx;

+ 254 - 0
scripts/render-video.js

@@ -0,0 +1,254 @@
+#!/usr/bin/env node
+/**
+ * HTML animation → MP4 via Playwright recordVideo + ffmpeg.
+ *
+ * Requires: global playwright (`npm install -g playwright`), ffmpeg on PATH.
+ *
+ * Usage:
+ *   NODE_PATH=$(npm root -g) node render-video.js <html-file> \
+ *     [--duration=30] [--width=1920] [--height=1080] \
+ *     [--trim=<seconds>] [--fontwait=1.5] [--readytimeout=8] \
+ *     [--keep-chrome]
+ *
+ * Design:
+ *   1. Warmup context (no record) — caches fonts/assets, closes cleanly
+ *   2. Record context (fresh, recordVideo ON) — WebM starts writing at
+ *      context creation. Babel-standalone compile + React mount +
+ *      fonts.ready can take 1.5-3s, during which WebM writes black frames.
+ *      We measure this by waiting for window.__ready (set by animations.jsx
+ *      Stage component after first paint), then trim exactly that offset.
+ *   3. addInitScript injects CSS hiding "chrome" elements (progress bar,
+ *      replay button, masthead, footer, etc.) that are fine for human
+ *      debugging but shouldn't appear in exported video.
+ *
+ * Animation-ready signal:
+ *   Set `window.__ready = true` in your HTML after first paint. This tells
+ *   the recorder "animation has started rendering — treat now as t=0".
+ *   If you use animations.jsx, Stage does this automatically. Otherwise
+ *   add: `document.fonts.ready.then(() => requestAnimationFrame(() => { window.__ready = true }));`
+ *   after your first render call.
+ *
+ *   Without __ready, falls back to --fontwait=1.5s (may leave 1-2s of black
+ *   at the start). Pass --trim=<seconds> to override manually.
+ *
+ * Chrome elements hidden by default (all common class names + `.no-record`
+ * convention). Pass --keep-chrome to disable this and see raw HTML.
+ *
+ * Output: next to the HTML file, same basename with .mp4 suffix.
+ */
+
+const { chromium } = require('playwright');
+const path = require('path');
+const fs = require('fs');
+const { spawnSync } = require('child_process');
+
+function arg(name, def) {
+  const p = process.argv.find(a => a.startsWith('--' + name + '='));
+  return p ? p.slice(name.length + 3) : def;
+}
+function hasFlag(name) {
+  return process.argv.includes('--' + name);
+}
+
+const HTML_FILE = process.argv[2];
+if (!HTML_FILE || HTML_FILE.startsWith('--')) {
+  console.error('Usage: node render-video.js <html-file>');
+  console.error('Example: NODE_PATH=$(npm root -g) node render-video.js my-animation.html');
+  process.exit(1);
+}
+
+const DURATION  = parseFloat(arg('duration', '30'));
+const WIDTH     = parseInt(arg('width', '1920'));
+const HEIGHT    = parseInt(arg('height', '1080'));
+const TRIM_OVERRIDE = arg('trim', null);              // manual override (seconds). If unset, auto-detected.
+const FONT_WAIT = parseFloat(arg('fontwait', '1.5')); // fallback when no __ready signal
+const READY_TIMEOUT = parseFloat(arg('readytimeout', '8'));
+const KEEP_CHROME = hasFlag('keep-chrome');
+
+const HTML_ABS = path.resolve(HTML_FILE);
+const BASENAME = path.basename(HTML_FILE, path.extname(HTML_FILE));
+const DIR      = path.dirname(HTML_ABS);
+const TMP_DIR  = path.join(DIR, '.video-tmp-' + Date.now() + '-' + process.pid);
+const MP4_OUT  = path.join(DIR, BASENAME + '.mp4');
+
+// CSS to hide "chrome" elements during recording.
+// Covers class-name conventions seen across skill-built animations,
+// plus a `.no-record` explicit opt-out class.
+const HIDE_CHROME_CSS = `
+  .no-record,
+  .progress, .progress-bar,
+  .counter, .tCur,
+  .phases, .phase-label, .phase,
+  .replay, button.replay,
+  .masthead, .kicker, .title,
+  .footer,
+  [data-role="chrome"], [data-record="hidden"] {
+    display: none !important;
+  }
+`;
+
+console.log(`▸ Rendering: ${HTML_FILE}`);
+console.log(`  size: ${WIDTH}x${HEIGHT} · duration: ${DURATION}s · hide-chrome: ${!KEEP_CHROME}`);
+console.log(`  output: ${MP4_OUT}`);
+
+(async () => {
+  fs.mkdirSync(TMP_DIR, { recursive: true });
+
+  const browser = await chromium.launch();
+  const url = 'file://' + HTML_ABS;
+
+  // ── Phase 1: WARMUP (no recording, caches fonts/assets) ─────────────
+  console.log('▸ Warmup (caching fonts)…');
+  const warmupCtx = await browser.newContext({
+    viewport: { width: WIDTH, height: HEIGHT },
+  });
+  const warmupPage = await warmupCtx.newPage();
+  await warmupPage.goto(url, { waitUntil: 'networkidle' });
+  await warmupPage.waitForTimeout(FONT_WAIT * 1000);
+  await warmupCtx.close();
+
+  // ── Phase 2: RECORD (fresh context, animation from t=0) ─────────────
+  console.log('▸ Recording (clean start)…');
+  const recordCtx = await browser.newContext({
+    viewport: { width: WIDTH, height: HEIGHT },
+    deviceScaleFactor: 1,
+    recordVideo: {
+      dir: TMP_DIR,
+      size: { width: WIDTH, height: HEIGHT },
+    },
+  });
+
+  // Inject CSS + JS heuristic to hide "chrome" elements.
+  // Two layers:
+  //   A. CSS selectors for common class-name conventions (cheap)
+  //   B. JS heuristic for fixed-position bars containing buttons or time
+  //      readouts (catches inline-styled chrome like <Stage> controls)
+  // Persists across reloads via addInitScript.
+  if (!KEEP_CHROME) {
+    await recordCtx.addInitScript(css => {
+      const HIDE_MARK = 'data-video-hidden';
+
+      function injectStyle() {
+        const style = document.createElement('style');
+        style.setAttribute('data-inject', 'render-video-chrome-hide');
+        style.textContent = css;
+        (document.head || document.documentElement).appendChild(style);
+      }
+
+      function hideChromeBars() {
+        const vh = window.innerHeight;
+        document.querySelectorAll('div, nav, header, footer, section, aside')
+          .forEach(el => {
+            if (el.hasAttribute(HIDE_MARK)) return;
+            if (el.dataset.recordKeep === 'true') return;
+            const s = getComputedStyle(el);
+            if (s.position !== 'fixed' && s.position !== 'sticky') return;
+            const r = el.getBoundingClientRect();
+            // Only skinny bars (not full-screen overlays)
+            if (r.height > vh * 0.25) return;
+            const atBottom = r.bottom >= vh - 30;
+            const atTop = r.top <= 30 && r.height < 80;
+            if (!atBottom && !atTop) return;
+            // Chrome-like: contains button or scrubber/time glyphs
+            const txt = el.textContent || '';
+            const hasBtn = !!el.querySelector('button, [role="button"]');
+            const hasCtrls = /[⏸▶⏮⏭↻↺↩↪]|\d+\.\d+\s*s/.test(txt);
+            if (hasBtn || hasCtrls) {
+              el.style.setProperty('display', 'none', 'important');
+              el.setAttribute(HIDE_MARK, '1');
+            }
+          });
+      }
+
+      const start = () => {
+        injectStyle();
+        hideChromeBars();
+        // Re-run as React/Vue commits DOM changes
+        const obs = new MutationObserver(hideChromeBars);
+        obs.observe(document.body, { childList: true, subtree: true });
+        setTimeout(() => obs.disconnect(), 6000);
+      };
+
+      if (document.readyState === 'loading') {
+        document.addEventListener('DOMContentLoaded', start, { once: true });
+      } else {
+        start();
+      }
+    }, HIDE_CHROME_CSS);
+  }
+
+  // Record context opens page. The WebM starts writing the moment the
+  // context is created — so we track T0 here and measure how many seconds
+  // elapse before the animation is actually ready (Babel compile + React
+  // mount + fonts.ready). That elapsed time = exact trim offset.
+  const T0 = Date.now();
+  const page = await recordCtx.newPage();
+  await page.goto(url, { waitUntil: 'networkidle' });
+
+  // Wait for animation ready signal. Stage component (animations.jsx) sets
+  // window.__ready = true on its first rAF after mount + fonts.ready.
+  // Fallback: if HTML doesn't set __ready within READY_TIMEOUT, use fontwait.
+  let animationStartSec;
+  const hasReady = await page.waitForFunction(
+    () => window.__ready === true,
+    { timeout: READY_TIMEOUT * 1000 },
+  ).then(() => true).catch(() => false);
+
+  if (hasReady) {
+    animationStartSec = (Date.now() - T0) / 1000;
+    console.log(`▸ Ready at ${animationStartSec.toFixed(2)}s (from window.__ready)`);
+  } else {
+    await page.waitForTimeout(FONT_WAIT * 1000);
+    animationStartSec = (Date.now() - T0) / 1000;
+    console.log(`▸ No window.__ready signal; using fallback wait (${animationStartSec.toFixed(2)}s)`);
+    console.log(`  tip: in animations.jsx-based HTML this is automatic;`);
+    console.log(`       otherwise set window.__ready=true after your first paint.`);
+  }
+
+  // Now let the animation play out its full duration
+  await page.waitForTimeout(DURATION * 1000 + 300);
+
+  await page.close();
+  await recordCtx.close();
+  await browser.close();
+
+  const webmFiles = fs.readdirSync(TMP_DIR).filter(f => f.endsWith('.webm'));
+  if (webmFiles.length === 0) {
+    console.error('✗ No webm produced');
+    process.exit(1);
+  }
+  const webmPath = path.join(TMP_DIR, webmFiles[0]);
+  console.log(`▸ WebM: ${(fs.statSync(webmPath).size / 1024 / 1024).toFixed(1)} MB`);
+
+  // Resolve final trim offset:
+  //   - manual --trim=X   → use X (explicit user override)
+  //   - otherwise          → use animationStartSec (measured), with a tiny
+  //                          +0.05s nudge to clear the first Babel-commit frame
+  const resolvedTrim = TRIM_OVERRIDE !== null
+    ? parseFloat(TRIM_OVERRIDE)
+    : animationStartSec + 0.05;
+
+  console.log(`▸ ffmpeg: trim=${resolvedTrim.toFixed(2)}s${TRIM_OVERRIDE !== null ? ' (manual)' : ' (auto)'}, encode H.264…`);
+  const ffmpeg = spawnSync('ffmpeg', [
+    '-y',
+    '-ss', String(resolvedTrim),
+    '-i', webmPath,
+    '-t', String(DURATION),
+    '-c:v', 'libx264',
+    '-pix_fmt', 'yuv420p',
+    '-crf', '18',
+    '-preset', 'medium',
+    '-movflags', '+faststart',
+    MP4_OUT,
+  ], { stdio: ['ignore', 'ignore', 'pipe'] });
+
+  if (ffmpeg.status !== 0) {
+    console.error('✗ ffmpeg failed:\n' + ffmpeg.stderr.toString().slice(-2000));
+    process.exit(1);
+  }
+
+  fs.rmSync(TMP_DIR, { recursive: true, force: true });
+
+  const mp4Size = (fs.statSync(MP4_OUT).size / 1024 / 1024).toFixed(1);
+  console.log(`✓ Done: ${MP4_OUT} (${mp4Size} MB)`);
+})();

+ 154 - 0
scripts/verify.py

@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+"""
+verify.py — Playwright封装,用于验证claude-design产出的HTML
+
+Usage:
+    python verify.py path/to/design.html                    # 基础:打开+截图+抓控制台错误
+    python verify.py design.html --viewports 1920x1080,375x667  # 多viewport
+    python verify.py deck.html --slides 10                  # 幻灯片逐页截(前10张)
+    python verify.py design.html --output ./screenshots/   # 输出目录
+    python verify.py design.html --show                    # 非headless,打开真实浏览器
+
+依赖:
+    pip install playwright
+    playwright install chromium
+"""
+
+import argparse
+import sys
+import os
+import time
+from pathlib import Path
+
+
+def parse_viewport(s):
+    w, h = s.split('x')
+    return {'width': int(w), 'height': int(h)}
+
+
+def verify_html(html_path, viewports=None, slides=0, output_dir=None, show=False, wait=2000):
+    try:
+        from playwright.sync_api import sync_playwright
+    except ImportError:
+        print("ERROR: playwright未安装。")
+        print("运行: pip install playwright && playwright install chromium")
+        sys.exit(1)
+
+    html_path = Path(html_path).resolve()
+    if not html_path.exists():
+        print(f"ERROR: 文件不存在: {html_path}")
+        sys.exit(1)
+
+    if output_dir is None:
+        output_dir = html_path.parent / 'screenshots'
+    output_dir = Path(output_dir)
+    output_dir.mkdir(parents=True, exist_ok=True)
+
+    file_url = html_path.as_uri()
+    stem = html_path.stem
+
+    if viewports is None:
+        viewports = [{'width': 1440, 'height': 900}]
+
+    console_errors = []
+    page_errors = []
+
+    with sync_playwright() as p:
+        browser = p.chromium.launch(headless=not show)
+
+        for viewport in viewports:
+            context = browser.new_context(viewport=viewport, device_scale_factor=2)
+            page = context.new_page()
+
+            page.on("console", lambda msg: console_errors.append(f"[{msg.type}] {msg.text}") if msg.type in ("error", "warning") else None)
+            page.on("pageerror", lambda err: page_errors.append(str(err)))
+
+            print(f"\n→ 打开 {file_url} @ {viewport['width']}x{viewport['height']}")
+            page.goto(file_url, wait_until='networkidle')
+            page.wait_for_timeout(wait)
+
+            if slides > 0:
+                for i in range(slides):
+                    screenshot_path = output_dir / f"{stem}-slide-{str(i + 1).zfill(2)}.png"
+                    page.screenshot(path=str(screenshot_path), full_page=False)
+                    print(f"  ✓ slide {i+1} → {screenshot_path.name}")
+
+                    if i < slides - 1:
+                        page.keyboard.press('ArrowRight')
+                        page.wait_for_timeout(500)
+            else:
+                suffix = f"-{viewport['width']}x{viewport['height']}" if len(viewports) > 1 else ""
+                screenshot_path = output_dir / f"{stem}{suffix}.png"
+                page.screenshot(path=str(screenshot_path), full_page=False)
+                print(f"  ✓ 截图 → {screenshot_path.name}")
+
+                full_path = output_dir / f"{stem}{suffix}-full.png"
+                page.screenshot(path=str(full_path), full_page=True)
+                print(f"  ✓ 完整页 → {full_path.name}")
+
+            if show:
+                print("  (浏览器窗口保持打开,按Enter关闭...)")
+                input()
+
+            context.close()
+
+        browser.close()
+
+    print("\n" + "=" * 50)
+    print("验证报告")
+    print("=" * 50)
+
+    if page_errors:
+        print(f"\n❌ Page Errors ({len(page_errors)}):")
+        for e in page_errors:
+            print(f"  - {e}")
+    else:
+        print("\n✅ 无JavaScript错误")
+
+    if console_errors:
+        print(f"\n⚠️  Console Errors/Warnings ({len(console_errors)}):")
+        for e in console_errors[:20]:
+            print(f"  - {e}")
+        if len(console_errors) > 20:
+            print(f"  ... 还有{len(console_errors) - 20}条")
+    else:
+        print("✅ Console干净")
+
+    print(f"\n📸 截图保存至: {output_dir}")
+
+    return 0 if not page_errors else 1
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Verify HTML design outputs with Playwright",
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+    )
+    parser.add_argument("html_path", help="HTML file path")
+    parser.add_argument("--viewports", default="1440x900",
+                        help="逗号分隔的viewport列表,格式 WxH(默认 1440x900)")
+    parser.add_argument("--slides", type=int, default=0,
+                        help="幻灯片模式:截取前N张(需要HTML支持ArrowRight翻页)")
+    parser.add_argument("--output", default=None,
+                        help="输出目录(默认HTML所在目录的screenshots/)")
+    parser.add_argument("--show", action="store_true",
+                        help="非headless,打开真实浏览器窗口")
+    parser.add_argument("--wait", type=int, default=2000,
+                        help="打开页面后等待的毫秒数(默认2000)")
+
+    args = parser.parse_args()
+
+    viewports = [parse_viewport(v) for v in args.viewports.split(",")]
+
+    return verify_html(
+        html_path=args.html_path,
+        viewports=viewports,
+        slides=args.slides,
+        output_dir=args.output,
+        show=args.show,
+        wait=args.wait,
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 38 - 0
test-prompts.json

@@ -0,0 +1,38 @@
+[
+  {
+    "id": 1,
+    "prompt": "我想做一个SaaS产品的登录页面,给我3个风格方向对比看看",
+    "expected": "触发clarifying questions问design context/brand;产出3个variation的design_canvas;不用紫渐变/emoji/Inter等AI slop;有具体理由说明每个variation的差异维度",
+    "tests": "workflow问问题 + variations逻辑 + 反AI slop清单 + design_canvas使用"
+  },
+  {
+    "id": 2,
+    "prompt": "帮我做一份10页的产品pitch deck,讲一个AI工具的创业项目",
+    "expected": "用deck_stage.js起手;先口头vocalize设计系统(色彩/字型/layout节奏)等确认;Section divider/content/data/quote多种layout交替;字号≥24px;1-indexed labels",
+    "tests": "Junior Designer先汇报再做 + deck_stage使用 + 视觉节奏 + scale规范"
+  },
+  {
+    "id": 3,
+    "prompt": "做个30秒的HTML动画,讲神经网络怎么工作",
+    "expected": "用animations.jsx的Stage+Sprite;先写时间轴再写组件;入场easeOut出场easeIn;分phase讲故事而不是堆动画;文字停留≥3秒",
+    "tests": "animations工作流 + easing正确 + 节奏设计 + 时长控制"
+  },
+  {
+    "id": 4,
+    "prompt": "做一个 Habit Tracker App 原型",
+    "expected": "问用户要 overview 平铺 or flow demo(默认走 overview);用 assets/ios_frame.jsx,不手写 Dynamic Island;Tracker 属高密度型,每屏 ≥ 3 处信息密度元素(习惯完成率、连续天数、趋势曲线、成就badge等,非装饰);至少 5-7 屏并排(首页/新建习惯/详情/统计/设置)",
+    "tests": "overview/flow 形态路由 + ios_frame 硬绑定 + 信息密度分型(高密度型)+ 多屏并排"
+  },
+  {
+    "id": 5,
+    "prompt": "做一个读书笔记 App 原型",
+    "expected": "overview 平铺为主;ios_frame.jsx;读书笔记偏内容展示类,信息密度要求不如 Tracker 极端,但笔记列表页仍需 ≥ 3 层信息(书籍、引文、标签、进度);至少 4-6 屏(首页书架/笔记详情/标注高亮/搜索/笔记本管理);字体优先 serif display",
+    "tests": "overview 默认 + ios_frame + 信息层次 + 内容为主的视觉节奏"
+  },
+  {
+    "id": 6,
+    "prompt": "做一个跑步记录 App 原型",
+    "expected": "overview 平铺;ios_frame.jsx;跑步 App 属高密度型(地图、配速曲线、心率区间、每公里分段数据),每屏 ≥ 3 处产品差异化信息;至少 5 屏(今日总览/跑步中实时数据/路线地图/历史记录/月度统计);避免撞 AI slop(不用紫渐变、不堆装饰 icon,但数据可视化 icon 允许保留)",
+    "tests": "overview + ios_frame + 高密度型数据可视化 + 地图/图表混排 + slop 边界条件"
+  }
+]