Frontend Labs
⋯

public/js/lib/dom.js

// Tiny DOM helpers shared by every station.

// h("div", { class: "x", onclick: fn }, "text", childNode, [more]) -> Element.
// Text always goes in as text nodes, never as HTML.
export function h(tag, attrs = {}, ...children) {
  const el = document.createElement(tag);
  for (const [k, v] of Object.entries(attrs ?? {})) {
    if (k.startsWith("on") && typeof v === "function") el.addEventListener(k.slice(2), v);
    else if (k === "style" && typeof v === "object") Object.assign(el.style, v);
    else if (v === true) el.setAttribute(k, "");
    else if (v !== false && v != null) el.setAttribute(k, String(v));
  }
  for (const c of children.flat(Infinity)) {
    if (c != null && c !== false) el.append(c instanceof Node ? c : document.createTextNode(String(c)));
  }
  return el;
}

export const $ = (sel, root = document) => root.querySelector(sel);
export const $$ = (sel, root = document) => [...root.querySelectorAll(sel)];
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export const nextFrame = () => new Promise((r) => requestAnimationFrame(r));
export const ms = (n) => (n == null || Number.isNaN(n) ? "—" : n < 10 ? `${n.toFixed(1)} ms` : `${Math.round(n)} ms`);
export const bytes = (n) =>
  n == null ? "—" : n < 1024 ? `${n} B` : n < 1048576 ? `${(n / 1024).toFixed(1)} KB` : n < 1073741824 ? `${(n / 1048576).toFixed(1)} MB` : `${(n / 1073741824).toFixed(1)} GB`;

// A station section: title, a one-line lede, and a body.
export function station(title, lede, ...body) {
  return h("article", { class: "station" }, h("header", {}, h("h2", {}, title), h("p", { class: "lede" }, lede)), ...body);
}

export function panel(title, ...body) {
  return h("section", { class: "panel", "aria-label": title }, h("h3", {}, title), ...body);
}

export function button(label, onclick, attrs = {}) {
  return h("button", { type: "button", onclick, ...attrs }, label);
}

// Busy-wait on purpose: the Rendering, Vitals and Threads stations need to
// block the main thread to show what blocking does.
export function blockFor(duration) {
  const end = performance.now() + duration;
  while (performance.now() < end);
}

export function storage(key, fallback) {
  // localStorage can throw (private mode, blocked site data): never let that break the lab.
  return {
    get() {
      try {
        const v = localStorage.getItem(key);
        return v == null ? fallback : JSON.parse(v);
      } catch {
        return fallback;
      }
    },
    set(v) {
      try {
        localStorage.setItem(key, JSON.stringify(v));
      } catch {}
    },
  };
}

Generated from SOURCE.md · 8 Oct 2026 16:36