routing copper traces in eighty lines
the traces on the home board are an svg drawn from measured positions. the only clever part is a small function that turns right angles into 45° chamfers.
real boards do not route traces with right angles. they turn at 45°. a trace drawn with square corners looks like a flowchart, not copper.
the traces between U1 and the project footprints are an svg drawn after the page lays out. the script measures where each pin and pad landed, builds a three-segment orthogonal path between them, and hands the corners to one function:
type Point = { x: number; y: number };
// Traces are orthogonal: every segment runs along one axis.
const length = (a: Point, b: Point) => Math.abs(b.x - a.x) + Math.abs(b.y - a.y);
const toward = (a: Point, b: Point, d: number) => ({
x: a.x + Math.sign(b.x - a.x) * d,
y: a.y + Math.sign(b.y - a.y) * d,
});
const xy = ({ x, y }: Point) => `${x.toFixed(1)} ${y.toFixed(1)}`;
/** Cut every corner at 45°, the way copper is routed. */
const chamfer = (pts: Point[], r: number) => {
const path = pts.flatMap((p, i) => {
const prev = pts[i - 1], next = pts[i + 1];
if (!prev || !next) return [p];
const d = Math.min(r, length(prev, p) / 2, length(p, next) / 2);
return [toward(p, prev, d), toward(p, next, d)];
});
return 'M' + path.map(xy).join(' L');
};
every corner is replaced by two points: one r pixels back toward the previous point, one r pixels ahead toward the next. the path skips the corner and cuts straight between them. that cut is the chamfer.
traces only run horizontally or vertically, so there is no trigonometry. Math.sign gives the direction, and a zero-length segment gets direction zero instead of a division by zero.
r is clamped to half of each neighbouring segment, so two close bends never cross. twelve pixels is enough to read as copper.
the rest is bookkeeping. on desktop each net leaves a side pin on U1, jogs at a different x so the traces do not overlap, and lands on the left pad of its footprint. below 64rem the footprints stack, so one bottom pin feeds the first footprint and each footprint’s out-pad feeds the next. a ResizeObserver reroutes on resize, after fonts have loaded, so the first draw is the only draw.
the draw-in animation is stroke-dashoffset from the path’s measured length to zero, staggered by net index. it is gated behind prefers-reduced-motion. without javascript the board still renders; you just don’t get the copper.
eighty lines, no library, and the only script on the site apart from the theme toggle.