Idle Clicker

A themeable idle game inspired by Universal Paperclips. Make units by hand, sell them, automate production, buy upgrades. Runs in any browser (phone, tablet, desktop); progress is saved in cookies and can be exported/imported as a JSON or TXT file.

Zero runtime dependencies: a small Node server (http module only) plus a vanilla JS frontend (ES modules).

Run it

docker compose up -d --build     # http://localhost:8080  (GAME_PORT=9000 to change the port)

The container is a single image running as the unprivileged node user, with a read-only filesystem, no capabilities and no-new-privileges.

Without Docker: npm start (Node 22+). Tests: npm test.

How the project is organised

server/                  static file server + /api/themes and /api/locales (auto-discovery)
public/
  index.html             page skeleton (texts come from i18n via data-i18n attributes)
  css/                   base (tokens) · layout (responsive grid) · components
  data/                  GAME CONTENT, theme-independent: rules, generators, upgrades (ids + numbers)
  locales/               core UI texts, one file per language  (en, fr)
  themes/<id>/           ONE FOLDER PER THEME
    theme.json             order + (later) image paths
    theme.css              colours/fonts, as CSS variables
    locales/<lang>.json    the theme's vocabulary: names of resources, generators, upgrades
  js/
    engine/              game rules, pure JS, no DOM (also used by the tests)
    services/            cookies · storage · save import/export · i18n · formatting · data loading
    ui/                  DOM helpers · main view · settings dialog · toasts
    main.js              wires everything together
tests/                   node:test suites (engine, save import/export, server, content completeness)

The key idea: mechanics are shared, themes are skins. public/data/ defines generators and upgrades by id (gen1, click_1, …) with their numbers; a theme only gives those ids a name, a colour palette and, later, images. Rebalancing the game is a change in public/data/, not in each theme.

Add a theme

  1. Copy public/themes/paperclip/ to public/themes/<your-id>/ (lowercase letters, digits, dashes).
  2. Edit theme.css (palette, font, radius), theme.json (order), and the texts in locales/*.json.
  3. Reload. The server discovers the folder; no code change and no rebuild list. (With Docker, rebuild the image or mount the folder.)

npm test fails if a theme misses a name for any resource, generator or upgrade, or lacks a language the game supports.

Add a language

  1. Copy public/locales/en.json to public/locales/<code>.json and translate it (set meta.name to the language's own name).
  2. Add public/themes/<id>/locales/<code>.json to every theme.

Missing keys fall back to English, then to the raw key. Strings accept {param} and {@other.key} (embeds another entry, which is how core texts use the theme's vocabulary, e.g. {@theme.res.material}).

Images

Every icon is currently a grey square. To use real images, put files in the theme folder and list them in its theme.json:

{ "order": 1, "assets": { "gen.gen1": "img/autoclipper.svg", "res.units": "img/clip.svg", "upg.click_1": "img/bender.svg" } }

Keys: res.units|funds|material, gen.<id>, upg.<id>. Anything not listed keeps the placeholder.

Saves

  • Each theme has its own save slot, stored in cookies (ic_save_<theme>_*, chunked and base64-encoded because a cookie holds ~4 KB), plus one preferences cookie (theme + language).
  • Save & settings exports the current theme's save as .json or .txt (download or copy/paste) and imports either (file picker or pasted text). Imports are validated and sanitised: unknown ids are dropped, numbers clamped.
  • Progress continues while the game is closed: up to 8 h, at 50 % efficiency (public/data/rules.json).
  • Cookies are sent with every request to the server. A save is under 2 KB and there is one per theme, so this stays far below header limits, but it is the reason to keep saves compact.

Tuning the game

Everything numeric is in public/data/: rules.json (market, marketing, offline, autosave), generators.json, upgrades.json. Customers want baseDemand × demandMultiplier units/s at the fair price and demand collapses above it, so: raise the price when supply is short, buy marketing/demand upgrades to sell more.

S
Description
No description provided
Readme Unlicense
418 KiB
Languages
JavaScript 87.4%
CSS 8.7%
HTML 3.6%
Dockerfile 0.3%