feat(home): apply Figma design — new hero + location photos, WhyParents gallery
Some checks are pending
CI / Type Check (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Unit Tests (push) Waiting to run
Deploy / Build & Push Image (push) Waiting to run
Deploy / Deploy to VPS (push) Blocked by required conditions

- Hero: "Започаткуйте традицію:" title at 96px, subtitle 48px/Black
  (щороку фотографуйтесь біля улюбленого динозавра); removed separate
  T-Rex/family overlays — T-Rex now baked into hero-bg2
- WhyParents: replaced desktop auto-scroll photo gallery with 3 static
  video thumbnail cards with play buttons (Figma Gallery_holder layout)
- Assets: replace hero + 5 location images with real 2026 park photos
  from Figma; add why-parents-video.webp thumbnail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-05-28 15:01:19 +01:00
parent 492f7934b6
commit 0e732bfbd5
11 changed files with 54 additions and 113 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 943 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -13,9 +13,8 @@ import type { HomePageHero } from '@/types/globals'
export const revalidate = 60
const STATIC_HERO: NonNullable<HomePageHero> = {
title: 'Шуміленд — світ, де казка оживає',
subtitle:
'Сімейний тематичний парк, де гра допомагає пізнавати світ, а кожна прогулянка перетворюється на незабутню пригоду.',
title: 'Започаткуйте традицію:',
subtitle: 'щороку фотографуйтесь біля улюбленого динозавра',
ctaLabel: 'Купити квиток',
ctaHref: '/kvytky',
}

View file

@ -3,8 +3,6 @@ import type { HomePageHero, Media } from '@/types/globals'
import { BtnPrimary } from '@/components/ui/BtnPrimary'
const IMG_BG2 = '/images/figma/hero-bg2.webp'
const IMG_BG1 = '/images/figma/hero-bg1.webp'
const IMG_FAMILY = '/images/figma/hero-bg-family.webp'
interface HeroProps {
hero?: HomePageHero | null
@ -24,7 +22,6 @@ export function Hero({ hero }: HeroProps) {
const { title, subtitle, ctaLabel, ctaHref, backgroundImage, backgroundVideo } = hero
const showCta = Boolean(ctaLabel && ctaHref && isSafeHref(ctaHref))
const bgMedia = isMedia(backgroundImage) ? backgroundImage : null
const useDefaultLayers = !bgMedia && !backgroundVideo
return (
<section className="relative mx-[10px] -mt-[60px] h-[clamp(480px,calc(56.25vw+60px),calc(100vh+60px))] overflow-hidden rounded-b-[20px] bg-black lg:-mt-[120px] lg:h-[clamp(600px,calc(56.25vw+120px),calc(100vh+120px))]">
@ -46,14 +43,12 @@ export function Hero({ hero }: HeroProps) {
className="pointer-events-none absolute inset-0 h-full w-full object-cover"
/>
) : (
<>
<img
src={IMG_BG2}
alt=""
aria-hidden="true"
className="pointer-events-none absolute inset-0 h-full w-full object-cover"
/>
</>
<img
src={IMG_BG2}
alt=""
aria-hidden="true"
className="pointer-events-none absolute inset-0 h-full w-full object-cover"
/>
)}
{/* ── Gradient: mobile ── */}
@ -75,16 +70,16 @@ export function Hero({ hero }: HeroProps) {
}}
/>
{/* ── Text content (above T-Rex z-[20]) ── */}
{/* ── Text content ── */}
<div className="relative z-[25] flex flex-col px-6 pt-[100px] pb-[60px] lg:px-0 lg:pt-[228px] lg:pb-[80px]">
<div className="lg:mx-auto lg:w-full lg:max-w-[1504px] lg:px-0">
<div className="flex max-w-[85vw] flex-col gap-5 md:gap-6 lg:max-w-[1032px] lg:pl-[154px]">
<div className="flex max-w-[85vw] flex-col gap-5 md:gap-6 lg:max-w-[1032px] lg:gap-[38px] lg:pl-[154px]">
{title && (
<h1
className="font-bold text-white uppercase"
style={{
fontFamily: 'var(--font-montserrat, Montserrat), sans-serif',
fontSize: 'clamp(28px, 6.25vw, 120px)',
fontSize: 'clamp(28px, 5vw, 96px)',
lineHeight: 1.2,
fontWeight: 700,
whiteSpace: 'pre-line',
@ -98,10 +93,9 @@ export function Hero({ hero }: HeroProps) {
className="text-white"
style={{
fontFamily: 'var(--font-montserrat, Montserrat), sans-serif',
fontSize: 'clamp(14px, 1.25vw, 24px)',
fontWeight: 500,
fontSize: 'clamp(18px, 2.5vw, 48px)',
fontWeight: 900,
lineHeight: 1.5,
maxWidth: '629px',
}}
>
{subtitle}
@ -115,24 +109,6 @@ export function Hero({ hero }: HeroProps) {
</div>
</div>
</div>
{/* ── T-Rex overlay — above text, desktop only ── */}
{useDefaultLayers && (
<>
<img
src={IMG_BG1}
alt=""
aria-hidden="true"
className="pointer-events-none absolute inset-0 z-[20] hidden h-full w-full object-cover lg:block"
/>
<img
src={IMG_FAMILY}
alt=""
aria-hidden="true"
className="pointer-events-none absolute inset-0 z-[30] hidden h-full w-full object-cover lg:block"
/>
</>
)}
</section>
)
}

View file

@ -4,20 +4,7 @@
import { useState, useRef, useEffect } from 'react'
import type { HomePageWhyParentsItem, Media } from '@/types/globals'
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_GALLERY = [
'/images/figma/why-parents-1.webp',
'/images/figma/why-parents-2.webp',
'/images/figma/why-parents-3.webp',
'/images/figma/why-parents-4.webp',
'/images/figma/gallery-1.webp',
'/images/figma/gallery-3.webp',
]
const VIDEO_THUMBNAIL = '/images/figma/why-parents-video.webp'
const STATIC_ITEMS: HomePageWhyParentsItem[] = [
{
@ -58,11 +45,8 @@ interface WhyParentsProps {
title?: string
}
export function WhyParents({ items, sideGallery, title }: WhyParentsProps) {
export function WhyParents({ items, sideGallery: _sideGallery, title }: WhyParentsProps) {
const [openIndex, setOpenIndex] = useState<number>(0)
const galleryRef = useRef<HTMLDivElement>(null)
const galleryPausedRef = useRef(false)
const galleryRafRef = useRef<number | null>(null)
const autoTimer = useRef<ReturnType<typeof setInterval> | null>(null)
const activeItems = items && items.length > 0 ? items : STATIC_ITEMS
@ -84,36 +68,6 @@ export function WhyParents({ items, sideGallery, title }: WhyParentsProps) {
}, 4000)
}
const galleryUrls: string[] =
sideGallery && sideGallery.length > 0
? sideGallery.map((g) => getMediaUrl(g.image) ?? '/images/figma/gallery-1.webp')
: STATIC_GALLERY
const doubled = [...galleryUrls, ...galleryUrls]
useEffect(() => {
const el = galleryRef.current
if (!el) return
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
let last = performance.now()
const tick = (now: number) => {
const dt = now - last
last = now
if (!galleryPausedRef.current) {
const half = el.scrollWidth / 2
if (half > 0) {
el.scrollLeft += dt * 0.04
if (el.scrollLeft >= half) el.scrollLeft -= half
}
}
galleryRafRef.current = requestAnimationFrame(tick)
}
galleryRafRef.current = requestAnimationFrame(tick)
return () => {
if (galleryRafRef.current) cancelAnimationFrame(galleryRafRef.current)
}
}, [])
return (
<section className="py-[20px] md:py-[60px] lg:py-[40px]">
<div className="mx-auto max-w-[1204px] px-8">
@ -180,44 +134,56 @@ export function WhyParents({ items, sideGallery, title }: WhyParentsProps) {
</div>
</div>
{/* Horizontal auto-scroll gallery — desktop */}
<div className="hidden flex-1 overflow-hidden lg:block">
<div className="relative">
{/* 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
ref={galleryRef}
className="flex gap-5 overflow-x-auto"
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
onMouseEnter={() => {
galleryPausedRef.current = true
}}
onMouseLeave={() => {
galleryPausedRef.current = false
}}
key={i}
className="relative flex-none overflow-hidden rounded-[20px] bg-[#d6f2c0]"
style={{ width: 505, height: h, marginTop: mt }}
>
{doubled.map((src, i) => (
<div
key={i}
className={`relative h-[488px] w-[505px] flex-none overflow-hidden rounded-[20px] ${i === 0 ? 'bg-[#d6f2c0]' : ''}`}
>
<img
src={src}
alt=""
<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="h-full w-full object-cover transition-transform duration-700 ease-out hover:scale-110"
loading={i < 2 ? 'eager' : 'lazy'}
/>
className="ml-1"
>
<path d="M5 3L19 12L5 21V3Z" fill="#396817" />
</svg>
</div>
))}
</div>
</div>
</div>
))}
</div>
{/* Mobile: horizontal scroll */}
{/* Mobile: horizontal scroll with video thumbnails */}
<div
className="flex w-full gap-5 overflow-x-auto pb-2 lg:hidden"
style={{ scrollbarWidth: 'none' }}
>
{galleryUrls.map((src, i) => (
{[
'/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]"