export const $ = (selector, root = document) => root.querySelector(selector); /** Create an element: h('button', { class: 'btn', text: 'Go', onclick }, ...children). */ export function h(tag, props = {}, ...children) { const el = document.createElement(tag); for (const [key, value] of Object.entries(props)) { if (value == null || value === false) continue; if (key === 'class') el.className = value; else if (key === 'text') el.textContent = value; else if (key === 'dataset') Object.assign(el.dataset, value); else if (key.startsWith('on')) el.addEventListener(key.slice(2).toLowerCase(), value); else el.setAttribute(key, value === true ? '' : value); } el.append(...children.flat().filter((c) => c != null && c !== false)); return el; } // ---------------------------------------------------------------------- icons // Every icon is a grey square for now. When a theme provides images, list them // in its theme.json ("assets": { "gen.gen1": "img/autoclipper.svg", ... }) and // they replace the placeholders, with no change to the UI code. let assetBase = ''; let assets = {}; export function setThemeAssets(themeId, themeAssets) { assetBase = `themes/${themeId}/`; assets = themeAssets ?? {}; } /** key examples: "res.units", "gen.gen1", "upg.click_1" */ export function icon(key) { const path = assets[key]; if (path) return h('img', { class: 'icon', src: assetBase + path, alt: '', loading: 'lazy' }); return h('span', { class: 'icon icon--placeholder', 'aria-hidden': 'true', dataset: { icon: key } }); } /** Re-resolve the icons that live in index.html (the resource bar). */ export function refreshStaticIcons(root = document) { for (const el of root.querySelectorAll('.resourcebar [data-icon]')) el.replaceWith(icon(el.dataset.icon)); } // ------------------------------------------------------------ buyable blocks // A block you click to buy (instead of a button inside it): it is announced as // a button, reachable with the keyboard (Enter / Space), outlined when hovered // and greyed out when it cannot be bought. /** Make `el` act as one big button. `activate(event)` runs on click, Enter or Space. */ export function makeBuyable(el, activate) { el.classList.add('card--buy'); el.setAttribute('role', 'button'); el.tabIndex = 0; const allowed = () => el.getAttribute('aria-disabled') !== 'true'; el.addEventListener('click', (event) => { if (allowed()) activate(event); }); el.addEventListener('keydown', (event) => { if ((event.key === 'Enter' || event.key === ' ') && event.target === el) { event.preventDefault(); if (allowed()) activate(event); } }); } /** Show whether a buyable block can currently be bought. */ export function setBuyable(el, canBuy) { el.classList.toggle('is-disabled', !canBuy); el.setAttribute('aria-disabled', String(!canBuy)); }