perf: compress images to WebP (-97%) and fix slider/performance issues
- Convert all 40+ public/images/figma assets from raw PNG/JPG to WebP at max 1920px, 82% quality: 397 MB → 13 MB total (96.7% reduction) - Update all component image references to .webp - Add 1-year Cache-Control headers for /images/* and /_next/static/* - Fix GallerySlider initial scroll offset (first card no longer clipped by mask) - Fix arrow2.svg missing explicit width/height (Lighthouse unsized-images) - Hero, LocationsSlider: aspect-ratio height + seamless infinite loop (prior session) - Add drizzle-kit to dependencies for production schema push (prior session) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
@ -1,9 +1,23 @@
|
|||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const ONE_YEAR = 'public, max-age=31536000, immutable'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
reactStrictMode: true,
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/images/:path*',
|
||||
headers: [{ key: 'Cache-Control', value: ONE_YEAR }],
|
||||
},
|
||||
{
|
||||
source: '/_next/static/:path*',
|
||||
headers: [{ key: 'Cache-Control', value: ONE_YEAR }],
|
||||
},
|
||||
]
|
||||
},
|
||||
webpack: (config) => {
|
||||
config.watchOptions = {
|
||||
...config.watchOptions,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cyrillic-to-translit-js": "^3.2.1",
|
||||
"drizzle-kit": "0.31.7",
|
||||
"graphql": "^16.9.0",
|
||||
"next": "^16.2.6",
|
||||
"payload": "^3.33.0",
|
||||
|
|
|
|||
|
|
@ -45,7 +45,20 @@ export default buildConfig({
|
|||
editor: lexicalEditor(),
|
||||
sharp,
|
||||
|
||||
collections: [Users, Media, Pages, BlogPosts, Categories, Tags, Tariffs, Leads, Orders, Locations, Reviews, BirthdayPackages],
|
||||
collections: [
|
||||
Users,
|
||||
Media,
|
||||
Pages,
|
||||
BlogPosts,
|
||||
Categories,
|
||||
Tags,
|
||||
Tariffs,
|
||||
Leads,
|
||||
Orders,
|
||||
Locations,
|
||||
Reviews,
|
||||
BirthdayPackages,
|
||||
],
|
||||
|
||||
globals: [HomePage, CheckoutPage, ThankYouPage, Header, Footer, SiteSettings],
|
||||
|
||||
|
|
|
|||
3
pnpm-lock.yaml
generated
|
|
@ -29,6 +29,9 @@ importers:
|
|||
cyrillic-to-translit-js:
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
drizzle-kit:
|
||||
specifier: 0.31.7
|
||||
version: 0.31.7
|
||||
graphql:
|
||||
specifier: ^16.9.0
|
||||
version: 16.14.0
|
||||
|
|
|
|||
BIN
public/images/figma/081e52b5-d35a-41d2-b506-a9d751b0b563.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/images/figma/109f13b4-25f9-4f94-a06d-77421ff2b4fe.webp
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/images/figma/17d78753-cec3-40ca-a1fc-4125c2b79eff.webp
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
public/images/figma/2936ec5e-4f99-441e-9bf2-34f23c283170.webp
Normal file
|
After Width: | Height: | Size: 456 KiB |
BIN
public/images/figma/2c6a3e5e-7346-4c3e-b8a0-fae1facb87ad.webp
Normal file
|
After Width: | Height: | Size: 664 KiB |
BIN
public/images/figma/455670b9-c89a-4bea-81fe-5a4d93b25483.webp
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
public/images/figma/5088f41f-b898-4958-b7f6-519496b65382.webp
Normal file
|
After Width: | Height: | Size: 826 B |
BIN
public/images/figma/58218f84-457d-4ff5-8d4e-2ace40b45568.webp
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
public/images/figma/640999e1-7096-4623-bf96-12bb8ef62ffc.webp
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
public/images/figma/7a2627b2-b6ce-4325-a0b1-fbc3393aca4c.webp
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
public/images/figma/908a8aab-6129-4d7d-b7ac-c43b6fa60044.webp
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
public/images/figma/abacda18-57c7-441d-9313-c70d22c6f0f0.webp
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
public/images/figma/ac7f3a5e-0e66-4971-bccc-89901a7c314d.webp
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
public/images/figma/b5597790-7b54-4ad7-9977-4cb15633286e.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/figma/c3053789-6cd0-4dda-9774-d4ae4bc400e1.webp
Normal file
|
After Width: | Height: | Size: 517 KiB |
BIN
public/images/figma/check-mark.webp
Normal file
|
After Width: | Height: | Size: 722 B |
BIN
public/images/figma/de9ad287-5e83-4a0c-ad06-f2420bff4096.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/figma/e42d6611-82e6-47b0-9d84-271f9810ab1a.webp
Normal file
|
After Width: | Height: | Size: 567 KiB |
BIN
public/images/figma/e9a8cee6-6ee5-4c74-b270-1c133a762c0a.webp
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
public/images/figma/f4e2bff2-754c-460c-baba-baa95521bfc9.webp
Normal file
|
After Width: | Height: | Size: 779 KiB |
BIN
public/images/figma/f5d32fc0-8ea7-4fd1-b193-819d6aa1a68e.webp
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/images/figma/footer-bg.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/figma/gallery-1.webp
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
public/images/figma/gallery-2.webp
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
public/images/figma/gallery-3.webp
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
public/images/figma/gallery-4.webp
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/images/figma/gallery-6.webp
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
public/images/figma/gallery-7.webp
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
public/images/figma/gallery-8.webp
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/images/figma/hero-bg-family.webp
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
public/images/figma/hero-bg1.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/images/figma/hero-bg2.webp
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
public/images/figma/hero-blur-mask.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/figma/loc-dinopark.webp
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
public/images/figma/loc-divo-lis.webp
Normal file
|
After Width: | Height: | Size: 301 KiB |
BIN
public/images/figma/loc-map.webp
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/images/figma/news-bg1.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/figma/news-bg2.webp
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/images/figma/news-bg3.webp
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
public/images/figma/news-bg4.webp
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
public/images/figma/news-bg5.webp
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
public/images/figma/news-bg6.webp
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
public/images/figma/review-avatar-bg.webp
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
public/images/figma/video-preview.webp
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
public/images/figma/why-parents-1.webp
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/images/figma/why-parents-2.webp
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/images/figma/why-parents-3.webp
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
public/images/figma/why-parents-4.webp
Normal file
|
After Width: | Height: | Size: 96 KiB |
74
scripts/compress-images.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import sharp from 'sharp'
|
||||
import { readdir, stat } from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const imgDir = path.resolve(__dirname, '../public/images/figma')
|
||||
|
||||
const SKIP = /\.(svg|webp|gif)$/i
|
||||
|
||||
const SIZE_LIMITS = {
|
||||
// hero / footer / video-preview — full bleed, keep wide
|
||||
'hero-bg1.png': { width: 1920 },
|
||||
'hero-bg2.png': { width: 1920 },
|
||||
'hero-bg-family.png': { width: 1920 },
|
||||
'footer-bg.png': { width: 1920 },
|
||||
'video-preview.png': { width: 1920 },
|
||||
// location cards — displayed at ~560px, 2× retina = 1120
|
||||
'loc-dinopark.jpg': { width: 1200 },
|
||||
'loc-divo-lis.png': { width: 1200 },
|
||||
// gallery tiles — displayed at ~380px, 2× = 760
|
||||
'gallery-1.png': { width: 800 },
|
||||
'gallery-2.png': { width: 800 },
|
||||
'gallery-3.png': { width: 800 },
|
||||
'gallery-4.png': { width: 800 },
|
||||
'gallery-6.png': { width: 800 },
|
||||
'gallery-7.png': { width: 800 },
|
||||
'gallery-8.png': { width: 800 },
|
||||
// why-parents side tiles
|
||||
'why-parents-1.png': { width: 800 },
|
||||
'why-parents-2.png': { width: 800 },
|
||||
'why-parents-3.png': { width: 800 },
|
||||
'why-parents-4.png': { width: 800 },
|
||||
// news / blog thumbnails
|
||||
'news-bg1.jpg': { width: 800 },
|
||||
'news-bg2.png': { width: 800 },
|
||||
'news-bg3.jpg': { width: 800 },
|
||||
// misc
|
||||
'review-avatar-bg.jpg': { width: 160 },
|
||||
'check-mark.png': { width: 48 },
|
||||
}
|
||||
|
||||
const files = await readdir(imgDir)
|
||||
|
||||
let totalBefore = 0
|
||||
let totalAfter = 0
|
||||
|
||||
for (const file of files) {
|
||||
if (SKIP.test(file)) continue
|
||||
if (file.endsWith('.webp')) continue
|
||||
|
||||
const src = path.join(imgDir, file)
|
||||
const dst = path.join(imgDir, file.replace(/\.(png|jpg|jpeg)$/i, '.webp'))
|
||||
|
||||
const srcStat = await stat(src)
|
||||
totalBefore += srcStat.size
|
||||
|
||||
const limit = SIZE_LIMITS[file] ?? { width: 1920 }
|
||||
|
||||
try {
|
||||
const info = await sharp(src)
|
||||
.resize({ width: limit.width, withoutEnlargement: true })
|
||||
.webp({ quality: 82, effort: 4 })
|
||||
.toFile(dst)
|
||||
|
||||
totalAfter += info.size
|
||||
const pct = ((1 - info.size / srcStat.size) * 100).toFixed(1)
|
||||
console.log(`✓ ${file} → ${file.replace(/\.(png|jpg|jpeg)$/i, '.webp')} ${(srcStat.size / 1e6).toFixed(1)}MB → ${(info.size / 1e6).toFixed(1)}MB (${pct}% smaller)`)
|
||||
} catch (err) {
|
||||
console.error(`✗ ${file}: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal: ${(totalBefore / 1e6).toFixed(1)}MB → ${(totalAfter / 1e6).toFixed(1)}MB (${((1 - totalAfter / totalBefore) * 100).toFixed(1)}% reduction)`)
|
||||
|
|
@ -19,7 +19,7 @@ const STATIC_LOCATIONS: LocationCMS[] = [
|
|||
slug: 'dynopark',
|
||||
tagline: 'портал у світ динозаврів',
|
||||
shortDesc: 'Прогуляйтесь серед реалістичних динозаврів у повний зріст. Понад 20 видів доісторичних тварин у природному середовищі.',
|
||||
image: '/images/figma/loc-dinopark.jpg',
|
||||
image: '/images/figma/loc-dinopark.webp',
|
||||
},
|
||||
{
|
||||
id: 'dyvolis',
|
||||
|
|
@ -27,7 +27,7 @@ const STATIC_LOCATIONS: LocationCMS[] = [
|
|||
slug: 'dyvolis',
|
||||
tagline: 'зона казкових топіарних фігур',
|
||||
shortDesc: 'Чарівний ліс з інтерактивними атракціонами, мотузковими парками та пригодами для всіх вікових груп.',
|
||||
image: '/images/figma/loc-divo-lis.png',
|
||||
image: '/images/figma/loc-divo-lis.webp',
|
||||
},
|
||||
{
|
||||
id: 'maze',
|
||||
|
|
@ -35,7 +35,7 @@ const STATIC_LOCATIONS: LocationCMS[] = [
|
|||
slug: 'maze',
|
||||
tagline: 'справжній виклик кмітливості',
|
||||
shortDesc: 'Захоплюючий лабіринт з дзеркалами, оптичними ілюзіями та таємничими кімнатами.',
|
||||
image: '/images/figma/gallery-1.png',
|
||||
image: '/images/figma/gallery-1.webp',
|
||||
},
|
||||
{
|
||||
id: 'tir',
|
||||
|
|
@ -43,7 +43,7 @@ const STATIC_LOCATIONS: LocationCMS[] = [
|
|||
slug: 'tir',
|
||||
tagline: 'перемога, яку ви розділите разом',
|
||||
shortDesc: 'Влаштуйте дружні змагання, дайте малечі декілька уроків та виграйте класний приз.',
|
||||
image: '/images/figma/gallery-3.png',
|
||||
image: '/images/figma/gallery-3.webp',
|
||||
},
|
||||
{
|
||||
id: 'playground',
|
||||
|
|
@ -51,7 +51,7 @@ const STATIC_LOCATIONS: LocationCMS[] = [
|
|||
slug: 'playground',
|
||||
tagline: 'територія забав і нових друзів',
|
||||
shortDesc: 'Поки малеча підкорює гірки та знаходить перших друзів, ви можете нарешті зробити паузу.',
|
||||
image: '/images/figma/gallery-8.png',
|
||||
image: '/images/figma/gallery-8.webp',
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ export default async function LocationsPage() {
|
|||
<div className="mx-auto max-w-[1204px] px-8 py-16">
|
||||
<div className="flex flex-col gap-10">
|
||||
{locations.map((loc, i) => {
|
||||
const imgUrl = getMediaUrl(loc.image) ?? '/images/figma/loc-dinopark.jpg'
|
||||
const imgUrl = getMediaUrl(loc.image) ?? '/images/figma/loc-dinopark.webp'
|
||||
const color = COLORS[i % COLORS.length]
|
||||
const slug = loc.slug
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@ export const Leads: CollectionConfig = {
|
|||
},
|
||||
{ name: 'message', type: 'textarea', label: 'Повідомлення' },
|
||||
{ name: 'groupSize', type: 'number', label: 'Кількість учасників' },
|
||||
{ name: 'preferredDate', type: 'date', label: 'Бажана дата', admin: { date: { pickerAppearance: 'dayOnly' } } },
|
||||
{
|
||||
name: 'preferredDate',
|
||||
type: 'date',
|
||||
label: 'Бажана дата',
|
||||
admin: { date: { pickerAppearance: 'dayOnly' } },
|
||||
},
|
||||
{ name: 'packageSlug', type: 'text', label: 'Пакет (slug)' },
|
||||
{ name: 'notes', type: 'textarea' },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,10 +46,9 @@ export function BirthdayBookingForm({ defaultPackage }: BirthdayBookingFormProps
|
|||
packageSlug: packageSlug || undefined,
|
||||
groupSize: guestCount ? Number(guestCount) : undefined,
|
||||
preferredDate: preferredDate || undefined,
|
||||
message: [
|
||||
childAge ? `Вік іменинника: ${childAge}` : '',
|
||||
wishes,
|
||||
].filter(Boolean).join('\n') || undefined,
|
||||
message:
|
||||
[childAge ? `Вік іменинника: ${childAge}` : '', wishes].filter(Boolean).join('\n') ||
|
||||
undefined,
|
||||
...utm,
|
||||
}),
|
||||
})
|
||||
|
|
@ -61,7 +60,7 @@ export function BirthdayBookingForm({ defaultPackage }: BirthdayBookingFormProps
|
|||
}
|
||||
setSuccess(true)
|
||||
} catch {
|
||||
setError('Помилка мережі. Перевірте з\'єднання та спробуйте ще раз.')
|
||||
setError("Помилка мережі. Перевірте з'єднання та спробуйте ще раз.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -73,7 +72,7 @@ export function BirthdayBookingForm({ defaultPackage }: BirthdayBookingFormProps
|
|||
<h3 className="text-[24px] font-bold text-white" style={{ fontFamily: FONT }}>
|
||||
Заявку отримано!
|
||||
</h3>
|
||||
<p className="text-white/70 text-[16px]" style={{ fontFamily: FONT }}>
|
||||
<p className="text-[16px] text-white/70" style={{ fontFamily: FONT }}>
|
||||
Менеджер зв'яжеться з вами протягом 30 хвилин для уточнення деталей свята.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -220,7 +219,11 @@ function Field({
|
|||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor={htmlFor} className="text-[14px] font-medium text-white/80" style={{ fontFamily: FONT }}>
|
||||
<label
|
||||
htmlFor={htmlFor}
|
||||
className="text-[14px] font-medium text-white/80"
|
||||
style={{ fontFamily: FONT }}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ export function GroupRequestForm() {
|
|||
formSource: 'group-request',
|
||||
groupSize: groupSize ? Number(groupSize) : undefined,
|
||||
preferredDate: preferredDate || undefined,
|
||||
message: [groupType ? `Тип групи: ${groupType}` : '', message].filter(Boolean).join('\n') || undefined,
|
||||
message:
|
||||
[groupType ? `Тип групи: ${groupType}` : '', message].filter(Boolean).join('\n') ||
|
||||
undefined,
|
||||
...utm,
|
||||
}),
|
||||
})
|
||||
|
|
@ -53,7 +55,7 @@ export function GroupRequestForm() {
|
|||
}
|
||||
setSuccess(true)
|
||||
} catch {
|
||||
setError('Помилка мережі. Перевірте з\'єднання та спробуйте ще раз.')
|
||||
setError("Помилка мережі. Перевірте з'єднання та спробуйте ще раз.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -65,7 +67,7 @@ export function GroupRequestForm() {
|
|||
<h3 className="text-[24px] font-bold text-white" style={{ fontFamily: FONT }}>
|
||||
Заявку отримано!
|
||||
</h3>
|
||||
<p className="text-white/70 text-[16px]" style={{ fontFamily: FONT }}>
|
||||
<p className="text-[16px] text-white/70" style={{ fontFamily: FONT }}>
|
||||
Менеджер зателефонує вам протягом 30 хвилин для уточнення деталей.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -198,7 +200,11 @@ function Field({
|
|||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor={htmlFor} className="text-[14px] font-medium text-white/80" style={{ fontFamily: FONT }}>
|
||||
<label
|
||||
htmlFor={htmlFor}
|
||||
className="text-[14px] font-medium text-white/80"
|
||||
style={{ fontFamily: FONT }}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Link from 'next/link'
|
|||
import { getGlobal } from '@/lib/payload'
|
||||
import type { FooterGlobal } from '@/types/globals'
|
||||
|
||||
const IMG_BG = '/images/figma/footer-bg.png'
|
||||
const IMG_BG = '/images/figma/footer-bg.webp'
|
||||
const LOGO_G1 = '/images/figma/logo-g1-lg.svg'
|
||||
const LOGO_G2 = '/images/figma/logo-g2-lg.svg'
|
||||
const LOGO_G3 = '/images/figma/logo-g3-lg.svg'
|
||||
|
|
@ -19,10 +19,14 @@ const STATIC_NAV = [
|
|||
]
|
||||
|
||||
const SOCIAL_ICONS: Record<string, string> = {
|
||||
instagram: 'M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z',
|
||||
facebook: 'M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z',
|
||||
youtube: 'M23.495 6.205a3.007 3.007 0 0 0-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 0 0 .527 6.205a31.247 31.247 0 0 0-.522 5.805 31.247 31.247 0 0 0 .522 5.783 3.007 3.007 0 0 0 2.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 0 0 2.088-2.088 31.247 31.247 0 0 0 .5-5.783 31.247 31.247 0 0 0-.5-5.805zM9.609 15.601V8.408l6.264 3.602z',
|
||||
tiktok: 'M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z',
|
||||
instagram:
|
||||
'M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z',
|
||||
facebook:
|
||||
'M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z',
|
||||
youtube:
|
||||
'M23.495 6.205a3.007 3.007 0 0 0-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 0 0 .527 6.205a31.247 31.247 0 0 0-.522 5.805 31.247 31.247 0 0 0 .522 5.783 3.007 3.007 0 0 0 2.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 0 0 2.088-2.088 31.247 31.247 0 0 0 .5-5.783 31.247 31.247 0 0 0-.5-5.805zM9.609 15.601V8.408l6.264 3.602z',
|
||||
tiktok:
|
||||
'M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z',
|
||||
}
|
||||
|
||||
export async function Footer() {
|
||||
|
|
@ -52,13 +56,22 @@ export async function Footer() {
|
|||
{/* Logo */}
|
||||
<Link href="/" aria-label="Шуміленд — на головну" className="shrink-0">
|
||||
<div className="relative" style={{ width: '120px', height: '104px' }}>
|
||||
<div className="absolute" style={{ top: '91.32%', right: '21.81%', bottom: '0.97%', left: '22.26%' }}>
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ top: '91.32%', right: '21.81%', bottom: '0.97%', left: '22.26%' }}
|
||||
>
|
||||
<img src={LOGO_G1} alt="" aria-hidden="true" className="block h-full w-full" />
|
||||
</div>
|
||||
<div className="absolute" style={{ top: '71.76%', right: '2.82%', bottom: '7.3%', left: '1.41%' }}>
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ top: '71.76%', right: '2.82%', bottom: '7.3%', left: '1.41%' }}
|
||||
>
|
||||
<img src={LOGO_G2} alt="" aria-hidden="true" className="block h-full w-full" />
|
||||
</div>
|
||||
<div className="absolute" style={{ top: '1.61%', right: '2.82%', bottom: '38.73%', left: '21.27%' }}>
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ top: '1.61%', right: '2.82%', bottom: '38.73%', left: '21.27%' }}
|
||||
>
|
||||
<img src={LOGO_G3} alt="" aria-hidden="true" className="block h-full w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -78,7 +91,7 @@ export async function Footer() {
|
|||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
) : null,
|
||||
) : null
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
@ -132,7 +145,9 @@ export async function Footer() {
|
|||
<path d={icon} />
|
||||
</svg>
|
||||
) : (
|
||||
<span className="text-[12px] font-bold text-white">{s.platform?.[0]?.toUpperCase()}</span>
|
||||
<span className="text-[12px] font-bold text-white">
|
||||
{s.platform?.[0]?.toUpperCase()}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import Link from 'next/link'
|
||||
import type { BirthdayPackageCMS } from '@/types/globals'
|
||||
|
||||
const IMG_CHECK = '/images/figma/check-mark.png'
|
||||
const IMG_CHECK = '/images/figma/check-mark.webp'
|
||||
|
||||
const STATIC_PACKAGES: BirthdayPackageCMS[] = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ function getMediaUrl(img: Media | string | null | undefined): string | null {
|
|||
}
|
||||
|
||||
const STATIC_IMAGES: GalleryImage[] = [
|
||||
{ src: '/images/figma/gallery-1.png', alt: 'Шуміленд — фото 1', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/why-parents-1.png', alt: 'Шуміленд — сімейні враження', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-2.png', alt: 'Шуміленд — фото 2', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-3.png', alt: 'Шуміленд — фото 3', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/why-parents-2.png', alt: 'Шуміленд — прогулянка', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-4.png', alt: 'Шуміленд — фото 4', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-6.png', alt: 'Шуміленд — атракціони', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-7.png', alt: 'Шуміленд — фото 5', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-8.png', alt: 'Шуміленд — фото 6', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-1.webp', alt: 'Шуміленд — фото 1', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/why-parents-1.webp', alt: 'Шуміленд — сімейні враження', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-2.webp', alt: 'Шуміленд — фото 2', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-3.webp', alt: 'Шуміленд — фото 3', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/why-parents-2.webp', alt: 'Шуміленд — прогулянка', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-4.webp', alt: 'Шуміленд — фото 4', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-6.webp', alt: 'Шуміленд — атракціони', width: 320, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-7.webp', alt: 'Шуміленд — фото 5', width: 380, height: 420, radius: 20 },
|
||||
{ src: '/images/figma/gallery-8.webp', alt: 'Шуміленд — фото 6', width: 320, height: 420, radius: 20 },
|
||||
]
|
||||
|
||||
interface GalleryProps {
|
||||
|
|
@ -29,7 +29,7 @@ export function Gallery({ images, title }: GalleryProps) {
|
|||
const items: GalleryImage[] =
|
||||
images && images.length > 0
|
||||
? images.map((img, i) => ({
|
||||
src: getMediaUrl(img.image) ?? '/images/figma/gallery-1.png',
|
||||
src: getMediaUrl(img.image) ?? '/images/figma/gallery-1.webp',
|
||||
alt: img.alt ?? `Шуміленд — фото ${i + 1}`,
|
||||
width: i % 2 === 0 ? 320 : 380,
|
||||
height: 420,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ export function GallerySlider({ images, speed = 60 }: GallerySliderProps) {
|
|||
if (!el) return
|
||||
if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return
|
||||
|
||||
// ~1px per tick — tune with speed prop
|
||||
el.scrollLeft = 1
|
||||
|
||||
const pxPerTick = 1
|
||||
const intervalMs = Math.max(8, Math.round((speed * 1000) / (el.scrollWidth / 2)))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
import type { HomePageHero, Media } from '@/types/globals'
|
||||
import { BtnPrimary } from '@/components/ui/BtnPrimary'
|
||||
|
||||
const IMG_BG2 = '/images/figma/hero-bg2.png'
|
||||
const IMG_BG1 = '/images/figma/hero-bg1.png'
|
||||
const IMG_FAMILY = '/images/figma/hero-bg-family.png'
|
||||
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
|
||||
|
|
@ -28,8 +28,7 @@ export function Hero({ hero }: HeroProps) {
|
|||
|
||||
return (
|
||||
<section
|
||||
className="relative mx-[10px] -mt-[60px] overflow-hidden rounded-b-[20px] bg-black lg:-mt-[120px]"
|
||||
style={{ minHeight: 'min(1080px, 100vh)' }}
|
||||
className="relative mx-[10px] -mt-[60px] overflow-hidden rounded-b-[20px] bg-black h-[clamp(480px,calc(56.25vw+60px),calc(100vh+60px))] lg:-mt-[120px] lg:h-[clamp(600px,calc(56.25vw+120px),calc(100vh+120px))]"
|
||||
>
|
||||
{/* ── Background ──────────────────────────────────────────────── */}
|
||||
{backgroundVideo ? (
|
||||
|
|
@ -91,7 +90,7 @@ export function Hero({ hero }: HeroProps) {
|
|||
alt=""
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0 z-[20] h-full w-full object-cover"
|
||||
style={{ objectPosition: 'right center' }}
|
||||
style={{ objectPosition: 'center center' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const STATIC_LOCATIONS: LocationData[] = [
|
|||
tagline: 'портал у світ динозаврів',
|
||||
description:
|
||||
'Ви бачили їх у фільмах та мультиках, а тепер час зустріти в реальному житті та роздивитися їх зблизька! Найбільші динозаври України, які гарчать і рухаються, як справжні.',
|
||||
image: '/images/figma/loc-dinopark.jpg',
|
||||
image: '/images/figma/loc-dinopark.webp',
|
||||
href: '/lokatsii#dynopark',
|
||||
},
|
||||
{
|
||||
|
|
@ -24,7 +24,7 @@ const STATIC_LOCATIONS: LocationData[] = [
|
|||
tagline: 'зона казкових топіарних фігур',
|
||||
description:
|
||||
'Тут на лісових стежках оселилися єдинороги, дракони та добрі лісові звірята. Це ідеальне місце, щоб пофантазувати разом із дитиною.',
|
||||
image: '/images/figma/loc-divo-lis.png',
|
||||
image: '/images/figma/loc-divo-lis.webp',
|
||||
href: '/lokatsii#dyvolis',
|
||||
},
|
||||
{
|
||||
|
|
@ -33,7 +33,7 @@ const STATIC_LOCATIONS: LocationData[] = [
|
|||
tagline: 'справжній виклик кмітливості',
|
||||
description:
|
||||
'Чи зможете ви разом знайти вихід? Це справжній пригодницький виклик для всієї родини! Тут діти вчаться бути уважними та впевненими у собі.',
|
||||
image: '/images/figma/gallery-1.png',
|
||||
image: '/images/figma/gallery-1.webp',
|
||||
href: '/lokatsii#maze',
|
||||
},
|
||||
{
|
||||
|
|
@ -42,7 +42,7 @@ const STATIC_LOCATIONS: LocationData[] = [
|
|||
tagline: 'перемога, яку ви розділите разом',
|
||||
description:
|
||||
'Для дітей це не просто гра, а можливість проявити себе та "заробити" подарунок. Влаштуйте дружні змагання, дайте малечі декілька уроків та виграйте класний приз.',
|
||||
image: '/images/figma/gallery-3.png',
|
||||
image: '/images/figma/gallery-3.webp',
|
||||
href: '/lokatsii#tir',
|
||||
},
|
||||
{
|
||||
|
|
@ -51,7 +51,7 @@ const STATIC_LOCATIONS: LocationData[] = [
|
|||
tagline: 'територія забав і нових друзів',
|
||||
description:
|
||||
'Поки малеча підкорює гірки, випробовує безпечні лазанки та знаходить перших друзів, ви можете нарешті зробити паузу та просто спостерігати.',
|
||||
image: '/images/figma/gallery-8.png',
|
||||
image: '/images/figma/gallery-8.webp',
|
||||
href: '/lokatsii#playground',
|
||||
},
|
||||
]
|
||||
|
|
@ -69,7 +69,7 @@ export function Locations({ data, title }: LocationsProps) {
|
|||
slug: loc.slug,
|
||||
tagline: loc.tagline ?? '',
|
||||
description: loc.shortDesc ?? '',
|
||||
image: getMediaUrl(loc.image) ?? '/images/figma/loc-dinopark.jpg',
|
||||
image: getMediaUrl(loc.image) ?? '/images/figma/loc-dinopark.webp',
|
||||
href: loc.href ?? `/lokatsii#${loc.slug}`,
|
||||
}))
|
||||
: STATIC_LOCATIONS
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { BtnGradient } from '@/components/ui/BtnGradient'
|
||||
import { useAutoScroll } from '@/hooks/useAutoScroll'
|
||||
|
||||
export interface LocationData {
|
||||
name: string
|
||||
|
|
@ -21,7 +20,20 @@ export function LocationsSlider({ locations }: LocationsSliderProps) {
|
|||
const trackRef = useRef<HTMLDivElement>(null)
|
||||
const pauseTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const [autoPaused, setAutoPaused] = useState(false)
|
||||
useAutoScroll(trackRef, { speed: 1.5, intervalMs: 16, disabled: autoPaused })
|
||||
const doubled = [...locations, ...locations]
|
||||
|
||||
useEffect(() => {
|
||||
const el = trackRef.current
|
||||
if (!el) return
|
||||
if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return
|
||||
const id = setInterval(() => {
|
||||
if (autoPaused) return
|
||||
const half = el.scrollWidth / 2
|
||||
el.scrollLeft += 1
|
||||
if (el.scrollLeft >= half) el.scrollLeft = 0
|
||||
}, 16)
|
||||
return () => clearInterval(id)
|
||||
}, [autoPaused])
|
||||
|
||||
function scrollByOne(dir: 1 | -1) {
|
||||
trackRef.current?.scrollBy({ left: dir * 580, behavior: 'smooth' })
|
||||
|
|
@ -62,9 +74,9 @@ export function LocationsSlider({ locations }: LocationsSliderProps) {
|
|||
className="flex gap-5 overflow-x-auto scroll-smooth pb-4"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
{locations.map((loc) => (
|
||||
{doubled.map((loc, idx) => (
|
||||
<article
|
||||
key={loc.name}
|
||||
key={`${loc.slug}-${idx}`}
|
||||
className="group w-full flex-none overflow-hidden rounded-[20px] md:w-[min(560px,90vw)] lg:w-[560px]"
|
||||
>
|
||||
<div className="relative h-[420px]">
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ import { BtnDetails } from '@/components/ui/BtnDetails'
|
|||
|
||||
const FALLBACK_NEWS = [
|
||||
{
|
||||
image: '/images/figma/news-bg1.jpg',
|
||||
image: '/images/figma/news-bg1.webp',
|
||||
title: 'Відкриття нового сезону у Шуміленді',
|
||||
body: 'На території є чисті вбиральні, де можна також помити руку. Поруч розташовані велика паркувальна зона та укриття. Зручний громадський транспорт: метро, маршрутні таксі, що з\'єднують правий та лівий береги.',
|
||||
href: '/blog',
|
||||
},
|
||||
{
|
||||
image: '/images/figma/news-bg2.png',
|
||||
image: '/images/figma/news-bg2.webp',
|
||||
title: 'Нові атракціони у ДиноПарку',
|
||||
body: 'На території є чисті вбиральні, де можна також помити руку. Поруч розташовані велика паркувальна зона та укриття. Зручний громадський транспорт: метро, маршрутні таксі, що з\'єднують правий та лівий береги.',
|
||||
href: '/blog',
|
||||
},
|
||||
{
|
||||
image: '/images/figma/news-bg3.jpg',
|
||||
image: '/images/figma/news-bg3.webp',
|
||||
title: 'Програма на вихідні в Шуміленді',
|
||||
body: 'На території є чисті вбиральні, де можна також помити руку. Поруч розташовані велика паркувальна зона та укриття. Зручний громадський транспорт: метро, маршрутні таксі, що з\'єднують правий та лівий береги.',
|
||||
href: '/blog',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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 IMG_AVATAR_DEFAULT = '/images/figma/review-avatar-bg.webp'
|
||||
|
||||
function getMediaUrl(img: Media | string | null | undefined): string | null {
|
||||
if (!img) return null
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { useState } from 'react'
|
||||
import type { Media } from '@/types/globals'
|
||||
|
||||
const IMG_POSTER_DEFAULT = '/images/figma/video-preview.png'
|
||||
const IMG_POSTER_DEFAULT = '/images/figma/video-preview.webp'
|
||||
const IMG_PLAY = '/images/figma/btn-video-play.svg'
|
||||
|
||||
function getMediaUrl(img: Media | string | null | undefined): string | null {
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ function getMediaUrl(img: Media | string | null | undefined): string | null {
|
|||
}
|
||||
|
||||
const STATIC_GALLERY = [
|
||||
'/images/figma/why-parents-1.png',
|
||||
'/images/figma/why-parents-2.png',
|
||||
'/images/figma/why-parents-3.png',
|
||||
'/images/figma/why-parents-4.png',
|
||||
'/images/figma/gallery-1.png',
|
||||
'/images/figma/gallery-3.png',
|
||||
'/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 STATIC_ITEMS: HomePageWhyParentsItem[] = [
|
||||
|
|
@ -62,7 +62,7 @@ export function WhyParents({ items, sideGallery, title }: WhyParentsProps) {
|
|||
|
||||
const galleryUrls: string[] =
|
||||
sideGallery && sideGallery.length > 0
|
||||
? sideGallery.map((g) => getMediaUrl(g.image) ?? '/images/figma/gallery-1.png')
|
||||
? sideGallery.map((g) => getMediaUrl(g.image) ?? '/images/figma/gallery-1.webp')
|
||||
: STATIC_GALLERY
|
||||
|
||||
const doubled = [...galleryUrls, ...galleryUrls]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export function BtnDetails({ children, className = '', variant = 'dark', ...rest
|
|||
const content = (
|
||||
<>
|
||||
{children}
|
||||
<img src={arrow} alt="" aria-hidden="true" className="w-10 h-auto flex-none" />
|
||||
<img src={arrow} alt="" aria-hidden="true" width={40} height={40} className="w-10 h-auto flex-none" />
|
||||
</>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export function validateEnv(): void {
|
|||
const missing = REQUIRED_VARS.filter((key) => !process.env[key])
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`Missing required environment variables:\n${missing.map((k) => ` - ${k}`).join('\n')}\n\nCheck your .env file.`,
|
||||
`Missing required environment variables:\n${missing.map((k) => ` - ${k}`).join('\n')}\n\nCheck your .env file.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||