Files
den-repo/README.md
2026-08-14 15:56:15 -04:00

7.2 KiB
Raw Blame History

den.js

An invisible kobold lives on your website.

You never see him. You see his paw prints, his little dialogue, and — after ten minutes of quiet — the bed he drags out to sleep in. He walks to your cursor, gets bored and wanders off to sniff at things, remembers you between visits, and depending on how well he knows you he will either steal your buttons and run, or bring them to you as gifts.

One script tag. No dependencies. No build step required.

<script src="/den.js"></script>

That's it. That's the integration.


What he does

Movement. He has a position, a heading, and a turn rate, so he can't snap around instantly — chase him with your cursor and the prints carve an arc as he wheels to follow. He trots when you're close, sprints when you're far, and lays prints by distance travelled rather than on a timer, so his stride stays physically consistent at any framerate.

Moods. chase when your cursor moves, rest when he arrives, wander when he gets bored, errand when he's been sent somewhere, mischief when he's committing a crime. Emote bubbles float off him — when he notices you, 👃 while sniffing, 💤 while dozing, hearts when he's fond of you.

Sleep. Ten minutes with no input and he yawns, walks the tight circle every canid walks before lying down, and a bed fades in with a breathing lump under the blanket. Any keypress, click, scroll, or mouse twitch wakes him — groggily.

Memory. Visit count, lifetime pets, and his name persist in localStorage. Return after a week and he says so. Pet him ten times and he decides you're friends; at twenty-five he starts lingering near your cursor instead of wandering off, and his stealing turns into gift-giving.

A shell. A >_ den tab sits in the corner of every page. Open it and you can come, sit, pet, fetch, sleep, where, status, or name him.

Presence. Optional. With den.php on the server he'll notice how many other people are on the site and mention that he can smell them.


Install

Copy src/den.js (or a build from build/) to your web root and add the script tag. Nothing else is required — he injects his own CSS and brings his own audio.

For the presence counter, also copy src/den.php to your web root, change the secret at the top, and make sure PHP can write one file next to it:

touch ../owo_presence.txt
chown www-data:www-data ../owo_presence.txt

The storage file lives one level above the document root so it can never be fetched over HTTP.


Page integration

Everything beyond the script tag is declarative markup.

Points of interest

Mark anything you want him to visit and comment on. Pipe-separate alternate lines and he'll pick one at random:

<div class="panel" data-den-poi="yip (sign it)|yip yip (write something)">

When he decides to wander, there's a 45% chance he heads for a POI that's currently on screen instead of a random point. He skips whichever one he visited last, so he explores rather than fixating.

Stealable things

Nothing is stealable unless you say so:

<span class="badge" data-den-steal>free gifs</span>

He self-limits to elements between 8220px wide and 890px tall, and only ones currently visible. The original element is hidden and a fixed-position clone follows him, so the page layout doesn't reflow while he's got it. Everything always comes back — there are timeouts on every phase, and clicking him returns the item instantly.

Showing the presence count

<span data-den-others></span>

Filled in on load and refreshed every 60 seconds.


Events

Pages tell him what happened; he decides how to feel about it.

denEvent('celebrate',   { text: 'YIP!! (a new signature!!)' });
denEvent('investigate', { el: '#guestbook', text: 'yip? (what is this)' });
denEvent('investigate', { x: 400, y: 300, run: true });
denEvent('alarm',       { text: 'YIP?! (INTRUDER)' });
denEvent('mischief');
denEvent('sleepy');
denEvent('zoomies');

celebrate is a happy spin with a heart burst. investigate sends him on an errand — while on one he ignores your cursor, because he's busy. alarm startles him into a panicked sprint.

Lower-level helpers, if you want to drive him directly:

denSay('yip!!');        // speech bubble at his current position
denYip();               // the synthesized squeak
denSetName('crumb');    // returns the sanitized name, or null
denGetName();
denOthers;              // number, current presence count

A worked example

Redirect after a successful form post with a marker, then:

const q = new URLSearchParams(location.search);
if (q.get('signed') === '1') {
  history.replaceState(null, '', location.pathname);
  setTimeout(() => denEvent('celebrate', { text: 'YIP!! (for the hoard!!)' }), 3400);
}

The delay lets him finish climbing out of his burrow first.


Secret codes

Typed anywhere outside a text field:

type he says
yip yip!!
owo *notices u*
uwu yip (no)
kobold yip!! (that is me)
shrine YIP?! (how do u know about that)
zoomies ten seconds of chaos
sleep bedtime
↑↑↓↓←→←→BA same as zoomies

Building

Optional — src/den.js runs fine as-is. Builds are for size and, if you want it, hiding the surprises above from anyone who opens view-source.

npm install -g terser javascript-obfuscator
./build.fish
file size notes
src/den.js 33 KB readable source, the one you edit
build/den.min.js 20 KB minified; recommended
build/den.obf.js 88 KB string-encoded and control-flow flattened

The obfuscated build hides all his dialogue and secrets from casual reading, at 4x the file size. Nothing stops a determined reader — this is spoiler protection, not security.

build.fish also bumps the ?v=N cache-buster in any .php file that references a build, because forgetting that will cost you an afternoon convincing yourself the code is broken when the browser is simply serving you last week's kobold.


Testing

test/sim.js runs him headless in jsdom with a mocked clock, so you can step through behaviour that would otherwise take ten minutes of sitting still:

npm install jsdom
node test/sim.js                 # readable source
node test/sim.js build/den.obf.js  # verify a build still works

It drives a full mischief cycle and asserts the stolen element is restored.


Notes

  • Respects prefers-reduced-motion — under it he doesn't spawn at all, and the API becomes no-ops so your page code never errors.
  • Everything is position: fixed with pointer-events: none, so he can't intercept clicks or affect layout.
  • Petting ignores clicks on links, buttons, and form fields.
  • If den.php is missing, presence silently no-ops.
  • He injects text-align: left on his own UI, because inheritable properties leak in from host pages. If something of his looks off on a new page, check what the host's body is setting.

License

MIT — see LICENSE.

Built for owo.ing. If you put him on your site, he's yours now. Name him something good.