Rename shop items to plain sprite labels and improve thumbnail visibility.
Use code-side display names in the shop UI so labels stay correct even when DB rows lag, and add a neutral thumbnail glow so dark colors remain visible.
This commit is contained in:
@@ -262,6 +262,19 @@
|
||||
@apply border-2 border-border bg-card px-2 py-1 font-pixel leading-snug text-card-foreground;
|
||||
box-shadow: var(--retro-shadow-sm);
|
||||
}
|
||||
|
||||
.shop-item-thumbnail {
|
||||
background: hsl(var(--muted) / 0.22);
|
||||
}
|
||||
|
||||
.shop-item-thumbnail-glow {
|
||||
background: radial-gradient(
|
||||
circle at 50% 58%,
|
||||
hsl(52 55% 90% / 0.55) 0%,
|
||||
hsl(48 30% 88% / 0.18) 45%,
|
||||
transparent 72%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes emotion-pop {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "@/lib/shop-storage";
|
||||
import {
|
||||
getLayerLabel,
|
||||
getShopItemDisplayName,
|
||||
type ShopItemRecord,
|
||||
type ShopLayerId,
|
||||
} from "@/lib/shop-catalog";
|
||||
@@ -103,6 +104,8 @@ function ShopItemCard({
|
||||
? null
|
||||
: getShopItemThumbnailColors(item, characterColors);
|
||||
|
||||
const displayName = getShopItemDisplayName(item);
|
||||
|
||||
const actionLabel = (() => {
|
||||
if (isPending) {
|
||||
return owned ? "Updating..." : "Processing...";
|
||||
@@ -123,27 +126,33 @@ function ShopItemCard({
|
||||
<Card className="flex h-full flex-col overflow-hidden">
|
||||
<CardContent className="flex h-full flex-col gap-3 p-3">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<div className="flex size-12 shrink-0 items-center justify-center overflow-hidden border-2 border-border bg-zinc-950/80 p-1">
|
||||
{room ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={item.image_path || room.sceneImage}
|
||||
alt=""
|
||||
className="size-full object-cover image-pixelated"
|
||||
/>
|
||||
) : thumbnailColors ? (
|
||||
<TintedSpriteIcon
|
||||
src={item.image_path}
|
||||
color={thumbnailColors.color}
|
||||
skinColor={thumbnailColors.skinColor}
|
||||
size={32}
|
||||
/>
|
||||
<div className="shop-item-thumbnail relative flex size-12 shrink-0 items-center justify-center overflow-hidden border-2 border-border p-1">
|
||||
{thumbnailColors || room ? (
|
||||
<div aria-hidden className="shop-item-thumbnail-glow pointer-events-none absolute inset-0" />
|
||||
) : null}
|
||||
<div className="relative z-10 flex size-full items-center justify-center">
|
||||
{room ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={item.image_path || room.sceneImage}
|
||||
alt=""
|
||||
className="size-full object-cover image-pixelated"
|
||||
/>
|
||||
) : thumbnailColors ? (
|
||||
<TintedSpriteIcon
|
||||
src={item.image_path}
|
||||
color={thumbnailColors.color}
|
||||
skinColor={thumbnailColors.skinColor}
|
||||
size={32}
|
||||
className="drop-shadow-[0_0_3px_hsl(52_55%_90%_/_0.85)]"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-1 items-start justify-between gap-2">
|
||||
<p className="min-w-0 pt-1 text-sm font-medium leading-snug">
|
||||
{item.name}
|
||||
{displayName}
|
||||
</p>
|
||||
<ShopItemPriceBadge
|
||||
price={item.price}
|
||||
@@ -249,7 +258,7 @@ export function ShopContent() {
|
||||
try {
|
||||
const item = await purchaseShopItem(itemId);
|
||||
await equipShopItem(itemId);
|
||||
toast(`Purchased and equipped ${item.name}.`, "success");
|
||||
toast(`Purchased and equipped ${getShopItemDisplayName(item)}.`, "success");
|
||||
await refreshShop();
|
||||
} catch (purchaseError) {
|
||||
toast(
|
||||
@@ -274,10 +283,10 @@ export function ShopContent() {
|
||||
|
||||
if (item.type !== "room" && isEquipped) {
|
||||
await unequipShopItem(item.id);
|
||||
toast(`Unequipped ${item.name}.`, "default");
|
||||
toast(`Unequipped ${getShopItemDisplayName(item)}.`, "default");
|
||||
} else if (!isEquipped) {
|
||||
await equipShopItem(item.id);
|
||||
toast(`Equipped ${item.name}.`, "success");
|
||||
toast(`Equipped ${getShopItemDisplayName(item)}.`, "success");
|
||||
}
|
||||
|
||||
await refreshShop();
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
buildShopItemPreviewCustomization,
|
||||
getShopPreviewRoomId,
|
||||
} from "@/lib/shop-preview";
|
||||
import type { ShopItemRecord } from "@/lib/shop-catalog";
|
||||
import { getShopItemDisplayName, type ShopItemRecord } from "@/lib/shop-catalog";
|
||||
import { routes } from "@/lib/routes";
|
||||
|
||||
type ShopItemPreviewModalProps = {
|
||||
@@ -89,7 +89,7 @@ export function ShopItemPreviewModal({
|
||||
>
|
||||
Preview on your bit
|
||||
</p>
|
||||
<p className="mt-1 truncate text-sm">{item.name}</p>
|
||||
<p className="mt-1 truncate text-sm">{getShopItemDisplayName(item)}</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -61,25 +61,25 @@ function presetFromHex(id: string, name: string, hex: string): ColorPreset {
|
||||
|
||||
export const VARIANT_DISPLAY_NAMES: Record<string, string> = {
|
||||
"skin-1": "Default Form",
|
||||
"pants-1": "Slim Fit Jeans",
|
||||
"pants-2": "Rolled Cuff Shorts",
|
||||
"pants-3": "Pleated Skirt",
|
||||
"shoes-1": "Canvas Sneakers",
|
||||
"shoes-2": "Chunky High-Tops",
|
||||
"shoes-3": "Neon Glow Runners",
|
||||
"torso-1": "Crew Neck Tee",
|
||||
"torso-2": "Zip-Up Hoodie",
|
||||
"torso-3": "Button-Up Shirt",
|
||||
"torso-4": "Crimson Blouse",
|
||||
"eyes-1": "Round Gentle Eyes",
|
||||
"eyes-2": "Starry Spark Eyes",
|
||||
"head-1": "Spiked Mohawk",
|
||||
"head-2": "Cozy Knit Beanie",
|
||||
"head-3": "Retro Snapback Cap",
|
||||
"head-4": "Long Hair",
|
||||
"head-5": "Shoulder-Length Hair",
|
||||
"head-6": "Flowing Side Locks",
|
||||
"head-7": "Messy Shag Cut",
|
||||
"pants-1": "Shorts",
|
||||
"pants-2": "Pants",
|
||||
"pants-3": "Skirt",
|
||||
"shoes-1": "Boots",
|
||||
"shoes-2": "Shoes",
|
||||
"shoes-3": "Loafers",
|
||||
"torso-1": "T-Shirt",
|
||||
"torso-2": "Button-up",
|
||||
"torso-3": "Vest",
|
||||
"torso-4": "Robe",
|
||||
"eyes-1": "Eyes",
|
||||
"eyes-2": "Eyelashes",
|
||||
"head-1": "Short",
|
||||
"head-2": "Short-medium",
|
||||
"head-3": "Mohawk",
|
||||
"head-4": "Long",
|
||||
"head-5": "Pixie",
|
||||
"head-6": "Middle-part",
|
||||
"head-7": "Bun",
|
||||
};
|
||||
|
||||
export function getVariantDisplayName(variantId: string, fallbackLabel = variantId) {
|
||||
|
||||
@@ -19,25 +19,25 @@ export const ROOM_SCENE_IMAGE_PATH = (id: RoomBackgroundId) =>
|
||||
export const ROOM_BACKGROUNDS: RoomBackground[] = [
|
||||
{
|
||||
id: "room-1",
|
||||
name: "Sunny Field",
|
||||
name: "Plains",
|
||||
free: true,
|
||||
sceneImage: ROOM_SCENE_IMAGE_PATH("room-1"),
|
||||
},
|
||||
{
|
||||
id: "room-2",
|
||||
name: "Golden Dusk",
|
||||
name: "Sunset",
|
||||
free: true,
|
||||
sceneImage: ROOM_SCENE_IMAGE_PATH("room-2"),
|
||||
},
|
||||
{
|
||||
id: "room-3",
|
||||
name: "Starlit Night",
|
||||
name: "Forest",
|
||||
free: false,
|
||||
sceneImage: ROOM_SCENE_IMAGE_PATH("room-3"),
|
||||
},
|
||||
{
|
||||
id: "room-4",
|
||||
name: "Mystic Forest",
|
||||
name: "Night",
|
||||
free: false,
|
||||
sceneImage: ROOM_SCENE_IMAGE_PATH("room-4"),
|
||||
},
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { isShopOnlyStyleVariant } from "@/lib/character/variant-access";
|
||||
import type { CharacterLayerId } from "@/lib/character/presets";
|
||||
import { CHARACTER_LAYERS, NONE_VARIANT_ID } from "@/lib/character/presets";
|
||||
import { getShopRoomBackgrounds } from "@/lib/room-backgrounds";
|
||||
import {
|
||||
CHARACTER_LAYERS,
|
||||
getVariantDisplayName,
|
||||
NONE_VARIANT_ID,
|
||||
} from "@/lib/character/presets";
|
||||
import { getRoomBackground, getShopRoomBackgrounds } from "@/lib/room-backgrounds";
|
||||
|
||||
export type ShopLayerId = Exclude<CharacterLayerId, "skin">;
|
||||
|
||||
@@ -95,6 +99,14 @@ export function getLayerLabel(layerId: ShopLayerId) {
|
||||
return CHARACTER_LAYERS.find((layer) => layer.id === layerId)?.label ?? layerId;
|
||||
}
|
||||
|
||||
export function getShopItemDisplayName(item: ShopItemRecord): string {
|
||||
if (item.type === "room") {
|
||||
return getRoomBackground(item.id).name;
|
||||
}
|
||||
|
||||
return getVariantDisplayName(item.id, item.name);
|
||||
}
|
||||
|
||||
export function findShopItemById(
|
||||
items: readonly ShopItemRecord[],
|
||||
itemId: string,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Align shop item display names with sprite-based labels.
|
||||
|
||||
update public.shop_items set name = 'Plains' where id = 'room-1';
|
||||
update public.shop_items set name = 'Sunset' where id = 'room-2';
|
||||
update public.shop_items set name = 'Forest' where id = 'room-3';
|
||||
update public.shop_items set name = 'Night' where id = 'room-4';
|
||||
|
||||
update public.shop_items set name = 'Eyes' where id = 'eyes-1';
|
||||
update public.shop_items set name = 'Eyelashes' where id = 'eyes-2';
|
||||
|
||||
update public.shop_items set name = 'Short' where id = 'head-1';
|
||||
update public.shop_items set name = 'Short-medium' where id = 'head-2';
|
||||
update public.shop_items set name = 'Mohawk' where id = 'head-3';
|
||||
update public.shop_items set name = 'Long' where id = 'head-4';
|
||||
update public.shop_items set name = 'Pixie' where id = 'head-5';
|
||||
update public.shop_items set name = 'Middle-part' where id = 'head-6';
|
||||
update public.shop_items set name = 'Bun' where id = 'head-7';
|
||||
|
||||
update public.shop_items set name = 'T-Shirt' where id = 'torso-1';
|
||||
update public.shop_items set name = 'Button-up' where id = 'torso-2';
|
||||
update public.shop_items set name = 'Vest' where id = 'torso-3';
|
||||
update public.shop_items set name = 'Robe' where id = 'torso-4';
|
||||
|
||||
update public.shop_items set name = 'Shorts' where id = 'pants-1';
|
||||
update public.shop_items set name = 'Pants' where id = 'pants-2';
|
||||
update public.shop_items set name = 'Skirt' where id = 'pants-3';
|
||||
|
||||
update public.shop_items set name = 'Boots' where id = 'shoes-1';
|
||||
update public.shop_items set name = 'Shoes' where id = 'shoes-2';
|
||||
update public.shop_items set name = 'Loafers' where id = 'shoes-3';
|
||||
@@ -1,40 +1,18 @@
|
||||
-- Character layer styles sold in the shop (id = variant id, type = layer id).
|
||||
|
||||
delete from public.user_items
|
||||
where item_id in ('hat_blue', 'glasses_round', 'room_sunset', 'room-space', 'room-neon');
|
||||
|
||||
delete from public.shop_items
|
||||
where id in (
|
||||
'hat_blue',
|
||||
'glasses_round',
|
||||
'room_sunset',
|
||||
'room-space',
|
||||
'room-neon',
|
||||
'pants-1',
|
||||
'pants-2',
|
||||
'shoes-1',
|
||||
'shoes-2',
|
||||
'torso-1',
|
||||
'torso-2',
|
||||
'eyes-1',
|
||||
'eyes-2',
|
||||
'head-1',
|
||||
'head-2'
|
||||
);
|
||||
-- Shop-only catalog items (variant ids 3+ and paid rooms).
|
||||
|
||||
insert into public.shop_items (id, name, type, price, image_path)
|
||||
values
|
||||
('pants-3', 'Pleated Skirt', 'pants', 30, '/character/pants/pants-3.png'),
|
||||
('shoes-3', 'Neon Glow Runners', 'shoes', 30, '/character/shoes/shoes-3.png'),
|
||||
('torso-3', 'Button-Up Shirt', 'torso', 35, '/character/torso/torso-3.png'),
|
||||
('torso-4', 'Crimson Blouse', 'torso', 40, '/character/torso/torso-4.png'),
|
||||
('head-3', 'Retro Snapback Cap', 'head', 40, '/character/head/head-3.png'),
|
||||
('head-4', 'Long Hair', 'head', 45, '/character/head/head-4.png'),
|
||||
('head-5', 'Shoulder-Length Hair', 'head', 50, '/character/head/head-5.png'),
|
||||
('head-6', 'Flowing Side Locks', 'head', 55, '/character/head/head-6.png'),
|
||||
('head-7', 'Messy Shag Cut', 'head', 60, '/character/head/head-7.png'),
|
||||
('room-3', 'Starlit Night', 'room', 40, '/room/room-3.png'),
|
||||
('room-4', 'Mystic Forest', 'room', 50, '/room/room-4.png')
|
||||
('head-3', 'Mohawk', 'head', 40, '/character/head/head-3.png'),
|
||||
('head-4', 'Long', 'head', 45, '/character/head/head-4.png'),
|
||||
('head-5', 'Pixie', 'head', 50, '/character/head/head-5.png'),
|
||||
('head-6', 'Middle-part', 'head', 55, '/character/head/head-6.png'),
|
||||
('head-7', 'Bun', 'head', 60, '/character/head/head-7.png'),
|
||||
('torso-3', 'Vest', 'torso', 35, '/character/torso/torso-3.png'),
|
||||
('torso-4', 'Robe', 'torso', 40, '/character/torso/torso-4.png'),
|
||||
('pants-3', 'Skirt', 'pants', 30, '/character/pants/pants-3.png'),
|
||||
('shoes-3', 'Loafers', 'shoes', 30, '/character/shoes/shoes-3.png'),
|
||||
('room-3', 'Forest', 'room', 40, '/room/room-3.png'),
|
||||
('room-4', 'Night', 'room', 50, '/room/room-4.png')
|
||||
on conflict (id) do update
|
||||
set
|
||||
name = excluded.name,
|
||||
|
||||
Reference in New Issue
Block a user