This commit is contained in:
Your Name committed 2026-06-28 07:58:15 -04:00
1 parent e405af357c
commit e1cf9e62e7
51 files changed
+2598 -168

No files matched your search

@@ -2,8 +2,8 @@
import Image from 'next/image'
import Link from '../LocalizedLink'
import { Clock, Mail, MapPin, MessageCircle, Send, ChevronLeft, ChevronRight } from 'lucide-react'
import { useState, useRef } from 'react'
import { Clock, Mail, MapPin, MessageCircle, Send } from 'lucide-react'
import { useState } from 'react'
import type { Category, PageSection, Product } from '@homaroma/types'
import { getCategoryImage } from '@homaroma/types'
import ProductCard from '../ProductCard'
@@ -11,8 +11,10 @@ import { localizedFromRecord, resolveCategoryName, resolveLocalized, toRecord }
import { useLang } from '../../hooks/useLang'
import { useTranslation } from 'react-i18next'
import { Swiper, SwiperSlide } from 'swiper/react'
import { Autoplay } from 'swiper/modules'
import { Autoplay, Pagination, Navigation } from 'swiper/modules'
import 'swiper/css'
import 'swiper/css/pagination'
import 'swiper/css/navigation'
type DynamicPageRendererProps = {
sections: PageSection[]
@@ -402,33 +404,38 @@ function EditorialCarouselSection({ payload, lang }: { payload: Record<string, u
const bgClass = backgroundColor ? `bg-pop-${backgroundColor}` : 'bg-surface-page'
const textClass = textColor ? `text-pop-${textColor}` : 'text-text-main'
const scrollContainerRef = useRef<HTMLDivElement>(null)
const scrollBy = (offset: number) => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({ left: offset, behavior: 'smooth' })
}
}
const isSplitScreen = payload.layout === 'split_screen'
const imagePosition = typeof payload.imagePosition === 'string' ? payload.imagePosition : 'left'
if (!slides.length) return null
return (
<section className={`px-6 py-16 md:py-24 ${bgClass} ${textClass}`}>
<div className="mx-auto max-w-7xl space-y-12">
<section className={`overflow-hidden py-16 md:py-24 ${bgClass} ${textClass}`}>
<div className={isSplitScreen ? 'w-full space-y-12' : 'mx-auto max-w-7xl px-6 space-y-12'}>
{title && (
<div className="mx-auto max-w-3xl space-y-4 text-center">
<h2 className={`text-4xl font-semibold md:text-5xl ${textClass}`}>{renderAccentTitle(title)}</h2>
<div className="mx-auto h-px w-16 bg-brand-accent" />
<div className={`space-y-4 text-center ${isSplitScreen ? 'px-4 md:px-8' : ''}`}>
<h2 className={`font-serif text-3xl font-light tracking-wide md:text-5xl ${textClass}`}>
{renderAccentTitle(title)}
</h2>
<div className="mx-auto h-px w-16 bg-brand-primary" />
</div>
)}
<div className="relative">
<button
onClick={() => scrollBy(-400)}
className="hidden md:flex bg-surface-card rounded-full shadow-card hover:text-brand-primary h-10 w-10 items-center justify-center absolute z-10 top-1/2 -translate-y-1/2 -left-5"
aria-label="Scroll left"
<div className="relative select-none pb-12">
<Swiper
modules={[Autoplay, Pagination, Navigation]}
spaceBetween={isSplitScreen ? 0 : 30}
slidesPerView={1}
breakpoints={isSplitScreen ? undefined : {
640: { slidesPerView: 2 },
1024: { slidesPerView: 3 }
}}
pagination={{ clickable: true, bulletActiveClass: 'swiper-pagination-bullet-active !bg-brand-primary' }}
navigation={true}
autoplay={{ delay: 5000, disableOnInteraction: false }}
grabCursor={true}
className="w-full"
>
<ChevronLeft className="h-6 w-6 text-brand-primary" />
</button>
<div ref={scrollContainerRef} className="flex overflow-x-auto snap-x snap-mandatory hide-scrollbar gap-6 pb-8">
{slides.map((slide, index) => {
const slideTitle = localizedFromRecord(slide, 'title', resolvedLang)
const description = localizedFromRecord(slide, 'description', resolvedLang)
@@ -438,43 +445,74 @@ function EditorialCarouselSection({ payload, lang }: { payload: Record<string, u
const imageUrl = typeof slide.imageUrl === 'string' ? slide.imageUrl : ''
return (
<article key={index} className="ui-card overflow-hidden group w-[85vw] md:w-[350px] flex-none snap-start">
{imageUrl ? (
<div className="relative aspect-[4/3] overflow-hidden">
<Image
src={imageUrl}
alt={slideTitle || 'Editorial slide'}
fill
unoptimized
className="h-full w-full object-cover transition duration-700 group-hover:scale-105"
/>
</div>
) : null}
<div className="space-y-4 p-6 flex flex-col flex-1 justify-between">
<div>
{category && <span className="text-[10px] font-bold uppercase tracking-[0.24em] text-brand-primary block">{category}</span>}
{slideTitle && <h3 className="text-xl font-bold text-text-main mt-6 group-hover:text-brand-primary transition-colors">{renderAccentTitle(slideTitle)}</h3>}
{description && <p className="text-base text-muted-text mt-3 whitespace-pre-wrap">{description}</p>}
</div>
{buttonText && buttonLink && (
<div className="pt-2">
<Link href={buttonLink} className="ui-section-dark-link inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest transition-colors group/link">
{buttonText} <span className="group-hover/link:translate-x-1 transition-transform">&rarr;</span>
</Link>
<SwiperSlide key={index} className="h-auto flex flex-col">
{isSplitScreen ? (
<div className={`group grid grid-cols-1 bg-surface-card overflow-hidden transition-all duration-300 ${
imagePosition === 'left' ? 'md:grid-cols-[65%_35%]' : 'md:grid-cols-[35%_65%]'
}`}>
{imageUrl ? (
<div className={`relative aspect-[4/3] md:aspect-auto overflow-hidden h-64 md:h-full min-h-[300px] md:min-h-[500px] lg:min-h-[600px] ${
imagePosition === 'left' ? 'order-1 md:order-1' : 'order-1 md:order-2'
}`}>
<Image
src={imageUrl}
alt={slideTitle || 'Editorial slide'}
fill
unoptimized
className="h-full w-full object-cover transition duration-700 group-hover:scale-105"
/>
</div>
) : null}
<div className={`space-y-6 p-6 md:p-16 lg:p-24 flex flex-col justify-center ${
imagePosition === 'left' ? 'order-2 md:order-2' : 'order-2 md:order-1'
}`}>
<div className="space-y-3">
{category && <span className="block text-[10px] font-bold uppercase tracking-[0.2em] text-brand-primary">{category}</span>}
{slideTitle && <h3 className="font-serif text-xl font-light tracking-wide text-text-main">{renderAccentTitle(slideTitle)}</h3>}
{description && <p className="line-clamp-6 text-xs font-light leading-relaxed text-muted-text md:text-sm">{description}</p>}
</div>
{buttonText && buttonLink && (
<div className="pt-2">
<Link href={buttonLink} className="inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-brand-primary hover:text-text-main transition-colors group/link">
{buttonText} <span className="group-hover/link:translate-x-1 transition-transform">&rarr;</span>
</Link>
</div>
)}
</div>
)}
</div>
</article>
</div>
) : (
<div className="ui-card overflow-hidden group flex flex-col h-full transition-all duration-300 hover:border-brand-primary/30">
{imageUrl ? (
<div className="relative aspect-[4/3] overflow-hidden">
<Image
src={imageUrl}
alt={slideTitle || 'Editorial slide'}
fill
unoptimized
className="h-full w-full object-cover transition duration-700 group-hover:scale-105"
/>
</div>
) : null}
<div className="p-6 flex flex-col flex-1 justify-between space-y-6">
<div className="space-y-3">
{category && <span className="block text-[10px] font-bold uppercase tracking-[0.2em] text-brand-primary">{category}</span>}
{slideTitle && <h3 className="font-serif text-xl font-light tracking-wide text-text-main">{renderAccentTitle(slideTitle)}</h3>}
{description && <p className="line-clamp-3 text-xs font-light leading-relaxed text-muted-text md:text-sm">{description}</p>}
</div>
{buttonText && buttonLink && (
<div className="pt-2">
<Link href={buttonLink} className="inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-brand-primary hover:text-text-main transition-colors group/link">
{buttonText} <span className="group-hover/link:translate-x-1 transition-transform">&rarr;</span>
</Link>
</div>
)}
</div>
</div>
)}
</SwiperSlide>
)
})}
</div>
<button
onClick={() => scrollBy(400)}
className="hidden md:flex bg-surface-card rounded-full shadow-card hover:text-brand-primary h-10 w-10 items-center justify-center absolute z-10 top-1/2 -translate-y-1/2 -right-5"
aria-label="Scroll right"
>
<ChevronRight className="h-6 w-6 text-brand-primary" />
</button>
</Swiper>
</div>
</div>
</section>