diff --git a/resources/lang/en.json b/resources/lang/en.json
index 46f64046d..936f531f5 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -394,6 +394,7 @@
},
"effects": {
"button_title": "Pick an effect!",
+ "search": "Search...",
"title": "Effects",
"type": {
"transportShipTrail": "Boat Trail"
diff --git a/src/client/EffectsModal.ts b/src/client/EffectsModal.ts
index f5aa0591d..8a8df80fd 100644
--- a/src/client/EffectsModal.ts
+++ b/src/client/EffectsModal.ts
@@ -15,6 +15,11 @@ export class EffectsModal extends BaseModal {
@state() private cosmetics: Cosmetics | null = null;
@state() private userMeResponse: UserMeResponse | false = false;
+ @state() private search = "";
+
+ private handleSearch(event: Event) {
+ this.search = (event.target as HTMLInputElement).value;
+ }
connectedCallback() {
super.connectedCallback();
@@ -43,6 +48,19 @@ export class EffectsModal extends BaseModal {
ariaLabel: translateText("common.back"),
rightContent: html``,
})}
+
+
+
+
`;
}
@@ -66,6 +84,7 @@ export class EffectsModal extends BaseModal {
mode="select"
.cosmetics=${this.cosmetics}
.userMeResponse=${this.userMeResponse}
+ .search=${this.search}
>
`;
diff --git a/src/client/components/EffectsGrid.ts b/src/client/components/EffectsGrid.ts
index 9b5d391a4..cc9d1550a 100644
--- a/src/client/components/EffectsGrid.ts
+++ b/src/client/components/EffectsGrid.ts
@@ -16,6 +16,7 @@ import {
purchaseCosmetic,
resolveCosmetics,
ResolvedCosmetic,
+ translateCosmetic,
} from "../Cosmetics";
import { translateText } from "../Utils";
import "./CosmeticButton";
@@ -47,6 +48,7 @@ export class EffectsGrid extends LitElement {
false;
@property({ type: String }) mode: "select" | "purchase" = "select";
@property({ attribute: false }) affiliateCode: string | null = null;
+ @property({ type: String }) search = "";
private userSettings = new UserSettings();
private _onChange = () => this.requestUpdate();
@@ -77,6 +79,17 @@ export class EffectsGrid extends LitElement {
this.requestUpdate();
}
+ private matchesSearch(r: ResolvedCosmetic): boolean {
+ const q = this.search.trim().toLowerCase();
+ if (!q) return true;
+ const name = (r.cosmetic as Effect | null)?.name;
+ if (!name) return false;
+ return (
+ name.toLowerCase().includes(q) ||
+ translateCosmetic("effects", name).toLowerCase().includes(q)
+ );
+ }
+
private itemsForType(
all: ResolvedCosmetic[],
effectType: EffectType,
@@ -85,15 +98,15 @@ export class EffectsGrid extends LitElement {
(r) =>
r.type === "effect" &&
r.cosmetic !== null &&
- r.effectType === effectType,
+ r.effectType === effectType &&
+ this.matchesSearch(r),
);
if (this.mode === "purchase") {
return ofType.filter((r) => r.relationship === "purchasable");
}
- return [
- noneTile(effectType),
- ...ofType.filter((r) => r.relationship === "owned"),
- ];
+ const owned = ofType.filter((r) => r.relationship === "owned");
+ // The Default tile has no name to match — hide it while searching.
+ return this.search.trim() ? owned : [noneTile(effectType), ...owned];
}
private renderTile(