clipon / frontend /src /components /CaptionEditor.jsx
yonagush
Fix YouTube DNS failure via Invidious proxy + retheme to chainstreet gold
a72d248
import { useState } from 'react'
import { X, Smile } from 'lucide-react'
import clsx from 'clsx'
/**
* Caption style editor modal
* Props: { isOpen, onClose, clipId, jobId }
*/
function CaptionEditor({ isOpen, onClose, clipId, jobId }) {
const [style, setStyle] = useState('highlight')
const [fontSize, setFontSize] = useState(48)
const [textColor, setTextColor] = useState('#FFFFFF')
const [highlightColor, setHighlightColor] = useState('#E8A020')
const [bgColor, setBgColor] = useState('#000000')
const [bgOpacity, setBgOpacity] = useState(0.5)
const [position, setPosition] = useState('bottom')
const [emoji, setEmoji] = useState(true)
const [language, setLanguage] = useState('en')
if (!isOpen) return null
const styles = [
{ id: 'highlight', label: 'Highlight Word', icon: '✨' },
{ id: 'box', label: 'Box', icon: '📦' },
{ id: 'outline', label: 'Outline', icon: '⭕' },
{ id: 'bold', label: 'Bold', icon: '𝗕' },
{ id: 'none', label: 'None', icon: '—' },
]
const positions = [
{ id: 'top', label: 'Top', pos: 'top-4' },
{ id: 'middle', label: 'Middle', pos: 'top-1/2' },
{ id: 'bottom', label: 'Bottom', pos: 'bottom-4' },
]
const handleSave = () => {
// Mock API call
console.log({
style,
fontSize,
textColor,
highlightColor,
bgColor,
bgOpacity,
position,
emoji,
language,
})
onClose()
}
return (
<>
{/* Overlay */}
<div
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 animate-fade-in"
onClick={onClose}
/>
{/* Modal */}
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 animate-scale-in">
<div className="glass-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto rounded-3xl">
{/* Header */}
<div className="flex items-center justify-between p-8 border-b border-white/10 sticky top-0 bg-dark-900/50 backdrop-blur">
<h2 className="text-2xl font-bold text-white">Edit Captions</h2>
<button
onClick={onClose}
className="p-2 hover:bg-white/10 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-white/70" />
</button>
</div>
<div className="p-8 space-y-8">
{/* Caption Style */}
<div>
<label className="block text-sm font-semibold text-white mb-4">Style</label>
<div className="grid grid-cols-5 gap-3">
{styles.map((s) => (
<button
key={s.id}
onClick={() => setStyle(s.id)}
className={clsx(
'p-4 rounded-xl text-center font-semibold transition-all flex flex-col items-center gap-2',
style === s.id
? 'glass-lg bg-primary-500/20 border-primary-500/50 shadow-glow'
: 'glass hover:bg-white/10'
)}
>
<span className="text-2xl">{s.icon}</span>
<span className="text-xs text-white/70">{s.label}</span>
</button>
))}
</div>
</div>
{/* Font Size */}
<div>
<div className="flex justify-between items-center mb-3">
<label className="text-sm font-semibold text-white">Font Size</label>
<span className="text-2xl font-bold text-primary-500">{fontSize}px</span>
</div>
<input
type="range"
min="24"
max="80"
value={fontSize}
onChange={(e) => setFontSize(parseInt(e.target.value))}
className="w-full h-2 bg-white/10 rounded-full accent-primary-500"
/>
</div>
{/* Colors */}
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-semibold text-white mb-3">Text Color</label>
<div className="flex gap-3">
<input
type="color"
value={textColor}
onChange={(e) => setTextColor(e.target.value)}
className="w-16 h-10 rounded-lg cursor-pointer"
/>
<input
type="text"
value={textColor}
onChange={(e) => setTextColor(e.target.value)}
className="input-field flex-1 text-sm font-mono"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-white mb-3">Highlight Color</label>
<div className="flex gap-3">
<input
type="color"
value={highlightColor}
onChange={(e) => setHighlightColor(e.target.value)}
className="w-16 h-10 rounded-lg cursor-pointer"
/>
<input
type="text"
value={highlightColor}
onChange={(e) => setHighlightColor(e.target.value)}
className="input-field flex-1 text-sm font-mono"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-white mb-3">Background</label>
<div className="flex gap-3">
<input
type="color"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
className="w-16 h-10 rounded-lg cursor-pointer"
/>
<input
type="text"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
className="input-field flex-1 text-sm font-mono"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-white mb-3">
Background Opacity: {Math.round(bgOpacity * 100)}%
</label>
<input
type="range"
min="0"
max="1"
step="0.1"
value={bgOpacity}
onChange={(e) => setBgOpacity(parseFloat(e.target.value))}
className="w-full h-2 bg-white/10 rounded-full accent-primary-500"
/>
</div>
</div>
{/* Position */}
<div>
<label className="block text-sm font-semibold text-white mb-4">Position</label>
<div className="grid grid-cols-3 gap-3">
{positions.map((p) => (
<button
key={p.id}
onClick={() => setPosition(p.id)}
className={clsx(
'p-4 rounded-xl font-semibold transition-all',
position === p.id
? 'glass-lg bg-primary-500/20 border-primary-500/50'
: 'glass hover:bg-white/10'
)}
>
{p.label}
</button>
))}
</div>
</div>
{/* Language */}
<div>
<label className="block text-sm font-semibold text-white mb-3">Language</label>
<select
value={language}
onChange={(e) => setLanguage(e.target.value)}
className="input-field-lg w-full bg-white/5"
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="it">Italian</option>
<option value="pt">Portuguese</option>
<option value="ja">Japanese</option>
<option value="zh">Chinese</option>
<option value="ko">Korean</option>
</select>
</div>
{/* Emoji Captions */}
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={emoji}
onChange={(e) => setEmoji(e.target.checked)}
className="w-4 h-4 rounded accent-primary-500"
/>
<span className="text-sm font-semibold text-white flex items-center gap-2">
<Smile className="w-4 h-4" />
Include Emoji Captions
</span>
</label>
{/* Preview */}
<div className="glass-lg p-8 rounded-2xl">
<p className="text-xs text-white/60 mb-4">Preview</p>
<div className="aspect-9-16 bg-dark-850 rounded-xl flex items-end justify-center overflow-hidden relative">
{/* Phone frame mockup */}
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-dark-900 pointer-events-none" />
{/* Caption preview */}
<div
className={`${position === 'bottom' ? 'mb-8' : position === 'middle' ? 'my-auto' : 'mt-8'} px-4 text-center transition-all`}
style={{
fontSize: `${fontSize / 2}px`,
color: textColor,
}}
>
<p className="font-bold">Sample caption text</p>
{emoji && <p>✨ 🎬 🔥</p>}
</div>
</div>
</div>
{/* Actions */}
<div className="flex gap-3">
<button onClick={onClose} className="btn-secondary-lg flex-1">
Cancel
</button>
<button onClick={handleSave} className="btn-primary-lg flex-1">
Apply Changes
</button>
</div>
</div>
</div>
</div>
</>
)
}
export default CaptionEditor