Homepage update & add 3 public lobbies (#3191)

## Description:

Update UI 
check https://homepageupdate.openfront.dev/ 

Improved mobile UI (now fills whole screen for all modals) e.g.:
<img width="432" height="852" alt="image"
src="https://github.com/user-attachments/assets/56de40af-4137-4c57-96b7-3910c9a665b8"
/>

Converted PublicLobby to be "GameModeSelector" to get a nicer 4x4 grid
div, where <GameModeSelector> now handles all the username validation
now (removed redundant code from modals such as matchmaking) also fixed
a bug where someone could have "[XX] X" as thier username (when the
minimum should be 3 chars for their name)

Now visually displays the 3 lobbies ffa/team/special (which is a
continuation from the work done in: #3196 )
<img width="818" height="563" alt="image"
src="https://github.com/user-attachments/assets/a15cd31b-6061-4fb8-83ee-ffde6225cfa7"
/>

updated the background:
<img width="1919" height="807" alt="image"
src="https://github.com/user-attachments/assets/358a7434-51b8-4540-baf2-d1be05053c44"
/>



slightly updated the glassy-look to be less glassy:
<img width="825" height="729" alt="image"
src="https://github.com/user-attachments/assets/1801871b-bbf8-43db-ac53-489337ae80a5"
/>



## 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:

w.o.n
This commit is contained in:
Ryan
2026-02-18 23:11:01 -06:00
committed by GitHub
parent 2a7db43db3
commit 70f2abb181
48 changed files with 1016 additions and 725 deletions
+33 -93
View File
@@ -1,7 +1,13 @@
import version from "resources/version.txt?raw";
import { UserMeResponse } from "../core/ApiSchemas";
import { EventBus } from "../core/EventBus";
import { GAME_ID_REGEX, GameRecord, GameStartInfo } from "../core/Schemas";
import {
GAME_ID_REGEX,
GameInfo,
GameRecord,
GameStartInfo,
PublicGameInfo,
} from "../core/Schemas";
import { GameEnv } from "../core/configuration/Config";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { GameType } from "../core/game/Game";
@@ -17,6 +23,8 @@ import { FlagInput } from "./FlagInput";
import "./FlagInputModal";
import { FlagInputModal } from "./FlagInputModal";
import { GameInfoModal } from "./GameInfoModal";
import "./GameModeSelector";
import { GameModeSelector } from "./GameModeSelector";
import { GameStartingModal } from "./GameStartingModal";
import "./GoogleAdElement";
import { GutterAds } from "./GutterAds";
@@ -32,9 +40,7 @@ import { MatchmakingModal } from "./Matchmaking";
import { initNavigation } from "./Navigation";
import "./NewsModal";
import "./PatternInput";
import "./PublicLobby";
import { PublicLobby, ShowPublicLobbyModalEvent } from "./PublicLobby";
import { SinglePlayerModal } from "./SinglePlayerModal";
import "./SinglePlayerModal";
import { TerritoryPatternsModal } from "./TerritoryPatternsModal";
import { TokenLoginModal } from "./TokenLoginModal";
import {
@@ -55,6 +61,7 @@ import "./components/Footer";
import "./components/MainLayout";
import "./components/MobileNavBar";
import "./components/PlayPage";
import "./components/RankedModal";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import "./styles.css";
@@ -203,15 +210,16 @@ declare global {
BOLT_AD_CLICKED: string;
SHOW_HIDDEN_CONTAINER: string;
};
currentPageId?: string;
showPage?: (pageId: string) => void;
}
// Extend the global interfaces to include your custom events
interface DocumentEventMap {
"join-lobby": CustomEvent<JoinLobbyEvent>;
"show-public-lobby-modal": CustomEvent<ShowPublicLobbyModalEvent>;
"kick-player": CustomEvent;
"join-changed": CustomEvent;
"open-matchmaking": CustomEvent<undefined>;
}
}
@@ -223,6 +231,7 @@ export interface JoinLobbyEvent {
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
source?: "public" | "private" | "host" | "matchmaking" | "singleplayer";
publicLobbyInfo?: GameInfo | PublicGameInfo;
}
class Client {
@@ -236,21 +245,18 @@ class Client {
private hostModal: HostPrivateLobbyModal;
private joinModal: JoinLobbyModal;
private publicLobby: PublicLobby;
private gameModeSelector: GameModeSelector;
private userSettings: UserSettings = new UserSettings();
private patternsModal: TerritoryPatternsModal;
private tokenLoginModal: TokenLoginModal;
private matchmakingModal: MatchmakingModal;
private gutterAds: GutterAds;
private turnstileTokenPromise: Promise<{
token: string;
createdAt: number;
}> | null = null;
constructor() {}
async initialize(): Promise<void> {
crazyGamesSDK.maybeInit();
// Prefetch turnstile token so it is available when
@@ -293,7 +299,9 @@ class Client {
console.warn("Username input element not found");
}
this.publicLobby = document.querySelector("public-lobby") as PublicLobby;
this.gameModeSelector = document.querySelector(
"game-mode-selector",
) as GameModeSelector;
window.addEventListener("beforeunload", async () => {
console.log("Browser is closing");
@@ -309,41 +317,16 @@ class Client {
this.gutterAds = gutterAds;
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
document.addEventListener(
"show-public-lobby-modal",
this.handleShowPublicLobbyModal.bind(this),
);
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
document.addEventListener(
"update-game-config",
this.handleUpdateGameConfig.bind(this),
);
const spModal = document.querySelector(
"single-player-modal",
) as SinglePlayerModal;
if (!spModal || !(spModal instanceof SinglePlayerModal)) {
console.warn("Singleplayer modal element not found");
}
const singlePlayer = document.getElementById("single-player");
if (singlePlayer === null) throw new Error("Missing single-player");
singlePlayer.addEventListener("click", () => {
if (this.usernameInput?.isValid()) {
window.showPage?.("page-single-player");
} else {
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message: this.usernameInput?.validationError,
color: "red",
duration: 3000,
},
}),
);
}
});
document.addEventListener(
"open-matchmaking",
this.handleOpenMatchmaking.bind(this),
);
const hlpModal = document.querySelector("help-modal") as HelpModal;
if (!hlpModal || !(hlpModal instanceof HelpModal)) {
@@ -512,23 +495,6 @@ class Client {
} else {
this.hostModal.eventBus = this.eventBus;
}
const hostLobbyButton = document.getElementById("host-lobby-button");
if (hostLobbyButton === null) throw new Error("Missing host-lobby-button");
hostLobbyButton.addEventListener("click", () => {
if (this.usernameInput?.isValid()) {
window.showPage?.("page-host-lobby");
} else {
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message: this.usernameInput?.validationError,
color: "red",
duration: 3000,
},
}),
);
}
});
this.joinModal = document.querySelector(
"join-lobby-modal",
@@ -538,26 +504,6 @@ class Client {
} else {
this.joinModal.eventBus = this.eventBus;
}
const joinPrivateLobbyButton = document.getElementById(
"join-private-lobby-button",
);
if (joinPrivateLobbyButton === null)
throw new Error("Missing join-private-lobby-button");
joinPrivateLobbyButton.addEventListener("click", () => {
if (this.usernameInput?.isValid()) {
window.showPage?.("page-join-lobby");
} else {
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message: this.usernameInput?.validationError,
color: "red",
duration: 3000,
},
}),
);
}
});
if (this.userSettings.darkMode()) {
document.documentElement.classList.add("dark");
@@ -773,8 +719,7 @@ class Client {
(searchParams.toString() ? "?" + searchParams.toString() : "") +
window.location.hash;
history.replaceState(null, "", newUrl);
// Wait for matchmaking button to be defined, then trigger its click handler
// This goes through username validation instead of bypassing it
// Wait for matchmaking button to be defined, then trigger its click handler.
customElements.whenDefined("matchmaking-button").then(() => {
const matchmakingButton = document.querySelector(
"matchmaking-button button",
@@ -798,12 +743,14 @@ class Client {
this.gameStop(true);
document.body.classList.remove("in-game");
}
if (lobby.source === "public") {
this.joinModal?.open(lobby.gameID, lobby.publicLobbyInfo);
}
const config = await getServerConfigFromClient();
// Only update URL immediately for private lobbies, not public ones
if (lobby.source !== "public") {
this.updateJoinUrlForShare(lobby.gameID, config);
}
this.gameStop = joinLobby(
this.eventBus,
{
@@ -856,7 +803,7 @@ class Client {
modal.isModalOpen = false;
}
});
this.publicLobby.stop();
this.gameModeSelector.stop();
document.querySelectorAll(".ad").forEach((ad) => {
(ad as HTMLElement).style.display = "none";
});
@@ -872,8 +819,8 @@ class Client {
}
},
() => {
this.joinModal.close();
this.publicLobby.stop();
this.joinModal?.closeWithoutLeaving();
this.gameModeSelector.stop();
incrementGamesPlayed();
document.querySelectorAll(".ad").forEach((ad) => {
@@ -915,17 +862,6 @@ class Client {
}
}
private handleShowPublicLobbyModal(
event: CustomEvent<ShowPublicLobbyModalEvent>,
) {
const { lobby } = event.detail;
console.log(`Opening JoinLobbyModal for public lobby ${lobby.gameID}`);
// Open the join lobby modal page and pass the lobby info
window.showPage?.("page-join-lobby");
this.joinModal?.open(lobby.gameID, true);
}
private async handleLeaveLobby(/* event: CustomEvent */) {
if (this.gameStop === null) {
return;
@@ -946,6 +882,10 @@ class Client {
crazyGamesSDK.gameplayStop();
}
private handleOpenMatchmaking(_event: CustomEvent<undefined>) {
this.matchmakingModal?.open();
}
private handleKickPlayer(event: CustomEvent) {
const { target } = event.detail;