v0.01
This commit is contained in:
commit
234e769f4d
34 files changed
+4667
No files matched your search
@@ -0,0 +1,134 @@
|
||||
// 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-help', 'scr-pause', 'scr-result', 'scr-editor'];
|
||||
const TABS = [
|
||||
['all', '全部'], ['jump', '跳刺'], ['endur', '耐久'], ['boss', 'BOSS'], ['troll', '坑向'], ['custom', '自制'],
|
||||
];
|
||||
|
||||
export const UI = {
|
||||
H: {}, // handlers
|
||||
filter: 'all',
|
||||
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;
|
||||
$$('#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.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);
|
||||
|
||||
if (!list.length) {
|
||||
grid.innerHTML = '<div style="color:#666;padding:20px">这个分类下还没有关卡。去「自制关卡」造一个吧。</div>';
|
||||
return;
|
||||
}
|
||||
list.forEach(l => {
|
||||
const rec = Save.get(l.id);
|
||||
const card = document.createElement('div');
|
||||
card.className = 'lv-card' + (rec ? ' cleared' : '');
|
||||
const stars = '★'.repeat(l.diff || 1) + '☆'.repeat(Math.max(0, 5 - (l.diff || 1)));
|
||||
card.innerHTML = `
|
||||
<div class="idx">${l.builtin === false ? 'U' + (l.no || '') : String(l.index + 1).padStart(2, '0')}</div>
|
||||
<div class="nm">${escapeHtml(l.name || 'STAGE')}</div>
|
||||
<div class="meta">
|
||||
<span class="tag ${TYPE_CLASS[l.type] || 't-custom'}">${TYPE_LABEL[l.type] || '自制'}</span>
|
||||
<span style="color:#ffc63a">${stars}</span>
|
||||
</div>
|
||||
<div class="st">${rec ? `✔ 死亡 ${rec.deaths} · ${fmtTime(rec.time)}` : '未通关'}</div>
|
||||
${rec ? '<div class="chk">✔</div>' : ''}
|
||||
`;
|
||||
card.onclick = () => this.H.play(l);
|
||||
grid.appendChild(card);
|
||||
});
|
||||
},
|
||||
|
||||
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]));
|
||||
}
|
||||
Reference in new issue
Block a user