A team of tiny, judgy Pokémon who live in your browser, watch you scroll, and stage a fullscreen intervention when you've been on Reddit for forty-five minutes.
You ever wish your browser had a small friend? You ever wish that small friend had Strong Opinions about your YouTube rabbit holes? Congratulations — you wished a thing into existence and now it's a Chromium extension.
ShardPet drops 1–5 animated Gen 5 Pokémon onto every web page you visit. They wander. They hop. They occasionally stop to think about their life. You can click one to swap it for another. They are pixelated, they are emotionally unavailable, and they will not stop you from opening Twitter.
But the other thing they do — that's where it gets unhinged. Add a productivity threshold. Add an allowlist of sites that "count as work" (a generous fiction we are all in on). The moment you spend too long anywhere else, two dozen of your cached Pokémon materialise across your screen at once, in a glorious pixel-art fullscreen "GET BACK TO WORK!" overlay. They are everywhere. They are watching. They will not leave until you click "Dismiss" and admit you have a problem.
It is also, somehow, designed to use less CPU than your desktop wallpaper.
Pets, vibing, doing their little walk:
Pets, no longer vibing, staging an intervention:
- 1–5 wandering Pokémon that walk, idle, and occasionally hop. Click one to reroll it. They have free will. Mostly.
- Highly customisable: count, size (24–128 px — yes you can make them huge), bottom offset, speed (slow / normal / fast / "Slowpoke is sprinting"), per-host blacklist for sites where you'd rather not be perceived.
- The Productivity Nag™: per-hostname cumulative timer (1–120 min, default 15 min), allowlist for sites that don't count, and a 5-minute cooldown after each dismiss because we're not monsters.
- Live timer pill (optional) — a tiny top-right HUD that judges you in real time.
reddit.com • 4m37s / 5m00sis a deeply uncomfortable string. - Toolbar popup for all settings. No cursed options page. Click the icon, fiddle, leave.
- Reduced-motion aware — auto-detects
prefers-reduced-motionso it doesn't enrage your vestibular system. - Shadow-DOM isolated — page CSS can't touch our Pokémon, our Pokémon can't touch page CSS. Diplomatic immunity.
- All 649 Gen 1–5 Pokémon included by default — every species the PokéAPI animated BW sprite set covers, fetched once and cached locally forever (or until you hit Resync).
ShardPet is engineered to be invisible on a CPU profile. Genuinely.
- One
requestAnimationFrameloop, frame-skipped to ~30 FPS. That's it. There is no other loop. - Movement is
transform: translate3donly — pure GPU compositor, zero layout, zero paint. Your browser doesn't even notice. - Animation fully suspends when the tab is hidden. Background tabs cost zero. Less than zero, even, in spirit.
- The work timer also pauses on hidden tabs. We are not psychopaths.
chrome.storage.localwrites are throttled to once every ~30 seconds (with forced flushes when something actually matters), so 17 open tabs don't all fight to write the same number.- The work-timer interval doesn't even start if you've turned the nag off. We respect your toggles.
- All 649 Gen 1–5 sprites fetched once at install with bounded concurrency (~16 in flight) so we don't hammer GitHub's CDN. Total cache lands around 5–9 MB on disk; the extension declares
unlimitedStorageto keep it comfortably under quota. Hand-on-heart, the extension code itself is still smaller than the GIF you sent in chat earlier. - DOM only re-spawns when visual settings actually change. Nudging the offset slider patches CSS variables in place, like a civilised codebase.
Mozilla Add-ons / Chrome Web Store listing TBD. For now it's BYO-build.
Option A — download the release:
- Grab the latest
shardpet-vX.Y.Z.zipfrom Releases and unzip it. (v2.0.0is the current full roster;v1.0.0is the original 24-Pokémon minimal build if you want a smaller footprint.) - Open
chrome://extensions(or whatever your Chromium flavour calls it — Helium, Brave, Edge, Arc, the new one your friend told you about last week). - Flip on Developer mode (top right corner, you've got this).
- Click Load unpacked → pick the unzipped folder.
- Pin the extension. Watch a Pikachu appear. Cry a little.
Option B — build from source like a normal person:
git clone https://github.com/hett-patell/ShardPet.git
cd ShardPet
npm install
npm run build
# now load `dist/` via "Load unpacked" as aboveOn first install the extension fetches a curated set of Gen 5 Black/White animated sprites from PokéAPI's public sprite CDN. After that, it could survive on a desert island (with internet for, uh, browsing).
Click the ShardPet toolbar icon. A 340px popup falls out. Two sections.
- Enable / disable the whole show
- Count (1–5), size (24–128 px), bottom offset
- Speed:
slow/normal/fast - Reduced motion:
auto(follow OS) /off/on - Blacklist — hostnames where Pokémon won't appear (one per line; subdomains match). Useful for
mail.example.com, banking sites, your therapist's intake form.
- Enable / disable the "Get back to work!" overlay
- Trigger threshold: 1–120 minutes of cumulative time on a non-allowlisted hostname (default 15 min)
- Allowlist — sites you're allowed to use without being yelled at (one per line; subdomains match). Standard inclusions:
github.com,linear.app,notion.so, your company's Jira, the docs page you're definitely going to read this time. - Show live timer pill — top-right HUD. Recommended for the first day so you can debug your own brain.
- Reset timers — nukes per-hostname accumulators and any active cooldown. For when you've earned a clean slate or want to test the overlay without sitting through five minutes of suspense.
- Resync sprites — re-fetches the cache from PokéAPI. Run this if you ever feel your pets are getting stale.
When the overlay fires: title in pixel-art font, two dozen Pokémon (sampled fresh from the cache each time) scattered across the screen via grid-jitter placement — each gets its own distinct zone, no clumping, no overlap. Dismiss with the button, click outside the title card, or hit Esc. That starts a 5-minute cooldown and zeroes the dismissed hostname's counter, so the next nag is also a full threshold away. Mercy.
npm install # install deps
npm run dev # Vite dev (popup HMR)
npm run build # production build → dist/
npm test # unit tests (Vitest, all green)
npm run typecheck # strict TS, no `any`s, no excusessrc/
background.ts # MV3 service worker: fetch + cache sprites once
content.ts # injected into every page; mounts lane, timer, overlay
overlay.ts # "Get back to work!" overlay (shadow-DOM, scattered)
overlay.css # pixel-art title font, scatter animations
popup/
index.html # 340px Game Boy-styled settings panel
popup.ts # bound form ↔ chrome.storage + live preview lane
storage.ts # typed chrome.storage.local accessors + defaults
styles.css # lane + sprite styles (loaded as ?raw into shadow)
sprite-fetcher.ts # PokéAPI fetch + base64 (no FileReader; MV3-safe)
pokemon-list.ts # curated list of IDs + sprite URL helper
wander.ts # pure walk/idle/hop state machine (unit-tested)
work-timer.ts # pure cumulative timer + cooldown state (unit-tested)
icons/ # 16/32/48/128 PNGs for the toolbar + Chrome menus
tests/ # Vitest specs for the four pure modules
The pure modules (wander.ts, work-timer.ts, storage.ts, sprite-fetcher.ts) have zero DOM dependencies and are tested independently. Everything stateful and DOM-y in content.ts is a thin shell over them. Refactor with confidence.
Q: Why does it ask permission for a pokeapi/sprites URL?
A: That's the GitHub raw URL where the animated GIFs live. The extension hits it exactly once on install — and again only if you click Resync sprites. After that it's offline forever.
Q: Does it slow down web pages?
A: On a typical laptop the wander loop costs well under 1% CPU. The work timer wakes once every 5 seconds while the tab is visible. Hidden tabs do exactly zero work. Memory: a few hundred KB for the cached sprite data URLs plus up to five <img> elements. Your browser eats more for breakfast.
Q: Will it leak its styles into the pages I visit?
A: No. Both the lane and the overlay live in closed shadow roots, and the host elements use all: initial. Your CSS-in-JS library can rest easy.
Q: My Pokémon is stuck. A: Click them. They reroll. If they're all stuck (i.e. the loop is dead), open a new tab — content scripts are per-page.
Q: It triggered immediately after I dismissed it. Bug?
A: Fixed in v1.0.0. applyDismiss now resets the dismissed hostname's accumulator so the next nag also requires a fresh threshold's worth of time. You can resume your descent into Twitter responsibly.
Q: What's different in v2?
A: Three things, mostly: (1) all 649 Gen 1–5 Pokémon (v1 shipped 24 starters); (2) sprite cache now refreshes every 90 days so you pick up upstream fixes; (3) several quiet correctness/perf fixes — the storage-event suppression race is gone, allowlist hits no longer leave dead keys behind, the per-hostname history is capped at 100 entries to stop unbounded growth, the latin1 TextDecoder replaced a chunked base64 hot-path, and the default focus threshold is now 15 min instead of an aggressive 5. v1 is preserved as the "minimal" build if you want the lighter cache.
Q: What's new in v2.1?
A: A bit of polish and a bit of plumbing. The popup got a full Game Boy-themed pixel-art rewrite — chunky borders, segmented controls instead of dropdowns, a real live preview lane so you can see your settings before saving, and a proper bundled toolbar icon (no more generic puzzle piece). Pet count now goes up to 5 instead of 3. The "Get back to work" overlay caps at 24 sprites instead of dumping all 649 on the page — same vibe, far less melted GPU. Plus several lifecycle fixes: the overlay can no longer become undismissable if you disable the extension mid-nag, hostnames are now case-insensitive (so GitHub.com in your allowlist actually matches github.com), the popup threshold slider goes to 120 like the docs always claimed, and pages restored from bfcache no longer leak a stale rAF loop into detached DOM nodes.
Q: Can I add my own Pokémon list?
A: Yes — edit src/pokemon-list.ts, rebuild, and click Resync sprites. Mythical / legendary Pokémon are excellent candidates for "shame me extra hard."
Q: Does this work in Firefox? A: Not currently — it's a Manifest V3 Chromium extension. Firefox MV3 support is technically there but the manifest fields differ slightly. PRs welcome from someone braver than me.
Q: Will Nintendo sue me? A: They're going to sue me, not you. You're fine.
PRs welcome. Issues welcomer. Bugs are inevitable; the absence of tests is unforgivable. If you add a feature, add a test for the pure logic. If you change UI, please rebuild and confirm nothing flies into low-earth orbit.
Source code: MIT — see LICENSE. Do whatever you want, just don't blame me when your Snorlax gets sentient.
Pokémon names and sprite imagery are © Nintendo / Game Freak / Creatures Inc. Sprites are not redistributed in this repository — they are fetched at runtime from PokéAPI's public sprite CDN. This is an unofficial, non-commercial fan project, not affiliated with or endorsed by Nintendo, Game Freak, Creatures Inc., or The Pokémon Company. Please don't sue us, we just wanted a small friend in our browser.
| Repo | What it does |
|---|---|
| ShardLure | SSH honeypot + threat-intel dashboard |
| ShardC2 | Red-team C2 framework in Go |
| ShardFlow | Layer-2 LAN workbench (ARP, drop, throttle) |
| ShardShell | PHP post-exploitation shell |
| ShardPass | Minimal TOTP authenticator (Chrome MV3) |
| ShardPet | Pixel-Pokémon browser extension |