Added dark mode

This commit is contained in:
NewHappyRabbit
2025-02-20 00:48:45 +02:00
parent 5f26cf72c8
commit e9eb006cea
17 changed files with 488 additions and 74 deletions
+30
View File
@@ -0,0 +1,30 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { UserSettings } from "../core/game/UserSettings";
@customElement("dark-mode-button")
export class DarkModeButton extends LitElement {
private userSettings: UserSettings = new UserSettings();
@state() private darkMode: boolean = this.userSettings.darkMode();
createRenderRoot() {
return this;
}
toggleDarkMode() {
this.userSettings.toggleDarkMode();
this.darkMode = this.userSettings.darkMode();
}
render() {
return html`
<button
title="Toggle Dark Mode"
class="absolute top-0 right-0 md:top-[10px] md:right-[10px] border-none bg-none cursor-pointer text-2xl"
@click=${() => this.toggleDarkMode()}
>
${this.darkMode ? "☀️" : "🌙"}
</button>
`;
}
}
+34 -48
View File
@@ -1,6 +1,7 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import Countries from "./data/countries.json";
import { UserSettings } from "../core/game/UserSettings";
const flagKey: string = "flag";
@@ -9,41 +10,13 @@ export class FlagInput extends LitElement {
@state() private flag: string = "";
@state() private search: string = "";
@state() private showModal: boolean = false;
private userSettings: UserSettings = new UserSettings();
static styles = css`
.hidden {
display: none;
.dark-mode .flag-button {
background: rgba(55, 65, 81, 0.7);
}
.flag-container {
display: flex;
position: relative;
}
.flag-button {
display: flex;
border: 1px solid rgba(0, 0, 0, 0.3);
background: rgba(255, 255, 255, 0.7);
cursor: pointer;
padding: 4px;
border-radius: 8px;
}
.selected-flag {
width: 48px;
}
// .flag-button {
// display: flex;
// border: 0px;
// background: none;
// cursor: pointer;
// padding: 0px;
// }
// .selected-flag {
// width: 50px;
// }
.flag-modal {
display: flex;
flex-direction: column;
@@ -70,6 +43,12 @@ export class FlagInput extends LitElement {
font-size: 1.3rem;
}
.dark-mode .flag-search {
background: rgb(55, 65, 81);
color: white;
border: 1px solid rgb(209, 213, 219);
}
.flag-dropdown {
overflow-y: auto;
overflow-x: hidden;
@@ -115,7 +94,11 @@ export class FlagInput extends LitElement {
}
private setFlag(flag: string) {
this.flag = flag;
if (flag == "") {
this.flag = "";
} else {
this.flag = flag;
}
this.showModal = false;
this.storeFlag(flag);
}
@@ -156,35 +139,38 @@ export class FlagInput extends LitElement {
this.dispatchFlagEvent();
}
createRenderRoot() {
return this;
}
render() {
return html`
<div class="flag-container">
<div class="flex relative">
<button
@click=${() => (this.showModal = !this.showModal)}
class="flag-button"
class="border p-[4px] rounded-lg flex cursor-pointer border-black/30 dark:border-gray-300/60 bg-white/70 dark:bg-[rgba(55,65,81,0.7)]"
title="Pick a flag!"
>
<img class="selected-flag" src="/flags/${this.flag || "xx"}.svg" />
<img class="size-[48px]" src="/flags/${this.flag || "xx"}.svg" />
</button>
${this.showModal
? html`
<div class="flag-modal ${this.showModal ? "" : "hidden"}">
<div
class="text-white flex flex-col gap-[0.5rem] absolute top-[60px] left-[0px] w-[780%] h-[500px] max-h-[50vh] bg-gray-900/80 backdrop-blur-md p-[10px] rounded-[8px] z-[3] ${this
.showModal
? ""
: "hidden"}"
>
<input
class="flag-search"
class="h-[2rem] border-none text-center border border-gray-300 rounded-xl shadow-sm text-2xl text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-300/60 dark:bg-gray-700 dark:text-white"
type="text"
placeholder="Search..."
@change=${this.handleSearch}
@keyup=${this.handleSearch}
/>
<div class="flag-dropdown">
<!-- Show each flag as button -->
<button
@click=${() => this.setFlag("")}
class="dropdown-item"
>
<img class="country-flag" src="/flags/xx.svg" />
<span class="country-name">None</span>
</button>
<div
class="flex flex-wrap justify-evenly gap-[1rem] overflow-y-auto overflow-x-hidden"
>
${Countries.filter(
(country) =>
country.name
@@ -197,10 +183,10 @@ export class FlagInput extends LitElement {
(country) => html`
<button
@click=${() => this.setFlag(country.code)}
class="dropdown-item"
class="text-center cursor-pointer border-none bg-none opacity-70 sm:w-[calc(33.3333%-15px) w-[calc(100%/4-15px)]"
>
<img
class="country-flag"
class="country-flag w-full h-auto"
src="/flags/${country.code}.svg"
/>
<span class="country-name">${country.name}</span>
+17 -4
View File
@@ -1,7 +1,5 @@
import { ClientGameRunner, joinLobby } from "./ClientGameRunner";
import backgroundImage from "../../resources/images/EuropeBackground.svg";
import favicon from "../../resources/images/Favicon.svg";
import "./PublicLobby";
import "./UsernameInput";
import "./styles.css";
@@ -16,15 +14,20 @@ import "./FlagInput";
import { FlagInput } from "./FlagInput";
import page from "page";
import { PublicLobby } from "./PublicLobby";
import { UserSettings } from "../core/game/UserSettings";
import "./DarkModeButton";
import { DarkModeButton } from "./DarkModeButton";
class Client {
private gameStop: () => void;
private usernameInput: UsernameInput | null = null;
private flagInput: FlagInput | null = null;
private darkModeButton: DarkModeButton | null = null;
private joinModal: JoinPrivateLobbyModal;
private publicLobby: PublicLobby;
private userSettings: UserSettings = new UserSettings();
constructor() {}
@@ -34,6 +37,13 @@ class Client {
consolex.warn("Flag input element not found");
}
this.darkModeButton = document.querySelector(
"dark-mode-button",
) as DarkModeButton;
if (!this.darkModeButton) {
consolex.warn("Dark mode button element not found");
}
this.usernameInput = document.querySelector(
"username-input",
) as UsernameInput;
@@ -92,6 +102,11 @@ class Client {
}
});
if (this.userSettings.darkMode()) {
document.body.classList.add("dark");
} else {
document.body.classList.remove("dark");
}
page("/join/:lobbyId", (ctx) => {
const lobbyId = ctx.params.lobbyId;
@@ -151,8 +166,6 @@ document.addEventListener("DOMContentLoaded", () => {
new Client().initialize();
});
document.body.style.backgroundImage = `url(${backgroundImage})`;
function setFavicon(): void {
const link = document.createElement("link");
link.type = "image/x-icon";
+4 -2
View File
@@ -5,6 +5,7 @@ import {
MAX_USERNAME_LENGTH,
validateUsername,
} from "../core/validations/username";
import { UserSettings } from "../core/game/UserSettings";
const usernameKey: string = "username";
@@ -13,6 +14,7 @@ export class UsernameInput extends LitElement {
@state() private username: string = "";
@property({ type: String }) validationError: string = "";
private _isValid: boolean = true;
private userSettings: UserSettings = new UserSettings();
// Remove static styles since we're using Tailwind
@@ -40,11 +42,11 @@ export class UsernameInput extends LitElement {
@change=${this.handleChange}
placeholder="Enter your username"
maxlength="${MAX_USERNAME_LENGTH}"
class="w-full px-4 py-2 bg-white border border-gray-300 rounded-xl shadow-sm text-2xl text-gray-900 text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="w-full px-4 py-2 border border-gray-300 rounded-xl shadow-sm text-2xl text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-300/60 dark:bg-gray-700 dark:text-white"
/>
${this.validationError
? html`<div
class="mt-2 px-3 py-1 text-lg text-red-600 bg-white border border-red-600 rounded"
class="mt-2 px-3 py-1 text-lg border rounded bg-white text-red-600 border-red-600 dark:bg-gray-700 dark:text-red-300 dark:border-red-300"
>
${this.validationError}
</div>`
+4
View File
@@ -1,4 +1,8 @@
[
{
"code": "xx",
"name": "None"
},
{
"code": "af",
"continent": "Asia",
+1 -1
View File
@@ -118,7 +118,7 @@ export function createRenderer(
new TerritoryLayer(game, eventBus),
new StructureLayer(game, eventBus),
new UnitLayer(game, eventBus, clientID),
new NameLayer(game, game.config().theme(), transformHandler, clientID),
new NameLayer(game, transformHandler, clientID),
eventsDisplay,
new RadialMenu(
eventBus,
+3 -3
View File
@@ -114,7 +114,7 @@ export class Leaderboard extends LitElement implements Layer {
top: 10px;
left: 10px;
z-index: 9999;
background-color: rgba(30, 30, 30, 0.7);
background-color: rgb(31 41 55 / 0.7);
padding: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
border-radius: 10px;
@@ -136,7 +136,7 @@ export class Leaderboard extends LitElement implements Layer {
color: white;
}
th {
background-color: rgba(44, 44, 44, 0.5);
background-color: rgb(31 41 55 / 0.5);
color: white;
}
.myPlayer {
@@ -147,7 +147,7 @@ export class Leaderboard extends LitElement implements Layer {
font-size: 1em;
}
tr:nth-child(even) {
background-color: rgba(44, 44, 44, 0.5);
background-color: rgb(31 41 55 / 0.5);
}
tbody tr {
cursor: pointer;
+12 -5
View File
@@ -26,6 +26,7 @@ class RenderInfo {
public lastRenderCalc: number,
public location: Cell,
public fontSize: number,
public fontColor: string,
public element: HTMLElement,
) {}
}
@@ -45,13 +46,14 @@ export class NameLayer implements Layer {
private container: HTMLDivElement;
private myPlayer: PlayerView | null = null;
private firstPlace: PlayerView | null = null;
private theme: Theme;
constructor(
private game: GameView,
private theme: Theme,
private transformHandler: TransformHandler,
private clientID: ClientID,
) {
this.theme = game.config().theme();
this.traitorIconImage = new Image();
this.traitorIconImage.src = traitorIcon;
this.allianceIconImage = new Image();
@@ -96,6 +98,9 @@ export class NameLayer implements Layer {
this.firstPlace = sorted[0];
}
if (this.theme !== this.game.config().theme()) {
this.theme = this.game.config().theme();
}
for (const player of this.game.playerViews()) {
if (player.isAlive()) {
if (!this.seenPlayers.has(player)) {
@@ -106,6 +111,7 @@ export class NameLayer implements Layer {
0,
null,
0,
"",
this.createPlayerElement(player),
),
);
@@ -149,12 +155,10 @@ export class NameLayer implements Layer {
element.style.alignItems = "center";
element.style.gap = "0px";
const textColor = player.type() == PlayerType.Human ? "#000000" : "#4D4D4D";
const nameDiv = document.createElement("div");
nameDiv.classList.add("player-name");
nameDiv.innerHTML = player.name();
nameDiv.style.color = textColor;
nameDiv.style.color = this.theme.textColor(player.info());
nameDiv.style.fontFamily = this.theme.font();
nameDiv.style.whiteSpace = "nowrap";
nameDiv.style.overflow = "hidden";
@@ -178,7 +182,7 @@ export class NameLayer implements Layer {
const troopsDiv = document.createElement("div");
troopsDiv.classList.add("player-troops");
troopsDiv.textContent = renderTroops(player.troops());
troopsDiv.style.color = textColor;
troopsDiv.style.color = this.theme.textColor(player.info());
troopsDiv.style.fontFamily = this.theme.font();
troopsDiv.style.zIndex = "3";
troopsDiv.style.marginTop = "-5%";
@@ -219,6 +223,7 @@ export class NameLayer implements Layer {
// Calculate base size and scale
const baseSize = Math.max(1, Math.floor(render.player.nameLocation().size));
render.fontSize = Math.max(4, Math.floor(baseSize * 0.4));
render.fontColor = this.theme.textColor(render.player.info());
// Screen space calculations
const size = this.transformHandler.scale * baseSize;
@@ -243,7 +248,9 @@ export class NameLayer implements Layer {
".player-troops",
) as HTMLDivElement;
nameDiv.style.fontSize = `${render.fontSize}px`;
nameDiv.style.color = render.fontColor;
troopsDiv.style.fontSize = `${render.fontSize}px`;
troopsDiv.style.color = render.fontColor;
troopsDiv.textContent = renderTroops(render.player.troops());
// Handle icons
+2 -2
View File
@@ -156,11 +156,11 @@ export class OptionsMenu extends LitElement implements Layer {
title: "Toggle Emojis",
children: "🙂: " + (this.userSettings.emojis() ? "On" : "Off"),
})}
<!-- ${button({
${button({
onClick: this.onToggleDarkModeButtonClick,
title: "Dark Mode",
children: "🌙: " + (this.userSettings.darkMode() ? "On" : "Off"),
})} -->
})}
</div>
</div>
`;
+9 -3
View File
@@ -1,16 +1,22 @@
import { Layer } from "./Layer";
import { GameView } from "../../../core/game/GameView";
import { Theme } from "../../../core/configuration/Config";
export class TerrainLayer implements Layer {
private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D;
private imageData: ImageData;
private theme: Theme;
constructor(private game: GameView) {}
shouldTransform(): boolean {
return true;
}
tick() {}
tick() {
if (this.game.config().theme() !== this.theme) {
this.redraw();
}
}
init() {
console.log("redrew terrain layer");
@@ -34,9 +40,9 @@ export class TerrainLayer implements Layer {
}
initImageData() {
const theme = this.game.config().theme();
this.theme = this.game.config().theme();
this.game.forEachTile((tile) => {
let terrainColor = theme.terrainColor(this.game, tile);
let terrainColor = this.theme.terrainColor(this.game, tile);
// TODO: isn'te tileref and index the same?
const index = this.game.y(tile) * this.game.width() + this.game.x(tile);
const offset = index * 4;
+15 -4
View File
@@ -25,18 +25,25 @@
opacity: 0;
}
body::before {
.bg-image {
content: "";
background-image: url("/images/EuropeBackground.svg");
background-position: center;
background-attachment: fixed;
background-size: cover;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
filter: blur(4px);
z-index: -1;
}
.dark .bg-image {
filter: blur(4px) brightness(0.7);
}
/* Critical styles to prevent layout shift */
.container {
opacity: 1;
@@ -148,13 +155,17 @@
</div>
</div>
<div class="bg-image"></div>
<dark-mode-button></dark-mode-button>
<!-- Main container with responsive padding -->
<div class="flex justify-center items-center flex-grow">
<div class="container px-4 sm:px-6 lg:px-8 py-4 sm:py-6 lg:py-8">
<div
class="relative flex gap-1 items-center max-w-sm sm:max-w-md lg:max-w-lg xl:max-w-xl mx-auto p-2 pb-4"
>
<flag-input></flag-input>
<flag-input class="w-[20%] md:w-[15%]"></flag-input>
<username-input class="w-full"></username-input>
</div>
+19
View File
@@ -1,3 +1,22 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Add custom scrollbar styles */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}