mirror of
https://github.com/jnMetaCode/superpowers-zh.git
synced 2026-09-02 22:54:06 +08:00
Merge pull request #61 from jnMetaCode/feat/site
feat(site): 新增项目官网(静态站点 + Cloudflare Pages 自动部署)
This commit is contained in:
33
.github/workflows/deploy-site.yml
vendored
Normal file
33
.github/workflows/deploy-site.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Deploy site to Cloudflare Pages
|
||||
|
||||
# 官网自动部署:site/ 或 skills/ 变更时重建并发布。
|
||||
# 需在仓库 Settings → Secrets 配置 CLOUDFLARE_API_TOKEN 与 CLOUDFLARE_ACCOUNT_ID。
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'site/**'
|
||||
- 'skills/**'
|
||||
- '.github/workflows/deploy-site.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
steps:
|
||||
# Action 全部 SHA 固定(防供应链/标签劫持,因本 job 持有可写的 Cloudflare token)
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Build site
|
||||
run: node site/build.mjs
|
||||
- name: Deploy to Cloudflare Pages
|
||||
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy site/dist --project-name=superpowers-zh-site
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,6 +6,9 @@ node_modules/
|
||||
inspo
|
||||
triage/
|
||||
|
||||
# 官网生成产物(源在 site/build.mjs + site/template/)
|
||||
site/dist/
|
||||
|
||||
# 安装器生成的 skills 副本(源在 skills/ 目录)
|
||||
.codex/skills/
|
||||
.gemini/skills/
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"superpowers-zh": "bin/superpowers-zh.js"
|
||||
},
|
||||
"scripts": {
|
||||
"site": "node site/build.mjs",
|
||||
"site:deploy": "node site/build.mjs && wrangler pages deploy site/dist --project-name=superpowers-zh-site",
|
||||
"version": "node scripts/sync-plugin-version.js && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json .cursor-plugin/plugin.json .codex-plugin/plugin.json gemini-extension.json"
|
||||
},
|
||||
"files": [
|
||||
|
||||
77
site/README.md
Normal file
77
site/README.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# superpowers-zh 官网
|
||||
|
||||
零依赖静态站生成器。一条命令产出**中/英双语**站点 + 每个 skill 的**详情(操作文档)页**,内容全部来自 `../skills/*/SKILL.md`,与源文件同步、不会漂移。
|
||||
|
||||
## 构建 / 预览
|
||||
|
||||
```bash
|
||||
node site/build.mjs # 生成 site/dist/(42 个页面)
|
||||
npx serve site/dist # 本地预览(或 python3 -m http.server)
|
||||
```
|
||||
|
||||
产物结构:
|
||||
|
||||
```
|
||||
dist/
|
||||
index.html # 中文首页
|
||||
en/index.html # 英文首页
|
||||
skills/<name>.html # 中文 skill 详情(操作文档)× 20
|
||||
en/skills/<name>.html # 英文 skill 详情 × 20
|
||||
styles.css app.js # 深/浅主题 + 交互(原生 JS,零依赖)
|
||||
assets/ # 图标 + 赞助商 logo
|
||||
_headers # Cloudflare Pages 缓存策略
|
||||
```
|
||||
|
||||
## 特性
|
||||
|
||||
- **中英双语**:服务端生成两套真实页面(非前端切换),SEO 友好、无闪烁。导航栏「中文 / EN」互切。
|
||||
- **深 / 浅主题**:CSS 变量 + localStorage 记忆,内联脚本在首屏前生效,无白屏闪烁。导航栏 ◐ 按钮切换。
|
||||
- **Skill 详情页**:内置零依赖 Markdown 渲染器(`md.mjs`),把每个 `SKILL.md` 渲染成完整操作文档,支持标题/代码块/表格/列表/引用。
|
||||
- **安装命令生成器、Skill 搜索 / 分类筛选、一键复制** —— 全部原生 JS。
|
||||
|
||||
## 改内容改哪里
|
||||
|
||||
| 想改什么 | 改哪里 |
|
||||
|---|---|
|
||||
| Skill 正文 / 文档内容 | **源头** `skills/<name>/SKILL.md`(改完重新 `node site/build.mjs`) |
|
||||
| Skill 中/英标题、英文简介、分组 | `site/build.mjs` 的 `SKILL_META` |
|
||||
| 首页所有文案(中/英) | `site/build.mjs` 的 `T.zh` / `T.en` |
|
||||
| 支持工具 / 安装命令 | `site/build.mjs` 的 `TOOLS`(与 `bin/superpowers-zh.js` 的 `TARGETS` 对齐) |
|
||||
| 样式 / 主题 | `site/template/styles.css` |
|
||||
| 交互逻辑 | `site/template/app.js` |
|
||||
| Markdown 渲染规则 | `site/md.mjs` |
|
||||
|
||||
## 部署到 Cloudflare Pages
|
||||
|
||||
发布目录 `site/dist`,三选一:
|
||||
|
||||
### 方式 A — 连接仓库自动构建(推荐,无需密钥)
|
||||
|
||||
Cloudflare Dashboard → Pages → 连接 GitHub 仓库 `jnMetaCode/superpowers-zh`:
|
||||
|
||||
- **Build command**:`node site/build.mjs`
|
||||
- **Build output directory**:`site/dist`
|
||||
|
||||
之后每次 push 自动重建发布。绑自定义域名(建议 `superpowers.aibuzhiyu.com`)。
|
||||
|
||||
### 方式 B — 命令行一键发布
|
||||
|
||||
```bash
|
||||
npx wrangler login # 浏览器登录(交互,仅首次)
|
||||
npm run site:deploy # = 构建 + wrangler pages deploy site/dist
|
||||
```
|
||||
|
||||
> 在本会话中可用 `!` 前缀直接登录:`!npx wrangler login`
|
||||
|
||||
### 方式 C — GitHub Actions 自动部署
|
||||
|
||||
仓库已含 `.github/workflows/deploy-site.yml`。在 **Settings → Secrets and variables → Actions** 配置:
|
||||
|
||||
- `CLOUDFLARE_API_TOKEN`(Pages 编辑权限)
|
||||
- `CLOUDFLARE_ACCOUNT_ID`
|
||||
|
||||
之后 `site/` 或 `skills/` 变更 push 到 `main` 即自动部署。
|
||||
|
||||
---
|
||||
|
||||
> 国内访问优先用 Cloudflare Pages + 自定义域名;GitHub Pages 在国内访问较慢,不建议作为主站。
|
||||
599
site/build.mjs
Normal file
599
site/build.mjs
Normal file
@@ -0,0 +1,599 @@
|
||||
#!/usr/bin/env node
|
||||
// superpowers-zh 官网生成器 —— 零依赖。
|
||||
// 服务端生成中/英两套静态页 + 每个 skill 的详情(操作文档)页。
|
||||
// skill 卡片与详情正文均直接读取 ../skills/*/SKILL.md,与源文件同步、不漂移。
|
||||
|
||||
import {
|
||||
readFileSync, writeFileSync, mkdirSync, readdirSync,
|
||||
existsSync, copyFileSync, rmSync,
|
||||
} from 'fs';
|
||||
import { resolve, dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createHash } from 'crypto';
|
||||
import { renderMarkdown } from './md.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = resolve(__dirname, '..');
|
||||
const SKILLS_DIR = join(ROOT, 'skills');
|
||||
const DIST = join(__dirname, 'dist');
|
||||
const TEMPLATE = join(__dirname, 'template');
|
||||
const PKG = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
|
||||
|
||||
const esc = s => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
|
||||
// ---- frontmatter 解析 ----
|
||||
function parseFrontmatter(md) {
|
||||
const m = md.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (!m) return {};
|
||||
const out = {};
|
||||
for (const line of m[1].split('\n')) {
|
||||
const kv = line.match(/^(\w[\w-]*):\s*(.*)$/);
|
||||
if (!kv) continue;
|
||||
let val = kv[2].trim();
|
||||
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
||||
val = val.slice(1, -1);
|
||||
}
|
||||
out[kv[1]] = val;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---- skill 展示元数据:中/英标题 + 英文简介 + 分组 ----
|
||||
const SKILL_META = {
|
||||
'using-superpowers': { group: 'meta', title: '使用 Superpowers · 引导', titleEn: 'Using Superpowers · Bootstrap', descEn: 'The bootstrap skill — establishes how to discover and invoke skills at the start of every conversation.' },
|
||||
'brainstorming': { group: 'flow', title: '头脑风暴', titleEn: 'Brainstorming', descEn: 'Explore intent, requirements and design before any creative work — feature, component or behavior change.' },
|
||||
'writing-plans': { group: 'flow', title: '编写实现计划', titleEn: 'Writing Plans', descEn: 'Turn a spec into a step-by-step implementation plan before writing any code.' },
|
||||
'executing-plans': { group: 'flow', title: '执行计划', titleEn: 'Executing Plans', descEn: 'Execute a written plan in a separate session with review checkpoints.' },
|
||||
'subagent-driven-development': { group: 'flow', title: '子代理驱动开发', titleEn: 'Subagent-Driven Dev', descEn: 'Run a plan of independent tasks within the current session via subagents.' },
|
||||
'dispatching-parallel-agents': { group: 'flow', title: '并行代理调度', titleEn: 'Dispatching Parallel Agents', descEn: 'Fan out 2+ independent tasks with no shared state or ordering dependency.' },
|
||||
'workflow-runner': { group: 'flow', title: '工作流运行器', titleEn: 'Workflow Runner', descEn: 'Run agency-orchestrator YAML workflows directly using the current session LLM — no API key.' },
|
||||
'test-driven-development': { group: 'quality', title: '测试驱动开发 · TDD', titleEn: 'Test-Driven Development', descEn: 'Write the test before the implementation, for every feature and bug fix.' },
|
||||
'systematic-debugging': { group: 'quality', title: '系统化调试', titleEn: 'Systematic Debugging', descEn: 'Reproduce and locate the root cause before proposing any fix.' },
|
||||
'verification-before-completion':{ group: 'quality', title: '完成前验证', titleEn: 'Verification Before Completion', descEn: 'Run verification and back every claim with evidence before saying it is done.' },
|
||||
'requesting-code-review': { group: 'review', title: '发起代码审查', titleEn: 'Requesting Code Review', descEn: 'Validate work against requirements before merging or shipping.' },
|
||||
'receiving-code-review': { group: 'review', title: '接收代码审查', titleEn: 'Receiving Code Review', descEn: 'Apply review feedback with technical rigor — verify, don\'t blindly comply.' },
|
||||
'using-git-worktrees': { group: 'git', title: 'Git Worktree 隔离', titleEn: 'Using Git Worktrees', descEn: 'Start isolated feature work in a dedicated git worktree.' },
|
||||
'finishing-a-development-branch':{ group: 'git', title: '收尾开发分支', titleEn: 'Finishing a Branch', descEn: 'Wrap up finished work via structured merge / PR / cleanup options.' },
|
||||
'writing-skills': { group: 'meta', title: '编写 Skill', titleEn: 'Writing Skills', descEn: 'Create, edit and validate skills before deploying them.' },
|
||||
'mcp-builder': { group: 'meta', title: 'MCP 服务器构建', titleEn: 'MCP Builder', descEn: 'Methodology for building production-grade MCP servers that connect AI to external tools.' },
|
||||
'chinese-code-review': { group: 'china', title: '中文代码审查', titleEn: 'Chinese Code Review', descEn: 'Chinese review phrasing, severity tiers, and common anti-patterns in domestic teams.' },
|
||||
'chinese-commit-conventions': { group: 'china', title: '中文提交规范', titleEn: 'Chinese Commit Conventions', descEn: 'Conventional Commits adapted for Chinese, with commitlint / husky / changelog templates.' },
|
||||
'chinese-documentation': { group: 'china', title: '中文文档排版', titleEn: 'Chinese Documentation', descEn: 'CN/EN spacing, punctuation, term preservation and Chinese typography conventions.' },
|
||||
'chinese-git-workflow': { group: 'china', title: '国内 Git 平台', titleEn: 'Chinese Git Workflow', descEn: 'Gitee / Coding.net / JiHu GitLab / CNB access, credentials, CI and mirror sync.' },
|
||||
};
|
||||
|
||||
const GROUPS = [
|
||||
{ id: 'all', zh: '全部', en: 'All' },
|
||||
{ id: 'flow', zh: '工作流程', en: 'Workflow' },
|
||||
{ id: 'quality', zh: '质量 · 测试 · 调试', en: 'Quality' },
|
||||
{ id: 'review', zh: '代码审查', en: 'Code Review' },
|
||||
{ id: 'git', zh: 'Git · 分支', en: 'Git' },
|
||||
{ id: 'meta', zh: '元 · 构建', en: 'Meta' },
|
||||
{ id: 'china', zh: '🇨🇳 中国原创', en: '🇨🇳 China-native' },
|
||||
];
|
||||
|
||||
// ---- 支持的工具(与 bin/superpowers-zh.js 的 TARGETS 对齐) ----
|
||||
const TOOLS = [
|
||||
{ name: 'Claude Code', type: 'CLI', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Cursor', type: 'IDE', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Windsurf', type: 'IDE', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Codex CLI', type: 'CLI', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Gemini CLI', type: 'CLI', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Kiro', type: 'IDE', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Trae', type: 'IDE', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Qoder', type: 'IDE', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Aider', type: 'CLI', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'OpenCode', type: 'CLI', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Qwen Code', type: 'IDE', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Antigravity', type: 'CLI', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'DeerFlow 2.0', type: 'Agent', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'VS Code · Copilot', type: 'IDE', cmd: 'npx superpowers-zh', auto: true },
|
||||
{ name: 'Copilot CLI', type: 'CLI', cmd: 'npx superpowers-zh --tool copilot', auto: false },
|
||||
{ name: 'Hermes Agent', type: 'CLI', cmd: 'npx superpowers-zh --tool hermes', auto: false },
|
||||
{ name: 'Claw Code', type: 'CLI', cmd: 'npx superpowers-zh --tool claw', auto: false },
|
||||
{ name: 'OpenClaw', type: 'CLI', cmd: 'npx superpowers-zh', auto: true },
|
||||
];
|
||||
|
||||
// ---- 双语文案 ----
|
||||
const T = {
|
||||
zh: {
|
||||
htmlLang: 'zh-CN',
|
||||
title: 'superpowers-zh · AI 编程超能力中文增强版',
|
||||
desc: 'superpowers(159k+ ⭐)完整汉化 + 4 个中国原创 skills,一条 npx 命令为 18 款 AI 编程工具装上系统化工作方法论。',
|
||||
nav: { why: '特性', install: '安装', skills: 'Skills', tools: '支持工具', faq: 'FAQ', github: 'GitHub ↗' },
|
||||
heroBadge: 'superpowers 159k+ ⭐ · 完整汉化 + 中国原创',
|
||||
heroH1: '给你的 AI 编程工具<br>装上<span class="grad">真正会干活</span>的超能力',
|
||||
heroLead: '{n} 个经过实战验证的工作方法论 skill —— 从头脑风暴到 TDD,从系统化调试到代码审查。<br>一条命令,自动识别项目里的工具并安装。',
|
||||
heroBtn1: '查看安装命令', heroBtn2: 'GitHub 源码',
|
||||
stats: ['Skills', '中国原创', '支持工具', '当前版本'],
|
||||
whyTitle: '为什么选择 superpowers-zh?',
|
||||
whySub: '不是又一套提示词模板 —— 是让 AI 真正按工程方法干活的系统化能力。',
|
||||
plTitle: '一条龙工作流,每一步都有 skill 把关',
|
||||
plSub: 'skill 之间彼此衔接,AI 会在合适的节点自动触发对应方法论。',
|
||||
cmpTitle: '装上之后,AI 不再"上来就写"',
|
||||
cmpBad: '❌ 没装', cmpGood: '✅ 装了 superpowers-zh',
|
||||
cmpBadPre: '你:给用户模块加个批量导出功能\nAI:好的,我来实现……(直接开写)\n export async function exportUsers() { … }\n你:等等,格式不对,没分页,\n 大数据量会 OOM……',
|
||||
cmpGoodPre: '你:给用户模块加个批量导出功能\nAI:先理清需求 —— 导出格式?数据量级?\n 要分页/流式吗?要权限校验吗?\n (触发 brainstorming)\n → 写计划 → TDD → 验证 → 审查',
|
||||
instTitle: '选你的工具,拿到安装命令',
|
||||
instSub: '大多数工具 <code>npx superpowers-zh</code> 会自动识别项目目录并安装;识别不出的用 <code>--tool</code> 指定。',
|
||||
instLabel: '我用的是',
|
||||
instNoteAuto: '在你的项目根目录运行,<b>自动识别 {name}</b> 并安装。安装后重启工具即可生效。',
|
||||
instNoteManual: '{name} 无法自动识别,需用 <code>--tool</code> 显式指定。在项目根目录运行,安装后重启工具即可生效。',
|
||||
skTitle: '{n} 个 Skill,覆盖开发全流程',
|
||||
skSub: '点击任意卡片查看完整操作文档。',
|
||||
skSearch: '搜索 skill…(如 调试 / review / TDD)',
|
||||
skEmpty: '没有匹配的 skill。',
|
||||
skDetail: '查看文档 →',
|
||||
tagCn: '中国原创',
|
||||
ucTitle: '典型使用场景', ucSub: '每个场景背后都是一组协同工作的 skill。',
|
||||
toolsTitle: '一套 skill,18 款工具通用', toolsSub: '换工具不用换习惯,方法论跟着你走。',
|
||||
faqTitle: '常见问题',
|
||||
bookTitle: '装好之后,配上方法论效率翻倍',
|
||||
bookDesc: '《AI 编程实战 · 方法论三卷书》—— 10 个 AI 编程工具完整教程 + 真实踩坑。在线书 + PDF,永久免费。',
|
||||
bookBtn: '免费阅读 ↗',
|
||||
sponsorTitle: '赞助商',
|
||||
sponsorDesc: '稳定高速的 API 中继服务,为 Claude Code、Codex 等平台提供 API 中继与 AI 生图服务。',
|
||||
sponsorCta: '🙏 想出现在这里?联系 <b>jnMetaCode@qq.com</b>',
|
||||
ctaTitle: '准备好让 AI 真正会干活了吗?',
|
||||
ctaDesc: '一条命令,{n} 个实战方法论装进你的工具。免费、开源、零依赖。',
|
||||
ctaBtn1: '查看安装命令', ctaBtn2: '⭐ Star on GitHub',
|
||||
footCols: [
|
||||
{ h: '产品', links: [['特性', '#why'], ['Skills', '#skills'], ['支持工具', '#tools'], ['FAQ', '#faq']] },
|
||||
{ h: '资源', links: [['GitHub', 'https://github.com/jnMetaCode/superpowers-zh'], ['npm', 'https://www.npmjs.com/package/superpowers-zh'], ['上游 obra/superpowers', 'https://github.com/obra/superpowers'], ['方法论三卷书', 'https://book.aibuzhiyu.com/']] },
|
||||
{ h: '社区', links: [['提交 Issue', 'https://github.com/jnMetaCode/superpowers-zh/issues'], ['贡献指南', 'https://github.com/jnMetaCode/superpowers-zh/blob/main/CLAUDE.md']] },
|
||||
],
|
||||
footTag: 'AI 编程超能力 · 中文增强版 · MIT License',
|
||||
copyright: '© 2026 superpowers-zh · 基于 <a href="https://github.com/obra/superpowers" target="_blank" rel="noopener">obra/superpowers</a> 的中文增强 fork',
|
||||
copy: '复制', copied: '已复制 ✓',
|
||||
backToSkills: '← 返回全部 Skill',
|
||||
detailInstall: '安装此 skill',
|
||||
detailSource: '在 GitHub 查看源文件 ↗',
|
||||
features: [
|
||||
{ icon: '🧠', t: '20 个实战方法论', d: '不是 prompt 模板,是经过跨会话对抗式压力测试调优的工作方法论 —— 从头脑风暴到 TDD、调试、代码审查。' },
|
||||
{ icon: '🔌', t: '18 款工具通用', d: '一套 skill,Claude Code / Cursor / Codex / Gemini CLI / Windsurf… 全适配,换工具不用换习惯。' },
|
||||
{ icon: '⚡', t: '一条命令安装', d: 'npx superpowers-zh 自动识别项目里用的是哪款工具并安装,零配置,装完重启即生效。' },
|
||||
{ icon: '🇨🇳', t: '中国原创 Skills', d: '中文代码审查话术、中文提交规范、中文文档排版、国内 Git 平台(Gitee/Coding/极狐)配置 —— 上游没有。' },
|
||||
{ icon: '📖', t: '完整汉化上游', d: '同步 obra/superpowers(159k+ ⭐),核心 skill 全部中文母语化,不是机翻,是逐条校准。' },
|
||||
{ icon: '🔓', t: '零依赖 · MIT 开源', d: '纯 Markdown skill,不引入任何外部依赖、不联网、不上传代码,按需触发零运行时开销。' },
|
||||
],
|
||||
pipeline: [
|
||||
{ n: '头脑风暴', d: '动手前先理清意图与需求' },
|
||||
{ n: '写计划', d: '把需求拆成可执行步骤' },
|
||||
{ n: 'TDD', d: '先写测试再写实现' },
|
||||
{ n: '系统化调试', d: '先复现定位再改' },
|
||||
{ n: '代码审查', d: '合并前严谨验收' },
|
||||
{ n: '完成前验证', d: '用证据证明真的好了' },
|
||||
],
|
||||
usecases: [
|
||||
{ tag: '开发新功能', skills: 'brainstorming → writing-plans → TDD', desc: 'AI 先反问需求、写出实现计划,再用测试驱动落地,而不是上来就糊一坨代码。' },
|
||||
{ tag: '修 Bug', skills: 'systematic-debugging', desc: '强制先复现、定位根因,再提修复方案 —— 杜绝"猜一个改一下"的瞎试循环。' },
|
||||
{ tag: '提交 / 合并前', skills: 'verification-before-completion → code-review', desc: '必须跑验证命令、拿证据说话,再走一轮代码审查,才允许声称"完成"。' },
|
||||
{ tag: '国内团队协作', skills: 'chinese-commit-conventions → chinese-code-review', desc: '中文 commit 规范 + 分级 review 话术,配 Gitee / Coding / 极狐 GitLab 工作流。' },
|
||||
],
|
||||
faq: [
|
||||
{ q: 'superpowers-zh 是免费的吗?', a: '完全免费。MIT 协议开源,永久免费,不含任何付费墙或订阅。' },
|
||||
{ q: '支持哪些 AI 编程工具?', a: '共 18 款:Claude Code、Cursor、Windsurf、Codex CLI、Gemini CLI、Kiro、Trae、Qoder、Aider、OpenCode、Qwen Code、Antigravity、DeerFlow、VS Code(Copilot)、Copilot CLI、Hermes Agent、Claw Code、OpenClaw。' },
|
||||
{ q: '和上游 obra/superpowers 是什么关系?', a: '本项目是 obra/superpowers(159k+ ⭐)的中文增强 fork:完整汉化上游全部核心 skill,并叠加 4 个面向中国开发者的原创 skill。中文化与中国特色内容只在本 fork 提供。' },
|
||||
{ q: '安装后怎么生效?', a: 'npx 会把 skill 文件装到你项目对应工具的目录(如 .claude/skills/),重启 AI 工具后,它会在恰当时机自动触发相应 skill —— 无需你每次手动调用。' },
|
||||
{ q: '会拖慢我的 AI 吗?会上传代码吗?', a: '不会。skill 是按需触发的纯 Markdown,零运行时、不联网、不上传任何代码或数据,全程在本地。' },
|
||||
{ q: '怎么更新或卸载?', a: '更新:重新运行 npx superpowers-zh 覆盖即可。卸载:npx superpowers-zh --uninstall 清理已安装的 skill 与 bootstrap 文件。' },
|
||||
],
|
||||
},
|
||||
en: {
|
||||
htmlLang: 'en',
|
||||
title: 'superpowers-zh · Battle-tested AI coding skills (CN-enhanced)',
|
||||
desc: 'Full Chinese localization of superpowers (159k+ ⭐) plus 4 China-native skills. One npx command installs systematic workflow methodology into 18 AI coding tools.',
|
||||
nav: { why: 'Features', install: 'Install', skills: 'Skills', tools: 'Tools', faq: 'FAQ', github: 'GitHub ↗' },
|
||||
heroBadge: 'superpowers 159k+ ⭐ · Full CN localization + China-native skills',
|
||||
heroH1: 'Give your AI coding tools<br>superpowers that <span class="grad">actually ship</span>',
|
||||
heroLead: '{n} battle-tested workflow skills — from brainstorming to TDD, systematic debugging to code review.<br>One command auto-detects your tool and installs.',
|
||||
heroBtn1: 'Get the command', heroBtn2: 'GitHub',
|
||||
stats: ['Skills', 'China-native', 'Tools', 'Version'],
|
||||
whyTitle: 'Why superpowers-zh?',
|
||||
whySub: 'Not another prompt-template pack — real engineering methodology that makes AI work properly.',
|
||||
plTitle: 'An end-to-end workflow, every step guarded by a skill',
|
||||
plSub: 'Skills chain together; the AI triggers the right methodology at the right moment.',
|
||||
cmpTitle: 'After install, AI stops "coding before thinking"',
|
||||
cmpBad: '❌ Without', cmpGood: '✅ With superpowers-zh',
|
||||
cmpBadPre: 'You: Add bulk export to the users module\nAI: Sure, implementing… (starts coding)\n export async function exportUsers() { … }\nYou: Wait — wrong format, no paging,\n it OOMs on large data…',
|
||||
cmpGoodPre: 'You: Add bulk export to the users module\nAI: First, the requirements — what format?\n What data volume? Paging/streaming?\n Permission checks? (triggers brainstorming)\n → plan → TDD → verify → review',
|
||||
instTitle: 'Pick your tool, get the command',
|
||||
instSub: 'For most tools <code>npx superpowers-zh</code> auto-detects the project and installs; otherwise pass <code>--tool</code>.',
|
||||
instLabel: "I'm using",
|
||||
instNoteAuto: 'Run it in your project root — it <b>auto-detects {name}</b> and installs. Restart the tool to take effect.',
|
||||
instNoteManual: '{name} can\'t be auto-detected; pass <code>--tool</code> explicitly. Run in the project root, then restart the tool.',
|
||||
skTitle: '{n} skills, covering the whole dev workflow',
|
||||
skSub: 'Click any card for the full operating doc.',
|
||||
skSearch: 'Search skills… (e.g. debug / review / TDD)',
|
||||
skEmpty: 'No matching skills.',
|
||||
skDetail: 'Read docs →',
|
||||
tagCn: 'China-native',
|
||||
ucTitle: 'Typical use cases', ucSub: 'Each scenario is backed by a set of cooperating skills.',
|
||||
toolsTitle: 'One skill set, 18 tools', toolsSub: 'Switch tools without switching habits — the methodology follows you.',
|
||||
faqTitle: 'FAQ',
|
||||
bookTitle: 'Pair it with the methodology for 2× efficiency',
|
||||
bookDesc: '"AI Coding in Practice · The Three-Volume Methodology" — full tutorials for 10 AI coding tools plus real-world pitfalls. Online book + PDF, free forever.',
|
||||
bookBtn: 'Read free ↗',
|
||||
sponsorTitle: 'Sponsors',
|
||||
sponsorDesc: 'A fast, reliable API relay for Claude Code, Codex and more — API relay and AI image generation.',
|
||||
sponsorCta: '🙏 Want to appear here? Contact <b>jnMetaCode@qq.com</b>',
|
||||
ctaTitle: 'Ready to make your AI actually ship?',
|
||||
ctaDesc: 'One command installs {n} battle-tested skills into your tool. Free, open-source, zero-dependency.',
|
||||
ctaBtn1: 'Get the command', ctaBtn2: '⭐ Star on GitHub',
|
||||
footCols: [
|
||||
{ h: 'Product', links: [['Features', '#why'], ['Skills', '#skills'], ['Tools', '#tools'], ['FAQ', '#faq']] },
|
||||
{ h: 'Resources', links: [['GitHub', 'https://github.com/jnMetaCode/superpowers-zh'], ['npm', 'https://www.npmjs.com/package/superpowers-zh'], ['Upstream obra/superpowers', 'https://github.com/obra/superpowers'], ['Methodology book', 'https://book.aibuzhiyu.com/']] },
|
||||
{ h: 'Community', links: [['Open an Issue', 'https://github.com/jnMetaCode/superpowers-zh/issues'], ['Contributing', 'https://github.com/jnMetaCode/superpowers-zh/blob/main/CLAUDE.md']] },
|
||||
],
|
||||
footTag: 'AI coding superpowers · Chinese-enhanced · MIT License',
|
||||
copyright: '© 2026 superpowers-zh · A Chinese-enhanced fork of <a href="https://github.com/obra/superpowers" target="_blank" rel="noopener">obra/superpowers</a>',
|
||||
copy: 'Copy', copied: 'Copied ✓',
|
||||
backToSkills: '← Back to all skills',
|
||||
detailInstall: 'Install this skill set',
|
||||
detailSource: 'View source on GitHub ↗',
|
||||
features: [
|
||||
{ icon: '🧠', t: '20 battle-tested methods', d: 'Not prompt templates — workflow methodology hardened by cross-session adversarial testing, from brainstorming to TDD, debugging and review.' },
|
||||
{ icon: '🔌', t: 'Works in 18 tools', d: 'One skill set for Claude Code / Cursor / Codex / Gemini CLI / Windsurf and more. Switch tools, keep your habits.' },
|
||||
{ icon: '⚡', t: 'One-command install', d: 'npx superpowers-zh auto-detects your tool and installs. Zero config; restart to take effect.' },
|
||||
{ icon: '🇨🇳', t: 'China-native skills', d: 'Chinese code-review phrasing, commit conventions, doc typography, and domestic Git platforms (Gitee/Coding/JiHu) — not in upstream.' },
|
||||
{ icon: '📖', t: 'Fully localized upstream', d: 'Tracks obra/superpowers (159k+ ⭐); every core skill localized into native Chinese — calibrated, not machine-translated.' },
|
||||
{ icon: '🔓', t: 'Zero-dep · MIT', d: 'Pure Markdown skills. No external deps, no network, no code upload. Triggered on demand with zero runtime cost.' },
|
||||
],
|
||||
pipeline: [
|
||||
{ n: 'Brainstorm', d: 'Clarify intent before coding' },
|
||||
{ n: 'Plan', d: 'Break work into steps' },
|
||||
{ n: 'TDD', d: 'Test first, then implement' },
|
||||
{ n: 'Debug', d: 'Reproduce & locate first' },
|
||||
{ n: 'Review', d: 'Rigorous pre-merge check' },
|
||||
{ n: 'Verify', d: 'Prove it with evidence' },
|
||||
],
|
||||
usecases: [
|
||||
{ tag: 'New feature', skills: 'brainstorming → writing-plans → TDD', desc: 'AI questions the requirements, writes a plan, then builds test-first — instead of dumping code immediately.' },
|
||||
{ tag: 'Fixing a bug', skills: 'systematic-debugging', desc: 'Forces reproduce-and-locate-root-cause before any fix — no more "guess and tweak" loops.' },
|
||||
{ tag: 'Before merge', skills: 'verification-before-completion → code-review', desc: 'Must run verification with evidence, then a review pass, before claiming "done".' },
|
||||
{ tag: 'CN team workflow', skills: 'chinese-commit-conventions → chinese-code-review', desc: 'Chinese commit conventions + tiered review phrasing, wired for Gitee / Coding / JiHu GitLab.' },
|
||||
],
|
||||
faq: [
|
||||
{ q: 'Is superpowers-zh free?', a: 'Completely free. MIT-licensed open source, forever, with no paywall or subscription.' },
|
||||
{ q: 'Which AI coding tools are supported?', a: '18 tools: Claude Code, Cursor, Windsurf, Codex CLI, Gemini CLI, Kiro, Trae, Qoder, Aider, OpenCode, Qwen Code, Antigravity, DeerFlow, VS Code (Copilot), Copilot CLI, Hermes Agent, Claw Code, OpenClaw.' },
|
||||
{ q: 'How does it relate to obra/superpowers?', a: 'This is a Chinese-enhanced fork of obra/superpowers (159k+ ⭐): a full localization of all core skills plus 4 China-native skills. The localization and China-specific content live only in this fork.' },
|
||||
{ q: 'How does it take effect after install?', a: 'npx installs skill files into your tool\'s directory (e.g. .claude/skills/). After restarting your AI tool, it auto-triggers the right skill at the right moment — no manual invocation needed.' },
|
||||
{ q: 'Will it slow my AI down or upload my code?', a: 'No. Skills are on-demand Markdown: zero runtime, no network, no code or data upload — everything stays local.' },
|
||||
{ q: 'How do I update or uninstall?', a: 'Update: re-run npx superpowers-zh to overwrite. Uninstall: npx superpowers-zh --uninstall removes installed skills and bootstrap files.' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// ---- 读取 skills(含正文) ----
|
||||
function loadSkills() {
|
||||
const skills = [];
|
||||
for (const entry of readdirSync(SKILLS_DIR, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const file = join(SKILLS_DIR, entry.name, 'SKILL.md');
|
||||
if (!existsSync(file)) continue;
|
||||
const raw = readFileSync(file, 'utf8');
|
||||
const fm = parseFrontmatter(raw);
|
||||
const meta = SKILL_META[entry.name] || { title: entry.name, titleEn: entry.name, descEn: '', group: 'flow' };
|
||||
skills.push({
|
||||
name: fm.name || entry.name,
|
||||
title: meta.title, titleEn: meta.titleEn,
|
||||
group: meta.group,
|
||||
desc: (fm.description || '').trim(),
|
||||
descEn: meta.descEn || '',
|
||||
china: meta.group === 'china',
|
||||
raw,
|
||||
});
|
||||
}
|
||||
const order = GROUPS.map(g => g.id);
|
||||
skills.sort((a, b) => {
|
||||
if (a.name === 'using-superpowers') return -1;
|
||||
if (b.name === 'using-superpowers') return 1;
|
||||
return order.indexOf(a.group) - order.indexOf(b.group);
|
||||
});
|
||||
return skills;
|
||||
}
|
||||
|
||||
// ---- 公共布局 ----
|
||||
// base: 资源相对前缀('' / '../' / '../../');langHref: 切换语言的目标 URL
|
||||
function layout({ lang, base, title, desc, body, langHref, extraHead = '' }) {
|
||||
const t = T[lang];
|
||||
const other = lang === 'zh' ? 'EN' : '中文';
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="${t.htmlLang}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${esc(title)}</title>
|
||||
<meta name="description" content="${esc(desc)}">
|
||||
<meta property="og:title" content="${esc(title)}">
|
||||
<meta property="og:description" content="${esc(desc)}">
|
||||
<meta property="og:type" content="website">
|
||||
<link rel="icon" href="${base}assets/app-icon.png">
|
||||
<link rel="stylesheet" href="${base}styles.css">
|
||||
<script>(function(){try{var m=localStorage.getItem('sp-theme');if(m==='light')document.documentElement.setAttribute('data-theme','light');}catch(e){}})();</script>
|
||||
${extraHead}</head>
|
||||
<body>
|
||||
<header class="nav">
|
||||
<a class="brand" href="${base}index.html">
|
||||
<img src="${base}assets/superpowers-small.svg" alt="" width="26" height="26">
|
||||
<span>superpowers<b>-zh</b></span>
|
||||
</a>
|
||||
<nav>
|
||||
<a href="${base}index.html#why">${t.nav.why}</a>
|
||||
<a href="${base}index.html#install">${t.nav.install}</a>
|
||||
<a href="${base}index.html#skills">${t.nav.skills}</a>
|
||||
<a href="${base}index.html#faq">${t.nav.faq}</a>
|
||||
<a href="https://github.com/jnMetaCode/superpowers-zh" target="_blank" rel="noopener">${t.nav.github}</a>
|
||||
<a class="lang-switch" href="${langHref}">${other}</a>
|
||||
<button class="theme-btn" id="themeBtn" aria-label="theme" title="切换主题">◐</button>
|
||||
</nav>
|
||||
</header>
|
||||
${body}
|
||||
<footer>
|
||||
<div class="foot-inner foot-cols">
|
||||
<div class="foot-brand">
|
||||
<strong>superpowers<b>-zh</b></strong>
|
||||
<span>${t.footTag}</span>
|
||||
</div>
|
||||
${t.footCols.map(col => `<div class="foot-col"><h4>${col.h}</h4>${col.links.map(l => `<a href="${l[1]}"${l[1].startsWith('http') ? ' target="_blank" rel="noopener"' : ''}>${l[0]}</a>`).join('')}</div>`).join('')}
|
||||
</div>
|
||||
<p class="copyright">${t.copyright}</p>
|
||||
</footer>
|
||||
<script src="${base}app.js"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
// ---- 首页正文 ----
|
||||
function renderLanding(skills, lang) {
|
||||
const t = T[lang];
|
||||
const total = skills.length;
|
||||
const cnCount = skills.filter(s => s.china).length;
|
||||
const toolData = JSON.stringify(TOOLS.map(x => ({ name: x.name, cmd: x.cmd, auto: x.auto })));
|
||||
const fill = (s, map) => s.replace(/\{(\w+)\}/g, (_, k) => map[k]);
|
||||
|
||||
const cards = skills.map(s => {
|
||||
const title = lang === 'zh' ? s.title : s.titleEn;
|
||||
const d = lang === 'zh' ? s.desc : (s.descEn || s.desc);
|
||||
return `
|
||||
<a class="card" href="skills/${esc(s.name)}.html" data-group="${s.group}" data-name="${esc(s.name)}" data-title="${esc(title)}">
|
||||
<div class="card-head"><h3>${esc(title)}</h3>${s.china ? `<span class="tag tag-cn">${t.tagCn}</span>` : ''}</div>
|
||||
<code class="card-id">${esc(s.name)}</code>
|
||||
<p>${esc(d)}</p>
|
||||
<span class="card-more">${t.skDetail}</span>
|
||||
</a>`;
|
||||
}).join('');
|
||||
|
||||
const filters = GROUPS.map((g, i) =>
|
||||
`<button class="chip${i === 0 ? ' active' : ''}" data-filter="${g.id}">${lang === 'zh' ? g.zh : g.en}</button>`).join('');
|
||||
const toolOpts = TOOLS.map((x, i) => `<option value="${i}">${esc(x.name)}${x.auto ? '' : '(--tool)'}</option>`).join('');
|
||||
const toolWall = TOOLS.map(x => `<div class="tool-pill"><span class="tool-name">${esc(x.name)}</span><span class="tool-type">${esc(x.type)}</span></div>`).join('');
|
||||
const feats = t.features.map(f => `<article class="feat"><div class="feat-icon">${f.icon}</div><h3>${esc(f.t)}</h3><p>${esc(f.d)}</p></article>`).join('');
|
||||
const pipe = t.pipeline.map((p, i) => `<div class="pl-step"><div class="pl-num">${i + 1}</div><div class="pl-body"><b>${esc(p.n)}</b><span>${esc(p.d)}</span></div></div>${i < t.pipeline.length - 1 ? '<div class="pl-arrow">→</div>' : ''}`).join('');
|
||||
const ucs = t.usecases.map(u => `<article class="usecase"><span class="uc-tag">${esc(u.tag)}</span><code class="uc-skills">${esc(u.skills)}</code><p>${esc(u.desc)}</p></article>`).join('');
|
||||
const faqs = t.faq.map(f => `<details class="faq-item"><summary>${esc(f.q)}</summary><div class="faq-a">${esc(f.a)}</div></details>`).join('');
|
||||
|
||||
return `
|
||||
<main id="top">
|
||||
<section class="hero">
|
||||
<div class="badge">${t.heroBadge}</div>
|
||||
<h1>${t.heroH1}</h1>
|
||||
<p class="lead">${fill(t.heroLead, { n: total })}</p>
|
||||
<div class="cmd-hero" data-copy="npx superpowers-zh"><code>$ npx superpowers-zh</code><button class="copy-btn">${t.copy}</button></div>
|
||||
<div class="hero-cta">
|
||||
<a class="btn btn-primary" href="#install">${t.heroBtn1}</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/jnMetaCode/superpowers-zh" target="_blank" rel="noopener">${t.heroBtn2}</a>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div><b>${total}</b><span>${t.stats[0]}</span></div>
|
||||
<div><b>${cnCount}</b><span>${t.stats[1]}</span></div>
|
||||
<div><b>18</b><span>${t.stats[2]}</span></div>
|
||||
<div><b>v${PKG.version}</b><span>${t.stats[3]}</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="why" class="why">
|
||||
<h2 class="section-title">${t.whyTitle}</h2>
|
||||
<p class="section-sub">${t.whySub}</p>
|
||||
<div class="feat-grid">${feats}</div>
|
||||
</section>
|
||||
|
||||
<section class="pipeline">
|
||||
<h2 class="section-title">${t.plTitle}</h2>
|
||||
<p class="section-sub">${t.plSub}</p>
|
||||
<div class="pl-track">${pipe}</div>
|
||||
</section>
|
||||
|
||||
<section class="compare">
|
||||
<h2 class="section-title">${t.cmpTitle}</h2>
|
||||
<div class="compare-grid">
|
||||
<div class="compare-col bad"><div class="compare-label">${t.cmpBad}</div><pre>${esc(t.cmpBadPre)}</pre></div>
|
||||
<div class="compare-col good"><div class="compare-label">${t.cmpGood}</div><pre>${esc(t.cmpGoodPre)}</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="install" class="install">
|
||||
<h2 class="section-title">${t.instTitle}</h2>
|
||||
<p class="section-sub">${t.instSub}</p>
|
||||
<div class="install-box">
|
||||
<label for="toolSel">${t.instLabel}</label>
|
||||
<select id="toolSel">${toolOpts}</select>
|
||||
<div class="cmd-out" data-copy="npx superpowers-zh"><code id="cmdText">npx superpowers-zh</code><button class="copy-btn">${t.copy}</button></div>
|
||||
<p class="install-note" id="installNote"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="skills" class="skills">
|
||||
<h2 class="section-title">${fill(t.skTitle, { n: total })}</h2>
|
||||
<p class="section-sub">${t.skSub}</p>
|
||||
<div class="skill-controls">
|
||||
<input id="search" type="search" placeholder="${esc(t.skSearch)}" autocomplete="off">
|
||||
<div class="chips">${filters}</div>
|
||||
</div>
|
||||
<div class="grid" id="grid">${cards}</div>
|
||||
<p class="empty" id="empty" hidden>${t.skEmpty}</p>
|
||||
</section>
|
||||
|
||||
<section class="usecases">
|
||||
<h2 class="section-title">${t.ucTitle}</h2>
|
||||
<p class="section-sub">${t.ucSub}</p>
|
||||
<div class="uc-grid">${ucs}</div>
|
||||
</section>
|
||||
|
||||
<section id="tools" class="tools">
|
||||
<h2 class="section-title">${t.toolsTitle}</h2>
|
||||
<p class="section-sub">${t.toolsSub}</p>
|
||||
<div class="tool-wall">${toolWall}</div>
|
||||
</section>
|
||||
|
||||
<section id="faq" class="faq">
|
||||
<h2 class="section-title">${t.faqTitle}</h2>
|
||||
<div class="faq-list">${faqs}</div>
|
||||
</section>
|
||||
|
||||
<section class="book"><div class="book-inner"><div>
|
||||
<h2>${t.bookTitle}</h2><p>${t.bookDesc}</p>
|
||||
<a class="btn btn-primary" href="https://book.aibuzhiyu.com/" target="_blank" rel="noopener">${t.bookBtn}</a>
|
||||
</div></div></section>
|
||||
|
||||
<section class="sponsor">
|
||||
<h2 class="section-title">${t.sponsorTitle}</h2>
|
||||
<a class="sponsor-card" href="https://www.5cookie.cc/sign-up?aff=Pj7u" target="_blank" rel="noopener">
|
||||
<img src="assets/sponsors/5cookie-code.png" alt="5Cookie Code" width="160">
|
||||
<div><b>5Cookie Code</b><span>${t.sponsorDesc}</span></div>
|
||||
</a>
|
||||
<p class="sponsor-cta">${t.sponsorCta}</p>
|
||||
</section>
|
||||
|
||||
<section class="cta"><div class="cta-inner">
|
||||
<h2>${t.ctaTitle}</h2><p>${fill(t.ctaDesc, { n: total })}</p>
|
||||
<div class="cta-cmd" data-copy="npx superpowers-zh"><code>$ npx superpowers-zh</code><button class="copy-btn">${t.copy}</button></div>
|
||||
<div class="hero-cta">
|
||||
<a class="btn btn-primary" href="#install">${t.ctaBtn1}</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/jnMetaCode/superpowers-zh" target="_blank" rel="noopener">${t.ctaBtn2}</a>
|
||||
</div>
|
||||
</div></section>
|
||||
</main>
|
||||
<script>window.__TOOLS__=${toolData};window.__I18N__={auto:${JSON.stringify(t.instNoteAuto)},manual:${JSON.stringify(t.instNoteManual)},copy:${JSON.stringify(t.copy)},copied:${JSON.stringify(t.copied)}};</script>`;
|
||||
}
|
||||
|
||||
// ---- skill 详情(操作文档)页正文 ----
|
||||
function renderDetail(skill, lang) {
|
||||
const t = T[lang];
|
||||
const title = lang === 'zh' ? skill.title : skill.titleEn;
|
||||
const bodyHtml = renderMarkdown(skill.raw);
|
||||
const cnNotice = lang === 'en'
|
||||
? '<div class="doc-notice">📖 This skill\'s content is written in Chinese — superpowers-zh is a Chinese-localized toolkit.</div>'
|
||||
: '';
|
||||
const srcUrl = `https://github.com/jnMetaCode/superpowers-zh/blob/main/skills/${skill.name}/SKILL.md`;
|
||||
return `
|
||||
<main class="doc">
|
||||
<a class="doc-back" href="index.html#skills">${t.backToSkills}</a>
|
||||
<header class="doc-head">
|
||||
<div class="doc-titles">
|
||||
<h1>${esc(title)}</h1>
|
||||
<code>${esc(skill.name)}</code>
|
||||
${skill.china ? `<span class="tag tag-cn">${t.tagCn}</span>` : ''}
|
||||
</div>
|
||||
<p class="doc-lead">${esc(lang === 'zh' ? skill.desc : (skill.descEn || skill.desc))}</p>
|
||||
<div class="doc-actions">
|
||||
<div class="cmd-out doc-cmd" data-copy="npx superpowers-zh"><code>$ npx superpowers-zh</code><button class="copy-btn">${t.copy}</button></div>
|
||||
<a class="btn btn-ghost" href="${srcUrl}" target="_blank" rel="noopener">${t.detailSource}</a>
|
||||
</div>
|
||||
</header>
|
||||
${cnNotice}
|
||||
<article class="doc-body">${bodyHtml}</article>
|
||||
<a class="doc-back" href="index.html#skills">${t.backToSkills}</a>
|
||||
</main>
|
||||
<script>window.__I18N__={copy:${JSON.stringify(t.copy)},copied:${JSON.stringify(t.copied)}};</script>`;
|
||||
}
|
||||
|
||||
// ---- 构建 ----
|
||||
function build() {
|
||||
const skills = loadSkills();
|
||||
rmSync(DIST, { recursive: true, force: true });
|
||||
mkdirSync(join(DIST, 'assets', 'sponsors'), { recursive: true });
|
||||
mkdirSync(join(DIST, 'skills'), { recursive: true });
|
||||
mkdirSync(join(DIST, 'en', 'skills'), { recursive: true });
|
||||
|
||||
// 资源
|
||||
copyFileSync(join(TEMPLATE, 'styles.css'), join(DIST, 'styles.css'));
|
||||
copyFileSync(join(TEMPLATE, 'app.js'), join(DIST, 'app.js'));
|
||||
copyFileSync(join(ROOT, 'assets', 'app-icon.png'), join(DIST, 'assets', 'app-icon.png'));
|
||||
copyFileSync(join(ROOT, 'assets', 'superpowers-small.svg'), join(DIST, 'assets', 'superpowers-small.svg'));
|
||||
copyFileSync(join(ROOT, 'assets', 'sponsors', '5cookie-code.png'), join(DIST, 'assets', 'sponsors', '5cookie-code.png'));
|
||||
|
||||
// 中文站(根)
|
||||
writeFileSync(join(DIST, 'index.html'), layout({
|
||||
lang: 'zh', base: '', title: T.zh.title, desc: T.zh.desc,
|
||||
body: renderLanding(skills, 'zh'), langHref: 'en/index.html',
|
||||
}));
|
||||
// 英文站(/en/)
|
||||
writeFileSync(join(DIST, 'en', 'index.html'), layout({
|
||||
lang: 'en', base: '../', title: T.en.title, desc: T.en.desc,
|
||||
body: renderLanding(skills, 'en'), langHref: '../index.html',
|
||||
}));
|
||||
|
||||
// 详情(操作文档)页 ×2 语言
|
||||
for (const s of skills) {
|
||||
writeFileSync(join(DIST, 'skills', `${s.name}.html`), layout({
|
||||
lang: 'zh', base: '../', title: `${s.title} · superpowers-zh`, desc: s.desc,
|
||||
body: renderDetail(s, 'zh'), langHref: `../en/skills/${s.name}.html`,
|
||||
}));
|
||||
writeFileSync(join(DIST, 'en', 'skills', `${s.name}.html`), layout({
|
||||
lang: 'en', base: '../../', title: `${s.titleEn} · superpowers-zh`, desc: s.descEn || s.desc,
|
||||
body: renderDetail(s, 'en'), langHref: `../../skills/${s.name}.html`,
|
||||
}));
|
||||
}
|
||||
|
||||
// 收集所有生成页面里的内联 <script> 内容,算 SHA-256 作为 CSP hash 白名单。
|
||||
// 本站脚本由本生成器产出(可信),用 hash 即可严格禁用 'unsafe-inline'/'unsafe-eval'
|
||||
// 而不误伤自有内联脚本——注入的外来脚本则被 CSP 拦截。
|
||||
const scriptHashes = new Set();
|
||||
const collectScriptHashes = (dir) => {
|
||||
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, ent.name);
|
||||
if (ent.isDirectory()) { collectScriptHashes(p); continue; }
|
||||
if (!ent.name.endsWith('.html')) continue;
|
||||
const html = readFileSync(p, 'utf8');
|
||||
for (const m of html.matchAll(/<script>([\s\S]*?)<\/script>/g)) {
|
||||
scriptHashes.add("'sha256-" + createHash('sha256').update(m[1], 'utf8').digest('base64') + "'");
|
||||
}
|
||||
}
|
||||
};
|
||||
collectScriptHashes(DIST);
|
||||
|
||||
// 严格 CSP:脚本仅允许 'self' + 本站内联脚本的 hash;样式仅 'self'(无内联样式);
|
||||
// 禁用插件/内联事件;锁死 base-uri 与 frame 祖先(防点击劫持)。外链均为 <a> 导航,不受影响。
|
||||
const csp = [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' " + [...scriptHashes].join(' '),
|
||||
"style-src 'self'",
|
||||
"img-src 'self' data:",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self'",
|
||||
"object-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"form-action 'self'",
|
||||
].join('; ');
|
||||
|
||||
// Cloudflare Pages:全站安全响应头(/*)+ 缓存策略(静态资源长缓存,HTML 不缓存)
|
||||
writeFileSync(join(DIST, '_headers'),
|
||||
'/*\n' +
|
||||
' Content-Security-Policy: ' + csp + '\n' +
|
||||
' X-Content-Type-Options: nosniff\n' +
|
||||
' X-Frame-Options: DENY\n' +
|
||||
' Referrer-Policy: no-referrer\n' +
|
||||
' Cross-Origin-Opener-Policy: same-origin\n' +
|
||||
' Permissions-Policy: geolocation=(), microphone=(), camera=()\n' +
|
||||
'/assets/*\n Cache-Control: public, max-age=31536000, immutable\n' +
|
||||
'/styles.css\n Cache-Control: public, max-age=86400\n' +
|
||||
'/app.js\n Cache-Control: public, max-age=86400\n' +
|
||||
'/*.html\n Cache-Control: public, max-age=0, must-revalidate\n');
|
||||
|
||||
const pages = 2 + skills.length * 2;
|
||||
console.log(`✅ 生成 ${pages} 个页面:中/英首页 + ${skills.length} 个 skill × 2 语言详情页 → ${DIST}`);
|
||||
}
|
||||
|
||||
build();
|
||||
BIN
site/md.mjs
Normal file
BIN
site/md.mjs
Normal file
Binary file not shown.
92
site/template/app.js
Normal file
92
site/template/app.js
Normal file
@@ -0,0 +1,92 @@
|
||||
// superpowers-zh 官网交互 —— 零依赖原生 JS
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var TOOLS = window.__TOOLS__ || [];
|
||||
var I18N = window.__I18N__ || { copy: '复制', copied: '已复制 ✓' };
|
||||
|
||||
// ---------- 主题切换(深 / 浅) ----------
|
||||
var themeBtn = document.getElementById('themeBtn');
|
||||
if (themeBtn) {
|
||||
themeBtn.addEventListener('click', function () {
|
||||
var light = document.documentElement.getAttribute('data-theme') === 'light';
|
||||
if (light) {
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
try { localStorage.setItem('sp-theme', 'dark'); } catch (e) {}
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
try { localStorage.setItem('sp-theme', 'light'); } catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 复制按钮(事件委托) ----------
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.copy-btn');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
var box = btn.closest('[data-copy]');
|
||||
if (!box) return;
|
||||
navigator.clipboard.writeText(box.getAttribute('data-copy')).then(function () {
|
||||
var old = btn.textContent;
|
||||
btn.textContent = I18N.copied;
|
||||
btn.classList.add('done');
|
||||
setTimeout(function () { btn.textContent = old; btn.classList.remove('done'); }, 1600);
|
||||
}).catch(function () { btn.textContent = '×'; });
|
||||
});
|
||||
|
||||
// ---------- 安装命令生成器 ----------
|
||||
var sel = document.getElementById('toolSel');
|
||||
var cmdText = document.getElementById('cmdText');
|
||||
var cmdBox = cmdText ? cmdText.closest('[data-copy]') : null;
|
||||
var note = document.getElementById('installNote');
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function updateCmd() {
|
||||
var t = TOOLS[sel.value];
|
||||
if (!t) return;
|
||||
cmdText.textContent = t.cmd;
|
||||
cmdBox.setAttribute('data-copy', t.cmd);
|
||||
var tpl = t.auto ? I18N.auto : I18N.manual;
|
||||
if (note && tpl) note.innerHTML = tpl.replace('{name}', escapeHtml(t.name));
|
||||
}
|
||||
if (sel && note) {
|
||||
sel.addEventListener('change', updateCmd);
|
||||
updateCmd();
|
||||
}
|
||||
|
||||
// ---------- Skill 搜索 + 筛选 ----------
|
||||
var grid = document.getElementById('grid');
|
||||
var search = document.getElementById('search');
|
||||
var empty = document.getElementById('empty');
|
||||
var chips = Array.prototype.slice.call(document.querySelectorAll('.chip'));
|
||||
var cards = grid ? Array.prototype.slice.call(grid.querySelectorAll('.card')) : [];
|
||||
var activeFilter = 'all';
|
||||
|
||||
function applyFilter() {
|
||||
var q = (search ? search.value : '').trim().toLowerCase();
|
||||
var visible = 0;
|
||||
cards.forEach(function (card) {
|
||||
var group = card.getAttribute('data-group');
|
||||
var hay = (card.getAttribute('data-name') + ' ' + card.getAttribute('data-title') + ' ' + card.textContent).toLowerCase();
|
||||
var show = (activeFilter === 'all' || group === activeFilter) && (!q || hay.indexOf(q) !== -1);
|
||||
card.style.display = show ? '' : 'none';
|
||||
if (show) visible++;
|
||||
});
|
||||
if (empty) empty.hidden = visible !== 0;
|
||||
}
|
||||
|
||||
chips.forEach(function (chip) {
|
||||
chip.addEventListener('click', function () {
|
||||
chips.forEach(function (c) { c.classList.remove('active'); });
|
||||
chip.classList.add('active');
|
||||
activeFilter = chip.getAttribute('data-filter');
|
||||
applyFilter();
|
||||
});
|
||||
});
|
||||
if (search) search.addEventListener('input', applyFilter);
|
||||
})();
|
||||
448
site/template/styles.css
Normal file
448
site/template/styles.css
Normal file
@@ -0,0 +1,448 @@
|
||||
/* superpowers-zh 官网样式 —— 深色开发者风格 */
|
||||
:root {
|
||||
--bg: #0a0b10;
|
||||
--bg-soft: #12141c;
|
||||
--bg-card: #161924;
|
||||
--border: #242838;
|
||||
--border-soft: #1c2030;
|
||||
--text: #e7e9f0;
|
||||
--text-dim: #9aa0b4;
|
||||
--text-faint: #6b7186;
|
||||
--accent: #7c6cff;
|
||||
--accent-2: #4dd6c8;
|
||||
--accent-grad: linear-gradient(110deg, #7c6cff 0%, #4dd6c8 100%);
|
||||
--good: #3ddc84;
|
||||
--bad: #ff6b6b;
|
||||
--radius: 14px;
|
||||
--maxw: 1080px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||
"Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans SC", sans-serif;
|
||||
line-height: 1.7;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code, pre, .cmd-hero code, .cmd-out code {
|
||||
font-family: "SF Mono", "JetBrains Mono", Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
|
||||
/* 背景光晕 */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: -30%; left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 900px; height: 900px;
|
||||
background: radial-gradient(circle, rgba(124,108,255,0.18), transparent 60%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* ---------- 导航 ---------- */
|
||||
.nav {
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
max-width: var(--maxw); margin: 0 auto;
|
||||
padding: 16px 24px;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
.nav::after {
|
||||
content: ""; position: absolute; inset: 0;
|
||||
background: rgba(10,11,16,0.6);
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
z-index: -1;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 9px; font-size: 18px; font-weight: 600; }
|
||||
.brand b { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
||||
.nav nav { display: flex; gap: 24px; font-size: 14px; }
|
||||
.nav nav a { color: var(--text-dim); transition: color .2s; }
|
||||
.nav nav a:hover { color: var(--text); }
|
||||
|
||||
main { position: relative; z-index: 1; }
|
||||
section { max-width: var(--maxw); margin: 0 auto; padding: 80px 24px; }
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(24px, 4vw, 34px);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.section-sub { text-align: center; color: var(--text-dim); margin-bottom: 36px; font-size: 15px; }
|
||||
.section-sub code { background: var(--bg-soft); padding: 2px 7px; border-radius: 6px; color: var(--accent-2); font-size: 13px; }
|
||||
|
||||
/* ---------- HERO ---------- */
|
||||
.hero { text-align: center; padding-top: 70px; padding-bottom: 60px; }
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 6px 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 100px;
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
background: var(--bg-soft);
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: clamp(32px, 6vw, 56px);
|
||||
font-weight: 800;
|
||||
line-height: 1.18;
|
||||
letter-spacing: -0.03em;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.grad { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
||||
.lead { color: var(--text-dim); font-size: clamp(15px, 2.2vw, 18px); max-width: 640px; margin: 0 auto 36px; }
|
||||
|
||||
.cmd-hero, .cmd-out {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||||
max-width: 460px; margin: 0 auto;
|
||||
background: #06070b;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 14px 14px 14px 20px;
|
||||
}
|
||||
.cmd-hero code { font-size: 16px; color: var(--accent-2); }
|
||||
.copy-btn {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-card);
|
||||
color: var(--text-dim);
|
||||
padding: 7px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all .2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.copy-btn:hover { color: var(--text); border-color: var(--accent); }
|
||||
.copy-btn.done { color: var(--good); border-color: var(--good); }
|
||||
|
||||
.hero-cta { display: flex; gap: 14px; justify-content: center; margin: 32px 0 50px; flex-wrap: wrap; }
|
||||
.btn {
|
||||
padding: 12px 26px; border-radius: 10px; font-size: 15px; font-weight: 600;
|
||||
cursor: pointer; transition: all .2s; border: 1px solid transparent; display: inline-block;
|
||||
}
|
||||
.btn-primary { background: var(--accent-grad); color: #0a0b10; }
|
||||
.btn-primary:hover { filter: brightness(1.1); transform: translateY(-1px); }
|
||||
.btn-ghost { border-color: var(--border); color: var(--text); background: var(--bg-soft); }
|
||||
.btn-ghost:hover { border-color: var(--accent); }
|
||||
|
||||
.stats {
|
||||
display: flex; gap: 40px; justify-content: center; flex-wrap: wrap;
|
||||
padding-top: 20px;
|
||||
}
|
||||
.stats div { display: flex; flex-direction: column; align-items: center; }
|
||||
.stats b { font-size: 30px; font-weight: 800; background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
||||
.stats span { font-size: 13px; color: var(--text-faint); margin-top: 2px; }
|
||||
|
||||
/* ---------- 效果对比 ---------- */
|
||||
.compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.compare-col { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--bg-card); }
|
||||
.compare-col.bad { border-color: rgba(255,107,107,0.3); }
|
||||
.compare-col.good { border-color: rgba(61,220,132,0.3); }
|
||||
.compare-label { padding: 12px 18px; font-weight: 600; font-size: 14px; border-bottom: 1px solid var(--border-soft); }
|
||||
.compare-col.bad .compare-label { color: var(--bad); background: rgba(255,107,107,0.06); }
|
||||
.compare-col.good .compare-label { color: var(--good); background: rgba(61,220,132,0.06); }
|
||||
.compare-col pre {
|
||||
padding: 18px; font-size: 13px; line-height: 1.75; color: var(--text-dim);
|
||||
white-space: pre-wrap; word-break: break-word;
|
||||
}
|
||||
|
||||
/* ---------- 安装命令生成器 ---------- */
|
||||
.install-box {
|
||||
max-width: 560px; margin: 0 auto;
|
||||
background: var(--bg-card); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 28px;
|
||||
}
|
||||
.install-box label { display: block; font-size: 14px; color: var(--text-dim); margin-bottom: 8px; }
|
||||
#toolSel {
|
||||
width: 100%; padding: 12px 14px; margin-bottom: 18px;
|
||||
background: #06070b; border: 1px solid var(--border); border-radius: 10px;
|
||||
color: var(--text); font-size: 15px; cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239aa0b4' d='M6 8L2 4h8z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat; background-position: right 14px center;
|
||||
}
|
||||
.cmd-out { max-width: 100%; }
|
||||
.cmd-out code { font-size: 15px; color: var(--accent-2); overflow-x: auto; }
|
||||
.install-note { margin-top: 14px; font-size: 13px; color: var(--text-faint); line-height: 1.6; }
|
||||
|
||||
/* ---------- Skills ---------- */
|
||||
.skill-controls { display: flex; flex-direction: column; gap: 16px; align-items: center; margin-bottom: 32px; }
|
||||
#search {
|
||||
width: 100%; max-width: 460px; padding: 12px 16px;
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px;
|
||||
color: var(--text); font-size: 15px;
|
||||
}
|
||||
#search:focus { outline: none; border-color: var(--accent); }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
|
||||
.chip {
|
||||
padding: 7px 15px; border-radius: 100px; font-size: 13px; cursor: pointer;
|
||||
background: var(--bg-soft); border: 1px solid var(--border); color: var(--text-dim);
|
||||
transition: all .2s;
|
||||
}
|
||||
.chip:hover { color: var(--text); }
|
||||
.chip.active { background: var(--accent); border-color: var(--accent); color: #0a0b10; font-weight: 600; }
|
||||
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 16px; }
|
||||
.card {
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: 22px; transition: all .2s; position: relative;
|
||||
}
|
||||
.card:hover { border-color: var(--accent); transform: translateY(-2px); }
|
||||
.card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
|
||||
.card-head h3 { font-size: 17px; font-weight: 700; }
|
||||
.tag { font-size: 11px; padding: 2px 8px; border-radius: 6px; font-weight: 600; }
|
||||
.tag-cn { background: rgba(255,107,107,0.12); color: #ff8a8a; border: 1px solid rgba(255,107,107,0.25); }
|
||||
.card-id { display: inline-block; font-size: 12px; color: var(--text-faint); margin-bottom: 12px; }
|
||||
.card p { font-size: 13.5px; color: var(--text-dim); line-height: 1.65; }
|
||||
.empty { text-align: center; color: var(--text-faint); padding: 40px; }
|
||||
|
||||
/* ---------- 工具墙 ---------- */
|
||||
.tool-wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
|
||||
.tool-pill {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 18px; background: var(--bg-card);
|
||||
border: 1px solid var(--border); border-radius: 10px;
|
||||
}
|
||||
.tool-name { font-weight: 600; font-size: 14px; }
|
||||
.tool-type { font-size: 11px; color: var(--text-faint); border: 1px solid var(--border); padding: 1px 7px; border-radius: 5px; }
|
||||
|
||||
/* ---------- 书 / 赞助 ---------- */
|
||||
.book-inner {
|
||||
background: linear-gradient(135deg, rgba(124,108,255,0.12), rgba(77,214,200,0.08));
|
||||
border: 1px solid var(--border); border-radius: 18px; padding: 44px;
|
||||
text-align: center;
|
||||
}
|
||||
.book-inner h2 { font-size: 26px; margin-bottom: 12px; }
|
||||
.book-inner p { color: var(--text-dim); margin-bottom: 24px; max-width: 560px; margin-left: auto; margin-right: auto; }
|
||||
|
||||
.sponsor-card {
|
||||
display: flex; align-items: center; gap: 24px; max-width: 620px; margin: 0 auto;
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: 24px; transition: border-color .2s;
|
||||
}
|
||||
.sponsor-card:hover { border-color: var(--accent); }
|
||||
.sponsor-card img { border-radius: 10px; flex-shrink: 0; }
|
||||
.sponsor-card b { display: block; font-size: 17px; margin-bottom: 6px; }
|
||||
.sponsor-card span { font-size: 13.5px; color: var(--text-dim); line-height: 1.6; }
|
||||
.sponsor-cta { text-align: center; margin-top: 24px; font-size: 14px; color: var(--text-faint); }
|
||||
.sponsor-cta b { color: var(--accent-2); }
|
||||
|
||||
/* ---------- 页脚 ---------- */
|
||||
footer { border-top: 1px solid var(--border-soft); margin-top: 40px; padding: 40px 24px; }
|
||||
.foot-inner {
|
||||
max-width: var(--maxw); margin: 0 auto;
|
||||
display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px;
|
||||
}
|
||||
.foot-inner strong { display: block; }
|
||||
.foot-inner span { font-size: 13px; color: var(--text-faint); }
|
||||
.foot-inner nav { display: flex; gap: 22px; font-size: 14px; }
|
||||
.foot-inner nav a { color: var(--text-dim); transition: color .2s; }
|
||||
.foot-inner nav a:hover { color: var(--accent-2); }
|
||||
.copyright { max-width: var(--maxw); margin: 24px auto 0; font-size: 12.5px; color: var(--text-faint); text-align: center; }
|
||||
.copyright a { color: var(--text-dim); }
|
||||
|
||||
/* ---------- 响应式 ---------- */
|
||||
@media (max-width: 720px) {
|
||||
section { padding: 56px 18px; }
|
||||
.nav nav { gap: 16px; font-size: 13px; }
|
||||
.nav nav a:nth-child(1), .nav nav a:nth-child(2) { display: none; }
|
||||
.compare-grid { grid-template-columns: 1fr; }
|
||||
.stats { gap: 28px; }
|
||||
.sponsor-card { flex-direction: column; text-align: center; }
|
||||
.foot-inner { flex-direction: column; align-items: flex-start; }
|
||||
}
|
||||
|
||||
/* ========== 扩充板块 ========== */
|
||||
|
||||
/* 为什么选择 — 卖点网格 */
|
||||
.feat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 18px; }
|
||||
.feat {
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: 28px; transition: all .2s;
|
||||
}
|
||||
.feat:hover { border-color: var(--accent); transform: translateY(-2px); }
|
||||
.feat-icon {
|
||||
width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 26px; border-radius: 12px; margin-bottom: 16px;
|
||||
background: linear-gradient(135deg, rgba(124,108,255,0.18), rgba(77,214,200,0.12));
|
||||
}
|
||||
.feat h3 { font-size: 18px; font-weight: 700; margin-bottom: 10px; }
|
||||
.feat p { font-size: 14px; color: var(--text-dim); line-height: 1.7; }
|
||||
|
||||
/* 工作流可视化 */
|
||||
.pl-track {
|
||||
display: flex; align-items: stretch; gap: 10px; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.pl-step {
|
||||
display: flex; gap: 12px; align-items: flex-start;
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px;
|
||||
padding: 16px 18px; flex: 1 1 220px; max-width: 250px; transition: border-color .2s;
|
||||
}
|
||||
.pl-step:hover { border-color: var(--accent); }
|
||||
.pl-num {
|
||||
flex-shrink: 0; width: 26px; height: 26px; border-radius: 8px;
|
||||
background: var(--accent-grad); color: #0a0b10; font-weight: 800; font-size: 14px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.pl-body { display: flex; flex-direction: column; gap: 3px; }
|
||||
.pl-body b { font-size: 15px; }
|
||||
.pl-body code { font-size: 11px; color: var(--text-faint); }
|
||||
.pl-body span { font-size: 12.5px; color: var(--text-dim); }
|
||||
.pl-arrow { display: flex; align-items: center; color: var(--accent); font-size: 20px; font-weight: 700; }
|
||||
|
||||
/* 使用场景 */
|
||||
.uc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
|
||||
.usecase {
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: 24px; transition: border-color .2s;
|
||||
}
|
||||
.usecase:hover { border-color: var(--accent); }
|
||||
.uc-tag {
|
||||
display: inline-block; font-size: 13px; font-weight: 700; padding: 4px 12px;
|
||||
border-radius: 8px; margin-bottom: 12px;
|
||||
background: linear-gradient(135deg, rgba(124,108,255,0.2), rgba(77,214,200,0.15)); color: var(--text);
|
||||
}
|
||||
.uc-skills { display: block; font-size: 12.5px; color: var(--accent-2); margin-bottom: 10px; }
|
||||
.usecase p { font-size: 14px; color: var(--text-dim); line-height: 1.7; }
|
||||
|
||||
/* FAQ */
|
||||
.faq-list { max-width: 760px; margin: 0 auto; display: flex; flex-direction: column; gap: 12px; }
|
||||
.faq-item {
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px;
|
||||
padding: 0 22px; transition: border-color .2s;
|
||||
}
|
||||
.faq-item[open] { border-color: var(--accent); }
|
||||
.faq-item summary {
|
||||
cursor: pointer; padding: 18px 0; font-size: 16px; font-weight: 600;
|
||||
list-style: none; display: flex; justify-content: space-between; align-items: center;
|
||||
}
|
||||
.faq-item summary::-webkit-details-marker { display: none; }
|
||||
.faq-item summary::after { content: "+"; color: var(--accent); font-size: 22px; font-weight: 400; transition: transform .2s; }
|
||||
.faq-item[open] summary::after { transform: rotate(45deg); }
|
||||
.faq-a { padding: 0 0 20px; font-size: 14.5px; color: var(--text-dim); line-height: 1.75; }
|
||||
|
||||
/* 收尾 CTA */
|
||||
.cta-inner {
|
||||
background: linear-gradient(135deg, rgba(124,108,255,0.16), rgba(77,214,200,0.10));
|
||||
border: 1px solid var(--border); border-radius: 20px; padding: 56px 32px; text-align: center;
|
||||
}
|
||||
.cta-inner h2 { font-size: clamp(24px, 4vw, 34px); font-weight: 800; margin-bottom: 12px; }
|
||||
.cta-inner > p { color: var(--text-dim); margin-bottom: 28px; font-size: 16px; }
|
||||
.cta-cmd {
|
||||
display: inline-flex; align-items: center; gap: 12px; margin: 0 auto 28px;
|
||||
background: #06070b; border: 1px solid var(--border); border-radius: 12px; padding: 12px 12px 12px 20px;
|
||||
}
|
||||
.cta-cmd code { font-size: 16px; color: var(--accent-2); }
|
||||
.cta .hero-cta { margin: 0; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.pl-arrow { display: none; }
|
||||
.pl-step { max-width: none; }
|
||||
}
|
||||
|
||||
/* ========== 浅色主题 ========== */
|
||||
html[data-theme="light"] {
|
||||
--bg: #f7f8fc;
|
||||
--bg-soft: #eef0f7;
|
||||
--bg-card: #ffffff;
|
||||
--border: #e2e5ee;
|
||||
--border-soft: #eaecf3;
|
||||
--text: #1a1d2b;
|
||||
--text-dim: #545a70;
|
||||
--text-faint: #8b91a6;
|
||||
}
|
||||
html[data-theme="light"] body::before { background: radial-gradient(circle, rgba(124,108,255,0.10), transparent 60%); }
|
||||
html[data-theme="light"] .nav::after { background: rgba(247,248,252,0.7); }
|
||||
html[data-theme="light"] .cmd-hero, html[data-theme="light"] .cmd-out,
|
||||
html[data-theme="light"] #toolSel, html[data-theme="light"] .cta-cmd,
|
||||
html[data-theme="light"] .doc-body pre, html[data-theme="light"] .doc-body code { background: #f0f2f8; }
|
||||
html[data-theme="light"] .btn-primary, html[data-theme="light"] .pl-num { color: #fff; }
|
||||
html[data-theme="light"] .chip.active { color: #fff; }
|
||||
|
||||
/* ========== 导航:语言 / 主题按钮 ========== */
|
||||
.lang-switch { font-weight: 600; border: 1px solid var(--border); padding: 3px 10px; border-radius: 7px; }
|
||||
.lang-switch:hover { border-color: var(--accent); }
|
||||
.theme-btn {
|
||||
background: var(--bg-soft); border: 1px solid var(--border); color: var(--text-dim);
|
||||
width: 30px; height: 28px; border-radius: 7px; cursor: pointer; font-size: 15px; line-height: 1;
|
||||
}
|
||||
.theme-btn:hover { color: var(--text); border-color: var(--accent); }
|
||||
|
||||
/* 卡片改为链接 */
|
||||
a.card { display: block; color: inherit; }
|
||||
.card-more { display: inline-block; margin-top: 12px; font-size: 13px; color: var(--accent-2); font-weight: 600; }
|
||||
a.card:hover .card-more { color: var(--accent); }
|
||||
|
||||
/* ========== 三列页脚 ========== */
|
||||
.foot-cols { align-items: flex-start; }
|
||||
.foot-brand strong { display: block; font-size: 16px; }
|
||||
.foot-brand strong b { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
||||
.foot-brand span { font-size: 13px; color: var(--text-faint); display: block; margin-top: 4px; max-width: 240px; }
|
||||
.foot-col { display: flex; flex-direction: column; gap: 9px; }
|
||||
.foot-col h4 { font-size: 13px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 2px; }
|
||||
.foot-col a { font-size: 14px; color: var(--text-dim); transition: color .2s; }
|
||||
.foot-col a:hover { color: var(--accent-2); }
|
||||
|
||||
/* ========== Skill 详情(操作文档)页 ========== */
|
||||
.doc { max-width: 820px; padding-top: 36px; }
|
||||
.doc-back { display: inline-block; font-size: 14px; color: var(--text-dim); margin: 8px 0 24px; transition: color .2s; }
|
||||
.doc-back:hover { color: var(--accent-2); }
|
||||
.doc-head { border-bottom: 1px solid var(--border-soft); padding-bottom: 28px; margin-bottom: 8px; }
|
||||
.doc-titles { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.doc-titles h1 { font-size: clamp(26px, 5vw, 38px); font-weight: 800; letter-spacing: -0.02em; }
|
||||
.doc-titles code { font-size: 13px; color: var(--text-faint); background: var(--bg-soft); padding: 3px 9px; border-radius: 6px; }
|
||||
.doc-lead { color: var(--text-dim); font-size: 16px; margin-top: 14px; line-height: 1.7; }
|
||||
.doc-actions { display: flex; gap: 12px; margin-top: 22px; flex-wrap: wrap; align-items: center; }
|
||||
.doc-cmd { margin: 0; max-width: 320px; }
|
||||
.doc-notice {
|
||||
background: rgba(124,108,255,0.1); border: 1px solid rgba(124,108,255,0.25);
|
||||
border-radius: 10px; padding: 12px 16px; font-size: 14px; color: var(--text-dim); margin: 24px 0;
|
||||
}
|
||||
|
||||
/* Markdown 正文排版 */
|
||||
.doc-body { font-size: 15.5px; line-height: 1.8; color: var(--text); }
|
||||
.doc-body h1, .doc-body h2, .doc-body h3, .doc-body h4 { font-weight: 700; line-height: 1.35; margin: 1.8em 0 .7em; letter-spacing: -0.01em; }
|
||||
.doc-body h1 { font-size: 26px; } .doc-body h2 { font-size: 22px; padding-bottom: .3em; border-bottom: 1px solid var(--border-soft); }
|
||||
.doc-body h3 { font-size: 18px; } .doc-body h4 { font-size: 16px; color: var(--text-dim); }
|
||||
.doc-body p { margin: .9em 0; }
|
||||
.doc-body a { color: var(--accent-2); text-decoration: underline; text-underline-offset: 2px; }
|
||||
.doc-body ul, .doc-body ol { margin: .8em 0; padding-left: 1.5em; }
|
||||
.doc-body li { margin: .35em 0; }
|
||||
.doc-body li > ul, .doc-body li > ol { margin: .3em 0; }
|
||||
.doc-body code { background: var(--bg-soft); padding: 2px 6px; border-radius: 5px; font-size: 13.5px; color: var(--accent-2); }
|
||||
.doc-body pre {
|
||||
background: #06070b; border: 1px solid var(--border); border-radius: 12px;
|
||||
padding: 18px 20px; overflow-x: auto; margin: 1.1em 0; position: relative;
|
||||
}
|
||||
.doc-body pre code { background: none; padding: 0; color: #cdd3e6; font-size: 13.5px; line-height: 1.7; }
|
||||
.doc-body pre[data-lang]:not([data-lang=""])::before {
|
||||
content: attr(data-lang); position: absolute; top: 8px; right: 12px;
|
||||
font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .05em;
|
||||
}
|
||||
.doc-body blockquote {
|
||||
border-left: 3px solid var(--accent); background: var(--bg-soft);
|
||||
padding: 10px 16px; margin: 1.1em 0; border-radius: 0 8px 8px 0; color: var(--text-dim);
|
||||
}
|
||||
.doc-body hr { border: none; border-top: 1px solid var(--border-soft); margin: 2em 0; }
|
||||
.md-table { overflow-x: auto; margin: 1.2em 0; }
|
||||
.doc-body table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||||
.doc-body th, .doc-body td { border: 1px solid var(--border); padding: 9px 13px; text-align: left; }
|
||||
.doc-body th { background: var(--bg-soft); font-weight: 600; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.nav nav { gap: 12px; }
|
||||
.nav nav a:nth-child(1), .nav nav a:nth-child(2), .nav nav a:nth-child(3) { display: none; }
|
||||
.foot-cols { flex-direction: row; flex-wrap: wrap; gap: 32px; }
|
||||
}
|
||||
Reference in New Issue
Block a user