From a2600d1caaa652e562eb5904a1434705dfaa8ffc Mon Sep 17 00:00:00 2001 From: filemon Date: Mon, 6 Apr 2026 01:15:11 -0300 Subject: [PATCH] Fix room layout proportions, responsive sizing, and visual composition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses layout issues where the room felt cut off and controls were disproportionate, especially on mobile. Key changes: RoomActionButton — responsive sizing: Mobile: size-14 circle, size-7 icons Desktop: size-20 circle, size-9 icons Tighter gap-1 and text-[10px] labels on mobile ItemCarousel — responsive behavior: Mobile: focused item only + compact arrows, no prev/next previews Desktop: focused item + translucent side previews Smaller focused item (text-4xl mobile, text-5xl desktop) BlobbiRoomHero — reduced visual footprint: Blobbi visual: tighter responsive scale (size-48 -> size-60 -> size-80) Stats crown: reduced margin (mb-6 sm:mb-10 vs mb-14) Stat indicators: size-14 sm:size-[4.5rem] (was size-[4.5rem] sm:size-20) Glow blur reduced on mobile (-m-16 vs -m-24) overflow-hidden to prevent hero from clipping HomeRoom — unified bottom bar: Removed -mt-10 negative margin hack that caused visual clipping Photo, Carousel, and Companion now sit in one flex row with items-center alignment, forming a single cohesive bottom composition All rooms — standardized bottom bar pattern: Consistent px-3 sm:px-6 pb-4 sm:pb-6 pt-1 spacing flex items-center justify-between gap-1 sm:gap-3 layout Spacer divs match button widths (w-14 sm:w-20) for balance Room shell content area uses flex flex-col to ensure rooms fill the available vertical space correctly. --- .../rooms/components/BlobbiCareRoom.tsx | 53 +++++------- .../rooms/components/BlobbiHatcheryRoom.tsx | 18 ++-- .../rooms/components/BlobbiHomeRoom.tsx | 86 +++++++++---------- .../rooms/components/BlobbiKitchenRoom.tsx | 25 ++---- .../rooms/components/BlobbiRestRoom.tsx | 31 +++---- .../rooms/components/BlobbiRoomHero.tsx | 43 +++++----- .../rooms/components/BlobbiRoomShell.tsx | 2 +- src/blobbi/rooms/components/ItemCarousel.tsx | 34 ++++---- .../rooms/components/RoomActionButton.tsx | 20 ++--- 9 files changed, 140 insertions(+), 172 deletions(-) diff --git a/src/blobbi/rooms/components/BlobbiCareRoom.tsx b/src/blobbi/rooms/components/BlobbiCareRoom.tsx index 864be26f..2aeff9c0 100644 --- a/src/blobbi/rooms/components/BlobbiCareRoom.tsx +++ b/src/blobbi/rooms/components/BlobbiCareRoom.tsx @@ -3,13 +3,7 @@ /** * BlobbiCareRoom — The bathroom / hygiene room. * - * Layout: - * - BlobbiRoomHero (Blobbi visual + stats) - * - Center: single-focus carousel with hygiene + medicine items - * - Bottom left: towel (RoomActionButton style) - * - Bottom right: shower (RoomActionButton style) - * - * Future: interactions could become drag-based. + * Bottom bar: Towel (left) | hygiene+medicine carousel (center) | Shower (right) */ import { useMemo } from 'react'; @@ -40,48 +34,41 @@ export function BlobbiCareRoom({ ctx }: BlobbiCareRoomProps) { []); const isDisabled = isPublishing || actionInProgress !== null || isUsingItem; - - // Towel is shown as a dedicated button, not in the carousel const towelItem = hygieneItems.find(i => i.id === 'hyg_towel'); - // Carousel: hygiene (except towel) + medicine const carouselEntries = useMemo(() => { const hygiene = getLiveShopItems() .filter(i => i.type === 'hygiene' && i.id !== 'hyg_towel') .map(i => ({ id: i.id, icon: {i.icon}, label: i.name })); - const medicine = getLiveShopItems() .filter(i => i.type === 'medicine') .map(i => ({ id: i.id, icon: {i.icon}, label: i.name })); - return [...hygiene, ...medicine]; }, []); return (
- {/* ── Hero ── */} - {/* ── Bottom Action Bar ── */} {!isActiveFloatingCompanion && ( -
-
- {/* Bottom left — Towel */} -
- {towelItem && ( - {towelItem.icon}} - label="Towel" - color="text-cyan-500" - glowHex="#06b6d4" - onClick={() => handleUseItemFromTab(towelItem.id)} - disabled={isDisabled} - loading={isUsingItem && usingItemId === towelItem.id} - /> - )} -
+
+
+ {/* Left — Towel */} + {towelItem ? ( + {towelItem.icon}} + label="Towel" + color="text-cyan-500" + glowHex="#06b6d4" + onClick={() => handleUseItemFromTab(towelItem.id)} + disabled={isDisabled} + loading={isUsingItem && usingItemId === towelItem.id} + /> + ) : ( +
+ )} - {/* Center: single-focus carousel */} + {/* Center carousel */}
- {/* Bottom right — Shower */} + {/* Right — Shower */} } + icon={} label="Shower" color="text-blue-500" glowHex="#3b82f6" diff --git a/src/blobbi/rooms/components/BlobbiHatcheryRoom.tsx b/src/blobbi/rooms/components/BlobbiHatcheryRoom.tsx index f9cca590..21dc041d 100644 --- a/src/blobbi/rooms/components/BlobbiHatcheryRoom.tsx +++ b/src/blobbi/rooms/components/BlobbiHatcheryRoom.tsx @@ -119,24 +119,24 @@ export function BlobbiHatcheryRoom({ ctx }: BlobbiHatcheryRoomProps) { {/* ── Bottom Action Bar ── */} {!isActiveFloatingCompanion && ( -
-
- {/* Bottom left — Blobbis selector */} +
+
+ {/* Left — Blobbis selector */} } + icon={} label="Blobbis" color="text-primary" glowHex="var(--primary)" onClick={() => setShowBlobbisPanel(true)} badge={companions.length > 1 ? ( - + {companions.length} ) : undefined} /> {/* Center — Main hatch/evolve action */} -
+
{/* Active process: Hatch/Evolve CTA or progress */} {hasActiveProcess && allCompleted && !isTasksLoading && ( @@ -128,7 +127,7 @@ export function BlobbiRoomHero({ ctx, className, hideStats, hideName }: BlobbiRo } return ( -
+
{/* Stats crown */} {!hideStats && } @@ -140,7 +139,7 @@ export function BlobbiRoomHero({ ctx, className, hideStats, hideName }: BlobbiRo animation: `blobbi-bob ${4 - (currentStats.happiness / 100) * 1.5}s ease-in-out infinite, blobbi-sway ${6 - (currentStats.happiness / 100) * 2}s ease-in-out infinite`, } : undefined} > -
+
@@ -159,7 +158,7 @@ export function BlobbiRoomHero({ ctx, className, hideStats, hideName }: BlobbiRo {/* Blobbi Name — hidden for eggs */} {!hideName && !isEgg && (

{companion.name} @@ -195,7 +194,7 @@ function StatsCrown({ const count = allStats.length; const isSmall = heroWidth < 400; const arcSpread = isSmall - ? (count <= 2 ? 90 : count <= 3 ? 130 : 160) + ? (count <= 2 ? 80 : count <= 3 ? 120 : 150) : (count <= 2 ? 80 : count <= 3 ? 120 : 160); const arcHalf = arcSpread / 2; const angles = count === 1 @@ -203,11 +202,11 @@ function StatsCrown({ : allStats.map((_, i) => -arcHalf + (arcSpread / (count - 1)) * i); return ( -
+
{allStats.map((s, i) => { const angleDeg = angles[i]; const angleRad = (angleDeg * Math.PI) / 180; - const radius = Math.min(210, Math.max(140, (heroWidth - 340) / (640 - 340) * (210 - 140) + 140)); + const radius = Math.min(180, Math.max(100, (heroWidth - 340) / (640 - 340) * (180 - 100) + 100)); const x = Math.sin(angleRad) * radius; const y = Math.cos(angleRad) * radius - radius; @@ -251,7 +250,7 @@ function StatIndicator({ stat, value, color, status = 'normal' }: StatIndicatorP return (
@@ -265,10 +264,10 @@ function StatIndicator({ stat, value, color, status = 'normal' }: StatIndicatorP />
- {IconComponent && } + {IconComponent && } {isLow && ( )} diff --git a/src/blobbi/rooms/components/BlobbiRoomShell.tsx b/src/blobbi/rooms/components/BlobbiRoomShell.tsx index 4b9fc9e0..09a7cb09 100644 --- a/src/blobbi/rooms/components/BlobbiRoomShell.tsx +++ b/src/blobbi/rooms/components/BlobbiRoomShell.tsx @@ -147,7 +147,7 @@ export function BlobbiRoomShell({ The `direction` in nav state tells which way to animate. For now, we simply render the active room instantly. */} -
+
diff --git a/src/blobbi/rooms/components/ItemCarousel.tsx b/src/blobbi/rooms/components/ItemCarousel.tsx index facf1962..45d2491a 100644 --- a/src/blobbi/rooms/components/ItemCarousel.tsx +++ b/src/blobbi/rooms/components/ItemCarousel.tsx @@ -3,11 +3,10 @@ /** * ItemCarousel — Single-focus carousel for room items. * - * Shows one main item at centre with translucent prev/next previews. - * Left/right arrows cycle through items in a loop. + * Mobile: focused item only + compact arrows (no prev/next previews) + * Desktop: focused item + translucent prev/next previews + arrows * * Each "item" is an opaque entry with id, icon, label, and an onUse callback. - * The carousel doesn't know about shop items or actions — the parent maps them. */ import { useState, useCallback } from 'react'; @@ -58,7 +57,7 @@ export function ItemCarousel({ if (count === 0) { return ( -
+

Nothing here yet

); @@ -68,15 +67,16 @@ export function ItemCarousel({ const prevItem = items[(index - 1 + count) % count]; const nextItem = items[(index + 1) % count]; const isThisActive = activeItemId === current.id; + const showPreviews = count >= 3; return ( -
+
{/* Left arrow */} - {/* Preview (prev) — only when 3+ items */} - {count >= 3 && ( -
+ {/* Preview (prev) — desktop only, 3+ items */} + {showPreviews && ( +
{prevItem.icon}
)} @@ -98,24 +98,24 @@ export function ItemCarousel({ onClick={() => onUse(current.id)} disabled={disabled} className={cn( - 'relative flex flex-col items-center gap-1 py-2 px-4 rounded-2xl transition-all duration-200 shrink-0', + 'relative flex flex-col items-center gap-0.5 py-1 px-3 sm:px-4 rounded-2xl transition-all duration-200 shrink-0', 'hover:-translate-y-0.5 active:scale-95', isThisActive && 'bg-accent/40', disabled && !isThisActive && 'opacity-50 pointer-events-none', )} > - + {current.icon} - {current.label} + {current.label} {isThisActive && ( - + )} - {/* Preview (next) — only when 3+ items */} - {count >= 3 && ( -
+ {/* Preview (next) — desktop only, 3+ items */} + {showPreviews && ( +
{nextItem.icon}
)} @@ -125,7 +125,7 @@ export function ItemCarousel({ onClick={next} disabled={disabled} className={cn( - 'size-8 rounded-full flex items-center justify-center shrink-0', + 'size-7 sm:size-8 rounded-full flex items-center justify-center shrink-0', 'text-muted-foreground/40 hover:text-foreground/70 hover:bg-accent/40', 'transition-all duration-200 active:scale-90', disabled && 'opacity-30 pointer-events-none', diff --git a/src/blobbi/rooms/components/RoomActionButton.tsx b/src/blobbi/rooms/components/RoomActionButton.tsx index 43330081..a0209bb7 100644 --- a/src/blobbi/rooms/components/RoomActionButton.tsx +++ b/src/blobbi/rooms/components/RoomActionButton.tsx @@ -3,12 +3,12 @@ /** * RoomActionButton — Unified circular action button for all rooms. * - * Matches the visual language of the original Photo and Companion buttons: - * - Large rounded-full circle with soft radial glow background - * - Icon centred inside - * - Label beneath - * - Hover lift + scale, active scale-down - * - Consistent size across all rooms + * Responsive sizing: + * - Mobile: size-14 circle, size-7 icons + * - Desktop (sm+): size-20 circle, size-9 icons + * + * Matches the soft radial glow of the original Photo / Companion buttons + * but at a smaller scale so the bottom bar feels proportional on mobile. */ import { Loader2 } from 'lucide-react'; @@ -48,7 +48,7 @@ export function RoomActionButton({ onClick={onClick} disabled={disabled} className={cn( - 'flex flex-col items-center gap-1.5 transition-all duration-300 ease-out', + 'flex flex-col items-center gap-1 transition-all duration-300 ease-out shrink-0', 'hover:-translate-y-1 hover:scale-110 active:scale-95', disabled && 'opacity-50 pointer-events-none', className, @@ -56,13 +56,13 @@ export function RoomActionButton({ >
{loading ? ( - + ) : ( icon )} @@ -73,7 +73,7 @@ export function RoomActionButton({
)}
- {label} + {label} ); }