feat: New features (research, invest, gambling, new game+, trophies)
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
// The section menu: horizontal tabs on desktop/tablet, a burger menu on phones
|
||||
// (the markup is the same, the CSS decides). Tabs unlock with progress.
|
||||
|
||||
import { isUnlocked } from '../engine/engine.js';
|
||||
import { has, t } from '../services/i18n.js';
|
||||
import { $, h } from './dom.js';
|
||||
import { showToast } from './toast.js';
|
||||
|
||||
const labelKey = (id) => (has(`theme.tab.${id}`) ? `theme.tab.${id}` : `tab.${id}`);
|
||||
|
||||
/**
|
||||
* @param tabs data/tabs.json
|
||||
* @param onSelect (tabId) called when the visible section changes
|
||||
*/
|
||||
export function createTabs(tabs, onSelect) {
|
||||
const list = $('#tabs');
|
||||
const menu = $('#menu');
|
||||
const toggle = $('#menu-toggle');
|
||||
const buttons = new Map();
|
||||
let active = 'main';
|
||||
let known = null; // unlocked tabs seen so far (null until the first update)
|
||||
const unseen = new Set();
|
||||
|
||||
const view = (id) => $(`[data-view="${id}"]`);
|
||||
|
||||
function select(id) {
|
||||
active = id;
|
||||
unseen.delete(id);
|
||||
for (const tab of tabs) {
|
||||
const section = view(tab.id);
|
||||
if (section) section.hidden = tab.id !== id;
|
||||
buttons.get(tab.id)?.button.setAttribute('aria-current', tab.id === id ? 'page' : 'false');
|
||||
}
|
||||
buttons.get(id)?.item.classList.remove('tab--new');
|
||||
setMenuOpen(false);
|
||||
onSelect(id);
|
||||
}
|
||||
|
||||
function setMenuOpen(open) {
|
||||
menu.toggleAttribute('data-open', open);
|
||||
toggle.setAttribute('aria-expanded', String(open));
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', () => setMenuOpen(!menu.hasAttribute('data-open')));
|
||||
|
||||
/** Build the buttons (call again after a language or theme change to relabel). */
|
||||
function rebuild() {
|
||||
list.replaceChildren();
|
||||
buttons.clear();
|
||||
for (const tab of tabs) {
|
||||
const button = h('button', {
|
||||
class: 'tab',
|
||||
type: 'button',
|
||||
'aria-current': tab.id === active ? 'page' : 'false',
|
||||
text: t(labelKey(tab.id)),
|
||||
onclick: () => select(tab.id),
|
||||
});
|
||||
const item = h('li', { class: 'tabs__item' }, button);
|
||||
item.hidden = known ? !known.has(tab.id) : tab.id !== 'main';
|
||||
if (unseen.has(tab.id)) item.classList.add('tab--new');
|
||||
list.append(item);
|
||||
buttons.set(tab.id, { item, button });
|
||||
}
|
||||
}
|
||||
|
||||
/** Call every frame: reveals newly unlocked tabs, flags the ones needing attention. */
|
||||
function update(state) {
|
||||
const unlocked = new Set(tabs.filter((tab) => isUnlocked(state, tab.unlock)).map((tab) => tab.id));
|
||||
|
||||
for (const tab of tabs) {
|
||||
const entry = buttons.get(tab.id);
|
||||
const isNew = known && unlocked.has(tab.id) && !known.has(tab.id);
|
||||
entry.item.hidden = !unlocked.has(tab.id);
|
||||
if (isNew) {
|
||||
unseen.add(tab.id);
|
||||
entry.item.classList.add('tab--new');
|
||||
showToast(t('toast.tabUnlocked', { name: t(labelKey(tab.id)) }));
|
||||
}
|
||||
}
|
||||
known = unlocked;
|
||||
|
||||
// An opportunity is waiting on the contracts tab.
|
||||
buttons.get('contracts')?.item.classList.toggle('tab--alert', Boolean(state.events.current));
|
||||
|
||||
if (!unlocked.has(active)) select('main');
|
||||
}
|
||||
|
||||
return { rebuild, update, select, get active() { return active; } };
|
||||
}
|
||||
Reference in New Issue
Block a user