homepage UI improvements (#3352)

## Description:

A bunch of small UI improvements:

* Make the content width a bit smaller so gutter ads fit
* remove the "duos" "trios" "quads" description on the game card since
it's redundant
* update UI in game card
* minor footer layout changes
* update z-index to ensure content appears above ads
* removed hasUnusualThumbnailSize, instead just check the map ratio
* Use "object cover" for non-irregular maps to the entire game card is
filed
* remove white ouline from the version
* changed solo button to sky blue
* make timer "s" lowercase


I think we may need to change the openfront logo color a bit too to
match the color palette, but we can do that in a follow up.

<img width="1591" height="969" alt="Screenshot 2026-03-05 at 2 04 48 PM"
src="https://github.com/user-attachments/assets/7bb9ea4c-5a17-47e1-bdad-9d6437b363b3"
/>


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

evan
This commit is contained in:
Evan
2026-03-05 15:17:28 -08:00
committed by GitHub
parent 47ef5a032b
commit 0733c680b9
11 changed files with 239 additions and 165 deletions
+15 -15
View File
@@ -125,7 +125,7 @@
<div id="hex-grid" class="fixed inset-0 -z-50 pointer-events-none">
<div
id="background-layer"
class="absolute inset-0 bg-cover bg-center opacity-50 [filter:brightness(1.0)] dark:[filter:sepia(0.2)_saturate(1.2)_hue-rotate(180deg)_brightness(0.9)]"
class="absolute inset-0 bg-cover bg-center opacity-60 [filter:brightness(0.5)_saturate(1.4)] dark:[filter:sepia(0.2)_saturate(1.2)_hue-rotate(180deg)_brightness(0.4)]"
style="
background-image: url(&quot;/resources/images/background.webp&quot;);
"
@@ -182,73 +182,73 @@
<matchmaking-modal
id="page-matchmaking"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></matchmaking-modal>
<news-modal
id="page-news"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></news-modal>
<single-player-modal
id="page-single-player"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></single-player-modal>
<host-lobby-modal
id="page-host-lobby"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></host-lobby-modal>
<join-lobby-modal
id="page-join-lobby"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></join-lobby-modal>
<territory-patterns-modal
id="page-item-store"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></territory-patterns-modal>
<user-setting
id="page-settings"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></user-setting>
<leaderboard-modal
id="page-leaderboard"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></leaderboard-modal>
<troubleshooting-modal
id="page-troubleshooting"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></troubleshooting-modal>
<account-modal
id="page-account"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></account-modal>
<help-modal
id="page-help"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></help-modal>
<language-modal
id="page-language"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></language-modal>
<flag-input-modal
id="flag-input-modal"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></flag-input-modal>
<ranked-modal
id="page-ranked"
inline
class="hidden w-full h-full page-content"
class="hidden w-full h-full page-content relative z-50"
></ranked-modal>
</main-layout>
+1 -5
View File
@@ -362,13 +362,10 @@
},
"public_lobby": {
"title": "Waiting for Game Start...",
"teams_Duos": "{team_count} teams of 2 (Duos)",
"teams_Trios": "{team_count} teams of 3 (Trios)",
"teams_Quads": "{team_count} teams of 4 (Quads)",
"waiting_for_players": "Waiting for players",
"connecting": "Connecting to lobby...",
"starting_in": "Starting in {time}",
"starting_game": "Starting game…",
"starting_game": "Starting…",
"teams_hvn": "Humans vs Nations",
"teams_hvn_detailed": "{num} Humans vs {num} Nations",
"teams": "{num} teams",
@@ -464,7 +461,6 @@
"teams": "Teams"
},
"mode_selector": {
"special_title": "Special Mix",
"teams_title": "Teams",
"teams_count": "{teamCount} teams",
"teams_of": "{teamCount} teams of {playersPerTeam}",
+2 -5
View File
@@ -1,7 +1,7 @@
import { html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { GameEndInfo } from "../core/Schemas";
import { GameMapType, hasUnusualThumbnailSize } from "../core/game/Game";
import { GameMapType } from "../core/game/Game";
import { fetchGameById } from "./Api";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
import { UsernameInput } from "./UsernameInput";
@@ -107,7 +107,6 @@ export class GameInfoModal extends LitElement {
if (!info) {
return html``;
}
const isUnusualThumbnailSize = hasUnusualThumbnailSize(info.config.gameMap);
return html`
<div
class="h-37.5 flex relative justify-between rounded-xl bg-black/20 items-center"
@@ -115,9 +114,7 @@ export class GameInfoModal extends LitElement {
${this.mapImage
? html`<img
src="${this.mapImage}"
class="absolute place-self-start col-span-full row-span-full h-full rounded-xl mask-[linear-gradient(to_left,transparent,#fff)] ${isUnusualThumbnailSize
? "object-cover object-center"
: ""}"
class="absolute place-self-start col-span-full row-span-full h-full rounded-xl mask-[linear-gradient(to_left,transparent,#fff)] object-cover object-center"
/>`
: html`<div
class="place-self-start col-span-full row-span-full h-full rounded-xl bg-gray-300"
+199 -115
View File
@@ -1,5 +1,6 @@
import { html, LitElement, nothing, type TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getServerConfigFromClient } from "src/core/configuration/ConfigLoader";
import {
Duos,
GameMapType,
@@ -27,7 +28,9 @@ const CARD_BG = "bg-[color-mix(in_oklab,var(--frenchBlue)_70%,black)]";
@customElement("game-mode-selector")
export class GameModeSelector extends LitElement {
@state() private lobbies: PublicGames | null = null;
@state() private mapAspectRatios: Map<GameMapType, number> = new Map();
private serverTimeOffset: number = 0;
private defaultLobbyTime: number = 0;
private lobbySocket = new PublicLobbySocket((lobbies) =>
this.handleLobbiesUpdate(lobbies),
@@ -61,6 +64,9 @@ export class GameModeSelector extends LitElement {
connectedCallback() {
super.connectedCallback();
this.lobbySocket.start();
getServerConfigFromClient().then((config) => {
this.defaultLobbyTime = config.gameCreationRate() / 1000;
});
}
disconnectedCallback() {
@@ -81,6 +87,30 @@ export class GameModeSelector extends LitElement {
}),
);
this.requestUpdate();
const allGames = Object.values(lobbies.games ?? {}).flat();
for (const game of allGames) {
const mapType = game.gameConfig?.gameMap as GameMapType;
if (mapType && !this.mapAspectRatios.has(mapType)) {
// New Map reference triggers Lit reactivity; placeholder ratio 1 lets
// has() guard against duplicate in-flight fetches.
this.mapAspectRatios = new Map(this.mapAspectRatios).set(mapType, 1);
terrainMapFileLoader
.getMapData(mapType)
.manifest()
.then((m: any) => {
if (m?.map?.width && m?.map?.height) {
this.mapAspectRatios = new Map(this.mapAspectRatios).set(
mapType,
m.map.width / m.map.height,
);
}
})
.catch((e) =>
console.error(`Failed to load manifest for ${mapType}`, e),
);
}
}
}
render() {
@@ -89,74 +119,110 @@ export class GameModeSelector extends LitElement {
const special = this.lobbies?.games?.["special"]?.[0];
return html`
<div class="flex flex-col gap-4 w-[84%] lg:w-full mx-auto pb-4 lg:pb-0">
<div class="order-first lg:order-none h-14 lg:hidden">
${this.renderSoloButton()}
<div class="flex flex-col gap-4 w-[84%] sm:w-full mx-auto pb-4 sm:pb-0">
<!-- Solo: mobile only, top -->
<div class="sm:hidden h-14">
${this.renderSmallActionCard(
translateText("main.solo"),
this.openSinglePlayerModal,
"bg-sky-600",
)}
</div>
<!-- Create/ranked/join: mobile only, below solo -->
<div class="sm:hidden grid grid-cols-3 gap-4 h-14">
${this.renderSmallActionCard(
translateText("main.create"),
this.openHostLobby,
"bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]",
)}
${this.renderSmallActionCard(
translateText("mode_selector.ranked_title"),
this.openRankedMenu,
"bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]",
)}
${this.renderSmallActionCard(
translateText("main.join"),
this.openJoinLobby,
"bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]",
)}
</div>
<!-- Game cards grid -->
<div
class="grid grid-cols-1 lg:grid-cols-[3fr_2fr] lg:grid-rows-2 gap-4 lg:h-[28rem]"
class="grid grid-cols-1 sm:grid-cols-[2fr_1fr] gap-4 sm:h-[min(24rem,40vh)]"
>
${ffa
? html`<div class="lg:row-span-2">
${this.renderLobbyCard(ffa, this.getLobbyTitle(ffa))}
<!-- Left col: main card (desktop only) -->
${special
? html`<div class="hidden sm:block">
${this.renderSpecialLobbyCard(special)}
</div>`
: nothing}
${teams
? this.renderLobbyCard(teams, this.getLobbyTitle(teams))
: nothing}
${special ? this.renderSpecialLobbyCard(special) : nothing}
: ffa
? html`<div class="hidden sm:block">
${this.renderLobbyCard(ffa, this.getLobbyTitle(ffa))}
</div>`
: nothing}
<!-- Right col: FFA + teams (desktop only) -->
<div class="hidden sm:flex sm:flex-col sm:gap-4">
${special && ffa
? html`<div class="flex-1 min-h-0">
${this.renderLobbyCard(ffa, this.getLobbyTitle(ffa))}
</div>`
: nothing}
${teams
? html`<div class="flex-1 min-h-0">
${this.renderLobbyCard(teams, this.getLobbyTitle(teams))}
</div>`
: nothing}
</div>
<!-- Mobile: special, ffa, teams inline -->
<div class="sm:hidden">
${special ? this.renderSpecialLobbyCard(special) : nothing}
</div>
<div class="sm:hidden">
${ffa
? this.renderLobbyCard(ffa, this.getLobbyTitle(ffa))
: nothing}
</div>
<div class="sm:hidden">
${teams
? this.renderLobbyCard(teams, this.getLobbyTitle(teams))
: nothing}
</div>
</div>
<!-- Solo: full width, desktop only -->
<div class="hidden sm:block h-14">
${this.renderSmallActionCard(
translateText("main.solo"),
this.openSinglePlayerModal,
"bg-sky-600",
)}
</div>
<!-- Bottom row: create + ranked + join (desktop only) -->
<div class="hidden sm:grid grid-cols-3 gap-4 h-14">
${this.renderSmallActionCard(
translateText("main.create"),
this.openHostLobby,
"bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]",
)}
${this.renderSmallActionCard(
translateText("mode_selector.ranked_title"),
this.openRankedMenu,
"bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]",
)}
${this.renderSmallActionCard(
translateText("main.join"),
this.openJoinLobby,
"bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]",
)}
</div>
${this.renderQuickActionsSection()}
</div>
`;
}
private renderSpecialLobbyCard(lobby: PublicGameInfo) {
const subtitle = this.getLobbyTitle(lobby);
const mainTitle = translateText("mode_selector.special_title");
const titleContent = subtitle
? html`
<span class="block">${mainTitle}</span>
<span class="block text-[10px] leading-tight text-white/70">
${subtitle}
</span>
`
: mainTitle;
return this.renderLobbyCard(lobby, titleContent);
}
private renderSoloButton() {
const title = translateText("main.solo");
return html`
<button
@click=${this.openSinglePlayerModal}
class="flex items-center justify-center w-full h-full rounded-xl bg-blue-600 border-0 transition-transform hover:scale-[1.02] active:scale-[0.98] text-sm lg:text-base font-bold text-white uppercase tracking-wider text-center"
>
${title}
</button>
`;
}
private renderQuickActionsSection() {
return html`
<div class="flex flex-col gap-2">
<div class="h-14 hidden lg:block">${this.renderSoloButton()}</div>
<div class="grid grid-cols-3 gap-2 h-14">
${this.renderSmallActionCard(
translateText("mode_selector.ranked_title"),
this.openRankedMenu,
)}
${this.renderSmallActionCard(
translateText("main.create"),
this.openHostLobby,
)}
${this.renderSmallActionCard(
translateText("main.join"),
this.openJoinLobby,
)}
</div>
</div>
`;
return this.renderLobbyCard(lobby, this.getLobbyTitle(lobby));
}
private openRankedMenu = () => {
@@ -181,11 +247,15 @@ export class GameModeSelector extends LitElement {
(document.querySelector("join-lobby-modal") as JoinLobbyModal)?.open();
};
private renderSmallActionCard(title: string, onClick: () => void) {
private renderSmallActionCard(
title: string,
onClick: () => void,
bgClass: string = CARD_BG,
) {
return html`
<button
@click=${onClick}
class="flex items-center justify-center w-full h-full rounded-xl ${CARD_BG} border-0 transition-transform hover:scale-[1.02] active:scale-[0.98] text-sm lg:text-base font-bold text-white uppercase tracking-wider text-center"
class="flex items-center justify-center w-full h-full rounded-xl ${bgClass} border-0 transition-transform hover:scale-[1.02] active:scale-[0.98] text-sm lg:text-base font-bold text-white uppercase tracking-wider text-center"
>
${title}
</button>
@@ -198,6 +268,11 @@ export class GameModeSelector extends LitElement {
) {
const mapType = lobby.gameConfig!.gameMap as GameMapType;
const mapImageSrc = terrainMapFileLoader.getMapData(mapType).webpPath;
const aspectRatio = this.mapAspectRatios.get(mapType);
// Use object-contain for extreme aspect ratios (e.g. Amazon River ~20:1) so
// the full map is visible instead of being cropped by object-cover.
const useContain =
aspectRatio !== undefined && (aspectRatio > 4 || aspectRatio < 0.25);
const timeRemaining = lobby.startsAt
? Math.max(
0,
@@ -208,12 +283,14 @@ export class GameModeSelector extends LitElement {
: undefined;
let timeDisplay: string = "";
let timeDisplayUppercase = false;
if (timeRemaining === undefined) {
timeDisplay = "-s";
timeDisplay = renderDuration(this.defaultLobbyTime);
} else if (timeRemaining > 0) {
timeDisplay = renderDuration(timeRemaining);
} else {
timeDisplay = translateText("public_lobby.starting_game");
timeDisplayUppercase = true;
}
const mapName = getMapName(lobby.gameConfig?.gameMap);
@@ -229,59 +306,78 @@ export class GameModeSelector extends LitElement {
return html`
<button
@click=${() => this.validateAndJoin(lobby)}
class="group flex flex-col w-full h-44 lg:h-full text-white uppercase rounded-2xl overflow-hidden transition-transform duration-200 hover:scale-[1.02] active:scale-[0.98] ${CARD_BG}"
class="group relative w-full h-44 sm:h-full text-white uppercase rounded-2xl transition-transform duration-200 hover:scale-[1.02] active:scale-[0.98]"
style="background-color: color-mix(in oklab, var(--frenchBlue) 75%, black)"
>
<div class="relative flex-1 overflow-hidden ${CARD_BG}">
<!-- Image clipped separately so overflow-hidden doesn't block absolute children -->
<div
class="absolute inset-0 rounded-2xl overflow-hidden pointer-events-none"
>
${mapImageSrc
? html`<img
src="${mapImageSrc}"
alt="${mapName ?? lobby.gameConfig?.gameMap ?? "map"}"
draggable="false"
class="absolute inset-0 w-full h-full object-contain object-center scale-[1.05] pointer-events-none"
class="absolute inset-0 w-full h-full ${useContain
? "object-contain"
: "object-cover object-center scale-[1.05]"} [image-rendering:auto]"
/>`
: null}
<div
class="absolute inset-x-2 bottom-2 flex items-end justify-between gap-2"
>
${modifierLabels.length > 0
? html`<div class="flex flex-col items-start gap-1">
${modifierLabels.map(
(label) =>
html`<span
class="px-2 py-0.5 rounded text-[10px] font-medium uppercase tracking-wide bg-teal-600 text-white shadow-[0_0_6px_rgba(13,148,136,0.35)]"
>${label}</span
>`,
)}
</div>`
: html`<div></div>`}
<div class="shrink-0">
<span
class="text-[10px] font-bold uppercase tracking-widest bg-blue-600 px-2 py-0.5 rounded"
>${timeDisplay}</span
>
</div>
</div>
<!-- Top row: modifiers + timer -->
<div
class="absolute inset-x-2 top-2 flex items-start justify-between gap-2"
>
${modifierLabels.length > 0
? html`<div class="flex flex-col items-start gap-1">
${modifierLabels.map(
(label) =>
html`<span
class="px-2 py-0.5 rounded text-xs font-bold uppercase tracking-widest bg-teal-600 text-white shadow-[0_0_6px_rgba(13,148,136,0.35)]"
>${label}</span
>`,
)}
</div>`
: html`<div></div>`}
<div class="shrink-0">
<span
class="text-xs font-bold tracking-widest ${timeDisplayUppercase
? "uppercase"
: "normal-case"} bg-sky-600 px-2.5 py-1 rounded"
>${timeDisplay}</span
>
</div>
</div>
<div class="flex items-center justify-between px-3 py-2">
<div class="flex flex-col gap-0.5 min-w-0">
<h3
class="text-sm lg:text-base font-bold uppercase tracking-wider text-left leading-tight"
>
${titleContent}
</h3>
${mapName
? html`<p
class="text-[10px] text-white/70 uppercase tracking-wider text-left"
>
${mapName}
</p>`
: ""}
</div>
<!-- Bottom bar: map name + mode, with player count floating above -->
<div
class="absolute bottom-0 left-0 right-0 flex flex-col px-3 py-2 bg-black/55 backdrop-blur-sm rounded-b-2xl"
style="overflow: visible;"
>
<span
class="text-xs font-bold uppercase tracking-widest shrink-0 ml-2"
class="absolute bottom-full right-2 mb-1 flex items-center gap-1 text-xs font-bold tracking-widest bg-black/70 backdrop-blur-sm px-2 py-0.5 rounded"
>
${lobby.numClients}/${lobby.gameConfig?.maxPlayers}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 inline-block"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"
></path>
</svg>
</span>
${mapName
? html`<p
class="text-sm sm:text-base font-bold uppercase tracking-wider text-left leading-tight"
>
${mapName}
</p>`
: ""}
<h3 class="text-xs text-white/70 uppercase tracking-wider text-left">
${titleContent}
</h3>
</div>
</button>
`;
@@ -334,31 +430,19 @@ export class GameModeSelector extends LitElement {
const teamCount = totalPlayers
? Math.floor(totalPlayers / 2)
: undefined;
return teamCount
? translateText("public_lobby.teams_Duos", {
team_count: String(teamCount),
})
: formatTeamsOf(undefined, 2);
return formatTeamsOf(teamCount, 2);
}
case Trios: {
const teamCount = totalPlayers
? Math.floor(totalPlayers / 3)
: undefined;
return teamCount
? translateText("public_lobby.teams_Trios", {
team_count: String(teamCount),
})
: formatTeamsOf(undefined, 3);
return formatTeamsOf(teamCount, 3);
}
case Quads: {
const teamCount = totalPlayers
? Math.floor(totalPlayers / 4)
: undefined;
return teamCount
? translateText("public_lobby.teams_Quads", {
team_count: String(teamCount),
})
: formatTeamsOf(undefined, 4);
return formatTeamsOf(teamCount, 4);
}
case HumansVsNations: {
const humanSlots = config.maxPlayers ?? lobby.numClients;
+4 -4
View File
@@ -120,8 +120,8 @@ export class GutterAds extends LitElement {
return html`
<!-- Left Gutter Ad -->
<div
class="hidden xl:flex fixed transform -translate-y-1/2 w-[160px] min-h-[600px] z-[100] pointer-events-auto items-center justify-center"
style="left: calc(50% - 10cm - 230px); top: calc(50% + 10px);"
class="hidden xl:flex fixed transform -translate-y-1/2 w-[160px] min-h-[600px] z-40 pointer-events-auto items-center justify-center"
style="left: calc(50% - 10.5cm - 208px); top: calc(50% + 10px);"
>
<div
id="${this.leftContainerId}"
@@ -131,8 +131,8 @@ export class GutterAds extends LitElement {
<!-- Right Gutter Ad -->
<div
class="hidden xl:flex fixed transform -translate-y-1/2 w-[160px] min-h-[600px] z-[100] pointer-events-auto items-center justify-center"
style="left: calc(50% + 10cm + 70px); top: calc(50% + 10px);"
class="hidden xl:flex fixed transform -translate-y-1/2 w-[160px] min-h-[600px] z-40 pointer-events-auto items-center justify-center"
style="left: calc(50% + 10.5cm + 48px); top: calc(50% + 10px);"
>
<div
id="${this.rightContainerId}"
+1 -1
View File
@@ -49,7 +49,7 @@ export class DesktopNavBar extends LitElement {
return html`
<nav
class="hidden lg:flex w-full bg-slate-900 items-center justify-center gap-8 py-4 shrink-0 z-50 relative"
class="hidden lg:flex w-full bg-zinc-900/90 backdrop-blur-md items-center justify-center gap-8 py-4 shrink-0 z-50 relative"
>
<div class="flex flex-col items-center justify-center">
<div class="h-8 text-[#2563eb]">
+7 -5
View File
@@ -10,7 +10,7 @@ export class Footer extends LitElement {
render() {
return html`
<footer
class="[.in-game_&]:hidden bg-slate-950/70 backdrop-blur-md flex flex-col items-center justify-center gap-1 pt-1 pb-3 text-white/50 w-full border-t border-white/10 shrink-0 mt-auto"
class="[.in-game_&]:hidden bg-zinc-900/90 backdrop-blur-md flex flex-col items-center justify-center gap-1 pt-1 pb-3 text-white/50 w-full border-t border-white/10 shrink-0 mt-auto relative z-50"
>
<div class="flex items-center justify-center gap-4 lg:gap-6 pt-2">
<a
@@ -73,19 +73,21 @@ export class Footer extends LitElement {
/>
</a>
</div>
<div class="text-xs mt-1 lg:mt-2 grid grid-cols-3 w-full px-4">
<div
class="text-xs mt-1 lg:mt-2 flex items-center justify-center gap-4 px-4"
>
<a
href="/terms-of-service.html"
data-i18n="main.terms_of_service"
target="_blank"
class="hover:text-white transition-colors text-left"
class="hover:text-white transition-colors"
></a>
<span data-i18n="main.copyright" class="text-center"></span>
<span data-i18n="main.copyright"></span>
<a
href="/privacy-policy.html"
data-i18n="main.privacy_policy"
target="_blank"
class="hover:text-white transition-colors text-right"
class="hover:text-white transition-colors"
></a>
</div>
</footer>
+1 -1
View File
@@ -22,7 +22,7 @@ export class MainLayout extends LitElement {
class="relative [.in-game_&]:hidden flex flex-col flex-1 overflow-hidden w-full px-0 lg:px-[clamp(1.5rem,3vw,3rem)] pt-0 lg:pt-[clamp(0.75rem,1.5vw,1.5rem)] pb-0 lg:pb-[clamp(0.75rem,1.5vw,1.5rem)]"
>
<div
class="w-full lg:max-w-[24cm] mx-auto flex flex-col flex-1 gap-0 lg:gap-[clamp(1.5rem,3vw,3rem)] overflow-y-auto overflow-x-hidden"
class="w-full lg:max-w-[20cm] mx-auto flex flex-col flex-1 gap-0 lg:gap-[clamp(1.5rem,3vw,3rem)] overflow-y-auto overflow-x-hidden"
>
${this._initialChildren}
</div>
+9 -5
View File
@@ -100,13 +100,16 @@ export class PlayPage extends LitElement {
</div>
<div
class="w-full pb-4 lg:pb-0 flex flex-col gap-0 lg:grid lg:grid-cols-12 lg:gap-2"
class="w-full pb-4 lg:pb-0 flex flex-col gap-4 lg:grid lg:grid-cols-[2fr_1fr] lg:gap-4"
>
<!-- Mobile: spacer for fixed top bar -->
<div class="lg:hidden h-[calc(env(safe-area-inset-top)+56px)]"></div>
<div
class="px-2 py-2 bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)] border-y border-white/10 overflow-visible lg:col-span-9 lg:flex lg:items-center lg:gap-x-2 lg:h-[60px] lg:p-3 lg:relative lg:z-20 lg:border-y-0 lg:rounded-xl"
class="lg:hidden h-[calc(env(safe-area-inset-top)+56px)] lg:col-span-2 -mb-4"
></div>
<!-- Username: left col -->
<div
class="px-2 py-2 bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)] border-y border-white/10 overflow-visible lg:flex lg:items-center lg:gap-x-2 lg:h-[60px] lg:p-3 lg:relative lg:z-20 lg:border-y-0 lg:rounded-xl"
>
<div class="flex items-center gap-2 min-w-0 w-full">
<username-input
@@ -121,7 +124,8 @@ export class PlayPage extends LitElement {
</div>
</div>
<div class="hidden lg:flex lg:col-span-3 h-[60px] gap-2">
<!-- Skin + flag: right col -->
<div class="hidden lg:flex h-[60px] gap-2">
<pattern-input
id="pattern-input-desktop"
show-select-label
-4
View File
@@ -32,8 +32,4 @@
.l-header__highlightText {
color: #2563eb;
font-weight: 700;
filter: drop-shadow(1px 1px 0px rgb(255, 255, 255))
drop-shadow(-1px -1px 0px rgb(255, 255, 255))
drop-shadow(1px -1px 0px rgb(255, 255, 255))
drop-shadow(-1px 1px 0px rgb(255, 255, 255));
}
-5
View File
@@ -140,11 +140,6 @@ export enum GameMapType {
export type GameMapName = keyof typeof GameMapType;
/** Maps that have unusual thumbnail dimensions requiring object-fit: cover */
export function hasUnusualThumbnailSize(map: GameMapType): boolean {
return map === GameMapType.AmazonRiver || map === GameMapType.Passage;
}
export const mapCategories: Record<string, GameMapType[]> = {
continental: [
GameMapType.World,