230 lines
9.5 KiB
TypeScript
230 lines
9.5 KiB
TypeScript
'use client'
|
||
/* eslint-disable @next/next/no-img-element */
|
||
|
||
import { useState, useRef, useEffect } from 'react'
|
||
import type { HomePageWhyParentsItem, Media } from '@/types/globals'
|
||
|
||
const VIDEO_THUMBNAIL = '/images/figma/why-parents-video-v2.webp'
|
||
|
||
const STATIC_ITEMS: HomePageWhyParentsItem[] = [
|
||
{
|
||
title: 'Подорож кількома світами за один день',
|
||
description:
|
||
'ДиноПарк, Диво Ліс, Дзеркальний лабіринт — кожна локація це окремий всесвіт пригод для дітей і батьків.',
|
||
},
|
||
{
|
||
title: 'Свіже повітря та затишок лісу',
|
||
description:
|
||
"Ми оновлюємо тематику та декорації до кожного сезону, тому тут буде цікаво кожного візиту. А у вас з'являться нові яскраві фото у сімейному альбомі.",
|
||
},
|
||
{
|
||
title: 'Нова казка кожної пори року',
|
||
description:
|
||
'Зима, весна, літо, осінь — кожен сезон у парку неповторний. Святкові декорації, сезонні активності та тематичні заходи чекають на вас.',
|
||
},
|
||
{
|
||
title: 'Безпека понад усе',
|
||
description:
|
||
'Всі атракції та зони проходять регулярну перевірку. Охоронці, медичний персонал та чіткі правила безпеки забезпечують спокій для батьків.',
|
||
},
|
||
{
|
||
title: 'Все необхідне — поруч і без пошуків',
|
||
description:
|
||
'Паркування, вбиральні, зона для годування немовлят, укриття, фудкорт — все на місці, щоб ваш відпочинок був справді комфортним.',
|
||
},
|
||
{
|
||
title: 'Фудкорт — смачно для всієї родини',
|
||
description:
|
||
'Хот-доги, піца, кава, лимонади та багато іншого. Є дитяче меню та здорові перекуси — ніхто не залишиться голодним.',
|
||
},
|
||
]
|
||
|
||
interface WhyParentsProps {
|
||
items?: HomePageWhyParentsItem[] | null
|
||
sideGallery?: { image?: Media | string | null }[] | null
|
||
title?: string
|
||
}
|
||
|
||
const INTERVAL = 4000
|
||
|
||
export function WhyParents({ items, sideGallery: _sideGallery, title }: WhyParentsProps) {
|
||
const [openIndex, setOpenIndex] = useState<number>(0)
|
||
const [progressKey, setProgressKey] = useState<number>(0)
|
||
const autoTimer = useRef<ReturnType<typeof setInterval> | null>(null)
|
||
const lenRef = useRef(0)
|
||
|
||
const activeItems = items && items.length > 0 ? items : STATIC_ITEMS
|
||
|
||
useEffect(() => {
|
||
lenRef.current = activeItems.length
|
||
})
|
||
|
||
useEffect(() => {
|
||
const tick = () => {
|
||
setOpenIndex((prev) => (prev + 1) % lenRef.current)
|
||
setProgressKey((k) => k + 1)
|
||
}
|
||
autoTimer.current = setInterval(tick, INTERVAL)
|
||
return () => {
|
||
if (autoTimer.current) clearInterval(autoTimer.current)
|
||
}
|
||
}, [])
|
||
|
||
function handleItemClick(i: number) {
|
||
setOpenIndex(i)
|
||
setProgressKey((k) => k + 1)
|
||
if (autoTimer.current) clearInterval(autoTimer.current)
|
||
autoTimer.current = setInterval(() => {
|
||
setOpenIndex((prev) => (prev + 1) % lenRef.current)
|
||
setProgressKey((k) => k + 1)
|
||
}, INTERVAL)
|
||
}
|
||
|
||
return (
|
||
<section className="py-[20px] md:py-[60px] lg:py-[40px]">
|
||
<div className="mx-auto max-w-[1204px] px-8">
|
||
<h2
|
||
className="mb-[40px] text-[24px] font-bold text-[#272727] uppercase md:mb-[60px] md:text-[32px]"
|
||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||
>
|
||
{title ?? 'Чому батьки обирають Шуміленд'}
|
||
</h2>
|
||
|
||
<div className="flex flex-col items-start gap-16 lg:flex-row lg:items-stretch">
|
||
<div className="relative w-full flex-none lg:w-auto">
|
||
<div className="absolute top-8 left-0 hidden h-[488px] w-[333px] rounded-[30px] bg-[#396817] lg:block" />
|
||
|
||
<div className="relative flex flex-col gap-6 lg:ml-[76px] lg:min-h-[600px]">
|
||
{activeItems.map((item, i) => {
|
||
const isOpen = openIndex === i
|
||
return (
|
||
<button
|
||
key={i}
|
||
onClick={() => handleItemClick(i)}
|
||
className="relative flex w-full flex-col gap-2.5 overflow-hidden rounded-[10px] bg-[#f1fbeb] px-[50px] py-[20px] text-left shadow-[0_4px_60px_0_rgba(242,139,74,0.25)] transition-all duration-200 lg:w-[628px]"
|
||
aria-expanded={isOpen}
|
||
>
|
||
<div className="flex items-center gap-5">
|
||
<span
|
||
className="flex-1 text-[20px] leading-tight font-bold text-[#272727]"
|
||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||
>
|
||
{item.title}
|
||
</span>
|
||
<svg
|
||
width="19"
|
||
height="10"
|
||
viewBox="0 0 19 10"
|
||
fill="none"
|
||
className="flex-none transition-transform duration-200"
|
||
style={{ transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||
aria-hidden="true"
|
||
>
|
||
<path
|
||
d="M1 1L9.5 9L18 1"
|
||
stroke="#f28b4a"
|
||
strokeWidth="2"
|
||
strokeLinecap="round"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
<div
|
||
className={`grid transition-[grid-template-rows] duration-300 ease-out ${isOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'}`}
|
||
>
|
||
<div className="overflow-hidden">
|
||
<p
|
||
className="pt-2 text-[16px] leading-[1.6] font-light text-[#272727]"
|
||
style={{ fontFamily: 'var(--font-poppins, Poppins), sans-serif' }}
|
||
>
|
||
{item.description}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
{/* Progress bar — CSS animation, keyed to reset on each advance */}
|
||
{isOpen && (
|
||
<div className="absolute bottom-0 left-0 h-[3px] w-full">
|
||
<div
|
||
key={progressKey}
|
||
className="h-full bg-[#f28b4a]"
|
||
style={{
|
||
animation: `progress-fill ${INTERVAL}ms linear forwards`,
|
||
}}
|
||
/>
|
||
</div>
|
||
)}
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Desktop: 3 video thumbnail cards — Figma Gallery_holder */}
|
||
<div className="hidden flex-1 overflow-hidden lg:flex lg:items-start lg:gap-5">
|
||
{(
|
||
[
|
||
{ h: 546, mt: 0 },
|
||
{ h: 488, mt: 29 },
|
||
{ h: 488, mt: 29 },
|
||
] as { h: number; mt: number }[]
|
||
).map(({ h, mt }, i) => (
|
||
<div
|
||
key={i}
|
||
className="relative flex-none overflow-hidden rounded-[20px] bg-[#d6f2c0]"
|
||
style={{ width: 505, height: h, marginTop: mt }}
|
||
>
|
||
<img
|
||
src={VIDEO_THUMBNAIL}
|
||
alt=""
|
||
aria-hidden="true"
|
||
className="h-full w-full object-cover"
|
||
loading={i === 0 ? 'eager' : 'lazy'}
|
||
/>
|
||
<div className="absolute inset-0 flex items-center justify-center">
|
||
<div className="flex h-[100px] w-[100px] items-center justify-center rounded-full bg-white/80 shadow-[0_4px_125px_0_rgba(23,27,36,0.6)]">
|
||
<svg
|
||
width="36"
|
||
height="36"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
aria-hidden="true"
|
||
className="ml-1"
|
||
>
|
||
<path d="M5 3L19 12L5 21V3Z" fill="#396817" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Mobile: horizontal scroll with video thumbnails */}
|
||
<div
|
||
className="flex w-full gap-5 overflow-x-auto pb-2 lg:hidden"
|
||
style={{ scrollbarWidth: 'none' }}
|
||
>
|
||
{[
|
||
'/images/figma/why-parents-1.webp',
|
||
'/images/figma/why-parents-2.webp',
|
||
'/images/figma/why-parents-3.webp',
|
||
'/images/figma/why-parents-4.webp',
|
||
].map((src, i) => (
|
||
<div
|
||
key={i}
|
||
className="flex-none overflow-hidden rounded-[20px]"
|
||
style={{ width: '280px', height: '200px' }}
|
||
>
|
||
<img
|
||
src={src}
|
||
alt=""
|
||
aria-hidden="true"
|
||
className="h-full w-full object-cover"
|
||
loading="lazy"
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|