game/codex.html

600 lines
26 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="stylesheet" href="assets/fonts/yusen-fonts.css" />
<link rel="icon" type="image/png" sizes="48x48" href="assets/ui/logo_planet_48.png?v=20260723">
<link rel="stylesheet" href="assets/common.css" />
<script src="assets/game-api.js"></script>
<style>
/* ===== 图鉴页面专用样式(公共变量/背景/导航由 common.css 提供) ===== */
/* 动态星空背景 */
body::before{
content:'';position:fixed;inset:0;z-index:-1;
background-image:
radial-gradient(1px 1px at 10% 20%,rgba(255,255,255,.3),transparent),
radial-gradient(1px 1px at 30% 65%,rgba(255,255,255,.2),transparent),
radial-gradient(1.5px 1.5px at 55% 15%,rgba(255,255,255,.25),transparent),
radial-gradient(1px 1px at 70% 80%,rgba(255,255,255,.15),transparent),
radial-gradient(1px 1px at 85% 40%,rgba(255,255,255,.2),transparent),
radial-gradient(1.5px 1.5px at 15% 85%,rgba(255,255,255,.1),transparent),
radial-gradient(1px 1px at 45% 50%,rgba(255,255,255,.15),transparent),
radial-gradient(1px 1px at 92% 12%,rgba(255,255,255,.2),transparent);
}
/* ===== 筛选区 ===== */
.filters{
position:sticky;top:0;z-index:49;
background:#0c1120;
padding:8px 20px;border-bottom:1px solid rgba(255,255,255,.06);
box-shadow:0 1px 0 rgba(0,0,0,.4);
}
.filterRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.filterRow+.filterRow{margin-top:6px;}
.filterLabel{font-size:13px;color:#7b8ea8;text-transform:uppercase;letter-spacing:1px;margin-right:4px;font-weight:700;}
/* 搜索框 */
.searchBox{
margin-left:auto;display:flex;align-items:center;gap:10px;
padding:6px 14px;border-radius:999px;font-size:13px;
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);
color:#9fb3cc;transition:.18s;min-width:180px;
}
.searchBox:focus-within{
background:rgba(139,92,246,.16);border-color:rgba(139,92,246,.5);
box-shadow:0 0 16px rgba(139,92,246,.25);
}
.searchBox input{
flex:1;min-width:0;background:transparent;border:none;outline:none;
color:#e6f0fb;font:inherit;font-size:15px;
}
.searchBox input::placeholder{color:#7b8ea8;}
.searchBox .icon{font-size:16px;opacity:.75;}
.searchBox .clear{
cursor:pointer;opacity:.5;font-size:13px;padding:0 6px;
transition:.15s;
}
.searchBox .clear:hover{opacity:1;color:#ffb4b4;}
/* 分类标签 */
.catTab{
padding:5px 14px;border-radius:999px;font-size:14px;cursor:pointer;
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);color:#9fb3cc;transition:.18s;
}
.catTab:hover{background:rgba(139,92,246,.14);color:#d0dfff;border-color:rgba(139,92,246,.3);}
.catTab.active{background:rgba(139,92,246,.2);color:#fff;border-color:rgba(139,92,246,.5);box-shadow:0 0 12px rgba(139,92,246,.2);}
/* 品质筛点 */
.ql{display:inline-flex;align-items:center;gap:6px;cursor:pointer;padding:4px 11px;border-radius:8px;transition:.15s;}
.ql:hover{background:rgba(255,255,255,.04);}
.ql .dot{width:11px;height:11px;border-radius:50%;display:inline-block;}
.ql .lbl{font-size:13px;color:#9fb3cc;}
.ql.active .lbl{color:#fff;font-weight:600;}
.ql.active .dot{box-shadow:0 0 8px currentColor;}
/* ===== 数据统计条 ===== */
/* statsBar 已移除 */
/* ===== 卡片网格 ===== */
.grid{
padding:20px 24px 40px;
display:grid;
grid-template-columns:repeat(auto-fill,minmax(160px,1fr));
gap:14px;
}
@media (min-width:900px){.grid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));}}
@media (max-width:500px){.grid{padding:14px 12px;gap:10px;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));}}
/* ===== 卡片 ===== */
.card{
position:relative;border-radius:14px;cursor:pointer;user-select:none;
background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);
padding:16px 12px 12px;transition:.22s;overflow:hidden;
display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center;
}
.card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;border-radius:14px 14px 0 0;opacity:0;transition:.22s;}
.card:hover{transform:translateY(-3px);box-shadow:0 8px 24px rgba(0,0,0,.4);}
.card:hover::before{opacity:1;}
.card .icon{font-size:36px;line-height:1.2;margin-bottom:2px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.3));}
.card .name{font-size:14px;font-weight:700;color:#e6f0fb;line-height:1.3;}
.card .sub{font-size:11px;color:#7b8ea8;line-height:1.3;}
/* 品质色条 */
.card.q-common::before{background:linear-gradient(90deg,#9ca3af,#d1d5db);}
.card.q-uncommon::before{background:linear-gradient(90deg,#22c55e,#4ade80);}
.card.q-rare::before{background:linear-gradient(90deg,#3b82f6,#60a5fa);}
.card.q-epic::before{background:linear-gradient(90deg,#a855f7,#c084fc);}
.card.q-legendary::before{background:linear-gradient(90deg,#f59e0b,#fbbf24);}
/* 品质光晕 */
.card.q-common:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(156,163,175,.08);}
.card.q-uncommon:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(34,197,94,.12);}
.card.q-rare:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(59,130,246,.12);}
.card.q-epic:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(168,85,247,.12);}
.card.q-legendary:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(245,158,11,.12);}
/* 品质角标 */
.card .qbadge{
position:absolute;top:8px;right:8px;
font-size:9px;font-weight:700;padding:1px 6px;border-radius:6px;
text-transform:uppercase;letter-spacing:.5px;
}
.qbadge.common{background:rgba(156,163,175,.2);color:#9ca3af;border:1px solid rgba(156,163,175,.3);}
.qbadge.uncommon{background:rgba(34,197,94,.15);color:#4ade80;border:1px solid rgba(34,197,94,.3);}
.qbadge.rare{background:rgba(59,130,246,.15);color:#60a5fa;border:1px solid rgba(59,130,246,.3);}
.qbadge.epic{background:rgba(168,85,247,.15);color:#c084fc;border:1px solid rgba(168,85,247,.3);}
.qbadge.legendary{background:rgba(245,158,11,.15);color:#fbbf24;border:1px solid rgba(245,158,11,.3);}
/* 生物/野怪/种族 专属样式 */
.card.creature{border-style:solid;border-color:rgba(100,200,255,.12);}
.card.creature .icon{font-size:38px;}
.card.creature .tags{display:flex;gap:4px;flex-wrap:wrap;justify-content:center;margin-top:2px;}
.card.creature .tag{font-size:9px;padding:1px 6px;border-radius:4px;background:rgba(255,255,255,.06);color:#8899aa;}
.card.monster{border-color:rgba(168,85,247,.18);}
.card.monster .icon{font-size:38px;}
.card.monster.tier-elite{border-color:rgba(168,85,247,.35);box-shadow:0 0 14px rgba(168,85,247,.1);}
.card.monster.tier-boss{border-color:rgba(245,158,11,.4);box-shadow:0 0 18px rgba(245,158,11,.14);}
.card.monster .tags{display:flex;gap:4px;flex-wrap:wrap;justify-content:center;margin-top:2px;}
.card.monster .tag{font-size:9px;padding:1px 6px;border-radius:4px;background:rgba(255,255,255,.06);color:#8899aa;}
.card.race{border-color:rgba(100,200,255,.14);}
.card.race .icon{font-size:38px;}
.card.race .tags{display:flex;gap:4px;flex-wrap:wrap;justify-content:center;margin-top:2px;}
.card.race .tag{font-size:9px;padding:1px 6px;border-radius:4px;background:rgba(255,255,255,.06);color:#8899aa;}
/* 野怪等级角标 */
.card .tierbadge{position:absolute;top:8px;right:8px;font-size:9px;font-weight:700;padding:1px 6px;border-radius:6px;letter-spacing:.5px;}
.tierbadge.normal{background:rgba(156,163,175,.2);color:#9ca3af;border:1px solid rgba(156,163,175,.3);}
.tierbadge.elite{background:rgba(168,85,247,.18);color:#c084fc;border:1px solid rgba(168,85,247,.35);}
.tierbadge.boss{background:rgba(245,158,11,.18);color:#fbbf24;border:1px solid rgba(245,158,11,.35);}
/* ===== 空状态 ===== */
.empty{
grid-column:1/-1;text-align:center;padding:60px 20px;color:#556677;
font-size:14px;display:none;
}
.empty.show{display:block;}
/* ===== 详情弹窗 ===== */
.modalOverlay{
position:fixed;inset:0;z-index:1000;
background:rgba(4,7,15,.65);backdrop-filter:blur(6px);
display:flex;align-items:center;justify-content:center;
opacity:0;visibility:hidden;transition:.28s;padding:20px;
}
.modalOverlay.open{opacity:1;visibility:visible;}
.modalBox{
width:520px;max-width:100%;max-height:90vh;overflow-y:auto;
background:linear-gradient(180deg,rgba(17,23,42,.98),rgba(10,14,26,.98));
border:1px solid rgba(139,92,246,.35);border-radius:18px;
box-shadow:0 12px 48px rgba(0,0,0,.6);
padding:28px 28px 24px;transform:translateY(20px);transition:transform .3s ease;
}
.modalOverlay.open .modalBox{transform:translateY(0);}
.md-close{
position:absolute;top:12px;right:14px;width:28px;height:28px;border-radius:8px;
border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04);color:#9aa6c4;
font-size:13px;cursor:pointer;transition:.12s;
}
.md-close:hover{background:rgba(239,68,68,.2);color:#ffb4b4;}
.md-header{display:flex;align-items:center;gap:16px;margin-bottom:20px;position:relative;}
.md-icon{font-size:52px;line-height:1;}
.md-info{flex:1;}
.md-title{font-size:22px;font-weight:800;color:#eef2fb;}
.md-meta{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;}
.md-meta .mt{
font-size:11px;padding:2px 10px;border-radius:6px;
background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);color:#9fb3cc;
}
.md-section{margin-top:18px;}
.md-section h3{font-size:13px;font-weight:700;color:#7b8ea8;margin-bottom:8px;text-transform:uppercase;letter-spacing:1px;}
.md-desc{font-size:14px;color:#cdd8e6;line-height:1.7;padding:10px 14px;border-radius:10px;background:rgba(255,255,255,.03);}
.md-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.md-grid .gi{padding:8px 12px;border-radius:8px;background:rgba(255,255,255,.03);display:flex;justify-content:space-between;align-items:center;}
.md-grid .gi .gl{font-size:12px;color:#8899aa;}
.md-grid .gi .gv{font-size:13px;font-weight:600;color:#cdd8e6;}
/* 品质等级条 */
.qualityBar{display:flex;gap:6px;margin:12px 0;}
.qualityBar .qlv{
flex:1;height:4px;border-radius:2px;background:rgba(255,255,255,.08);
transition:.3s;
}
.qualityBar .qlv.on{background:linear-gradient(90deg,#9ca3af,#22c55e,#3b82f6,#a855f7,#f59e0b);}
.qualityBar .qlv.on+.qlv.on{background:linear-gradient(90deg,#22c55e,#3b82f6,#a855f7,#f59e0b);}
.qualityBar .qlv.on+.qlv.on+.qlv.on{background:linear-gradient(90deg,#3b82f6,#a855f7,#f59e0b);}
.qualityBar .qlv.on+.qlv.on+.qlv.on+.qlv.on{background:linear-gradient(90deg,#a855f7,#f59e0b);}
.qualityBar .qlv.on+.qlv.on+.qlv.on+.qlv.on+.qlv.on{background:#f59e0b;}
.md-recipes{margin-top:14px;display:flex;flex-direction:column;gap:8px;}
.md-recipe{
display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:10px;
background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);
}
.md-recipe .rp-icon{font-size:20px;}
.md-recipe .rp-name{font-size:13px;color:#cdd8e6;font-weight:600;}
.md-recipe .rp-num{font-size:12px;color:#8899aa;margin-left:auto;}
.md-chips{margin-top:14px;display:flex;flex-wrap:wrap;gap:8px;}
.md-chip{padding:6px 12px;border-radius:8px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);font-size:13px;color:#cdd8e6;}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<a href="index.html" class="logo"><span class="cn">宇森</span></a>
<nav class="nav">
<a href="announce.html">公告</a>
<a href="background.html">背景</a>
<a href="rules.html">规则</a>
<a href="codex.html">图鉴</a>
<a href="story.html">剧情</a>
<a href="forum.html" class="auth-only">论坛</a>
<a href="worlddata.html" class="auth-only">世界数据</a>
</nav>
<div class="spacer"></div>
<div class="user-box" id="userBox"></div>
</header>
<main class="page-content hide-scroll">
<!-- ===== 筛选区 ===== -->
<div class="filters" id="filters">
<div class="filterRow">
<span class="filterLabel">分类</span>
<span class="catTab active" data-cat="all" onclick="setCat('all')">🏛️ 全部</span>
<span class="catTab" data-cat="material" onclick="setCat('material')">🪵 材料</span>
<span class="catTab" data-cat="processed" onclick="setCat('processed')">🏭 加工品</span>
<span class="catTab" data-cat="creature" onclick="setCat('creature')">🐾 生物</span>
<span class="catTab" data-cat="monster" onclick="setCat('monster')">👹 野怪</span>
<span class="catTab" data-cat="race" onclick="setCat('race')">🌍 种族</span>
<span class="catTab" data-cat="special" onclick="setCat('special')">✨ 特殊</span>
<div class="searchBox">
<span class="icon">🔍</span>
<input id="searchInput" type="text" placeholder="搜索…" />
<span class="clear" id="searchClear" onclick="clearSearch()" title="清空" style="display:none;"></span>
</div>
</div>
<div class="filterRow">
<span class="filterLabel">品质</span>
<span class="ql active" data-ql="all" onclick="setQual('all')">
<span class="dot" style="background:linear-gradient(135deg,#9ca3af,#fbbf24);"></span>
<span class="lbl">全部</span>
</span>
<span class="ql" data-ql="common" onclick="setQual('common')">
<span class="dot" style="background:#9ca3af;"></span>
<span class="lbl">普通</span>
</span>
<span class="ql" data-ql="uncommon" onclick="setQual('uncommon')">
<span class="dot" style="background:#22c55e;"></span>
<span class="lbl">优秀</span>
</span>
<span class="ql" data-ql="rare" onclick="setQual('rare')">
<span class="dot" style="background:#3b82f6;"></span>
<span class="lbl">精良</span>
</span>
<span class="ql" data-ql="epic" onclick="setQual('epic')">
<span class="dot" style="background:#a855f7;"></span>
<span class="lbl">史诗</span>
</span>
<span class="ql" data-ql="legendary" onclick="setQual('legendary')">
<span class="dot" style="background:#f59e0b;"></span>
<span class="lbl">传说</span>
</span>
</div>
</div>
<!-- ===== 卡片网格 ===== -->
<div class="grid" id="grid"></div>
<!-- ===== 详情弹窗 ===== -->
<div class="modalOverlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
<div class="modalBox" id="modalBox" style="position:relative;"></div>
</div>
<script>
// =====================================================================
// 宇森图鉴 — 全部数据来自后端 Odoo (yt_world 模块),前端无硬编码默认数据
// =====================================================================
const QUALITIES = ['common','uncommon','rare','epic','legendary'];
const QUALITY_NAMES = { common:'普通', uncommon:'优秀', rare:'精良', epic:'史诗', legendary:'传说' };
const TIER_NAMES = { normal:'普通野怪', elite:'精英野怪', boss:'Boss' };
const CAT_ORDER = ['material','processed','creature','monster','race','special'];
let ALL_ENTRIES = []; // 归一化后的全部条目(来自后端)
let currentCat = 'all';
let currentQual = 'all';
let searchTerm = '';
// ---------- 筛选交互 ----------
function setCat(cat){
currentCat = cat;
document.querySelectorAll('.catTab').forEach(el=>el.classList.toggle('active',el.dataset.cat===cat));
renderGrid();
}
function setQual(ql){
currentQual = ql;
document.querySelectorAll('.ql').forEach(el=>el.classList.toggle('active',el.dataset.ql===ql));
renderGrid();
}
function getQualIndex(q){ return QUALITIES.indexOf(q); }
// ---------- 搜索 ----------
function setSearch(v){
searchTerm = v;
const clr = document.getElementById('searchClear');
if(clr) clr.style.display = v ? 'inline' : 'none';
renderGrid();
}
function clearSearch(){
const inp = document.getElementById('searchInput');
if(inp) inp.value = '';
setSearch('');
}
document.addEventListener('DOMContentLoaded', ()=>{
const inp = document.getElementById('searchInput');
if(inp){
inp.addEventListener('input', e=> setSearch(e.target.value));
inp.addEventListener('keydown', e=>{ if(e.key==='Escape'){ clearSearch(); inp.blur(); } });
}
});
// ---------- 后端数据归一化 ----------
function normResource(r){
return {
type:'resource', id:r.code, cat:r.category, name:r.name, icon:r.icon||'📦',
qual:r.quality||'common', source:r.source||'', uses:r.uses||'',
recipe:Array.isArray(r.recipe)?r.recipe:[], desc:r.description||''
};
}
function normCreature(c){
return {
type:'creature', id:c.code, cat:'creature', name:c.name, icon:c.icon||'🐾',
hab:c.habitat||'', tame:!!c.tameable, diet:c.diet||'',
products:Array.isArray(c.products)?c.products:[], desc:c.description||''
};
}
function normMonster(m){
return {
type:'monster', id:m.code, cat:'monster', name:m.name, icon:m.icon||'👹',
tier:m.tier||'normal', levelMin:m.level_min||1, levelMax:m.level_max||1,
hab:m.habitat||'', drops:Array.isArray(m.drops)?m.drops:[], desc:m.description||''
};
}
function normRace(r){
return {
type:'race', id:r.code, cat:'race', name:r.name, icon:r.icon||'🧑',
layer:r.layer||'', traits:Array.isArray(r.traits)?r.traits:[], desc:r.description||''
};
}
function findEntry(code){ return ALL_ENTRIES.find(x=>x.id===code); }
// ---------- 加载(核心:从后端取数) ----------
async function loadCodexData(){
const grid = document.getElementById('grid');
grid.innerHTML = '<div class="empty show">正在从服务器加载图鉴数据…</div>';
try{
const [res, cre, mon, rac] = await Promise.all([
API.codexResources(), API.codexCreatures(), API.codexMonsters(), API.codexRaces()
]);
ALL_ENTRIES = [
...(res||[]).map(normResource),
...(cre||[]).map(normCreature),
...(mon||[]).map(normMonster),
...(rac||[]).map(normRace),
];
renderGrid();
}catch(err){
console.error('[codex] 加载失败:', err);
grid.innerHTML = '<div class="empty show">⚠️ 加载失败,请确认 Odoo 后端8069已启动且 yt_world 模块已更新。</div>';
}
}
// ---------- 渲染网格 ----------
function renderGrid(){
const q = searchTerm.trim().toLowerCase();
let filtered = ALL_ENTRIES.filter(e=>{
if(currentCat!=='all' && e.cat!==currentCat) return false;
if(q){
const hay = [e.name, e.desc, (e.products||[]).join(' '), (e.drops||[]).join(' '),
(e.traits||[]).join(' '), e.hab||'', e.layer||''].join(' ');
if(!hay.toLowerCase().includes(q)) return false;
}
if(currentQual!=='all'){
if(e.type!=='resource') return false;
if(e.qual!==currentQual) return false;
}
return true;
});
filtered.sort((a,b)=>{
const ac = CAT_ORDER.indexOf(a.cat), bc = CAT_ORDER.indexOf(b.cat);
return (ac===-1?99:ac)-(bc===-1?99:bc) || (a.id||'').localeCompare(b.id||'');
});
const grid = document.getElementById('grid');
grid.innerHTML = '';
if(!filtered.length){
grid.innerHTML = '<div class="empty show">没有匹配的条目</div>';
} else {
filtered.forEach(e=>{
const div = document.createElement('div');
div.className = 'card';
if(e.type==='resource'){
div.classList.add('q-'+e.qual);
div.innerHTML = `
<span class="qbadge ${e.qual}">${QUALITY_NAMES[e.qual]}</span>
<div class="icon">${e.icon}</div>
<div class="name">${e.name}</div>
`;
} else if(e.type==='creature'){
div.classList.add('creature');
div.innerHTML = `
<div class="icon">${e.icon}</div>
<div class="name">${e.name}</div>
<div class="tags">
<span class="tag">${e.hab||''}</span>
<span class="tag">${e.diet||''}</span>
<span class="tag">${e.tame?'可驯养':'野性'}</span>
</div>
`;
} else if(e.type==='monster'){
div.classList.add('monster','tier-'+e.tier);
div.innerHTML = `
<span class="tierbadge ${e.tier}">${TIER_NAMES[e.tier]}</span>
<div class="icon">${e.icon}</div>
<div class="name">${e.name}</div>
<div class="tags">
<span class="tag">Lv ${e.levelMin}-${e.levelMax}</span>
<span class="tag">${e.hab||''}</span>
</div>
`;
} else if(e.type==='race'){
div.classList.add('race');
div.innerHTML = `
<div class="icon">${e.icon}</div>
<div class="name">${e.name}</div>
<div class="tags">
<span class="tag">${e.layer||''}</span>
</div>
`;
}
div.addEventListener('click',()=>showDetail(e));
grid.appendChild(div);
});
}
updateStats(filtered);
}
function updateStats(){}
// =====================================================================
// 详 情 弹 窗
// =====================================================================
function closeModal(){
document.getElementById('modalOverlay').classList.remove('open');
}
function showDetail(e){
const overlay = document.getElementById('modalOverlay');
const box = document.getElementById('modalBox');
const qi = e.qual ? getQualIndex(e.qual) : -1;
let html = '<div style="position:relative;">';
html += `<button class="md-close" onclick="closeModal()" style="position:absolute;top:-8px;right:-8px;">✕</button>`;
// 头部
html += `<div class="md-header">`;
html += `<div class="md-icon">${e.icon}</div>`;
html += `<div class="md-info"><div class="md-title">${e.name}</div><div class="md-meta">`;
if(e.type==='resource'){
const c = e.qual==='common'?'156,163,175':e.qual==='uncommon'?'34,197,94':e.qual==='rare'?'59,130,246':e.qual==='epic'?'168,85,247':'245,158,11';
const t = e.qual==='common'?'#9ca3af':e.qual==='uncommon'?'#4ade80':e.qual==='rare'?'#60a5fa':e.qual==='epic'?'#c084fc':'#fbbf24';
html += `<span class="mt" style="background:rgba(${c},.15);color:${t}">${QUALITY_NAMES[e.qual]}</span>`;
html += `<span class="mt">${e.cat==='material'?'材料':e.cat==='processed'?'加工品':'特殊'}</span>`;
} else if(e.type==='creature'){
html += `<span class="mt">🐾 生物</span><span class="mt">${e.hab||''}</span><span class="mt">${e.diet||''}</span><span class="mt">${e.tame?'✅ 可驯养':'❌ 野性'}</span>`;
} else if(e.type==='monster'){
const tc = e.tier==='boss'?'245,158,11':e.tier==='elite'?'168,85,247':'156,163,175';
const tt = e.tier==='boss'?'#fbbf24':e.tier==='elite'?'#c084fc':'#9ca3af';
html += `<span class="mt" style="background:rgba(${tc},.18);color:${tt}">${TIER_NAMES[e.tier]}</span>`;
html += `<span class="mt">Lv ${e.levelMin}-${e.levelMax}</span><span class="mt">${e.hab||''}</span>`;
} else if(e.type==='race'){
html += `<span class="mt">🌍 种族</span><span class="mt">${e.layer||''}</span>`;
}
html += `</div></div></div>`;
// 品质等级条(资源)
if(e.type==='resource'){
html += `<div class="qualityBar">`;
QUALITIES.forEach((q,i)=>{ html += `<div class="qlv ${i<=qi?'on':''}" title="${QUALITY_NAMES[q]}"></div>`; });
html += `</div>`;
}
// 描述
if(e.desc){
html += `<div class="md-section"><h3>📖 描述</h3><div class="md-desc">${e.desc}</div></div>`;
}
// 资源详情
if(e.type==='resource'){
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
html += `<div class="gi"><span class="gl">来源</span><span class="gv">${e.source||'未知'}</span></div>`;
html += `<div class="gi"><span class="gl">用途</span><span class="gv">${e.uses||'—'}</span></div>`;
html += `</div></div>`;
if(e.recipe && e.recipe.length){
html += `<div class="md-section"><h3>🏭 加工配方</h3><div class="md-recipes">`;
e.recipe.forEach(r=>{
const src = findEntry(r.code);
html += `<div class="md-recipe"><span class="rp-icon">${src?src.icon:'❓'}</span><span class="rp-name">${src?src.name:r.code}</span><span class="rp-num">×${r.num}</span></div>`;
});
html += `</div></div>`;
}
}
// 生物详情
if(e.type==='creature'){
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
html += `<div class="gi"><span class="gl">栖息地</span><span class="gv">${e.hab||'未知'}</span></div>`;
html += `<div class="gi"><span class="gl">食性</span><span class="gv">${e.diet||'—'}</span></div>`;
html += `<div class="gi"><span class="gl">可驯养</span><span class="gv">${e.tame?'✅ 是':'❌ 否'}</span></div>`;
html += `</div></div>`;
if(e.products && e.products.length){
html += `<div class="md-section"><h3>🥚 产物</h3><div class="md-chips">` + e.products.map(p=>`<span class="md-chip">${p}</span>`).join('') + `</div></div>`;
}
}
// 野怪详情
if(e.type==='monster'){
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
html += `<div class="gi"><span class="gl">等级</span><span class="gv">${TIER_NAMES[e.tier]}</span></div>`;
html += `<div class="gi"><span class="gl">等级范围</span><span class="gv">Lv ${e.levelMin}-${e.levelMax}</span></div>`;
html += `<div class="gi"><span class="gl">栖息地</span><span class="gv">${e.hab||'未知'}</span></div>`;
html += `</div></div>`;
if(e.drops && e.drops.length){
html += `<div class="md-section"><h3>💎 掉落</h3><div class="md-chips">` + e.drops.map(d=>`<span class="md-chip">${d}</span>`).join('') + `</div></div>`;
}
}
// 种族详情
if(e.type==='race'){
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
html += `<div class="gi"><span class="gl">所属层域</span><span class="gv">${e.layer||'未知'}</span></div>`;
html += `</div></div>`;
if(e.traits && e.traits.length){
html += `<div class="md-section"><h3>✨ 天赋特性</h3><div class="md-chips">` + e.traits.map(t=>`<span class="md-chip">${t}</span>`).join('') + `</div></div>`;
}
}
html += '</div>';
box.innerHTML = html;
overlay.classList.add('open');
}
// ===== 键盘关闭 =====
document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeModal(); });
// ===== 初始化:从后端拉取图鉴 =====
document.addEventListener('DOMContentLoaded', loadCodexData);
</script>
<script src="assets/common.js"></script>
</main>
</div>
</body>
</html>