// builder-combined.jsx — 16°S Post Builder (Dark + Light unified)
// Same builder, with a theme toggle at the top. The toggle swaps between
// the dark template components (TplCoordinates / TplCallSheet / …) and
// their light counterparts (TplCoordinatesLight / …).
//
// Carousel mode is dark-only (SecondarySlide uses dark scrims by design),
// so switching to light while in carousel mode falls back to single-post.

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

// ──────────────────────────────────────────────────────────────────────────
// Geometry: decimal degrees → DMS string  39.4700 → "39°28′12″N"
// ──────────────────────────────────────────────────────────────────────────
function toDMS(decimal, axis /* 'lat' | 'lng' */) {
  if (decimal == null || isNaN(decimal)) return '';
  const positive = decimal >= 0;
  const abs = Math.abs(decimal);
  const deg = Math.floor(abs);
  const minFloat = (abs - deg) * 60;
  const min = Math.floor(minFloat);
  const sec = Math.round((minFloat - min) * 60);
  let d = deg, m = min, s = sec;
  if (s === 60) { s = 0; m += 1; }
  if (m === 60) { m = 0; d += 1; }
  const hemi = axis === 'lat' ? (positive ? 'N' : 'S') : (positive ? 'E' : 'W');
  const pad = (n) => String(n).padStart(2, '0');
  return `${d}°${pad(m)}′${pad(s)}″${hemi}`;
}
function toDMSShort(lat, lng) {
  if (lat == null || lng == null) return '';
  const fmt = (d, axis) => {
    const positive = d >= 0;
    const abs = Math.abs(d);
    const deg = Math.floor(abs);
    const min = Math.round((abs - deg) * 60);
    const hemi = axis === 'lat' ? (positive ? 'N' : 'S') : (positive ? 'E' : 'W');
    return `${deg}°${String(min).padStart(2, '0')}′${hemi}`;
  };
  return `${fmt(lat, 'lat')} · ${fmt(lng, 'lng')}`;
}

// ──────────────────────────────────────────────────────────────────────────
// Geocoding via Nominatim (OpenStreetMap). Debounced.
// ──────────────────────────────────────────────────────────────────────────
function useGeocode(query, enabled = true) {
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  useEffect(() => {
    if (!enabled || !query || query.trim().length < 2) {
      setResults([]); setLoading(false); setError(null);
      return;
    }
    const ctl = new AbortController();
    setLoading(true); setError(null);
    const t = setTimeout(async () => {
      try {
        const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&addressdetails=1&limit=6`;
        const res = await fetch(url, { signal: ctl.signal, headers: { 'Accept-Language': 'en' } });
        if (!res.ok) throw new Error('Geocode failed');
        setResults(await res.json());
      } catch (e) {
        if (e.name !== 'AbortError') setError(e.message);
      } finally { setLoading(false); }
    }, 350);
    return () => { ctl.abort(); clearTimeout(t); };
  }, [query, enabled]);
  return { results, loading, error };
}

function extractRegion(addr) {
  if (!addr) return '';
  return addr.state || addr.region || addr.county || addr.country || '';
}
function extractPlace(item) {
  const a = item.address || {};
  return a.city || a.town || a.village || a.hamlet || a.municipality
    || (item.display_name || '').split(',')[0];
}

// ──────────────────────────────────────────────────────────────────────────
// Templates registries — one set per theme.
// ──────────────────────────────────────────────────────────────────────────
const TEMPLATE_NAMES = {
  coords:    'Coordinates',
  callsheet: 'Call Sheet',
  parallel:  'Parallel',
  bigtype:   'Big Type',
  weather:   'Weather Slate',
};
const TEMPLATE_SETS = {
  dark: {
    coords:    window.TplCoordinates,
    callsheet: window.TplCallSheet,
    parallel:  window.TplParallel,
    bigtype:   window.TplBigType,
    weather:   window.TplWeather,
  },
  light: {
    coords:    window.TplCoordinatesLight,
    callsheet: window.TplCallSheetLight,
    parallel:  window.TplParallelLight,
    bigtype:   window.TplBigTypeLight,
    weather:   window.TplWeatherLight,
  },
};

const RATIOS = {
  '1:1':  { name: '1:1 · feed',    w: 540, h: 540, target: [1080, 1080] },
  '4:5':  { name: '4:5 · feed',    w: 480, h: 600, target: [1080, 1350] },
  '9:16': { name: '9:16 · story',  w: 380, h: 676, target: [1080, 1920] },
  '16:9': { name: '16:9 · landscape', w: 820, h: 461, target: [1920, 1080] },
};

// ──────────────────────────────────────────────────────────────────────────
// Map shared post fields onto each template's data shape.
// ──────────────────────────────────────────────────────────────────────────
function buildData(template, fields) {
  let { location, region, lat, lng, client, project, date, headline, frame, day, wind, dir, sea, event, headlineMode, mediaKind } = fields;
  const latDMS = lat != null ? toDMS(lat, 'lat') : '';
  const lngDMS = lng != null ? toDMS(lng, 'lng') : '';
  const coordsShort = toDMSShort(lat, lng);
  const isClientMode = headlineMode === 'client';
  const primary   = isClientMode ? (client || location) : (location || '');
  const secondary = isClientMode ? (location || client) : (client || '');
  const baseExtras = { mediaKind: mediaKind || 'photo' };
  switch (template) {
    case 'coords':
      return { ...baseExtras, location: primary || '—', region: region || '', lat: latDMS, lng: lngDMS, client: secondary || '', project: project || '', date: date || '' };
    case 'callsheet':
      return { ...baseExtras, location: primary || '—', region: region || '', lat: latDMS, lng: lngDMS, client: secondary || '', project: project || '', day: day || '', date: date || '', frame: frame || '' };
    case 'parallel': {
      const h = headline?.trim() || primary || '—';
      return {
        ...baseExtras,
        location: location || '', lat: latDMS, lng: lngDMS,
        client: secondary || '', project: project || '', date: date || '',
        headline: h.includes('\n') ? h : h.split(' ').length > 1
          ? h.replace(/ ([^ ]+)$/, '\n$1')
          : h + '\n.',
      };
    }
    case 'bigtype': {
      const lines = (headline?.trim() || primary || '—').split('\n');
      while (lines.length < 3) lines.unshift('');
      const [line1, line2, accent] = lines.slice(-3);
      return { ...baseExtras, line1, line2, accent, client: secondary || '', location: location || '', coords: coordsShort, date: date || '' };
    }
    case 'weather':
      return { ...baseExtras, location: primary || '—', lat: latDMS, lng: lngDMS, client: secondary || '', project: project || '', wind: wind || '00', dir: dir || 'N', sea: sea || '0.0m', event: event || '', day: day || '', date: date || '' };
    default: return {};
  }
}

// ──────────────────────────────────────────────────────────────────────────
// UI bits
// ──────────────────────────────────────────────────────────────────────────
function Field({ label, value, onChange, placeholder, type = 'text' }) {
  return (
    <label>
      <span className="lbl">{label}</span>
      <input type={type} value={value || ''} placeholder={placeholder} onChange={(e) => onChange(e.target.value)} />
    </label>
  );
}
function TextArea({ label, value, onChange, placeholder, rows = 2 }) {
  return (
    <label>
      <span className="lbl">{label}</span>
      <textarea rows={rows} value={value || ''} placeholder={placeholder} onChange={(e) => onChange(e.target.value)} />
    </label>
  );
}

function LocationField({ value, onPick, lat, lng }) {
  const [query, setQuery] = useState(value || '');
  const [open, setOpen] = useState(false);
  const [active, setActive] = useState(0);
  const [touched, setTouched] = useState(false);
  const wrapRef = useRef(null);

  useEffect(() => { if ((value || '') !== query && !touched) setQuery(value || ''); }, [value]);

  const { results, loading, error } = useGeocode(open ? query : '', open);

  useEffect(() => {
    const onDoc = (e) => { if (!wrapRef.current?.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  const pick = (item) => {
    const name = extractPlace(item);
    onPick({
      location: name,
      region: extractRegion(item.address),
      lat: parseFloat(item.lat),
      lng: parseFloat(item.lon),
    });
    setQuery(name);
    setOpen(false);
    setTouched(false);
  };
  const onKey = (e) => {
    if (!open || results.length === 0) return;
    if (e.key === 'ArrowDown') { e.preventDefault(); setActive((i) => Math.min(i + 1, results.length - 1)); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setActive((i) => Math.max(i - 1, 0)); }
    else if (e.key === 'Enter') { e.preventDefault(); pick(results[active]); }
    else if (e.key === 'Escape') { setOpen(false); }
  };

  return (
    <label>
      <span className="lbl">Location · auto lat/long</span>
      <div className="geo-wrap" ref={wrapRef}>
        <input
          type="text"
          value={query}
          placeholder="Type a place — Valencia, Le Mans, Porto Cervo…"
          onChange={(e) => { setQuery(e.target.value); setOpen(true); setActive(0); setTouched(true); }}
          onFocus={() => setOpen(true)}
          onKeyDown={onKey}
          autoComplete="off"
        />
        {open && query.trim().length >= 2 && (
          <div className="geo-list">
            {loading && <div className="geo-item"><div className="gi-meta">Searching…</div></div>}
            {!loading && results.length === 0 && !error && (
              <div className="geo-item"><div className="gi-meta">No matches</div></div>
            )}
            {!loading && error && (
              <div className="geo-item"><div className="gi-meta" style={{ color: '#c44' }}>· {error}</div></div>
            )}
            {!loading && results.map((r, i) => (
              <div
                key={r.place_id}
                className={`geo-item ${i === active ? 'active' : ''}`}
                onMouseDown={(e) => { e.preventDefault(); pick(r); }}
                onMouseEnter={() => setActive(i)}
              >
                <div className="gi-name">{extractPlace(r)}</div>
                <div className="gi-meta">
                  {r.display_name?.split(',').slice(1, 3).join(',').trim() || extractRegion(r.address)}
                  {' · '}
                  {toDMSShort(parseFloat(r.lat), parseFloat(r.lon))}
                </div>
              </div>
            ))}
          </div>
        )}
        <div className="geo-status">
          {lat != null && lng != null
            ? <>· {toDMS(lat, 'lat')} &nbsp; {toDMS(lng, 'lng')}</>
            : <>· No coordinates yet</>}
        </div>
      </div>
    </label>
  );
}

// ──────────────────────────────────────────────────────────────────────────
// App
// ──────────────────────────────────────────────────────────────────────────
const DEFAULT_FIELDS = {
  location: 'Valencia',
  region: 'Valencian Community',
  lat: 39.4699,
  lng: -0.3763,
  client: 'Mercedes-AMG',
  project: 'F1 Pre-Season Test',
  date: 'May 2026',
  headline: '',
  frame: 'A_047',
  day: 'Day 02',
  wind: '24',
  dir: 'NNW',
  sea: '1.4m',
  event: 'F1 Pre-Season Test',
  headlineMode: 'location',
  mediaKind: 'photo',
};

function App() {
  const [theme, setTheme] = useState('dark');     // 'dark' | 'light'
  const [mode, setMode] = useState('single');     // 'single' | 'carousel'  (dark-only)
  const [template, setTemplate] = useState('coords');
  const [ratio, setRatio] = useState('1:1');
  const [fields, setFields] = useState(DEFAULT_FIELDS);
  const [exporting, setExporting] = useState(false);
  const [videoExporting, setVideoExporting] = useState(false);
  const [videoProgress, setVideoProgress] = useState({ frac: 0, label: '' });
  const [posReset, setPosReset] = useState(0);
  // Carousel state
  const [slideCount, setSlideCount] = useState(3);
  const [slideIndex, setSlideIndex] = useState(0);
  const [secondaryStyles, setSecondaryStyles] = useState({ watermark: false, caption: true, meta: false });
  const [carouselExporting, setCarouselExporting] = useState(false);
  const [carouselProgress, setCarouselProgress] = useState({ frac: 0, label: '' });
  const previewRef = useRef(null);
  const getPreviewNode = useCallback(() => previewRef.current, []);

  const set = (patch) => setFields((f) => ({ ...f, ...patch }));

  // Mirror theme onto html element for body/global colors.
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]);

  // Force single mode when switching to light (carousel is dark-only).
  useEffect(() => {
    if (theme === 'light' && mode === 'carousel') setMode('single');
  }, [theme, mode]);

  const Component = TEMPLATE_SETS[theme][template];
  const name = TEMPLATE_NAMES[template];
  const r = RATIOS[ratio];
  const data = buildData(template, fields);

  // Shared slot id across themes so dropping a photo persists when toggling theme.
  const slotId = `builder-${template}`;

  // ────────── PNG export ──────────
  const onExport = useCallback(async () => {
    if (!previewRef.current) return;
    setExporting(true);
    try {
      const node = previewRef.current;
      const [tw, th] = r.target;
      const scale = tw / r.w;
      const hidden = [...node.querySelectorAll('[data-no-export]')];
      const prevVis = hidden.map((el) => el.style.visibility);
      hidden.forEach((el) => { el.style.visibility = 'hidden'; });
      let dataUrl;
      try {
        dataUrl = await htmlToImage.toPng(node, {
          width: tw, height: th,
          pixelRatio: 1,
          style: { transform: `scale(${scale})`, transformOrigin: 'top left', width: r.w + 'px', height: r.h + 'px' },
          cacheBust: true,
        });
      } finally {
        hidden.forEach((el, i) => { el.style.visibility = prevVis[i]; });
      }
      const a = document.createElement('a');
      const safe = (s) => (s || 'post').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
      a.download = `16ds-${theme}-${safe(fields.location)}-${template}-${ratio.replace(':', 'x')}.png`;
      a.href = dataUrl;
      a.click();
    } catch (e) {
      console.error(e);
      alert('Export failed: ' + e.message);
    } finally { setExporting(false); }
  }, [r, theme, template, ratio, fields.location]);

  // ────────── Video bake export ──────────
  const onExportVideo = useCallback(async () => {
    if (!previewRef.current) return;
    setVideoExporting(true);
    setVideoProgress({ frac: 0, label: 'Starting…' });
    try {
      const [tw, th] = r.target;
      const blob = await window.exportFinalVideo({
        previewNode: previewRef.current,
        targetW: tw, targetH: th,
        maxDuration: 90,
        fps: 30,
        onProgress: (frac, label) => setVideoProgress({ frac, label }),
      });
      const safe = (s) => (s || 'post').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
      const ext = blob.extension || 'webm';
      const a = document.createElement('a');
      a.download = `16ds-${theme}-${safe(fields.location)}-${template}-${ratio.replace(':', 'x')}.${ext}`;
      a.href = URL.createObjectURL(blob);
      a.click();
      setTimeout(() => URL.revokeObjectURL(a.href), 5000);
      setVideoProgress({ frac: 1, label: 'Done' });
    } catch (e) {
      console.error(e);
      alert('Video export failed: ' + e.message);
    } finally {
      setVideoExporting(false);
      setTimeout(() => setVideoProgress({ frac: 0, label: '' }), 1500);
    }
  }, [r, theme, template, ratio, fields.location]);

  // ────────── Carousel JPG export ──────────
  const onExportCarousel = useCallback(async () => {
    if (!previewRef.current) return;
    setCarouselExporting(true);
    const safe = (s) => (s || 'post').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
    try {
      const [tw, th] = r.target;
      const scale = tw / r.w;
      const slideEls = [...previewRef.current.querySelectorAll('[data-carousel-slide]')];
      if (slideEls.length === 0) throw new Error('No carousel slides found');
      const hidden = [...previewRef.current.querySelectorAll('[data-no-export]')];
      const prevVis = hidden.map((el) => el.style.visibility);
      hidden.forEach((el) => { el.style.visibility = 'hidden'; });
      try {
        for (let i = 0; i < slideEls.length; i++) {
          setCarouselProgress({ frac: i / slideEls.length, label: `Slide ${i + 1} of ${slideEls.length}` });
          await new Promise((res) => setTimeout(res, 30));
          const slide = slideEls[i];
          const dataUrl = await htmlToImage.toJpeg(slide, {
            width: tw, height: th,
            quality: 0.92,
            pixelRatio: 1,
            backgroundColor: '#02102B',
            style: { transform: `scale(${scale})`, transformOrigin: 'top left', width: r.w + 'px', height: r.h + 'px' },
            cacheBust: true,
          });
          const a = document.createElement('a');
          a.download = `16ds-${safe(fields.location)}-carousel-${ratio.replace(':', 'x')}-${String(i + 1).padStart(2, '0')}.jpg`;
          a.href = dataUrl;
          a.click();
          await new Promise((res) => setTimeout(res, 120));
        }
      } finally {
        hidden.forEach((el, i) => { el.style.visibility = prevVis[i]; });
      }
      setCarouselProgress({ frac: 1, label: 'Done' });
    } catch (e) {
      console.error(e);
      alert('Carousel export failed: ' + e.message);
    } finally {
      setCarouselExporting(false);
      setTimeout(() => setCarouselProgress({ frac: 0, label: '' }), 1500);
    }
  }, [r, template, ratio, fields.location, slideCount]);

  return (
    <div className="app" data-theme={theme}>
      {/* ───────────── LEFT PANEL ───────────── */}
      <aside className="panel">
        <div className="brand">
          <div className="mark">16°S</div>
          <div className="label">Post Builder</div>
        </div>

        {/* Theme toggle */}
        <div className="group">
          <div className="group-title">// Theme</div>
          <div className="ratio-row" style={{ gridTemplateColumns: '1fr 1fr' }}>
            <button
              className={`ratio-btn ${theme === 'dark' ? 'active' : ''}`}
              onClick={() => setTheme('dark')}
            >Dark</button>
            <button
              className={`ratio-btn ${theme === 'light' ? 'active' : ''}`}
              onClick={() => setTheme('light')}
            >Light</button>
          </div>
        </div>

        {/* Mode — carousel is dark-only */}
        {theme === 'dark' && (
          <div className="group">
            <div className="group-title">// Mode</div>
            <div className="ratio-row" style={{ gridTemplateColumns: '1fr 1fr' }}>
              <button
                className={`ratio-btn ${mode === 'single' ? 'active' : ''}`}
                onClick={() => setMode('single')}
              >Single Post</button>
              <button
                className={`ratio-btn ${mode === 'carousel' ? 'active' : ''}`}
                onClick={() => { setMode('carousel'); setSlideIndex(0); set({ mediaKind: 'photo' }); }}
              >Carousel</button>
            </div>
          </div>
        )}

        {/* Template */}
        <div className="group">
          <div className="group-title">// Template</div>
          <div className="template-row">
            {Object.entries(TEMPLATE_NAMES).map(([k, n]) => (
              <button
                key={k}
                className={`ratio-btn ${template === k ? 'active' : ''}`}
                onClick={() => setTemplate(k)}
              >{n}</button>
            ))}
          </div>
        </div>

        {/* Ratio */}
        <div className="group">
          <div className="group-title">// Aspect Ratio</div>
          <div className="ratio-row">
            {Object.entries(RATIOS).map(([k]) => (
              <button
                key={k}
                className={`ratio-btn ${ratio === k ? 'active' : ''}`}
                onClick={() => setRatio(k)}
              >{k}</button>
            ))}
          </div>
        </div>

        {/* Media */}
        <div className="group">
          <div className="group-title">// Media</div>
          <div className="ratio-row" style={{ gridTemplateColumns: '1fr 1fr' }}>
            <button
              className={`ratio-btn ${fields.mediaKind !== 'video' ? 'active' : ''}`}
              onClick={() => set({ mediaKind: 'photo' })}
              disabled={mode === 'carousel'}
              style={mode === 'carousel' ? { opacity: 0.55 } : null}
            >Photo</button>
            <button
              className={`ratio-btn ${fields.mediaKind === 'video' ? 'active' : ''}`}
              onClick={() => set({ mediaKind: 'video' })}
              disabled={mode === 'carousel'}
              style={mode === 'carousel' ? { opacity: 0.35, cursor: 'not-allowed' } : null}
              title={mode === 'carousel' ? 'Carousels are photo-only' : undefined}
            >Video</button>
          </div>
          {mode === 'carousel' && (
            <div className="hint">Carousels are photo-only.</div>
          )}
        </div>

        <div className="group">
          <div className="group-title">// Headline source</div>
          <div className="ratio-row" style={{ gridTemplateColumns: '1fr 1fr' }}>
            <button
              className={`ratio-btn ${fields.headlineMode !== 'client' ? 'active' : ''}`}
              onClick={() => set({ headlineMode: 'location' })}
            >Location</button>
            <button
              className={`ratio-btn ${fields.headlineMode === 'client' ? 'active' : ''}`}
              onClick={() => set({ headlineMode: 'client' })}
            >Client</button>
          </div>
        </div>

        <div className="group">
          <div className="group-title">// Place</div>
          <LocationField
            value={fields.location}
            lat={fields.lat}
            lng={fields.lng}
            onPick={(p) => set(p)}
          />
          <Field label="Region · sub-label" value={fields.region} onChange={(v) => set({ region: v })} placeholder="Valencian Community, Sardinia…" />
        </div>

        <div className="group">
          <div className="group-title">// Project</div>
          <Field label="Client" value={fields.client} onChange={(v) => set({ client: v })} placeholder="Mercedes-AMG" />
          <Field label="Project" value={fields.project} onChange={(v) => set({ project: v })} placeholder="F1 Pre-Season Test" />
          <Field label="Date" value={fields.date} onChange={(v) => set({ date: v })} placeholder="May 2026" />
        </div>

        {(template === 'parallel' || template === 'bigtype') && (
          <div className="group">
            <div className="group-title">// Headline</div>
            <TextArea
              label={template === 'bigtype' ? 'Headline · 3 lines (last is accent)' : 'Headline · last line accent'}
              value={fields.headline}
              onChange={(v) => set({ headline: v })}
              placeholder={template === 'bigtype' ? 'Built\nfor the\nchase.' : 'Open\nsea.'}
              rows={3}
            />
          </div>
        )}

        {template === 'callsheet' && (
          <div className="group">
            <div className="group-title">// Slate</div>
            <Field label="Day" value={fields.day} onChange={(v) => set({ day: v })} placeholder="Day 03" />
            <Field label="Frame ID" value={fields.frame} onChange={(v) => set({ frame: v })} placeholder="A_0214" />
          </div>
        )}

        {template === 'coords' && (
          <div className="group">
            <div className="group-title">// Slate</div>
            <Field label="Frame ID" value={fields.frame} onChange={(v) => set({ frame: v })} placeholder="A_047" />
          </div>
        )}

        {template === 'weather' && (
          <div className="group">
            <div className="group-title">// Conditions</div>
            <Field label="Wind · knots" value={fields.wind} onChange={(v) => set({ wind: v })} placeholder="38" />
            <Field label="Direction" value={fields.dir} onChange={(v) => set({ dir: v })} placeholder="NNW" />
            <Field label="Sea state" value={fields.sea} onChange={(v) => set({ sea: v })} placeholder="6.2m" />
            <Field label="Event" value={fields.event} onChange={(v) => set({ event: v })} placeholder="Clipper Round-the-World" />
            <Field label="Day" value={fields.day} onChange={(v) => set({ day: v })} placeholder="Day 47" />
            <button
              type="button"
              className="btn ghost"
              style={{ marginTop: 4 }}
              onClick={() => {
                try {
                  const prefix = theme === 'dark' ? '16ds-weather-pos-dark-' : '16ds-weather-pos-light-';
                  Object.keys(localStorage)
                    .filter((k) => k.startsWith(prefix))
                    .forEach((k) => localStorage.removeItem(k));
                } catch (e) {}
                setPosReset((n) => n + 1);
              }}
            >Reset card position</button>
          </div>
        )}

        {/* Carousel controls — dark-only */}
        {theme === 'dark' && mode === 'carousel' && (
          <div className="group">
            <div className="group-title">// Carousel</div>
            <label>
              <span className="lbl">Slides ({slideCount})</span>
              <input
                type="range"
                min={2} max={10} step={1}
                value={slideCount}
                onChange={(e) => {
                  const n = parseInt(e.target.value, 10);
                  setSlideCount(n);
                  if (slideIndex >= n) setSlideIndex(n - 1);
                }}
                style={{ width: '100%', accentColor: 'var(--blue-light)' }}
              />
            </label>
            <label>
              <span className="lbl">Slide 2+ overlays · stack any</span>
              <div className="ratio-row" style={{ gridTemplateColumns: '1fr 1fr 1fr' }}>
                {[
                  ['watermark', 'Mark'],
                  ['caption', 'Caption'],
                  ['meta', 'Client/Loc'],
                ].map(([k, label]) => (
                  <button
                    key={k}
                    className={`ratio-btn ${secondaryStyles[k] ? 'active' : ''}`}
                    onClick={() => setSecondaryStyles((s) => ({ ...s, [k]: !s[k] }))}
                  >{label}</button>
                ))}
              </div>
            </label>
            <label>
              <span className="lbl">Navigate · slide {slideIndex + 1} / {slideCount}</span>
              <div style={{ display: 'flex', gap: 6 }}>
                <button
                  className="ratio-btn"
                  style={{ flex: 1 }}
                  onClick={() => setSlideIndex(Math.max(0, slideIndex - 1))}
                  disabled={slideIndex === 0}
                >← Prev</button>
                <button
                  className="ratio-btn"
                  style={{ flex: 1 }}
                  onClick={() => setSlideIndex(Math.min(slideCount - 1, slideIndex + 1))}
                  disabled={slideIndex === slideCount - 1}
                >Next →</button>
              </div>
            </label>
            <div className="hint">Swipe / drag the preview to navigate. Slide 1 = current template, slides 2+ = drop additional photos.</div>
          </div>
        )}

        {/* Export */}
        <div className="group">
          <div className="group-title">// Export</div>
          <div className="actions">
            {mode === 'carousel' && theme === 'dark' ? (
              <button className="btn" onClick={onExportCarousel} disabled={carouselExporting || exporting || videoExporting}>
                {carouselExporting
                  ? `Rendering… ${Math.round(carouselProgress.frac * 100)}%`
                  : `Download ${slideCount} JPGs · ${r.target.join(' × ')}`}
              </button>
            ) : (
              <button className="btn" onClick={onExport} disabled={exporting || videoExporting}>
                {exporting ? 'Rendering…' : `Download ${r.target.join(' × ')} PNG`}
              </button>
            )}
          </div>
          {mode === 'carousel' && carouselExporting && (
            <>
              <div className="progress-track">
                <div className="progress-fill" style={{ width: `${Math.round(carouselProgress.frac * 100)}%` }} />
              </div>
              <div className="progress-label">{carouselProgress.label}</div>
            </>
          )}
          {fields.mediaKind === 'video' && (
            <>
              <div style={{ marginTop: 14 }}>
                <div className="group-title" style={{ marginBottom: 8 }}>// Video playback</div>
                <window.VideoControls getPreviewNode={getPreviewNode} />
              </div>
              <div className="actions" style={{ marginTop: 14 }}>
                <button className="btn" onClick={onExportVideo} disabled={exporting || videoExporting}>
                  {videoExporting ? `Recording… ${Math.round(videoProgress.frac * 100)}%` : `Export final video (≤ 90s)`}
                </button>
              </div>
              {videoExporting && (
                <>
                  <div className="progress-track">
                    <div className="progress-fill" style={{ width: `${Math.round(videoProgress.frac * 100)}%` }} />
                  </div>
                  <div className="progress-label">{videoProgress.label}</div>
                </>
              )}
              <div className="hint" style={{ marginTop: 10 }}>
                Records in real time. A 30s video takes ~30s. Output is MP4 or WebM depending on browser.
              </div>
            </>
          )}
        </div>
      </aside>

      {/* ───────────── STAGE ───────────── */}
      <main className="stage">
        <div className="stage-inner">
          <div ref={previewRef} className="stage-frame" style={{ width: r.w, height: r.h }}>
            {mode === 'carousel' && theme === 'dark' ? (
              <window.Carousel
                w={r.w}
                h={r.h}
                slideCount={slideCount}
                currentIndex={slideIndex}
                onIndexChange={setSlideIndex}
                secondaryStyles={secondaryStyles}
                location={fields.location}
                client={fields.client}
                carouselId={template}
                firstSlide={<Component key={`${template}-${ratio}-${posReset}`} w={r.w} h={r.h} slotId={slotId} data={{ ...data, mediaKind: 'photo' }} />}
              />
            ) : (
              <Component key={`${theme}-${template}-${ratio}-${posReset}`} w={r.w} h={r.h} slotId={slotId} data={data} />
            )}
          </div>
          <div className="stage-meta">
            <span>// {name}</span>
            <span><span className="accent">{ratio}</span> · {r.target.join(' × ')} px · Drop a {fields.mediaKind === 'video' ? 'video' : 'photo'} onto the frame</span>
          </div>
        </div>
      </main>
    </div>
  );
}

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