{
  "version": 3,
  "sources": ["narrator.js"],
  "sourcesContent": ["/* narrator.js — the one voice that explains the visibility report.\n *\n * The numbers are drawn on four surfaces (the card, the crawl page, the reader,\n * the report and its PDF). What they lacked was a narrator: the sentence that\n * says what was measured, what visibility means, and how the numbers were\n * counted, in the same words everywhere. This module holds those words. Every\n * surface quotes it; none paraphrases it.\n *\n * Written from the buyer's side: buyers, questions, answers, engines. Never\n * queries, responses, platforms or verdicts in customer copy.\n */\n\nexport const ENGINES = [\"ChatGPT\", \"Claude\", \"Gemini\", \"Perplexity\"];\nconst list = (xs) => xs.length > 1 ? `${xs.slice(0, -1).join(\", \")} and ${xs[xs.length - 1]}` : xs.join(\"\");\nconst nf = (n) => (n == null ? \"\" : Number(n).toLocaleString());\n\n/* Beat 1 · what we measured. `total` questions, `engines` names, `when` a date string. */\nexport function measured({ total, engines = ENGINES, when } = {}) {\n  return `We asked ${list(engines)} the ${total ? nf(total) + \" \" : \"\"}questions your buyers ask, once each${when ? `, on ${when}` : \"\"}, and read every answer.`;\n}\n\n/* Beat 2 · what visibility means. The definition every surface prints. */\nexport const MEANING = \"Visibility is how often an engine names you in the answer itself. Top pick is when it names you first. Cited but not named is when it read your page and recommended somebody else. Absent is the rest, and it is where buyers shortlist without you.\";\n\n/* The subheadline under a score. */\nexport const SUBHEAD = `How often ${list(ENGINES)} put you in the answer when your buyers ask.`;\n\n/* What a surface says beside a number it cannot fully stand behind: a crawl\n   aggregated before we separated being named in an answer from being cited as\n   a source. Every surface that draws such a crawl prints this, in these words,\n   so the caveat cannot be on the reader and missing from the printed report.\n   See deliverable-model.scoredV2Of for when it applies. */\nexport const SCORING_CAVEAT = \"This crawl was scored before we separated being named in an answer from being cited as a source. Its visibility number counts both, so it reads high. We are recounting it against the answers themselves.\";\n\n/* How we count. One paragraph, the same on the reader, the report and the PDF. */\nexport function method({ total, engines = ENGINES, when } = {}) {\n  const runs = engines.length;\n  return [\n    `Each of the ${total ? nf(total) + \" \" : \"\"}questions was put to ${list(engines)} once${when ? `, on ${when}` : \"\"}, from a fresh session, the way a buyer would ask it. That is ${total ? nf(total * runs) + \" \" : \"\"}answers, each stored word for word with the sources it cited.`,\n    `For every answer we record two separate things. First, whether the answer names you in the words a buyer reads: that is visibility, and being the first name it recommends is a top pick. Second, whether the engine cited one of your pages as a source, which shows up in the footnotes whether or not the answer ever says your name.`,\n    `Your visibility score is the share of questions where at least one engine names you in the answer. A citation on its own does not count. If an engine reads your page, cites it, and then recommends a competitor, that question is a gap, not a win, and the report shows it as cited but not named.`,\n    `Wins are counted, not rated. Top pick is the number of questions an engine put you first on, out of all ${total ? nf(total) + \" \" : \"\"}asked. We deliberately do not publish a win rate measured against the questions you appear in: that number climbs as your visibility falls, because a company named in only the three questions it is perfect for would score a hundred percent. The one win rate here is against a named competitor, where the denominator is something that actually happened. See how we count that below.`,\n    `Head to head is the only rate we hold you to against a rival. We take the questions where an answer named you both, and count which of you that engine recommended first. A record of four to one means five answers had you both in them and you were chosen in four. The questions behind every record are listed, so you can read the five and check us.`,\n    `Share of voice counts how often each company is named across every answer, on the same rule. The breakdowns by buyer, stage, feature and pain point are the same questions grouped by who was asking and why. Nothing here is modelled or estimated: every number is a count of the answers, which are in your account to read.`,\n  ].join(\"\\n\\n\");\n}\n\n/* Short definitions, for a hover or a tap on the number. */\nexport const DEFINE = {\n  visibility: \"Share of questions where at least one engine names you in the answer a buyer reads. A citation on its own does not count.\",\n  visible: \"Questions where at least one engine names you in the answer.\",\n  top_pick: \"Questions where an engine recommends you first.\",\n  absent: \"Questions no engine names you in, and no engine cites you for. Buyers shortlist from these answers without you.\",\n  cited_only: \"The engine used one of your pages as a source and then wrote an answer that never says your name. It can read you; it did not recommend you. These are the shortest moves in the report.\",\n  named_and_cited: \"The answer names you and the engine cites one of your pages. The strongest position a question can be in.\",\n  named_only: \"The answer names you, but the engine leaned on somebody else's pages to write it.\",\n  top_pick_count: \"How many of the questions asked an engine put you first on. A count, out of the whole crawl, not a rate against the questions you happen to appear in.\",\n  share_of_voice: \"How often you are named across every answer, against every other company named.\",\n  head_to_head: \"Of the questions that named you both, how many the engines recommended you over them. One verdict per question, taken from the majority of the engines that named a vendor; an even split counts for neither side. The denominator is the questions you were both in, so it does not move when your visibility does.\",\n  h2h_absent: \"Questions no engine named you in, won by a rival. Head to head cannot see these: you were never in the room.\",\n  ci: \"The range the true rate is likely in, given how many questions were asked. Wider means fewer questions.\",\n  engine_ring: \"Share of questions this engine names you in.\",\n  citations_client: \"Every time an engine used one of your pages as a source, counted across all four engines. One page cited in ten answers counts ten times.\",\n};\n\n/* The cover's headline: a verdict in the buyer's words, picked by the score,\n   so the first line a customer reads says what the number means for them. */\nexport function coverHeadline(pct) {\n  const p = Number(pct);\n  if (pct == null || !Number.isFinite(p)) return \"Where AI puts you when your buyers ask.\";\n  if (p < 10) return \"Your buyers are asking AI about your market. It almost never names you.\";\n  if (p < 30) return \"AI names you now and then. Most of the time, it recommends someone else.\";\n  if (p < 50) return \"You are in the conversation. Most of it still goes to someone else.\";\n  if (p < 75) return \"AI names you in at least half your buyers' questions. Now it needs to pick you first.\";\n  return \"AI names you in almost every buyer question. The work now is staying first.\";\n}\n\n/* The line under that headline: what the rest of the cover walks through. */\nexport const COVER_PROMISE = \"Below: where you're missing, who AI recommends instead, and the first thing to fix.\";\n\n/* How we count, short: four lines for the cover. The full method() stays the\n   report's and the PDF's, and the cover links to it. */\nexport function methodShort({ total, engines = ENGINES, when } = {}) {\n  return [\n    `We asked ${list(engines)} your ${total ? nf(total) + \" \" : \"\"}buyer questions${when ? ` on ${when}` : \"\"} and saved all ${total ? nf(total * engines.length) + \" \" : \"\"}answers word for word.`,\n    \"A question counts as visible when at least one engine names you in the answer. Being cited as a source, without your name, does not count.\",\n    \"Top pick is the number of questions where an engine recommended you first.\",\n    \"Nothing is estimated. Every number is a count of answers you can open and read.\",\n  ];\n}\n\n/* THE PLAN, IN THE CUSTOMER'S WORDS. L1, L2 and L3 are our names for the\n   layers. A customer needs what the move is and who does it. The fourth lane\n   is not a layer: it is every off-site move the new pages carry, as one list. */\nexport const LANES = {\n  L1: { name: \"Fix the site\", short: \"Site fix\", line: \"Technical fixes so the engines can read your pages at all.\" },\n  L2: { name: \"Improve pages you have\", short: \"Page edit\", line: \"Edits that make pages you already have answer your buyers' questions.\" },\n  L3: { name: \"Create new pages\", short: \"New page\", line: \"New pages for questions nothing on your site answers yet.\" },\n  /* An older plan can carry moves with no layer at all. They still get a home. */\n  other: { name: \"Other moves\", short: \"Move\", line: \"Moves the plan did not file under a layer.\" },\n  off: {\n    name: \"Off your site\", short: \"Off-site\", line: \"Posts, listings and mentions on other sites the engines already read.\", who: \"Whoever speaks for the company\",\n    why: \"The engines trust what other people say about you as much as what you say. Each move below puts your name on a site they already read, and each one backs a new page in your plan.\",\n  },\n};\nexport const PRIORITY = { critical: \"Do first\", high: \"High\", medium: \"Medium\", low: \"Low\" };\nexport const OWNER_LABEL = { Engineering: \"Web developer\", Content: \"Writer\", Marketing: \"Marketing\", Design: \"Design\" };\nexport const PLAN_HOW = \"Work down the list. It is ranked in the order we would do it, and each move says who does it and how many buyer questions it wins back.\";\n/* The off-site moves, grouped by what the customer actually does. */\nexport const OFF_KINDS = [\n  { key: \"listed\", name: \"Get listed where buyers check\", line: \"Directories and listings the engines read when buyers ask for a recommendation.\" },\n  { key: \"reviews\", name: \"Ask clients for reviews\", line: \"Named reviews on the sites the engines cite when buyers check you out.\" },\n  { key: \"press\", name: \"Write for other publications\", line: \"Guest posts, quotes and newsletters that put your name on someone else's site.\" },\n  { key: \"publish\", name: \"Post under your own name\", line: \"Articles on LinkedIn and similar platforms that the engines read and cite.\" },\n  { key: \"community\", name: \"Show up in communities\", line: \"The groups where your buyers ask each other for recommendations.\" },\n  { key: \"other\", name: \"Other ways to get mentioned\", line: \"\" },\n];\n\n/* The key under the score: one line per way a question can land, short enough\n   to read without opening anything. The long forms are DEFINE, on hover. */\nexport const KEY = {\n  top_pick: \"An engine recommended you first.\",\n  named: \"In the answer, behind someone else.\",\n  cited_only: \"It read your page, then recommended someone else.\",\n  absent: \"Not named, not cited. Buyers shortlist without you.\",\n};\n\n/* The long form behind the score, for the tooltip a customer taps. Four beats:\n   what the number is, what it is not, why we draw the line there, and where to\n   check it. A number a customer cannot defend in their own review meeting is\n   worth nothing to them. */\nexport function visibilityTooltip({ total, visible, presence, scoredV2 = true } = {}) {\n  const p = presence || {};\n  const beats = [\n    `Of ${nf(total)} buyer questions, at least one engine named you in ${nf(visible)}.`,\n    \"Named means the answer says your name in the text a buyer reads.\",\n  ];\n  /* A crawl scored before the named-vs-cited rule counted a citation as a\n     presence, so its headline is higher than this definition supports. Saying\n     the definition over that number without saying which rule produced it is\n     the same error the rule was written to end. */\n  if (!scoredV2) {\n    beats.push(\n      \"This crawl was scored before we separated the two. Its number counts a question as visible \" +\n      \"when an engine cited one of your pages, even where the answer never said your name, so read \" +\n      \"it as a ceiling rather than the figure above. We are recounting it against the answers; \" +\n      \"the score can only come down.\",\n    );\n    return beats.join(\" \");\n  }\n  if (p.cited_only) {\n    beats.push(\n      `${nf(p.cited_only)} more questions cite one of your pages as a source without ever naming you. ` +\n      \"We do not count those as visible. The engine could read you and still recommended somebody else, \" +\n      \"which is a gap you can close, not a presence you already have.\",\n    );\n  }\n  beats.push(\"Every question is counted once. An engine either names you or it does not, and you can open any answer and check.\");\n  return beats.join(\" \");\n}\n\n/* Beat 3 · where it breaks: the tension sentence, from the payload when it has one. */\nexport function tension(numbers) {\n  const n = numbers || {};\n  return n.core_insight?.headline || n.dichotomy?.headline || n.dichotomy?.gap || \"\";\n}\n\n/* Beat 4 · what to do. */\nexport function toDo({ actions, absent } = {}) {\n  if (!actions) return \"\";\n  return `${nf(actions)} moves, ranked in the order we would do them${absent ? `, each tied to the questions it wins back` : \"\"}.`;\n}\n"],
  "mappings": "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",
  "names": ["list", "xs", "nf", "n", "total", "engines", "when", "runs", "pct", "p", "visible", "presence", "scoredV2", "beats", "numbers", "actions", "absent"]
}
