Roll up the web, one word at a time.
A tiny, dependency-free bookmarklet that turns any page's text into a katamari-style sticky ball. Hold a mouse button and drive — the ball has real momentum, rolls as it travels, and grows by volume. Words stick where it touches them, ride around the sphere, and vanish behind it. Hit Esc and the page snaps back, untouched. The name is the game: moji, written characters, plus maki, rolling something up — MojiMaki is text, rolled up.
Drag me up to your bookmarks bar — then click the bookmark on any page.
The bookmarklet loads mojimaki.js from this site —
so it works on any page, with zero installs.
Drag the green pill into your bookmarks bar. That's the whole install — one bookmark, no extension, no account.
Open any page, click the bookmark, then hold a mouse button and move. The ball accelerates, coasts, and bounces off the page's edges — real momentum, not a cursor sticker.
Everything sticks where the ball touches it and rides around the sphere as it rolls. Growth is earned up a ladder of eight word classes — small words first, bold and headings last — with the early rungs arriving in a handful of words and the top a deliberate long tail. A ball eats its own rung and the one below, so the small words survive for the next roller. Esc restores everything.
Everything is static. There is no build step, no dependency, no tracking, and no network activity after the script loads. Grab the two files and put them anywhere that serves bytes:
# any static host works — GitHub Pages, Netlify, S3, a Raspberry Pi… curl -O https://your-host.example/mojimaki.js # the whole game (~96 KB, ~30 KB gzipped) curl -O https://your-host.example/index.html # this page (optional)
Point your own bookmarklet at your copy by pasting this as a bookmark URL (swap in your host):
javascript:(function(){if(window.MojiMaki){MojiMaki.start();return;}window.MOJIMAKI_CONFIG={autoStart:true};var s=document.createElement('script');s.src='https://your-host.example/mojimaki.js';s.onerror=function(){alert('MojiMaki failed to load from '+s.src);};(document.body||document.documentElement).appendChild(s);})()
Or bake it into a site you control, configured however you like:
<script>window.MOJIMAKI_CONFIG={ startRadius:24, respectSize:false }</script>
<script src="mojimaki.js"></script>
Pass an options object to MojiMaki.start({...}), or set
window.MOJIMAKI_CONFIG before the script loads. The fun ones:
| Option | Default | What it does |
|---|---|---|
| control | 'hold' | 'hold': press & hold to thrust (classic feel). 'follow': ball chases the cursor with easing. |
| accel / friction | 0.5 / 0.95 | Thrust per 25 ms tick and velocity decay — the momentum feel. |
| growth | 0.28 | Volume multiplier per pickup — how much of each word's volume the ball keeps. Growth is still r³ volume conservation, the katamari way, but earned slowly (1.0 = quick growth). |
| respectSize | true | Cargo heavier than the ball by volume bounces off until you've grown. false = eat anything, instantly. |
| tierWeights | true | Bold text and headings are extra heavy: a word needs ball volume ≥ word volume × its tier weight, and heavier pickups grow you faster. false = size is the only gate. |
| weights | {bold:4, h1:8, h2:7, h3:6, h4:5, h5:4.5, h6:4} | Tuning knobs for the tiers above. Lower = easier game, higher = stricter progression. |
| wordClasses | true | The eight-rung ladder, climbed by appetite: every word gets a class — small words, larger words, small bold, largest words, medium bold, headers, larger bold, largest headers — and each word eaten is worth a little XP. A ball at rung k eats classes k-1 and k (small words survive for the next roller); the band widens to whatever food is left, so nobody starves on a picked-over page. Words above the band flash violet; below it they are simply left alone; too-heavy ones flash red/amber/orange. false = no ladder. |
| classCost | [0, 6, 20, 55, 130, 300, 700, 1600] | Cumulative XP that unlocks each rung, low to high — six small words buys rung 1, and the largest headers want 1600 XP (some pages cannot pay that; that is what the deep end is for). The HUD shows the ladder as a row of dots that fill as rungs are earned; the next dot pulses when you're close. |
| classXP | [1, 2, 3, 4, 5, 6, 7, 8] | XP a word of each class is worth. |
| resume | null | {volume, count, xp} — carry the ball between scenes: MojiMaki stashes it on the way out of a hole and hands it back on the way into the next. |
| growthEase | 190 | Ms — the visible radius eases toward its new target as words absorb, instead of jumping. 0 = instant. |
| maxAttached | 75 | Cargo cap. When exceeded (or when the ball outgrows them) the oldest words are absorbed — like the original. |
| elementPickups | ['img'] | Whole elements you can roll up. Images carry real volume, so they feed the ball properly. |
| ballColor / chipBorder | '#2f9e5b' / '' | Ball outline + rolling stripes; word-chip outline ('' = none, '1px solid #bbb' = the classic outlined look). |
| startRadius / maxRadius | 18 / 460 | Where the ball begins, and where it stops growing. |
| bounce | true | Bounce off the document's edges. |
| sound | true | Synthesized pickup pops + edge-bounce thuds. Click the page once so the browser allows audio; press M to mute. |
| wander | false | Autopilot demo mode — the ball drives itself. Real input takes over instantly. |
| autoroll | false | The self-driving ball: toggle it with the ► button in the pill (right next to the mute button) or the A key. While it rolls, your mouse stays free — moving it never stops autoroll; only holding a button takes the wheel, and releasing hands it straight back. Words it picks on its own still count, but feed no size and no rungs — take the wheel to grow. |
| cleanupKey | 'Escape' | Key that stops the game and restores the page. null disables. |
The full list with defaults lives at the top of mojimaki.js — it's one commented file, have at it.
The roll angle advances by arc-length ÷ radius, so the stripes on the ball spin honestly. Words stick exactly where the ball touches them, foreshorten as they roll over the top, and slip behind the ball until they come around again.
Picked-up words are hidden, not deleted, so the layout never
re-flows while you play. Esc restores the original DOM
node-for-node — no reload needed. Form controls and
position:fixed chrome are politely left alone.
Every object is indexed into a 100 px spatial grid across all cells it touches, so collision checks only look near the ball — a 10,000-word page rolls just as smoothly as a small one.
The game renders in its own top layer, above sticky headers and cookie banners — the ball is never hidden under a page's chrome. And if momentum carries it off-screen, a pulsing arrow at the screen edge points the way home.
The party/ folder ships an experimental multiplayer
layer: a zero-dependency Node relay opens Maki Station — a
shared home base with twenty holes, each one dropping into a different
field — a random Wikipedia article, the Hacker News front page, a
post from a homelab blog, a slice of Moby Dick — plus a
twenty-first, the deep end: a hole that endlessly stitches
more random pages on as you roll down, with the ball's size ceiling
off. Every field sits on a centered reading card with wordless
trails on either side — roll a trail and the page scrolls along, so
you can read the article without rolling it up. Roll into a hole
and you see everyone who fell through it as ghost balls — each of
you wears only the words you picked, and a word feeds
exactly one Maki. Run npm start (or
docker compose up --build from the project root) and
send the link to a friend.
One file, ~96 KB unminified (~30 KB gzipped), no dependencies, no requests after load, no analytics, MIT licensed. If your host can serve a text file, it can host MojiMaki. Rename it, restyle it, fork it.