Inspiration
The modern web is loud. Every page has become a fight between the content a reader actually came for and the elements competing for attention, from auto-playing videos and sticky promotional banners to dense navigation menus and unrelenting ad clutter, and for people who experience sensory overload, ADHD, or simple day-to-day fatigue with fighting a page just to read one article, this is not a minor inconvenience but a real barrier to using the web. We wanted to build a tool that did not simply strip a page down blindly, but instead understood what actually mattered on that page, and that, just as importantly, never got that judgment wrong in a way that could hurt someone, such as by hiding a consent banner or a password field.
What it does
Distill is a Chrome extension that simplifies visually cluttered webpages in real time, calibrated to how a given user actually wants to read. It extracts the structure of a page, classifies every block of content on that page, and simplifies accordingly, dimming or hiding advertisements, navigation, and autoplay media while boosting the content the user came for in the first place. Furthermore, instead of presenting a fully decluttered page all at once, Distill reveals content block by block, so that the transition itself is never overwhelming. Every simplification decision passes through a safety check before anything is hidden, and consent banners, password fields, payment forms, and warnings are never eligible for collapse, regardless of what the classifier decides about the rest of the page.
How we built it
We split the build into two independent tracks, working in parallel to move quickly without blocking one another.
Frontend
On the frontend (React, Vite, and Manifest V3), a content script extracts page structure into a set of typed blocks, each tagged with element type, position, and interactivity, and local rule-based heuristics handle simplification directly in the browser through dimming, hiding, and pausing, supported by a full snapshot and restore pattern so that any simplification can be perfectly undone. A calibration flow builds a personal visual profile for each user, and that profile drives an extension popup interface through a block-by-block reveal, complete with a progress indicator.
Backend
On the backend (FastAPI and Python), extracted blocks are classified through a layered pipeline in which a rule-based pass handles the obvious cases, ambiguous blocks are escalated to a large language model (Qwen2.5-7B-Instruct, accessed through Featherless AI) for classification, and every resulting label, whether produced by the rules or the model, passes through Pydantic validation with a deterministic fallback in the event that the model is unavailable, slow, or returns an invalid response. Furthermore, this layered design means the extension never depends on the language model being available in order to function, and it degrades gracefully to rule-based simplification automatically when needed.
The two sides communicate over a shared block schema, anchored by a dedicated safety flags field that is checked before any collapse decision is made, on both the rule-based path and the language-model-driven path alike.
Challenges we ran into
Building the frontend and backend independently in parallel allowed us to move quickly, but it also meant our data models diverged without either side noticing, a familiar risk in distributed team development. Midway through the build, we discovered that our two sides had drifted toward genuinely different classification philosophies, with the frontend organized around explicit boolean safety flags (is this a consent control, a warning, or a password field) and the backend having moved instead toward a single enum-based category assigned to each block. Merging these two models naively would have silently dropped exactly the safety signal that mattered most, and this was a serious concern given that we had already identified and fixed an earlier bug in which safety-critical elements, such as a cookie consent banner, were being hidden by our own heuristics, meaning the schema drift risked reintroducing that same failure one layer deeper, inside classification rather than collapse logic. We resolved the issue by conducting a full side-by-side comparison of every field before changing any code, and we ultimately kept category (what kind of block this is) and safety (whether this block must be protected regardless of category) as two separate, orthogonal fields in the schema, so that a future change to classification could never again silently compromise safety.
We also ran into a resource concurrency challenge. Initially, we used DeepSeek-V4-Flash-073 as it is a more powerful model than the Qwen2.5-7B-Instruct. However, each request uses 4 concurrent units, often resulting in an error that stopped the backend from working because there were no LLM calls. Qwen only uses 1 concurrent unit per request, allowing for our program to work effectively. Moreover, Qwen is especially good for extracting info cleanly or outputting rigid JSON formats reliably – the exact use case that we require.
Accomplishments that we're proud of
We are proud that the failure mode of hiding the wrong element is not merely handled reactively within Distill, but structurally prevented by design. Any block flagged as a consent control, password field, payment field, or warning is protected from collapse before either the rule-based path or the language-model path is ever given a say, and that protection is enforced through a single shared check that both paths run through, rather than through duplicated logic that could drift apart over time. In a system that ultimately relies on a language model to make judgment calls about a page it has never seen before, building a deterministic safety net beneath that judgment felt like the right foundation to establish first.
What we learned
Independent, parallel development moves quickly, but it requires a shared contract established from day one, or at minimum an explicit checkpoint built in to catch drift before it compounds into a larger problem. Furthermore, we learned that adding a safety check is not truly complete until every path capable of reaching a collapse decision has been traced and confirmed to route through that check, not simply the most obvious one, since in our case both the rule-based fallback path and the language-model-driven path needed to be verified independently.
What's next for Distill
We hope to extend the safety flag system with more nuanced priority handling, so that classification tuning, meaning decisions about what should be collapsed, and safety, meaning decisions about what must never be hidden, can continue to evolve independently without risking regressions in either. We also hope to expand calibration further, learning not only a static visual profile but adapting simplification over time as a given user's needs shift across different kinds of pages.
Built With
- fastapi
- featherless
- featherlessai
- manifest
- mediapipe
- pydantic
- python
- react
- slowapi
- tailwind
- tensorflow
- typescript
- vite
Log in or sign up for Devpost to join the conversation.