Files
sub2api/docs/superpowers/plans/2026-04-19-puro-ai-landing-auth.md
puro design d941550bf6
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
docs: implementation plan for PURO AI landing+auth [CI SKIP]
13 tasks, ~4-6 hours total work. Each task has concrete file paths,
full code blocks, verification steps. Scope: Landing + AuthLayout split
+ LoginView/RegisterView restyle + public Docs + puro.css design system.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 19:04:23 +08:00

1747 lines
55 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PURO AI · Landing + Auth + Docs Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:**`ai.puro.im` 上线 PURO AI 品牌化的 Landing、Login、Register、Docs 四个页面,落地 `puro.css` 作为全站新设计系统,保持所有后台页面和现有 Auth 逻辑不变。
**Architecture:** 复用 Claude Design 产出的 `docs/design-drafts/v2/` 静态 HTML翻译成 Vue 3 SFC 组件CSS 通过全局引入 `puro.css`(含 tokens + primitives路由改造让 `/` 根据登录态动态展示 Landing 或 Dashboard。所有 hardcoded 中文通过 vue-i18n 的 `zh.ts` 集中管理。
**Tech Stack:** Vue 3.4+ (Composition API, TypeScript), Vue Router 4, vue-i18n, Pinia, Tailwind CSS保留 legacy primary 色板不动Vite 5, pnpm 10, vitestrouter 用)。
**Spec:** `docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md`(第 3/4/4.5/6 节为本计划权威依据)
**Branch:** `feat/design-landing-auth`(已创建 · 当前 HEAD `3a16b3ec`
---
## 文件结构
```
frontend/
├── index.html ← MODIFY 加 Google Fonts preconnect + link
├── src/
│ ├── assets/
│ │ └── puro.css ← NEW 复制 docs/design-drafts/v2/puro.css
│ ├── main.ts ← MODIFY import './assets/puro.css'
│ ├── router/
│ │ └── index.ts ← MODIFY `/` auth-aware + `/docs` public
│ ├── views/
│ │ ├── landing/
│ │ │ └── LandingView.vue ← NEW 6 sections, dashboard mockup 静态
│ │ ├── docs/
│ │ │ └── DocsView.vue ← NEW 6 subsections, curl/codex/claude-code 示例
│ │ └── auth/
│ │ ├── LoginView.vue ← MODIFY 套新 narrative slot + 小幅改文案
│ │ └── RegisterView.vue ← MODIFY 同上
│ ├── components/
│ │ └── layout/
│ │ └── AuthLayout.vue ← MODIFY 加 `narrative` 命名 slot条件渲染
│ └── i18n/
│ └── locales/
│ └── zh.ts ← MODIFY 新增 `landing.*` / `docs.*` / 扩 `auth.*`
└── tailwind.config.js ← MODIFY extend color 加 puro.* 家族(给 Vue 用)
```
**不动的文件**:所有 `views/admin/**``views/user/**``views/setup/**``App.vue``AppHeader/AppLayout/AppSidebar.vue`、backend 全部。
**设计决策**
- `puro.css` 里已有的 primitives class`.btn-primary`, `.card`, `.input` 等)直接用 class 引用,**不抽 Vue 组件**(减少 scope 膨胀)
- Tailwind 现有 `primary`teal #14b8a6**不改**——admin 页还用。新加 `puro.cyan` / `puro.purple` / `puro.amber` 等命名空间,新页面用 `text-puro-cyan`
- i18n 只补 `zh.ts`(本期默认中文);`en.ts` 键留空或复用 `zh`
---
## Task 1: 引入 puro.css 设计系统 + Google Fonts
**Files:**
- Create: `frontend/src/assets/puro.css`
- Modify: `frontend/src/main.ts:7`
- Modify: `frontend/index.html`
- [ ] **Step 1: 复制 puro.css 到 assets**
```bash
cp /Users/mini/Work/dev/sub2api/docs/design-drafts/v2/puro.css \
/Users/mini/Work/dev/sub2api/frontend/src/assets/puro.css
```
- [ ] **Step 2: 在 main.ts 里 import紧接 style.css**
Modify `frontend/src/main.ts`,把第 7 行:
```ts
import './style.css'
```
改为:
```ts
import './style.css'
import './assets/puro.css'
```
- [ ] **Step 3: 在 index.html `<head>` 里加 Google Fonts preconnect + link**
Modify `frontend/index.html`,在 `<head>` 里(`<title>` 之前)插入:
```html
<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;600&display=swap" rel="stylesheet">
```
- [ ] **Step 4: 启动 dev server 验证 CSS 变量可用**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
Expected: 启动成功(端口默认 5173无 console error。
浏览器打开 http://localhost:5173DevTools Console 执行:
```js
getComputedStyle(document.documentElement).getPropertyValue('--cyan')
```
Expected: `" #22d3ee"`
**停掉 dev server**(保留终端)。
- [ ] **Step 5: 扩展 tailwind.config.js 加 puro 色板**
Modify `frontend/tailwind.config.js`,在 `theme.extend.colors` 里(`dark` 键后)加:
```js
// PURO AI 设计系统色板(给新页面 Landing/Auth/Docs 用,不影响 admin
puro: {
cyan: '#22d3ee',
'cyan-2': '#67e8f9',
purple: '#a855f7',
amber: '#fbbf24',
green: '#34d399',
red: '#f87171',
// 平台品牌点色
claude: '#d97757',
gpt: '#10a37f',
gemini: '#4285f4',
codex: '#f0a030'
}
```
- [ ] **Step 6: 验证 Tailwind class 生效**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
DevTools Console:
```js
// 临时在页面里插入 div
document.body.insertAdjacentHTML('afterbegin', '<div class="bg-puro-cyan" id="test-puro" style="padding:20px">test</div>')
// 读取实际背景色
getComputedStyle(document.getElementById('test-puro')).backgroundColor
```
Expected: `"rgb(34, 211, 238)"`
删除测试元素,停掉 dev server。
- [ ] **Step 7: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/assets/puro.css frontend/src/main.ts frontend/index.html frontend/tailwind.config.js
git commit -m "$(cat <<'EOF'
feat(design): scaffold PURO AI design system
- Add puro.css (tokens + primitives) as global stylesheet
- Load Inter + JetBrains Mono via Google Fonts
- Extend tailwind.config with puro.* color namespace (no conflict with legacy primary/accent palettes)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```
---
## Task 2: LandingView — 骨架 + Nav + Hero
**Files:**
- Create: `frontend/src/views/landing/LandingView.vue`
- [ ] **Step 1: 建 LandingView.vue 骨架**
Create `frontend/src/views/landing/LandingView.vue`:
```vue
<template>
<div class="puro-page">
<div class="bg-glow"></div>
<div class="grain"></div>
<!-- NAV -->
<nav class="nav">
<div class="container nav-inner">
<a href="/" class="brand">
<span class="hex"></span>
<span>PURO AI</span>
</a>
<div class="nav-links">
<a href="#features">产品</a>
<a href="/docs">文档</a>
</div>
<div class="nav-cta">
<router-link to="/login" class="btn btn-ghost">登录</router-link>
<router-link to="/register" class="btn btn-primary">免费试用 </router-link>
</div>
</div>
</nav>
<!-- HERO -->
<section class="hero container">
<div class="hero-eyebrow">
<span class="pill">ChatGPT Plus · Claude Pro · Codex · Gemini</span>
</div>
<h1 class="hero-title">
你的 AI 订阅<br>
<span class="text-puro-cyan">已经付过钱了</span>
</h1>
<p class="hero-sub">
Claude Pro · ChatGPT Plus · Codex · Gemini 订阅<br>
聚合成统一 API零改动接入 OpenAI / Anthropic SDK
</p>
<div class="hero-cta">
<router-link to="/login" class="btn btn-primary btn-lg">登录 </router-link>
<a href="mailto:admin@puro.im" class="btn btn-ghost btn-lg">联系咨询</a>
</div>
<div class="hero-micro">
已验证可用 Codex CLI · Claude Code · curl · 服务器出口新加坡
</div>
</section>
</div>
</template>
<script setup lang="ts">
// LandingView — public marketing landing page for PURO AI
// Rendered at `/` when user is unauthenticated (see router/index.ts)
</script>
<style scoped>
.puro-page {
min-height: 100vh;
background: var(--bg-0);
color: var(--text-0);
font-family: var(--font-sans);
position: relative;
overflow-x: hidden;
}
.hero {
text-align: center;
padding: 100px 24px 80px;
position: relative;
z-index: 2;
}
.hero-eyebrow { margin-bottom: 24px; }
.hero-title {
font-size: clamp(36px, 5.5vw, 64px);
font-weight: 800;
line-height: 1.1;
letter-spacing: -0.03em;
margin-bottom: 20px;
}
.hero-sub {
font-size: 18px;
color: var(--text-2);
line-height: 1.6;
max-width: 640px;
margin: 0 auto 36px;
}
.hero-cta {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 20px;
}
.hero-micro {
font-size: 13px;
color: var(--text-3);
font-family: var(--font-mono);
}
</style>
```
- [ ] **Step 2: 临时加 /landing-preview 路由方便预览**
Modify `frontend/src/router/index.ts`,在 `// ==================== Public Routes ====================` 注释后(靠近 `/home` 定义那里)插入:
```ts
{
path: '/landing-preview',
name: 'LandingPreview',
component: () => import('@/views/landing/LandingView.vue'),
meta: {
requiresAuth: false,
title: 'PURO AI'
}
},
```
- [ ] **Step 3: 启动 dev server 预览**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
浏览器开 http://localhost:5173/landing-preview
Expected:
- 暗黑底
- "⬢ PURO AI" + "登录" + "免费试用 →" 顶栏
- 中央 "你的 AI 订阅,已经付过钱了。""已经付过钱了" 是 cyan 色)
- "登录 →" + "联系咨询" 两个按钮
- 底部 cyan/purple radial 光晕
**停掉 dev server**
- [ ] **Step 4: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/views/landing/LandingView.vue frontend/src/router/index.ts
git commit -m "feat(landing): LandingView scaffold with Nav + Hero
Temporary route /landing-preview added for dev iteration. Will flip / to
this view once all 6 sections are in place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 3: LandingView — ② 模型墙 + ③ 三大特性
**Files:**
- Modify: `frontend/src/views/landing/LandingView.vue`
- [ ] **Step 1: 在 `<section class="hero">` 结束标签之后、script 之前,插入 ② + ③**
```vue
<!-- 模型墙 -->
<section class="block container" id="models">
<div class="section-header">
<div class="section-kicker">支持的 AI 平台</div>
<h2 class="section-title">通过 OAuth 直接复用你的订阅</h2>
<p class="section-sub">无需申请官方 API key也无需切换账号</p>
</div>
<div class="model-wall">
<div class="model-card">
<div class="model-dot" style="background: var(--p-claude)"></div>
<div class="model-name">Claude Pro / Max</div>
<div class="model-meta">Anthropic OAuth</div>
</div>
<div class="model-card">
<div class="model-dot" style="background: var(--p-gpt)"></div>
<div class="model-name">ChatGPT Plus / Pro</div>
<div class="model-meta">OpenAI OAuth</div>
</div>
<div class="model-card">
<div class="model-dot" style="background: var(--p-codex)"></div>
<div class="model-name">Codex CLI</div>
<div class="model-meta">OpenAI OAuth</div>
</div>
<div class="model-card">
<div class="model-dot" style="background: var(--p-gemini)"></div>
<div class="model-name">Gemini Code Assist</div>
<div class="model-meta">Google OAuth</div>
</div>
<div class="model-card is-muted">
<div class="model-dot" style="background: var(--text-3)"></div>
<div class="model-name">更多</div>
<div class="model-meta">规划中</div>
</div>
</div>
</section>
<!-- 三特性 -->
<section class="block container" id="features">
<div class="section-header">
<div class="section-kicker">核心特性</div>
<h2 class="section-title">一套 key三件武器</h2>
</div>
<div class="features">
<div class="feature card">
<div class="feature-icon"></div>
<h3>一个 key 接所有模型</h3>
<p>不再为每个 provider 申请 API key配置 base_url统一 <code class="mono">sk-</code> Claude / GPT / Gemini model 自动路由到对应账号池</p>
</div>
<div class="feature card">
<div class="feature-icon">🔄</div>
<h3>账号池高可用</h3>
<p>支持多账号自动调度与 failover某个上游触发限流 / 冷却时流量切到下一个健康账号token 刷新全自动</p>
</div>
<div class="feature card">
<div class="feature-icon">📊</div>
<h3>用量看板</h3>
<p>每条请求的 tokens费用上游账号延迟全可视化模型分布饼图 + 趋势曲线 + Top 排行</p>
</div>
</div>
</section>
```
- [ ] **Step 2: 在 `<style scoped>` 里追加样式**
```css
.container {
max-width: 1120px;
margin: 0 auto;
padding: 0 24px;
position: relative;
z-index: 2;
}
.block { padding: 80px 24px; }
.section-header { text-align: center; margin-bottom: 40px; }
.section-kicker {
font-size: 12px;
font-weight: 600;
color: var(--cyan);
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: 12px;
font-family: var(--font-mono);
}
.section-title {
font-size: clamp(28px, 3.5vw, 40px);
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 12px;
}
.section-sub { color: var(--text-2); font-size: 15px; }
/* model wall */
.model-wall {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.model-card {
padding: 20px;
border: 1px solid var(--border);
border-radius: var(--r-lg);
background: var(--bg-1);
display: flex;
align-items: center;
gap: 12px;
}
.model-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.model-name { font-weight: 600; font-size: 14px; }
.model-meta { font-size: 11px; color: var(--text-3); font-family: var(--font-mono); margin-top: 2px; }
.model-card.is-muted { opacity: 0.5; }
.model-card.is-muted .model-name { color: var(--text-2); }
/* features */
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
.feature {
padding: 28px 24px;
border: 1px solid var(--border);
border-radius: var(--r-lg);
background: var(--bg-1);
}
.feature-icon { font-size: 28px; margin-bottom: 14px; }
.feature h3 { font-size: 18px; font-weight: 700; margin-bottom: 10px; }
.feature p { color: var(--text-2); font-size: 14px; line-height: 1.6; }
.feature code { color: var(--cyan); font-size: 13px; }
```
- [ ] **Step 3: 验证**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
浏览器开 http://localhost:5173/landing-preview
Expected:
- Hero 下方出现"支持的 AI 平台"section5 张模型卡片横排
- 再下方"一套 key三件武器"section3 张特性卡片
停掉 dev server。
- [ ] **Step 4: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/views/landing/LandingView.vue
git commit -m "feat(landing): add Models wall + Features sections
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 4: LandingView — ④ Code Demo + ⑤ Dashboard mockup
**Files:**
- Modify: `frontend/src/views/landing/LandingView.vue`
- [ ] **Step 1: 在 features section 之后插入 Code Demo + Dashboard mockup**
```vue
<!-- Code Demo -->
<section class="block container" id="code">
<div class="section-header">
<div class="section-kicker">快速接入</div>
<h2 class="section-title"> base_url 一改就能用</h2>
<p class="section-sub">兼容 OpenAI / Anthropic / Gemini SDK<span class="text-puro-cyan">零代码改动</span></p>
</div>
<div class="code-demo">
<div class="code-block">
<div class="code-title mono">~/.codex/config.toml</div>
<pre class="mono"><code><span class="cm">[model_providers.OpenAI]</span>
base_url = <span class="str">"https://ai.puro.im"</span>
wire_api = <span class="str">"responses"</span>
requires_openai_auth = <span class="kw">true</span></code></pre>
</div>
<div class="code-block">
<div class="code-title mono">curl</div>
<pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/responses \
-H <span class="str">"Authorization: Bearer sk-xxx"</span> \
-d <span class="str">'{"model":"gpt-5.4","input":"hello"}'</span></code></pre>
</div>
</div>
<div class="code-foot">支持 OpenAI Responses API · Anthropic Messages API · Gemini generateContent · 流式 SSE &amp; WebSocket</div>
</section>
<!-- Dashboard mockup -->
<section class="block container" id="dashboard">
<div class="section-header">
<div class="section-kicker">用量透明</div>
<h2 class="section-title">每条请求都看得见</h2>
<p class="section-sub">不像第三方 API 池子那种"扣了多少不告诉你"扣哪个账号跑哪个模型用了多少 tokens上游响应几秒一目了然</p>
</div>
<div class="dash-mock">
<div class="dash-header">
<span class="dash-title">Dashboard · 预览</span>
<div class="dash-dots"><span></span><span></span><span></span></div>
</div>
<div class="dash-body">
<div class="stat-row">
<div class="stat"><div class="stat-label">今日请求</div><div class="stat-value">1,842</div><div class="stat-delta">+12.3%</div></div>
<div class="stat"><div class="stat-label">输入 Tokens</div><div class="stat-value">2.1M</div><div class="stat-delta">+8.1%</div></div>
<div class="stat"><div class="stat-label">输出 Tokens</div><div class="stat-value">485K</div><div class="stat-delta">+15.6%</div></div>
<div class="stat"><div class="stat-label">今日费用</div><div class="stat-value">$1.23</div><div class="stat-delta down">-4.2%</div></div>
</div>
<div class="chart-card">
<div class="chart-title"> 30 天用量趋势</div>
<svg viewBox="0 0 600 120" class="chart-svg">
<polyline points="0,90 40,80 80,70 120,65 160,60 200,50 240,55 280,45 320,40 360,35 400,30 440,25 480,20 520,25 560,15 600,10"
fill="none" stroke="#22d3ee" stroke-width="2"/>
<polyline points="0,100 40,95 80,90 120,88 160,85 200,82 240,80 280,78 320,75 360,73 400,70 440,68 480,65 520,63 560,60 600,58"
fill="none" stroke="#a855f7" stroke-width="2" stroke-dasharray="4 4"/>
</svg>
</div>
<table class="log-table mono">
<thead>
<tr><th>时间</th><th>模型</th><th>上游</th><th>状态</th><th>用量</th></tr>
</thead>
<tbody>
<tr><td>12:34:07</td><td>gpt-5.4</td><td><span class="provider gpt"><span class="dot"></span>ChatGPT #1</span></td><td class="status-200">200</td><td>2,341</td></tr>
<tr><td>12:34:02</td><td>claude-opus-4-7</td><td><span class="provider claude"><span class="dot"></span>Claude #2</span></td><td class="status-200">200</td><td>5,102</td></tr>
<tr><td>12:33:58</td><td>gemini-2.5-pro</td><td><span class="provider gemini"><span class="dot"></span>Gemini #1</span></td><td class="status-200">200</td><td>843</td></tr>
<tr><td>12:33:41</td><td>gpt-5.4</td><td><span class="provider gpt"><span class="dot"></span>ChatGPT #2</span></td><td class="status-429">429</td><td></td></tr>
</tbody>
</table>
</div>
</div>
</section>
```
- [ ] **Step 2: 追加样式到 `<style scoped>`**
```css
/* code demo */
.code-demo {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 820px) { .code-demo { grid-template-columns: 1fr; } }
.code-block {
border: 1px solid var(--border);
border-radius: var(--r-lg);
background: var(--bg-code);
overflow: hidden;
}
.code-title {
padding: 10px 16px;
background: var(--bg-1);
font-size: 11px;
color: var(--text-3);
border-bottom: 1px solid var(--border);
}
.code-block pre {
padding: 16px;
font-size: 13px;
line-height: 1.6;
color: var(--text-1);
overflow-x: auto;
}
.cm { color: var(--text-3); }
.str { color: var(--cyan); }
.kw { color: var(--amber); }
.code-foot {
margin-top: 20px;
text-align: center;
font-size: 12px;
color: var(--text-3);
font-family: var(--font-mono);
}
/* dash mockup */
.dash-mock {
border: 1px solid var(--border);
border-radius: var(--r-xl);
background: var(--bg-1);
overflow: hidden;
box-shadow: 0 40px 80px -40px rgba(0,0,0,0.8);
}
.dash-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.dash-title { font-size: 12px; color: var(--text-2); font-family: var(--font-mono); }
.dash-dots { display: flex; gap: 6px; }
.dash-dots span { width: 10px; height: 10px; border-radius: 50%; background: var(--border-2); }
.dash-body { padding: 20px; }
.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
@media (max-width: 720px) { .stat-row { grid-template-columns: repeat(2, 1fr); } }
.stat {
padding: 14px;
border: 1px solid var(--border);
border-radius: var(--r-md);
background: rgba(15,23,42,0.6);
}
.stat-label { font-size: 10px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
.stat-value { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; font-family: var(--font-mono); }
.stat-delta { font-size: 11px; color: var(--green, #34d399); margin-top: 4px; font-family: var(--font-mono); }
.stat-delta.down { color: var(--red, #f87171); }
.chart-card {
border: 1px solid var(--border);
border-radius: var(--r-md);
background: rgba(15,23,42,0.6);
padding: 16px;
margin-bottom: 20px;
}
.chart-title { font-size: 12px; color: var(--text-2); margin-bottom: 12px; }
.chart-svg { width: 100%; height: 120px; display: block; }
.log-table { width: 100%; font-size: 12px; border-collapse: collapse; }
.log-table th {
text-align: left; color: var(--text-3); font-weight: 500;
padding: 10px 12px; border-bottom: 1px solid var(--border);
text-transform: uppercase; font-size: 10px; letter-spacing: 0.08em;
}
.log-table td { padding: 10px 12px; border-bottom: 1px solid rgba(30,41,59,0.5); color: var(--text-1); }
.log-table tr:last-child td { border-bottom: none; }
.log-table .status-200 { color: var(--green, #34d399); }
.log-table .status-429 { color: var(--amber); }
.log-table .provider { display: inline-flex; align-items: center; gap: 6px; }
.log-table .provider .dot { width: 6px; height: 6px; border-radius: 50%; }
.provider.claude .dot { background: var(--p-claude); }
.provider.gpt .dot { background: var(--p-gpt); }
.provider.gemini .dot { background: var(--p-gemini); }
```
- [ ] **Step 3: 验证**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
http://localhost:5173/landing-preview
Expected:
- Code Demo 两个代码块并排(移动端堆叠)
- Dashboard mockup4 格 stats + 折线 SVG + 4 行 log table
停掉 dev server。
- [ ] **Step 4: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/views/landing/LandingView.vue
git commit -m "feat(landing): add Code Demo + Dashboard mockup sections
Dashboard mockup uses static data (stats, SVG chart, log table) — no
backend dependency. Visually demonstrates the product's usage transparency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 5: LandingView — ⑥ Footer + 顶部 Nav 样式
**Files:**
- Modify: `frontend/src/views/landing/LandingView.vue`
- [ ] **Step 1: 在 `⑤ Dashboard` section 后、`</template>` 前加 Footer**
```vue
<!-- Footer -->
<footer class="puro-footer">
<div class="container footer-grid">
<div class="footer-brand">
<div class="brand"><span class="hex"></span><span>PURO AI</span></div>
<p class="footer-tagline">Self-hosted on puro.im</p>
<p class="footer-meta">© 2026 puro.im · MIT License<br>fork of Wei-Shaw/sub2api</p>
</div>
<div class="footer-col">
<div class="footer-col-title">产品</div>
<a href="/docs">文档</a>
<a href="https://git.puro.im/purovps/sub2api/releases" target="_blank" rel="noopener">更新日志</a>
</div>
<div class="footer-col">
<div class="footer-col-title">资源</div>
<a href="https://git.puro.im/purovps/sub2api" target="_blank" rel="noopener">GitHub</a>
<a href="/docs#codex">Codex 配置示例</a>
<a href="https://status.puro.im" target="_blank" rel="noopener">API 状态</a>
</div>
<div class="footer-col">
<div class="footer-col-title">联系</div>
<a href="mailto:admin@puro.im">admin@puro.im</a>
<a href="https://git.puro.im" target="_blank" rel="noopener">git.puro.im</a>
</div>
</div>
</footer>
```
- [ ] **Step 2: 追加 Nav 和 Footer 样式到 `<style scoped>`**
```css
.nav {
position: sticky;
top: 0;
z-index: 10;
background: rgba(10,14,26,0.75);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
}
.nav-inner {
display: flex;
align-items: center;
gap: 28px;
padding: 16px 24px;
max-width: 1120px;
margin: 0 auto;
}
.brand {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 16px;
}
.brand .hex {
color: var(--cyan);
font-size: 20px;
}
.nav-links {
display: flex;
gap: 20px;
font-size: 14px;
color: var(--text-2);
}
.nav-links a:hover { color: var(--text-0); }
.nav-cta {
display: flex;
gap: 10px;
margin-left: auto;
}
@media (max-width: 640px) {
.nav-links { display: none; }
}
/* footer */
.puro-footer {
margin-top: 80px;
padding: 60px 24px 40px;
border-top: 1px solid var(--border);
}
.footer-grid {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: 36px;
}
@media (max-width: 720px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
.footer-brand .brand { margin-bottom: 12px; }
.footer-tagline { color: var(--text-2); font-size: 13px; margin-bottom: 8px; }
.footer-meta { color: var(--text-3); font-size: 12px; line-height: 1.7; }
.footer-col-title {
color: var(--text-0);
font-size: 13px;
font-weight: 600;
margin-bottom: 12px;
}
.footer-col a {
display: block;
color: var(--text-2);
font-size: 13px;
padding: 4px 0;
}
.footer-col a:hover { color: var(--cyan); }
/* pill */
.pill {
display: inline-block;
padding: 6px 14px;
border: 1px solid var(--border-2);
border-radius: 999px;
font-size: 12px;
color: var(--text-2);
background: rgba(15,23,42,0.6);
}
```
- [ ] **Step 3: 验证整页 Landing**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
http://localhost:5173/landing-preview
Expected 整页滚动可见顺序:
1. Navsticky滚动时背景半透明 blur
2. Hero
3. 模型墙
4. 三特性
5. Code Demo
6. Dashboard mockup
7. Footer 4 列
移动端(窗口缩到 <640pxNav 菜单折叠、footer 变 2 列、stats 变 2 列、code demo 堆叠。
停掉 dev server。
- [ ] **Step 4: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/views/landing/LandingView.vue
git commit -m "feat(landing): Footer + Nav styles (sticky blur + responsive)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 6: 路由 `/` auth-aware
**Files:**
- Modify: `frontend/src/router/index.ts`
- Test: `frontend/src/router/__tests__/guards.spec.ts`(若有现成的 / guard 测试,补上 Landing 分支;没有就新加)
- [ ] **Step 1: 查看当前 `/` 的 redirect 逻辑**
Run:
```bash
grep -n "path: '/'" /Users/mini/Work/dev/sub2api/frontend/src/router/index.ts
grep -n "path: '/landing-preview'" /Users/mini/Work/dev/sub2api/frontend/src/router/index.ts
```
Expected: `path: '/'` 在一行redirect 到 `/home``/landing-preview` 在另一行。
- [ ] **Step 2: 把 `/landing-preview` 改名成 `/` 的实际挂载**
Modify `frontend/src/router/index.ts`
1. 删除 `/landing-preview` 那 10 行临时 route
2. 把原来的:
```ts
{
path: '/',
redirect: '/home'
},
```
替换为:
```ts
{
path: '/',
name: 'Landing',
component: () => import('@/views/landing/LandingView.vue'),
meta: {
requiresAuth: false,
title: 'PURO AI — 你的 AI 订阅,已经付过钱了',
redirectIfAuth: '/dashboard' // authenticated users bounce to dashboard
}
},
```
- [ ] **Step 3: 在 guard 里加 redirectIfAuth 分支**
Navigation guard 在 `frontend/src/router/index.ts:524``router.beforeEach((to, _from, next) => { ... })`。auth store 暴露 `isAuthenticated` computed定义见 `stores/auth.ts:31`)。
`authStore.checkAuth()` 那行line ~532后、所有其它逻辑之前插入
```ts
// Auth-aware redirects for public pages (e.g. '/' shows Landing to anons, bounces authed users)
const redirectIfAuth = to.meta.redirectIfAuth as string | undefined
if (redirectIfAuth && authStore.isAuthenticated) {
return next(redirectIfAuth)
}
```
- [ ] **Step 4: TypeScript meta 字段声明**
Modify `frontend/src/router/meta.d.ts`(若存在)或 `router/index.ts` 顶部 module augmentation`RouteMeta` 增加 `redirectIfAuth?: string`
```ts
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
redirectIfAuth?: string
}
}
```
(若 `meta.d.ts` 已有 module augmentation追加字段即可。
- [ ] **Step 5: 验证未登录 → Landing、登录后 → Dashboard**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
场景 1匿名DevTools → Application → Clear storage → 全清 localStorage/sessionStorage
- http://localhost:5173/ → **LandingView** 呈现
- 点 Hero "登录 →" → 跳 /login
场景 2登录态
- 在 /login 输 `admin@puro.im` / `fJni5YDmEY242owh` 登录(见 spec §5 "当前状态 snapshot"
- 登录后自动跳到 /dashboard既有行为
- 手动再访问 http://localhost:5173/ → 被 guard 跳到 /dashboard新加的 redirectIfAuth 生效)
停掉 dev server。
- [ ] **Step 6: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/router/index.ts
git commit -m "feat(router): mount Landing at / with auth-aware redirect
Anonymous visitors see PURO AI landing page. Authenticated users are
redirected to /dashboard via meta.redirectIfAuth guard extension.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 7: AuthLayout 改造(加 narrative slot
**Files:**
- Modify: `frontend/src/components/layout/AuthLayout.vue`
- [ ] **Step 1: 读现有 AuthLayout 的 slot 结构**
Run:
```bash
wc -l /Users/mini/Work/dev/sub2api/frontend/src/components/layout/AuthLayout.vue
```
Expected: ~90-120 行。读它确认 slot 有默认 + `#footer`
- [ ] **Step 2: 改 AuthLayout.vue template 为可选左右分栏**
把整个 `<template>` 换成:
```vue
<template>
<div class="auth-shell" :class="{ 'auth-shell-split': hasNarrative }">
<div class="bg-glow soft"></div>
<!-- LEFT: Narrative (split mode only, hidden on mobile) -->
<aside v-if="hasNarrative" class="auth-narrative">
<slot name="narrative"></slot>
</aside>
<!-- RIGHT: Form -->
<main class="auth-main">
<div class="auth-main-inner">
<div class="mb-8 text-center" v-if="!hasNarrative">
<!-- 兼容旧调用 narrative slot 时退回居中品牌头 -->
<template v-if="settingsLoaded">
<div class="mb-4 inline-flex h-14 w-14 items-center justify-center overflow-hidden rounded-2xl">
<img :src="siteLogo || '/logo.png'" alt="Logo" class="h-full w-full object-contain" />
</div>
<h1 class="text-2xl font-bold">{{ siteName }}</h1>
</template>
</div>
<slot />
<div class="mt-6 text-center text-sm">
<slot name="footer" />
</div>
</div>
</main>
</div>
</template>
```
- [ ] **Step 3: 在 `<script setup>` 里加 `useSlots` 检测 narrative**
`<script setup lang="ts">` 顶部追加:
```ts
import { useSlots } from 'vue'
const slots = useSlots()
const hasNarrative = computed(() => !!slots.narrative)
```
`computed` 如果还没 import 就加上)
- [ ] **Step 4: 在 `<style scoped>` 里加 split layout 样式**
把现有 `<style scoped>`(或在文件末新增)补入:
```css
.auth-shell {
min-height: 100vh;
background: var(--bg-0);
color: var(--text-0);
font-family: var(--font-sans);
display: flex;
position: relative;
overflow: hidden;
}
.auth-shell-split { display: grid; grid-template-columns: 1fr 1fr; }
@media (max-width: 900px) {
.auth-shell-split { grid-template-columns: 1fr; }
.auth-narrative { display: none; }
}
.auth-narrative {
position: relative;
padding: 48px 56px;
background: linear-gradient(135deg, #0a0e1a 0%, #1e1b4b 100%);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.auth-main {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
position: relative;
z-index: 2;
}
.auth-main-inner { width: 100%; max-width: 420px; }
```
- [ ] **Step 5: 验证无 narrative 场景(旧行为兼容)**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
http://localhost:5173/forgot-password这个页面用 AuthLayout 但不会传 narrative
Expected: 居中品牌卡片,看起来像以前。
停掉 dev server。
- [ ] **Step 6: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/components/layout/AuthLayout.vue
git commit -m "refactor(auth): AuthLayout supports optional narrative slot (split mode)
Backward-compatible: without narrative slot, falls back to centered brand
card. With narrative slot, renders 50/50 split on desktop, collapses to
single column on mobile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 8: LoginView 套 narrative slot + 文案微调
**Files:**
- Modify: `frontend/src/views/auth/LoginView.vue`
- [ ] **Step 1: 在 `<AuthLayout>` 开标签后插入 `<template #narrative>`**
找到 `<AuthLayout>`(第 2 行附近)后,紧跟着加:
```vue
<template #narrative>
<div class="auth-narrative-inner">
<div class="brand"><span class="hex"></span><span>PURO AI</span></div>
<div class="auth-narrative-hero">
<div class="auth-narrative-headline">
<span class="num-5">5</span> 个订阅<br>
<span class="num-1">1</span> key
</div>
<p class="auth-narrative-sub">
省去切换账号的繁琐<br>
省去为多个高昂订阅重复买单<br>
<span class="auth-narrative-tagline">PURO纯粹 AI 调用回归本质</span>
</p>
</div>
<div class="auth-narrative-foot mono">Claude · ChatGPT · Codex · Gemini</div>
</div>
</template>
```
- [ ] **Step 2: 调登录表单的 heading 文案**
把现有 `<h2>{{ t('auth.welcomeBack') }}</h2>` 对应的那行 heading 改为:
```vue
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">登录</h2>
<p class="mt-2 text-sm text-gray-500 dark:text-dark-400">用你的 PURO AI 账户继续</p>
```
(直接 hardcode 中文,避开改 i18nTask 11 会统一补进 zh.ts。
- [ ] **Step 3: 在 LoginView 的 `<style scoped>` 里(如果没有就加一个)追加 narrative 内部样式**
```vue
<style scoped>
.auth-narrative-inner {
position: relative;
z-index: 2;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
color: var(--text-0);
}
.brand {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 18px;
}
.brand .hex { color: var(--cyan); font-size: 24px; }
.auth-narrative-hero { }
.auth-narrative-headline {
font-size: clamp(40px, 5vw, 64px);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.03em;
margin-bottom: 24px;
}
.auth-narrative-headline .num-5 { color: var(--amber); }
.auth-narrative-headline .num-1 { color: var(--cyan); }
.auth-narrative-sub {
font-size: 15px;
line-height: 1.7;
color: var(--text-1);
}
.auth-narrative-tagline {
display: block;
margin-top: 12px;
font-size: 12px;
color: var(--text-3);
}
.auth-narrative-foot {
font-size: 12px;
color: var(--text-3);
}
</style>
```
- [ ] **Step 4: 验证**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
http://localhost:5173/login
Expected桌面:
- 左侧PURO AI 品牌 + "5 个订阅 → 1 个 key"5 橙色1 青色)+ 三句排比副文 + 底部小字
- 右侧:原有登录表单(邮箱/密码/CTA/OAuthheading 改成"登录"
- 移动端(缩到 <900pxnarrative 隐藏,表单居中
登录流程一定要验:
- 用已有账号登录一次,看是否正常跳 DashboardOAuth/Turnstile/2FA 逻辑未动过,应都保留)
停掉 dev server。
- [ ] **Step 5: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/views/auth/LoginView.vue
git commit -m "feat(auth): LoginView with PURO narrative split layout
- Left: ⬢ PURO AI brand + '5→1' headline + value props
- Right: unchanged form (OAuth / Turnstile / 2FA all preserved)
- Heading copy updated to Chinese (i18n key consolidation in later task)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 9: RegisterView 套同样的 narrative
**Files:**
- Modify: `frontend/src/views/auth/RegisterView.vue`
- [ ] **Step 1: 读 RegisterView 当前 `<AuthLayout>` 块**
Run:
```bash
grep -n "AuthLayout\|welcomeBack\|createAccount" /Users/mini/Work/dev/sub2api/frontend/src/views/auth/RegisterView.vue | head -10
```
- [ ] **Step 2: 在 `<AuthLayout>` 开标签后插入同款 narrative slot**
(与 LoginView 完全相同的 `<template #narrative>` 块——**复制粘贴** LoginView 的 narrative template。
- [ ] **Step 3: 调注册表单 heading**
把现有 `<h2>` heading 那行替换为:
```vue
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">创建账户</h2>
<p class="mt-2 text-sm text-gray-500 dark:text-dark-400">5 分钟开始用 PURO AI</p>
```
- [ ] **Step 4: 把 LoginView 的 narrative `<style scoped>` 复制到 RegisterView**
(整段 `.auth-narrative-inner / .brand / .auth-narrative-*` 的 CSS 复制一份)
- [ ] **Step 5: 验证**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
http://localhost:5173/register
Expected: 同 Login 的 split layout右侧表单是注册表单邮箱 + 密码 + 确认密码 + Turnstile + CTAheading "创建账户"。
停掉 dev server。
- [ ] **Step 6: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/views/auth/RegisterView.vue
git commit -m "feat(auth): RegisterView with PURO narrative split layout
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 10: DocsView
**Files:**
- Create: `frontend/src/views/docs/DocsView.vue`
- Modify: `frontend/src/router/index.ts`
- [ ] **Step 1: 建 DocsView.vue**
Create `frontend/src/views/docs/DocsView.vue`:
```vue
<template>
<div class="puro-page">
<div class="bg-glow soft"></div>
<nav class="nav">
<div class="container nav-inner">
<router-link to="/" class="brand"><span class="hex"></span><span>PURO AI</span></router-link>
<div class="nav-links">
<router-link to="/">首页</router-link>
<a href="#codex">Codex</a>
<a href="#claude-code">Claude Code</a>
<a href="#curl">curl</a>
</div>
<div class="nav-cta">
<router-link to="/login" class="btn btn-primary">登录 </router-link>
</div>
</div>
</nav>
<section class="docs-hero container">
<h1>快速接入 PURO AI</h1>
<p class="subtitle">三步走 key base_url 发请求</p>
</section>
<div class="container docs-body">
<section id="get-key" class="docs-section">
<h2>1. 获取 API key</h2>
<p>当前 PURO AI 不开放自助注册付费联系管理员获取</p>
<div class="callout">
<a href="mailto:admin@puro.im">admin@puro.im</a>
</div>
<p class="note">未来通过 iShare 入口开放订阅购买</p>
</section>
<section id="codex" class="docs-section">
<h2>2. Codex CLI 接入</h2>
<p>修改 <code class="mono">~/.codex/config.toml</code></p>
<pre class="mono"><code>model_provider = <span class="str">"OpenAI"</span>
model = <span class="str">"gpt-5.4"</span>
wire_api = <span class="str">"responses"</span>
[model_providers.OpenAI]
name = <span class="str">"OpenAI"</span>
base_url = <span class="str">"https://ai.puro.im"</span>
wire_api = <span class="str">"responses"</span>
requires_openai_auth = <span class="kw">true</span></code></pre>
<p>然后 <code class="mono">~/.codex/auth.json</code></p>
<pre class="mono"><code>{
<span class="str">"OPENAI_API_KEY"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span>
}</code></pre>
<p>验证</p>
<pre class="mono"><code><span class="cm">$</span> codex exec --sandbox read-only <span class="str">"say hi"</span></code></pre>
</section>
<section id="claude-code" class="docs-section">
<h2>3. Claude Code 接入</h2>
<p>修改 <code class="mono">~/.claude/settings.json</code></p>
<pre class="mono"><code>{
<span class="str">"base_url"</span>: <span class="str">"https://ai.puro.im"</span>,
<span class="str">"api_key"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span>
}</code></pre>
<p class="note">Claude Code 通过 <code class="mono">/v1/messages</code> endpoint 调用 Anthropic 兼容 API</p>
</section>
<section id="curl" class="docs-section">
<h2>4. curl 直连测试</h2>
<p>OpenAI Responses API</p>
<pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/responses \
-H <span class="str">"Authorization: Bearer sk-xxx"</span> \
-H <span class="str">"Content-Type: application/json"</span> \
-d <span class="str">'{"model":"gpt-5.4","input":"hello"}'</span></code></pre>
<p>Anthropic Messages API</p>
<pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/v1/messages \
-H <span class="str">"Authorization: Bearer sk-xxx"</span> \
-H <span class="str">"Content-Type: application/json"</span> \
-H <span class="str">"anthropic-version: 2023-06-01"</span> \
-d <span class="str">'{"model":"claude-opus-4-7","max_tokens":100,"messages":[{"role":"user","content":"hi"}]}'</span></code></pre>
</section>
<section id="models" class="docs-section">
<h2>5. 支持的模型</h2>
<ul class="model-list">
<li><code class="mono">gpt-5.4</code> · OpenAIvia ChatGPT Plus / Codex OAuth</li>
<li><code class="mono">gpt-5.4-codex</code> · OpenAI Codex 专用</li>
<li><code class="mono">claude-opus-4-7</code> · Anthropicvia Claude Pro / Max OAuth</li>
<li><code class="mono">claude-sonnet-4-6</code> · Anthropic</li>
<li><code class="mono">gemini-2.5-pro</code> · Googlevia Code Assist OAuth</li>
<li><code class="mono">gemini-2.5-flash</code> · Google</li>
</ul>
<p class="note">后端 pricing 表实时跟进 <code class="mono">model-price-repo</code>完整清单登录后在 <router-link to="/dashboard">控制台</router-link> 查看</p>
</section>
<section id="feedback" class="docs-section">
<h2>6. 问题反馈</h2>
<p>遇到问题或希望补接某个平台</p>
<div class="callout">
<a href="mailto:admin@puro.im">admin@puro.im</a>
</div>
</section>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.puro-page {
min-height: 100vh;
background: var(--bg-0);
color: var(--text-0);
font-family: var(--font-sans);
position: relative;
}
.container { max-width: 860px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 2; }
.nav {
position: sticky;
top: 0;
z-index: 10;
background: rgba(10,14,26,0.75);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
}
.nav-inner { display: flex; align-items: center; gap: 28px; padding: 16px 24px; max-width: 1120px; margin: 0 auto; }
.brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 16px; }
.brand .hex { color: var(--cyan); font-size: 20px; }
.nav-links { display: flex; gap: 20px; font-size: 14px; color: var(--text-2); }
.nav-links a:hover { color: var(--text-0); }
.nav-cta { margin-left: auto; }
.docs-hero { padding: 80px 24px 40px; text-align: center; }
.docs-hero h1 { font-size: clamp(32px, 4vw, 48px); font-weight: 800; letter-spacing: -0.03em; margin-bottom: 12px; }
.docs-hero .subtitle { color: var(--text-2); font-size: 16px; }
.docs-body { padding-bottom: 80px; }
.docs-section { margin: 48px 0; scroll-margin-top: 80px; }
.docs-section h2 {
font-size: 22px;
font-weight: 700;
margin-bottom: 14px;
color: var(--text-0);
border-bottom: 1px solid var(--border);
padding-bottom: 8px;
}
.docs-section p { color: var(--text-1); font-size: 14px; line-height: 1.8; margin: 12px 0; }
.docs-section .note { color: var(--text-3); font-size: 13px; }
.docs-section code.mono { background: var(--bg-1); padding: 2px 6px; border-radius: var(--r-sm); font-size: 13px; color: var(--cyan); }
.docs-section pre.mono {
background: var(--bg-code);
border: 1px solid var(--border);
border-radius: var(--r-md);
padding: 16px;
font-size: 13px;
line-height: 1.6;
color: var(--text-1);
overflow-x: auto;
margin: 12px 0;
}
.docs-section pre .str { color: var(--cyan); }
.docs-section pre .kw { color: var(--amber); }
.docs-section pre .cm { color: var(--text-3); }
.callout {
padding: 16px 20px;
background: var(--bg-1);
border: 1px solid var(--border-2);
border-left: 3px solid var(--cyan);
border-radius: var(--r-md);
margin: 12px 0;
}
.callout a { color: var(--cyan); font-family: var(--font-mono); font-size: 14px; }
.model-list { list-style: none; padding: 0; }
.model-list li { padding: 8px 0; color: var(--text-1); font-size: 14px; border-bottom: 1px dashed var(--border); }
.model-list li:last-child { border-bottom: none; }
</style>
```
- [ ] **Step 2: 在 router/index.ts 注册 `/docs` public route**
插入(在 `/home` 附近的 Public Routes 区):
```ts
{
path: '/docs',
name: 'Docs',
component: () => import('@/views/docs/DocsView.vue'),
meta: {
requiresAuth: false,
title: 'PURO AI · 文档'
}
},
```
- [ ] **Step 3: 验证**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
http://localhost:5173/docs
Expected:
- 顶部 sticky nav⬢ PURO AI / 首页 / Codex / Claude Code / curl / 登录)
- Hero"快速接入 PURO AI"
- 6 个 numbered sections
- 代码块深色高亮
点 Nav 里的 "Codex" / "Claude Code" / "curl" 锚点会跳到对应段。
停掉 dev server。
- [ ] **Step 4: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/views/docs/DocsView.vue frontend/src/router/index.ts
git commit -m "feat(docs): public DocsView with Codex/Claude Code/curl quickstart
Public route /docs, no auth required. Six sections: get key, codex CLI
config, claude code settings, curl smoke test, supported models, feedback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 11: i18n 整理Login/Register heading
**Files:**
- Modify: `frontend/src/i18n/locales/zh.ts`
- Modify: `frontend/src/views/auth/LoginView.vue`
- Modify: `frontend/src/views/auth/RegisterView.vue`
**Scope**:本期**只把 Login/Register 的 heading 文案**纳入 i18nLanding 和 Docs 整页内容量大、语言只中文,暂不 i18n 化,后续英文版再说)。
- [ ] **Step 1: zh.ts 里查看现有 auth namespace**
Run:
```bash
grep -n "auth:" /Users/mini/Work/dev/sub2api/frontend/src/i18n/locales/zh.ts | head -3
```
- [ ] **Step 2: 在 `auth` namespace 里加 PURO 专用 keys**
Modify `zh.ts`,在 `auth: { ... }` 里面追加(注意保留现有键):
```ts
// PURO AI redesign
puroLoginTitle: '登录',
puroLoginSub: '用你的 PURO AI 账户继续',
puroRegisterTitle: '创建账户',
puroRegisterSub: '5 分钟开始用 PURO AI',
```
- [ ] **Step 3: 把 LoginView / RegisterView 里 hardcoded 的两行 heading 换成 t() 调用**
LoginView
```vue
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">{{ t('auth.puroLoginTitle') }}</h2>
<p class="mt-2 text-sm text-gray-500 dark:text-dark-400">{{ t('auth.puroLoginSub') }}</p>
```
RegisterView 同理用 `auth.puroRegisterTitle` / `auth.puroRegisterSub`
- [ ] **Step 4: 验证 zh.ts 没语法错误**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run type-check 2>&1 | tail -5
```
Expected: 无 TypeScript 错误。
- [ ] **Step 5: dev server 确认 heading 显示正确**
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev
```
http://localhost:5173/login → heading "登录"
http://localhost:5173/register → heading "创建账户"
停掉 dev server。
- [ ] **Step 6: 提交**
```bash
cd /Users/mini/Work/dev/sub2api
git add frontend/src/i18n/locales/zh.ts frontend/src/views/auth/LoginView.vue frontend/src/views/auth/RegisterView.vue
git commit -m "chore(i18n): consolidate PURO auth heading keys into zh.ts
Landing and Docs text remain hardcoded Chinese this cycle (see spec §6
Note 5). Only Login/Register heading strings are moved into i18n.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 12: Build check + manual smoke test
**Files:**
- 无代码改动
- [ ] **Step 1: TypeScript + Vue 类型检查**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run type-check 2>&1 | tail -20
```
Expected: No errors.
- [ ] **Step 2: Lint check如果有**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run lint 2>&1 | tail -20
```
Expected: No errors, 或最多 warnings可接受
- [ ] **Step 3: 产品构建**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run build 2>&1 | tail -20
```
Expected: `✓ built in Xs` · 产物写到 `../backend/internal/web/dist/`
- [ ] **Step 4: 本地预览 prod bundle**
Run:
```bash
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run preview 2>&1 | head -5
```
Expected: preview server 启动(默认 4173
开浏览器做**验收清单走一遍**(对齐 spec §7
- [ ] http://localhost:4173/ — Landing 6 section 全在
- [ ] 窗口缩到 <640px — Nav 菜单隐藏、Footer 2 列、stats 2 列
- [ ] http://localhost:4173/login — 左右分栏narrative 左,表单右
- [ ] 窗口缩到 <900px — narrative 隐藏,表单独占
- [ ] http://localhost:4173/register — 同 login 布局
- [ ] http://localhost:4173/docs — 6 sections锚点跳转工作
- [ ] http://localhost:4173/dashboard — 后台页面**外观不变**(重要!)
- [ ] http://localhost:4173/accounts管理员— 外观不变
停掉 preview。
- [ ] **Step 5: 记录验收结果在 spec checklist**
Modify `docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md`,在第 7 节验收清单里,把跑通的项目 `[ ]` 改成 `[x]`
- [ ] **Step 6: 提交验收清单更新**
```bash
cd /Users/mini/Work/dev/sub2api
git add docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md
git commit -m "docs: check off local acceptance items [CI SKIP]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## Task 13: 合并到 main + CI 部署 + ai.puro.im 实机验证
**Files:**
- 无代码改动
- [ ] **Step 1: push 最新到 gitea**
```bash
cd /Users/mini/Work/dev/sub2api && git push gitea feat/design-landing-auth
```
- [ ] **Step 2: 创建 PR**
```bash
curl -s -u 'purovps:Qweewqzzx1' -X POST https://git.puro.im/api/v1/repos/purovps/sub2api/pulls \
-H 'Content-Type: application/json' \
-d '{
"title": "feat: PURO AI landing + auth + docs redesign",
"body": "Implements docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md\n\nSee plan: docs/superpowers/plans/2026-04-19-puro-ai-landing-auth.md\n\nScope (path B):\n- Landing page at / (anon)\n- AuthLayout split mode\n- LoginView / RegisterView with PURO narrative\n- Public /docs page\n- puro.css design system\n\nNot touched: admin pages, backend, auth logic.",
"head": "feat/design-landing-auth",
"base": "main"
}' | head -c 300
```
Expected: JSON with `"number": N` = PR 号。
- [ ] **Step 3: 合并 PRmanual via Gitea UI 或 API**
Via Gitea UIhttps://git.puro.im/purovps/sub2api/pulls/<N>/merge点 Merge。
或 API把 N 替换成上一步 PR 号):
```bash
curl -s -u 'purovps:Qweewqzzx1' -X POST https://git.puro.im/api/v1/repos/purovps/sub2api/pulls/<N>/merge \
-H 'Content-Type: application/json' \
-d '{"Do":"merge"}'
```
- [ ] **Step 4: 跟 CI 构建(预计 90-180 秒)**
Run:
```bash
ssh vps "for i in \$(seq 1 20); do
running=\$(docker ps --filter 'name=^drone-' --format '{{.Names}} | {{.Image}}' | head -1)
if [ -z \"\$running\" ]; then echo '(build done)'; break; fi
echo \"\$running\"
sleep 10
done
echo '--- container age ---'
docker ps --filter 'name=^sub2api$' --format '{{.Names}} {{.Status}}'"
```
Expected: 最后 `sub2api` 容器 status 显示 `Up (N) seconds` 的很新时间戳。
- [ ] **Step 5: 线上实机验证(对齐 spec §7 完整 checklist**
分别访问(用匿名浏览器 / 无痕窗口):
```
https://ai.puro.im/ → PURO AI Landing
https://ai.puro.im/docs → 精简文档
https://ai.puro.im/login → split layout 登录
https://ai.puro.im/register → split layout 注册
```
登录后访问:
```
https://ai.puro.im/ → 被 guard 跳到 /dashboard
https://ai.puro.im/dashboard → 后台外观不变
```
发一个 AI 请求验证中转没坏:
```bash
curl -sS -X POST https://ai.puro.im/responses \
-H 'Authorization: Bearer sk-d2132de2f0b4c1ab64ef7241a16d254cab483f1f8afd47ad4a89e39cf6e2345a' \
-H 'Content-Type: application/json' \
-d '{"model":"gpt-5.4","input":"say: redesign-live","stream":false}' \
-w '\n-- HTTP %{http_code} / %{time_total}s --\n' | head -c 500
```
Expected: HTTP 200 + "redesign-live" 响应。
- [ ] **Step 6: 打 tag 标记里程碑**
```bash
cd /Users/mini/Work/dev/sub2api
git checkout main && git pull gitea main
git tag -a v0.2.0-puro-redesign -m "feat: PURO AI landing + auth + docs redesign"
git push gitea v0.2.0-puro-redesign
```
---
## 收尾 checklist
本计划执行完spec §7 的 11 个验收项应全部打勾:
- [ ] puro.css 全局引入
- [ ] / (匿名) → Landing
- [ ] / (登录) → Dashboard
- [ ] Landing 6 section 齐
- [ ] Dashboard mockup 纯静态
- [ ] /login split 布局
- [ ] /register split 布局
- [ ] /docs 公开可访问含配置示例
- [ ] OAuth/Turnstile/2FA 正常
- [ ] 后台外观不变
- [ ] CI 通过ai.puro.im 加载正常