247 lines
11 KiB
HTML
247 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||
<title>宇森 · 登录</title>
|
||
<link rel="icon" type="image/png" sizes="48x48" href="assets/ui/logo_planet_48.png?v=20260723">
|
||
<link rel="icon" type="image/png" sizes="256x256" href="assets/ui/logo_planet_256.png?v=20260723">
|
||
<style>
|
||
:root{
|
||
--bg-0:#080c1a; --bg-1:#0e1730; --bg-2:#15213f;
|
||
--violet:#8b5cf6; --magenta:#c77dff; --cyan:#4cc9f0; --gold:#e7b85c;
|
||
--text:#e9edf6; --text-dim:#9aa6c4;
|
||
--card:rgba(255,255,255,.05); --card-bd:rgba(255,255,255,.12);
|
||
--glow:0 0 22px rgba(139,92,246,.30);
|
||
}
|
||
*{box-sizing:border-box;margin:0;padding:0;}
|
||
html,body{height:100%;}
|
||
body{
|
||
font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC",system-ui,-apple-system,sans-serif;color:var(--text);
|
||
background:#000;
|
||
display:flex;align-items:center;justify-content:center;min-height:100dvh;overflow:hidden;position:relative;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||
}
|
||
/* 关闭装饰光斑,避免遮挡背景轮播 */
|
||
body::before,body::after{display:none;}
|
||
|
||
/* 登录页背景(固定单图 login_bg,原图直出,不加特效) */
|
||
.bg-slides{position:fixed;inset:0;z-index:0;background:url('assets/slides/login_bg.webp') center/cover no-repeat;}
|
||
.bg-slides{position:fixed;inset:0;z-index:0;background:url('assets/slides/login_bg.png') center/cover no-repeat;}
|
||
|
||
.wrap{position:relative;z-index:2;width:min(420px,92vw);}
|
||
.brand{text-align:center;margin-bottom:26px;}
|
||
.brand .cn{font-family:"PingFang SC","Microsoft YaHei",system-ui,sans-serif;font-weight:900;font-size:42px;letter-spacing:8px;color:#fff;text-shadow:0 0 18px rgba(139,92,246,.6);}
|
||
.brand .en{font-size:16px;color:var(--magenta);letter-spacing:5px;margin-top:8px;font-weight:600;text-shadow:0 1px 6px rgba(0,0,0,.7);}
|
||
.brand .tag{font-size:13px;color:var(--text-dim);margin-top:10px;}
|
||
|
||
.card{background:var(--card);border:1px solid var(--card-bd);border-radius:18px;padding:26px 26px 30px;backdrop-filter:blur(10px);box-shadow:var(--glow);}
|
||
.tabs{display:flex;gap:6px;margin-bottom:20px;background:rgba(8,12,26,.5);border-radius:12px;padding:5px;}
|
||
.tabs button{flex:1;font-family:inherit;font-size:14px;font-weight:500;color:var(--text-dim);background:transparent;border:none;padding:9px;border-radius:9px;cursor:pointer;transition:.2s;}
|
||
.tabs button.active{color:#fff;background:rgba(139,92,246,.28);box-shadow:var(--glow);}
|
||
|
||
.field{margin-bottom:14px;}
|
||
.field label{display:block;font-size:12px;color:var(--text-dim);margin-bottom:6px;letter-spacing:1px;}
|
||
.field input{width:100%;background:rgba(8,12,26,.6);border:1px solid var(--card-bd);border-radius:10px;color:var(--text);padding:11px 13px;font-family:inherit;font-size:14px;outline:none;transition:.2s;}
|
||
.field input:focus{border-color:var(--violet);box-shadow:0 0 0 3px rgba(139,92,246,.18);}
|
||
.pw-wrap{position:relative;}
|
||
.pw-wrap input{padding-right:40px;}
|
||
.pw-toggle,.random-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:transparent;border:none;color:var(--text-dim);cursor:pointer;font-size:16px;line-height:1;padding:4px 6px;border-radius:6px;transition:.2s;}
|
||
.pw-toggle:hover,.random-btn:hover{color:#fff;background:rgba(255,255,255,.08);}
|
||
.random-btn{font-size:15px;}
|
||
|
||
.submit{width:100%;margin-top:6px;font-size:15px;font-weight:700;color:#0b1020;background:linear-gradient(120deg,var(--gold),#f0d49a);padding:12px;border-radius:11px;cursor:pointer;border:none;letter-spacing:2px;transition:.22s;box-shadow:0 0 18px rgba(231,184,92,.35);}
|
||
.submit:hover{filter:brightness(1.08);}
|
||
.submit:disabled{opacity:.6;cursor:default;}
|
||
|
||
.msg{margin-top:14px;font-size:13px;text-align:center;min-height:18px;}
|
||
.msg.err{color:#ff8a8a;}
|
||
.msg.ok{color:#8be39a;}
|
||
|
||
.back{text-align:center;margin-top:18px;font-size:13px;color:var(--text-dim);}
|
||
.back a{color:var(--cyan);text-decoration:none;}
|
||
.back a:hover{text-decoration:underline;}
|
||
|
||
.hidden{display:none;}
|
||
|
||
@media (max-width:420px){
|
||
.brand .cn{font-size:34px;letter-spacing:5px;}
|
||
.card{padding:22px 18px 26px;}
|
||
.field input{padding:12px 13px;font-size:15px;}
|
||
.submit{padding:13px;font-size:15px;}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="bg-slides"></div>
|
||
<div class="wrap">
|
||
<div class="brand">
|
||
<div class="cn">宇森</div>
|
||
<div class="en">YUSEN · GARDEN OF CIVILIZATION</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="tabs">
|
||
<button id="tabLogin" class="active">登录</button>
|
||
<button id="tabReg">注册</button>
|
||
</div>
|
||
|
||
<!-- 登录 -->
|
||
<form id="loginForm" autocomplete="off">
|
||
<div class="field">
|
||
<label>账号</label>
|
||
<input id="lLogin" type="text" placeholder="用户名" />
|
||
</div>
|
||
<div class="field">
|
||
<label>密码</label>
|
||
<div class="pw-wrap">
|
||
<input id="lPwd" type="password" placeholder="密码" />
|
||
<button type="button" class="pw-toggle" data-target="lPwd" aria-label="显示密码">👁</button>
|
||
</div>
|
||
</div>
|
||
<button class="submit" type="submit">进入世界</button>
|
||
</form>
|
||
|
||
<!-- 注册 -->
|
||
<form id="regForm" class="hidden" autocomplete="off">
|
||
<div class="field">
|
||
<label>账号</label>
|
||
<input id="rLogin" type="text" placeholder="登录用账号(字母/数字/._-)" />
|
||
</div>
|
||
<div class="field">
|
||
<label>昵称</label>
|
||
<div class="pw-wrap">
|
||
<input id="rName" type="text" placeholder="可随机生成,也可自定义" />
|
||
<button type="button" class="random-btn" id="randomNameBtn" aria-label="随机昵称">🎲</button>
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>密码</label>
|
||
<div class="pw-wrap">
|
||
<input id="rPwd" type="password" placeholder="至少 8 位,字母+数字" />
|
||
<button type="button" class="pw-toggle" data-target="rPwd" aria-label="显示密码">👁</button>
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>确认密码</label>
|
||
<div class="pw-wrap">
|
||
<input id="rPwd2" type="password" placeholder="再次输入密码" />
|
||
<button type="button" class="pw-toggle" data-target="rPwd2" aria-label="显示密码">👁</button>
|
||
</div>
|
||
</div>
|
||
<button class="submit" type="submit">创建账号</button>
|
||
</form>
|
||
|
||
<div class="msg" id="msg"></div>
|
||
</div>
|
||
|
||
<div class="back"><a href="index.html">← 返回官网</a></div>
|
||
</div>
|
||
|
||
<script src="assets/game-api.js"></script>
|
||
<script>
|
||
const tabLogin = document.getElementById('tabLogin');
|
||
const tabReg = document.getElementById('tabReg');
|
||
const loginForm = document.getElementById('loginForm');
|
||
const regForm = document.getElementById('regForm');
|
||
const msg = document.getElementById('msg');
|
||
|
||
// 回跳目标(进入游戏流程带 ?next=world.html)
|
||
const params = new URLSearchParams(location.search);
|
||
const nextUrl = params.get('next') || 'world.html';
|
||
// 已登录则直接跳走
|
||
(async () => {
|
||
try { const me = await API.me(); if (me && me.uid) location.href = nextUrl; } catch (e) {}
|
||
})();
|
||
|
||
function showMsg(text, kind){ msg.textContent = text || ''; msg.className = 'msg' + (kind ? ' ' + kind : ''); }
|
||
function switchTab(which){
|
||
if (which === 'login'){
|
||
tabLogin.classList.add('active'); tabReg.classList.remove('active');
|
||
loginForm.classList.remove('hidden'); regForm.classList.add('hidden');
|
||
} else {
|
||
tabReg.classList.add('active'); tabLogin.classList.remove('active');
|
||
regForm.classList.remove('hidden'); loginForm.classList.add('hidden');
|
||
}
|
||
showMsg('');
|
||
}
|
||
tabLogin.onclick = () => switchTab('login');
|
||
tabReg.onclick = () => switchTab('reg');
|
||
|
||
// 密码显示/隐藏切换(眼睛按钮)
|
||
document.querySelectorAll('.pw-toggle').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const input = document.getElementById(btn.dataset.target);
|
||
if (!input) return;
|
||
if (input.type === 'password') {
|
||
input.type = 'text';
|
||
btn.textContent = '🙈';
|
||
btn.setAttribute('aria-label', '隐藏密码');
|
||
} else {
|
||
input.type = 'password';
|
||
btn.textContent = '👁';
|
||
btn.setAttribute('aria-label', '显示密码');
|
||
}
|
||
input.focus();
|
||
});
|
||
});
|
||
|
||
// 随机昵称生成
|
||
const rName = document.getElementById('rName');
|
||
const ADJ = ['云游','星尘','微光','风吟','夜语','深海','荒原','天穹','逐风','灵叶','虚境','晨星','暮雨','浮世','苍穹'];
|
||
const NOUN = ['旅者','观察者','行者','拾光人','园丁','筑梦师','游侠','学者','守夜人','引路人','浪人','渡鸦','白鹿'];
|
||
function randomName() {
|
||
const a = ADJ[Math.floor(Math.random() * ADJ.length)];
|
||
const n = NOUN[Math.floor(Math.random() * NOUN.length)];
|
||
const num = String(Math.floor(Math.random() * 9000) + 1000);
|
||
return a + n + num;
|
||
}
|
||
document.getElementById('randomNameBtn').addEventListener('click', () => {
|
||
rName.value = randomName();
|
||
});
|
||
|
||
loginForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
showMsg('');
|
||
const login = document.getElementById('lLogin').value.trim();
|
||
const password = document.getElementById('lPwd').value;
|
||
if (!login || !password){ showMsg('请填写账号和密码', 'err'); return; }
|
||
const btn = loginForm.querySelector('.submit'); btn.disabled = true; btn.textContent = '登录中…';
|
||
try {
|
||
const r = await API.login(login, password);
|
||
if (r.ok){ location.href = nextUrl; }
|
||
else { showMsg(r.error || '登录失败', 'err'); }
|
||
} catch (err) {
|
||
showMsg('网络错误,请稍后重试', 'err');
|
||
} finally {
|
||
btn.disabled = false; btn.textContent = '进入世界';
|
||
}
|
||
});
|
||
|
||
regForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
showMsg('');
|
||
const login = document.getElementById('rLogin').value.trim();
|
||
const name = document.getElementById('rName').value.trim();
|
||
const password = document.getElementById('rPwd').value;
|
||
const pwd2 = document.getElementById('rPwd2').value;
|
||
if (!login) { showMsg('请填写账号', 'err'); return; }
|
||
if (password.length < 8) { showMsg('密码至少 8 位', 'err'); return; }
|
||
if (!/[a-zA-Z]/.test(password) || !/\d/.test(password)) {
|
||
showMsg('密码必须同时包含字母和数字', 'err'); return;
|
||
}
|
||
if (password !== pwd2) { showMsg('两次密码输入不一致', 'err'); return; }
|
||
const btn = regForm.querySelector('.submit'); btn.disabled = true; btn.textContent = '注册中…';
|
||
try {
|
||
const r = await API.register({ name, login, password });
|
||
if (r.ok){ location.href = nextUrl; }
|
||
else { showMsg(r.error || '注册失败', 'err'); }
|
||
} catch (err) {
|
||
showMsg('网络错误,请稍后重试', 'err');
|
||
} finally {
|
||
btn.disabled = false; btn.textContent = '创建账号';
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|