← All Projects

ManaBleed

A Magic: The Gathering price tracker and collection binder manager.

Python Jinja2 SQLite Tailwind CSS Scryfall API
ManaBleed screenshot

Built out of a practical need to track Magic: The Gathering card prices and manage collection binders. Card prices move every day, and following them across a collection usually means a pile of browser tabs or a spreadsheet. ManaBleed keeps nightly-updated prices for every printing in one fast, searchable site. It adds a deck cost calculator for pricing out a decklist, a personal "My Binder" watchlist for the cards you own or want, and reprint-risk analysis for spotting cards whose price might be about to drop.

The entire site is pre-rendered static HTML: no application server, no client-side framework, and no database exposed to the internet. A single Python script using Jinja2 renders every page from a local SQLite database, and a web server serves the result directly. That is over 100,000 card pages, one per printing. The data comes from Scryfall. A nightly harvester pulls its bulk data and card rulings into SQLite, which holds roughly 110,000 printings, a price-history row per card per day (millions of rows in total), and tens of thousands of official rulings. A scheduled job then does a full rebuild every night. Interactivity is vanilla JavaScript, one small file per feature, with no bundler: an ARIA-compliant search box driven by a prebuilt JSON index, a deck calculator that parses pasted or uploaded decklists and resolves the right printings, an embeddable price-chart widget, and a Web Share button with a clipboard fallback. "My Binder" lives in the visitor's own browser with export and import, so no accounts are needed. Styling is Tailwind CSS, compiled with the standalone CLI binary, so the project needs no Node.js at all.

The most instructive incident was a Tailwind CSS build that ran out of memory in production. Tailwind's automatic content scanning walks the whole project directory, which included the 100,000-page output folder and the virtualenv. The fix was to switch scanning off and declare explicit source paths. It has a side effect worth knowing about: a Tailwind class that only appears inside a string built in Python is never compiled, and quietly does nothing in the browser. That caused two real bugs, and the rule since is that markup generated in Python gets inline CSS instead. Caching needed the same care. The web server serves CSS and JS with a 30-day immutable cache, so every asset URL carries a content hash computed at build time from the stylesheet, templates, and scripts. Deploying a changed file without a full rebuild would leave pages pointing at stale URLs, so a full rebuild is part of every deploy. Getting the server's location-matching precedence right for the CORS rules and the search index was a related lesson. And the upstream data changed underneath the pipeline. Scryfall switched its bulk export from a single JSON array to gzip-compressed JSON Lines, which broke the harvester until it was rewritten to stream the file line by line.

Visit Site