diff --git a/apps/admin/src/components/products/ProductStoryScrollForm.tsx b/apps/admin/src/components/products/ProductStoryScrollForm.tsx
index a56900f..f737246 100644
--- a/apps/admin/src/components/products/ProductStoryScrollForm.tsx
+++ b/apps/admin/src/components/products/ProductStoryScrollForm.tsx
@@ -160,6 +160,53 @@ export default function ProductStoryScrollForm({ payload, onChange, onBrowseMedi
+ {/* Visual Theme Selector (Dark Theater vs Light Luxury) */}
+
diff --git a/apps/backend/src/routes/pages.ts b/apps/backend/src/routes/pages.ts
index 5f9c820..d3381aa 100644
--- a/apps/backend/src/routes/pages.ts
+++ b/apps/backend/src/routes/pages.ts
@@ -1,6 +1,6 @@
import { Elysia, t } from "elysia";
import prisma from "../lib/prisma";
-// Trigger reload: support story_scroll and apple_watch_tour in ProductLayoutSectionPayloadSchema
+// Trigger reload: support storyTheme in ProductLayoutSectionPayloadSchema
import {
LocalizedStringSchema,
HeroSectionPayloadSchema,
diff --git a/apps/website/src/app/[lang]/products/[slug]/page.tsx b/apps/website/src/app/[lang]/products/[slug]/page.tsx
index 2f85bb4..5754780 100644
--- a/apps/website/src/app/[lang]/products/[slug]/page.tsx
+++ b/apps/website/src/app/[lang]/products/[slug]/page.tsx
@@ -123,6 +123,7 @@ function ProductMainSection({
const storyScenes = Array.isArray(payload.storyScenes) ? (payload.storyScenes as any[]) : undefined
const endCtaTitle = (payload.endCtaTitle as string) || undefined
const endCtaButtonText = (payload.endCtaButtonText as string) || undefined
+ const storyTheme = (payload.storyTheme as string) || 'dark'
return (
@@ -161,6 +162,7 @@ function ProductMainSection({
lang="th"
endCtaTitle={endCtaTitle}
endCtaButtonText={endCtaButtonText}
+ storyTheme={storyTheme}
/>
diff --git a/apps/website/src/components/CinematicStoryScroll.tsx b/apps/website/src/components/CinematicStoryScroll.tsx
index 3823e12..847c106 100644
--- a/apps/website/src/components/CinematicStoryScroll.tsx
+++ b/apps/website/src/components/CinematicStoryScroll.tsx
@@ -23,6 +23,7 @@ interface Props {
lang: string
endCtaTitle?: string
endCtaButtonText?: string
+ storyTheme?: 'dark' | 'light' | string
}
const DEFAULT_SCENES: StoryScene[] = [
@@ -37,7 +38,7 @@ const DEFAULT_SCENES: StoryScene[] = [
id: 'material',
kicker: '02 / MATERIAL',
title: 'CERAMIC\nCOMPOUND.',
- description: 'สูตรผสมเนื้อเซรามิกเกรดพรีเมียม สึกหรอน้อย เสียงเบียบเงียบ ไร้แรงสั่นสะเทือน และลดฝุ่นผงจับจานเบรกอย่างเห็นได้ชัด',
+ description: 'สูตรผสมเนื้อเซรามิกเกรดพรีเมียม สึกหรอน้อย เสียงเงียบ ไร้แรงสั่นสะเทือน และลดฝุ่นผงจับจานเบรกอย่างเห็นได้ชัด',
chips: ['Low Noise', 'Low Vibration', 'Cleaner Braking'],
theme: 'material',
},
@@ -87,11 +88,14 @@ export default function CinematicStoryScroll({
lang,
endCtaTitle,
endCtaButtonText,
+ storyTheme = 'dark',
}: Props) {
const [activeScene, setActiveScene] = useState
('hero')
const [isStoryVisible, setIsStoryVisible] = useState(false)
const storyRef = useRef(null)
+ const isLight = storyTheme === 'light'
+
const activeScenes = useMemo(() => {
if (scenes && scenes.length > 0) return 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))
@@ -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('.story-trigger')
+ if (triggers[index]) {
+ triggers[index].scrollIntoView({ behavior: 'smooth' })
+ }
+ }
+
const activeIndex = Math.max(0, activeScenes.findIndex(s => s.id === activeScene))
return (
-
+
+ {/* Top Atmosphere Gradient Blend (prevents harsh abrupt cut into dark) */}
+ {!isLight && (
+
+ )}
+
{/* Side Progress Dots (Desktop) */}
{activeScenes.map((s, idx) => (
-
scrollToScene(idx)}
+ className={`rounded-full transition-all duration-300 cursor-pointer ${
activeIndex === idx
- ? 'h-7 bg-white shadow-lg shadow-white/30'
- : 'h-2 bg-white/20'
+ ? isLight
+ ? '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}`}
/>
))}
@@ -195,47 +230,97 @@ export default function CinematicStoryScroll({
{/* Sticky 100vh Stage */}
{/* Ambient Reactive Glow Backdrops */}
-
-
-
-
-
+ {isLight ? (
+ <>
+ {/* Light Theme Reactive Glows */}
+
+
+
+
+
+ >
+ ) : (
+ <>
+ {/* Dark Theater Reactive Glows */}
+
+
+
+
+
+ >
+ )}
{/* Desktop Layout: Left Showcase Visual, Right Copy */}
@@ -243,7 +328,17 @@ export default function CinematicStoryScroll({
- {/* Product Image */}
+ {/* Product Image Stage */}
{mainShowcaseImage ? (
) : (
🏎️
-
Premium Racing Spec
+
+ Premium Spec
+
)}
{/* Pulsing Core Ring */}
@@ -298,24 +399,36 @@ export default function CinematicStoryScroll({
}`}
>
{/* Kicker */}
-
+
{scene.kicker}
{/* Headline */}
-
+
{scene.title}
{/* Metric Highlight (e.g. 300°C) */}
{scene.metric && (
-
+
{scene.metric}
)}
{/* Description */}
-
+
{scene.description}
@@ -325,7 +438,11 @@ export default function CinematicStoryScroll({
{scene.chips.map((chip, idx) => (
{chip}
@@ -336,14 +453,22 @@ export default function CinematicStoryScroll({
{/* Fitment Grid for Fitment Scene */}
{scene.id === 'fitment' && fitmentModels.length > 0 && (
-
- รุ่นรถยอดนิยมที่รองรับ ({fitmentModels.length} รุ่น):
+
+ รุ่นรถที่รองรับ ({fitmentModels.length} รุ่น):
{fitmentModels.slice(0, 18).map((model, idx) => (
{model}
@@ -370,20 +495,42 @@ export default function CinematicStoryScroll({
{/* End CTA Section */}
-
-
+
+
END OF PRODUCT STORY
-
+
{endCtaTitle || 'SELECT YOUR MODEL.'}
-
- พร้อมยกระดับสมรรถนะการเบรกให้มอเตอร์ไซค์ของคุณ สั่งซื้อได้ทันทีตรงรุ่น
+
+ พร้อมยกระดับสมรรถนะให้รถของคุณ สั่งซื้อได้ทันทีตรงรุ่น
diff --git a/packages/types/index.ts b/packages/types/index.ts
index 34360ff..b15db38 100644
--- a/packages/types/index.ts
+++ b/packages/types/index.ts
@@ -1125,6 +1125,7 @@ export const ProductLayoutSectionPayloadSchema = z.object({
showBreadcrumbs: z.boolean().optional(),
showBrandMeta: z.boolean().optional(),
storyHeroImage: z.string().optional(),
+ storyTheme: z.enum(['dark', 'light']).optional(),
storyScenes: z.array(StorySceneSchema).optional(),
endCtaTitle: z.string().optional(),
endCtaButtonText: z.string().optional(),