From 5ebcf33bca9ebeb5ec69742ab4e2e64f6bb12596 Mon Sep 17 00:00:00 2001 From: pricewowen <231476659+pricewowen@users.noreply.github.com> Date: Sun, 24 May 2026 07:09:40 -0600 Subject: [PATCH] Polish quiz completion UX, room PNG backgrounds, and habits list order Polish quiz completion UX, rooms, and app layout. --- app/globals.css | 22 ++ components/avatar/avatar-customize-layout.tsx | 2 +- components/avatar/pixel-avatar.tsx | 10 +- components/character/character-creator.tsx | 5 +- components/daily-quiz/daily-quiz-form.tsx | 125 +++-------- components/daily-quiz/wellness-slider.tsx | 16 +- components/habits/habit-tracker.tsx | 13 +- components/layout/app-page-header.tsx | 2 +- components/layout/app-shell.tsx | 7 +- components/pet/parallax-room-background.tsx | 76 +------ components/pet/room-background-picker.tsx | 16 +- components/pet/room-style-selector.tsx | 16 +- .../profile/profile-preferences-form.tsx | 8 +- components/shop/shop-content.tsx | 20 +- components/shop/shop-item-preview-modal.tsx | 2 +- lib/room-backgrounds.ts | 204 ++++-------------- lib/shop-storage.ts | 7 +- public/room/room-1.png | Bin 0 -> 5131 bytes public/room/room-2.png | Bin 0 -> 14968 bytes public/room/room-3.png | Bin 0 -> 1024 bytes public/room/room-4.png | Bin 0 -> 1998 bytes public/shop/room-forest.svg | 45 ---- public/shop/room-night.svg | 36 ---- .../20260525200000_room_png_ids.sql | 59 +++++ supabase/seed.sql | 4 +- 25 files changed, 219 insertions(+), 476 deletions(-) create mode 100644 public/room/room-1.png create mode 100644 public/room/room-2.png create mode 100644 public/room/room-3.png create mode 100644 public/room/room-4.png delete mode 100644 public/shop/room-forest.svg delete mode 100644 public/shop/room-night.svg create mode 100644 supabase/migrations/20260525200000_room_png_ids.sql diff --git a/app/globals.css b/app/globals.css index 423b662..d402579 100644 --- a/app/globals.css +++ b/app/globals.css @@ -222,6 +222,28 @@ .image-pixelated { image-rendering: pixelated; } + + .field-locked { + @apply cursor-not-allowed border-border/60 bg-muted/70 text-muted-foreground opacity-75; + } + + .form-section-locked { + @apply border-border/50 bg-muted/35; + } + + .form-section-locked .field-locked, + .form-section-locked input:disabled, + .form-section-locked textarea:disabled { + @apply cursor-not-allowed opacity-75; + } + + .form-section-locked input[type="range"]:disabled { + @apply opacity-50 grayscale; + } + + .form-section-locked label { + @apply text-muted-foreground; + } } @keyframes pet-breathe { diff --git a/components/avatar/avatar-customize-layout.tsx b/components/avatar/avatar-customize-layout.tsx index 6de9399..fde45aa 100644 --- a/components/avatar/avatar-customize-layout.tsx +++ b/components/avatar/avatar-customize-layout.tsx @@ -15,7 +15,7 @@ export function AvatarCustomizeLayout({ children, }: AvatarCustomizeLayoutProps) { return ( -
+
diff --git a/components/avatar/pixel-avatar.tsx b/components/avatar/pixel-avatar.tsx index 86aa9ba..66718c3 100644 --- a/components/avatar/pixel-avatar.tsx +++ b/components/avatar/pixel-avatar.tsx @@ -11,6 +11,7 @@ type PixelAvatarProps = { mood?: AvatarMood; size?: "sm" | "md" | "lg"; className?: string; + showMoodLabel?: boolean; }; const sizeStyles = { @@ -23,6 +24,7 @@ export function PixelAvatar({ mood = "neutral", size = "lg", className, + showMoodLabel = true, }: PixelAvatarProps) { return (
-

- {mood} -

+ {showMoodLabel ? ( +

+ {mood} +

+ ) : null}
); } diff --git a/components/character/character-creator.tsx b/components/character/character-creator.tsx index fa60dac..c5da3a3 100644 --- a/components/character/character-creator.tsx +++ b/components/character/character-creator.tsx @@ -371,10 +371,7 @@ export function CharacterCreator({
- +
(null); const [isReady, setIsReady] = useState(cachedQuiz !== null); - const [liveCondition, setLiveCondition] = useState( - cachedQuiz?.liveCondition ?? cachedQuiz?.submission?.condition ?? null, - ); const applyQuizData = useCallback( (data: NonNullable>) => { @@ -58,19 +51,13 @@ export function DailyQuizForm() { setSubmission(data.submission); setAnswers(data.submission.answers); setJournal(data.submission.journal); - setLiveCondition(data.submission.condition); } else { setSubmission(null); - setLiveCondition(data.liveCondition); } }, [], ); - const refreshLiveCondition = useCallback(async () => { - setLiveCondition(await getAvatarConditionForToday()); - }, []); - useEffect(() => { let cancelled = false; @@ -107,18 +94,6 @@ export function DailyQuizForm() { }; }, [applyQuizData]); - useEffect(() => { - const handleDataUpdated = () => { - void refreshLiveCondition(); - }; - - window.addEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated); - - return () => { - window.removeEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated); - }; - }, [refreshLiveCondition]); - const isCompleted = submission !== null; const canSubmit = !isCompleted && !isSubmitting; @@ -143,7 +118,6 @@ export function DailyQuizForm() { try { const savedEntry = await saveDailyEntry(answers, journal); setSubmission(savedEntry); - setLiveCondition(savedEntry.condition); } catch (submitError) { setError( submitError instanceof Error @@ -164,65 +138,32 @@ export function DailyQuizForm() { return (
{isCompleted && submission ? ( - <> - - -
-
-
-

- Today's quiz is complete -

-

- Your wellness check-in and journal are saved for today. You - can review your answers below, and come back tomorrow for - your next quiz. -

-
-
-
- - - - Your pet today - - Based on your wellness check-in and completed habits today. - - - - + +
+
+
+

+ Today's quiz is complete +

+

+ Your answers are saved below. Come back tomorrow for your next + check-in. How you did today affects your pet. Spend time with{" "} + + your pet + {" "} + whenever you want to see how they are doing. +

+
+
+
) : (
Today's check-in @@ -230,14 +171,14 @@ export function DailyQuizForm() {
)} - + {isCompleted ? "Your answers today" : "Wellness"} {isCompleted - ? "These fields are locked until tomorrow's quiz." + ? "What you answered today." : "Rate your wellness from 1 to 5. Sleep length uses hours."} @@ -284,12 +225,12 @@ export function DailyQuizForm() { - + Daily journal {isCompleted - ? "Your journal entry for today." + ? "What you wrote today." : "Write about your day."} @@ -303,7 +244,7 @@ export function DailyQuizForm() { disabled={isCompleted} maxLength={JOURNAL_MAX_LENGTH} onChange={(event) => setJournal(event.target.value)} - className="min-h-32 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-60" + className="min-h-32 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring field-locked disabled:opacity-75" placeholder="Write about your day." /> {!isCompleted ? ( diff --git a/components/daily-quiz/wellness-slider.tsx b/components/daily-quiz/wellness-slider.tsx index c074ceb..21da67a 100644 --- a/components/daily-quiz/wellness-slider.tsx +++ b/components/daily-quiz/wellness-slider.tsx @@ -30,10 +30,14 @@ export function WellnessSlider({ const displayValue = formatValue ? formatValue(value) : `${value}${unit ?? ""}`; return ( -
+
- - {displayValue} + + + {displayValue} +
onChange(Number(event.target.value))} - className="w-full accent-primary disabled:opacity-60" + className={ + disabled + ? "w-full cursor-not-allowed accent-muted-foreground opacity-50 grayscale" + : "w-full accent-primary" + } />
); diff --git a/components/habits/habit-tracker.tsx b/components/habits/habit-tracker.tsx index ce452bf..46e92dd 100644 --- a/components/habits/habit-tracker.tsx +++ b/components/habits/habit-tracker.tsx @@ -63,20 +63,9 @@ function DailyTaskList({ ); } - const sortedTasks = [...tasks].sort((a, b) => { - const aCompleted = getIsCompleted(a); - const bCompleted = getIsCompleted(b); - - if (aCompleted === bCompleted) { - return 0; - } - - return aCompleted ? 1 : -1; - }); - return ( <> - {sortedTasks.map((habit) => { + {tasks.map((habit) => { const isCompleted = getIsCompleted(habit); const pending = isPending(habit.id); const toggleError = getToggleError(habit.id); diff --git a/components/layout/app-page-header.tsx b/components/layout/app-page-header.tsx index 37ed7f1..261b267 100644 --- a/components/layout/app-page-header.tsx +++ b/components/layout/app-page-header.tsx @@ -10,7 +10,7 @@ export function AppPageHeader({ eyebrow, }: AppPageHeaderProps) { return ( -
+
{eyebrow ? (

{eyebrow} diff --git a/components/layout/app-shell.tsx b/components/layout/app-shell.tsx index 5046105..cf5e4e1 100644 --- a/components/layout/app-shell.tsx +++ b/components/layout/app-shell.tsx @@ -9,7 +9,7 @@ type AppShellProps = { wide?: boolean; /** Use remaining viewport height between top bar and bottom nav. */ fillViewport?: boolean; - /** Center page content horizontally (pairs well with fillViewport). */ + /** Center page body horizontally (header stays left-aligned in the content column). */ centered?: boolean; }; @@ -27,7 +27,7 @@ export function AppShell({ centered = false, }: AppShellProps) { return ( -

+
{title ? ( @@ -46,6 +44,7 @@ export function AppShell({
{children} diff --git a/components/pet/parallax-room-background.tsx b/components/pet/parallax-room-background.tsx index e1e83ba..0e6bf67 100644 --- a/components/pet/parallax-room-background.tsx +++ b/components/pet/parallax-room-background.tsx @@ -1,7 +1,5 @@ "use client"; -import { useEffect, useRef, useState } from "react"; - import { getRoomBackground, type RoomBackgroundId, @@ -11,83 +9,25 @@ import { cn } from "@/lib/utils"; type ParallaxRoomBackgroundProps = { roomId: RoomBackgroundId; className?: string; - interactive?: boolean; }; export function ParallaxRoomBackground({ roomId, className, - interactive = true, }: ParallaxRoomBackgroundProps) { - const containerRef = useRef(null); - const [offset, setOffset] = useState({ x: 0, y: 0 }); const room = getRoomBackground(roomId); - const usesSceneImage = Boolean(room.sceneImage); - - useEffect(() => { - if (!interactive || usesSceneImage) { - return; - } - - const container = containerRef.current; - if (!container) { - return; - } - - const handlePointerMove = (event: PointerEvent) => { - const rect = container.getBoundingClientRect(); - const x = ((event.clientX - rect.left) / rect.width - 0.5) * 2; - const y = ((event.clientY - rect.top) / rect.height - 0.5) * 2; - setOffset({ x, y }); - }; - - container.addEventListener("pointermove", handlePointerMove); - return () => { - container.removeEventListener("pointermove", handlePointerMove); - }; - }, [interactive, usesSceneImage]); - - if (room.sceneImage) { - return ( -
- {/* eslint-disable-next-line @next/next/no-img-element */} - -
- ); - } return (
- {room.layers.map((layer, index) => ( -
- ))} + {/* eslint-disable-next-line @next/next/no-img-element */} +
); } diff --git a/components/pet/room-background-picker.tsx b/components/pet/room-background-picker.tsx index 176016a..5a9be92 100644 --- a/components/pet/room-background-picker.tsx +++ b/components/pet/room-background-picker.tsx @@ -71,16 +71,12 @@ export function RoomBackgroundPicker({ "hover:-translate-y-0.5", )} > - {room.sceneImage ? ( - // eslint-disable-next-line @next/next/no-img-element - - ) : ( -
- )} + {/* eslint-disable-next-line @next/next/no-img-element */} +

{room.name}

diff --git a/components/pet/room-style-selector.tsx b/components/pet/room-style-selector.tsx index 2e512d0..4c13e1b 100644 --- a/components/pet/room-style-selector.tsx +++ b/components/pet/room-style-selector.tsx @@ -70,16 +70,12 @@ export function RoomStyleSelector({ : "border-border/60 hover:border-border", )} > - {room.sceneImage ? ( - // eslint-disable-next-line @next/next/no-img-element - - ) : ( -
- )} + {/* eslint-disable-next-line @next/next/no-img-element */} + {locked ? (
- +
diff --git a/components/shop/shop-content.tsx b/components/shop/shop-content.tsx index 9423a4b..135f53a 100644 --- a/components/shop/shop-content.tsx +++ b/components/shop/shop-content.tsx @@ -79,16 +79,12 @@ function ShopItemCard({
{room ? ( - item.image_path ? ( - // eslint-disable-next-line @next/next/no-img-element - - ) : ( -
- ) + // eslint-disable-next-line @next/next/no-img-element + ) : ( ([]); const [ownedItemIds, setOwnedItemIds] = useState([]); const [equippedItems, setEquippedItems] = useState([]); - const [equippedRoomBackground, setEquippedRoomBackground] = useState("room-day"); + const [equippedRoomBackground, setEquippedRoomBackground] = useState("room-1"); const [coins, setCoins] = useState(null); const [error, setError] = useState(null); const [pendingItemId, setPendingItemId] = useState(null); @@ -153,7 +149,7 @@ export function ShopContent() { setError(null); let nextEquippedItems: string[] = []; - let nextEquippedRoomBackground = "room-day"; + let nextEquippedRoomBackground = "room-1"; try { const inventory = await getShopInventory(); diff --git a/components/shop/shop-item-preview-modal.tsx b/components/shop/shop-item-preview-modal.tsx index 7ac03a2..7ad1248 100644 --- a/components/shop/shop-item-preview-modal.tsx +++ b/components/shop/shop-item-preview-modal.tsx @@ -105,7 +105,7 @@ export function ShopItemPreviewModal({
- +
+ `/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 0000000000000000000000000000000000000000..805250daa8483377138566e2c7c6d394120b5d76 GIT binary patch literal 5131 zcmeHLdsGuw9v&1GS*Y)=8|gYmtyrDRWJp3XCfZ1%WHk{1Q4}kanHv~PGU?1f0xJ|; zwW6Mt^;H#o)w)*Q>RDHvH*K4`e3vQ_XT$Z~r`i-1Z z`d_|OF@N}=@d-aKJv?FI7zb6+(C^LigITRF8@4?EZhhHl)iy_QN6pQK>WlkZOB3Rp zlGRApr=%JS{y5(dLzZX1H*DATSxw`k8p`I~o^xs6vbL=^v3tn_uAa#My#60m=R0Bs zw|}`WtKM<<@UnS(uCGEBTh`w(jGR_|@zdh>e77{$-)~T@>pvpfINUlrcDBOzo2txP zhn717LrSlX-Z^T)qLhKx%u7~WN*t8(wXK30vRPAf_4N^TCw~!-iOdH_MxEX?45?Z& zE5WWidueoqrKhQ4Q+sXW3(4(&Rg7*a9TOl&45d#WKYIP0`(s;rZ5E@(%yk}_CVWR~25r)c2Li;-$s{P z=FIz5T;G`6sjm(`JM4D@%=OU|du=-4S#VaXoYi(l*s?lCUwP%jjm)9a>^tPAE23iR z%+7m2fL<=AX9jooQd|!e%;`E0{w^+x9x0?=-!-Y4rK5HLq1^>e#Wn zYJS!_UjJ>jcws|EL+#-4bNkok7FA3gyMA9&+Tqg$+UA^Tr*3gOxRn>@7Syh9n4eQ` zYGmHrTG9p@Pi86T&zGN?q9|Th`GyWG+mqy|=32Acnx1N&UC{nnN44Uaf@9{&6yPr) zs;^CMe9IxJd#ay`{>zb=+?1Qn)jgtzK%x*CU1arX3q^-#^nf{HcVY)|VU5=b-(aUHYE$ zNsR1!Q>A%eIT6#d{n)!>(v}Z7F*4^^@mKdRA4=LB*Iy@0;?Et49@KBBW8LD8=DoW% z4`203>$$&uGy%_A>sGCCklga-Z}y%;$k~Ih+wSaJjUfGUIapK1RfyKXu#w4id4X)4DwR&yWEW{(I7-xXj0Cxr=cml08oueqarYCfGZ-E6Jt@M zz`Ce(%akw$Y?+k#lH{Xs-0%0R{7EWa$ivkJg8?TYJx_|Q%;P-1V1$g0M6ygL&Q~b7gBX^L`2A} zegqHWI|I60PLMn(6lLL=LYc*8F)8H7=n_ zy^c*{NP;DRI)cjP6(!os0GSGst2oF*(?FY~*6A^W-k`=bI<*?p6FLHJ8leFOJ)=+7 zkr5O#1P)r8c1O-ireYzLi**4FP&+Y3r_*5?Eu?a44F-%=Cnu}5I<=l;Uu#`E? zW>S(W;$csko0eR>;DPnRd0E~sJ{-#AJRn1&W!WH^K}Tw}q+Y&~LhB1MW&uHjzLiWJivl%!2iLNkeI767>EC7u3c%H7n zolNQQv=Swu*!CTZ`Zt_0DM){nfY_jn(3Vl8|2ZTqpdf=V^f{Ta`d-c1*BVnQF6y8@13pZby;rBi^ zeHXwG!*q@TUXg#?)FUAwSt4#GaL%DVc$0$nJUnUFfhPiYyQ^ESCsN>vz}@cppUD;b@Ua!Y+)om=&-eOa*J+!@oC$yQ zYBI+oXk4^;#n=}TEpcskF-2gYYgCTz=?VK3Enhs_AL;Qt5`#pcNOU>!e`$FCkhAl` e$5G!W*H#I!tm>V6$XK%$(^lC{=1zd84}=3Hyql&NUGj{d*8ne zurTgFCtO`+k&rNXT-7!0RiRD*TRR&Qb1N9Y-o+LMfH|9+AR#$V7i(&juQK9ZZ;&9+ z{0MzlK1+SYTR)E_V|R9&8{uAaUV9#o3sjTqQ2UwQoP7C!UHkj<{$)K|cxyifg?tBQ zbNFP@<+|OnHSO&Sb8;r|?&iuwp4Mn69 zo>leCf!o0@k+&Fzw=K5$$(VRopeVup*F}8w>rS|@v)6->lIBhL5R#p2Zxi2e2Ns`y z+ukm+s>F|~wN`QU=V&nlI)Va16G zVsVYals!keL+?x_OPc}*I3x^3(Xy1$#?e9n{n}EO!9Q_8B#Ga-DvY2*K3O!TRDv5F z4#lD3h5?-+!5;u4vDImfC(Dfl!WhI@6Hf(d0IflUh8@{NG}1y;OGHLoAu$Rmx-qM2 zGMqFXU1exQl`dd{=ey@0BRuc>n50qP&M z#q_Y&UfVN$o7jx~tjPH)R7;L{H1`{pgL&&CEMi6pgC23;@wifm^80p!3Y?U7;0+@m6InSq>2_dp&`C}#dm z1k;B(f0CHTBH+jQgQA_bY`Ix;-xelZe3DR~ zRi|`IbRjzwlwRlSsC=}zn~*erGkyRTzJ|Y4Nw-Xth~p>@Fgnr-{PdPBVpiEUhjj*@ z#!7IRad_ zD7*H>XqP-&v!eY#sZEGgrlfD07w-2S6EoJc;8LgKhhI^q8Bm zd|3<}d_*Ax?H=o=hZ}J}>F>PUa36Ty#KPL`X8^Xh;u@SNFBMPi&%2HzrUr=m?jXNr zG$l%D$-{f1xf9km`yw|o5^d(mV)l*pI6jUGvE$d0_C}V}rahkqn@PEPMem zd)svH0EC}Pg>Ri{^tDvOgoEO^1ESVObJWxzXaS;{*Fjnw3F0fDt4SJeRBj&$l~a2% zrw`MP?WS?b6p>QwY1gSM8cPgoaOj9W+0M^$0y)!mX-*F0=@&Oo^KMvSqpJL@b%C5l zz8=UT0zZ0nYbD~&gS_ovR~wi3@`?36yNRm#Ji7^AVs-CJ`6^*Kk)(nqow|jc8^=|W zg`Wne*{+Y@NdEZpN+oiW$jvp@&v8b${fk{iKy9^WCoJV@n%gnslG|JZi)wM9(Am^T zNZ_Hoqjr0ZyqNu9fv{U3iN?&-R0NY$1#<<{Y*O&RUh&*+$ose3t!%zESDGxWhSfzL z(Ufu=eIK7mTf*Oei`;-=H^wf64dH8Dh%LFUk~_S6Si8!8kT`-qK!sF1=XJ)2A2bYT zAhFb?p?oZ=`e3zTS@#2aFX;B8sbPb$c+n7^%xanrmdMiL#%%aYrLzy;ikgbIiu5Ia z6mWjfTYRq(v``hP(ab z1#(86nbY2zd7FWGE}#gf5+j4eCjGPm$8Nn0J9D8HqCgKw+~A&5s$bi88q4!(9!0ng z`j>#pPjQ0;MBcb^r$3EQlCJL`*4N|)z_?3ShMyD7Ro ze7}uC|LOi|ZfkG!YyX7Eaz6Ctz@0w9Nspq}Tyc^o<;T49%4t-qK|-!g=ZEQdhhX9| z$cf#WO{fkKblD=-9JUp^wPX4_b1Oo&41YEja3OK&l5Bm>wn0TgA}}z&->fwiYIT8b7KhbHLn7Pf~^G1%v{FR z4yNX+sBYwHX#_F`iU{KfIfL&BtYP+0fU~s~907KQ0Dtp>?|=X5W(NX(L+mXfKurZ@ zfP{@548X(2!^Xk-!r9!B3n+{O5VA8i0jo+%{Yi1(f&k6z?QOyA>`qQjY);&4Hg=}$ zoFEX0or8;=i;MLh!HRH!+e4jM;Ru>v6n}6?!VpGw_tKf$zyZHFp@udN_7EWOejM;8 z`7dE#INx{v>EKuUH$B4Mm|gb1!E=8+_W*V-4h{iU4lY(M5c^;4??)9B{$>qF{Hfx- zp6t$0TXs%14t8tnf3QHWOK>XFG z0|NG|>v!B%#wP6dLH+jpR~uZVdkYlh06qkAv3) zcCRuIA1e=sfC(#zpT~$5$_3*$3aSLM01F*hAq)uwPX7#%48&>(;)k+w!npZ(K>{$CF&E!oD3t8X?^7FU_1CC=Q5oM;83_mo@CX=z zSUGv29QRa)0<0h?hY2f8fZrI#Z_LHd3HyWUm#|I%Y%%3$Q4mD-}-4tT~SK$AHNzKg03I6}a^LOaqSj6n?oowtZ zmF$!ZEnr6W|C;BYf&a#&dcRsB?Co4+|C@vQ-*7^I$B>a36mAN;UoZbGmjCKE|0j71<$-Z?@fh>6 z8gW2*Sb2;&d00U(7$+-_2`2~CnBUk0W@z${>?8&1O74N03r5YtK@$jvCyw|L_q=kS51U| ztutWRUx}yYU~6k-4zv45t^O-_{x7({*#DWU|DE|?VSiXl*x0(<7p|GTk`w%2y8kD@ zKN#fAjbLzu&A$r$uaG}v`O9WI#q9=&q_!2tChv>a1dc#H2iKF!vj@JOj1(JJ`X4yB0z#JhSus63BVcs zkwJ7thsJ=PB2ddrIj&y|1R^Ftt{7)2I&VB)xs^700%>SyPP}e+XM!*NdcGpf-$exX z^jtn^_C4CTGCjJsYCgT4xibc|=}X_7-N;_MTwNVr3vJz8T=M`?exmk#N@3n`leugP zmKS;|y1@MS<7A4X!{;mfx_R;ynfl47tNhZJlEA>XN)%<3C4g1X(b>t>n_y>Zfktfm z=-O_s6%Sr;NEw+j9DGa$)yD@jGO4 zrbJ>ACC%^Z=FqL@R=a8A1^!A<{LtHMk(p$9{1yD~4(DPIbXJ3V?k;0(vgm8wWkx~G zm}LaEsa=bOec;#5Wwel-rjzs$dBNprqys$M0~&F{P^+z|)VovZ_2P8kUZoe`g6Z2H z^E>x9-_TFg);Tifl0y=dCxWZ49%Ro`0W z{8V=5Y}dy4`tvt?h^tG)fO|4?6QNZ4_H2HTpPbT?oPj9>rh-o0o?}!28)K*|5!zxnn1}(-a3s)F4$L#hkjR@iSpw@m_6bRr4;@Oo?d~ zBB2NI{E4QkKvkNJf)w$=p*$&k?gSy^LrQ}yGrdr5K)tK7LUyzmm5nVU`EY%}xVh#? zaks*QJv`L+csGzYdeeXjP<_YmkhFNw*g0Z`L7EmY`Iv=P&LwiMa1gVOi5dV~bPjT( zZ$I&-Yxpry$wTrk_zbQ8)-#0MtXk6@k3(Jb*$%Rii{4izhE`BL2t`l4jdJx8BM+Q+ zfwUS5<~y`t=y}raZ_RB09{aV{Sb#i3*?Ff;oJRrz@k%r90I}zR5&5^ri z*@MG{w-v+$iFA3<5uTW#>LeO^aJ{tfZD zXd=f&t-pH3qxKA9V(Y=~=BA-c@68}Bk3?=|b0r$Vyp#5_x64xVpkX<>)t`>4(mh#; z{+tfs;fas@M{+>EIoK^ZLVjIln--Tc3rtR&zJ*dg5;lkgamB$I#Twa%Xg7)eKBdahsMd*@p&R182*D_b7ZXmt>!x$0$t!`QMbC zZTgwBP^OW-T>M<%px1<@${{Zx;j7TX8>dQpGkHwj;n4#YU?9H2B>i>%&Do^|2bWkfz}9pG6J21YPARGW056J1pQRMVTbZ zUGG0@vC*5e;@!C4TDBrRiF;dm`1}$nK>2;Ij_gZuPOR{dSX=!-`uPO=EK5>&L>76H z5YD^JB^L3)cbqia<@8YD*sJ$E!-PkGpH`dbTo|%4!f_eXu|i{;?|EAKK5|lv%1`1i z6dNgdlVJ_`g}b%U3lct%6UHqx1iO}MC2rkuw)c?{{#=@3Pp3$4-ryS{N!;jF(=@9K z%v%vh@mz9M-9HLDBZBO>`kXzQPQ#8$BVX%TkVHQTbsR!dIfHt!f@ ztqbOK@dfbr3QWlx1C8;G}vmc2y`e84Z!dvC6NU0JdjMg;S7_#9LcP;YE^HyYWFG6XIr;pBb50N9vvM@%tky zi?*08R7Z}!sM3>S^}iw`^YKBV9?wyDfdF_WS3->rH;u+WnchE&=15}DW3bacp` zuZi<3aj? zF5`jNF&DI{G}9=FwwowhI&^2I-umgP}ew(wbolfY5@ zfzNGx^F{UfhlQ*CkC|KKqDhT-ay!DRyrGGL{2^d*zCgyylz43!6`jzURph%1rRJ*& zvuDK>1|VR}CpAH-bK zxMc|q0pXHx3Nhh7L>Xt?QrzHV5lL4ceGx)PpzsJJB;xF&}{xkq(iWaOr z)b!afmRKyrj0BP%nY$^Y!@?6?A{x;p{yfp(AvelCMM_r&ixyESdhA;pY&qt1J3O7! zc7Ea2)3p;lj?ni(%1}m=&yq#)eOMK*iQk@&*Mx&Gy}qol42qK}FxhPxRaI}sN;xeG z^bnGNmx#^o^(A@~9Z992dl$Y@B~2K^O0Wq0aP1!#G-(s?Xe$#VQJrk$hITJ>b|e2o zu((XzQC+n5^fnggRqrZ)Zcd)CFmy5vt--ed5V!OCMqG`q2fFHjt@~s8z`>P%qv9p8 zjAj<%1a;X4tKKo2#DaJ!03g(3&L0mU7w2iDwrXivV0sd649d)04|Lmw8Wya{%oWkc zJw<5wAB(v>A)xVI-lQln;NQEYQ*#;m)Kk?(RN8>_@&sVSSqjliM%3;r z0~1ItxfA&Ct_GJ+{Or-lUloUXVpylF0PP>86#41R(L!lIT776v?ix|b?doR<^&YkN z5AI>;fNvQX$cL4s1(?^Lp`2^HkRTuyNC|6+_Mf!jO7LCne<|Bf;04izsC}no2Lyqc zd$gQFTZa)dTv-WEKTT6APYmHGEFIILvWO@xR52`a@s$H`Bli6t$~WMQ!pppk=`zxE z_muqn?Bilg z%>~#kKQswJcsz&dJZiZUJ)kJh+x1x-#IsoN{CUq4we-hq1P=z#TdXxUk4hc4I0&UB zLLzkBHW8y8a-NP467(dsC#29j&exAKOVi_9cQU<0mn=x)lOcE}_Dy7oM$hR7ELmP1 zUG}{P=A3%e zft@Z)MrbXn9_(DG|ABg=CclNJR>u>uSMvJO z0fU7ig5&n=Yss$J@@?X(>+3~XOdT8&Tv9s|`7iW%NtJ|$OL=h&;mOo%t%TS^*-^RH zl2;5AAIeL2+65)?wJ$TNNcm3+fKMDVmv)Qk_6Buw7I6Cvwc>elH?uc3pOsg!H9T?$ zrn{J+Jqk3Qcvr@Vmn2R6ZdQkChb*jM$sn;e(3DU|oBC!|mZX?EP{K!d3#0) z&xna^oHX?;S!Bj3TEQtEN%HVzZHy?EDBd{@M_ZncYIO|Cu2*{j*+GaseSHuu7R6(2 z!P3tDB45VBIn&tyWiYh4n+qpyAPGCJ756sjwhRfYWjY}LHdc% zlf}m4VyG;n}i_X5kCzhr7*DNIPY*Yu}IXlgkP zjrrZrj3ZxS!+5@eXHY^HmyLPvlPUJG3VGpRveEI?n7WH3C)hAf^y4kiQ-zj{@d}OR zu0>?B_qzFNUWp0Zm|l5@Vbt*KMB7wL5mK&a&p)9YqisrguM;TR4kq%3^>Qn*BwoX?5Srf-C2C?s1k=}T(5IY8!(g&Gi7u$BxC3{fi>)%Sy8 zV!k>0LP2ohhOc!a*Y8Z^m-@k&^81zT;R{pm&b6th7Q`QrV8bp#d!D+I4~WN2! z-GiT4`0BoP3Qf-}dWYA?Og7?immsfK*EIOzJ&Hi~_ywbNq)|1gU5D9xNvqD}=ZpX~ z*<0aM(>GrpuNfRU6LQCezTZh)wbnBotMoeR1)4ab5`m|zjOuJ>8D8_940!jDUoCJbT!3bk7oQ+8Tn+BGe1zw6tk*6IJovaLo>8q zfVq3O&d=%hHd1{26#Qv>1dE%?1T&_gCVzhY=@tcz68PN8l=HednVVwb>U(^4_KUZn zWOj!f#zaOlCu#+O6+}mnZcQY6o1kR98PqBa4f$cCP-&&k@!C{L@%>T)d+jYP{MeJS zR;{?6p0L(_+6;qorpR(ug$BB|dO}TJ5>eYgP<6BqQ_D4_ zK7xhS@ib#%&rBq!KrcG{#S{ILDrAqg4nQSSI*OME*mUP+-$y2;bvwdMc4>PLtcXPs zgdOmrdpOENEB?&2zs=1i&9867Kegx!%*`3JlQF2v%m&jztsF&IDsI6B%y`~(IS#Z5 z2v71VLjncN*&@Uv5}SO4*N!s%_N%%o zR$j7#Ih@CGe6q-x^=>{U2fhEEr%-7|u(FJ*=Xl|wNj+DnzZboJ{>uD}JIk%{&4Gw9 zOKR_t)aKxd8Qi!ILQoF3=Q!7{K{EkSeqHh`g)gOzXsa(~;>AI@UlI}HR|??GrkOhi z9kZ1m90WW(KD=~`WaBWuur0GnFL}q!{cQm~p|fIOD>O@(r*yf3UX?i-)gv?dajRtj ze;e3WeoBKAu*I~v1n zDtuO4>EM!{KA@=Z!%^ST?)ZCm^3oiz7frGRzfNJ~daU8KK#J~2d;aMYc}Ima$B=p@ zwg4b8!AGX<=qJavUs_+86XH&U@p+F3PYv!4!nS&OPnvQsrTI{PD~kZDC;)zXv@5Cr2xZnRz;D7Vg!c4A60(;$h3#DLYwtM==o`f z!%O*S3YJ@Pq(9b*gHCLjlC}1iD8?ZH@0ZskG8~pza7xlpcwicXY0Tr^Gur#$SM_b8 z`_vTysEpNJ?9a$D;)@R~Sj-N*C&l>|C&G!|BZDrfj=oIQx0gRp^JubWIvE@j;g6xP zPZB1QVP#vwtXHHfdN~|Q?1f9KL9OmyVE4}OO;_uOWx;{qrSHX<{FNwf2Pu;HMMsj@ zx>M#k38IjK)2eE{5+Vd4&$`(8R%wB(H`CSpYt4oojSb_^7d+^@cl1l*sxGQEEPkMqjDgs5X z^}|D%!j_>h*asYvK9bi)b`R)X>YiLd;EXN!owxC>N&7=?2N%<(+!8&pU*oMStsHpt zj1iE_6CT;~)=7GLR4z4#e&V3bs@GM@d=%fhc78$$I?I1DUWGpX_?VC$-SRG!9dNBI zHRgF>MIMCToHv?G$x?(y+st=;ZP0{0xaRc&eLA6&^8|jIHmS{nMQ8DhN@%UJKSfKf zfqoCQI>e#uwG8&u#iPpUOT9p0F3TsQgLtD8U#czCC|oRaF~=4UEcFGSbW9cii5~eS z(77ScF!u3%!jz>aW+uxXH55}O2X~G?B=cqGYokp}RLY*T!|cv^HVWDS#I_hliFv)3 zj{P9*7{4SMJJaNcRlU*3yw-Lm*7*G?!3a%w8-_Au5v=_p-YL1fLqidiyf4F)h!SYe zw($N3vzk0*^{0;lA@DhLgK*e44|P;_QdAcHI1{Wu%<+huPJ+g?dZ<5#Z9RzCK1#Dr zzJTJ*S}rm>fXoH?`Ddw@#|CNmD}ksrA@QeSP@@oqXCk=TzUu;IhSyrji~MfI`%;6M ziA$Fjm_DoYioT4M+~9^oT1&f37YmD*&lZ<)~-}@$$;Ek1&POp!TZMBUuoDt_U!7mZl#oH381wb1&Qc49xhmyvBNvzwhVKW%EBWPO#b5GwNK?*3dNk4S>}D?ym| z+*d8skR`d{D@y05tKK4T>YtPejSW)b*0Rf}9guLs$0A6`u5=arRfZvK1`L*wJP7h#Vq zC7-HPI@>}^{oHSZ7IiKA3(r$DA!uoZ4u8}a3OUR4FPZ`kaj}NUD*K^@yb5GI2iqKL z_9CnmiSpmvJg+vqun!e`mc#rUspZfJPWm4!QHKY#_;uye8IEsqu%`lirwf&NRq7w> z1Qv5k>B~((Ar-uN5D=<;v^+ivDVvpfFQ8OW_?bDy!DbG2FP)*cy*ovtU)ou76Vw)v z8ZDq)O>lsYux)FdrGY1w< zQFRI&x@1HIsf~!m0P~Wa6}ccOsbjn;x~+{EhfQc`)WhCLulA|+(s zxWBIo>52Pw{mu;I*B>8OY}vbS3LF**UOr^P;BR7Uzqtd1pffUF1cEd)IjZUZ5@ zd|tMK6tqI=jzc|DpZh2TUp6g6RPo7uC5)KPOvk&QUWH{LyfLrqk2=2X+DX8We0bjkJ{Pa%1P)+>2C z7uPRYz|Q(Rd9s>?CE@q4MIMb$E z!cNuX;M|WP>CDUi<4j7M_ytO$kXB9onl#hXuaxE0v}`Z6H28veuQmBXT_{i!bU-`! z=Se}QTty6d>bGfxFKtAO|Sl0+q@cFLcz%)Lkcx>w$qt{{)h z^LF#qdkD#V-S;<=&)Y#2?ioXFO39NHGCQG5I2ld3n1+Z=YPnECoadaqn}N;)Z}W+X zq)~))Uk_GfqdjYxoJx7n)y@e^vFn2MaECB|H;Cr3T|uQLX(LnZEYe0<*WIBmL>C0l z9lP`mBoUw(kds;JapTDDAxRPzf|)}g1{S9rpF@b%gdaSfEU?3p-ANA%E8z{Y zMa@hUyna23|1O7+@;2v*cw1(0oPL0`rPp0CSF=npkue{qEV#s{e5#*d)EN%%tP7d& zPyJ?|a*3s7iq0E^7O{2<(018CDbg~+=h>OA+HIdb!TR`m>~t6gZ%OI&d1{-~r~V0E z+6@sDbT%pr8lQvdSXw93$KCB`N#g6SKG#^}tVr#h=8xtKkdmC=+Y!Q#d)ms>KeNxo zqvGGK2B%|t=ibSz>3mpLgQ|3w{_%l826Se;AiMUZ{MQ*#lp;z9eX=*M1*ZMLH%a$A zwlC-5D#AMB*|4dkL!NHMMRMF2tZ|wLz`bpSeZa``ij!r*_!E!Ot3JA-oB_JMFcZo(T{Z@;6PuqIOnCNceNl(|lKe8CGaXAeOfpXc34y4P|0`Gn2g zvAp<~BYCECvlUV&zd>d^s%rWLhNC!<^TdPKi7)L`x-;H>S2v~e)*gj)^p-hIDPKBj zd`xE!6f}{iAm2IM(}pvq@}daUDb_OIcy*ANoql_q=@L#mJo{FpSG=pNvsdzqWA`Z} zoo68U6OYwcE%W!y3(>Gg5+n2sX5Ym-^H>uw?=0{>&8?;!p)v?nHsJJ1VPitWBKc*r z>F@8Wzl~spa5Fb_LPChX4fPaw^)klR#|+N-+SbHDfvH17G)cS>>6XBAQwA=(u)6(r zMPZ+8nWK53;vem`Qr$>MD2uW!14wUPmng5 zw&_LyfR<;VR{S==2Z(IGtZUE?QRwO8^Hsd)ybGVxEvHwBXd%pqA;G8)#lBr6Jsp5BS?w+I1WiNato?okz zAf}vUd;348{UA!v-ophbIjyu#%FHBn8)tleJZ#B&R#)~p)B~~oGt9wNIdpP&in3RO z?r_EZS>g-as)PBAx9KD6bL<)MT5w_7$1?BN>8z)$+_j0Vk>dp53#w~Ep9>}{Yhf5Z zCVY&ZeyV$%Eaa=W4`-r$XWKA;3>Z~>kshLDhqI;48%KxJJ+2n+_nTHz`)oChbr4sV zBF6n#Ub|NNa5*4lD5rE6ow4u(9R|oc4z-tlG^XK7H36>EId3YdP?v+i3JjdE^i1gH z6BY?A^yA7uFdp7M0{d^{ZizCbT?>bq6B?^T`C_3o`KA;Fr9PU@Z-;VW9Q*GnFE0r^ zkFT$KD`9#7RAqaY`}#*o)%^bCL~V<`b~d@*v7QiDG5sC+XlA`#zAKN$Glj2dkA!W$ zIX;N|TyAxch;6qf9E+qto-;r$LC(Q6k-r4&lP_EI6i*giLE-8&ta1R2MXc7`U&+s_ zw9ppM_2vfF=YB-4Y+ckY+ToNN)?&lo;n8TqfK4Npk2g9vhgoJHeM1e8NL72jnbEf% z6G`EQ*s^2_5z1R3Bk+txc5Av~N>)s{1YtKuFwz#6jU`rYlJJK-m#9l${wDZoC!zf6 zykP1Teyu*&*9KZXJu)uTp)!7jt1EQ);~<`qUGom4WnHDmO2ON_AsLwYoJk+N_c+${ zAMJBH<0zBqa>4T)%O>ihJVGNc5%W+>{(wz67)^vSybBBUqK$LvjR-kkLC=8P93q{EiaRcA~ E1w`|C3IG5A literal 0 HcmV?d00001 diff --git a/public/room/room-3.png b/public/room/room-3.png new file mode 100644 index 0000000000000000000000000000000000000000..9705a9eb498480171633190f8a3093c38de4b9da GIT binary patch literal 1024 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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,