Let's talk

// case study

ArborKin

A private, multi-user family tree app — built solo, shipped to Azure, used by real people. Full stack from data model to layout algorithm to auth system.

← all web design work
~8klines of C#
12EF migrations
11xUnit tests
3Azure services
solofull-stack build
ArborKin — primary screenshot
Sign-in — Google SSO or email/password, invite-only
.NET 10C# 13Blazor ServerEF CoreMudBlazorAzure App ServiceAzure SQLBlob Storage

ArborKin is a private, invite-only family tree app — not a portfolio demo, a real product with real users adding real relatives. The brief was self-imposed: build something with the same architectural discipline as client work, end to end, alone.

The core design problem was the tree canvas itself: laying out generations so the visual hierarchy is instantly readable, without a JS layout library doing the work. Every node's pixel position is computed in C# before the browser ever paints — the Y-axis is a real birth-year timeline, so generation gaps render proportionally instead of as evenly-spaced rows.

Beyond layout, most of the hard problems were about Blazor Server's specific constraints — SignalR round-trip latency on uploads, re-renders fighting client-side drag state — solved by being deliberate about which layer (C#, JS interop, or the browser itself) owns which piece of behavior.

// design & build highlights
01

JS-Free Tree Layout

Every node's pixel position is computed in C# before anything renders. No getBoundingClientRect, no post-render step, no flicker. The SVG connector layer uses the same pre-calculated coordinates — always aligned on first paint.

02

Blazor Upload Latency

Photos were taking five minutes. Root cause: every byte travels over SignalR to the server before reaching Azure Blob Storage. Fix: Canvas API compression in the browser first — a 12 MB phone photo becomes ~400 KB before Blazor ever touches it. Minutes to seconds.

03

Drag State Ownership

Blazor re-renders reset dragged element positions mid-gesture. Solution: JS owns the gesture (mousemove updates style.left/top directly), and on mouseup calls a [JSInvokable] method. Blazor stores the result, persists it to localStorage, and restores it on next mount — no conflicts.

ArborKin additional screenshot
The tree canvas. Focus person is centered; ancestors flow upward, descendants downward. The Y-axis is a real birth-year timeline — generation gaps render proportionally.
ArborKin additional screenshot
Detail drawer — vitals, bio & photos without leaving the canvas
ArborKin additional screenshot
Dark mode — single CSS variable swap, persisted to localStorage
ArborKin additional screenshot
Edit form — relationship pickers with full autocomplete