[object Object]

← 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

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&rsquo;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 →