/* Multiligo Social Studio — Main app
 * Loads layouts from layouts.jsx (registered on window.*)
 */

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============================================================
// Formats
// ============================================================
// 2026 önerilen oranlar — Instagram artık feed için 4:5'i öneriyor.
const FORMATS = [
  { id: 'ig-post',     name: 'IG Post',       sub: '4:5 önerilen',  w: 1080, h: 1350, network: 'instagram', maxSlides: 1, captionLimit: 2200 },
  { id: 'ig-square',   name: 'IG Post',       sub: '1:1 kare',      w: 1080, h: 1080, network: 'instagram', maxSlides: 1, captionLimit: 2200 },
  { id: 'ig-carousel', name: 'IG Carousel',   sub: '4:5 · 10 slayt',w: 1080, h: 1350, network: 'instagram', maxSlides: 10, captionLimit: 2200 },
  { id: 'ig-story',    name: 'IG Story',      sub: '9:16',          w: 1080, h: 1920, network: 'instagram', maxSlides: 1, captionLimit: 0 },
  { id: 'ig-reel',     name: 'IG Reels',      sub: '9:16 · çoklu klip',w: 1080, h: 1920, network: 'instagram', maxSlides: 8, captionLimit: 2200 },
  { id: 'li-post',     name: 'LinkedIn Post', sub: '1:1 kare',      w: 1200, h: 1200, network: 'linkedin', maxSlides: 1, captionLimit: 3000 },
  { id: 'li-portrait', name: 'LinkedIn Post', sub: '4:5 dikey',     w: 1080, h: 1350, network: 'linkedin', maxSlides: 1, captionLimit: 3000 },
  { id: 'li-land',     name: 'LinkedIn Post', sub: '1.91:1 yatay',  w: 1200, h: 627,  network: 'linkedin', maxSlides: 1, captionLimit: 3000 },
  { id: 'li-pdf',      name: 'LinkedIn PDF',  sub: '4:5 belge',     w: 1080, h: 1350, network: 'linkedin', maxSlides: 10, captionLimit: 3000 },
];

// ============================================================
// Brands (logo + accent)
// ============================================================
const BRANDS = [
  { id: 'multiligo',         name: 'Multiligo',         logo: 'assets/logo.png',
    logoWhite: 'assets/logo_white.png',                 accent: '#FF2939',  website: 'multiligo.com',
    font: 'Onest' },
  { id: 'inovakademi',       name: 'İnovakademi',       logo: 'assets/inovakademi_logo.png',
    logoWhite: 'assets/inovakademi_logo_beyaz.png',     accent: '#6D28D9',  website: 'inovakademi.com',
    font: 'Plus Jakarta Sans', fontUrl: 'Plus+Jakarta+Sans:wght@200..800' },
  { id: 'gaziantep-triko',   name: 'Gaziantep Triko',   logo: 'assets/gaziantep_triko_logo.png',
    logoWhite: 'assets/gaziantep_triko_logo.png',       accent: '#1E3A8A',  invertOnDark: true, website: 'gaziantepclothing.com',
    font: 'Playfair Display', fontUrl: 'Playfair+Display:ital,wght@0,400..900;1,400..900' },
  { id: 'gaziantep-ayakkabi',name: 'Gaziantep Ayakkabı',logo: 'assets/gaziantep_ayakkabi_triko.png',
    logoWhite: 'assets/gaziantep_ayakkabi_triko.png',   accent: '#92400E',  invertOnDark: true, website: 'gaziantepshoes.com',
    font: 'Cormorant Garamond', fontUrl: 'Cormorant+Garamond:ital,wght@0,300..700;1,300..700' },
  { id: 'globby',            name: 'Globby',            logo: 'assets/globby_logo.png',
    logoWhite: 'assets/globby_logo_white.png',          accent: '#6E39F0',  website: 'theglobby.com',
    font: 'Space Grotesk', fontUrl: 'Space+Grotesk:wght@300..700' },
];

const LOGO_SIZE_MAP = { xs: 0.6, s: 0.8, std: 1, l: 1.4, xl: 2, '2xl': 2.8 };
const LOGO_SIZE_OPTIONS = [
  { id: 'xs',  labelTr: 'XS',       labelEn: 'XS' },
  { id: 's',   labelTr: 'S',        labelEn: 'S' },
  { id: 'std', labelTr: 'Standart', labelEn: 'Standard' },
  { id: 'l',   labelTr: 'L',        labelEn: 'L' },
  { id: 'xl',  labelTr: 'XL',       labelEn: 'XL' },
  { id: '2xl', labelTr: '2XL',      labelEn: '2XL' },
];

const BRANDING_SIZE_MAP = { xs: 0.7, s: 0.85, std: 1, l: 1.3, xl: 1.6 };
const BRANDING_SIZE_OPTIONS = [
  { id: 'xs',  labelTr: 'XS',       labelEn: 'XS' },
  { id: 's',   labelTr: 'S',        labelEn: 'S' },
  { id: 'std', labelTr: 'Standart', labelEn: 'Standard' },
  { id: 'l',   labelTr: 'L',        labelEn: 'L' },
  { id: 'xl',  labelTr: 'XL',       labelEn: 'XL' },
];

function hexToRgb(hex) {
  const h = hex.replace('#', '');
  return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];
}
function mixHex(hex, target, pct) {
  const [r,g,b] = hexToRgb(hex);
  const [tr,tg,tb] = hexToRgb(target);
  const m = (a,b) => Math.round(a + (b - a) * pct).toString(16).padStart(2,'0');
  return '#' + m(r,tr) + m(g,tg) + m(b,tb);
}

const CONTENT_TYPES_TR = [
  { id: 'hizmet', label: 'Hizmetler' },
  { id: 'platform', label: 'Platformlar' },
  { id: 'rehber', label: 'Rehber' },
  { id: 'bilgi', label: 'Faydalı Bilgi' },
  { id: 'haber', label: 'Sektör Haberi' },
  { id: 'vaka', label: 'Vaka Çalışması' },
  { id: 'duyuru', label: 'Duyuru' },
];
const CONTENT_TYPES_EN = [
  { id: 'hizmet', label: 'Services' },
  { id: 'platform', label: 'Platforms' },
  { id: 'rehber', label: 'Guide' },
  { id: 'bilgi', label: 'Useful Info' },
  { id: 'haber', label: 'Industry News' },
  { id: 'vaka', label: 'Case Study' },
  { id: 'duyuru', label: 'Announcement' },
];

const BG_STYLES = [
  { id: 'paper', label: 'Paper' },
  { id: 'ink', label: 'Ink' },
  { id: 'red', label: 'Red' },
  { id: 'image', label: 'Image' },
];

const LOGO_POSITIONS = [
  { id: 'tl', label: '↖' },
  { id: 'tc', label: '↑' },
  { id: 'tr', label: '↗' },
  { id: 'bl', label: '↙' },
  { id: 'bc', label: '↓' },
  { id: 'br', label: '↘' },
  { id: 'none', label: 'Off' },
];

// Default slide template per format
function defaultSlide(format, language, contentType, layoutId) {
  const lang = language;
  const tr = lang === 'tr';
  const ct = (tr ? CONTENT_TYPES_TR : CONTENT_TYPES_EN).find(c => c.id === contentType);
  return {
    layout: layoutId || 'cover',
    eyebrow: ct ? ct.label : '',
    title: tr ? 'Başlığınızı buraya yazın' : 'Type your headline here',
    body: tr ? 'Kısa açıklayıcı metin. Sayı, kanıt veya somut bir fayda ile başlayın.' : 'Short supporting copy. Lead with evidence, a number, or a concrete benefit.',
    footer: '',
    cta: tr ? 'Detayları gör' : 'See details',
    quote: '',
    author: '',
    stat: '23+',
    statLabel: tr ? 'aktif platform' : 'active platforms',
    items: tr ? ['İlk madde', 'İkinci madde', 'Üçüncü madde'] : ['First point', 'Second point', 'Third point'],
    question: '',
    answer: '',
    highlight: '',
    textScale: 1,
    overlayOpacity: 0.5,
    image: null,
    video: null,
    bg: 'paper',
  };
}

// Formats where video upload is meaningful (vertical / motion-first)
const VIDEO_FORMATS = new Set(['ig-story', 'ig-reel']);

// ============================================================
// LocalStorage history
// ============================================================
const STORAGE_KEY = 'multiligo-social-studio:v1';
function loadHistory() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    return raw ? JSON.parse(raw) : { history: [], templates: [] };
  } catch { return { history: [], templates: [] }; }
}
function saveHistory(data) {
  try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch {}
}

// ============================================================
// Slide frame — renders the layout scaled to fit
// ============================================================
function SlideFrame({ slide, format, logoPos, showBranding = true, lang, index, total, scale, framed = true, safeZone = false }) {
  const props = {
    slide, bg: slide.bg || 'paper', logoPos, showBranding,
    textScale: slide.textScale || 1,
    overlayOpacity: slide.overlayOpacity ?? 0.5,
    index, total, lang,
    width: format.w, height: format.h,
  };
  const isVertical = format.h > format.w * 1.5;
  return (
    <div
      className={framed ? 'slide-frame' : ''}
      style={{
        width: format.w * scale,
        height: format.h * scale,
      }}
    >
      <div style={{
        width: format.w,
        height: format.h,
        transform: `scale(${scale})`,
        transformOrigin: 'top left',
        position: 'relative',
      }}>
        {window.renderLayout(slide.layout, props)}
        {safeZone && isVertical && (
          <>
            <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 260, background: 'rgba(220,38,38,0.18)', pointerEvents: 'none', zIndex: 200 }} />
            <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 420, background: 'rgba(220,38,38,0.18)', pointerEvents: 'none', zIndex: 200 }} />
            <div style={{ position: 'absolute', top: 260, right: 0, bottom: 420, width: 160, background: 'rgba(220,38,38,0.10)', pointerEvents: 'none', zIndex: 200 }} />
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// Field helpers
// ============================================================
function Field({ label, value, onChange, multiline, placeholder, limit }) {
  const Comp = multiline ? 'textarea' : 'input';
  const over = limit && value && value.length > limit;
  return (
    <div className="field">
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <span className="field-label">{label}</span>
        {limit && (
          <span className={'input-counter' + (over ? ' over' : '')}>
            {(value || '').length}/{limit}
          </span>
        )}
      </div>
      <Comp
        className={multiline ? 'textarea' : 'input'}
        value={value || ''}
        onChange={e => onChange(e.target.value)}
        placeholder={placeholder}
      />
    </div>
  );
}

function Dropzone({ image, onImage, label }) {
  const [dragging, setDragging] = useState(false);
  const inputRef = useRef();
  const handleFile = (file) => {
    if (!file || !file.type.startsWith('image/')) return;
    const reader = new FileReader();
    reader.onload = (e) => onImage(e.target.result);
    reader.readAsDataURL(file);
  };
  return (
    <div
      className={'dropzone' + (dragging ? ' drag-over' : '')}
      onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
      onDragLeave={() => setDragging(false)}
      onDrop={(e) => {
        e.preventDefault(); setDragging(false);
        handleFile(e.dataTransfer.files[0]);
      }}
      onClick={() => inputRef.current && inputRef.current.click()}
    >
      {image ? (
        <>
          <img src={image} alt="" />
          <div>{label || 'Click or drop to replace'}</div>
        </>
      ) : (
        <div>📷 {label || 'Drop image or click to upload'}</div>
      )}
      <input
        ref={inputRef}
        type="file" accept="image/*" hidden
        onChange={(e) => handleFile(e.target.files[0])}
      />
      {image && (
        <button
          className="btn ghost small"
          style={{ marginTop: 8 }}
          onClick={(e) => { e.stopPropagation(); onImage(null); }}
        >
          Remove
        </button>
      )}
    </div>
  );
}

function VideoDropzone({ video, onVideo, label }) {
  const [dragging, setDragging] = useState(false);
  const inputRef = useRef();
  const handleFile = (file) => {
    if (!file || !file.type.startsWith('video/')) return;
    // Object URL — videos are too large for data URLs / localStorage
    const url = URL.createObjectURL(file);
    onVideo({ url, name: file.name, size: file.size, type: file.type });
  };
  return (
    <div
      className={'dropzone' + (dragging ? ' drag-over' : '')}
      onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
      onDragLeave={() => setDragging(false)}
      onDrop={(e) => {
        e.preventDefault(); setDragging(false);
        handleFile(e.dataTransfer.files[0]);
      }}
      onClick={() => inputRef.current && inputRef.current.click()}
    >
      {video ? (
        <>
          <video
            src={video.url}
            muted autoPlay loop playsInline
            style={{
              maxWidth: '100%', maxHeight: 90, borderRadius: 4,
              display: 'block', margin: '0 auto 6px',
              background: '#000',
            }}
          />
          <div style={{ fontSize: 11 }}>
            {video.name}{video.size > 0 && ` · ${(video.size / 1024 / 1024).toFixed(1)} MB`}
          </div>
          <div style={{ marginTop: 4, fontSize: 10, color: 'var(--ml-ink-4)' }}>
            Click or drop to replace
          </div>
        </>
      ) : (
        <div>🎬 {label || 'Drop video (MP4/MOV) or click'}</div>
      )}
      <input
        ref={inputRef}
        type="file" accept="video/mp4,video/quicktime,video/webm" hidden
        onChange={(e) => handleFile(e.target.files[0])}
      />
      {video && (
        <button
          className="btn ghost small"
          style={{ marginTop: 8 }}
          onClick={(e) => {
            e.stopPropagation();
            try { URL.revokeObjectURL(video.url); } catch {}
            onVideo(null);
          }}
        >
          Remove
        </button>
      )}
    </div>
  );
}

// ============================================================
// Pexels image search panel
// ============================================================
function PexelsPanel({ onSelect, onSelectVideo, lang, showVideo }) {
  const tr = lang === 'tr';
  const [mediaType, setMediaType] = React.useState(showVideo ? 'videos' : 'photos');
  const [query, setQuery] = React.useState('');
  const [results, setResults] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState('');
  const [page, setPage] = React.useState(1);
  const [hasMore, setHasMore] = React.useState(false);
  const [orientation, setOrientation] = React.useState('');
  const [fetchingId, setFetchingId] = React.useState(null);

  const search = async (q, p, ori, type) => {
    if (!q.trim()) return;
    setLoading(true);
    setError('');
    try {
      const params = new URLSearchParams({ query: q, page: p, per_page: 15, type });
      if (ori && type === 'photos') params.set('orientation', ori);
      const res = await fetch(`/api/pexels?${params}`);
      const data = await res.json();
      if (data.error) throw new Error(data.error);
      const items = data.photos || data.videos || [];
      setResults(p === 1 ? items : prev => [...prev, ...items]);
      setHasMore(items.length === 15);
      setPage(p);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  };

  const handleSearch = (e) => {
    e && e.preventDefault();
    setResults([]);
    search(query, 1, orientation, mediaType);
  };

  const handleTypeChange = (type) => {
    setMediaType(type);
    setResults([]);
    setOrientation('');
    if (query.trim()) search(query, 1, '', type);
  };

  const handleOriChange = (ori) => {
    setOrientation(ori);
    if (query.trim()) search(query, 1, ori, mediaType);
  };

  const usePhoto = async (photo) => {
    const url = photo.src.large || photo.src.original;
    setFetchingId(photo.id);
    try {
      const res = await fetch(url);
      const blob = await res.blob();
      const dataUrl = await new Promise((resolve) => {
        const reader = new FileReader();
        reader.onload = (e) => resolve(e.target.result);
        reader.readAsDataURL(blob);
      });
      onSelect && onSelect(dataUrl);
    } catch {
      setError(tr ? 'Görsel indirilemedi' : 'Image download failed');
    } finally {
      setFetchingId(null);
    }
  };

  const useVideo = (video) => {
    const file = video.video_files
      ?.filter(f => f.quality !== 'hd' || f.width <= 1920)
      .sort((a, b) => (b.width || 0) - (a.width || 0))[0];
    if (!file) return;
    onSelectVideo && onSelectVideo({ url: file.link, name: `pexels-${video.id}.mp4` });
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {/* Type toggle */}
      <div style={{ display: 'flex', gap: 4 }}>
        <button className={'chip' + (mediaType === 'photos' ? ' is-active' : '')} onClick={() => handleTypeChange('photos')} type="button" style={{ fontSize: 11 }}>
          {tr ? 'Fotoğraf' : 'Photos'}
        </button>
        <button className={'chip' + (mediaType === 'videos' ? ' is-active' : '')} onClick={() => handleTypeChange('videos')} type="button" style={{ fontSize: 11 }}>
          {tr ? 'Video' : 'Videos'}
        </button>
      </div>

      <form onSubmit={handleSearch} style={{ display: 'flex', gap: 6 }}>
        <input
          className="field-input"
          style={{ flex: 1 }}
          value={query}
          onChange={e => setQuery(e.target.value)}
          placeholder={tr ? 'Anahtar kelime…' : 'Search keyword…'}
        />
        <button className="btn primary" type="submit" disabled={loading || !query.trim()}>
          {loading ? '…' : (tr ? 'Ara' : 'Search')}
        </button>
      </form>

      {mediaType === 'photos' && (
        <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
          {[['', tr ? 'Tümü' : 'All'], ['landscape', tr ? 'Yatay' : 'Landscape'], ['portrait', tr ? 'Dikey' : 'Portrait'], ['square', tr ? 'Kare' : 'Square']].map(([val, label]) => (
            <button
              key={val}
              className={'chip' + (orientation === val ? ' is-active' : '')}
              onClick={() => handleOriChange(val)}
              type="button"
              style={{ fontSize: 11 }}
            >{label}</button>
          ))}
        </div>
      )}

      {error && <div style={{ fontSize: 11, color: 'var(--ml-red)', padding: '4px 0' }}>{error}</div>}

      {results.length > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }}>
          {results.map(item => {
            const isVideo = !!item.video_files;
            const thumb = isVideo
              ? item.image
              : (item.src?.tiny || item.src?.small);
            return (
              <button
                key={item.id}
                onClick={() => isVideo ? useVideo(item) : usePhoto(item)}
                disabled={fetchingId === item.id}
                style={{ padding: 0, border: '2px solid transparent', borderRadius: 6, overflow: 'hidden', cursor: fetchingId === item.id ? 'wait' : 'pointer', aspectRatio: '1', background: '#eee', position: 'relative' }}
                title={item.alt || item.photographer || item.user?.name || ''}
              >
                <img
                  src={thumb}
                  alt=""
                  style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', opacity: fetchingId === item.id ? 0.5 : 1 }}
                  loading="lazy"
                />
                {fetchingId === item.id && (
                  <span style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <span className="ai-spinner" />
                  </span>
                )}
                {isVideo && fetchingId !== item.id && (
                  <span style={{ position: 'absolute', bottom: 3, right: 3, background: 'rgba(0,0,0,.55)', color: '#fff', fontSize: 10, padding: '1px 4px', borderRadius: 3 }}>
                    ▶ {item.duration ? `${item.duration}s` : 'MP4'}
                  </span>
                )}
              </button>
            );
          })}
        </div>
      )}

      {hasMore && (
        <button className="btn full" onClick={() => search(query, page + 1, orientation, mediaType)} disabled={loading} style={{ fontSize: 12 }}>
          {loading ? (tr ? 'Yükleniyor…' : 'Loading…') : (tr ? 'Daha fazla' : 'Load more')}
        </button>
      )}

      {results.length > 0 && (
        <div style={{ fontSize: 10, color: 'var(--ml-ink-4)', textAlign: 'center' }}>
          {tr ? 'Pexels tarafından sağlanmaktadır' : 'Photos provided by Pexels'}
        </div>
      )}
    </div>
  );
}

// AI generator panel
// ============================================================
function AiPanel({ topic, setTopic, onGenerate, generating, lang, contentType }) {
  const tr = lang === 'tr';
  return (
    <div>
      <Field
        label={tr ? 'Konu / brief' : 'Topic / brief'}
        value={topic}
        onChange={setTopic}
        multiline
        placeholder={tr
          ? 'Örn: KOBİ\'ler için e-ihracat rehberi, başlamak için 5 adım'
          : 'E.g.: 5 steps for SMEs to start exporting digitally'
        }
      />
      <div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 6 }}>
        <button
          className="btn primary full"
          onClick={onGenerate}
          disabled={generating || !topic.trim()}
        >
          {generating
            ? <><span className="ai-spinner"></span> {tr ? 'Üretiliyor…' : 'Generating…'}</>
            : (tr ? '✨  AI ile içerik üret' : '✨  Generate with AI')}
        </button>
        <div style={{ fontSize: 11, color: 'var(--ml-ink-4)', lineHeight: 1.4 }}>
          {tr
            ? 'AI, marka tonunda (formel, kanıt odaklı, biz/siz, emoji yok) içerik üretir.'
            : 'AI writes in brand voice (formal, evidence-led, plural-corporate, no emoji).'}
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Password gate
// ============================================================
const APP_PASSWORD = 'deneme123';
const AUTH_KEY = 'ml_auth';

function PasswordGate({ onUnlock }) {
  const [input, setInput] = React.useState('');
  const [err, setErr] = React.useState(false);

  const submit = (e) => {
    e.preventDefault();
    if (input === APP_PASSWORD) {
      sessionStorage.setItem(AUTH_KEY, '1');
      onUnlock();
    } else {
      setErr(true);
      setInput('');
    }
  };

  return (
    <div style={{
      minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: 'var(--ml-bg, #fafaf7)',
    }}>
      <form onSubmit={submit} style={{
        display: 'flex', flexDirection: 'column', gap: 16,
        width: 320, padding: 32,
        background: '#fff', borderRadius: 12,
        boxShadow: '0 4px 24px rgba(0,0,0,.10)',
      }}>
        <div style={{ fontWeight: 700, fontSize: 20, letterSpacing: '-0.02em' }}>Multiligo Social Studio</div>
        <input
          className="field-input"
          type="password"
          autoFocus
          placeholder="Şifre"
          value={input}
          onChange={e => { setInput(e.target.value); setErr(false); }}
          style={{ borderColor: err ? 'var(--ml-red)' : undefined }}
        />
        {err && <div style={{ fontSize: 12, color: 'var(--ml-red)', marginTop: -8 }}>Hatalı şifre</div>}
        <button className="btn primary" type="submit">Giriş</button>
      </form>
    </div>
  );
}

// ============================================================
// Main App
// ============================================================
function App() {
  const [formatId, setFormatId] = useState('ig-carousel');
  const [lang, setLang] = useState('tr');
  const [contentType, setContentType] = useState('rehber');
  const [logoPos, setLogoPos] = useState('br');
  const [showBranding, setShowBranding] = useState(true);
  const [currentSlide, setCurrentSlide] = useState(0);
  const [slides, setSlides] = useState(() => [defaultSlide('ig-carousel', 'tr', 'rehber', 'cover')]);
  const [topic, setTopic] = useState('');
  const [aiMode, setAiMode] = useState(true);
  const [generating, setGenerating] = useState(false);
  const [view, setView] = useState('canvas'); // canvas | feed
  const [storage, setStorage] = useState(loadHistory());
  const [caption, setCaption] = useState('');
  const [brandId, setBrandId] = useState(() => {
    try { return localStorage.getItem('ml.brand') || 'multiligo'; } catch { return 'multiligo'; }
  });
  const brand = BRANDS.find(b => b.id === brandId) || BRANDS[0];
  const [logoSize, setLogoSize] = useState(() => {
    try {
      const raw = localStorage.getItem('ml.logoSize') || 'std';
      if (raw === 'big') return 'l';
      return LOGO_SIZE_MAP[raw] != null ? raw : 'std';
    } catch { return 'std'; }
  });
  const logoScale = LOGO_SIZE_MAP[logoSize] ?? 1;
  const [brandingPos, setBrandingPos] = useState(() => {
    try { return localStorage.getItem('ml.brandingPos') || 'bl'; } catch { return 'bl'; }
  });
  const [brandingSize, setBrandingSize] = useState(() => {
    try {
      const raw = localStorage.getItem('ml.brandingSize') || 'std';
      return BRANDING_SIZE_MAP[raw] != null ? raw : 'std';
    } catch { return 'std'; }
  });
  const brandingScale = BRANDING_SIZE_MAP[brandingSize] ?? 1;
  const [safeZone, setSafeZone] = useState(() => {
    try { return localStorage.getItem('ml.safeZone') === 'true'; } catch { return false; }
  });

  // Keep __resources in sync during render so layouts read the correct values
  window.__resources = {
    ...(window.__resources || {}),
    logo: brand.logo,
    logoWhite: brand.logoWhite,
    invertOnDark: !!brand.invertOnDark,
    logoScale,
    website: brand.website || '',
    brandingPos,
    brandingScale,
    safeZone,
  };

  useEffect(() => {
    try { localStorage.setItem('ml.brand', brandId); } catch {}
    const r = document.documentElement.style;
    r.setProperty('--ml-red',     brand.accent);
    r.setProperty('--ml-red-600', mixHex(brand.accent, '#000000', 0.10));
    r.setProperty('--ml-red-700', mixHex(brand.accent, '#000000', 0.25));
    r.setProperty('--ml-red-100', mixHex(brand.accent, '#ffffff', 0.82));
    r.setProperty('--ml-red-050', mixHex(brand.accent, '#ffffff', 0.92));
    const fontFamily = brand.font || 'Onest';
    if (brand.fontUrl) {
      const linkId = 'gf-' + brand.id;
      if (!document.getElementById(linkId)) {
        const link = document.createElement('link');
        link.id = linkId;
        link.rel = 'stylesheet';
        link.href = `https://fonts.googleapis.com/css2?family=${brand.fontUrl}&display=swap`;
        document.head.appendChild(link);
      }
    }
    r.setProperty('--ml-font-sans', `"${fontFamily}", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif`);
  }, [brandId, brand.accent, brand.logo, brand.logoWhite, brand.invertOnDark]);

  useEffect(() => {
    try { localStorage.setItem('ml.logoSize', logoSize); } catch {}
  }, [logoSize]);
  useEffect(() => {
    try { localStorage.setItem('ml.brandingPos', brandingPos); } catch {}
  }, [brandingPos]);
  useEffect(() => {
    try { localStorage.setItem('ml.brandingSize', brandingSize); } catch {}
  }, [brandingSize]);
  useEffect(() => {
    try { localStorage.setItem('ml.safeZone', safeZone); } catch {}
  }, [safeZone]);

  const format = FORMATS.find(f => f.id === formatId);
  const isCarousel = format.maxSlides > 1;
  const slide = slides[currentSlide] || slides[0];

  useEffect(() => { saveHistory(storage); }, [storage]);

  // Reset slides when format changes
  const switchFormat = (id) => {
    const newFmt = FORMATS.find(f => f.id === id);
    setFormatId(id);
    if (slides.length > newFmt.maxSlides) {
      setSlides(slides.slice(0, newFmt.maxSlides));
      setCurrentSlide(0);
    }
  };

  const switchLang = (l) => {
    setLang(l);
    // refresh eyebrows in default content if untouched
  };

  const updateSlide = (patch) => {
    setSlides(slides.map((s, i) => i === currentSlide ? { ...s, ...patch } : s));
  };

  const addSlide = () => {
    if (slides.length >= format.maxSlides) return;
    const last = slides[slides.length - 1];
    const newSlide = { ...defaultSlide(formatId, lang, contentType, 'list'), bg: last.bg };
    setSlides([...slides, newSlide]);
    setCurrentSlide(slides.length);
  };

  const removeSlide = (idx) => {
    if (slides.length <= 1) return;
    const next = slides.filter((_, i) => i !== idx);
    setSlides(next);
    setCurrentSlide(Math.min(currentSlide, next.length - 1));
  };

  const duplicateSlide = () => {
    if (slides.length >= format.maxSlides) return;
    const copy = JSON.parse(JSON.stringify(slides[currentSlide]));
    const next = [...slides.slice(0, currentSlide + 1), copy, ...slides.slice(currentSlide + 1)];
    setSlides(next);
    setCurrentSlide(currentSlide + 1);
  };

  // ----- AI generation -----
  const generate = async () => {
    if (!topic.trim()) return;
    setGenerating(true);
    try {
      const slideCount = isCarousel ? Math.min(7, format.maxSlides) : 1;
      const contentLabel = (lang === 'tr' ? CONTENT_TYPES_TR : CONTENT_TYPES_EN).find(c => c.id === contentType)?.label || '';
      const isStory = formatId === 'ig-story';
      const isReel = formatId === 'ig-reel';
      const langName = lang === 'tr' ? 'Turkish' : 'English';

      const networkCtx = ({
        instagram: lang === 'tr'
          ? 'Instagram — görsel odaklı, mobil, genç-profesyonel kitle'
          : 'Instagram — visual, mobile-first, young professional audience',
        linkedin: lang === 'tr'
          ? 'LinkedIn — B2B profesyonel, veri odaklı, karar verici kitle'
          : 'LinkedIn — B2B professional, data-driven, decision-maker audience',
      })[format.network] || format.name;

      const layoutGuidance = (isReel || isStory)
        ? 'First slide MUST be hook layout. Prefer: hook, highlight, statement. Last slide: cta.'
        : format.network === 'linkedin'
          ? 'Prefer: stat, compare, list, editorial, qa, statement. Avoid: hook. Last slide: cta.'
          : 'Mix freely. Include at least one 2026 layout (highlight, arrow, mono, compare, hook, or editorial). Last slide: cta.';

      const prompt = `You are writing social media content for Multiligo, an Istanbul-based B2B digital platforms company.
Brand voice: ${langName}, formal, plural-corporate ("biz/siz"), evidence-led, calm. NO emoji, NO exclamation marks, NO hype adjectives. Use sentence case. Prefer concrete numbers over adjectives.

Topic / brief: ${topic}
Content category: ${contentLabel}
Platform / audience: ${networkCtx}
Format: ${format.name} ${format.sub} — generate ${slideCount === 1 ? '1 slide' : `5-${slideCount} slides (choose ideal count for this content)`}
Layout strategy: ${layoutGuidance}

Return ONLY a JSON object (no markdown, no commentary) shaped like:
{
  "caption": "social media caption (1-3 short paragraphs, ${lang === 'tr' ? 'Turkish' : 'English'}, ending with 3-5 hashtags)",
  "slides": [
    {
      "layout": "cover|statement|quote|stat|list|split|qa|step|photo|platform|index|cta|highlight|arrow|mono|compare|hook|editorial",
      "eyebrow": "short uppercase label (2-4 words)",
      "title": "headline (max 10 words — shorter for hook/stat/step/highlight)",
      "body": "supporting text (1 short sentence max)",
      "cta": "REQUIRED on every slide — short action phrase ending with → (e.g. 'Daha fazlası →')",
      "highlight": "for highlight layout: single word or short phrase in the title to mark with tape",
      "stat": "for stat layout: the number (e.g. '3x', '%87', '5+')",
      "statLabel": "for stat layout: what the number measures (1 short sentence)",
      "items": ["for list/index/platform: 3-5 short items", "for compare: items[0]=old way, items[1]=new way", "for mono: 3-5 terminal-style data lines"],
      "question": "for qa layout: the question",
      "answer": "for qa layout: the answer",
      "quote": "for quote layout: the pull-quote text",
      "author": "for quote layout: attribution"
    }
  ]
}

Layout hints:
- hook: eyebrow=attention-grabbing hook question, title=payoff answer. Best for Reels/Story first slide.
- highlight: title=full headline sentence, highlight=the ONE key word/phrase to emphasize with marker tape.
- compare: items[0]=old/bad/before, items[1]=new/better/after.
- mono: items=3-5 short terminal-style data or insight lines (start each with a symbol if relevant).
- editorial: long serif headline for newsletters or thought leadership announcements.
- stat: stat=the striking number, statLabel=what it measures (1 sentence context).

Rules:
- ${slideCount === 1 ? 'Generate exactly 1 slide.' : `First slide is the cover or hook. Last slide is cta. Mix layouts for variety.`}
- Every headline under 10 words. Body copy is max 1 sentence. No filler.
- cta field is REQUIRED on every single slide — never omit it.
- ${lang === 'tr' ? 'Tüm metin Türkçe.' : 'All text in English.'}`;

      const response = await window.__claudeAI({
        messages: [{ role: 'user', content: prompt }],
        max_tokens: 3000,
      });
      // Try to extract JSON from response
      let parsed;
      try {
        parsed = JSON.parse(response);
      } catch {
        const m = response.match(/\{[\s\S]*\}/);
        if (m) parsed = JSON.parse(m[0]);
        else throw new Error('Invalid AI response');
      }

      if (parsed.slides && Array.isArray(parsed.slides)) {
        const validLayouts = new Set((window.LAYOUTS || []).map(l => l.id));
        const next = parsed.slides.slice(0, format.maxSlides).map((s, i) => {
          const base = defaultSlide(formatId, lang, contentType);
          const layout = validLayouts.has(s.layout) ? s.layout : base.layout;
          return {
            ...base,
            ...s,
            layout,
            bg: i === 0 ? 'ink' : (i === parsed.slides.length - 1 ? 'red' : base.bg),
            items: s.items || base.items,
          };
        });
        setSlides(next);
        setCurrentSlide(0);
        if (parsed.caption) setCaption(parsed.caption);

        // save to history
        const histItem = {
          id: Date.now(),
          ts: new Date().toISOString(),
          topic: topic.slice(0, 80),
          format: formatId,
          lang, contentType,
          slides: next,
          caption: parsed.caption || '',
        };
        setStorage(s => ({ ...s, history: [histItem, ...s.history.slice(0, 19)] }));
      }
    } catch (err) {
      console.error('AI generation failed', err);
      alert((lang === 'tr' ? 'AI üretimi başarısız: ' : 'AI generation failed: ') + err.message);
    } finally {
      setGenerating(false);
    }
  };

  // ----- Save / load -----
  const saveCurrent = () => {
    const name = prompt(lang === 'tr' ? 'Şablon adı:' : 'Template name:', topic || 'Untitled');
    if (!name) return;
    const tpl = {
      id: Date.now(), name, ts: new Date().toISOString(),
      format: formatId, lang, contentType, logoPos,
      slides: JSON.parse(JSON.stringify(slides)),
      caption,
    };
    setStorage(s => ({ ...s, templates: [tpl, ...s.templates.slice(0, 49)] }));
  };

  const loadFromHistory = (item) => {
    setFormatId(item.format);
    setLang(item.lang || 'tr');
    setContentType(item.contentType || 'rehber');
    setSlides(item.slides);
    setCurrentSlide(0);
    if (item.caption) setCaption(item.caption);
    if (item.logoPos) setLogoPos(item.logoPos);
  };

  // ----- Export -----
  const [videoExporting, setVideoExporting] = useState(false);
  const [videoExportProgress, setVideoExportProgress] = useState(0);
  const [videoExportLabel, setVideoExportLabel] = useState('');
  const [exportFormat, setExportFormat] = useState('mp4'); // 'mp4' | 'webm'

  // Composite the text overlay onto the real video entirely inside ffmpeg.wasm.
  // The browser canvas/MediaRecorder path was unreliable on some GPUs:
  // drawImage(video) returned blank frames (video shown via a hardware overlay
  // that canvas can't read back), so exports had the text but no footage.
  // ffmpeg reads the actual decoded frames, so the video always lands.
  const exportVideoWithText = async (slideIdx) => {
    const slide = slides[slideIdx];
    if (!slide || !slide.video || !slide.video.url) return;
    if (videoExporting) return;
    if (!window.htmlToImage) { alert('html-to-image not loaded'); return; }
    if (!window.__loadFFmpeg) { alert('ffmpeg loader missing'); return; }
    setVideoExporting(true);
    setVideoExportProgress(0);
    setVideoExportLabel(lang === 'tr' ? 'Hazırlanıyor…' : 'Preparing…');
    try {
      // 1. Text overlay PNG — transparent everywhere except the gradient + text,
      //    so the video shows through when ffmpeg overlays it.
      const overlayNode = document.getElementById('export-overlay-' + slideIdx);
      if (!overlayNode) throw new Error('Overlay render node missing');
      const overlayPng = await window.htmlToImage.toPng(overlayNode, { pixelRatio: 1, cacheBust: true, width: format.w, height: format.h, imagePlaceholder: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' });
      const overlayBytes = new Uint8Array(await (await fetch(overlayPng)).arrayBuffer());

      // 2. Fetch the source video bytes (Pexels CORS download confirmed working).
      setVideoExportLabel(lang === 'tr' ? 'Video indiriliyor…' : 'Fetching video…');
      const srcResp = await fetch(slide.video.url);
      if (!srcResp.ok) throw new Error('Video indirilemedi (HTTP ' + srcResp.status + ')');
      const srcBytes = new Uint8Array(await srcResp.arrayBuffer());
      console.log('[vid-export] sources', { videoBytes: srcBytes.length, overlayBytes: overlayBytes.length, format: format.w + 'x' + format.h });

      // 3. ffmpeg composite: scale+crop video to cover the canvas, overlay text,
      //    constant 30 fps, H.264 yuv420p + faststart so it plays everywhere.
      setVideoExportLabel(lang === 'tr' ? 'Birleştiriliyor… (ilk seferde ~30 sn)' : 'Compositing… (~30s first time)');
      const { ffmpeg } = await window.__loadFFmpeg();
      ffmpeg.FS('writeFile', 'input.mp4', srcBytes);
      ffmpeg.FS('writeFile', 'overlay.png', overlayBytes);
      try { ffmpeg.setProgress(({ ratio }) => { if (ratio >= 0 && ratio <= 1) setVideoExportProgress(ratio); }); } catch {}
      const W = format.w, H = format.h;
      await ffmpeg.run(
        '-i', 'input.mp4',
        '-i', 'overlay.png',
        '-filter_complex',
        `[0:v]scale=${W}:${H}:force_original_aspect_ratio=increase,crop=${W}:${H},fps=30[bg];[bg][1:v]overlay=0:0:format=auto,format=yuv420p[v]`,
        '-map', '[v]', '-map', '0:a?',
        '-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '24',
        '-c:a', 'aac', '-b:a', '128k',
        '-movflags', '+faststart',
        '-shortest',
        'output.mp4',
      );
      const data = ffmpeg.FS('readFile', 'output.mp4');
      try { ffmpeg.FS('unlink', 'input.mp4'); ffmpeg.FS('unlink', 'overlay.png'); ffmpeg.FS('unlink', 'output.mp4'); } catch {}
      console.log('[vid-export] done', { mp4Bytes: data && data.length });
      if (!data || data.length < 1024) throw new Error('Çıktı boş döndü');

      const outBlob = new Blob([data.buffer], { type: 'video/mp4' });
      const url = URL.createObjectURL(outBlob);
      const a = document.createElement('a');
      a.href = url; a.download = `${brandId}-${formatId}-${Date.now()}.mp4`; a.click();
      setTimeout(() => URL.revokeObjectURL(url), 5000);
    } catch (err) {
      console.error('Video export failed', err);
      alert((lang === 'tr' ? 'Video export başarısız: ' : 'Video export failed: ') + err.message);
    } finally {
      setVideoExporting(false);
      setVideoExportProgress(0);
      setVideoExportLabel('');
    }
  };

  // Build the multi-clip reel entirely in ffmpeg.wasm — same reason as the
  // single export: canvas drawImage(video) returned blank frames on some GPUs.
  // Each slide becomes a uniform H.264 segment (video clip = footage + text
  // overlay; static slide = 5s still), then the segments are concatenated.
  const exportReelCombined = async () => {
    if (videoExporting) return;
    if (!window.htmlToImage) { alert('html-to-image not loaded'); return; }
    if (!window.__loadFFmpeg) { alert('ffmpeg loader missing'); return; }
    if (slides.length === 0) return;

    setVideoExporting(true);
    setVideoExportProgress(0);
    setVideoExportLabel(lang === 'tr' ? 'Hazırlanıyor…' : 'Preparing…');

    try {
      const PH = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
      const W = format.w, H = format.h;
      const toBytes = async (node) => {
        const png = await window.htmlToImage.toPng(node, { pixelRatio: 1, cacheBust: true, width: W, height: H, imagePlaceholder: PH });
        return new Uint8Array(await (await fetch(png)).arrayBuffer());
      };

      // ffmpeg.wasm 0.11 only runs ONE command reliably per instance, and the
      // multi-input concat filtergraph drops the video in the threaded wasm
      // build (text overlay survives → "blank" footage). So each clip is
      // composited exactly like the working single export, in its OWN fresh
      // ffmpeg instance (each does a single run), then the resulting segments
      // are joined with a final fresh instance via the concat demuxer.
      await window.__loadFFmpeg(); // ensures the script + window.FFmpeg are loaded
      const { createFFmpeg } = window.FFmpeg;
      const fresh = async () => {
        const ff = createFFmpeg({ log: false, corePath: location.origin + '/assets/ffmpeg/ffmpeg-core.js' });
        await ff.load();
        return ff;
      };
      const VENC = ['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '24', '-pix_fmt', 'yuv420p', '-video_track_timescale', '30000'];
      const N = slides.length;
      const segs = []; // encoded segment bytes, in order

      for (let i = 0; i < N; i++) {
        const s = slides[i];
        setVideoExportLabel(`${lang === 'tr' ? 'Sahne' : 'Clip'} ${i + 1}/${N}`);
        setVideoExportProgress(i / (N + 1));
        const ff = await fresh();
        try {
          if (s.video && s.video.url) {
            const ovNode = document.getElementById('export-overlay-' + i);
            if (!ovNode) throw new Error('Overlay node missing (' + i + ')');
            const ovBytes = await toBytes(ovNode);
            const srcResp = await fetch(s.video.url);
            if (!srcResp.ok) throw new Error('Video indirilemedi (HTTP ' + srcResp.status + ')');
            const srcBytes = new Uint8Array(await srcResp.arrayBuffer());
            ff.FS('writeFile', 'in.mp4', srcBytes);
            ff.FS('writeFile', 'ov.png', ovBytes);
            await ff.run(
              '-t', '15', '-i', 'in.mp4', '-i', 'ov.png',
              '-filter_complex', `[0:v]scale=${W}:${H}:force_original_aspect_ratio=increase,crop=${W}:${H},fps=30,setsar=1[bg];[bg][1:v]overlay=0:0:format=auto,format=yuv420p[v]`,
              '-map', '[v]', '-an', ...VENC, 'seg.mp4',
            );
          } else {
            const slNode = document.getElementById('export-slide-' + i);
            if (!slNode) throw new Error('Slide node missing (' + i + ')');
            const slBytes = await toBytes(slNode);
            ff.FS('writeFile', 'sl.png', slBytes);
            await ff.run(
              '-loop', '1', '-t', '5', '-i', 'sl.png',
              '-filter_complex', `[0:v]scale=${W}:${H},fps=30,setsar=1,format=yuv420p[v]`,
              '-map', '[v]', '-an', ...VENC, 'seg.mp4',
            );
          }
          const seg = ff.FS('readFile', 'seg.mp4');
          if (!seg || seg.length < 1024) throw new Error('Segment boş (' + (i + 1) + ')');
          segs.push(new Uint8Array(seg)); // copy out before the instance is torn down
          console.log('[reel] clip', i + 1, 'bytes', seg.length);
        } finally {
          try { ff.exit(); } catch {}
        }
      }

      // Join the uniform segments (stream copy — fast) in a fresh instance.
      setVideoExportLabel(lang === 'tr' ? 'Birleştiriliyor…' : 'Joining clips…');
      setVideoExportProgress(N / (N + 1));
      const ff = await fresh();
      let data;
      try {
        const lines = [];
        for (let i = 0; i < segs.length; i++) { ff.FS('writeFile', `s${i}.mp4`, segs[i]); lines.push(`file 's${i}.mp4'`); }
        ff.FS('writeFile', 'list.txt', new TextEncoder().encode(lines.join('\n')));
        await ff.run('-f', 'concat', '-safe', '0', '-i', 'list.txt', '-c', 'copy', '-movflags', '+faststart', 'reel.mp4');
        data = ff.FS('readFile', 'reel.mp4');
      } finally {
        try { ff.exit(); } catch {}
      }
      console.log('[reel] done', { clips: N, mp4Bytes: data && data.length });
      if (!data || data.length < 1024) throw new Error('Çıktı boş döndü');

      const outBlob = new Blob([data.buffer], { type: 'video/mp4' });
      const url = URL.createObjectURL(outBlob);
      const a = document.createElement('a');
      a.href = url; a.download = `${brandId}-reel-${Date.now()}.mp4`; a.click();
      setTimeout(() => URL.revokeObjectURL(url), 5000);
    } catch (err) {
      console.error('Reel export failed', err);
      alert((lang === 'tr' ? 'Reel export başarısız: ' : 'Reel export failed: ') + err.message);
    } finally {
      setVideoExporting(false);
      setVideoExportProgress(0);
      setVideoExportLabel('');
    }
  };
  const exportPng = async (slideIdx) => {
    const node = document.getElementById('export-slide-' + slideIdx);
    if (!node || !window.htmlToImage) return;
    try {
      const dataUrl = await window.htmlToImage.toPng(node, { pixelRatio: 1, cacheBust: true, imagePlaceholder: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' });
      const a = document.createElement('a');
      a.download = `${brandId}-${formatId}-${String(slideIdx + 1).padStart(2, '0')}.png`;
      a.href = dataUrl; a.click();
    } catch (e) {
      console.error(e);
      alert('Export failed: ' + e.message);
    }
  };

  const exportZip = async () => {
    if (!window.JSZip || !window.htmlToImage) { alert('Libraries not loaded'); return; }
    const zip = new window.JSZip();
    for (let i = 0; i < slides.length; i++) {
      const node = document.getElementById('export-slide-' + i);
      if (!node) continue;
      const dataUrl = await window.htmlToImage.toPng(node, { pixelRatio: 1, imagePlaceholder: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' });
      const blob = await fetch(dataUrl).then(r => r.blob());
      zip.file(`slide-${String(i + 1).padStart(2, '0')}.png`, blob);
    }
    if (caption) zip.file('caption.txt', caption);
    const blob = await zip.generateAsync({ type: 'blob' });
    const a = document.createElement('a');
    a.download = `${brandId}-${formatId}-${Date.now()}.zip`;
    a.href = URL.createObjectURL(blob); a.click();
  };

  const exportPdf = async () => {
    if (!window.jspdf || !window.htmlToImage) { alert('jsPDF not loaded'); return; }
    const { jsPDF } = window.jspdf;
    const pdf = new jsPDF({
      orientation: format.w > format.h ? 'landscape' : 'portrait',
      unit: 'px', format: [format.w, format.h],
    });
    for (let i = 0; i < slides.length; i++) {
      const node = document.getElementById('export-slide-' + i);
      if (!node) continue;
      const dataUrl = await window.htmlToImage.toPng(node, { pixelRatio: 1, imagePlaceholder: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' });
      if (i > 0) pdf.addPage([format.w, format.h], format.w > format.h ? 'landscape' : 'portrait');
      pdf.addImage(dataUrl, 'PNG', 0, 0, format.w, format.h);
    }
    pdf.save(`${brandId}-${formatId}-${Date.now()}.pdf`);
  };

  const canvasRef = useRef();
  const [canvasSize, setCanvasSize] = useState({ w: 800, h: 800 });
  useEffect(() => {
    const update = () => {
      if (!canvasRef.current) return;
      const r = canvasRef.current.getBoundingClientRect();
      setCanvasSize({ w: r.width, h: r.height });
    };
    update();
    window.addEventListener('resize', update);
    return () => window.removeEventListener('resize', update);
  }, []);

  const fitScale = useMemo(() => {
    const pad = 64;
    const maxW = canvasSize.w - pad;
    const maxH = canvasSize.h - pad - 80; // some headroom
    return Math.min(maxW / format.w, maxH / format.h, 0.65);
  }, [canvasSize, format]);

  const tr = lang === 'tr';

  return (
    <div className="app">
      {/* TOPBAR */}
      <div className="topbar">
        <div className="topbar-left">
          <div className="topbar-brand">
            <img key={brand.id} src={brand.logo} alt={brand.name} />
            <select
              className="brand-switcher"
              value={brandId}
              onChange={(e) => setBrandId(e.target.value)}
              title="Marka"
            >
              {BRANDS.map(b => <option key={b.id} value={b.id}>{b.name}</option>)}
            </select>
            <span>Social Studio</span>
            <span className="topbar-brand-sub">
              {format.name} · {format.sub} · {format.w}×{format.h}
            </span>
          </div>
        </div>
        <div className="topbar-right">
          <div className="lang-toggle">
            <button className={lang === 'tr' ? 'is-active' : ''} onClick={() => switchLang('tr')}>TR</button>
            <button className={lang === 'en' ? 'is-active' : ''} onClick={() => switchLang('en')}>EN</button>
          </div>
          <button className="btn ghost small" onClick={saveCurrent}>
            {tr ? 'Şablona kaydet' : 'Save template'}
          </button>
          <button className="btn ink small" onClick={exportPng.bind(null, currentSlide)}>
            PNG
          </button>
          {VIDEO_FORMATS.has(formatId) && slides.filter(s => s.video).length >= 2 && (
            <button
              className="btn primary small"
              onClick={exportReelCombined}
              disabled={videoExporting}
            >{videoExporting ? `${Math.round(videoExportProgress * 100)}%` : (tr ? '🎬 Reel birleştir' : '🎬 Combine reel')}</button>
          )}
          {isCarousel && (
            <>
              <button className="btn ink small" onClick={exportZip}>ZIP</button>
              <button className="btn ink small" onClick={exportPdf}>PDF</button>
            </>
          )}
        </div>
      </div>

      {/* LEFT SIDEBAR — Inputs */}
      <div className="sidebar">
        {/* Format */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Format' : 'Format'}</h3>
          <div className="format-grid">
            {FORMATS.map(f => {
              const ratio = f.w / f.h;
              const iconW = ratio >= 1 ? 28 : 28 * ratio;
              const iconH = ratio >= 1 ? 28 / ratio : 28;
              return (
                <button
                  key={f.id}
                  className={'format-btn' + (formatId === f.id ? ' is-active' : '')}
                  onClick={() => switchFormat(f.id)}
                >
                  <div className="format-icon" style={{ width: iconW, height: iconH }}></div>
                  <div>
                    <div className="format-label">{f.name}</div>
                    <div className="format-dim">{f.sub} · {f.w}×{f.h}</div>
                  </div>
                </button>
              );
            })}
          </div>
        </div>

        {/* Content type */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Kategori' : 'Category'}</h3>
          <div className="chips">
            {(tr ? CONTENT_TYPES_TR : CONTENT_TYPES_EN).map(c => (
              <button
                key={c.id}
                className={'chip red' + (contentType === c.id ? ' is-active' : '')}
                onClick={() => setContentType(c.id)}
              >{c.label}</button>
            ))}
          </div>
        </div>

        {/* AI / Manual tabs */}
        <div className="section">
          <h3 className="section-title">{tr ? 'İçerik' : 'Content'}</h3>
          <div className="content-tabs">
            <button
              className={'content-tab' + (aiMode ? ' is-active' : '')}
              onClick={() => setAiMode(true)}
            >{tr ? 'AI ile üret' : 'AI generate'}</button>
            <button
              className={'content-tab' + (!aiMode ? ' is-active' : '')}
              onClick={() => setAiMode(false)}
            >{tr ? 'Manuel yaz' : 'Manual'}</button>
          </div>
          {aiMode ? (
            <AiPanel
              topic={topic} setTopic={setTopic}
              onGenerate={generate} generating={generating}
              lang={lang} contentType={contentType}
            />
          ) : (
            <div style={{ fontSize: 12, color: 'var(--ml-ink-3)', lineHeight: 1.5 }}>
              {tr
                ? 'Sağ taraftaki "Slayt içeriği" panelinden başlık, açıklama ve diğer alanları manuel düzenleyin.'
                : 'Edit headline, body and other fields manually from the "Slide content" panel on the right.'}
            </div>
          )}
        </div>

        {/* Caption / post text */}
        {format.captionLimit > 0 && (
          <div className="section">
            <h3 className="section-title">{tr ? 'Gönderi metni (caption)' : 'Post caption'}</h3>
            <Field
              label=""
              value={caption}
              onChange={setCaption}
              multiline
              limit={format.captionLimit}
              placeholder={tr
                ? 'Gönderiyle yayınlanacak metin…'
                : 'Caption to publish with the post…'
              }
            />
          </div>
        )}

        {/* History */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Geçmiş' : 'History'}</h3>
          {storage.history.length === 0 && (
            <div className="history-empty">
              {tr ? 'Henüz geçmiş yok' : 'No history yet'}
            </div>
          )}
          {storage.history.slice(0, 6).map(item => (
            <button key={item.id} className="history-item" onClick={() => loadFromHistory(item)}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="history-item-title">{item.topic || 'Untitled'}</div>
                <div className="history-item-meta">
                  {item.format} · {new Date(item.ts).toLocaleDateString()}
                </div>
              </div>
              <span style={{ color: 'var(--ml-ink-4)' }}>↩</span>
            </button>
          ))}
        </div>

        {/* Templates */}
        {storage.templates.length > 0 && (
          <div className="section">
            <h3 className="section-title">{tr ? 'Şablonlarım' : 'My templates'}</h3>
            {storage.templates.slice(0, 8).map(item => (
              <button key={item.id} className="history-item" onClick={() => loadFromHistory(item)}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="history-item-title">{item.name}</div>
                  <div className="history-item-meta">{item.format}</div>
                </div>
                <span
                  className="icon-btn"
                  onClick={(e) => {
                    e.stopPropagation();
                    if (confirm(tr ? 'Sil?' : 'Delete?')) {
                      setStorage(s => ({ ...s, templates: s.templates.filter(t => t.id !== item.id) }));
                    }
                  }}
                >✕</span>
              </button>
            ))}
          </div>
        )}
      </div>

      {/* CANVAS */}
      <div className="canvas-wrap">
        <div className="canvas-toolbar">
          <div className="canvas-toolbar-left">
            <button
              className={'tool-tab' + (view === 'canvas' ? ' is-active' : '')}
              onClick={() => setView('canvas')}
            >{tr ? 'Tasarım' : 'Design'}</button>
            <button
              className={'tool-tab' + (view === 'feed' ? ' is-active' : '')}
              onClick={() => setView('feed')}
            >{tr ? 'Akış önizleme' : 'Feed preview'}</button>
          </div>
          <div className="canvas-toolbar-right">
            {isCarousel && (
              <>
                <span style={{ fontSize: 12, color: 'var(--ml-ink-3)', fontFamily: 'var(--ml-font-mono)' }}>
                  {currentSlide + 1} / {slides.length}
                </span>
                <button className="icon-btn" onClick={() => setCurrentSlide(Math.max(0, currentSlide - 1))}>←</button>
                <button className="icon-btn" onClick={() => setCurrentSlide(Math.min(slides.length - 1, currentSlide + 1))}>→</button>
              </>
            )}
          </div>
        </div>

        <div className="canvas-area" ref={canvasRef}>
          {view === 'canvas' && (
            <div className="canvas-stage">
              <SlideFrame
                key={brandId + ':' + logoSize}
                slide={slide}
                format={format}
                logoPos={logoPos}
                showBranding={showBranding}
                lang={lang}
                index={currentSlide}
                total={slides.length}
                scale={fitScale}
                safeZone={safeZone}
              />
            </div>
          )}
          {view === 'feed' && (
            <FeedPreview key={brandId + ':' + logoSize} slides={slides} format={format} logoPos={logoPos} showBranding={showBranding} lang={lang} caption={caption} brand={brand} />
          )}

          {/* Off-screen full-res slides for export */}
          <div style={{ position: 'fixed', left: -99999, top: 0, pointerEvents: 'none' }}>
            {slides.map((s, i) => (
              <React.Fragment key={brandId + ':' + logoSize + ':' + i}>
                <div id={'export-slide-' + i} style={{ width: format.w, height: format.h }}>
                  {window.renderLayout(s.layout, {
                    slide: s, bg: s.bg || 'paper', logoPos, showBranding,
                    textScale: s.textScale || 1, overlayOpacity: s.overlayOpacity ?? 0.5,
                    index: i, total: slides.length, lang,
                    width: format.w, height: format.h,
                  })}
                </div>
                {/* Text-only overlay for video composite — transparent bg, no video */}
                {s.video && s.video.url && (
                  <div id={'export-overlay-' + i}
                       style={{ width: format.w, height: format.h, background: 'transparent' }}>
                    {window.renderLayout(s.layout, {
                      slide: { ...s, video: null, image: null }, bg: 'overlay', logoPos, showBranding,
                      textScale: s.textScale || 1, overlayOpacity: s.overlayOpacity ?? 0.5,
                      index: i, total: slides.length, lang,
                      width: format.w, height: format.h,
                    })}
                  </div>
                )}
              </React.Fragment>
            ))}
          </div>
        </div>

        {/* Slide thumbs */}
        {isCarousel && (
          <div className="slide-thumbs">
            {slides.map((s, i) => (
              <button
                key={i}
                className={'slide-thumb' + (i === currentSlide ? ' is-active' : '')}
                onClick={() => setCurrentSlide(i)}
                onDoubleClick={() => removeSlide(i)}
                title={tr ? 'Çift tıkla → sil' : 'Double-click to remove'}
              >
                <div style={{ width: 56, height: 56, overflow: 'hidden', position: 'absolute', inset: 2 }}>
                  <div style={{
                    width: format.w, height: format.h,
                    transform: `scale(${56 / format.w})`, transformOrigin: 'top left',
                  }}>
                    {window.renderLayout(s.layout, {
                      slide: s, bg: s.bg || 'paper', logoPos, showBranding,
                      textScale: s.textScale || 1, overlayOpacity: s.overlayOpacity ?? 0.5,
                      index: i, total: slides.length, lang,
                      width: format.w, height: format.h,
                    })}
                  </div>
                </div>
                <span className="slide-thumb-num">{i + 1}</span>
              </button>
            ))}
            {slides.length < format.maxSlides && (
              <button
                className="slide-thumb slide-thumb-add"
                onClick={addSlide}
                title={tr ? 'Slayt ekle' : 'Add slide'}
              >+</button>
            )}
          </div>
        )}
      </div>

      {/* RIGHT SIDEBAR — Slide editor / style */}
      <div className="sidebar right">
        {/* Layout picker */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Düzen' : 'Layout'}</h3>
          <div className="layout-grid">
            {window.LAYOUTS.map(L => (
              <button
                key={L.id}
                className={'layout-card' + (slide.layout === L.id ? ' is-active' : '')}
                onClick={() => updateSlide({ layout: L.id })}
                title={L.desc}
              >
                <div className="mini">
                  {window.renderLayout(L.id, {
                    slide: { ...slide, eyebrow: L.name, bg: slide.bg || 'paper' },
                    bg: slide.bg || 'paper', logoPos: 'none',
                    index: 0, total: 1, lang,
                    width: format.w, height: format.h,
                  })}
                </div>
                <div className="layout-card-label">{L.name}</div>
              </button>
            ))}
          </div>
        </div>

        {/* Text size */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Yazı boyutu' : 'Text size'}</h3>
          <div className="chips">
            {[{v:1,label:'Normal'},{v:1.2,label:tr?'Büyük':'Large'},{v:1.4,label:tr?'Çok büyük':'X-Large'}].map(opt => (
              <button key={opt.v}
                className={'chip' + ((slide.textScale || 1) === opt.v ? ' is-active' : '')}
                onClick={() => updateSlide({ textScale: opt.v })}
              >{opt.label}</button>
            ))}
          </div>
        </div>

        {/* Background */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Arka plan' : 'Background'}</h3>
          <div className="swatch-row">
            {BG_STYLES.map(b => (
              <button
                key={b.id}
                className={'swatch ' + b.id + (slide.bg === b.id ? ' is-active' : '')}
                onClick={() => updateSlide({ bg: b.id })}
                title={b.label}
              />
            ))}
          </div>
          <div style={{ marginTop: 10 }}>
            <span className="field-label" style={{ display: 'block', marginBottom: 6 }}>
              {tr ? 'Karartma' : 'Overlay'}
            </span>
            <input type="range" min="0" max="1" step="0.05"
              value={slide.overlayOpacity ?? 0.5}
              onChange={e => updateSlide({ overlayOpacity: parseFloat(e.target.value) })}
              style={{ width: '100%' }}
            />
          </div>
        </div>

        {/* Logo position */}
        <div className="section">
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 8 }}>
            <h3 className="section-title" style={{ marginBottom: 0 }}>{tr ? 'Logo konumu' : 'Logo position'}</h3>
            {brand.font && brand.font !== 'Onest' && (
              <span style={{ fontSize: 11, color: 'var(--ml-ink-3)', fontFamily: `"${brand.font}", sans-serif` }}>
                {brand.font}
              </span>
            )}
          </div>
          <div className="chips">
            {LOGO_POSITIONS.map(p => (
              <button
                key={p.id}
                className={'chip' + (logoPos === p.id ? ' is-active' : '')}
                onClick={() => setLogoPos(p.id)}
              >{p.label}</button>
            ))}
          </div>
          <div style={{ marginTop: 10 }}>
            <span className="field-label" style={{ display: 'block', marginBottom: 6 }}>
              {tr ? 'Logo boyutu' : 'Logo size'}
            </span>
            <div className="chips">
              {LOGO_SIZE_OPTIONS.map(s => (
                <button
                  key={s.id}
                  className={'chip' + (logoSize === s.id ? ' is-active' : '')}
                  onClick={() => setLogoSize(s.id)}
                  title={LOGO_SIZE_MAP[s.id] + '×'}
                >{tr ? s.labelTr : s.labelEn}</button>
              ))}
            </div>
          </div>
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, fontSize: 13, cursor: 'pointer' }}>
            <input type="checkbox" checked={showBranding} onChange={e => setShowBranding(e.target.checked)} />
            {tr ? 'URL watermarkı göster' : 'Show URL watermark'}
          </label>
          {showBranding && (
            <div style={{ marginTop: 10 }}>
              <span className="field-label" style={{ display: 'block', marginBottom: 6 }}>
                {tr ? 'Watermark konumu' : 'Watermark position'}
              </span>
              <div className="chips">
                {LOGO_POSITIONS.filter(p => p.id !== 'none').map(p => (
                  <button
                    key={p.id}
                    className={'chip' + (brandingPos === p.id ? ' is-active' : '')}
                    onClick={() => setBrandingPos(p.id)}
                  >{p.label}</button>
                ))}
              </div>
            </div>
          )}
          {showBranding && (
            <div style={{ marginTop: 10 }}>
              <span className="field-label" style={{ display: 'block', marginBottom: 6 }}>
                {tr ? 'Watermark boyutu' : 'Watermark size'}
              </span>
              <div className="chips">
                {BRANDING_SIZE_OPTIONS.map(s => (
                  <button
                    key={s.id}
                    className={'chip' + (brandingSize === s.id ? ' is-active' : '')}
                    onClick={() => setBrandingSize(s.id)}
                  >{tr ? s.labelTr : s.labelEn}</button>
                ))}
              </div>
            </div>
          )}
        </div>

        {/* Safe Zone — only shown for 9:16 story/reel formats */}
        {(formatId === 'ig-story' || formatId === 'ig-reel') && (
          <div className="section">
            <h3 className="section-title">{tr ? 'Güvenli Alan' : 'Safe Zone'}</h3>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
              <input type="checkbox" checked={safeZone} onChange={e => setSafeZone(e.target.checked)} />
              {tr ? 'Instagram UI güvenli alanı' : 'Instagram UI safe zone'}
            </label>
            {safeZone && (
              <div style={{ fontSize: 11, color: 'var(--ml-ink-3)', lineHeight: 1.5, marginTop: 6 }}>
                {tr
                  ? 'Önizlemede kırmızı bantlar tehlikeli alanları gösterir. Dışa aktarmalarda içerik profil, altyazı ve ikon butonlarının dışında kalır.'
                  : 'Red bands in preview mark unsafe zones. Exports keep content clear of the profile, captions, and action icons.'}
              </div>
            )}
          </div>
        )}

        {/* Image upload + Pexels search */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Görsel' : 'Image'}</h3>
          <Dropzone
            image={slide.image}
            onImage={(img) => updateSlide({ image: img })}
            label={tr ? 'Görseli sürükleyin' : 'Drop image'}
          />
          <details style={{ marginTop: 10 }}>
            <summary style={{ cursor: 'pointer', fontSize: 12, color: 'var(--ml-ink-3)', userSelect: 'none' }}>
              {tr ? '🔍 Pexels\'ta ara' : '🔍 Search Pexels'}
            </summary>
            <div style={{ marginTop: 8 }}>
              <PexelsPanel
                lang={lang}
                onSelect={(src) => updateSlide({ image: src })}
              />
            </div>
          </details>
        </div>

        {/* Video upload (Reels / Story) */}
        {VIDEO_FORMATS.has(formatId) && (
          <div className="section">
            <h3 className="section-title">
              {tr ? 'Video (Reels / Story)' : 'Video (Reels / Story)'}
            </h3>
            <VideoDropzone
              video={slide.video}
              onVideo={(v) => updateSlide({ video: v })}
              label={tr ? 'Video sürükleyin (MP4/MOV)' : 'Drop video (MP4/MOV)'}
            />
            <details style={{ marginTop: 8 }}>
              <summary style={{ cursor: 'pointer', fontSize: 12, color: 'var(--ml-ink-3)', userSelect: 'none' }}>
                {tr ? '🔍 Pexels\'ta ara' : '🔍 Search Pexels'}
              </summary>
              <div style={{ marginTop: 8 }}>
                <PexelsPanel
                  lang={lang}
                  showVideo={true}
                  onSelectVideo={(v) => updateSlide({ video: { url: v.url, name: v.name, size: 0, type: 'video/mp4' } })}
                  onSelect={(src) => updateSlide({ image: src })}
                />
              </div>
            </details>

            {/* Output format toggle */}
            <div className="field" style={{ marginTop: 12 }}>
              <span className="field-label">{tr ? 'Çıktı formatı' : 'Output format'}</span>
              <div className="chips">
                <button
                  className={'chip' + (exportFormat === 'mp4' ? ' is-active' : '')}
                  onClick={() => setExportFormat('mp4')}
                  title={tr ? 'Daha geniş uyumluluk (Instagram, LinkedIn). İlk seferinde ~30 sn ek dönüşüm.' : 'Wider compatibility. ~30s first-run conversion.'}
                >MP4 {tr ? '(önerilen)' : '(recommended)'}</button>
                <button
                  className={'chip' + (exportFormat === 'webm' ? ' is-active' : '')}
                  onClick={() => setExportFormat('webm')}
                  title={tr ? 'Daha hızlı, dönüşüm yok. LinkedIn destekler, Instagram için MP4 gerekir.' : 'Faster, no conversion. LinkedIn supports; IG needs MP4.'}
                >WebM {tr ? '(hızlı)' : '(fast)'}</button>
              </div>
            </div>

            <div style={{
              marginTop: 8, fontSize: 11, lineHeight: 1.5,
              color: 'var(--ml-ink-4)',
            }}>
              {tr
                ? exportFormat === 'mp4'
                  ? 'Video, tasarımdaki yazıların arkasında oynar. MP4 dönüşümü ilk kullanımda kütüphane indirilir (~30 MB), sonra hızlanır.'
                  : 'Video, tasarımdaki yazıların arkasında oynar. WebM dönüşümsüz/hızlıdır; LinkedIn kabul eder, Instagram için MP4 gerekir.'
                : exportFormat === 'mp4'
                  ? 'Video plays behind text. First MP4 export downloads converter (~30MB) then caches.'
                  : 'Fast WebM export. LinkedIn supports; Instagram needs MP4.'}
            </div>
            {slide.video && (
              <>
                <button
                  className="btn primary small full"
                  style={{ marginTop: 8 }}
                  disabled={videoExporting}
                  onClick={() => exportVideoWithText(currentSlide)}
                >
                  {videoExporting
                    ? <><span className="ai-spinner"></span> {Math.round(videoExportProgress * 100)}%</>
                    : (tr ? '↓ Yazılı videoyu indir' : '↓ Export video with text')}
                </button>
                <button
                  className="btn ghost small full"
                  style={{ marginTop: 6 }}
                  onClick={() => {
                    const a = document.createElement('a');
                    a.href = slide.video.url;
                    a.download = slide.video.name || `${brandId}-${formatId}-raw.mp4`;
                    a.click();
                  }}
                >{tr ? 'Ham videoyu indir' : 'Download raw video'}</button>
              </>
            )}
          </div>
        )}

        {/* Slide content (manual) */}
        <div className="section">
          <h3 className="section-title">{tr ? 'Slayt içeriği' : 'Slide content'}</h3>
          <div className="section-row">
            <Field label="Eyebrow" value={slide.eyebrow} onChange={v => updateSlide({ eyebrow: v })} limit={40} />
            {slide.layout !== 'quote' && slide.layout !== 'qa' && (
              <Field label={tr ? 'Başlık' : 'Title'} value={slide.title} onChange={v => updateSlide({ title: v })} multiline limit={120} />
            )}
            {!['quote', 'qa', 'compare'].includes(slide.layout) && (
              <Field label={tr ? 'Açıklama' : 'Body'} value={slide.body} onChange={v => updateSlide({ body: v })} multiline limit={240} />
            )}
            {slide.layout === 'highlight' && (
              <Field label={tr ? 'Vurgulanan kelime' : 'Highlighted word'} value={slide.highlight} onChange={v => updateSlide({ highlight: v })} limit={30} />
            )}
            {slide.layout === 'quote' && (
              <>
                <Field label={tr ? 'Alıntı' : 'Quote'} value={slide.quote || slide.title} onChange={v => updateSlide({ quote: v })} multiline limit={200} />
                <Field label={tr ? 'Yazar' : 'Author'} value={slide.author} onChange={v => updateSlide({ author: v })} limit={60} />
                <Field label={tr ? 'Rol / Ünvan' : 'Role / Title'} value={slide.body} onChange={v => updateSlide({ body: v })} limit={60} />
              </>
            )}
            {slide.layout === 'qa' && (
              <>
                <Field label={tr ? 'Soru' : 'Question'} value={slide.question} onChange={v => updateSlide({ question: v })} multiline limit={140} />
                <Field label={tr ? 'Cevap' : 'Answer'} value={slide.answer} onChange={v => updateSlide({ answer: v })} multiline limit={280} />
              </>
            )}
            {(slide.layout === 'stat' || slide.layout === 'step') && (
              <>
                <Field
                  label={slide.layout === 'step' ? (tr ? 'Adım no' : 'Step no') : (tr ? 'Sayı' : 'Number')}
                  value={slide.stat} onChange={v => updateSlide({ stat: v })} limit={10} />
                {slide.layout === 'stat' && (
                  <Field label={tr ? 'Açıklayıcı' : 'Label'} value={slide.statLabel} onChange={v => updateSlide({ statLabel: v })} limit={80} />
                )}
              </>
            )}
            {(['list','index','platform','compare','mono'].includes(slide.layout)) && (
              <Field
                label={tr ? 'Maddeler (her satıra bir)' : 'Items (one per line)'}
                value={(slide.items || []).join('\n')}
                onChange={v => updateSlide({ items: v.split('\n').filter(x => x.trim()) })}
                multiline
              />
            )}
            <Field label="CTA" value={slide.cta} onChange={v => updateSlide({ cta: v })} limit={50} />
            <Field label="Footer / URL" value={slide.footer} onChange={v => updateSlide({ footer: v })} limit={60} />
          </div>
        </div>

        {/* Slide management */}
        {isCarousel && (
          <div className="section">
            <h3 className="section-title">{tr ? 'Slayt yönetimi' : 'Slide ops'}</h3>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              <button className="btn ghost small" onClick={duplicateSlide} disabled={slides.length >= format.maxSlides}>
                {tr ? 'Slaytı kopyala' : 'Duplicate'}
              </button>
              <button className="btn ghost small" onClick={() => removeSlide(currentSlide)} disabled={slides.length <= 1}>
                {tr ? 'Slaytı sil' : 'Delete slide'}
              </button>
              <button className="btn ghost small" onClick={addSlide} disabled={slides.length >= format.maxSlides}>
                {tr ? '+ Slayt ekle' : '+ Add slide'}
              </button>
            </div>
          </div>
        )}
      </div>

      {/* Recording modal — semitransparent, so the live canvas underneath
          remains uncovered and frames continue to composite (Chromium drops
          frames on fully-occluded canvases used with captureStream). */}
      {videoExporting && (
        <div style={{
          position: 'fixed', left: 0, right: 0, bottom: 0, height: 140,
          zIndex: 9999,
          background: 'linear-gradient(180deg, rgba(14,14,15,0) 0%, rgba(14,14,15,0.95) 100%)',
          color: 'white',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          gap: 24, padding: '0 32px',
          pointerEvents: 'none',
        }}>
          <div style={{
            width: 36, height: 36, borderRadius: '50%',
            border: '2.5px solid rgba(255,255,255,0.15)',
            borderTopColor: 'var(--ml-red)',
            animation: 'ml-spin 0.8s linear infinite',
            flexShrink: 0,
          }} />
          <div style={{ flex: 1, maxWidth: 420 }}>
            <div style={{ fontSize: 14, fontWeight: 600, letterSpacing: '-0.01em', marginBottom: 6 }}>
              {videoExportLabel || (tr ? 'Video hazırlanıyor…' : 'Preparing video…')}
              <span style={{ marginLeft: 10, fontSize: 12, color: 'rgba(255,255,255,0.6)', fontFamily: 'var(--ml-font-mono)' }}>
                {Math.round(videoExportProgress * 100)}%
              </span>
            </div>
            <div style={{
              height: 3, background: 'rgba(255,255,255,0.15)',
              borderRadius: 2, overflow: 'hidden',
            }}>
              <div style={{
                width: `${videoExportProgress * 100}%`, height: '100%',
                background: 'var(--ml-red)', transition: 'width 0.15s',
              }} />
            </div>
            <div style={{ fontSize: 10, color: 'rgba(255,255,255,0.5)', marginTop: 6 }}>
              {tr ? 'İşlem tamamlanana kadar bekleyin · sekmeyi kapatmayın'
                  : 'Please wait until done · keep this tab open'}
            </div>
          </div>
        </div>
      )}

    </div>
  );
}

// ============================================================
// Feed Preview (Instagram + LinkedIn simulation)
// ============================================================
function FeedPreview({ slides, format, logoPos, showBranding = true, lang, caption, brand = {} }) {
  const [active, setActive] = useState(0);
  const sim = format.network === 'instagram' ? 'instagram' : 'linkedin';
  const scale = 420 / format.w;
  const slide = slides[active];
  const brandName = brand.name || 'Brand';
  const brandHandle = brandName.toLowerCase().replace(/\s+/g, '');

  return (
    <div style={{ display: 'flex', gap: 32, padding: '24px 0', alignItems: 'flex-start', justifyContent: 'center' }}>
      <div className="feed-sim">
        <div className="feed-sim-head">
          <div className="feed-sim-avatar">{brandName[0]}</div>
          <div>
            <div className="feed-sim-name">{brandHandle}</div>
            <div className="feed-sim-meta">
              {sim === 'instagram' ? 'İstanbul, Turkey' : brandName + ' · ' + (lang === 'tr' ? 'Şirket sayfası' : 'Company page')}
            </div>
          </div>
          <div style={{ marginLeft: 'auto', color: 'var(--ml-ink-3)' }}>···</div>
        </div>
        <div className="feed-sim-image" style={{ width: 420, height: 420 * (format.h / format.w), overflow: 'hidden', position: 'relative' }}>
          <div style={{
            width: format.w, height: format.h,
            transform: `scale(${scale})`, transformOrigin: 'top left',
          }}>
            {window.renderLayout(slide.layout, {
              slide, bg: slide.bg || 'paper', logoPos, showBranding,
              textScale: slide.textScale || 1, overlayOpacity: slide.overlayOpacity ?? 0.5,
              index: active, total: slides.length, lang,
              width: format.w, height: format.h,
            })}
          </div>
          {slides.length > 1 && (
            <>
              <div style={{
                position: 'absolute', top: 12, right: 12,
                background: 'rgba(0,0,0,0.6)', color: 'white',
                padding: '4px 10px', borderRadius: 999,
                fontSize: 12, fontFamily: 'var(--ml-font-mono)',
              }}>{active + 1}/{slides.length}</div>
              <button
                style={{
                  position: 'absolute', left: 8, top: '50%', transform: 'translateY(-50%)',
                  width: 30, height: 30, borderRadius: '50%',
                  background: 'rgba(255,255,255,0.9)', border: 0, cursor: 'pointer',
                }}
                onClick={() => setActive(Math.max(0, active - 1))}
              >‹</button>
              <button
                style={{
                  position: 'absolute', right: 8, top: '50%', transform: 'translateY(-50%)',
                  width: 30, height: 30, borderRadius: '50%',
                  background: 'rgba(255,255,255,0.9)', border: 0, cursor: 'pointer',
                }}
                onClick={() => setActive(Math.min(slides.length - 1, active + 1))}
              >›</button>
            </>
          )}
        </div>
        {sim === 'instagram' && (
          <div className="feed-sim-actions">
            <span>♡</span><span>💬</span><span>↗</span>
            <span style={{ marginLeft: 'auto' }}>⌑</span>
          </div>
        )}
        <div className="feed-sim-caption">
          <strong>{brandHandle}</strong>{' '}
          {caption || (lang === 'tr' ? 'Gönderi metnini sol panelden ekleyin.' : 'Add caption text from the left panel.')}
        </div>
      </div>

      <div style={{ maxWidth: 280, color: 'var(--ml-ink-3)', fontSize: 13, lineHeight: 1.5 }}>
        <div style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--ml-ink-4)', marginBottom: 8 }}>
          {sim === 'instagram' ? 'Instagram preview' : 'LinkedIn preview'}
        </div>
        <p>
          {lang === 'tr'
            ? 'Bu, gönderinin akışta yaklaşık olarak nasıl görüneceğini gösterir. Carousel\'i kaydırarak diğer slaytları görüntüleyin.'
            : 'Approximation of how the post will appear in feed. Swipe carousel to see other slides.'}
        </p>
      </div>
    </div>
  );
}

function Root() {
  const [unlocked, setUnlocked] = React.useState(() => sessionStorage.getItem(AUTH_KEY) === '1');
  if (!unlocked) return <PasswordGate onUnlock={() => setUnlocked(true)} />;
  return <App />;
}

ReactDOM.createRoot(document.getElementById('root')).render(<Root />);
