- {/* L-shaped frosted layer */}
-
+ className="pointer-events-none absolute inset-0 lg:hidden"
+ style={{
+ background: 'linear-gradient(145deg, rgba(0,12,0,0.62) 40%, rgba(0,12,0,0.10) 90%)',
+ }}
+ />
- {title && (
-
-
+
+ {/* ── Family + dino — ABOVE text so dino overlays the title on the right ── */}
+ {useDefaultLayers && (
+

+ )}
+
+ {/* ── Text content — single layout, responsive padding ─────────── */}
+
+ {/* Align with header: max-w-[1204px] + px-[30px] mirrors HeaderClient inner container */}
+
+
+ {title && (
+
{title}
-
-
- )}
-
- {subtitle && (
-
- )}
-
- {showCta && (
-
- {ctaLabel}
-
- )}
-
-
- {/* Mobile / tablet — semantic layout (always in DOM, provides accessible content) */}
-
-
- {title && (
-
- {title}
)}
{subtitle && (
{subtitle}
@@ -163,6 +134,7 @@ export function Hero({ hero }: HeroProps) {
)}
+
)
diff --git a/src/components/sections/Locations.tsx b/src/components/sections/Locations.tsx
index 6004bf7..2866776 100644
--- a/src/components/sections/Locations.tsx
+++ b/src/components/sections/Locations.tsx
@@ -1,63 +1,92 @@
import { LocationsSlider } from './LocationsSlider'
import type { LocationData } from './LocationsSlider'
+import type { LocationCMS, Media } from '@/types/globals'
-const LOCATIONS: LocationData[] = [
+function getMediaUrl(img: Media | string | null | undefined): string | null {
+ if (!img) return null
+ if (typeof img === 'string') return img
+ return img.url ?? null
+}
+
+const STATIC_LOCATIONS: LocationData[] = [
{
name: 'ДиноПарк',
+ slug: 'dynopark',
tagline: 'портал у світ динозаврів',
description:
- 'Ви бачили їх у фільмах та мультиках, а тепер час зустріти в реальному житті та роздивитися їх зблизька! Найбільші динозаври України, які гарчать і рухаються, як справжні, захопливі відкриття та атмосфера справжньої наукової експедиції.',
+ 'Ви бачили їх у фільмах та мультиках, а тепер час зустріти в реальному житті та роздивитися їх зблизька! Найбільші динозаври України, які гарчать і рухаються, як справжні.',
image: '/images/figma/loc-dinopark.jpg',
- href: '/lokatsii',
+ href: '/lokatsii#dynopark',
},
{
name: 'Диво Ліс',
+ slug: 'dyvolis',
tagline: 'зона казкових топіарних фігур',
description:
- 'Тут на лісових стежках оселилися єдинороги, дракони та добрі лісові звірята. Це ідеальне місце, щоб пофантазувати разом із дитиною та придумати спільну яскраву історію.',
+ 'Тут на лісових стежках оселилися єдинороги, дракони та добрі лісові звірята. Це ідеальне місце, щоб пофантазувати разом із дитиною.',
image: '/images/figma/loc-divo-lis.png',
- href: '/lokatsii',
+ href: '/lokatsii#dyvolis',
},
{
name: 'Дзеркальний Лабіринт',
+ slug: 'maze',
tagline: 'справжній виклик кмітливості',
description:
- 'Чи зможете ви разом знайти вихід? Це справжній пригодницький виклик для всієї родини! Тут діти вчаться бути уважними та впевненими у собі, адже вони стають героями справжнього квесту.',
- image: '/images/figma/news-bg3.jpg',
- href: '/lokatsii',
+ 'Чи зможете ви разом знайти вихід? Це справжній пригодницький виклик для всієї родини! Тут діти вчаться бути уважними та впевненими у собі.',
+ image: '/images/figma/gallery-1.png',
+ href: '/lokatsii#maze',
},
{
name: 'Тир з призами',
+ slug: 'tir',
tagline: 'перемога, яку ви розділите разом',
description:
'Для дітей це не просто гра, а можливість проявити себе та "заробити" подарунок. Влаштуйте дружні змагання, дайте малечі декілька уроків та виграйте класний приз.',
- image: '/images/figma/news-bg4.png',
- href: '/lokatsii',
+ image: '/images/figma/gallery-3.png',
+ href: '/lokatsii#tir',
},
{
name: 'Дитячий майданчик',
+ slug: 'playground',
tagline: 'територія забав і нових друзів',
description:
- 'Поки малеча підкорює гірки, випробовує безпечні лазанки та знаходить перших друзів, ви можете нарешті зробити паузу та просто спостерігати за їхніми розвагами.',
- image: '/images/figma/news-bg5.png',
- href: '/lokatsii',
+ 'Поки малеча підкорює гірки, випробовує безпечні лазанки та знаходить перших друзів, ви можете нарешті зробити паузу та просто спостерігати.',
+ image: '/images/figma/gallery-8.png',
+ href: '/lokatsii#playground',
},
]
-export function Locations() {
+interface LocationsProps {
+ data?: LocationCMS[]
+ title?: string
+}
+
+export function Locations({ data, title }: LocationsProps) {
+ const locations: LocationData[] =
+ data && data.length > 0
+ ? data.map((loc) => ({
+ name: loc.name,
+ slug: loc.slug,
+ tagline: loc.tagline ?? '',
+ description: loc.shortDesc ?? '',
+ image: getMediaUrl(loc.image) ?? '/images/figma/loc-dinopark.jpg',
+ href: loc.href ?? `/lokatsii#${loc.slug}`,
+ }))
+ : STATIC_LOCATIONS
+
return (
-
+
- ЛАСКАВО ПРОСИМО ДО ШУМІЛЕНДУ
+ {title ?? 'ЛАСКАВО ПРОСИМО ДО ШУМІЛЕНДУ'}
-
-
+
+
)
diff --git a/src/components/sections/LocationsSlider.tsx b/src/components/sections/LocationsSlider.tsx
index 18edaa6..b8f6f35 100644
--- a/src/components/sections/LocationsSlider.tsx
+++ b/src/components/sections/LocationsSlider.tsx
@@ -1,11 +1,12 @@
'use client'
-import { useRef } from 'react'
+import { useRef, useState } from 'react'
import { BtnGradient } from '@/components/ui/BtnGradient'
import { useAutoScroll } from '@/hooks/useAutoScroll'
export interface LocationData {
name: string
+ slug: string
tagline: string
description: string
image: string
@@ -18,21 +19,32 @@ interface LocationsSliderProps {
export function LocationsSlider({ locations }: LocationsSliderProps) {
const trackRef = useRef
(null)
- useAutoScroll(trackRef, { speed: 0.5, intervalMs: 16 })
+ const pauseTimer = useRef | null>(null)
+ const [autoPaused, setAutoPaused] = useState(false)
+ useAutoScroll(trackRef, { speed: 1.5, intervalMs: 16, disabled: autoPaused })
function scrollByOne(dir: 1 | -1) {
- trackRef.current?.scrollBy({ left: dir * 714, behavior: 'smooth' })
+ trackRef.current?.scrollBy({ left: dir * 580, behavior: 'smooth' })
+ setAutoPaused(true)
+ if (pauseTimer.current) clearTimeout(pauseTimer.current)
+ pauseTimer.current = setTimeout(() => setAutoPaused(false), 3000)
}
return (
@@ -47,36 +59,36 @@ export function LocationsSlider({ locations }: LocationsSliderProps) {
>
{locations.map((loc) => (
-
+

-
+
{loc.name}
{loc.tagline}
{loc.description}
@@ -92,11 +104,17 @@ export function LocationsSlider({ locations }: LocationsSliderProps) {
diff --git a/src/components/sections/Reviews.tsx b/src/components/sections/Reviews.tsx
index 78d84e4..7690f71 100644
--- a/src/components/sections/Reviews.tsx
+++ b/src/components/sections/Reviews.tsx
@@ -1,39 +1,68 @@
'use client'
/* eslint-disable @next/next/no-img-element */
-import { useRef } from 'react'
-import { BtnDetails } from '@/components/ui/BtnDetails'
+import { useRef, useState } from 'react'
import { useAutoScroll } from '@/hooks/useAutoScroll'
+import type { ReviewCMS, Media } from '@/types/globals'
const IMG_RATE = '/images/figma/rate-stars.svg'
+const IMG_AVATAR_DEFAULT = '/images/figma/review-avatar-bg.jpg'
-const REVIEWS = [
+function getMediaUrl(img: Media | string | null | undefined): string | null {
+ if (!img) return null
+ if (typeof img === 'string') return img
+ return img.url ?? null
+}
+
+const STATIC_REVIEWS: ReviewCMS[] = [
{
- initial: 'Ж',
+ id: 'r1',
name: 'Женя Олейник',
- ago: '2 months ago',
+ initial: 'Ж',
+ ago: '2 місяці тому',
+ rating: 5,
text: 'Beautiful, interesting for children and adults. Large area and different locations. Few visitors on weekdays.',
+ source: 'google',
},
{
- initial: 'А',
+ id: 'r2',
name: 'Анна Калініченко',
- ago: '6 months ago',
+ initial: 'А',
+ ago: '6 місяців тому',
+ rating: 5,
text: 'A wonderful dinosaur park, a park of figures made of grass. You can climb on the figures, the kids are delighted! The dinosaurs move, roar, everyone works.',
+ source: 'google',
},
{
- initial: 'V',
+ id: 'r3',
name: 'Volodymyr Prisajnuk',
- ago: '10 months ago',
+ initial: 'V',
+ ago: '10 місяців тому',
+ rating: 5,
text: 'My family and I visited the open-air park at VDNH, we really liked it! It was much better than I expected. The dinosaurs were very memorable — incredible!',
+ source: 'google',
},
]
-export function Reviews() {
+interface ReviewsProps {
+ data?: ReviewCMS[]
+ title?: string
+}
+
+export function Reviews({ data, title }: ReviewsProps) {
+ const activeReviews = data && data.length > 0 ? data : STATIC_REVIEWS
+ const doubled = [...activeReviews, ...activeReviews]
+
const trackRef = useRef
(null)
- useAutoScroll(trackRef, { speed: 0.5, intervalMs: 16 })
+ const pauseTimer = useRef | null>(null)
+ const [autoPaused, setAutoPaused] = useState(false)
+ useAutoScroll(trackRef, { speed: 0.3, intervalMs: 16, disabled: autoPaused })
function scrollByOne(dir: 1 | -1) {
trackRef.current?.scrollBy({ left: dir * 611, behavior: 'smooth' })
+ setAutoPaused(true)
+ if (pauseTimer.current) clearTimeout(pauseTimer.current)
+ pauseTimer.current = setTimeout(() => setAutoPaused(false), 3000)
}
return (
@@ -43,13 +72,13 @@ export function Reviews() {
className="font-bold text-[24px] md:text-[32px] text-[#272727] uppercase mb-[40px] md:mb-[60px]"
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
>
- Відгуки
+ {title ?? 'Відгуки'}