9de3a6c743
- auth_screen.dart 整体重排:暖色径向光晕背景 + 居中卡片;hero 用 clay 渐变 app-icon(带光晕)+ 品牌 + 标语;胶囊分段 tab(滑块)替下划线;输入框聚焦态 (accent 边 + ring 光环);主按钮微光;进入错落淡入动效。全部走 token。 - 注册第二步:已验证邮箱胶囊 + 「改邮箱」返回 + 设密码;验证码框无占位点。 - 逻辑零回归:登录/注册两段式、发码、邮箱预填、密码显隐、错误提示全保留。 - design/preview/auth_redesign.html:HTML 视觉原型(评审用,不进构建)。 flutter analyze 0 error;114 tests passed(auth 不在 golden 集,无 golden 变化)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
247 lines
14 KiB
HTML
247 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<!-- 穿山甲 · 登录/注册页重设计原型(阶段 A 视觉评审用,非 app 构建产物) -->
|
|
<!-- 用真实 Pangolin token + Sora/Manrope + 真品牌 mark;右上角可切 明暗 / 中英 / 登录注册 -->
|
|
<html lang="zh" data-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>穿山甲 · 登录/注册 重设计</title>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@500;600;700&family=Manrope:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
|
|
:root{
|
|
--clay-50:#FAF3ED;--clay-100:#F2E2D4;--clay-200:#E6C7AC;--clay-300:#D9A982;--clay-400:#CC8B5C;
|
|
--clay-500:#B96A3D;--clay-600:#9E5630;--clay-700:#7E4426;--clay-800:#5E331D;--clay-900:#3D2213;
|
|
--sand-50:#FAF8F4;--sand-100:#F2EEE7;--sand-200:#E6DFD3;--sand-300:#D2C8B8;--sand-400:#B0A491;
|
|
--sand-500:#8C8270;--sand-600:#6B6253;--sand-700:#4E4940;--sand-800:#2E2A24;--sand-900:#1F1C18;--sand-950:#14110E;
|
|
--green-500:#5B8C5A;--red-500:#C0533B;
|
|
--bg:var(--sand-50);--bg-subtle:var(--sand-100);--surface:#fff;
|
|
--fg1:var(--sand-900);--fg2:var(--sand-600);--fg3:var(--sand-500);--fg-on-accent:#fff;
|
|
--accent:var(--clay-500);--accent-hover:var(--clay-600);--accent-subtle:var(--clay-50);--accent-border:var(--clay-200);
|
|
--border:var(--sand-200);--border-strong:var(--sand-300);--ring:rgba(185,106,61,.35);
|
|
--danger:var(--red-500);--danger-subtle:#F6E1DA;
|
|
--shadow-md:0 4px 14px rgba(45,30,20,.08);--shadow-lg:0 12px 32px rgba(45,30,20,.12);
|
|
--glow:rgba(185,106,61,.14);--icon-glow:rgba(185,106,61,.30);
|
|
--font-display:'Sora','Noto Sans SC',system-ui,sans-serif;
|
|
--font-sans:'Manrope','Noto Sans SC',system-ui,sans-serif;
|
|
--font-mono:'JetBrains Mono',ui-monospace,monospace;
|
|
}
|
|
[data-theme="dark"]{
|
|
--bg:var(--sand-950);--bg-subtle:var(--sand-900);--surface:#221E19;
|
|
--fg1:#F4EFE8;--fg2:#B6AC9C;--fg3:#897F6F;--fg-on-accent:#1F1C18;
|
|
--accent:var(--clay-400);--accent-hover:var(--clay-300);--accent-subtle:rgba(204,139,92,.14);--accent-border:rgba(204,139,92,.30);
|
|
--border:rgba(242,238,231,.10);--border-strong:rgba(242,238,231,.18);--ring:rgba(204,139,92,.45);
|
|
--danger:#D4715A;--danger-subtle:rgba(212,113,90,.16);
|
|
--shadow-md:0 4px 14px rgba(0,0,0,.45);--shadow-lg:0 12px 32px rgba(0,0,0,.5);
|
|
--glow:rgba(204,139,92,.16);--icon-glow:rgba(204,139,92,.32);
|
|
}
|
|
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
html,body{height:100%}
|
|
body{
|
|
font-family:var(--font-sans);background:var(--bg);color:var(--fg1);
|
|
display:grid;place-items:center;padding:24px;position:relative;overflow:hidden;
|
|
transition:background-color .3s ease;
|
|
}
|
|
/* 层1:暖色径向光晕 */
|
|
.atmosphere{position:fixed;inset:0;pointer-events:none;
|
|
background:
|
|
radial-gradient(60% 46% at 50% -4%, var(--glow), transparent 70%),
|
|
radial-gradient(40% 30% at 84% 92%, var(--glow), transparent 70%);}
|
|
|
|
/* 右上角控件 */
|
|
.controls{position:fixed;top:18px;right:18px;display:flex;gap:8px;z-index:10}
|
|
.controls button{
|
|
font-family:var(--font-sans);font-size:13px;font-weight:600;color:var(--fg2);
|
|
background:var(--surface);border:1px solid var(--border);border-radius:999px;
|
|
padding:7px 12px;cursor:pointer;box-shadow:var(--shadow-md);transition:.2s}
|
|
.controls button:hover{color:var(--accent);border-color:var(--accent-border)}
|
|
|
|
/* 层2:居中内容 */
|
|
.auth{width:100%;max-width:420px;position:relative;z-index:1}
|
|
@keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
|
|
.hero,.card,.tos{animation:fadeUp .55s cubic-bezier(.22,1,.36,1) both}
|
|
.hero{animation-delay:.05s}.card{animation-delay:.18s}.tos{animation-delay:.32s}
|
|
|
|
/* Hero */
|
|
.hero{text-align:center;margin-bottom:26px}
|
|
.app-icon{width:64px;height:64px;border-radius:20px;margin:0 auto 16px;
|
|
display:grid;place-items:center;
|
|
background:linear-gradient(135deg,var(--clay-500),var(--clay-700));
|
|
box-shadow:0 0 44px 4px var(--icon-glow), var(--shadow-lg);}
|
|
.app-icon svg{width:42px;height:42px}
|
|
.brand{font-family:var(--font-display);font-size:30px;font-weight:700;letter-spacing:-.02em;line-height:1.15}
|
|
.tagline{font-size:14px;color:var(--accent);font-weight:600;margin-top:6px;letter-spacing:.01em}
|
|
|
|
/* 卡片 */
|
|
.card{background:var(--surface);border:1px solid var(--border);border-radius:28px;
|
|
padding:26px 24px 24px;box-shadow:var(--shadow-md),var(--shadow-lg);}
|
|
|
|
/* 胶囊分段 tab */
|
|
.tabs{position:relative;display:flex;background:var(--bg-subtle);border-radius:999px;padding:4px;margin-bottom:22px}
|
|
.tabs .pill{position:absolute;top:4px;left:4px;width:calc(50% - 4px);height:calc(100% - 8px);
|
|
background:var(--accent);border-radius:999px;box-shadow:var(--shadow-md);
|
|
transition:transform .26s cubic-bezier(.22,1,.36,1)}
|
|
.tabs.reg .pill{transform:translateX(100%)}
|
|
.seg{flex:1;position:relative;z-index:1;background:none;border:0;cursor:pointer;
|
|
font-family:var(--font-sans);font-size:15px;font-weight:600;color:var(--fg3);
|
|
padding:9px 0;border-radius:999px;transition:color .2s}
|
|
.seg.on{color:var(--fg-on-accent)}
|
|
|
|
/* 错误条(示意,默认隐藏) */
|
|
.err{display:none;align-items:center;gap:8px;background:var(--danger-subtle);
|
|
border:1px solid var(--danger);border-radius:10px;padding:10px 14px;margin-bottom:14px;
|
|
color:var(--danger);font-size:14px}
|
|
.err.show{display:flex}
|
|
|
|
/* 表单 */
|
|
.form{display:none;flex-direction:column;gap:14px}
|
|
.form.on{display:flex}
|
|
.flabel{font-size:12px;font-weight:600;color:var(--fg2);margin-bottom:-6px}
|
|
.field{display:flex;align-items:center;gap:10px;background:var(--surface);
|
|
border:1.5px solid var(--border-strong);border-radius:10px;padding:0 14px;transition:.2s}
|
|
.field:focus-within{border-color:var(--accent);box-shadow:0 0 0 4px var(--ring)}
|
|
.field svg{width:18px;height:18px;color:var(--fg3);flex:none}
|
|
.field input{flex:1;border:0;outline:0;background:none;color:var(--fg1);
|
|
font-family:var(--font-sans);font-size:15px;padding:13px 0}
|
|
.field input::placeholder{color:var(--fg3)}
|
|
.field .eye,.field .send{background:none;border:0;cursor:pointer;color:var(--fg3);display:grid;place-items:center}
|
|
.field .send{color:var(--accent);font-weight:700;font-size:13px;font-family:var(--font-sans)}
|
|
.field input.code{font-family:var(--font-mono);letter-spacing:6px}
|
|
.forgot{align-self:flex-end;color:var(--accent);font-size:14px;font-weight:600;text-decoration:none;margin-top:-4px}
|
|
.primary{margin-top:6px;background:var(--accent);color:var(--fg-on-accent);border:0;border-radius:999px;
|
|
font-family:var(--font-sans);font-size:16px;font-weight:700;padding:14px;cursor:pointer;
|
|
box-shadow:0 6px 20px var(--icon-glow);transition:.2s}
|
|
.primary:hover{background:var(--accent-hover);transform:translateY(-1px)}
|
|
.sent{display:flex;align-items:center;gap:6px;color:var(--green-500);font-size:12px;margin-top:-6px}
|
|
.verified{display:flex;align-items:center;gap:8px;background:var(--bg-subtle);border-radius:10px;padding:11px 14px}
|
|
.verified svg{width:16px;height:16px;color:var(--green-500);flex:none}
|
|
.verified span{flex:1;font-weight:600;color:var(--fg1);font-size:14px}
|
|
.changemail{color:var(--accent);font-size:13px;font-weight:600;cursor:pointer;text-decoration:none}
|
|
|
|
.tos{text-align:center;color:var(--fg3);font-size:12px;margin-top:18px;line-height:1.6}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="atmosphere"></div>
|
|
<div class="controls">
|
|
<button id="lang">EN</button>
|
|
<button id="theme">☾ 深色</button>
|
|
</div>
|
|
|
|
<main class="auth">
|
|
<div class="hero">
|
|
<div class="app-icon">
|
|
<svg viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="穿山甲">
|
|
<g transform="translate(96,0) scale(-1,1)">
|
|
<path d="M16 59 Q17 51 26 50 Q34 31 45 30 Q55 30 62 37 Q69 41 75 45 Q85 49 89 44 Q92 50 84 52 Q75 53 67 53 Q66 62 60 62 L56 62 Q54 55 49 55 Q47 62 37 62 L33 62 Q31 56 26 56 Q20 59 16 59 Z" fill="#F4EFE8"/>
|
|
<g stroke="#9E5630" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.7">
|
|
<path d="M26 50 Q34 43 42 50"/><path d="M36 48 Q44 41 52 48"/><path d="M46 48 Q54 41 62 49"/>
|
|
</g>
|
|
<circle cx="23" cy="55" r="2.2" fill="#9E5630"/>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
<div class="brand" data-zh="穿山甲" data-en="Pangolin">穿山甲</div>
|
|
<div class="tagline" data-zh="极速畅连 · 安全稳定" data-en="Fast · Secure · Reliable">极速畅连 · 安全稳定</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="tabs" id="tabs">
|
|
<span class="pill"></span>
|
|
<button class="seg on" data-zh="登录" data-en="Sign in" onclick="setMode('login')">登录</button>
|
|
<button class="seg" data-zh="注册" data-en="Sign up" onclick="setMode('reg')">注册</button>
|
|
</div>
|
|
|
|
<div class="err"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg><span>邮箱或密码不正确</span></div>
|
|
|
|
<!-- 登录 -->
|
|
<form class="form on" id="login" onsubmit="return false">
|
|
<label class="flabel" data-zh="邮箱" data-en="Email">邮箱</label>
|
|
<div class="field">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/></svg>
|
|
<input type="email" placeholder="you@mail.com" value="me@pangolin.app">
|
|
</div>
|
|
<label class="flabel" data-zh="密码" data-en="Password">密码</label>
|
|
<div class="field">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
|
<input type="password" placeholder="••••••••" value="password">
|
|
<button type="button" class="eye" onclick="toggleEye(this)">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 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>
|
|
</button>
|
|
</div>
|
|
<a class="forgot" data-zh="忘记密码?" data-en="Forgot?">忘记密码?</a>
|
|
<button class="primary" data-zh="登 录" data-en="Sign in">登 录</button>
|
|
</form>
|
|
|
|
<!-- 注册(step 0:邮箱→发码→验证码→下一步) -->
|
|
<form class="form" id="reg" onsubmit="return false">
|
|
<label class="flabel" data-zh="邮箱" data-en="Email">邮箱</label>
|
|
<div class="field">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/></svg>
|
|
<input type="email" placeholder="you@mail.com">
|
|
<button type="button" class="send" data-zh="发送验证码" data-en="Send code">发送验证码</button>
|
|
</div>
|
|
<div class="sent"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg><span data-zh="验证码已发送" data-en="Code sent">验证码已发送</span></div>
|
|
<label class="flabel" data-zh="验证码" data-en="Code">验证码</label>
|
|
<div class="field">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/><path d="m9 12 2 2 4-4"/></svg>
|
|
<input class="code" inputmode="numeric" maxlength="6" placeholder="">
|
|
</div>
|
|
<button class="primary" data-zh="下一步" data-en="Continue" onclick="regNext()">下一步</button>
|
|
</form>
|
|
|
|
<!-- 注册 step 1:已验证邮箱 + 设密码 -->
|
|
<form class="form" id="reg2" onsubmit="return false">
|
|
<div class="verified">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/><path d="m9 12 2 2 4-4"/></svg>
|
|
<span>you@mail.com</span>
|
|
<a class="changemail" data-zh="改邮箱" data-en="Change" onclick="regBack()">改邮箱</a>
|
|
</div>
|
|
<label class="flabel" data-zh="设置密码" data-en="Set password">设置密码</label>
|
|
<div class="field">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
|
<input type="password" placeholder="•••••• (≥6 位)">
|
|
<button type="button" class="eye" onclick="toggleEye(this)">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 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>
|
|
</button>
|
|
</div>
|
|
<button class="primary" data-zh="创建账号" data-en="Create account">创建账号</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="tos" data-zh="继续即代表同意 服务条款 · 隐私政策" data-en="By continuing you agree to the Terms · Privacy">继续即代表同意 服务条款 · 隐私政策</div>
|
|
</main>
|
|
|
|
<script>
|
|
function setMode(m){
|
|
const reg = m==='reg';
|
|
document.getElementById('tabs').classList.toggle('reg',reg);
|
|
document.querySelectorAll('.seg').forEach((b,i)=>b.classList.toggle('on', i===(reg?1:0)));
|
|
document.getElementById('login').classList.toggle('on',!reg);
|
|
document.getElementById('reg').classList.toggle('on',reg);
|
|
document.getElementById('reg2').classList.remove('on');
|
|
}
|
|
function regNext(){document.getElementById('reg').classList.remove('on');document.getElementById('reg2').classList.add('on');}
|
|
function regBack(){document.getElementById('reg2').classList.remove('on');document.getElementById('reg').classList.add('on');}
|
|
function toggleEye(btn){
|
|
const inp = btn.parentElement.querySelector('input');
|
|
inp.type = inp.type==='password' ? 'text' : 'password';
|
|
}
|
|
document.getElementById('theme').onclick=function(){
|
|
const dark = document.documentElement.getAttribute('data-theme')==='dark';
|
|
document.documentElement.setAttribute('data-theme', dark?'light':'dark');
|
|
this.textContent = dark ? '☾ 深色' : '☀ 浅色';
|
|
};
|
|
let en=false;
|
|
document.getElementById('lang').onclick=function(){
|
|
en=!en; this.textContent = en?'中':'EN';
|
|
document.querySelectorAll('[data-zh]').forEach(el=>{
|
|
el.textContent = en ? el.getAttribute('data-en') : el.getAttribute('data-zh');
|
|
});
|
|
document.documentElement.lang = en?'en':'zh';
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|