ruby homepage changes

This commit is contained in:
Andras Schmelczer 2026-02-21 20:49:56 +00:00
parent 94ebd0f614
commit 7777d4046e
7 changed files with 312 additions and 223 deletions

View file

@ -3,11 +3,13 @@ import MapComponent from '../map/Map';
import { apiUrl, assertOk, authHeaders, logNonAbortError } from '../../lib/api';
import { formatValue } from '../../lib/format';
import { zoomToResolution } from '../../lib/map-utils';
import { FEATURE_GRADIENT } from '../../lib/consts';
import { gradientToCss } from '../../lib/utils';
import { SpinnerIcon } from '../ui/icons/SpinnerIcon';
import type { FeatureMeta, HexagonData } from '../../types';
const DEMO_VIEW_START = { longitude: -1.9, latitude: 52.2, zoom: 5.5, pitch: 0 };
const DEMO_VIEW_END = { longitude: -1.9, latitude: 52.2, zoom: 12, pitch: 0 };
const DEMO_VIEW_START = { longitude: -0.12, latitude: 51.51, zoom: 5.5, pitch: 0 };
const DEMO_VIEW_END = { longitude: -0.12, latitude: 51.51, zoom: 7, pitch: 0 };
function easeOutCubic(t: number): number {
return 1 - Math.pow(1 - t, 3);
@ -28,7 +30,7 @@ interface StageDef {
const STAGES: StageDef[] = [
// 0: No filters — the problem
{ filters: {} },
{ filters: {}, colorFeature: 'Estimated current price' },
// 1: Price filter — "affordable price"
{
filters: { 'Estimated current price': [0, 0.25] },
@ -58,6 +60,7 @@ const STAGES: StageDef[] = [
'Good+ primary schools within 5km': [0.3, 1],
'Number of restaurants within 2km': [0.15, 1],
},
colorFeature: 'Number of restaurants within 2km',
},
];
@ -74,27 +77,26 @@ const STEPS: { heading: string | null; body: React.ReactNode }[] = [
<strong className="text-navy-950 dark:text-warm-100">
&pound;300k&ndash;&pound;400k
</strong>{' '}
on a home. Your research method? Scrolling through listings and hoping for the best.
</p>
<p className="text-lg leading-relaxed mb-4">
Listings only show what&apos;s on the market <em>right now</em> &mdash; a tiny, random
slice of what&apos;s actually out there. You&apos;ll never see the 3-bed Victorian on a
quiet street that sold six months ago, or the one that&apos;ll list next month.
</p>
<p className="text-base italic text-warm-500 dark:text-warm-400">
Your home is not a box of cereal. Don&apos;t let a discount on the wrong property distract
you from finding the right one.
on a home, somewhere commutable to work in, say, London. Your research method? Picking some areas you think are good based on word of mouth... then hope for the best.
</p>
</>
),
},
{
heading: 'Set your requirements. The map shows you where they intersect.',
heading: null,
body: (
<p className="text-lg leading-relaxed">
Say you want a home at an{' '}
<strong className="text-navy-950 dark:text-warm-100">affordable price</strong>&hellip;
</p>
<>
<div className="flex items-center gap-3 mb-3">
<div className="shrink-0 w-8 h-8 rounded-full bg-teal-600 text-white flex items-center justify-center font-bold text-sm">
1
</div>
<h3 className="text-xl font-bold text-navy-950 dark:text-warm-100">Set your must-haves</h3>
</div>
<p className="text-lg leading-relaxed">
Say you want a home at an{' '}
<strong className="text-navy-950 dark:text-warm-100">affordable price</strong>&hellip;
</p>
</>
),
},
{
@ -110,33 +112,25 @@ const STEPS: { heading: string | null; body: React.ReactNode }[] = [
{
heading: null,
body: (
<>
<p className="text-lg leading-relaxed mb-4">
&hellip;and{' '}
<strong className="text-navy-950 dark:text-warm-100">
restaurants within walking distance
</strong>
.
</p>
<p className="text-lg leading-relaxed font-semibold text-navy-950 dark:text-warm-100">
You haven&apos;t opened a single listing yet &mdash; and you already know exactly where to
focus.
</p>
</>
<p className="text-lg leading-relaxed">
&hellip;and{' '}
<strong className="text-navy-950 dark:text-warm-100">
restaurants within walking distance
</strong>
.
</p>
),
},
{
heading: null,
body: (
<>
<p className="text-xl font-bold text-navy-950 dark:text-warm-100 mb-2">
That&apos;s just three filters.
<p className="text-lg leading-relaxed mb-4 font-semibold text-navy-950 dark:text-warm-100">
No area chosen. No listings browsed. Yet you already know exactly where your needs are met.
</p>
<p className="text-lg leading-relaxed">
We&apos;ve built <strong className="text-navy-950 dark:text-warm-100">43</strong>.
Spanning property prices, commute times, school ratings, crime rates, broadband speeds,
road noise, energy efficiency, amenities, deprivation scores, demographics, and more. All
layered on top of each other, all filterable at once.
That&apos;s just 3 filters. We&apos;ve built <strong className="text-navy-950 dark:text-warm-100">56</strong> &mdash;
covering commute times, crime, broadband, noise, schools, amenities, and more.
</p>
</>
),
@ -318,7 +312,7 @@ export default function ScrollStory({ features, theme }: ScrollStoryProps) {
const deferredHexData = useDeferredValue(hexData);
return (
<section ref={sectionRef} className="relative">
<section ref={sectionRef} className="snap-start relative">
{/* Sticky map background */}
<div className="sticky top-0 h-[60vh] md:h-[calc(100dvh-3rem)] z-0">
<div className="absolute inset-0">
@ -396,6 +390,23 @@ export default function ScrollStory({ features, theme }: ScrollStoryProps) {
</div>
);
})}
{/* Color legend */}
{viewFeatureName && colorRange && (
<div className="pt-4 border-t border-warm-200 dark:border-warm-700 transition-opacity duration-700">
<div className="text-xs font-semibold uppercase tracking-wider text-warm-500 dark:text-warm-400 mb-2">
Colour
</div>
<div className="text-sm font-medium text-navy-950 dark:text-warm-100 mb-1.5">
{viewFeatureName}
</div>
<div className="h-2.5 rounded-full overflow-hidden" style={{ background: gradientToCss(FEATURE_GRADIENT) }} />
<div className="flex justify-between mt-1 text-xs text-warm-500 dark:text-warm-400">
<span>{formatValue(colorRange[0], viewMeta!)}</span>
<span>{formatValue(colorRange[1], viewMeta!)}</span>
</div>
</div>
)}
</div>
</div>
</div>