Replace landing hero PixelAvatar with live PetHabitat preview.

Shows a tamagotchi-style mini pet screen for guests and signed-in users, with a skeleton while loading customization data.
This commit is contained in:
2026-05-24 05:09:27 -06:00
parent 7dab056eba
commit 365de4354b
3 changed files with 105 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
import { Suspense } from "react";
import { PixelAvatar } from "@/components/avatar/pixel-avatar";
import { LandingPetPreview } from "@/components/landing/landing-pet-preview";
import { LandingHeroActions } from "@/components/layout/landing-hero-actions";
import { SiteFooter, SiteHeader } from "@/components/layout/site-header";
import {
@@ -37,7 +37,7 @@ export default function Home() {
<Card className="border-dashed">
<CardContent className="flex flex-col items-center gap-6 pt-8">
<PixelAvatar mood="happy" />
<LandingPetPreview />
<div className="space-y-2 text-center">
<p className="text-sm font-medium">Your daily loop</p>
<p className="text-sm text-muted-foreground">

View File

@@ -0,0 +1,96 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { PetHabitat } from "@/components/pet/pet-habitat";
import {
defaultAvatarCustomization,
getAvatarCustomization,
type AvatarCustomization,
} from "@/lib/avatar-customization-storage";
import { routes } from "@/lib/routes";
import { createClient } from "@/lib/supabase/client";
import { cn } from "@/lib/utils";
function LandingPetPreviewSkeleton() {
return (
<div
aria-hidden="true"
className="tamagotchi-shell w-full max-w-sm overflow-hidden p-3 sm:p-4"
>
<div className="mb-3 space-y-2 border-b-2 border-border/60 pb-3">
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
<div className="h-2 w-16 animate-pulse rounded bg-muted" />
</div>
<div className="tamagotchi-lcd aspect-[4/3] w-full animate-pulse bg-muted/40" />
</div>
);
}
export function LandingPetPreview() {
const [isLoading, setIsLoading] = useState(true);
const [isSignedIn, setIsSignedIn] = useState(false);
const [customization, setCustomization] = useState<AvatarCustomization>(
defaultAvatarCustomization,
);
const loadPreview = useCallback(async () => {
setIsLoading(true);
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
setIsSignedIn(false);
setCustomization(defaultAvatarCustomization);
setIsLoading(false);
return;
}
setIsSignedIn(true);
try {
setCustomization(await getAvatarCustomization());
} catch {
setCustomization(defaultAvatarCustomization);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
void loadPreview();
}, [loadPreview]);
if (isLoading) {
return <LandingPetPreviewSkeleton />;
}
const preview = (
<PetHabitat
customization={customization}
className="w-full max-w-sm"
showStyleLink={false}
/>
);
if (isSignedIn) {
return (
<Link
href={routes.avatar}
aria-label="Go to your pet"
className={cn(
"block w-full max-w-sm rounded-sm transition-transform",
"hover:scale-[1.01] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
)}
>
{preview}
</Link>
);
}
return preview;
}

View File

@@ -22,6 +22,7 @@ type PetHabitatProps = {
customization: AvatarCustomization;
className?: string;
fillViewport?: boolean;
showStyleLink?: boolean;
};
const MOOD_LABELS = {
@@ -34,6 +35,7 @@ export function PetHabitat({
customization,
className,
fillViewport = false,
showStyleLink = true,
}: PetHabitatProps) {
const [condition, setCondition] = useState<AvatarCondition>(
defaultAvatarCondition,
@@ -87,9 +89,11 @@ export function PetHabitat({
<PixelGauge type="heart" value={condition.health} variant="overlay" />
<PixelGauge type="energy" value={condition.energy} variant="overlay" />
</div>
{showStyleLink ? (
<div className="absolute right-2 top-2 z-20">
<PetStyleLink />
</div>
) : null}
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-[2] h-8 bg-gradient-to-t from-black/35 to-transparent" />
</div>
</div>