fix(ui): remove black bg on hero images, improve dino quality, protect seed from overwriting admin data
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

- DyvoLisHero: add mix-blend-mode:screen to hero img so JPEG black bg
  becomes transparent and the orange ellipse shows through
- DinoPageContent: add contrast/saturation filter to hero-dino for
  richer colour rendering
- seed.ts: globals (home-page, header, footer, site-settings) now only
  updated when FORCE_SEED=true; tariffs seeded only when table is empty
  (never deleted), preventing accidental data loss on redeploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-06-03 17:08:43 +01:00
parent 04b28e7620
commit 5f23806a82
3 changed files with 212 additions and 189 deletions

View file

@ -298,6 +298,7 @@ const DYNO_CSS = `
.dino-page .hero-dino{
position:absolute;top:12px;right:-60px;height:1096px;width:auto;max-width:none;
z-index:0;pointer-events:none;
filter:contrast(1.08) saturate(1.12);
}
.dino-page .hero-text{position:relative;z-index:2;max-width:608px;padding:60px 0 40px;}
.dino-page .hero-text h1{

View file

@ -142,7 +142,7 @@ export function DyvoLisHero({
}}
/>
</div>
{/* Cat */}
{/* Cat — screen blend removes black JPEG background, shows ellipse behind */}
<img
src={heroImg}
alt="Топіарна фігура ДивоЛісу"
@ -152,6 +152,7 @@ export function DyvoLisHero({
top: '-20.7%',
width: '126.9%',
height: 'auto',
mixBlendMode: 'screen',
}}
loading="eager"
/>

View file

@ -5,6 +5,15 @@ import config from '../payload.config.js'
async function seed(): Promise<void> {
const payload = await getPayload({ config })
// FORCE_SEED=true is required to overwrite globals that may have been edited in the admin UI.
// Without it, only empty collections (users, blog posts, tariffs, locations) are seeded.
const force = process.env['FORCE_SEED'] === 'true'
if (!force) {
console.log('Running in safe mode — globals will NOT be overwritten.')
console.log('Set FORCE_SEED=true to re-seed all globals (will overwrite admin changes).')
}
// Admin user
const { totalDocs } = await payload.find({
collection: 'users',
@ -28,115 +37,123 @@ async function seed(): Promise<void> {
console.log('Users already exist, skipping user seed.')
}
if (!force) {
console.log('Skipping globals seed — run with FORCE_SEED=true to overwrite.')
}
// Home page global with real content
await payload.updateGlobal({
slug: 'home-page',
data: {
hero: {
title: 'Започаткуйте традицію:',
subtitle: 'щороку фотографуйтесь біля улюбленого динозавра',
if (force) {
await payload.updateGlobal({
slug: 'home-page',
data: {
hero: {
title: 'Започаткуйте традицію:',
subtitle: 'щороку фотографуйтесь біля улюбленого динозавра',
ctaLabel: 'Купити квиток',
ctaHref: '/kvytky',
},
sectionTitles: {
locations: 'ЛАСКАВО ПРОСИМО ДО ШУМІЛЕНДУ',
whyParents: 'ЧОМУ БАТЬКИ ОБИРАЮТЬ ШУМІЛЕНД',
birthday: 'ДЕНЬ НАРОДЖЕННЯ В ШУМІЛЕНДІ',
gallery: 'ФОТОГАЛЕРЕЯ',
reviews: 'ВІДГУКИ',
news: 'НОВИНИ',
},
locations: [
{
name: 'ДиноПарк',
shortDesc: 'Прогуляйтесь серед реалістичних динозаврів у повний зріст',
href: '/lokatsii',
},
{
name: 'Диво Ліс',
shortDesc: 'Чарівний ліс з інтерактивними атракціонами та мотузковими парками',
href: '/lokatsii',
},
{
name: 'Дзеркальний Лабіринт',
shortDesc: 'Захоплюючий лабіринт з дзеркалами та оптичними ілюзіями',
href: '/lokatsii',
},
],
features: [
{
icon: '🦕',
title: 'Безпека',
description: 'Атракціони сертифіковані, майданчик під постійним наглядом',
},
{ icon: '🌲', title: 'Природа', description: 'Парк розташований серед живої природи' },
{
icon: '🎉',
title: 'Свята',
description: 'Дні народження, корпоративи та шкільні екскурсії',
},
{
icon: '🎟️',
title: 'Квитки онлайн',
description: 'Купуйте квитки онлайн без черги на касі',
},
{
icon: '🍕',
title: 'Кафе та їжа',
description: 'Власне кафе з дитячим меню та легкими закусками',
},
{ icon: '🅿️', title: 'Парковка', description: 'Безкоштовна парковка для відвідувачів' },
],
news: { title: 'Новини', limit: 3 },
} as never,
overrideAccess: true,
})
console.log('Seeded home-page global')
}
if (force) {
// Header global
await payload.updateGlobal({
slug: 'header',
data: {
navLinks: [
{ label: 'Головна', href: '/' },
{ label: 'Локації', href: '/lokatsii' },
{ label: 'Блог', href: '/blog' },
{ label: 'Дні народження', href: '/dni-narodzhennia' },
{ label: 'Групові відвідування', href: '/grupovi-vidviduvannia' },
],
ctaLabel: 'Купити квиток',
ctaHref: '/kvytky',
},
sectionTitles: {
locations: 'ЛАСКАВО ПРОСИМО ДО ШУМІЛЕНДУ',
whyParents: 'ЧОМУ БАТЬКИ ОБИРАЮТЬ ШУМІЛЕНД',
birthday: 'ДЕНЬ НАРОДЖЕННЯ В ШУМІЛЕНДІ',
gallery: 'ФОТОГАЛЕРЕЯ',
reviews: 'ВІДГУКИ',
news: 'НОВИНИ',
},
locations: [
{
name: 'ДиноПарк',
shortDesc: 'Прогуляйтесь серед реалістичних динозаврів у повний зріст',
href: '/lokatsii',
},
{
name: 'Диво Ліс',
shortDesc: 'Чарівний ліс з інтерактивними атракціонами та мотузковими парками',
href: '/lokatsii',
},
{
name: 'Дзеркальний Лабіринт',
shortDesc: 'Захоплюючий лабіринт з дзеркалами та оптичними ілюзіями',
href: '/lokatsii',
},
],
features: [
{
icon: '🦕',
title: 'Безпека',
description: 'Атракціони сертифіковані, майданчик під постійним наглядом',
},
{ icon: '🌲', title: 'Природа', description: 'Парк розташований серед живої природи' },
{
icon: '🎉',
title: 'Свята',
description: 'Дні народження, корпоративи та шкільні екскурсії',
},
{
icon: '🎟️',
title: 'Квитки онлайн',
description: 'Купуйте квитки онлайн без черги на касі',
},
{
icon: '🍕',
title: 'Кафе та їжа',
description: 'Власне кафе з дитячим меню та легкими закусками',
},
{ icon: '🅿️', title: 'Парковка', description: 'Безкоштовна парковка для відвідувачів' },
],
news: { title: 'Новини', limit: 3 },
} as never,
overrideAccess: true,
})
console.log('Seeded home-page global')
} as never,
overrideAccess: true,
})
console.log('Seeded header global')
// Header global
await payload.updateGlobal({
slug: 'header',
data: {
navLinks: [
{ label: 'Головна', href: '/' },
{ label: 'Локації', href: '/lokatsii' },
{ label: 'Блог', href: '/blog' },
{ label: 'Дні народження', href: '/dni-narodzhennia' },
{ label: 'Групові відвідування', href: '/grupovi-vidviduvannia' },
],
ctaLabel: 'Купити квиток',
ctaHref: '/kvytky',
} as never,
overrideAccess: true,
})
console.log('Seeded header global')
// Footer global
await payload.updateGlobal({
slug: 'footer',
data: { copyrightText: `© Шуміленд ${new Date().getFullYear()}` } as never,
overrideAccess: true,
})
console.log('Seeded footer global')
// Footer global
await payload.updateGlobal({
slug: 'footer',
data: { copyrightText: `© Шуміленд ${new Date().getFullYear()}` } as never,
overrideAccess: true,
})
console.log('Seeded footer global')
// Site settings
await payload.updateGlobal({
slug: 'site-settings',
data: {
siteName: 'Шуміленд',
siteURL: process.env['NEXT_PUBLIC_SITE_URL'] ?? 'https://shumiland.ua',
} as never,
overrideAccess: true,
})
console.log('Seeded site-settings global')
// Site settings
await payload.updateGlobal({
slug: 'site-settings',
data: {
siteName: 'Шуміленд',
siteURL: process.env['NEXT_PUBLIC_SITE_URL'] ?? 'https://shumiland.ua',
} as never,
overrideAccess: true,
})
console.log('Seeded site-settings global')
// Remaining globals — initialize empty
for (const slug of ['checkout-page', 'thank-you-page'] as const) {
try {
await payload.updateGlobal({ slug, data: {} as never, overrideAccess: true })
console.log(`Initialized global: ${slug}`)
} catch (err) {
console.warn(`Could not initialize global ${slug}:`, err)
// Remaining globals — initialize empty
for (const slug of ['checkout-page', 'thank-you-page'] as const) {
try {
await payload.updateGlobal({ slug, data: {} as never, overrideAccess: true })
console.log(`Initialized global: ${slug}`)
} catch (err) {
console.warn(`Could not initialize global ${slug}:`, err)
}
}
}
@ -180,94 +197,98 @@ async function seed(): Promise<void> {
console.log('Blog posts already exist, skipping.')
}
// Tariffs — update to match Figma designs
// Tariffs — seed only if none exist (never delete admin-managed tariffs)
{
// Delete old tariffs and re-seed with correct data
const existing = await payload.find({ collection: 'tariffs', limit: 100, overrideAccess: true })
for (const t of existing.docs) {
await payload.delete({ collection: 'tariffs', id: t.id, overrideAccess: true })
}
const { totalDocs: tariffCount } = await payload.find({
collection: 'tariffs',
limit: 1,
overrideAccess: true,
})
const tariffs = [
// Individual dino-park tickets (shown on both Dino and DyvoLis pages)
{
ezy_id: 1001,
last_synced_name: 'Вхід до Динопарку',
display_name: 'Вхід до Динопарку',
last_synced_price: 300,
category_tag: 'dyno',
sort: 1,
visible: true,
},
{
ezy_id: 1002,
last_synced_name: 'Звичайна екскурсія',
display_name: 'Звичайна екскурсія',
last_synced_price: 150,
category_tag: 'dyno',
sort: 2,
visible: true,
},
{
ezy_id: 1003,
last_synced_name: 'Палеонтологічна екскурсія',
display_name: 'Палеонтологічна екскурсія',
last_synced_price: 300,
category_tag: 'dyno',
sort: 3,
visible: true,
},
{
ezy_id: 1004,
last_synced_name: 'ДиноРодо',
display_name: 'ДиноРодо',
last_synced_price: 50,
category_tag: 'dyno',
sort: 4,
visible: true,
},
// Combo tickets
{
ezy_id: 3001,
last_synced_name: 'Комбо на 1 людину',
display_name: 'Комбо на 1 людину',
last_synced_price: 600,
category_tag: 'combo',
sort: 1,
visible: true,
},
{
ezy_id: 3002,
last_synced_name: 'Комбо на 3 людини',
display_name: 'Комбо на 3 людини',
last_synced_price: 1500,
category_tag: 'combo',
sort: 2,
visible: true,
},
{
ezy_id: 3003,
last_synced_name: 'Комбо на 4 людини',
display_name: 'Комбо на 4 людини',
last_synced_price: 1800,
category_tag: 'combo',
sort: 3,
visible: true,
},
{
ezy_id: 3004,
last_synced_name: 'Комбо на 5 людин',
display_name: 'Комбо на 5 людин',
last_synced_price: 2000,
category_tag: 'combo',
sort: 4,
visible: true,
},
]
for (const t of tariffs) {
await payload.create({ collection: 'tariffs', data: t as never, overrideAccess: true })
if (tariffCount > 0) {
console.log('Tariffs already exist, skipping.')
} else {
const tariffs = [
// Individual dino-park tickets (shown on both Dino and DyvoLis pages)
{
ezy_id: 1001,
last_synced_name: 'Вхід до Динопарку',
display_name: 'Вхід до Динопарку',
last_synced_price: 300,
category_tag: 'dyno',
sort: 1,
visible: true,
},
{
ezy_id: 1002,
last_synced_name: 'Звичайна екскурсія',
display_name: 'Звичайна екскурсія',
last_synced_price: 150,
category_tag: 'dyno',
sort: 2,
visible: true,
},
{
ezy_id: 1003,
last_synced_name: 'Палеонтологічна екскурсія',
display_name: 'Палеонтологічна екскурсія',
last_synced_price: 300,
category_tag: 'dyno',
sort: 3,
visible: true,
},
{
ezy_id: 1004,
last_synced_name: 'ДиноРодо',
display_name: 'ДиноРодо',
last_synced_price: 50,
category_tag: 'dyno',
sort: 4,
visible: true,
},
// Combo tickets
{
ezy_id: 3001,
last_synced_name: 'Комбо на 1 людину',
display_name: 'Комбо на 1 людину',
last_synced_price: 600,
category_tag: 'combo',
sort: 1,
visible: true,
},
{
ezy_id: 3002,
last_synced_name: 'Комбо на 3 людини',
display_name: 'Комбо на 3 людини',
last_synced_price: 1500,
category_tag: 'combo',
sort: 2,
visible: true,
},
{
ezy_id: 3003,
last_synced_name: 'Комбо на 4 людини',
display_name: 'Комбо на 4 людини',
last_synced_price: 1800,
category_tag: 'combo',
sort: 3,
visible: true,
},
{
ezy_id: 3004,
last_synced_name: 'Комбо на 5 людин',
display_name: 'Комбо на 5 людин',
last_synced_price: 2000,
category_tag: 'combo',
sort: 4,
visible: true,
},
]
for (const t of tariffs) {
await payload.create({ collection: 'tariffs', data: t as never, overrideAccess: true })
}
console.log('Seeded tariffs')
}
console.log('Seeded tariffs (updated to match Figma)')
}
// Locations