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

/* ═══════════════════════════════════════════════
   THEME — Playful
   ═══════════════════════════════════════════════ */
const T = {
  pageBg: '#FDFCFA', text: '#1a1a1a', textSoft: '#555', textMuted: '#aaa',
  accent: '#FF5733', surface: '#F4F2EE', border: '#E8E4DE',
  hero: { size: 'clamp(64px, 9vw, 96px)', weight: 700, spacing: '-0.02em', lh: 1.05 },
  input: { border: '1.5px solid #ddd8d0', focus: '2px solid #FF5733', radius: '14px', pad: '14px 20px' },
  btn: { radius: '14px', pad: '14px 28px', spacing: '0.1em' },
  card: { radius: '20px', border: '1px solid #E8E4DE', shadow: '0 4px 28px rgba(0,0,0,0.035)', bg: '#fff' },
};

const MODELS = {
  scout: { id: 'meta-llama/llama-4-scout-17b-16e-instruct', label: 'Llama 4 Scout' },
  llama3: { id: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70B' },
  deepseek: { id: 'deepseek-r1-distill-llama-70b', label: 'DeepSeek R1 70B' },
};

const LOADER_MESSAGES = [
  'analyzing video...',
  'skimming through the content...',
  'finding the gist...',
  'got it, let me think how to make it more concise...',
  'cutting the noise...',
  'getting to the point...',
  'so many yada yada...',
  'alright, a part is already done...',
  'wrapping it up...',
];
const MSG_DELAYS = [400, 900, 900, 1300, 950, 750, 1100, 900, 700];

/* ═══════════════════════════════════════════════
   API UTILITIES
   ═══════════════════════════════════════════════ */

function extractVideoId(url) {
  const m = url.match(/(?:youtube\.com\/watch\?.*v=|youtu\.be\/|youtube\.com\/shorts\/)([^&\n?#\s]+)/);
  return m ? m[1] : null;
}

async function getVideoMeta(url) {
  const resp = await fetch(`https://www.youtube.com/oembed?url=${encodeURIComponent(url)}&format=json`);
  if (!resp.ok) throw new Error('Video not found or unavailable — check the URL.');
  return resp.json();
}

// Try several CORS proxies in order — if one is down or slow, fall through.
const CORS_PROXIES = [
  (u) => `https://api.allorigins.win/raw?url=${encodeURIComponent(u)}`,
  (u) => `https://corsproxy.io/?url=${encodeURIComponent(u)}`,
  (u) => `https://thingproxy.freeboard.io/fetch/${u}`,
];

async function proxyFetchText(url, timeout = 11000) {
  let lastErr;
  for (const wrap of CORS_PROXIES) {
    try {
      const res = await fetch(wrap(url), { signal: AbortSignal.timeout(timeout) });
      if (!res.ok) throw new Error(`status ${res.status}`);
      const text = await res.text();
      if (text && text.length > 0) return text;
      throw new Error('empty');
    } catch (e) { lastErr = e; }
  }
  throw lastErr || new Error('All proxies failed');
}

async function fetchTranscript(videoId) {
  // 1. Grab the watch page to find caption tracks
  const page = await proxyFetchText(`https://www.youtube.com/watch?v=${videoId}`);

  const m = page.match(/"captionTracks":\s*(\[.*?\])/);
  if (!m) throw new Error('No captions available');

  let baseUrl;
  try {
    const tracks = JSON.parse(m[1].replace(/\\u0026/g, '&').replace(/\\"/g, '"'));
    // Prefer a manually-created track; fall back to the first (often auto-generated).
    const manual = tracks.find(t => t.kind !== 'asr');
    baseUrl = (manual || tracks[0]).baseUrl;
  } catch {
    const single = m[1].match(/"baseUrl":"([^"]+)"/);
    if (!single) throw new Error('No captions available');
    baseUrl = single[1];
  }

  baseUrl = baseUrl
    .replace(/\\u0026/g, '&').replace(/\\u003d/g, '=')
    .replace(/\\u003c/g, '<').replace(/\\u003e/g, '>')
    .replace(/\\\//g, '/');

  // 2. Fetch the captions as JSON (json3) — cleaner than XML
  try {
    const jsonText = await proxyFetchText(baseUrl + '&fmt=json3');
    const data = JSON.parse(jsonText);
    const out = (data.events || [])
      .map(ev => (ev.segs || []).map(s => s.utf8).join(''))
      .join(' ').replace(/\s+/g, ' ').trim();
    if (out.length > 20) return out;
  } catch {}

  // 3. Fallback: XML format
  const xml = await proxyFetchText(baseUrl);
  if (!xml || !xml.includes('<text')) throw new Error('Empty transcript');
  const doc = new DOMParser().parseFromString(xml, 'text/xml');
  return [...doc.querySelectorAll('text')]
    .map(el => el.textContent
      .replace(/&amp;/g, '&').replace(/&#39;/g, "'")
      .replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>'))
    .filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
}

/* ─── FOUNDER KEY — never expose to users ─────────────────────────────────
   Replace with your actual Groq key before deploying.                     */
const GROQ_API_KEY = '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';

async function callGroq(prompt, modelId, maxTokens = 2048) {
  const body = {
      model: modelId,
      messages: [{ role: 'user', content: prompt }],
      max_tokens: maxTokens,
      temperature: 0.3,
    };
  if (modelId.includes('deepseek')) body.reasoning_format = 'hidden';
  const resp = await fetch('https://api.groq.com/openai/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${GROQ_API_KEY}`,
    },
    body: JSON.stringify(body),
  });

  if (!resp.ok) {
    const err = await resp.json().catch(() => ({}));
    const msg = err?.error?.message || '';
    if (resp.status === 401) throw new Error('Invalid Groq API key — check and try again.');
    if (resp.status === 429) throw new Error('Rate limit hit — wait a moment and try again.');
    throw new Error(msg || `Groq API error (${resp.status})`);
  }

  const data = await resp.json();
  return data.choices[0].message.content;
}

function parseJson(raw) {
  let s = raw.trim().replace(/<think>[\s\S]*?<\/think>/gi, '').replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '');
  const start = s.indexOf('{'), end = s.lastIndexOf('}');
  if (start === -1 || end === -1) throw new Error('Could not parse the summary — try again.');
  return JSON.parse(s.substring(start, end + 1));
}

async function fetchAndSummarize(url, modelId, langMode = 'english') {
  const videoId = extractVideoId(url);
  if (!videoId) throw new Error('Invalid YouTube URL');

  // Map full model id → short key for the backend function.
  const modelKey = Object.keys(MODELS).find(k => MODELS[k].id === modelId) || 'scout';

  // 1. Try the server-side Cloudflare function (deployed on stfu.cc).
  //    Fetches the transcript reliably server-side and keeps the key hidden.
  try {
    const res = await fetch('/api/summarize', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ videoId, model: modelKey, langMode }),
      signal: AbortSignal.timeout(45000),
    });
    if (res.ok) {
      const data = await res.json();
      if (data && Array.isArray(data.gist) && !data.error) return data;
    }
  } catch {}

  // 2. Fallback for local preview / file:// where the function isn't available.
  return clientSummarize(url, modelId, langMode);
}

async function clientSummarize(url, modelId, langMode = 'english') {
  const videoId = extractVideoId(url);
  if (!videoId) throw new Error('Invalid YouTube URL');

  const meta = await getVideoMeta(url);

  let transcript = null;
  try { transcript = await fetchTranscript(videoId); } catch {}

  const titleSafe = (meta.title || '').replace(/"/g, '\\"');
  const channelSafe = (meta.author_name || '').replace(/"/g, '\\"');

  // Detect the video's language so we can name it explicitly to the model.
  let videoLang = 'English';
  if (langMode === 'video language') {
    try {
      const sample = `${meta.title || ''}\n${(transcript || '').substring(0, 600)}`;
      const detectPrompt = `What language is the following text written in? Reply with ONLY the English name of the language (e.g. "German", "Spanish", "Japanese"). Nothing else.\n\nTEXT:\n${sample}`;
      const detected = (await callGroq(detectPrompt, modelId)).trim().split(/[\s.,\n]/)[0];
      if (detected && /^[A-Za-z]+$/.test(detected)) videoLang = detected;
    } catch {}
  }

  const langInstruction = langMode === 'video language'
    ? `Write EVERY string field (gist, noise labels, verdict) entirely in ${videoLang}. Do NOT use English. Do NOT translate to English.`
    : 'Write every string field (gist, noise labels, verdict) in ENGLISH, regardless of the video\'s original language.';

  const prompt = `You are STFU (Standard Fundament) — a tool that cuts through YouTube clickbait and gives people the actual substance in seconds.

⚠️ OUTPUT LANGUAGE (TOP PRIORITY): ${langInstruction}

🎯 MOST IMPORTANT JOB — ANSWER THE CLICKBAIT:
The title makes a promise, asks a question, or teases an answer. The user clicked because they want THAT answer. Your #1 gist point MUST directly deliver it, concretely, in the first sentence.
- Title: "Just Do These 3 Exercises" → point 1 names the 3 exercises (e.g. "Squats, push-ups, and pull-ups.").
- Title: "The REAL reason he quit" → point 1 states the actual reason.
- Title: "I tried X for 30 days" → point 1 states what actually happened / the result.
Do NOT make the user read a paragraph to find the answer. Lead with it.

🚫 ABSOLUTELY NO SPECULATION — this is critical:
- You have the transcript below. Base EVERY word ONLY on what it actually says.
- NEVER write "likely", "probably", "the video discusses", "might include", "the study mentioned" or any guessing language. These are BANNED.
- If the transcript clearly says the 3 exercises are squats, push-ups, pull-ups → say exactly that.
- Do NOT invent studies, statistics, or topics that aren't in the transcript. (A fitness video about exercises is NOT about "visceral fat studies" unless the transcript says so.)
- If there is NO transcript, do NOT guess the content. Instead set the verdict to explain a transcript wasn't available, and only state what you can genuinely infer from the title — clearly flagged as inference, not fact.

Each gist point must have:
- A "headline": 4–8 word bold label that states the actual answer/finding (not a topic)
- A "detail": 1–3 sentences that unpack it with the SPECIFICS from the transcript — names, numbers, steps, the actual answer. Concise, not padded.

EXAMPLE for title "Getting Jacked At Home Is Simple (Just Do These 3 Exercises)":
GOOD point 1 → headline: "The 3 exercises: squats, push-ups, pull-ups", detail: "These three bodyweight movements cover legs, push, and pull. The video gives a progression for each based on your level — e.g. incline push-ups before full push-ups, negatives before full pull-ups."
BAD point 1 → headline: "Belly Fat Reduction Is Possible", detail: "The video likely discusses a study on visceral fat..." ← WRONG: speculative AND doesn't answer the title.

Return ONLY a valid JSON object — no markdown, no code fences, no explanation. Exact structure:
{
  "videoTitle": "${titleSafe}",
  "channel": "${channelSafe}",
  "gist": [
    {"headline": "Short bold finding (4–7 words)", "detail": "2–4 sentences fully unpacking this point with real info, numbers, context."},
    {"headline": "Another specific finding", "detail": "Full unpacked explanation with substance."},
    {"headline": "Third finding — add 4th or 5th if needed", "detail": "Full unpacked explanation."}
  ],
  "noise": [
    {"label": "filler type (e.g. sponsor, intro, anecdote, CTA)", "time": "estimated duration"}
  ],
  "signalPercent": <integer 0-100, be honest — most YouTube videos are 10-30%>,
  "actualContent": "estimated useful duration e.g. '4:30'",
  "totalDuration": "estimated total video length",
  "verdict": "one punchy slightly snarky sentence — is this worth watching in full, or did the user just get the gist?",
  "noTranscript": ${!transcript}
}

OUTPUT LANGUAGE — reminder: ${langInstruction}

VIDEO: "${titleSafe}"
CHANNEL: ${channelSafe}
${transcript ? `TRANSCRIPT (base everything strictly on this):\n${transcript.substring(0, 12000)}` : 'NO TRANSCRIPT IS AVAILABLE FOR THIS VIDEO. Do not invent content. Only state what the TITLE itself directly promises, clearly framed as inference. Set "signalPercent" low and use the verdict to tell the user no transcript was available so this is a best-guess from the title only.'}`;

  const raw = await callGroq(prompt, modelId);
  return parseJson(raw);
}



/* ═══════════════════════════════════════════════
   COMPONENTS
   ═══════════════════════════════════════════════ */


function Hero({ compact }) {
  return (
    <div style={{ textAlign: 'center', transition: 'all 0.6s cubic-bezier(0.4,0,0.2,1)',
      paddingTop: compact ? '12px' : 'clamp(32px, 8vh, 100px)',
      paddingBottom: compact ? '20px' : '28px' }}>
      <h1 style={{
        fontFamily: "'Bricolage Grotesque', system-ui, sans-serif",
        fontSize: compact ? 'clamp(28px, 5vw, 40px)' : 'clamp(72px, 11vw, 144px)',
        fontWeight: 800, letterSpacing: '-0.03em', lineHeight: 1.0,
        color: T.accent,
        transition: 'font-size 0.6s cubic-bezier(0.4,0,0.2,1)',
        marginBottom: compact ? '2px' : '6px',
      }}>STFU</h1>
      <p style={{
        fontFamily: "'Bricolage Grotesque', system-ui, sans-serif",
        fontSize: compact ? 'clamp(11px, 2vw, 14px)' : 'clamp(20px, 3.5vw, 32px)',
        fontWeight: 700, letterSpacing: '-0.01em',
        color: '#0a0a0a',
        transition: 'all 0.6s cubic-bezier(0.4,0,0.2,1)',
        marginBottom: compact ? 0 : '10px',
      }}>Standard Fundament</p>
      <p style={{
        fontFamily: "'Bricolage Grotesque', system-ui, sans-serif",
        fontSize: '15px', color: '#c5c0b8',
        transition: 'all 0.4s ease', opacity: compact ? 0 : 1,
        maxHeight: compact ? 0 : '40px', overflow: 'hidden', marginTop: compact ? 0 : '4px',
      }}>skip the fluff — just tell me the thing.</p>
    </div>
  );
}

function SearchBar({ onSubmit, loading }) {
  const [url, setUrl] = useState('');
  const [error, setError] = useState('');
  const [focused, setFocused] = useState(false);
  const [shaking, setShaking] = useState(false);
  const inputRef = useRef(null);

  const isYT = (u) => /youtube\.com\/watch|youtu\.be\/|youtube\.com\/shorts/.test(u);
  const shake = (msg) => { setError(msg); setShaking(true); setTimeout(() => setShaking(false), 500); };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!url.trim()) { shake('paste a youtube link first'); inputRef.current?.focus(); return; }
    if (!isYT(url)) { shake("that doesn't look like a youtube link"); return; }
    setError('');
    onSubmit(url.trim());
  };

  return (
    <div style={{ width: '100%', maxWidth: '620px', margin: '0 auto', padding: '0 24px' }}>
      <form onSubmit={handleSubmit}
        style={{
          display: 'flex', alignItems: 'center',
          border: focused ? `2px solid ${T.accent}` : '1.5px solid #ddd8d0',
          borderRadius: '100px', backgroundColor: '#fff',
          padding: '5px 5px 5px 22px',
          boxShadow: focused ? `0 0 0 4px ${T.accent}12` : '0 2px 16px rgba(0,0,0,0.05)',
          transition: 'border 0.2s, box-shadow 0.2s, opacity 0.3s',
          animation: shaking ? 'stfu-shake 0.45s ease' : 'none',
          opacity: loading ? 0.6 : 1,
        }}>
        <input ref={inputRef} type="text" value={url} disabled={loading}
          onChange={e => { setUrl(e.target.value); if (error) setError(''); }}
          onFocus={() => setFocused(true)} onBlur={() => setFocused(false)}
          placeholder="paste any youtube link..."
          style={{
            flex: 1, fontFamily: "'Bricolage Grotesque', sans-serif",
            fontSize: '15px', border: 'none', outline: 'none',
            backgroundColor: 'transparent', color: T.text, minWidth: 0,
          }}
        />
        <button type="submit" disabled={loading}
          style={{
            fontFamily: "'Bricolage Grotesque', sans-serif",
            fontSize: '13px', fontWeight: 700, letterSpacing: '0.1em',
            padding: '12px 26px', backgroundColor: '#0a0a0a', color: '#fff',
            border: 'none', borderRadius: '100px', cursor: loading ? 'not-allowed' : 'pointer',
            transition: 'all 0.15s ease', whiteSpace: 'nowrap', textTransform: 'uppercase',
            flexShrink: 0,
          }}
          onMouseEnter={e => { if (!loading) e.currentTarget.style.background = '#333'; }}
          onMouseLeave={e => { e.currentTarget.style.background = '#0a0a0a'; }}
          onMouseDown={e => { if (!loading) e.currentTarget.style.transform = 'scale(0.97)'; }}
          onMouseUp={e => { e.currentTarget.style.transform = 'scale(1)'; }}
        >STFU</button>
      </form>
      <div style={{ overflow: 'hidden', maxHeight: error ? '36px' : '0', transition: 'max-height 0.3s ease' }}>
        <p style={{ fontFamily: "'Bricolage Grotesque', sans-serif", fontSize: '12px', color: T.accent,
          marginTop: '10px', textAlign: 'center' }}>{error}</p>
      </div>
    </div>
  );
}

function LangToggle({ value, onChange }) {
  const opts = ['english', 'video language'];
  return (
    <div style={{ display: 'flex', justifyContent: 'center', marginTop: '12px' }}>
      <div style={{ display: 'flex', backgroundColor: T.surface, borderRadius: '100px',
        border: `1px solid ${T.border}`, padding: '3px', gap: '2px' }}>
        {opts.map(opt => {
          const active = value === opt;
          return (
            <button key={opt} onClick={() => onChange(opt)}
              style={{ fontFamily: "'Bricolage Grotesque', sans-serif", fontSize: '12px',
                fontWeight: active ? 600 : 400, padding: '5px 14px', borderRadius: '100px',
                border: 'none', cursor: 'pointer', transition: 'all 0.18s ease',
                backgroundColor: active ? '#0a0a0a' : 'transparent',
                color: active ? '#fff' : T.textMuted,
                letterSpacing: '0.01em' }}>
              {opt === 'english' ? '🇬🇧 English' : '🌐 Video language'}
            </button>
          );
        })}
      </div>
    </div>
  );
}

function LoaderMessages({ onComplete, waitingForApi }) {
  const [count, setCount] = useState(0);
  const [done, setDone] = useState(false);

  useEffect(() => {
    const ts = [];
    let cum = 0;
    LOADER_MESSAGES.forEach((_, i) => {
      cum += MSG_DELAYS[i] || 800;
      ts.push(setTimeout(() => setCount(i + 1), cum));
    });
    cum += 900;
    ts.push(setTimeout(() => { setDone(true); onComplete(); }, cum));
    return () => ts.forEach(clearTimeout);
  }, []);

  return (
    <div style={{ maxWidth: '620px', margin: '30px auto 0', padding: '0 24px',
      opacity: done && !waitingForApi ? 0 : 1,
      transform: done && !waitingForApi ? 'translateY(-8px)' : 'translateY(0)',
      transition: 'opacity 0.4s ease, transform 0.4s ease' }}>
      <div style={{ fontFamily: "'Space Mono', monospace", fontSize: '13px', lineHeight: 2,
        display: 'flex', flexDirection: 'column', gap: '1px' }}>
        {LOADER_MESSAGES.slice(0, count).map((msg, i) => {
          const isLatest = i === count - 1 && !done;
          return (
            <div key={i} style={{ color: isLatest ? T.textSoft : T.textMuted,
              animation: 'stfu-fadeInUp 0.28s ease both',
              display: 'flex', alignItems: 'center', gap: '10px', transition: 'color 0.3s' }}>
              <span style={{ width: '6px', height: '6px', borderRadius: '50%', flexShrink: 0,
                backgroundColor: isLatest ? T.accent : T.border, transition: 'background 0.3s' }}></span>
              {msg}
              {isLatest && <span style={{ display: 'inline-block', width: '6px', height: '14px',
                backgroundColor: T.textSoft, animation: 'stfu-blink 0.75s step-end infinite',
                marginLeft: '1px', flexShrink: 0 }}></span>}
            </div>
          );
        })}
        {waitingForApi && (
          <div style={{ color: T.textMuted, animation: 'stfu-fadeInUp 0.3s ease both',
            display: 'flex', alignItems: 'center', gap: '10px' }}>
            <span style={{ width: '6px', height: '6px', borderRadius: '50%', flexShrink: 0,
              backgroundColor: T.accent, animation: 'stfu-pulse 1.2s ease-in-out infinite' }}></span>
            <span style={{ animation: 'stfu-pulse 1.2s ease-in-out infinite' }}>
              pulling it all together...
            </span>
          </div>
        )}
      </div>
    </div>
  );
}

function ResultCard({ data }) {
  const [copied, setCopied] = useState(false);
  const [barW, setBarW] = useState(0);
  useEffect(() => { const t = setTimeout(() => setBarW(data.signalPercent || 0), 200); return () => clearTimeout(t); }, [data.signalPercent]);

  const handleCopy = async () => {
    const text = [
      `TL;DW: "${data.videoTitle}"`,
      `${data.channel}${data.totalDuration ? ` · ${data.totalDuration}` : ''}`,
      `\nThe Gist:\n${(data.gist || []).map((g, i) => {
        const h = typeof g === 'object' ? g.headline : g;
        const d = typeof g === 'object' ? g.detail   : '';
        return `${i + 1}. ${h}${d ? '\n   ' + d : ''}`;
      }).join('\n')}`,
      `\nThe Noise:\n${(data.noise || []).map(n => `- ${n.label}: ${n.time}`).join('\n')}`,
      `\nSignal/Noise: ${data.signalPercent}% signal${data.actualContent ? ` (${data.actualContent} actual / ${data.totalDuration} total)` : ''}`,
      `\n${data.verdict}`,
      data.noTranscript ? '\n[Note: summary based on title — transcript unavailable]' : '',
      `\n— via STFU (stfu.cc)`,
    ].join('\n');
    try { await navigator.clipboard.writeText(text); } catch {}
    setCopied(true);
    setTimeout(() => setCopied(false), 2200);
  };

  const sh = { fontFamily: "'Space Mono', monospace", fontSize: '10px', fontWeight: 700,
    letterSpacing: '0.14em', textTransform: 'uppercase', marginBottom: '14px' };

  return (
    <div style={{ maxWidth: '620px', margin: '30px auto 0', padding: '0 24px',
      animation: 'stfu-fadeInUp 0.55s cubic-bezier(0.4,0,0.2,1) both' }}>
      <div style={{ backgroundColor: T.card.bg, borderRadius: T.card.radius,
        border: T.card.border, boxShadow: T.card.shadow, overflow: 'hidden' }}>

        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '20px 24px 14px' }}>
          <span style={{ fontFamily: "'Space Mono', monospace", fontSize: '11px',
            fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: T.accent }}>TL;DW</span>
          <button onClick={handleCopy} style={{ fontFamily: "'Bricolage Grotesque', sans-serif",
            fontSize: '12px', fontWeight: 500, cursor: 'pointer', transition: 'all 0.2s ease',
            color: copied ? '#fff' : T.textMuted, background: copied ? T.accent : T.surface,
            border: `1px solid ${copied ? T.accent : T.border}`, borderRadius: '6px',
            padding: '5px 12px', display: 'flex', alignItems: 'center', gap: '5px' }}>
            {copied
              ? <><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>Copied</>
              : 'Copy All'}
          </button>
        </div>

        <div style={{ padding: '0 24px 18px' }}>
          <p style={{ fontSize: '14px', fontWeight: 500, color: T.textSoft, lineHeight: 1.55, textWrap: 'pretty' }}>
            "{data.videoTitle}"
          </p>
          <p style={{ fontSize: '12px', color: T.textMuted, marginTop: '4px', fontFamily: "'Space Mono', monospace" }}>
            {data.channel}{data.totalDuration ? ` · ${data.totalDuration}` : ''}
          </p>
          {data.noTranscript && (
            <span style={{ fontSize: '11px', color: T.textMuted, marginTop: '6px',
              fontStyle: 'italic', background: T.surface, padding: '4px 10px', borderRadius: '6px',
              display: 'inline-block' }}>no transcript — summary based on title</span>
          )}
        </div>

        <div style={{ height: '1px', background: T.border, margin: '0 24px' }}></div>

        <div style={{ padding: '20px 24px' }}>
          <h3 style={{ ...sh, color: T.text }}>The Gist</h3>
          <ol style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '20px' }}>
            {(data.gist || []).map((point, i) => {
              const headline = typeof point === 'object' ? point.headline : point;
              const detail   = typeof point === 'object' ? point.detail   : null;
              return (
                <li key={i} style={{ display: 'flex', gap: '14px' }}>
                  <span style={{ fontFamily: "'Space Mono', monospace", fontSize: '12px',
                    fontWeight: 700, color: T.accent, minWidth: '20px', paddingTop: '3px', flexShrink: 0 }}>{i + 1}.</span>
                  <div>
                    <p style={{ fontSize: '16px', fontWeight: 700, color: T.text, lineHeight: 1.4,
                      margin: '0 0 5px', textWrap: 'pretty' }}>{headline}</p>
                    {detail && <p style={{ fontSize: '14px', color: T.textSoft, lineHeight: 1.7,
                      margin: 0, textWrap: 'pretty' }}>{detail}</p>}
                  </div>
                </li>
              );
            })}
          </ol>
        </div>

        <div style={{ height: '1px', background: T.border, margin: '0 24px' }}></div>

        {data.noise && data.noise.length > 0 && (
          <div style={{ padding: '20px 24px' }}>
            <h3 style={{ ...sh, color: T.textMuted }}>The Noise</h3>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '9px' }}>
              {data.noise.map((item, i) => (
                <div key={i} style={{ display: 'flex', justifyContent: 'space-between',
                  alignItems: 'center', fontSize: '15px', color: T.textMuted }}>
                  <span>{item.label}</span>
                  <span style={{ fontFamily: "'Space Mono', monospace", fontSize: '13px',
                    marginLeft: '16px', flexShrink: 0 }}>{item.time}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        <div style={{ padding: '4px 24px 18px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
            <span style={{ fontFamily: "'Space Mono', monospace", fontSize: '10px', fontWeight: 700,
              letterSpacing: '0.14em', textTransform: 'uppercase', color: T.textMuted }}>Signal / Noise</span>
            <span style={{ fontFamily: "'Space Mono', monospace", fontSize: '11px', color: T.textMuted }}>
              {data.actualContent && data.totalDuration ? `${data.actualContent} signal · ${data.totalDuration} total` : ''}
            </span>
          </div>
          <div style={{ height: '6px', backgroundColor: T.surface, borderRadius: '3px', overflow: 'hidden', marginBottom: '8px' }}>
            <div style={{ height: '100%', width: `${barW}%`, backgroundColor: T.accent,
              borderRadius: '3px', transition: 'width 1s cubic-bezier(0.4,0,0.2,1)' }}></div>
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '11px', fontFamily: "'Space Mono', monospace" }}>
            <span style={{ color: T.textMuted }}>signal <span style={{ color: T.accent, fontWeight: 700 }}>{data.signalPercent}%</span></span>
            <span style={{ color: T.textMuted }}>noise <span style={{ color: T.text, fontWeight: 700 }}>{100 - (data.signalPercent || 0)}%</span></span>
          </div>
        </div>

        <div style={{ padding: '18px 24px', backgroundColor: T.surface, borderTop: `1px solid ${T.border}` }}>
          <p style={{ fontSize: '15px', fontWeight: 500, color: T.text, fontStyle: 'italic', lineHeight: 1.65, textWrap: 'pretty' }}>
            {data.verdict}
          </p>
        </div>
      </div>
    </div>
  );
}

function ErrorCard({ message }) {
  return (
    <div style={{ maxWidth: '620px', margin: '30px auto 0', padding: '0 24px', animation: 'stfu-fadeInUp 0.5s ease both' }}>
      <div style={{ backgroundColor: T.card.bg, borderRadius: T.card.radius, border: T.card.border, padding: '32px 24px', textAlign: 'center' }}>
        <p style={{ fontSize: '15px', fontWeight: 600, color: T.text, marginBottom: '8px' }}>couldn't analyze that video.</p>
        <p style={{ fontSize: '13px', color: T.textMuted, lineHeight: 1.6 }}>{message || 'Try a different URL or try again in a moment.'}</p>
      </div>
    </div>
  );
}

function ChromeCTA({ visible }) {
  if (!visible) return null;
  return (
    <div style={{ textAlign: 'center', margin: '44px auto 0', padding: '0 24px 44px', maxWidth: '520px', animation: 'stfu-fadeIn 0.5s ease 0.2s both' }}>
      <div style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', padding: '10px 18px', borderRadius: '100px',
          backgroundColor: T.pageBg, border: `1px solid ${T.border}`, cursor: 'default' }}>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={T.textMuted} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/>
          <line x1="21.17" y1="8" x2="12" y2="8"/><line x1="3.95" y1="6.06" x2="8.54" y2="14"/>
          <line x1="10.88" y1="21.94" x2="15.46" y2="14"/>
        </svg>
        <span style={{ fontSize: '12px', fontWeight: 500, color: T.textSoft }}>Chrome extension</span>
        <span style={{ fontSize: '10px', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase',
          color: '#fff', backgroundColor: T.accent, padding: '2px 8px', borderRadius: '100px' }}>Coming soon</span>
      </div>
    </div>
  );
}

/* ═══════════════════════════════════════════════
   APP
   ═══════════════════════════════════════════════ */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "model": "deepseek" }/*EDITMODE-END*/;

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const modelId = MODELS[tweaks.model]?.id || MODELS.scout.id;
  const [langMode, setLangMode] = useState('english');

  const [appState, setAppState] = useState('idle');
  const [loadKey, setLoadKey] = useState(0);
  const [loaderDone, setLoaderDone] = useState(false);
  const [apiResult, setApiResult] = useState(null);
  const [apiError, setApiError] = useState(null);
  const genRef = useRef(0);

  useEffect(() => {
    if (appState !== 'loading' || !loaderDone) return;
    if (apiResult) { setAppState('result'); return; }
    if (apiError) { setAppState('error'); }
  }, [loaderDone, apiResult, apiError, appState]);

  const handleSubmit = useCallback((url) => {
    const gen = ++genRef.current;
    setLoadKey(gen);
    setAppState('loading');
    setLoaderDone(false);
    setApiResult(null);
    setApiError(null);

    fetchAndSummarize(url, modelId, langMode)
      .then(data => { if (genRef.current === gen) setApiResult(data); })
      .catch(err => { if (genRef.current === gen) setApiError(err.message || 'Something went wrong'); });
  }, [modelId, langMode]);

  const handleLoaderDone = useCallback(() => setLoaderDone(true), []);
  const waitingForApi = loaderDone && appState === 'loading' && !apiResult && !apiError;
  const isCompact = appState !== 'idle';

  return (
    <div style={{ minHeight: '100vh', backgroundColor: T.pageBg, display: 'flex', flexDirection: 'column' }}>
      <TweaksPanel>
        <TweakSection label="AI Model" />
        <TweakRadio label="Model" value={tweaks.model} options={['scout', 'llama3', 'deepseek']}
          onChange={v => setTweak('model', v)} />
      </TweaksPanel>



      <div style={{ flex: 1, display: 'flex', flexDirection: 'column',
        justifyContent: isCompact ? 'flex-start' : 'center',
        paddingBottom: isCompact ? 0 : 100,
        transition: 'all 0.6s cubic-bezier(0.4,0,0.2,1)' }}>
        <Hero compact={isCompact} />
        <SearchBar onSubmit={handleSubmit} loading={appState === 'loading'} />
        <LangToggle value={langMode} onChange={setLangMode} />

        {appState === 'loading' && (
          <LoaderMessages key={loadKey} onComplete={handleLoaderDone} waitingForApi={waitingForApi} />
        )}

        {appState === 'result' && apiResult && <ResultCard data={apiResult} />}
        {appState === 'error' && <ErrorCard message={apiError} />}

        <ChromeCTA visible={appState !== 'loading'} />

        <div style={{ textAlign: 'center', padding: '16px 0 32px', marginTop: '8px' }}>
          <a href="privacy.html"
            style={{ fontSize: '11px', color: T.textMuted, textDecoration: 'none',
              fontFamily: "'Bricolage Grotesque', sans-serif", letterSpacing: '0.02em',
              transition: 'color 0.2s' }}
            onMouseEnter={e => e.currentTarget.style.color = T.text}
            onMouseLeave={e => e.currentTarget.style.color = T.textMuted}>
            Privacy Policy
          </a>
        </div>
      </div>
    </div>
  );
}

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