x
This commit is contained in:
1 parent
235cf6047d
commit
88feee666f
7 files changed
+1309
-2
No files matched your search
@@ -310,6 +310,268 @@ function UspBarSection({ payload, lang }: { payload: Record<string, unknown>, la
|
||||
)
|
||||
}
|
||||
|
||||
function AnnouncementBarMinimal({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const slides = Array.isArray(payload.slides) ? payload.slides : []
|
||||
const backgroundColor = typeof payload.backgroundColor === 'string' && payload.backgroundColor ? payload.backgroundColor : '#0F1713'
|
||||
const textColor = typeof payload.textColor === 'string' && payload.textColor ? payload.textColor : '#DCE6E0'
|
||||
const speed = typeof payload.speed === 'number' ? payload.speed : 4000
|
||||
|
||||
if (!slides.length) return null
|
||||
|
||||
return (
|
||||
<section
|
||||
className="announcement-bar-minimal absolute left-0 top-0 z-[60] w-full overflow-hidden px-4 py-2 text-center text-[11px] md:text-xs font-medium tracking-[0.2em] uppercase select-none transition-colors"
|
||||
style={{ backgroundColor, color: textColor }}
|
||||
>
|
||||
<Swiper
|
||||
modules={[Autoplay]}
|
||||
autoplay={{ delay: speed, disableOnInteraction: false }}
|
||||
loop={slides.length > 1}
|
||||
className="w-full"
|
||||
>
|
||||
{slides.map((slide, index) => (
|
||||
<SwiperSlide key={index} className="flex items-center justify-center">
|
||||
<span className="inline-block truncate max-w-full">
|
||||
{localizedFromRecord({ slide }, 'slide', resolvedLang)}
|
||||
</span>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function HeroMinimalSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const tagline = localizedFromRecord(payload, 'tagline', resolvedLang)
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const imageUrl = typeof payload.imageUrl === 'string' ? payload.imageUrl : '/images/hero.png'
|
||||
const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 20
|
||||
const align = typeof payload.contentAlignment === 'string' ? payload.contentAlignment : 'center'
|
||||
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? (payload.focalPoint as { x: string, y: string }) : undefined
|
||||
|
||||
const alignClass = align === 'left' ? 'text-left items-start' : align === 'right' ? 'text-right items-end ml-auto' : 'text-center items-center mx-auto'
|
||||
|
||||
return (
|
||||
<section className="relative w-full h-[75vh] min-h-[500px] md:h-[85vh] bg-surface-page overflow-hidden flex items-center justify-center">
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={title || 'Hero'}
|
||||
fill
|
||||
unoptimized
|
||||
className="object-cover"
|
||||
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black transition-opacity pointer-events-none" style={{ opacity: overlayOpacity / 100 }} />
|
||||
<div className="relative z-10 w-full max-w-5xl mx-auto px-6 py-12 flex flex-col justify-center">
|
||||
<div className={`flex flex-col gap-4 max-w-3xl ${alignClass}`}>
|
||||
{tagline && (
|
||||
<span className="text-xs md:text-sm font-medium uppercase tracking-[0.3em] text-[#EAEFEA] drop-shadow-md">
|
||||
{tagline}
|
||||
</span>
|
||||
)}
|
||||
{title && (
|
||||
<h1 className="font-serif text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-light text-white leading-tight tracking-wide drop-shadow-md">
|
||||
{renderAccentTitle(title, 'text-[#EAEFEA] font-medium')}
|
||||
</h1>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function HeroSplitMinimalSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const subtitle = localizedFromRecord(payload, 'subtitle', resolvedLang)
|
||||
const imageUrl = typeof payload.imageUrl === 'string' ? payload.imageUrl : '/images/oil_ozone.png'
|
||||
const imagePosition = payload.imagePosition === 'left' ? 'left' : 'right'
|
||||
const backgroundColor = typeof payload.backgroundColor === 'string' && payload.backgroundColor ? payload.backgroundColor : undefined
|
||||
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? (payload.focalPoint as { x: string, y: string }) : undefined
|
||||
|
||||
return (
|
||||
<section className="w-full py-16 md:py-24 bg-surface-page" style={{ backgroundColor }}>
|
||||
<div className="max-w-6xl mx-auto px-6 md:px-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10 md:gap-16 items-center">
|
||||
<div className={`flex flex-col justify-center gap-6 ${imagePosition === 'left' ? 'md:order-2' : 'md:order-1'}`}>
|
||||
{title && (
|
||||
<h2 className="font-serif text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-light text-text-main leading-snug tracking-wide">
|
||||
{renderAccentTitle(title, 'text-brand-primary font-normal')}
|
||||
</h2>
|
||||
)}
|
||||
{subtitle && (
|
||||
<p className="text-sm md:text-base text-muted-text font-light leading-relaxed max-w-md">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={`relative aspect-[3/4] w-full max-w-md mx-auto overflow-hidden rounded-sm bg-surface-card ${imagePosition === 'left' ? 'md:order-1' : 'md:order-2'}`}>
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={title || 'Editorial'}
|
||||
fill
|
||||
unoptimized
|
||||
className="object-cover"
|
||||
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function HeroVideoMinimalSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const tagline = localizedFromRecord(payload, 'tagline', resolvedLang)
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const videoUrl = typeof payload.videoUrl === 'string' ? payload.videoUrl : ''
|
||||
const posterImageUrl = typeof payload.posterImageUrl === 'string' ? payload.posterImageUrl : undefined
|
||||
const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 25
|
||||
const height = typeof payload.height === 'string' ? payload.height : 'large'
|
||||
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? (payload.focalPoint as { x: string, y: string }) : undefined
|
||||
|
||||
const getHeightClass = (h: string) => {
|
||||
switch (h) {
|
||||
case 'full':
|
||||
return 'h-screen'
|
||||
case 'medium':
|
||||
return 'h-[55vh] min-h-[400px]'
|
||||
case 'large':
|
||||
default:
|
||||
return 'h-[80vh] min-h-[500px]'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={`relative w-full ${getHeightClass(height)} overflow-hidden flex items-center justify-center bg-[#0B0F17] text-white`}>
|
||||
{videoUrl ? (
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
poster={posterImageUrl}
|
||||
className="absolute inset-0 w-full h-full object-cover z-0"
|
||||
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||
>
|
||||
<source src={videoUrl} type="video/mp4" />
|
||||
<source src={videoUrl} type="video/webm" />
|
||||
</video>
|
||||
) : posterImageUrl ? (
|
||||
<Image
|
||||
src={posterImageUrl}
|
||||
alt="Hero Poster"
|
||||
fill
|
||||
unoptimized
|
||||
className="object-cover z-0"
|
||||
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="absolute inset-0 z-10 bg-black transition-opacity pointer-events-none" style={{ opacity: overlayOpacity / 100 }} />
|
||||
|
||||
<div className="relative z-20 mx-auto px-6 py-12 w-full max-w-4xl text-center flex flex-col items-center justify-center gap-4">
|
||||
{tagline && (
|
||||
<p className="text-xs md:text-sm font-medium uppercase tracking-[0.35em] text-white/80 drop-shadow-md">
|
||||
{tagline}
|
||||
</p>
|
||||
)}
|
||||
{title && (
|
||||
<h1 className="font-serif text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-light leading-tight tracking-wide drop-shadow-md text-white">
|
||||
{renderAccentTitle(title)}
|
||||
</h1>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function BannerMinimalSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const subtitle = localizedFromRecord(payload, 'subtitle', resolvedLang)
|
||||
const imageUrl = typeof payload.imageUrl === 'string' && payload.imageUrl ? payload.imageUrl : undefined
|
||||
const linkUrl = typeof payload.linkUrl === 'string' ? payload.linkUrl : ''
|
||||
const backgroundColor = typeof payload.backgroundColor === 'string' && payload.backgroundColor ? payload.backgroundColor : undefined
|
||||
const textColor = typeof payload.textColor === 'string' && payload.textColor ? payload.textColor : undefined
|
||||
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? (payload.focalPoint as { x: string, y: string }) : undefined
|
||||
|
||||
const inner = (
|
||||
<div
|
||||
className={`relative w-full py-12 md:py-16 px-6 text-center overflow-hidden flex flex-col items-center justify-center gap-2 ${!imageUrl && !backgroundColor ? 'bg-surface-card' : ''}`}
|
||||
style={{ backgroundColor, color: textColor }}
|
||||
>
|
||||
{imageUrl && (
|
||||
<>
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={title || 'Banner'}
|
||||
fill
|
||||
unoptimized
|
||||
className="object-cover z-0 opacity-40"
|
||||
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/20 z-0" />
|
||||
</>
|
||||
)}
|
||||
<div className="relative z-10 max-w-3xl mx-auto space-y-2">
|
||||
{title && (
|
||||
<h3 className="font-serif text-xl sm:text-2xl md:text-3xl font-light tracking-wide text-inherit">
|
||||
{renderAccentTitle(title)}
|
||||
</h3>
|
||||
)}
|
||||
{subtitle && (
|
||||
<p className="text-xs sm:text-sm opacity-80 font-light tracking-wider max-w-xl mx-auto">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<section className="w-full bg-surface-page">
|
||||
{linkUrl ? <Link href={linkUrl} className="block group">{inner}</Link> : inner}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function UspMinimalSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const items = Array.isArray(payload.items) ? payload.items : []
|
||||
const separator = typeof payload.separator === 'string' ? payload.separator : 'dot'
|
||||
const backgroundColor = typeof payload.backgroundColor === 'string' && payload.backgroundColor ? payload.backgroundColor : undefined
|
||||
const textColor = typeof payload.textColor === 'string' && payload.textColor ? payload.textColor : undefined
|
||||
|
||||
if (items.length === 0) return null
|
||||
|
||||
const sepSymbol = separator === 'slash' ? '/' : separator === 'line' ? '|' : '·'
|
||||
|
||||
return (
|
||||
<section className="w-full py-6 border-t border-b border-line-primary/10 bg-surface-page" style={{ backgroundColor, color: textColor }}>
|
||||
<div className="max-w-6xl mx-auto px-6">
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-xs sm:text-sm font-medium uppercase tracking-widest text-inherit/90">
|
||||
{items.map((item: any, idx: number) => (
|
||||
<span key={idx} className="inline-flex items-center gap-x-4">
|
||||
{idx > 0 && <span className="opacity-40 font-light select-none">{sepSymbol}</span>}
|
||||
<span>{localizedFromRecord({ item }, 'item', resolvedLang)}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function HeroV2Section({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
@@ -1675,6 +1937,34 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
)
|
||||
case 'HERO_VIDEO':
|
||||
return <HeroVideoSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'HERO_MINIMAL':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
<HeroMinimalSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'HERO_SPLIT_MINIMAL':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
<HeroSplitMinimalSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'HERO_VIDEO_MINIMAL':
|
||||
return <HeroVideoMinimalSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'BANNER_MINIMAL':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
<BannerMinimalSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'ANNOUNCEMENT_BAR_MINIMAL':
|
||||
return <AnnouncementBarMinimal key={section.id} payload={payload} lang={lang} />
|
||||
case 'USP_MINIMAL':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
<UspMinimalSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'USP_BAR':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PageSection } from '@homaroma/types'
|
||||
|
||||
const announcementTypes = new Set(['ANNOUNCEMENT_BAR'])
|
||||
const announcementTypes = new Set(['ANNOUNCEMENT_BAR', 'ANNOUNCEMENT_BAR_MINIMAL'])
|
||||
const heroTypes = new Set(['HERO', 'BANNER'])
|
||||
|
||||
function sectionType(section: PageSection) {
|
||||
|
||||
Reference in new issue
Block a user