// Agent Speak, main app
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// Inline SVG icon set (lucide-style strokes)
const ICONS = {
  'check': <polyline points="20 6 9 17 4 12" />,
  'copy': <><rect x="9" y="9" width="13" height="13" rx="2" /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></>,
  'check-circle-2': <><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></>,
  'x-circle': <><circle cx="12" cy="12" r="10" /><path d="m15 9-6 6" /><path d="m9 9 6 6" /></>,
  'play': <polygon points="6 3 20 12 6 21 6 3" />,
  'sparkles': <><path d="M12 3l1.6 4.4L18 9l-4.4 1.6L12 15l-1.6-4.4L6 9l4.4-1.6z" /><path d="M19 14l.7 1.9L21.5 17l-1.8.7L19 19.5l-.7-1.8L16.5 17l1.8-.6z" /></>,
  'refresh': <><path d="M3 12a9 9 0 0 1 15-6.7L21 8" /><path d="M21 3v5h-5" /><path d="M21 12a9 9 0 0 1-15 6.7L3 16" /><path d="M3 21v-5h5" /></>,
  'pencil': <><path d="M12 20h9" /><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z" /></>,
  'alert-triangle': <><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /></>,
  'eye': <><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z" /><circle cx="12" cy="12" r="3" /></>,
  'loader': <><line x1="12" y1="2" x2="12" y2="6" /><line x1="12" y1="18" x2="12" y2="22" /><line x1="4.93" y1="4.93" x2="7.76" y2="7.76" /><line x1="16.24" y1="16.24" x2="19.07" y2="19.07" /><line x1="2" y1="12" x2="6" y2="12" /><line x1="18" y1="12" x2="22" y2="12" /><line x1="4.93" y1="19.07" x2="7.76" y2="16.24" /><line x1="16.24" y1="7.76" x2="19.07" y2="4.93" /></>,
  'link': <><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></>,
  'type': <><polyline points="4 7 4 4 20 4 20 7" /><line x1="9" y1="20" x2="15" y2="20" /><line x1="12" y1="4" x2="12" y2="20" /></>,
  'code': <><polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" /></>,
  'info': <><circle cx="12" cy="12" r="10" /><line x1="12" y1="16" x2="12" y2="12" /><line x1="12" y1="8" x2="12.01" y2="8" /></>,
  'file-text': <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /><polyline points="10 9 9 9 8 9" /></>,
  'shopping-bag': <><path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z" /><line x1="3" y1="6" x2="21" y2="6" /><path d="M16 10a4 4 0 0 1-8 0" /></>,
  'book-open': <><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" /></>,
  'clock': <><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></>,
  'download': <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></>,
  'image': <><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path d="m21 15-4.586-4.586a2 2 0 0 0-2.828 0L6 18" /></>,
  'x': <><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>,
  'search': <><circle cx="11" cy="11" r="7" /><line x1="21" y1="21" x2="16.65" y2="16.65" /></>,
  'arrow-down': <><line x1="12" y1="5" x2="12" y2="19" /><polyline points="19 12 12 19 5 12" /></>,
  'arrow-up': <><line x1="12" y1="19" x2="12" y2="5" /><polyline points="5 12 12 5 19 12" /></>,
  'arrow-right': <><line x1="5" y1="12" x2="19" y2="12" /><polyline points="12 5 19 12 12 19" /></>,
  'upload': <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="17 8 12 3 7 8" /><line x1="12" y1="3" x2="12" y2="15" /></>,
};

function Icon({ name, size = 14, className = '', strokeWidth = 2 }) {
  const path = ICONS[name];
  if (!path) return <span className={className} style={{ width: size, height: size, display: 'inline-block' }} />;
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width={size}
      height={size}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={strokeWidth}
      strokeLinecap="round"
      strokeLinejoin="round"
      className={`inline-block shrink-0 ${name === 'loader' ? 'animate-spin' : ''} ${className}`}
      aria-hidden="true"
    >
      {path}
    </svg>
  );
}

// CopyButton, confirms inline for 1.5s
function CopyButton({ text, label = 'Copy', className = '', variant = 'ghost', tiny = false }) {
  const [copied, setCopied] = useState(false);
  const onCopy = useCallback(() => {
    const t = typeof text === 'function' ? text() : text;
    if (navigator.clipboard) {
      navigator.clipboard.writeText(t);
    } else {
      const ta = document.createElement('textarea');
      ta.value = t;
      document.body.appendChild(ta);
      ta.select();
      document.execCommand('copy');
      ta.remove();
    }
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  }, [text]);
  const base =
    variant === 'solid'
      ? 'bg-slate-900 text-white hover:bg-slate-800'
      : variant === 'dark'
      ? 'bg-slate-800 text-slate-100 hover:bg-slate-700 border border-slate-700'
      : 'bg-white text-slate-700 hover:bg-slate-50 border border-slate-200';
  const sizing = tiny ? 'px-2 h-6 text-[11.5px]' : 'px-2.5 h-7 text-[12px]';
  return (
    <button
      onClick={(e) => { e.preventDefault(); onCopy(); }}
      aria-label={copied ? 'Copied' : label}
      className={`inline-flex items-center gap-1.5 ${sizing} rounded-md font-medium transition-colors duration-150 ${base} ${className}`}
    >
      <Icon name={copied ? 'check' : 'copy'} size={tiny ? 11 : 12} />
      <span>{copied ? 'Copied' : label}</span>
    </button>
  );
}

// Animated count-up for hero number
function CountUp({ to, duration = 600, className = '', suffix = '' }) {
  const [val, setVal] = useState(to);
  useEffect(() => {
    let raf;
    const start = performance.now();
    const tick = (t) => {
      const p = Math.min(1, (t - start) / duration);
      const eased = 1 - Math.pow(1 - p, 3); // ease-out cubic
      setVal(Math.round(to * eased));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    setVal(0);
    raf = requestAnimationFrame(tick);
    // Failsafe: rAF is throttled when the frame is backgrounded (e.g. during
    // screenshot capture), which can strand the number mid-animation. Guarantee
    // the final value lands regardless.
    const settle = setTimeout(() => setVal(to), duration + 80);
    return () => { cancelAnimationFrame(raf); clearTimeout(settle); };
  }, [to, duration]);
  return (
    <span className={className}>
      {val}
      {suffix}
    </span>
  );
}

// Severity color helpers
function severityFromRate(rate) {
  if (rate <= 20) return 'pass';
  if (rate <= 50) return 'warn';
  return 'fail';
}
const SEV_STYLES = {
  pass: { bg: 'bg-emerald-50', border: 'border-emerald-200', text: 'text-emerald-900', accent: 'text-emerald-700', dot: 'bg-emerald-500' },
  warn: { bg: 'bg-amber-50', border: 'border-amber-200', text: 'text-amber-900', accent: 'text-amber-700', dot: 'bg-amber-500' },
  fail: { bg: 'bg-rose-50', border: 'border-rose-200', text: 'text-rose-900', accent: 'text-rose-700', dot: 'bg-rose-500' },
};

// ---------- Brand: Logo mark ----------
// Two overlapping speech-bubble forms, a solid dark "Agent" bubble facing right
// and an iridescent "Speak" bubble facing left. Reads as agents in dialogue.
function LogoMark({ size = 28, className = '' }) {
  const id = `iridescent-${size}`;
  return (
    <svg
      viewBox="0 0 40 40"
      width={size}
      height={size}
      className={`logo-mark ${className}`}
      aria-label="Agent Speak"
      role="img"
    >
      <defs>
        <linearGradient id={id} x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stopColor="#5eead4" />
          <stop offset="35%" stopColor="#a78bfa" />
          <stop offset="70%" stopColor="#f472b6" />
          <stop offset="100%" stopColor="#fb923c" />
        </linearGradient>
      </defs>
      {/* Back bubble, dark, points down-right */}
      <path
        d="M5 10 a6 6 0 0 1 6 -6 h12 a6 6 0 0 1 6 6 v8 a6 6 0 0 1 -6 6 h-9 l-5 4 v-4 h2 a6 6 0 0 1 -6 -6 z"
        fill="#0b1120"
      />
      {/* Front bubble, iridescent, points down-left, overlaps */}
      <path
        d="M14 18 a6 6 0 0 1 6 -6 h12 a6 6 0 0 1 6 6 v8 a6 6 0 0 1 -6 6 h-2 v4 l-5 -4 h-5 a6 6 0 0 1 -6 -6 z"
        fill={`url(#${id})`}
      />
    </svg>
  );
}

// ---------- Header (branded, dark hero band) ----------
function Header() {
  return (
    <header className="hero-stripes hero-grain text-white relative overflow-hidden">
      {/* nav row */}
      <div className="relative max-w-[1100px] mx-auto px-6 h-14 flex items-center justify-between">
        <div className="flex items-center gap-2.5">
          <LogoMark size={26} />
          <span className="text-[15px] font-semibold tracking-tight">
            Agent <span className="serif italic font-normal" style={{ fontSize: '17px' }}>Speak</span>
          </span>
        </div>
        <div className="flex items-center gap-2">
          <a
            href="blog/index.html"
            className="text-[12px] text-white/70 hover:text-white px-2.5 h-7 rounded-md inline-flex items-center gap-1.5 transition-colors"
          >
            <Icon name="file-text" size={12} /> Blog
          </a>
          <a
            href="#how"
            className="text-[12px] text-white/70 hover:text-white px-2.5 h-7 rounded-md inline-flex items-center gap-1.5 transition-colors"
          >
            <Icon name="book-open" size={12} /> How it works
          </a>
        </div>
      </div>

      {/* Hero statement */}
      <div className="relative max-w-[1100px] mx-auto px-6 pt-12 pb-16">
        <div className="max-w-3xl">
          <div className="text-[11px] uppercase tracking-[0.22em] text-white/55 mb-6">
            Free AI visibility check
          </div>
          <h1
            className="display font-semibold text-white"
            style={{ fontSize: 'clamp(36px, 5.4vw, 64px)', lineHeight: 1.08, letterSpacing: '-0.025em', textWrap: 'balance' }}
          >
            Make sure your content is visible{' '}
            <span className="serif italic font-normal iridescent-text">when AI agents are <span style={{ whiteSpace: 'nowrap' }}>looking.</span></span>
          </h1>
          <p className="mt-7 text-[16px] text-white/70 max-w-xl leading-relaxed">
            Your next customer asks an AI agent before they ask you. If your content isn’t quotable, the agent recommends someone else. Paste a page, product description, or doc, and see the questions yours can’t answer yet.
          </p>
        </div>
      </div>

      {/* bottom seam, soft fade into light app */}
      <div className="absolute left-0 right-0 bottom-0 h-12 pointer-events-none" style={{ background: 'linear-gradient(to bottom, rgba(248,250,252,0), #f8fafc)' }} />
    </header>
  );
}

// ---------- Input panel ----------
function InputPanel({ onRun, running }) {
  const [mode, setMode] = useState('paste'); // 'paste' | 'screenshot' | 'upload'
  const [preset, setPreset] = useState('Generic');
  const [value, setValue] = useState('');
  const [shotPreview, setShotPreview] = useState(null); // dataURL
  const [shotStatus, setShotStatus] = useState('idle'); // idle | extracting | done | error
  const [shotError, setShotError] = useState('');
  const [fileName, setFileName] = useState('');
  const [uploadIsHtml, setUploadIsHtml] = useState(false);
  const [uploadError, setUploadError] = useState('');
  const [sampleText, setSampleText] = useState(null);
  const fileRef = useRef(null);
  const docRef = useRef(null);

  const loadSample = (key, run = false) => {
    const s = window.SAMPLES[key];
    setMode('paste');
    setPreset(s.preset);
    setValue(s.text);
    setSampleText(s.text);
    setShotPreview(null);
    setShotStatus('idle');
    setFileName('');
    setUploadIsHtml(false);
    setUploadError('');
    // One-click: a sample should show value immediately, not make the user
    // hunt for the run button. Samples are free (canned + cached).
    if (run) onRun({ mode: 'paste', preset: s.preset, raw: s.text, canned: true });
  };

  useEffect(() => {
    const onEvt = (e) => {
      const d = e.detail || {};
      if (typeof d === 'string') loadSample(d);
      else loadSample(d.key, !!d.run);
    };
    window.addEventListener('aca:load-sample', onEvt);
    return () => window.removeEventListener('aca:load-sample', onEvt);
  }, [onRun]);

  const handleFile = async (file) => {
    if (!file) return;
    if (!file.type.startsWith('image/')) {
      setShotError('That file isn\'t an image. Try a PNG or JPG screenshot.');
      setShotStatus('error');
      return;
    }
    const reader = new FileReader();
    reader.onload = async (e) => {
      const dataUrl = e.target.result;
      setShotPreview(dataUrl);
      setShotStatus('extracting');
      setShotError('');
      try {
        const extracted = await window.claude.complete({
          messages: [
            {
              role: 'user',
              content: [
                {
                  type: 'image',
                  source: { type: 'base64', media_type: file.type, data: dataUrl.split(',')[1] },
                },
                {
                  type: 'text',
                  text:
                    'This is a screenshot of a web page. Extract every piece of visible text: headings, body copy, prices, CTAs, navigation labels, footer, exactly as written. Preserve order. Use line breaks to separate sections. Output only the text. No commentary, no markdown.',
                },
              ],
            },
          ],
        });
        const cleaned = String(extracted || '').trim();
        if (cleaned.length < 30) {
          setShotError('Couldn\'t read enough text from that screenshot. Try a higher-resolution capture.');
          setShotStatus('error');
          return;
        }
        setValue(cleaned);
        setShotStatus('done');
      } catch (err) {
        setShotError('Extraction failed. Try again or paste the text directly.');
        setShotStatus('error');
      }
    };
    reader.readAsDataURL(file);
  };

  // Upload content: read a local HTML / text / Markdown file as plain text.
  const handleDoc = (file) => {
    if (!file) return;
    const okExt = /\.(html?|txt|md|markdown)$/i.test(file.name);
    if (!okExt && !file.type.startsWith('text')) {
      setUploadError('Upload an HTML, text, or Markdown file, or switch to “Write your copy.”');
      return;
    }
    const reader = new FileReader();
    reader.onload = (e) => {
      const text = String(e.target.result || '');
      if (text.trim().length < 30) {
        setUploadError('That file has almost no readable text. Try another.');
        return;
      }
      const isHtml = /\.html?$/i.test(file.name) || /<[a-z][\s\S]*>/i.test(text);
      setValue(text);
      setUploadIsHtml(isHtml);
      setFileName(file.name);
      setUploadError('');
    };
    reader.readAsText(file);
  };

  const handlePaste = (e) => {
    if (mode !== 'screenshot') return;
    const items = e.clipboardData?.items || [];
    for (const it of items) {
      if (it.type.startsWith('image/')) {
        const f = it.getAsFile();
        if (f) {
          e.preventDefault();
          handleFile(f);
          return;
        }
      }
    }
  };

  const canRun = value.trim().length > 30;

  const handleRun = () => {
    if (!canRun) return;
    const engineMode = mode === 'upload' && uploadIsHtml ? 'html' : 'paste';
    const isSample = sampleText !== null && value.trim() === sampleText.trim();
    onRun({ mode: engineMode, preset, raw: value, canned: isSample });
  };

  return (
    <div>
      <section className="bg-white border border-slate-200 rounded-xl shadow-[0_24px_64px_-24px_rgba(15,23,42,0.45),0_2px_4px_-1px_rgba(15,23,42,0.06)]">
        {/* Three ways in */}
        <div className="px-6 pt-5">
          <div className="flex items-center gap-1 border-b border-slate-200 overflow-x-auto">
            {[
              { id: 'paste', label: 'Write your copy', icon: 'type' },
              { id: 'screenshot', label: 'Paste a screenshot', icon: 'image' },
              { id: 'upload', label: 'Upload content', icon: 'upload' },
            ].map((t) => (
              <button
                key={t.id}
                onClick={() => setMode(t.id)}
                className={`relative inline-flex items-center gap-1.5 px-3 pb-2.5 pt-1 text-[13px] font-medium transition-colors whitespace-nowrap ${
                  mode === t.id ? 'text-slate-900' : 'text-slate-500 hover:text-slate-700'
                }`}
              >
                <Icon name={t.icon} size={13} /> {t.label}
                {mode === t.id && <span className="absolute left-0 right-0 -bottom-px h-px bg-slate-900" />}
              </button>
            ))}
          </div>
        </div>

      {/* Body */}
      <div className="px-6 pt-4 pb-5" onPaste={handlePaste}>
        {mode === 'screenshot' ? (
          <div>
            <label className="flex items-center justify-between mb-1.5">
              <span className="text-[12px] font-medium text-slate-500">Screenshot</span>
              {shotStatus === 'done' && (
                <span className="text-[12px] text-emerald-700 inline-flex items-center gap-1">
                  <Icon name="check" size={12} /> Read {value.length.toLocaleString()} characters
                </span>
              )}
              {shotStatus === 'extracting' && (
                <span className="text-[12px] text-slate-500 inline-flex items-center gap-1">
                  <Icon name="loader" size={12} /> Reading text from image…
                </span>
              )}
            </label>

            {!shotPreview ? (
              <div
                onClick={() => fileRef.current?.click()}
                onDragOver={(e) => e.preventDefault()}
                onDrop={(e) => {
                  e.preventDefault();
                  handleFile(e.dataTransfer.files?.[0]);
                }}
                className="border-2 border-dashed border-slate-200 hover:border-slate-300 hover:bg-slate-50 rounded-md py-10 px-6 cursor-pointer transition-colors text-center"
                role="button"
                aria-label="Upload screenshot"
                tabIndex={0}
              >
                <div className="inline-flex items-center justify-center w-9 h-9 rounded-md bg-slate-100 text-slate-600 mb-3">
                  <Icon name="image" size={18} />
                </div>
                <div className="text-[14px] font-medium text-slate-900 mb-1">Drop a screenshot, or click to browse</div>
                <div className="text-[12px] text-slate-500">
                  You can also paste an image (⌘V) directly here. PNG or JPG, up to ~5 MB.
                </div>
              </div>
            ) : (
              <div className="border border-slate-200 rounded-md overflow-hidden">
                <div className="bg-slate-50 border-b border-slate-200 px-3 py-2 flex items-center justify-between">
                  <span className="text-[12px] text-slate-600 inline-flex items-center gap-1.5">
                    <Icon name="image" size={12} /> Preview
                  </span>
                  <button
                    onClick={() => {
                      setShotPreview(null);
                      setValue('');
                      setShotStatus('idle');
                      setShotError('');
                    }}
                    className="text-[12px] text-slate-500 hover:text-slate-900 inline-flex items-center gap-1"
                  >
                    <Icon name="x" size={12} /> Replace
                  </button>
                </div>
                <div className="grid grid-cols-1 md:grid-cols-2">
                  <div className="bg-slate-100 p-3 max-h-[260px] overflow-auto">
                    <img src={shotPreview} alt="screenshot preview" className="w-full rounded border border-slate-200" />
                  </div>
                  <div className="border-l border-slate-200 p-3 max-h-[260px] overflow-auto">
                    {shotStatus === 'extracting' ? (
                      <div className="text-[12px] text-slate-500 space-y-2">
                        <div className="h-2 bg-slate-100 rounded animate-pulse" />
                        <div className="h-2 bg-slate-100 rounded animate-pulse w-5/6" />
                        <div className="h-2 bg-slate-100 rounded animate-pulse w-4/6" />
                        <div className="mt-3">Reading your content…</div>
                      </div>
                    ) : (
                      <textarea
                        value={value}
                        onChange={(e) => setValue(e.target.value)}
                        rows={10}
                        className="w-full text-[12px] leading-relaxed text-slate-700 bg-transparent resize-none outline-none"
                        placeholder="Extracted text will appear here. Edit if anything looks off."
                      />
                    )}
                  </div>
                </div>
              </div>
            )}

            <input
              ref={fileRef}
              type="file"
              accept="image/*"
              className="hidden"
              onChange={(e) => handleFile(e.target.files?.[0])}
            />
            {shotError && (
              <div className="mt-2 text-[12px] text-rose-700">{shotError}</div>
            )}
          </div>
        ) : mode === 'upload' ? (
          <div>
            {!fileName ? (
              <div
                onClick={() => docRef.current?.click()}
                onDragOver={(e) => e.preventDefault()}
                onDrop={(e) => {
                  e.preventDefault();
                  handleDoc(e.dataTransfer.files?.[0]);
                }}
                className="border-2 border-dashed border-slate-200 hover:border-slate-300 hover:bg-slate-50 rounded-md py-10 px-6 cursor-pointer transition-colors text-center"
                role="button"
                aria-label="Upload a content file"
                tabIndex={0}
              >
                <div className="inline-flex items-center justify-center w-9 h-9 rounded-md bg-slate-100 text-slate-600 mb-3">
                  <Icon name="upload" size={18} />
                </div>
                <div className="text-[14px] font-medium text-slate-900 mb-1">Drop a file, or click to browse</div>
                <div className="text-[12px] text-slate-500">
                  HTML, text, or Markdown. We pull the readable copy and ignore the markup.
                </div>
              </div>
            ) : (
              <div className="border border-slate-200 rounded-md overflow-hidden">
                <div className="bg-slate-50 border-b border-slate-200 px-3 py-2 flex items-center justify-between">
                  <span className="text-[12px] text-slate-600 inline-flex items-center gap-1.5 min-w-0">
                    <Icon name="file-text" size={12} />
                    <span className="truncate">{fileName}</span>
                    {uploadIsHtml && <span className="text-slate-400 shrink-0">· HTML</span>}
                  </span>
                  <button
                    onClick={() => {
                      setFileName('');
                      setValue('');
                      setUploadIsHtml(false);
                      setUploadError('');
                    }}
                    className="text-[12px] text-slate-500 hover:text-slate-900 inline-flex items-center gap-1 shrink-0"
                  >
                    <Icon name="x" size={12} /> Replace
                  </button>
                </div>
                <textarea
                  value={value}
                  onChange={(e) => setValue(e.target.value)}
                  rows={10}
                  className="w-full p-3 text-[13px] leading-relaxed text-slate-700 bg-white resize-y outline-none"
                  placeholder="The file's text appears here. Edit if anything looks off."
                />
              </div>
            )}
            <input
              ref={docRef}
              type="file"
              accept=".html,.htm,.txt,.md,.markdown,text/html,text/plain,text/markdown"
              className="hidden"
              onChange={(e) => handleDoc(e.target.files?.[0])}
            />
            {uploadError && <div className="mt-2 text-[12px] text-rose-700">{uploadError}</div>}
          </div>
        ) : (
          <div>
            <label className="flex items-center justify-between mb-1.5">
              <span className="text-[12px] font-medium text-slate-500">Your content</span>
              <span className="text-[12px] text-slate-400">{value.length.toLocaleString()} chars</span>
            </label>
            <textarea
              value={value}
              onChange={(e) => setValue(e.target.value)}
              placeholder={"Paste everything a visitor would read. For example:\n\nHeadline: what this content is\nBody: what you do, the details that matter\nPricing or plan names, with numbers\nButton labels (Start free trial, Book a call…)\nFooter: contact, hours, policies"}
              rows={10}
              className="w-full p-3 rounded-md border border-slate-200 bg-white text-[13px] leading-relaxed text-slate-900 placeholder:text-slate-400 ring-accent resize-y"
            />
          </div>
        )}
      </div>

        {/* Footer: run + reassurance. No category picker — we read the page itself. */}
        <div className="px-6 pb-5 flex flex-col-reverse sm:flex-row sm:items-center gap-3 sm:justify-between border-t border-slate-100 pt-4">
          <div className="flex items-center gap-1.5 text-[11.5px] text-slate-400">
            <Icon name="sparkles" size={12} />
            <span>We read your content and ask only the questions it should answer. ~5 sec.</span>
          </div>
          <button
            onClick={handleRun}
            disabled={!canRun || running}
            className={`inline-flex items-center gap-2 px-4 h-10 rounded-lg text-[13px] font-medium transition-colors shrink-0 ${
              canRun && !running ? 'bg-slate-900 text-white hover:bg-slate-800' : 'bg-slate-100 text-slate-400 cursor-not-allowed'
            }`}
          >
            {running ? (
              <>
                <Icon name="loader" size={13} /> Reading your content…
              </>
            ) : (
              <>
                <Icon name="play" size={13} /> See what AI can’t answer
              </>
            )}
          </button>
        </div>
      </section>

      {/* New here? One quiet line of samples — discoverable, not competing with the form. */}
      <div className="mt-3 flex flex-wrap items-center justify-center gap-x-2.5 gap-y-1 text-[12px] text-slate-400">
        <span>New here? Try a sample:</span>
        <button onClick={() => loadSample('saas')} className="font-medium text-slate-500 hover:text-slate-800 underline decoration-slate-300 underline-offset-2 transition-colors">SaaS pricing</button>
        <span className="text-slate-300">·</span>
        <button onClick={() => loadSample('ecom')} className="font-medium text-slate-500 hover:text-slate-800 underline decoration-slate-300 underline-offset-2 transition-colors">Ecommerce returns</button>
        <span className="text-slate-300">·</span>
        <button onClick={() => loadSample('ux')} className="font-medium text-slate-500 hover:text-slate-800 underline decoration-slate-300 underline-offset-2 transition-colors">Product UX strings</button>
      </div>
    </div>
  );
}

// ---------- Loading state ----------
function LoadingState({ onCancel }) {
  // Honest phases that match what the model is actually doing — reading the
  // page, then generating the questions a real visitor would ask. We advance
  // through them and hold on the last until the result lands.
  const phases = [
    'Reading your content',
    'Working out what it’s for',
    'Picturing a real visitor',
    'Drafting the questions they’d ask',
    'Checking what your copy answers',
    'Writing the copy to close the gaps',
  ];
  const [idx, setIdx] = useState(0);
  useEffect(() => {
    const i = setInterval(() => setIdx((x) => Math.min(x + 1, phases.length - 1)), 850);
    return () => clearInterval(i);
  }, []);
  const pct = Math.round(((idx + 1) / phases.length) * 92);
  return (
    <section className="hero-stripes hero-grain text-white rounded-xl px-8 py-10 fade-in relative overflow-hidden">
      <div className="relative flex items-center gap-3 mb-5">
        <span className="relative flex w-2 h-2">
          <span className="absolute inset-0 rounded-full opacity-75 animate-ping iridescent" />
          <span className="relative inline-flex w-2 h-2 rounded-full iridescent" />
        </span>
        <span className="text-[11px] font-medium uppercase tracking-[0.22em] text-white/55">Reading your content</span>
      </div>
      <div className="relative text-[20px] text-white leading-snug font-medium min-h-[28px]">
        {phases[idx]}<span className="text-white/40">…</span>
      </div>
      <div className="relative mt-2 text-[13px] text-white/45">
        No checklist. We ask only what this content should answer.
      </div>
      <div className="relative mt-7 h-px w-full bg-white/10 overflow-hidden">
        <div
          className="h-full iridescent transition-all duration-700 ease-out"
          style={{ width: `${pct}%` }}
        />
      </div>
      {onCancel && (
        <button
          onClick={onCancel}
          className="relative mt-5 inline-flex items-center gap-1.5 text-[12.5px] text-white/55 hover:text-white/90 transition-colors"
        >
          <Icon name="x" size={13} /> Cancel
        </button>
      )}
    </section>
  );
}

// ---------- Page read: what the AI understood, + correction + re-check ----------
// The whole pitch is "we read your page." This makes that read visible, lets
// the user correct it if it's wrong, and gives them a one-click re-check after
// they've edited their copy. When the deep read failed, it says so plainly
// instead of pretending the generic fallback is page-aware.
function PageRead({ analysis, onCorrect, onRecheck }) {
  const [editing, setEditing] = useState(false);
  const [hint, setHint] = useState('');

  // Degraded path: the model wasn't available or didn't return usable JSON.
  if (!analysis.pageAware) {
    return (
      <section className="rounded-xl border border-amber-200 bg-amber-50 px-5 py-4 flex flex-col sm:flex-row sm:items-center gap-3 sm:justify-between">
        <div className="flex items-start gap-2.5">
          <span className="text-amber-600 mt-0.5 shrink-0"><Icon name="alert-triangle" size={16} /></span>
          <p className="text-[13.5px] text-amber-900 leading-snug" style={{ textWrap: 'pretty' }}>
            <span className="font-semibold">This is a generic check.</span> We couldn’t read your content in depth this time, so these questions aren’t tailored to it. Re-run for content-specific results.
          </p>
        </div>
        <button
          onClick={onRecheck}
          className="shrink-0 inline-flex items-center gap-1.5 px-3 h-9 rounded-md bg-amber-600 text-white text-[13px] font-medium hover:bg-amber-700 transition-colors"
        >
          <Icon name="refresh" size={13} /> Try again
        </button>
      </section>
    );
  }

  return (
    <section className="rounded-xl border border-slate-200 bg-white px-5 py-4">
      <div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:justify-between">
        <div className="flex items-start gap-2.5 min-w-0">
          <span className="text-teal-600 mt-0.5 shrink-0"><Icon name="eye" size={16} /></span>
          <p className="text-[13.5px] text-slate-600 leading-snug" style={{ textWrap: 'pretty' }}>
            We read this as{' '}
            <span className="font-semibold text-slate-900">{analysis.pageType || 'this content'}</span>
            {analysis.audience ? <>, written for a <span className="font-semibold text-slate-900">{analysis.audience}</span></> : null}.
            {' '}The questions below are the ones that content should answer.
          </p>
        </div>
        <div className="flex items-center gap-2 shrink-0">
          {!editing && (
            <button
              onClick={() => setEditing(true)}
              className="inline-flex items-center gap-1.5 px-2.5 h-8 rounded-md border border-slate-200 text-[12.5px] font-medium text-slate-600 hover:text-slate-900 hover:border-slate-300 transition-colors"
            >
              <Icon name="pencil" size={12} /> Not quite?
            </button>
          )}
          <button
            onClick={onRecheck}
            title="Re-run on the same text. Use it after you edit your content"
            className="inline-flex items-center gap-1.5 px-2.5 h-8 rounded-md border border-slate-200 text-[12.5px] font-medium text-slate-600 hover:text-slate-900 hover:border-slate-300 transition-colors"
          >
            <Icon name="refresh" size={12} /> Re-check
          </button>
        </div>
      </div>

      {editing && (
        <form
          onSubmit={(e) => { e.preventDefault(); const h = hint.trim(); if (h) onCorrect(h); }}
          className="mt-3 pt-3 border-t border-slate-100 flex flex-col sm:flex-row gap-2 sm:items-center"
        >
          <label className="text-[12.5px] text-slate-500 shrink-0">This content is actually a…</label>
          <input
            autoFocus
            value={hint}
            onChange={(e) => setHint(e.target.value)}
            placeholder="e.g. product description, help article, job posting"
            className="flex-1 h-9 px-3 rounded-md border border-slate-200 bg-white text-[13px] text-slate-900 placeholder:text-slate-400 ring-accent"
          />
          <div className="flex items-center gap-2 shrink-0">
            <button
              type="submit"
              disabled={!hint.trim()}
              className={`inline-flex items-center gap-1.5 px-3 h-9 rounded-md text-[13px] font-medium transition-colors ${
                hint.trim() ? 'bg-slate-900 text-white hover:bg-slate-800' : 'bg-slate-100 text-slate-400 cursor-not-allowed'
              }`}
            >
              <Icon name="refresh" size={12} /> Re-read
            </button>
            <button
              type="button"
              onClick={() => { setEditing(false); setHint(''); }}
              className="inline-flex items-center justify-center w-9 h-9 rounded-md text-slate-400 hover:text-slate-700 hover:bg-slate-50 transition-colors"
              aria-label="Cancel"
            >
              <Icon name="x" size={14} />
            </button>
          </div>
        </form>
      )}
    </section>
  );
}

// ---------- The Agent-Ready Framework, in code ----------
// This is the expertise the tool is built on, made visible. Each gap is
// explained by a principle from the framework (why agents behave this way),
// and linked to the article that goes deeper. Curated and consistent — not
// improvised per run — so the standard reads as authored, not generated.
const BLOG = 'blog/';
const AGENT_PRINCIPLES = [
  { test: /(price|cost|pricing|plan|billing|fee|rate)/i,
    why: 'Agents quote prices verbatim. With no number on the page, an assistant says “I’m not sure,” then recommends a competitor who stated theirs.',
    href: BLOG + 'optimize-product-pages-for-ai-search.html', label: 'Optimizing for AI search' },
  { test: /(trust|proof|review|testimonial|secur|safe|privacy|complian|certif)/i,
    why: 'Agents won’t vouch for a vague claim. “Trusted by thousands” isn’t quotable; a named number or certification is.',
    href: BLOG + 'hidden-questions-before-buying.html', label: 'The questions buyers never say' },
  { test: /(trial|refund|cancel|guarantee|return|exchange|money-back)/i,
    why: 'The exit question decides the purchase. Agents relay clear policies; ambiguity reads as risk and stalls the buyer.',
    href: BLOG + 'hidden-questions-before-buying.html', label: 'The questions buyers never say' },
  { test: /(feature|scope|include|integrat|capab|spec)/i,
    why: 'A feature only counts if it’s framed as an answer to a need. Agents skip capability lists that don’t map to a real question.',
    href: BLOG + 'product-content-answer-customer-questions.html', label: 'Make content answer real questions' },
  { test: /(support|sla|response|help|service)/i,
    why: 'Agents quote specifics. “Priority support” with no response time isn’t an answer. It’s an adjective.',
    href: BLOG + 'clear-content-beats-clever.html', label: 'Clear beats clever' },
  { test: /(how|process|start|setup|onboard|sign\s?up|join|get started|step)/i,
    why: 'Buyers ask “how do I start” before they commit. A vague next step stalls the buyer and gives the agent nothing to relay.',
    href: BLOG + '12-questions-your-page-isnt-answering.html', label: 'The 12 questions you’re missing' },
  { test: /(compar|differ|vs|alternative|competitor)/i,
    why: 'Buyers ask agents “how is X different from Y” constantly. If your page doesn’t answer it, a competitor’s page does.',
    href: BLOG + 'best-page-types-for-aeo.html', label: 'Which pages to optimize first' },
];
const DEFAULT_PRINCIPLE = {
  why: 'Agents quote sentences they can verify. State this as a specific fact and an assistant can lift it word-for-word into an answer.',
  href: BLOG + 'how-to-get-quoted-by-ai.html', label: 'How to get quoted by AI',
};
function principleFor(intent, q) {
  const hay = `${intent || ''} ${q || ''}`;
  return AGENT_PRINCIPLES.find((p) => p.test.test(hay)) || DEFAULT_PRINCIPLE;
}

// ---------- Hero result ----------
function HeroResult({ analysis, delta }) {
  const sev = severityFromRate(analysis.failRate);
  const styles = SEV_STYLES[sev];
  const aud = analysis.audience || 'reader';
  const audPlural = analysis.audiencePlural || 'readers';
  // Echo the detected content type in the headline when we have it.
  const contentNoun = (analysis.pageType || '').replace(/^(a|an)\s+/i, '').trim() || 'content';
  const sentence =
    sev === 'fail'
      ? `An agent quoting this content would say "I'm not sure" more than half the time. The fixes below are mostly one sentence each.`
      : sev === 'warn'
      ? `Mixed signal. An agent can answer the easy stuff and stalls on the rest. Fix the silent rows first.`
      : `Solid. An agent can quote this content on most of what ${audPlural} ask. Tighten the few stragglers below.`;

  return (
    <section className={`rounded-xl border ${styles.border} ${styles.bg} px-6 sm:px-8 py-8 sm:py-10 fade-in`} style={{ boxShadow: '0 1px 0 rgba(15,23,42,0.04)' }}>
      <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 mb-5">
        <div className="flex items-center gap-2">
          <span className={`w-1.5 h-1.5 rounded-full ${styles.dot}`} />
          <span className={`text-[12px] font-medium uppercase tracking-wider ${styles.accent}`}>Result</span>
        </div>
        <span className={`hidden sm:inline ${styles.text} opacity-20`}>·</span>
        <span className={`text-[12px] ${styles.text} opacity-60`}>
          Measured against the <span className="font-semibold opacity-100">Agent-Ready Framework</span>: 10 signals agents weigh when choosing what to quote. By Michelle Savage.
        </span>
      </div>
      <div
        className={`display font-semibold ${styles.text} flex items-baseline gap-3 flex-wrap`}
        role="img"
        aria-label={`${analysis.failed} of ${analysis.results.length} ${aud} questions went unanswered. Lower is better.`}
      >
        <span style={{ fontSize: 'clamp(44px, 12vw, 56px)' }}>
          <CountUp to={analysis.failed} duration={600} />
          <span className={`${styles.text} opacity-60`}> of </span>
          <span>{analysis.results.length}</span>
        </span>
        <span className={`text-[13px] font-medium uppercase tracking-[0.14em] ${styles.accent} opacity-80`}>
          unanswered · lower is better
        </span>
      </div>
      <div className={`mt-3 text-[17px] sm:text-[18px] font-medium heading ${styles.text}`} style={{ textWrap: 'pretty' }}>
        {aud} question{analysis.results.length === 1 ? '' : 's'} this {contentNoun} didn’t answer.
      </div>
      <div className={`mt-1 text-[14px] ${styles.accent}`}>
        <CountUp to={analysis.failRate} duration={600} suffix="%" /> silent · {analysis.passed} answered cleanly
      </div>
      {delta && (
        (() => {
          const diff = delta.prevFailed - analysis.failed;
          const better = diff > 0;
          const same = diff === 0;
          return (
            <div className={`mt-4 inline-flex items-center gap-2 px-3 h-8 rounded-full text-[12.5px] font-medium ${
              same ? 'bg-slate-100 text-slate-600' : better ? 'bg-teal-50 text-teal-700 border border-teal-200' : 'bg-rose-50 text-rose-700 border border-rose-200'
            }`}>
              <Icon name={same ? 'refresh' : better ? 'arrow-down' : 'alert-triangle'} size={13} />
              {same
                ? `No change since last run · still ${analysis.failed} of ${delta.prevTotal}`
                : `${better ? 'Down' : 'Up'} from ${delta.prevFailed} to ${analysis.failed} unanswered${better ? ` · ${diff} gap${diff === 1 ? '' : 's'} closed` : ''}`}
            </div>
          );
        })()
      )}
      {analysis.headline && (
        <div className={`mt-6 max-w-2xl border-l-2 ${styles.border} pl-4`}>
          <div className={`text-[11px] uppercase tracking-[0.18em] ${styles.accent} mb-1.5 opacity-80`}>The headline</div>
          <p className={`text-[16px] heading ${styles.text} leading-snug`} style={{ textWrap: 'pretty' }}>{analysis.headline}</p>
        </div>
      )}
      <div className="mt-5">
        <p className={`text-[13.5px] max-w-xl ${styles.text} opacity-80 leading-relaxed`}>{sentence}</p>
      </div>
      <p className={`mt-4 text-[12px] ${styles.text} opacity-50 leading-relaxed`} style={{ textWrap: 'pretty' }}>
        This is a read of your content, not a fixed score. Run it twice and the count may shift by one or two.
      </p>
    </section>
  );
}

// ---------- Translation: the screenshot moment ----------
// What you wrote, beside what an agent actually keeps. The gap between
// them is the whole pitch. Sits right under the hero number.
function TranslationBox({ analysis }) {
  const t = analysis.translation;
  if (!t || !t.original_excerpt) return null;
  return (
    <section className="bg-white border border-slate-200 rounded-xl overflow-hidden">
      <div className="px-6 pt-5 pb-4 border-b border-slate-100">
        <div className="text-[11px] uppercase tracking-[0.18em] text-slate-400 font-medium mb-1.5">The translation</div>
        <h2 className="text-[15px] font-semibold text-slate-900 heading">
          What you wrote, and what an agent keeps.
        </h2>
      </div>
      <div className="grid grid-cols-1 md:grid-cols-2">
        <div className="p-6 md:border-r border-slate-100">
          <div className="text-[11px] uppercase tracking-[0.16em] text-slate-400 font-medium mb-2.5">What you wrote</div>
          <blockquote className="serif text-slate-800 leading-snug" style={{ fontSize: '20px', textWrap: 'pretty' }}>
            “{t.original_excerpt}”
          </blockquote>
        </div>
        <div className="p-6 bg-slate-50/60 border-t md:border-t-0 border-slate-100">
          <div className="text-[11px] uppercase tracking-[0.16em] text-teal-700 font-medium mb-2.5">What an agent keeps</div>
          {t.ai_quote ? (
            <blockquote className="serif text-slate-900 leading-snug" style={{ fontSize: '20px', textWrap: 'pretty' }}>
              “{t.ai_quote}”
            </blockquote>
          ) : (
            <div className="flex items-start gap-2.5">
              <span className="text-rose-500 mt-0.5 shrink-0"><Icon name="x-circle" size={18} /></span>
              <p className="text-[15px] text-rose-700 leading-relaxed font-medium" style={{ textWrap: 'pretty' }}>
                Nothing. Strip the voice and no quotable fact is left, so an agent moves on.
              </p>
            </div>
          )}
        </div>
      </div>
      <div className="px-6 py-4 border-t border-slate-100 flex items-start gap-2.5 bg-white">
        <span className="text-amber-500 mt-0.5 shrink-0"><Icon name="info" size={15} /></span>
        <p className="text-[13.5px] text-slate-600 leading-relaxed" style={{ textWrap: 'pretty' }}>
          <span className="font-medium text-slate-800">What got lost:</span> {t.what_got_lost}
        </p>
      </div>
    </section>
  );
}

// ---------- Send to your team: the shareable card ----------
// Dark, screenshot-ready. One stat, one headline, one copy-paste summary.
function ShareCard({ analysis, meta }) {
  const total = analysis.results.length;
  const answered = analysis.passed;
  const SITE = 'agentspeak.ai';
  const summary =
    `I ran our content through Agent Speak. An AI agent can answer ${answered} of ${total} questions a ${analysis.audience || 'customer'} would ask about us. ` +
    `The biggest gap: ${analysis.headline}\n\nRun your own free → ${SITE}`;

  const focusInput = () => {
    const ta = document.querySelector('textarea');
    window.scrollTo({ top: 0, behavior: 'smooth' });
    if (ta) setTimeout(() => ta.focus(), 400);
  };

  return (
    <section>
      <SectionHeading title="Send this to your team" subtitle="One screenshot says it all. The card carries the link, so anyone who sees it can run their own." />
      <div className="rounded-xl overflow-hidden mt-4">
        <div className="hero-stripes hero-grain text-white relative">
          <div className="relative z-10 px-8 py-9 md:px-10 md:py-10">
            <div className="flex items-center gap-2 mb-6">
              <LogoMark size={18} />
              <span className="text-[12px] font-semibold tracking-tight">Agent Speak</span>
            </div>
            <div className="grid grid-cols-1 md:grid-cols-12 gap-7 md:gap-9 items-center">
              <div className="md:col-span-5">
                <div className="serif leading-none" style={{ fontSize: 'clamp(64px, 9vw, 96px)' }}>
                  <span className="iridescent-text">{answered}</span>
                  <span className="text-white/40 italic" style={{ fontSize: '0.5em' }}> of </span>
                  <span className="text-white/90">{total}</span>
                </div>
                <div className="text-[12px] uppercase tracking-[0.16em] text-white/55 font-medium mt-3">
                  {analysis.audience || 'customer'} questions answered
                </div>
              </div>
              <div className="md:col-span-7 md:border-l border-white/10 md:pl-9">
                <div className="text-[11px] uppercase tracking-[0.18em] text-rose-300/90 font-medium mb-2.5">The biggest gap</div>
                <p className="serif text-white leading-snug" style={{ fontSize: 'clamp(20px, 2.4vw, 26px)', textWrap: 'balance' }}>
                  {analysis.headline}
                </p>
              </div>
            </div>
          </div>
          {/* self-promoting footer — readable in any screenshot */}
          <div className="relative z-10 border-t border-white/10 px-8 md:px-10 py-4 flex items-center justify-between">
            <span className="text-[12.5px] text-white/55">
              {analysis.pageType ? <span className="text-white/40">{analysis.pageType} · </span> : null}
              See what AI says about your content
            </span>
            <span className="text-[13px] font-semibold tracking-tight iridescent-text">{SITE}</span>
          </div>
        </div>
      </div>
      <div className="bg-white border border-t-0 border-slate-200 rounded-b-xl px-6 py-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 -mt-px">
        <p className="text-[13px] text-slate-500" style={{ textWrap: 'pretty' }}>
          Screenshot the card for Slack, or copy the summary as plain text.
        </p>
        <div className="flex items-center gap-2 shrink-0">
          <button
            onClick={focusInput}
            className="inline-flex items-center gap-1.5 px-3 h-8 rounded-md border border-slate-200 bg-white text-slate-700 text-[13px] font-medium hover:bg-slate-50 hover:border-slate-300 transition-colors"
          >
            Run another <Icon name="arrow-up" size={12} />
          </button>
          <CopyButton text={summary} label="Copy summary" variant="solid" />
        </div>
      </div>
    </section>
  );
}

// ---------- Monthly re-check email capture ----------
// Highest-intent moment: right after the report, tied to the recurring value
// the user just experienced (the re-check + delta). Stores locally; wire to an
// ESP later. This is the top of the funnel to the paid monthly briefing.
function RecheckSignup({ analysis, meta }) {
  const [email, setEmail] = useState('');
  const [done, setDone] = useState(false);
  const [error, setError] = useState('');

  useEffect(() => {
    try { if (localStorage.getItem('agentspeak_recheck_email')) setDone(true); } catch (e) {}
  }, []);

  const submit = (e) => {
    e.preventDefault();
    const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());
    if (!ok) { setError('That doesn’t look like a valid email.'); return; }
    try {
      const list = JSON.parse(localStorage.getItem('agentspeak_recheck_signups') || '[]');
      list.push({ email: email.trim(), pageType: analysis.pageType || null, failed: analysis.failed, total: analysis.results.length, when: new Date().toISOString() });
      localStorage.setItem('agentspeak_recheck_signups', JSON.stringify(list));
      localStorage.setItem('agentspeak_recheck_email', email.trim());
    } catch (e) {}
    setDone(true);
  };

  return (
    <section className="rounded-xl border border-slate-200 bg-white overflow-hidden relative">
      <div className="absolute inset-x-0 top-0 h-1 iridescent" />
      <div className="px-6 py-6 md:px-8 md:py-7 flex flex-col md:flex-row md:items-center gap-5 md:gap-8">
        <div className="md:flex-1">
          <h2 className="text-[16px] font-semibold text-slate-900 heading">Want this to keep paying off?</h2>
          <p className="text-[13.5px] text-slate-600 mt-1.5 leading-relaxed" style={{ textWrap: 'pretty' }}>
            Your content changes, and so does how AI reads it. We’ll re-check this every month and email you
            what moved: new gaps, closed gaps, and the copy to fix them. No spam, just the delta.
          </p>
        </div>
        {done ? (
          <div className="md:w-[300px] shrink-0 flex items-center gap-2.5 px-4 py-3 rounded-lg bg-teal-50 border border-teal-200">
            <span className="text-teal-600 shrink-0"><Icon name="check" size={16} strokeWidth={2.5} /></span>
            <span className="text-[13.5px] text-teal-800 font-medium">You’re in. We’ll send your first re-check soon.</span>
          </div>
        ) : (
          <form onSubmit={submit} className="md:w-[360px] shrink-0">
            <div className="flex items-center gap-2">
              <input
                type="email"
                value={email}
                onChange={(e) => { setEmail(e.target.value); setError(''); }}
                placeholder="you@company.com"
                className="flex-1 h-10 px-3 rounded-md border border-slate-200 bg-white text-[13.5px] text-slate-900 placeholder:text-slate-400 ring-accent"
              />
              <button type="submit" className="inline-flex items-center gap-1.5 px-3.5 h-10 rounded-md bg-slate-900 text-white text-[13px] font-medium hover:bg-slate-800 transition-colors whitespace-nowrap">
                Monitor it <Icon name="arrow-right" size={13} />
              </button>
            </div>
            {error ? <div className="text-[12px] text-rose-600 mt-1.5">{error}</div> : <div className="text-[11.5px] text-slate-400 mt-1.5">Monthly re-check. Unsubscribe anytime.</div>}
          </form>
        )}
      </div>
    </section>
  );
}

// ---------- Question list ----------
function QuestionList({ analysis }) {
  const [filter, setFilter] = useState('all');
  const filtered = analysis.results.filter((r) => (filter === 'fail' ? !r.pass : filter === 'pass' ? r.pass : true));
  return (
    <section className="bg-white border border-slate-200 rounded-xl">
      <div className="px-6 py-4 border-b border-slate-100">
        <div className="flex items-start justify-between gap-4">
          <div>
            <h2 className="text-[15px] font-semibold text-slate-900 heading">What an agent asked your content</h2>
            <p className="text-[12px] text-slate-500 mt-0.5">
              {analysis.passed} answered · {analysis.failed} silent
            </p>
          </div>
          <div className="inline-flex items-center bg-slate-50 border border-slate-200 rounded-lg p-0.5 shrink-0">
            {[
              { id: 'all', label: `All ${analysis.results.length}` },
              { id: 'fail', label: `Failed ${analysis.failed}` },
              { id: 'pass', label: `Passed ${analysis.passed}` },
            ].map((f) => (
              <button
                key={f.id}
                onClick={() => setFilter(f.id)}
                className={`px-2.5 h-7 rounded-md text-[12px] font-medium transition-colors whitespace-nowrap ${
                  filter === f.id ? 'bg-white text-slate-900 border border-slate-200 shadow-sm' : 'text-slate-600 hover:text-slate-900'
                }`}
              >
                {f.label}
              </button>
            ))}
          </div>
        </div>
        <p className="mt-3 text-[13px] text-slate-600 leading-relaxed max-w-2xl" style={{ textWrap: 'pretty' }}>
          Before an AI agent recommends you, it checks your content for the answers a {analysis.audience || 'customer'} needs. These are the questions it ran. A <span className="text-rose-600 font-medium">silent</span> row is one your copy didn’t answer, so the agent can’t cite you on it and moves on to a competitor that can.
        </p>
      </div>
      <ul className="row-divide">
        {filtered.map((r) => (
          <li key={r.id} className="px-6 py-3 flex items-start gap-3 hover:bg-slate-50/50 transition-colors duration-150">
            <span className={`mt-0.5 ${r.pass ? 'text-emerald-600' : 'text-rose-600'} shrink-0`}>
              <Icon name={r.pass ? 'check-circle-2' : 'x-circle'} size={16} />
            </span>
            <div className="flex-1 min-w-0">
              <div className="flex items-baseline gap-2 flex-wrap">
                <span className="text-[14px] text-slate-900 font-medium">{r.q}</span>
                <span className="text-[11px] uppercase tracking-wide text-slate-400 font-medium">{r.intent}</span>
              </div>
              <div className="mt-1 text-[13px] leading-relaxed">
                {r.pass ? (
                  <span className="text-slate-600">
                    <span className="text-slate-400">Source: </span>
                    <span>"{r.snippet}"</span>
                  </span>
                ) : (
                  <span className="text-rose-700">
                    <span className="text-rose-500">Missing: </span>
                    {r.missing}
                  </span>
                )}
              </div>
              {!r.pass && (
                <button
                  onClick={() => showAddition(r.id)}
                  className="mt-1.5 inline-flex items-center gap-1 text-[12px] font-medium text-teal-700 hover:text-teal-800 transition-colors"
                >
                  See suggested copy <Icon name="arrow-down" size={11} />
                </button>
              )}
            </div>
          </li>
        ))}
        {filtered.length === 0 && <li className="px-6 py-8 text-center text-[13px] text-slate-500">No questions in this filter.</li>}
      </ul>
    </section>
  );
}

// ---------- Additions ----------
function Additions({ analysis }) {
  if (analysis.additions.length === 0) {
    return (
      <section className="bg-white border border-slate-200 rounded-xl px-6 py-8 text-center">
        <div className="text-emerald-600 mb-2 flex justify-center">
          <Icon name="check-circle-2" size={20} />
        </div>
        <h2 className="text-[15px] font-semibold text-slate-900 heading">Nothing to add</h2>
        <p className="text-[13px] text-slate-500 mt-1">Every question has a clean answer in your content. Rare.</p>
      </section>
    );
  }
  return (
    <section>
      <SectionHeading
        title="Suggested copy to add"
        subtitle={`${analysis.additions.length} ready-to-paste block${analysis.additions.length === 1 ? '' : 's'}, one per silent spot. Swap the [bracketed bits] for your real numbers.`}
      />
      <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-4">
        {[...analysis.additions]
          .sort((a, b) => (a.id === analysis.topGapId ? -1 : b.id === analysis.topGapId ? 1 : 0))
          .map((a) => {
            const isTop = a.id === analysis.topGapId;
            return (
          <div key={a.id} id={`addition-${a.id}`} data-addition-id={a.id} className={`bg-white rounded-xl overflow-hidden scroll-mt-24 transition-shadow ${isTop ? 'md:col-span-2 border-2 border-transparent' : 'border border-slate-200'}`} style={isTop ? { backgroundImage: 'linear-gradient(white, white), linear-gradient(110deg, #5eead4, #a78bfa 45%, #f472b6)', backgroundOrigin: 'border-box', backgroundClip: 'padding-box, border-box' } : undefined}>
            {isTop && (
              <div className="px-5 pt-3.5 flex items-center gap-2">
                <span className="inline-flex items-center gap-1.5 px-2 h-6 rounded-full iridescent text-white text-[10.5px] font-semibold uppercase tracking-wide">
                  <Icon name="sparkles" size={11} /> Start here
                </span>
                <span className="text-[12px] text-slate-500">Biggest gap. Fixing this moves the number most.</span>
              </div>
            )}
            <div className="px-5 pt-4 pb-3 border-b border-slate-100 flex items-start justify-between gap-3">
              <div>
                <div className="text-[11px] uppercase tracking-wider text-slate-400 font-medium mb-1">To answer</div>
                <div className="text-[14px] font-medium text-slate-900 leading-snug">{a.q}</div>
              </div>
              <CopyButton text={a.template.replace(/\*\*/g, '')} />
            </div>
            <div className="px-5 py-4 text-[13px] leading-relaxed text-slate-700 whitespace-pre-wrap">
              {a.template.split(/(\*\*[^*]+\*\*|\[[^\]]+\])/).map((seg, i) => {
                if (/^\*\*[^*]+\*\*$/.test(seg))
                  return (
                    <span key={i} className="font-semibold text-slate-900">
                      {seg.replace(/\*\*/g, '')}
                    </span>
                  );
                if (/^\[[^\]]+\]$/.test(seg))
                  return (
                    <span key={i} className="mono text-[12px] px-1 py-px rounded bg-slate-100 text-slate-700 border border-slate-200">
                      {seg}
                    </span>
                  );
                return <span key={i}>{seg}</span>;
              })}
            </div>
            {(() => {
              const p = principleFor(a.intent, a.q);
              return (
                <div className="px-5 py-3.5 border-t border-slate-100 bg-slate-50/50 flex items-start gap-2.5">
                  <span className="text-violet-500 shrink-0 mt-px"><Icon name="sparkles" size={13} /></span>
                  <div className="min-w-0">
                    <span className="text-[11px] uppercase tracking-[0.13em] font-semibold text-violet-600">Why agents care</span>
                    <p className="text-[12.5px] text-slate-600 leading-relaxed mt-0.5" style={{ textWrap: 'pretty' }}>
                      {p.why}{' '}
                      <a href={p.href} className="text-violet-700 font-medium whitespace-nowrap hover:text-violet-900 underline decoration-violet-300 underline-offset-2 hover:decoration-violet-600">
                        {p.label} →
                      </a>
                    </p>
                  </div>
                </div>
              );
            })()}
          </div>
            );
          })}
      </div>
    </section>
  );
}

// ---------- Rewrites ----------
function Rewrites({ analysis }) {
  if (analysis.rewrites.length === 0) return null;
  return (
    <section>
      <SectionHeading title="Tighter ways to say what's already there" subtitle="Hype phrases and weak buttons, rewritten as facts. Each fix tells you why." />
      <div className="bg-white border border-slate-200 rounded-xl mt-4 row-divide">
        {analysis.rewrites.map((r, i) => (
          <div key={i} className="px-6 py-5">
            <div className="flex items-center gap-2 mb-3">
              <span className={`inline-flex items-center px-1.5 h-5 rounded text-[10.5px] font-medium uppercase tracking-wide ${
                r.kind === 'cta'
                  ? 'bg-rose-50 text-rose-700'
                  : r.kind === 'idiom'
                    ? 'bg-fuchsia-50 text-fuchsia-700'
                    : r.kind === 'time'
                      ? 'bg-sky-50 text-sky-700'
                      : 'bg-amber-50 text-amber-700'
              }`}>
                {r.kind === 'cta'
                  ? 'Weak CTA'
                  : r.kind === 'idiom'
                    ? 'AI mangle risk'
                    : r.kind === 'time'
                      ? 'Vague time'
                      : 'Vague language'}
              </span>
              <span className="text-[12px] text-slate-500">{r.why}</span>
            </div>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
              <div className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
                <div className="text-[11px] uppercase tracking-wider font-medium text-slate-500 mb-1.5">Before</div>
                <div className="text-[13px] text-slate-700 leading-relaxed line-through decoration-slate-300 decoration-1">{r.before}</div>
              </div>
              <div className="rounded-lg border border-emerald-200 bg-emerald-50/60 px-4 py-3 relative">
                <div className="flex items-center justify-between mb-1.5">
                  <div className="text-[11px] uppercase tracking-wider font-medium text-emerald-700">After</div>
                  <CopyButton text={r.after} className="!h-6 !px-2" />
                </div>
                <div className="text-[13px] text-slate-900 leading-relaxed">{r.after}</div>
              </div>
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}

// ---------- Schema blocks ----------
function SchemaBlock({ label, rationale, json }) {
  const stringified = JSON.stringify(json, null, 2);
  const wrapped = `<script type="application/ld+json">\n${stringified}\n</script>`;
  return (
    <div className="bg-white border border-slate-200 rounded-xl overflow-hidden">
      <div className="px-5 pt-4 pb-3 border-b border-slate-100">
        <div className="flex items-center justify-between gap-3">
          <div>
            <div className="flex items-center gap-2">
              <span className="mono text-[11px] px-1.5 py-0.5 rounded bg-slate-100 text-slate-700 border border-slate-200">{label}</span>
              <span className="text-[12px] text-slate-500">{rationale}</span>
            </div>
          </div>
          <CopyButton text={wrapped} label="Copy as <script>" />
        </div>
      </div>
      <pre
        className="mono text-[12px] leading-relaxed px-5 py-4 overflow-x-auto"
        style={{ background: '#0f172a', color: '#e2e8f0' }}
      >
        <code>{stringified}</code>
      </pre>
    </div>
  );
}

function SchemaSection({ analysis }) {
  return (
    <section>
      <SectionHeading title="Schema, written for you" subtitle="JSON-LD generated from your actual content. Drop it into <head>, no plugin needed." />
      <div className="grid grid-cols-1 gap-3 mt-4">
        <SchemaBlock label="FAQPage" rationale={`Built from ${analysis.meta.faqs.length || 'extracted'} Q&A pair${analysis.meta.faqs.length === 1 ? '' : 's'}`} json={analysis.schema.faq} />
        <SchemaBlock label="Product" rationale={analysis.meta.planNames.length ? `Detected plans: ${analysis.meta.planNames.join(', ')}` : 'Add real plan names to strengthen this'} json={analysis.schema.product} />
        <SchemaBlock label="Organization" rationale={`Detected name: ${analysis.meta.orgName}`} json={analysis.schema.org} />
      </div>
    </section>
  );
}

// ---------- Scorecard ----------
function Scorecard({ analysis }) {
  return (
    <section>
      <SectionHeading title="How agent-ready this content is" subtitle="Ten signals an agent weights when picking what to quote. Score 1–5 each. The 'why' on every row tells you what moved the number." />er." />
      <div className="bg-white border border-slate-200 rounded-xl mt-4 row-divide">
        {analysis.scorecard.map((d) => (
          <div key={d.name} className="px-6 py-4 flex items-center gap-5">
            <div className="flex-1 min-w-0">
              <div className="text-[14px] font-medium text-slate-900">{d.name}</div>
              <div className="text-[12px] text-slate-500 mt-0.5">{d.why}</div>
            </div>
            <div className="flex items-center gap-1.5">
              {[1, 2, 3, 4, 5].map((n) => (
                <span
                  key={n}
                  className={`w-5 h-1.5 rounded-full ${
                    n <= d.score ? (d.score >= 4 ? 'bg-emerald-500' : d.score >= 3 ? 'bg-amber-500' : 'bg-rose-500') : 'bg-slate-100'
                  }`}
                />
              ))}
            </div>
            <div className="w-8 text-right text-[14px] font-semibold text-slate-900 mono">{d.score}</div>
          </div>
        ))}
      </div>
    </section>
  );
}

// ---------- Source preview with highlights ----------
function SourcePreview({ analysis }) {
  // Build segments
  const text = analysis.original;
  const flags = analysis.flags;
  const segs = [];
  let cursor = 0;
  for (const f of flags) {
    if (f.start < cursor) continue; // overlapping; skip
    if (f.start > cursor) segs.push({ kind: 'plain', text: text.slice(cursor, f.start) });
    segs.push({ kind: 'flag', text: text.slice(f.start, f.end), severity: f.severity, why: f.why });
    cursor = f.end;
  }
  if (cursor < text.length) segs.push({ kind: 'plain', text: text.slice(cursor) });

  return (
    <section>
      <SectionHeading
        title="Your content, with the weak spots circled"
        subtitle={
          <>
            <span className="inline-flex items-center gap-1.5 mr-3">
              <span className="w-2 h-2 rounded-sm bg-rose-500" /> high severity
            </span>
            <span className="inline-flex items-center gap-1.5">
              <span className="w-2 h-2 rounded-sm bg-amber-500" /> medium severity
            </span>
          </>
        }
      />
      <div id="source-preview" className="bg-white border border-slate-200 rounded-xl mt-4 px-6 py-5 scroll-mt-24">
        <div className="text-[14px] leading-[1.7] text-slate-800 whitespace-pre-wrap">
          {segs.map((s, i) =>
            s.kind === 'plain' ? (
              <span key={i}>{s.text}</span>
            ) : (
              <span
                key={i}
                title={s.why}
                data-flag-text={s.text.toLowerCase()}
                className={`relative cursor-help rounded-sm px-0.5 transition-all ${
                  s.severity === 'rose' ? 'bg-rose-100 text-rose-900' : 'bg-amber-100 text-amber-900'
                }`}
                style={{
                  textDecoration: 'underline',
                  textDecorationStyle: 'wavy',
                  textDecorationColor: s.severity === 'rose' ? '#e11d48' : '#d97706',
                  textDecorationThickness: '1px',
                  textUnderlineOffset: '3px',
                }}
              >
                {s.text}
              </span>
            )
          )}
        </div>
        {flags.length === 0 && <div className="text-[13px] text-slate-500">No vague terms or weak CTAs detected.</div>}
      </div>
    </section>
  );
}

// ---------- Action checklist ----------
function flashTarget(el) {
  if (!el) return;
  // Use scrollTo on window, not scrollIntoView (per project guidance), to avoid breaking the app shell.
  const rect = el.getBoundingClientRect();
  const top = window.scrollY + rect.top - 100;
  window.scrollTo({ top, behavior: 'smooth' });
  el.classList.add('flash-target');
  setTimeout(() => el.classList.remove('flash-target'), 1600);
}

function showFlagInSource(text) {
  const lower = (text || '').toLowerCase();
  const matches = document.querySelectorAll(`[data-flag-text="${CSS.escape(lower)}"]`);
  if (matches.length === 0) {
    // Try first source-preview element if exact match not found
    const fallback = document.getElementById('source-preview');
    if (fallback) flashTarget(fallback);
    return;
  }
  flashTarget(matches[0]);
}

function showAddition(id) {
  const el = document.getElementById(`addition-${id}`);
  if (el) flashTarget(el);
}

function copyAdditionFix(id, additions) {
  const a = additions.find((x) => x.id === id);
  if (!a) return null;
  return a.template.replace(/\*\*/g, '');
}

function ChecklistGroup({ items, label, analysis }) {
  if (!items.length) return null;
  return (
    <div className="bg-white border border-slate-200 rounded-xl overflow-hidden mb-4">
      <div className="px-6 py-3 border-b border-slate-100 flex items-baseline justify-between bg-slate-50/60">
        <div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">{label}</div>
        <div className="text-[11.5px] text-slate-400">{items.length} item{items.length === 1 ? '' : 's'}</div>
      </div>
      <div className="row-divide">
        {items.map((c, i) => {
          const fixText = c.fixId && c.fixId.startsWith('addition:')
            ? copyAdditionFix(c.fixId.split(':')[1], analysis.additions)
            : null;
          return (
            <div key={i} className="px-6 py-3.5 group">
              <label className="flex items-start gap-3 cursor-pointer">
                <input type="checkbox" className="mt-1 w-3.5 h-3.5 rounded border-slate-300 accent-slate-900 flex-shrink-0" />
                <div className="flex-1 min-w-0">
                  <div className="flex items-baseline gap-2 flex-wrap">
                    <span className="text-[14px] font-medium text-slate-900">{c.title}</span>
                    <span className="inline-flex items-center gap-1 text-[11.5px] text-slate-500">
                      <Icon name="clock" size={11} /> {c.effort}
                    </span>
                  </div>
                  <div className="text-[13px] text-slate-600 mt-0.5 leading-relaxed">{c.detail}</div>
                  <div className="mt-2 flex items-center gap-3 text-[12px] flex-wrap">
                    {c.flagText && (
                      <button
                        type="button"
                        onClick={(e) => { e.preventDefault(); showFlagInSource(c.flagText); }}
                        className="inline-flex items-center gap-1 text-slate-500 hover:text-slate-900 transition-colors"
                      >
                        <Icon name="search" size={11} /> Show me where
                      </button>
                    )}
                    {c.fixId && c.fixId.startsWith('addition:') && (
                      <>
                        <button
                          type="button"
                          onClick={(e) => { e.preventDefault(); showAddition(c.fixId.split(':')[1]); }}
                          className="inline-flex items-center gap-1 text-slate-500 hover:text-slate-900 transition-colors"
                        >
                          <Icon name="arrow-down" size={11} /> Jump to the fix
                        </button>
                        {fixText && <CopyButton text={fixText} label="Copy fix" tiny />}
                      </>
                    )}
                  </div>
                </div>
              </label>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function Checklist({ analysis }) {
  const high = (analysis.groupedChecklist && analysis.groupedChecklist.high) || analysis.checklist.filter((c) => c.severity === 'high');
  const med = (analysis.groupedChecklist && analysis.groupedChecklist.med) || analysis.checklist.filter((c) => c.severity !== 'high');
  return (
    <section>
      <SectionHeading title="What to fix first" subtitle="Top of the list moves the number most. Most fixes are under five minutes." />
      <div className="mt-4">
        <ChecklistGroup items={high} label={`Fix today \u00b7 high impact`} analysis={analysis} />
        <ChecklistGroup items={med} label={`Polish \u00b7 nice to have`} analysis={analysis} />
      </div>
    </section>
  );
}

// ---------- Export ----------
function buildMarkdown(analysis, meta) {
  const lines = [];
  lines.push(`# Agent Speak report`);
  lines.push(``);
  lines.push(`**Industry preset:** ${meta.preset}  `);
  lines.push(`**Generated:** ${new Date().toISOString().split('T')[0]}`);
  lines.push(``);
  lines.push(`## Headline`);
  lines.push(`**${analysis.failed} of ${analysis.results.length}** customer questions cannot be answered from this content (${analysis.failRate}% failure rate).`);
  lines.push(``);
  lines.push(`## Agent simulation`);
  for (const r of analysis.results) {
    lines.push(`- ${r.pass ? '✅' : '❌'} **${r.q}** _(${r.intent})_`);
    if (r.pass) lines.push(`  - Source: "${r.snippet}"`);
    else lines.push(`  - Missing: ${r.missing}`);
  }
  lines.push(``);
  if (analysis.additions.length) {
    lines.push(`## Copy-paste additions`);
    for (const a of analysis.additions) {
      lines.push(`### ${a.q}`);
      lines.push(``);
      lines.push(a.template);
      lines.push(``);
    }
  }
  if (analysis.rewrites.length) {
    lines.push(`## Rewrites`);
    for (const r of analysis.rewrites) {
      lines.push(`- _${r.why}_`);
      lines.push(`  - Before: ${r.before}`);
      lines.push(`  - After: ${r.after}`);
    }
    lines.push(``);
  }
  lines.push(`## Structured data`);
  lines.push('```json');
  lines.push(JSON.stringify(analysis.schema.faq, null, 2));
  lines.push('```');
  lines.push('```json');
  lines.push(JSON.stringify(analysis.schema.product, null, 2));
  lines.push('```');
  lines.push('```json');
  lines.push(JSON.stringify(analysis.schema.org, null, 2));
  lines.push('```');
  lines.push(``);
  lines.push(`## Scorecard`);
  for (const d of analysis.scorecard) lines.push(`- **${d.name}:** ${d.score}/5. ${d.why}`);
  lines.push(``);
  lines.push(`## Action checklist`);
  for (const c of analysis.checklist) lines.push(`- [${c.severity.toUpperCase()} · ${c.effort}] ${c.title}. ${c.detail}`);
  return lines.join('\n');
}

function ExportSection({ analysis, meta }) {
  const onDownload = () => {
    const md = buildMarkdown(analysis, meta);
    const blob = new Blob([md], { type: 'text/markdown' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `agent-copy-advisor-report-${new Date().toISOString().split('T')[0]}.md`;
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(url);
  };
  return (
    <section className="px-2 flex flex-wrap items-center justify-between gap-3">
      <p className="text-[13px] text-slate-500">
        Prefer a doc? <span className="text-slate-700 font-medium">Download the full report</span> as Markdown: every gap, fix, and snippet. Pastes into Notion or Docs cleanly.
      </p>
      <div className="flex items-center gap-2">
        <CopyButton text={() => buildMarkdown(analysis, meta)} label="Copy markdown" />
        <button
          onClick={onDownload}
          className="inline-flex items-center gap-1.5 px-3 h-8 rounded-md border border-slate-200 bg-white text-slate-700 text-[13px] font-medium hover:bg-slate-50 hover:border-slate-300 transition-colors"
        >
          <Icon name="download" size={13} /> Download .md
        </button>
      </div>
    </section>
  );
}

// ---------- shared section heading ----------
function SectionHeading({ title, subtitle }) {
  return (
    <div className="px-1">
      <h2 className="text-[15px] font-semibold text-slate-900 heading">{title}</h2>
      <div className="text-[12.5px] text-slate-500 mt-1 max-w-2xl">{subtitle}</div>
    </div>
  );
}

// ---------- soft quota gate ----------
// Cheap client-side cost control: a handful of free fresh analyses per day,
// then ask for an email to keep going. NOT real security (incognito bypasses
// it) — the real limits are server-side per the deploy spec. This just stops
// casual over-use and turns heavy interest into list growth.
const FREE_RUNS_PER_DAY = 4;
function quotaState() {
  try {
    const today = new Date().toISOString().slice(0, 10);
    const q = JSON.parse(localStorage.getItem('agentspeak_quota') || '{}');
    const used = q.day === today ? (q.count || 0) : 0;
    const unlocked = !!localStorage.getItem('agentspeak_recheck_email');
    return { used, today, unlocked, remaining: Math.max(0, FREE_RUNS_PER_DAY - used) };
  } catch (e) { return { used: 0, today: '', unlocked: false, remaining: FREE_RUNS_PER_DAY }; }
}
function bumpQuota() {
  try {
    const today = new Date().toISOString().slice(0, 10);
    const q = JSON.parse(localStorage.getItem('agentspeak_quota') || '{}');
    const count = (q.day === today ? (q.count || 0) : 0) + 1;
    localStorage.setItem('agentspeak_quota', JSON.stringify({ day: today, count }));
  } catch (e) {}
}

// ---------- Full breakdown: collapsed by default (progressive disclosure) ----------
// The result and the fixes lead. The analytical working — schema, signal
// scores, source text — sits one click away for those who want it.
function FullBreakdown({ analysis }) {
  const [open, setOpen] = useState(false);
  return (
    <div className="pt-6 mt-2 border-t border-slate-200">
      <button
        onClick={() => setOpen((v) => !v)}
        className="w-full flex items-center gap-3 text-left group"
        aria-expanded={open}
      >
        <span className="text-[11px] uppercase tracking-[0.18em] text-slate-400 group-hover:text-slate-600 font-semibold transition-colors">The full breakdown</span>
        <span className="h-px flex-1 bg-slate-100" />
        <span className="inline-flex items-center gap-1 text-[12px] text-slate-400 group-hover:text-slate-700 transition-colors">
          {open ? 'Hide' : 'Show'}
          <span style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s', display: 'inline-flex' }}><Icon name="arrow-down" size={12} /></span>
        </span>
      </button>
      {!open ? (
        <p className="text-[12.5px] text-slate-400 mt-1.5">Schema, signal scores, and your source text. The working underneath the result.</p>
      ) : (
        <div className="space-y-6 mt-4 fade-in">
          <SchemaSection analysis={analysis} />
          <Scorecard analysis={analysis} />
          <SourcePreview analysis={analysis} />
          <Checklist analysis={analysis} />
        </div>
      )}
    </div>
  );
}

// ---------- quota gate (email unlock) ----------
function QuotaGate({ onUnlock }) {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');
  const submit = (e) => {
    e.preventDefault();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) { setError('That doesn’t look like a valid email.'); return; }
    try {
      localStorage.setItem('agentspeak_recheck_email', email.trim());
      const list = JSON.parse(localStorage.getItem('agentspeak_recheck_signups') || '[]');
      list.push({ email: email.trim(), via: 'quota-gate', when: new Date().toISOString() });
      localStorage.setItem('agentspeak_recheck_signups', JSON.stringify(list));
    } catch (e) {}
    onUnlock();
  };
  return (
    <section className="hero-stripes hero-grain text-white rounded-xl px-8 py-10 fade-in relative overflow-hidden">
      <div className="relative max-w-xl">
        <div className="inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.2em] text-white/55 mb-4">
          <Icon name="sparkles" size={13} /> You’re on a roll
        </div>
        <h2 className="display font-semibold leading-tight" style={{ fontSize: 'clamp(24px, 3.4vw, 32px)' }}>
          That’s your free checks for today.
        </h2>
        <p className="text-[15px] text-white/70 mt-3 leading-relaxed" style={{ textWrap: 'pretty' }}>
          Drop your email to keep going: unlimited checks, plus a monthly re-check of your content emailed to you.
          No spam, unsubscribe anytime.
        </p>
        <form onSubmit={submit} className="mt-6 flex flex-col sm:flex-row gap-2 max-w-md">
          <input
            type="email"
            value={email}
            onChange={(e) => { setEmail(e.target.value); setError(''); }}
            placeholder="you@company.com"
            className="flex-1 h-11 px-3.5 rounded-lg bg-white/10 border border-white/20 text-white placeholder:text-white/40 text-[14px] focus:outline-none focus:border-white/50"
          />
          <button type="submit" className="inline-flex items-center justify-center gap-1.5 h-11 px-5 rounded-lg bg-white text-slate-900 text-[13.5px] font-semibold hover:bg-white/90 transition-colors whitespace-nowrap">
            Keep checking <Icon name="arrow-right" size={14} />
          </button>
        </form>
        {error && <div className="text-[12.5px] text-rose-300 mt-2">{error}</div>}
        <p className="text-[12px] text-white/40 mt-4">Already gave us your email on another device? It unlocks here too once you’re signed in.</p>
      </div>
    </section>
  );
}

// ---------- App root ----------
function App() {
  const [analysis, setAnalysis] = useState(null);
  const [running, setRunning] = useState(false);
  const [meta, setMeta] = useState({ preset: 'SaaS' });
  const [delta, setDelta] = useState(null); // { prevFailed, prevTotal } when re-checking same text
  const [gated, setGated] = useState(false); // quota exhausted → show email gate
  const resultsRef = useRef(null);
  const runIdRef = useRef(0);
  const pendingRef = useRef(null); // input held back by the gate

  const hashText = (s) => {
    let h = 0;
    const str = String(s || '');
    for (let i = 0; i < str.length; i++) { h = (h * 31 + str.charCodeAt(i)) | 0; }
    return String(h);
  };

  const handleRun = useCallback(async (input) => {
    // Sample runs and cached/identical inputs cost nothing — let them through.
    // Fresh, novel analyses count against the daily free quota.
    const isFreeRun = input.canned || input.cached || (window.isCachedInput && window.isCachedInput(input));
    if (!isFreeRun) {
      const q = quotaState();
      if (!q.unlocked && q.remaining <= 0) {
        pendingRef.current = input;
        setGated(true);
        return;
      }
      bumpQuota();
    }

    const myId = ++runIdRef.current;
    setGated(false);
    setRunning(true);
    setMeta(input);
    setDelta(null);
    const started = Date.now();
    let a;
    try {
      a = await window.runSmartAnalysis(input);
    } catch (e) {
      a = window.runAnalysis(input);
    }
    // A newer run started, or the user cancelled — drop this result.
    if (myId !== runIdRef.current) return;
    // keep the loader up for at least 700ms so it doesn't flash
    const elapsed = Date.now() - started;
    if (elapsed < 700) await new Promise((r) => setTimeout(r, 700 - elapsed));
    if (myId !== runIdRef.current) return;

    // Before/after: compare to the last run of THIS same page text.
    if (!a.tooThin) {
      try {
        const key = hashText(input.raw);
        const prev = JSON.parse(localStorage.getItem('agentspeak_runs') || '{}');
        if (prev[key] && typeof prev[key].failed === 'number') {
          setDelta({ prevFailed: prev[key].failed, prevTotal: prev[key].total });
        }
        prev[key] = { failed: a.failed, total: (a.results || []).length, ts: Date.now() };
        localStorage.setItem('agentspeak_runs', JSON.stringify(prev));
      } catch (e) {}
    }

    setAnalysis(a);
    setRunning(false);
    requestAnimationFrame(() => {
      resultsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    });
  }, []);

  const cancelRun = useCallback(() => {
    runIdRef.current++; // invalidate the in-flight run
    setRunning(false);
  }, []);

  // Re-run the same text — for the "edited the page, check again" loop and
  // for re-reading with a user's page-type correction.
  const rerun = useCallback((extra = {}) => {
    if (!meta || !meta.raw) return;
    handleRun({ ...meta, ...extra });
  }, [meta, handleRun]);

  return (
    <div className="min-h-screen">
      <Header />
      <main className="max-w-[1100px] mx-auto px-6 pb-12 space-y-6 -mt-8 relative z-10">
        <InputPanel onRun={handleRun} running={running} />

        {gated && !running && (
          <QuotaGate onUnlock={() => { setGated(false); if (pendingRef.current) { const p = pendingRef.current; pendingRef.current = null; handleRun(p); } }} />
        )}

        {!analysis && !running && !gated && <EmptyState />}
        {running && <LoadingState onCancel={cancelRun} />}

        {analysis && !running && analysis.tooThin && (
          <ThinState wordCount={analysis.wordCount} />
        )}

        {analysis && !running && !analysis.tooThin && (
          <div ref={resultsRef} className="space-y-6 fade-in">
            <PageRead analysis={analysis} onCorrect={(hint) => rerun({ hint })} onRecheck={() => rerun()} />
            <HeroResult analysis={analysis} delta={delta} />
            {analysis.failed === 0 ? (
              <CleanState analysis={analysis} />
            ) : (
              <>
                <Additions analysis={analysis} />
                <Rewrites analysis={analysis} />
              </>
            )}
            <TranslationBox analysis={analysis} />
            <QuestionList analysis={analysis} />
            <ShareCard analysis={analysis} meta={meta} />
            <RecheckSignup analysis={analysis} meta={meta} />
            <FullBreakdown analysis={analysis} />
            <ExportSection analysis={analysis} meta={meta} />
            <div className="mt-2 text-center text-[11px] text-slate-400 leading-relaxed">
              Graded on the Agent-Ready Framework by Michelle Savage. <a href="blog/index.html" className="underline decoration-slate-300 hover:decoration-slate-500 hover:text-slate-600 transition-colors">Read the thinking behind it &rarr;</a>
            </div>
          </div>
        )}

        <footer className="pt-8 pb-2 flex items-center justify-between text-[12px] text-slate-400">
          <div className="flex items-center gap-2">
            <LogoMark size={16} />
            <span>Agent Speak</span>
          </div>
          <div>Runs in your browser. Nothing leaves it.</div>
        </footer>
      </main>
    </div>
  );
}

// ---------- Thin-input edge state ----------
function ThinState({ wordCount }) {
  const focusInput = () => {
    const ta = document.querySelector('textarea');
    if (ta) { ta.focus(); ta.scrollIntoView({ behavior: 'smooth', block: 'center' }); }
  };
  return (
    <section className="bg-white border border-slate-200 rounded-xl px-8 py-10 text-center relative overflow-hidden fade-in">
      <div className="absolute inset-x-0 top-0 h-1 iridescent" />
      <div className="max-w-md mx-auto">
        <div className="inline-flex items-center justify-center w-11 h-11 rounded-full bg-amber-50 text-amber-600 mb-4">
          <Icon name="alert-triangle" size={20} />
        </div>
        <h2 className="text-[18px] font-semibold text-slate-900 heading">That’s not quite enough to read</h2>
        <p className="text-[14px] text-slate-600 mt-2 leading-relaxed" style={{ textWrap: 'pretty' }}>
          {wordCount ? `We only found ${wordCount} word${wordCount === 1 ? '' : 's'}.` : 'We couldn’t find readable copy.'}{' '}
          Paste the real text from your content (headings, body, prices, button labels) and we’ll read it properly.
        </p>
        <button
          onClick={focusInput}
          className="mt-6 inline-flex items-center gap-1.5 px-4 h-10 rounded-lg bg-slate-900 text-white text-[13px] font-medium hover:bg-slate-800 transition-colors"
        >
          <Icon name="arrow-up" size={13} /> Add your content
        </button>
      </div>
    </section>
  );
}

// ---------- Clean-sweep celebration (0 gaps) ----------
function CleanState({ analysis }) {
  const total = analysis.results.length;
  return (
    <section className="hero-stripes hero-grain text-white rounded-xl px-8 py-10 relative overflow-hidden fade-in">
      <div className="relative">
        <div className="inline-flex items-center justify-center w-12 h-12 rounded-full iridescent text-white mb-5">
          <Icon name="check" size={24} strokeWidth={2.5} />
        </div>
        <h2 className="display font-semibold leading-tight" style={{ fontSize: 'clamp(26px, 4vw, 36px)' }}>
          Nothing slipped through.
        </h2>
        <p className="text-[15px] text-white/70 mt-3 max-w-xl leading-relaxed" style={{ textWrap: 'pretty' }}>
          An AI agent can answer all {total} question{total === 1 ? '' : 's'} a {analysis.audience || 'customer'} would ask about this content. That’s rare. Most content goes silent on a third of them. Screenshot the card below and send it to your team.
        </p>
      </div>
    </section>
  );
}

function EmptyState() {
  const loadSample = (key) => {
    window.scrollTo({ top: 0, behavior: 'smooth' });
    const ev = new CustomEvent('aca:load-sample', { detail: { key, run: true } });
    window.dispatchEvent(ev);
  };
  return (
    <section className="bg-white border border-slate-200 rounded-xl px-8 py-12 relative overflow-hidden">
      {/* iridescent top accent — the brand signature, carried in */}
      <div className="absolute inset-x-0 top-0 h-1 iridescent" />
      <div
        className="absolute -top-24 -right-24 w-80 h-80 rounded-full pointer-events-none opacity-60"
        style={{ background: 'radial-gradient(circle, rgba(167,139,250,0.16), rgba(94,234,212,0.10) 45%, transparent 70%)' }}
        aria-hidden="true"
      />
      <div className="max-w-2xl mx-auto relative">
        <div className="flex items-center gap-2 mb-4">
          <span className="w-1.5 h-1.5 rounded-full iridescent" />
          <span className="text-[11px] uppercase tracking-[0.22em] text-slate-400">Start here</span>
        </div>
        <h2 className="display font-semibold text-slate-900 leading-[1.05]" style={{ fontSize: '40px' }}>
          A customer just opened ChatGPT and asked about your content.
          <br />
          <span className="serif italic font-normal iridescent-text">What do they hear back?</span>
        </h2>
        <p className="text-[15px] text-slate-600 mt-5 leading-relaxed">
          Probably not what you'd want. Most content is written for humans skimming, not agents quoting.
          Paste yours and you'll see the silent spots in 60 seconds, with the exact words to fix them.
        </p>

        <div className="mt-7 flex flex-wrap items-center gap-2">
          <button
            onClick={() => loadSample('saas')}
            className="group inline-flex items-center gap-1.5 px-3 h-9 rounded-md border border-slate-200 bg-white hover:border-teal-300 hover:bg-teal-50/40 text-[13px] font-medium text-slate-700 transition-colors"
          >
            <span className="text-teal-500"><Icon name="file-text" size={13} /></span> Try a SaaS pricing page
          </button>
          <button
            onClick={() => loadSample('ecom')}
            className="group inline-flex items-center gap-1.5 px-3 h-9 rounded-md border border-slate-200 bg-white hover:border-violet-300 hover:bg-violet-50/40 text-[13px] font-medium text-slate-700 transition-colors"
          >
            <span className="text-violet-500"><Icon name="shopping-bag" size={13} /></span> Try a returns policy
          </button>
          <button
            onClick={() => loadSample('ux')}
            className="group inline-flex items-center gap-1.5 px-3 h-9 rounded-md border border-slate-200 bg-white hover:border-pink-300 hover:bg-pink-50/40 text-[13px] font-medium text-slate-700 transition-colors"
          >
            <span className="text-pink-500"><Icon name="message-square" size={13} /></span> Try product UX strings
          </button>
          <span className="text-[12px] text-slate-400 ml-1">Each runs instantly.</span>
        </div>

        {/* Cold open: an example result so the value is visible before any work */}
        <div className="mt-9">
          <div className="flex items-center gap-2 mb-3">
            <span className="inline-flex items-center gap-1.5 px-2 h-5 rounded-full bg-slate-100 text-slate-500 text-[10px] font-semibold uppercase tracking-wide">Example</span>
            <span className="text-[12px] text-slate-400">What your report will look like</span>
            <span className="h-px flex-1 bg-slate-100" />
          </div>
          <div className="rounded-xl border border-slate-200 overflow-hidden bg-white" style={{ boxShadow: '0 20px 48px -28px rgba(15,23,42,0.25)', opacity: 0.92 }} aria-hidden="true">
            {/* page read line */}
            <div className="px-5 py-3 border-b border-slate-100 flex items-center gap-2.5 bg-slate-50/60">
              <span className="text-teal-600 shrink-0"><Icon name="eye" size={15} /></span>
              <p className="text-[12.5px] text-slate-600 leading-snug">
                We read this as a <span className="font-semibold text-slate-900">SaaS pricing page</span>, written for a <span className="font-semibold text-slate-900">customer</span>.
              </p>
            </div>
            {/* mini hero */}
            <div className="px-5 py-4 border-b border-slate-100">
              <div className="flex items-baseline gap-3 flex-wrap">
                <span className="display font-semibold text-amber-600 whitespace-nowrap" style={{ fontSize: '30px' }}>3 <span className="opacity-50">of</span> 7</span>
                <span className="text-[11px] font-medium uppercase tracking-[0.14em] text-amber-600/80">unanswered · lower is better</span>
              </div>
              <p className="text-[13px] text-slate-600 mt-2">Customers ask what it costs and what happens after the trial. The page doesn’t say.</p>
            </div>
            {/* start-here card */}
            <div className="px-5 py-4">
              <div className="rounded-lg p-px" style={{ background: 'linear-gradient(110deg, #5eead4, #a78bfa 45%, #f472b6)' }}>
                <div className="rounded-[7px] bg-white px-4 py-3.5">
                  <div className="flex items-center gap-2 mb-2.5">
                    <span className="inline-flex items-center gap-1.5 px-2 h-5 rounded-full iridescent text-white text-[10px] font-semibold uppercase tracking-wide">
                      <Icon name="sparkles" size={10} /> Start here
                    </span>
                    <span className="text-[11.5px] text-slate-500">Biggest gap</span>
                  </div>
                  <div className="text-[12px] text-slate-400 uppercase tracking-wider font-medium mb-1">To answer</div>
                  <div className="text-[13.5px] font-medium text-slate-900 mb-2">What does it cost after the free trial?</div>
                  <p className="text-[12.5px] leading-relaxed text-slate-600">
                    After your 14-day trial, plans start at <span className="mono text-[11.5px] px-1 py-px rounded bg-slate-100 border border-slate-200">[$X]</span>/month per seat. No card required to start.
                  </p>
                </div>
              </div>
            </div>
          </div>
          <p className="text-[12px] text-slate-400 mt-2.5 text-center">Your own report runs in about a minute.</p>
        </div>
      </div>

      {/* Three steps. Plain language. No triplets in body copy. */}
      <div className="mt-12 grid grid-cols-1 md:grid-cols-3 gap-px bg-slate-100 border border-slate-200 rounded-lg overflow-hidden relative">
        {[
          {
            n: '01',
            color: '#14b8a6',
            title: 'We read what your page is for',
            body: 'No checklist. We work out the page’s job, then ask only what a real buyer would.',
          },
          {
            n: '02',
            color: '#8b5cf6',
            title: 'You see what it never answered',
            body: 'Each gap is one line: what the buyer needed, and where your page went quiet.',
          },
          {
            n: '03',
            color: '#ec4899',
            title: 'You paste the fix',
            body: 'One copy block per gap, built from your page. Drop in your numbers and you’re done.',
          },
        ].map((s) => (
          <div key={s.n} className="bg-white px-5 py-5 relative">
            <div className="absolute inset-x-0 top-0 h-0.5" style={{ background: s.color, opacity: 0.85 }} />
            <div className="text-[11px] font-semibold tracking-[0.18em] mb-2" style={{ color: s.color }}>{s.n}</div>
            <div className="text-[14px] font-semibold text-slate-900 mb-1.5">{s.title}</div>
            <div className="text-[12.5px] text-slate-600 leading-relaxed">{s.body}</div>
          </div>
        ))}
      </div>
    </section>
  );
}

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