This commit is contained in:
Andras Schmelczer 2026-03-15 17:38:26 +00:00
parent 80c093b7ba
commit f72c43a9fa
101 changed files with 2168 additions and 1177 deletions

View file

@ -11,10 +11,10 @@ import type { FeatureMeta } from '../../types';
export default function HomePage({
onOpenDashboard,
onOpenPricing,
onOpenPricing: _onOpenPricing,
theme = 'light',
features = [],
hidePricing,
hidePricing: _hidePricing,
}: {
onOpenDashboard: () => void;
onOpenPricing: () => void;
@ -79,8 +79,8 @@ export default function HomePage({
House hunting? Make your biggest investment your smartest move.
</p>
<p className="text-lg text-warm-400 mb-8 max-w-xl">
So many options - choosing the right one can feel overwhelming. Our interactive map makes it simple: select your must-haves and instantly see the areas that
fit.
So many options - choosing the right one can feel overwhelming. Our interactive map
makes it simple: select your must-haves and instantly see the areas that fit.
</p>
<div className="flex items-center gap-4 mb-10">
<button
@ -100,7 +100,11 @@ export default function HomePage({
const scroller = target.closest('.overflow-y-auto') as HTMLElement | null;
if (!scroller) return;
const start = scroller.scrollTop;
const end = start + target.getBoundingClientRect().top - scroller.getBoundingClientRect().top - 48;
const end =
start +
target.getBoundingClientRect().top -
scroller.getBoundingClientRect().top -
48;
const distance = end - start;
const duration = 1200;
let startTime: number;
@ -146,7 +150,8 @@ export default function HomePage({
const scroller = target.closest('.overflow-y-auto') as HTMLElement | null;
if (!scroller) return;
const start = scroller.scrollTop;
const end = start + target.getBoundingClientRect().top - scroller.getBoundingClientRect().top;
const end =
start + target.getBoundingClientRect().top - scroller.getBoundingClientRect().top;
const distance = end - start;
const duration = 1200;
let startTime: number;
@ -176,13 +181,13 @@ export default function HomePage({
</h2>
<div className="space-y-4 text-lg md:text-xl leading-relaxed text-warm-700 dark:text-warm-300">
<p>
Listings show what&apos;s available, not what&apos;s possible &mdash; fragments without context.
Traditional tools force you to begin with a location, separating area insight from property detail.
You search, cross-reference, and repeat per location.
Listings show what&apos;s available, not what&apos;s possible &mdash; fragments
without context. Traditional tools force you to begin with a location, separating area
insight from property detail. You search, cross-reference, and repeat per location.
</p>
<p>
We take a different approach. Start with what matters to you, and the right places reveal themselves.
No context lost. No property missed.
We take a different approach. Start with what matters to you, and the right places
reveal themselves. No context lost. No property missed.
</p>
</div>
</div>
@ -217,66 +222,75 @@ export default function HomePage({
{/* Right: Comparison table */}
<div id="comparison">
<h2 className="text-3xl font-bold text-navy-950 dark:text-warm-100 mb-10">
Others vs{' '}<span className="inline-flex items-baseline gap-3 whitespace-nowrap">Perfect Postcode <LogoIcon className="w-8 h-8 text-teal-600 dark:text-teal-400" /></span>
Others vs{' '}
<span className="inline-flex items-baseline gap-3 whitespace-nowrap">
Perfect Postcode{' '}
<LogoIcon className="w-8 h-8 text-teal-600 dark:text-teal-400" />
</span>
</h2>
<div className="overflow-x-auto rounded-xl border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-800 shadow-sm">
<table className="w-full text-left">
<thead>
<tr className="border-b border-warm-200 dark:border-warm-700 bg-warm-50 dark:bg-warm-800">
<th className="px-2 md:px-5 py-3 md:py-4 text-xs md:text-sm font-bold text-navy-950 dark:text-warm-100" />
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-bold text-navy-950 dark:text-warm-100 text-center">
Listing portals
</th>
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-bold text-navy-950 dark:text-warm-100 text-center">
{'\u201CCheck my postcode\u201D'}
</th>
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-bold text-navy-950 dark:text-warm-100 text-center">
Area guides
</th>
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-extrabold text-navy-950 dark:text-warm-100 text-center bg-teal-50 dark:bg-teal-900/30">
Perfect Postcode
</th>
</tr>
</thead>
<tbody>
{FEATURE_ROWS.map((row, i) => (
<tr
key={row.feature}
className={
i < FEATURE_ROWS.length - 1
? 'border-b border-warm-100 dark:border-warm-800'
: ''
}
>
<td className="px-2 md:px-5 py-2.5 md:py-3.5 text-xs md:text-sm text-warm-700 dark:text-warm-300">
{row.feature}
{row.subtitle && (
<div className="italic text-warm-500 dark:text-warm-400">{row.subtitle}</div>
)}
</td>
{[row.listings, row.postcode, row.guides].map((has, j) => (
<td
key={j}
className={`px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base md:text-lg ${has ? 'text-green-500' : 'text-red-500'}`}
>
{has ? '\u2713' : '\u2717'}
</td>
))}
<td className="px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base md:text-lg text-green-500 bg-teal-50 dark:bg-teal-900/30">
&#x2713;
</td>
<div className="overflow-x-auto rounded-xl border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-800 shadow-sm">
<table className="w-full text-left">
<thead>
<tr className="border-b border-warm-200 dark:border-warm-700 bg-warm-50 dark:bg-warm-800">
<th className="px-2 md:px-5 py-3 md:py-4 text-xs md:text-sm font-bold text-navy-950 dark:text-warm-100" />
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-bold text-navy-950 dark:text-warm-100 text-center">
Listing portals
</th>
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-bold text-navy-950 dark:text-warm-100 text-center">
{'\u201CCheck my postcode\u201D'}
</th>
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-bold text-navy-950 dark:text-warm-100 text-center">
Area guides
</th>
<th className="px-1.5 md:px-3 py-3 md:py-4 text-[10px] md:text-xs font-extrabold text-navy-950 dark:text-warm-100 text-center bg-teal-50 dark:bg-teal-900/30">
Perfect Postcode
</th>
</tr>
))}
</tbody>
</table>
</div>
</thead>
<tbody>
{FEATURE_ROWS.map((row, i) => (
<tr
key={row.feature}
className={
i < FEATURE_ROWS.length - 1
? 'border-b border-warm-100 dark:border-warm-800'
: ''
}
>
<td className="px-2 md:px-5 py-2.5 md:py-3.5 text-xs md:text-sm text-warm-700 dark:text-warm-300">
{row.feature}
{row.subtitle && (
<div className="italic text-warm-500 dark:text-warm-400">
{row.subtitle}
</div>
)}
</td>
{[row.listings, row.postcode, row.guides].map((has, j) => (
<td
key={j}
className={`px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base md:text-lg ${has ? 'text-green-500' : 'text-red-500'}`}
>
{has ? '\u2713' : '\u2717'}
</td>
))}
<td className="px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base md:text-lg text-green-500 bg-teal-50 dark:bg-teal-900/30">
&#x2713;
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{/* Scrollytelling: Problem + Solution + Demo map */}
<h2 id="demo" className="text-3xl font-bold text-navy-950 dark:text-warm-100 text-center pt-16 mb-8">
<h2
id="demo"
className="text-3xl font-bold text-navy-950 dark:text-warm-100 text-center pt-16 mb-8"
>
See It in Action
</h2>
<ScrollStory features={features} theme={theme} />
@ -311,27 +325,48 @@ export default function HomePage({
const FEATURE_ROWS = [
// listings postcode guides
{ feature: 'Search without choosing an area first', subtitle: '(start with needs, not a location)', listings: false, postcode: false, guides: false },
{ feature: 'Area data', subtitle: '(crime, schools, noise, broadband)', listings: false, postcode: true, guides: true },
{ feature: 'Property-specific data', subtitle: '(price, EPC, floor area)', listings: true, postcode: false, guides: false },
{ feature: '56 combinable filters in one place', subtitle: '(all insights, one interactive map)', listings: false, postcode: false, guides: false },
{
feature: 'Search without choosing an area first',
subtitle: '(start with needs, not a location)',
listings: false,
postcode: false,
guides: false,
},
{
feature: 'Area data',
subtitle: '(crime, schools, noise, broadband)',
listings: false,
postcode: true,
guides: true,
},
{
feature: 'Property-specific data',
subtitle: '(price, EPC, floor area)',
listings: true,
postcode: false,
guides: false,
},
{
feature: '56 combinable filters in one place',
subtitle: '(all insights, one interactive map)',
listings: false,
postcode: false,
guides: false,
},
];
const HOW_STEPS = [
{
title: 'Set your must-haves',
description:
'Budget, commute, schools \u2014 the map shows only what qualifies.',
description: 'Budget, commute, schools \u2014 the map shows only what qualifies.',
},
{
title: 'Explore areas and discover hidden gems',
description:
'Zoom in, dig into details and nice to haves.',
description: 'Zoom in, dig into details and nice to haves.',
},
{
title: 'Drill into postcodes',
description:
'See individual properties, sale prices, floor area, and compare.',
description: 'See individual properties, sale prices, floor area, and compare.',
},
{
title: 'Shortlist with confidence',