Christmas Themed Homepage (#2608)

## Description:
Adds the following:
- Snowflake animation with snowflakes falling from the top to the bottom
of the screen
- Changed homepage color theme from blue and white to Green and Red.
Also changed the openfront logo to a red, green, and white gradient.
- Added a santa hat on the announcements button

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

<img width="1616" height="836" alt="Screenshot 2025-12-12 at 3 01 17 PM"
src="https://github.com/user-attachments/assets/82e29db3-3bc0-4392-b5bf-dd57c15784a3"
/>

<img width="1616" height="836" alt="Screenshot 2025-12-12 at 2 58 54 PM"
src="https://github.com/user-attachments/assets/232da646-6923-4966-acba-5240074e7e3f"
/>


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

Restart

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Restart2008
2025-12-15 20:26:42 -08:00
committed by GitHub
parent 648ae1943f
commit f256f497ce
15 changed files with 205 additions and 50 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

+1 -1
View File
@@ -379,7 +379,7 @@ export class AccountButton extends LitElement {
<div class="fixed top-4 right-4 z-[9998]">
<button
@click="${this.open}"
class="w-12 h-12 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-2xl hover:shadow-3xl transition-all duration-200 flex items-center justify-center text-xl focus:outline-none focus:ring-4 focus:ring-blue-500 focus:ring-offset-4"
class="w-12 h-12 bg-red-600 hover:bg-red-700 text-white rounded-full shadow-2xl hover:shadow-2xl transition-all duration-200 flex items-center justify-center text-xl focus:outline-none focus:ring-4 focus:ring-red-500 focus:ring-offset-4"
title="${buttonTitle}"
>
${this.renderIcon()}
+55 -1
View File
@@ -1,3 +1,4 @@
import Snowflake3Png from "../../resources/images/Snowflake.webp";
import version from "../../resources/version.txt";
import { UserMeResponse } from "../core/ApiSchemas";
import { EventBus } from "../core/EventBus";
@@ -41,6 +42,7 @@ import { UsernameInput } from "./UsernameInput";
import { incrementGamesPlayed, isInIframe } from "./Utils";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import "./snow.css";
import "./styles.css";
declare global {
@@ -540,6 +542,9 @@ class Client {
document.querySelectorAll(".ad").forEach((ad) => {
(ad as HTMLElement).style.display = "none";
});
// Hide snowflakes when joining lobby
document.documentElement.classList.add("in-game");
removeSnowflakes(); // Stop snowflakes when joining a game
// show when the game loads
const startingModal = document.querySelector(
@@ -577,6 +582,9 @@ class Client {
this.gameStop = null;
this.gutterAds.hide();
this.publicLobby.leaveLobby();
// Show snowflakes when leaving lobby (back to homepage)
document.documentElement.classList.remove("in-game");
enableSnowflakes(); // Restart snowflakes when leaving a game
}
private handleKickPlayer(event: CustomEvent) {
@@ -644,12 +652,58 @@ class Client {
}
}
}
function enableSnowflakes() {
// Respect user's motion preferences
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)",
).matches;
if (prefersReducedMotion) {
return;
}
const snowContainer = document.querySelector(".snow") as HTMLElement;
if (!snowContainer) {
console.warn("Snow container element not found");
return;
}
// Clear existing snowflakes if any
removeSnowflakes();
const isMobile = window.innerWidth <= 768;
const numberOfSnowflakes = isMobile ? 30 : 75; // Increased count
for (let i = 0; i < numberOfSnowflakes; i++) {
const snowflake = document.createElement("div");
snowflake.classList.add("snowflake");
snowflake.style.left = `${Math.random() * 100}vw`; // Random horizontal position
snowflake.style.animationDuration = `${Math.random() * 10 + 5}s`; // Random duration between 5-15s
snowflake.style.animationDelay = `${Math.random() * -10}s`; // Random delay
snowflake.style.opacity = `${Math.random() * 0.5 + 0.5}`; // Random opacity between 0.5-1
const size = Math.random() * 20 + 10; // Random size between 10-30px
snowflake.style.width = `${size}px`;
snowflake.style.height = `${size}px`;
snowflake.style.backgroundImage = `url(${Snowflake3Png})`;
snowContainer.appendChild(snowflake);
}
}
function removeSnowflakes() {
const snowContainer = document.querySelector(".snow") as HTMLElement;
if (snowContainer) {
snowContainer.replaceChildren();
}
}
// Initialize the client when the DOM is loaded
document.addEventListener("DOMContentLoaded", () => {
new Client().initialize();
});
// Initially enable snowflakes if not in-game
if (!document.documentElement.classList.contains("in-game")) {
enableSnowflakes();
}
});
async function getTurnstileToken(): Promise<{
token: string;
createdAt: number;
+1 -1
View File
@@ -173,7 +173,7 @@ export class MatchmakingButton extends LitElement {
<div class="z-[9999]">
<button
@click="${this.open}"
class="w-full h-16 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-2xl hover:shadow-3xl transition-all duration-200 flex items-center justify-center text-xl focus:outline-none focus:ring-4 focus:ring-blue-500 focus:ring-offset-4"
class="w-full h-16 bg-red-600 hover:bg-red-700 text-white rounded-full shadow-2xl hover:shadow-2xl transition-all duration-200 flex items-center justify-center text-xl focus:outline-none focus:ring-4 focus:ring-red-500 focus:ring-offset-4"
title="${translateText("matchmaking_modal.title")}"
>
Matchmaking
+5
View File
@@ -3,6 +3,7 @@ import { resolveMarkdown } from "lit-markdown";
import { customElement, property, query } from "lit/decorators.js";
import changelog from "../../resources/changelog.md";
import megaphone from "../../resources/images/Megaphone.svg";
import santaHatIcon from "../../resources/images/SantaHat.webp";
import version from "../../resources/version.txt";
import { translateText } from "../client/Utils";
import "./components/baseComponents/Button";
@@ -166,6 +167,10 @@ export class NewsButton extends LitElement {
src="${megaphone}"
alt=${translateText("news.title")}
/>
<div
class="santa-hat-overlay absolute bg-contain bg-no-repeat pointer-events-none"
style="background-image: url('${santaHatIcon}')"
></div>
</button>
</div>
<news-modal></news-modal>
+7 -12
View File
@@ -151,8 +151,8 @@ export class PublicLobby extends LitElement {
?disabled=${this.isButtonDebounced}
class="isolate grid h-40 grid-cols-[100%] grid-rows-[100%] place-content-stretch w-full overflow-hidden ${this
.isLobbyHighlighted
? "bg-gradient-to-r from-green-600 to-green-500"
: "bg-gradient-to-r from-blue-600 to-blue-500"} text-white font-medium rounded-xl transition-opacity duration-200 hover:opacity-90 ${this
? "bg-gradient-to-r from-emerald-600 to-emerald-500"
: "bg-gradient-to-r from-red-800 to-red-700"} text-white font-medium rounded-xl transition-opacity duration-200 hover:opacity-90 ${this
.isButtonDebounced
? "opacity-70 cursor-not-allowed"
: ""}"
@@ -174,18 +174,13 @@ export class PublicLobby extends LitElement {
<div class="text-lg md:text-2xl font-semibold">
${translateText("public_lobby.join")}
</div>
<div class="text-md font-medium text-blue-100">
<span
class="text-sm ${this.isLobbyHighlighted
? "text-green-600"
: "text-blue-600"} bg-white rounded-sm px-1"
<div class="text-md font-medium text-white-400">
<span class="text-sm text-red-800 bg-white rounded-sm px-1 mr-1"
>${modeLabel}</span
>
${teamDetailLabel
? html`<span
class="text-sm ${this.isLobbyHighlighted
? "text-green-600"
: "text-blue-600"} bg-white rounded-sm px-1 ml-1"
class="text-sm text-red-800 bg-white rounded-sm px-1 ml-1"
>${teamDetailLabel}</span
>`
: ""}
@@ -198,10 +193,10 @@ export class PublicLobby extends LitElement {
</div>
<div>
<div class="text-md font-medium text-blue-100">
<div class="text-md font-medium text-white-400">
${lobby.numClients} / ${lobby.gameConfig.maxPlayers}
</div>
<div class="text-md font-medium text-blue-100">${timeDisplay}</div>
<div class="text-md font-medium text-white-400">${timeDisplay}</div>
</div>
</div>
</button>
+3 -3
View File
@@ -80,7 +80,7 @@ export class StatsModal extends LitElement {
${translateText("stats_modal.loading")}
</p>
<div
class="w-6 h-6 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"
class="w-6 h-6 border-4 border-red-500 border-t-transparent rounded-full animate-spin"
></div>
</div>
`;
@@ -91,7 +91,7 @@ export class StatsModal extends LitElement {
<div class="flex flex-col items-center justify-center p-6 text-white">
<p class="mb-4 text-center">${this.error}</p>
<button
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium"
class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded text-sm font-medium"
@click=${() => this.loadLeaderboard()}
>
Retry
@@ -219,7 +219,7 @@ export class StatsButton extends LitElement {
<div class="fixed top-20 right-4 z-[9998]">
<button
@click="${this.open}"
class="w-12 h-12 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-2xl hover:shadow-3xl transition-all duration-200 flex items-center justify-center text-xl focus:outline-none focus:ring-4 focus:ring-blue-500 focus:ring-offset-4"
class="w-12 h-12 bg-red-600 hover:bg-red-700 text-white rounded-full shadow-2xl hover:shadow-2xl transition-all duration-200 flex items-center justify-center text-xl focus:outline-none focus:ring-4 focus:ring-red-500 focus:ring-offset-4"
title="${translateText("stats_modal.title")}"
>
<img src="/icons/stats.svg" alt="Stats" class="w-6 h-6" />
@@ -70,10 +70,10 @@ export class OModal extends LitElement {
backdrop-filter: blur(8px);
}
`;
public open() {
this.isModalOpen = true;
}
public close() {
this.isModalOpen = false;
this.dispatchEvent(
+46 -3
View File
@@ -73,6 +73,13 @@
position: absolute;
width: 100%;
height: 100vh;
background: linear-gradient(
to bottom,
rgba(139, 26, 26, 0.5),
rgba(255, 255, 255, 0.5),
rgba(39, 174, 96, 0.5)
);
}
.dark .bg-image {
@@ -83,6 +90,39 @@
.component-hideable:has(> .parent-hidden) {
display: none;
}
.snow {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1050;
}
.snowflake {
position: absolute;
background-size: contain;
background-repeat: no-repeat;
pointer-events: none;
animation-name: snowflake-fall;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-duration: 10s; /* Adjust duration to taste */
z-index: 9999; /* Increased z-index for visibility */
opacity: 0.8; /* Default opacity */
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.5)); /* Subtle glow */
}
@keyframes snowflake-fall {
from {
transform: translateY(-100%) rotate(0deg); /* Start off-screen */
}
to {
transform: translateY(105vh) rotate(360deg); /* Fall completely out of view */
}
}
</style>
<!-- Immediate execution to prevent FOUC -->
@@ -144,6 +184,7 @@
<body
class="h-full select-none font-sans min-h-screen bg-opacity-0 bg-cover bg-center bg-fixed transition-opacity duration-300 ease-in-out flex flex-col"
>
<div class="snow"></div>
<header class="l-header">
<div class="l-header__content">
<svg
@@ -162,8 +203,10 @@
x2="100%"
y2="0%"
>
<stop offset="0%" style="stop-color: #2563eb" />
<stop offset="100%" style="stop-color: #3b82f6" />
<stop offset="0%" style="stop-color: #e53935" />
<!-- Vibrant Red -->
<stop offset="100%" style="stop-color: #388e3c" />
<!-- Deep Green -->
</linearGradient>
</defs>
<g>
@@ -286,7 +329,7 @@
id="settings-button"
title="Settings"
class="fixed bottom-4 right-4 z-50 rounded-full p-2 shadow-lg transition-colors duration-300 flex items-center justify-center"
style="width: 80px; height: 80px; background-color: #0075ff"
style="width: 80px; height: 80px; background-color: var(--primaryColor)"
>
<img
src="../../resources/images/SettingIconWhite.svg"
+58
View File
@@ -0,0 +1,58 @@
.snow {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1050;
}
.snowflake {
position: absolute;
background-size: contain;
background-repeat: no-repeat;
pointer-events: none;
animation-name: snowflake-fall;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-duration: 10s; /* Adjust duration to taste */
z-index: 10;
opacity: 0.8; /* Default opacity */
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.5)); /* Subtle glow */
will-change: transform, opacity;
}
.santa-hat-overlay {
position: absolute;
width: 55px;
height: 55px;
background-image: url("/resources/images/SantaHat.webp"); /* Use direct path for CSS background */
background-size: contain;
background-repeat: no-repeat;
top: -15px;
right: -15px;
z-index: 1000;
}
@keyframes snowflake-fall {
from {
transform: translateY(-100%) rotate(0deg); /* Start off-screen */
}
to {
transform: translateY(105vh) rotate(360deg); /* Fall completely out of view */
}
}
html.in-game .santa-hat-overlay {
display: none;
}
html.modal-active .santa-hat-overlay {
display: none;
}
@media (max-width: 768px) {
.snowflake {
filter: none;
}
}
+15 -15
View File
@@ -50,7 +50,7 @@
padding: 15px 20px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
background-color: var(--primaryColor);
color: white;
border: none;
border-radius: 8px;
@@ -60,7 +60,7 @@
}
.start-game-button:not(:disabled):hover {
background-color: #0056b3;
background-color: var(--primaryColorHover);
}
.start-game-button:disabled {
@@ -107,8 +107,8 @@
}
.option-card.selected {
border-color: #4a9eff;
background: rgba(74, 158, 255, 0.1);
border-color: var(--primaryColor);
background: rgba(229, 57, 53, 0.1);
}
.option-card-title {
@@ -152,8 +152,8 @@ label.option-card:hover {
}
.option-card.selected .checkbox-icon {
border-color: #4a9eff;
background: #4a9eff;
border-color: var(--primaryColor);
background: var(--primaryColor);
}
.option-card.selected .checkbox-icon::after {
@@ -251,14 +251,14 @@ label.option-card:hover {
#bots-count::-moz-range-progress,
#private-lobby-bots-count::-moz-range-progress {
height: 8px;
background-color: #0075ff;
background-color: var(--primaryColor);
}
#bots-count::-moz-range-thumb,
#private-lobby-bots-count::-moz-range-thumb {
height: 16px;
width: 16px;
background: #0075ff;
background: var(--primaryColor);
border: none;
border-radius: 50%;
}
@@ -269,7 +269,7 @@ label.option-card:hover {
height: 8px;
background: linear-gradient(
to right,
#0075ff var(--progress, 0%),
var(--primaryColor) var(--progress, 0%),
white var(--progress, 0%)
);
}
@@ -279,7 +279,7 @@ label.option-card:hover {
-webkit-appearance: none;
height: 16px;
width: 16px;
background: #0075ff;
background: var(--primaryColor);
border: none;
border-radius: 50%;
margin-top: -4px;
@@ -291,8 +291,8 @@ label.option-card:hover {
}
.random-map.selected {
border: 2px solid #4a9eff;
background: rgba(74, 158, 255, 0.1);
border: 2px solid var(--primaryColor);
background: rgba(229, 57, 53, 0.1);
}
#helpModal table {
@@ -601,11 +601,11 @@ label.option-card:hover {
/* News Button Notification */
news-button .active button {
position: relative;
border-color: #2563eb !important;
border-color: var(--primaryColor) !important;
border-width: 2px !important;
box-shadow:
0 0 0 1px rgba(37, 99, 235, 0.5),
0 0 8px rgba(37, 99, 235, 0.4);
0 0 0 1px rgba(139, 26, 26, 0.5),
0 0 8px rgba(139, 26, 26, 0.4);
}
news-button .active button::after {
+1 -1
View File
@@ -32,7 +32,7 @@
.c-button--secondary {
background: var(--secondaryColor);
color: var(--fontColor);
color: var(--fontColorLight);
}
.c-button--secondary:hover,
+11 -11
View File
@@ -6,21 +6,21 @@
--boxBackgroundColor: #111827cc;
--fontColor: #202020;
--fontColorLight: #fff;
--primaryColor: #2563eb;
--primaryColorHover: #1d4ed8;
--primaryColor: #8b1a1a;
--primaryColorHover: #6b1515;
--primaryColorDisabled: linear-gradient(
to right,
rgb(74, 74, 74),
rgb(61, 61, 61)
rgba(139, 26, 26, 0.7),
rgba(107, 21, 21, 0.7)
);
--secondaryColor: #dbeafe;
--secondaryColorHover: #bfdbfe;
--secondaryColor: #27ae60;
--secondaryColorHover: #1e8449;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--primaryColorDark: #3b82f6;
--primaryColorHoverDark: #2563eb;
--primaryColorDisabledDark: #4b5563;
--secondaryColorDark: #374151;
--secondaryColorHoverDark: #4b5563;
--primaryColorDark: #8b1a1a;
--primaryColorHoverDark: #6b1515;
--primaryColorDisabledDark: rgba(139, 26, 26, 0.5);
--secondaryColorDark: #27ae60;
--secondaryColorHoverDark: #1e8449;
--fontColorDark: #f3f4f6;
}
+1 -1
View File
@@ -30,7 +30,7 @@
}
.l-header__highlightText {
color: #2563eb;
color: var(--primaryColor);
font-weight: 700;
filter: drop-shadow(1px 1px 0px rgb(255, 255, 255))
drop-shadow(-1px -1px 0px rgb(255, 255, 255))