# 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.
```html
```
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, treats, and his name persist in
`localStorage`. Return after a week and he says so. Reach ten points of trust
(pets + treats) and he decides you're friends; at twenty-five he lingers near
your cursor instead of wandering off, and his stealing turns into gift-giving.
**Drift.** What he mutters while wandering depends on how you've treated him. Feed
him and he's content. Visit repeatedly without ever touching him and he starts
asking whether anyone's there.
**A shell.** A `>_ den` tab sits in the corner of every page. Open it and you can
`come`, `sit`, `pet`, `treat`, `fetch`, `rain`, `snow`, `sleep`, `where`,
`status`, or `name` him.
**Weather.** `denWeather('rain')` and he bolts for the nearest POI to wait it out,
sulking under cover until it stops. `denWeather('snow')` and he loses his mind —
a burst of speed, and his prints linger three times as long in it.
**Treats.** Drop one and he sprints for it. Treats build trust the same way pets
do, and he gets visibly hungry if he hasn't been fed in a while.
**Scrolling.** He's standing on the page, not floating above it — scroll and he
gets carried along, has to scramble back, and arrives out of breath.
**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:
```sh
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:
```html
```
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:
```html
free gifs
```
He self-limits to elements between 8–220px wide and 8–90px 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
```html
```
Filled in on load and refreshed every 60 seconds.
---
## Events
Pages tell him what happened; he decides how to feel about it.
```js
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');
denEvent('rain'); // also 'snow', 'clear'
denEvent('treat', { x: 400, y: 300 }); // or { el: '#bowl' }
```
`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:
```js
denSay('yip!!'); // speech bubble at his current position
denYip(); // the synthesized squeak
denSetName('crumb'); // returns the sanitized name, or null
denGetName();
denWeather('snow'); // 'rain' | 'snow' | 'clear'
denTreat(x, y); // drop a treat at a point
denOthers; // number, current presence count
```
### A worked example
Redirect after a successful form post with a marker, then:
```js
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.
```sh
npm install -g terser javascript-obfuscator
./build.fish
```
| file | size | notes |
| --- | --- | --- |
| `src/den.js` | 42 KB | readable source, the one you edit |
| `build/den.min.js` | 25 KB | minified; recommended |
| `build/den.obf.js` | 112 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:
```sh
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](LICENSE).
Built for [owo.ing](https://owo.ing). If you put him on your site, he's yours
now. Name him something good.