Inspiration
I kept catching myself an hour into YouTube Shorts and not really being sure when I'd opened the tab. Deleting the app worked for about a week, then I'd reinstall. Screen time blockers felt more like punishment than help, and they always block at the wrong granularity. No Shorts at all? No YouTube at all? Both feel like a hammer when the actual problem is a sieve.
The real issue isn't that I want to stop scrolling. It's that the algorithm doesn't know what I actually want to watch, and I don't trust myself to filter in the moment. By the time a junk reel hooks me, I'm not going to vote it down. I'm going to watch it.
So the idea was: build a filter you set when you're calm, and have it hold the line when you're not. Lock the strictness once a session starts so you can't loosen it the second you see something tempting.
What it does
Syte is a Chrome extension that watches your short-form video feeds (YouTube Shorts, Instagram Reels, TikTok) and quietly skips the reels you don't actually want to see. Every reel gets classified as Junk or Stay by Chrome's built-in Gemini Nano model, which runs on-device with no API key. Junk reels get auto-skipped by clicking the platform's own next button, so the result feels like a native scroll instead of an injection.
You control the filter two ways. Either a 1-to-10 strictness slider with built-in rubrics (level 1 lets almost everything through, level 10 only keeps stuff that genuinely teaches you something), or you write your own filter in plain English. Things like "nothing about celebrity drama" or "only educational content" or "skip anything that looks like a prank." When you start scrolling, the level locks for the rest of the session.
How I built it
Solo project, Chrome extension on Manifest V3. Vite + crxjs for the build, React + TypeScript for the popup and options UI, vanilla TypeScript for the content scripts that run on each platform.
The classifier call is window.LanguageModel, Chrome's built-in API for Gemini Nano. YouTube metadata comes for free from the public oEmbed endpoint. Instagram and TikTok don't have oEmbed, so for them it's DOM scraping: find the active <video> element by checking which one is closest to the viewport center, then walk up the tree to find the smallest container that looks like a single reel (has a username link, taller than half the viewport, narrower than the feed).
The active-video pick is just a closest-distance check. For each visible video with vertical center $y_i$:
$$d_i = \left| y_i - \frac{H_{\text{viewport}}}{2} \right|$$
The one with the smallest $d_i$ is the reel currently in the player. In code:
const center = window.innerHeight / 2;
const active = [...document.querySelectorAll("video")]
.map(v => ({ v, r: v.getBoundingClientRect() }))
.filter(({ r }) => r.height > 0 && r.bottom > 0 && r.top < window.innerHeight)
.reduce((best, cur) => {
const d = Math.abs(cur.r.top + cur.r.height / 2 - center);
return d < best.d ? { v: cur.v, d } : best;
}, { v: null, d: Infinity }).v;
It sounds obvious written out, but this is what unstuck the Instagram extractor after a week of it grabbing sidebar reels.
Anonymous verdict data (install ID, video ID, title, channel, verdict, level, platform) uploads to a Supabase Postgres table, opt-out in settings. That gives me a real corpus of what kinds of reels strict users actually skip, which I can use to tune the default rubrics over time.
Challenges I ran into
The Chrome AI API was the biggest one. I originally reused a single LanguageModel session across calls to save the model-warmup cost. That slowly poisoned the classifications because the session accumulates conversation history, and the model started "remembering" past verdicts and applying them in weird ways. A clean Stay would suddenly come back as Junk because three reels ago I'd scored something similar as Junk. The fix was to create a fresh session per call and destroy it after. It costs a few hundred milliseconds but actually works.
TikTok's For You page doesn't update the URL as you scroll. My video-ID extractor, which worked fine on YouTube and Instagram by polling window.location.pathname, returned nothing on the one page where people actually doomscroll. I had to write a fallback that walks the DOM to find the active video's /video/{id} link, and it's still rough because TikTok's DOM shape changes when they ship updates. It works on profile pages, but the FYP is the open backlog item.
Instagram virtualizes its feed heavily and uses rotating obfuscated CSS class names, so my first metadata extractor kept grabbing the wrong reel's caption (often the one half-visible in the sidebar). The viewport-center heuristic plus a "smallest plausible container" walk fixed it, but I had to iterate a few times on what "plausible" meant before reels stopped grabbing the wrong author handle.
Every code change requires reloading both the extension card and the open tab. Otherwise the old content script becomes an orphan and spams "Extension context invalidated" forever. I lost a lot of debug time before that two-step became muscle memory.
Accomplishments that I'm proud of
The whole pipeline runs on-device. No API key to ask the user for, no OpenAI or Anthropic bill to track, no "trust me your reel titles aren't being read by some third party." Gemini Nano does the classification right inside Chrome, and that just works in 2026 in a way it really didn't a year ago.
The lock mechanic actually shipped. It would have been easy to add an unlock button next to the slider and let users cheat in the moment, but I didn't. The lock holds until the next browser restart or an explicit confirm, and that one decision is what makes the product feel honest instead of being another willpower app.
Three platforms working end to end (well, two and a half, since the TikTok FYP is still flaky). The platform-adapter shape held up, which means adding X or Twitch later is going to be a few hundred lines, not a rewrite.
What we learned
Chrome's built-in language model is a real thing now, and almost nobody is using it. For a classifier-sized task it's free, fast, private, and good enough. It changes what you can ship as a side project without asking the user for a credit card or making them read a privacy policy.
Scraping social sites is a permanent maintenance tax. TikTok's DOM changed twice in the two weeks I was building this. You can't write a "stable" adapter for these platforms, you can only write one that fails loudly and is easy to patch when it breaks.
The discipline mechanic is the entire product. If you give people a strictness slider they will turn it down the moment they see a Junk reel they actually wanted to watch. Without the lock, Syte is just another extension with a slider. With it, it's the only thing I've tried that has actually shortened my scroll sessions in a way that holds past the first week.
What's next for Syte
The next thing being built is lookahead scoring. Right now there's a brief visible jump when a Junk reel becomes active and then auto-skips. The fix is to score the next one or two reels in the DOM queue while you're watching the current one, so the verdict is already cached and the skip is instant. That's actively in progress on the predictor branch.
After that:
- Fix the TikTok FYP properly, probably by reading the
__UNIVERSAL_DATA_FOR_REHYDRATION__JSON blob the page hydrates with instead of scraping the rendered DOM - Pre-warm Gemini Nano on page load so the first reel of a session doesn't have a 15-second cold start
- A channel allowlist so trusted creators always pass without spending a model call
- A "Wrong call?" feedback button in the popup so the rubric can learn from real corrections
- Ship it to the Chrome Web Store
Longer term, an iOS Safari extension would let this work on phones, where most doomscrolling actually happens. Apple's Foundation Models framework gives me an on-device classifier on that side too, so the architecture ports. That's a v2 problem.
Built With
- chrome
- chromestorage
- crxjs
- css
- eslint
- framermotion
- gemini
- git
- github
- gsap
- html
- javascript
- manifest
- nano
- npm
- postgresql
- radixui
- react
- reactthreefiber
- shadcn/ui
- supabase
- tailwindcss
- three.js
- typescript
- vercel
- vite
- youtubeoembedapi
Log in or sign up for Devpost to join the conversation.