feat: manage skyline background media via payload

This commit is contained in:
syntaxbullet
2026-06-22 13:02:47 +02:00
parent 9cf23552cc
commit 34ef0e1f22
6 changed files with 32 additions and 11 deletions

View File

@@ -11,6 +11,14 @@ export const SiteSettings: GlobalConfig = {
group: 'Site Settings',
},
fields: [
{
name: 'skylineImage',
type: 'upload',
relationTo: 'media',
admin: {
description: `Current frontend fallback image: ${defaultSiteSettings.skylineImageUrl}`,
},
},
{
name: 'genericPageKicker',
type: 'text',

View File

@@ -77,7 +77,7 @@ export default async function Page({ params }: Args) {
const [header, footer, siteSettings, route, preistraeger, events, posts] = await Promise.all([
payload.findGlobal({ slug: 'header', depth: 1 }).catch(() => undefined),
payload.findGlobal({ slug: 'footer', depth: 1 }).catch(() => undefined),
payload.findGlobal({ slug: 'site-settings', depth: 0 }).catch(() => undefined),
payload.findGlobal({ slug: 'site-settings', depth: 1 }).catch(() => undefined),
findRouteDoc(payload, pathname, draft),
findList(payload, 'preistraeger', draft).catch(() => []),
findList(payload, 'events', draft).catch(() => []),

View File

@@ -5894,6 +5894,10 @@ export interface Footer {
*/
export interface SiteSetting {
id: number;
/**
* Current frontend fallback image: /munich-skyline.jpg
*/
skylineImage?: (number | null) | Media;
genericPageKicker: string;
genericPageTitle: string;
genericPageDescription: string;
@@ -6026,6 +6030,7 @@ export interface FooterSelect<T extends boolean = true> {
* via the `definition` "site-settings_select".
*/
export interface SiteSettingsSelect<T extends boolean = true> {
skylineImage?: T;
genericPageKicker?: T;
genericPageTitle?: T;
genericPageDescription?: T;

View File

@@ -1,13 +1,23 @@
import React, { useEffect, useRef } from 'react';
import { mediaUrl } from '@/spa/cmsMediaField';
import { useCmsRoute } from '@/spa/cmsRoute';
import { defaultSiteSettings } from '@/spa/siteSettings';
/**
* MunichSkylineBg
* Drop inside any light section (position: relative required on parent).
* Uses backgroundPositionY for parallax – no transform, no GPU compositing layer.
* No mixBlendMode to avoid Safari compositing bleed into sibling elements.
*/
const MunichSkylineBg: React.FC = () => {
const MunichSkylineBg: React.FC<{ height?: string | number; inset?: boolean; opacity?: number }> = ({
height = '52%',
inset = false,
opacity = 0.08,
}) => {
const ref = useRef<HTMLDivElement>(null);
const siteSettings = useCmsRoute()?.siteSettings || defaultSiteSettings;
const skylineImage = mediaUrl(siteSettings.skylineImage, siteSettings.skylineImageUrl);
useEffect(() => {
const el = ref.current;
@@ -32,17 +42,14 @@ const MunichSkylineBg: React.FC = () => {
aria-hidden="true"
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '52%',
...(inset ? { inset: 0 } : { bottom: 0, left: 0, right: 0, height }),
pointerEvents: 'none',
zIndex: 0,
backgroundImage: 'url(/munich-skyline.jpg)',
backgroundImage: `url(${skylineImage})`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom center',
backgroundSize: 'cover',
opacity: 0.08,
opacity,
}}
/>
);

View File

@@ -537,9 +537,7 @@ const Netzwerk: React.FC = () => {
{/* ── 3. JURY-INTRO (editorial 2-col split) ────────────────────────────── */}
<section style={{ display: isMobile ? 'block' : 'grid', gridTemplateColumns: isMobile ? undefined : '40% 60%', minHeight: isMobile ? 'auto' : 400, position: 'relative', overflow: 'hidden', isolation: 'isolate', background: isMobile ? NAVY : undefined }}>
{!isMobile && <MunichSkylineBg />}
{isMobile && (
<div aria-hidden style={{ position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 0, backgroundImage: 'url(/munich-skyline.jpg)', backgroundRepeat: 'no-repeat', backgroundPosition: 'bottom center', backgroundSize: 'cover', opacity: 0.07 }} />
)}
{isMobile && <MunichSkylineBg inset opacity={0.07} />}
{isMobile ? (
/* Mobile: one cohesive navy module – copy + compact stat strip */
<div style={{ padding: '48px 24px', position: 'relative', zIndex: 1 }}>

View File

@@ -2,12 +2,15 @@ export type SpaSiteSettingsData = {
genericPageKicker: string
genericPageTitle: string
genericPageDescription: string
skylineImage?: unknown
skylineImageUrl: string
}
export const defaultSiteSettings: SpaSiteSettingsData = {
genericPageKicker: 'CMS',
genericPageTitle: 'Seite',
genericPageDescription: 'Diese Route wird aus Payload CMS geladen. Ergänze Inhalte im entsprechenden Collection-Dokument.',
skylineImageUrl: '/munich-skyline.jpg',
}
export function normalizeSiteSettings(settings?: Partial<SpaSiteSettingsData> | null): SpaSiteSettingsData {