diff --git a/src/components/marketing/property-card.tsx b/src/components/marketing/property-card.tsx index 177577a..9f4c859 100644 --- a/src/components/marketing/property-card.tsx +++ b/src/components/marketing/property-card.tsx @@ -20,8 +20,11 @@ export function PropertyCard({ property }: { property: PropertySummary }) { .filter(Boolean); return ( - -
+ // Flex column + clamped text: card height used to track content length, so + // one verbose listing stretched every sibling in the row. The price/CTA row + // is pushed to the bottom (mt-auto below) so cards line up. + +
-
+
-
{property.locationSummary}
-

{property.title}

-

{property.shortDescription}

+
{property.locationSummary}
+

{property.title}

+

{property.shortDescription}

{/* verification.label carries internal states ("Listing hidden", "Verification required"). Only the verified case is shown. */} {property.verification.status === "VERIFIED" ? (

{property.verification.label}

) : null}
-
+
{isLand ? ( <> - {property.landSizeSqm ? `${property.landSizeSqm} sqm` : "Land"} - {property.numberOfPlots ? `${property.numberOfPlots} plot${property.numberOfPlots === 1 ? "" : "s"}` : "Plots"} - {landSizes.length > 0 ? landSizes.slice(0, 2).join(", ") : "Flexible sizes"} + {property.landSizeSqm ? `${property.landSizeSqm} sqm` : "Land"} + {property.numberOfPlots ? `${property.numberOfPlots} plot${property.numberOfPlots === 1 ? "" : "s"}` : "Plots"} + {landSizes.length > 0 ? landSizes.slice(0, 2).join(", ") : "Flexible sizes"} ) : ( <> @@ -66,7 +69,7 @@ export function PropertyCard({ property }: { property: PropertySummary }) { )}
-
+
Starting from diff --git a/src/components/marketing/tenant-homepage.tsx b/src/components/marketing/tenant-homepage.tsx index edce099..14a6bc4 100644 --- a/src/components/marketing/tenant-homepage.tsx +++ b/src/components/marketing/tenant-homepage.tsx @@ -105,69 +105,20 @@ export async function TenantHomepage({ tenant }: { tenant: TenantContext }) { {siteContent.hero.subhead}

+ {/* One primary action. The secondary route (marketers) lives + in its own section further down, and the header already + carries the portal link — three CTAs here competed with the + search, which is the actual front door. */}
- + - - - - +
- {/* Property search — the front door of a real estate site. - Plain GET form to /properties, matching its filter params. */} -
- - - - -
-
-
- {[ - [siteContent.heroStats.inventoryLabel, latestInventory.total, siteContent.heroStats.inventoryNote], - [siteContent.heroStats.marketersLabel, leaderboard.length, siteContent.heroStats.marketersNote], - [siteContent.heroStats.trustLabel, testimonials.length, siteContent.heroStats.trustNote], - ].map(([label, value, note]) => ( - -
- {label} -
-
- {value} -
-

{note}

-
- ))}
@@ -194,25 +145,89 @@ export async function TenantHomepage({ tenant }: { tenant: TenantContext }) {
- -
-
- {siteContent.journey.heading} -
-
-
- {siteContent.journey.steps.map((step, index) => ( -
-
0{index + 1}
-
{step.title}
-

{step.description}

-
- ))} -
-
+ + {/* Search spans the hero's full width instead of sharing the text + column with the headline, which is what clipped the placeholders + ("City, state, or estate r", "Max budget ("). */} +
+
+ + + + +
+
+ + {/* Trust strip — only real numbers. A brand-new tenant otherwise + advertised "EXPERT MARKETERS 0 / HAPPY CLIENTS 0" in large type + above the fold, which is worse than showing nothing. A card with a + zero count is dropped, and the strip disappears entirely if fewer + than two survive. Labels and notes stay CMS-editable. */} + {(() => { + const stats = [ + { + label: siteContent.heroStats.inventoryLabel, + value: latestInventory.total, + note: siteContent.heroStats.inventoryNote, + }, + { + label: siteContent.heroStats.marketersLabel, + value: leaderboard.length, + note: siteContent.heroStats.marketersNote, + }, + { + label: siteContent.heroStats.trustLabel, + value: testimonials.length, + note: siteContent.heroStats.trustNote, + }, + ].filter((stat) => stat.value > 0); + + if (stats.length < 2) { + return null; + } + + return ( +
+ {stats.map((stat) => ( + +
+ {stat.label} +
+
{stat.value}
+

{stat.note}

+
+ ))} +
+ ); + })()} @@ -246,6 +261,32 @@ export async function TenantHomepage({ tenant }: { tenant: TenantContext }) { + {/* How it works — moved out of the hero's 0.8fr sidebar, where three + cards shared ~121px each and the copy broke to one word per line. + A primary trust explainer earns the full measure. */} + +
+ {/* No eyebrow badge here: the CMS heading is itself the section + name ("How it works" by default), so a badge above it just + repeats the words. Typography matches SectionHeading's title. */} +

+ {siteContent.journey.heading} +

+
+ {siteContent.journey.steps.map((step, index) => ( + +
0{index + 1}
+
{step.title}
+

{step.description}

+
+ ))} +
+
+
+ @@ -16,7 +21,9 @@ export function SectionHeading({

{title}

-

{description}

+ {description ? ( +

{description}

+ ) : null}
);