Improve Ingame UI (#3212)

## Description:

- **Dynamic sidebar offset for top bars** - GameLeftSidebar,
GameRightSidebar, and PlayerInfoOverlay now shift down when SpawnTimer
and/or ImmunityTimer bars are visible (7px per bar). Implemented via
events.
- **Fixed text overflow** in HeadsUpMessage.ts (Random spawn message is
long)
- **Fixed inconsistent text sizing** in EventsDisplay 
- **Alliance icon horizontal** in PlayerInfoOverlay so the size of the
overlay doesn't change if there is an alliance
- **Nation relation coloring** - Nation player names are now colored
based on their relation
- **Background & Blur Unification**
- **Border Radius & Page Edge Gap Standardization**
- **EventsDisplay collapsed button:** Fixed badge hidden / inline-block
CSS conflict (conditional rendering), added gap-2 between text and badge
- **Right panel spacing:** Changed right container from sm:w-1/2 to
sm:flex-1 to fill remaining space
- **Leaderboard**: Rounded grid corners (rounded-lg overflow-hidden),
removed last-row border, added `willUpdate` for auto-refresh on
hide/show click, plus button styled to match toggle buttons
- Other little CSS fixes (margins etc)

Showcase:
(Note the red mexico name on betrayal)


https://github.com/user-attachments/assets/f0ed91de-3a07-4564-a209-3d7723edee55

Two progress bars at the top, mobile UI not cut off:


https://github.com/user-attachments/assets/83f1fd64-ceab-4a74-8d16-6e1eeea1709d

HeadsUpMessage text overflow fixed, SpawnTimer does not cut off the
PlayerInfoOverlay:

<img width="516" height="929" alt="Screenshot 2026-02-14 214410"
src="https://github.com/user-attachments/assets/74f0edea-8c01-4394-a3d0-a3245922e0da"
/>

Previous:

<img width="306" height="118" alt="Screenshot 2026-02-14 213705"
src="https://github.com/user-attachments/assets/a7c7e8f3-f0e8-4213-8a8f-4f3677e9fc98"
/>

Smaller event panel text:

<img width="594" height="975" alt="Screenshot 2026-02-14 215738"
src="https://github.com/user-attachments/assets/33e80570-9260-40b0-b810-c71eda4861fc"
/>

## 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-15 04:48:43 +01:00
committed by GitHub
parent 8e889fe857
commit 0c7da790f1
15 changed files with 239 additions and 97 deletions
+4 -2
View File
@@ -249,7 +249,7 @@
<control-panel></control-panel>
</div>
<div
class="order-1 sm:order-none w-full sm:w-1/2 min-[1200px]:w-auto min-[1200px]:fixed min-[1200px]:right-0 min-[1200px]:bottom-0 flex flex-col sm:items-end pointer-events-none"
class="order-1 sm:order-none w-full sm:flex-1 min-[1200px]:w-auto min-[1200px]:fixed min-[1200px]:right-0 min-[1200px]:bottom-0 flex flex-col sm:items-end pointer-events-none"
>
<chat-display></chat-display>
<events-display></events-display>
@@ -262,7 +262,9 @@
<win-modal></win-modal>
<game-starting-modal></game-starting-modal>
<unit-display></unit-display>
<div class="flex flex-col items-end fixed top-4 right-4 z-1000 gap-2">
<div
class="flex flex-col items-end fixed top-0 right-0 min-[1200px]:top-4 min-[1200px]:right-4 z-1000 gap-2"
>
<game-right-sidebar></game-right-sidebar>
<replay-panel></replay-panel>
</div>
+1
View File
@@ -714,6 +714,7 @@
"show_units": "Show Units"
},
"events_display": {
"events": "Events",
"retreating": "retreating",
"alliance_request_status": "{name} {status} your alliance request",
"alliance_accepted": "accepted",
+3
View File
@@ -98,6 +98,7 @@ export function createRenderer(
console.error("GameLeftSidebar element not found in the DOM");
}
gameLeftSidebar.game = game;
gameLeftSidebar.eventBus = eventBus;
const teamStats = document.querySelector("team-stats") as TeamStats;
if (!teamStats || !(teamStats instanceof TeamStats)) {
@@ -246,6 +247,7 @@ export function createRenderer(
console.error("spawn timer not found");
}
spawnTimer.game = game;
spawnTimer.eventBus = eventBus;
spawnTimer.transformHandler = transformHandler;
const immunityTimer = document.querySelector(
@@ -255,6 +257,7 @@ export function createRenderer(
console.error("immunity timer not found");
}
immunityTimer.game = game;
immunityTimer.eventBus = eventBus;
const inGameHeaderAd = document.querySelector(
"in-game-header-ad",
+13 -11
View File
@@ -221,7 +221,7 @@ export class AttacksDisplay extends LitElement implements Layer {
return this.incomingAttacks.map(
(attack) => html`
<div
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs rounded px-1.5 py-0.5 overflow-hidden"
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs min-[1200px]:rounded-lg sm:rounded-r-lg px-1.5 py-0.5 overflow-hidden"
>
${this.renderButton({
content: html`<img
@@ -232,7 +232,7 @@ export class AttacksDisplay extends LitElement implements Layer {
<span class="inline-block min-w-[3rem] text-right"
>${renderTroops(attack.troops)}</span
>
<span class="truncate"
<span class="truncate ml-1"
>${(
this.game.playerBySmallID(attack.attackerID) as PlayerView
)?.name()}</span
@@ -254,7 +254,7 @@ export class AttacksDisplay extends LitElement implements Layer {
/>`,
onClick: () => this.handleRetaliate(attack),
className:
"ml-auto inline-flex items-center justify-center cursor-pointer bg-red-900/50 hover:bg-red-800/70 rounded px-1.5 py-1 border border-red-700/50",
"ml-auto inline-flex items-center justify-center cursor-pointer bg-red-900/50 hover:bg-red-800/70 rounded-lg px-1.5 py-1 border border-red-700/50",
translate: false,
})
: ""}
@@ -269,7 +269,7 @@ export class AttacksDisplay extends LitElement implements Layer {
return this.outgoingAttacks.map(
(attack) => html`
<div
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs rounded px-1.5 py-0.5 overflow-hidden"
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs min-[1200px]:rounded-lg sm:rounded-r-lg px-1.5 py-0.5 overflow-hidden"
>
${this.renderButton({
content: html`<img
@@ -280,7 +280,7 @@ export class AttacksDisplay extends LitElement implements Layer {
<span class="inline-block min-w-[3rem] text-right"
>${renderTroops(attack.troops)}</span
>
<span class="truncate"
<span class="truncate ml-1"
>${(
this.game.playerBySmallID(attack.targetID) as PlayerView
)?.name()}</span
@@ -311,7 +311,7 @@ export class AttacksDisplay extends LitElement implements Layer {
return this.outgoingLandAttacks.map(
(landAttack) => html`
<div
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs rounded px-1.5 py-0.5 overflow-hidden"
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs min-[1200px]:rounded-lg sm:rounded-r-lg px-1.5 py-0.5 overflow-hidden"
>
${this.renderButton({
content: html`<img
@@ -367,14 +367,14 @@ export class AttacksDisplay extends LitElement implements Layer {
return this.outgoingBoats.map(
(boat) => html`
<div
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs rounded px-1.5 py-0.5 overflow-hidden"
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs min-[1200px]:rounded-lg sm:rounded-r-lg px-1.5 py-0.5 overflow-hidden"
>
${this.renderButton({
content: html`${this.renderBoatIcon(boat)}
<span class="inline-block min-w-[3rem] text-right"
>${renderTroops(boat.troops())}</span
>
<span class="truncate text-xs"
<span class="truncate text-xs ml-1"
>${this.getBoatTargetName(boat)}</span
>`,
onClick: () => this.eventBus.emit(new GoToUnitEvent(boat)),
@@ -403,14 +403,16 @@ export class AttacksDisplay extends LitElement implements Layer {
return this.incomingBoats.map(
(boat) => html`
<div
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs rounded px-1.5 py-0.5 overflow-hidden"
class="flex items-center gap-0.5 w-full bg-gray-800/70 backdrop-blur-xs min-[1200px]:rounded-lg sm:rounded-r-lg px-1.5 py-0.5 overflow-hidden"
>
${this.renderButton({
content: html`${this.renderBoatIcon(boat)}
<span class="inline-block min-w-[3rem] text-right"
>${renderTroops(boat.troops())}</span
>
<span class="truncate text-xs">${boat.owner()?.name()}</span>`,
<span class="truncate text-xs ml-1"
>${boat.owner()?.name()}</span
>`,
onClick: () => this.eventBus.emit(new GoToUnitEvent(boat)),
className:
"text-left text-red-400 inline-flex items-center gap-0.5 lg:gap-1 min-w-0",
@@ -439,7 +441,7 @@ export class AttacksDisplay extends LitElement implements Layer {
return html`
<div
class="w-full mb-1 pointer-events-auto grid grid-cols-2 lg:grid-cols-1 gap-1 text-white text-sm lg:text-base"
class="w-full mb-1 mt-1 sm:mt-0 pointer-events-auto grid grid-cols-2 lg:grid-cols-1 gap-1 text-white text-sm lg:text-base"
>
${this.renderOutgoingAttacks()} ${this.renderOutgoingLandAttacks()}
${this.renderBoats()} ${this.renderIncomingAttacks()}
+1 -1
View File
@@ -261,7 +261,7 @@ export class ControlPanel extends LitElement implements Layer {
return html`
<div
class="pointer-events-auto ${this._isVisible
? "relative z-[60] w-full lg:max-w-[400px] text-sm lg:text-base bg-gray-800/70 p-1.5 pr-2 lg:p-5 shadow-lg lg:rounded-tr-xl min-[1200px]:rounded-xl backdrop-blur-sm"
? "relative z-[60] w-full lg:max-w-[400px] text-sm lg:text-base bg-gray-800/70 p-1.5 pr-2 lg:p-5 shadow-lg sm:rounded-tr-lg min-[1200px]:rounded-lg backdrop-blur-xs"
: "hidden"}"
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
>
+14 -12
View File
@@ -788,17 +788,19 @@ export class EventsDisplay extends LitElement implements Layer {
>
${this.renderButton({
content: html`
Events
<span
class="${this.newEvents
? ""
: "hidden"} inline-block px-2 bg-red-500 rounded-lg text-sm"
>${this.newEvents}</span
>
<span class="flex items-center gap-2">
${translateText("events_display.events")}
${this.newEvents > 0
? html`<span
class="inline-block px-2 bg-red-500 rounded-lg text-sm"
>${this.newEvents}</span
>`
: ""}
</span>
`,
onClick: this.toggleHidden,
className:
"text-white cursor-pointer pointer-events-auto w-fit p-2 lg:p-3 rounded-lg bg-gray-800/70 backdrop-blur-sm",
"text-white cursor-pointer pointer-events-auto w-fit p-2 lg:p-3 min-[1200px]:rounded-lg sm:rounded-tl-lg bg-gray-800/70 backdrop-blur-xs",
})}
</div>
`
@@ -809,9 +811,9 @@ export class EventsDisplay extends LitElement implements Layer {
>
<!-- Button Bar -->
<div
class="w-full p-2 lg:p-3 bg-gray-800/70 min-[1200px]:rounded-t-lg lg:rounded-tl-lg"
class="w-full p-2 lg:p-3 bg-gray-800/70 min-[1200px]:rounded-t-lg sm:rounded-tl-lg"
>
<div class="flex justify-between items-center">
<div class="flex justify-between items-center gap-3">
<div class="flex gap-4">
${this.renderToggleButton(
swordIcon,
@@ -857,7 +859,7 @@ export class EventsDisplay extends LitElement implements Layer {
>
<div>
<table
class="w-full max-h-none border-collapse text-white shadow-lg lg:text-base text-md md:text-xs pointer-events-auto"
class="w-full max-h-none border-collapse text-white shadow-lg text-xs lg:text-sm pointer-events-auto"
>
<tbody>
${filteredEvents.map(
@@ -896,7 +898,7 @@ export class EventsDisplay extends LitElement implements Layer {
${event.buttons.map(
(btn) => html`
<button
class="inline-block px-3 py-1 text-white rounded-sm text-md md:text-sm cursor-pointer transition-colors duration-300
class="inline-block px-3 py-1 text-white rounded-sm text-xs lg:text-sm cursor-pointer transition-colors duration-300
${btn.className.includes("btn-info")
? "bg-blue-500 hover:bg-blue-600"
: btn.className.includes(
+21 -2
View File
@@ -1,10 +1,13 @@
import { Colord } from "colord";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { GameMode } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { translateText } from "../../Utils";
import { ImmunityBarVisibleEvent } from "./ImmunityTimer";
import { Layer } from "./Layer";
import { SpawnBarVisibleEvent } from "./SpawnTimer";
import leaderboardRegularIcon from "/images/LeaderboardIconRegularWhite.svg?url";
import leaderboardSolidIcon from "/images/LeaderboardIconSolidWhite.svg?url";
import teamRegularIcon from "/images/TeamIconRegularWhite.svg?url";
@@ -22,9 +25,14 @@ export class GameLeftSidebar extends LitElement implements Layer {
private isPlayerTeamLabelVisible = false;
@state()
private playerTeam: string | null = null;
@state()
private spawnBarVisible = false;
@state()
private immunityBarVisible = false;
private playerColor: Colord = new Colord("#FFFFFF");
public game: GameView;
public eventBus: EventBus;
private _shownOnInit = false;
createRenderRoot() {
@@ -33,6 +41,12 @@ export class GameLeftSidebar extends LitElement implements Layer {
init() {
this.isVisible = true;
this.eventBus.on(SpawnBarVisibleEvent, (e) => {
this.spawnBarVisible = e.visible;
});
this.eventBus.on(ImmunityBarVisibleEvent, (e) => {
this.immunityBarVisible = e.visible;
});
if (this.isTeamGame) {
this.isPlayerTeamLabelVisible = true;
}
@@ -68,6 +82,10 @@ export class GameLeftSidebar extends LitElement implements Layer {
}
}
private get barOffset(): number {
return (this.spawnBarVisible ? 7 : 0) + (this.immunityBarVisible ? 7 : 0);
}
private toggleLeaderboard(): void {
this.isLeaderboardShow = !this.isLeaderboardShow;
}
@@ -90,9 +108,10 @@ export class GameLeftSidebar extends LitElement implements Layer {
render() {
return html`
<aside
class=${`fixed top-4 left-4 z-1000 flex flex-col max-h-[calc(100vh-80px)] overflow-y-auto p-2 bg-slate-800/40 backdrop-blur-xs shadow-xs rounded-lg transition-transform duration-300 ease-out transform ${
class=${`fixed top-0 min-[1200px]:top-4 left-0 min-[1200px]:left-4 z-1000 flex flex-col max-h-[calc(100vh-80px)] overflow-y-auto p-2 bg-gray-800/70 backdrop-blur-xs shadow-xs min-[1200px]:rounded-lg rounded-br-lg transition-all duration-300 ease-out transform ${
this.isVisible ? "translate-x-0" : "hidden"
}`}
style="margin-top: ${this.barOffset}px;"
>
<div class="flex items-center gap-4 xl:gap-6 text-white">
<div
@@ -152,7 +171,7 @@ export class GameLeftSidebar extends LitElement implements Layer {
${this.isPlayerTeamLabelVisible
? html`
<div
class="flex items-center w-full text-white"
class="flex items-center w-full text-white mt-2"
@contextmenu=${(e: Event) => e.preventDefault()}
>
${translateText("help_modal.ui_your_team")}
+23 -1
View File
@@ -6,9 +6,11 @@ import { GameView } from "../../../core/game/GameView";
import { crazyGamesSDK } from "../../CrazyGamesSDK";
import { PauseGameIntentEvent, SendWinnerEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { ImmunityBarVisibleEvent } from "./ImmunityTimer";
import { Layer } from "./Layer";
import { ShowReplayPanelEvent } from "./ReplayPanel";
import { ShowSettingsModalEvent } from "./SettingsModal";
import { SpawnBarVisibleEvent } from "./SpawnTimer";
import exitIcon from "/images/ExitIconWhite.svg?url";
import FastForwardIconSolid from "/images/FastForwardIconSolidWhite.svg?url";
import pauseIcon from "/images/PauseIconWhite.svg?url";
@@ -37,6 +39,8 @@ export class GameRightSidebar extends LitElement implements Layer {
private hasWinner = false;
private isLobbyCreator = false;
private spawnBarVisible = false;
private immunityBarVisible = false;
createRenderRoot() {
return this;
@@ -49,6 +53,15 @@ export class GameRightSidebar extends LitElement implements Layer {
this._isVisible = true;
this.game.inSpawnPhase();
this.eventBus.on(SpawnBarVisibleEvent, (e) => {
this.spawnBarVisible = e.visible;
this.updateParentOffset();
});
this.eventBus.on(ImmunityBarVisibleEvent, (e) => {
this.immunityBarVisible = e.visible;
this.updateParentOffset();
});
this.eventBus.on(SendWinnerEvent, () => {
this.hasWinner = true;
this.requestUpdate();
@@ -91,6 +104,15 @@ export class GameRightSidebar extends LitElement implements Layer {
}
}
private updateParentOffset(): void {
const offset =
(this.spawnBarVisible ? 7 : 0) + (this.immunityBarVisible ? 7 : 0);
const parent = this.parentElement as HTMLElement;
if (parent) {
parent.style.marginTop = `${offset}px`;
}
}
private secondsToHms = (d: number): string => {
const pad = (n: number) => (n < 10 ? `0${n}` : n);
@@ -153,7 +175,7 @@ export class GameRightSidebar extends LitElement implements Layer {
return html`
<aside
class=${`w-fit flex flex-row items-center gap-3 py-2 px-3 bg-gray-800/70 backdrop-blur-xs shadow-xs rounded-lg transition-transform duration-300 ease-out transform text-white ${
class=${`w-fit flex flex-row items-center gap-3 py-2 px-3 bg-gray-800/70 backdrop-blur-xs shadow-xs min-[1200px]:rounded-lg rounded-bl-lg transition-transform duration-300 ease-out transform text-white ${
this._isVisible ? "translate-x-0" : "translate-x-full"
}`}
@contextmenu=${(e: Event) => e.preventDefault()}
+3 -3
View File
@@ -146,10 +146,10 @@ export class HeadsUpMessage extends LitElement implements Layer {
? html`
<div
class="fixed top-[15%] left-1/2 -translate-x-1/2 z-[11000]
inline-flex items-center justify-center h-8 lg:h-10
inline-flex items-center justify-center min-h-8 lg:min-h-10
w-fit max-w-[90vw]
bg-gray-900/60 rounded-md lg:rounded-lg
backdrop-blur-md text-white text-md lg:text-xl px-3 lg:px-4
bg-gray-800/70 rounded-md lg:rounded-lg
backdrop-blur-xs text-white text-md lg:text-xl px-3 lg:px-4 py-1
text-center break-words"
style="word-wrap: break-word; hyphens: auto;"
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
+31 -16
View File
@@ -1,14 +1,21 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { EventBus, GameEvent } from "../../../core/EventBus";
import { GameMode } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
export class ImmunityBarVisibleEvent implements GameEvent {
constructor(public readonly visible: boolean) {}
}
@customElement("immunity-timer")
export class ImmunityTimer extends LitElement implements Layer {
public game: GameView;
public eventBus: EventBus;
private isVisible = false;
private _barVisible = false;
private isActive = false;
private progressRatio = 0;
@@ -46,24 +53,24 @@ export class ImmunityTimer extends LitElement implements Layer {
this.game.inSpawnPhase()
) {
this.setInactive();
return;
} else {
const immunityEnd = spawnPhaseTurns + immunityDuration;
const ticks = this.game.ticks();
if (ticks >= immunityEnd || ticks < spawnPhaseTurns) {
this.setInactive();
} else {
const elapsedTicks = Math.max(0, ticks - spawnPhaseTurns);
this.progressRatio = Math.min(
1,
Math.max(0, elapsedTicks / immunityDuration),
);
this.isActive = true;
this.requestUpdate();
}
}
const immunityEnd = spawnPhaseTurns + immunityDuration;
const ticks = this.game.ticks();
if (ticks >= immunityEnd || ticks < spawnPhaseTurns) {
this.setInactive();
return;
}
const elapsedTicks = Math.max(0, ticks - spawnPhaseTurns);
this.progressRatio = Math.min(
1,
Math.max(0, elapsedTicks / immunityDuration),
);
this.isActive = true;
this.requestUpdate();
this.emitBarVisibility();
}
private setInactive() {
@@ -73,6 +80,14 @@ export class ImmunityTimer extends LitElement implements Layer {
}
}
private emitBarVisibility() {
const nowVisible = this.isVisible && this.isActive;
if (nowVisible !== this._barVisible) {
this._barVisible = nowVisible;
this.eventBus?.emit(new ImmunityBarVisibleEvent(this._barVisible));
}
}
shouldTransform(): boolean {
return false;
}
+39 -10
View File
@@ -55,6 +55,12 @@ export class Leaderboard extends LitElement implements Layer {
init() {}
willUpdate(changed: Map<string, unknown>) {
if (changed.has("visible") && this.visible) {
this.updateLeaderboard();
}
}
getTickIntervalMs() {
return 1000;
}
@@ -184,10 +190,10 @@ export class Leaderboard extends LitElement implements Layer {
@contextmenu=${(e: Event) => e.preventDefault()}
>
<div
class="grid bg-gray-800/70 w-full text-xs md:text-xs lg:text-sm"
class="grid bg-gray-800/85 w-full text-xs md:text-xs lg:text-sm rounded-lg overflow-hidden"
style="grid-template-columns: 30px 100px 70px 55px 105px;"
>
<div class="contents font-bold bg-gray-700/50">
<div class="contents font-bold bg-gray-700/60">
<div class="py-1 md:py-2 text-center border-b border-slate-500">
#
</div>
@@ -234,28 +240,51 @@ export class Leaderboard extends LitElement implements Layer {
${repeat(
this.players,
(p) => p.player.id(),
(player) => html`
(player, index) => html`
<div
class="contents hover:bg-slate-600/60 ${player.isOnSameTeam
? "font-bold"
: ""} cursor-pointer"
@click=${() => this.handleRowClickPlayer(player.player)}
>
<div class="py-1 md:py-2 text-center border-b border-slate-500">
<div
class="py-1 md:py-2 text-center ${index <
this.players.length - 1
? "border-b border-slate-500"
: ""}"
>
${player.position}
</div>
<div
class="py-1 md:py-2 text-center border-b border-slate-500 truncate"
class="py-1 md:py-2 text-center ${index <
this.players.length - 1
? "border-b border-slate-500"
: ""} truncate"
>
${player.name}
</div>
<div class="py-1 md:py-2 text-center border-b border-slate-500">
<div
class="py-1 md:py-2 text-center ${index <
this.players.length - 1
? "border-b border-slate-500"
: ""}"
>
${player.score}
</div>
<div class="py-1 md:py-2 text-center border-b border-slate-500">
<div
class="py-1 md:py-2 text-center ${index <
this.players.length - 1
? "border-b border-slate-500"
: ""}"
>
${player.gold}
</div>
<div class="py-1 md:py-2 text-center border-b border-slate-500">
<div
class="py-1 md:py-2 text-center ${index <
this.players.length - 1
? "border-b border-slate-500"
: ""}"
>
${player.maxTroops}
</div>
</div>
@@ -265,9 +294,9 @@ export class Leaderboard extends LitElement implements Layer {
</div>
<button
class="mt-1 px-1.5 pb-0.5 md:px-2 text-xs md:text-xs lg:text-sm
class="mt-2 p-0.5 px-1.5 md:px-2 text-xs md:text-xs lg:text-sm
border rounded-md border-slate-500 transition-colors
text-white mx-auto block hover:bg-white/10"
text-white mx-auto block hover:bg-white/10 bg-gray-700/50"
@click=${() => {
this.showTopFive = !this.showTopFive;
this.updateLeaderboard();
@@ -22,8 +22,10 @@ import {
} from "../../Utils";
import { getFirstPlacePlayer, getPlayerIcons } from "../PlayerIcons";
import { TransformHandler } from "../TransformHandler";
import { ImmunityBarVisibleEvent } from "./ImmunityTimer";
import { Layer } from "./Layer";
import { CloseRadialMenuEvent } from "./RadialMenu";
import { SpawnBarVisibleEvent } from "./SpawnTimer";
import allianceIcon from "/images/AllianceIcon.svg?url";
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
import cityIcon from "/images/CityIconWhite.svg?url";
@@ -77,8 +79,17 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
@state()
private _isInfoVisible: boolean = false;
@state()
private spawnBarVisible = false;
@state()
private immunityBarVisible = false;
private _isActive = false;
private get barOffset(): number {
return (this.spawnBarVisible ? 7 : 0) + (this.immunityBarVisible ? 7 : 0);
}
private lastMouseUpdate = 0;
init() {
@@ -89,6 +100,12 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
this.maybeShow(e.x, e.y),
);
this.eventBus.on(CloseRadialMenuEvent, () => this.hide());
this.eventBus.on(SpawnBarVisibleEvent, (e) => {
this.spawnBarVisible = e.visible;
});
this.eventBus.on(ImmunityBarVisibleEvent, (e) => {
this.immunityBarVisible = e.visible;
});
this._isActive = true;
}
@@ -155,6 +172,24 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
this.requestUpdate();
}
private getPlayerNameColor(
player: PlayerView,
myPlayer: PlayerView | null | undefined,
isFriendly: boolean,
): string {
if (isFriendly) return "text-green-500";
if (
myPlayer &&
myPlayer !== player &&
player.type() === PlayerType.Nation
) {
const relation =
this.playerProfile?.relations[myPlayer.smallID()] ?? Relation.Neutral;
return this.getRelationClass(relation);
}
return "text-white";
}
private getRelationClass(relation: Relation): string {
switch (relation) {
case Relation.Hostile:
@@ -255,7 +290,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
.find((alliance) => alliance.other === player.id());
if (alliance !== undefined) {
allianceHtml = html` <div
class="flex flex-col items-center ml-auto mr-0 text-sm font-bold leading-tight"
class="flex items-center ml-auto mr-0 gap-1 text-sm font-bold leading-tight"
>
<img src=${allianceIcon} width="20" height="20" />
${this.allianceExpirationText(alliance)}
@@ -293,9 +328,11 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
<!-- Right: Player identity + Units below -->
<div class="flex flex-col justify-between self-stretch">
<div
class="flex items-center gap-2 font-bold text-sm lg:text-lg ${isFriendly
? "text-green-500"
: "text-white"}"
class="flex items-center gap-2 font-bold text-sm lg:text-lg ${this.getPlayerNameColor(
player,
myPlayer,
isFriendly ?? false,
)}"
>
${player.cosmetics.flag
? player.cosmetics.flag!.startsWith("!")
@@ -452,11 +489,12 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
return html`
<div
class="fixed top-0 lg:top-4 left-0 right-0 sm:left-1/2 sm:right-auto sm:-translate-x-1/2 z-[1001]"
class="fixed top-0 min-[1200px]:top-4 left-0 right-0 sm:left-1/2 sm:right-auto sm:-translate-x-1/2 z-[1001]"
style="margin-top: ${this.barOffset}px;"
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
>
<div
class="bg-gray-800/70 backdrop-blur-xs shadow-xs lg:rounded-lg shadow-lg transition-all duration-300 text-white text-lg lg:text-base w-full sm:w-auto sm:min-w-[400px] overflow-hidden ${containerClasses}"
class="bg-gray-800/70 backdrop-blur-xs shadow-xs min-[1200px]:rounded-lg sm:rounded-b-lg shadow-lg transition-all duration-300 text-white text-lg lg:text-base w-full sm:w-auto sm:min-w-[400px] overflow-hidden ${containerClasses}"
>
${this.player !== null ? this.renderPlayerInfo(this.player) : ""}
${this.unit !== null ? this.renderUnitInfo(this.unit) : ""}
+1 -1
View File
@@ -68,7 +68,7 @@ export class ReplayPanel extends LitElement implements Layer {
return html`
<div
class="p-2 bg-gray-800/70 backdrop-blur-xs shadow-xs rounded-lg"
class="p-2 bg-gray-800/70 backdrop-blur-xs shadow-xs min-[1200px]:rounded-lg rounded-l-lg"
@contextmenu=${(e: Event) => e.preventDefault()}
>
<label class="block mb-2 text-white" translate="no">
+39 -30
View File
@@ -1,16 +1,23 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { EventBus, GameEvent } from "../../../core/EventBus";
import { GameMode, Team } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
export class SpawnBarVisibleEvent implements GameEvent {
constructor(public readonly visible: boolean) {}
}
@customElement("spawn-timer")
export class SpawnTimer extends LitElement implements Layer {
public game: GameView;
public eventBus: EventBus;
public transformHandler: TransformHandler;
private ratios = [0];
private _barVisible = false;
private colors = ["rgba(0, 128, 255, 0.7)", "rgba(0, 0, 0, 0.5)"];
private isVisible = false;
@@ -37,39 +44,41 @@ export class SpawnTimer extends LitElement implements Layer {
this.game.ticks() / this.game.config().numSpawnPhaseTurns(),
];
this.colors = ["rgba(0, 128, 255, 0.7)"];
this.requestUpdate();
return;
} else {
this.ratios = [];
this.colors = [];
if (this.game.config().gameConfig().gameMode === GameMode.Team) {
const teamTiles: Map<Team, number> = new Map();
for (const player of this.game.players()) {
const team = player.team();
if (team === null) continue;
const tiles = teamTiles.get(team) ?? 0;
teamTiles.set(team, tiles + player.numTilesOwned());
}
const theme = this.game.config().theme();
const total = sumIterator(teamTiles.values());
if (total > 0) {
for (const [team, count] of teamTiles) {
const ratio = count / total;
this.ratios.push(ratio);
this.colors.push(theme.teamColor(team).toRgbString());
}
}
}
}
this.ratios = [];
this.colors = [];
if (this.game.config().gameConfig().gameMode !== GameMode.Team) {
this.requestUpdate();
return;
}
const teamTiles: Map<Team, number> = new Map();
for (const player of this.game.players()) {
const team = player.team();
if (team === null) throw new Error("Team is null");
const tiles = teamTiles.get(team) ?? 0;
teamTiles.set(team, tiles + player.numTilesOwned());
}
const theme = this.game.config().theme();
const total = sumIterator(teamTiles.values());
if (total === 0) {
this.requestUpdate();
return;
}
for (const [team, count] of teamTiles) {
const ratio = count / total;
this.ratios.push(ratio);
this.colors.push(theme.teamColor(team).toRgbString());
}
this.requestUpdate();
this.emitBarVisibility();
}
private emitBarVisibility() {
const nowVisible = this.isVisible && this.ratios.length > 0;
if (nowVisible !== this._barVisible) {
this._barVisible = nowVisible;
this.eventBus?.emit(new SpawnBarVisibleEvent(this._barVisible));
}
}
shouldTransform(): boolean {
+2 -2
View File
@@ -132,7 +132,7 @@ export class TeamStats extends LitElement implements Layer {
return html`
<div
class="max-h-[30vh] overflow-y-auto grid bg-slate-800/70 w-full text-white text-xs md:text-sm mt-2"
class="max-h-[30vh] overflow-x-hidden overflow-y-auto grid bg-slate-800/85 w-full text-white text-xs md:text-sm mt-2 rounded-lg"
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
>
<div
@@ -140,7 +140,7 @@ export class TeamStats extends LitElement implements Layer {
style="--cols:${this.showUnits ? 5 : 4};"
>
<!-- Header -->
<div class="contents font-bold bg-slate-700/50">
<div class="contents font-bold bg-slate-700/60">
<div class="p-1.5 md:p-2.5 text-center border-b border-slate-500">
${translateText("leaderboard.team")}
</div>