diff --git a/src/components/admin/advertisements/AdvertisementPreview.jsx b/src/components/admin/advertisements/AdvertisementPreview.jsx new file mode 100644 index 0000000..22a0314 --- /dev/null +++ b/src/components/admin/advertisements/AdvertisementPreview.jsx @@ -0,0 +1,97 @@ +// components/admin/advertisements/AdvertisementPreview.jsx + +import { ChevronLeft, ChevronRight, Megaphone } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Progress } from "@/components/ui/progress"; + +// ── AdvertisementPreview ───────────────────────────────────────────────────── +/** + * Live, presentational preview of a single ad slide as it will actually + * render inside the real Hero/Banner carousel (see components/generic/Blocks/ + * Client/Advertisements/{Hero,Banner}.jsx) — same image + gradient overlay + + * bottom-left badge/headline/description layout, just scaled down for the + * admin form. The prev/next/progress chrome below the slide is static (no + * real carousel behind it, since there's only ever one slide to preview) — + * it's there purely so the admin recognizes this as "inside a carousel." + * + * Props: + * format — "hero" | "banner", determines slide height/typography + * badgeLabels — string[] + * headline — string + * description — string + * imageSrc — resolved image URL, or null/undefined + */ +export function AdvertisementPreview({ format = "hero", badgeLabels, headline, description, imageSrc }) { + const isHero = format !== "banner"; + const labels = Array.isArray(badgeLabels) ? badgeLabels.filter(Boolean) : []; + + return ( +
+

+ Preview — this is how it will appear in the carousel. +

+ + + {imageSrc ? ( + {headline + ) : ( + + )} + +
+ +
+ {labels.length > 0 && ( +
+ {labels.map((label, i) => ( + + {label} + + ))} +
+ )} + {headline && ( +
+ {headline} +
+ )} + {description && ( +

+ {description} +

+ )} +
+ + + + {/* Static carousel chrome — communicates "this sits inside a carousel," not functional */} +
+
+ + +
+
+ +
+
+
+ ); +} + +function cnRounded(isHero) { + return "border overflow-hidden pl-0 py-0 " + (isHero ? "rounded-2xl" : "rounded-xl"); +} + +function cnHeight(isHero) { + return isHero ? "h-56" : "h-48"; +} diff --git a/src/components/generic/Blocks/Admin/AudioBlock.jsx b/src/components/generic/Blocks/Admin/AudioBlock.jsx index 15a51f3..aa02ba7 100644 --- a/src/components/generic/Blocks/Admin/AudioBlock.jsx +++ b/src/components/generic/Blocks/Admin/AudioBlock.jsx @@ -211,7 +211,7 @@ export function AudioBlock({ content, onUpdate, readOnly = false }) { }} /> ) : ( -
+
)}
@@ -291,7 +291,9 @@ export function AudioBlock({ content, onUpdate, readOnly = false }) { value={muted ? 0 : volume} onChange={handleVolume} aria-label="Volume" - className="w-16 h-1.5" + className="w-16 h-1.5 accent-primary cursor-pointer + [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:cursor-pointer + [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:size-3 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:cursor-pointer" />
diff --git a/src/components/generic/Blocks/Admin/VideoBlock.jsx b/src/components/generic/Blocks/Admin/VideoBlock.jsx index 8c711ca..02cbd08 100644 --- a/src/components/generic/Blocks/Admin/VideoBlock.jsx +++ b/src/components/generic/Blocks/Admin/VideoBlock.jsx @@ -31,6 +31,7 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) { const poster = thumbnailUrl ?? content?.thumbnail_url ?? undefined; const vidRef = useRef(null); + const wrapRef = useRef(null); const [playing, setPlaying] = useState(false); const [progress, setProgress] = useState(0); const [currentTime, setCurrentTime] = useState(0); @@ -38,7 +39,13 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) { const [muted, setMuted] = useState(false); const [volume, setVolume] = useState(1); const [overlayVisible,setOverlayVisible]= useState(true); - const [isFullscreen, setIsFullscreen] = useState(false); + // Native Fullscreen API works on desktop (all browsers) and Android Chrome. + // iOS Safari doesn't support Fullscreen API on arbitrary elements at all, so + // pseudoFullscreen is a CSS-only (fixed inset-0) fallback that keeps our + // custom controls instead of falling back to native