feat: simplify latest carousel and use leaflet import

This commit is contained in:
Tero Halla-aho 2025-11-24 21:21:31 +02:00
parent 1257042a66
commit 74f4853686
3 changed files with 53 additions and 128 deletions

View file

@ -134,21 +134,32 @@ p {
margin-top: 20px;
}
.latest-grid {
.latest-carousel {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 14px;
margin-top: 14px;
gap: 12px;
margin-top: 12px;
}
.latest-card {
.carousel-window {
position: relative;
width: 100%;
overflow: hidden;
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 16px;
overflow: hidden;
background: rgba(255, 255, 255, 0.02);
}
.carousel-track {
display: flex;
transition: transform 500ms ease;
width: 100%;
}
.carousel-slide {
min-width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 240px;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
align-items: stretch;
}
.latest-cover {
@ -168,67 +179,6 @@ p {
gap: 6px;
}
.latest-rail {
display: grid;
gap: 8px;
max-height: 320px;
overflow: auto;
padding-right: 4px;
}
.rail-item {
width: 100%;
display: grid;
grid-template-columns: 76px 1fr;
gap: 10px;
padding: 8px;
border-radius: 12px;
border: 1px solid rgba(148, 163, 184, 0.16);
background: rgba(255, 255, 255, 0.01);
cursor: pointer;
color: inherit;
}
.rail-item.active {
border-color: var(--accent);
background: rgba(34, 211, 238, 0.08);
}
.rail-thumb {
width: 100%;
height: 64px;
border-radius: 10px;
overflow: hidden;
background: rgba(14, 165, 233, 0.06);
}
.rail-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.rail-fallback {
width: 100%;
height: 100%;
background: linear-gradient(110deg, rgba(34, 211, 238, 0.1), rgba(14, 165, 233, 0.1));
}
.rail-text {
display: grid;
gap: 4px;
text-align: left;
}
.rail-title {
font-weight: 600;
}
.rail-sub {
color: var(--muted);
font-size: 13px;
}
.dot-row {
display: flex;
gap: 6px;

View file

@ -1,6 +1,7 @@
'use client';
import Link from 'next/link';
import dynamic from 'next/dynamic';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useI18n } from '../components/I18nProvider';
@ -47,25 +48,10 @@ function haversineKm(a: LatLng, b: LatLng) {
return 2 * R * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
}
async function loadLeaflet(): Promise<any> {
if (typeof window === 'undefined') return Promise.reject();
if ((window as any).L) return (window as any).L;
const linkId = 'leaflet-css';
if (!document.getElementById(linkId)) {
const link = document.createElement('link');
link.id = linkId;
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
document.head.appendChild(link);
}
try {
const mod = await import('leaflet');
(window as any).L = mod;
return mod;
} catch (err) {
return Promise.reject(err);
}
}
const LeafletWrapper = dynamic(async () => {
const L = await import('leaflet');
return L;
}, { ssr: false });
function ListingsMap({
listings,
@ -88,7 +74,7 @@ function ListingsMap({
useEffect(() => {
let cancelled = false;
loadLeaflet()
LeafletWrapper
.then((L) => {
if (cancelled) return;
setReady(true);

View file

@ -127,48 +127,37 @@ export default function HomePage() {
) : !activeListing ? (
<p style={{ color: '#cbd5e1', marginTop: 10 }}>{t('mapNoResults')}</p>
) : (
<div className="latest-grid" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
<div className="latest-card">
{activeListing.coverImage ? (
<img src={activeListing.coverImage} alt={activeListing.title} className="latest-cover" />
) : (
<div className="latest-cover placeholder" />
)}
<div className="latest-meta">
<span className="badge">
{activeListing.city}, {activeListing.region}
</span>
<h3 style={{ margin: '6px 0 4px' }}>{activeListing.title}</h3>
<p style={{ margin: 0 }}>{activeListing.teaser}</p>
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
<Link className="button secondary" href={`/listings/${activeListing.slug}`}>
{t('openListing')}
</Link>
</div>
</div>
</div>
<div className="latest-rail">
{latest.map((item, idx) => (
<button
key={item.id}
className={`rail-item ${idx === activeIndex ? 'active' : ''}`}
onClick={() => setActiveIndex(idx)}
>
<div className="rail-thumb">
{item.coverImage ? <img src={item.coverImage} alt={item.title} /> : <div className="rail-fallback" />}
</div>
<div className="rail-text">
<div className="rail-title">{item.title}</div>
<div className="rail-sub">{item.city}, {item.region}</div>
</div>
</button>
))}
<div className="dot-row">
{latest.map((_, idx) => (
<span key={idx} className={`dot ${idx === activeIndex ? 'active' : ''}`} onClick={() => setActiveIndex(idx)} />
<div className="latest-carousel" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
<div className="carousel-window">
<div className="carousel-track" style={{ transform: `translateX(-${activeIndex * 100}%)` }}>
{latest.map((item) => (
<article key={item.id} className="carousel-slide">
{item.coverImage ? (
<img src={item.coverImage} alt={item.title} className="latest-cover" />
) : (
<div className="latest-cover placeholder" />
)}
<div className="latest-meta">
<span className="badge">
{item.city}, {item.region}
</span>
<h3 style={{ margin: '6px 0 4px' }}>{item.title}</h3>
<p style={{ margin: 0 }}>{item.teaser}</p>
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
<Link className="button secondary" href={`/listings/${item.slug}`}>
{t('openListing')}
</Link>
</div>
</div>
</article>
))}
</div>
</div>
<div className="dot-row">
{latest.map((_, idx) => (
<span key={idx} className={`dot ${idx === activeIndex ? 'active' : ''}`} onClick={() => setActiveIndex(idx)} />
))}
</div>
</div>
)}
</section>