Back to City Breaker
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.

src/ui/dom.ts
export function esc(v: unknown): string {
  return String(v).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[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;
}