mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 22:41:57 +00:00
Polished Player Panel (#2235)
## Description: - Polished Player Panel UI. - Made alliance list unstructured. - Removed blur background. ## Please complete the following: - [ ] 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: abodcraft1 --------- Co-authored-by: Evan <evanpelle@gmail.com>
This commit is contained in:
@@ -193,6 +193,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
|
||||
private closeSend = () => {
|
||||
this.sendTarget = null;
|
||||
this.sendMode = "none";
|
||||
};
|
||||
|
||||
private confirmSend = (
|
||||
@@ -418,10 +419,15 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
}}
|
||||
/>`
|
||||
: ""}
|
||||
<h1 class="text-2xl font-bold tracking-[-0.01em] truncate text-zinc-50">
|
||||
${other.name()}
|
||||
</h1>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h2
|
||||
class="text-xl font-bold tracking-[-0.01em] text-zinc-50 truncate"
|
||||
title=${other.name()}
|
||||
>
|
||||
${other.name()}
|
||||
</h2>
|
||||
</div>
|
||||
${chip
|
||||
? html`<span
|
||||
class=${`inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs font-semibold ${chip.classes}`}
|
||||
@@ -445,28 +451,28 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
return html`
|
||||
<div class="mb-1 flex justify-between gap-2">
|
||||
<div
|
||||
class="inline-flex items-center gap-1.5 rounded-full bg-white/[0.04] px-2.5 py-1
|
||||
text-base font-semibold text-zinc-200"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg bg-white/[0.04] px-3 py-1.5
|
||||
text-white w-[140px] min-w-[140px] flex-shrink-0"
|
||||
>
|
||||
<span class="mr-0.5">💰</span>
|
||||
<span translate="no" class="inline-block w-[45px] text-right">
|
||||
<span translate="no" class="tabular-nums w-[5ch]font-semibold">
|
||||
${renderNumber(other.gold() || 0)}
|
||||
</span>
|
||||
<span class="opacity-95 whitespace-nowrap"
|
||||
>${translateText("player_panel.gold")}</span
|
||||
<span class="text-zinc-200 whitespace-nowrap">
|
||||
${translateText("player_panel.gold")}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="inline-flex items-center gap-1.5 rounded-full bg-white/[0.04] px-2.5 py-1
|
||||
text-base font-semibold text-zinc-200"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg bg-white/[0.04] px-3 py-1.5
|
||||
text-white w-[140px] min-w-[140px] flex-shrink-0"
|
||||
>
|
||||
<span class="mr-0.5">🛡️</span>
|
||||
<span translate="no" class="inline-block w-[45px] text-right">
|
||||
<span translate="no" class="tabular-nums w-[5ch] font-semibold">
|
||||
${renderTroops(other.troops() || 0)}
|
||||
</span>
|
||||
<span class="opacity-95 whitespace-nowrap"
|
||||
>${translateText("player_panel.troops")}</span
|
||||
<span class="text-zinc-200 whitespace-nowrap">
|
||||
${translateText("player_panel.troops")}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -476,32 +482,34 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
private renderStats(other: PlayerView, my: PlayerView) {
|
||||
return html`
|
||||
<!-- Betrayals -->
|
||||
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2 text-base">
|
||||
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2">
|
||||
<div
|
||||
class="flex items-center gap-2 font-semibold text-zinc-300 leading-snug"
|
||||
class="flex items-center gap-2 text-[15px] font-medium text-zinc-100 leading-snug"
|
||||
>
|
||||
<span aria-hidden="true">⚠️</span>
|
||||
<span>${translateText("player_panel.betrayals")}</span>
|
||||
</div>
|
||||
<div class="text-right font-semibold text-zinc-200">
|
||||
<div class="text-right text-[14px] font-semibold text-zinc-200">
|
||||
${other.data.betrayals ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trading / Embargo -->
|
||||
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2 text-base">
|
||||
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2">
|
||||
<div
|
||||
class="flex items-center gap-2 font-semibold text-zinc-300 leading-snug"
|
||||
class="flex items-center gap-2 text-[15px] font-medium text-zinc-100 leading-snug"
|
||||
>
|
||||
<span aria-hidden="true">⚓</span>
|
||||
<span>${translateText("player_panel.trading")}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2 font-semibold">
|
||||
<div
|
||||
class="flex items-center justify-end gap-2 text-[14px] font-semibold"
|
||||
>
|
||||
${other.hasEmbargoAgainst(my)
|
||||
? html`<span class="text-[#f59e0b]"
|
||||
? html`<span class="text-amber-400"
|
||||
>${translateText("player_panel.stopped")}</span
|
||||
>`
|
||||
: html`<span class="text-emerald-400"
|
||||
: html`<span class="text-blue-400"
|
||||
>${translateText("player_panel.active")}</span
|
||||
>`}
|
||||
</div>
|
||||
@@ -512,60 +520,57 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
private renderAlliances(other: PlayerView) {
|
||||
const allies = other.allies();
|
||||
|
||||
const nameCollator = new Intl.Collator(undefined, { sensitivity: "base" });
|
||||
const alliesSorted = [...allies].sort((a, b) =>
|
||||
nameCollator.compare(a.name(), b.name()),
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="text-base select-none">
|
||||
<!-- Header -->
|
||||
<div class="select-none">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="font-semibold text-zinc-300 text-base">
|
||||
<div
|
||||
id="alliances-title"
|
||||
class="text-[15px] font-medium text-zinc-200"
|
||||
>
|
||||
${translateText("player_panel.alliances")}
|
||||
</div>
|
||||
<span
|
||||
aria-label="Alliance count"
|
||||
class="inline-flex items-center justify-center min-w-[20px] h-5 px-[6px] rounded-[10px]
|
||||
text-[12px] text-zinc-100 bg-white/10 border border-white/20"
|
||||
aria-labelledby="alliances-title"
|
||||
class="inline-flex items-center justify-center min-w-[20px] h-5 px-[6px] rounded-[10px]
|
||||
text-[12px] text-zinc-100 bg-white/10 border border-white/20"
|
||||
>
|
||||
${allies.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-1 rounded-lg border border-zinc-600 bg-zinc-800/80">
|
||||
<div
|
||||
class="max-h-[72px] overflow-y-auto p-2 text-zinc-200 text-[12.5px] leading-relaxed"
|
||||
<div
|
||||
class="rounded-lg bg-zinc-800/70 ring-1 ring-zinc-700/60 w-full min-w-0"
|
||||
>
|
||||
<ul
|
||||
class="max-h-[120px] overflow-y-auto p-2
|
||||
flex flex-wrap gap-1.5
|
||||
scrollbar-thin scrollbar-thumb-zinc-600 hover:scrollbar-thumb-zinc-500 scrollbar-track-zinc-800"
|
||||
role="list"
|
||||
aria-label="Alliance list"
|
||||
aria-labelledby="alliances-title"
|
||||
translate="no"
|
||||
>
|
||||
${allies.length > 0
|
||||
? allies.map((p) => {
|
||||
const color = p.territoryColor().toHex();
|
||||
return html`
|
||||
<div
|
||||
role="listitem"
|
||||
class="grid grid-cols-[16px_1fr] items-center gap-2 w-full h-[30px]
|
||||
px-2 rounded-lg border border-transparent text-left
|
||||
hover:bg-[#141821] hover:border-white/30 transition-colors"
|
||||
${alliesSorted.length === 0
|
||||
? html`<li class="text-zinc-400 text-[14px] px-1">
|
||||
${translateText("common.none")}
|
||||
</li>`
|
||||
: alliesSorted.map(
|
||||
(p) =>
|
||||
html`<li
|
||||
class="max-w-full inline-flex items-center gap-1.5
|
||||
rounded-md border border-white/10 bg-white/[0.05]
|
||||
px-2.5 py-1 text-[14px] text-zinc-100
|
||||
hover:bg-white/[0.08] active:scale-[0.99] transition"
|
||||
title=${p.name()}
|
||||
>
|
||||
<span
|
||||
class="inline-block w-3 h-3 rounded-full mr-2"
|
||||
style="background-color: ${color}"
|
||||
>
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="truncate select-none pointer-events-none font-medium"
|
||||
>
|
||||
${p.name()}
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
: html`
|
||||
<div class="py-2 text-zinc-300">
|
||||
${translateText("common.none")}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<span class="truncate">${p.name()}</span>
|
||||
</li>`,
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -580,7 +585,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
</div>
|
||||
<div class="text-right font-semibold">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2 py-0.5 text-base font-bold ${this.getExpiryColorClass(
|
||||
class="inline-flex items-center rounded-full px-2 py-0.5 text-[14px] font-bold ${this.getExpiryColorClass(
|
||||
this.allianceExpirySeconds,
|
||||
)}"
|
||||
>${this.allianceExpiryText}</span
|
||||
@@ -605,7 +610,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
const canEmbargo = this.actions?.interaction?.canEmbargo;
|
||||
|
||||
return html`
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="grid auto-cols-fr grid-flow-col gap-1">
|
||||
${actionButton({
|
||||
onClick: (e: MouseEvent) => this.handleChat(e, my, other),
|
||||
@@ -657,6 +662,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
<ui-divider></ui-divider>
|
||||
|
||||
<div class="grid auto-cols-fr grid-flow-col gap-1">
|
||||
${other !== my
|
||||
@@ -754,80 +760,85 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
|
||||
<div
|
||||
class="fixed inset-0 z-[1001] flex items-center justify-center overflow-auto
|
||||
bg-black/15 backdrop-blur-sm backdrop-brightness-110 pointer-events-auto"
|
||||
bg-black/15 backdrop-brightness-110 pointer-events-auto"
|
||||
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
||||
@wheel=${(e: MouseEvent) => e.stopPropagation()}
|
||||
@click=${() => this.hide()}
|
||||
>
|
||||
<div
|
||||
class="pointer-events-auto max-h-[90vh] overflow-y-auto min-w-[240px] w-auto px-4 py-2"
|
||||
class="pointer-events-auto max-h-[90vh] min-w-[300px] max-w-[400px] px-4 py-2"
|
||||
@click=${(e: MouseEvent) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
class=${`relative mt-2 w-full bg-zinc-900/90 backdrop-blur-sm p-5 shadow-2xl rounded-xl text-zinc-200
|
||||
${other.isTraitor() ? "traitor-ring" : "ring-1 ring-zinc-700"}`}
|
||||
>
|
||||
<!-- Close button -->
|
||||
<button
|
||||
@click=${this.handleClose}
|
||||
class="absolute -top-3 -right-3 flex h-7 w-7 items-center justify-center
|
||||
rounded-full bg-zinc-700 text-white shadow hover:bg-red-500 transition-colors"
|
||||
aria-label=${translateText("common.close") || "Close"}
|
||||
title=${translateText("common.close") || "Close"}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
||||
<div class="relative">
|
||||
<div
|
||||
class="flex flex-col gap-2 font-sans antialiased text-[14px] leading-relaxed"
|
||||
class="absolute inset-2 -z-10 rounded-2xl bg-black/25 backdrop-blur-[2px]"
|
||||
></div>
|
||||
<div
|
||||
class=${`relative w-full bg-zinc-900/95 p-6 rounded-2xl text-zinc-100 overflow-visible shadow-2xl shadow-black/50
|
||||
${other.isTraitor() ? "traitor-ring" : "ring-1 ring-white/5"}`}
|
||||
>
|
||||
<!-- Identity (flag, name, type, traitor, relation) -->
|
||||
<div class="mb-1">${this.renderIdentityRow(other, my)}</div>
|
||||
<!-- Close button -->
|
||||
<button
|
||||
@click=${this.handleClose}
|
||||
class="absolute -top-3 -right-3 flex h-7 w-7 items-center justify-center
|
||||
rounded-full bg-zinc-700 text-white shadow hover:bg-red-500 transition-colors"
|
||||
aria-label=${translateText("common.close") || "Close"}
|
||||
title=${translateText("common.close") || "Close"}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
||||
${this.sendTarget
|
||||
? html`
|
||||
<send-resource-modal
|
||||
.open=${this.sendMode !== "none"}
|
||||
.mode=${this.sendMode}
|
||||
.total=${this.sendMode === "troops"
|
||||
? myTroopsNum
|
||||
: myGoldNum}
|
||||
.uiState=${this.uiState}
|
||||
.myPlayer=${my}
|
||||
.target=${this.sendTarget}
|
||||
.gameView=${this.g}
|
||||
.eventBus=${this.eventBus}
|
||||
.format=${this.sendMode === "troops"
|
||||
? renderTroops
|
||||
: renderNumber}
|
||||
@confirm=${this.confirmSend}
|
||||
@close=${this.closeSend}
|
||||
></send-resource-modal>
|
||||
`
|
||||
: ""}
|
||||
<div
|
||||
class="flex flex-col gap-2 font-sans antialiased text-[14.5px] leading-relaxed"
|
||||
>
|
||||
<!-- Identity (flag, name, type, traitor, relation) -->
|
||||
<div class="mb-1">${this.renderIdentityRow(other, my)}</div>
|
||||
|
||||
<ui-divider></ui-divider>
|
||||
${this.sendTarget
|
||||
? html`
|
||||
<send-resource-modal
|
||||
.open=${this.sendMode !== "none"}
|
||||
.mode=${this.sendMode}
|
||||
.total=${this.sendMode === "troops"
|
||||
? myTroopsNum
|
||||
: myGoldNum}
|
||||
.uiState=${this.uiState}
|
||||
.myPlayer=${my}
|
||||
.target=${this.sendTarget}
|
||||
.gameView=${this.g}
|
||||
.eventBus=${this.eventBus}
|
||||
.format=${this.sendMode === "troops"
|
||||
? renderTroops
|
||||
: renderNumber}
|
||||
@confirm=${this.confirmSend}
|
||||
@close=${this.closeSend}
|
||||
></send-resource-modal>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<!-- Resources -->
|
||||
${this.renderResources(other)}
|
||||
<ui-divider></ui-divider>
|
||||
|
||||
<ui-divider></ui-divider>
|
||||
<!-- Resources -->
|
||||
${this.renderResources(other)}
|
||||
|
||||
<!-- Stats: betrayals / trading -->
|
||||
${this.renderStats(other, my)}
|
||||
<ui-divider></ui-divider>
|
||||
|
||||
<ui-divider></ui-divider>
|
||||
<!-- Stats: betrayals / trading -->
|
||||
${this.renderStats(other, my)}
|
||||
|
||||
<!-- Alliances list -->
|
||||
${this.renderAlliances(other)}
|
||||
<ui-divider></ui-divider>
|
||||
|
||||
<!-- Alliance time remaining -->
|
||||
${this.renderAllianceExpiry()}
|
||||
<!-- Alliances list -->
|
||||
${this.renderAlliances(other)}
|
||||
|
||||
<ui-divider></ui-divider>
|
||||
<!-- Alliance time remaining -->
|
||||
${this.renderAllianceExpiry()}
|
||||
|
||||
<!-- Actions -->
|
||||
${this.renderActions(my, other)}
|
||||
<ui-divider class="mt-1"></ui-divider>
|
||||
|
||||
<!-- Actions -->
|
||||
${this.renderActions(my, other)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -244,19 +244,22 @@ export class SendResourceModal extends LitElement {
|
||||
private renderHeader() {
|
||||
const name = this.target?.name?.() ?? "";
|
||||
return html`
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div class="mb-3 flex items-center justify-between relative">
|
||||
<h2
|
||||
id="send-title"
|
||||
class="text-lg font-semibold tracking-tight text-zinc-100"
|
||||
>
|
||||
${this.heading ?? this.i18n.title(name)}
|
||||
</h2>
|
||||
<!-- Close button -->
|
||||
<button
|
||||
class="rounded-md px-2 text-2xl leading-none text-zinc-300 hover:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-white/30"
|
||||
type="button"
|
||||
@click=${() => this.closeModal()}
|
||||
class="absolute -top-3 -right-3 flex h-7 w-7 items-center justify-center rounded-full bg-zinc-700 text-white shadow hover:bg-red-500 transition-colors focus-visible:ring-2 focus-visible:ring-white/30 focus:outline-none"
|
||||
aria-label=${this.i18n.closeLabel()}
|
||||
title=${this.i18n.closeLabel()}
|
||||
>
|
||||
×
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -264,7 +267,6 @@ export class SendResourceModal extends LitElement {
|
||||
|
||||
private renderAvailable() {
|
||||
const total = this.getTotalNumber();
|
||||
const cap = this.getCapacityLeft();
|
||||
|
||||
return html`
|
||||
<div class="mb-4 pb-3 border-b border-zinc-800">
|
||||
@@ -277,21 +279,6 @@ export class SendResourceModal extends LitElement {
|
||||
<span class="opacity-90">${this.i18n.availableChip()}</span>
|
||||
<span class="font-mono tabular-nums">${this.format(total)}</span>
|
||||
</span>
|
||||
|
||||
${cap !== null
|
||||
? html`
|
||||
<!-- Cap -->
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-full bg-amber-500/10 px-2 py-0.5 ring-1 ring-amber-400/40 text-amber-200"
|
||||
title=${this.i18n.capTooltip()}
|
||||
>
|
||||
<span class="opacity-90">${this.i18n.cap()}</span>
|
||||
<span class="font-mono tabular-nums"
|
||||
>${this.format(cap)}</span
|
||||
>
|
||||
</span>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -554,9 +541,11 @@ export class SendResourceModal extends LitElement {
|
||||
const allowed = this.limitAmount(this.sendAmount);
|
||||
|
||||
return html`
|
||||
<div class="fixed inset-0 z-[1100] flex items-center justify-center p-4">
|
||||
<div
|
||||
class="absolute inset-0 z-[1100] flex items-center justify-center p-4"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-0 bg-black/60 backdrop-blur-sm rounded-2xl"
|
||||
class="absolute inset-0 bg-black/60 rounded-2xl"
|
||||
@click=${() => this.closeModal()}
|
||||
></div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user