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. There is one game: themes (paperclips, witch potions, alien minerals, leaf-piling insects, computers) are only skins, so switching theme keeps every amount.

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 (ids + numbers): rules, generators, upgrades,
                         investments, research, legacy perks, achievements, tabs
  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): engine (production, market,
                         time) + one module per system (investments, gamble, research, legacy, contracts, achievements)
    services/            cookies · storage · save import/export · i18n · formatting · data loading
    ui/                  DOM helpers · main view · one view per section · tabs/menu · settings · toasts ·
                         fx (the quick purchase animations)
    main.js              wires everything together
scripts/                 tools: campaign bot (pacing), icon list generator
tests/                   node:test suites (engine, systems, balance and pacing, 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.

The game beyond the factory

The production screen is the core loop. Further sections appear in the menu as you progress (a tab bar on tablets and desktops, a burger menu on phones; the three resources stay visible in a bar at the top):

Section Unlocks at (lifetime production) What it is
Contracts 20 K Sell a target amount in time for funds, research, chips or a production boost; opportunities (surge, rush, windfall) to seize within seconds; rare incidents.
Investments 100 K 9 societies in 3 risk tiers (low ±5 %, medium ±15 %, high ±50 % every 60 s), dividends, a 0.5 % fee. Themed names.
Research 500 K Labs (bought with funds) produce research points slowly; a tree of 15 permanent bonuses, including a late one that reveals the balanced price.
Gamble (themed: Quantum Computer, Crystal Ball…) 5 M Chips are costly and each one costs more. Stake them on a cycle: win, jackpot or crash. Reliability upgrades tilt the odds; chips boost demand with diminishing returns.
Legacy 500 M Restart the run for permanent points, earned from everything you have ever produced with diminishing returns (the first point at 10 trillion, each further one costs more). A modest passive bonus plus repeatable perks. Research, achievements and statistics survive.
Achievements always 23 milestones, each with a small permanent bonus.

Everything is data-driven: tabs.json (unlocks), investments.json, research.json, legacy.json, achievements.json and the gamble, research, legacy, contracts and events blocks of rules.json. All bonuses share one vocabulary of effects (engine/effects.js). Texts for research, perks and achievements are in the core locale files; only the theme vocabulary (the gamble's name, the chip/research/legacy currencies, the nine societies) is per 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. See Icons to produce at the end of this file for the exhaustive list, the format and how to plug images in.

Saves

  • One shared game for all themes, stored in cookies (ic_game_*, chunked and base64-encoded because a cookie holds ~4 KB), plus one preferences cookie (theme + language). Saves from the first version (one per theme) are merged automatically: the most advanced one is kept.
  • Save & settings exports the save as .json or .txt (download or copy/paste) and imports either (file picker or pasted text). The file does not depend on the theme. 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 late-game save is about 4 KB (base64), 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, raw material, marketing, offline, autosave), generators.json, upgrades.json, endless.json.

  • Endless upgrades: the 36 one-time upgrades are milestones. When a family is complete (manual work, each automation tier, global output, demand, sale value, material efficiency, discounts, stack size), it continues as a repeatable line in endless.json: each level multiplies the effect once more (effect.value) and costs baseCost × costGrowth ^ level, up to maxLevel (10 000, which cannot be reached: costs overflow long before). Costs must climb much faster than effects. Income is the product of several lines, so if the ratios ln(effect) / ln(costGrowth) of the income-driving lines add up to more than 1, the economy explodes (an early tuning reached 1e207 units/s in two hours). A test guards this sum. Endless levels belong to the run: a legacy restart resets them.

  • Material lots can be bought in part: if the whole lot costs more than your funds, you get as much as the funds buy, at the same unit price, so a player can never be locked out of material.

  • Selling: demand is a smooth curve, baseDemand × demandMultiplier × (referencePrice / price) ^ elasticity units/s: lower the price and more people buy, with no ceiling. The best price is where demand just matches your output (selling for less gains no volume, selling for more leaves stock unsold). That point moves with every generator, upgrade and marketing level, and the game does not display it: the market hint only says whether you are selling too cheap, too dear, or about right.

  • Raw material (wire, herbs…): the price per unit starts high (startUnitPrice, about a third of the sale price) and falls towards floorUnitPrice as total production grows (economies of scale). A lot covers about 10 s of current output (in doublings), multiplied by the "stack" upgrades (batch_size_mult, a chain of five, ×180 in total). A lot costs in proportion to its size, so bigger stacks cost more per purchase while the price per unit still falls with scale. The market is erratic: the price is re-drawn every 10 s (updateEverySeconds) with wide random swings plus frequent shortages and gluts (spikeChance, spikeSize), and is shown to the cent, with ▲/▼ for its last move. The auto-buy upgrade is patient: it waits for a fair price unless stock is nearly gone.

Pacing: how long the game lasts

A bot plays the whole game (automation, upgrades, endless levels, labs, research, legacy restarts) so the pacing can be measured instead of guessed:

npm run campaign -- 120 10     # 120 simulated hours, one step = 10 s (takes a few seconds)

It prints the restarts, how fast each run starts, and when each long-term goal is reached. Knobs can be tried without editing the data files (see the top of scripts/campaign.mjs), e.g. THRESH=1e14 PERK_GROWTH=2.5 npm run campaign -- 200 10. The bot is a careful player that never gambles, invests or does contracts, so a person takes at least as long.

Where the game stands (bot hours): first restart about 10 h; 5 research nodes about 7 h, all 15 about 60 h; the first restarts are 10 to 20 h apart and get longer; no legacy perk is maxed after 200 h. tests/balance.test.js plays 40 hours on every npm test and fails if the game is finished too quickly (first restart before 6 h, all research or a perk maxed within 40 h) or runs away (a restarted run producing more than a million units per second after 2 minutes).

The levers, from the biggest: legacy.threshold, exponent and the passive bonus in rules.json (how fast restarts pay), perk costGrowth in legacy.json, research cost and rpPerLabPerSecond, endless baseCost / costGrowth.

A lesson kept in the code. The first legacy system multiplied output by 1.05 per point and gave points from a single run's production. A few thousand points gave ×1e100 and a run took seconds (a player reached 6e136 units/s two minutes into their fourth game). Now points come from lifetime production with diminishing returns, the passive bonus grows with the square root of the points and never compounds, and saves from older versions are repaired on load: runaway legacy points and perks are capped and the run restarts (research and achievements are kept). The player is told with a message that stays until clicked.

Icons to produce

Every icon the game shows is a grey square until a theme provides an image. The exhaustive work list is in ICONS.txt: a checklist per theme, with the file name, the key and what to draw for each icon. It is generated from the game data, so it is always complete. All 23 achievements share a single trophy icon.

  • Format: one square image per icon, SVG preferred (or PNG at 128×128 px or more), transparent background, readable at 28 px (top bar) and 48 px (lists). Files go in public/themes/<theme>/img/.

  • Plugging them in: list each file in the theme's theme.json; anything not listed keeps the grey square, so images can be added a few at a time:

    { "order": 1, "assets": { "res.units": "img/res-units.svg", "gen.gen1": "img/gen-gen1.svg" } }
    
  • npm run icons -- --write regenerates ICONS.txt, npm run icons -- --manifest prints a complete assets skeleton to start from. npm test fails if ICONS.txt is out of date or if the interface uses an icon key that is not listed.

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