Growth Log

Notes on what clicked while building each project, kept as a public log.

2026-07-19

Pocket Sketchbook

Complexity 2/5Impact MediumEffort Quick

The sketchbook's spine, a gradient gutter down the center, had to sit behind everything, including the background paint layer. My first attempt gave it a z-index, thinking 'behind the covers' meant 'above the background, below the art.' That put it above the covers instead. The real fix wasn't a z-index value at all. It was DOM order: making the spine the first child with no z-index, so normal stacking (paint order follows DOM order when nothing's stacked) put it behind everything by default.

z-index is what you reach for when you're trying to force a decision instead of designing the structure that produces it. I was tuning a number to solve a stacking problem, when the actual fix was where the element sits in the tree: the layered-canvas equivalent of fixing a group hierarchy in a design file instead of nudging layers up and down one at a time.

Interview-ready: I fixed a persistent z-order bug in a layered canvas UI (paint layer, cover art, decorative spine) by realizing the fix wasn't a z-index tweak but DOM position: putting the element first in paint order instead of trying to out-rank every other layer's stacking value.

2026-07-13

Design By Bulletin

Complexity 3/5Impact HighEffort Moderate

Design By Bulletin runs as three cooperating agents: Editorial, Assignment Scout, and Art Department. The hard problem turned out not to be getting agents to produce content. It's knowing whether what they produced is true. A test suite can confirm the JSON is well-formed and nothing crashed, but it can't tell you a sentence describing a source article is actually about that article. I ended up building a separate review layer, gates that check content-level rules plus a full read-through that fact-checks claims against the original sources, because 'the code works' and 'the agent told the truth' are two completely different guarantees.

QA and editorial review catch different things. The test suite catches a broken layout: wrong font, missing image, crashed render. Editorial review catches a caption that's confidently wrong. You need both. Shipping only the first because it's easier to automate is how you publish polished misinformation.

Interview-ready: I designed a three-agent editorial pipeline for an autonomous AI magazine and learned that automated tests only prove the output is well-formed, not true. So I added a separate fact-checking review layer that verifies agent claims against their original sources before anything publishes.

2026-07-17

UI/UX Performance Art

Complexity 2/5Impact MediumEffort Quick

I gave this piece a real route by wrapping the static file in an iframe instead of porting it to React. It's deliberately vanilla HTML, CSS, and JS, so a port would've been the wrong fix for a piece that isn't broken. The wrapper is thin, but iframe content doesn't control the real browser tab. The iframe's own <title> never reaches the outer window, so I had to duplicate that exact title string in the Next.js layout too, or the tab would just show the generic route name instead of the piece's own disguised title.

A component's local state and what the outside world can see are two different things. The iframe is a sealed environment: its <title> is local UI that never leaks past its own frame, the same way a modal's internal focus state doesn't leak to the page behind it. Anything the outside needs to know has to be set explicitly from the outside, never assumed to propagate.

Interview-ready: I gave a deliberately non-React vanilla HTML/CSS/JS piece a real app route via a thin iframe wrapper instead of porting it, and learned iframe content can't set the real browser tab title. The outer Next.js layout has to duplicate it explicitly.

2026-07-18

Major Arcana

Complexity 2/5Impact MediumEffort Quick

For Major Arcana's card art direction, I used Midjourney as a fast way to explore visual language before writing any code, generating dozens of moodboard variations for a single card's palette and composition in the time one hand-sketch would take. It didn't replace the design decision. It collapsed the exploration phase: I could throw out 90% of directions in minutes, and the ones that stuck became the actual reference the Motion (Framer Motion) implementation matched against.

Static-mocking a UI in HTML before touching React and generating throwaway moodboards before touching a design tool come from the same instinct: explore cheaply before you build the expensive version. Midjourney doesn't get attached to its first idea, so it widens the funnel before I have to narrow it. The judgment of what's actually good stays entirely mine; the tool just makes the bad options cheaper to see and discard.

Interview-ready: I used Midjourney as a rapid moodboard and art-direction tool for Major Arcana's card visuals, generating and discarding dozens of directions in the time one hand-sketch would take, before implementing the chosen direction with Motion (Framer Motion). The design judgment stayed mine; the tool just widened the exploration phase.

2026-06-25

Pocket Animator

Complexity 4/5Impact HighEffort Deep

The original pen-tool mock tangled "where is the card right now" with "set this SVG transform" in one file. Splitting that into a dependency-free core (owns state only, hands resolved frames to a render callback) plus a themed preset (tarot) meant the same engine could drive SVG, DOM cards, or a test array. Every timeline edit became a pure function, so undo/redo is just an array of snapshots. That separation paid off again later: Swiss Pocket Animator reuses the exact same engine, just restyled, with zero forked logic.

The core and the preset split cleanly into tokens and components. The core is geometry and timing primitives with no opinion about looks. The preset is a themed set that picks the actions, the deck count, the coordinate origin. One engine, many skins. You never fork the primitive to restyle.

Interview-ready: I extracted a one-off animation mock into a reusable, dependency-free motion engine, separating pure state transitions from the one stateful transport loop, then layered a themed "preset" on top instead of forking the code per use case. That's why the Swiss-design reskin later cost almost nothing.

2026-06-11

Shaders & Effects

Complexity 2/5Impact MediumEffort Quick

CardShine, one of the effects that flips through on the /shaders deck, had a sizing bug where it used relative w-full h-full while every other effect in the same deck used absolute inset-0. That was inconsistent enough that its own padding and margin variables were silently shaving about 32px off its visible size compared to its siblings. The fix wasn't a one-off patch. It was removing the whole prop-based sizing system: CardShine now always fills its parent via absolute inset-0, matching every other effect's contract, so a new effect added to the deck never has to think about sizing at all.

When every member of a family, like this effects deck, is supposed to behave interchangeably, one member quietly opting out of the shared positioning convention becomes a trap for whoever assumes uniform behavior next. The fix wasn't a note in the docs. It was making the contract impossible to violate: always fill, no sizing props.

Interview-ready: I found and fixed a sizing inconsistency in a shared card-effect system: one effect used relative sizing with padding while its siblings used absolute inset-0, silently losing about 32px. The fix removed the prop-based sizing API entirely, so every effect in the deck now shares one non-optional fill contract.