game/index.html

1115 lines
74 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, viewport-fit=cover" />
<title>宇森 · Yusen活的文明系统模拟</title>
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=ZCOOL+QingKe+HuangYou&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
<noscript><link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=ZCOOL+QingKe+HuangYou&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet" /></noscript>
<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">
<link rel="apple-touch-icon" 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,.045); --card-bd:rgba(255,255,255,.10);
--glow:0 0 22px rgba(139,92,246,.28);
}
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%;width:100%;overflow:hidden;}
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,.14), transparent 60%),
radial-gradient(820px 560px at 8% 112%, rgba(76,201,240,.10), transparent 55%),
linear-gradient(160deg, var(--bg-0) 0%, var(--bg-1) 48%, var(--bg-2) 100%);
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:fl1 20s ease-in-out infinite;}
body::after{width:340px;height:340px;background:rgba(76,201,240,.12);bottom:-110px;left:-60px;animation:fl2 24s ease-in-out infinite;}
@keyframes fl1{0%,100%{transform:translate(0,0)}50%{transform:translate(-36px,36px)}}
@keyframes fl2{0%,100%{transform:translate(0,0)}50%{transform:translate(36px,-28px)}}
.app{position:relative;z-index:2;height:100vh;display:flex;flex-direction:column;}
.topbar{height:64px;flex:0 0 auto;display:flex;align-items:center;gap:22px;padding:0 30px;border-bottom:1px solid var(--card-bd);background:rgba(8,12,26,.55);backdrop-filter:blur(10px);}
.logo{display:flex;align-items:baseline;gap:9px;cursor:pointer;}
.logo .cn{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:28px;letter-spacing:4px;color:#fff;text-shadow:0 0 16px rgba(139,92,246,.55);}
.logo .en{font-family:"Press Start 2P",monospace;font-size:9px;color:var(--magenta);letter-spacing:1px;}
.nav{display:flex;gap:6px;margin-left:12px;}
.nav button{font-family:"Noto Sans SC",sans-serif;font-size:15px;font-weight:500;color:var(--text-dim);background:transparent;border:1px solid transparent;padding:8px 15px;border-radius:10px;cursor:pointer;transition:.22s;}
.nav button:hover{color:var(--text);background:rgba(139,92,246,.12);}
.nav button.active{color:#fff;background:rgba(139,92,246,.22);border-color:var(--card-bd);box-shadow:var(--glow);}
.spacer{flex:1;}
.user-box{display:flex;align-items:center;gap:10px;font-size:14px;color:var(--text);}
.user-box .ub-name{color:#fff;font-weight:500;}
.user-box .ub-logout{font-family:inherit;font-size:12px;color:var(--text-dim);background:rgba(255,255,255,.06);border:1px solid var(--card-bd);padding:5px 12px;border-radius:8px;cursor:pointer;transition:.2s;}
.user-box .ub-logout:hover{color:#fff;background:rgba(139,92,246,.18);}
.user-box .auth-link{color:var(--text-dim);text-decoration:none;font-size:14px;transition:.2s;padding:5px 8px;}
.user-box .auth-link:hover{color:#fff;}
.user-box .auth-btn{font-family:inherit;font-size:13px;color:#0b1020;background:linear-gradient(120deg,var(--gold),#f0d49a);border:none;padding:6px 16px;border-radius:20px;cursor:pointer;font-weight:600;transition:.2s;}
.user-box .auth-btn:hover{filter:brightness(1.1);box-shadow:0 0 16px rgba(231,184,92,.4);}
.nav .auth-only{display:none;} /* 登录专属 tab未登录时隐藏 */
.nav .auth-only.visible{display:inline-block;}
.cta:hover{filter:brightness(1.08);}
.content{flex:1;position:relative;overflow:hidden;}
.panel{position:absolute;inset:0;display:none;flex-direction:column;padding:28px 48px;overflow:hidden;}
.panel.active{display:flex;}
.panel-scroll{flex:1;min-height:0;overflow-y:auto;padding-right:8px;}
/* 隐藏内部滚动条但保留滚轮/触控滑动(规则/世界数据/公告/论坛/图鉴/剧情等) */
.rules-scroll,#worldDataContent,#announce .notes,.threads,.codex-grid,.story-reader,.panel-scroll,.toc-list{
scrollbar-width:none; -ms-overflow-style:none;
}
.rules-scroll::-webkit-scrollbar,#worldDataContent::-webkit-scrollbar,#announce .notes::-webkit-scrollbar,
.threads::-webkit-scrollbar,.codex-grid::-webkit-scrollbar,.story-reader::-webkit-scrollbar,
.panel-scroll::-webkit-scrollbar,.toc-list::-webkit-scrollbar{display:none;width:0;height:0;}
/* ===== 介绍(图片轮播主页) ===== */
#intro{padding:0;}
.hero{position:relative;flex:1;border-radius:0;overflow:hidden;}
.hero .slideshow{position:absolute;inset:0;overflow:hidden;}
.hero .slide-track{position:absolute;inset:0;display:flex;flex-direction:column;transition:transform 1.25s cubic-bezier(0.4,0,0.2,1);will-change:transform;}
.hero .slide{flex:0 0 100%;width:100%;height:100%;background-size:cover;background-position:center;position:relative;}
.hero .slide::before{content:"";position:absolute;inset:0;
background:linear-gradient(90deg, rgba(8,12,26,.92) 0%, rgba(8,12,26,.70) 22%, rgba(8,12,26,.40) 40%, transparent 58%),
linear-gradient(180deg, rgba(8,12,26,.55) 0%, transparent 35%);}
.hero .copy{position:absolute;left:56px;top:56px;z-index:3;pointer-events:none;}
.hero .copy h1{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:56px;letter-spacing:10px;color:#fff;text-shadow:0 2px 24px rgba(0,0,0,.9),0 0 32px rgba(139,92,246,.7);line-height:1.2;}
.hero .copy .sub2{font-family:"Press Start 2P",monospace;font-size:11px;color:var(--gold);margin:6px 0 0;letter-spacing:2px;text-shadow:0 1px 8px rgba(0,0,0,.9);}
.hero .copy .tag{font-size:20px;font-weight:500;color:#e8ecf6;margin-top:4px;text-shadow:0 1px 10px rgba(0,0,0,.85);}
/* 轮播左下角特色文案 */
.slide-label{position:absolute;left:56px;bottom:72px;z-index:4;pointer-events:none;opacity:0;transform:translateY(16px);transition:opacity .8s ease, transform .8s ease;}
.slide.active .slide-label{opacity:1;transform:translateY(0);}
.slide-label .sl-en{font-family:"Press Start 2P",monospace;font-size:13px;color:var(--cyan);letter-spacing:3px;margin-bottom:10px;display:block;text-shadow:0 1px 8px rgba(0,0,0,.9);}
.slide-label .sl-cn{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:46px;letter-spacing:7px;color:#fff;text-shadow:0 2px 22px rgba(0,0,0,.95),0 0 30px rgba(139,92,246,.65);line-height:1.25;}
.slide-label .sl-desc{font-size:16px;color:#c8d2ec;margin-top:12px;max-width:500px;line-height:1.85;text-shadow:0 1px 8px rgba(0,0,0,.88);}
/* 轮播指示器(竖排右侧) */
.sl-dots{position:absolute;right:40px;bottom:96px;top:auto;transform:none;z-index:5;display:flex;flex-direction:column;gap:14px;pointer-events:auto;}
.sl-dot{width:12px;height:12px;border-radius:50%;background:rgba(255,255,255,.22);border:1px solid rgba(255,255,255,.4);cursor:pointer;transition:.3s;flex:0 0 auto;}
.sl-dot:hover{background:rgba(255,255,255,.5);}
.sl-dot.active{background:var(--gold);border-color:var(--gold);box-shadow:0 0 12px rgba(231,184,92,.6);transform:scale(1.15);}
/* 轮播右上角竖排「起源历史」 */
.slide-origin{position:absolute;right:72px;top:48px;z-index:4;pointer-events:none;writing-mode:vertical-rl;text-orientation:upright;
font-family:"Noto Sans SC",sans-serif;font-size:16px;font-weight:500;letter-spacing:3px;line-height:1.55;color:rgba(240,244,252,.94);
text-shadow:0 1px 3px rgba(0,0,0,.92);max-height:28vh;overflow:hidden;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;
opacity:0;transform:translateY(-12px);transition:opacity .9s ease .25s, transform .9s ease .25s;}
.slide.active .slide-origin{opacity:1;transform:translateY(0);}
.slide-origin .so-tag{color:var(--gold);font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:19px;letter-spacing:5px;margin-bottom:7px;text-shadow:0 1px 4px rgba(0,0,0,.92),0 0 10px rgba(231,184,92,.45);}
.hero .enter-wrap{position:absolute;left:0;right:0;bottom:28px;display:flex;justify-content:center;z-index:5;}
.hero .enter-btn{font-family:"Noto Sans SC",sans-serif;font-size:17px;font-weight:700;color:#0b1020;background:linear-gradient(120deg,var(--gold),#f0d49a);padding:13px 42px;border-radius:30px;cursor:pointer;box-shadow:0 0 26px rgba(231,184,92,.5);letter-spacing:3px;transition:.25s;border:none;}
.hero .enter-btn:hover{filter:brightness(1.1);transform:translateY(-2px);box-shadow:0 0 40px rgba(231,184,92,.75);}
/* ===== 特色 ===== */
#features{align-items:center;justify-content:center;gap:22px;}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;width:min(1060px,100%);}
.card{background:var(--card);border:1px solid var(--card-bd);border-radius:14px;padding:18px 20px;transition:.26s;min-height:132px;}
.card:hover{transform:translateY(-4px);border-color:var(--violet);box-shadow:var(--glow);background:rgba(139,92,246,.12);}
.card .ic{font-size:28px;margin-bottom:8px;filter:drop-shadow(0 0 6px rgba(199,125,255,.6));}
.card h3{font-size:17px;color:#fff;margin-bottom:7px;letter-spacing:1px;}
.card p{font-size:13px;color:var(--text-dim);line-height:1.6;}
/* ===== 背景 ===== */
#background{flex-direction:column;align-items:stretch;gap:0;overflow:hidden;padding:28px 48px;}
.bg-page{max-width:1080px;width:100%;margin:0 auto;padding:24px 0;border-top:1px solid var(--card-bd);}
.bg-page:first-child{border-top:none;padding-top:0;}
.bg-page-head{text-align:center;margin-bottom:28px;}
.bg-page-title{font-family:"Noto Serif SC",serif;font-weight:800;font-size:40px;letter-spacing:3px;line-height:1.2;margin:0 0 10px;
background:linear-gradient(120deg,#ffffff 0%,#f0d49a 48%,#c79bff 100%);
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
.bg-page-sub{font-size:13px;letter-spacing:7px;color:var(--text-dim);text-transform:uppercase;}
.bg-page-body{font-size:17px;line-height:2.05;color:#e2e8f6;max-width:840px;margin:0 auto 34px;}
.bg-page-body p{margin:0 0 18px;}
.bg-page-items{display:grid;grid-template-columns:repeat(auto-fill,minmax(262px,1fr));gap:18px;}
.bg-card{display:flex;gap:15px;align-items:flex-start;background:linear-gradient(135deg,rgba(139,92,246,.10),rgba(76,201,240,.05));
border:1px solid var(--card-bd);border-radius:16px;padding:18px 20px;transition:.26s;}
.bg-card:hover{transform:translateY(-4px);border-color:var(--violet);box-shadow:var(--glow);background:rgba(139,92,246,.16);}
.bg-card-ic{font-size:27px;line-height:1.15;filter:drop-shadow(0 0 9px rgba(139,92,246,.55));flex:0 0 auto;}
.bg-card-title{font-size:17px;font-weight:700;color:#fff;margin-bottom:6px;}
.bg-card-desc{font-size:14.5px;line-height:1.66;color:var(--text-dim);}
.bg-empty{text-align:center;color:var(--text-dim);padding:70px 0;font-size:16px;}
.layers{flex:0 0 270px;display:flex;flex-direction:column;gap:8px;justify-content:center;}
.layer{border-radius:12px;padding:11px 15px;border:1px solid var(--card-bd);position:relative;}
.layer .lt{font-size:15px;color:#fff;font-weight:500;letter-spacing:1px;}
.layer .ld{font-size:12px;color:var(--text-dim);margin-top:4px;line-height:1.5;}
.layer.l1{background:linear-gradient(100deg,rgba(76,201,240,.18),rgba(139,92,246,.08));}
.layer.l2{background:linear-gradient(100deg,rgba(199,125,255,.18),rgba(139,92,246,.07));}
.layer.l3{background:linear-gradient(100deg,rgba(139,92,246,.24),rgba(76,201,240,.06));border-color:var(--violet);}
.layer.l4{background:linear-gradient(100deg,rgba(139,92,246,.14),rgba(8,12,26,.18));}
.layer.l5{background:linear-gradient(100deg,rgba(231,184,92,.14),rgba(8,12,26,.26));}
.layer .star{position:absolute;right:12px;top:9px;font-size:11px;color:var(--cyan);}
.bg-text{flex:1;display:flex;flex-direction:column;justify-content:center;overflow-y:auto;}
.bg-text h2{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:30px;letter-spacing:3px;color:#fff;text-shadow:0 0 16px rgba(139,92,246,.5);margin-bottom:16px;}
.bg-text p{font-size:14.5px;color:var(--text-dim);line-height:1.95;max-width:640px;}
/* ===== 规则 ===== */
#rules{flex-direction:column;gap:0;}
.rules-scroll{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:22px;padding-right:8px;}
.rule-block h3{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:22px;color:#fff;letter-spacing:2px;margin-bottom:6px;}
.rule-intro{font-size:13.5px;color:var(--text-dim);margin-bottom:12px;line-height:1.7;max-width:780px;}
.tier-list{display:flex;flex-direction:column;gap:8px;}
.tier-row{display:grid;grid-template-columns:96px 116px 1fr;gap:12px;align-items:center;background:var(--card);border:1px solid var(--card-bd);border-radius:10px;padding:10px 14px;transition:.2s;}
.tier-row:hover{border-color:var(--violet);background:rgba(139,92,246,.10);}
.tier-row .tn{font-family:"Press Start 2P",monospace;font-size:10px;color:var(--cyan);}
.tier-row .tname{font-size:15px;color:#fff;font-weight:500;}
.tier-row .tdesc{font-size:13px;color:var(--text-dim);line-height:1.5;}
/* ===== 图鉴 ===== */
#codex{padding:28px 48px;gap:0;}
.codex-wrap{display:flex;flex-direction:column;gap:13px;flex:1;min-height:0;}
.codex-cats{display:flex;gap:6px;flex-wrap:wrap;}
.codex-cats button{font-family:"Noto Sans SC",sans-serif;font-size:14px;color:var(--text-dim);background:var(--card);border:1px solid var(--card-bd);padding:8px 15px;border-radius:11px;cursor:pointer;transition:.2s;}
.codex-cats button:hover{color:#fff;}
.codex-cats button.active{color:#fff;background:rgba(139,92,246,.24);border-color:var(--magenta);box-shadow:var(--glow);}
.codex-subrow{display:flex;align-items:center;gap:12px;}
.codex-subs{display:flex;gap:6px;flex-wrap:wrap;flex:1;}
.codex-subs button{font-size:12px;color:var(--text-dim);background:var(--card);border:1px solid var(--card-bd);padding:5px 12px;border-radius:14px;cursor:pointer;transition:.2s;}
.codex-subs button:hover{color:#fff;}
.codex-subs button.active{color:#fff;background:rgba(76,201,240,.22);border-color:var(--cyan);}
.codex-search{background:rgba(8,12,26,.6);border:1px solid var(--card-bd);border-radius:18px;color:var(--text);padding:7px 14px;font-size:13px;outline:none;width:200px;flex:0 0 auto;}
.codex-search:focus{border-color:var(--violet);}
.codex-count{font-size:12px;color:var(--text-dim);white-space:nowrap;flex:0 0 auto;}
.codex-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;overflow-y:auto;padding:4px 6px 4px 0;align-content:start;flex:1;min-height:0;}
.cx{background:var(--card);border:1px solid var(--card-bd);border-radius:13px;overflow:hidden;transition:.24s;display:flex;flex-direction:column;cursor:default;}
.cx:hover{transform:translateY(-3px);border-color:var(--violet);box-shadow:var(--glow);}
.cx .thumb{width:100%;aspect-ratio:1/1;display:block;overflow:hidden;background:linear-gradient(135deg,rgba(139,92,246,.22),rgba(76,201,240,.10));}
.cx .thumb svg{width:100%;height:100%;display:block;}
.cx .body{padding:10px 12px;}
.cx .nm{font-size:14.5px;color:#fff;font-weight:500;}
.cx .meta{display:flex;justify-content:space-between;margin-top:5px;font-size:11px;}
.cx .meta .sub{color:var(--cyan);}
.cx .meta .tier{color:var(--gold);}
.cx .ds{font-size:11.5px;color:var(--text-dim);line-height:1.5;margin-top:6px;}
/* ===== 论坛 ===== */
#forum{flex-direction:row;gap:28px;}
.f-cats{flex:0 0 200px;display:flex;flex-direction:column;gap:12px;}
.f-cats .ch{font-size:15.5px;color:var(--text-dim);background:var(--card);border:1px solid var(--card-bd);padding:13px 18px;border-radius:12px;cursor:pointer;transition:.22s;letter-spacing:.5px;}
.f-cats .ch:hover{color:#fff;background:rgba(139,92,246,.12);}
.f-cats .ch.active{color:#fff;background:linear-gradient(120deg,rgba(139,92,246,.28),rgba(199,125,255,.16));border-color:var(--magenta);box-shadow:var(--glow);}
.f-main{flex:1;display:flex;flex-direction:column;min-width:0;}
.threads{flex:1;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px;padding-right:8px;align-content:start;}
.th{display:flex;align-items:center;gap:16px;background:var(--card);border:1px solid var(--card-bd);border-radius:14px;padding:16px 20px;transition:.22s;cursor:pointer;}
.th:hover{border-color:var(--violet);background:rgba(139,92,246,.12);transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.25);}
.th .pin{font-size:12px;font-weight:700;letter-spacing:1px;color:var(--gold);background:rgba(231,184,92,.16);border:1px solid rgba(231,184,92,.45);border-radius:6px;padding:2px 8px;}
.th .tt{flex:1;font-size:16.5px;color:#fff;letter-spacing:.3px;}
.th .meta{font-size:13px;color:var(--text-dim);white-space:nowrap;}
.th .rp{color:var(--cyan);font-weight:600;}
.post-form{display:none;flex-direction:column;gap:10px;background:var(--card);border:1px solid var(--magenta);border-radius:14px;padding:16px;margin-bottom:14px;}
.post-form.show{display:flex;}
.post-form input,.post-form textarea{background:rgba(8,12,26,.6);border:1px solid var(--card-bd);border-radius:10px;color:var(--text);padding:10px 14px;font-family:inherit;font-size:14px;outline:none;}
.post-form textarea{resize:vertical;min-height:72px;}
.post-form .row{display:flex;gap:10px;justify-content:flex-end;}
.post-form .row button{font-size:14px;padding:8px 18px;border-radius:10px;cursor:pointer;border:1px solid var(--card-bd);background:var(--card);color:var(--text);}
.post-form .row .ok{background:linear-gradient(120deg,var(--gold),#f0d49a);color:#0b1020;font-weight:700;border:none;}
/* ===== 公告 ===== */
#announce{gap:0;align-items:stretch;}
#announce .notes{flex:1;min-height:0;width:100%;display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:18px;overflow-y:auto;padding-right:8px;align-content:start;}
#announce .notes{grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:20px;}
.note{display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(135deg,rgba(139,92,246,.10),rgba(76,201,240,.04));border:1px solid var(--card-bd);border-left:4px solid var(--magenta);border-radius:16px;transition:.24s;}
.note:hover{transform:translateY(-3px);border-color:var(--violet);background:linear-gradient(135deg,rgba(139,92,246,.16),rgba(76,201,240,.07));box-shadow:var(--glow);}
.note.pin{border-left-color:var(--gold);background:linear-gradient(135deg,rgba(231,184,92,.12),rgba(139,92,246,.06));}
.note.pin:hover{background:linear-gradient(135deg,rgba(231,184,92,.18),rgba(139,92,246,.08));}
.note.feat{grid-column:1 / -1;flex-direction:row;align-items:stretch;}
.note.feat .note-cover{flex:0 0 44%;width:44%;min-height:260px;}
.note.feat .note-body{flex:1;justify-content:center;}
.note.feat .note-title{font-size:27px;}
.note.feat .body{font-size:16px;}
.note-cover{height:168px;flex:0 0 auto;background-size:cover;background-position:center;background-repeat:no-repeat;border-bottom:1px solid var(--card-bd);position:relative;}
.note-cover.empty{background:linear-gradient(135deg,rgba(139,92,246,.24),rgba(76,201,240,.10));}
.note-cover.empty::after{content:'封面图 · 后台配置';position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:12px;letter-spacing:2px;color:rgba(255,255,255,.5);}
.note-body{padding:18px 22px;display:flex;flex-direction:column;min-width:0;}
.note .date{font-size:13px;font-weight:600;color:var(--cyan);letter-spacing:1px;margin-bottom:8px;}
.note-title{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:21px;color:#fff;letter-spacing:1px;margin-bottom:10px;line-height:1.32;text-shadow:0 0 14px rgba(139,92,246,.4);}
.note .body{font-size:15px;color:var(--text);line-height:1.9;}
.note .body p{margin:0 0 11px;}
.note .body p:last-child{margin-bottom:0;}
/* ===== 致玩家 ===== */
#letter{align-items:center;justify-content:center;}
#letter .letter-wrap{max-width:680px;width:100%;overflow-y:auto;padding:10px 20px;}
#letter .letter-wrap h2{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:30px;letter-spacing:4px;color:#fff;text-shadow:0 0 18px rgba(139,92,246,.5);margin-bottom:6px;text-align:center;}
#letter .letter-wrap .lt-sub{text-align:center;font-family:"Press Start 2P",monospace;font-size:9px;color:var(--magenta);letter-spacing:1px;margin-bottom:28px;}
#letter .letter-wrap .lt-body{font-size:15px;color:var(--text-dim);line-height:2.1;}
#letter .letter-wrap .lt-body p{margin-bottom:16px;}
#letter .letter-wrap .lt-body p:first-child{font-size:17px;color:#fff;font-weight:500;}
#letter .letter-wrap .lt-body .hl{color:var(--gold);font-weight:500;}
#letter .letter-wrap .lt-sign{text-align:right;margin-top:28px;font-size:14px;color:var(--magenta);font-style:italic;}
/* ===== 论坛(竖排:顶部分类条 + 下方纵向帖子流) ===== */
#forum{flex-direction:column;gap:20px;}
.f-cats{flex:0 0 auto;flex-direction:row;flex-wrap:wrap;gap:10px;}
.f-cats .ch{flex:0 0 auto;}
.f-main{flex:1;min-height:0;}
.threads{width:100%;}
/* ===== 公告(内容居中) ===== */
#announce .notes{margin:0;}
/* ===== 剧情(小说阅读器:左目录 + 右正文,竖排阅读感) ===== */
#story{padding:28px 48px;}
.story-wrap{display:flex;flex-direction:row;width:100%;flex:1;min-height:0;}
.story-toc{flex:0 0 286px;display:flex;flex-direction:column;border-right:1px solid var(--card-bd);background:rgba(8,12,26,.35);min-height:0;}
.toc-head{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:24px;letter-spacing:3px;color:#fff;padding:24px 0 12px;text-shadow:0 0 16px rgba(139,92,246,.5);}
.toc-head small{display:block;font-family:"Press Start 2P",monospace;font-size:8px;color:var(--magenta);letter-spacing:1px;margin-top:6px;}
.toc-list{flex:1;overflow-y:auto;padding:0 12px 24px;}
.toc-loading{padding:30px 16px;color:var(--text-dim);font-size:14px;}
.toc-vol{margin-top:10px;}
.toc-vol .vname{font-size:14px;color:var(--gold);letter-spacing:1px;padding:8px 10px 4px;font-weight:500;}
.toc-vol .vintro{font-size:12px;color:var(--text-dim);line-height:1.6;padding:0 10px 8px;}
.toc-ch{display:block;width:100%;text-align:left;font-family:inherit;font-size:15px;color:var(--text-dim);background:transparent;border:none;border-left:2px solid transparent;padding:10px 12px;cursor:pointer;transition:.18s;border-radius:0 8px 8px 0;line-height:1.45;}
.toc-ch:hover{color:#fff;background:rgba(139,92,246,.12);}
.toc-ch.active{color:#fff;background:rgba(139,92,246,.20);border-left-color:var(--magenta);}
.story-reader{flex:1;overflow-y:auto;min-width:0;background:linear-gradient(180deg, rgba(139,92,246,.04), transparent 240px);}
.reader-loading{padding:80px 20px;text-align:center;color:var(--text-dim);font-size:15px;}
.reader-inner{max-width:840px;margin:0 auto;padding:48px 52px 100px;}
.reader-inner .r-vol{font-size:12px;color:var(--cyan);letter-spacing:2px;margin-bottom:10px;}
.reader-inner h1{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:32px;letter-spacing:2px;color:#fff;text-shadow:0 0 16px rgba(139,92,246,.5);margin-bottom:18px;line-height:1.3;}
.reader-inner .body{font-size:17.5px;color:#eef2fb;line-height:2.0;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;}
.reader-inner .body p{margin-bottom:20px;text-indent:2em;}
.reader-inner .body p:first-child{text-indent:0;}
.reader-inner .body h2,.reader-inner .body h3{color:#fff;margin:22px 0 10px;font-size:18px;}
/* ===== 世界数据报表 ===== */
#worlddata{overflow:hidden;padding:28px 48px;}
#worldDataContent{flex:1;min-height:0;overflow-y:auto;width:100%;padding-right:8px;}
.wd-tag{display:inline-block;font-size:12px;letter-spacing:2px;color:var(--gold);background:rgba(231,184,92,.12);border:1px solid rgba(231,184,92,.38);border-radius:6px;padding:3px 11px;margin-bottom:22px;}
.wd-h{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:22px;letter-spacing:3px;color:#fff;margin:38px 0 18px;text-shadow:0 0 14px rgba(139,92,246,.45);display:flex;align-items:center;gap:10px;}
.wd-h::before{content:"";width:4px;height:22px;border-radius:3px;background:linear-gradient(180deg,var(--violet),var(--cyan));box-shadow:0 0 10px rgba(139,92,246,.6);}
.wd-h small{font-family:"Press Start 2P",monospace;font-size:8px;color:var(--magenta);letter-spacing:1px;}
.wd-kpi{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:20px;}
.wd-kpi-card{position:relative;overflow:hidden;background:linear-gradient(135deg,rgba(139,92,246,.12),rgba(76,201,240,.05));border:1px solid var(--card-bd);border-radius:16px;padding:22px 26px;transition:.26s;}
.wd-kpi-card:hover{transform:translateY(-4px);border-color:var(--violet);box-shadow:var(--glow);}
.wd-kpi-card::after{content:"";position:absolute;right:-26px;top:-26px;width:88px;height:88px;border-radius:50%;background:radial-gradient(circle,rgba(199,125,255,.22),transparent 70%);}
.wd-kpi-ic{font-size:22px;filter:drop-shadow(0 0 7px rgba(139,92,246,.55));}
.wd-kpi-num{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:40px;line-height:1.1;color:#fff;margin:10px 0 4px;}
.wd-kpi-num .u{font-size:14px;color:var(--text-dim);margin-left:4px;letter-spacing:1px;}
.wd-kpi-label{font-size:13px;color:var(--text-dim);letter-spacing:1px;}
.wd-kpi-delta{position:absolute;right:14px;top:16px;font-size:11px;font-weight:700;padding:1px 7px;border-radius:5px;}
.wd-up{color:#7CFC9B;background:rgba(124,252,155,.12);}
.wd-down{color:#ff8585;background:rgba(255,133,133,.12);}
.wd-bars{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px 26px;}
.wd-bar-row{margin-bottom:4px;}
.wd-bar-top{display:flex;justify-content:space-between;font-size:13.5px;margin-bottom:6px;}
.wd-bar-top .k{color:var(--text);}
.wd-bar-top .v{color:var(--gold);font-weight:700;}
.wd-track{height:9px;border-radius:6px;background:rgba(255,255,255,.07);overflow:hidden;}
.wd-fill{height:100%;border-radius:6px;background:linear-gradient(90deg,var(--cyan),var(--violet));box-shadow:0 0 9px rgba(139,92,246,.55);}
.wd-table{width:100%;border-collapse:separate;border-spacing:0;font-size:14px;background:rgba(255,255,255,.02);border:1px solid var(--card-bd);border-radius:14px;overflow:hidden;}
.wd-table th{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:14px;letter-spacing:2px;color:#fff;background:linear-gradient(135deg,rgba(139,92,246,.22),rgba(76,201,240,.12));text-align:left;padding:13px 16px;border-bottom:1px solid var(--card-bd);}
.wd-table td{padding:12px 16px;border-bottom:1px solid rgba(255,255,255,.06);color:var(--text-dim);line-height:1.5;}
.wd-table tr:last-child td{border-bottom:none;}
.wd-table tr:hover td{background:rgba(139,92,246,.08);color:#eaf0ff;}
.wd-table .lead{color:#fff;font-weight:600;}
.wd-table .pill{display:inline-block;font-size:12px;padding:2px 9px;border-radius:20px;background:rgba(76,201,240,.14);color:var(--cyan);border:1px solid rgba(76,201,240,.34);}
.wd-rank{display:flex;flex-direction:column;gap:12px;}
.wd-rank-row{display:flex;align-items:center;gap:14px;background:linear-gradient(135deg,rgba(139,92,246,.08),rgba(255,255,255,.02));border:1px solid var(--card-bd);border-radius:13px;padding:12px 16px;transition:.22s;}
.wd-rank-row:hover{border-color:var(--violet);transform:translateX(4px);}
.wd-rank-no{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:22px;width:34px;text-align:center;color:var(--gold);flex:0 0 auto;}
.wd-rank-row:nth-child(1) .wd-rank-no{color:#ffd56b;}
.wd-rank-row:nth-child(2) .wd-rank-no{color:#cfd8e8;}
.wd-rank-row:nth-child(3) .wd-rank-no{color:#e0a86b;}
.wd-rank-main{flex:1;min-width:0;}
.wd-rank-name{font-size:15px;color:#fff;margin-bottom:5px;display:flex;justify-content:space-between;}
.wd-rank-name .s{color:var(--gold);font-weight:700;font-size:13px;}
.wd-rank-track{height:8px;border-radius:5px;background:rgba(255,255,255,.07);overflow:hidden;}
.wd-rank-fill{height:100%;border-radius:5px;background:linear-gradient(90deg,var(--magenta),var(--violet));box-shadow:0 0 8px rgba(199,125,255,.5);}
@media (max-width:900px){
.grid{grid-template-columns:repeat(2,1fr);}
#background,#forum{flex-direction:column;overflow-y:auto;}
.layers,.f-cats{flex:none;}
#story .story-wrap{flex-direction:column;overflow-y:auto;}
.story-toc{flex:none;border-right:none;border-bottom:1px solid var(--card-bd);}
.toc-list{max-height:220px;}
.hero .copy h1{font-size:42px;}
.nav button{padding:8px 10px;font-size:13px;}
.nav{overflow-x:auto;scrollbar-width:none;}
.nav::-webkit-scrollbar{display:none;}
}
/* ===== 移动端适配(手机 / 平板竖屏) ===== */
@media (max-width:768px){
html,body{height:100%;}
body{height:100dvh;}
.app{height:100dvh;}
.topbar{height:auto;min-height:54px;flex-wrap:wrap;align-items:center;padding:8px 14px;gap:10px;}
.logo .cn{font-size:22px;letter-spacing:2px;}
.logo .en{display:none;}
.spacer{display:none;}
.user-box{margin-left:auto;font-size:13px;gap:8px;}
.user-box .auth-btn{padding:6px 13px;font-size:12px;}
.nav{order:3;flex:1 0 100%;width:100%;margin-left:0;gap:4px;padding-top:6px;border-top:1px solid var(--card-bd);}
.nav button{font-size:13px;padding:7px 9px;white-space:nowrap;}
.hero .copy{left:18px;right:18px;bottom:82px;}
.hero .copy{left:18px;top:28px;}
.hero .copy h1{font-size:32px;letter-spacing:4px;}
.hero .copy .tag{font-size:16px;}
.slide-label{left:18px;bottom:78px;}
.slide-label .sl-cn{font-size:24px;letter-spacing:3px;}
.slide-label .sl-desc{font-size:13px;max-width:100%;}
.sl-dots{right:16px;gap:12px;}
.hero .enter-wrap{bottom:28px;}
.hero .enter-btn{font-size:15px;padding:11px 30px;}
.panel{padding:18px 18px;}
.bg-page-title{font-size:30px;}
.bg-page-body{font-size:15.5px;line-height:1.9;}
.grid{grid-template-columns:repeat(2,1fr);gap:12px;}
.card{min-height:120px;padding:15px 16px;}
.codex-search{width:140px;}
.reader-inner{padding:26px 18px 80px;}
.reader-inner h1{font-size:23px;}
#worlddata{padding:28px 16px 80px;}
#background{padding:30px 16px 80px;}
.story-toc{flex-basis:auto;}
#story{padding:18px 18px;}
.tier-row{grid-template-columns:72px 92px 1fr;gap:8px;}
.note.feat{flex-direction:column;}
.note.feat .note-cover{flex:0 0 auto;width:100%;min-height:180px;}
#announce .notes{grid-template-columns:1fr;}
}
@media (max-width:480px){
.topbar{padding:8px 10px;gap:8px;}
.logo .cn{font-size:20px;}
.nav button{font-size:12px;padding:6px 7px;}
.user-box .auth-btn{padding:6px 11px;}
.hero .copy{left:14px;top:20px;}
.hero .copy h1{font-size:26px;letter-spacing:2px;}
.hero .copy .tag{font-size:14px;}
.slide-label{left:14px;bottom:70px;}
.slide-label .sl-cn{font-size:20px;}
.slide-label .sl-desc{font-size:12.5px;}
.sl-dots{right:12px;gap:10px;}
.grid{grid-template-columns:1fr;}
.panel{padding:14px 14px;}
.codex-search{width:118px;flex:1 1 100%;}
.codex-subrow{flex-wrap:wrap;}
.reader-inner{padding:22px 14px 70px;}
#story{padding:14px 14px;}
.tier-row{grid-template-columns:60px 76px 1fr;gap:6px;}
.codex-cats button,.codex-subs button{padding:7px 11px;font-size:13px;}
}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<div class="logo" id="logoHome"><span class="cn">宇森</span></div>
<nav class="nav">
<button data-tab="background">背景</button>
<button data-tab="rules">规则</button>
<button data-tab="codex">图鉴</button>
<button data-tab="forum" class="auth-only">论坛</button>
<button data-tab="announce">公告</button>
<button data-tab="story">剧情</button>
<button data-tab="worlddata" class="auth-only">世界数据</button>
</nav>
<div class="spacer"></div>
<div class="user-box" id="userBox"></div>
</header>
<main class="content">
<!-- ===== 介绍(视频主页) ===== -->
<section class="panel active" id="intro">
<div class="hero">
<div class="slideshow" id="slideshow">
<!-- 五层世界轮播,由 JS 渲染 -->
</div>
<div class="enter-wrap">
<button class="enter-btn" id="enterGame">进入游戏</button>
</div>
</div>
</section>
<!-- ===== 背景(特色 + 世界背景 + 致玩家 合并平铺) ===== -->
<section class="panel" id="background">
<div class="panel-scroll" id="bgBody"></div>
</section>
<!-- ===== 规则 ===== -->
<section class="panel" id="rules">
<div class="rules-scroll">
<!-- ① 世界等级 · 五阶段 -->
<div class="rule-block">
<h3>① 世界等级 · 五阶段</h3>
<p class="rule-intro">世界从凡墟到溯元的整体进化阶段,决定可解锁的内容深度与事件池上限。园丁对世界施加的影响会推动阶段跃迁——这是文明能"发生什么"的舞台上限。</p>
<div class="tier-list">
<div class="tier-row"><span class="tn">阶段一</span><span class="tname">凡墟</span><span class="tdesc">万物生灵尚处蒙昧凡土,天地灵气稀薄,各族散居荒野,无族群联盟与政权,只求狩猎采集求生,一切秩序遵循自然法则。</span></div>
<div class="tier-row"><span class="tn">阶段二</span><span class="tname">灵启</span><span class="tdesc">天地灵气复苏觉醒,各族聚拢形成聚落,诞生劳作分工、屋舍营建与物物交换,不同族群率先凝聚部族,世间初代势力自此诞生。</span></div>
<div class="tier-row"><span class="tn">阶段三</span><span class="tname">玄界</span><span class="tdesc">各族部落兼并融合,广袤大地形成多元族群共治的完整疆域,征伐、道统、造物三大发展路线并行;各族大能、上古秘闻等传奇事件不断现世,玄荒文明迎来鼎盛割据时代。</span></div>
<div class="tier-row"><span class="tn">阶段四</span><span class="tname">穹冥</span><span class="tdesc">生灵突破地表桎梏,打通地底深渊与浩瀚星空,天地上下各界尽数纳入族群管辖;各类上古神器、本命法器完整解锁,文明疆域遍布天地穹苍。</span></div>
<div class="tier-row"><span class="tn">阶段五</span><span class="tname">溯元</span><span class="tdesc">各族顶尖文明勘破这片天地的诞生本源,挣脱本世界规则束缚,可踏上飞升之路、往复轮回,前往更高维度开启全新文明演化。</span></div>
</div>
</div>
<!-- ② 势力等级 -->
<div class="rule-block">
<h3>② 势力等级</h3>
<p class="rule-intro">文明内部群体的聚合度,由人口、领土、影响力累计。势力等级越高,可调动的世界资源与外交权重越大——决定文明"能调动什么"。</p>
<div class="tier-list">
<div class="tier-row"><span class="tn">Lv.1</span><span class="tname">墟族</span><span class="tdesc">血缘小群体,蛮荒无制度。</span></div>
<div class="tier-row"><span class="tn">Lv.2</span><span class="tname">灵聚</span><span class="tdesc">灵气聚落联盟,初步治理与贸易。</span></div>
<div class="tier-row"><span class="tn">Lv.3</span><span class="tname">玄邑</span><span class="tdesc">疆域整合,军法、道规体系完善。</span></div>
<div class="tier-row"><span class="tn">Lv.4</span><span class="tname">冥朝</span><span class="tdesc">一统大地,掌控地下浅层资源调度。</span></div>
<div class="tier-row"><span class="tn">Lv.5</span><span class="tname">穹统</span><span class="tdesc">天地大陆霸主,穿梭星空深渊作战。</span></div>
<div class="tier-row"><span class="tn">Lv.6</span><span class="tname">溯盟</span><span class="tdesc">多维文明联合体,触及世界本源博弈。</span></div>
</div>
</div>
<!-- ③ 人物角色等级 -->
<div class="rule-block">
<h3>③ 人物角色等级 · 十二阶段</h3>
<p class="rule-intro">个体(英雄 / 领袖 / 特殊 NPC的成长阶序通过历练、天赋点亮与装备积累提升影响可承担的职责与战力——决定个体"能做成什么"。</p>
<div class="tier-list">
<div class="tier-row"><span class="tn">Lv.1</span><span class="tname">萌新</span><span class="tdesc">初入世界的无名者。</span></div>
<div class="tier-row"><span class="tn">Lv.2</span><span class="tname">学徒</span><span class="tdesc">掌握基础技艺。</span></div>
<div class="tier-row"><span class="tn">Lv.3</span><span class="tname">熟手</span><span class="tdesc">可独立完成任务。</span></div>
<div class="tier-row"><span class="tn">Lv.4</span><span class="tname">好手</span><span class="tdesc">在某领域崭露头角。</span></div>
<div class="tier-row"><span class="tn">Lv.5</span><span class="tname">精英</span><span class="tdesc">群体中的中坚。</span></div>
<div class="tier-row"><span class="tn">Lv.6</span><span class="tname">大师</span><span class="tdesc">技艺臻于化境。</span></div>
<div class="tier-row"><span class="tn">Lv.7</span><span class="tname">宗匠</span><span class="tdesc">开宗立派的人物。</span></div>
<div class="tier-row"><span class="tn">Lv.8</span><span class="tname">传奇</span><span class="tdesc">事迹被载入编年史。</span></div>
<div class="tier-row"><span class="tn">Lv.9</span><span class="tname">神话</span><span class="tdesc">近乎传说的存在。</span></div>
<div class="tier-row"><span class="tn">Lv.10</span><span class="tname">半神</span><span class="tdesc">触及世界法则。</span></div>
<div class="tier-row"><span class="tn">Lv.11</span><span class="tname">圣者</span><span class="tdesc">被信仰托举的意志。</span></div>
<div class="tier-row"><span class="tn">Lv.12</span><span class="tname">永恒</span><span class="tdesc">超越生死的世界锚点。</span></div>
</div>
</div>
<!-- ④ 制度说明 -->
<div class="rule-block">
<h3>④ 制度说明</h3>
<p class="rule-intro">三者相互独立又彼此加成:<b style="color:#fff">世界等级</b>是舞台上限,决定能发生什么;<b style="color:#fff">势力等级</b>是群体进度,决定能调动什么;<b style="color:#fff">角色等级</b>是个体成长,决定能做成什么。园丁的核心策略,是在三者间分配有限的引导资源,让文明自洽地向上演化。<br><br>跃迁逻辑:角色历练与装备积累→提升势力实力→推动世界阶段跃迁→解锁更深内容(更深的地下层、更远的星空层、更强的神器)。每一级提升都会扩大可叙事的事件池,世界因此越活越厚。</p>
</div>
<!-- ⑤ 货币等级 -->
<div class="rule-block">
<h3>⑤ 货币等级 · 六档</h3>
<p class="rule-intro">文明流通的价值尺度,从凡俗零钱到本源货币,越稀有的货币可交易的资源层级越高——决定文明"能交换什么"。<br><br><b style="color:#fff">兑换规则:</b>100 低等级货币 = 1 高等级货币。</p>
<div class="tier-list">
<div class="tier-row"><span class="tn">Lv.1</span><span class="tname">墟铢</span><span class="tdesc">凡俗通用零钱,以贝壳、骨片、碎石铸成。<b>100 墟铢 = 1 灵币</b></span></div>
<div class="tier-row"><span class="tn">Lv.2</span><span class="tname">灵币</span><span class="tdesc">蕴含微量灵气的金属铸币,聚落以上势力日常使用。<b>100 灵币 = 1 玄锭</b></span></div>
<div class="tier-row"><span class="tn">Lv.3</span><span class="tname">玄锭</span><span class="tdesc">刻有玄纹的稀有锭块,用于高价值交易与功法图纸。<b>100 玄锭 = 1 冥玉</b></span></div>
<div class="tier-row"><span class="tn">Lv.4</span><span class="tname">冥玉</span><span class="tdesc">产自地底深渊的幽暗玉石,蕴含浓郁能量。<b>100 冥玉 = 1 穹鎏</b></span></div>
<div class="tier-row"><span class="tn">Lv.5</span><span class="tname">穹鎏</span><span class="tdesc">来自星空矿脉的稀有鎏金晶体,跨层贸易硬通货。<b>100 穹鎏 = 1 元玺</b></span></div>
<div class="tier-row"><span class="tn">Lv.6</span><span class="tname">元玺</span><span class="tdesc">追溯世界本源凝聚而成的至高货币,可兑换神器、传说事件与维度权限。</span></div>
</div>
</div>
</div>
</section>
<!-- ===== 图鉴 ===== -->
<section class="panel" id="codex">
<div class="codex-wrap">
<div class="codex-cats" id="codexCats"></div>
<div class="codex-subrow">
<div class="codex-subs" id="codexSubs"></div>
<input class="codex-search" id="codexSearch" placeholder="搜索名称 / 描述…" />
<span class="codex-count" id="codexCount"></span>
</div>
<div class="codex-grid" id="codexGrid"></div>
</div>
</section>
<!-- ===== 论坛 ===== -->
<section class="panel" id="forum">
<div class="f-cats">
<div class="ch active" data-fcat="all">全部</div>
<div class="ch" data-fcat="discuss">综合讨论</div>
<div class="ch" data-fcat="strategy">攻略</div>
<div class="ch" data-fcat="lore">背景考据</div>
<div class="ch" data-fcat="showcase">作品秀</div>
<div class="ch" data-fcat="bug">反馈</div>
</div>
<div class="f-main">
<div class="post-form" id="postForm">
<input id="pfTitle" placeholder="标题" />
<textarea id="pfBody" placeholder="说点什么…(演示:仅本次会话内可见)"></textarea>
<div class="row"><button id="pfCancel">取消</button><button class="ok" id="pfOk">发布</button></div>
</div>
<div class="threads" id="threads">
<div class="th" data-fcat="discuss"><span class="pin">置顶</span><span class="tt">【公告】宇森官网第一阶段上线,欢迎园丁们入驻</span><span class="meta"><span class="rp">128</span> 回复 · 官方</span></div>
<div class="th" data-fcat="strategy"><span class="tt">新手向:园丁视角的三种开局思路</span><span class="meta"><span class="rp">42</span> 回复 · 行者</span></div>
<div class="th" data-fcat="discuss"><span class="tt">你们更喜欢哪种族?来投个票</span><span class="meta"><span class="rp">76</span> 回复 · 雾岛</span></div>
<div class="th" data-fcat="showcase"><span class="tt">手绘了我的主世界浮空岛,求轻喷</span><span class="meta"><span class="rp">31</span> 回复 · 青柠</span></div>
<div class="th" data-fcat="bug"><span class="tt">反馈:飞入微观时偶现卡顿</span><span class="meta"><span class="rp">9</span> 回复 · K</span></div>
<div class="th" data-fcat="strategy"><span class="tt">地下矿脉怎么规划最省心?</span><span class="meta"><span class="rp">18</span> 回复 · 老周</span></div>
</div>
</div>
</section>
<!-- ===== 公告 ===== -->
<section class="panel" id="announce">
<div class="notes">
<div class="note pin feat">
<div class="note-cover empty"></div>
<div class="note-body">
<div class="date">2026.07.09 · 官方</div>
<div class="note-title">宇森官网第一阶段上线,欢迎园丁们入驻</div>
<div class="body">
<p>历经三个月打磨,宇森官网第一阶段正式上线。本次更新带来八大板块——介绍、特色、背景、规则、图鉴、论坛、公告、致玩家,全部以单页切换形式呈现,介绍页即一段概念宣传片,让你在三秒内读懂我们要做什么。</p>
<p>「园丁视角」是宇森的核心:你不是旁观者,而是世界的第一位居民。上传你的世界种子,观看五层世界自上而下生长——从星空陨石带到地心熔岩,每一寸地形、每一个种族,都由你的选择塑造。</p>
<p>我们同步开放了社区论坛与图鉴共建入口。欢迎在「论坛」留下你的构想,或在「图鉴」认领尚未完成的种族条目;被采纳的内容,会带上你的署名进入正式世界观。</p>
</div>
</div>
</div>
<div class="note">
<div class="note-cover empty"></div>
<div class="note-body">
<div class="date">2026.07.09</div>
<div class="note-title">图鉴系统全面升级204 种族 + 程序化矢量插画</div>
<div class="body">
<p>图鉴系统完成一次大版本升级:种族条目扩充至 204 条,系别、性格、天赋、神器与传奇事件全面扩量,装备槽位对齐「头 / 手 / 胸 / 裤 / 鞋 / 戒指(双)/ 项链 / 宝物」九宫格。</p>
<p>所有缩略图改用程序化矢量插画生成,零 AI 生图、零版权风险,且在任意分辨率下都不糊。后续会逐步开放玩家投稿的「同人词条」分区。</p>
</div>
</div>
</div>
<div class="note">
<div class="note-cover empty"></div>
<div class="note-body">
<div class="date">2026.07.08</div>
<div class="note-title">概念宣传片《垂直生长》完成</div>
<div class="body">
<p>概念宣传片《垂直生长》正式杀青。片长 90 秒,以「自上而下」的镜头语言呈现五层世界的生长过程:天空层改为云朵地基块,星空层改为陨石科技带,地心则翻涌着熔岩血脉。</p>
<p>这支片子将成为官网介绍页的自动背景,也会在后续对外宣发中使用。如果你喜欢这种克制的科幻基调,记得在论坛给我们反馈。</p>
</div>
</div>
</div>
<div class="note">
<div class="note-cover empty"></div>
<div class="note-body">
<div class="date">2026.07.01</div>
<div class="note-title">园丁视角核心玩法原型启动</div>
<div class="body">
<p>园丁视角核心玩法原型进入验证阶段。首版采用六边形地图,配合「活 NPC」系统——NPC 拥有独立的日程、记忆与情绪,会对你的长期行为产生不同反应。</p>
<p>这一阶段我们重点关注「放手感」:玩家拖拽、缩放、种植、引导文明,每一步都要足够顺滑。手感达标后,再叠加叙事与冲突。</p>
</div>
</div>
</div>
<div class="note">
<div class="note-cover empty"></div>
<div class="note-body">
<div class="date">2026.06.25</div>
<div class="note-title">五大世界层设定公开</div>
<div class="body">
<p>我们公开了宇森的底层世界观——五大世界层:星空、天空、大地、地下、地心。每一层对应一种文明形态与资源循环,层与层之间通过「根系」与「星轨」双向连通。</p>
<p>这套设定不是装饰,而是玩法骨架:你在大地的每一次开采,都会改变地心的压力;地心的每一次喷发,又会重塑天空的气候。</p>
</div>
</div>
</div>
<div class="note">
<div class="note-cover empty"></div>
<div class="note-body">
<div class="date">2026.06.20</div>
<div class="note-title">社区共建计划:你的名字可以写进世界观</div>
<div class="body">
<p>图鉴系统规划正式确定,分为「种族 / 建筑 / 生物 / 神器」四大类目。我们决定把相当一部分条目开放给社区共建。</p>
<p>被采纳的投稿会标注作者署名,并永久保留在官方图鉴中。第一批评审将在论坛「共建」分区进行,欢迎有想法的园丁报名。</p>
</div>
</div>
</div>
<div class="note">
<div class="note-cover empty"></div>
<div class="note-body">
<div class="date">2026.06.12</div>
<div class="note-title">限量内测招募开启</div>
<div class="body">
<p>宇森首轮封闭内测招募正式开启,名额 500 人。我们将优先邀请在论坛持续输出高质量内容的园丁,以及愿意参与玩法共创的核心用户。</p>
<p>内测的重点不是「好不好玩」,而是「哪里别扭」——你的每一条吐槽,都会直接进入我们的周度复盘会。</p>
</div>
</div>
</div>
</div>
</section>
<!-- ===== 剧情(小说阅读器:左目录 + 右正文) ===== -->
<section class="panel" id="story">
<div class="story-wrap">
<aside class="story-toc">
<div class="toc-head">目录<small>CONTENTS</small></div>
<div class="toc-list" id="storyTocList"><div class="toc-loading">加载中…</div></div>
</aside>
<article class="story-reader" id="storyReader">
<div class="reader-loading">从左侧目录选择章节开始阅读</div>
</article>
</div>
</section>
<!-- ===== 世界数据(登录后可见) ===== -->
<section class="panel" id="worlddata">
<div id="worldDataContent"></div>
</section>
</main>
</div>
<script>
// top nav
const navBtns=document.querySelectorAll('.nav button');
const panels=document.querySelectorAll('.panel');
navBtns.forEach(b=>b.addEventListener('click',()=>{
navBtns.forEach(x=>x.classList.remove('active'));
panels.forEach(p=>p.classList.remove('active'));
b.classList.add('active');
document.getElementById(b.dataset.tab).classList.add('active');
}));
// logo 点击回主页intro 面板)
document.getElementById('logoHome').addEventListener('click',()=>{
navBtns.forEach(x=>x.classList.remove('active'));
panels.forEach(p=>p.classList.remove('active'));
document.getElementById('intro').classList.add('active');
});
// ============ 五层世界图片轮播 ============
(function(){
const slides = [
{ img:'assets/slides/01_starry_sky.png', en:'STARRY SKY', cn:'星空', desc:'陨石星空带悬浮于世界最外层,是文明触及维度的起点。星轨连通各浮空岛,穹鎏晶体自星尘中凝结。',
origin:'世界未形,星尘先于万物浮游于虚无。首缕维度自星隙坍缩成形,穹鎏晶体由尘凝就,文明自此仰望苍穹之始。' },
{ img:'assets/slides/02_sky.png', en:'SKY', cn:'天空', desc:'云朵地基块托起浮空岛群,灵气复苏的第一缕风拂过。各族初代聚落,在此仰望也更在此扎根。',
origin:'云基初结,浮空岛群自苍穹垂落凡尘。灵气复苏,初民循风而居,于云上筑城,仰望亦扎根,道统由此萌发。' },
{ img:'assets/slides/03_earth.png', en:'EARTH', cn:'大地', desc:'平原与森林铺展成万物生灵的摇篮,玄荒文明于斯鼎盛。城邦林立,征伐与道统在沃土上并行。',
origin:'沃野铺展为万灵摇篮,玄荒先族自林泉走出。城邦并起,征伐与道统并驰沃土之上,文明于此鼎盛绵延。' },
{ img:'assets/slides/04_underground.png',en:'UNDERGROUND',cn:'地下', desc:'溶洞晶矿与暗河在深处低语,冥玉生于幽暗玉石之中。地心工坊调度浅层资源,灯火不熄。',
origin:'溶洞深开,晶矿与暗河在幽处低语。冥玉生于幽玉深心,地心工坊取浅层之资,灯火长明,匠魂不熄。' },
{ img:'assets/slides/05_core.png', en:'CORE', cn:'地心', desc:'熔岩奔流的地心,是世界诞生的本源。元玺在此凝聚,追溯天地初开的那一瞬永恒。',
origin:'熔岩奔流处,乃世界本源所钟。元玺于亿载炽火中凝聚成形,溯天地初开一瞬,万物归处,亦是起始。' }
];
const container = document.getElementById('slideshow');
if(!container) return;
let current = 0, timer = null;
const INTERVAL = 5500;
// 竖向轨道5 张图上下滑动
const track = document.createElement('div');
track.className='slide-track';
container.appendChild(track);
// 渲染 slide + label
slides.forEach((s,i)=>{
const el = document.createElement('div');
el.className='slide'+(i===0?' active':'');
el.style.backgroundImage="url('"+s.img+"')";
el.innerHTML='<div class="slide-label"><span class="sl-en">'+s.en+'</span><div class="sl-cn">'+s.cn+'</div><div class="sl-desc">'+s.desc+'</div></div>'
+'<div class="slide-origin"><span class="so-tag">起源</span>'+s.origin+'</div>';
track.appendChild(el);
});
const all = track.querySelectorAll('.slide');
// 指示器
const dotsWrap = document.createElement('div');
dotsWrap.className='sl-dots';
slides.forEach((_,i)=>{
const d=document.createElement('div');
d.className='sl-dot'+(i===0?' active':'');
d.addEventListener('click',()=>go(i));
dotsWrap.appendChild(d);
});
document.querySelector('.hero').appendChild(dotsWrap);
const dots = dotsWrap.querySelectorAll('.sl-dot');
function go(idx){
if(idx<0)idx=slides.length-1;if(idx>=slides.length)idx=0;
if(idx===current) return;
all[current].classList.remove('active');dots[current].classList.remove('active');
current=idx;
track.style.transform='translateY('+(-current*100)+'%)';
all[current].classList.add('active');dots[current].classList.add('active');
resetTimer();
}
function next(){ go((current+1)%slides.length); }
function prev(){ go((current-1+slides.length)%slides.length); }
function resetTimer(){ clearInterval(timer); timer=setInterval(next,INTERVAL); }
// 鼠标滚轮上下滑动
let lock=false,wAccum=0;
document.querySelector('.hero').addEventListener('wheel',function(e){
wAccum+=e.deltaY;
if(lock)return;
if(Math.abs(wAccum)>50){
lock=true;
if(wAccum>0)next();else prev();
wAccum=0;
setTimeout(function(){lock=false;},1250);
}
},{passive:true});
resetTimer();
})();
// ============ 程序化矢量缩略图(零 AI 生图,不消耗积分) ============
function hashStr(s){let h=2166136261;for(let i=0;i<s.length;i++){h^=s.charCodeAt(i);h=Math.imul(h,16777619);}return h>>>0;}
function rng(seed){let s=seed>>>0;return function(){s=(Math.imul(s,1664525)+1013904223)>>>0;return s/4294967296;};}
const TIERCOL={'普通':'#9aa6c4','精良':'#5fd38a','稀有':'#4cc9f0','史诗':'#c77dff','传说':'#e7b85c'};
function starPts(cx,cy,spikes,outer,inner){
let pts=[],rot=-Math.PI/2;
for(let i=0;i<spikes*2;i++){const rad=i%2?inner:outer;const a=rot+i*Math.PI/spikes;pts.push((cx+Math.cos(a)*rad).toFixed(1)+','+(cy+Math.sin(a)*rad).toFixed(1));}
return pts.join(' ');
}
function polygonPts(cx,cy,sides,R){
let pts=[];for(let i=0;i<sides;i++){const a=-Math.PI/2+i*2*Math.PI/sides;pts.push((cx+Math.cos(a)*R).toFixed(1)+','+(cy+Math.sin(a)*R).toFixed(1));}
return pts.join(' ');
}
// 各品类图形生成器seed 用于唯一渐变 idr 为确定性随机)
function shRace(r,seed){
const v=Math.floor(r()*3);
let s=`<ellipse cx="50" cy="55" rx="19" ry="21" fill="url(#fg${seed})" stroke="rgba(255,255,255,.25)" stroke-width="1"/>`;
if(v===0){s+=`<path d="M35 40 L31 20 L43 36 Z" fill="url(#fg${seed})"/><path d="M65 40 L69 20 L57 36 Z" fill="url(#fg${seed})"/>`;}
else if(v===1){s+=`<path d="M32 52 L20 42 L36 46 Z" fill="url(#fg${seed})"/><path d="M68 52 L80 42 L64 46 Z" fill="url(#fg${seed})"/>`;}
else{s+=`<path d="M34 36 L42 24 L50 34 L58 24 L66 36 Z" fill="url(#fg${seed})"/>`;}
s+=`<circle cx="43" cy="55" r="2.6" fill="#0b1020"/><circle cx="57" cy="55" r="2.6" fill="#0b1020"/>`;
return s;
}
function shBuild(r,seed){
let s=`<rect x="34" y="44" width="32" height="34" rx="2" fill="url(#fg${seed})" opacity=".92"/>`;
s+=`<path d="M30 44 L50 28 L70 44 Z" fill="url(#fg${seed})"/>`;
for(let i=0;i<3;i++)s+=`<rect x="${39+i*7}" y="52" width="4" height="7" rx="1" fill="rgba(8,12,26,.7)"/>`;
return s;
}
function shElement(r,seed){
const sides=3+Math.floor(r()*6);
let s=`<polygon points="${polygonPts(50,52,sides,25)}" fill="none" stroke="url(#fg${seed})" stroke-width="3"/>`;
s+=`<circle cx="50" cy="52" r="7" fill="url(#fg${seed})"/>`;
return s;
}
function shPersona(r,seed){
let s=`<path d="M32 36 Q50 28 68 36 Q72 56 50 70 Q28 56 32 36 Z" fill="url(#fg${seed})" opacity=".9"/>`;
s+=`<circle cx="43" cy="48" r="2.8" fill="#0b1020"/><circle cx="57" cy="48" r="2.8" fill="#0b1020"/>`;
s+=`<path d="M43 60 Q50 64 57 60" stroke="#0b1020" stroke-width="2" fill="none"/>`;
return s;
}
function shTalent(r,seed){
const sp=4+Math.floor(r()*4);
let s=`<polygon points="${starPts(50,52,sp,26,11)}" fill="url(#fg${seed})" opacity=".92"/>`;
s+=`<circle cx="50" cy="52" r="5" fill="rgba(8,12,26,.55)"/>`;
return s;
}
function shRelic(r,seed){
let s=`<path d="M50 24 L70 46 L50 76 L30 46 Z" fill="url(#fg${seed})" opacity=".9" stroke="rgba(255,255,255,.3)" stroke-width="1"/>`;
s+=`<path d="M30 46 L70 46 M50 24 L42 46 L50 76 M50 24 L58 46 L50 76" stroke="rgba(8,12,26,.4)" stroke-width="1" fill="none"/>`;
return s;
}
function shEquip(sub,seed){
switch(sub){
case 'head': return `<path d="M32 56 Q32 30 50 30 Q68 30 68 56 L68 60 L32 60 Z" fill="url(#fg${seed})"/><rect x="44" y="44" width="12" height="5" rx="2" fill="rgba(8,12,26,.6)"/>`;
case 'hand': {let s=`<rect x="36" y="40" width="28" height="22" rx="6" fill="url(#fg${seed})"/><rect x="33" y="44" width="9" height="14" rx="3" fill="url(#fg${seed})"/>`;for(let i=0;i<3;i++)s+=`<rect x="${40+i*8}" y="62" width="5" height="8" rx="2" fill="url(#fg${seed})"/>`;return s;}
case 'chest': return `<path d="M30 46 Q50 38 70 46 L70 66 Q50 72 30 66 Z" fill="url(#fg${seed})"/><rect x="47" y="46" width="6" height="18" fill="rgba(8,12,26,.5)"/>`;
case 'pants': return `<path d="M38 32 L62 32 L60 74 L52 74 L50 50 L48 74 L40 74 Z" fill="url(#fg${seed})"/>`;
case 'shoes': return `<rect x="34" y="44" width="28" height="6" rx="3" fill="url(#fg${seed})"/><path d="M34 48 L60 48 L66 64 L34 64 Z" fill="url(#fg${seed})"/>`;
case 'ring': return `<circle cx="50" cy="55" r="16" fill="none" stroke="url(#fg${seed})" stroke-width="5"/><path d="M50 29 L57 40 L43 40 Z" fill="url(#fg${seed})"/>`;
case 'neck': return `<path d="M32 42 Q50 64 68 42" fill="none" stroke="url(#fg${seed})" stroke-width="3"/><path d="M44 58 L50 72 L56 58 Z" fill="url(#fg${seed})"/>`;
case 'treasure': return `<rect x="38" y="60" width="24" height="9" rx="2" fill="url(#fg${seed})"/><circle cx="50" cy="46" r="14" fill="url(#fg${seed})" opacity=".9"/><circle cx="50" cy="46" r="6" fill="rgba(255,255,255,.4)"/>`;
default: return `<circle cx="50" cy="50" r="18" fill="url(#fg${seed})"/>`;
}
}
function shEvent(r,seed){
let s=`<ellipse cx="50" cy="52" rx="22" ry="28" fill="none" stroke="url(#fg${seed})" stroke-width="3"/>`;
s+=`<ellipse cx="50" cy="52" rx="12" ry="16" fill="url(#fg${seed})" opacity=".5"/>`;
return s;
}
function shapeFor(cat,sub,r,seed){
switch(cat){
case 'race': return shRace(r,seed);
case 'build': return shBuild(r,seed);
case 'element': return shElement(r,seed);
case 'persona': return shPersona(r,seed);
case 'talent': return shTalent(r,seed);
case 'relic': return shRelic(r,seed);
case 'equip': return shEquip(sub,seed);
case 'event': return shEvent(r,seed);
default: return `<circle cx="50" cy="50" r="18" fill="url(#fg${seed})"/>`;
}
}
function thumbSVG(it){
const seed=hashStr(it.cat+it.sub+it.name);
const r=rng(seed);
const h=Math.floor(r()*360);
const h2=(h+40)%360;
const tcol=TIERCOL[it.tier]||'#9aa6c4';
const shape=shapeFor(it.cat,it.sub,r,seed);
return `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">`+
`<defs>`+
`<radialGradient id="bg${seed}" cx="50%" cy="36%" r="78%">`+
`<stop offset="0%" stop-color="hsl(${h},48%,26%)"/>`+
`<stop offset="100%" stop-color="hsl(${h},42%,9%)"/>`+
`</radialGradient>`+
`<linearGradient id="fg${seed}" x1="0" y1="0" x2="0" y2="1">`+
`<stop offset="0%" stop-color="hsl(${h},72%,66%)"/>`+
`<stop offset="100%" stop-color="hsl(${h2},70%,56%)"/>`+
`</linearGradient>`+
`</defs>`+
`<rect width="100" height="100" fill="url(#bg${seed})"/>`+
shape+
`<circle cx="50" cy="50" r="46" fill="none" stroke="${tcol}" stroke-width="2.5" opacity=".9"/>`+
`</svg>`;
}
// ============ 图鉴(数据驱动 + 程序化缩略图) ============
// ---- 数据源层 ----
// 当前: 内置框架样例 (window.CODEX)
// 将来: 从 Odoo 后台取数 (模型 game.codex.entry),经已代理的 /jsonrpc 读取
// 启用方式: 在 codex-data.js 之前设置 window.CODEX_SOURCE='odoo'
const CODEX_SOURCE = window.CODEX_SOURCE || 'odoo'; // 'embedded' | 'odoo'
async function loadCodex(){
if (CODEX_SOURCE === 'odoo') return await fetchCodexFromOdoo();
return window.CODEX;
}
// Odoo JSON-RPC 取数预留接口后台模型就绪后启用nginx 已代理 /jsonrpc -> Odoo:8069
async function fetchCodexFromOdoo(){
const r = await fetch('/game/api/codex?db=game', {credentials:'same-origin'});
const d = await r.json();
const ICON={'race':'👥','build':'🏗️','element':'🔮','persona':'🎭','talent':'⚡','relic':'💎','equip':'🛡️','event':'🌟'};
const groups = (d && d.groups) || [];
const categories = groups.map(g=>({id:g.key, name:g.label, icon:ICON[g.key]||'◆'}));
const subs={}; const items=[];
groups.forEach(g=>{(g.subs||[]).forEach(s=>{(subs[g.key]=subs[g.key]||[]).push(s.name);(s.items||[]).forEach(it=>items.push({cat:g.key, sub:it.sub, name:it.name, tier:it.tier, desc:it.desc}));});});
return { categories, subs, items };
}
(async function(){
const grid=document.getElementById('codexGrid');
const catBar=document.getElementById('codexCats');
const subBar=document.getElementById('codexSubs');
const search=document.getElementById('codexSearch');
const countEl=document.getElementById('codexCount');
const data = await loadCodex();
if(!data||!grid) return;
const {categories,subs,items}=data;
let curCat=categories[0].id, curSub='all', q='';
const iconOf=id=>(categories.find(c=>c.id===id)||{}).icon||'◆';
catBar.innerHTML=categories.map(c=>`<button data-cat="${c.id}" class="${c.id===curCat?'active':''}">${c.icon} ${c.name}</button>`).join('');
function renderSubs(){
const list=subs[curCat]||[];
subBar.innerHTML=`<button data-sub="all" class="${curSub==='all'?'active':''}">全部</button>`+
list.map(s=>`<button data-sub="${s}" class="${curSub===s?'active':''}">${s}</button>`).join('');
subBar.querySelectorAll('button').forEach(b=>b.onclick=()=>{curSub=b.dataset.sub;renderSubs();renderGrid();});
}
function renderGrid(){
let arr=items.filter(it=>it.cat===curCat&&(curSub==='all'||it.sub===curSub));
if(q) arr=arr.filter(it=>it.name.includes(q)||it.desc.includes(q));
countEl.textContent='共 '+arr.length+' 条';
grid.innerHTML=arr.map(it=>
`<div class="cx"><div class="thumb">${thumbSVG(it)}</div>`+
`<div class="body"><div class="nm">${it.name}</div>`+
`<div class="meta"><span class="sub">${it.sub}</span><span class="tier">${it.tier}</span></div>`+
`<div class="ds">${it.desc}</div></div></div>`).join('');
}
catBar.querySelectorAll('button').forEach(b=>b.onclick=()=>{
curCat=b.dataset.cat;curSub='all';
catBar.querySelectorAll('button').forEach(x=>x.classList.remove('active'));
b.classList.add('active');renderSubs();renderGrid();
});
search.oninput=e=>{q=e.target.value.trim();renderGrid();};
renderSubs();renderGrid();
})();
// forum category
const fCats=document.querySelectorAll('.f-cats .ch');
const threads=document.querySelectorAll('#threads .th');
fCats.forEach(b=>b.addEventListener('click',()=>{
fCats.forEach(x=>x.classList.remove('active'));
b.classList.add('active');
const c=b.dataset.fcat;
threads.forEach(t=>{ t.style.display=(c==='all'||t.dataset.fcat===c)?'':'none'; });
}));
// forum post (in-session demo)
const postForm=document.getElementById('postForm');
const newPostBtn=document.getElementById('newPost');
if(newPostBtn && postForm) newPostBtn.addEventListener('click',()=>postForm.classList.toggle('show'));
document.getElementById('pfCancel').addEventListener('click',()=>postForm.classList.remove('show'));
document.getElementById('pfOk').addEventListener('click',()=>{
const t=document.getElementById('pfTitle').value.trim();
if(!t)return;
const el=document.createElement('div');
el.className='th';el.dataset.fcat='discuss';
el.innerHTML='<span class="tt">'+t+'</span><span class="meta"><span class="rp">0</span> 回复 · 我</span>';
document.getElementById('threads').prepend(el);
document.getElementById('pfTitle').value='';
document.getElementById('pfBody').value='';
postForm.classList.remove('show');
});
// 登录态 / 进入游戏 逻辑见末尾 <script>(需 game-api.js 先加载)
</script>
<script src="assets/game-api.js"></script>
<script src="assets/site-app.js"></script>
<script>
// ============ 登录态 / 右上角个人信息(从 Odoo 取) ============
(function(){
const userBox = document.getElementById('userBox');
let cachedMe = null;
function renderLoggedOut(){
if(!userBox) return;
userBox.innerHTML = '<a href="login.html?next=world.html" class="auth-link">登录</a><a href="login.html?next=world.html" class="auth-btn">注册</a>';
document.querySelectorAll('.nav .auth-only').forEach(el => el.classList.remove('visible'));
}
function renderLoggedIn(me){
if(!userBox) return;
userBox.innerHTML = '<span class="ub-name"></span><button class="ub-logout" id="ubLogout">退出</button>';
const nm = userBox.querySelector('.ub-name');
if(nm) nm.textContent = me.name || me.login;
document.querySelectorAll('.nav .auth-only').forEach(el => el.classList.add('visible'));
const lb = document.getElementById('ubLogout');
if(lb) lb.addEventListener('click', async ()=>{
try { await API.logout(); } catch(e){}
location.href = 'index.html';
});
}
async function refreshUserBox(){
try{
cachedMe = await API.me();
if(cachedMe && cachedMe.uid) renderLoggedIn(cachedMe);
else renderLoggedOut();
}catch(e){ renderLoggedOut(); }
}
refreshUserBox();
// ============ 背景页:从 odoo 拉取页面板块渲染 ============
async function renderBackground(){
const box = document.getElementById('bgBody');
if(!box) return;
try{
const r = await fetch('/game/api/pages?db=game', {credentials:'same-origin'});
const d = await r.json();
const pages = (d && d.items) || [];
if(!pages.length){ box.innerHTML = '<div class="bg-empty">板块内容即将上线…</div>'; return; }
box.innerHTML = pages.map(p=>{
const cards = (p.items||[]).map(it=>`
<div class="bg-card">
<div class="bg-card-ic">${it.icon||'✦'}</div>
<div class="bg-card-body">
<div class="bg-card-title">${it.title||''}</div>
<div class="bg-card-desc">${it.desc||''}</div>
</div>
</div>`).join('');
return `
<section class="bg-page">
<div class="bg-page-head">
<h2 class="bg-page-title">${p.title||''}</h2>
${p.subtitle?`<div class="bg-page-sub">${p.subtitle}</div>`:''}
</div>
${p.body_html?`<div class="bg-page-body">${p.body_html}</div>`:''}
${cards?`<div class="bg-page-items">${cards}</div>`:''}
</section>`;
}).join('');
}catch(e){
box.innerHTML = '<div class="bg-empty">板块加载失败,请刷新重试。</div>';
}
}
renderBackground();
// ============ 世界数据:大报表(演示数据,后端接口待接入) ============
async function renderWorldData(){
const box = document.getElementById('worldDataContent');
if(!box) return;
// TODO: 后端接入后改为 await API.worldData() 取真实数据
const data = worldDataMock();
const kpis = data.kpi.map(k=>`
<div class="wd-kpi-card">
<span class="wd-kpi-delta ${k.delta>=0?'wd-up':'wd-down'}">${k.delta>=0?'▲':'▼'} ${Math.abs(k.delta)}%</span>
<div class="wd-kpi-ic">${k.icon}</div>
<div class="wd-kpi-num">${k.num}<span class="u">${k.unit||''}</span></div>
<div class="wd-kpi-label">${k.label}</div>
</div>`).join('');
const bars = data.bars.map(b=>`
<div class="wd-bar-row">
<div class="wd-bar-top"><span class="k">${b.k}</span><span class="v">${b.v}%</span></div>
<div class="wd-track"><div class="wd-fill" style="width:${b.v}%"></div></div>
</div>`).join('');
const rows = data.layers.map(l=>`
<tr>
<td class="lead">${l.lv}</td>
<td>${l.band}</td>
<td>${l.eco}</td>
<td>${l.pop}</td>
<td><span class="pill">${l.terr}</span></td>
</tr>`).join('');
const ranks = data.factions.map((f,i)=>`
<div class="wd-rank-row">
<div class="wd-rank-no">${i+1}</div>
<div class="wd-rank-main">
<div class="wd-rank-name"><span>${f.name}</span><span class="s">${f.score} 影响力</span></div>
<div class="wd-rank-track"><div class="wd-rank-fill" style="width:${f.pct}%"></div></div>
</div>
</div>`).join('');
box.innerHTML = `
<span class="wd-tag">演示数据 · 后端接口待接入</span>
<div class="wd-kpi">${kpis}</div>
<div class="wd-h">核心指标进度 <small>CORE METRICS</small></div>
<div class="wd-bars">${bars}</div>
<div class="wd-h">世界五层结构 <small>WORLD LAYERS</small></div>
<table class="wd-table">
<thead><tr><th>层级</th><th>高度带</th><th>地表生态</th><th>人口占比</th><th>代表地形</th></tr></thead>
<tbody>${rows}</tbody>
</table>
<div class="wd-h">势力影响力排行 <small>FACTION RANKING</small></div>
<div class="wd-rank">${ranks}</div>
`;
}
function worldDataMock(){
return {
kpi:[
{icon:'👥',num:'12,840',unit:'人',label:'世界总人口',delta:3},
{icon:'🌍',num:'Lv.7',unit:'',label:'当前世界等级',delta:1},
{icon:'🏛️',num:'38',unit:'个',label:'活跃势力',delta:5},
{icon:'⛪',num:'9.6M',unit:'',label:'信仰值总量',delta:8},
{icon:'⚔️',num:'214',unit:'',label:'在线角色',delta:-2},
{icon:'💎',num:'1.3M',unit:'',label:'流通晶币',delta:4}
],
bars:[
{k:'世界安定度',v:78},
{k:'生态健康度',v:64},
{k:'信仰传播度',v:88},
{k:'文明探索度',v:52},
{k:'势力平衡度',v:71},
{k:'资源充裕度',v:60}
],
layers:[
{lv:'第一层 · 地表',band:'080m',eco:'平原 / 森林 / 河流',pop:'58%',terr:'草原城邦'},
{lv:'第二层 · 丘陵',band:'80260m',eco:'梯田 / 矿脉 / 林地',pop:'22%',terr:'山道驿站'},
{lv:'第三层 · 高地',band:'260600m',eco:'苔原 / 雪线 / 圣峰',pop:'12%',terr:'云端神殿'},
{lv:'第四层 · 地下',band:'-30-200m',eco:'溶洞 / 晶矿 / 暗河',pop:'6%',terr:'地心工坊'},
{lv:'第五层 · 秘境',band:'位面裂隙',eco:'虚空 / 混沌 / 星界',pop:'2%',terr:'裂隙哨站'}
],
factions:[
{name:'曦光同盟',score:9420,pct:100},
{name:'玄铁工盟',score:8730,pct:93},
{name:'碧波联邦',score:7610,pct:81},
{name:'夜枭议会',score:6880,pct:73},
{name:'荒原游商',score:5240,pct:56},
{name:'星语祭司团',score:4310,pct:46}
]
};
}
renderWorldData();
// ============ 进入游戏:已登录→世界页;未登录→登录页 ============
const enterBtn = document.getElementById('enterGame');
if(enterBtn) enterBtn.addEventListener('click', async ()=>{
try {
const me = cachedMe || await API.me();
if (me && me.uid) location.href = 'world.html';
else location.href = 'login.html?next=world.html';
} catch(e) {
location.href = 'login.html?next=world.html';
}
});
})();
</script>
</body>
</html>