-
+
+ `/room/${id}.png`;
export const ROOM_BACKGROUNDS: RoomBackground[] = [
-
{
-
- id: "room-day",
-
+ id: "room-1",
name: "Sunny Field",
-
free: true,
-
- previewClassName: "bg-gradient-to-b from-sky-400 via-sky-300 to-lime-400",
-
- layers: [
-
- { id: "sky", className: "bg-gradient-to-b from-sky-500 via-sky-300 to-sky-200", speed: 0.15 },
-
- { id: "clouds", className: "bg-[radial-gradient(circle_at_20%_30%,rgba(255,255,255,0.85)_0%,transparent_35%),radial-gradient(circle_at_70%_20%,rgba(255,255,255,0.7)_0%,transparent_30%)]", speed: 0.35 },
-
- { id: "hills", className: "bg-[linear-gradient(to_top,#3d8b37_0%,#3d8b37_40%,transparent_40%),radial-gradient(ellipse_120%_80%_at_50%_100%,#2f6f2c_0%,transparent_70%)]", speed: 0.55 },
-
- { id: "grass", className: "bg-[repeating-linear-gradient(90deg,rgba(0,0,0,0.06)_0_2px,transparent_2px_6px)] opacity-40", speed: 0.75 },
-
- ],
-
+ sceneImage: ROOM_SCENE_IMAGE_PATH("room-1"),
},
-
{
-
- id: "room-dusk",
-
+ id: "room-2",
name: "Golden Dusk",
-
free: true,
-
- previewClassName: "bg-gradient-to-b from-orange-400 via-pink-400 to-indigo-700",
-
- layers: [
-
- { id: "sky", className: "bg-gradient-to-b from-orange-500 via-rose-400 to-indigo-800", speed: 0.12 },
-
- { id: "sun", className: "bg-[radial-gradient(circle_at_50%_85%,rgba(255,220,120,0.95)_0%,rgba(255,160,80,0.35)_18%,transparent_45%)]", speed: 0.25 },
-
- { id: "hills", className: "bg-[radial-gradient(ellipse_130%_70%_at_50%_100%,#1f2937_0%,transparent_72%)]", speed: 0.5 },
-
- { id: "stars", className: "bg-[radial-gradient(circle_at_12%_18%,rgba(255,255,255,0.8)_1px,transparent_1px),radial-gradient(circle_at_25%_42%,rgba(255,255,255,0.7)_1px,transparent_1px),radial-gradient(circle_at_38%_12%,rgba(255,255,255,0.9)_1px,transparent_1px),radial-gradient(circle_at_52%_35%,rgba(255,255,255,0.65)_1px,transparent_1px),radial-gradient(circle_at_68%_22%,rgba(255,255,255,0.75)_1px,transparent_1px),radial-gradient(circle_at_82%_48%,rgba(255,255,255,0.7)_1px,transparent_1px),radial-gradient(circle_at_18%_65%,rgba(255,255,255,0.85)_1px,transparent_1px),radial-gradient(circle_at_45%_58%,rgba(255,255,255,0.6)_1px,transparent_1px),radial-gradient(circle_at_72%_72%,rgba(255,255,255,0.7)_1px,transparent_1px),radial-gradient(circle_at_90%_15%,rgba(255,255,255,0.8)_1px,transparent_1px),radial-gradient(circle_at_8%_82%,rgba(255,255,255,0.65)_1px,transparent_1px),radial-gradient(circle_at_58%_88%,rgba(255,255,255,0.75)_1px,transparent_1px)] opacity-20", speed: 0.65 },
-
- ],
-
+ sceneImage: ROOM_SCENE_IMAGE_PATH("room-2"),
},
-
{
-
- id: "room-night",
-
+ id: "room-3",
name: "Starlit Night",
-
free: false,
-
- previewClassName: "bg-gradient-to-b from-[#1e1b4b] via-[#4c1d95] to-[#0f172a]",
-
- sceneImage: "/shop/room-night.svg",
-
- layers: [],
-
+ sceneImage: ROOM_SCENE_IMAGE_PATH("room-3"),
},
-
{
-
- id: "room-forest",
-
+ id: "room-4",
name: "Mystic Forest",
-
free: false,
-
- previewClassName: "bg-gradient-to-b from-[#064e3b] via-[#14532d] to-[#052e16]",
-
- sceneImage: "/shop/room-forest.svg",
-
- layers: [],
-
+ sceneImage: ROOM_SCENE_IMAGE_PATH("room-4"),
},
-
];
-
-
const ROOM_MAP = new Map(ROOM_BACKGROUNDS.map((room) => [room.id, room]));
-
-
-const LEGACY_ROOM_IDS = new Set(["room-space", "room-neon"]);
-
-
+const LEGACY_ROOM_ID_MAP: Record = {
+ "room-day": "room-1",
+ "room-dusk": "room-2",
+ "room-night": "room-3",
+ "room-forest": "room-4",
+ "room-space": "room-1",
+ "room-neon": "room-1",
+};
export function getRoomBackground(id: string | null | undefined): RoomBackground {
-
- if (id && LEGACY_ROOM_IDS.has(id)) {
-
- return ROOM_MAP.get(DEFAULT_ROOM_BACKGROUND)!;
-
- }
-
-
-
- return ROOM_MAP.get((id as RoomBackgroundId) ?? DEFAULT_ROOM_BACKGROUND) ?? ROOM_MAP.get(DEFAULT_ROOM_BACKGROUND)!;
-
+ const normalized = normalizeRoomBackgroundId(id);
+ return ROOM_MAP.get(normalized) ?? ROOM_MAP.get(DEFAULT_ROOM_BACKGROUND)!;
}
-
-
export function isValidRoomBackgroundId(id: string): id is RoomBackgroundId {
-
return ROOM_MAP.has(id as RoomBackgroundId);
-
}
-
-
-export function normalizeRoomBackgroundId(id: string | null | undefined): RoomBackgroundId {
-
- if (id && LEGACY_ROOM_IDS.has(id)) {
-
+export function normalizeRoomBackgroundId(
+ id: string | null | undefined,
+): RoomBackgroundId {
+ if (!id) {
return DEFAULT_ROOM_BACKGROUND;
-
}
-
-
- if (id && isValidRoomBackgroundId(id)) {
-
+ if (isValidRoomBackgroundId(id)) {
return id;
-
}
-
-
- return DEFAULT_ROOM_BACKGROUND;
-
+ return LEGACY_ROOM_ID_MAP[id] ?? DEFAULT_ROOM_BACKGROUND;
}
-
-
export function isRoomBackgroundUnlocked(
-
roomId: RoomBackgroundId,
-
ownedItemIds: readonly string[],
-
): boolean {
-
const room = getRoomBackground(roomId);
-
if (room.free) {
-
return true;
-
}
-
-
- return ownedItemIds.includes(roomId);
-
+ return ownedItemIds.some(
+ (ownedId) => normalizeRoomBackgroundId(ownedId) === roomId,
+ );
}
-
-
export function getShopRoomBackgrounds() {
-
return ROOM_BACKGROUNDS.filter((room) => !room.free).map((room) => ({
-
id: room.id,
-
name: room.name,
-
type: "room" as const,
-
- price: room.id === "room-night" ? 40 : 50,
-
- image_path: room.sceneImage ?? "",
-
+ price: room.id === "room-3" ? 40 : 50,
+ image_path: room.sceneImage,
}));
-
}
-
-
diff --git a/lib/shop-storage.ts b/lib/shop-storage.ts
index df105da..06ed7aa 100644
--- a/lib/shop-storage.ts
+++ b/lib/shop-storage.ts
@@ -1,5 +1,6 @@
import { notifyHabitPetDataUpdated } from "@/lib/app-events";
import { getCoins } from "@/lib/avatar-progression-storage";
+import { normalizeRoomBackgroundId } from "@/lib/room-backgrounds";
import { type ShopItemRecord } from "@/lib/shop-catalog";
import { createClient } from "@/lib/supabase/client";
@@ -103,7 +104,7 @@ export async function getEquippedRoomBackground(): Promise {
const userId = await getAuthenticatedUserId();
if (!userId) {
- return "room-day";
+ return "room-1";
}
const supabase = createClient();
@@ -115,13 +116,13 @@ export async function getEquippedRoomBackground(): Promise {
if (error) {
if (/room_background/i.test(error.message)) {
- return "room-day";
+ return "room-1";
}
throw new Error(error.message);
}
- return (data?.room_background as string | null) ?? "room-day";
+ return normalizeRoomBackgroundId(data?.room_background as string | null);
}
export async function equipShopItem(itemId: string) {
diff --git a/public/room/room-1.png b/public/room/room-1.png
new file mode 100644
index 0000000..805250d
Binary files /dev/null and b/public/room/room-1.png differ
diff --git a/public/room/room-2.png b/public/room/room-2.png
new file mode 100644
index 0000000..de72077
Binary files /dev/null and b/public/room/room-2.png differ
diff --git a/public/room/room-3.png b/public/room/room-3.png
new file mode 100644
index 0000000..9705a9e
Binary files /dev/null and b/public/room/room-3.png differ
diff --git a/public/room/room-4.png b/public/room/room-4.png
new file mode 100644
index 0000000..8817955
Binary files /dev/null and b/public/room/room-4.png differ
diff --git a/public/shop/room-forest.svg b/public/shop/room-forest.svg
deleted file mode 100644
index af6b333..0000000
--- a/public/shop/room-forest.svg
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/shop/room-night.svg b/public/shop/room-night.svg
deleted file mode 100644
index 8f209e4..0000000
--- a/public/shop/room-night.svg
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/supabase/migrations/20260525200000_room_png_ids.sql b/supabase/migrations/20260525200000_room_png_ids.sql
new file mode 100644
index 0000000..9022523
--- /dev/null
+++ b/supabase/migrations/20260525200000_room_png_ids.sql
@@ -0,0 +1,59 @@
+-- Room backgrounds: PNG assets in /public/room with ids room-1 .. room-4.
+
+alter table public.avatar_state
+ alter column room_background set default 'room-1';
+
+update public.avatar_state
+set room_background = case room_background
+ when 'room-day' then 'room-1'
+ when 'room-dusk' then 'room-2'
+ when 'room-night' then 'room-3'
+ when 'room-forest' then 'room-4'
+ when 'room-space' then 'room-1'
+ when 'room-neon' then 'room-1'
+ else room_background
+end
+where room_background in (
+ 'room-day',
+ 'room-dusk',
+ 'room-night',
+ 'room-forest',
+ 'room-space',
+ 'room-neon'
+);
+
+update public.user_items
+set item_id = case item_id
+ when 'room-night' then 'room-3'
+ when 'room-forest' then 'room-4'
+ else item_id
+end
+where item_id in ('room-night', 'room-forest');
+
+delete from public.shop_items
+where id in ('room-night', 'room-forest');
+
+insert into public.shop_items (id, name, type, price, image_path)
+values
+ ('room-3', 'Starlit Night', 'room', 40, '/room/room-3.png'),
+ ('room-4', 'Mystic Forest', 'room', 50, '/room/room-4.png')
+on conflict (id) do update
+set
+ name = excluded.name,
+ type = excluded.type,
+ price = excluded.price,
+ image_path = excluded.image_path;
+
+alter table public.avatar_state
+ drop constraint if exists avatar_state_room_background_check;
+
+alter table public.avatar_state
+ add constraint avatar_state_room_background_check
+ check (
+ room_background in (
+ 'room-1',
+ 'room-2',
+ 'room-3',
+ 'room-4'
+ )
+ );
diff --git a/supabase/seed.sql b/supabase/seed.sql
index 1b41564..98bf955 100644
--- a/supabase/seed.sql
+++ b/supabase/seed.sql
@@ -33,8 +33,8 @@ values
('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-night', 'Starlit Night', 'room', 40, '/shop/room-night.svg'),
- ('room-forest', 'Mystic Forest', 'room', 50, '/shop/room-forest.svg')
+ ('room-3', 'Starlit Night', 'room', 40, '/room/room-3.png'),
+ ('room-4', 'Mystic Forest', 'room', 50, '/room/room-4.png')
on conflict (id) do update
set
name = excluded.name,