den.js 1.0.0 — he exists
This commit is contained in:
232
README.md
Normal file
232
README.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# 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
|
||||
<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:
|
||||
|
||||
```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
|
||||
<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:
|
||||
|
||||
```html
|
||||
<span class="badge" data-den-steal>free gifs</span>
|
||||
```
|
||||
|
||||
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
|
||||
<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.
|
||||
|
||||
```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');
|
||||
```
|
||||
|
||||
`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();
|
||||
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` | 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:
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user