diff --git a/src/features/room-select/ui/RoomPreviewCard.tsx b/src/features/room-select/ui/RoomPreviewCard.tsx index e0703d8..cfb1a61 100644 --- a/src/features/room-select/ui/RoomPreviewCard.tsx +++ b/src/features/room-select/ui/RoomPreviewCard.tsx @@ -29,8 +29,8 @@ export const RoomPreviewCard = ({ className={cn( 'group relative overflow-hidden rounded-3xl border text-left transition-all duration-250 motion-reduce:transition-none', compact - ? 'h-[112px] min-w-[164px] snap-start p-2.5 sm:min-w-0 sm:aspect-[4/3]' - : 'h-[338px] p-3 sm:h-[420px] sm:p-4', + ? 'h-[98px] min-w-[152px] snap-start p-2 sm:min-w-0 sm:aspect-[5/4]' + : 'h-[292px] p-3 sm:h-[334px] sm:p-4 lg:h-[356px]', selected ? cinematic ? 'border-sky-200/44 shadow-[0_0_0_1px_rgba(186,230,253,0.34),0_20px_50px_rgba(2,6,23,0.32)]' @@ -86,14 +86,14 @@ export const RoomPreviewCard = ({ ) : null} {compact ? ( -
+

{room.name}

{room.vibeLabel}

) : ( -
+

Selected Space

-

+

{room.name}

{room.description}

diff --git a/src/widgets/app-hub/ui/AppHubWidget.tsx b/src/widgets/app-hub/ui/AppHubWidget.tsx index 203b4a0..c719383 100644 --- a/src/widgets/app-hub/ui/AppHubWidget.tsx +++ b/src/widgets/app-hub/ui/AppHubWidget.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; -import { getRoomCardBackgroundStyle, ROOM_THEMES } from '@/entities/room'; +import { ROOM_THEMES } from '@/entities/room'; import { GOAL_CHIPS, SOUND_PRESETS, @@ -52,7 +52,7 @@ export const AppHubWidget = () => { const router = useRouter(); const { pushToast } = useToast(); const { thoughts, thoughtCount, clearThoughts } = useThoughtInbox(); - const { selectedRoom, selectedRoomId, selectRoom } = useRoomSelection( + const { selectedRoomId, selectRoom } = useRoomSelection( ROOM_THEMES[0].id, ); @@ -108,15 +108,11 @@ export const AppHubWidget = () => {
{ className={cn( 'absolute inset-0', cinematic - ? 'bg-[linear-gradient(180deg,rgba(2,6,23,0.22)_0%,rgba(2,6,23,0.34)_50%,rgba(2,6,23,0.52)_100%)]' - : 'bg-[linear-gradient(180deg,rgba(248,250,252,0.44)_0%,rgba(241,245,249,0.22)_42%,rgba(15,23,42,0.3)_100%)]', + ? 'bg-[radial-gradient(circle_at_24%_16%,rgba(148,163,184,0.14),transparent_36%),radial-gradient(circle_at_78%_14%,rgba(125,211,252,0.1),transparent_34%),linear-gradient(180deg,rgba(2,6,23,0.16)_0%,rgba(2,6,23,0.32)_52%,rgba(2,6,23,0.5)_100%)]' + : 'bg-[radial-gradient(circle_at_20%_14%,rgba(148,163,184,0.16),transparent_38%),radial-gradient(circle_at_82%_16%,rgba(125,211,252,0.12),transparent_38%),linear-gradient(180deg,rgba(248,250,252,0.32)_0%,rgba(241,245,249,0.12)_40%,rgba(15,23,42,0.26)_100%)]', )} />
{ />
-
-
+ { onGoalChipSelect={handleGoalChipSelect} onQuickEnter={handleQuickEnter} onOpenCustomEntry={() => setCustomEntryOpen(true)} + inStage /> -
- -
- -
- -
- setThoughtInboxOpen(true)} - /> -
+ )} + /> +
+ setThoughtInboxOpen(true)} + />
diff --git a/src/widgets/rooms-gallery-widget/ui/RoomsGalleryWidget.tsx b/src/widgets/rooms-gallery-widget/ui/RoomsGalleryWidget.tsx index 72ab734..6a5725a 100644 --- a/src/widgets/rooms-gallery-widget/ui/RoomsGalleryWidget.tsx +++ b/src/widgets/rooms-gallery-widget/ui/RoomsGalleryWidget.tsx @@ -1,5 +1,9 @@ -import { useState } from 'react'; -import { getHubRoomSections, type RoomTheme } from '@/entities/room'; +import { useEffect, useMemo, useState, type ReactNode } from 'react'; +import { + getHubRoomSections, + getRoomCardBackgroundStyle, + type RoomTheme, +} from '@/entities/room'; import { RoomPreviewCard } from '@/features/room-select'; import type { AppHubVisualMode } from '@/shared/config/appHubVisualMode'; import { cn } from '@/shared/lib/cn'; @@ -10,6 +14,7 @@ interface RoomsGalleryWidgetProps { rooms: RoomTheme[]; selectedRoomId: string; onRoomSelect: (roomId: string) => void; + startPanel: ReactNode; } export const RoomsGalleryWidget = ({ @@ -17,79 +22,148 @@ export const RoomsGalleryWidget = ({ rooms, selectedRoomId, onRoomSelect, + startPanel, }: RoomsGalleryWidgetProps) => { const [isCatalogOpen, setCatalogOpen] = useState(false); + const [carouselOffset, setCarouselOffset] = useState(0); const cinematic = visualMode === 'cinematic'; const selectedRoom = rooms.find((room) => room.id === selectedRoomId) ?? rooms[0]; - const { recommendedRooms, allRooms } = getHubRoomSections(rooms, selectedRoom.id, 5); + const { recommendedRooms, allRooms } = getHubRoomSections(rooms, selectedRoom.id, 6); + const canRotate = recommendedRooms.length > 1; + const rotatedRecommendedRooms = useMemo(() => { + if (recommendedRooms.length === 0) { + return []; + } + + const normalizedOffset = + ((carouselOffset % recommendedRooms.length) + recommendedRooms.length) % + recommendedRooms.length; + + return [ + ...recommendedRooms.slice(normalizedOffset), + ...recommendedRooms.slice(0, normalizedOffset), + ]; + }, [carouselOffset, recommendedRooms]); + + useEffect(() => { + setCarouselOffset(0); + }, [selectedRoomId]); return ( -
-
-
-

- Scene -

-

- 오늘의 공간 -

-
- -
- - - -
-

+
- 추천 공간 -

-
-
- {recommendedRooms.map((room) => ( - - ))} + /> +
+ +
+
+
+

Hub Stage

+

오늘의 공간

+
+
+ +
+
+

Selected Space

+

+ {selectedRoom.name} +

+

{selectedRoom.description}

+
+ +
+
{startPanel}
+ +
+
+

추천 공간

+
+ +
+ + +
+
+
+
+
+ {rotatedRecommendedRooms.map((room) => ( + + ))} +
+
+
+
-
+
void; onQuickEnter: () => void; onOpenCustomEntry: () => void; + inStage?: boolean; } export const StartRitualWidget = ({ @@ -23,16 +24,20 @@ export const StartRitualWidget = ({ onGoalChipSelect, onQuickEnter, onOpenCustomEntry, + inStage = false, }: StartRitualWidgetProps) => { const cinematic = visualMode === 'cinematic'; + const visibleGoalChips = inStage ? goalChips.slice(0, 2) : goalChips.slice(0, 3); return ( @@ -43,11 +48,11 @@ export const StartRitualWidget = ({ cinematic ? 'text-white/48' : 'text-brand-dark/46', )} > - Start Ritual + {inStage ? 'Quick Entry' : 'Start Ritual'}

@@ -59,7 +64,7 @@ export const StartRitualWidget = ({ cinematic ? 'text-white/72' : 'text-brand-dark/68', )} > - 비어 있어도 괜찮아요. 필요하면 공간 안에서 수정할 수 있어요. + 비어 있어도 바로 입장할 수 있어요.

@@ -76,7 +81,7 @@ export const StartRitualWidget = ({ />
- {goalChips.slice(0, 4).map((chip) => ( + {visibleGoalChips.map((chip) => ( -
+
diff --git a/src/widgets/thought-summary-entry/ui/ThoughtSummaryEntryWidget.tsx b/src/widgets/thought-summary-entry/ui/ThoughtSummaryEntryWidget.tsx index 6ceb674..e1cc8f5 100644 --- a/src/widgets/thought-summary-entry/ui/ThoughtSummaryEntryWidget.tsx +++ b/src/widgets/thought-summary-entry/ui/ThoughtSummaryEntryWidget.tsx @@ -5,14 +5,45 @@ interface ThoughtSummaryEntryWidgetProps { visualMode: AppHubVisualMode; thoughtCount: number; onOpen: () => void; + compact?: boolean; } export const ThoughtSummaryEntryWidget = ({ visualMode, thoughtCount, onOpen, + compact = false, }: ThoughtSummaryEntryWidgetProps) => { const cinematic = visualMode === 'cinematic'; + const countLabel = thoughtCount > 99 ? '99+' : `${thoughtCount}`; + + if (compact) { + return ( + + ); + } return (