This commit is contained in:
Your Name committed 2026-07-28 01:33:29 -04:00
1 parent a2da091b15
commit b6caa0abfe
4 files changed
+93 -4

No files matched your search

@@ -89,6 +89,61 @@ export default function HeroVideoForm({
</div> </div>
</div> </div>
{/* Interactive Focal Point Picker */}
<div className="space-y-2 pt-2 border-t border-white/5">
<div className="flex items-center justify-between">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
Video & Poster Focal Point Alignment (Mobile View Focus)
</label>
<span className="text-[10px] font-mono text-cyan-glow">
{payload.focalPoint ? `X: ${payload.focalPoint.x}, Y: ${payload.focalPoint.y}` : 'Center (50% 50%)'}
</span>
</div>
<div
className="relative h-32 w-full rounded-xl overflow-hidden border border-white/10 bg-black/60 cursor-crosshair group shadow-inner"
onClick={(e) => {
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 ? (
<img
src={payload.posterImageUrl}
alt="Poster focal preview"
className="w-full h-full object-cover opacity-70"
style={{ objectPosition: payload.focalPoint ? `${payload.focalPoint.x} ${payload.focalPoint.y}` : '50% 50%' }}
/>
) : payload.videoUrl ? (
<video
src={payload.videoUrl}
className="w-full h-full object-cover opacity-70"
muted
preload="metadata"
style={{ objectPosition: payload.focalPoint ? `${payload.focalPoint.x} ${payload.focalPoint.y}` : '50% 50%' }}
/>
) : (
<div className="w-full h-full flex items-center justify-center text-slate-500 text-xs font-mono">
Click anywhere in box to set Focal Point
</div>
)}
{/* Focal Point Pin Marker */}
<div
className="absolute w-6 h-6 -ml-3 -mt-3 border-2 border-cyan-glow rounded-full bg-cyan-glow/30 pointer-events-none transition-all flex items-center justify-center shadow-[0_0_12px_rgba(34,211,238,0.8)]"
style={{
left: payload.focalPoint?.x || '50%',
top: payload.focalPoint?.y || '50%'
}}
>
<div className="w-1.5 h-1.5 bg-white rounded-full"></div>
</div>
<div className="absolute inset-x-0 bottom-1 text-center text-[9px] font-bold text-white/70 uppercase tracking-widest pointer-events-none drop-shadow">
Click media box above to move Focus Pin
</div>
</div>
</div>
{/* Video Settings: Opacity & Height */} {/* Video Settings: Opacity & Height */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 pt-2 border-t border-white/5"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 pt-2 border-t border-white/5">
<div className="space-y-2"> <div className="space-y-2">
+17 -2
View File
@@ -22,6 +22,8 @@ export default function DynamicHeroVideo({ payload }: DynamicHeroVideoProps) {
const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 40; const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 40;
const height = payload.height || 'large'; 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) => { const getHeightClass = (h: string) => {
switch (h) { switch (h) {
case 'full': case 'full':
@@ -66,13 +68,26 @@ export default function DynamicHeroVideo({ payload }: DynamicHeroVideoProps) {
playsInline playsInline
poster={posterImageUrl} poster={posterImageUrl}
className="absolute inset-0 w-full h-full object-cover z-0" 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/mp4" />
<source src={videoUrl} type="video/webm" /> <source src={videoUrl} type="video/webm" />
{posterImageUrl && <img src={posterImageUrl} alt="Hero fallback" className="w-full h-full object-cover" />} {posterImageUrl && (
<img
src={posterImageUrl}
alt="Hero fallback"
className="w-full h-full object-cover"
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
/>
)}
</video> </video>
) : posterImageUrl ? ( ) : posterImageUrl ? (
<img src={posterImageUrl} alt="Hero Poster" className="absolute inset-0 w-full h-full object-cover z-0" /> <img
src={posterImageUrl}
alt="Hero Poster"
className="absolute inset-0 w-full h-full object-cover z-0"
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
/>
) : null} ) : null}
{/* Customizable Overlay Opacity */} {/* Customizable Overlay Opacity */}
@@ -169,6 +169,7 @@ function HeroVideoSection({ payload, lang }: { payload: Record<string, unknown>,
const posterImageUrl = typeof payload.posterImageUrl === 'string' ? payload.posterImageUrl : undefined const posterImageUrl = typeof payload.posterImageUrl === 'string' ? payload.posterImageUrl : undefined
const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 40 const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 40
const height = typeof payload.height === 'string' ? payload.height : 'large' 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) => { const getHeightClass = (h: string) => {
switch (h) { switch (h) {
@@ -192,13 +193,30 @@ function HeroVideoSection({ payload, lang }: { payload: Record<string, unknown>,
playsInline playsInline
poster={posterImageUrl} poster={posterImageUrl}
className="absolute inset-0 w-full h-full object-cover z-0" 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/mp4" />
<source src={videoUrl} type="video/webm" /> <source src={videoUrl} type="video/webm" />
{posterImageUrl && <Image src={posterImageUrl} alt="Hero fallback" fill unoptimized className="object-cover" />} {posterImageUrl && (
<Image
src={posterImageUrl}
alt="Hero fallback"
fill
unoptimized
className="object-cover"
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
/>
)}
</video> </video>
) : posterImageUrl ? ( ) : posterImageUrl ? (
<Image src={posterImageUrl} alt="Hero Poster" fill unoptimized className="object-cover z-0" /> <Image
src={posterImageUrl}
alt="Hero Poster"
fill
unoptimized
className="object-cover z-0"
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
/>
) : null} ) : null}
<div className="absolute inset-0 z-10 bg-black transition-opacity" style={{ opacity: overlayOpacity / 100 }}></div> <div className="absolute inset-0 z-10 bg-black transition-opacity" style={{ opacity: overlayOpacity / 100 }}></div>
+1
View File
@@ -444,6 +444,7 @@ export const HeroVideoSectionPayloadSchema = z.object({
loop: z.boolean().optional().default(true), loop: z.boolean().optional().default(true),
backgroundColor: z.string().optional(), backgroundColor: z.string().optional(),
textColor: z.string().optional(), textColor: z.string().optional(),
focalPoint: z.object({ x: z.string(), y: z.string() }).optional(),
}); });
export type HeroVideoSectionPayload = z.infer<typeof HeroVideoSectionPayloadSchema>; export type HeroVideoSectionPayload = z.infer<typeof HeroVideoSectionPayloadSchema>;