SOURCE / PINNED RELEASE
Made of little things.
City Breaker
- Release
- 94d2b8664b10…
- Author-recorded commit
- df23103cbcc1…
- License
- LICENSE
- Author’s source reference
- nostr://npub1sj2vketepqw9v6fmge5fdzuj7atj9sv6qv2jss05n2z6rl64lcfqumdsnt/wss%3A%2F%2Fgit.napplet.soy%2F/n-cf8b858653e
Archive hash verified: eeeed725668c1ece…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
export function esc(v: unknown): string {
return String(v).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]!);
}
export function pct(v: number): string {
return `${Math.round(v * 100)}%`;
}
export function bar(v: number, cls = ''): string {
const w = Math.max(0, Math.min(1, v)) * 100;
return `<span class="bar ${cls}" role="meter" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${Math.round(w)}"><span style="width:${w.toFixed(1)}%"></span></span>`;
}
/** Wire `[data-act]` clicks inside `el` to handlers. */
export function bind(el: HTMLElement, handlers: Record<string, (target: HTMLElement) => void>): void {
el.addEventListener('click', (e) => {
const t = (e.target as HTMLElement).closest<HTMLElement>('[data-act]');
if (!t || !el.contains(t)) return;
const h = handlers[t.dataset.act!];
if (h) {
e.preventDefault();
h(t);
}
});
}
export function modal(root: HTMLElement, html: string, handlers: Record<string, (t: HTMLElement, close: () => void) => void>): () => void {
const back = document.createElement('div');
back.className = 'modal-back';
back.innerHTML = `<div class="modal" role="dialog" aria-modal="true">${html}</div>`;
root.append(back);
const close = () => back.remove();
const wrapped: Record<string, (t: HTMLElement) => void> = {};
for (const [k, f] of Object.entries(handlers)) wrapped[k] = (t) => f(t, close);
wrapped.close ??= () => close();
bind(back, wrapped);
back.querySelector<HTMLElement>('button, [tabindex]')?.focus();
back.addEventListener('keydown', (e) => {
if (e.key === 'Escape') close();
});
return close;
}
