Notification dot for new versions (+ mobile dot improvements) (#3265)

## Description:

- **News notification dot (desktop + mobile)**: Added a red pinging dot
on the "News" nav entry that appears when a new version is released. The
current app version is saved to localStorage (`newsSeenVersion`) on
first visit. On subsequent visits, if the version has changed, the dot
appears. Clicking "News" dismisses it by updating the stored version.

- **Mobile Store**: Replaced the static "NEW" text badge on the Store
nav item with a red pinging dot (matching the desktop navbar style). The
dot is conditionally shown based on cosmetics hash changes tracked in
localStorage, and dismissed when the user clicks Store.

- **Help dot on mobile**: Added the yellow help dot (already present on
desktop) to the mobile navbar for consistency, shown for users with
fewer than 10 games played.

### Screenshots:

<img width="1028" height="97" alt="Screenshot 2026-02-21 174029"
src="https://github.com/user-attachments/assets/1ed460dd-4e41-4287-bcb9-73f431e8a953"
/>

<img width="513" height="700" alt="Screenshot 2026-02-21 174333"
src="https://github.com/user-attachments/assets/c6b81296-d36b-424e-9637-e738acd8007a"
/>

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin
This commit is contained in:
FloPinguin
2026-02-21 21:15:36 -06:00
committed by GitHub
parent 6a30d2b38b
commit 2fd8757e66
4 changed files with 165 additions and 66 deletions
+27 -49
View File
@@ -1,15 +1,10 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getCosmeticsHash } from "../Cosmetics";
import { getGamesPlayed } from "../Utils";
const HELP_SEEN_KEY = "helpSeen";
const STORE_SEEN_HASH_KEY = "storeSeenHash";
import { customElement } from "lit/decorators.js";
import { NavNotificationsController } from "./NavNotificationsController";
@customElement("desktop-nav-bar")
export class DesktopNavBar extends LitElement {
@state() private _helpSeen = localStorage.getItem(HELP_SEEN_KEY) === "true";
@state() private _hasNewCosmetics = false;
private _notifications = new NavNotificationsController(this);
createRenderRoot() {
return this;
@@ -26,12 +21,6 @@ export class DesktopNavBar extends LitElement {
this._updateActiveState(current);
});
}
// Check if cosmetics have changed
getCosmeticsHash().then((hash: string | null) => {
const seenHash = localStorage.getItem(STORE_SEEN_HASH_KEY);
this._hasNewCosmetics = hash !== null && hash !== seenHash;
});
}
disconnectedCallback() {
@@ -54,30 +43,6 @@ export class DesktopNavBar extends LitElement {
});
}
private showHelpDot(): boolean {
// Only show one dot at a time to prevent
// overwhelming users.
return getGamesPlayed() < 10 && !this._helpSeen;
}
private showStoreDot(): boolean {
return this._hasNewCosmetics && !this.showHelpDot();
}
private onHelpClick = () => {
localStorage.setItem(HELP_SEEN_KEY, "true");
this._helpSeen = true;
};
private onStoreClick = () => {
this._hasNewCosmetics = false;
getCosmeticsHash().then((hash: string | null) => {
if (hash !== null) {
localStorage.setItem(STORE_SEEN_HASH_KEY, hash);
}
});
};
render() {
window.currentPageId ??= "page-play";
const currentPage = window.currentPageId;
@@ -142,13 +107,26 @@ export class DesktopNavBar extends LitElement {
data-i18n="main.play"
></button>
<!-- Desktop Navigation Menu Items -->
<button
class="nav-menu-item ${currentPage === "page-news"
? "active"
: ""} text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-news"
data-i18n="main.news"
></button>
<div class="relative">
<button
class="nav-menu-item ${currentPage === "page-news"
? "active"
: ""} text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-news"
data-i18n="main.news"
@click=${this._notifications.onNewsClick}
></button>
${this._notifications.showNewsDot()
? html`
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full animate-ping"
></span>
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full"
></span>
`
: ""}
</div>
<div class="relative no-crazygames">
<button
class="nav-menu-item ${currentPage === "page-item-store"
@@ -156,9 +134,9 @@ export class DesktopNavBar extends LitElement {
: ""} text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-item-store"
data-i18n="main.store"
@click=${this.onStoreClick}
@click=${this._notifications.onStoreClick}
></button>
${this.showStoreDot()
${this._notifications.showStoreDot()
? html`
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full animate-ping"
@@ -184,9 +162,9 @@ export class DesktopNavBar extends LitElement {
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-help"
data-i18n="main.help"
@click=${this.onHelpClick}
@click=${this._notifications.onHelpClick}
></button>
${this.showHelpDot()
${this._notifications.showHelpDot()
? html`
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-400 rounded-full animate-ping"