"use client"; import { useEffect, useMemo, useState, type CSSProperties, type ChangeEvent, } from "react"; import { getVideoAttributionText } from "@/lib/branding"; import { CURATED_FONTS, googleFontsStylesheetUrl, SYSTEM_FONT, type CuratedFontKey, type WatermarkFontKey, } from "@/lib/fonts"; import { curatedFontApiUrl, previewFontFamilyCss, } from "@/lib/preview-typography"; import { WATERMARK_OFFSET_MAX, WATERMARK_OFFSET_MIN, WATERMARK_POSITIONS, WATERMARK_TEXT_MAX, type WatermarkMode, type WatermarkPosition, type WatermarkSettings, } from "@/lib/watermark"; type Props = { enabled: boolean; locked: boolean; previewImageUrl: string | null; value: WatermarkSettings; onChange: (next: WatermarkSettings) => void; onUploadLogo: (file: File) => Promise; onUploadFont: (file: File) => Promise; logoPreviewUrl?: string | null; }; const POSITION_LABELS: Record = { "top-left": "Top left", "top-right": "Top right", "bottom-left": "Bottom left", "bottom-right": "Bottom right", center: "Center", }; const CUSTOM_PREVIEW_FAMILY = "S2VIDCustomWm"; function previewStyle( position: WatermarkPosition, offsetX: number, offsetY: number, ): CSSProperties { const base: CSSProperties = { position: "absolute", maxWidth: "32%", pointerEvents: "none", }; const ox = `${offsetX}px`; const oy = `${offsetY}px`; switch (position) { case "top-left": return { ...base, top: oy, left: ox }; case "top-right": return { ...base, top: oy, right: ox }; case "bottom-left": return { ...base, bottom: oy, left: ox }; case "center": return { ...base, top: "50%", left: "50%", transform: `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px))`, }; case "bottom-right": default: return { ...base, bottom: oy, right: ox }; } } function previewFontFamily(fontKey: WatermarkFontKey | undefined): string { return previewFontFamilyCss(fontKey); } export function WatermarkPreview({ enabled, locked, previewImageUrl, value, onChange, onUploadLogo, onUploadFont, logoPreviewUrl, }: Props) { const [logoUploading, setLogoUploading] = useState(false); const [logoError, setLogoError] = useState(null); const [fontUploading, setFontUploading] = useState(false); const [fontError, setFontError] = useState(null); const [customFontObjectUrl, setCustomFontObjectUrl] = useState(null); const overlay = useMemo( () => previewStyle(value.position, value.offsetX, value.offsetY), [value.position, value.offsetX, value.offsetY], ); const textFontStyle = useMemo( () => ({ fontFamily: previewFontFamily(value.fontKey) }), [value.fontKey], ); // Load bundled + curated fonts for live canvas preview (same files as FFmpeg) useEffect(() => { const styleId = "s2vid-watermark-preview-fonts"; let el = document.getElementById(styleId) as HTMLStyleElement | null; if (!el) { el = document.createElement("style"); el.id = styleId; document.head.appendChild(el); } el.textContent = [ `@font-face { font-family: '${SYSTEM_FONT.previewFamily}'; src: url('${curatedFontApiUrl(SYSTEM_FONT.key)}') format('truetype'); font-display: swap; }`, ...CURATED_FONTS.map( (f) => `@font-face { font-family: 'S2VIDPreview-${f.key}'; src: url('${curatedFontApiUrl(f.key)}') format('truetype'); font-display: swap; }`, ), ].join("\n"); }, []); // Legacy Google Fonts link (kept for any older preview paths) useEffect(() => { const id = "s2vid-watermark-google-fonts"; if (document.getElementById(id)) return; const link = document.createElement("link"); link.id = id; link.rel = "stylesheet"; link.href = googleFontsStylesheetUrl(CURATED_FONTS.map((f) => f.key)); document.head.appendChild(link); }, []); // @font-face for uploaded custom font (browser preview only) useEffect(() => { if (!customFontObjectUrl) return; const styleId = "s2vid-watermark-custom-font"; let el = document.getElementById(styleId) as HTMLStyleElement | null; if (!el) { el = document.createElement("style"); el.id = styleId; document.head.appendChild(el); } el.textContent = ` @font-face { font-family: '${CUSTOM_PREVIEW_FAMILY}'; src: url('${customFontObjectUrl}'); font-display: swap; }`; return () => { /* keep style until next custom font replaces it */ }; }, [customFontObjectUrl]); useEffect(() => { return () => { if (customFontObjectUrl) URL.revokeObjectURL(customFontObjectUrl); }; }, [customFontObjectUrl]); function patch(partial: Partial) { if (locked) return; onChange({ ...value, ...partial }); } async function handleLogo(e: ChangeEvent) { const file = e.target.files?.[0]; e.target.value = ""; if (!file || locked) return; setLogoError(null); setLogoUploading(true); try { const path = await onUploadLogo(file); patch({ mode: "logo", logoPath: path }); } catch (err) { setLogoError(err instanceof Error ? err.message : "Logo upload failed"); } finally { setLogoUploading(false); } } async function handleFont(e: ChangeEvent) { const file = e.target.files?.[0]; e.target.value = ""; if (!file || locked) return; setFontError(null); setFontUploading(true); try { const objectUrl = URL.createObjectURL(file); if (customFontObjectUrl) URL.revokeObjectURL(customFontObjectUrl); setCustomFontObjectUrl(objectUrl); const path = await onUploadFont(file); patch({ mode: "text", fontKey: "custom", fontPath: path }); } catch (err) { setFontError(err instanceof Error ? err.message : "Font upload failed"); } finally { setFontUploading(false); } } function setFontKey(key: WatermarkFontKey) { if (key === "custom") { patch({ fontKey: "custom", fontPath: value.fontPath ?? null }); return; } patch({ fontKey: key, fontPath: null }); } return (

Watermark layout

{previewImageUrl ? ( // eslint-disable-next-line @next/next/no-img-element ) : (
Upload a cover image to preview
)} {enabled && value.mode !== "none" && (
{value.mode === "logo" && logoPreviewUrl ? ( // eslint-disable-next-line @next/next/no-img-element ) : ( {value.mode === "text" && value.text?.trim() ? value.text.trim().slice(0, WATERMARK_TEXT_MAX) : getVideoAttributionText()} )}
)}
{( [ ["none", "No watermark"], ["default", "Songs2VID badge"], ["text", "Custom text"], ["logo", "PNG logo"], ] as const ).map(([mode, label]) => ( ))}
{value.mode === "text" && ( <> {(value.fontKey === "custom" || (value.fontKey && value.fontKey !== "system" && CURATED_FONTS.some((f) => f.key === (value.fontKey as CuratedFontKey)))) && (

Preview: The quick brown fox jumps over the lazy dog

)} {value.fontKey === "custom" && (
void handleFont(e)} className="block w-full text-sm text-gray-300 file:mr-4 file:rounded file:border-0 file:bg-accent file:px-4 file:py-2 file:text-sm file:text-white" /> {fontUploading && (

Uploading font…

)} {fontError &&

{fontError}

} {value.fontPath && !fontError && (

Custom font ready for render

)}
)} )} {value.mode === "logo" && (
void handleLogo(e)} className="block w-full text-sm text-gray-300 file:mr-4 file:rounded file:border-0 file:bg-accent file:px-4 file:py-2 file:text-sm file:text-white" /> {logoUploading &&

Uploading logo…

} {logoError &&

{logoError}

}
)}

Position

{WATERMARK_POSITIONS.map((pos) => ( ))}
); }