Add dark mode
This commit is contained in:
parent
5e210e14bd
commit
7235df0a97
14 changed files with 304 additions and 139 deletions
|
|
@ -44,12 +44,14 @@ function drawHex(ctx: CanvasRenderingContext2D, cx: number, cy: number, r: numbe
|
|||
ctx.closePath();
|
||||
}
|
||||
|
||||
function HexCanvas({ scrollProgress }: { scrollProgress: number }) {
|
||||
function HexCanvas({ scrollProgress, isDark = false }: { scrollProgress: number; isDark?: boolean }) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const hexesRef = useRef<Hex[]>([]);
|
||||
const animRef = useRef(0);
|
||||
const scrollRef = useRef(scrollProgress);
|
||||
scrollRef.current = scrollProgress;
|
||||
const isDarkRef = useRef(isDark);
|
||||
isDarkRef.current = isDark;
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
|
@ -100,13 +102,14 @@ function HexCanvas({ scrollProgress }: { scrollProgress: number }) {
|
|||
if (hex.y < -hex.size * 2) hex.y += h + hex.size * 4;
|
||||
if (hex.y > h + hex.size * 2) hex.y -= h + hex.size * 4;
|
||||
|
||||
ctx!.globalAlpha = hex.opacity * globalAlpha;
|
||||
ctx!.fillStyle = '#00a28c';
|
||||
const dark = isDarkRef.current;
|
||||
ctx!.globalAlpha = hex.opacity * globalAlpha * (dark ? 0.6 : 1);
|
||||
ctx!.fillStyle = dark ? '#058172' : '#00a28c';
|
||||
drawHex(ctx!, hex.x, hex.y, hex.size);
|
||||
ctx!.fill();
|
||||
|
||||
ctx!.globalAlpha = hex.opacity * 0.5 * globalAlpha;
|
||||
ctx!.strokeStyle = '#05c9aa';
|
||||
ctx!.globalAlpha = hex.opacity * 0.5 * globalAlpha * (dark ? 0.6 : 1);
|
||||
ctx!.strokeStyle = dark ? '#0a665b' : '#05c9aa';
|
||||
ctx!.lineWidth = 1;
|
||||
drawHex(ctx!, hex.x, hex.y, hex.size);
|
||||
ctx!.stroke();
|
||||
|
|
@ -155,7 +158,7 @@ function useFadeInRef() {
|
|||
|
||||
// --- Page ---
|
||||
|
||||
export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => void }) {
|
||||
export default function HomePage({ onOpenDashboard, theme = 'light' }: { onOpenDashboard: () => void; theme?: 'light' | 'dark' }) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [scrollProgress, setScrollProgress] = useState(0);
|
||||
|
||||
|
|
@ -182,27 +185,27 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
const ctaRef = useFadeInRef();
|
||||
|
||||
return (
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto bg-warm-50 relative">
|
||||
<HexCanvas scrollProgress={scrollProgress} />
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto bg-warm-50 dark:bg-warm-900 relative">
|
||||
<HexCanvas scrollProgress={scrollProgress} isDark={theme === 'dark'} />
|
||||
|
||||
<div className="relative" style={{ zIndex: 1 }}>
|
||||
{/* Hero */}
|
||||
<div className="max-w-3xl mx-auto px-6 pt-20 pb-24">
|
||||
<div
|
||||
ref={heroRef}
|
||||
className="fade-in-section backdrop-blur-sm bg-warm-50/60 rounded-2xl p-8 -mx-2"
|
||||
className="fade-in-section backdrop-blur-sm bg-warm-50/60 dark:bg-warm-900/60 rounded-2xl p-8 -mx-2"
|
||||
>
|
||||
<p className="text-teal-600 font-semibold tracking-wide uppercase text-sm mb-4">
|
||||
Find where to live, not just what's for sale
|
||||
</p>
|
||||
<h1 className="text-5xl font-extrabold text-navy-950 mb-6 leading-[1.1] tracking-tight">
|
||||
<h1 className="text-5xl font-extrabold text-navy-950 dark:text-warm-100 mb-6 leading-[1.1] tracking-tight">
|
||||
Every neighbourhood
|
||||
<br />
|
||||
in England & Wales.
|
||||
<br />
|
||||
<span className="text-teal-600">One map. Your rules.</span>
|
||||
</h1>
|
||||
<p className="text-xl text-warm-600 mb-8 leading-relaxed max-w-xl">
|
||||
<p className="text-xl text-warm-600 dark:text-warm-400 mb-8 leading-relaxed max-w-xl">
|
||||
Set the commute, budget, school rating, noise level, and crime threshold you'll
|
||||
accept. Narrowit shows you every area that qualifies — instantly.
|
||||
</p>
|
||||
|
|
@ -223,13 +226,13 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
{/* The flip */}
|
||||
<div className="max-w-3xl mx-auto px-6 pb-20">
|
||||
<div ref={problemRef} className="fade-in-section">
|
||||
<div className="rounded-2xl backdrop-blur-sm bg-warm-50/40 border border-warm-200/50 p-8">
|
||||
<div className="rounded-2xl backdrop-blur-sm bg-warm-50/40 dark:bg-warm-800/40 border border-warm-200/50 dark:border-warm-700/50 p-8">
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-warm-400 uppercase tracking-wide mb-2">
|
||||
The old way
|
||||
</h3>
|
||||
<p className="text-warm-700 leading-relaxed">
|
||||
<p className="text-warm-700 dark:text-warm-300 leading-relaxed">
|
||||
Pick a postcode. Google the schools. Check crime stats on another site. Look up
|
||||
commute times. Realise it's too expensive. Start over. Repeat 40 times.
|
||||
</p>
|
||||
|
|
@ -238,7 +241,7 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
<h3 className="text-sm font-semibold text-teal-600 uppercase tracking-wide mb-2">
|
||||
With Narrowit
|
||||
</h3>
|
||||
<p className="text-warm-700 leading-relaxed">
|
||||
<p className="text-warm-700 dark:text-warm-300 leading-relaxed">
|
||||
Tell the map what you need. Every hexagon that lights up is a place worth
|
||||
looking at. Drill into any one to see individual properties, prices, and energy
|
||||
ratings.
|
||||
|
|
@ -252,21 +255,21 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
{/* Filter showcase */}
|
||||
<div className="max-w-4xl mx-auto px-6 pb-20">
|
||||
<div ref={filtersRef} className="fade-in-section">
|
||||
<h2 className="text-3xl font-bold text-navy-950 mb-2 text-center">
|
||||
<h2 className="text-3xl font-bold text-navy-950 dark:text-warm-100 mb-2 text-center">
|
||||
12 datasets. One slider each.
|
||||
</h2>
|
||||
<p className="text-warm-500 text-center mb-10 max-w-lg mx-auto">
|
||||
<p className="text-warm-500 dark:text-warm-400 text-center mb-10 max-w-lg mx-auto">
|
||||
Every filter narrows the map in real time. Combine as many as you like.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{FILTERS.map((f) => (
|
||||
<div
|
||||
key={f.label}
|
||||
className="rounded-xl bg-white border border-warm-200 p-4 shadow-sm hover:shadow-md hover:border-teal-300 transition-all"
|
||||
className="rounded-xl bg-white dark:bg-warm-800 border border-warm-200 dark:border-warm-700 p-4 shadow-sm hover:shadow-md hover:border-teal-300 dark:hover:border-teal-600 transition-all"
|
||||
>
|
||||
<div className="text-2xl mb-2">{f.icon}</div>
|
||||
<div className="font-semibold text-navy-950 text-sm">{f.label}</div>
|
||||
<div className="text-xs text-warm-500 mt-0.5">{f.example}</div>
|
||||
<div className="font-semibold text-navy-950 dark:text-warm-100 text-sm">{f.label}</div>
|
||||
<div className="text-xs text-warm-500 dark:text-warm-400 mt-0.5">{f.example}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -276,7 +279,7 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
{/* How it works */}
|
||||
<div className="max-w-3xl mx-auto px-6 pb-20">
|
||||
<div ref={howRef} className="fade-in-section">
|
||||
<h2 className="text-3xl font-bold text-navy-950 mb-10 text-center">
|
||||
<h2 className="text-3xl font-bold text-navy-950 dark:text-warm-100 mb-10 text-center">
|
||||
Three clicks to clarity
|
||||
</h2>
|
||||
<div className="space-y-6">
|
||||
|
|
@ -286,8 +289,8 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
{i + 1}
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold text-navy-950 text-lg">{step.title}</h3>
|
||||
<p className="text-warm-600 mt-0.5">{step.body}</p>
|
||||
<h3 className="font-semibold text-navy-950 dark:text-warm-100 text-lg">{step.title}</h3>
|
||||
<p className="text-warm-600 dark:text-warm-400 mt-0.5">{step.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -302,7 +305,7 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
{STATS.map((s) => (
|
||||
<div key={s.label}>
|
||||
<div className="text-3xl font-extrabold text-teal-600">{s.value}</div>
|
||||
<div className="text-sm text-warm-500 mt-1">{s.label}</div>
|
||||
<div className="text-sm text-warm-500 dark:text-warm-400 mt-1">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -312,8 +315,8 @@ export default function HomePage({ onOpenDashboard }: { onOpenDashboard: () => v
|
|||
{/* Final CTA */}
|
||||
<div className="max-w-3xl mx-auto px-6 pb-24">
|
||||
<div ref={ctaRef} className="fade-in-section text-center">
|
||||
<h2 className="text-3xl font-bold text-navy-950 mb-3">Ready to narrow it down?</h2>
|
||||
<p className="text-warm-500 mb-8 max-w-md mx-auto">
|
||||
<h2 className="text-3xl font-bold text-navy-950 dark:text-warm-100 mb-3">Ready to narrow it down?</h2>
|
||||
<p className="text-warm-500 dark:text-warm-400 mb-8 max-w-md mx-auto">
|
||||
100% open data. No account required. Just set your filters and go.
|
||||
</p>
|
||||
<button
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue