Play Minesweeper Online for Free https://mnswpr.com
Find a file
Ayo 4fa88fb431 feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard)
Extract the leaderboard into a generic, backend-agnostic package and add
rolling Today/Week/Month/All-Time windows, a web component, and local-first
development against the Firestore emulator.

Package (leaderboard/):
- LeaderBoardService: backend-agnostic core via a storage-adapter seam, with
  Firebase and Supabase adapters (Supabase client injected, no added dep)
- Rolling time windows (last 24h / 7d / 30d) with hover tooltips; top-N by score
- <cozy-leaderboard> web component built on web-component-base: compose the UI
  in HTML, configure the backend once in JS
- Every user-facing string is configurable (labels, tooltips, empty/loading/
  error messages, anonymous name) so i18n lives in the app; README + CONFIGURATION

App (mnswpr.com):
- Compose the board declaratively in index.html via <cozy-leaderboard>
- Nickname + randomized greeting bar; score submission through the element
- Legends: the current all-time leaders frozen into a static /legends page
- Firebase config and leaderboard namespace via Vite env vars; emulator-first
  local dev (VITE_FIRESTORE_EMULATOR)

Firebase schema-as-code:
- firebase.json, .firebaserc, firestore.rules (public reads, create-only scores,
  no client updates/deletes, namespace-generalized), empty indexes (rolling
  windows need none)
- prod (mw-*) vs dev/test (mw-test-*) separation by collection namespace
- emulator config, seed script, and docs (firebase-leaderboards.md,
  leaderboard-env-migration.md, AYO.md)

Utils/tests: UTC date-bucket helper (retained as metadata) with Vitest coverage.

Reviewed-on: https://git.ayo.run/ayo/mnswpr/pulls/1
Co-authored-by: Ayo <ayo@ayco.io>
Co-committed-by: Ayo <ayo@ayco.io>
2026-07-03 13:42:03 +02:00
.github/workflows chore: update release action to not publish to npm 2026-04-03 03:18:06 +02:00
.husky chore: use eslint for linting & formatting 2026-04-02 14:41:52 +02:00
.vscode chore: use eslint for linting & formatting 2026-04-02 14:41:52 +02:00
app feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
docs feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
leaderboard feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
lib chore: release v0.4.36 2026-07-03 10:12:31 +02:00
scripts feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
test feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
utils feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
.firebaserc feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
.gitignore feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
.nvmrc chore: update release gh action 2026-04-03 03:03:08 +02:00
AGENTS.md test: add tests 2026-07-02 20:31:29 +02:00
AYO.md feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
eslint.config.js feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
firebase.json feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
firestore.indexes.json feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
firestore.rules feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
LICENSE Create LICENSE 2023-03-10 09:42:58 +01:00
package.json feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
pnpm-lock.yaml feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
pnpm-workspace.yaml feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
README.md feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard) 2026-07-03 13:42:03 +02:00
screenshot.png chore: add sreenshot 2026-07-03 10:29:17 +02:00
vitest.config.js test: add tests 2026-07-02 20:31:29 +02:00

Play Minesweeper Online for Free

Netlify Status

Play it here: mnswpr.com. This is the classic game Minesweeper built with vanilla web technologies (i.e., no framework dependency).

mnswpr gameplay

How to Play

The goal is to reveal every safe cell without detonating a mine. Your first click is always safe.

  • Left click — reveal a cell
  • Right click — flag / unflag a suspected mine
  • Left + right click together (chording) — reveal the neighbors of a satisfied number
  • Touch — tap to reveal, long-press to flag

Ways to Use

The web is a wonderful, free, and open platform to create and distribute value. You can use mnswpr in different ways:

  • as a deployed web app
  • as a library with npm i @ayo-run/mnswpr
  • as a web component (coming soon).

Using it as a library takes only a few lines — mount it onto any element by id:

import '@ayo-run/mnswpr/mnswpr.css'
import mnswpr from '@ayo-run/mnswpr'

const game = new mnswpr('app')
game.initialize()

Tooling

The project has gone through years of existence. It started from 2019 when tooling was massively different. I have modernized it since and have witnessed how much easier and faster it is to build now - even without web frameworks or LLMs!

As of now the tooling I use are:

  • Vite for bundling and development server
  • Eslint for JS linting & CSS linting
  • ESLint Stylistic for JS formatting
  • Husky for git hooks
  • PNPM for dependency & workspace management
  • and a bunch of automation using scripts and Continuous Integration actions

Because a big part of this project's purpose is to track how the software development industry evolves — and because it has come a long way in modernizing along the way — I now also use it as a playground for coding agents. It's a small, framework-free, well-scoped codebase, which makes it a great sandbox to see how AI agents read, reason about, and change real code. To help them get their bearings quickly, the repo ships an AGENTS.md describing the architecture and conventions.

Development

Technology Stack: HTML, JS, and CSS; Google Firebase for leader board store; Netlify for hosting

To start development, you need node. I highly recommend pnpm to be used as well. Once you know you have this, you can do the following:

  1. Install dependencies: pnpm i
  2. Start the dev server: pnpm run dev

The rest of the everyday commands:

pnpm test        # run the Vitest suite
pnpm lint        # ESLint (JS + CSS)
pnpm lint:fix    # ESLint with autofix
pnpm build       # build the website
pnpm build:lib   # build the publishable library

Leaderboard (local Firestore emulator)

The leader board is backed by Google Firestore. For local development the app talks to the Firestore emulator by default — fully local, no cloud, no deploy. The flag VITE_FIRESTORE_EMULATOR=1 is already set in app/.env.development.

You need a JDK 21+ installed (the emulator runs on Java; firebase-tools itself is fetched on demand via npx). Then, in two terminals:

pnpm emulators      # terminal 1 — start the Firestore emulator on :8080 (+ UI)
pnpm seed:emulator  # terminal 2, once — fill it with sample scores
pnpm dev            # terminal 2 — run the app against the emulator

If the emulator isn't running, the board simply shows "unavailable" (a refused connection). To skip the emulator — for quick UI-only work, or if you don't have a JDK — set VITE_FIRESTORE_EMULATOR= (empty) in a local, gitignored app/.env.local; the app then uses the cloud mw-test namespace instead.

See docs/firebase-leaderboards.md for the full data model, security rules, environments, and deployment.

Contributing

Contributions are welcome! See AGENTS.md for the architecture, conventions, and release workflow before opening a pull request.

You just want to play?

👉 The live site is here: mnswpr.com

Background

One day, while working in my home office, I heard loud and fast mouse clicks coming from our bedroom. It's my wife, playing her favorite game (Minesweeper) on a crappy website full of advertisements.

I can't allow this, it's a security issue. 🤣

But it is also an opportunity.

I wanted to give her the same game, with a similar leader board she can dominate. And this is also a chance for me to dig deeper into vanilla JS.

Can I make a page with complex interactions (more on this later) without any library dependency?

What I have learned:

  1. JS is awesome
  2. We don't always need JS frameworks (or TS)
  3. Even subtle UI changes can improve user gameplay experience
  4. There's more ways to break your app than you are initially aware of
  5. Competition motivates users to use your app more
  6. Hash in bundled filenames helps avoid issues with browser caching (when shipping versions fast)

License

BSD-2-Clause


Just keep building.
A project by Ayo