VeePeenini, Part 16: An Album Worth Printing, and Freezing the Museum
· Vitor Pontual · 8 min read
The Cup is over. My friends have a champion, a final leaderboard nobody can un-win, and a lounge full of arguments that are now, permanently, settled. Back in Part 1 this whole thing was a sketch on a plane on May 24, with the tournament kicking off in under three weeks. Now there’s a whole finished World Cup behind it. So this is the last entry, and it’s about the two jobs left before I put the tools down: making the album worth printing, and freezing the game into something my friends can still open a year from now without it changing under them.
An album that looks like the game
I’d already shipped the printable album back in Part 12. It was real: a cover, a two-page spread for every country, the leaderboard, each player’s points broken down, and numbers on the page I could actually stand behind. I was proud of the trust part. I was not proud of how it looked.
Because it looked like a generic light Panini yearbook. White pages, stickers pasted on. Which is a perfectly nice thing, except it was nothing like VeePeenini itself — the game is a dark, gold, monospace, arcade-cabinet kind of place, and the album exported as if it belonged to a different, blander app. It was a white page with stickers on it. I wanted it to be the game, printed.
So in the final days I had it fully redesigned. The rule I set was specific: keep the structure — that’s the real Panini album, and the whole point is that it’s the physical object I traded as a kid: one country per two-page spread, a cover, a back page, a thing you can hold. But throw out the skin. Re-dress every page in VeePeenini’s own language: near-black field, a reserved gold bloom, gold hairline rules, dark gold-edged sticker cards, numbered dark pockets for the slots you never filled. The country pages had to read as VeePeenini and not as a white sticker sheet.
The country pages, re-dressed in the game’s own dark/gold language instead of a white sticker sheet.
Then, once it looked right, I wanted it to feel personal. A yearbook of anonymous grids is fine; a keepsake that knows who you were is better. So a whole surprise-and-delight pass went in, all of it grounded in the game’s own personality rather than bolted-on cleverness:
- Mila’s scouting report. Mila is the house mascot who’s been narrating this game since launch. Now she opens a page in your album and files a scouting report on you — your strengths, your weaknesses, signed by the house. It roasts your own stats to your face, which is exactly the right tone for a group of nine friends.
- A rivalry page. Your head-to-head duel record against each friend, by name, from the real duel results. Who you actually beat, and who owns you.
- “First in the World” foil cards. Holographic plaques for the countries you were the very first player in the whole league to complete. Only one person can ever be first to close a country, so it’s the rarest brag in the game, and it gets the same gold foil frame the champion page uses.
- It opens by telling you who you were. The album now leads with your archetype — the kind of player you turned out to be — before it shows you a single Group A goalkeeper. You meet yourself first, then the stickers.
Mila files a scouting report on you, roasting your own stats to your face.
Holographic plaques for the countries you were the very first player in the league to complete.
The honest engineering beat is worth naming, because it taught me the same lesson twice. First: the renderer fights you. The album is built with react-pdf, which is a deliberately constrained thing — no CSS filters, no blur. So every bit of glow, every foil shimmer, every scanline that makes it feel like an arcade cabinet had to be faked by hand in SVG, because the easy CSS way simply doesn’t exist there. That’s fine; constraints are clarifying.
Second, and this is the real lesson: render it with the real thing, not the sample. Through the whole build I’d tested the album against sparse test data — a few stickers, a couple of countries. It looked perfect. Then I exported my own album, which is 100% complete, and pages started overflowing to a third page. One country had eleven defenders in its squad; the cramped two-page spread I’d designed simply couldn’t hold fourteen cards on a side. The sparse test data never had a squad that full, so I never saw it. A few countries with an eight-row “Road to the Final” table did the same on the other page. None of that showed up until I fed it a real, maxed-out album and watched it break. I reclaimed the vertical budget so the worst case — a real country with three keepers and eleven defenders — fits one page, then did a final pass to pull two sparse near-empty finale pages back down so nothing dribbled onto a wasted sheet. Every one of those fixes came from real production data, not the neat little sample. The sample lies to you; a real, maxed-out album doesn’t.
Freezing the museum
With the album finished, one job remained, and it’s a strange one to describe: turn the machine off without tearing it down.
VeePeenini spent six weeks as a live game. Background schedulers polled the score API, fetched and locked odds at kickoff, ran a nightly catch-up scan, snapshotted the leaderboard, resolved the bracket as results landed. All of that was the game being alive. But the Cup is over. There are no more matches to poll, no more odds to lock, no more standings to move. Leaving all that machinery running would mean a game that keeps making background API calls and state changes forever, for a tournament that already ended. That’s not alive; that’s a zombie.
So I froze it. Two moves.
First, I turned off every background scheduler — the poller, the odds fetch and lock, the nightly catch-up, the leaderboard snapshot, the bracket resolution. No more background calls, no more state changes. The game stops doing things.
Second, I gated the state-changing actions a player can still take. No new daily packs enter circulation. No new duels, no new trades or transfers — they return a polite “the tournament is over” instead of quietly mutating a final leaderboard. So the standings and every album lock, permanently. (I left decline and cancel open, so anyone with a pending trade can still tidy it away. You can put your affairs in order; you just can’t start new ones.)
But — and this is the part I care about — the site stays alive. Every tab still opens. The album still exports. Wrapped still plays. Nothing changes and everything’s viewable. The game becomes a living museum. You can wander the halls, read the plaques, print the catalogue. You just can’t move the exhibits.
The final table, frozen. Nothing moves now; you can still wander in and look. (Names anonymized.)
There’s one design call in here I’m genuinely happy with. The freeze triggers on the exact same signal that unlocked Wrapped, back in Part 15: the tournament being complete. One condition — isCompetitionComplete() — is what says “the season’s over, show everyone their year-in-review” and what says “stop the schedulers, lock the actions.” Same truth, read two ways. That self-consistency is what makes it clean rather than a pile of manual off-switches I’d have to remember. And because it’s a condition and not a demolition, it re-opens itself: if there’s ever a next Cup, with real fixtures still unplayed, the whole game wakes up on its own. I didn’t build an ending. I built a pause that knows how to un-pause.
The wrap
That’s the series. Sixteen entries, and here’s the honest shape of the thing.
It started as a sketch on a plane with a little under three weeks on the clock. It got built — the four pillars, the live lounge, the grading, the trades — and then it did the only thing that actually mattered: it ran a whole World Cup. Real friends, real stakes, arguing over a 1-0 in the 89th minute, hunting the one sticker they were missing, calling each other out in the lounge. It held up through a knockout bracket I lost sleep over, through a house move that relocated the live server, through a mid-tournament redesign to keep it a game and not a chore. And now it’s a sealed keepsake: nine people can still open it, replay their Wrapped, and print an album that finally looks like the game they played.
I’ve said the split every time, and it held all the way to the end. I owned the product — the idea, the scope, the priorities, the calls that had consequences, and the paranoia about the specific small things that would quietly betray my friends’ trust. Claude wrote most of the code, fast, far faster than I could alone. What that felt like, over sixteen entries, was this: my job was the part that doesn’t compile. Knowing what to build, in what order, and just as often what to leave off, and where a boring detail off to the side was about to break something people cared about. That was the whole job; the code was almost never the bottleneck. The velocity was real. But velocity doesn’t tell you that a renamed stadium will strand a bracket, or that a real album has a country with eleven defenders, or that you should freeze on the same signal you unlock Wrapped with. That part was mine, and it’s the part I enjoyed.
So that’s what I built, in the end. A living museum my friends can still wander through — read the plaques, print the catalogue, replay their Wrapped — where nothing moves because nothing’s supposed to, until the day real fixtures make it move again. The whistle’s blown, the museum’s sealed, and the lights are still on.