Yours after install
The registry ships the real source files. What lands in components/botui/ is the same text as the repository, and a test in CI proves it byte for byte.
Loading states, streaming indicators, agent status — the surfaces an agent product lives on. Install one with npx, and the code lands in your repo. No runtime dependency, nothing to keep current. Read it, change it, delete the name.
npx @botharness/botui add dot-matrixAlso works with shadcn, against the same registry:npx shadcn@latest add https://ui.botharness.ai/r/dot-matrix.json
dot / cell 198% dot / pitch 198% 100% = two dots touching touching at dot / cell = 1.00 pitch 43.5 × 43.5 px dot 86.1 px silhouette square dot square renderer css speed 1.00×
A traversal is one function of lattice position, so anything you can compute per cell you can animate. These two are not presets — they are `order` functions, written here and running below.
export const chevron: OrderFn = (col, row, cols, rows) => {
const mid = (rows - 1) / 2;
const arm = Math.abs(row - mid) / (mid || 1);
const along = col / (cols - 1 || 1);
// one band per step of the grid: any fewer and adjacent cells share a band and the arrow
// thickens, any more and there is no cell left to light
const bands = Math.max(cols, rows);
return Math.round(Math.abs(along - arm) * bands) / bands;
};
/**
* RINGS expanding from a dot the visitor chooses.
*
* Chebyshev distance — `max(|dc|, |dr|)` — rather than Euclidean, and that choice is the
* whole character: on an integer lattice the Chebyshev radius takes only a handful of
* distinct values, so every dot on a ring shares one phase and the field reads as discrete
* bands. Euclidean would give nearly every dot its own value and turn the same traversal
* into a smooth travelling wave. The concentric `ring` preset is this function with the
* origin pinned to the middle; here the origin is a parameter, which is the request the
* library's own table could not express.
*
* Normalised by the largest distance the lattice can hold, so the outermost dot leads at 1
* whatever the grid size — otherwise a 9×9 and a 3×3 would animate at different rates for
* the same nominal shape.
*/
export function ringsFrom(origin: { col: number; row: number }): OrderFn {
return (col, row, cols, rows) => {
const d = Math.max(Math.abs(col - origin.col), Math.abs(row - origin.row));
// The furthest cell in the lattice FROM THE ORIGIN, which is whichever corner is
// diagonally opposite it. Normalising by a fixed denominator — `cols`, or
// `max(cols, rows)` — is the tempting one-liner and it is wrong twice over: it makes
// the outermost dot's value depend on the GRID SIZE rather than on the origin, so a
// 9×9 and a 3×3 animate at different rates for the same nominal shape, and it leaves
// the last ring short of 1, so the wave never quite arrives.
const far = Math.max(origin.col, cols - 1 - origin.col, origin.row, rows - 1 - origin.row);
return far > 0 ? Math.min(1, d / far) : 0;
};
}
/** the cell nearest a click, so a visitor can pick the origin with a pointer */
export function nearestCell(
at: { x: number; y: number },
pitch: { x: number; y: number },
cols: number,
rows: number,
): { col: number; row: number } {
// the field is centred, so a click at (0,0) in field space is the top-left cell
const cx = (cols - 1) / 2;
const cy = (rows - 1) / 2;
const col = Math.round(cx + (at.x / pitch.x) * cx);
const row = Math.round(cy + (at.y / pitch.y) * cy);
return {
col: Math.min(cols - 1, Math.max(0, col)),
row: Math.min(rows - 1, Math.max(0, row)),
};
}
Read from /registry.json — the same document the CLI installs from.
Loading the registry…
The registry ships the real source files. What lands in components/botui/ is the same text as the repository, and a test in CI proves it byte for byte.
There is no runtime dependency to upgrade and nothing that can break under you. When BotUI ships something better, you take the diff when you want to.
Lattice, traversal, brightness envelope, dot polygon, renderer. Each is a table you can extend without touching the layers above.