import { html, LitElement, nothing, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import {
Flag,
Pack,
Pattern,
Skin,
Subscription,
} from "../../core/CosmeticSchemas";
import { PlayerPattern } from "../../core/Schemas";
import {
PaymentMethod,
ResolvedCosmetic,
translateCosmetic,
} from "../Cosmetics";
import { translateText } from "../Utils";
import "./CapIcon";
import "./CosmeticContainer";
import "./CosmeticInfo";
import { renderPatternPreview } from "./PatternPreview";
import "./PlutoniumIcon";
import { DEFAULT_DOLLAR_LABEL_KEY } from "./PurchaseButton";
@customElement("cosmetic-button")
export class CosmeticButton extends LitElement {
@property({ type: Object })
resolved!: ResolvedCosmetic;
@property({ type: Boolean })
selected: boolean = false;
@property({ type: Function })
onSelect?: (resolved: ResolvedCosmetic) => void;
@property({ type: Function })
onPurchase?: (resolved: ResolvedCosmetic, method: PaymentMethod) => void;
/** True if the user already has a subscription (any tier). */
@property({ type: Boolean })
userHasSubscription: boolean = false;
/** Colour variants of one pattern; 2+ become clickable swatches. */
@property({ attribute: false })
variants?: ResolvedCosmetic[];
/** Key of the swatch the user has picked; null until they pick one. */
@state() private activeVariantKey: string | null = null;
/** The variant currently previewed/purchased: picked swatch, else fallback. */
private get activeResolved(): ResolvedCosmetic {
const variants = this.variants;
if (variants && variants.length > 0) {
return (
variants.find((v) => v.key === this.activeVariantKey) ?? variants[0]
);
}
return this.resolved;
}
createRenderRoot() {
return this;
}
private handleClick() {
this.onSelect?.(this.activeResolved);
}
private get displayName(): string {
const c = this.activeResolved.cosmetic;
if (c === null) {
return translateText("territory_patterns.pattern.default");
}
if (
this.activeResolved.type === "pattern" ||
this.activeResolved.type === "skin"
) {
return translateCosmetic("territory_patterns.pattern", c.name);
}
if (this.activeResolved.type === "pack") {
return (c as Pack).displayName;
}
if (this.activeResolved.type === "subscription") {
return translateCosmetic("subscriptions", c.name);
}
return translateCosmetic("flags", c.name);
}
/** True when the variants carry colour palettes to show as swatches. */
private get hasColorRow(): boolean {
return (
this.variants !== undefined &&
this.variants.some((v) => v.colorPalette !== null)
);
}
/** Row of clickable split-circle colour swatches, one per palette. */
private renderColorSwatches(): TemplateResult | typeof nothing {
if (!this.hasColorRow) {
return nothing;
}
const activeKey = this.activeResolved.key;
return html`
${this.variants!.map((v) => {
const primary = v.colorPalette?.primaryColor ?? "#ffffff";
const secondary = v.colorPalette?.secondaryColor ?? "#000000";
const isActive = v.key === activeKey;
const label = v.colorPalette
? translateCosmetic(
"territory_patterns.color_palette",
v.colorPalette.name,
)
: "";
const outline = isActive
? "0 0 0 2px rgba(255,255,255,0.95)"
: "inset 0 0 0 1px rgba(255,255,255,0.2), 0 0 0 1px rgba(0,0,0,0.45)";
return html``;
})}
`;
}
private renderPreview(): TemplateResult {
if (this.activeResolved.type === "pattern") {
const c = this.activeResolved.cosmetic;
const playerPattern: PlayerPattern | null =
c === null
? null
: {
name: c.name,
patternData: (c as Pattern).pattern,
colorPalette: this.activeResolved.colorPalette ?? undefined,
};
return renderPatternPreview(playerPattern, 150, 150);
}
if (this.activeResolved.type === "skin") {
const c = this.activeResolved.cosmetic as Skin | null;
if (c === null) {
// "Default" tile — visually consistent with pattern's default tile.
return html`
${translateText("territory_patterns.pattern.default")}
`;
}
return html`
`;
}
if (this.activeResolved.type === "pack") {
const pack = this.activeResolved.cosmetic as Pack;
const isHard = pack.currency === "hard";
const icon = isHard
? html``
: html``;
const colorClass = isHard ? "text-green-400" : "text-amber-700";
const currencyKey = isHard ? "cosmetics.hard" : "cosmetics.soft";
return html`
${icon}
${pack.amount.toLocaleString()}
${translateText(currencyKey)}
${pack.bonusAmount > 0
? html`
${translateText("cosmetics.free", {
numFree: pack.bonusAmount.toLocaleString(),
})}
`
: nothing}
`;
}
if (this.activeResolved.type === "subscription") {
const sub = this.activeResolved.cosmetic as Subscription;
return html`
${sub.description}
${sub.dailyHardCurrency.toLocaleString()}
${translateText("cosmetics.per_day")}
${sub.dailySoftCurrency.toLocaleString()}
${translateText("cosmetics.per_day")}
`;
}
const c = this.activeResolved.cosmetic as Flag;
return html`
{
const img = e.currentTarget as HTMLImageElement;
const fallback = "/flags/xx.svg";
if (img.src && !img.src.endsWith(fallback)) {
img.src = fallback;
}
}}
/>`;
}
render() {
const active = this.activeResolved;
const c = active.cosmetic;
const priced = c as Pattern | Skin | Flag | Pack | null;
const priceHard = priced?.priceHard;
const priceSoft = priced?.priceSoft;
const artist = priced?.artist;
const isPurchasable = active.relationship === "purchasable";
const type = active.type;
const isPattern = type === "pattern";
const isSkin = type === "skin";
const isOwnedSubscription =
type === "subscription" && active.relationship === "owned";
const dollarLabelKey =
type === "subscription"
? this.userHasSubscription
? "store.switch_button"
: "store.subscribe_button"
: DEFAULT_DOLLAR_LABEL_KEY;
const priceSuffix =
type === "subscription" ? translateText("store.price_per_month") : "";
const sizeClass = type === "flag" ? "gap-1 p-1.5 w-36" : "gap-2 p-3 w-48";
const crazygamesClass = isPattern || isSkin ? "no-crazygames " : "";
// Colour-row tiles top-align so the skin box, swatches and price buttons
// line up across the grid; other tiles fill height with justify-between.
const hasColorRow = this.hasColorRow;
return html`
this.onPurchase?.(this.activeResolved, "dollar")
: undefined}
.onPurchaseHard=${isPurchasable && priceHard !== undefined
? () => this.onPurchase?.(this.activeResolved, "hard")
: undefined}
.onPurchaseSoft=${isPurchasable && priceSoft !== undefined
? () => this.onPurchase?.(this.activeResolved, "soft")
: undefined}
.name=${this.displayName}
>
${this.renderColorSwatches()}
${isOwnedSubscription
? html`
${translateText("store.current_plan")}
`
: nothing}
`;
}
}