This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
v3 drops the scrolling landing for a single interactive node graph: every feature and every write-up as a node, wired by category and by each feature’s own notes. Two views — a draggable force-directed graph and a flat layered one — and an audit pass across performance, engineering, and accessibility.
A central paul-explore root, an Apps hub for the features, and one hub per write-up category. Edges wire the trunk (root to hubs), the leaves (hub to its items), and the bridges: a dashed line from each feature to its own write-up, built from the thoughtsHref that already lived in the feature data. The graph is derived, not hand-authored — add a feature or a thought and it just appears, wired up.
The same nodes and edges render two ways. The force view is the playground: draggable nodes, a physics settle, hover to highlight a node and its neighbours. The flat view is the reference: a tidy grouped-column layout (each hub/category a column, its items stacked below) that fits on a screen and is easy to scan. A switch in the header flips between them; the data layer (graphData.ts) is shared, only the layout and rendering differ.
No graph library. The force simulation is ~150 lines of pure functions: pairwise repulsion, spring forces along edges, a pull to the origin, and a collision pass, all scaled by an alpha that decays so the graph cools and settles. Keeping it pure means it’s testable and framework-free; the only new dependency is GSAP, for the intro reveal and click sparks.
The nicest decision: the simulation runs in its own abstract coordinate space, and the renderer fits that space to the viewport every frame. So “use the available space” and “lay out the graph” are decoupled — the physics never has to know the screen size, and resizing just re-fits.
Float64Array per frame, so the collector isn’t chasing garbage mid-drag.Most of the work was in the last 10%. A tour of the gotchas:
draggable={false} and it works.An interactive canvas is easy to get wrong for keyboard and screen reader users, so the audit leaned here:
main, header, nav) and a single h1; decorative layers (background, legend, sparks) are aria-hidden.prefers-reduced-motion gets a static layout with no drift, no physics, and no intro animation./tcg pages, /calendar) got their missing landmarks.The flat view started life as one enormous horizontal row — technically a graph, practically unreadable and mostly off-screen. Grouped columns fixed that on the desktop. On a phone even eight columns don’t fit, so under 768px the flat view becomes a stacked, grouped list — the same data, laid out for a thumb. Since a feature and its write-up often share a name (there’s a Calendar feature and a Calendar write-up), the write-ups wear a notes tag so they don’t read as duplicates.
Give the force view a mobile fallback of its own, and animate the cross-fade when toggling views. The best-practice a11y sweep across the older routes already landed as a follow-up, so the whole site holds the higher bar now, not just this page.