const SAMPLE_SCRIPT = `The year is 1977. NASA launches two spacecraft from Cape Canaveral, each carrying a golden record etched with the sounds of Earth — heartbeats, thunder, birdsong, greetings in fifty-five languages.

Voyager 1 and Voyager 2 are not coming back. They are bottle messages, hurled into the dark on the slim chance that, in some distant epoch, another civilization picks them up.

Forty-seven years later, they are still transmitting.`;

const SAMPLE_SCRIPT_VI = `Năm 1977. NASA phóng hai tàu vũ trụ từ Cape Canaveral, mỗi tàu mang theo một chiếc đĩa vàng khắc ghi những âm thanh của Trái Đất — tiếng nhịp tim, tiếng sấm, tiếng chim hót và những lời chào bằng 55 ngôn ngữ khác nhau.

Voyager 1 và Voyager 2 sẽ không quay trở lại. Chúng là những bức thư gửi trong chai, được thả vào bóng tối vũ trụ với hy vọng mong manh rằng, trong một kỷ nguyên xa xôi nào đó, một nền văn minh khác sẽ nhận được.

Bốn mươi bảy năm sau, chúng vẫn đang tiếp tục truyền tín hiệu.`;

const TXT_INPUT = {
  vi: {
    language: "Ngôn ngữ",
    style: "Phong cách",
    templates: "Mẫu",
    videoSettings: "Cài đặt Video",
    aspect: "Tỷ lệ",
    resolution: "Độ phân giải",
    scenes: "Số phân cảnh",
    voiceover: "Giọng đọc",
    subtitles: "Phụ đề",
    pacing: "Tốc độ",
    burnCaptions: "Chèn phụ đề trực tiếp vào video",
    llmDecides: "LLM tự quyết định",
    showLess: "Thu gọn",
    showAll: "Xem tất cả ({count})",
    hoverTemplate: "Rê chuột vào mẫu để xem thử chuyển động",
    project: "Dự án",
    uploadsTitle: "Tài nguyên của bạn",
    uploadsSub: "Tải lên ảnh tham chiếu",
    optional: "không bắt buộc",
    addImagesText: "+ Thêm hình ảnh",
    addMoreText: "+ Thêm tiếp",
    uploadsDescHint: "Mô tả hình ảnh (ví dụ: 'Ảnh chân dung CEO', 'Ảnh sản phẩm')",
    uploadsExpl: "Agent kịch bản sẽ đọc từng mô tả và tự động lồng ghép hình ảnh vào phân cảnh phù hợp. Bỏ qua nếu bạn không muốn dùng ảnh cá nhân.",
    pipeline: "Tiến trình",
    pipelineSteps: "Phân tích · Phác thảo · Biên tập · Xuất video",
    wallTime: "~{time}s thời gian thực tế",
    generateVideo: "Tạo Video",
    confirmTitle: "Bắt đầu tạo video?",
    confirmDesc: "Quy trình sẽ gọi LLM, tải tài nguyên, render Remotion — quá trình này có thể mất vài phút và tốn tài nguyên.",
    cancel: "Hủy",
    start: "Bắt đầu",
    words: "Số từ",
    estLength: "Độ dài ước tính",
    wordsCount: "{count} từ",
    charsCount: "{count} ký tự",
    estTime: "~{min}:{sec} ước tính",
    scriptMode: "Kịch bản",
    articleMode: "Đường dẫn bài viết",
    audioMode: "Âm thanh",
    customPrompt: "Yêu cầu tùy chỉnh",
    customPromptSub: "Gửi trực tiếp đến Agent 1",
    customPromptPlh: "Hướng dẫn bổ sung về phong cách, giọng điệu, cấu trúc, hình ảnh, bảng màu, tốc độ...",
    customStyle: "Mô tả phong cách",
    customStyleSub: "Áp dụng cho toàn bộ video",
    customStylePlh: "Mô tả phong cách hình ảnh: màu sắc, kiểu chữ, hiệu ứng chuyển cảnh, bố cục, tông màu... Ví dụ: Nền tối với điểm nhấn neon xanh, chữ sans-serif đậm, hiệu ứng fade mượt mà.",
    imageStyle: "Phong cách ảnh AI",
    imageStyleSub: "Áp dụng khi gen ảnh bằng AI",
    imageStylePlh: "Ví dụ: 3D render, cinematic, watercolor, anime...",
    templatesOnly: "Chỉ dùng các mẫu bên dưới",
    templatesOnlyHint: "Bắt buộc Agent 1 phải chọn template cho mọi scene (không cho custom-from-scratch)",
    aiWrite: "Viết bằng AI",
    aiWriting: "Đang viết...",
    aiWriteFail: "Không tạo được phong cách. Thử lại.",
    aiIdeaPlh: "Nhập ý tưởng phong cách (vd: cyberpunk neon tối, năng động)...",
    articleUrl: "Đường dẫn bài viết",
    urlPlaceholder: "Dán URL để trích xuất nội dung bài viết...",
    dropAudio: "Kéo thả tệp .mp3, .wav, hoặc .m4a",
    whisperDesc: "Whisper-large tự động chuyển giọng nói thành kịch bản · tối đa 25 phút",
    browseFiles: "Duyệt tệp",
    uploadBtn: "Tải lên",
    trySample: "Dùng thử mẫu",
    clear: "Xóa",
    autoSaved: "Đã tự động lưu",
    relaxed: "THƯ THẢ",
    snappy: "NHANH",
    scriptPlaceholder: "Dán kịch bản, lời thuyết minh hoặc nội dung bài viết. Hoặc kéo thả tệp .txt / .md / .srt.",
    dropFileText: "Thả tệp để tải",
  },
  en: {
    language: "Language",
    style: "Style",
    templates: "Templates",
    videoSettings: "Video Settings",
    aspect: "Aspect",
    resolution: "Resolution",
    scenes: "Scenes",
    voiceover: "Voiceover",
    subtitles: "Subtitles",
    pacing: "Pacing",
    burnCaptions: "Burn captions directly into the video",
    llmDecides: "LLM decides",
    showLess: "Show less",
    showAll: "Show all ({count})",
    hoverTemplate: "Hover a template to preview its motion",
    project: "Project",
    uploadsTitle: "Your assets",
    uploadsSub: "Upload reference images",
    optional: "optional",
    addImagesText: "+ Add images",
    addMoreText: "+ Add more",
    uploadsDescHint: "Describe this image (e.g. 'CEO portrait', 'product hero shot')",
    uploadsExpl: "The script agent will read each description and choose whether to weave the image into a fitting scene. Skip this entirely if you don't want to use your own photos.",
    pipeline: "Pipeline",
    pipelineSteps: "Analyze · Storyboard · Edit · Render",
    wallTime: "~{time}s wall time",
    generateVideo: "Generate Video",
    confirmTitle: "Generate Video?",
    confirmDesc: "The pipeline will call LLM, fetch media, and render via Remotion — this may take a few minutes and consume resources.",
    cancel: "Cancel",
    start: "Start",
    words: "Words",
    estLength: "Est. length",
    wordsCount: "{count} words",
    charsCount: "{count} chars",
    estTime: "~{min}:{sec} est.",
    scriptMode: "Script",
    articleMode: "Article URL",
    audioMode: "Audio",
    customPrompt: "Custom Prompt",
    customPromptSub: "Sent directly to Agent 1",
    customPromptPlh: "Extra instructions for style, tone, structure, media, color palette, pacing...",
    customStyle: "Style description",
    customStyleSub: "Applied to the whole video",
    customStylePlh: "Describe the visual style: colors, typography, transitions, layout, mood... e.g. Dark background with neon-blue accents, bold sans-serif, smooth fade transitions.",
    imageStyle: "AI Image Style",
    imageStyleSub: "Applied when generating AI images",
    imageStylePlh: "e.g. 3D render, cinematic, watercolor, anime...",
    templatesOnly: "Use only the templates below",
    templatesOnlyHint: "Force Agent 1 to pick a template for every scene (no fully-custom scenes)",
    aiWrite: "Write with AI",
    aiWriting: "Writing...",
    aiWriteFail: "Could not generate a style. Try again.",
    aiIdeaPlh: "Type a style idea (e.g. dark cyberpunk neon, energetic)...",
    articleUrl: "Article URL",
    urlPlaceholder: "https://example.com/article",
    dropAudio: "Drop .mp3, .wav, or .m4a",
    whisperDesc: "Whisper-large transcribes to script · max 25 min",
    browseFiles: "Browse files",
    uploadBtn: "Upload",
    trySample: "Try sample",
    clear: "Clear",
    autoSaved: "Auto-saved",
    relaxed: "RELAXED",
    snappy: "SNAPPY",
    scriptPlaceholder: "Paste a script, narration, or article body. Or drop a .txt / .md / .srt file.",
    dropFileText: "Drop to load file",
  }
};

const TEMPLATE_TRANSLATIONS = {
  vi: {
    "FullImageScene": {
      name: "Ảnh toàn màn hình",
      description: "Một ảnh toàn màn hình với hiệu ứng Ken Burns, tùy chọn tiêu đề."
    },
    "CenterImageScene": {
      name: "Ảnh căn giữa",
      description: "Ảnh nằm giữa (tròn hoặc chữ nhật) với hiệu ứng lia máy chậm, tùy chọn nhãn."
    },
    "ImageGridScene": {
      name: "Lưới ảnh",
      description: "2-4 ảnh xếp cùng nhau (4 ảnh thành lưới 2x2), nhãn tùy chọn; hiệu ứng vào (lật/trượt/zoom/mờ) do agent chọn."
    },
    "ImageTextScene": {
      name: "Ảnh + chữ",
      description: "Một ảnh một bên + cột chữ (tiêu đề và nội dung) bên kia."
    },
    "BigStatScene": {
      name: "Số liệu lớn",
      description: "Một con số khổng lồ chạy từ 0 kèm nhãn — không có media."
    },
    "QuoteScene": {
      name: "Trích dẫn",
      description: "Trích dẫn lớn căn giữa kèm tên người nói — không có media."
    },
    "BulletListScene": {
      name: "Danh sách",
      description: "Danh sách dọc hiện từng dòng một, đồng bộ với lời thoại — không có media."
    },
    "CardGridScene": {
      name: "Lưới thẻ",
      description: "Một hàng thẻ kính mờ hoạt họa xuất hiện so le."
    },
    "TextOnlyScene": {
      name: "Chỉ có văn bản",
      description: "Tiêu đề hoạt họa kèm phụ đề tùy chọn trên nền giấy, không có hình ảnh."
    },
    "VideoBackgroundScene": {
      name: "Video nền",
      description: "Video toàn màn hình không có lớp phủ."
    },
    "RankingScrollScene": {
      name: "Cuộn xếp hạng",
      description: "Dải cuộn ngang gồm các cột xếp hạng để so sánh/xếp hạng."
    }
  },
  en: {
    "FullImageScene": {
      name: "Full Image",
      description: "One fullscreen image with Ken Burns motion, optional title."
    },
    "CenterImageScene": {
      name: "Center Image",
      description: "Image centered (circle or rectangle) with a slow pan, optional badge."
    },
    "ImageGridScene": {
      name: "Image Grid",
      description: "2-4 images laid out together (4 becomes a 2x2 grid), optional labels; entrance effect chosen by the agent."
    },
    "ImageTextScene": {
      name: "Image + Text",
      description: "One image on one side and a heading + body text column on the other."
    },
    "BigStatScene": {
      name: "Big Stat",
      description: "One giant number that counts up from 0, plus a label — no media."
    },
    "QuoteScene": {
      name: "Quote",
      description: "Large centered pull-quote with author attribution — no media."
    },
    "BulletListScene": {
      name: "Bullet List",
      description: "Vertical list whose rows reveal one-by-one, synced to narration — no media."
    },
    "CardGridScene": {
      name: "Card Grid",
      description: "A row of animated glass cards with staggered pop-in."
    },
    "TextOnlyScene": {
      name: "Text Only",
      description: "Animated headline plus optional subtitle on the paper background, no media."
    },
    "VideoBackgroundScene": {
      name: "Video Background",
      description: "Fullscreen video with zero overlays."
    },
    "RankingScrollScene": {
      name: "Ranking Scroll",
      description: "Horizontal scrolling strip of ranked columns for ranking/comparison videos."
    }
  }
};

const translateStylePreset = (s, lang) => {
  if (lang !== 'vi') return s;
  const translations = {
    presentation: {
      name: "Trình chiếu",
      sub: "Slide tối giản, texture giấy nhám",
      chips: ["Slide", "Giấy nhám"]
    },
    analytics: {
      name: "Phân tích",
      sub: "Biểu đồ, trực quan hóa dữ liệu chuẩn McKinsey",
      chips: ["Biểu đồ", "Đơn sắc"]
    },
    kids: {
      name: "Trẻ em",
      sub: "Hoạt họa sinh động, nhiều màu sắc",
      chips: ["Năng động", "Nhãn dán"]
    },
    fintech: {
      name: "Công nghệ tài chính",
      sub: "Bảng giao dịch, biểu đồ nến",
      chips: ["Mã chứng khoán", "Biểu đồ nến"]
    },
    documentary: {
      name: "Phim tài liệu",
      sub: "Cảm giác phim tài liệu điện ảnh",
      chips: ["Màu phim", "Nhạc nền"]
    },
    news: {
      name: "Tin tức",
      sub: "Đồ họa truyền hình, phần ba dưới màn hình",
      chips: ["Lower-3rd", "Tin chạy"]
    },
    social_short: {
      name: "Video ngắn MXH",
      sub: "Mở đầu giật gân, định dạng dọc",
      chips: ["Hook ấn tượng", "B-roll"]
    },
    education: {
      name: "Giáo dục",
      sub: "Bài giảng giải thích rõ ràng, trực quan",
      chips: ["Sơ đồ", "Các bước"]
    },
    cinematic: {
      name: "Điện ảnh",
      sub: "Góc quay rộng, lia máy chậm, nhạc kịch tính",
      chips: ["Khung hình rộng", "Hạt nhiễu"]
    },
    tech_startup: {
      name: "Khởi nghiệp công nghệ",
      sub: "Hiện đại, chuyển sắc phong phú, hiệu ứng kính",
      chips: ["Chuyển sắc", "Kính mờ"]
    },
    luxury: {
      name: "Sang trọng",
      sub: "Cao cấp, chữ serif có chân, điểm nhấn vàng",
      chips: ["Chữ có chân", "Sắc vàng"]
    },
    podcast: {
      name: "Podcast",
      sub: "Dạng sóng âm, thẻ trích dẫn",
      chips: ["Sóng âm", "Trích dẫn"]
    },
    report: {
      name: "Báo cáo",
      sub: "Trực quan hóa doanh nghiệp, dashboard KPI",
      chips: ["Bảng biểu", "KPI"]
    },
    infographic: {
      name: "Đồ họa thông tin",
      sub: "Minh họa phẳng, cuộn xếp hạng",
      chips: ["Xếp hạng", "Nghệ thuật phẳng"]
    },
    custom: {
      name: "Tùy chỉnh",
      sub: "Tự mô tả phong cách, chọn mẫu",
      chips: ["Tự do", "Của bạn"]
    }
  };
  const t = translations[s.id];
  return t ? { ...s, name: t.name, sub: t.sub, chips: t.chips } : s;
};

const VOICEOVER_LABELS = {
  vi: {
    "narrator-warm": "Người dẫn chuyện — giọng nam trầm ấm",
    "narrator-clear": "Người dẫn chuyện — giọng nữ cao rõ ràng",
    "doc-reserved": "Phim tài liệu — trang trọng",
    "creator-bright": "Nhà sáng tạo — tươi vui, nhanh",
    "off": "Không thuyết minh",
  },
  en: {
    "narrator-warm": "Narrator — warm baritone",
    "narrator-clear": "Narrator — clear alto",
    "doc-reserved": "Documentary — reserved",
    "creator-bright": "Creator — bright, fast",
    "off": "No voiceover",
  }
};

const POSITIONS_TRANS = {
  vi: {
    top: "Trên",
    middle: "Giữa",
    bottom: "Dưới",
  },
  en: {
    top: "Top",
    middle: "Mid",
    bottom: "Bottom",
  }
};

const STYLE_PRESETS = [
  { id: "presentation",  name: "Presentation",  sub: "Minimal slides, paper texture",     chips: ["Slides", "Paper"],     swatch: ["#FFFFFF", "#E8E2D5", "#222222"] },
  { id: "analytics",     name: "Analytics",     sub: "Charts, McKinsey-clean data viz",   chips: ["Charts", "Mono"],      swatch: ["#0D1117", "#F0F6FF", "#58A6FF"] },
  { id: "kids",          name: "Kids",          sub: "Bouncy, colorful, cartoon",         chips: ["Bouncy", "Stickers"],  swatch: ["#FFD700", "#FF69B4", "#87CEEB"] },
  { id: "fintech",       name: "Fintech",       sub: "Trading terminal, candlesticks",    chips: ["Tickers", "Candles"],  swatch: ["#060B14", "#00D4AA", "#FF4757"] },
  { id: "documentary",   name: "Documentary",   sub: "Cinematic documentary feel",        chips: ["LUT", "Score"],        swatch: ["#2b2118", "#7a5a3a", "#d6b87a"] },
  { id: "news",          name: "News",          sub: "Broadcast graphics, lower thirds",  chips: ["Lower-3rd", "Ticker"], swatch: ["#1a1a1a", "#d62828", "#ffffff"] },
  { id: "social_short",  name: "Social Short",  sub: "Punchy hook, vertical-ready",       chips: ["Hook", "B-roll"],      swatch: ["#0a0a0a", "#e63946", "#f4f1ec"] },
  { id: "education",     name: "Education",     sub: "Clear, lesson-style explainer",     chips: ["Diagrams", "Steps"],   swatch: ["#f5f5f0", "#1a73e8", "#fbbc04"] },
  { id: "cinematic",     name: "Cinematic",     sub: "Wide shots, slow pans, score",      chips: ["Anamorphic", "Grain"], swatch: ["#1e1813", "#7a5a3a", "#d6b87a"] },
  { id: "tech_startup",  name: "Tech Startup",  sub: "Modern, gradient-rich, glassy",     chips: ["Gradient", "Glass"],   swatch: ["#0f0f23", "#8b5cf6", "#06b6d4"] },
  { id: "luxury",        name: "Luxury",        sub: "Premium, serif type, gold accents", chips: ["Serif", "Gold"],       swatch: ["#0a0a0a", "#d4af37", "#ffffff"] },
  { id: "podcast",       name: "Podcast",       sub: "Waveform-driven, quote cards",      chips: ["Waveform", "Quote"],   swatch: ["#1a1a2e", "#e94560", "#fafafa"] },
  { id: "report",        name: "Report",        sub: "Corporate, KPI dashboard",          chips: ["Tables", "KPI"],       swatch: ["#ffffff", "#0a3d62", "#3498db"] },
  { id: "infographic",   name: "Infographic",   sub: "Flat illustration, ranking scroll",  chips: ["Ranking", "FlatArt"],  swatch: ["#FFFFFF", "#111111", "#F5A623"] },
  { id: "custom",        name: "Custom",        sub: "Describe your own style, pick templates", chips: ["Free-text", "Yours"], swatch: ["#6366F1", "#EC4899", "#F59E0B"] },
];

// Pre-filled scaffold for the custom-style textarea. Mirrors the field structure
// of the built-in style presets so users know what to describe. Seeded only when
// the user picks the "custom" style and the field is still empty.
const CUSTOM_STYLE_SCAFFOLD = {
  vi: "PHONG CÁCH: \nBỐ CỤC: \nMÀU SẮC: \nHIỆU ỨNG: \nCHUYỂN CẢNH: \nCHỮ: \nLIÊN TỤC: ",
  en: "STYLE: \nLAYOUT: \nCOLORS: \nEFFECTS: \nTRANSITIONS: \nTYPOGRAPHY: \nCONTINUITY: ",
};

const LANGUAGES = [
  { code: "Vietnamese",  label: "Tiếng Việt" },
  { code: "English",     label: "English" },
  { code: "Japanese",    label: "日本語" },
  { code: "Korean",      label: "한국어" },
  { code: "Chinese",     label: "中文" },
  { code: "Spanish",     label: "Español" },
];

const SUBTITLE_PRESETS = [
  { id: "studio",   name: "Studio",   text: "#FFFFFF", outline: "#000000", back: "#000000A0", highlight: "#FFD60A" },
  { id: "broadcast",name: "Broadcast",text: "#F4F1EC", outline: "#1B1815", back: "#1B181500", highlight: "#FF8A3D" },
  { id: "neon",     name: "Neon",     text: "#F8FAFF", outline: "#0A0E1A", back: "#0A0E1AB0", highlight: "#19E5BE" },
  { id: "minimal",  name: "Minimal",  text: "#FFFFFF", outline: "#00000000",back: "#00000000", highlight: "#FFFFFF" },
];

const POSITIONS = [
  { id: "top",    label: "Top" },
  { id: "middle", label: "Mid" },
  { id: "bottom", label: "Bottom" },
];

function hexToRgba(h) {
  if (!h) return "transparent";
  let hex = h.replace("#", "");
  if (hex.length === 6) hex += "FF";
  const r = parseInt(hex.slice(0, 2), 16);
  const g = parseInt(hex.slice(2, 4), 16);
  const b = parseInt(hex.slice(4, 6), 16);
  const a = parseInt(hex.slice(6, 8), 16) / 255;
  return `rgba(${r},${g},${b},${a})`;
}

// ── tiny UI primitives ──────────────────────────────────────────────

function Field({ label, hint, children, right }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}>
        <label style={{
          fontFamily: "var(--font-mono)",
          fontSize: 10.5,
          letterSpacing: "0.08em",
          textTransform: "uppercase",
          color: "var(--fg-2)",
          fontWeight: 500,
        }}>{label}</label>
        {right || (hint && <span style={{ fontSize: 11, color: "var(--fg-2)" }} className="mono">{hint}</span>)}
      </div>
      {children}
    </div>
  );
}

function Select({ value, onChange, options }) {
  return (
    <div style={{ position: "relative" }}>
      <select
        value={value}
        onChange={(e) => onChange(e.target.value)}
        style={{
          appearance: "none",
          width: "100%",
          background: "var(--bg-2)",
          border: "1px solid var(--line)",
          borderRadius: "var(--radius)",
          padding: "9px 32px 9px 12px",
          fontSize: 13,
          color: "var(--fg-0)",
          outline: "none",
          cursor: "pointer",
        }}
      >
        {options.map((o) => <option key={o.value} value={o.value} style={{ background: "var(--bg-2)" }}>{o.label}</option>)}
      </select>
      <svg width="10" height="10" viewBox="0 0 10 10" style={{
        position: "absolute", right: 12, top: "50%", transform: "translateY(-50%)",
        pointerEvents: "none", color: "var(--fg-2)"
      }}>
        <path d="M2 4 L5 7 L8 4" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
      </svg>
    </div>
  );
}

function NumberStepper({ value, onChange, min = 0, max = 99, step = 1, suffix, zeroLabel, editable }) {
  const showZeroLabel = zeroLabel && value === 0;
  const clamp = (v) => Math.max(min, Math.min(max, v));
  return (
    <div style={{
      display: "flex", alignItems: "center",
      background: "var(--bg-2)",
      border: "1px solid var(--line)",
      borderRadius: "var(--radius)",
      overflow: "hidden",
      height: 34,
    }}>
      <button
        onClick={() => onChange(Math.max(min, value - step))}
        style={{ width: 28, height: "100%", color: "var(--fg-2)", borderRight: "1px solid var(--line)" }}
      >−</button>
      {editable ? (
        <input
          type="number"
          min={min} max={max} step={step}
          value={value}
          onChange={(e) => {
            const raw = e.target.value;
            if (raw === "") { onChange(min); return; }
            const v = parseInt(raw, 10);
            if (!Number.isNaN(v)) onChange(clamp(v));
          }}
          style={{
            flex: 1, width: "100%", minWidth: 0, textAlign: "center",
            background: "transparent", border: 0, outline: 0,
            fontSize: 13, fontFamily: "var(--font-mono)", color: "var(--fg-0)",
            MozAppearance: "textfield",
          }}
        />
      ) : (
        <div style={{ flex: 1, textAlign: "center", fontSize: 13, fontFamily: "var(--font-mono)", color: "var(--fg-0)" }}>
          {showZeroLabel ? zeroLabel : <>{value}{suffix && <span style={{ color: "var(--fg-3)", marginLeft: 2 }}>{suffix}</span>}</>}
        </div>
      )}
      <button
        onClick={() => onChange(Math.min(max, value + step))}
        style={{ width: 28, height: "100%", color: "var(--fg-2)", borderLeft: "1px solid var(--line)" }}
      >+</button>
    </div>
  );
}

function Switch({ value, onChange }) {
  return (
    <button
      type="button"
      role="switch"
      aria-checked={value}
      onClick={() => onChange(!value)}
      style={{
        position: "relative",
        width: 34, height: 20, borderRadius: 999,
        background: value ? "var(--accent)" : "var(--bg-3)",
        border: "1px solid var(--line)",
        transition: "background .15s",
        cursor: "pointer",
        flexShrink: 0,
      }}
    >
      <span style={{
        position: "absolute", top: 1, left: value ? 15 : 1,
        width: 16, height: 16, borderRadius: 999,
        background: value ? "var(--accent-fg)" : "var(--fg-2)",
        transition: "left .15s",
      }}/>
    </button>
  );
}

function CollapsibleSection({ heading, defaultOpen = false, summary, children }) {
  const [open, setOpen] = React.useState(defaultOpen);
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: open ? 14 : 0 }}>
      <button
        type="button"
        onClick={() => setOpen((o) => !o)}
        style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          width: "100%",
          paddingBottom: 8,
          borderBottom: "1px solid var(--line)",
          background: "transparent",
          cursor: "pointer",
        }}
      >
        <h3 className="mono" style={{
          margin: 0, fontSize: 11, fontWeight: 600, letterSpacing: "0.12em",
          color: "var(--fg-1)", textTransform: "uppercase",
        }}>{heading}</h3>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {!open && summary && (
            <span className="mono" style={{ fontSize: 11, color: "var(--fg-2)" }}>{summary}</span>
          )}
          <svg width="10" height="10" viewBox="0 0 10 10" style={{
            transform: open ? "rotate(180deg)" : "rotate(0)",
            transition: "transform .15s",
            color: "var(--fg-2)",
          }}>
            <path d="M2 4 L5 7 L8 4" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </div>
      </button>
      {open && <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>{children}</div>}
    </div>
  );
}

function Segmented({ value, onChange, options }) {
  return (
    <div style={{
      display: "flex",
      background: "var(--bg-0)",
      border: "1px solid var(--line)",
      borderRadius: "var(--radius)",
      padding: 2,
      gap: 2,
    }}>
      {options.map((o) => (
        <button key={o.id}
          onClick={() => onChange(o.id)}
          style={{
            flex: 1,
            padding: "6px 8px",
            fontSize: 12,
            fontWeight: 500,
            color: value === o.id ? "var(--fg-0)" : "var(--fg-2)",
            background: value === o.id ? "var(--bg-3)" : "transparent",
            borderRadius: 4,
            transition: "background .12s, color .12s",
          }}>
          {o.label}
        </button>
      ))}
    </div>
  );
}

function ColorChip({ value, onChange, allowAlpha = true }) {
  const swatches = ["#FFFFFF", "#000000", "#FFD60A", "#FF8A3D", "#19E5BE", "#FF4D6D", "#7B61FF", "#3D8BFF"];
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);
  return (
    <div ref={ref} style={{ position: "relative" }}>
      <button
        onClick={() => setOpen((o) => !o)}
        style={{
          display: "flex", alignItems: "center", gap: 8,
          height: 28, padding: "0 8px 0 4px",
          background: "var(--bg-2)", border: "1px solid var(--line)",
          borderRadius: "var(--radius-sm)",
        }}
      >
        <span style={{
          display: "inline-block", width: 18, height: 18, borderRadius: 3,
          background: `linear-gradient(45deg, #555 25%, transparent 25%, transparent 75%, #555 75%) 0 0/8px 8px,
                       linear-gradient(45deg, #555 25%, transparent 25%, transparent 75%, #555 75%) 4px 4px/8px 8px,
                       #222`,
          position: "relative",
        }}>
          <span style={{ position: "absolute", inset: 0, background: hexToRgba(value), borderRadius: 3 }}/>
        </span>
        <span className="mono" style={{ fontSize: 11, color: "var(--fg-1)" }}>{value.toUpperCase()}</span>
      </button>
      {open && (
        <div style={{
          position: "absolute", top: "calc(100% + 6px)", right: 0, zIndex: 20,
          background: "var(--bg-2)", border: "1px solid var(--line)",
          borderRadius: "var(--radius)",
          padding: 10, boxShadow: "var(--shadow-2)",
          display: "flex", flexDirection: "column", gap: 8, width: 180,
        }}>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(8, 1fr)", gap: 4 }}>
            {swatches.map((s) => (
              <button key={s} onClick={() => { onChange(s + (allowAlpha ? "FF" : "")); setOpen(false); }}
                style={{ width: 18, height: 18, borderRadius: 3, background: s, border: "1px solid var(--line)" }}/>
            ))}
          </div>
          <input type="color"
            value={value.slice(0, 7)}
            onChange={(e) => onChange(e.target.value + (allowAlpha ? value.slice(7) || "FF" : ""))}
            style={{ width: "100%", height: 28, background: "transparent", border: "1px solid var(--line)", borderRadius: 4 }}
          />
          {allowAlpha && (
            <div>
              <div style={{ fontSize: 10, color: "var(--fg-3)", marginBottom: 4 }} className="mono">ALPHA</div>
              <input type="range" min="0" max="255"
                value={parseInt(value.slice(7) || "FF", 16)}
                onChange={(e) => {
                  const a = parseInt(e.target.value).toString(16).padStart(2, "0").toUpperCase();
                  onChange(value.slice(0, 7) + a);
                }}
                style={{ width: "100%" }}
              />
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ── source pane ─────────────────────────────────────────────────────

function SourcePane({ source, setSource, customPrompt, setCustomPrompt, sourceMode, setSourceMode, audioFile, setAudioFile, uiLang = "vi" }) {
  const t = TXT_INPUT[uiLang] || TXT_INPUT.vi;
  const [drag, setDrag] = React.useState(false);
  const fileRef = React.useRef(null);
  const audioRef = React.useRef(null);

  const pickAudio = (f) => {
    if (!f) return;
    const url = URL.createObjectURL(f);
    const el = document.createElement("audio");
    el.preload = "metadata";
    el.onloadedmetadata = () => {
      setAudioFile && setAudioFile({ file: f, name: f.name, duration: el.duration || 0 });
      URL.revokeObjectURL(url);
    };
    el.onerror = () => {
      setAudioFile && setAudioFile({ file: f, name: f.name, duration: 0 });
      URL.revokeObjectURL(url);
    };
    el.src = url;
  };

  const wordCount = source.trim() ? source.trim().split(/\s+/).length : 0;
  const charCount = source.length;
  const estSec = Math.max(0, Math.round(wordCount / 2.6));

  const onDrop = (e) => {
    e.preventDefault();
    setDrag(false);
    const f = e.dataTransfer.files?.[0];
    if (f && f.type.startsWith("text") || (f && /\.(txt|md|srt)$/i.test(f.name))) {
      const r = new FileReader();
      r.onload = () => setSource(String(r.result));
      r.readAsText(f);
    }
  };

  return (
    <section style={{
      background: "var(--bg-1)",
      border: "1px solid var(--line)",
      borderRadius: "var(--radius-lg)",
      display: "flex",
      flexDirection: "column",
      overflow: "hidden",
      minHeight: 0,
    }}>
      {/* tabbed header */}
      <header style={{
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
        padding: "10px 14px",
        borderBottom: "1px solid var(--line)",
        background: "var(--bg-1)",
      }}>
        <div style={{ display: "flex", gap: 4 }}>
          {[
            { id: "script", label: t.scriptMode },
            { id: "audio", label: t.audioMode || (uiLang === "vi" ? "Từ audio" : "From audio") },
          ].map((tab) => (
            <button key={tab.id}
              onClick={() => setSourceMode(tab.id)}
              style={{
                padding: "6px 10px",
                fontSize: 12.5,
                fontWeight: 500,
                color: sourceMode === tab.id ? "var(--fg-0)" : "var(--fg-2)",
                borderRadius: "var(--radius)",
                background: sourceMode === tab.id ? "var(--bg-3)" : "transparent",
              }}>
              {tab.label}
            </button>
          ))}
        </div>
        <div style={{ display: "flex", gap: 12, color: "var(--fg-2)", fontSize: 11 }} className="mono">
          <span>{t.wordsCount.replace("{count}", wordCount)}</span>
          <span>·</span>
          <span>{t.charsCount.replace("{count}", charCount)}</span>
          <span>·</span>
          <span style={{ color: "var(--accent)" }}>{t.estTime.replace("{min}", Math.floor(estSec/60)).replace("{sec}", String(estSec%60).padStart(2,"0"))}</span>
        </div>
      </header>

      {/* body */}
      {sourceMode === "script" && (
        <div
          onDragEnter={(e) => { e.preventDefault(); setDrag(true); }}
          onDragOver={(e) => e.preventDefault()}
          onDragLeave={() => setDrag(false)}
          onDrop={onDrop}
          style={{
            position: "relative",
            flex: 1,
            minHeight: 360,
            display: "flex",
            flexDirection: "column",
          }}
        >
          <textarea
            value={source}
            onChange={(e) => setSource(e.target.value)}
            placeholder={t.scriptPlaceholder}
            style={{
              flex: 1,
              width: "100%",
              padding: "20px 24px",
              background: "transparent",
              border: 0,
              outline: 0,
              resize: "none",
              fontSize: 15,
              lineHeight: 1.7,
              color: "var(--fg-0)",
              fontFamily: "var(--font-sans)",
            }}
          />
          {drag && (
            <div style={{
              position: "absolute", inset: 8,
              border: "2px dashed var(--accent)",
              borderRadius: "var(--radius)",
              background: "var(--accent-soft)",
              display: "flex", alignItems: "center", justifyContent: "center",
              color: "var(--accent)",
              fontWeight: 500,
              pointerEvents: "none",
            }}>{t.dropFileText}</div>
          )}

          <div style={{
            padding: "14px 16px",
            borderTop: "1px solid var(--line)",
            background: "var(--bg-0)",
            display: "flex",
            flexDirection: "column",
            gap: 8,
          }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 10 }}>
              <label className="mono" style={{ fontSize: 11, color: "var(--fg-2)", letterSpacing: "0.08em", textTransform: "uppercase" }}>
                {t.customPrompt}
              </label>
              <span className="mono" style={{ fontSize: 11, color: "var(--fg-3)" }}>
                {t.customPromptSub}
              </span>
            </div>
            <textarea
              value={customPrompt}
              onChange={(e) => setCustomPrompt(e.target.value)}
              placeholder={t.customPromptPlh}
              style={{
                width: "100%",
                minHeight: 82,
                padding: "10px 12px",
                background: "var(--bg-2)",
                border: "1px solid var(--line)",
                borderRadius: "var(--radius)",
                outline: 0,
                resize: "vertical",
                fontSize: 13,
                lineHeight: 1.55,
                color: "var(--fg-0)",
                fontFamily: "var(--font-sans)",
              }}
            />
          </div>

          {/* footer toolbar */}
          <footer style={{
            display: "flex", alignItems: "center", justifyContent: "space-between",
            padding: "8px 12px",
            borderTop: "1px solid var(--line)",
            background: "var(--bg-0)",
          }}>
            <div style={{ display: "flex", gap: 4 }}>
              <input ref={fileRef} type="file" accept=".txt,.md,.srt,text/*" style={{ display: "none" }}
                onChange={(e) => {
                  const f = e.target.files?.[0]; if (!f) return;
                  const r = new FileReader(); r.onload = () => setSource(String(r.result)); r.readAsText(f);
                }}/>
              <ToolButton onClick={() => fileRef.current?.click()} icon="upload" label={t.uploadBtn}/>
              <ToolButton onClick={() => setSource(uiLang === "vi" ? SAMPLE_SCRIPT_VI : SAMPLE_SCRIPT)} icon="sparkles" label={t.trySample}/>
              <ToolButton onClick={() => setSource("")} icon="trash" label={t.clear}/>
            </div>
            <div style={{ display: "flex", gap: 10, fontSize: 11, color: "var(--fg-2)" }} className="mono">
              <span><span style={{ color: "var(--ok)" }}>●</span> {t.autoSaved}</span>
            </div>
          </footer>
        </div>
      )}

      {sourceMode === "article" && (
        <div style={{ padding: 24, display: "flex", flexDirection: "column", gap: 16, flex: 1 }}>
          <Field label={t.articleUrl} hint="https or arXiv">
            <input type="text" placeholder={t.urlPlaceholder}
              style={{
                background: "var(--bg-2)", border: "1px solid var(--line)",
                borderRadius: "var(--radius)", padding: "10px 12px",
                fontSize: 14, color: "var(--fg-0)", outline: 0,
              }}
            />
          </Field>
          <div style={{
            flex: 1, minHeight: 200,
            border: "1px dashed var(--line-strong)",
            borderRadius: "var(--radius)",
            display: "flex", alignItems: "center", justifyContent: "center",
            color: "var(--fg-3)", fontSize: 13,
            background: "repeating-linear-gradient(45deg, transparent, transparent 8px, var(--bg-2) 8px, var(--bg-2) 9px)",
          }}>
            <div style={{ textAlign: "center" }}>
              <div style={{ fontSize: 28, marginBottom: 8, color: "var(--fg-2)" }}>⤓</div>
              <div className="mono">{uiLang === "vi" ? "Dán URL để trích xuất nội dung bài viết" : "Paste a URL to scrape article body"}</div>
              <div className="mono" style={{ fontSize: 11, marginTop: 4, color: "var(--fg-3)" }}>{uiLang === "vi" ? "Trích xuất chế độ dễ đọc" : "readability-mode extraction"}</div>
            </div>
          </div>
        </div>
      )}

      {sourceMode === "audio" && (
        <div style={{ padding: 24, flex: 1, display: "flex", flexDirection: "column", gap: 14 }}>
          <input
            ref={audioRef}
            type="file"
            accept="audio/*"
            style={{ display: "none" }}
            onChange={(e) => pickAudio(e.target.files?.[0])}
          />
          <div
            onClick={() => audioRef.current && audioRef.current.click()}
            onDragEnter={(e) => { e.preventDefault(); setDrag(true); }}
            onDragOver={(e) => e.preventDefault()}
            onDragLeave={() => setDrag(false)}
            onDrop={(e) => { e.preventDefault(); setDrag(false); pickAudio(e.dataTransfer.files?.[0]); }}
            style={{
              flex: 1,
              border: `1px dashed ${drag ? "var(--accent)" : "var(--line-strong)"}`,
              borderRadius: "var(--radius-lg)",
              display: "flex", flexDirection: "column",
              alignItems: "center", justifyContent: "center",
              color: "var(--fg-2)",
              gap: 12,
              padding: 40,
              cursor: "pointer",
              background: drag ? "var(--accent-soft)" : "transparent",
            }}>
            <svg width="40" height="40" viewBox="0 0 40 40" fill="none">
              <rect x="14" y="8" width="12" height="20" rx="6" stroke="currentColor" strokeWidth="1.5"/>
              <path d="M10 22 a10 10 0 0 0 20 0 M20 32 v4" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round"/>
            </svg>
            {audioFile && audioFile.name ? (
              <>
                <div style={{ fontSize: 14, color: "var(--fg-0)", fontWeight: 500 }}>{audioFile.name}</div>
                <div className="mono" style={{ fontSize: 11, color: "var(--accent)" }}>
                  {audioFile.duration ? `${Math.floor(audioFile.duration / 60)}:${String(Math.round(audioFile.duration % 60)).padStart(2, "0")}` : ""}
                  {" · "}{uiLang === "vi" ? "Bấm để đổi file" : "Click to replace"}
                </div>
              </>
            ) : (
              <>
                <div style={{ fontSize: 14, color: "var(--fg-1)" }}>{t.dropAudio}</div>
                <div className="mono" style={{ fontSize: 11, color: "var(--fg-2)", textAlign: "center", maxWidth: 360 }}>
                  {uiLang === "vi"
                    ? "Video sẽ bám theo audio này (không tạo giọng đọc). Tự transcribe → chia phân cảnh theo nội dung."
                    : "The video follows this audio (no TTS). Auto-transcribed → scenes timed to the narration."}
                </div>
                <div style={{
                  marginTop: 8,
                  padding: "8px 14px",
                  background: "var(--bg-3)",
                  border: "1px solid var(--line-strong)",
                  borderRadius: "var(--radius)",
                  color: "var(--fg-0)", fontSize: 13, fontWeight: 500,
                }}>{t.browseFiles}</div>
              </>
            )}
          </div>

          <div style={{
            display: "flex",
            flexDirection: "column",
            gap: 8,
            marginTop: 4,
          }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 10 }}>
              <label className="mono" style={{ fontSize: 11, color: "var(--fg-2)", letterSpacing: "0.08em", textTransform: "uppercase" }}>
                {t.customPrompt}
              </label>
              <span className="mono" style={{ fontSize: 11, color: "var(--fg-3)" }}>
                {t.customPromptSub}
              </span>
            </div>
            <textarea
              value={customPrompt}
              onChange={(e) => setCustomPrompt(e.target.value)}
              placeholder={t.customPromptPlh}
              style={{
                width: "100%",
                minHeight: 82,
                padding: "10px 12px",
                background: "var(--bg-2)",
                border: "1px solid var(--line)",
                borderRadius: "var(--radius)",
                outline: 0,
                resize: "vertical",
                fontSize: 13,
                lineHeight: 1.55,
                color: "var(--fg-0)",
                fontFamily: "var(--font-sans)",
                boxSizing: "border-box",
              }}
            />
          </div>
        </div>
      )}
    </section>
  );
}

const ICONS = {
  upload: (<><path d="M8 11V3 M4.5 6.5 L8 3 L11.5 6.5" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/><path d="M3 11v2h10v-2" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round"/></>),
  sparkles: (<><path d="M8 3 L9 6 L12 7 L9 8 L8 11 L7 8 L4 7 L7 6 Z" fill="currentColor"/><circle cx="12.5" cy="3.5" r="0.7" fill="currentColor"/><circle cx="3.5" cy="12.5" r="0.7" fill="currentColor"/></>),
  trash: (<><path d="M3 4.5 H13 M6 4.5 V3 H10 V4.5 M5 4.5 L5.5 13 H10.5 L11 4.5" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>),
};
function ToolButton({ onClick, icon, label }) {
  return (
    <button onClick={onClick}
      style={{
        display: "flex", alignItems: "center", gap: 6,
        padding: "6px 10px",
        fontSize: 12,
        color: "var(--fg-1)",
        borderRadius: "var(--radius)",
        transition: "background .12s",
      }}
      onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-3)"}
      onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}
    >
      <svg width="14" height="14" viewBox="0 0 16 16">{ICONS[icon]}</svg>
      {label}
    </button>
  );
}

// ── animated template previews (pure CSS mockups, no real assets) ────

const TPL_KEYFRAMES = `
@keyframes tplKenburns { 0%{transform:scale(1.12) translate(6%,4%)} 100%{transform:scale(1.45) translate(-8%,-6%)} }
@keyframes tplZoomIn { 0%{transform:scale(1)} 100%{transform:scale(1.5)} }
@keyframes tplZoomOut { 0%{transform:scale(1.5)} 100%{transform:scale(1)} }
@keyframes tplSlideL { 0%{transform:translateX(-130%);opacity:0} 50%,100%{transform:translateX(0);opacity:1} }
@keyframes tplSlideR { 0%{transform:translateX(130%);opacity:0} 50%,100%{transform:translateX(0);opacity:1} }
@keyframes tplPop { 0%{transform:scale(.2);opacity:0} 65%{transform:scale(1.12);opacity:1} 100%{transform:scale(1)} }
@keyframes tplFadeUp { 0%{transform:translateY(10px);opacity:0} 60%,100%{transform:translateY(0);opacity:1} }
@keyframes tplRankIn { 0%{transform:translateX(170%) scale(.75);opacity:0} 70%{opacity:1} 88%{transform:translateX(-4%) scale(1.04)} 100%{transform:translateX(0) scale(1);opacity:1} }
@keyframes tplMarquee { 0%{transform:translateX(0)} 100%{transform:translateX(-250px)} }
@keyframes tplShimmer { 0%{background-position:0% 50%} 100%{background-position:200% 50%} }
@keyframes tplFlip { 0%{transform:rotateY(90deg);opacity:0} 55%{opacity:1} 100%{transform:rotateY(0);opacity:1} }
@keyframes tplFloat { 0%{transform:translateY(3px)} 100%{transform:translateY(-5px)} }
`;

const tplPill = (extra) => ({
  position: "absolute", padding: "2px 6px", fontSize: 8, fontWeight: 700,
  letterSpacing: "0.04em", color: "#fff", background: "rgba(0,0,0,0.55)",
  borderRadius: 3, fontFamily: "var(--font-mono)", ...extra,
});

function PreviewBox({ children, paper }) {
  return (
    <div style={{
      position: "relative", width: 208, height: 117, borderRadius: 6,
      overflow: "hidden", border: "1px solid var(--line)",
      background: paper ? "#EFE9DC" : "var(--bg-0)",
    }}>{children}</div>
  );
}

// A fake "photo" with clear landmarks (sun + hills) so that zoom / pan motion
// is actually perceivable — a flat gradient looks static when scaled.
function MockPhoto({ sky = ["#7dd3fc", "#fcd34d"], hills = ["#15803d", "#4ade80"], anim, round = 0 }) {
  return (
    <div style={{ position: "absolute", inset: 0, overflow: "hidden", borderRadius: round }}>
      <div style={{ position: "absolute", inset: 0, transformOrigin: "center", animation: anim }}>
        <div style={{ position: "absolute", inset: 0, background: `linear-gradient(180deg, ${sky[0]}, ${sky[1]})` }} />
        <div style={{ position: "absolute", top: "16%", right: "18%", width: "16%", aspectRatio: "1", borderRadius: "50%", background: "#fff7d6", boxShadow: "0 0 8px #fde68a" }} />
        <div style={{ position: "absolute", left: "-12%", bottom: 0, width: "72%", height: "58%", borderRadius: "50% 50% 0 0", background: hills[1] }} />
        <div style={{ position: "absolute", right: "-12%", bottom: 0, width: "78%", height: "44%", borderRadius: "50% 50% 0 0", background: hills[0] }} />
      </div>
    </div>
  );
}

function TemplateMockup({ id }) {
  switch (id) {
    case "FullImageScene":
      return (
        <PreviewBox>
          <MockPhoto anim="tplKenburns 3.5s ease-in-out infinite alternate" />
          <div style={tplPill({ top: 8, left: 8 })}>HEADLINE</div>
        </PreviewBox>
      );
    case "CenterImageScene":
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }}>
            <div style={{ position: "relative", width: 66, height: 66, borderRadius: "50%", overflow: "hidden", boxShadow: "0 4px 12px rgba(0,0,0,0.2)" }}>
              <MockPhoto sky={["#fb923c", "#fde68a"]} hills={["#9a3412", "#ea580c"]} anim="tplZoomOut 3s ease-in-out infinite alternate" round="50%" />
            </div>
          </div>
          <div style={tplPill({ bottom: 8, left: "50%", transform: "translateX(-50%)" })}>139x</div>
        </PreviewBox>
      );
    case "ImageGridScene": {
      const cards = [
        { sky: ["#93c5fd", "#e0f2fe"], hills: ["#1e3a8a", "#3b82f6"], label: "One" },
        { sky: ["#5eead4", "#fef9c3"], hills: ["#115e59", "#0d9488"], label: "Two" },
        { sky: ["#fca5a5", "#fde68a"], hills: ["#9a3412", "#ef4444"], label: "Three" },
      ];
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", top: 8, left: "50%", transform: "translateX(-50%)", fontSize: 9, fontWeight: 700, color: "#222", fontFamily: "var(--font-mono)", zIndex: 2 }}>TITLE</div>
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", gap: 7, padding: "28px 10px 8px", perspective: 600 }}>
            {cards.map((c, i) => (
              <div key={i} style={{ flex: 1, animation: `tplFloat 2.4s ease-in-out ${i * 0.2}s infinite alternate` }}>
                <div style={{ animation: `tplFlip .7s cubic-bezier(.2,.7,.3,1) ${i * 0.18}s both`, transformOrigin: "center", display: "flex", flexDirection: "column", gap: 3, alignItems: "center" }}>
                  <div style={{ position: "relative", width: "100%", height: 52, borderRadius: 5, overflow: "hidden", border: "1px solid rgba(0,0,0,0.12)", boxShadow: "0 4px 10px rgba(0,0,0,0.2)" }}>
                    <MockPhoto sky={c.sky} hills={c.hills} />
                  </div>
                  <div style={{ fontSize: 8, fontWeight: 700, color: "#222", fontFamily: "var(--font-mono)" }}>{c.label}</div>
                </div>
              </div>
            ))}
          </div>
        </PreviewBox>
      );
    }
    case "ImageTextScene":
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", gap: 10, padding: 12 }}>
            <div style={{ position: "relative", width: "46%", height: 74, borderRadius: 6, overflow: "hidden", boxShadow: "0 6px 14px rgba(0,0,0,0.18)", animation: "tplSlideL 2.8s ease-out infinite" }}>
              <MockPhoto sky={["#93c5fd", "#e0f2fe"]} hills={["#1e3a8a", "#3b82f6"]} anim="tplZoomIn 4s ease-in-out infinite alternate" />
            </div>
            <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 6, animation: "tplFadeUp 2.8s ease-out infinite" }}>
              <div style={{ width: "85%", height: 12, borderRadius: 3, background: "#222" }} />
              <div style={{ width: "100%", height: 7, borderRadius: 3, background: "rgba(0,0,0,0.3)" }} />
              <div style={{ width: "92%", height: 7, borderRadius: 3, background: "rgba(0,0,0,0.3)" }} />
              <div style={{ width: "70%", height: 7, borderRadius: 3, background: "rgba(0,0,0,0.3)" }} />
            </div>
          </div>
        </PreviewBox>
      );
    case "BigStatScene":
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 7 }}>
            <div style={{ fontFamily: "var(--font-mono)", fontWeight: 800, fontSize: 46, color: "var(--accent)", lineHeight: 1, animation: "tplPop 3s ease-out infinite" }}>87%</div>
            <div style={{ width: 90, height: 9, borderRadius: 3, background: "rgba(0,0,0,0.35)" }} />
          </div>
        </PreviewBox>
      );
    case "QuoteScene":
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 7, padding: "0 24px", animation: "tplFadeUp 3s ease-out infinite" }}>
            <div style={{ fontSize: 42, lineHeight: 0.6, color: "var(--accent)", opacity: 0.5, fontFamily: "Georgia, serif" }}>&ldquo;</div>
            <div style={{ width: "80%", height: 9, borderRadius: 3, background: "#222" }} />
            <div style={{ width: "62%", height: 9, borderRadius: 3, background: "#222" }} />
            <div style={{ width: 50, height: 6, borderRadius: 3, background: "rgba(0,0,0,0.4)", marginTop: 3 }} />
          </div>
        </PreviewBox>
      );
    case "BulletListScene":
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", top: 10, left: 14, fontSize: 9, fontWeight: 700, color: "#222", fontFamily: "var(--font-mono)" }}>TAKEAWAYS</div>
          <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", justifyContent: "center", gap: 9, padding: "24px 22px 10px" }}>
            {[0, 1, 2].map((i) => (
              <div key={i} style={{ display: "flex", alignItems: "center", gap: 8, animation: `tplSlideL 2.6s ease-out ${0.2 + i * 0.25}s infinite` }}>
                <div style={{ width: 9, height: 9, borderRadius: "50%", background: "var(--accent)", flexShrink: 0 }} />
                <div style={{ flex: 1, height: 8, borderRadius: 3, background: "rgba(0,0,0,0.3)" }} />
              </div>
            ))}
          </div>
        </PreviewBox>
      );
    case "CardGridScene":
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", top: 10, left: "50%", transform: "translateX(-50%)", fontSize: 9, fontWeight: 700, color: "#222", fontFamily: "var(--font-mono)" }}>STEPS</div>
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", gap: 8 }}>
            {[0, 1, 2].map((i) => (
              <div key={i} style={{
                width: 40, height: 48, borderRadius: 6,
                background: "rgba(255,255,255,0.9)", border: "1px solid rgba(0,0,0,0.12)",
                boxShadow: "0 2px 6px rgba(0,0,0,0.12)",
                animation: `tplPop 2.4s ease-out ${i * 0.3}s infinite`,
              }} />
            ))}
          </div>
        </PreviewBox>
      );
    case "TextOnlyScene":
      return (
        <PreviewBox paper>
          <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 8, animation: "tplFadeUp 3s ease-out infinite" }}>
            <div style={{ width: 120, height: 14, borderRadius: 3, background: "#222" }} />
            <div style={{ width: 80, height: 8, borderRadius: 3, background: "rgba(0,0,0,0.35)" }} />
          </div>
        </PreviewBox>
      );
    case "VideoBackgroundScene":
      return (
        <PreviewBox>
          <div style={{
            position: "absolute", inset: 0,
            background: "linear-gradient(90deg,#1e3a8a,#9333ea,#db2777,#1e3a8a)",
            backgroundSize: "200% 100%",
            animation: "tplShimmer 3s linear infinite",
          }} />
          <div style={{
            position: "absolute", top: "50%", left: "50%", transform: "translate(-50%,-50%)",
            width: 0, height: 0, borderLeft: "16px solid rgba(255,255,255,0.92)",
            borderTop: "11px solid transparent", borderBottom: "11px solid transparent",
          }} />
        </PreviewBox>
      );
    case "RankingScrollScene": {
      const cols = [
        { badge: "#ef4444", sky: ["#fca5a5", "#fde68a"], hills: ["#991b1b", "#ef4444"] },
        { badge: "#f59e0b", sky: ["#fdba74", "#fef3c7"], hills: ["#9a3412", "#f59e0b"] },
        { badge: "#10b981", sky: ["#6ee7b7", "#ecfccb"], hills: ["#065f46", "#10b981"] },
        { badge: "#3b82f6", sky: ["#93c5fd", "#e0f2fe"], hills: ["#1e3a8a", "#3b82f6"] },
        { badge: "#8b5cf6", sky: ["#c4b5fd", "#ede9fe"], hills: ["#5b21b6", "#8b5cf6"] },
      ];
      const seq = [...cols, ...cols]; // duplicate for a seamless right→left marquee
      return (
        <PreviewBox>
          <div style={{ position: "absolute", top: 6, left: 8, fontSize: 8, fontWeight: 700, color: "#fff", fontFamily: "var(--font-mono)", zIndex: 3 }}>RANKING</div>
          <div style={{
            position: "absolute", top: 22, bottom: 6, left: 6, display: "flex",
            animation: "tplMarquee 5s linear 1.3s infinite",
          }}>
            {seq.map((col, i) => {
              const first = i < cols.length;
              return (
                <div key={i} style={{
                  flex: "0 0 45px", marginRight: 5, position: "relative",
                  borderRadius: 5, overflow: "hidden", border: "1px solid rgba(255,255,255,0.15)",
                  animation: first ? "tplRankIn .6s cubic-bezier(.25,.9,.3,1) both" : "none",
                  animationDelay: first ? `${i * 0.15}s` : undefined,
                }}>
                  <MockPhoto sky={col.sky} hills={col.hills} />
                  <div style={{ position: "absolute", top: 3, left: 3, width: 15, height: 15, background: col.badge, transform: "rotate(45deg)", borderRadius: 2 }} />
                  <div style={{ position: "absolute", top: 4, left: 5, fontSize: 7, fontWeight: 800, color: "#fff", fontFamily: "var(--font-mono)", zIndex: 2 }}>{`#${(i % cols.length) + 1}`}</div>
                  <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, height: 11, background: "rgba(255,255,255,0.9)" }} />
                </div>
              );
            })}
          </div>
        </PreviewBox>
      );
    }
    default:
      return (
        <PreviewBox>
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, color: "var(--fg-3)" }}>No preview</div>
        </PreviewBox>
      );
  }
}

function TemplatePreview({ id }) {
  return (
    <>
      <style>{TPL_KEYFRAMES}</style>
      <TemplateMockup id={id} />
    </>
  );
}

// ── settings panes ──────────────────────────────────────────────────

function SettingsPane({ settings, setSettings, customStyle, setCustomStyle, uiLang = "vi" }) {
  const t = TXT_INPUT[uiLang] || TXT_INPUT.vi;
  const set = (k, v) => setSettings({ ...settings, [k]: v });
  const [enabledIds, setEnabledIds] = React.useState(null);
  const [templatePool, setTemplatePool] = React.useState([]);
  const [templateDefaults, setTemplateDefaults] = React.useState({});
  const [exclusiveGroup, setExclusiveGroup] = React.useState(null);
  const [showModal, setShowModal] = React.useState(false);
  const [aiWriting, setAiWriting] = React.useState(false);
  const [styleIdea, setStyleIdea] = React.useState("");

  const writeStyleWithAI = async () => {
    if (aiWriting) return;
    setAiWriting(true);
    try {
      const res = await fetch("/api/custom-style/generate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          // Prefer the dedicated idea box; fall back to whatever's in the textarea.
          brief: (styleIdea || "").trim() || customStyle || "",
          language: settings.language || "Vietnamese",
        }),
      });
      if (!res.ok) throw new Error(`API ${res.status}`);
      const data = await res.json();
      if (data.style) setCustomStyle(data.style);
    } catch (e) {
      alert(t.aiWriteFail + " (" + (e.message || e) + ")");
    } finally {
      setAiWriting(false);
    }
  };

  React.useEffect(() => {
    setShowModal(false);
  }, [settings.style]);

  React.useEffect(() => {
    fetch("/api/styles")
      .then((r) => r.json())
      .then((data) => {
        setEnabledIds(new Set(data.styles || []));
        setTemplatePool(data.pool || []);
        setTemplateDefaults(data.defaults || {});
        setExclusiveGroup(data.exclusive_group || null);
        // Seed the per-job selection from the current style's defaults if unset.
        if (settings.templates == null) {
          set("templates", (data.defaults || {})[settings.style] || []);
        }
      })
      .catch(() => {});
  }, []);

  // Effective selection — fall back to the style's defaults until the user edits it.
  const selectedTemplates = settings.templates || templateDefaults[settings.style] || [];
  const selectedSet = new Set(selectedTemplates);

  // Exclusivity / style-gating for the exclusive group (e.g. infographic's RankingScrollScene):
  // it is selectable only under the matching style, and when selected it is the sole template.
  const groupById = React.useMemo(
    () => Object.fromEntries(templatePool.map((t) => [t.id, t.group])),
    [templatePool]
  );
  const styleMatchesExclusive = !!exclusiveGroup && (settings.style || "").toLowerCase() === exclusiveGroup;
  const exclusiveSelected = selectedTemplates.some((id) => groupById[id] === exclusiveGroup);

  const isTemplateDisabled = (tpl) => {
    if (tpl.group === exclusiveGroup) return !styleMatchesExclusive; // gated to its style
    return exclusiveSelected; // a normal template is locked while an exclusive one is selected
  };

  const toggleTemplate = (id) => {
    const isExclusive = groupById[id] === exclusiveGroup;
    const cur = settings.templates || templateDefaults[settings.style] || [];
    if (isExclusive) {
      if (!styleMatchesExclusive) return; // cannot enable under a non-matching style
      // exclusive: toggling on makes it the sole selection
      set("templates", cur.includes(id) ? cur.filter((x) => x !== id) : [id]);
      return;
    }
    if (exclusiveSelected) return; // locked out while an exclusive template is selected
    set("templates", cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id]);
  };

  const activeTemplates = templatePool.map((tpl) => {
    const translation = TEMPLATE_TRANSLATIONS[uiLang]?.[tpl.id];
    return translation ? { ...tpl, name: translation.name, description: translation.description } : tpl;
  });
  const selectedCount = activeTemplates.filter((tpl) => selectedSet.has(tpl.id)).length;

  const displayStyles = (enabledIds
    ? STYLE_PRESETS.filter((s) => enabledIds.has(s.id))
    : STYLE_PRESETS).map(s => translateStylePreset(s, uiLang));
  const [showAllStyles, setShowAllStyles] = React.useState(false);
  const COLLAPSED_COUNT = 4;
  const activeIdx = displayStyles.findIndex((s) => s.id === settings.style);
  const forceExpand = activeIdx >= COLLAPSED_COUNT;
  const visibleStyles = (showAllStyles || forceExpand) ? displayStyles : displayStyles.slice(0, COLLAPSED_COUNT);
  const showToggle = displayStyles.length > COLLAPSED_COUNT && !forceExpand;

  const resoLabel = settings.resolution === "1080" ? "1080p" : "4K";
  const sceneLabel = (settings.numScenes ?? 0) === 0 
    ? (uiLang === "vi" ? "tự động phân cảnh" : "auto scenes") 
    : (uiLang === "vi" ? `${settings.numScenes} phân cảnh` : `${settings.numScenes} scene${settings.numScenes > 1 ? "s" : ""}`);
  const videoSummary = `${settings.aspect} · ${resoLabel} · ${sceneLabel} · ${settings.pacing.toFixed(2)}×`;

  return (
    <section style={{
      background: "var(--bg-1)",
      border: "1px solid var(--line)",
      borderRadius: "var(--radius-lg)",
      padding: 16,
      display: "flex", flexDirection: "column", gap: 18,
    }}>
      <SectionHeading>{t.project}</SectionHeading>

      <Field label={t.language}>
        <Select value={settings.language}
          onChange={(v) => set("language", v)}
          options={LANGUAGES.map(l => ({ value: l.code, label: l.label }))} />
      </Field>

      <Field label={t.style} right={showToggle && (
        <button
          type="button"
          onClick={() => setShowAllStyles((o) => !o)}
          style={{
            fontFamily: "var(--font-mono)",
            fontSize: 10.5,
            color: "var(--accent)",
            letterSpacing: "0.04em",
            textTransform: "uppercase",
            display: "flex", alignItems: "center", gap: 4,
            background: "transparent",
            cursor: "pointer",
          }}
        >
          {showAllStyles ? t.showLess : t.showAll.replace("{count}", displayStyles.length)}
          <svg width="9" height="9" viewBox="0 0 10 10" style={{
            transform: showAllStyles ? "rotate(180deg)" : "rotate(0)",
            transition: "transform .15s",
          }}>
            <path d="M2 4 L5 7 L8 4" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </button>
      )}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          {visibleStyles.map((s) => {
            const isSelected = settings.style === s.id;
            return (
              <button key={s.id}
                type="button"
                onClick={() => {
                  // Seed the custom-style scaffold on first pick (only when empty).
                  if (s.id === "custom" && !(customStyle || "").trim()) {
                    setCustomStyle(CUSTOM_STYLE_SCAFFOLD[uiLang] || CUSTOM_STYLE_SCAFFOLD.vi);
                  }
                  setSettings({ ...settings, style: s.id, templates: templateDefaults[s.id] || [], ...(s.defaultAspect ? { aspect: s.defaultAspect } : {}) });
                }}
                style={{
                  textAlign: "left",
                  padding: "12px 14px",
                  background: isSelected 
                    ? "linear-gradient(135deg, var(--accent-soft) 0%, var(--bg-3) 100%)" 
                    : "var(--bg-2)",
                  border: `1.5px solid ${isSelected ? "var(--accent)" : "var(--line)"}`,
                  borderRadius: "var(--radius)",
                  display: "flex", flexDirection: "column", gap: 10,
                  transition: "all .12s ease-in-out",
                  boxShadow: isSelected ? "0 4px 20px rgba(0,0,0,0.15), 0 0 0 1px var(--accent)" : "none",
                  cursor: "pointer",
                  width: "100%",
                }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
                  <div style={{ display: "flex", alignItems: "center", marginLeft: 4 }}>
                    {s.swatch.map((c, i) => (
                      <div key={i} style={{ 
                        width: 14, 
                        height: 14, 
                        borderRadius: "50%", 
                        background: c, 
                        border: "1.5px solid var(--bg-1)",
                        marginLeft: i > 0 ? -4 : 0,
                        boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
                        zIndex: 3 - i,
                      }}/>
                    ))}
                  </div>
                  {isSelected && (
                    <div style={{
                      width: 14, height: 14, borderRadius: "50%",
                      background: "var(--accent)", color: "var(--accent-fg)",
                      display: "flex", alignItems: "center", justifyContent: "center",
                      fontSize: 8, fontWeight: 700,
                    }}>✓</div>
                  )}
                </div>
                <div>
                  <div style={{ fontSize: 13.5, fontWeight: 700, color: isSelected ? "var(--fg-0)" : "var(--fg-1)" }}>{s.name}</div>
                  <div style={{ fontSize: 11, color: isSelected ? "var(--fg-2)" : "var(--fg-3)", marginTop: 4, lineHeight: 1.4 }}>{s.sub}</div>
                </div>
                <div style={{ display: "flex", gap: 4, flexWrap: "wrap", marginTop: "auto" }}>
                  {s.chips.slice(0, 2).map((c) => (
                    <span key={c} style={{
                      fontFamily: "var(--font-mono)",
                      fontSize: 9, padding: "2px 6px",
                      background: "var(--bg-0)",
                      border: "1px solid var(--line-soft)",
                      borderRadius: 4,
                      color: "var(--fg-2)",
                    }}>{c}</span>
                  ))}
                </div>
              </button>
            );
          })}
        </div>
      </Field>

      {settings.style === "custom" && (
        <Field
          label={t.customStyle}
          hint={<span style={{ fontSize: 11, color: "var(--fg-2)" }}>{t.customStyleSub}</span>}
        >
          {/* Idea box + AI writer: type a short idea, AI expands it into the full block below. */}
          <div style={{ display: "flex", gap: 6, marginBottom: 8 }}>
            <input
              type="text"
              value={styleIdea}
              onChange={(e) => setStyleIdea(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); writeStyleWithAI(); } }}
              placeholder={t.aiIdeaPlh}
              style={{
                flex: 1, minWidth: 0,
                padding: "8px 10px",
                background: "var(--bg-2)",
                border: "1px solid var(--line)",
                borderRadius: "var(--radius)",
                outline: 0,
                fontSize: 12.5,
                color: "var(--fg-0)",
                fontFamily: "var(--font-sans)",
              }}
            />
            <button
              type="button"
              onClick={writeStyleWithAI}
              disabled={aiWriting}
              style={{
                display: "flex", alignItems: "center", gap: 5, flexShrink: 0,
                padding: "0 12px",
                fontSize: 11.5, fontWeight: 600,
                color: aiWriting ? "var(--fg-3)" : "var(--accent-fg)",
                background: aiWriting ? "var(--bg-3)" : "var(--accent)",
                border: "none",
                borderRadius: "var(--radius)",
                cursor: aiWriting ? "default" : "pointer",
                whiteSpace: "nowrap",
              }}
            >
              <svg width="12" height="12" viewBox="0 0 16 16" style={aiWriting ? { animation: "spin 0.8s linear infinite" } : undefined}>
                {aiWriting
                  ? <path d="M8 2 a6 6 0 0 1 6 6" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round"/>
                  : <path d="M8 2 L9.2 5.6 L13 7 L9.2 8.4 L8 12 L6.8 8.4 L3 7 L6.8 5.6 Z M13 2 L13.6 3.4 L15 4 L13.6 4.6 L13 6 L12.4 4.6 L11 4 L12.4 3.4 Z" fill="currentColor"/>}
              </svg>
              {aiWriting ? t.aiWriting : t.aiWrite}
            </button>
          </div>
          <textarea
            value={customStyle}
            onChange={(e) => setCustomStyle(e.target.value)}
            placeholder={t.customStylePlh}
            rows={8}
            style={{
              width: "100%",
              padding: "10px 12px",
              background: "var(--bg-2)",
              border: "1px solid var(--accent)",
              borderRadius: "var(--radius)",
              outline: 0,
              resize: "vertical",
              fontSize: 13,
              lineHeight: 1.55,
              color: "var(--fg-0)",
              fontFamily: "var(--font-sans)",
              boxSizing: "border-box",
            }}
          />
        </Field>
      )}

      <Field
        label={t.imageStyle}
        hint={<span style={{ fontSize: 11, color: "var(--fg-2)" }}>{t.imageStyleSub}</span>}
      >
        <input
          type="text"
          value={settings.image_style || ""}
          onChange={(e) => set("image_style", e.target.value)}
          placeholder={t.imageStylePlh}
          style={{
            width: "100%",
            padding: "8px 10px",
            background: "var(--bg-2)",
            border: "1px solid var(--line)",
            borderRadius: "var(--radius)",
            outline: 0,
            fontSize: 12.5,
            color: "var(--fg-0)",
            fontFamily: "var(--font-sans)",
            boxSizing: "border-box",
          }}
        />
      </Field>

      {activeTemplates.length > 0 && (
        <Field label={uiLang === "vi" ? "Mẫu Thiết Kế Trực Quan" : "Visual Templates"} hint={uiLang === "vi" ? `${selectedCount}/${activeTemplates.length} mẫu đã chọn` : `${selectedCount}/${activeTemplates.length} selected`}>
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            {/* Strict-mode toggle: when ON, Agent 1 must pick a template for every scene. */}
            <div style={{
              display: "flex", alignItems: "center", justifyContent: "space-between",
              gap: 12,
              padding: "8px 10px",
              background: "var(--bg-2)",
              border: "1px solid var(--line)",
              borderRadius: "var(--radius)",
              opacity: selectedCount === 0 ? 0.5 : 1,
            }}>
              <div style={{ minWidth: 0, flex: 1 }}>
                <div style={{ fontSize: 12, fontWeight: 600, color: "var(--fg-0)" }}>{t.templatesOnly}</div>
                <div style={{ fontSize: 10.5, color: "var(--fg-2)", marginTop: 2, lineHeight: 1.35 }}>{t.templatesOnlyHint}</div>
              </div>
              <Switch
                value={selectedCount === 0 ? false : (settings.templatesOnly !== false)}
                onChange={(v) => set("templatesOnly", v)}
              />
            </div>

            {selectedCount === 0 && (
              <div style={{ fontSize: 11, color: "var(--fg-2)", padding: "6px 8px", border: "1px dashed var(--line-strong)", borderRadius: "var(--radius)" }}>
                {uiLang === "vi"
                  ? "Chưa chọn mẫu nào — Agent 2 sẽ tự build TSX tuỳ chỉnh cho từng phân cảnh dựa trên mô tả hình ảnh."
                  : "No templates selected — Agent 2 will build fully custom TSX for each scene from the visual descriptions."}
              </div>
            )}
            {activeTemplates.filter((tpl) => selectedSet.has(tpl.id)).slice(0, 3).map((tpl) => (
              <div key={tpl.id}
                style={{
                  display: "flex",
                  gap: 12,
                  padding: "6px 8px",
                  background: "var(--bg-2)",
                  border: "1px solid var(--line)",
                  borderRadius: "var(--radius)",
                  alignItems: "center",
                  boxSizing: "border-box",
                }}
              >
                {/* Visual live scaled thumbnail (compact size) */}
                <div style={{
                  width: 96,
                  height: 54, // 96 * 9 / 16 = 54
                  borderRadius: 4,
                  overflow: "hidden",
                  border: "1px solid var(--line-soft)",
                  position: "relative",
                  background: "var(--bg-0)",
                  boxShadow: "0 1px 3px rgba(0,0,0,0.12)",
                  flexShrink: 0,
                }}>
                  <div style={{
                    transform: `scale(${96 / 208})`,
                    transformOrigin: "top left",
                    width: 208,
                    height: 117,
                  }}>
                    <TemplatePreview id={tpl.id} />
                  </div>
                </div>
                {/* Details */}
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 12, fontWeight: 700, color: "var(--fg-0)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{tpl.name}</div>
                  <div style={{ fontSize: 10, color: "var(--fg-2)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", marginTop: 2 }}>{tpl.description}</div>
                </div>
              </div>
            ))}

            {/* View All Button */}
            <button
              type="button"
              onClick={() => setShowModal(true)}
              style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                gap: 6,
                padding: "8px 12px",
                background: "transparent",
                border: "1px dashed var(--line-strong)",
                borderRadius: "var(--radius)",
                color: "var(--accent)",
                fontSize: 11.5,
                fontWeight: 600,
                cursor: "pointer",
                transition: "all .12s",
                marginTop: 2,
              }}
              onMouseEnter={(e) => {
                e.currentTarget.style.background = "var(--accent-soft)";
                e.currentTarget.style.borderColor = "var(--accent)";
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.background = "transparent";
                e.currentTarget.style.borderColor = "var(--line-strong)";
              }}
            >
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                <path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>
              </svg>
              {uiLang === "vi" ? `Chọn mẫu thiết kế (${selectedCount}/${activeTemplates.length})` : `Choose templates (${selectedCount}/${activeTemplates.length})`}
            </button>
          </div>
        </Field>
      )}

      {showModal && (
        <div 
          style={{
            position: "fixed",
            inset: 0,
            zIndex: 9999,
            background: "rgba(0, 0, 0, 0.75)",
            backdropFilter: "blur(12px)",
            WebkitBackdropFilter: "blur(12px)",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            padding: 20,
            animation: "fadeIn .2s ease-out",
          }}
          onClick={() => setShowModal(false)}
        >
          <div 
            style={{
              width: "100%",
              maxWidth: 860,
              maxHeight: "85vh",
              background: "var(--bg-1)",
              border: "1px solid var(--line)",
              borderRadius: "var(--radius-xl)",
              boxShadow: "0 20px 50px rgba(0,0,0,0.5)",
              display: "flex",
              flexDirection: "column",
              overflow: "hidden",
              animation: "slideUp .25s cubic-bezier(0.16, 1, 0.3, 1)",
            }}
            onClick={(e) => e.stopPropagation()} // Prevent click propagation to close modal
          >
            {/* Modal Header */}
            <div style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              padding: "16px 24px",
              borderBottom: "1px solid var(--line)",
              background: "var(--bg-2)",
            }}>
              <div>
                <h3 style={{ margin: 0, fontSize: 16, fontWeight: 700, color: "var(--fg-0)" }}>
                  {uiLang === "vi" ? "Tất Cả Mẫu Thiết Kế Trực Quan" : "All Design Templates"}
                </h3>
                <p style={{ margin: "4px 0 0 0", fontSize: 12, color: "var(--fg-2)" }}>
                  {uiLang === "vi" ? `Tick để bật mẫu cho video — đã chọn ${selectedCount}/${activeTemplates.length}` : `Tick to enable a template — ${selectedCount}/${activeTemplates.length} selected`}
                </p>
              </div>
              <button
                type="button"
                onClick={() => setShowModal(false)}
                style={{
                  width: 32,
                  height: 32,
                  borderRadius: "50%",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  background: "var(--bg-3)",
                  color: "var(--fg-2)",
                  transition: "all .12s",
                  cursor: "pointer",
                }}
                onMouseEnter={(e) => {
                  e.currentTarget.style.background = "var(--err)";
                  e.currentTarget.style.color = "#fff";
                }}
                onMouseLeave={(e) => {
                  e.currentTarget.style.background = "var(--bg-3)";
                  e.currentTarget.style.color = "var(--fg-2)";
                }}
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                  <line x1="18" y1="6" x2="6" y2="18"></line>
                  <line x1="6" y1="6" x2="18" y2="18"></line>
                </svg>
              </button>
            </div>

            {/* Modal Body */}
            <div style={{
              flex: 1,
              overflowY: "auto",
              padding: 24,
              display: "grid",
              gridTemplateColumns: "1fr 1fr",
              gap: 16,
              background: "var(--bg-1)",
              maxHeight: "calc(85vh - 120px)",
            }}>
              {activeTemplates.map((tpl) => {
                const isOn = selectedSet.has(tpl.id);
                const disabled = isTemplateDisabled(tpl);
                const isExclusive = tpl.group === exclusiveGroup;
                const lockHint = disabled
                  ? (isExclusive
                      ? (uiLang === "vi" ? "Chỉ dùng với style Infographic" : "Infographic style only")
                      : (uiLang === "vi" ? "Bị khóa khi đã chọn mẫu Infographic" : "Locked while the Infographic template is selected"))
                  : null;
                return (
                <div key={tpl.id}
                  role="checkbox"
                  aria-checked={isOn}
                  aria-disabled={disabled}
                  onClick={() => { if (!disabled) toggleTemplate(tpl.id); }}
                  style={{
                    display: "flex",
                    gap: 16,
                    padding: 12,
                    background: isOn ? "var(--accent-soft)" : "var(--bg-2)",
                    border: `1.5px solid ${isOn ? "var(--accent)" : "var(--line)"}`,
                    borderRadius: "var(--radius-lg)",
                    alignItems: "flex-start",
                    boxShadow: "var(--shadow-1)",
                    cursor: disabled ? "not-allowed" : "pointer",
                    opacity: disabled ? 0.45 : 1,
                    transition: "all .12s",
                    position: "relative",
                  }}
                >
                  {/* Selection checkbox */}
                  <div style={{
                    position: "absolute",
                    top: 8,
                    right: 8,
                    width: 20,
                    height: 20,
                    borderRadius: 6,
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    background: isOn ? "var(--accent)" : "var(--bg-0)",
                    border: `1.5px solid ${isOn ? "var(--accent)" : "var(--line-strong)"}`,
                    color: "var(--accent-fg)",
                    fontSize: 11,
                    fontWeight: 700,
                  }}>{isOn ? "✓" : ""}</div>
                  {/* Visual live mockup - standard 208x117 size (100% crisp) */}
                  <div style={{
                    width: 208,
                    height: 117,
                    borderRadius: 6,
                    overflow: "hidden",
                    border: "1.5px solid var(--line-strong)",
                    position: "relative",
                    background: "var(--bg-0)",
                    boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
                    flexShrink: 0,
                  }}>
                    <TemplatePreview id={tpl.id} />
                  </div>
                  {/* Text details */}
                  <div style={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 6 }}>
                    <div style={{ fontSize: 14, fontWeight: 700, color: "var(--fg-0)" }}>{tpl.name}</div>
                    <div style={{ fontSize: 12, color: "var(--fg-2)", lineHeight: 1.45 }}>{tpl.description}</div>
                    {lockHint && (
                      <div style={{ fontSize: 10.5, color: "var(--fg-2)", fontStyle: "italic", marginTop: 2 }}>🔒 {lockHint}</div>
                    )}
                  </div>
                </div>
                );
              })}
            </div>
          </div>
        </div>
      )}

      <CollapsibleSection heading={t.videoSettings} summary={videoSummary} defaultOpen={false}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <Field label={t.aspect}>
            <Segmented value={settings.aspect}
              onChange={(v) => set("aspect", v)}
              options={[
                { id: "16:9", label: "16:9" },
                { id: "9:16", label: "9:16" },
                { id: "1:1", label: "1:1" },
              ]}/>
          </Field>
          <Field label={t.resolution}>
            <Segmented value={settings.resolution}
              onChange={(v) => set("resolution", v)}
              options={[
                { id: "1080", label: "1080p" },
                { id: "4k", label: "4K" },
              ]}/>
          </Field>
        </div>

        <Field label={t.scenes} right={
          <span className="mono" style={{ fontSize: 11, color: "var(--fg-2)" }}>
            {(settings.numScenes ?? 0) === 0 ? t.llmDecides : (uiLang === "vi" ? `${settings.numScenes} phân cảnh` : `${settings.numScenes} scene${settings.numScenes > 1 ? "s" : ""}`)}
          </span>
        }>
          <NumberStepper
            value={settings.numScenes ?? 0}
            onChange={(v) => set("numScenes", v)}
            min={0} max={200}
            editable
          />
        </Field>

        <Field label={t.voiceover}>
          <Select value={settings.voice}
            onChange={(v) => set("voice", v)}
            options={[
              { value: "narrator-warm", label: VOICEOVER_LABELS[uiLang]["narrator-warm"] },
              { value: "narrator-clear", label: VOICEOVER_LABELS[uiLang]["narrator-clear"] },
              { value: "doc-reserved",   label: VOICEOVER_LABELS[uiLang]["doc-reserved"] },
              { value: "creator-bright", label: VOICEOVER_LABELS[uiLang]["creator-bright"] },
              { value: "off",            label: VOICEOVER_LABELS[uiLang]["off"] },
            ]}/>
        </Field>

        <Field label={t.subtitles} right={<Switch value={settings.enableSubtitles !== false} onChange={(v) => set("enableSubtitles", v)}/>}>
          <div style={{ fontSize: 11, color: "var(--fg-2)" }}>{t.burnCaptions}</div>
        </Field>

        <Field label={t.pacing} right={<span className="mono" style={{ fontSize: 11, color: "var(--fg-2)" }}>{settings.pacing.toFixed(2)}×</span>}>
          <input type="range" min="0.7" max="1.4" step="0.05"
            value={settings.pacing}
            onChange={(e) => set("pacing", parseFloat(e.target.value))}
            style={{ width: "100%", accentColor: "oklch(0.78 0.16 70)" }}/>
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 10, color: "var(--fg-3)" }} className="mono">
            <span>{t.relaxed}</span><span>{t.snappy}</span>
          </div>
        </Field>
      </CollapsibleSection>
    </section>
  );
}

function SectionHeading({ children, right }) {
  return (
    <div style={{
      display: "flex", justifyContent: "space-between", alignItems: "center",
      paddingBottom: 8,
      borderBottom: "1px solid var(--line)",
      marginBottom: 2,
    }}>
      <h3 className="mono" style={{
        margin: 0, fontSize: 11, fontWeight: 600, letterSpacing: "0.12em",
        color: "var(--fg-1)", textTransform: "uppercase",
      }}>{children}</h3>
      {right}
    </div>
  );
}

// ── input screen root ───────────────────────────────────────────────

function UploadsPane({ uploads, setUploads, uiLang = "vi" }) {
  const t = TXT_INPUT[uiLang] || TXT_INPUT.vi;
  const fileRef = React.useRef(null);
  const MAX_FILES = 20;

  const addFiles = (fileList) => {
    if (!fileList || !fileList.length) return;
    const room = Math.max(0, MAX_FILES - uploads.length);
    const incoming = Array.from(fileList)
      .filter((f) => f.type.startsWith("image/"))
      .slice(0, room)
      .map((file) => ({
        file,
        description: "",
        previewUrl: URL.createObjectURL(file),
      }));
    if (incoming.length === 0) return;
    setUploads([...uploads, ...incoming]);
  };

  const removeAt = (idx) => {
    const next = uploads.slice();
    const [removed] = next.splice(idx, 1);
    if (removed?.previewUrl) URL.revokeObjectURL(removed.previewUrl);
    setUploads(next);
  };

  const updateDescription = (idx, description) => {
    const next = uploads.slice();
    next[idx] = { ...next[idx], description };
    setUploads(next);
  };

  return (
    <section style={{
      background: "var(--bg-1)",
      border: "1px solid var(--line)",
      borderRadius: "var(--radius-lg)",
      overflow: "hidden",
    }}>
      <header style={{
        padding: "10px 14px",
        borderBottom: "1px solid var(--line)",
        display: "flex", alignItems: "center", justifyContent: "space-between",
      }}>
        <div>
          <div className="mono" style={{ fontSize: 11, color: "var(--fg-1)", letterSpacing: "0.08em", textTransform: "uppercase" }}>
            {t.uploadsTitle}
          </div>
          <div style={{ fontSize: 13, color: "var(--fg-2)", marginTop: 2 }}>
            {t.uploadsSub} <span style={{ color: "var(--fg-3)" }}>· {t.optional}</span>
          </div>
        </div>
        <span className="mono" style={{ fontSize: 11, color: "var(--fg-3)" }}>
          {uploads.length}/{MAX_FILES}
        </span>
      </header>

      <div style={{ padding: 14, display: "flex", flexDirection: "column", gap: 10 }}>
        <input
          ref={fileRef}
          type="file"
          accept="image/jpeg,image/png,image/webp,image/gif"
          multiple
          style={{ display: "none" }}
          onChange={(e) => { addFiles(e.target.files); e.target.value = ""; }}
        />
        <button
          onClick={() => fileRef.current?.click()}
          disabled={uploads.length >= MAX_FILES}
          style={{
            padding: "10px 12px",
            border: "1px dashed var(--line-strong)",
            borderRadius: "var(--radius)",
            background: "transparent",
            color: uploads.length >= MAX_FILES ? "var(--fg-3)" : "var(--fg-1)",
            cursor: uploads.length >= MAX_FILES ? "not-allowed" : "pointer",
            fontSize: 13,
          }}
        >
          {uploads.length === 0 ? t.addImagesText : t.addMoreText}
        </button>

        {uploads.length === 0 && (
          <div className="mono" style={{ fontSize: 11, color: "var(--fg-2)", lineHeight: 1.5 }}>
            {t.uploadsExpl}
          </div>
        )}

        {uploads.map((u, idx) => (
          <div key={idx} style={{
            display: "grid",
            gridTemplateColumns: "56px 1fr auto",
            gap: 10,
            alignItems: "stretch",
            padding: 8,
            background: "var(--bg-2)",
            border: "1px solid var(--line)",
            borderRadius: "var(--radius)",
          }}>
            <img
              src={u.previewUrl}
              alt=""
              style={{
                width: 56, height: 56, objectFit: "cover",
                borderRadius: 6, border: "1px solid var(--line)",
              }}
            />
            <textarea
              placeholder={t.uploadsDescHint}
              value={u.description}
              onChange={(e) => updateDescription(idx, e.target.value)}
              rows={2}
              style={{
                background: "var(--bg-0)",
                border: "1px solid var(--line)",
                borderRadius: 6,
                padding: "6px 8px",
                fontSize: 12,
                color: "var(--fg-0)",
                resize: "vertical",
                outline: 0,
                fontFamily: "var(--font-sans)",
              }}
            />
            <button
              onClick={() => removeAt(idx)}
              title="Remove"
              style={{
                width: 28,
                alignSelf: "start",
                padding: 0,
                border: "1px solid var(--line)",
                borderRadius: 6,
                background: "transparent",
                color: "var(--fg-2)",
                cursor: "pointer",
                fontSize: 14,
                lineHeight: 1,
              }}
            >
              ×
            </button>
          </div>
        ))}
      </div>
    </section>
  );
}


function InputScreen({ state, setState, onGenerate, uiLang = "vi" }) {
  const { source, customPrompt = "", customStyle = "", sourceMode, settings, uploads = [], audioFile = null } = state;
  // Functional updates so multiple setters fired in the same event handler
  // (e.g. picking the custom style seeds both `settings` and `customStyle`)
  // compose instead of clobbering each other.
  const setSource = (v) => setState((s) => ({ ...s, source: v }));
  const setCustomPrompt = (v) => setState((s) => ({ ...s, customPrompt: v }));
  const setCustomStyle = (v) => setState((s) => ({ ...s, customStyle: v }));
  const setSourceMode = (v) => setState((s) => ({ ...s, sourceMode: v }));
  const setSettings = (v) => setState((s) => ({ ...s, settings: v }));
  const setUploads = (v) => setState((s) => ({ ...s, uploads: v }));
  const setAudioFile = (v) => setState((s) => ({ ...s, audioFile: v }));

  const wordCount = source.trim() ? source.trim().split(/\s+/).length : 0;
  const estSec = Math.max(0, Math.round(wordCount / 2.6));
  const audioReady = sourceMode === "audio" && audioFile && audioFile.file;
  const canGenerate = audioReady || wordCount >= 20;
  const [confirmOpen, setConfirmOpen] = React.useState(false);
  const t = TXT_INPUT[uiLang] || TXT_INPUT.vi;

  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: "1fr 440px",
      gap: 18,
      padding: "0 28px 28px",
      maxWidth: 1600,
      margin: "0 auto",
    }}>
      <div style={{ display: "flex", flexDirection: "column", gap: 18, minHeight: 0 }}>
        <SourcePane
          source={source}
          setSource={setSource}
          customPrompt={customPrompt}
          setCustomPrompt={setCustomPrompt}
          sourceMode={sourceMode}
          setSourceMode={setSourceMode}
          audioFile={audioFile}
          setAudioFile={setAudioFile}
          uiLang={uiLang}
        />

        {/* generate strip */}
        <div style={{
          background: "var(--bg-1)",
          border: "1px solid var(--line)",
          borderRadius: "var(--radius-lg)",
          padding: "14px 18px",
          display: "flex", alignItems: "center", justifyContent: "space-between",
        }}>
          <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <span className="mono" style={{ fontSize: 11, color: "var(--fg-2)", letterSpacing: "0.08em", textTransform: "uppercase" }}>{t.pipeline}</span>
              <PipelineDots />
            </div>
            <div className="mono" style={{ fontSize: 11.5, color: "var(--fg-2)" }}>
              {t.pipelineSteps} &nbsp;·&nbsp; <span style={{ color: "var(--fg-0)" }}>{t.wallTime.replace("{time}", Math.max(35, Math.round(estSec * 0.9)))}</span>
            </div>
          </div>
          <button
            disabled={!canGenerate}
            onClick={() => setConfirmOpen(true)}
            style={{
              padding: "12px 22px",
              background: canGenerate ? "var(--accent)" : "var(--bg-3)",
              color: canGenerate ? "var(--accent-fg)" : "var(--fg-3)",
              fontWeight: 600,
              fontSize: 14,
              letterSpacing: "-0.005em",
              borderRadius: "var(--radius)",
              boxShadow: canGenerate ? "0 1px 0 rgba(255,255,255,.2) inset, 0 4px 12px var(--accent-soft)" : "none",
              transition: "transform .08s, box-shadow .12s",
              display: "flex", alignItems: "center", gap: 8,
              cursor: canGenerate ? "pointer" : "not-allowed",
            }}
            onMouseDown={(e) => canGenerate && (e.currentTarget.style.transform = "scale(0.98)")}
            onMouseUp={(e) => e.currentTarget.style.transform = "scale(1)"}
            onMouseLeave={(e) => e.currentTarget.style.transform = "scale(1)"}
          >
            {t.generateVideo}
            <span className="mono" style={{
              padding: "2px 6px",
              background: canGenerate ? "rgba(0,0,0,0.18)" : "transparent",
              borderRadius: 3, fontSize: 11, fontWeight: 600,
            }}>⌘ ↵</span>
          </button>
        </div>
      </div>

      <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
        <SettingsPane settings={settings} setSettings={setSettings} customStyle={customStyle} setCustomStyle={setCustomStyle} uiLang={uiLang}/>
        <UploadsPane uploads={uploads} setUploads={setUploads} uiLang={uiLang}/>
      </div>

      {confirmOpen && (
        <ConfirmGenerateModal
          settings={settings}
          wordCount={wordCount}
          estSec={estSec}
          onCancel={() => setConfirmOpen(false)}
          onConfirm={() => { setConfirmOpen(false); onGenerate(); }}
          uiLang={uiLang}
        />
      )}
    </div>
  );
}

function ConfirmGenerateModal({ settings, wordCount, estSec, onCancel, onConfirm, uiLang = "vi" }) {
  const [saveTemplate, setSaveTemplate] = React.useState(() => {
    try {
      const saved = localStorage.getItem("autocut.saveTemplateOption");
      return saved !== "false";
    } catch (e) {
      return true;
    }
  });

  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onCancel();
      else if (e.key === "Enter") {
        try {
          localStorage.setItem("autocut.saveTemplateOption", String(saveTemplate));
        } catch (e) {}
        onConfirm(saveTemplate);
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onCancel, onConfirm, saveTemplate]);

  const t = TXT_INPUT[uiLang] || TXT_INPUT.vi;
  const stylePreset = STYLE_PRESETS.find((s) => s.id === settings.style) || {};
  const styleName = translateStylePreset(stylePreset, uiLang).name || settings.style;
  const estLabel = `~${Math.floor(estSec / 60)}:${String(estSec % 60).padStart(2, "0")}`;
  const rows = [
    [t.style,       styleName],
    [t.aspect,      settings.aspect],
    [t.resolution,  `${settings.resolution}p`],
    [t.language,    LANGUAGES.find(l => l.code === settings.language)?.label || settings.language],
    [t.words,       String(wordCount)],
    [t.estLength,   estLabel],
  ];
  if (settings.image_style) {
    rows.push([t.imageStyle, settings.image_style]);
  }

  return (
    <div
      onClick={onCancel}
      style={{
        position: "fixed", inset: 0, zIndex: 100,
        background: "rgba(0,0,0,0.6)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 40,
      }}>
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: 420, maxWidth: "92vw",
          background: "var(--bg-1)",
          border: "1px solid var(--line)",
          borderRadius: "var(--radius-lg)",
          padding: "22px 24px",
          display: "flex", flexDirection: "column", gap: 14,
          boxShadow: "0 20px 60px rgba(0,0,0,0.4)",
        }}>
        <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
          <div style={{ fontSize: 16, fontWeight: 600, color: "var(--fg-0)", letterSpacing: "-0.005em" }}>
            {t.confirmTitle}
          </div>
          <div style={{ fontSize: 12.5, color: "var(--fg-2)", lineHeight: 1.5 }}>
            {t.confirmDesc}
          </div>
        </div>

        <div style={{
          display: "grid", gridTemplateColumns: "auto 1fr", rowGap: 6, columnGap: 16,
          padding: "12px 14px",
          background: "var(--bg-2)",
          border: "1px solid var(--line)",
          borderRadius: "var(--radius)",
        }}>
          {rows.map(([k, v]) => (
            <React.Fragment key={k}>
              <span className="mono" style={{ fontSize: 11, color: "var(--fg-2)", letterSpacing: "0.06em", textTransform: "uppercase" }}>{k}</span>
              <span className="mono" style={{ fontSize: 12.5, color: "var(--fg-0)" }}>{v}</span>
            </React.Fragment>
          ))}
        </div>

        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, marginTop: 4 }}>
          <label style={{
            display: "flex",
            alignItems: "center",
            gap: 8,
            cursor: "pointer",
            userSelect: "none",
            fontSize: 12.5,
            color: "var(--fg-1)",
          }}>
            <input
              type="checkbox"
              checked={saveTemplate}
              onChange={(e) => setSaveTemplate(e.target.checked)}
              style={{
                accentColor: "var(--accent)",
                cursor: "pointer",
              }}
            />
            {uiLang === "vi" ? "Lưu lại mẫu thiết lập" : "Save your template"}
          </label>

          <div style={{ display: "flex", gap: 8 }}>
            <button
              onClick={onCancel}
              style={{
                padding: "9px 16px",
                background: "var(--bg-2)",
                border: "1px solid var(--line)",
                borderRadius: "var(--radius)",
                color: "var(--fg-0)",
                fontSize: 13,
                fontWeight: 500,
                cursor: "pointer",
              }}>
              {t.cancel}
            </button>
            <button
              onClick={() => {
                try {
                  localStorage.setItem("autocut.saveTemplateOption", String(saveTemplate));
                } catch (e) {}
                onConfirm(saveTemplate);
              }}
              style={{
                padding: "9px 18px",
                background: "var(--accent)",
                color: "var(--accent-fg)",
                border: "none",
                borderRadius: "var(--radius)",
                fontSize: 13,
                fontWeight: 600,
                boxShadow: "0 1px 0 rgba(255,255,255,.2) inset, 0 4px 12px var(--accent-soft)",
                cursor: "pointer",
              }}>
              {t.start}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function PipelineDots() {
  return (
    <div style={{ display: "flex", gap: 3 }}>
      {[0, 1, 2, 3].map((i) => (
        <span key={i} style={{
          width: 5, height: 5, borderRadius: 999,
          background: "var(--accent)",
          opacity: 0.4,
          animation: `pulse 1.6s ${i * 0.18}s ease-in-out infinite`,
        }}/>
      ))}
    </div>
  );
}

Object.assign(window, {
  InputScreen, UploadsPane,
  STYLE_PRESETS, LANGUAGES, SUBTITLE_PRESETS,
  Field, Select, NumberStepper, Segmented, ColorChip, Switch, CollapsibleSection,
  ToolButton, SectionHeading, hexToRgba, SAMPLE_SCRIPT, SAMPLE_SCRIPT_VI,
  translateStylePreset,
});
