// SQSEO — Competitive Landscape (Phase 19). Real competitors discovered from
// the user's own site profile (autocomplete "vs/alternatives" + in-house
// scraping), each with what they sell + keyword overlap, plus a longtail
// keyword-gap table (they target, you don't). Discover / rescan / add-manual /
// remove all live. Everything via GET/POST /api/competitors[/discover].
(function init(){
if(!window.LongtailIQDesignSystem_ae8f12 || !window.LTQ || !window.LTQ.useApi || !window.LTQ.api){return setTimeout(init,30);}
const React = window.React;
const DS = window.LongtailIQDesignSystem_ae8f12;
const { Card, Icon, Badge, Button, OpportunityScore } = DS;
const { SectionTitle, EmptyState, useApi, api, toast, Score } = window.LTQ;
const t = (k, v) => (window.LTQ.t ? window.LTQ.t(k, v) : k);

const VIA_LABEL = (v) => ({
  autocomplete_vs: t("comp.via.vs"), autocomplete_or: t("comp.via.alt"),
  autocomplete_best: t("comp.via.category"), own_site: t("comp.via.own"),
  llm: t("comp.via.ai"), manual: t("comp.via.manual"),
}[v] || v);

function Dot({ color, title }) {
  return <span title={title} style={{ width: 9, height: 9, borderRadius: 999, background: color, flex: "none", display: "inline-block" }} />;
}

function CompetitorCard({ c, onRemove }) {
  const s = c.summary || {};
  const busy = c.status === "scanning";
  const failed = c.status === "failed";
  return (
    <Card pad={16} className={busy || failed ? "" : "lt-card-3d"} style={{ display: "flex", flexDirection: "column", gap: 10, opacity: failed ? 0.62 : 1 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 11 }}>
        <span style={{ width: 34, height: 34, flex: "none", borderRadius: 9, display: "grid", placeItems: "center", background: "var(--ink-50)" }}>
          <Icon name="globe" size={17} style={{ color: c.color || "var(--viz-blue)" }} />
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 700, color: "var(--text-strong)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.name || c.domain}</div>
          <div style={{ fontSize: 11.5, color: "var(--text-faint)", fontFamily: "var(--font-mono)" }}>{c.domain}</div>
        </div>
        <button onClick={() => onRemove(c)} aria-label={t("comp.remove")} style={{ width: 26, height: 26, flex: "none", display: "grid", placeItems: "center", border: "none", background: "transparent", cursor: "pointer", color: "var(--text-faint)", borderRadius: 7 }}><Icon name="x" size={14} /></button>
      </div>
      {busy ? (
        <div style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12, color: "var(--text-muted)" }}>
          <span className="lt-spin" style={{ width: 13, height: 13, border: "2px solid var(--ink-200)", borderTopColor: "var(--accent-500)", borderRadius: 999 }} /> {t("comp.scanning")}
        </div>
      ) : failed ? (
        <div style={{ fontSize: 12, color: "var(--text-faint)" }}>{t("comp.unreadable")}</div>
      ) : (
        <>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            <Badge tone="neutral" variant="soft">{VIA_LABEL(c.discovered_via)}</Badge>
            {s.type && <Badge tone="neutral" variant="soft">{s.type}</Badge>}
            {s.platform && <Badge tone="neutral" variant="soft">{s.platform}</Badge>}
          </div>
          <div style={{ display: "flex", gap: 16 }}>
            <div><div className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 18, fontWeight: 700, color: "var(--text-strong)" }}>{c.overlap}%</div><div style={{ fontSize: 11, color: "var(--text-faint)" }}>{t("comp.overlap")}</div></div>
            <div><div className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 18, fontWeight: 700, color: "var(--text-strong)" }}>{c.gaps}</div><div style={{ fontSize: 11, color: "var(--text-faint)" }}>{t("comp.gap_kw")}</div></div>
          </div>
          {(s.topKeywords || []).length > 0 && (
            <div style={{ fontSize: 12, color: "var(--text-muted)", lineHeight: 1.5 }}>
              <span style={{ color: "var(--text-faint)" }}>{t("comp.they_target")} </span>{s.topKeywords.slice(0, 4).join(", ")}
            </div>
          )}
        </>
      )}
    </Card>
  );
}

function SovBar({ pct, color, faint, delay }) {
  const reduced = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const [grown, setGrown] = React.useState(reduced);
  React.useEffect(() => {
    if (reduced) return;
    const id = setTimeout(() => setGrown(true), (delay || 0) + 80);
    return () => clearTimeout(id);
  }, []);
  const w = Math.max(pct, pct > 0 ? 2 : 0);
  return (
    <span style={{ position: "relative", display: "block", height: 7, background: "var(--ink-100)", borderRadius: 999, overflow: "hidden", width: "100%" }}>
      {/* GPU-friendly fill: scaleX from the left on a spring (no width animation) */}
      <span style={{ position: "absolute", left: 0, top: 0, height: "100%", width: w + "%", background: faint ? "var(--ink-300)" : color, borderRadius: 999,
        transformOrigin: "left", transform: grown ? "scaleX(1)" : "scaleX(0)", transition: reduced ? "none" : "transform 0.8s var(--ease-spring)" }} />
    </span>
  );
}

// Deterministic AI-share-of-voice — who owns the answers to the mined questions,
// BM25-computed from crawled content (no LLM). Renders nothing until there's a
// scored leaderboard (the surrounding screen already guides scan/discover).
function ShareOfVoice({ data, history }) {
  const board = (data && data.leaderboard) || [];
  const rows = (data && data.perQuestion) || [];
  const meta = (data && data.meta) || {};
  if (!board.length || !meta.scored) return null;
  const max = Math.max(1, ...board.map((b) => b.sov));
  const cols = board.filter((b) => b.id !== "you"); // legend for the table
  // P9 change indicators: pp deltas vs the previous stored snapshot.
  const changes = (history && history.changes) || [];
  const deltaOf = (id) => { const ch = changes.find((x) => x.id === id); return ch && ch.delta != null ? ch.delta : null; };
  const movement = (history && history.movement) || null;
  return (
    <Card pad={0} style={{ overflow: "hidden" }}>
      <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--border-subtle)", display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
        <Icon name="audio-lines" size={17} style={{ color: "var(--accent-500)" }} />
        <span style={{ fontSize: 14, fontWeight: 700, color: "var(--text-strong)" }}>{t("sov.title")}</span>
        <Badge tone="accent" variant="soft">{t("sov.hint")}</Badge>
        <span style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-faint)" }}>{t("sov.across", { n: meta.scored })}</span>
      </div>

      {/* ownership leaderboard */}
      <div style={{ padding: "14px 18px", display: "flex", flexDirection: "column", gap: 9, borderBottom: rows.length ? "1px solid var(--border-subtle)" : "none" }}>
        <div style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--text-faint)" }}>{t("sov.owns")}</div>
        {board.map((b, bi) => {
          const you = b.id === "you";
          return (
            <div key={b.id} style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <span style={{ width: 128, flex: "none", display: "flex", alignItems: "center", gap: 7, minWidth: 0 }}>
                <Dot color={you ? "var(--accent-500)" : b.color} />
                <span style={{ fontSize: 12.5, fontWeight: you ? 700 : 600, color: you ? "var(--accent-600)" : "var(--text-strong)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{you ? t("sov.you") : b.label}</span>
              </span>
              <span style={{ flex: 1, minWidth: 60 }}><SovBar pct={(b.sov / max) * 100} color={you ? "var(--accent-500)" : b.color} delay={bi * 90} /></span>
              <span className="lt-num" style={{ width: 46, textAlign: "right", fontFamily: "var(--font-mono)", fontSize: 12.5, fontWeight: 700, color: "var(--text-strong)" }}>{b.sov}%</span>
              {deltaOf(b.id) != null && deltaOf(b.id) !== 0 && (
                <span className="lt-num" title={t("sov.since_prev")} style={{ width: 52, textAlign: "right", fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 700, display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: 2, color: deltaOf(b.id) > 0 ? "var(--success-600, #059669)" : "var(--danger-600, #dc2626)" }}>
                  <Icon name={deltaOf(b.id) > 0 ? "trending-up" : "trending-down"} size={11} />
                  {Math.abs(deltaOf(b.id))}
                </span>
              )}
              <span style={{ width: 62, textAlign: "right", fontSize: 11, color: "var(--text-faint)" }}>{t("sov.leads", { n: b.wins })}</span>
              {b.avgPosition != null && (
                <span className="lt-num" style={{ width: 84, textAlign: "right", fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--text-faint)" }}>{t("sov.avg_pos", { n: b.avgPosition })}</span>
              )}
            </div>
          );
        })}
      </div>

      {/* P16 trend: the Peec-style multi-line SoV time series (needs ≥2 snapshots) */}
      {(() => {
        const days = (history && history.series) || [];
        const MultiLineChart = window.LTQ.MultiLineChart;
        if (days.length < 2 || !MultiLineChart) return null;
        // Align every corpus on the snapshot days; a corpus missing from an
        // older snapshot flatlines at 0 until it appears.
        const ids = board.map((b) => ({ id: b.id, label: b.id === "you" ? t("sov.you") : b.label, color: b.id === "you" ? "var(--accent-500)" : b.color || "var(--viz-blue)" }));
        const series = ids.map((c) => ({
          label: c.label,
          color: c.color,
          points: days.map((d) => {
            const row = (d.leaderboard || []).find((l) => l.id === c.id);
            return row ? row.sov : 0;
          }),
        }));
        const xLabels = days.map((d, i) => (i === 0 || i === days.length - 1 || days.length <= 7 ? String(d.day).slice(5) : ""));
        return (
          <div style={{ padding: "14px 18px 6px", borderBottom: "1px solid var(--border-subtle)" }}>
            <div style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--text-faint)", marginBottom: 8 }}>{t("sov.trend_title")}</div>
            <MultiLineChart series={series} xLabels={xLabels} height={180} markerIndex={days.length - 1} />
          </div>
        );
      })()}

      {/* P9 movement attribution: who took which answers since the last snapshot */}
      {movement && (movement.gained.length > 0 || movement.lost.length > 0) && (
        <div style={{ padding: "10px 18px", borderBottom: rows.length ? "1px solid var(--border-subtle)" : "none", fontSize: 12, color: "var(--text-muted)" }}
          title={movement.lost.map((l) => `${l.question} → ${l.to}`).join("\n")}>
          {t("sov.movement", { gained: movement.gained.length, lost: movement.lost.length })}
        </div>
      )}

      {/* per-question ownership */}
      {rows.length > 0 && (
        <div className="lt-scroll" style={{ overflowX: "auto" }}>
          <table style={{ borderCollapse: "collapse", width: "100%", minWidth: 560 }}>
            <thead><tr>
              <th style={thStyle("left")}>{t("sov.q_col")}</th>
              <th style={{ ...thStyle("left"), width: 74 }} title={t("sov.vol_tip")}>{t("sov.vol_col")}</th>
              <th style={thStyle("left")}>{t("sov.leads_col")}</th>
              <th style={thStyle("left")}>{t("sov.your_share")}</th>
            </tr></thead>
            <tbody>
              {rows.map((r, i) => {
                const lead = cols.find((c) => c.id === r.top);
                const leadYou = r.top === "you";
                const mine = (r.shares.find((s) => s.id === "you") || {}).share || 0;
                return (
                  <tr key={i} onMouseEnter={e => e.currentTarget.style.background = "var(--ink-50)"} onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                    <td style={{ ...tdStyle(i, rows.length), fontSize: 13, fontWeight: 600, color: "var(--text-strong)", maxWidth: 320 }}>{r.question}</td>
                    <td style={tdStyle(i, rows.length)} title={t("sov.vol_tip")}>
                      <span style={{ display: "inline-flex", gap: 2.5, alignItems: "center" }} aria-label={`${r.promptVolume || 1}/5`}>
                        {[1, 2, 3, 4, 5].map((d) => (
                          <span key={d} style={{ width: 5, height: 11, borderRadius: 2, background: d <= (r.promptVolume || 1) ? "var(--accent-500)" : "var(--ink-100)" }} />
                        ))}
                      </span>
                    </td>
                    <td style={tdStyle(i, rows.length)}>
                      <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}>
                        <Dot color={leadYou ? "var(--accent-500)" : (lead ? lead.color : "var(--ink-300)")} />
                        <span style={{ fontSize: 12.5, fontWeight: 600, color: leadYou ? "var(--accent-600)" : "var(--text-body)" }}>{leadYou ? t("sov.you") : (lead ? lead.label : t("sov.none"))}</span>
                      </span>
                    </td>
                    <td style={tdStyle(i, rows.length)}>
                      <span style={{ display: "flex", alignItems: "center", gap: 9, minWidth: 120, maxWidth: 200 }}>
                        <SovBar pct={mine} color="var(--accent-500)" faint={mine === 0} delay={i * 35} />
                        <span className="lt-num" style={{ width: 40, textAlign: "right", fontFamily: "var(--font-mono)", fontSize: 12, fontWeight: 600, color: mine > 0 ? "var(--text-strong)" : "var(--text-faint)" }}>{mine}%</span>
                      </span>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </Card>
  );
}

// TF-IDF topical gaps — single-term TOPICS competitors cover densely in their body
// text that the user barely touches (broader themes than the longtail keyword-gap
// table). Deterministic, no LLM. Renders nothing until there are gaps.
function TopicalGaps({ data }) {
  const gaps = (data && data.topicalGaps) || [];
  if (!gaps.length) return null;
  return (
    <Card pad={0} style={{ overflow: "hidden" }}>
      <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--border-subtle)", display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
        <Icon name="layers" size={17} style={{ color: "var(--violet-500)" }} />
        <span style={{ fontSize: 14, fontWeight: 700, color: "var(--text-strong)" }}>{t("sov.tg_title")}</span>
        <Badge tone="accent" variant="soft">{t("sov.tg_hint")}</Badge>
      </div>
      <div style={{ padding: "14px 18px", display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(260px,1fr))", gap: "14px 20px" }}>
        {gaps.map((g, i) => (
          <div key={i} style={{ display: "flex", flexDirection: "column", gap: 6 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <span style={{ flex: 1, minWidth: 0, fontSize: 13.5, fontWeight: 600, color: "var(--text-strong)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{g.term}</span>
              <span style={{ display: "inline-flex", gap: 3, flex: "none" }}>
                {(g.competitors || []).slice(0, 5).map((c, j) => <Dot key={j} color={c.color} title={c.label} />)}
              </span>
            </div>
            <SovBar pct={g.strength} color="var(--viz-violet)" delay={i * 45} />
          </div>
        ))}
      </div>
    </Card>
  );
}

// P10 — the space's citation graph: which external domains you + rivals link
// out to. Rival-endorsed domains you don't cite yet are the "earn a mention /
// add a citation" shortlist. Pure crawl data — no AI anywhere.
function CitedSources({ data }) {
  const sources = (data && data.citedSources) || [];
  if (!sources.length) return null;
  return (
    <Card pad={0} style={{ overflow: "hidden" }}>
      <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--border-subtle)", display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
        <Icon name="link-2" size={17} style={{ color: "var(--accent-500)" }} />
        <span style={{ fontSize: 14, fontWeight: 700, color: "var(--text-strong)" }}>{t("sov.sources_title")}</span>
        <span style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-faint)" }}>{t("sov.sources_hint")}</span>
      </div>
      <div style={{ padding: "10px 18px 14px", display: "flex", flexDirection: "column", gap: 7 }}>
        {sources.slice(0, 10).map((src) => (
          <div key={src.domain} style={{ display: "flex", alignItems: "center", gap: 10, minWidth: 0 }}>
            <span className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text-strong)", flex: "none", maxWidth: 220, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{src.domain}</span>
            <span style={{ fontSize: 11, color: "var(--text-faint)", flex: "none" }} title={src.corpora.join(", ")}>{t("sov.sources_pages", { n: src.pages })}</span>
            {!src.youCite && <Badge tone="warning" variant="soft">{t("sov.sources_gap")}</Badge>}
            <span style={{ flex: 1 }} />
            <span className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--text-muted)" }}>{src.citedBy}×</span>
          </div>
        ))}
      </div>
    </Card>
  );
}

function CompetitorGaps({ onNavigate }) {
  const { loading, data, reload } = useApi("/api/competitors");
  const sov = useApi("/api/share-of-voice");
  const sovHist = useApi("/api/share-of-voice/history");

  // First visit with a live leaderboard but no stored history yet: take
  // today's snapshot so change-tracking starts now (idempotent per day; the
  // scheduler keeps it daily afterwards).
  const snapOnce = React.useRef(false);
  React.useEffect(() => {
    const hasBoard = sov.data && sov.data.leaderboard && sov.data.leaderboard.length > 0;
    const histLoaded = sovHist.data && Array.isArray(sovHist.data.series);
    if (!snapOnce.current && hasBoard && histLoaded && sovHist.data.series.length === 0) {
      snapOnce.current = true;
      api.post("/api/share-of-voice/snapshot", {}).then(() => sovHist.reload());
    }
  }, [sov.data, sovHist.data]);
  const [disc, setDisc] = React.useState(null);
  const [adding, setAdding] = React.useState("");
  const [busyAdd, setBusyAdd] = React.useState(false);

  const competitors = (data && data.competitors) || [];
  const gaps = (data && data.gaps) || [];
  const ready = competitors.filter((c) => c.status === "ready");

  // poll discovery status
  const refreshDisc = React.useCallback(async () => {
    const r = await api.get("/api/competitors/discover");
    if (r.ok) setDisc(r.data.discovery);
    return r.ok ? r.data.discovery : null;
  }, []);
  React.useEffect(() => { refreshDisc(); }, [refreshDisc]);
  const active = disc && (disc.status === "pending" || disc.status === "running");
  React.useEffect(() => {
    if (!active) return;
    const id = setInterval(async () => {
      const d = await refreshDisc();
      if (d && (d.status === "complete" || d.status === "failed")) { reload(); if (d.status === "complete") { sov.reload(); toast(t("comp.done", { n: d.scanned }), { tone: "success" }); } }
    }, 2500);
    return () => clearInterval(id);
  }, [active]);

  const discover = async () => {
    const r = await api.post("/api/competitors/discover");
    if (r.ok) setDisc(r.data.discovery);
    else toast(r.error || t("comp.failed"), { tone: "error" });
  };
  const addManual = async () => {
    const url = adding.trim(); if (!url) return;
    setBusyAdd(true);
    const r = await api.post("/api/competitors", { url });
    setBusyAdd(false);
    if (r.ok) { setAdding(""); reload(); }
    else toast((r.data && r.data.details && r.data.details.suggestion ? t("comp.did_you_mean", { s: r.data.details.suggestion }) : r.error) || t("comp.add_failed"), { tone: "error" });
  };
  const remove = async (c) => {
    const r = await api.del("/api/competitors/" + c.id);
    if (!r.ok) toast(r.error || t("comp.add_failed"), { tone: "error" });
    reload();
  };

  // dynamic competitor columns for the gap table (cap for width; rest as +N)
  const cols = ready.slice(0, 5);
  const emptyLanding = !loading && competitors.length === 0 && !active;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
      <SectionTitle eyebrow={t("comp.eyebrow")} title={t("nav.competitor")} sub={t("comp.sub")}
        right={!emptyLanding && (
          <Button variant="secondary" size="sm" leadingIcon={active ? undefined : "radar"} onClick={discover} disabled={active}>
            {active ? t("comp.discovering") : t("comp.rescan")}
          </Button>
        )} />

      {active && (
        <Card pad={16} style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <span className="lt-spin" style={{ width: 18, height: 18, border: "2px solid var(--ink-200)", borderTopColor: "var(--accent-500)", borderRadius: 999, flex: "none" }} />
          <div style={{ fontSize: 13.5, color: "var(--text-body)" }}>
            {disc.phase === "discover" ? t("comp.phase_discover") : disc.phase === "scrape" ? t("comp.phase_scrape", { done: disc.scanned, total: disc.found }) : t("comp.phase_compare")}
          </div>
        </Card>
      )}

      {loading ? (
        <Card pad={0}><div style={{ padding: 24 }}><span className="lt-shimmer" style={{ display: "block", height: 200, borderRadius: 10, background: "var(--ink-100)" }} /></div></Card>
      ) : emptyLanding ? (
        <Card pad={0}><EmptyState icon="radar" sq="search" title={t("comp.empty_title")} body={t("comp.empty_body")}
          action={<Button variant="primary" leadingIcon="radar" onClick={discover}>{t("comp.discover_cta")}</Button>} /></Card>
      ) : (
        <>
          {/* competitor cards */}
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(240px,1fr))", gap: 12 }}>
            {competitors.map((c) => <CompetitorCard key={c.id} c={c} onRemove={remove} />)}
          </div>

          {/* add competitor */}
          <Card pad={14} style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
            <Icon name="plus-circle" size={16} style={{ color: "var(--text-faint)" }} />
            <input value={adding} onChange={(e) => setAdding(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") addManual(); }} placeholder={t("comp.add_ph")}
              style={{ flex: 1, minWidth: 180, height: 38, border: "1px solid var(--border-strong)", borderRadius: 9, padding: "0 12px", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-strong)", outline: "none" }} />
            <Button variant="secondary" size="sm" onClick={addManual} disabled={busyAdd || !adding.trim()}>{busyAdd ? t("comp.adding") : t("comp.add")}</Button>
          </Card>

          {/* deterministic AI share of voice + topical gaps (shared fetch) */}
          <ShareOfVoice data={sov.data} history={sovHist.data} />
          <CitedSources data={sov.data} />
          <TopicalGaps data={sov.data} />

          {/* gap table */}
          {gaps.length > 0 && (
            <Card pad={0} style={{ overflow: "hidden" }}>
              <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--border-subtle)", display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ fontSize: 14, fontWeight: 700, color: "var(--text-strong)" }}>{t("comp.gap_table")}</span>
                <Badge tone="accent" variant="soft">{t("comp.gap_table_hint")}</Badge>
              </div>
              <div className="lt-scroll" style={{ overflowX: "auto" }}>
                <table style={{ borderCollapse: "collapse", width: "100%", minWidth: 640 }}>
                  <thead><tr>
                    <th style={thStyle("left")}>{t("comp.col_query")}</th>
                    <th style={thStyle("center")}>{t("comp.col_you")}</th>
                    {cols.map((c) => <th key={c.id} style={thStyle("center")} title={c.domain}><Dot color={c.color} /> {(c.name || c.domain).slice(0, 12)}</th>)}
                    <th style={thStyle("right")}>{t("results.col_score")}</th>
                  </tr></thead>
                  <tbody>
                    {gaps.slice(0, 40).map((r, i) => (
                      <tr key={r.id || i} onMouseEnter={e => e.currentTarget.style.background = "var(--ink-50)"} onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                        <td style={{ ...tdStyle(i, gaps.length), fontSize: 13.5, fontWeight: 600, color: "var(--text-strong)" }}>{r.query}</td>
                        <td style={{ ...tdStyle(i, gaps.length), textAlign: "center" }}><GapCheck on={!!r.you} color="var(--viz-green)" /></td>
                        {cols.map((c) => <td key={c.id} style={{ ...tdStyle(i, gaps.length), textAlign: "center" }}><GapCheck on={!!(r.ranks && r.ranks[c.domain])} color={c.color} /></td>)}
                        <td style={{ ...tdStyle(i, gaps.length), textAlign: "right" }}><Score score={r.score} width={72} /></td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </Card>
          )}
        </>
      )}
    </div>
  );
}

const thStyle = (align) => ({ textAlign: align, padding: "0 14px", height: 44, background: "var(--ink-50)", borderBottom: "1px solid var(--border-strong)", fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--text-faint)", whiteSpace: "nowrap" });
const tdStyle = (i, n) => ({ padding: 14, borderBottom: i < n - 1 ? "1px solid var(--border-subtle)" : "none" });
function GapCheck({ on, color }) {
  return on
    ? <span style={{ width: 20, height: 20, borderRadius: 6, display: "inline-grid", placeItems: "center", background: color, color: "#fff" }}><Icon name="check" size={12} strokeWidth={3} /></span>
    : <span style={{ width: 20, height: 20, borderRadius: 6, display: "inline-grid", placeItems: "center", background: "var(--ink-100)", color: "var(--ink-300)" }}><Icon name="minus" size={12} /></span>;
}
window.LTQ.CompetitorGaps = CompetitorGaps;
})();
