← back to Homesonspec
Home detail: branded hero header + status badge, 'more homes in this community' carousel, prominent builder-contact CTA (builders-want-to-sell), full brand reskin
77b81ebe0d84870dcaea1201ae2c20bac1f31d5a · 2026-07-22 18:19:25 -0700 · Steve
Files touched
M apps/web/src/app/homes/[id]/page.tsx
Diff
commit 77b81ebe0d84870dcaea1201ae2c20bac1f31d5a
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 22 18:19:25 2026 -0700
Home detail: branded hero header + status badge, 'more homes in this community' carousel, prominent builder-contact CTA (builders-want-to-sell), full brand reskin
---
apps/web/src/app/homes/[id]/page.tsx | 131 ++++++++++++++++++++++++++++-------
1 file changed, 105 insertions(+), 26 deletions(-)
diff --git a/apps/web/src/app/homes/[id]/page.tsx b/apps/web/src/app/homes/[id]/page.tsx
index 58188fc..f8e3abd 100644
--- a/apps/web/src/app/homes/[id]/page.tsx
+++ b/apps/web/src/app/homes/[id]/page.tsx
@@ -12,6 +12,11 @@ const STATUS_LABELS: Record<string, string> = {
UNDER_CONSTRUCTION: "Under construction",
PLANNED: "Planned",
};
+const STATUS_DOT: Record<string, string> = {
+ MOVE_IN_READY: "#16a34a",
+ UNDER_CONSTRUCTION: "#f59e0b",
+ PLANNED: "#3b82f6",
+};
export default async function HomeDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
@@ -27,12 +32,28 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
});
if (!home || home.status !== "PUBLISHED" || home.isDemo) notFound();
- const [communityIncentives, evidence] = await Promise.all([
+ const [communityIncentives, evidence, otherHomes] = await Promise.all([
prisma.incentive.findMany({ where: { communityId: home.communityId, scope: "COMMUNITY" } }),
prisma.sourceEvidence.findMany({
where: { entityType: "INVENTORY_HOME", entityId: home.id },
orderBy: { field: "asc" },
}),
+ // "Carousel of homes in the project" — other available homes in this community.
+ prisma.inventoryHome.findMany({
+ where: {
+ communityId: home.communityId,
+ status: "PUBLISHED",
+ isDemo: false,
+ freshness: { not: "INACTIVE" },
+ id: { not: home.id },
+ },
+ select: {
+ id: true, street: true, city: true, state: true, price: true,
+ beds: true, bathsTotal: true, sqft: true, constructionStatus: true,
+ },
+ orderBy: { publishedAt: "desc" },
+ take: 12,
+ }),
]);
const incentives = [...home.incentives, ...communityIncentives];
@@ -58,34 +79,49 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
return (
<div className="mx-auto max-w-5xl px-4 py-8">
<nav className="text-sm text-neutral-500">
- <Link href="/search" className="hover:text-teal-700">Search</Link> ·{" "}
- <Link href={`/communities/${home.community.slug}`} className="hover:text-teal-700">
+ <Link href="/search" className="hover:text-brand-700">Search</Link> ·{" "}
+ <Link href={`/communities/${home.community.slug}`} className="hover:text-brand-700">
{home.community.name}
</Link>
</nav>
- <div className="mt-3 flex flex-wrap items-start justify-between gap-3">
- <div>
- <h1 className="text-3xl font-bold">{home.street ?? "Address available from builder"}</h1>
- <p className="mt-1 text-neutral-600">
- {home.city}, {home.state} {home.zip} · {home.community.name} ·{" "}
- <Link href={`/builders/${home.builder.slug}`} className="text-teal-700 hover:underline">
- {home.builder.name}
- </Link>
- </p>
- </div>
- <div className="text-right">
- <div className="text-3xl font-bold text-teal-800" data-testid="detail-price">
- {fmtPrice(home.price === null ? null : Number(home.price))}
+ {/* Branded hero header. Honest — no fabricated photography; builder imagery
+ renders here once ingested (see the image-ingestion follow-up). */}
+ <div className="mt-3 overflow-hidden rounded-2xl border border-brand-600/30 shadow-[var(--shadow-card)]">
+ <div className="relative h-56 bg-gradient-to-br from-brand-800 via-brand-700 to-brand-500 sm:h-64">
+ <span className="badge-ready absolute left-4 top-4 gap-1.5">
+ <span className="h-2 w-2 rounded-full" style={{ background: STATUS_DOT[home.constructionStatus] ?? "#16a34a" }} />
+ {STATUS_LABELS[home.constructionStatus] ?? home.constructionStatus}
+ </span>
+ <div className="absolute inset-x-0 bottom-0 flex flex-wrap items-end justify-between gap-3 bg-gradient-to-t from-brand-950/80 to-transparent p-5 text-white">
+ <div>
+ <h1 className="font-display text-2xl font-semibold sm:text-3xl">
+ {home.street ?? "Address available from builder"}
+ </h1>
+ <p className="mt-1 text-sm text-brand-100">
+ {home.city}, {home.state} {home.zip} · {home.community.name}
+ </p>
+ </div>
+ <div className="text-right">
+ <div className="font-display text-3xl font-semibold" data-testid="detail-price">
+ {fmtPrice(home.price === null ? null : Number(home.price))}
+ </div>
+ {home.previousPrice && home.price && Number(home.previousPrice) !== Number(home.price) ? (
+ <div className="text-sm text-brand-200 line-through">{fmtPrice(Number(home.previousPrice))}</div>
+ ) : null}
+ </div>
</div>
- {home.previousPrice && home.price && Number(home.previousPrice) !== Number(home.price) ? (
- <div className="text-sm text-neutral-400 line-through">{fmtPrice(Number(home.previousPrice))}</div>
- ) : null}
</div>
</div>
+ <p className="mt-2 text-sm text-neutral-600">
+ Built by{" "}
+ <Link href={`/builders/${home.builder.slug}`} className="font-semibold text-brand-700 hover:underline">
+ {home.builder.name}
+ </Link>
+ </p>
{/* Verification block — the trust surface. */}
- <div className="mt-4 flex flex-wrap items-center gap-3 rounded-lg border border-neutral-200 bg-neutral-50 p-3 text-sm" data-testid="verification-block">
+ <div className="mt-4 flex flex-wrap items-center gap-3 rounded-xl border border-neutral-200 bg-white p-3 text-sm shadow-[var(--shadow-card)]" data-testid="verification-block">
<VerificationBadge label={home.verificationLabel} />
<span className="text-neutral-600">
Last verified:{" "}
@@ -95,7 +131,7 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
</span>
{home.source ? <span className="text-neutral-500">Source: {home.source.name}</span> : null}
{home.sourceUrl ? (
- <a href={home.sourceUrl} rel="nofollow noopener noreferrer" className="text-teal-700 hover:underline">
+ <a href={home.sourceUrl} rel="nofollow noopener noreferrer" className="text-brand-700 hover:underline">
Original source
</a>
) : null}
@@ -103,7 +139,7 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
<div className="mt-6 grid gap-8 md:grid-cols-[1fr_320px]">
<div>
- <h2 className="text-xl font-semibold">Home facts</h2>
+ <h2 className="font-display text-xl font-semibold text-brand-900">Home facts</h2>
<dl className="mt-3 grid grid-cols-2 gap-x-6 gap-y-2 text-sm sm:grid-cols-3">
{facts.map(([label, value]) => (
<div key={label} className="border-b border-neutral-100 py-1.5">
@@ -118,7 +154,7 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
{incentives.length > 0 && (
<section className="mt-8">
- <h2 className="text-xl font-semibold">Incentives</h2>
+ <h2 className="font-display text-xl font-semibold text-brand-900">Incentives</h2>
<ul className="mt-3 space-y-3">
{incentives.map((incentive) => (
<li key={incentive.id} className="rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm">
@@ -136,7 +172,7 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
)}
<section className="mt-8">
- <h2 className="text-xl font-semibold">Data provenance</h2>
+ <h2 className="font-display text-xl font-semibold text-brand-900">Data provenance</h2>
<p className="mt-1 text-sm text-neutral-500">
Every displayed fact traces to source evidence with a retrieval timestamp.
</p>
@@ -172,10 +208,53 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
</section>
</div>
- <aside>
- <LeadForm homeId={home.id} communityId={home.communityId} builderName={home.builder.name} />
+ <aside className="self-start lg:sticky lg:top-[84px]">
+ <div className="card p-4">
+ <h2 className="font-display text-lg font-semibold text-brand-900">Contact {home.builder.name}</h2>
+ <p className="mt-1 text-sm text-neutral-600">
+ This builder is actively selling this home — send a note and they’ll follow up. No obligation.
+ </p>
+ <div className="mt-3">
+ <LeadForm homeId={home.id} communityId={home.communityId} builderName={home.builder.name} />
+ </div>
+ </div>
</aside>
</div>
+
+ {/* Carousel of homes in the project — other available homes in this community. */}
+ {otherHomes.length > 0 && (
+ <section className="mt-12">
+ <div className="flex items-end justify-between">
+ <h2 className="font-display text-xl font-semibold text-brand-900">
+ More homes in {home.community.name}
+ </h2>
+ <Link href={`/communities/${home.community.slug}`} className="text-sm font-semibold text-brand-700 hover:text-brand-800">
+ View community →
+ </Link>
+ </div>
+ <div className="mt-4 flex snap-x gap-4 overflow-x-auto pb-3" data-testid="community-carousel">
+ {otherHomes.map((h) => (
+ <Link key={h.id} href={`/homes/${h.id}`} className="card-interactive w-64 shrink-0 snap-start overflow-hidden">
+ <div className="relative h-32 bg-gradient-to-br from-brand-800 via-brand-700 to-brand-500">
+ <span className="absolute left-2 top-2 inline-flex items-center gap-1 rounded-full bg-white/95 px-2 py-0.5 text-[11px] font-medium text-neutral-700">
+ <span className="h-1.5 w-1.5 rounded-full" style={{ background: STATUS_DOT[h.constructionStatus] ?? "#16a34a" }} />
+ {STATUS_LABELS[h.constructionStatus] ?? h.constructionStatus}
+ </span>
+ </div>
+ <div className="p-3">
+ <div className="font-display text-lg font-semibold text-brand-900">
+ {fmtPrice(h.price === null ? null : Number(h.price))}
+ </div>
+ <div className="mt-0.5 truncate text-sm text-neutral-600">{h.street ?? "Address from builder"}</div>
+ <div className="mt-1 text-xs text-neutral-500">
+ {h.beds ?? "—"} bd · {h.bathsTotal?.toString() ?? "—"} ba · {h.sqft?.toLocaleString() ?? "—"} sqft
+ </div>
+ </div>
+ </Link>
+ ))}
+ </div>
+ </section>
+ )}
</div>
);
}
← da530f2 Amazon-style faceted search rail: State (multi-select, 50-st
·
back to Homesonspec
·
Builder images: capture per-home photos from Lennar feed (el 13997e3 →