Polish quiz completion UX, room PNG backgrounds, and habits list order
Polish quiz completion UX, rooms, and app layout.
This commit is contained in:
@@ -1,232 +1,102 @@
|
||||
export type RoomBackgroundId =
|
||||
|
||||
| "room-day"
|
||||
|
||||
| "room-dusk"
|
||||
|
||||
| "room-night"
|
||||
|
||||
| "room-forest";
|
||||
|
||||
|
||||
|
||||
export type RoomBackgroundLayer = {
|
||||
|
||||
id: string;
|
||||
|
||||
className: string;
|
||||
|
||||
speed: number;
|
||||
|
||||
};
|
||||
|
||||
|
||||
| "room-1"
|
||||
| "room-2"
|
||||
| "room-3"
|
||||
| "room-4";
|
||||
|
||||
export type RoomBackground = {
|
||||
|
||||
id: RoomBackgroundId;
|
||||
|
||||
name: string;
|
||||
|
||||
free: boolean;
|
||||
|
||||
previewClassName: string;
|
||||
|
||||
/** Full-scene SVG used for shop thumbnail and in-app preview parity. */
|
||||
sceneImage?: string;
|
||||
|
||||
layers: RoomBackgroundLayer[];
|
||||
|
||||
sceneImage: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_ROOM_BACKGROUND: RoomBackgroundId = "room-1";
|
||||
|
||||
|
||||
export const DEFAULT_ROOM_BACKGROUND: RoomBackgroundId = "room-day";
|
||||
|
||||
|
||||
export const ROOM_SCENE_IMAGE_PATH = (id: RoomBackgroundId) =>
|
||||
`/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<string, RoomBackgroundId> = {
|
||||
"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,
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<string> {
|
||||
const userId = await getAuthenticatedUserId();
|
||||
|
||||
if (!userId) {
|
||||
return "room-day";
|
||||
return "room-1";
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
@@ -115,13 +116,13 @@ export async function getEquippedRoomBackground(): Promise<string> {
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user