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

55 KiB
Raw Blame History

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.vueAppHeader/AppLayout/AppSidebar.vue、backend 全部。

设计决策

  • puro.css 里已有的 primitives class.btn-primary, .card, .input 等)直接用 class 引用,不抽 Vue 组件(减少 scope 膨胀)
  • Tailwind 现有 primaryteal #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

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 行:

import './style.css'

改为:

import './style.css'
import './assets/puro.css'
  • Step 3: 在 index.html <head> 里加 Google Fonts preconnect + link

Modify frontend/index.html,在 <head> 里(<title> 之前)插入:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
  • Step 4: 启动 dev server 验证 CSS 变量可用

Run:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev

Expected: 启动成功(端口默认 5173无 console error。

浏览器打开 http://localhost:5173DevTools Console 执行:

getComputedStyle(document.documentElement).getPropertyValue('--cyan')

Expected: " #22d3ee"

停掉 dev server(保留终端)。

  • Step 5: 扩展 tailwind.config.js 加 puro 色板

Modify frontend/tailwind.config.js,在 theme.extend.colors 里(dark 键后)加:

        // 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:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev

DevTools Console:

// 临时在页面里插入 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: 提交
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:

<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 定义那里)插入:

  {
    path: '/landing-preview',
    name: 'LandingPreview',
    component: () => import('@/views/landing/LandingView.vue'),
    meta: {
      requiresAuth: false,
      title: 'PURO AI'
    }
  },
  • Step 3: 启动 dev server 预览

Run:

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: 提交
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 之前,插入 ② + ③

    <!--  模型墙 -->
    <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> 里追加样式
.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:

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: 提交
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

    <!--  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>
/* 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:

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: 提交
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>"

Files:

  • Modify: frontend/src/views/landing/LandingView.vue

  • Step 1: 在 ⑤ Dashboard section 后、</template> 前加 Footer

    <!--  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>
.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:

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: 提交
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:

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. 把原来的:
  {
    path: '/',
    redirect: '/home'
  },

替换为:

  {
    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:524router.beforeEach((to, _from, next) => { ... })。auth store 暴露 isAuthenticated computed定义见 stores/auth.ts:31)。

authStore.checkAuth() 那行line ~532后、所有其它逻辑之前插入

  // 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 augmentationRouteMeta 增加 redirectIfAuth?: string

import 'vue-router'
declare module 'vue-router' {
  interface RouteMeta {
    redirectIfAuth?: string
  }
}

(若 meta.d.ts 已有 module augmentation追加字段即可。

  • Step 5: 验证未登录 → Landing、登录后 → Dashboard

Run:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev

场景 1匿名DevTools → Application → Clear storage → 全清 localStorage/sessionStorage

场景 2登录态

  • 在 /login 输 admin@puro.im / fJni5YDmEY242owh 登录(见 spec §5 "当前状态 snapshot"
  • 登录后自动跳到 /dashboard既有行为
  • 手动再访问 http://localhost:5173/ → 被 guard 跳到 /dashboard新加的 redirectIfAuth 生效)

停掉 dev server。

  • Step 6: 提交
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:

wc -l /Users/mini/Work/dev/sub2api/frontend/src/components/layout/AuthLayout.vue

Expected: ~90-120 行。读它确认 slot 有默认 + #footer

  • Step 2: 改 AuthLayout.vue template 为可选左右分栏

把整个 <template> 换成:

<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"> 顶部追加:

import { useSlots } from 'vue'
const slots = useSlots()
const hasNarrative = computed(() => !!slots.narrative)

computed 如果还没 import 就加上)

  • Step 4: 在 <style scoped> 里加 split layout 样式

把现有 <style scoped>(或在文件末新增)补入:

.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:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev

http://localhost:5173/forgot-password这个页面用 AuthLayout 但不会传 narrative

Expected: 居中品牌卡片,看起来像以前。

停掉 dev server。

  • Step 6: 提交
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 行附近)后,紧跟着加:

    <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 改为:

        <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 内部样式
<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:

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: 提交
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:

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 那行替换为:

        <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:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev

http://localhost:5173/register

Expected: 同 Login 的 split layout右侧表单是注册表单邮箱 + 密码 + 确认密码 + Turnstile + CTAheading "创建账户"。

停掉 dev server。

  • Step 6: 提交
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:

<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 区):

  {
    path: '/docs',
    name: 'Docs',
    component: () => import('@/views/docs/DocsView.vue'),
    meta: {
      requiresAuth: false,
      title: 'PURO AI · 文档'
    }
  },
  • Step 3: 验证

Run:

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: 提交
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:

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: { ... } 里面追加(注意保留现有键):

    // PURO AI redesign
    puroLoginTitle: '登录',
    puroLoginSub: '用你的 PURO AI 账户继续',
    puroRegisterTitle: '创建账户',
    puroRegisterSub: '5 分钟开始用 PURO AI',
  • Step 3: 把 LoginView / RegisterView 里 hardcoded 的两行 heading 换成 t() 调用

LoginView

<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:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run type-check 2>&1 | tail -5

Expected: 无 TypeScript 错误。

  • Step 5: dev server 确认 heading 显示正确
cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev

http://localhost:5173/login → heading "登录" http://localhost:5173/register → heading "创建账户"

停掉 dev server。

  • Step 6: 提交
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:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run type-check 2>&1 | tail -20

Expected: No errors.

  • Step 2: Lint check如果有

Run:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run lint 2>&1 | tail -20

Expected: No errors, 或最多 warnings可接受

  • Step 3: 产品构建

Run:

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:

cd /Users/mini/Work/dev/sub2api/frontend && pnpm run preview 2>&1 | head -5

Expected: preview server 启动(默认 4173

开浏览器做验收清单走一遍(对齐 spec §7

停掉 preview。

  • Step 5: 记录验收结果在 spec checklist

Modify docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md,在第 7 节验收清单里,把跑通的项目 [ ] 改成 [x]

  • Step 6: 提交验收清单更新
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

cd /Users/mini/Work/dev/sub2api && git push gitea feat/design-landing-auth
  • Step 2: 创建 PR
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//merge点 Merge。

或 API把 N 替换成上一步 PR 号):

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:

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 请求验证中转没坏:

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 标记里程碑
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 加载正常