game/login.html
pengyuthon 954cbc6823 feat: 沙盘游戏核心运行时文件上线 + 微观世界加载页UI优化
- 提交 world.html 及 assets/(地形贴图/背景/视频/js)、index/login/terrain_preview
- world.html: 微观世界入场加载页重构为左下角提示药丸 + 全宽进度条
- 排除 .bak/__bak 备份文件(.gitignore)
2026-07-23 12:58:22 +08:00

194 lines
8.5 KiB
HTML
Raw 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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>宇森 · 登录</title>
<link href="https://fonts.googleapis.com/css2?family=ZCOOL+QingKe+HuangYou&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet" />
<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:"Noto Sans SC",system-ui,sans-serif;color:var(--text);
background:
radial-gradient(1000px 620px at 82% -8%, rgba(139,92,246,.16), transparent 60%),
radial-gradient(820px 560px at 8% 112%, rgba(76,201,240,.12), transparent 55%),
linear-gradient(160deg, var(--bg-0), var(--bg-1) 48%, var(--bg-2));
display:flex;align-items:center;justify-content:center;min-height:100vh;overflow:hidden;position:relative;
}
body::before,body::after{content:"";position:fixed;border-radius:50%;filter:blur(80px);z-index:0;pointer-events:none;}
body::before{width:380px;height:380px;background:rgba(139,92,246,.20);top:-110px;right:-70px;animation:f1 20s ease-in-out infinite;}
body::after{width:340px;height:340px;background:rgba(76,201,240,.12);bottom:-110px;left:-60px;animation:f2 24s ease-in-out infinite;}
@keyframes f1{0%,100%{transform:translate(0,0)}50%{transform:translate(-36px,36px)}}
@keyframes f2{0%,100%{transform:translate(0,0)}50%{transform:translate(36px,-28px)}}
.wrap{position:relative;z-index:2;width:min(420px,92vw);}
.brand{text-align:center;margin-bottom:26px;}
.brand .cn{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:38px;letter-spacing:6px;color:#fff;text-shadow:0 0 18px rgba(139,92,246,.6);}
.brand .en{font-size:11px;color:var(--magenta);letter-spacing:3px;margin-top:6px;}
.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);}
.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;}
.hint{margin-bottom:16px;font-size:13px;color:var(--cyan);background:rgba(76,201,240,.10);border:1px solid rgba(76,201,240,.30);border-radius:10px;padding:9px 12px;text-align:center;}
.hidden{display:none;}
</style>
</head>
<body>
<div class="wrap">
<div class="brand">
<div class="cn">宇森</div>
<div class="en">YUSEN · GARDEN OF CIVILIZATION</div>
<div class="tag">你不是玩家,你是园丁。</div>
</div>
<div class="card">
<div class="hint hidden" id="hint">登录后即可进入游戏</div>
<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="Odoo 用户名 / 邮箱" />
</div>
<div class="field">
<label>密码</label>
<input id="lPwd" type="password" placeholder="密码" />
</div>
<button class="submit" type="submit">进入世界</button>
</form>
<!-- 注册 -->
<form id="regForm" class="hidden" autocomplete="off">
<div class="field">
<label>昵称</label>
<input id="rName" type="text" placeholder="显示名称(可空)" />
</div>
<div class="field">
<label>账号</label>
<input id="rLogin" type="text" placeholder="登录用账号" />
</div>
<div class="field">
<label>邮箱</label>
<input id="rEmail" type="email" placeholder="邮箱(可空)" />
</div>
<div class="field">
<label>密码</label>
<input id="rPwd" type="password" placeholder="至少 6 位" />
</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';
const hint = document.getElementById('hint');
if (params.get('next')) hint.classList.remove('hidden');
// 已登录则直接跳走
(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');
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 payload = {
name: document.getElementById('rName').value.trim(),
login: document.getElementById('rLogin').value.trim(),
email: document.getElementById('rEmail').value.trim(),
password: document.getElementById('rPwd').value,
};
if (!payload.login || !payload.password){ showMsg('账号和密码必填', 'err'); return; }
const btn = regForm.querySelector('.submit'); btn.disabled = true; btn.textContent = '注册中…';
try {
const r = await API.register(payload);
if (r.ok){ location.href = nextUrl; }
else { showMsg(r.error || '注册失败', 'err'); }
} catch (err) {
showMsg('网络错误,请稍后重试', 'err');
} finally {
btn.disabled = false; btn.textContent = '创建园丁账号';
}
});
</script>
</body>
</html>