Browsers have a little spot to save text for each website, called localStorage. It sticks around after the page is closed, so it's perfect for high scores, unlocked levels, and settings.
Click the game to add a coin. Then refresh the page. Your coins are still there.
localStorage.setItem(name, value) saves a value under a name, here "coin-demo". localStorage.getItem(name) gets it back, even after the page has been closed.
If nothing's been saved yet, getItem() gives back null, so ?? 0 starts you at 0 coins. Everything's saved as text, so Number() turns it back into a number.
To save a bunch of things at once, put them in an object and turn it into text with JSON.stringify(). JSON.parse() turns it back:
const saveName = "my-game-save";
// Load the save, or start fresh if there isn't one yet
let save = JSON.parse(localStorage.getItem(saveName)) ?? {level: 1, coins: 0, music: true};
function saveGame() {
localStorage.setItem(saveName, JSON.stringify(save));
}
// Later, like when a level is beaten:
save.level++;
saveGame();
To wipe the save, like for a "New Game" button, use localStorage.removeItem(saveName).
localStorage. On GitHub Pages, that's every project on your account, so something like "score" could clash with another game. "my-game-best" won't.localStorage from the browser's developer tools, so don't trust it for anything that matters, like an online leaderboard. Someone will give themselves a billion coins.