diff --git a/src/components/sections/DinoActivities.tsx b/src/components/sections/DinoActivities.tsx index 3cb46b2..2c7e073 100644 --- a/src/components/sections/DinoActivities.tsx +++ b/src/components/sections/DinoActivities.tsx @@ -1,10 +1,13 @@ /* eslint-disable @next/next/no-img-element */ const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' } +const FONT_POP = { fontFamily: 'var(--font-poppins, Poppins), sans-serif' } interface Activity { name: string + subtitle?: string | null price?: string | null + priceUnit?: string | null description?: string | null imageUrl?: string | null href?: string | null @@ -17,21 +20,48 @@ interface DinoActivitiesProps { } const DEFAULT_ACTIVITIES: Activity[] = [ - { name: 'Звичайна екскурсія', price: '150 грн', href: '#tickets' }, - { name: 'Палеонтологічна екскурсія', price: '300 грн', href: '#tickets' }, - { name: 'ДиноРодео', price: '50 грн', href: '#tickets' }, + { + name: 'Звичайна екскурсія', + subtitle: 'дізнайтеся більше про світ диновелетнів', + price: '150 грн', + priceUnit: 'за 1 людину', + description: + 'На екскурсії ви та дітлахи дізнаєтесь, які динозаври були найбільшими та найнебезпечнішими, чим вони харчувалися, як полювали і чому зникли з лиця Землі.', + imageUrl: '/dynopark/Gemini_Generated_Image_7b985p7b985p7b98_.jpg', + href: '#tickets', + }, + { + name: 'Палеонтологічна екскурсія', + subtitle: 'відчуйте себе першовідкривачами', + price: '300 грн', + priceUnit: 'за 1 людину', + description: + 'Це справжня наукова пригода! Діти візьмуть участь у розкопках, власноруч викопають справжню скамʼянілість і почують захопливі факти про динозаврів.', + imageUrl: '/dynopark/Gemini_Generated_Image_a10736a10736a107_.jpg', + href: '#tickets', + }, + { + name: 'ДиноРодео', + subtitle: 'прокатіться на динозаврі з вітерком', + price: '50 грн', + priceUnit: 'за 1 людину', + description: + 'Безпечна пригода для дітлахів — висота фігури не більше 1,6 м. Але нудьгувати не доведеться: динозавр коливається, рухає хвостом та головою.', + imageUrl: '/dynopark/Gemini_Generated_Image_gc7t4lgc7t4lgc7t_.jpg', + href: '#tickets', + }, ] export function DinoActivities({ - title = 'Додаткові розваги у динопарку', - description = 'Хочете дізнатись ще більше про динозаврів? Замовте екскурсію з гідом, поринь у світ палеонтологічних розкопок або підкорюй справжнього динозавра!', + title = 'Додаткові розваги у ДиноПарку', + description = 'Хочете зробити пригоду ще цікавішою? Замовте екскурсію — і дізнайтесь більше про динозаврів: їхнє походження, спосіб життя та цікаві факти.', activities = DEFAULT_ACTIVITIES, }: DinoActivitiesProps) { if (!activities.length) return null return (
-
+

{description && (

{description}

)} -
+
{activities.map((act, i) => ( ))} @@ -60,9 +90,12 @@ export function DinoActivities({ function ActivityCard({ activity }: { activity: Activity }) { const href = activity.href ?? '#' return ( -
- {/* Photo area */} -
+
+ {/* Full-height photo */} +
{activity.imageUrl ? ( ) : ( -
+
)} - {/* Price badge */} + + {/* Gradient overlay — bottom-heavy for text legibility */} + - {/* Content */} -
-

- {activity.name} -

- {activity.description && ( -

- {activity.description} + {/* Card text — overlaid at bottom */} +

+

+ {activity.name}

- )} - - Замовити екскурсію - - + {activity.subtitle && ( +

+ {activity.subtitle} +

+ )} + {activity.description && ( +

+ {activity.description} +

+ )} + + {/* CTA */} + +
) diff --git a/src/components/sections/DinoGallery.tsx b/src/components/sections/DinoGallery.tsx index 3eaec91..7d73923 100644 --- a/src/components/sections/DinoGallery.tsx +++ b/src/components/sections/DinoGallery.tsx @@ -8,9 +8,10 @@ interface DinoGalleryProps { } const FALLBACK_GALLERY = [ - '/images/figma/2c6a3e5e-7346-4c3e-b8a0-fae1facb87ad.jpg', - '/images/figma/2936ec5e-4f99-441e-9bf2-34f23c283170.jpg', - '/images/figma/7a2627b2-b6ce-4325-a0b1-fbc3393aca4c.png', + '/dynopark/50182754852060_1.jpg', + '/dynopark/4da8605d916401919bbe9cf115d8f8a5_1.jpg', + '/dynopark/Untitled_6_2.jpg', + '/dynopark/if-when-jurassic-world-rebirth-gets-a-se.jpg', ] export function DinoGallery({ images }: DinoGalleryProps) { diff --git a/src/components/sections/DinoHero.tsx b/src/components/sections/DinoHero.tsx index 5a62527..6890dc7 100644 --- a/src/components/sections/DinoHero.tsx +++ b/src/components/sections/DinoHero.tsx @@ -2,6 +2,7 @@ import { BtnPrimary } from '@/components/ui/BtnPrimary' const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' } +const FONT_INTER = { fontFamily: 'Inter, sans-serif' } const DEFAULT_FEATURES = ['Повнорозмірні анімовані динозавра', 'Реалістичні рухи та звуки'] @@ -15,29 +16,29 @@ interface DinoHeroProps { } export function DinoHero({ - title = 'Динопарк — портал у світ динозаврів', - description = 'Великі динозаври, що рухаються та гарчать, справжнє роздоволлє, цікаві екскурсії та динородео — тут є все, щоб ваша дитина не нудьгувала.', + title = 'ДиноПарк — портал у світ динозаврів', + description = 'Великі динозаври, що рухаються та гарчать, справжні розкопки, цікаві екскурсії та динородео — тут є все, щоб ваша дитина не нудьгувала.', stat = '26', statLabel = 'унікальних експонатів', features = DEFAULT_FEATURES, heroImageUrl, }: DinoHeroProps) { return ( -
+
{/* Left column */} -
-
-
+
+
+
{/* Text */} -
+

{title}

{description} @@ -48,34 +49,35 @@ export function DinoHero({

{/* Stat badge + feature list */} -
+
+ {/* "26 унікальних експонатів" pill */}
{statLabel} @@ -85,11 +87,12 @@ export function DinoHero({ {features.map((f, i) => (
{f} @@ -100,50 +103,50 @@ export function DinoHero({
- {/* Right panel — hero image on yellow circle */} + {/* Right panel — hero image on orange circle */}

+
{loading ? Array.from({ length: 4 }).map((_, i) => ) : dynoTariffs.map((t) => )} @@ -177,16 +179,16 @@ export function DinoTickets({ {/* Combo */} {!loading && comboTariffs.length > 0 && ( <> -
-

+

Комбо -

+

{comboDescription}

diff --git a/src/components/sections/DinoWheel.tsx b/src/components/sections/DinoWheel.tsx index 4b92a34..6cfd763 100644 --- a/src/components/sections/DinoWheel.tsx +++ b/src/components/sections/DinoWheel.tsx @@ -4,14 +4,13 @@ import { useState, useEffect, useRef, useCallback } from 'react' const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' } - -const WAVE_BG = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Ccircle cx='80' cy='80' r='60' fill='none' stroke='rgba(255,255,255,0.04)' stroke-width='1.5'/%3E%3Ccircle cx='80' cy='80' r='40' fill='none' stroke='rgba(255,255,255,0.03)' stroke-width='1'/%3E%3Ccircle cx='0' cy='0' r='60' fill='none' stroke='rgba(255,255,255,0.04)' stroke-width='1.5'/%3E%3Ccircle cx='160' cy='0' r='60' fill='none' stroke='rgba(255,255,255,0.04)' stroke-width='1.5'/%3E%3Ccircle cx='0' cy='160' r='60' fill='none' stroke='rgba(255,255,255,0.04)' stroke-width='1.5'/%3E%3Ccircle cx='160' cy='160' r='60' fill='none' stroke='rgba(255,255,255,0.04)' stroke-width='1.5'/%3E%3C/svg%3E")` +const FONT_POP = { fontFamily: 'var(--font-poppins, Poppins), sans-serif' } interface DinoSpec { name: string - epoch?: string | null + latinName?: string | null length?: string | null - weight?: string | null + height?: string | null imageUrl?: string | null thumbnailUrl?: string | null } @@ -20,276 +19,626 @@ interface DinoWheelProps { dinos?: DinoSpec[] } +// 25 dinosaurs from Figma with actual /dynopark/ image paths const FALLBACK: DinoSpec[] = [ - { name: 'Тиранозавр Рекс', epoch: 'Крейдяний', length: '12 м', weight: '7 т' }, - { name: 'Карнотавр', epoch: 'Крейдяний', length: '8 м', weight: '1.5 т' }, - { name: 'Трицератопс', epoch: 'Крейдяний', length: '9 м', weight: '12 т' }, - { name: 'Велоцираптор', epoch: 'Крейдяний', length: '2 м', weight: '15 кг' }, - { name: 'Спінозавр', epoch: 'Крейдяний', length: '15 м', weight: '20 т' }, - { name: 'Птеранодон', epoch: 'Юрський', length: '2.5 м', weight: '20 кг' }, - { name: 'Брахіозавр', epoch: 'Юрський', length: '26 м', weight: '56 т' }, - { name: 'Анкілозавр', epoch: 'Крейдяний', length: '8 м', weight: '7 т' }, + { + name: 'Тиранозавр Рекс', + latinName: 'Гігантська Версія', + length: '20 м.', + height: '7 м.', + imageUrl: '/dynopark/T-Rex_2_1.jpg', + }, + { + name: 'Барионікс', + length: '7 м.', + height: '2,5 м.', + imageUrl: null, + }, + { + name: 'Овіраптор', + length: '3 м.', + imageUrl: '/dynopark/Oviraptor_TD_1.jpg', + }, + { + name: 'Спінозавр', + length: '15 м.', + height: '5,8 м.', + imageUrl: '/dynopark/has-anyone-tried-incubating-and-releasin.jpg', + }, + { + name: 'Карнотавр', + length: '10 м.', + height: '3,2 м.', + imageUrl: '/dynopark/carnotaurus_1.jpg', + }, + { + name: 'Ділофозавр на скелі', + length: '6 м.', + imageUrl: '/dynopark/dilophosaurus_1.jpg', + }, + { + name: 'Брахіозавр', + length: '25 м.', + height: '10 м.', + imageUrl: '/dynopark/brachiosaurus_2.jpg', + }, + { + name: 'Пара Брахіозаврів', + latinName: 'Закохані', + length: '12 м.', + height: '6 м.', + imageUrl: '/dynopark/brachiosaurus_2.jpg', + }, + { + name: 'Ураноза́вр', + length: '6 м.', + height: '2,2 м.', + imageUrl: '/dynopark/ouranosaurus_nigeriensis_1.jpg', + }, + { + name: 'Стиракозавр', + length: '6 м.', + height: '2,2 м.', + imageUrl: '/dynopark/styracosaurus_1.jpg', + }, + { + name: 'Космоцератопс', + latinName: 'Пара Закоханих', + length: '5 м.', + height: '2,5 м.', + imageUrl: null, + }, + { + name: 'Стегозавр', + length: '15 м.', + height: '7 м.', + imageUrl: '/dynopark/stegosaurus_1.jpg', + }, + { + name: 'Анкілозавр', + length: '8 м.', + height: '2,6 м.', + imageUrl: '/dynopark/ankylosaurus_1.jpg', + }, + { + name: 'Паразавролоф', + length: '7 м.', + height: '2,2 м.', + imageUrl: '/dynopark/favorite-parasaurolophus-design-v0-0supu.jpg', + }, + { + name: 'Паразавролоф', + latinName: 'Сімейство', + length: '6 м.', + height: '2 м.', + imageUrl: '/dynopark/favorite-parasaurolophus-design-v0-0supu.jpg', + }, + { + name: 'Велоцираптор', + length: '4 м.', + height: '1,7 м.', + imageUrl: '/dynopark/velociraptor_1_1.jpg', + }, + { + name: 'Кетцалькоатль', + height: '6,5 м.', + imageUrl: null, + }, + { + name: 'Птеродактиль', + latinName: 'з гніздом та яйцями', + height: '2 м.', + imageUrl: '/dynopark/1697108661_poknok-art-p-pteranodoni-51_1.jpg', + }, + { + name: 'Яйця Динозаврів', + height: '1,5 м.', + imageUrl: '/dynopark/pngtree-3d-baby-dinosaur-nesting-out-of-.jpg', + }, + { + name: 'Трицератопс', + length: '6 м.', + height: '2,45 м.', + imageUrl: '/dynopark/202007_Triceratops_horridus.svg_1.jpg', + }, + { + name: 'Тиранозавр', + latinName: 'для ДиноРодео', + length: '4 м.', + height: '1,6 м.', + imageUrl: '/dynopark/tyrannosaurus_rex_1.jpg', + }, + { + name: 'Карнотавр', + latinName: 'для ДиноРодео', + length: '3,5 м.', + height: '1,5 м.', + imageUrl: '/dynopark/carnotaurus_1.jpg', + }, + { + name: 'Тиранозавр', + latinName: 'Версія 2', + length: '4 м.', + height: '1,8 м.', + imageUrl: '/dynopark/tyrannosaurus_rex_1.jpg', + }, + { + name: 'Велоцираптор', + latinName: 'Версія 2', + length: '4 м.', + height: '1,8 м.', + imageUrl: '/dynopark/velociraptor_1.jpg', + }, + { + name: 'Тиранозавр Рекс', + latinName: 'Рев до Небес', + length: '6 м.', + height: '2,8 м.', + imageUrl: '/dynopark/1675801348_grizly-club-p-klipart-tiranno.jpg', + }, ] -const DINO_EMOJIS = ['🦖', '🦕', '🦖', '🦕', '🦖', '🦕', '🦕', '🦖'] +const DINO_EMOJIS = [ + '🦖', + '🦕', + '🦖', + '🦕', + '🦖', + '🦕', + '🦕', + '🦖', + '🦖', + '🦕', + '🦖', + '🦕', + '🦕', + '🦖', + '🦕', + '🦖', + '🦕', + '🦅', + '🦅', + '🥚', + '🦖', + '🦖', + '🦖', + '🦖', + '🦖', +] +// ── Gallery photos at the bottom of the wheel section ───────────────────── +const GALLERY_PHOTOS = [ + { src: '/dynopark/50182754852060_1.jpg', alt: 'Динопарк — парк динозаврів' }, + { src: '/dynopark/Untitled_6_2.jpg', alt: 'Динопарк — фото парку' }, + { src: '/dynopark/if-when-jurassic-world-rebirth-gets-a-se.jpg', alt: 'Динопарк — відвідувачі' }, +] + +// ── Wheel SVG ────────────────────────────────────────────────────────────── +function DinoWheelSVG({ rotation, n, size }: { rotation: number; n: number; size: number }) { + const cx = size / 2 + const cy = size / 2 + const outerR = size * 0.485 + const innerR = size * 0.295 + + const dividers: { x1: number; y1: number; x2: number; y2: number }[] = [] + for (let i = 0; i < n; i++) { + const rad = ((i / n) * 360 * Math.PI) / 180 + dividers.push({ + x1: cx + innerR * Math.cos(rad), + y1: cy + innerR * Math.sin(rad), + x2: cx + outerR * Math.cos(rad), + y2: cy + outerR * Math.sin(rad), + }) + } + + return ( + + ) +} + +// ── Main component ───────────────────────────────────────────────────────── export function DinoWheel({ dinos }: DinoWheelProps) { const items = dinos && dinos.length > 0 ? dinos : FALLBACK const n = items.length const [active, setActive] = useState(0) const [visible, setVisible] = useState(true) - const timer = useRef | null>(null) + const [wheelAngle, setWheelAngle] = useState(270) + const activeRef = useRef(0) + const timerRef = useRef | null>(null) - const goTo = useCallback((i: number) => { - setVisible(false) - setTimeout(() => { - setActive(i) - setVisible(true) - }, 220) - }, []) - - const resetTimer = useCallback( - (next: number) => { - if (timer.current) clearInterval(timer.current) - timer.current = setInterval(() => { - setVisible(false) - setTimeout(() => { - setActive((p) => { - const nx = (p + 1) % n - return nx - }) - setVisible(true) - }, 220) - }, 4000) - goTo(next) - }, - [n, goTo] - ) + // Responsive wheel size + const containerRef = useRef(null) + const [wheelSize, setWheelSize] = useState(820) useEffect(() => { - timer.current = setInterval(() => { - setVisible(false) - setTimeout(() => { - setActive((p) => (p + 1) % n) - setVisible(true) - }, 220) - }, 4000) - return () => { - if (timer.current) clearInterval(timer.current) + function measure() { + if (containerRef.current) { + const w = containerRef.current.offsetWidth + setWheelSize(Math.min(820, Math.max(340, w))) + } } + measure() + window.addEventListener('resize', measure) + return () => window.removeEventListener('resize', measure) + }, []) + + const WHEEL_SIZE = wheelSize + const DINO_RADIUS = WHEEL_SIZE * 0.39 + const DINO_ACTIVE_SIZE = Math.round(WHEEL_SIZE * 0.1) + const DINO_INACTIVE_SIZE = Math.round(WHEEL_SIZE * 0.065) + + const angleForItem = useCallback((i: number) => 270 - (i / n) * 360, [n]) + + const goTo = useCallback( + (nextIdx: number) => { + setVisible(false) + const target = angleForItem(nextIdx) + setWheelAngle((current) => { + const currentNorm = ((current % 360) + 360) % 360 + const targetNorm = ((target % 360) + 360) % 360 + let diff = targetNorm - currentNorm + if (diff > 180) diff -= 360 + if (diff < -180) diff += 360 + return current + diff + }) + setTimeout(() => { + setActive(nextIdx) + activeRef.current = nextIdx + setVisible(true) + }, 240) + }, + [angleForItem] + ) + + const startTimer = useCallback(() => { + if (timerRef.current) clearInterval(timerRef.current) + timerRef.current = setInterval(() => { + const next = (activeRef.current + 1) % n + goTo(next) + }, 4500) + }, [n, goTo]) + + useEffect(() => { + startTimer() + return () => { + if (timerRef.current) clearInterval(timerRef.current) + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [n]) + function handleSelect(i: number) { + if (timerRef.current) clearInterval(timerRef.current) + goTo(i) + setTimeout(startTimer, 8000) + } + const current = items[active]! - // Arc positions: center at (50%, 80%) of wheel container, radius ~42% - // Angles from 200° to 340° (CSS coords: 0=right, 90=down) - // Items placed in a "smile" arc below center - const getArcPos = (i: number) => { - const startDeg = 200 - const spanDeg = 140 - const deg = startDeg + (i / Math.max(n - 1, 1)) * spanDeg - const rad = (deg * Math.PI) / 180 - // Percentage offset from center - const cx = 50 // % - const cy = 32 // % from top — center of the circle - const r = 38 // % radius - const x = cx + r * Math.cos(rad) - const y = cy + r * Math.sin(rad) - return { x, y } - } - return ( -
-
- {/* Desktop: two-column layout */} -
- {/* Left: info card */} -
-
-

- Мешканець парку -

-

- {current.name} -

+
+ {/* Topographic wave bg */} + + {/* ── Info panel — centered, above the wheel ── */} +
+
+

+ {current.name} +

+ {current.latinName && ( +

+ ({current.latinName}) +

+ )} +
+ {current.length && ( + + Довжина{' '} + {current.length} + + )} + {current.length && current.height && |} + {current.height && ( + + Висота{' '} + {current.height} + + )}
+
+
- {/* Right: wheel */} -
- {/* Circular wheel container */} -
- {/* Outer ring decoration */} -
-
+ {/* ── Wheel ── */} +
+ {/* Arch container: shows top half of the circle */} +
+ {/* Full circle positioned so center is at bottom of container */} +
+ - {/* Center: active dino image */} -
- {current.imageUrl ? ( - {current.name} - ) : ( - - )} -
+ {items.map((dino, i) => { + const naturalDeg = (i / n) * 360 + const isActive = i === active + const iconSize = isActive ? DINO_ACTIVE_SIZE : DINO_INACTIVE_SIZE - {/* Arc thumbnails */} - {items.map((dino, i) => { - const { x, y } = getArcPos(i) - const isActive = i === active - return ( - - ) - })} -
- - {/* Mobile: dino name below wheel */} -

- {current.name} -

+
+ + ) + })}
+ + {/* ── Prev / dots / Next ── */} +
+ + +
+ {items.map((_, i) => ( +
+ + +
+ + {/* ── Photo gallery row ── */} +
+ {GALLERY_PHOTOS.map((photo, i) => ( +
+ {photo.alt} +
+ ))} +
+ + {/* ── Mobile name list ── */} +
+ {items.map((dino, i) => ( + + ))} +
) } - -function StatRow({ label, value }: { label: string; value: string }) { - return ( -
- - {label} - - - {value} - -
- ) -} diff --git a/src/components/sections/DinoWhyVisit.tsx b/src/components/sections/DinoWhyVisit.tsx index 58757cb..85689c5 100644 --- a/src/components/sections/DinoWhyVisit.tsx +++ b/src/components/sections/DinoWhyVisit.tsx @@ -1,8 +1,15 @@ 'use client' +/* eslint-disable @next/next/no-img-element */ import { useState, useRef, useEffect } from 'react' const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' } +const FONT_POP = { fontFamily: 'var(--font-poppins, Poppins), sans-serif' } + +interface ReviewPhoto { + src: string + label?: string | null +} interface ReviewVideo { src: string @@ -14,40 +21,62 @@ interface DinoWhyVisitProps { title?: string items?: Array<{ title: string; description: string }> reviewVideos?: ReviewVideo[] + reviewPhotos?: ReviewPhoto[] } const DEFAULT_ITEMS = [ { title: 'Навчання через гру', description: - 'Дітки дізнаються про стародавніх тварин через захопливі ігри та інтерактивні вправи з гідом.', + "Через гру дитина краще запам'ятовує факти. Вона може вивчати не лише історію про динозаврів, а й нові іноземні слова, розуміти науку та захоплюватись природою.", }, { title: 'Дитячі очі, що палають захватом', description: - 'Реалістичні рухи та звуки динозаврів створюють ефект повного занурення — дитина точно не забуде цього дня.', + "Здивування та вау-ефект, коли дитина побачить цих велетнів, неможливо передати. Це щирі емоції радості та справжнього щастя, які закарбуються у пам'яті назавжди.", }, { title: 'Неймовірні фотографії', description: - 'Сфотографуйтесь поруч із улюбленим динозавром або зробіть фото з екскурсоводом — тепла згадка для всієї родини.', + 'На локації багато зон, де можна зробити красиві фотографії не лише на згадку, а й для соцмереж. Кожен знімок — окремий маленький шедевр.', + }, +] + +const DEFAULT_REVIEW_PHOTOS: ReviewPhoto[] = [ + { + src: '/dynopark/Gemini_Generated_Image_l7a8tql7a8tql7a8_.jpg', + label: 'ЗАРАЗ МИ ДІЗНАЄМОСЯ...', + }, + { + src: '/dynopark/Gemini_Generated_Image_mlbfxbmlbfxbmlbf_.jpg', + label: 'ЗАРАЗ МИ ДІЗНАЄМОСЯ...', + }, + { + src: '/dynopark/Gemini_Generated_Image_novmrqnovmrqnovm_.jpg', + label: 'ЗАРАЗ МИ ДІЗНАЄМОСЯ...', + }, + { + src: '/dynopark/Gemini_Generated_Image_r9d5kbr9d5kbr9d5_.jpg', + label: 'ЗАРАЗ МИ ДІЗНАЄМОСЯ...', }, ] export function DinoWhyVisit({ - title = 'Чому варто відвідати динопарк', + title = 'Чому варто відвідати Динопарк', items = DEFAULT_ITEMS, reviewVideos, + reviewPhotos, }: DinoWhyVisitProps) { - const videos = reviewVideos && reviewVideos.length > 0 ? reviewVideos : [] - const vn = videos.length + const photos = reviewPhotos && reviewPhotos.length > 0 ? reviewPhotos : DEFAULT_REVIEW_PHOTOS + const vn = photos.length const [openIndex, setOpenIndex] = useState(0) - const [videoActive, setVideoActive] = useState(0) - const [playingIndex, setPlayingIndex] = useState(null) - const videoPausedRef = useRef(false) + const [photoActive, setPhotoActive] = useState(0) const accordionTimer = useRef | null>(null) - const videoTimer = useRef | null>(null) - const videoRefs = useRef<(HTMLVideoElement | null)[]>([]) + const photoTimer = useRef | null>(null) + const hovering = useRef(false) + + // Videos take precedence over photos if provided + const hasVideos = reviewVideos && reviewVideos.length > 0 useEffect(() => { accordionTimer.current = setInterval(() => { @@ -59,16 +88,14 @@ export function DinoWhyVisit({ }, [items.length]) useEffect(() => { - if (vn <= 0) return - videoTimer.current = setInterval(() => { - if (!videoPausedRef.current) { - setVideoActive((prev) => (prev + 1) % vn) - } - }, 5000) + if (vn <= 0 || hasVideos) return + photoTimer.current = setInterval(() => { + if (!hovering.current) setPhotoActive((prev) => (prev + 1) % vn) + }, 3800) return () => { - if (videoTimer.current) clearInterval(videoTimer.current) + if (photoTimer.current) clearInterval(photoTimer.current) } - }, [vn]) + }, [vn, hasVideos]) function handleItemClick(i: number) { setOpenIndex(i) @@ -78,74 +105,60 @@ export function DinoWhyVisit({ }, 4000) } - function handlePlayVideo(i: number) { - if (playingIndex === i) return - if (playingIndex !== null && videoRefs.current[playingIndex]) { - videoRefs.current[playingIndex]!.pause() - videoRefs.current[playingIndex]!.currentTime = 0 - } - videoPausedRef.current = true - setPlayingIndex(i) - setVideoActive(i) - setTimeout(() => { - videoRefs.current[i]?.play() - }, 50) - } - - function handleVideoNav(i: number) { - if (playingIndex !== null && videoRefs.current[playingIndex]) { - videoRefs.current[playingIndex]!.pause() - videoRefs.current[playingIndex]!.currentTime = 0 - } - setPlayingIndex(null) - videoPausedRef.current = false - setVideoActive(i) + function handlePhotoNav(i: number) { + setPhotoActive(i) + if (photoTimer.current) clearInterval(photoTimer.current) + photoTimer.current = setInterval(() => { + if (!hovering.current) setPhotoActive((prev) => (prev + 1) % vn) + }, 3800) } return ( -
-
+
+

{title}

-
- {/* Accordion */} +
+ {/* ── Accordion (left) ── */}
-
-
+ {/* Green decorative block behind accordion */} +
+
{items.map((item, i) => { const isOpen = openIndex === i return ( - )} -
- ) - })} -
- -
- -
- {videos.map((_, i) => ( + {photos.map((photo, i) => { + const isActive = i === photoActive + return ( +
+ {isActive + {/* Label overlay */} + {photo.label && ( +
+

+ {photo.label} +

+
+ )} +
+ ) + })} +
+ + {/* Photo nav dots */} +
+ {photos.map((_, i) => (
-
-
- )} + )} +
{/* Bottom quote */}

- Запросіть традицію щоразу знайомитись з новим динозавром або щоразу фотографуватись біля - улюбленого динозавра. З часом ці знімки складуться у захопливий ковток улюблених / назад - — тепла згадка для всієї родини. + Започаткуйте традицію: щотижня знайомтеся з новим диногероєм або щороку фотографуйтесь + біля улюбленого динозавра. З часом ці знімки складуться у захопливу колекцію — тепла + згадка для всієї родини.

) } + +// ── Video carousel (when CMS provides videos) ───────────────────────────── +function VideoCarousel({ + videos, +}: { + videos: { src: string; poster?: string | null; label?: string | null }[] +}) { + const n = videos.length + const [active, setActive] = useState(0) + const [playing, setPlaying] = useState(null) + const refs = useRef<(HTMLVideoElement | null)[]>([]) + const paused = useRef(false) + + useEffect(() => { + if (n <= 0) return + const t = setInterval(() => { + if (!paused.current) setActive((p) => (p + 1) % n) + }, 5000) + return () => clearInterval(t) + }, [n]) + + function handlePlay(i: number) { + if (playing === i) return + if (playing !== null && refs.current[playing]) { + refs.current[playing]!.pause() + refs.current[playing]!.currentTime = 0 + } + paused.current = true + setPlaying(i) + setActive(i) + setTimeout(() => refs.current[i]?.play(), 50) + } + + return ( +
{ + paused.current = true + }} + onMouseLeave={() => { + if (playing === null) paused.current = false + }} + > + {videos.map((v, i) => { + const isActive = i === active + const isPlaying = i === playing + return ( +
+
+ ) + })} +
+ ) +}