This commit is contained in:
Your Name committed 2026-10-04 18:59:27 +07:00
1 parent b641acbeb7
commit e349ef5b84
14 files changed
+720 -558

No files matched your search

@@ -97,6 +97,136 @@ function AnnouncementBar({ payload, lang }: { payload: Record<string, unknown>,
)
}
const getMobilePositionClasses = (pos: string) => {
switch (pos) {
case 'top-left':
return {
wrapper: 'justify-start items-start text-left pt-20 pb-12 pl-6 pr-6',
content: 'items-start text-left',
actions: 'items-start justify-start'
}
case 'top-center':
return {
wrapper: 'justify-start items-center text-center pt-20 pb-12 px-6',
content: 'items-center text-center',
actions: 'items-center justify-center'
}
case 'top-right':
return {
wrapper: 'justify-start items-end text-right pt-20 pb-12 pr-6 pl-6',
content: 'items-end text-right',
actions: 'items-end justify-end'
}
case 'center-left':
return {
wrapper: 'justify-center items-start text-left py-16 pl-6 pr-6',
content: 'items-start text-left',
actions: 'items-start justify-start'
}
case 'center-right':
return {
wrapper: 'justify-center items-end text-right py-16 pr-6 pl-6',
content: 'items-end text-right',
actions: 'items-end justify-end'
}
case 'bottom-left':
return {
wrapper: 'justify-end items-start text-left pt-12 pb-16 pl-6 pr-6',
content: 'items-start text-left',
actions: 'items-start justify-start'
}
case 'bottom-center':
return {
wrapper: 'justify-end items-center text-center pt-12 pb-16 px-6',
content: 'items-center text-center',
actions: 'items-center justify-center'
}
case 'bottom-right':
return {
wrapper: 'justify-end items-end text-right pt-12 pb-16 pr-6 pl-6',
content: 'items-end text-right',
actions: 'items-end justify-end'
}
case 'center':
default:
return {
wrapper: 'justify-center items-center text-center py-16 px-6',
content: 'items-center text-center',
actions: 'items-center justify-center'
}
}
}
const getDesktopPositionClasses = (pos: string) => {
switch (pos) {
case 'top-left':
return {
wrapper: 'md:justify-start md:items-start md:text-left md:pt-32 md:pb-12 md:pl-20 md:pr-8',
content: 'md:items-start md:text-left',
actions: 'md:items-start md:justify-start'
}
case 'top-center':
return {
wrapper: 'md:justify-start md:items-center md:text-center md:pt-32 md:pb-12 md:px-8',
content: 'md:items-center md:text-center',
actions: 'md:items-center md:justify-center'
}
case 'top-right':
return {
wrapper: 'md:justify-start md:items-end md:text-right md:pt-32 md:pb-12 md:pr-20 md:pl-8',
content: 'md:items-end md:text-right',
actions: 'md:items-end md:justify-end'
}
case 'center-left':
return {
wrapper: 'md:justify-center md:items-start md:text-left md:py-16 md:pl-20 md:pr-8',
content: 'md:items-start md:text-left',
actions: 'md:items-start md:justify-start'
}
case 'center-right':
return {
wrapper: 'md:justify-center md:items-end md:text-right md:py-16 md:pr-20 md:pl-8',
content: 'md:items-end md:text-right',
actions: 'md:items-end md:justify-end'
}
case 'bottom-left':
return {
wrapper: 'md:justify-end md:items-start md:text-left md:pt-12 md:pb-24 md:pl-20 md:pr-8',
content: 'md:items-start md:text-left',
actions: 'md:items-start md:justify-start'
}
case 'bottom-center':
return {
wrapper: 'md:justify-end md:items-center md:text-center md:pt-12 md:pb-24 md:px-8',
content: 'md:items-center md:text-center',
actions: 'md:items-center md:justify-center'
}
case 'bottom-right':
return {
wrapper: 'md:justify-end md:items-end md:text-right md:pt-12 md:pb-24 md:pr-20 md:pl-8',
content: 'md:items-end md:text-right',
actions: 'md:items-end md:justify-end'
}
case 'center':
default:
return {
wrapper: 'md:justify-center md:items-center md:text-center md:py-16 md:px-8',
content: 'md:items-center md:text-center',
actions: 'md:items-center md:justify-center'
}
}
}
function getHeroPositionClasses(mobilePos: string, desktopPos: string) {
const mClasses = getMobilePositionClasses(mobilePos)
const dClasses = getDesktopPositionClasses(desktopPos)
return {
wrapperClasses: `${mClasses.wrapper} ${dClasses.wrapper}`,
contentClasses: `${mClasses.content} ${dClasses.content}`,
actionsClasses: `${mClasses.actions} ${dClasses.actions}`,
}
}
function HeroSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
const { t } = useTranslation()
const i18nLang = useLang()
@@ -125,132 +255,7 @@ function HeroSection({ payload, lang }: { payload: Record<string, unknown>, lang
? rawButtonText.trim()
: 'Explore Collection'
const getMobilePositionClasses = (pos: string) => {
switch (pos) {
case 'top-left':
return {
wrapper: 'justify-start items-start text-left pt-20 pb-12 pl-6 pr-6',
content: 'items-start text-left',
actions: 'items-start justify-start'
}
case 'top-center':
return {
wrapper: 'justify-start items-center text-center pt-20 pb-12 px-6',
content: 'items-center text-center',
actions: 'items-center justify-center'
}
case 'top-right':
return {
wrapper: 'justify-start items-end text-right pt-20 pb-12 pr-6 pl-6',
content: 'items-end text-right',
actions: 'items-end justify-end'
}
case 'center-left':
return {
wrapper: 'justify-center items-start text-left py-16 pl-6 pr-6',
content: 'items-start text-left',
actions: 'items-start justify-start'
}
case 'center-right':
return {
wrapper: 'justify-center items-end text-right py-16 pr-6 pl-6',
content: 'items-end text-right',
actions: 'items-end justify-end'
}
case 'bottom-left':
return {
wrapper: 'justify-end items-start text-left pt-12 pb-16 pl-6 pr-6',
content: 'items-start text-left',
actions: 'items-start justify-start'
}
case 'bottom-center':
return {
wrapper: 'justify-end items-center text-center pt-12 pb-16 px-6',
content: 'items-center text-center',
actions: 'items-center justify-center'
}
case 'bottom-right':
return {
wrapper: 'justify-end items-end text-right pt-12 pb-16 pr-6 pl-6',
content: 'items-end text-right',
actions: 'items-end justify-end'
}
case 'center':
default:
return {
wrapper: 'justify-center items-center text-center py-16 px-6',
content: 'items-center text-center',
actions: 'items-center justify-center'
}
}
}
const getDesktopPositionClasses = (pos: string) => {
switch (pos) {
case 'top-left':
return {
wrapper: 'md:justify-start md:items-start md:text-left md:pt-32 md:pb-12 md:pl-20 md:pr-8',
content: 'md:items-start md:text-left',
actions: 'md:items-start md:justify-start'
}
case 'top-center':
return {
wrapper: 'md:justify-start md:items-center md:text-center md:pt-32 md:pb-12 md:px-8',
content: 'md:items-center md:text-center',
actions: 'md:items-center md:justify-center'
}
case 'top-right':
return {
wrapper: 'md:justify-start md:items-end md:text-right md:pt-32 md:pb-12 md:pr-20 md:pl-8',
content: 'md:items-end md:text-right',
actions: 'md:items-end md:justify-end'
}
case 'center-left':
return {
wrapper: 'md:justify-center md:items-start md:text-left md:py-16 md:pl-20 md:pr-8',
content: 'md:items-start md:text-left',
actions: 'md:items-start md:justify-start'
}
case 'center-right':
return {
wrapper: 'md:justify-center md:items-end md:text-right md:py-16 md:pr-20 md:pl-8',
content: 'md:items-end md:text-right',
actions: 'md:items-end md:justify-end'
}
case 'bottom-left':
return {
wrapper: 'md:justify-end md:items-start md:text-left md:pt-12 md:pb-24 md:pl-20 md:pr-8',
content: 'md:items-start md:text-left',
actions: 'md:items-start md:justify-start'
}
case 'bottom-center':
return {
wrapper: 'md:justify-end md:items-center md:text-center md:pt-12 md:pb-24 md:px-8',
content: 'md:items-center md:text-center',
actions: 'md:items-center md:justify-center'
}
case 'bottom-right':
return {
wrapper: 'md:justify-end md:items-end md:text-right md:pt-12 md:pb-24 md:pr-20 md:pl-8',
content: 'md:items-end md:text-right',
actions: 'md:items-end md:justify-end'
}
case 'center':
default:
return {
wrapper: 'md:justify-center md:items-center md:text-center md:py-16 md:px-8',
content: 'md:items-center md:text-center',
actions: 'md:items-center md:justify-center'
}
}
}
const mClasses = getMobilePositionClasses(mobilePosition)
const dClasses = getDesktopPositionClasses(desktopPosition)
const wrapperClasses = `${mClasses.wrapper} ${dClasses.wrapper}`
const contentClasses = `${mClasses.content} ${dClasses.content}`
const actionsClasses = `${mClasses.actions} ${dClasses.actions}`
const { wrapperClasses, contentClasses, actionsClasses } = getHeroPositionClasses(mobilePosition, desktopPosition)
const overlayStyle = typeof payload.overlayStyle === 'string' ? payload.overlayStyle : 'gradient'
const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 45
@@ -313,7 +318,7 @@ function HeroSection({ payload, lang }: { payload: Record<string, unknown>, lang
</h1>
)}
{description && (
<p className="ui-hero-copy !text-white/90 drop-shadow-md !pb-2">
<p className={`ui-hero-copy !text-white/90 drop-shadow-md !pb-2 ${contentClasses.includes('text-left') ? '!mx-0' : contentClasses.includes('text-right') ? '!ml-auto !mr-0' : '!mx-auto'}`}>
{description}
</p>
)}
@@ -430,19 +435,26 @@ function HeroCarouselSection({ payload, lang }: { payload: Record<string, unknow
</div>
{/* Optional Text Overlay */}
{(title || subtitle || buttonText) && (
<div className="absolute inset-0 z-10 bg-gradient-to-t from-black/70 via-black/20 to-transparent flex flex-col justify-end p-6 md:p-10">
<div className="max-w-xl space-y-1.5 md:space-y-2">
{subtitle && <p className="text-[10px] md:text-xs uppercase font-bold tracking-widest text-brand-accent drop-shadow-md">{subtitle}</p>}
{title && <h3 className="text-lg md:text-3xl font-bold text-white drop-shadow-md leading-tight">{renderAccentTitle(title)}</h3>}
{buttonText && (
<span className="inline-block mt-2 px-4 py-1.5 bg-brand-primary text-white text-xs font-bold rounded-full shadow-lg hover:bg-brand-primary-hover transition-all">
{buttonText}
</span>
)}
{(title || subtitle || buttonText) && (() => {
const slideDesktopPos = typeof slide.buttonPosition === 'string' ? slide.buttonPosition : 'bottom-left'
const slideMobilePos = typeof slide.mobileButtonPosition === 'string' ? slide.mobileButtonPosition : slideDesktopPos
const { wrapperClasses: sWrap, contentClasses: sContent, actionsClasses: sActions } = getHeroPositionClasses(slideMobilePos, slideDesktopPos)
return (
<div className={`absolute inset-0 z-10 bg-gradient-to-t from-black/70 via-black/20 to-transparent flex flex-col p-6 md:p-10 ${sWrap}`}>
<div className={`max-w-xl space-y-1.5 md:space-y-2 flex flex-col ${sContent}`}>
{subtitle && <p className="text-[10px] md:text-xs uppercase font-bold tracking-widest text-brand-accent drop-shadow-md">{subtitle}</p>}
{title && <h3 className="text-lg md:text-3xl font-bold text-white drop-shadow-md leading-tight">{renderAccentTitle(title)}</h3>}
{buttonText && (
<div className={`flex ${sActions}`}>
<span className="inline-block mt-2 px-4 py-1.5 bg-brand-primary text-white text-xs font-bold rounded-full shadow-lg hover:bg-brand-primary-hover transition-all">
{buttonText}
</span>
</div>
)}
</div>
</div>
</div>
)}
)
})()}
</div>
)
@@ -491,8 +503,12 @@ function HeroVideoSection({ payload, lang }: { payload: Record<string, unknown>,
}
}
const desktopPosition = typeof payload.buttonPosition === 'string' ? payload.buttonPosition : 'center'
const mobilePosition = typeof payload.mobileButtonPosition === 'string' ? payload.mobileButtonPosition : desktopPosition
const { wrapperClasses, contentClasses, actionsClasses } = getHeroPositionClasses(mobilePosition, desktopPosition)
return (
<section className={`relative w-full ${getHeightClass(height)} overflow-hidden flex items-center justify-center bg-[#0B0F17] text-white`}>
<section className={`relative w-full ${getHeightClass(height)} overflow-hidden flex bg-[#0B0F17] text-white`}>
{videoUrl ? (
<video
autoPlay={payload.autoplay !== false}
@@ -529,42 +545,44 @@ function HeroVideoSection({ payload, lang }: { payload: Record<string, unknown>,
<div className="absolute inset-0 z-10 bg-black transition-opacity" style={{ opacity: overlayOpacity / 100 }}></div>
<div className="relative z-20 mx-auto px-6 py-16 w-full max-w-5xl text-center space-y-6 flex flex-col items-center justify-center">
{tagline && (
<p className="text-xs md:text-sm font-semibold uppercase tracking-[0.35em] text-brand-accent drop-shadow-md">
{tagline}
</p>
)}
{title && (
<h1 className="font-serif text-4xl md:text-6xl lg:text-7xl font-light leading-tight tracking-wide drop-shadow-lg text-white">
{renderAccentTitle(title)}
</h1>
)}
{description && (
<p className="max-w-2xl text-sm md:text-lg font-light leading-relaxed opacity-90 drop-shadow">
{description}
</p>
)}
{(buttonText || secondaryButtonText) && (
<div className="flex flex-wrap items-center justify-center gap-4 pt-4">
{buttonText && buttonLink && (
<Link
href={buttonLink}
className="px-8 py-3.5 bg-white text-neutral-900 font-medium text-xs uppercase tracking-widest hover:bg-brand-primary hover:text-white transition-all shadow-lg rounded-sm"
>
{buttonText}
</Link>
)}
{secondaryButtonText && secondaryButtonLink && (
<Link
href={secondaryButtonLink}
className="px-8 py-3.5 bg-transparent border border-white/40 text-white font-medium text-xs uppercase tracking-widest hover:bg-white/10 hover:border-white transition-all rounded-sm backdrop-blur-sm"
>
{secondaryButtonText}
</Link>
)}
</div>
)}
<div className={`relative z-20 mx-auto px-6 py-16 w-full max-w-5xl h-full flex flex-col ${wrapperClasses}`}>
<div className={`flex flex-col space-y-6 max-w-3xl ${contentClasses}`}>
{tagline && (
<p className="text-xs md:text-sm font-semibold uppercase tracking-[0.35em] text-brand-accent drop-shadow-md">
{tagline}
</p>
)}
{title && (
<h1 className="font-serif text-4xl md:text-6xl lg:text-7xl font-light leading-tight tracking-wide drop-shadow-lg text-white">
{renderAccentTitle(title)}
</h1>
)}
{description && (
<p className="max-w-2xl text-sm md:text-lg font-light leading-relaxed opacity-90 drop-shadow">
{description}
</p>
)}
{(buttonText || secondaryButtonText) && (
<div className={`flex flex-wrap gap-4 pt-4 ${actionsClasses}`}>
{buttonText && buttonLink && (
<Link
href={buttonLink}
className="px-8 py-3.5 bg-white text-neutral-900 font-medium text-xs uppercase tracking-widest hover:bg-brand-primary hover:text-white transition-all shadow-lg rounded-sm"
>
{buttonText}
</Link>
)}
{secondaryButtonText && secondaryButtonLink && (
<Link
href={secondaryButtonLink}
className="px-8 py-3.5 bg-transparent border border-white/40 text-white font-medium text-xs uppercase tracking-widest hover:bg-white/10 hover:border-white transition-all rounded-sm backdrop-blur-sm"
>
{secondaryButtonText}
</Link>
)}
</div>
)}
</div>
</div>
</section>
)
@@ -1103,8 +1121,12 @@ function BannerSection({ payload, lang }: { payload: Record<string, unknown>, la
const linkUrl = typeof payload.linkUrl === 'string' ? payload.linkUrl : ''
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? (payload.focalPoint as { x: string, y: string }) : undefined
const desktopPosition = typeof payload.buttonPosition === 'string' ? payload.buttonPosition : 'center-left'
const mobilePosition = typeof payload.mobileButtonPosition === 'string' ? payload.mobileButtonPosition : desktopPosition
const { wrapperClasses: bWrap, contentClasses: bContent, actionsClasses: bActions } = getHeroPositionClasses(mobilePosition, desktopPosition)
const content = (
<div className="ui-banner-shell">
<div className={`ui-banner-shell flex flex-col ${bWrap}`}>
<Image
src={imageUrl}
alt={title || 'Banner'}
@@ -1114,10 +1136,14 @@ function BannerSection({ payload, lang }: { payload: Record<string, unknown>, la
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
/>
<div className="ui-banner-overlay" />
<div className="ui-banner-content">
<div className={`ui-banner-content flex flex-col space-y-4 max-w-2xl ${bContent}`}>
{title && <h2 className="text-3xl font-semibold text-brand-primary drop-shadow-md md:text-5xl">{title}</h2>}
{description && <p className="text-surface-page/90 drop-shadow-sm">{description}</p>}
{buttonText && <span className="inline-flex cursor-pointer rounded-full bg-surface-card px-6 py-3 font-semibold text-text-main transition-colors hover:bg-brand-primary hover:text-surface-card">{buttonText}</span>}
{buttonText && (
<div className={`flex ${bActions}`}>
<span className="inline-flex cursor-pointer rounded-full bg-surface-card px-6 py-3 font-semibold text-text-main transition-colors hover:bg-brand-primary hover:text-surface-card">{buttonText}</span>
</div>
)}
</div>
</div>
)