| import { useState } from 'react' |
| import { X, Smile } from 'lucide-react' |
| import clsx from 'clsx' |
|
|
| |
| |
| |
| |
| 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 = () => { |
| |
| 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 |
|
|