Independent product
Every point has a story
Bowman World Cup Pool
Who owns Spain? Can I still win? I built a family World Cup pool around the questions that make a match personal, then kept refining it through scoring rules, a visual rebuild, and a deliberate archive. I directed the product, design, and development with three coding agents.

- Contribution
- Product design and AI-assisted development
Facts
- Build story
- June–July 2026 · from a team draw to a tournament companion
- My work
- Product direction, interface design, and development with coding agents
- Built with
- React, TypeScript, and Vite
- Data
- ESPN's unofficial public endpoints, local cache, and a bundled snapshot
A few teams to make every match personal
I wanted the family pool to answer the questions around the television: who owns that team, what did that result change, and is there still a way to catch the leader? Ten owners and a shared draw gave the tournament a smaller, more personal story.
The first version was a team generator. Once the draw was agreed, generating another one became the wrong feature. I fixed the allocation and removed regeneration. From that point on, the site’s job was to follow the agreement.
The draw became the starting point

Start with the person following the tournament
A tournament table tells you who is winning. A family pool needs another layer: which teams are yours, what their results earned, and how far you are from the leader.
I organized the experience around owner pages. Each page brings rank, total points, the gap to first place, and team status into one overview. From there, you can inspect individual teams without losing the context of your place in the pool.
Open the score and see where it came from
At first, the plan still depended on a scorekeeper. I removed a dormant shared scoresheet, then went further: why ask someone to type results that were already public? The next decision removed manual score entry and derived points from match data.
That shifted the work from maintaining totals to making the rules understandable. A family pool needs a score people can question and an explanation they can find.
A total you can inspect

A single total hides the rules. I put the scoring events behind an expandable summary, keeping the overview short while making the calculation available when someone wants to check it.
The scoring model derives points from match results and tournament progression. It replaces manual score entry with a shared set of rules applied to the tournament data.
The rules were part of the interface

The first design was not the last word
The early Night Match interface leaned into the atmosphere of a sports broadcast. As standings, fixtures, player data, and research accumulated, the page needed a clearer reading order. I moved toward a light sports desk with quieter styling and more direct labels.
The iteration changed more than the palette. “Pool pressure” gave way to “Today at a glance.” I kept standings beside matches, retained the direct owner links, and simplified the language around them.
Earlier: a broadcast-style dashboard

Later: standings beside their explanation

I wrote the visual standard into the repository so the next coding session had the same reference. Tokens, page hierarchy, and copy guidance made the design direction available to every agent.
Who can still win, and what has to happen?
Near the end of a tournament, the useful question changes. Knowing who leads is less interesting than knowing whether anyone can catch them. I added a possible-finishes view that connects the remaining match outcomes to the final pool table.
The scenario engine enumerates the two semifinal winners, the champion, and the third-place winner. With all four matches still open, that creates sixteen result paths. Each path runs through the same scoring model as the actual standings, then contributes to each owner's best and worst finish, points range, and number of winning paths.
From tournament outcomes to family standings

The number of winning paths is a count of possibilities, not a probability. The engine does not assume every outcome is equally likely. Completed matches constrain the remaining paths, and the interface can identify a locked podium or a fully settled table.
That makes the bracket personal. A match matters because of what it can change for the people in the pool, rather than simply because it occupies a slot in the tournament.
Follow a team into the match behind the points
The application extends beyond the leaderboard into group tables, team pages, match previews, and player statistics. Owner markers connect that wider tournament information back to the pool.
In the match-preview implementation, the owners' stakes appear before recent form, head-to-head history, and likely lineups. Additional match statistics and research events carry their own source notes. Missing context has an explicit unavailable state.
The statistics area separates scoring and assists from physical performance and research datasets. Speed, distance, expected goals, and team possession answer different questions, so their labels and units travel with the values. These are source-reviewed capabilities; availability depends on the corresponding feed.
Make the matchup personal

Keep exploring beyond the table

Add context without changing the score
Keep supplementary match and player data separate from the pool calculation. A richer preview should never silently change the rules behind someone's points.
Three coding agents still needed a design director
I worked with Claude, Codex, and Cursor. My contribution was deciding what to build, setting the visual direction, reviewing the result, and deciding what needed another pass. Shared repository guidance, scoped tasks, and verification checks made those decisions usable across sessions.
The build record documents a blank page despite passing automated checks. The lesson changed the process: inspect the actual page at desktop and phone widths, and keep screenshots beside the review. A passing check could no longer stand in for seeing the interface.
A review changed what “finished” looked like

The same decision at phone width

The Finish Ledger is a specific example. The target pictured a complete podium; the implementation had to respect an unfinished competition. The July 15 review approved winner and runner-up labels while third place retained a gap. Celebration had to wait for the scoring model to establish the result.
I also recorded decisions to decline additional data integrations. Those written decisions kept later sessions from repeatedly reopening the same scope questions.
One set of results has to tell the whole story
Combining feeds created a less visible design problem. One source might know a match had finished while another still had yesterday's group table. Mixing them could award result points while losing the qualification bonuses that belong to the same tournament state.
I kept results, qualification, and final group placement together in one tournament object for scoring. The documented contract requires the fallback snapshot to contain both matches and group rows. Static fixture metadata can enrich the page, but it does not decide whether a match is final or a team has advanced.
This is the engineering behind a simple promise in the interface: the total and its explanation should agree. The same calculation supports the owner page, standings, and hypothetical finish paths. Repository guards and integration tests encode that boundary.
The application reads ESPN's unofficial endpoints in the browser. It keeps a local cache and a bundled snapshot as fallbacks. That avoids a separate scorekeeping backend, but makes the age and source of the displayed scores part of the interface.
The September portfolio review encountered the fallback state: owner totals and scoring details remained available, with a snapshot label and Retry. That recovery behavior is distinct from the deliberate archive described on the product’s Story page.
Make the source visible beside the score
Preserve the last available view and explain its status. A readable scoreboard should not imply that its data is current when the feed is unavailable.
Give the pool an ending worth keeping
As the remaining possibilities narrowed, the interface changed from following a competition to remembering it. The finish view replaced gaps with locked podium labels and kept the path through the results available.
The build record describes a deliberate July 20 freeze, and the product’s Story page labels its record archived. The live-data fallback remains a separate behavior in other inspected views. Preserving that distinction matters: an old score can be a recovery state or an intentional record.
The final screen remains part of the story

What began as a draw became a place to follow your teams, question the score, and explore what could happen next. The strongest lesson for me was that the product kept improving when I changed the question—from “what else can I add?” to “what does someone watching this match need now?”
Results
- A deployed family-pool application connecting the fixed draw, owner standings, scoring receipts, match context, and possible finishes.
- A documented design evolution from the early dark interface to the lighter sports desk, with target-to-implementation reviews.
- An archived build story preserving the product’s decisions, iterations, and finish state.
Evidence limit
Original project artifacts and September 2026 browser captures are shown alongside the build record and source review. Images contain application snapshot values, not independently verified tournament results. Historical targets, implementations, and current deployment states are labelled separately. Scenario enumeration and scoring contracts were reviewed in source; the upstream test suite was not rerun. No adoption, uptime, or measured usability improvement is claimed.
Related work
CFB Model LabIndependent product
College football, beyond the scoreboard

I built CFB Model Lab to explore why my predictions disagreed with the betting lines. A longtime interest in college football became a way to learn Python and modeling, and bring that research into a product I could use every week.
- Product design
- Interactive data visualization
- Design engineering
- Python and React
Neural DriftCreative coding
Draw with rules, then change them

I built a generative-art workspace to explore how simple rules change a drawing. The controls change particle flow, color, and trails, with numbered seeds that let you return to a starting point and explore another direction.
- Generative art
- Interaction design
- JavaScript and p5.js