`; const copy = async (text) => { try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1600); } catch { setCopied(false); } }; const clearAll = () => { setData({ ...EMPTY }); setFilledKeys([]); }; const runResearch = async () => { const nm = researchName.trim(); if (!nm) { setResearchError("Enter a name to research."); return; } setResearching(true); setResearchError(""); setResearchNote(""); setFilledKeys([]); try { const result = await researchEntity(nm, researchContext); if (result.ambiguous) { setResearchNote(result.note || "Multiple people share this name. Add more context and try again."); setResearching(false); return; } const touched = []; setData((d) => { const next = { ...d }; ALL_KEYS.forEach((k) => { if (!(k in result)) return; const incoming = toArray(result[k]); if (!incoming.length) return; const existing = many(next, k); if (!overwrite && existing.length) return; next[k] = incoming; touched.push(k); }); if (!one(next, "name") && nm) next.name = [nm]; return next; }); setFilledKeys(touched); setResearchNote( touched.length ? `Auto-filled ${touched.length} field(s). Review every value before using the schema; researched data is a draft, not a verified source.` : "Research returned no new fields. The individual may have a thin public footprint, or existing fields already cover it." ); } catch (e) { setResearchError(e.message || "Research failed. You can still fill the form manually."); } finally { setResearching(false); } }; const group = FIELD_GROUPS.find((g) => g.id === activeGroup); const signalScore = useMemo(() => { let s = 0; if (one(data, "name")) s += 8; if (isUrl(one(data, "deployUrl"))) s += 10; if (isUrl(one(data, "image"))) s += 8; if (one(data, "description")) s += 8; if (one(data, "jobTitle") || one(data, "hasOccupation")) s += 8; const sameAsCount = ["linkedin", "crunchbase", "twitter", "github"].filter((k) => one(data, k)).length + many(data, "otherSameAs").length; s += Math.min(sameAsCount, 4) * 5; if (one(data, "wikidata")) s += 10; const positive = ["knowsAbout", "award", "affiliation", "memberOf", "alumniOf"].reduce((n, k) => n + many(data, k).length, 0); s += Math.min(positive, 6) * 3; return Math.min(s, 100); }, [data]); const scoreColor = signalScore >= 75 ? "var(--good)" : signalScore >= 45 ? "var(--warn)" : "var(--bad)"; const verdictColor = guide.verdict === "strong" ? "var(--good)" : guide.verdict === "borderline" ? "var(--warn)" : "var(--bad)"; return (
Reputation Citadel · Entity Tooling

Person Schema Generator

Build valid, comprehensive schema.org/Person JSON-LD engineered to corroborate the entity and crowd the graph with positive associations.

{signalScore}
signal load

Auto-fill from research

draft only · review before use

Enter a name and any disambiguating context. The tool researches public sources and drafts the fields below. Nothing is committed to the schema until you have checked it.

{researchError &&
{researchError}
} {researchNote &&
{researchNote}
}

{group.blurb}

{group.fields.map((f) => { const wasFilled = filledKeys.includes(f.key); const vals = data[f.key] || [""]; return (
{f.label} {wasFilled && auto-filled} {f.kind === "area" ? (