// DOM 界面:菜单 / 关卡选择 / HUD / 结算
import { LEVELS, TYPE_LABEL, TYPE_CLASS } from './levels/index.js';
import { Save } from './save.js';
import { fmtTime } from './util.js';
const $ = s => document.querySelector(s);
const $$ = s => Array.from(document.querySelectorAll(s));
const SCREENS = ['scr-title', 'scr-levels', 'scr-settings', 'scr-pause', 'scr-result', 'scr-editor'];
const TABS = [
['all', '全部'], ['jump', '跳刺'], ['endur', '耐久'], ['boss', 'BOSS'], ['troll', '坑向'], ['custom', '自制'],
];
export const UI = {
H: {}, // handlers
filter: 'all',
manage: false, // 自制页「管理」态:开启后每张自制地图显示删除按钮
cur: 'scr-title',
toastTimer: 0,
init(handlers) {
this.H = handlers;
// 按钮
$$('[data-act]').forEach(btn => {
btn.addEventListener('click', () => {
const a = btn.dataset.act;
if (this.H[a]) this.H[a]();
});
});
// 分类 tabs
const tabs = $('#level-tabs');
tabs.innerHTML = '';
TABS.forEach(([k, label]) => {
const b = document.createElement('button');
b.className = 'tab' + (k === 'all' ? ' on' : '');
b.textContent = label;
b.onclick = () => {
this.filter = k;
this.setManage(false); // 切换分类时退出管理模式
$$('#level-tabs .tab').forEach(t => t.classList.remove('on'));
b.classList.add('on');
this.renderLevels();
};
tabs.appendChild(b);
});
},
show(name) {
SCREENS.forEach(s => $('#' + s).classList.toggle('hidden', s !== name));
this.cur = name;
$('#hud').classList.toggle('hidden', name !== null && name !== '');
if (name === 'scr-levels') { this.setManage(false); this.renderLevels(); }
},
showGame() {
SCREENS.forEach(s => $('#' + s).classList.add('hidden'));
this.cur = '';
$('#hud').classList.remove('hidden');
},
renderLevels() {
const grid = $('#level-grid');
grid.innerHTML = '';
const customs = Save.customs().map((l, i) => ({ ...l, index: 100 + i, no: i + 1 }));
const all = [...LEVELS, ...customs];
const list = all.filter(l => this.filter === 'all' ? true : l.type === this.filter);
// 「管理地图」按钮只在自制分类下显示
this.updateManageBtn();
if (!list.length) {
grid.innerHTML = '
这个分类下还没有关卡。去「自制关卡」造一个吧。
';
return;
}
list.forEach(l => {
const rec = Save.get(l.id);
const card = document.createElement('div');
card.className = 'lv-card' + (rec ? ' cleared' : '') + (this.manage && l.builtin === false ? ' manage' : '');
const stars = '★'.repeat(l.diff || 1) + '☆'.repeat(Math.max(0, 5 - (l.diff || 1)));
card.innerHTML = `
${l.builtin === false ? 'U' + (l.no || '') : String(l.index + 1).padStart(2, '0')}
${escapeHtml(l.name || 'STAGE')}
${TYPE_LABEL[l.type] || '自制'}
${stars}
${rec ? `✔ 死亡 ${rec.deaths} · ${fmtTime(rec.time)}` : '未通关'}
${rec ? '✔
' : ''}
`;
// 管理态下自制卡不再可进入,只保留删除按钮,避免误点开始
if (!(this.manage && l.builtin === false)) card.onclick = () => this.H.play(l);
// 管理态:自制地图显示删除按钮
if (l.builtin === false && this.manage) {
const del = document.createElement('button');
del.className = 'lv-del';
del.textContent = '✕';
del.title = '删除这张地图';
del.onclick = (e) => { e.stopPropagation(); this.delCustom(l); };
card.appendChild(del);
}
grid.appendChild(card);
});
},
/** 刷新「管理地图」按钮:仅在自制分类可见;管理态文案变「完成」并高亮 */
updateManageBtn() {
const mb = $('#manage-btn');
if (!mb) return;
mb.classList.toggle('hidden', this.filter !== 'custom');
mb.textContent = this.manage ? '完成' : '管理地图';
mb.classList.toggle('on', this.manage);
},
/** 切换自制页的管理态(显示/隐藏删除按钮) */
setManage(on) {
// 仅自制分类可进入管理态;非自制分类的开启请求按退出处理(按钮不可见,须自动复位)
if (on && this.filter !== 'custom') on = false;
this.manage = !!on;
this.updateManageBtn();
this.renderLevels();
},
/** 删除一张自制地图(从关卡选择界面管理) */
delCustom(l) {
if (typeof confirm === 'function' && !confirm('删除地图「' + (l.name || 'MY MAP') + '」?此操作不可恢复')) return;
Save.removeCustom(l.id);
this.renderLevels();
},
hud({ name, deaths, ms, apples, totalApples, hasGun, save, totalSaves }) {
$('#hud-level').textContent = name || '';
$('#hud-deaths').textContent = 'DEATH ' + deaths;
$('#hud-time').textContent = fmtTime(ms);
const ap = $('#hud-apples');
if (totalApples > 0) { ap.style.display = ''; ap.textContent = `苹果 ${apples}/${totalApples}`; }
else ap.style.display = 'none';
const sv = $('#hud-save');
if (totalSaves > 0) {
sv.style.display = '';
sv.textContent = save > 0 ? `存档 ${save}/${totalSaves}` : `存档 0/${totalSaves}`;
sv.classList.toggle('on', save > 0);
} else sv.style.display = 'none';
},
bossBar(boss) {
const bar = $('#boss-bar');
if (!boss) { bar.classList.add('hidden'); return; }
bar.classList.remove('hidden');
$('#boss-name').textContent = boss.name;
$('#boss-fill').style.width = Math.max(0, (boss.hp / boss.maxHp) * 100) + '%';
},
toast(text) {
const el = $('#toast');
if (!text) { el.classList.remove('show'); return; }
if (el.textContent !== text) el.textContent = text;
el.classList.add('show');
},
result({ name, ms, deaths, apples, totalApples, better, hasNext }) {
$('#r-name').textContent = name;
$('#r-time').textContent = fmtTime(ms);
$('#r-death').textContent = deaths;
$('#r-apple').textContent = totalApples ? `${apples}/${totalApples}` : '—';
$('#r-best').textContent = better ? '★ 新纪录!' : '';
$('#scr-result').querySelector('[data-act="next"]').style.display = hasNext ? '' : 'none';
this.show('scr-result');
},
};
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
}