Files
sub2api/docs/design-drafts/v2/Login.html
puro design 3a16b3ecde
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: archive Claude Design v2 output [CI SKIP]
10 HTML pages + puro.css + HANDOFF.md + 2 images (~810KB total).
Reference artifacts for Stage 3 Vue 3 translation.

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

352 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="dark">
<title>登录 — PURO AI</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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">
<link rel="stylesheet" href="puro.css">
<style>
body { min-height: 100vh; overflow-x: hidden; }
.split {
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 100vh;
position: relative;
z-index: 1;
}
/* ============ LEFT (NARRATIVE) ============ */
.narrative {
position: relative;
overflow: hidden;
padding: 48px 56px;
display: flex;
flex-direction: column;
justify-content: space-between;
border-right: 1px solid var(--border);
background: linear-gradient(135deg, rgba(34,211,238,0.04), transparent 60%), rgba(15, 23, 42, 0.3);
}
.narrative::before {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(600px 400px at 20% 20%, rgba(34,211,238,0.08), transparent 60%),
radial-gradient(500px 400px at 90% 80%, rgba(168,85,247,0.06), transparent 60%);
pointer-events: none;
}
.narrative-inner { position: relative; display: flex; flex-direction: column; gap: 28px; z-index: 1; }
.brand-top {
display: inline-flex; align-items: center; gap: 10px;
font-weight: 700; font-size: 15px; letter-spacing: -0.01em; color: var(--text-0);
}
.brand-top svg { width: 22px; height: 22px; color: var(--cyan); }
.n-kicker { font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.1em; color: var(--cyan); }
.n-headline {
font-size: 52px; font-weight: 800; letter-spacing: -0.03em; line-height: 1.05;
display: flex; align-items: baseline; flex-wrap: wrap; gap: 14px;
}
.n-headline .amber { color: var(--amber); }
.n-headline .cyan { color: var(--cyan); }
.n-headline .arrow { font-size: 38px; color: var(--text-3); font-weight: 400; }
.n-sub { color: var(--text-2); font-size: 15px; line-height: 1.75; max-width: 440px; }
.n-sub .line { display: block; }
.n-sub .puro { color: var(--text-0); font-weight: 600; }
.route-demo {
margin-top: 10px;
font-family: var(--font-mono);
font-size: 12px;
background: rgba(2, 6, 23, 0.6);
border: 1px solid var(--border);
border-radius: var(--r-md);
padding: 18px 22px;
max-width: 440px;
}
.route-demo .row { display: flex; gap: 20px; padding: 4px 0; align-items: center; }
.route-demo .k { color: var(--text-3); min-width: 70px; }
.route-demo .v { color: var(--text-0); }
.route-demo .pill-inline { padding: 2px 8px; border-radius: 4px; background: rgba(34,211,238,0.08); border: 1px solid rgba(34,211,238,0.25); color: var(--cyan); }
.route-demo .pill-inline.amber { background: rgba(251,191,36,0.08); border-color: rgba(251,191,36,0.25); color: var(--amber); }
.route-demo .pill-inline.green { background: rgba(52,211,153,0.08); border-color: rgba(52,211,153,0.25); color: var(--green); }
.route-demo .dot-g { width: 6px; height: 6px; border-radius: 50%; background: var(--green); display: inline-block; margin-right: 6px; box-shadow: 0 0 0 3px rgba(52,211,153,0.15); }
.n-bottom {
position: relative; z-index: 1;
font-family: var(--font-mono); font-size: 11px; color: var(--text-3);
display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
}
.n-bottom .sep { color: var(--border-2); }
.n-bottom .live { color: var(--green); display: inline-flex; align-items: center; gap: 6px; }
.n-bottom .live .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--green); box-shadow: 0 0 6px var(--green); }
/* ============ RIGHT (FORM) ============ */
.form-side {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 48px 56px;
position: relative;
}
.back-home {
position: absolute; top: 32px; right: 32px;
font-family: var(--font-mono); font-size: 12px; color: var(--text-3);
display: inline-flex; align-items: center; gap: 6px;
}
.back-home:hover { color: var(--text-0); }
.form-card { width: 100%; max-width: 400px; }
.form-card h1 { font-size: 32px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 6px; }
.form-card .sub { color: var(--text-2); font-size: 14px; margin-bottom: 32px; }
.field { margin-bottom: 16px; }
.field label { display: block; font-size: 12px; color: var(--text-2); font-weight: 500; margin-bottom: 8px; letter-spacing: 0.01em; }
.input-wrap {
position: relative;
display: flex; align-items: center;
background: rgba(2, 6, 23, 0.4);
border: 1px solid var(--border-2);
border-radius: var(--r-md);
transition: all .15s;
}
.input-wrap:focus-within { border-color: var(--cyan); background: rgba(34, 211, 238, 0.04); box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.1); }
.input-wrap .icon { padding: 0 12px; color: var(--text-3); flex-shrink: 0; display: flex; }
.input-wrap input {
flex: 1; background: none; border: none; outline: none;
font-family: inherit; color: var(--text-0); font-size: 14px;
padding: 12px 14px 12px 0;
}
.input-wrap input::placeholder { color: var(--text-3); }
.eye { display: flex; padding: 10px 12px; color: var(--text-3); background: none; border: none; cursor: pointer; }
.eye:hover { color: var(--text-1); }
.field-meta { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; }
.check { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-2); cursor: pointer; user-select: none; }
.check input { position: absolute; opacity: 0; pointer-events: none; }
.check .box { width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--border-2); background: rgba(2, 6, 23, 0.4); position: relative; flex-shrink: 0; }
.check input:checked ~ .box { background: var(--cyan); border-color: var(--cyan); }
.check input:checked ~ .box::after {
content: ""; position: absolute; left: 4px; top: 1px;
width: 4px; height: 8px; border: solid #042f2e; border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.forgot { font-size: 13px; color: var(--text-3); }
.forgot:hover { color: var(--cyan); }
/* Submit button — explicit styles to ensure rendering */
.submit-btn {
width: 100%;
padding: 14px 20px !important;
font-size: 14px !important;
margin-top: 8px;
position: relative;
background: var(--cyan) !important;
color: #042f2e !important;
border: 1px solid var(--cyan) !important;
font-weight: 600 !important;
z-index: 2;
}
.submit-btn:hover { background: var(--cyan-2) !important; }
.submit-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.submit-btn .spinner { width: 14px; height: 14px; border: 2px solid rgba(0,0,0,0.2); border-top-color: #042f2e; border-radius: 50%; animation: spin .7s linear infinite; display: none; }
.submit-btn.loading .spinner { display: inline-block; }
.submit-btn.loading .label { opacity: 0.5; }
@keyframes spin { to { transform: rotate(360deg); } }
.ghost-btn { width: 100%; padding: 14px 20px !important; font-size: 14px !important; font-weight: 500 !important; }
.divider {
display: flex; align-items: center; gap: 14px;
margin: 24px 0;
color: var(--text-3); font-size: 11px;
font-family: var(--font-mono); letter-spacing: 0.15em;
}
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.linuxdo-ico {
width: 18px; height: 18px; border-radius: 3px;
background: linear-gradient(135deg, #f0a030, #f05050);
display: inline-flex; align-items: center; justify-content: center;
font-size: 10px; font-weight: 800; color: white;
}
.foot { margin-top: 24px; text-align: center; font-size: 13px; color: var(--text-2); }
.foot a { color: var(--cyan); font-weight: 500; }
.foot a:hover { color: var(--cyan-2); }
.legal { margin-top: 20px; text-align: center; font-size: 11px; color: var(--text-3); font-family: var(--font-mono); }
.legal a { color: var(--text-2); border-bottom: 1px dashed var(--border-2); }
.legal a:hover { color: var(--cyan); border-color: var(--cyan); }
@media (max-width: 900px) {
.split { grid-template-columns: 1fr; }
.narrative { padding: 32px 28px; border-right: none; border-bottom: 1px solid var(--border); }
.n-headline { font-size: 36px; }
.route-demo, .n-bottom { display: none; }
.form-side { padding: 40px 28px; }
.back-home { top: 20px; right: 20px; }
}
</style>
</head>
<body>
<div class="bg-glow"></div>
<div class="grain"></div>
<div class="split">
<!-- LEFT: NARRATIVE -->
<section class="narrative">
<div class="narrative-inner">
<a href="Landing.html" class="brand-top">
<svg viewBox="0 0 24 24" fill="none">
<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z" stroke="currentColor" stroke-width="1.8" fill="rgba(34, 211, 238, 0.08)"/>
<path d="M12 7L17 9.5V14.5L12 17L7 14.5V9.5L12 7Z" fill="currentColor"/>
</svg>
PURO AI
</a>
<div>
<div class="n-kicker">// 你的订阅,已经付过钱了</div>
<h1 class="n-headline" style="margin-top: 12px;">
<span class="amber">N</span> 个订阅
<span class="arrow"></span>
<span class="cyan">1</span> 个 key
</h1>
</div>
<div class="n-sub">
<span class="line">省去切换账号的繁琐,</span>
<span class="line">省去为多个高昂订阅重复买单。</span>
<span class="line" style="margin-top: 8px;"><span class="puro">PURO</span>(纯粹)—— 让 AI 调用回归本质。</span>
</div>
<div class="route-demo">
<div class="row"><span class="k">POST</span><span class="v">/v1/chat/completions</span></div>
<div class="row"><span class="k">model</span><span class="pill-inline">claude-sonnet-4-5</span></div>
<div class="row"><span class="k">route →</span><span class="pill-inline amber">claude-pool-03</span></div>
<div class="row"><span class="k">status</span><span><span class="dot-g"></span><span style="color:var(--green)">200</span><span style="color:var(--text-3); margin:0 6px;">·</span>213ms<span style="color:var(--text-3); margin:0 6px;">·</span>42 tok</span></div>
</div>
</div>
<div class="n-bottom">
<span>Claude</span><span class="sep">·</span>
<span>ChatGPT</span><span class="sep">·</span>
<span>Codex</span><span class="sep">·</span>
<span>Gemini</span>
<span class="sep">|</span>
<span class="live"><span class="dot"></span>ai.puro.im · operational</span>
</div>
</section>
<!-- RIGHT: FORM -->
<section class="form-side">
<a href="Landing.html" class="back-home">← 返回首页</a>
<form class="form-card" id="login-form" autocomplete="off" novalidate>
<h1>登录</h1>
<p class="sub">用你的 PURO AI 账户继续</p>
<div class="field">
<label for="email">邮箱</label>
<div class="input-wrap">
<span class="icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="5" width="18" height="14" rx="2"/>
<path d="M3 7l9 6 9-6"/>
</svg>
</span>
<input type="email" id="email" name="email" placeholder="you@puro.im" required>
</div>
</div>
<div class="field">
<label for="password">密码</label>
<div class="input-wrap">
<span class="icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="11" width="16" height="10" rx="2"/>
<path d="M8 11V7a4 4 0 0 1 8 0v4"/>
</svg>
</span>
<input type="password" id="password" name="password" placeholder="••••••••" required>
<button type="button" class="eye" id="toggle-pw" aria-label="切换显示密码">
<svg id="eye-open" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
<svg id="eye-closed" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="display:none;">
<path d="M9.88 9.88A3 3 0 0 0 14.12 14.12M10.73 5.08A10.94 10.94 0 0 1 12 5c6.5 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68M6.61 6.61A13.53 13.53 0 0 0 2 12s3.5 7 10 7a9.77 9.77 0 0 0 5.39-1.61M2 2l20 20"/>
</svg>
</button>
</div>
<div class="field-meta">
<label class="check">
<input type="checkbox" checked>
<span class="box"></span>
记住我
</label>
<a href="#" class="forgot">忘记密码?</a>
</div>
</div>
<button type="submit" class="btn btn-primary submit-btn" id="submit-btn">
<span class="spinner"></span>
<span class="label">登录 →</span>
</button>
<div class="divider">OR</div>
<button type="button" class="btn btn-ghost ghost-btn">
<span class="linuxdo-ico">L</span>
使用 LinuxDO 登录
</button>
<div class="foot">
没有账户?<a href="Register.html">注册</a>
</div>
<div class="legal">
登录即表示你同意 <a href="#">服务条款</a><a href="#">隐私政策</a>
</div>
</form>
</section>
</div>
<script>
const pw = document.getElementById('password');
const toggle = document.getElementById('toggle-pw');
const eyeOpen = document.getElementById('eye-open');
const eyeClosed = document.getElementById('eye-closed');
toggle.addEventListener('click', () => {
const show = pw.type === 'password';
pw.type = show ? 'text' : 'password';
eyeOpen.style.display = show ? 'none' : 'block';
eyeClosed.style.display = show ? 'block' : 'none';
});
const form = document.getElementById('login-form');
const btn = document.getElementById('submit-btn');
form.addEventListener('submit', (e) => {
e.preventDefault();
if (!form.email.value || !form.password.value) return;
btn.classList.add('loading');
btn.disabled = true;
setTimeout(() => {
btn.classList.remove('loading');
btn.disabled = false;
btn.querySelector('.label').textContent = '✓ 登录成功';
btn.style.background = 'var(--green)';
setTimeout(() => { window.location.href = 'Dashboard.html'; }, 800);
}, 1000);
});
</script>
</body>
</html>