This commit is contained in:
Your Name committed 2026-10-17 08:25:18 +07:00
1 parent 3f9db48df4
commit 9a5a30778e
5 files changed
+268 -71

No files matched your search

@@ -160,6 +160,53 @@ export default function ProductStoryScrollForm({ payload, onChange, onBrowseMedi
</button> </button>
</div> </div>
{/* Visual Theme Selector (Dark Theater vs Light Luxury) */}
<div className="p-4 rounded-2xl bg-night-950 border border-white/10 space-y-3">
<div className="flex items-center justify-between">
<label className="text-xs font-bold uppercase tracking-widest text-slate-300 flex items-center gap-2">
<Layers className="w-4 h-4 text-cyan-glow" />
ธีมการแสดงผลของ Story (Visual Theme)
</label>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<button
type="button"
onClick={() => onChange({ ...payload, storyTheme: 'dark' })}
className={`p-3.5 rounded-xl border text-left transition-all cursor-pointer ${
(payload.storyTheme || 'dark') === 'dark'
? 'bg-cyan-glow/10 border-cyan-glow/50 text-white ring-1 ring-cyan-glow/30'
: 'bg-white/5 border-white/10 text-slate-400 hover:text-white'
}`}
>
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-black uppercase tracking-wider">Dark Theater (Apple Pro)</span>
{(payload.storyTheme || 'dark') === 'dark' && <CheckCircle className="w-4 h-4 text-cyan-glow" />}
</div>
<p className="text-[11px] text-slate-400 leading-relaxed">
ฉากมืดสไตล์โรงภาพยนตร์ แสงนีออนและ Radial Glow เรืองแสงรอบสินค้า
</p>
</button>
<button
type="button"
onClick={() => onChange({ ...payload, storyTheme: 'light' })}
className={`p-3.5 rounded-xl border text-left transition-all cursor-pointer ${
payload.storyTheme === 'light'
? 'bg-cyan-glow/10 border-cyan-glow/50 text-white ring-1 ring-cyan-glow/30'
: 'bg-white/5 border-white/10 text-slate-400 hover:text-white'
}`}
>
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-black uppercase tracking-wider">Light Studio (Clean Luxury)</span>
{payload.storyTheme === 'light' && <CheckCircle className="w-4 h-4 text-cyan-glow" />}
</div>
<p className="text-[11px] text-slate-400 leading-relaxed">
ฉากสว่าง หรูหรา คลีนตา กลมกลืนกับธีมสีครีม/ขาวของเว็บไซต์ ไม่เป็นกล่องดำ
</p>
</button>
</div>
</div>
{/* 1. Core Hero Showcase Image */} {/* 1. Core Hero Showcase Image */}
<div className="p-4 rounded-2xl bg-night-950 border border-white/10 space-y-4"> <div className="p-4 rounded-2xl bg-night-950 border border-white/10 space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
+1 -1
View File
@@ -1,6 +1,6 @@
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import prisma from "../lib/prisma"; import prisma from "../lib/prisma";
// Trigger reload: support story_scroll and apple_watch_tour in ProductLayoutSectionPayloadSchema // Trigger reload: support storyTheme in ProductLayoutSectionPayloadSchema
import { import {
LocalizedStringSchema, LocalizedStringSchema,
HeroSectionPayloadSchema, HeroSectionPayloadSchema,
@@ -123,6 +123,7 @@ function ProductMainSection({
const storyScenes = Array.isArray(payload.storyScenes) ? (payload.storyScenes as any[]) : undefined const storyScenes = Array.isArray(payload.storyScenes) ? (payload.storyScenes as any[]) : undefined
const endCtaTitle = (payload.endCtaTitle as string) || undefined const endCtaTitle = (payload.endCtaTitle as string) || undefined
const endCtaButtonText = (payload.endCtaButtonText as string) || undefined const endCtaButtonText = (payload.endCtaButtonText as string) || undefined
const storyTheme = (payload.storyTheme as string) || 'dark'
return ( return (
<div className="space-y-12"> <div className="space-y-12">
@@ -161,6 +162,7 @@ function ProductMainSection({
lang="th" lang="th"
endCtaTitle={endCtaTitle} endCtaTitle={endCtaTitle}
endCtaButtonText={endCtaButtonText} endCtaButtonText={endCtaButtonText}
storyTheme={storyTheme}
/> />
</div> </div>
</div> </div>
@@ -23,6 +23,7 @@ interface Props {
lang: string lang: string
endCtaTitle?: string endCtaTitle?: string
endCtaButtonText?: string endCtaButtonText?: string
storyTheme?: 'dark' | 'light' | string
} }
const DEFAULT_SCENES: StoryScene[] = [ const DEFAULT_SCENES: StoryScene[] = [
@@ -37,7 +38,7 @@ const DEFAULT_SCENES: StoryScene[] = [
id: 'material', id: 'material',
kicker: '02 / MATERIAL', kicker: '02 / MATERIAL',
title: 'CERAMIC\nCOMPOUND.', title: 'CERAMIC\nCOMPOUND.',
description: 'สูตรผสมเนื้อเซรามิกเกรดพรีเมียม สึกหรอน้อย เสียงเบียบเงียบ ไร้แรงสั่นสะเทือน และลดฝุ่นผงจับจานเบรกอย่างเห็นได้ชัด', description: 'สูตรผสมเนื้อเซรามิกเกรดพรีเมียม สึกหรอน้อย เสียงเงียบ ไร้แรงสั่นสะเทือน และลดฝุ่นผงจับจานเบรกอย่างเห็นได้ชัด',
chips: ['Low Noise', 'Low Vibration', 'Cleaner Braking'], chips: ['Low Noise', 'Low Vibration', 'Cleaner Braking'],
theme: 'material', theme: 'material',
}, },
@@ -87,11 +88,14 @@ export default function CinematicStoryScroll({
lang, lang,
endCtaTitle, endCtaTitle,
endCtaButtonText, endCtaButtonText,
storyTheme = 'dark',
}: Props) { }: Props) {
const [activeScene, setActiveScene] = useState<string>('hero') const [activeScene, setActiveScene] = useState<string>('hero')
const [isStoryVisible, setIsStoryVisible] = useState(false) const [isStoryVisible, setIsStoryVisible] = useState(false)
const storyRef = useRef<HTMLDivElement | null>(null) const storyRef = useRef<HTMLDivElement | null>(null)
const isLight = storyTheme === 'light'
const activeScenes = useMemo(() => { const activeScenes = useMemo(() => {
if (scenes && scenes.length > 0) return scenes if (scenes && scenes.length > 0) return scenes
return DEFAULT_SCENES return DEFAULT_SCENES
@@ -136,7 +140,10 @@ export default function CinematicStoryScroll({
} }
}) })
}, },
{ threshold: 0.55 } {
rootMargin: '-20% 0px -20% 0px',
threshold: [0.1, 0.4],
}
) )
triggers.forEach((t) => observer.observe(t)) triggers.forEach((t) => observer.observe(t))
@@ -167,25 +174,53 @@ export default function CinematicStoryScroll({
} }
} }
// Active scene index const scrollToScene = (index: number) => {
const storyEl = storyRef.current
if (!storyEl) return
const triggers = storyEl.querySelectorAll<HTMLElement>('.story-trigger')
if (triggers[index]) {
triggers[index].scrollIntoView({ behavior: 'smooth' })
}
}
const activeIndex = Math.max(0, activeScenes.findIndex(s => s.id === activeScene)) const activeIndex = Math.max(0, activeScenes.findIndex(s => s.id === activeScene))
return ( return (
<div ref={storyRef} className="relative w-full bg-[#0c1320] text-white overflow-hidden transition-colors duration-700 select-none"> <div
ref={storyRef}
className={`relative w-full overflow-hidden transition-colors duration-700 select-none ${
isLight
? 'bg-[#f7f5ed] text-slate-900 border-y border-line-primary/20'
: 'bg-[#0b101c] text-white'
}`}
>
{/* Top Atmosphere Gradient Blend (prevents harsh abrupt cut into dark) */}
{!isLight && (
<div className="absolute top-0 left-0 right-0 h-32 bg-gradient-to-b from-surface-page via-[#0b101c]/60 to-[#0b101c] pointer-events-none z-20" />
)}
{/* Side Progress Dots (Desktop) */} {/* Side Progress Dots (Desktop) */}
<div <div
className={`fixed right-6 top-1/2 -translate-y-1/2 z-40 hidden lg:flex flex-col gap-2.5 transition-opacity duration-300 pointer-events-none ${ className={`fixed right-6 top-1/2 -translate-y-1/2 z-40 hidden lg:flex flex-col gap-2.5 transition-opacity duration-300 pointer-events-auto ${
isStoryVisible ? 'opacity-100' : 'opacity-0' isStoryVisible ? 'opacity-100' : 'opacity-0'
}`} }`}
> >
{activeScenes.map((s, idx) => ( {activeScenes.map((s, idx) => (
<div <button
key={s.id} key={s.id}
className={`w-2 rounded-full transition-all duration-300 ${ type="button"
onClick={() => scrollToScene(idx)}
className={`rounded-full transition-all duration-300 cursor-pointer ${
activeIndex === idx activeIndex === idx
? 'h-7 bg-white shadow-lg shadow-white/30' ? isLight
: 'h-2 bg-white/20' ? 'w-2.5 h-7 bg-brand-primary shadow-md'
: 'w-2.5 h-7 bg-white shadow-lg shadow-white/40'
: isLight
? 'w-2.5 h-2.5 bg-slate-300 hover:bg-slate-400'
: 'w-2.5 h-2.5 bg-white/20 hover:bg-white/50'
}`} }`}
title={`Scene 0${idx + 1}`}
aria-label={`Jump to scene 0${idx + 1}`}
/> />
))} ))}
</div> </div>
@@ -195,47 +230,97 @@ export default function CinematicStoryScroll({
{/* Sticky 100vh Stage */} {/* Sticky 100vh Stage */}
<div className="sticky top-0 h-screen w-full overflow-hidden flex items-center justify-center"> <div className="sticky top-0 h-screen w-full overflow-hidden flex items-center justify-center">
{/* Ambient Reactive Glow Backdrops */} {/* Ambient Reactive Glow Backdrops */}
<div {isLight ? (
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${ <>
activeScene === 'heat' ? 'opacity-100' : 'opacity-0' {/* Light Theme Reactive Glows */}
}`} <div
style={{ className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
background: 'radial-gradient(circle at 35% 50%, rgba(255, 102, 0, 0.45), transparent 40%), linear-gradient(110deg, #180d08 0%, #0d1422 75%)', activeScene === 'heat' ? 'opacity-100' : 'opacity-0'
}} }`}
/> style={{
<div background: 'radial-gradient(circle at 35% 50%, rgba(255, 120, 30, 0.16), transparent 45%), linear-gradient(180deg, #fef7ee 0%, #faeee0 100%)',
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${ }}
activeScene === 'material' ? 'opacity-100' : 'opacity-0' />
}`} <div
style={{ className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
background: 'radial-gradient(circle at 35% 50%, rgba(30, 110, 255, 0.35), transparent 38%), linear-gradient(180deg, #0d1728 0%, #09101a 100%)', activeScene === 'material' ? 'opacity-100' : 'opacity-0'
}} }`}
/> style={{
<div background: 'radial-gradient(circle at 35% 50%, rgba(30, 120, 255, 0.14), transparent 45%), linear-gradient(180deg, #f4f8fe 0%, #edf3fd 100%)',
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${ }}
activeScene === 'friction' ? 'opacity-100' : 'opacity-0' />
}`} <div
style={{ className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
backgroundImage: 'linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px)', activeScene === 'friction' ? 'opacity-100' : 'opacity-0'
backgroundSize: '40px 40px', }`}
}} style={{
/> backgroundImage: 'linear-gradient(rgba(0,0,0,0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(0,0,0,0.04) 1px, transparent 1px)',
<div backgroundSize: '40px 40px',
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${ }}
activeScene === 'durability' ? 'opacity-100' : 'opacity-0' />
}`} <div
style={{ className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
background: 'radial-gradient(circle at 40% 60%, rgba(216, 179, 90, 0.28), transparent 35%), linear-gradient(120deg, #131c28 0%, #0a0e16 100%)', activeScene === 'durability' ? 'opacity-100' : 'opacity-0'
}} }`}
/> style={{
<div background: 'radial-gradient(circle at 40% 60%, rgba(216, 179, 90, 0.18), transparent 45%), linear-gradient(120deg, #faf6ed 0%, #f3ece0 100%)',
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${ }}
activeScene === 'hero' ? 'opacity-100' : 'opacity-0' />
}`} <div
style={{ className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
background: 'radial-gradient(circle at 35% 50%, rgba(20, 103, 255, 0.3), transparent 35%), linear-gradient(180deg, #0c1320 0%, #0a0f18 100%)', activeScene === 'hero' ? 'opacity-100' : 'opacity-0'
}} }`}
/> style={{
background: 'radial-gradient(circle at 35% 50%, rgba(20, 103, 255, 0.12), transparent 42%), linear-gradient(180deg, #f7f5ed 0%, #f0ede3 100%)',
}}
/>
</>
) : (
<>
{/* Dark Theater Reactive Glows */}
<div
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
activeScene === 'heat' ? 'opacity-100' : 'opacity-0'
}`}
style={{
background: 'radial-gradient(circle at 35% 50%, rgba(255, 102, 0, 0.45), transparent 40%), linear-gradient(110deg, #180d08 0%, #0d1422 75%)',
}}
/>
<div
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
activeScene === 'material' ? 'opacity-100' : 'opacity-0'
}`}
style={{
background: 'radial-gradient(circle at 35% 50%, rgba(30, 110, 255, 0.35), transparent 38%), linear-gradient(180deg, #0d1728 0%, #09101a 100%)',
}}
/>
<div
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
activeScene === 'friction' ? 'opacity-100' : 'opacity-0'
}`}
style={{
backgroundImage: 'linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px)',
backgroundSize: '40px 40px',
}}
/>
<div
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
activeScene === 'durability' ? 'opacity-100' : 'opacity-0'
}`}
style={{
background: 'radial-gradient(circle at 40% 60%, rgba(216, 179, 90, 0.28), transparent 35%), linear-gradient(120deg, #131c28 0%, #0a0e16 100%)',
}}
/>
<div
className={`absolute inset-0 transition-opacity duration-700 pointer-events-none ${
activeScene === 'hero' ? 'opacity-100' : 'opacity-0'
}`}
style={{
background: 'radial-gradient(circle at 35% 50%, rgba(20, 103, 255, 0.3), transparent 35%), linear-gradient(180deg, #0c1320 0%, #0a0f18 100%)',
}}
/>
</>
)}
{/* Desktop Layout: Left Showcase Visual, Right Copy */} {/* Desktop Layout: Left Showcase Visual, Right Copy */}
<div className="relative w-full max-w-7xl mx-auto px-6 md:px-12 h-full flex flex-col lg:flex-row items-center justify-between gap-8 z-10"> <div className="relative w-full max-w-7xl mx-auto px-6 md:px-12 h-full flex flex-col lg:flex-row items-center justify-between gap-8 z-10">
@@ -243,7 +328,17 @@ export default function CinematicStoryScroll({
<div className="w-full lg:w-1/2 flex items-center justify-center relative mt-16 lg:mt-0"> <div className="w-full lg:w-1/2 flex items-center justify-center relative mt-16 lg:mt-0">
<div <div
className={`relative w-64 h-64 sm:w-80 sm:h-80 md:w-96 md:h-96 lg:w-[440px] lg:h-[440px] rounded-full transition-all duration-700 flex items-center justify-center ${ className={`relative w-64 h-64 sm:w-80 sm:h-80 md:w-96 md:h-96 lg:w-[440px] lg:h-[440px] rounded-full transition-all duration-700 flex items-center justify-center ${
activeScene === 'heat' isLight
? activeScene === 'heat'
? 'scale-105 border-[14px] border-orange-400 shadow-[0_20px_60px_rgba(255,102,0,0.25)]'
: activeScene === 'material'
? 'scale-110 border-[14px] border-blue-500 shadow-[0_20px_60px_rgba(20,103,255,0.22)]'
: activeScene === 'control'
? 'scale-95 -rotate-12 border-[14px] border-cyan-500 shadow-[0_20px_50px_rgba(6,182,212,0.2)]'
: activeScene === 'durability'
? 'scale-100 border-[14px] border-[#d8b35a] shadow-[0_20px_50px_rgba(216,179,90,0.25)]'
: 'scale-100 border-[14px] border-slate-300 shadow-[0_20px_60px_rgba(0,0,0,0.08)]'
: activeScene === 'heat'
? 'scale-105 border-[16px] border-orange-500 shadow-[0_0_90px_rgba(255,102,0,0.6)]' ? 'scale-105 border-[16px] border-orange-500 shadow-[0_0_90px_rgba(255,102,0,0.6)]'
: activeScene === 'material' : activeScene === 'material'
? 'scale-110 border-[16px] border-blue-500 shadow-[0_0_80px_rgba(20,103,255,0.5)]' ? 'scale-110 border-[16px] border-blue-500 shadow-[0_0_80px_rgba(20,103,255,0.5)]'
@@ -254,10 +349,12 @@ export default function CinematicStoryScroll({
: 'scale-100 border-[16px] border-[#1467ff] shadow-[0_0_70px_rgba(20,103,255,0.4)]' : 'scale-100 border-[16px] border-[#1467ff] shadow-[0_0_70px_rgba(20,103,255,0.4)]'
}`} }`}
style={{ style={{
background: 'radial-gradient(circle, rgba(12, 19, 32, 0.95) 45%, rgba(6, 10, 18, 0.98) 100%)', background: isLight
? 'radial-gradient(circle, #ffffff 50%, #f4f0e6 100%)'
: 'radial-gradient(circle, rgba(16, 24, 40, 0.95) 45%, rgba(8, 14, 24, 0.98) 100%)',
}} }}
> >
{/* Product Image */} {/* Product Image Stage */}
{mainShowcaseImage ? ( {mainShowcaseImage ? (
<div className="relative w-4/5 h-4/5 p-4 flex items-center justify-center"> <div className="relative w-4/5 h-4/5 p-4 flex items-center justify-center">
<Image <Image
@@ -265,20 +362,24 @@ export default function CinematicStoryScroll({
alt={resolvedProductName} alt={resolvedProductName}
fill fill
sizes="(max-width: 1024px) 320px, 440px" sizes="(max-width: 1024px) 320px, 440px"
className="object-contain drop-shadow-[0_20px_35px_rgba(0,0,0,0.7)] transition-transform duration-700" className="object-contain drop-shadow-[0_20px_35px_rgba(0,0,0,0.4)] transition-transform duration-700"
priority priority
/> />
</div> </div>
) : ( ) : (
<div className="text-center p-6 space-y-2"> <div className="text-center p-6 space-y-2">
<span className="text-4xl">🏎️</span> <span className="text-4xl">🏎️</span>
<p className="text-xs uppercase tracking-widest text-slate-400 font-bold">Premium Racing Spec</p> <p className={`text-xs uppercase tracking-widest font-bold ${isLight ? 'text-slate-500' : 'text-slate-400'}`}>
Premium Spec
</p>
</div> </div>
)} )}
{/* Pulsing Core Ring */} {/* Pulsing Core Ring */}
<div <div
className="absolute inset-4 rounded-full border border-white/10 pointer-events-none animate-spin-slow" className={`absolute inset-4 rounded-full border pointer-events-none animate-spin-slow ${
isLight ? 'border-slate-300/60' : 'border-white/10'
}`}
style={{ animationDuration: '40s' }} style={{ animationDuration: '40s' }}
/> />
</div> </div>
@@ -298,24 +399,36 @@ export default function CinematicStoryScroll({
}`} }`}
> >
{/* Kicker */} {/* Kicker */}
<div className="text-xs md:text-sm font-black tracking-[0.2em] uppercase text-cyan-300"> <div
className={`text-xs md:text-sm font-black tracking-[0.2em] uppercase ${
isLight ? 'text-brand-primary' : 'text-cyan-300'
}`}
>
{scene.kicker} {scene.kicker}
</div> </div>
{/* Headline */} {/* Headline */}
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black tracking-tight leading-[0.95] whitespace-pre-line text-white"> <h2
className={`text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black tracking-tight leading-[0.95] whitespace-pre-line ${
isLight ? 'text-slate-900' : 'text-white'
}`}
>
{scene.title} {scene.title}
</h2> </h2>
{/* Metric Highlight (e.g. 300°C) */} {/* Metric Highlight (e.g. 300°C) */}
{scene.metric && ( {scene.metric && (
<div className="text-5xl sm:text-6xl md:text-7xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-r from-orange-400 via-amber-300 to-yellow-200"> <div className="text-5xl sm:text-6xl md:text-7xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-r from-orange-500 via-amber-500 to-yellow-500">
{scene.metric} {scene.metric}
</div> </div>
)} )}
{/* Description */} {/* Description */}
<p className="text-sm md:text-base text-slate-300 leading-relaxed max-w-md"> <p
className={`text-sm md:text-base leading-relaxed max-w-md ${
isLight ? 'text-slate-600' : 'text-slate-300'
}`}
>
{scene.description} {scene.description}
</p> </p>
@@ -325,7 +438,11 @@ export default function CinematicStoryScroll({
{scene.chips.map((chip, idx) => ( {scene.chips.map((chip, idx) => (
<span <span
key={idx} key={idx}
className="px-3.5 py-1.5 rounded-full text-xs font-semibold bg-white/10 border border-white/20 text-slate-200 backdrop-blur-sm" className={`px-3.5 py-1.5 rounded-full text-xs font-semibold backdrop-blur-sm ${
isLight
? 'bg-slate-900/5 border border-slate-300 text-slate-800'
: 'bg-white/10 border border-white/20 text-slate-200'
}`}
> >
{chip} {chip}
</span> </span>
@@ -336,14 +453,22 @@ export default function CinematicStoryScroll({
{/* Fitment Grid for Fitment Scene */} {/* Fitment Grid for Fitment Scene */}
{scene.id === 'fitment' && fitmentModels.length > 0 && ( {scene.id === 'fitment' && fitmentModels.length > 0 && (
<div className="pt-2"> <div className="pt-2">
<p className="text-xs uppercase font-bold tracking-wider text-slate-400 mb-2"> <p
รุ่นรถยอดนิยมที่รองรับ ({fitmentModels.length} รุ่น): className={`text-xs uppercase font-bold tracking-wider mb-2 ${
isLight ? 'text-slate-500' : 'text-slate-400'
}`}
>
รุ่นรถที่รองรับ ({fitmentModels.length} รุ่น):
</p> </p>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-48 overflow-y-auto custom-scrollbar pr-1"> <div className="grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-48 overflow-y-auto custom-scrollbar pr-1">
{fitmentModels.slice(0, 18).map((model, idx) => ( {fitmentModels.slice(0, 18).map((model, idx) => (
<span <span
key={idx} key={idx}
className="px-3 py-2 rounded-xl text-center text-xs font-bold bg-white/10 border border-white/15 text-white truncate" className={`px-3 py-2 rounded-xl text-center text-xs font-bold truncate ${
isLight
? 'bg-white border border-slate-200 text-slate-900 shadow-sm'
: 'bg-white/10 border border-white/15 text-white'
}`}
title={model} title={model}
> >
{model} {model}
@@ -370,20 +495,42 @@ export default function CinematicStoryScroll({
</div> </div>
{/* End CTA Section */} {/* End CTA Section */}
<section className="relative z-10 min-h-[50vh] flex flex-col items-center justify-center text-center px-6 py-20 bg-gradient-to-b from-[#0c1320] via-[#09101a] to-[#060a12] border-t border-white/10 space-y-5"> <section
<div className="text-xs uppercase font-black tracking-[0.25em] text-cyan-400"> className={`relative z-10 min-h-[50vh] flex flex-col items-center justify-center text-center px-6 py-20 border-t space-y-5 ${
isLight
? 'bg-[#f0ede3] border-slate-200 text-slate-900'
: 'bg-gradient-to-b from-[#0b101c] via-[#09101a] to-surface-page border-white/10 text-white'
}`}
>
<div
className={`text-xs uppercase font-black tracking-[0.25em] ${
isLight ? 'text-brand-primary' : 'text-cyan-400'
}`}
>
END OF PRODUCT STORY END OF PRODUCT STORY
</div> </div>
<h3 className="text-3xl sm:text-5xl md:text-6xl font-black text-white tracking-tight"> <h3
className={`text-3xl sm:text-5xl md:text-6xl font-black tracking-tight ${
isLight ? 'text-slate-900' : 'text-white'
}`}
>
{endCtaTitle || 'SELECT YOUR MODEL.'} {endCtaTitle || 'SELECT YOUR MODEL.'}
</h3> </h3>
<p className="text-sm md:text-base text-slate-400 max-w-md mx-auto leading-relaxed"> <p
พร้อมยกระดับสมรรถนะการเบรกให้มอเตอร์ไซค์ของคุณ สั่งซื้อได้ทันทีตรงรุ่น className={`text-sm md:text-base max-w-md mx-auto leading-relaxed ${
isLight ? 'text-slate-600' : 'text-slate-400'
}`}
>
พร้อมยกระดับสมรรถนะให้รถของคุณ สั่งซื้อได้ทันทีตรงรุ่น
</p> </p>
<button <button
type="button" type="button"
onClick={scrollToPurchase} onClick={scrollToPurchase}
className="mt-4 px-8 py-4 rounded-2xl font-black text-sm uppercase tracking-wider bg-gradient-to-r from-cyan-400 to-blue-500 hover:from-cyan-300 hover:to-blue-400 text-night-950 shadow-xl shadow-cyan-500/25 transition-all transform hover:scale-105 active:scale-95 cursor-pointer" className={`mt-4 px-8 py-4 rounded-2xl font-black text-sm uppercase tracking-wider transition-all transform hover:scale-105 active:scale-95 cursor-pointer shadow-xl ${
isLight
? 'bg-brand-primary hover:bg-brand-primary-hover text-white shadow-brand-primary/25'
: 'bg-gradient-to-r from-cyan-400 to-blue-500 hover:from-cyan-300 hover:to-blue-400 text-slate-950 shadow-cyan-500/25'
}`}
> >
{endCtaButtonText || 'Choose Your Bike / สั่งซื้อรุ่นของคุณ'} {endCtaButtonText || 'Choose Your Bike / สั่งซื้อรุ่นของคุณ'}
</button> </button>
+1
View File
@@ -1125,6 +1125,7 @@ export const ProductLayoutSectionPayloadSchema = z.object({
showBreadcrumbs: z.boolean().optional(), showBreadcrumbs: z.boolean().optional(),
showBrandMeta: z.boolean().optional(), showBrandMeta: z.boolean().optional(),
storyHeroImage: z.string().optional(), storyHeroImage: z.string().optional(),
storyTheme: z.enum(['dark', 'light']).optional(),
storyScenes: z.array(StorySceneSchema).optional(), storyScenes: z.array(StorySceneSchema).optional(),
endCtaTitle: z.string().optional(), endCtaTitle: z.string().optional(),
endCtaButtonText: z.string().optional(), endCtaButtonText: z.string().optional(),