game/index.html
李鹏宇 f5c7ea4ddf 1.世界等级五个阶段,自己想办法命名和设计
2.势力等级分为6个
3.人物角色等级分为14个
4.货币也分为6档

一个背景的菜单栏。
写特色,介绍,背景,说明,致玩家等等
2026-07-23 18:53:54 +08:00

609 lines
42 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>宇森 · Yusen活的文明系统模拟</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&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,.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;}
.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);}
.cta:hover{filter:brightness(1.08);}
.content{flex:1;position:relative;overflow:hidden;}
.panel{position:absolute;inset:0;display:none;flex-direction:column;padding:30px 44px;overflow:hidden;}
.panel.active{display:flex;}
/* ===== 介绍(视频主页) ===== */
#intro{padding:0;}
.hero{position:relative;flex:1;border-radius:0;overflow:hidden;}
.hero video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
.hero .veil{position:absolute;inset:0;background:linear-gradient(110deg, rgba(8,12,26,.80) 0%, rgba(14,23,48,.28) 46%, rgba(8,12,26,.5) 100%);}
.hero .copy{position:absolute;left:56px;bottom:48px;right:56px;}
.hero .copy h1{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:64px;letter-spacing:8px;color:#fff;text-shadow:0 0 28px rgba(139,92,246,.8);line-height:1;}
.hero .copy .sub2{font-family:"Press Start 2P",monospace;font-size:13px;color:var(--gold);margin:12px 0 16px;letter-spacing:1px;}
.hero .copy .tag{font-size:22px;font-weight:500;color:#fff;margin-bottom:9px;}
.hero .copy .desc{font-size:15px;color:var(--text-dim);max-width:560px;line-height:1.7;}
.hero .copy{z-index:3;pointer-events:none;}
.hero .enter-wrap{position:absolute;left:0;right:0;bottom:44px;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;}
.sec-title{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:32px;letter-spacing:4px;color:#fff;text-shadow:0 0 18px rgba(139,92,246,.5);}
.sec-title small{display:block;font-family:"Press Start 2P",monospace;font-size:9px;color:var(--magenta);letter-spacing:1px;margin-top:7px;}
.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:row;gap:38px;align-items:stretch;}
.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:14px;}
.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:22px 44px;gap:0;}
.codex-wrap{display:flex;flex-direction:column;gap:13px;height:100%;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(176px,1fr));gap:14px;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:22px;}
.f-cats{flex:0 0 180px;display:flex;flex-direction:column;gap:8px;}
.f-cats .ch{font-size:14px;color:var(--text-dim);background:var(--card);border:1px solid var(--card-bd);padding:11px 14px;border-radius:10px;cursor:pointer;transition:.2s;}
.f-cats .ch:hover{color:#fff;}
.f-cats .ch.active{color:#fff;background:rgba(139,92,246,.22);border-color:var(--magenta);}
.f-main{flex:1;display:flex;flex-direction:column;min-width:0;}
.f-head{display:flex;align-items:center;margin-bottom:12px;gap:12px;}
.f-head h2{font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:24px;letter-spacing:2px;color:#fff;}
.f-head .new{margin-left:auto;font-size:13px;color:#0b1020;background:linear-gradient(120deg,var(--gold),#f0d49a);padding:8px 15px;border-radius:9px;font-weight:700;cursor:pointer;}
.f-head .new:hover{filter:brightness(1.08);}
.threads{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:10px;padding-right:6px;}
.th{display:flex;align-items:center;gap:14px;background:var(--card);border:1px solid var(--card-bd);border-radius:11px;padding:13px 16px;transition:.2s;cursor:pointer;}
.th:hover{border-color:var(--violet);background:rgba(139,92,246,.10);}
.th .pin{font-size:10px;font-family:"Press Start 2P",monospace;color:var(--gold);}
.th .tt{flex:1;font-size:15px;color:#fff;}
.th .meta{font-size:12px;color:var(--text-dim);white-space:nowrap;}
.th .rp{color:var(--cyan);font-weight:500;}
.post-form{display:none;flex-direction:column;gap:8px;background:var(--card);border:1px solid var(--magenta);border-radius:12px;padding:14px;margin-bottom:12px;}
.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:8px;color:var(--text);padding:9px 12px;font-family:inherit;font-size:13px;outline:none;}
.post-form textarea{resize:vertical;min-height:64px;}
.post-form .row{display:flex;gap:8px;justify-content:flex-end;}
.post-form .row button{font-size:13px;padding:7px 16px;border-radius:8px;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:18px;}
#announce .notes{flex:1;min-height:0;display:flex;flex-direction:column;gap:14px;overflow-y:auto;padding-right:6px;}
.note{border-left:3px solid var(--magenta);padding:12px 16px;background:var(--card);border:1px solid var(--card-bd);border-left:3px solid var(--magenta);border-radius:0 12px 12px 0;}
.note .date{font-family:"Press Start 2P",monospace;font-size:9px;color:var(--cyan);margin-bottom:8px;}
.note .body{font-size:14px;color:var(--text);line-height:1.7;}
.note.pin{box-shadow:var(--glow);}
/* ===== 致玩家 ===== */
#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;}
@media (max-width:900px){
.grid{grid-template-columns:repeat(2,1fr);}
#background,#forum{flex-direction:column;overflow-y:auto;}
.layers,.f-cats{flex:none;}
.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;}
}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<div class="logo"><span class="cn">宇森</span><span class="en">YUSEN</span></div>
<nav class="nav">
<button data-tab="intro" class="active">介绍</button>
<button data-tab="features">特色</button>
<button data-tab="background">背景</button>
<button data-tab="rules">规则</button>
<button data-tab="codex">图鉴</button>
<button data-tab="forum">论坛</button>
<button data-tab="announce">公告</button>
<button data-tab="letter">致玩家</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">
<video autoplay muted loop playsinline>
<source src="assets/yusen-trailer.mp4" type="video/mp4" />
</video>
<div class="veil"></div>
<div class="enter-wrap">
<button class="enter-btn" id="enterGame">进入游戏</button>
</div>
<div class="copy">
<h1>宇森</h1>
<div class="tag">你不是玩家,你是园丁。</div>
<div class="desc">一款活的文明系统模拟——播种一个会自己生长的世界,从地心到陨石星空,见证文明在垂直切面中自发演化。</div>
</div>
</div>
</section>
<!-- ===== 特色 ===== -->
<section class="panel" id="features"></section>
<!-- ===== 背景 ===== -->
<section class="panel" id="background"></section>
<!-- ===== 规则 ===== -->
<section class="panel" id="rules">
<div class="sec-title">等级制度<small>TIERS &amp; RANK SYSTEM</small></div>
<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="guide">玩法攻略</div>
<div class="ch" data-fcat="fan">同人创作</div>
<div class="ch" data-fcat="bug">BUG 反馈</div>
</div>
<div class="f-main">
<div class="f-head">
<h2>社区论坛</h2>
<div class="new" id="newPost">发新帖</div>
</div>
<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">PIN</span><span class="tt">【公告】宇森官网第一阶段上线,欢迎园丁们入驻</span><span class="meta"><span class="rp">128</span> 回复 · 官方</span></div>
<div class="th" data-fcat="guide"><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="fan"><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="guide"><span class="tt">地下矿脉怎么规划最省心?</span><span class="meta"><span class="rp">18</span> 回复 · 老周</span></div>
</div>
</div>
</section>
<!-- ===== 公告 ===== -->
<section class="panel" id="announce">
<div class="sec-title">公告<small>ANNOUNCEMENTS</small></div>
<div class="notes">
<div class="note pin"><div class="date">2026.07.09</div><div class="body">宇森官网第一阶段上线:介绍 / 特色 / 背景 / 规则 / 图鉴 / 论坛 / 公告 / 致玩家 八块,单页切换呈现,介绍页即概念宣传片。</div></div>
<div class="note"><div class="date">2026.07.09</div><div class="body">图鉴系统升级:种族 204 条、系别/性格/天赋/神器/传奇事件全面扩量,装备槽位对齐 头/手/胸/裤/鞋/戒指(双)/项链/宝物;全部缩略图改为程序化矢量插画,零 AI 生图。</div></div>
<div class="note"><div class="date">2026.07.08</div><div class="body">概念宣传片《垂直生长》完成,呈现五层世界自上而下生长的镜头语言(天空层改为云朵地基块,星空层改为陨石科技带)。</div></div>
<div class="note"><div class="date">2026.07.01</div><div class="body">园丁视角核心玩法原型启动,首版六边形地图与活 NPC 验证中。</div></div>
<div class="note"><div class="date">2026.06.20</div><div class="body">图鉴系统规划确定:种族 / 建筑 / 生物 / 神器 四大类目,美术持续补充中。</div></div>
</div>
</section>
<!-- ===== 致玩家 ===== -->
<section class="panel" id="letter"></section>
</main>
</div>
<script src="codex-data.js"></script>
<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');
}));
// ============ 程序化矢量缩略图(零 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');
document.getElementById('newPost').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');
async function refreshUserBox(){
if(!userBox) return;
try{
const me = await API.me();
if(me && me.uid){
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;
const lb = document.getElementById('ubLogout');
if(lb) lb.addEventListener('click', async ()=>{
await API.logout();
location.href = 'index.html';
});
} else {
userBox.innerHTML = ''; // 未登录:右上角不显示登录入口,登录经“进入游戏”引导
}
}catch(e){ userBox.innerHTML = ''; }
}
refreshUserBox();
// ============ 进入游戏:已登录→世界页;未登录→提示登录 ============
// TEMP: 本地临时默认直接进入游戏,跳过 Odoo 登录校验;打通后端后改回 API.me() 判断
const enterBtn = document.getElementById('enterGame');
if(enterBtn) enterBtn.addEventListener('click', ()=>{
location.href = 'world.html';
});
})();
</script>
</body>
</html>