How this thing works
The build
Static HTML, CSS and plain JavaScript on the front. No framework, no bundler. The catalog
lives in data/games.json — mirrored as data/games.js so it also
works straight off a filesystem — and every page reads from that one list.
Accounts, friends and messages are served by a small Node + SQLite backend in
server/. It is entirely optional: without it the site is still a complete,
deployable static arcade.
Cover art is real where a game ships its own icon, and generated from the title's id where it doesn't — a deterministic geometric plate, so no two entries look alike and there are no placeholder images to ship.
There are no webfonts either. Everything uses the fonts already on your machine, which means nothing breaks when a network blocks font CDNs.
Compatibility flags
- Stable — tested, loads reliably on a typical restricted network.
- Patchy — works, but some filters interfere.
- Blocked often — expect trouble.
- Untested — no data yet.
Separately, each title says whether it plays in page or opens a new tab. Some games refuse to be framed; those get a one-click launch instead, and the player tells you which you're getting before you click.
Flags describe reliability, not permission. Follow whatever rules apply where you are.
Accounts (optional)
You never need one. Browsing, playing, pinning and playtime all work signed out, exactly as they always have. An account adds three things: your save follows you to other devices, you get friends, and you get direct messages.
- Signing up adopts whatever you already played here — nothing is lost.
- Two devices merge rather than overwrite: the higher playtime and the union of your pins always win, so signing in somewhere new can't wipe your progress.
- No email, no verification, no password reset — so don't forget your password.
- The first account created on a fresh install becomes the administrator.
- Passwords are stored as salted scrypt hashes, never in plain text.
- If the hub's server isn't running, account features simply disappear and the arcade carries on.
What's stored
There are no analytics and no ad trackers. Pins, history, ratings, playtime and your skin
choice sit in this browser's localStorage under the ach: prefix.
Signed in, everything except your skin also syncs to the hub's own server — nowhere else.
- Signed out: clearing site data, private browsing, or a different device resets it all.
- Signed in: one session cookie,
HttpOnlyandSameSite=Lax. It identifies your session and nothing more. - Settings → Export writes a JSON backup; Import restores it elsewhere.
- Account settings → Delete erases your profile, friendships, messages and synced save for good.
- Individual games load their own assets; what they do is up to them.
Friends and messages
- Add people by @username or by friend code — a six-character handle like
ABC-123you can share without giving out your username. One box takes either. Rotate it any time from the Friends page and the old one stops working. - Friend requests go one way until accepted; either side can remove the link later.
- Groups hold up to 25 people and are friends-only, so they can't be used to get around someone's message settings. The owner can rename, add and remove; anyone can leave, and ownership passes on rather than leaving a group headless.
- The chat dock sits bottom-right on every page, including while a game is running, so you can reply without leaving what you're doing.
- Blocking hides you from that person's search and profile view, and stops their messages.
- You can restrict messages to friends only in Settings → Privacy.
- Activity on your profile is opt-out and only ever visible to friends.
- Report anything from a profile or conversation; it lands in the moderation queue.
- Messages are stored in plain text on the hub's server. Moderators can remove them. Treat it as a school notice board, not a private channel.
Images in chat
You can share a screenshot, take a photo with your camera, or attach an image from disk. Everything is downscaled and re-encoded in your browser before it is sent, so a huge PNG becomes a reasonable JPEG rather than being rejected.
- Images are private to the conversation they were posted in — the server checks membership on every fetch, so a link alone is not enough.
- Deleting the message destroys the image with it.
- Screenshots go through the browser's own screen-share picker. A page cannot silently screenshot itself, and a game running in a frame from another site can never be read into a canvas — so you choose what to share, every time.
- The camera needs HTTPS and your explicit permission. Nothing is captured until you press the shutter, and the stream is stopped the moment the dialog closes.
Calling
You can ring a friend for voice, video or a shared screen from their profile, or from the chat dock while a game is running. The audio and video go straight from one browser to the other; the hub's server only passes the few setup messages needed to introduce you. Nothing is recorded and nothing is relayed through us.
- One-to-one calls are friends-only. Calling a group rings everyone in that conversation — being in the group is the permission there, so it works even between people who haven't added each other.
- Calls hold four people. Everyone connects to everyone else, so each extra person costs all the others another upload stream. Past four you'd need a relay server that forwards video, and that is the part that costs real money — so the limit is honest rather than arbitrary.
- Screen sharing goes through the browser's own picker, exactly like screenshots. You choose the window or tab, every time, and the browser shows its own "stop sharing" control that always works.
- A call belongs to the page that started it, so navigating away ends it. Playing a game doesn't — the game runs in a frame inside the same page, which is the case that matters.
- Some networks block direct browser-to-browser connections outright. Where that happens the tile says it can't connect rather than sitting there silently.
Support
Feedback is a one-way note — send it and someone reads it. A support ticket is a conversation that stays open until it's sorted, which is what you want for a lost save or a problem with another person. Tickets need an account so there's somewhere to reply to; you can have five open at once, and you can close your own any time.
Keys
- / jump to the search box
- K open the finder
- R random title
- P play / reload, on a game page
- F pin the title you're on
- ? settings
- Esc close anything open
- Ctrl+L the admin console, for staff — a modifier combo so it still reaches you mid-game, where the single-key shortcuts deliberately stand down. Admins can change the letter in Settings; the sidebar always works too.
Adding a title
The owner can add one from the browser: Admin → Games takes a title, a host and a path, and it is live for everyone on their next page load. That layers over the shipped list rather than editing it, so it also lets you repoint a game whose host moved, or hide one, without touching a file. The same screen puts a hidden title back.
For a bulk change, or anything you want in version control, append an object to
data/games.json and regenerate the derived files:
{
"id": "my-game",
"title": "My Game",
"category": "arcade",
"description": "One line about it.",
"gradient": "linear-gradient(135deg, #ff5c33, #7c2d12)",
"source": "/games/my-game/index.html",
"direct": "/games/my-game/index.html",
"platform": "local",
"embed": "allowed",
"schoolRisk": "low"
}
embed is "allowed"/true for in-page play or
false to force a new tab. preferDirect: true skips the player even
when framing works. schoolRisk is low | medium |
high. gradient seeds the generated cover. Unknown categories fall
back to Other. Where the game files are served from is set once in
js/config.js via gameBase.
Disclaimer
Hobby build. Games are third-party works owned by their creators and are provided as-is; how and where you use this is on you.