Skip to content

feat: rampage mode (the hedgehog can now knock your website over) - #43

Open
Colin-Moran wants to merge 1 commit into
PostHog:mainfrom
Colin-Moran:feat/rampage
Open

feat: rampage mode (the hedgehog can now knock your website over)#43
Colin-Moran wants to merge 1 commit into
PostHog:mainfrom
Colin-Moran:feat/rampage

Conversation

@Colin-Moran

@Colin-Moran Colin-Moran commented Aug 19, 2026

Copy link
Copy Markdown

I wanted the hedgehog to be able to knock stuff over!

what this adds

two easter egg keywords, in the existing secretMap style:

  • rampage puts a headband on him for 20 seconds. anything he lands on gets
    knocked off the page, tumbles under physics, and springs back into place.
  • earthquake does the whole visible page at once, which is exactly as
    productive as it sounds.

both are listed in the cheatcodes sheet.

GIF (the low fps doesn't do it justice)

rampage-stomp

how it works

your page elements were already rigid bodies. that's how he stands on your nav
bar. they just never pushed back. ShovedElement gives one of them a dynamic
Matter body and writes that body's position onto the real element as a CSS
transform every frame, so the thing tumbling across the screen is your actual
button, not a picture of it.

why this can't hurt a host page

this is a library that runs on top of real products, including yours, so the
whole thing is built to be un-break-able:

worry answer
does it mutate the DOM? no. transform only. nothing added, nothing removed, no reflow
what if it's torn down mid-flight? restore happens in beforeUnload(), so it goes home on settle, on game destroy, and on host re-render alike
does it stomp on my styles? inline transform/transition/will-change are saved and put back exactly as found
what if the page is enormous? capped at 40 elements at once, and anything bigger than 40% of the viewport is skipped, because a cartwheeling layout wrapper reads as broken rather than funny
can it fire on its own? no. it's opt in, typed, and off by default

worth stating plainly: after everything settles, document.querySelectorAll finds
zero elements carrying a transform and zero carrying a leftover inline style.
i checked, because "it probably cleans up" is not a thing to say about a library
that ships to customers.

a note on the headband

skins/default/action has been sitting in the spritesheet since someone drew it
and no code has ever played it. rampage opens with it. it needed the AI paused
and the pose held, because update() reasserts walk/fall every frame and would
otherwise bin the animation on the very next tick, which is presumably why it
never got used.

that leaves phone, inspect, sign and flag still unplayed, if anyone wants
them.

how it was tested

unit tests on the offset maths, including the scroll compensation, which is the
part that is easy to get subtly wrong and invisible in a screenshot.

then in the playground, measured rather than eyeballed:

check result
earthquake 40 elements shoved, spread across the full width of the page rather than clustered where the element list happens to start
after it settles 0 elements transformed, 0 leftover inline styles, 0 entities left in the game loop
stomp, not rampaging element untouched, so it really is opt in
stomp, rampaging translate(-142px, -45px) rotate(-2.73rad), then home again
scrolled 200px mid-flight element moved 8px, staying glued to its body instead of sliding away with the page
headband held for 538ms instead of a single frame

the offset is measured against the element's nearest scrolling ancestor rather
than the window, because this project's own playground scrolls a panel rather
than the document, and so does the app this library actually lives in.

pnpm test, pnpm lint and pnpm build all pass.

what it doesn't do

no new art, no new dependencies, no public API changes, nothing exported. the
engine gets one new item and one new hedgehog trick, and every existing keyword
behaves exactly as before.

Adds two easter egg keywords that turn the host page into furniture.

`rampage` sends him on a tear for 20 seconds: anything he lands on gets
knocked off the page, tumbles under physics, and springs back into
place. `earthquake` does the whole visible page at once.

The page's elements were already physics bodies, since that's how he
walks on your buttons, but nothing ever pushed back. ShovedElement gives
a DOM element a dynamic Matter body and writes that body's position onto
the real element as a CSS transform each frame.

Deliberately transform-only, so this can never damage a host page:

- nothing is added to or removed from the DOM, so the page never reflows
- the element restores in beforeUnload(), which means it goes home
  whether it settles on its own, the game is torn down, or the host
  re-renders it out from under us
- inline transform/transition/will-change are saved and put back exactly
  as they were found
- it's opt in, capped at 40 elements at a time, and skips anything
  bigger than 40% of the viewport, since watching a layout wrapper
  cartwheel just reads as broken

The offset is measured against the element's nearest scrolling ancestor
rather than the window, so shoved elements stay glued to their bodies in
apps that scroll a panel instead of the document.

Rampage opens with the headband animation, which has been sitting in the
spritesheet unused since it was drawn. update() reasserts walk/fall every
frame, so the flourish also needs the AI paused and the pose held, or it
is gone before anyone sees it.
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

@Colin-Moran is attempting to deploy a commit to the PostHog Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant