diff --git a/apps/admin/src/components/page-builder/HeroVideoForm.tsx b/apps/admin/src/components/page-builder/HeroVideoForm.tsx index 85f4eb6..b00eb5f 100644 --- a/apps/admin/src/components/page-builder/HeroVideoForm.tsx +++ b/apps/admin/src/components/page-builder/HeroVideoForm.tsx @@ -89,6 +89,61 @@ export default function HeroVideoForm({ + {/* Interactive Focal Point Picker */} +
+
+ + + {payload.focalPoint ? `X: ${payload.focalPoint.x}, Y: ${payload.focalPoint.y}` : 'Center (50% 50%)'} + +
+
{ + const rect = e.currentTarget.getBoundingClientRect(); + const x = Math.round(((e.clientX - rect.left) / rect.width) * 100) + '%'; + const y = Math.round(((e.clientY - rect.top) / rect.height) * 100) + '%'; + updatePayloadValue('focalPoint', { x, y }); + }} + > + {payload.posterImageUrl ? ( + Poster focal preview + ) : payload.videoUrl ? ( +
+
+ {/* Video Settings: Opacity & Height */}
diff --git a/apps/spa/src/components/DynamicHeroVideo.tsx b/apps/spa/src/components/DynamicHeroVideo.tsx index 14c3ca1..93ee14d 100644 --- a/apps/spa/src/components/DynamicHeroVideo.tsx +++ b/apps/spa/src/components/DynamicHeroVideo.tsx @@ -22,6 +22,8 @@ export default function DynamicHeroVideo({ payload }: DynamicHeroVideoProps) { const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 40; const height = 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': @@ -66,13 +68,26 @@ export default function DynamicHeroVideo({ payload }: DynamicHeroVideoProps) { playsInline poster={posterImageUrl} className="absolute inset-0 w-full h-full object-cover z-0" + style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }} > - {posterImageUrl && Hero fallback} + {posterImageUrl && ( + Hero fallback + )} ) : posterImageUrl ? ( - Hero Poster + Hero Poster ) : null} {/* Customizable Overlay Opacity */} diff --git a/apps/website/src/components/dynamic/DynamicPageRenderer.tsx b/apps/website/src/components/dynamic/DynamicPageRenderer.tsx index c960197..a4f9e2b 100644 --- a/apps/website/src/components/dynamic/DynamicPageRenderer.tsx +++ b/apps/website/src/components/dynamic/DynamicPageRenderer.tsx @@ -169,6 +169,7 @@ function HeroVideoSection({ payload, lang }: { payload: Record, const posterImageUrl = typeof payload.posterImageUrl === 'string' ? payload.posterImageUrl : undefined const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 40 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) { @@ -192,13 +193,30 @@ function HeroVideoSection({ payload, lang }: { payload: Record, playsInline poster={posterImageUrl} className="absolute inset-0 w-full h-full object-cover z-0" + style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }} > - {posterImageUrl && Hero fallback} + {posterImageUrl && ( + Hero fallback + )} ) : posterImageUrl ? ( - Hero Poster + Hero Poster ) : null}
diff --git a/packages/types/index.ts b/packages/types/index.ts index 9c87ae6..905e579 100644 --- a/packages/types/index.ts +++ b/packages/types/index.ts @@ -444,6 +444,7 @@ export const HeroVideoSectionPayloadSchema = z.object({ loop: z.boolean().optional().default(true), backgroundColor: z.string().optional(), textColor: z.string().optional(), + focalPoint: z.object({ x: z.string(), y: z.string() }).optional(), }); export type HeroVideoSectionPayload = z.infer;