/* Compose tab — upload-first, Higgsfield AI as opt-in source */

const CALENDAR_STORAGE_KEY = 'hookline:calendar-posts:v2';
const COMPOSE_INCOMING_KEY = 'hookline:compose-incoming';

// Per-channel peak engagement windows (24h clock). Loosely modelled on
// Sprout Social / Hootsuite 2024-25 industry studies — peak hours where the
// audience is most active. Used to drive "Best time" recommendations and the
// weekly heatmap. Index 0 = best, descending importance.
const CHANNEL_PEAKS = {
  ig: { weekday: [11, 14, 19], weekend: [10, 13, 20], scoreBase: 8.4 },
  fb: { weekday: [9, 13, 15],  weekend: [12, 14],     scoreBase: 7.6 },
  tt: { weekday: [9, 12, 19, 22], weekend: [11, 16, 22], scoreBase: 9.1 },
  yt: { weekday: [15, 18, 21], weekend: [10, 15, 20], scoreBase: 8.0 },
};

const MARKET_TZ = {
  de: 'Berlin',     uk: 'London',  pl: 'Warsaw',
  ee: 'Tallinn',    lt: 'Vilnius', lv: 'Riga',
};

const DAY_NAMES_SHORT = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
const DAY_NAMES_FULL  = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];

function p2(n) { return String(n).padStart(2, '0'); }

function parseScheduleAt(s) {
  if (!s) return null;
  const d = new Date(String(s).replace(' ', 'T'));
  return isNaN(d) ? null : d;
}

function fmtScheduleAt(d) {
  return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate())
       + ' ' + p2(d.getHours()) + ':' + p2(d.getMinutes());
}

function fmtRelative(date, now) {
  if (!date || !now) return '';
  const diff = date.getTime() - now.getTime();
  if (diff < -60000) return 'in the past';
  const mins = Math.round(diff / 60000);
  if (mins < 1)  return 'now';
  if (mins < 60) return 'in ' + mins + ' min';
  const hours = Math.round(mins / 60);
  if (hours < 24) return 'in ' + hours + ' h';
  const days = Math.floor(hours / 24);
  const remH = hours - days * 24;
  if (days < 7)  return 'in ' + days + 'd' + (remH ? ' · ' + remH + 'h' : '');
  const weeks = Math.floor(days / 7);
  return 'in ' + weeks + 'w';
}

function fmtDayLabel(date, now) {
  if (!date || !now) return '';
  const sameDay = date.toDateString() === now.toDateString();
  const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1);
  const sameTomorrow = date.toDateString() === tomorrow.toDateString();
  if (sameDay)      return 'Today';
  if (sameTomorrow) return 'Tomorrow';
  const diffDays = Math.round((date - now) / 86400000);
  if (diffDays >= 2 && diffDays <= 6) return DAY_NAMES_FULL[date.getDay()];
  return date.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' });
}

function bestTimeSlotsFor(ch, mk, anchor) {
  const cfg = CHANNEL_PEAKS[ch] || CHANNEL_PEAKS.ig;
  const start = anchor instanceof Date ? new Date(anchor) : new Date();
  const marketAdj = { de: 0.25, uk: 0.18, pl: 0.05, ee: 0.0, lt: 0.0, lv: -0.05 }[mk] || 0;
  const slots = [];
  for (let d = 0; d < 7; d++) {
    const day = new Date(start);
    day.setDate(day.getDate() + d);
    const dow = day.getDay();
    const isWeekend = dow === 0 || dow === 6;
    const peaks = isWeekend ? cfg.weekend : cfg.weekday;
    peaks.forEach((h, i) => {
      const slot = new Date(day);
      slot.setHours(h, 0, 0, 0);
      if (slot <= start) return;
      const rankBoost = (peaks.length - i) * 0.18;
      const dayDecay  = -d * 0.04;
      const variance  = (((h * 13 + d * 7 + (mk ? mk.charCodeAt(0) : 0)) % 7) - 3) * 0.03;
      const score = Math.min(9.9, Math.max(6.0, cfg.scoreBase + rankBoost + marketAdj + dayDecay + variance));
      slots.push({ when: slot, score });
    });
  }
  slots.sort((a, b) => b.score - a.score);
  return slots.slice(0, 3);
}

function weeklyHeatmapFor(ch, mk) {
  const cfg = CHANNEL_PEAKS[ch] || CHANNEL_PEAKS.ig;
  const marketAdj = { de: 0, uk: 0, pl: 0, ee: 0, lt: 0, lv: 0 }[mk] || 0;
  // 6 rows × 7 cols. Rows = 4-hour buckets starting at 00:00.
  const grid = [];
  for (let row = 0; row < 6; row++) {
    const out = [];
    for (let col = 0; col < 7; col++) {
      const isWeekend = col === 0 || col === 6;
      const peaks = isWeekend ? cfg.weekend : cfg.weekday;
      const bucketStart = row * 4;
      const bucketEnd = bucketStart + 4;
      let val = 0;
      if (peaks.some(h => h >= bucketStart && h < bucketEnd)) val = 3;
      else if (row === 2 || row === 4) val = 2;            // morning + evening shoulders
      else if (row === 3) val = 1;                          // mid-day shoulder
      else if (row === 1 && isWeekend) val = 1;             // late-night weekend
      out.push(Math.max(0, Math.min(3, val + marketAdj)));
    }
    grid.push(out);
  }
  return grid;
}

function quickPicksFor(now) {
  if (!now) return [];
  const mk = (d) => ({ when: d });
  const today18 = new Date(now); today18.setHours(18, 0, 0, 0);
  const tom = new Date(now); tom.setDate(tom.getDate() + 1);
  const tom9  = new Date(tom); tom9.setHours(9, 0, 0, 0);
  const tom18 = new Date(tom); tom18.setHours(18, 0, 0, 0);
  const fri = new Date(now);
  const daysToFri = ((5 - fri.getDay()) + 7) % 7 || 7;
  fri.setDate(fri.getDate() + daysToFri);
  fri.setHours(10, 0, 0, 0);
  const out = [
    { label: 'Today 18:00',      when: today18 },
    { label: 'Tomorrow 09:00',   when: tom9 },
    { label: 'Tomorrow 18:00',   when: tom18 },
    { label: 'Friday 10:00',     when: fri },
  ].filter(p => p.when > now);
  return out.slice(0, 4);
}

function savePostToCalendar({ caption, channels, markets, scheduleAt, status, media, campaign, kind, originId }) {
  const dt = new Date(scheduleAt.replace(' ', 'T'));
  const y  = isNaN(dt) ? TODAY.getFullYear() : dt.getFullYear();
  const m  = isNaN(dt) ? TODAY.getMonth()    : dt.getMonth();
  const d  = isNaN(dt) ? TODAY.getDate()     : dt.getDate();
  const h  = isNaN(dt) ? 12                  : dt.getHours();
  const min = isNaN(dt) ? 0                  : dt.getMinutes();

  const ch = channels[0] || 'ig';
  const mk = markets[0]  || 'de';
  const validKindIds = new Set(kindsForChannel(ch).map(k => k.id));
  const safeKind = kind && validKindIds.has(kind) ? kind : defaultKindForChannel(ch);

  const post = {
    id: originId || Date.now().toString(),
    d, h, m, y, min,
    ch, mk,
    title: caption.slice(0, 80) || '(untitled)',
    kind: safeKind,
    status,
    campaign: campaign || null,
    media: media || null,
  };

  try {
    const existing = JSON.parse(localStorage.getItem(CALENDAR_STORAGE_KEY) || '[]');
    const idx = existing.findIndex(p => p.id === post.id);
    if (idx >= 0) existing[idx] = post;
    else existing.push(post);
    localStorage.setItem(CALENDAR_STORAGE_KEY, JSON.stringify(existing));
  } catch (e) {}
}

function ComposeTab() {
  const [source, setSource] = useState('library'); // library | localization | higgsfield
  const [caption, setCaption] = useState("Spring Pop-Up — opens this Saturday in Berlin Mitte. Limited drops, archive pieces, designer-led tours all afternoon.");
  const [channels, setChannels] = useState(['ig']);
  const [markets, setMarkets] = useState(['de']);
  const [scheduleAt, setScheduleAt] = useState('2026-05-17 14:00');
  const [selectedMedia, setSelectedMedia] = useState(null);
  const [selectedCampaign, setSelectedCampaign] = useState(null);
  const [kind, setKind] = useState(null);
  const [originId, setOriginId] = useState(null);
  const [thumbnail, setThumbnail] = useState(null); // { dataUrl, fileName } | null
  const [continuedFromPlanner, setContinuedFromPlanner] = useState(false);
  const [whenMode, setWhenMode] = useState('schedule'); // schedule | best | now
  const [toast, setToast] = useState(null);
  const toastTimer = useRef(null);

  // The Apply gate: the preview reads from the last "applied" snapshot, not the
  // live working state. Until the user clicks Apply changes, Schedule and Save
  // as draft are disabled. The signature compares the slice of state that
  // affects the preview (and the published post).
  const currentSig = useMemo(() => JSON.stringify({
    caption, channels, markets, scheduleAt,
    media: selectedMedia, campaign: selectedCampaign, kind, thumbnail,
  }), [caption, channels, markets, scheduleAt, selectedMedia, selectedCampaign, kind, thumbnail]);
  const [appliedSig, setAppliedSig] = useState(currentSig);
  const [appliedState, setAppliedState] = useState({
    caption, channels, markets, scheduleAt,
    media: selectedMedia, campaign: selectedCampaign, kind, thumbnail,
  });
  const isDirty = currentSig !== appliedSig;

  const applyChanges = () => {
    setAppliedState({
      caption, channels, markets, scheduleAt,
      media: selectedMedia, campaign: selectedCampaign, kind, thumbnail,
    });
    setAppliedSig(currentSig);
    flash('Preview updated');
  };

  // Read planning data handed off from the Calendar editor.
  useEffect(() => {
    try {
      const raw = sessionStorage.getItem(COMPOSE_INCOMING_KEY);
      if (!raw) return;
      sessionStorage.removeItem(COMPOSE_INCOMING_KEY);
      const incoming = JSON.parse(raw);
      if (incoming.title) setCaption(incoming.title);
      if (incoming.channels && incoming.channels.length) setChannels(incoming.channels);
      if (incoming.markets && incoming.markets.length)   setMarkets(incoming.markets);
      if (incoming.scheduleAt) setScheduleAt(incoming.scheduleAt);
      if (incoming.media)      setSelectedMedia(incoming.media);
      if (incoming.campaign)   setSelectedCampaign(incoming.campaign);
      if (incoming.kind)       setKind(incoming.kind);
      if (incoming.origin)     setOriginId(incoming.origin.id);
      if (incoming.source)     setSource(incoming.source);
      else if (incoming.media || incoming.campaign) setSource('library');
      setContinuedFromPlanner(true);
      // Seed the applied snapshot from the incoming planning data — so the
      // preview is in sync with the calendar handoff and Schedule isn't blocked.
      setTimeout(() => {
        const seed = {
          caption: incoming.title || caption,
          channels: incoming.channels || channels,
          markets:  incoming.markets  || markets,
          scheduleAt: incoming.scheduleAt || scheduleAt,
          media: incoming.media || null,
          campaign: incoming.campaign || null,
          kind: incoming.kind || null,
          thumbnail: null,
        };
        setAppliedState(seed);
        setAppliedSig(JSON.stringify(seed));
      }, 0);
    } catch (e) {}
  }, []);

  const flash = (msg) => {
    setToast(msg);
    if (toastTimer.current) clearTimeout(toastTimer.current);
    toastTimer.current = setTimeout(() => setToast(null), 3000);
  };

  const buildSavePayload = (status) => ({
    caption: appliedState.caption,
    channels: appliedState.channels,
    markets: appliedState.markets,
    scheduleAt: appliedState.scheduleAt,
    status,
    media: appliedState.media,
    campaign: appliedState.campaign,
    kind: appliedState.kind,
    originId,
  });

  const handleSchedule = () => {
    if (isDirty) return;
    savePostToCalendar(buildSavePayload('scheduled'));
    flash(originId ? 'Post updated & scheduled' : 'Post scheduled!');
    setContinuedFromPlanner(false);
  };

  const handleDraft = () => {
    if (isDirty) return;
    savePostToCalendar(buildSavePayload('draft'));
    flash('Saved as draft.');
    setContinuedFromPlanner(false);
  };

  const handlePostNow = () => {
    if (isDirty) return;
    const nowStr = fmtScheduleAt(new Date());
    const payload = { ...buildSavePayload('live'), scheduleAt: nowStr };
    savePostToCalendar(payload);
    flash('Published — going live now.');
    setContinuedFromPlanner(false);
    setWhenMode('schedule');
  };

  // The "primary channel" — used to drive the right-rail Format chips. We
  // intentionally do NOT switch this from the preview tabs (preview is
  // multi-platform); it's the first selected channel in the right rail.
  const primaryCh = channels[0] || 'ig';
  const thumbnailAllowed = channels.some(ch => kindAllowsThumbnail(ch, kind || defaultKindForChannel(ch)));

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 380px', gap: 22, padding: '24px 36px 60px' }}>

      {/* Composer */}
      <div className="stagger" style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>

        {continuedFromPlanner && (
          <ContinuedBanner onDismiss={() => setContinuedFromPlanner(false)} originId={originId} />
        )}

        {/* Source switcher */}
        <div className="panel" style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div className="caps" style={{ color: 'var(--fg-3)' }}>Creative source</div>
            <span className="chip mono" style={{ fontSize: 10 }}>Mediathek is default</span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10 }}>
            <SourceCard
              icon="photo_library" title="From Mediathek" sub="Pick from existing campaign assets."
              active={source==='library'} onClick={()=>setSource('library')} primary
            />
            <SourceCard
              icon="record_voice_over" title="Localization" sub="Dub one master cut into every market via 11Labs."
              active={source==='localization'} onClick={()=>setSource('localization')}
            />
            <SourceCard
              icon="auto_awesome" title="Higgsfield AI" sub="Generate when uploads aren't ready."
              active={source==='higgsfield'} onClick={()=>setSource('higgsfield')} accent
            />
          </div>
        </div>

        {/* Source body */}
        {source === 'localization' && <LocalizationFlow />}
        {source === 'library' && (
          <LibraryPanel
            selectedMedia={selectedMedia}
            onSelectMedia={setSelectedMedia}
            selectedCampaign={selectedCampaign}
            onSelectCampaign={setSelectedCampaign}
            channels={channels}
          />
        )}
        {source === 'higgsfield' && <HiggsfieldPanel />}

        {/* Caption, thumbnail and preview only apply when the user is
            composing a single post. The Localization flow exports per-market
            videos to Mediathek; scheduling/captioning happens later in those
            tabs, so we hide these here. */}
        {source !== 'localization' && (
          <div className="panel" style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <div className="caps" style={{ color: 'var(--fg-3)' }}>Caption</div>
              <span className="chip mono" style={{ fontSize: 10 }}>{caption.length} / 2200</span>
              <div style={{ flex: 1 }} />
              <button className="btn sm press"><Icon name="auto_awesome" size={13} /> Refine</button>
              <button className="btn sm press"><Icon name="translate" size={13} /> Translate · 6 markets</button>
            </div>
            <textarea
              value={caption}
              onChange={e => setCaption(e.target.value)}
              style={{
                minHeight: 140, resize: 'vertical', padding: 14,
                background: 'var(--bg-2)', border: '1px solid var(--line-2)', borderRadius: 12,
                fontFamily: 'Geist', fontSize: 14, color: 'var(--fg)', outline: 'none', lineHeight: 1.5,
              }}
            />
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {['#sustainable','#design','#engineering','#archivedrop','#popupberlin'].map(t => (
                <span key={t} className="chip">{t}</span>
              ))}
              <button className="btn sm ghost press" style={{ height: 24, padding: '0 8px' }}><Icon name="add" size={12} /> Tag</button>
            </div>
          </div>
        )}

        {source !== 'localization' && thumbnailAllowed && (
          <ThumbnailUploader
            thumbnail={thumbnail}
            onChange={setThumbnail}
            channels={channels}
            kind={kind}
          />
        )}

        {source !== 'localization' && (
          <PostPreview
            applied={appliedState}
            isDirty={isDirty}
            onApply={applyChanges}
          />
        )}

      </div>

      {/* Right rail: target + schedule */}
      <aside className="stagger" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>

        <div className="panel" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <div className="caps" style={{ color: 'var(--fg-3)' }}>Channel</div>
            <span className="chip mono" style={{ fontSize: 10 }} title="Each channel has its own formats — pick one at a time.">One per post</span>
          </div>
          <div role="radiogroup" style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {CHANNELS.map(c => {
              const on = channels[0] === c.code;
              return (
                <button key={c.code}
                  role="radio"
                  aria-checked={on}
                  onClick={() => {
                    if (on) return;
                    // Channel switch is the source of truth for format. Reset to
                    // the new channel's default kind so the Format panel and the
                    // preview don't carry over a kind that doesn't exist on the
                    // new platform (e.g. IG Reel → YT Long-form).
                    setChannels([c.code]);
                    setKind(defaultKindForChannel(c.code));
                  }}
                  className="press" style={{
                    display: 'flex', alignItems: 'center', gap: 10,
                    padding: '10px 12px', borderRadius: 10,
                    background: on ? 'var(--bg-3)' : 'transparent',
                    border: '1px solid ' + (on ? 'var(--line-3)' : 'var(--line)'),
                    cursor: 'pointer', color: 'var(--fg)', textAlign: 'left',
                  }}>
                  <ChannelGlyph ch={c.code} size={11} />
                  <span style={{ flex: 1, fontSize: 13 }}>{c.name}</span>
                  {on && <Icon name="check_circle" size={16} fill style={{ color: 'var(--accent)' }} />}
                </button>
              );
            })}
          </div>
        </div>

        {channels[0] && (
          <FormatPanel
            channel={channels[0]}
            kind={kind || defaultKindForChannel(channels[0])}
            setKind={setKind}
          />
        )}

        <div className="panel" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div className="caps" style={{ color: 'var(--fg-3)' }}>Markets</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
            {MARKETS.map(m => {
              const on = markets.includes(m.code);
              return (
                <button key={m.code} onClick={()=>setMarkets(s => on ? s.filter(x=>x!==m.code) : [...s, m.code])}
                  className="press chip" style={{
                    padding: '6px 10px', cursor: 'pointer',
                    background: on ? 'var(--bg-3)' : 'transparent',
                    borderColor: on ? 'var(--line-3)' : 'var(--line-2)',
                    color: on ? 'var(--fg)' : 'var(--fg-2)',
                  }}>
                  <Flag code={m.code} size={12} /> {m.code.toUpperCase()}
                </button>
              );
            })}
          </div>
        </div>

        <WhenPanel
          whenMode={whenMode}
          setWhenMode={setWhenMode}
          scheduleAt={scheduleAt}
          setScheduleAt={setScheduleAt}
          channel={primaryCh}
          market={markets[0] || 'de'}
        />

        {isDirty && (
          <div className="hairline anim-fade-up" style={{
            borderRadius: 10, padding: '10px 12px',
            background: 'rgba(241,192,84,.08)',
            borderColor: 'rgba(241,192,84,.35)',
            display: 'flex', alignItems: 'flex-start', gap: 8,
            fontSize: 12, lineHeight: 1.4, color: 'var(--warn)',
          }}>
            <Icon name="info" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
            <span>Changes pending — Apply them on the preview to enable Schedule.</span>
          </div>
        )}

        {whenMode === 'now' ? (
          <button
            className="btn primary lg press"
            style={{
              justifyContent: 'center',
              background: '#ff4040', borderColor: '#ff4040', color: '#fff',
              opacity: isDirty ? 0.45 : 1, cursor: isDirty ? 'not-allowed' : 'pointer',
            }}
            onClick={handlePostNow}
            disabled={isDirty}
            title={isDirty ? 'Apply changes on the preview first' : 'Publishes immediately to the selected channel'}
          >
            <span style={{ width: 8, height: 8, borderRadius: 99, background: '#fff', boxShadow: '0 0 0 3px rgba(255,255,255,.25)' }} />
            Post now
          </button>
        ) : (
          <button
            className="btn primary lg press"
            style={{ justifyContent: 'center', opacity: isDirty ? 0.45 : 1, cursor: isDirty ? 'not-allowed' : 'pointer' }}
            onClick={handleSchedule}
            disabled={isDirty}
            title={isDirty ? 'Apply changes on the preview first' : ''}
          >
            <Icon name="schedule_send" size={18} /> Schedule post
          </button>
        )}
        <button
          className="btn press"
          style={{ justifyContent: 'center', opacity: isDirty ? 0.45 : 1, cursor: isDirty ? 'not-allowed' : 'pointer' }}
          onClick={handleDraft}
          disabled={isDirty}
          title={isDirty ? 'Apply changes on the preview first' : ''}
        >
          Save as draft
        </button>
      </aside>

      {toast && (
        <div style={{
          position: 'fixed', bottom: 28, right: 28, zIndex: 9999,
          background: '#1a4a2e', border: '1px solid #2d7a4f',
          color: '#6fcf97', borderRadius: 12, padding: '12px 18px',
          fontSize: 13.5, fontFamily: 'Geist', fontWeight: 500,
          display: 'flex', alignItems: 'center', gap: 8,
          boxShadow: '0 4px 24px rgba(0,0,0,.45)',
          animation: 'fadeSlideUp .22s var(--ease)',
        }}>
          <Icon name="check_circle" size={16} fill style={{ color: '#6fcf97' }} />
          {toast}
        </div>
      )}
    </div>
  );
}

function FormatPanel({ channel, kind, setKind }) {
  const channelName = (CHANNELS.find(c => c.code === channel) || {}).name || channel;
  const formats = kindsForChannel(channel);
  const spec = kindSpec(channel, kind);
  const activeFormat = formats.find(f => f.id === kind) || formats[0];

  return (
    <div className="panel" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>Format</div>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 10, color: 'var(--fg-4)' }}>{channelName}</span>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 6 }}>
        {formats.map(k => {
          const on = k.id === kind;
          return (
            <button key={k.id}
              onClick={() => setKind(k.id)}
              className="press"
              aria-pressed={on}
              style={{
                display: 'inline-flex', alignItems: 'center', gap: 8,
                padding: '9px 11px', borderRadius: 10,
                background: on ? 'var(--bg-3)' : 'transparent',
                border: '1px solid ' + (on ? 'var(--line-3)' : 'var(--line)'),
                cursor: 'pointer', color: on ? 'var(--fg)' : 'var(--fg-2)',
                textAlign: 'left', fontFamily: 'Geist', fontSize: 12.5,
                fontWeight: on ? 600 : 500,
                transition: 'background .2s var(--ease), border-color .2s var(--ease)',
              }}>
              <Icon name={k.icon} size={14} fill={on} style={{ color: on ? 'var(--accent)' : 'var(--fg-3)' }} />
              <span style={{ flex: 1 }}>{k.label}</span>
              {on && <Icon name="check" size={13} style={{ color: 'var(--accent)' }} />}
            </button>
          );
        })}
      </div>
      {spec && (
        <div style={{
          display: 'flex', flexDirection: 'column', gap: 6,
          padding: '10px 11px', borderRadius: 10,
          background: 'var(--bg-2)', border: '1px solid var(--line)',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11.5, color: 'var(--fg-3)' }}>
            <Icon name="info" size={12} style={{ color: 'var(--fg-3)' }} />
            <span style={{ color: 'var(--fg-2)' }}>{activeFormat.label} specs</span>
            <div style={{ flex: 1 }} />
            {spec.cover && (
              <span className="mono" style={{ fontSize: 9.5, padding: '2px 6px', borderRadius: 99, background: 'rgba(122,167,255,.12)', color: 'var(--accent)' }}>
                cover
              </span>
            )}
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
            <span className="chip mono" style={{ fontSize: 10, color: 'var(--fg-2)' }}>{spec.aspect}</span>
            {spec.duration && (
              <span className="chip mono" style={{ fontSize: 10, color: 'var(--fg-2)' }}>{spec.duration}</span>
            )}
            {spec.caption && (
              <span className="chip mono" style={{ fontSize: 10, color: 'var(--fg-2)' }}>{spec.caption.toLocaleString()} chars</span>
            )}
          </div>
          {spec.note && (
            <div style={{ fontSize: 11, color: 'var(--fg-3)', lineHeight: 1.4 }}>{spec.note}</div>
          )}
        </div>
      )}
    </div>
  );
}

function WhenPanel({ whenMode, setWhenMode, scheduleAt, setScheduleAt, channel, market }) {
  const now = TODAY;
  const scheduled = parseScheduleAt(scheduleAt);
  const channelName = (CHANNELS.find(c => c.code === channel) || {}).name || channel;
  const tzCity = MARKET_TZ[market] || 'Berlin';

  const bestSlots  = useMemo(() => bestTimeSlotsFor(channel, market, now), [channel, market]);
  const heatmap    = useMemo(() => weeklyHeatmapFor(channel, market),       [channel, market]);
  const quickPicks = useMemo(() => quickPicksFor(now), [now]);

  const dateStr = scheduled
    ? scheduled.getFullYear() + '-' + p2(scheduled.getMonth() + 1) + '-' + p2(scheduled.getDate())
    : '';
  const timeStr = scheduled
    ? p2(scheduled.getHours()) + ':' + p2(scheduled.getMinutes())
    : '';

  const setDate = (yyyyMmDd) => {
    if (!yyyyMmDd) return;
    const [y, m, d] = yyyyMmDd.split('-').map(Number);
    const next = scheduled ? new Date(scheduled) : new Date(now);
    next.setFullYear(y, m - 1, d);
    setScheduleAt(fmtScheduleAt(next));
  };
  const setTime = (hhMm) => {
    if (!hhMm) return;
    const [h, mi] = hhMm.split(':').map(Number);
    const next = scheduled ? new Date(scheduled) : new Date(now);
    next.setHours(h, mi, 0, 0);
    setScheduleAt(fmtScheduleAt(next));
  };
  const applySlot = (date) => {
    setScheduleAt(fmtScheduleAt(date));
    setWhenMode('schedule');
  };

  const Tab = ({ id, label, icon }) => {
    const on = whenMode === id;
    return (
      <button
        onClick={() => setWhenMode(id)}
        className={'press' + (on ? ' on' : '')}
        style={{
          flex: 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6,
          padding: '7px 8px', fontSize: 12.5, fontWeight: on ? 600 : 500,
          background: on ? 'var(--bg-3)' : 'transparent',
          color: on ? 'var(--fg)' : 'var(--fg-2)',
          border: '1px solid ' + (on ? 'var(--line-3)' : 'transparent'),
          borderRadius: 8, cursor: 'pointer',
        }}>
        <Icon name={icon} size={13} fill={on} style={{ color: on ? (id === 'now' ? '#ff4040' : 'var(--accent)') : 'var(--fg-3)' }} />
        {label}
      </button>
    );
  };

  return (
    <div className="panel" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>When</div>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 10, color: 'var(--fg-4)' }}>{tzCity} · {market.toUpperCase()}</span>
      </div>

      <div style={{
        display: 'flex', gap: 4, padding: 4,
        background: 'var(--bg-2)', borderRadius: 10, border: '1px solid var(--line)',
      }}>
        <Tab id="schedule" label="Schedule" icon="event" />
        <Tab id="best"     label="Best time" icon="auto_awesome" />
        <Tab id="now"      label="Now"       icon="bolt" />
      </div>

      {whenMode === 'schedule' && (
        <div className="anim-fade-up" style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 110px', gap: 6 }}>
            <input
              type="date"
              value={dateStr}
              onChange={e => setDate(e.target.value)}
              className="input"
              style={{ fontFamily: 'Geist', fontSize: 13 }}
            />
            <input
              type="time"
              value={timeStr}
              onChange={e => setTime(e.target.value)}
              className="input"
              style={{ fontFamily: 'JetBrains Mono', fontSize: 13, textAlign: 'center' }}
            />
          </div>

          {scheduled && (
            <div style={{
              display: 'flex', alignItems: 'center', gap: 8,
              padding: '8px 10px', borderRadius: 10,
              background: 'var(--bg-2)', border: '1px solid var(--line)',
            }}>
              <Icon name="schedule" size={14} style={{ color: 'var(--fg-3)' }} />
              <div style={{ flex: 1, minWidth: 0, fontSize: 12.5, color: 'var(--fg)' }}>
                {fmtDayLabel(scheduled, now)} <span style={{ color: 'var(--fg-3)' }}>· {p2(scheduled.getHours())}:{p2(scheduled.getMinutes())}</span>
              </div>
              <span className="chip mono" style={{ fontSize: 10, color: 'var(--fg-3)' }}>{fmtRelative(scheduled, now)}</span>
            </div>
          )}

          {quickPicks.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              <div className="caps" style={{ color: 'var(--fg-4)', fontSize: 10 }}>Quick picks</div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {quickPicks.map(q => {
                  const on = scheduled && fmtScheduleAt(scheduled) === fmtScheduleAt(q.when);
                  return (
                    <button key={q.label}
                      onClick={() => setScheduleAt(fmtScheduleAt(q.when))}
                      className="press chip"
                      style={{
                        padding: '5px 9px', cursor: 'pointer', fontSize: 11.5,
                        background: on ? 'var(--bg-3)' : 'transparent',
                        borderColor: on ? 'var(--line-3)' : 'var(--line-2)',
                        color: on ? 'var(--fg)' : 'var(--fg-2)',
                      }}>
                      {q.label}
                    </button>
                  );
                })}
              </div>
            </div>
          )}

          {bestSlots[0] && (
            <button
              onClick={() => applySlot(bestSlots[0].when)}
              className="press"
              style={{
                display: 'flex', alignItems: 'center', gap: 10,
                padding: '10px 12px', borderRadius: 10, cursor: 'pointer',
                background: 'var(--bg-2)', border: '1px dashed var(--line-2)',
                color: 'var(--fg)', textAlign: 'left',
              }}>
              <Icon name="bolt" size={14} style={{ color: 'var(--accent)' }} />
              <div style={{ flex: 1, fontSize: 12, color: 'var(--fg-2)' }}>
                Best window for {market.toUpperCase()} · {channelName} →{' '}
                <span style={{ color: 'var(--fg)', fontWeight: 600 }}>
                  {fmtDayLabel(bestSlots[0].when, now)} {p2(bestSlots[0].when.getHours())}:{p2(bestSlots[0].when.getMinutes())}
                </span>
              </div>
              <span className="mono" style={{ fontSize: 10.5, color: 'var(--accent)' }}>Use →</span>
            </button>
          )}
        </div>
      )}

      {whenMode === 'best' && (
        <div className="anim-fade-up" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11.5, color: 'var(--fg-3)' }}>
            <Icon name="psychology" size={13} style={{ color: 'var(--accent)' }} />
            <span>AI · learned from {channelName} {market.toUpperCase()} engagement (last 90 days)</span>
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {bestSlots.map((s, i) => {
              const isTop = i === 0;
              return (
                <button
                  key={i}
                  onClick={() => applySlot(s.when)}
                  className="press"
                  style={{
                    display: 'flex', alignItems: 'center', gap: 10,
                    padding: '10px 12px', borderRadius: 10, cursor: 'pointer',
                    background: isTop ? 'rgba(122,167,255,.08)' : 'var(--bg-2)',
                    border: '1px solid ' + (isTop ? 'rgba(122,167,255,.32)' : 'var(--line)'),
                    color: 'var(--fg)', textAlign: 'left',
                  }}>
                  <div style={{
                    width: 24, height: 24, borderRadius: 6,
                    background: isTop ? 'var(--accent)' : 'var(--bg-3)',
                    color: isTop ? '#0e0e10' : 'var(--fg-2)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontFamily: 'JetBrains Mono', fontSize: 11, fontWeight: 700,
                  }}>{i + 1}</div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600 }}>
                      {fmtDayLabel(s.when, now)} · {p2(s.when.getHours())}:{p2(s.when.getMinutes())}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--fg-3)', marginTop: 1 }}>
                      {fmtRelative(s.when, now)} · {isTop ? 'Top match' : 'Strong'}
                    </div>
                  </div>
                  <div style={{
                    display: 'flex', alignItems: 'center', gap: 4,
                    fontFamily: 'JetBrains Mono', fontSize: 11.5,
                    color: isTop ? 'var(--accent)' : 'var(--fg-2)',
                  }}>
                    <Icon name="trending_up" size={12} />
                    {s.score.toFixed(1)}
                  </div>
                </button>
              );
            })}
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              <div className="caps" style={{ color: 'var(--fg-4)', fontSize: 10 }}>Weekly heat</div>
              <div style={{ flex: 1 }} />
              <div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9.5, color: 'var(--fg-4)' }}>
                Low
                {[0,1,2,3].map(v => (
                  <span key={v} style={{
                    width: 8, height: 8, borderRadius: 2,
                    background: v === 0 ? 'rgba(255,255,255,.04)'
                              : v === 1 ? 'rgba(122,167,255,.18)'
                              : v === 2 ? 'rgba(122,167,255,.45)'
                                        : 'var(--accent)',
                  }} />
                ))}
                High
              </div>
            </div>
            <div style={{
              display: 'grid', gridTemplateColumns: '24px repeat(7, 1fr)', gap: 3,
              padding: 8, borderRadius: 10,
              background: 'var(--bg-2)', border: '1px solid var(--line)',
            }}>
              <div />
              {DAY_NAMES_SHORT.map(d => (
                <div key={d} className="mono" style={{ fontSize: 9, color: 'var(--fg-4)', textAlign: 'center' }}>{d[0]}</div>
              ))}
              {heatmap.map((row, ri) => {
                const hourLabel = (ri * 4 === 0) ? '12a' : (ri * 4 < 12 ? (ri * 4) + 'a' : (ri * 4 === 12 ? '12p' : (ri * 4 - 12) + 'p'));
                return (
                  <React.Fragment key={ri}>
                    <div className="mono" style={{ fontSize: 8.5, color: 'var(--fg-4)', textAlign: 'right', paddingRight: 2, lineHeight: '14px' }}>{hourLabel}</div>
                    {row.map((v, ci) => (
                      <div key={ci} title={DAY_NAMES_FULL[ci] + ' · ' + hourLabel}
                        style={{
                          height: 14, borderRadius: 3,
                          background: v === 0 ? 'rgba(255,255,255,.04)'
                                    : v === 1 ? 'rgba(122,167,255,.18)'
                                    : v === 2 ? 'rgba(122,167,255,.45)'
                                              : 'var(--accent)',
                        }} />
                    ))}
                  </React.Fragment>
                );
              })}
            </div>
          </div>
        </div>
      )}

      {whenMode === 'now' && (
        <div className="anim-fade-up" style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{
            display: 'flex', alignItems: 'center', gap: 12,
            padding: 14, borderRadius: 12,
            background: 'rgba(255,64,64,.06)',
            border: '1px solid rgba(255,64,64,.25)',
          }}>
            <div style={{
              width: 36, height: 36, borderRadius: 99,
              background: '#ff4040', color: '#fff',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              boxShadow: '0 0 0 4px rgba(255,64,64,.18)',
            }}>
              <Icon name="bolt" size={18} fill />
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--fg)' }}>Publish immediately</div>
              <div style={{ fontSize: 11.5, color: 'var(--fg-3)', marginTop: 2 }}>
                Goes live at {p2(now.getHours())}:{p2(now.getMinutes())} {tzCity}. Skips the review window.
              </div>
            </div>
          </div>
          <button
            onClick={() => setWhenMode('best')}
            className="press"
            style={{
              display: 'flex', alignItems: 'center', gap: 8,
              padding: '9px 12px', borderRadius: 10, cursor: 'pointer',
              background: 'transparent', border: '1px dashed var(--line-2)',
              color: 'var(--fg-2)', textAlign: 'left', fontSize: 12,
            }}>
            <Icon name="schedule" size={13} style={{ color: 'var(--fg-3)' }} />
            <span style={{ flex: 1 }}>
              Tip — {channelName} {market.toUpperCase()} usually peaks at{' '}
              <span style={{ color: 'var(--fg)' }}>
                {bestSlots[0] ? p2(bestSlots[0].when.getHours()) + ':' + p2(bestSlots[0].when.getMinutes()) : '—'}
              </span>. See best time →
            </span>
          </button>
        </div>
      )}
    </div>
  );
}

function SourceCard({ icon, title, sub, active, onClick, primary, accent }) {
  return (
    <button onClick={onClick} className="press" style={{
      padding: 16, borderRadius: 14, cursor: 'pointer', textAlign: 'left',
      background: active ? 'var(--bg-3)' : 'var(--bg-1)',
      border: '1px solid ' + (active ? 'var(--line-3)' : 'var(--line)'),
      display: 'flex', flexDirection: 'column', gap: 8,
      color: 'var(--fg)',
      position: 'relative', overflow: 'hidden',
      transition: 'all .25s var(--ease)',
    }}>
      {primary && <span className="chip mono" style={{ position: 'absolute', top: 12, right: 12, fontSize: 9.5, color: 'var(--fg-3)' }}>DEFAULT</span>}
      {accent && <span className="chip mono" style={{ position: 'absolute', top: 12, right: 12, fontSize: 9.5, color: 'var(--accent)', borderColor: 'rgba(122,167,255,.3)' }}>AI</span>}
      <div style={{
        width: 36, height: 36, borderRadius: 10,
        background: active ? 'var(--fg)' : 'var(--bg-3)',
        color: active ? '#0e0e10' : (accent ? 'var(--accent)' : 'var(--fg)'),
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <Icon name={icon} size={18} fill={active} />
      </div>
      <div style={{ fontFamily: 'Hanken Grotesk', fontSize: 15, fontWeight: 600 }}>{title}</div>
      <div style={{ fontSize: 12, color: 'var(--fg-3)', lineHeight: 1.4 }}>{sub}</div>
    </button>
  );
}

function LocalizationFlow() {
  const [step, setStep] = useState(1);
  const [apiConnected, setApiConnected] = useState(true);
  const [credits, setCredits] = useState(12450);
  const [visualFile, setVisualFile] = useState({ name: 'spring-popup-master_v4.mp4', meta: '0:24 · 1080×1920 · 64 MB', ph: 'ph-warm', dur: '0:24' });
  const [audioFile, setAudioFile] = useState({ name: 'popup-bed_no-vocal.wav', meta: '0:24 · 48kHz · stereo · 8.2 MB' });
  const [voicesByLang, setVoicesByLang] = useState({
    de: 'Anke', uk: 'Rachel', pl: 'Marcin', ee: 'Liina', lt: 'Tomas', lv: 'Inga',
  });
  const [active, setActive] = useState(['de','uk','pl']);

  const hasFiles = !!visualFile && !!audioFile;
  const canLocalize = hasFiles && apiConnected;

  return (
    <div className="panel" style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
      <LocalizationStepHeader
        step={step}
        apiConnected={apiConnected}
        credits={credits}
        onJump={setStep}
        onToggleConnection={() => setApiConnected(c => !c)}
        onRetest={() => setCredits(c => c)}
      />

      {!apiConnected && (
        <ConnectElevenLabsCard onConnect={(balance) => { setApiConnected(true); setCredits(balance); }} />
      )}

      {step === 1 && (
        <Step1Upload
          visualFile={visualFile} setVisualFile={setVisualFile}
          audioFile={audioFile} setAudioFile={setAudioFile}
          hasFiles={hasFiles} apiConnected={apiConnected}
          canLocalize={canLocalize}
          onLocalize={() => setStep(2)}
        />
      )}
      {step === 2 && (
        <Step2Voices
          voicesByLang={voicesByLang} setVoicesByLang={setVoicesByLang}
          active={active} setActive={setActive}
          credits={credits}
          onBack={() => setStep(1)}
          onStart={() => setStep(3)}
        />
      )}
      {step === 3 && (
        <Step3Preview
          active={active}
          visualFile={visualFile}
          onBack={() => setStep(2)}
          onApprove={() => setStep(4)}
        />
      )}
      {step === 4 && (
        <Step4Export
          active={active}
          onBack={() => setStep(3)}
        />
      )}
    </div>
  );
}

function LocalizationStepHeader({ step, apiConnected, credits, onJump, onToggleConnection, onRetest }) {
  const [retesting, setRetesting] = useState(false);
  const steps = [
    { n: 1, label: 'Upload' },
    { n: 2, label: 'Voices' },
    { n: 3, label: 'Preview' },
    { n: 4, label: 'Export' },
  ];
  const fmt = (n) => n.toLocaleString('en-US');
  const handleRetest = () => {
    if (!apiConnected) return;
    setRetesting(true);
    setTimeout(() => { setRetesting(false); onRetest && onRetest(); }, 900);
  };
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 4, flex: '1 1 auto', minWidth: 0 }}>
        {steps.map((s, i) => {
          const done = step > s.n;
          const cur = step === s.n;
          const clickable = !cur;
          return (
            <React.Fragment key={s.n}>
              <button
                onClick={clickable ? () => onJump(s.n) : undefined}
                disabled={!clickable}
                className={clickable ? 'press' : ''}
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 7,
                  padding: '5px 10px 5px 5px', borderRadius: 999,
                  background: cur ? 'var(--bg-3)' : 'transparent',
                  border: '1px solid ' + (cur ? 'var(--line-3)' : 'transparent'),
                  cursor: clickable ? 'pointer' : 'default',
                  color: cur ? 'var(--fg)' : (done ? 'var(--fg-2)' : 'var(--fg-4)'),
                  transition: 'all .2s var(--ease)',
                }}>
                <span style={{
                  width: 20, height: 20, borderRadius: 99,
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  background: done ? 'var(--accent)' : (cur ? 'var(--fg)' : 'var(--bg-3)'),
                  color: done ? '#0a1424' : (cur ? '#0e0e10' : 'var(--fg-3)'),
                  fontFamily: 'JetBrains Mono', fontSize: 10, fontWeight: 600,
                }}>
                  {done ? <Icon name="check" size={11} /> : s.n}
                </span>
                <span style={{ fontSize: 12, fontWeight: cur ? 600 : 500 }}>{s.label}</span>
              </button>
              {i < steps.length - 1 && (
                <span style={{
                  width: 18, height: 1, flexShrink: 0,
                  background: step > s.n ? 'var(--accent)' : 'var(--line-2)',
                  opacity: step > s.n ? 0.6 : 1,
                }} />
              )}
            </React.Fragment>
          );
        })}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span className="chip mono" title={apiConnected ? `${fmt(credits)} credits available` : 'API key not connected'} style={{
          fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 5,
          color: apiConnected ? 'var(--good)' : 'var(--warn)',
          borderColor: apiConnected ? 'rgba(121,210,163,.32)' : 'rgba(245,196,84,.35)',
        }}>
          <span style={{
            width: 6, height: 6, borderRadius: 99,
            background: apiConnected ? 'var(--good)' : 'var(--warn)',
            animation: retesting ? 'pulse 1s var(--ease) infinite' : 'none',
          }} />
          11LABS · {apiConnected ? `CONNECTED · ${fmt(credits)} CR` : 'NOT CONNECTED'}
        </span>
        {apiConnected && (
          <button className="btn icon sm ghost press" onClick={handleRetest}
            disabled={retesting} title="Re-test connection"
            style={{ opacity: retesting ? 0.5 : 1 }}>
            <Icon name={retesting ? 'sync' : 'refresh'} size={12} />
          </button>
        )}
        <button className="btn icon sm ghost press" onClick={onToggleConnection}
          title={apiConnected ? 'Disconnect (demo)' : 'Connect (demo)'}>
          <Icon name="settings" size={12} />
        </button>
      </div>
    </div>
  );
}

function ConnectElevenLabsCard({ onConnect }) {
  const [token, setToken] = useState('');
  const [status, setStatus] = useState('idle');
  const [error, setError] = useState('');
  const ready = token.trim().length >= 6;

  const handleTest = () => {
    if (!ready || status === 'testing') return;
    setStatus('testing');
    setError('');
    setTimeout(() => {
      const looksValid = /^sk_[a-z0-9]{8,}/i.test(token.trim()) || token.trim().toLowerCase().startsWith('sk_');
      if (!looksValid) {
        setStatus('error');
        setError('Key rejected by 11Labs. Check it starts with sk_ and try again.');
      } else {
        const balance = 8000 + Math.floor(Math.random() * 12000);
        setStatus('success');
        setTimeout(() => onConnect && onConnect(balance), 700);
      }
    }, 1100);
  };

  const onChange = (e) => { setToken(e.target.value); if (status !== 'idle') { setStatus('idle'); setError(''); } };

  const tone = status === 'error' ? 'var(--warn)' : status === 'success' ? 'var(--good)' : 'var(--accent)';
  const toneBg = status === 'error' ? 'rgba(245,196,84,.05)' : status === 'success' ? 'rgba(121,210,163,.05)' : 'rgba(122,167,255,.05)';
  const toneBorder = status === 'error' ? 'rgba(245,196,84,.35)' : status === 'success' ? 'rgba(121,210,163,.32)' : 'rgba(122,167,255,.3)';
  const toneFill = status === 'error' ? 'rgba(245,196,84,.12)' : status === 'success' ? 'rgba(121,210,163,.12)' : 'rgba(122,167,255,.12)';

  return (
    <div className="anim-fade-up hairline" style={{
      borderRadius: 14, padding: 16,
      background: toneBg, borderColor: toneBorder,
      display: 'flex', flexDirection: 'column', gap: 12,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <div style={{
          width: 36, height: 36, borderRadius: 10,
          background: toneFill, border: '1px solid ' + toneBorder,
          display: 'flex', alignItems: 'center', justifyContent: 'center', color: tone,
        }}>
          <Icon name="vpn_key" size={18} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600 }}>Connect 11Labs</div>
          <div style={{ fontSize: 12, color: 'var(--fg-3)' }}>
            Paste an API key — we'll test it against 11Labs and read your credit balance before enabling dubbing.
          </div>
        </div>
        <a href="https://elevenlabs.io/app/settings/api-keys" target="_blank" rel="noreferrer"
          style={{ fontSize: 11.5, color: 'var(--accent)', textDecoration: 'none', whiteSpace: 'nowrap' }}>
          Get an API key →
        </a>
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        <input
          value={token}
          onChange={onChange}
          placeholder="sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
          className="input"
          style={{ flex: 1, fontFamily: 'JetBrains Mono', fontSize: 12 }}
          disabled={status === 'testing' || status === 'success'}
        />
        <button
          className={'btn press ' + (status === 'error' ? '' : 'accent')}
          disabled={!ready || status === 'testing' || status === 'success'}
          onClick={handleTest}
          style={{ opacity: (!ready || status === 'testing') ? 0.55 : 1, cursor: ready && status !== 'testing' ? 'pointer' : 'not-allowed' }}
        >
          <Icon name={status === 'success' ? 'check_circle' : status === 'testing' ? 'sync' : 'link'} size={14} fill={status === 'success'} />
          {status === 'success' ? 'Connected' : status === 'testing' ? 'Testing…' : 'Test & connect'}
        </button>
      </div>
      {status !== 'idle' && (
        <div className="mono" style={{ fontSize: 11, color: tone, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          <Icon name={status === 'error' ? 'error' : status === 'success' ? 'check_circle' : 'progress_activity'} size={12} fill={status !== 'testing'} />
          {status === 'testing' && 'Pinging 11Labs · /v1/user…'}
          {status === 'success' && 'Key valid · credit balance synced'}
          {status === 'error' && error}
        </div>
      )}
    </div>
  );
}

function Step1Upload({ visualFile, setVisualFile, audioFile, setAudioFile, hasFiles, apiConnected, canLocalize, onLocalize }) {
  const helper =
    !apiConnected ? 'Connect 11Labs above to enable Localize.' :
    !hasFiles ? 'Upload both visual and isolated audio bed to continue.' :
    'Both tracks ready — Localize to pick voices.';
  return (
    <div className="anim-fade-up" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>Step 1 · Source files</div>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
          Two-track upload · video + isolated audio bed
        </span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <UploadSlot
          kind="visual"
          title="Visual creative"
          sub="Video with original on-camera sound — the master cut."
          icon="movie_filter"
          accept=".mp4, .mov, max 1GB"
          acceptMime="video/*"
          filled={!!visualFile}
          file={visualFile}
          onClear={() => setVisualFile(null)}
          onFile={(f) => setVisualFile({ name: f.name, meta: `${(f.size / 1048576).toFixed(1)} MB`, ph: 'ph-warm', dur: '0:00' })}
        />
        <UploadSlot
          kind="audio"
          title="Music & SFX bed"
          sub="Separated track — what 11Labs keeps when dubbing voice."
          icon="graphic_eq"
          accept=".wav, .mp3, .aac"
          acceptMime="audio/*"
          filled={!!audioFile}
          file={audioFile}
          onClear={() => setAudioFile(null)}
          onFile={(f) => setAudioFile({ name: f.name, meta: `${(f.size / 1048576).toFixed(1)} MB` })}
          isAudio
        />
      </div>

      <div className="hairline-t" style={{ paddingTop: 14 }}>
        <div className="caps" style={{ color: 'var(--fg-3)', marginBottom: 8 }}>Recently uploaded</div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 8 }}>
          {['ph-warm','ph-cool','ph-rose','ph-green','ph-photo','ph-video'].map((p, i) => (
            <PhotoTile key={i} ph={p} style={{ aspectRatio: '1/1', borderRadius: 8 }} video={i % 2 === 0} dur={['0:18','0:24','0:08'][i%3]} />
          ))}
        </div>
      </div>

      <StepActions
        helper={helper}
        rightLabel="Localize"
        rightIcon="auto_awesome"
        rightAccent
        rightOnClick={onLocalize}
      />
    </div>
  );
}

const VOICE_LIBRARY = {
  de: ['Anke', 'Stefan', 'Lena', 'Klaus'],
  uk: ['Rachel', 'Adam', 'Charlotte', 'James'],
  pl: ['Marcin', 'Agnieszka', 'Piotr'],
  ee: ['Liina', 'Maarja', 'Jaan'],
  lt: ['Tomas', 'Rūta', 'Vytautas'],
  lv: ['Inga', 'Mārtiņš', 'Anna'],
};
const LANG_META = [
  { code: 'de', name: 'Deutsch',    dur: '0:24' },
  { code: 'uk', name: 'English UK', dur: '0:24' },
  { code: 'pl', name: 'Polski',     dur: '0:24' },
  { code: 'ee', name: 'Eesti',      dur: '0:24' },
  { code: 'lt', name: 'Lietuvių',   dur: '0:24' },
  { code: 'lv', name: 'Latviešu',   dur: '0:24' },
];

function Step2Voices({ voicesByLang, setVoicesByLang, active, setActive, credits, onBack, onStart }) {
  const [playing, setPlaying] = useState(null);
  const [swapOpen, setSwapOpen] = useState(null);
  const cost = Math.round(active.length * 14.4);
  const insufficient = cost > credits;

  const handlePlay = (code) => {
    setPlaying(code);
    setTimeout(() => setPlaying(p => p === code ? null : p), 1800);
  };
  const handleSwap = (code, newVoice) => {
    setVoicesByLang(v => ({ ...v, [code]: newVoice }));
    setSwapOpen(null);
  };

  return (
    <div className="anim-fade-up" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>Step 2 · Preview voices · {active.length} markets</div>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>AI dub · 6 markets available</span>
      </div>

      {/* engine bar */}
      <div className="hairline" style={{ borderRadius: 12, padding: '10px 12px', background: 'var(--bg-2)', display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <div style={{
            width: 26, height: 26, borderRadius: 8,
            background: 'rgba(122,167,255,.12)', border: '1px solid rgba(122,167,255,.35)',
            display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--accent)',
          }}>
            <Icon name="record_voice_over" size={14} fill />
          </div>
          <div>
            <div style={{ fontSize: 12.5, fontWeight: 500 }}>ElevenLabs Dubbing</div>
            <div className="mono" style={{ fontSize: 10, color: 'var(--fg-3)' }}>Eleven Multilingual v2 · lip-sync ON · pace matched to source</div>
          </div>
        </div>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 10.5, color: 'var(--fg-3)', display: 'inline-flex', alignItems: 'center', gap: 5 }}>
          <Icon name="headphones" size={11} /> Preview before you commit
        </span>
      </div>

      {/* per-market grid */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 10 }}>
        {LANG_META.map(l => {
          const on = active.includes(l.code);
          const voiceName = voicesByLang[l.code];
          const isPlaying = playing === l.code;
          const isSwapping = swapOpen === l.code;
          const options = (VOICE_LIBRARY[l.code] || []).filter(v => v !== voiceName);
          return (
            <div key={l.code} className="lift" style={{
              position: 'relative',
              borderRadius: 12, padding: 12, background: 'var(--bg-2)',
              border: '1px solid ' + (on ? 'var(--line-3)' : 'var(--line)'),
              display: 'flex', flexDirection: 'column', gap: 8,
              opacity: on ? 1 : 0.55,
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <Flag code={l.code} size={18} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 500 }}>{l.name}</div>
                  <div className="mono" style={{ fontSize: 10.5, color: 'var(--fg-3)' }}>Voice: {voiceName} · {l.dur}</div>
                </div>
                <button
                  className={'btn sm press' + (isPlaying ? ' accent' : '')}
                  style={{ height: 24, padding: '0 8px' }}
                  onClick={() => on && handlePlay(l.code)}
                  disabled={!on}
                  title={isPlaying ? 'Playing sample…' : 'Preview voice'}>
                  <Icon name={isPlaying ? 'graphic_eq' : 'play_arrow'} size={11} fill />
                  {isPlaying ? 'Playing' : 'Preview'}
                </button>
                <button
                  className={'btn sm ghost press' + (isSwapping ? ' on' : '')}
                  style={{ height: 24, padding: '0 8px' }}
                  onClick={() => on && setSwapOpen(s => s === l.code ? null : l.code)}
                  disabled={!on}
                  title="Swap to a different voice">
                  <Icon name="swap_horiz" size={11} /> Swap
                </button>
                <button onClick={() => setActive(s => on ? s.filter(x=>x!==l.code) : [...s, l.code])}
                  className={'switch' + (on ? ' on' : '')} aria-pressed={on} />
              </div>
              {isSwapping && (
                <div className="anim-fade-up hairline" style={{
                  borderRadius: 10, padding: 8, background: 'var(--bg-1)',
                  display: 'flex', flexWrap: 'wrap', gap: 6,
                }}>
                  {options.length === 0 && (
                    <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>No other voices for this language. Use Voice clone to add one.</span>
                  )}
                  {options.map(v => (
                    <button key={v} className="btn sm press" style={{ height: 24, padding: '0 10px' }} onClick={() => handleSwap(l.code, v)}>
                      <Icon name="record_voice_over" size={11} /> {v}
                    </button>
                  ))}
                </div>
              )}
            </div>
          );
        })}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <button className="btn sm press"><Icon name="settings_voice" size={13} /> Voice clone</button>
        <button className="btn sm press"><Icon name="add" size={13} /> Add language</button>
      </div>

      <StepActions
        leftLabel="Back"
        leftOnClick={onBack}
        helper={insufficient
          ? `~ est. ${cost} credits · only ${credits.toLocaleString('en-US')} available — top up before starting`
          : `~ 0.6 credits / sec · est. ${cost} credits · ${credits.toLocaleString('en-US')} available`}
        helperTone={insufficient ? 'warn' : undefined}
        rightLabel={`Start the process · ${active.length} ${active.length === 1 ? 'market' : 'markets'}`}
        rightIcon="play_arrow"
        rightAccent
        rightOnClick={onStart}
        rightDisabled={insufficient || active.length === 0}
      />
    </div>
  );
}

function Step3Preview({ active, visualFile, onBack, onApprove }) {
  const fallback = active[0] || 'de';
  const [market, setMarket] = useState(fallback);
  const [lines, setLines] = useState({
    de: [
      { t: '0:00', d: '0:03', text: 'Diesen Samstag in Berlin Mitte.' },
      { t: '0:03', d: '0:05', text: 'Limitierte Drops, Archivstücke.' },
      { t: '0:08', d: '0:06', text: 'Designer-geführte Touren — den ganzen Nachmittag.' },
      { t: '0:14', d: '0:04', text: 'Hookline · Spring Pop-Up.' },
    ],
    uk: [
      { t: '0:00', d: '0:03', text: 'This Saturday in Berlin Mitte.' },
      { t: '0:03', d: '0:05', text: 'Limited drops and archive pieces.' },
      { t: '0:08', d: '0:06', text: 'Designer-led tours — all afternoon.' },
      { t: '0:14', d: '0:04', text: 'Hookline · Spring Pop-Up.' },
    ],
    pl: [
      { t: '0:00', d: '0:03', text: 'W tę sobotę w Berlin Mitte.' },
      { t: '0:03', d: '0:05', text: 'Limitowane drops, archiwalne sztuki.' },
      { t: '0:08', d: '0:06', text: 'Oprowadzania z projektantami — całe popołudnie.' },
      { t: '0:14', d: '0:04', text: 'Hookline · Spring Pop-Up.' },
    ],
    ee: [
      { t: '0:00', d: '0:03', text: 'Sel laupäeval Berliini Mittes.' },
      { t: '0:03', d: '0:05', text: 'Limiteeritud drops, arhiivitükid.' },
      { t: '0:08', d: '0:06', text: 'Disainerite juhitud tuurid — terve pärastlõuna.' },
      { t: '0:14', d: '0:04', text: 'Hookline · Kevad Pop-Up.' },
    ],
    lt: [
      { t: '0:00', d: '0:03', text: 'Šį šeštadienį Berlyno Mitte.' },
      { t: '0:03', d: '0:05', text: 'Riboti drops, archyviniai gabalai.' },
      { t: '0:08', d: '0:06', text: 'Dizainerių vedamos ekskursijos — visą popietę.' },
      { t: '0:14', d: '0:04', text: 'Hookline · Pavasario Pop-Up.' },
    ],
    lv: [
      { t: '0:00', d: '0:03', text: 'Šajā sestdienā Berlīnes Mittē.' },
      { t: '0:03', d: '0:05', text: 'Ierobežoti drops, arhīva gabali.' },
      { t: '0:08', d: '0:06', text: 'Dizaineru vadītas tūres — visu pēcpusdienu.' },
      { t: '0:14', d: '0:04', text: 'Hookline · Pavasara Pop-Up.' },
    ],
  });
  const cur = lines[market] || lines.uk;
  const update = (i, val) => setLines(s => ({ ...s, [market]: s[market].map((l, idx) => idx === i ? { ...l, text: val } : l) }));
  const addLine = () => setLines(s => ({ ...s, [market]: [...(s[market] || []), { t: '0:18', d: '0:03', text: '' }] }));

  const langName = { de: 'Deutsch', uk: 'English UK', pl: 'Polski', ee: 'Eesti', lt: 'Lietuvių', lv: 'Latviešu' };

  return (
    <div className="anim-fade-up" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>Step 3 · Preview & edit translations</div>
        <span className="chip mono" style={{ fontSize: 9.5, color: 'var(--accent)', borderColor: 'rgba(122,167,255,.3)' }}>FROM 11LABS</span>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>Render complete · {active.length} markets dubbed</span>
      </div>

      {/* Video preview */}
      <div style={{ position: 'relative', borderRadius: 14, overflow: 'hidden', background: 'var(--bg-3)', border: '1px solid var(--line)', aspectRatio: '16/9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <PhotoTile ph={(visualFile && visualFile.ph) || 'ph-warm'} style={{ position: 'absolute', inset: 0 }} />
        <button className="press" style={{
          position: 'relative', zIndex: 1,
          width: 64, height: 64, borderRadius: 99,
          background: 'rgba(0,0,0,.55)', border: '1px solid rgba(255,255,255,.3)',
          color: '#fff', cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }} title="Play preview">
          <Icon name="play_arrow" size={28} fill />
        </button>
        <div style={{
          position: 'absolute', top: 12, left: 12, display: 'flex', gap: 6,
        }}>
          <span className="chip mono" style={{ fontSize: 10, background: 'rgba(0,0,0,.55)', borderColor: 'transparent', color: '#fff', display: 'inline-flex', alignItems: 'center', gap: 5 }}>
            <Flag code={market} size={11} /> {langName[market] || market.toUpperCase()}
          </span>
          <span className="chip mono" style={{ fontSize: 10, background: 'rgba(0,0,0,.55)', borderColor: 'transparent', color: '#fff' }}>DUBBED · LIP-SYNC</span>
        </div>
        <div style={{ position: 'absolute', bottom: 12, left: 12, right: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
          <span className="mono" style={{ fontSize: 10, color: '#fff', textShadow: '0 1px 2px rgba(0,0,0,.7)' }}>0:00 / 0:24</span>
          <div style={{ flex: 1, height: 3, borderRadius: 99, background: 'rgba(255,255,255,.18)', overflow: 'hidden' }}>
            <div style={{ width: '12%', height: '100%', background: 'var(--accent)' }} />
          </div>
          <button className="btn icon sm press" style={{ background: 'rgba(0,0,0,.55)', border: '1px solid transparent' }} title="Mute / unmute">
            <Icon name="volume_up" size={12} />
          </button>
        </div>
      </div>

      {/* Market switcher + actions */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <Icon name="closed_caption" size={14} fill style={{ color: 'var(--fg-2)' }} />
        <div style={{ fontSize: 12.5, color: 'var(--fg-2)' }}>Caption editor — shipped video</div>
        <div style={{ flex: 1 }} />
        <div className="seg">
          {active.map(c => (
            <button key={c} className={market===c?'on':''} onClick={()=>setMarket(c)}>
              <Flag code={c} size={11} /> {c.toUpperCase()}
            </button>
          ))}
        </div>
        <button className="btn sm press"><Icon name="auto_awesome" size={12} /> Re-time</button>
        <button className="btn sm press"><Icon name="download" size={12} /> SRT</button>
      </div>

      {/* timeline ruler */}
      <div style={{ position: 'relative', height: 22, borderRadius: 6, background: 'var(--bg-2)', border: '1px solid var(--line)' }}>
        {[0, 5, 10, 15, 20, 24].map(s => (
          <div key={s} style={{ position: 'absolute', left: `${(s/24)*100}%`, top: 0, bottom: 0, borderLeft: '1px solid var(--line-2)', paddingLeft: 4, fontFamily: 'JetBrains Mono', fontSize: 9, color: 'var(--fg-3)', display: 'flex', alignItems: 'center' }}>
            0:{String(s).padStart(2, '0')}
          </div>
        ))}
        {cur.map((l, i) => {
          const start = parseInt(l.t.split(':')[1]);
          const dur = parseInt(l.d.split(':')[1]);
          return (
            <div key={i} style={{
              position: 'absolute', top: 4, bottom: 4,
              left: `${(start/24)*100}%`, width: `${(dur/24)*100}%`,
              background: 'rgba(122,167,255,.18)', border: '1px solid rgba(122,167,255,.4)',
              borderRadius: 4,
            }} />
          );
        })}
      </div>

      {/* line editor */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {cur.map((l, i) => (
          <div key={i} className="lift" style={{
            display: 'grid', gridTemplateColumns: '64px 48px 1fr auto', gap: 10, alignItems: 'center',
            padding: '8px 10px', borderRadius: 10, background: 'var(--bg-2)', border: '1px solid var(--line)',
          }}>
            <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>{l.t}</span>
            <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>+{l.d}</span>
            <input
              value={l.text}
              onChange={e => update(i, e.target.value)}
              style={{
                background: 'transparent', border: 'none', outline: 'none',
                color: 'var(--fg)', fontFamily: 'Geist', fontSize: 13, padding: '4px 6px', borderRadius: 6,
                width: '100%',
              }}
              onFocus={e => e.target.style.background = 'var(--bg-3)'}
              onBlur={e => e.target.style.background = 'transparent'}
            />
            <div style={{ display: 'flex', gap: 4 }}>
              <button className="btn sm ghost press" style={{ height: 24, width: 24, padding: 0, justifyContent: 'center' }}><Icon name="play_arrow" size={12} fill /></button>
              <button className="btn sm ghost press" style={{ height: 24, width: 24, padding: 0, justifyContent: 'center' }}><Icon name="more_horiz" size={12} /></button>
            </div>
          </div>
        ))}
        <button className="btn sm ghost press" style={{ alignSelf: 'flex-start' }} onClick={addLine}><Icon name="add" size={12} /> Add line</button>
      </div>

      <StepActions
        leftLabel="Back"
        leftOnClick={onBack}
        helper="Edits sync across the per-market render."
        rightLabel="Approve translations"
        rightIcon="check_circle"
        rightAccent
        rightOnClick={onApprove}
      />
    </div>
  );
}

function Step4Export({ active, onBack }) {
  const [option, setOption] = useState('subtitles');
  return (
    <div className="anim-fade-up" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>Step 4 · Export & ship</div>
        <span className="chip mono" style={{ fontSize: 10, color: 'var(--good)', borderColor: 'rgba(121,210,163,.32)', display: 'inline-flex', alignItems: 'center', gap: 5 }}>
          <Icon name="check_circle" size={10} fill /> TRANSLATIONS APPROVED
        </span>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>{active.length} markets ready</span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10 }}>
        <ExportOptionCard
          icon="closed_caption"
          title="Burn-in subtitles"
          sub="Bake captions into the dubbed video, then download."
          active={option==='subtitles'}
          onClick={() => setOption('subtitles')}
        />
        <ExportOptionCard
          icon="download"
          title="Download (no subtitles)"
          sub={`${active.length} dubbed cuts as MP4, captions off.`}
          active={option==='download'}
          onClick={() => setOption('download')}
        />
        <ExportOptionCard
          icon="library_add"
          title="Push to Mediathek"
          sub="Send dubbed cuts to the asset library to schedule later."
          active={option==='mediathek'}
          onClick={() => setOption('mediathek')}
        />
      </div>

      {option === 'subtitles' && <BurnInSubtitlesEditor active={active} />}
      {option === 'download' && <DownloadPanel active={active} />}
      {option === 'mediathek' && <PushToMediathekPanel active={active} />}

      <StepActions
        leftLabel="Back"
        leftOnClick={onBack}
      />
    </div>
  );
}

function ExportOptionCard({ icon, title, sub, active, onClick }) {
  return (
    <button onClick={onClick} className="lift press" style={{
      padding: 14, borderRadius: 12, cursor: 'pointer', textAlign: 'left',
      background: active ? 'var(--bg-3)' : 'var(--bg-1)',
      border: '1px solid ' + (active ? 'var(--accent)' : 'var(--line)'),
      display: 'flex', flexDirection: 'column', gap: 8, color: 'var(--fg)',
    }}>
      <div style={{
        width: 32, height: 32, borderRadius: 9,
        background: active ? 'rgba(122,167,255,.14)' : 'var(--bg-3)',
        color: active ? 'var(--accent)' : 'var(--fg-2)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        border: '1px solid ' + (active ? 'rgba(122,167,255,.35)' : 'var(--line)'),
      }}>
        <Icon name={icon} size={16} fill={active} />
      </div>
      <div style={{ fontFamily: 'Hanken Grotesk', fontSize: 14, fontWeight: 600 }}>{title}</div>
      <div style={{ fontSize: 11.5, color: 'var(--fg-3)', lineHeight: 1.4 }}>{sub}</div>
    </button>
  );
}

function BurnInSubtitlesEditor({ active }) {
  const [position, setPosition] = useState('bottom');
  const [size, setSize] = useState('m');
  const [style, setStyle] = useState('clean');
  const [bg, setBg] = useState('shadow');

  return (
    <div className="anim-fade-up hairline" style={{
      borderRadius: 12, padding: 14, background: 'var(--bg-2)',
      display: 'flex', flexDirection: 'column', gap: 12,
    }}>
      <div className="mono" style={{ fontSize: 11, color: 'var(--fg-3)', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
        <Icon name="info" size={12} /> Caption preview lives on Step 3 — these settings only control how subtitles are baked in.
      </div>

      {/* Controls */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
        <KnobGroup label="Position">
          <div className="seg">
            {[['top','Top'],['middle','Middle'],['bottom','Bottom']].map(([id,n]) => (
              <button key={id} className={position===id?'on':''} onClick={()=>setPosition(id)}>{n}</button>
            ))}
          </div>
        </KnobGroup>
        <KnobGroup label="Size">
          <div className="seg">
            {[['s','S'],['m','M'],['l','L']].map(([id,n]) => (
              <button key={id} className={size===id?'on':''} onClick={()=>setSize(id)}>{n}</button>
            ))}
          </div>
        </KnobGroup>
        <KnobGroup label="Style">
          <div className="seg">
            {[['clean','Clean'],['bold','Bold'],['mono','Mono']].map(([id,n]) => (
              <button key={id} className={style===id?'on':''} onClick={()=>setStyle(id)}>{n}</button>
            ))}
          </div>
        </KnobGroup>
        <KnobGroup label="Background">
          <div className="seg">
            {[['none','None'],['shadow','Shadow'],['pill','Pill']].map(([id,n]) => (
              <button key={id} className={bg===id?'on':''} onClick={()=>setBg(id)}>{n}</button>
            ))}
          </div>
        </KnobGroup>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <button className="btn press"><Icon name="bookmark_add" size={14} /> Save preset</button>
        <div style={{ flex: 1 }} />
        <button className="btn accent press"><Icon name="movie_filter" size={14} fill /> Burn & download · {active.length} markets</button>
      </div>
    </div>
  );
}

function DownloadPanel({ active }) {
  const labels = { de: 'Deutsch', uk: 'English UK', pl: 'Polski', ee: 'Eesti', lt: 'Lietuvių', lv: 'Latviešu' };
  const [format, setFormat] = useState('mp4');
  const sizePer = format === 'mp4' ? 38 : (format === 'webm' ? 28 : 12);
  return (
    <div className="anim-fade-up hairline" style={{
      borderRadius: 12, padding: 14, background: 'var(--bg-2)',
      display: 'flex', flexDirection: 'column', gap: 12,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <KnobGroup label="Format">
          <div className="seg">
            {[['mp4','MP4'],['webm','WebM'],['srt','SRT only']].map(([id,n]) => (
              <button key={id} className={format===id?'on':''} onClick={()=>setFormat(id)}>{n}</button>
            ))}
          </div>
        </KnobGroup>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>~ {active.length * sizePer} MB total · {active.length} files</span>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {active.map(c => (
          <div key={c} className="hairline" style={{
            display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px',
            borderRadius: 10, background: 'var(--bg-1)',
          }}>
            <Flag code={c} size={16} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13, fontWeight: 500 }}>{labels[c] || c.toUpperCase()}</div>
              <div className="mono" style={{ fontSize: 10.5, color: 'var(--fg-3)' }}>spring-popup_{c}.{format} · 1080×1920 · ~ {sizePer} MB</div>
            </div>
            <button className="btn sm press"><Icon name="download" size={12} /> {format.toUpperCase()}</button>
          </div>
        ))}
      </div>
      <div>
        <button className="btn accent press"><Icon name="archive" size={14} /> Download all · ZIP</button>
      </div>
    </div>
  );
}

function PushToMediathekPanel({ active }) {
  const labels = { de: 'Deutsch', uk: 'English UK', pl: 'Polski', ee: 'Eesti', lt: 'Lietuvių', lv: 'Latviešu' };
  const [selected, setSelected] = useState(active);
  const [pushed, setPushed] = useState(false);

  const allOn = selected.length === active.length;
  const toggle = (c) => setSelected(s => s.includes(c) ? s.filter(x => x !== c) : [...s, c]);
  const toggleAll = () => setSelected(allOn ? [] : active);

  const handlePush = () => {
    if (selected.length === 0 || pushed) return;
    setPushed(true);
    setTimeout(() => setPushed(false), 2400);
  };

  return (
    <div className="anim-fade-up hairline" style={{
      borderRadius: 12, padding: 14, background: 'var(--bg-2)',
      display: 'flex', flexDirection: 'column', gap: 12,
    }}>
      <div className="hairline" style={{ borderRadius: 10, padding: '10px 12px', background: 'rgba(122,167,255,.06)', borderColor: 'rgba(122,167,255,.3)', display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <Icon name="folder" size={14} style={{ color: 'var(--accent)' }} />
        <div style={{ fontSize: 12, color: 'var(--fg-2)', flex: 1, minWidth: 0 }}>
          Files land in <span className="mono" style={{ color: 'var(--fg)' }}>Mediathek › Localization › Spring Pop-Up</span>, one per market. Open Mediathek to pick one and schedule.
        </div>
        <button className="btn sm ghost press" title="Change destination folder">
          <Icon name="folder_open" size={12} /> Change
        </button>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <button className="btn sm press" onClick={toggleAll}>
          <Icon name={allOn ? 'check_box' : 'check_box_outline_blank'} size={13} />
          {allOn ? 'Deselect all' : 'Select all'}
        </button>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>{selected.length} / {active.length} selected</span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 6 }}>
        {active.map(c => {
          const on = selected.includes(c);
          return (
            <button key={c} onClick={() => toggle(c)} className="hairline lift press" style={{
              display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px',
              borderRadius: 10, background: on ? 'var(--bg-3)' : 'var(--bg-1)',
              borderColor: on ? 'var(--line-3)' : 'var(--line)',
              textAlign: 'left', cursor: 'pointer', color: 'var(--fg)',
            }}>
              <Icon name={on ? 'check_box' : 'check_box_outline_blank'} size={14} style={{ color: on ? 'var(--accent)' : 'var(--fg-3)' }} />
              <Flag code={c} size={14} />
              <span style={{ fontSize: 12.5, fontWeight: 500, flex: 1 }}>{labels[c] || c.toUpperCase()}</span>
              <span className="mono" style={{ fontSize: 10, color: 'var(--fg-3)' }}>spring-popup_{c}.mp4</span>
            </button>
          );
        })}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
          Then jump to Mediathek to schedule each market manually.
        </span>
        <div style={{ flex: 1 }} />
        <button
          className={'btn accent press'}
          onClick={handlePush}
          disabled={selected.length === 0 || pushed}
          style={{ opacity: (selected.length === 0 || pushed) ? 0.55 : 1, cursor: selected.length === 0 ? 'not-allowed' : 'pointer' }}>
          <Icon name={pushed ? 'check_circle' : 'library_add'} size={14} fill />
          {pushed ? `Pushed · ${selected.length} in Mediathek` : `Push ${selected.length} to Mediathek`}
        </button>
      </div>
    </div>
  );
}

function StepActions({ leftLabel, leftIcon, leftOnClick, rightLabel, rightIcon, rightOnClick, rightDisabled, rightAccent, helper, helperTone }) {
  const helperColor = helperTone === 'warn' ? 'var(--warn)' : 'var(--fg-3)';
  return (
    <div className="hairline-t" style={{ paddingTop: 14, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
      {leftLabel && (
        <button className="btn press" onClick={leftOnClick}>
          <Icon name={leftIcon || 'arrow_back'} size={14} /> {leftLabel}
        </button>
      )}
      {helper && (
        <span className="mono" style={{ fontSize: 11, color: helperColor, display: 'inline-flex', alignItems: 'center', gap: 5 }}>
          {helperTone === 'warn' && <Icon name="warning" size={12} />}
          {helper}
        </span>
      )}
      <div style={{ flex: 1 }} />
      {rightLabel && (
        <button
          className={'btn lg press' + (rightAccent ? ' accent' : ' primary')}
          onClick={rightDisabled ? undefined : rightOnClick}
          disabled={!!rightDisabled}
          style={{ opacity: rightDisabled ? 0.45 : 1, cursor: rightDisabled ? 'not-allowed' : 'pointer' }}
        >
          <Icon name={rightIcon || 'arrow_forward'} size={15} fill /> {rightLabel}
        </button>
      )}
    </div>
  );
}

function UploadSlot({ title, sub, icon, accept, acceptMime, filled, file, onClear, onFile, isAudio }) {
  const inputRef = useRef(null);

  const handleFileChange = (e) => {
    const f = e.target.files && e.target.files[0];
    if (f && onFile) onFile(f);
    e.target.value = '';
  };

  const triggerPicker = () => inputRef.current && inputRef.current.click();

  const truncName = (name) => name.length > 24 ? name.slice(0, 21) + '…' : name;

  if (!filled) {
    return (
      <div className="dotgrid lift" onClick={triggerPicker} style={{
        border: '1.5px dashed var(--line-2)', borderRadius: 14, cursor: 'pointer',
        padding: '28px 14px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
        background: 'var(--bg-1)', textAlign: 'center', minHeight: 200, justifyContent: 'center',
      }}>
        <input ref={inputRef} type="file" accept={acceptMime} style={{ display: 'none' }} onChange={handleFileChange} />
        <div style={{
          width: 44, height: 44, borderRadius: 12,
          background: 'var(--bg-3)', border: '1px solid var(--line-2)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <Icon name={icon} size={20} />
        </div>
        <div style={{ fontFamily: 'Hanken Grotesk', fontSize: 15, fontWeight: 600 }}>{title}</div>
        <div style={{ fontSize: 11.5, color: 'var(--fg-3)', maxWidth: 220 }}>{sub}</div>
        <button className="btn sm primary press" style={{ marginTop: 4 }} onClick={(e) => { e.stopPropagation(); triggerPicker(); }}><Icon name="upload" size={13} /> Browse</button>
        <span className="mono" style={{ fontSize: 10, color: 'var(--fg-3)' }}>{accept}</span>
      </div>
    );
  }
  return (
    <div className="lift" style={{
      borderRadius: 14, padding: 12, background: 'var(--bg-2)',
      border: '1px solid var(--line-2)', display: 'flex', flexDirection: 'column', gap: 10, minHeight: 200,
    }}>
      <input ref={inputRef} type="file" accept={acceptMime} style={{ display: 'none' }} onChange={handleFileChange} />
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <Icon name={icon} size={14} style={{ color: 'var(--fg-2)' }} />
        <span className="caps" style={{ color: 'var(--fg-3)' }}>{title}</span>
        <div style={{ flex: 1 }} />
        <button className="btn sm ghost press" onClick={triggerPicker} title="Replace"><Icon name="autorenew" size={12} /></button>
      </div>

      {!isAudio && (
        <div style={{ position: 'relative', borderRadius: 10, overflow: 'hidden', flex: 1, minHeight: 110 }}>
          {file.ph
            ? <PhotoTile ph={file.ph} video dur={file.dur} style={{ width: '100%', height: '100%' }} />
            : (
              <div style={{ width: '100%', height: '100%', minHeight: 110, background: 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 6 }}>
                <Icon name="movie_filter" size={28} style={{ color: 'var(--fg-3)' }} />
                <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>{truncName(file.name)}</span>
              </div>
            )
          }
        </div>
      )}
      {isAudio && (
        <div style={{ position: 'relative', borderRadius: 10, overflow: 'hidden', flex: 1, minHeight: 110, background: 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 10px' }}>
          <Waveform />
          <button className="btn sm press" style={{ position: 'absolute', left: 8, bottom: 8, background: 'rgba(0,0,0,.55)' }}><Icon name="play_arrow" size={12} fill /> Preview</button>
          <span className="chip mono" style={{ position: 'absolute', right: 8, bottom: 8, fontSize: 9.5, background: 'rgba(0,0,0,.55)' }}>VOCAL-FREE</span>
        </div>
      )}

      <div>
        <div style={{ fontSize: 12.5, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{truncName(file.name)}</div>
        <div className="mono" style={{ fontSize: 10.5, color: 'var(--fg-3)' }}>{file.meta}</div>
      </div>
    </div>
  );
}

function Waveform() {
  // deterministic pseudo-waveform
  const bars = Array.from({ length: 56 }, (_, i) => {
    const v = Math.abs(Math.sin(i * 0.7) + Math.cos(i * 1.3) * 0.6) * 0.5 + 0.2;
    return Math.min(1, v);
  });
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 2, width: '100%', height: 70 }}>
      {bars.map((v, i) => (
        <div key={i} style={{
          flex: 1, height: `${v * 100}%`, borderRadius: 2,
          background: i < 18 ? 'var(--accent)' : 'var(--fg-3)',
          opacity: i < 18 ? 0.9 : 0.35,
          transition: 'all .3s var(--ease)',
        }} />
      ))}
    </div>
  );
}

function ContinuedBanner({ originId, onDismiss }) {
  return (
    <div className="anim-fade-up hairline" style={{
      borderRadius: 12, padding: '12px 14px',
      background: 'rgba(122,167,255,.06)', borderColor: 'rgba(122,167,255,.3)',
      display: 'flex', alignItems: 'center', gap: 12,
    }}>
      <div style={{
        width: 32, height: 32, borderRadius: 8,
        background: 'rgba(122,167,255,.14)', border: '1px solid rgba(122,167,255,.3)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--accent)', flexShrink: 0,
      }}>
        <Icon name="event_available" size={16} fill />
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 500 }}>
          {originId ? 'Editing planned post from Calendar' : 'Continued from Calendar planning'}
        </div>
        <div style={{ fontSize: 11.5, color: 'var(--fg-3)' }}>
          Channel, market, schedule and creative are pre-filled. Refine the caption and ship it.
        </div>
      </div>
      <button className="btn icon sm ghost press" onClick={onDismiss} title="Dismiss">
        <Icon name="close" size={14} />
      </button>
    </div>
  );
}

function SelectedCreativeHero({ asset, onReplace }) {
  const campaign = CAMPAIGNS.find(c => c.id === asset.campaign);
  return (
    <div className="anim-fade-up" style={{
      display: 'grid', gridTemplateColumns: '180px 1fr', gap: 16,
      padding: 14, background: 'var(--bg-2)', border: '1px solid var(--accent)',
      borderRadius: 14,
    }}>
      <div style={{ position: 'relative' }}>
        <PhotoTile
          ph={asset.ph}
          video={asset.kind === 'video'}
          dur={asset.dur}
          style={{ aspectRatio: '4/5', borderRadius: 10 }}
        />
        <div style={{
          position: 'absolute', top: 6, right: 6,
          background: 'var(--accent)', color: '#0a1424', borderRadius: 99,
          width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <Icon name="check" size={14} />
        </div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span className="caps" style={{ color: 'var(--accent)' }}>Selected creative</span>
          <span className="chip mono" style={{ fontSize: 10 }}>From Mediathek</span>
        </div>
        <div className="headline" style={{ fontSize: 16, lineHeight: 1.25, wordBreak: 'break-all' }}>{asset.name}</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          <span className="chip mono">
            {asset.kind === 'video' ? `VIDEO · ${asset.dur || ''}` : 'PHOTO'}
          </span>
          <span className="chip" style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
            <Flag code={asset.mk} size={11} /> {asset.mk.toUpperCase()}
          </span>
          {campaign && (
            <span className="chip" style={{
              borderColor: 'rgba(122,167,255,.35)', color: 'var(--accent)',
              display: 'inline-flex', alignItems: 'center', gap: 5,
            }}>
              <span style={{ width: 6, height: 6, borderRadius: 99, background: campaign.color }} />
              {campaign.name}
            </span>
          )}
          <span className="chip mono" style={{ color: 'var(--fg-3)' }}>
            <Icon name="hub" size={11} /> Reusable on any platform
          </span>
        </div>
        <p style={{ fontSize: 12.5, color: 'var(--fg-3)', lineHeight: 1.5, margin: 0 }}>
          Confirm the creative looks right, then write the caption below.
        </p>
        <div style={{ display: 'flex', gap: 6, marginTop: 'auto' }}>
          <button className="btn sm press" onClick={onReplace} title="Pick a different creative">
            <Icon name="autorenew" size={13} /> Replace
          </button>
        </div>
      </div>
    </div>
  );
}

function LibraryPanel({ selectedMedia, onSelectMedia, selectedCampaign, onSelectCampaign, channels = [] }) {
  // Channel segmentation is gone — assets are organised by format (photo/video),
  // campaign, and market only. Any creative can be used on any platform.
  const [formatFilter, setFormatFilter] = useState('all'); // all | photo | video
  const [marketFilter, setMarketFilter] = useState('all');
  // Re-key the just-clicked tile so the CSS animation replays from scratch.
  const [pulse, setPulse] = useState({ name: null, tick: 0 });
  const triggerPulse = (name) => setPulse((p) => ({ name, tick: p.tick + 1 }));

  const campaignFilter = selectedCampaign || 'all';
  const filtered = MEDIA_ASSETS.filter(a =>
    (campaignFilter === 'all' || a.campaign === campaignFilter) &&
    (formatFilter === 'all' || a.kind === formatFilter) &&
    (marketFilter === 'all' || a.mk === marketFilter)
  );
  const selectedAsset = MEDIA_ASSETS.find(a => a.name === selectedMedia) || null;

  return (
    <div className="panel" style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
      <style>{`
        @keyframes libraryTilePulse {
          0%   { transform: scale(1);    box-shadow: 0 0 0 0 rgba(122,167,255,.55); }
          45%  { transform: scale(1.05); box-shadow: 0 0 0 6px rgba(122,167,255,.22); }
          100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(122,167,255,0); }
        }
        .library-tile-pulse { animation: libraryTilePulse .35s var(--ease); }
      `}</style>

      {selectedAsset && (
        <SelectedCreativeHero asset={selectedAsset} onReplace={() => onSelectMedia(null)} />
      )}

      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>From Mediathek</div>
        <div className="seg">
          <button className={campaignFilter === 'all' ? 'on' : ''} onClick={() => onSelectCampaign(null)}>All campaigns</button>
          {CAMPAIGNS.map(c => (
            <button key={c.id} className={campaignFilter === c.id ? 'on' : ''} onClick={() => onSelectCampaign(c.id)}
              style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
              <span style={{ width: 6, height: 6, borderRadius: 99, background: c.color }} />
              {c.name}
            </button>
          ))}
        </div>
        <div style={{ flex: 1 }} />
        <div className="seg">
          <button className={formatFilter === 'all'   ? 'on' : ''} onClick={() => setFormatFilter('all')}>All formats</button>
          <button className={formatFilter === 'photo' ? 'on' : ''} onClick={() => setFormatFilter('photo')}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
            <Icon name="photo" size={12} /> Photo
          </button>
          <button className={formatFilter === 'video' ? 'on' : ''} onClick={() => setFormatFilter('video')}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
            <Icon name="movie" size={12} /> Video
          </button>
        </div>
        <div className="seg">
          <button className={marketFilter === 'all' ? 'on' : ''} onClick={() => setMarketFilter('all')}>All markets</button>
          {MARKETS.map(m => (
            <button key={m.code} className={marketFilter === m.code ? 'on' : ''} onClick={() => setMarketFilter(m.code)}
              style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <Flag code={m.code} size={11} /> {m.code.toUpperCase()}
            </button>
          ))}
        </div>
      </div>

      {filtered.length === 0 ? (
        <div className="hairline" style={{ borderRadius: 10, padding: 18, textAlign: 'center', color: 'var(--fg-3)', fontSize: 12.5 }}>
          No creatives match this filter combo. Try a different filter or upload to Mediathek.
        </div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))', gap: 10 }}>
          {filtered.map((a) => {
            const active = a.name === selectedMedia;
            const isPulsing = pulse.name === a.name;
            return (
              <button
                key={isPulsing ? `${a.name}:${pulse.tick}` : a.name}
                onClick={() => {
                  const next = active ? null : a.name;
                  onSelectMedia(next);
                  if (next) triggerPulse(next);
                }}
                className="press"
                style={{
                  borderRadius: 10, overflow: 'hidden',
                  border: '1px solid ' + (active ? 'var(--accent)' : 'var(--line)'),
                  background: active ? 'var(--bg-3)' : 'var(--bg-1)',
                  cursor: 'pointer', textAlign: 'left',
                  padding: 0, color: 'var(--fg)',
                  position: 'relative',
                  transition: 'background .15s var(--ease)',
                  animation: isPulsing ? 'libraryTilePulse .35s var(--ease)' : undefined,
                }}
              >
                <div style={{ position: 'relative' }}>
                  <PhotoTile ph={a.ph} video={a.kind === 'video'} dur={a.dur} style={{ aspectRatio: '1/1' }} />
                  <div style={{
                    position: 'absolute', top: 6, left: 6,
                    display: 'inline-flex', alignItems: 'center', gap: 4,
                    background: 'rgba(0,0,0,.45)', padding: '2px 6px',
                    borderRadius: 6, fontSize: 9.5, color: '#fff',
                    fontFamily: 'JetBrains Mono', letterSpacing: 0.04, textTransform: 'uppercase',
                  }}>
                    <Flag code={a.mk} size={9} /> {a.mk}
                  </div>
                  {active && (
                    <div style={{
                      position: 'absolute', top: 6, right: 6,
                      background: 'var(--accent)', color: '#0a1424', borderRadius: 99,
                      width: 18, height: 18, display: 'flex', alignItems: 'center', justifyContent: 'center',
                    }}>
                      <Icon name="check" size={12} />
                    </div>
                  )}
                </div>
                <div style={{ padding: 8, fontSize: 11, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.name}</div>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

function HiggsfieldPanel() {
  const [prompt, setPrompt] = useState('Editorial product shot — recycled aluminium wristwatch, soft daylight, deep shadow, calm sophistication.');
  const [aspect, setAspect] = useState('1:1');
  const [model, setModel] = useState('hf-soul');
  return (
    <div className="panel" style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14, position: 'relative' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <div style={{
          width: 28, height: 28, borderRadius: 8,
          background: 'rgba(122,167,255,.12)', border: '1px solid rgba(122,167,255,.35)',
          display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--accent)',
        }}>
          <Icon name="auto_awesome" size={16} fill />
        </div>
        <div className="headline" style={{ fontSize: 15 }}>Higgsfield generation</div>
        <span className="chip" style={{ color: 'var(--accent)', borderColor: 'rgba(122,167,255,.3)' }}>Connected</span>
        <div style={{ flex: 1 }} />
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>2.4 credits / image · 11.0 credits / 4s clip</span>
      </div>

      <textarea
        value={prompt} onChange={e => setPrompt(e.target.value)}
        placeholder="Describe what you want…"
        style={{
          minHeight: 100, padding: 14, resize: 'vertical',
          background: 'var(--bg-2)', border: '1px solid var(--line-2)', borderRadius: 12,
          fontFamily: 'Geist', fontSize: 14, color: 'var(--fg)', outline: 'none', lineHeight: 1.5,
        }}
      />

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 14 }}>
        <KnobGroup label="Model">
          <div className="seg">
            {[['hf-soul','Soul · v3'],['hf-motion','Motion · clip'],['hf-pose','Pose'],['hf-lite','Lite']].map(([id,n]) => (
              <button key={id} className={model===id?'on':''} onClick={()=>setModel(id)}>{n}</button>
            ))}
          </div>
        </KnobGroup>
        <KnobGroup label="Aspect">
          <div className="seg">
            {['1:1','4:5','9:16','16:9'].map(a => (
              <button key={a} className={aspect===a?'on':''} onClick={()=>setAspect(a)}>{a}</button>
            ))}
          </div>
        </KnobGroup>
        <KnobGroup label="Variations">
          <div className="seg">
            {['1','2','4'].map(v => <button key={v} className={v==='4'?'on':''}>{v}</button>)}
          </div>
        </KnobGroup>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <button className="btn ghost press"><Icon name="image" size={15} /> Reference image</button>
        <button className="btn ghost press"><Icon name="palette" size={15} /> Brand voice</button>
        <div style={{ flex: 1 }} />
        <button className="btn accent lg press"><Icon name="auto_awesome" size={16} fill /> Generate</button>
      </div>

      {/* Mock outputs */}
      <div className="hairline-t" style={{ paddingTop: 14 }}>
        <div className="caps" style={{ color: 'var(--fg-3)', marginBottom: 8 }}>Latest run · 4 variations</div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
          {['ph-warm','ph-cool','ph-rose','ph-photo'].map((p, i) => (
            <div key={i} className="lift" style={{ borderRadius: 10, overflow: 'hidden', border: '1px solid var(--line)', position: 'relative' }}>
              <PhotoTile ph={p} style={{ aspectRatio: '1/1' }} label={`v${i+1}`} />
              <div style={{ position: 'absolute', top: 8, left: 8, display: 'flex', gap: 4 }}>
                <span className="chip mono" style={{ fontSize: 9, padding: '2px 6px', background: 'rgba(0,0,0,.5)' }}>Higgsfield</span>
              </div>
              <div style={{ position: 'absolute', bottom: 8, right: 8, display: 'flex', gap: 4 }}>
                <button className="btn sm press" style={{ height: 26, padding: '0 8px', background: 'rgba(0,0,0,.6)' }}><Icon name="check" size={12} /> Use</button>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function KnobGroup({ label, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <span className="caps" style={{ color: 'var(--fg-3)' }}>{label}</span>
      {children}
    </div>
  );
}

/* ---------------- Thumbnail uploader ---------------- */

function ThumbnailUploader({ thumbnail, onChange, channels, kind }) {
  const inputRef = useRef(null);

  // Which platform(s) actually use a custom cover here?
  const platforms = (channels || []).filter(ch =>
    kindAllowsThumbnail(ch, kind || defaultKindForChannel(ch))
  );

  const onFile = (e) => {
    const f = (e.target.files || [])[0];
    if (!f) return;
    const reader = new FileReader();
    reader.onload = () => onChange({ dataUrl: reader.result, fileName: f.name });
    reader.readAsDataURL(f);
    e.target.value = '';
  };

  return (
    <div className="panel" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>Custom thumbnail</div>
        <span className="chip mono" style={{ fontSize: 10 }}>Cover image</span>
        <div style={{ flex: 1 }} />
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          {platforms.map(ch => (
            <span key={ch} title={`Used on ${(CHANNELS.find(c=>c.code===ch)||{}).name}`}>
              <ChannelGlyph ch={ch} size={10} />
            </span>
          ))}
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '160px 1fr', gap: 14, alignItems: 'stretch' }}>
        <div style={{
          aspectRatio: '9 / 16',
          minHeight: 0,
          borderRadius: 10, overflow: 'hidden',
          background: 'var(--bg-2)', border: '1px dashed var(--line-2)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          position: 'relative',
        }}>
          {thumbnail ? (
            <img src={thumbnail.dataUrl} alt={thumbnail.fileName}
              style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
          ) : (
            <div style={{ color: 'var(--fg-3)', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
              <Icon name="add_photo_alternate" size={28} />
              <span style={{ fontSize: 11 }}>No thumbnail</span>
            </div>
          )}
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <p style={{ fontSize: 12.5, color: 'var(--fg-2)', lineHeight: 1.5, margin: 0 }}>
            Pick a still that earns the click. {platforms.length > 1 ? 'It’ll be used as the cover on the platforms above.' : 'It’ll be used as the cover for this post.'}
          </p>
          <ul style={{ margin: 0, padding: '0 0 0 16px', color: 'var(--fg-3)', fontSize: 11.5, lineHeight: 1.65 }}>
            <li>Recommended ratio: <span className="mono" style={{ color: 'var(--fg-2)' }}>9:16</span> (vertical) for IG Reel · FB Reel · TT · Shorts, <span className="mono" style={{ color: 'var(--fg-2)' }}>16:9</span> for YT long-form.</li>
            <li>JPG / PNG · ≤ 2&nbsp;MB · safe area away from edges (UI overlays).</li>
          </ul>
          <input
            ref={inputRef}
            type="file"
            accept="image/*"
            style={{ display: 'none' }}
            onChange={onFile}
          />
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 'auto' }}>
            <button className="btn sm primary press" onClick={() => inputRef.current && inputRef.current.click()}>
              <Icon name="upload" size={13} /> {thumbnail ? 'Replace' : 'Upload thumbnail'}
            </button>
            {thumbnail && (
              <button className="btn sm press" onClick={() => onChange(null)}>
                <Icon name="delete" size={13} /> Remove
              </button>
            )}
            {thumbnail && (
              <span className="chip mono" style={{ fontSize: 10, color: 'var(--fg-3)' }}>
                <Icon name="image" size={11} /> {thumbnail.fileName}
              </span>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------------- Platform preview ---------------- */

const PREVIEW_TABS = [
  { code: 'ig', name: 'Instagram' },
  { code: 'fb', name: 'Facebook' },
  { code: 'tt', name: 'TikTok' },
  { code: 'yt', name: 'YouTube' },
];

function PostPreview({ applied, isDirty, onApply }) {
  const selected = applied.channels || [];
  // One channel per post — the preview only shows tabs for what's actually
  // selected. Anything else would let the user inspect a platform they're not
  // publishing to, which is misleading now that channels are single-select.
  const visibleTabs = PREVIEW_TABS.filter(t => selected.includes(t.code));
  const initialTab = visibleTabs[0]?.code || 'ig';
  const [tab, setTab] = useState(initialTab);

  // Channel changed → snap the preview tab to the selected channel.
  useEffect(() => {
    if (!selected.includes(tab) && selected.length > 0) {
      setTab(selected[0]);
    }
  }, [selected.join(',')]);

  return (
    <div className="panel" style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14, position: 'relative' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <div className="caps" style={{ color: 'var(--fg-3)' }}>Preview</div>
        {isDirty ? (
          <span className="chip" style={{ color: 'var(--warn)', borderColor: 'rgba(241,192,84,.35)' }}>
            <Icon name="circle" size={8} fill style={{ color: 'var(--warn)' }} /> Out of date
          </span>
        ) : (
          <span className="chip" style={{ color: 'var(--good)', borderColor: 'rgba(121,210,163,.35)' }}>
            <Icon name="check_circle" size={11} fill style={{ color: 'var(--good)' }} /> Up to date
          </span>
        )}
        <div style={{ flex: 1 }} />
        <button
          className={'btn sm press' + (isDirty ? ' primary' : '')}
          onClick={onApply}
          disabled={!isDirty}
          style={{ opacity: isDirty ? 1 : 0.4, cursor: isDirty ? 'pointer' : 'default' }}
          title={isDirty ? 'Update the preview with current edits' : 'Preview already matches current edits'}
        >
          <Icon name="auto_fix_high" size={13} /> Apply changes
        </button>
      </div>

      {/* Channel label — no tab strip needed now that posts target a single
          channel. A subtle label keeps the platform legible in the preview. */}
      {visibleTabs.length > 0 && (
        <div style={{
          alignSelf: 'flex-start', display: 'inline-flex', alignItems: 'center', gap: 6,
          padding: '4px 10px', borderRadius: 99,
          background: 'var(--bg-2)', border: '1px solid var(--line)',
          fontSize: 11.5, color: 'var(--fg-2)', fontFamily: 'Geist',
        }}>
          <ChannelGlyph ch={tab} size={10} />
          {(visibleTabs[0] || {}).name}
        </div>
      )}

      <div style={{ display: 'flex', justifyContent: 'center', padding: '8px 0' }}>
        <PreviewRenderer platform={tab} applied={applied} />
      </div>
    </div>
  );
}

function PreviewRenderer({ platform, applied }) {
  const asset = applied.media ? MEDIA_ASSETS.find(a => a.name === applied.media) : null;
  const handle = '@hookline';
  const caption = applied.caption || '';
  const market = (applied.markets && applied.markets[0]) || 'de';
  const kindId = applied.kind || defaultKindForChannel(platform);
  const props = { asset, handle, caption, market, thumbnail: applied.thumbnail, kindId };

  if (platform === 'ig') return <IGPreview {...props} />;
  if (platform === 'fb') return <FBPreview {...props} />;
  if (platform === 'tt') return <TTPreview {...props} />;
  if (platform === 'yt') return <YTPreview {...props} />;
  return null;
}

/* --- Platform-specific previews. Aspect ratios + chrome are inspired by each
   platform's feed; not pixel-perfect, just recognisable. --- */

function PreviewCard({ width = 340, children, style = {} }) {
  return (
    <div style={{
      width, background: 'var(--bg)',
      border: '1px solid var(--line-2)', borderRadius: 14, overflow: 'hidden',
      boxShadow: '0 6px 20px -10px rgba(0,0,0,.55)',
      ...style,
    }}>{children}</div>
  );
}

function PreviewAvatar({ size = 28, ch }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: 99,
      background: 'linear-gradient(135deg, var(--bg-3) 0%, var(--bg-4) 100%)',
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      border: ch === 'ig' ? '1.5px solid #d96d4a' : '1.5px solid var(--line-2)',
      flexShrink: 0,
    }}>
      <span style={{ fontFamily: 'Hanken Grotesk', fontWeight: 700, fontSize: size * 0.4, color: 'var(--fg-2)' }}>H</span>
    </div>
  );
}

function PreviewMedia({ asset, thumbnail, aspect = '1/1', overlayText = null }) {
  // Use thumbnail when provided (video cover); else show the asset's PhotoTile.
  return (
    <div style={{ position: 'relative', background: 'var(--bg-2)' }}>
      {thumbnail ? (
        <img src={thumbnail.dataUrl} alt="thumbnail"
          style={{ width: '100%', aspectRatio: aspect, objectFit: 'cover', display: 'block' }} />
      ) : asset ? (
        <PhotoTile
          ph={asset.ph}
          video={asset.kind === 'video'}
          dur={asset.dur}
          style={{ width: '100%', aspectRatio: aspect }}
        />
      ) : (
        <div style={{
          width: '100%', aspectRatio: aspect, background: 'var(--bg-2)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          color: 'var(--fg-4)', fontSize: 11, gap: 6, flexDirection: 'column',
        }}>
          <Icon name="image" size={28} />
          <span>No media selected</span>
        </div>
      )}
      {overlayText && (
        <div style={{
          position: 'absolute', left: 12, right: 12, bottom: 12,
          color: '#fff', fontSize: 13, fontWeight: 600, lineHeight: 1.3,
          textShadow: '0 2px 8px rgba(0,0,0,.6)',
          display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden',
        }}>{overlayText}</div>
      )}
    </div>
  );
}

function IGPreview({ asset, handle, caption, market, kindId }) {
  // Reel/Story → 9:16; everything else → 1:1.
  const aspect = (kindId === 'reel' || kindId === 'story') ? '9/16' : '1/1';
  return (
    <PreviewCard width={340}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px' }}>
        <PreviewAvatar ch="ig" />
        <div style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>{handle}.{market}</div>
          <div style={{ fontSize: 10.5, color: 'var(--fg-3)' }}>Sponsored · {market.toUpperCase()}</div>
        </div>
        <div style={{ flex: 1 }} />
        <Icon name="more_horiz" size={16} style={{ color: 'var(--fg-3)' }} />
      </div>
      <PreviewMedia asset={asset} aspect={aspect} />
      <div style={{ padding: '8px 12px', display: 'flex', alignItems: 'center', gap: 12, color: 'var(--fg-2)' }}>
        <Icon name="favorite_border" size={20} />
        <Icon name="chat_bubble_outline" size={18} />
        <Icon name="send" size={18} />
        <div style={{ flex: 1 }} />
        <Icon name="bookmark_border" size={20} />
      </div>
      <div style={{ padding: '0 12px 12px', display: 'flex', flexDirection: 'column', gap: 4 }}>
        <div style={{ fontSize: 12.5, fontWeight: 600 }}>1,284 likes</div>
        <div style={{ fontSize: 12.5, lineHeight: 1.4 }}>
          <span style={{ fontWeight: 600 }}>{handle}.{market}</span>{' '}
          <span style={{ color: 'var(--fg-2)' }}>{truncate(caption, 140)}</span>
        </div>
        <div style={{ fontSize: 11.5, color: 'var(--fg-3)' }}>View all 47 comments</div>
        <div style={{ fontSize: 10, color: 'var(--fg-4)', textTransform: 'uppercase', letterSpacing: 0.04 }}>2 hours ago</div>
      </div>
    </PreviewCard>
  );
}

function FBPreview({ asset, handle, caption, market, kindId }) {
  const aspect = kindId === 'story' ? '9/16' : (kindId === 'reel' ? '9/16' : '4/5');
  return (
    <PreviewCard width={360}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px' }}>
        <PreviewAvatar ch="fb" />
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Hookline {market.toUpperCase()}</div>
          <div style={{ fontSize: 10.5, color: 'var(--fg-3)', display: 'flex', alignItems: 'center', gap: 4 }}>
            2h · <Icon name="public" size={11} />
          </div>
        </div>
        <div style={{ flex: 1 }} />
        <Icon name="more_horiz" size={16} style={{ color: 'var(--fg-3)' }} />
      </div>
      <div style={{ padding: '0 12px 10px', fontSize: 12.5, lineHeight: 1.45, color: 'var(--fg)' }}>
        {truncate(caption, 220)}
      </div>
      <PreviewMedia asset={asset} aspect={aspect} />
      <div style={{ padding: '8px 12px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 11, color: 'var(--fg-3)' }}>
        <span>👍 ❤️ 286</span>
        <span>42 comments · 11 shares</span>
      </div>
      <div style={{ display: 'flex', borderTop: '1px solid var(--line)' }}>
        {[
          { icon: 'thumb_up', label: 'Like' },
          { icon: 'chat_bubble_outline', label: 'Comment' },
          { icon: 'share', label: 'Share' },
        ].map((b) => (
          <div key={b.label} style={{
            flex: 1, padding: '8px 10px',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
            fontSize: 12, fontWeight: 500, color: 'var(--fg-2)',
          }}>
            <Icon name={b.icon} size={15} /> {b.label}
          </div>
        ))}
      </div>
    </PreviewCard>
  );
}

function TTPreview({ asset, handle, caption, market, thumbnail, kindId }) {
  // TikTok is vertical; the preview uses a 9:16 frame with right-rail actions
  // overlaid on the media itself.
  return (
    <PreviewCard width={260} style={{ background: '#000' }}>
      <div style={{ position: 'relative' }}>
        <PreviewMedia asset={asset} thumbnail={thumbnail} aspect="9/16" />

        {/* Right action rail */}
        <div style={{
          position: 'absolute', right: 8, bottom: 70,
          display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14,
          color: '#fff',
        }}>
          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
            <PreviewAvatar size={36} ch="tt" />
            <div style={{
              marginTop: -8, width: 18, height: 18, borderRadius: 99,
              background: '#fe2c55', display: 'flex', alignItems: 'center', justifyContent: 'center',
              border: '1.5px solid #000',
            }}>
              <Icon name="add" size={12} style={{ color: '#fff' }} />
            </div>
          </div>
          {[
            { icon: 'favorite', n: '12.4K' },
            { icon: 'chat_bubble', n: '348' },
            { icon: 'bookmark', n: '1.8K' },
            { icon: 'send', n: 'Share' },
          ].map(a => (
            <div key={a.icon} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
              <Icon name={a.icon} size={22} fill style={{ color: '#fff', filter: 'drop-shadow(0 1px 2px rgba(0,0,0,.4))' }} />
              <span style={{ fontSize: 10, fontFamily: 'JetBrains Mono' }}>{a.n}</span>
            </div>
          ))}
        </div>

        {/* Bottom text overlay */}
        <div style={{
          position: 'absolute', left: 12, right: 60, bottom: 12,
          color: '#fff', display: 'flex', flexDirection: 'column', gap: 4,
          textShadow: '0 2px 6px rgba(0,0,0,.5)',
        }}>
          <div style={{ fontSize: 13, fontWeight: 700 }}>{handle}.{market}</div>
          <div style={{ fontSize: 11.5, lineHeight: 1.4, display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
            {truncate(caption, 140)}
          </div>
          <div style={{ fontSize: 10.5, opacity: 0.85, display: 'inline-flex', alignItems: 'center', gap: 5 }}>
            <Icon name="music_note" size={11} fill /> original sound — hookline
          </div>
        </div>
      </div>
    </PreviewCard>
  );
}

function YTPreview({ asset, handle, caption, market, thumbnail, kindId }) {
  if (kindId === 'short') {
    // YouTube Short — vertical
    return (
      <PreviewCard width={240} style={{ background: '#000' }}>
        <div style={{ position: 'relative' }}>
          <PreviewMedia asset={asset} thumbnail={thumbnail} aspect="9/16" />
          <div style={{
            position: 'absolute', right: 8, bottom: 70,
            display: 'flex', flexDirection: 'column', gap: 14, color: '#fff',
          }}>
            {[
              { icon: 'thumb_up', n: '8.2K' },
              { icon: 'thumb_down', n: '' },
              { icon: 'comment', n: '120' },
              { icon: 'share', n: 'Share' },
              { icon: 'more_vert', n: '' },
            ].map(a => (
              <div key={a.icon} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
                <Icon name={a.icon} size={22} style={{ color: '#fff' }} />
                {a.n && <span style={{ fontSize: 9.5, fontFamily: 'JetBrains Mono' }}>{a.n}</span>}
              </div>
            ))}
          </div>
          <div style={{
            position: 'absolute', left: 10, right: 50, bottom: 12,
            color: '#fff', display: 'flex', flexDirection: 'column', gap: 6,
            textShadow: '0 1px 4px rgba(0,0,0,.4)',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <PreviewAvatar size={24} ch="yt" />
              <span style={{ fontSize: 11.5, fontWeight: 600 }}>{handle}.{market}</span>
              <span style={{ fontSize: 10, padding: '2px 8px', borderRadius: 99, background: '#fff', color: '#000', fontWeight: 600 }}>Subscribe</span>
            </div>
            <div style={{ fontSize: 11, lineHeight: 1.35, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
              {truncate(caption, 100)}
            </div>
            <div style={{ fontSize: 10, opacity: 0.85 }}>#Shorts</div>
          </div>
        </div>
      </PreviewCard>
    );
  }

  // Long-form / live / premiere — 16:9 with title + channel row below
  return (
    <PreviewCard width={360}>
      <PreviewMedia asset={asset} thumbnail={thumbnail} aspect="16/9" />
      <div style={{ padding: 12, display: 'flex', gap: 10 }}>
        <PreviewAvatar size={32} ch="yt" />
        <div style={{ display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0, flex: 1 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600, lineHeight: 1.3 }}>
            {truncate(caption.split('.')[0] || 'Untitled video', 80)}
          </div>
          <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>
            Hookline {market.toUpperCase()} · 12K views · 2 hours ago
          </div>
          {kindId === 'live' && (
            <span className="chip" style={{ color: '#ff4040', borderColor: 'rgba(255,64,64,.4)', alignSelf: 'flex-start' }}>
              <span style={{ width: 6, height: 6, borderRadius: 99, background: '#ff4040' }} /> LIVE
            </span>
          )}
        </div>
      </div>
    </PreviewCard>
  );
}

function truncate(str, n) {
  if (!str) return '';
  if (str.length <= n) return str;
  return str.slice(0, n).trim() + '…';
}

Object.assign(window, { ComposeTab });
