fodda-mcp 1.46.97 → 1.46.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/README.md +15 -9
  2. package/dist/a2aHandler.d.ts +36 -2
  3. package/dist/a2aHandler.d.ts.map +1 -1
  4. package/dist/a2aHandler.js +24 -1
  5. package/dist/a2aHandler.js.map +1 -1
  6. package/dist/brandTemplate.d.ts +1 -1
  7. package/dist/brandTemplate.d.ts.map +1 -1
  8. package/dist/brandTemplate.js +226 -118
  9. package/dist/brandTemplate.js.map +1 -1
  10. package/dist/catalogCache.d.ts +21 -0
  11. package/dist/catalogCache.d.ts.map +1 -1
  12. package/dist/catalogCache.js +287 -39
  13. package/dist/catalogCache.js.map +1 -1
  14. package/dist/coverageRelevance.d.ts +82 -10
  15. package/dist/coverageRelevance.d.ts.map +1 -1
  16. package/dist/coverageRelevance.js +1274 -190
  17. package/dist/coverageRelevance.js.map +1 -1
  18. package/dist/editorialFill.d.ts.map +1 -1
  19. package/dist/editorialFill.js +13 -1
  20. package/dist/editorialFill.js.map +1 -1
  21. package/dist/enrichment.d.ts +19 -0
  22. package/dist/enrichment.d.ts.map +1 -1
  23. package/dist/enrichment.js +86 -3
  24. package/dist/enrichment.js.map +1 -1
  25. package/dist/foddaClient.d.ts +26 -0
  26. package/dist/foddaClient.d.ts.map +1 -0
  27. package/dist/foddaClient.js +195 -0
  28. package/dist/foddaClient.js.map +1 -0
  29. package/dist/helpService.d.ts +45 -0
  30. package/dist/helpService.d.ts.map +1 -0
  31. package/dist/helpService.js +428 -0
  32. package/dist/helpService.js.map +1 -0
  33. package/dist/index.d.ts +4 -10
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +131 -174
  36. package/dist/index.js.map +1 -1
  37. package/dist/pricingCache.d.ts.map +1 -1
  38. package/dist/pricingCache.js +118 -83
  39. package/dist/pricingCache.js.map +1 -1
  40. package/dist/searchTemplate.d.ts.map +1 -1
  41. package/dist/searchTemplate.js +16 -4
  42. package/dist/searchTemplate.js.map +1 -1
  43. package/dist/skillClient.d.ts.map +1 -1
  44. package/dist/skillClient.js +5 -31
  45. package/dist/skillClient.js.map +1 -1
  46. package/dist/systemPrompt.d.ts +3 -2
  47. package/dist/systemPrompt.d.ts.map +1 -1
  48. package/dist/systemPrompt.js +253 -103
  49. package/dist/systemPrompt.js.map +1 -1
  50. package/dist/test_claude_offering_and_oauth.js +205 -0
  51. package/dist/toolGuide.d.ts +56 -0
  52. package/dist/toolGuide.d.ts.map +1 -0
  53. package/dist/toolGuide.js +330 -0
  54. package/dist/toolGuide.js.map +1 -0
  55. package/dist/toolHandlers.d.ts +3 -2
  56. package/dist/toolHandlers.d.ts.map +1 -1
  57. package/dist/toolHandlers.js +1774 -330
  58. package/dist/toolHandlers.js.map +1 -1
  59. package/dist/tools.d.ts +1 -0
  60. package/dist/tools.d.ts.map +1 -1
  61. package/dist/tools.js +19 -18
  62. package/dist/tools.js.map +1 -1
  63. package/dist/types.d.ts +39 -0
  64. package/dist/types.d.ts.map +1 -1
  65. package/package.json +5 -3
  66. package/server.json +2 -2
  67. package/dist/test_adjacent_trends_polymorphic.d.ts +0 -2
  68. package/dist/test_adjacent_trends_polymorphic.d.ts.map +0 -1
  69. package/dist/test_adjacent_trends_polymorphic.js +0 -154
  70. package/dist/test_adjacent_trends_polymorphic.js.map +0 -1
  71. package/dist/test_analyst.d.ts +0 -2
  72. package/dist/test_analyst.d.ts.map +0 -1
  73. package/dist/test_analyst.js +0 -39
  74. package/dist/test_analyst.js.map +0 -1
  75. package/dist/test_analyst_local.d.ts +0 -2
  76. package/dist/test_analyst_local.d.ts.map +0 -1
  77. package/dist/test_analyst_local.js +0 -51
  78. package/dist/test_analyst_local.js.map +0 -1
  79. package/dist/test_book_a_call_live.d.ts +0 -2
  80. package/dist/test_book_a_call_live.d.ts.map +0 -1
  81. package/dist/test_book_a_call_live.js +0 -161
  82. package/dist/test_book_a_call_live.js.map +0 -1
  83. package/dist/test_brand_intelligence_endpoint.d.ts +0 -2
  84. package/dist/test_brand_intelligence_endpoint.d.ts.map +0 -1
  85. package/dist/test_brand_intelligence_endpoint.js +0 -20
  86. package/dist/test_brand_intelligence_endpoint.js.map +0 -1
  87. package/dist/test_byo_mcp_submission_clarity.d.ts +0 -2
  88. package/dist/test_byo_mcp_submission_clarity.d.ts.map +0 -1
  89. package/dist/test_byo_mcp_submission_clarity.js +0 -142
  90. package/dist/test_byo_mcp_submission_clarity.js.map +0 -1
  91. package/dist/test_capabilities_guidance.d.ts +0 -2
  92. package/dist/test_capabilities_guidance.d.ts.map +0 -1
  93. package/dist/test_capabilities_guidance.js +0 -372
  94. package/dist/test_capabilities_guidance.js.map +0 -1
  95. package/dist/test_client_provenance.d.ts +0 -2
  96. package/dist/test_client_provenance.d.ts.map +0 -1
  97. package/dist/test_client_provenance.js +0 -77
  98. package/dist/test_client_provenance.js.map +0 -1
  99. package/dist/test_connection_surface_cleanup.d.ts +0 -2
  100. package/dist/test_connection_surface_cleanup.d.ts.map +0 -1
  101. package/dist/test_connection_surface_cleanup.js +0 -203
  102. package/dist/test_connection_surface_cleanup.js.map +0 -1
  103. package/dist/test_consult_routing.d.ts +0 -2
  104. package/dist/test_consult_routing.d.ts.map +0 -1
  105. package/dist/test_consult_routing.js +0 -86
  106. package/dist/test_consult_routing.js.map +0 -1
  107. package/dist/test_consult_split.d.ts +0 -2
  108. package/dist/test_consult_split.d.ts.map +0 -1
  109. package/dist/test_consult_split.js +0 -30
  110. package/dist/test_consult_split.js.map +0 -1
  111. package/dist/test_coverage_relevance.d.ts +0 -12
  112. package/dist/test_coverage_relevance.d.ts.map +0 -1
  113. package/dist/test_coverage_relevance.js +0 -162
  114. package/dist/test_coverage_relevance.js.map +0 -1
  115. package/dist/test_coverage_replay.d.ts +0 -2
  116. package/dist/test_coverage_replay.d.ts.map +0 -1
  117. package/dist/test_coverage_replay.js +0 -170
  118. package/dist/test_coverage_replay.js.map +0 -1
  119. package/dist/test_dcr_and_legacy_deprecation.d.ts +0 -2
  120. package/dist/test_dcr_and_legacy_deprecation.d.ts.map +0 -1
  121. package/dist/test_dcr_and_legacy_deprecation.js +0 -143
  122. package/dist/test_dcr_and_legacy_deprecation.js.map +0 -1
  123. package/dist/test_demand_signal_logging.d.ts +0 -2
  124. package/dist/test_demand_signal_logging.d.ts.map +0 -1
  125. package/dist/test_demand_signal_logging.js +0 -92
  126. package/dist/test_demand_signal_logging.js.map +0 -1
  127. package/dist/test_earnings_activity_scope.d.ts +0 -5
  128. package/dist/test_earnings_activity_scope.d.ts.map +0 -1
  129. package/dist/test_earnings_activity_scope.js +0 -101
  130. package/dist/test_earnings_activity_scope.js.map +0 -1
  131. package/dist/test_earnings_divergence_live.d.ts +0 -5
  132. package/dist/test_earnings_divergence_live.d.ts.map +0 -1
  133. package/dist/test_earnings_divergence_live.js +0 -135
  134. package/dist/test_earnings_divergence_live.js.map +0 -1
  135. package/dist/test_earnings_hygiene_and_binding.d.ts +0 -5
  136. package/dist/test_earnings_hygiene_and_binding.d.ts.map +0 -1
  137. package/dist/test_earnings_hygiene_and_binding.js +0 -104
  138. package/dist/test_earnings_hygiene_and_binding.js.map +0 -1
  139. package/dist/test_exec_quotes_render.d.ts +0 -2
  140. package/dist/test_exec_quotes_render.d.ts.map +0 -1
  141. package/dist/test_exec_quotes_render.js +0 -86
  142. package/dist/test_exec_quotes_render.js.map +0 -1
  143. package/dist/test_expert_intro.d.ts +0 -2
  144. package/dist/test_expert_intro.d.ts.map +0 -1
  145. package/dist/test_expert_intro.js +0 -57
  146. package/dist/test_expert_intro.js.map +0 -1
  147. package/dist/test_expert_layer.d.ts +0 -11
  148. package/dist/test_expert_layer.d.ts.map +0 -1
  149. package/dist/test_expert_layer.js +0 -356
  150. package/dist/test_expert_layer.js.map +0 -1
  151. package/dist/test_financial_snapshot_render.d.ts +0 -2
  152. package/dist/test_financial_snapshot_render.d.ts.map +0 -1
  153. package/dist/test_financial_snapshot_render.js +0 -167
  154. package/dist/test_financial_snapshot_render.js.map +0 -1
  155. package/dist/test_gap_alert.d.ts +0 -12
  156. package/dist/test_gap_alert.d.ts.map +0 -1
  157. package/dist/test_gap_alert.js +0 -51
  158. package/dist/test_gap_alert.js.map +0 -1
  159. package/dist/test_grok_offering_surfaces.d.ts +0 -2
  160. package/dist/test_grok_offering_surfaces.d.ts.map +0 -1
  161. package/dist/test_grok_offering_surfaces.js +0 -155
  162. package/dist/test_grok_offering_surfaces.js.map +0 -1
  163. package/dist/test_human_agent_live.d.ts +0 -2
  164. package/dist/test_human_agent_live.d.ts.map +0 -1
  165. package/dist/test_human_agent_live.js +0 -48
  166. package/dist/test_human_agent_live.js.map +0 -1
  167. package/dist/test_identity_gap.d.ts +0 -2
  168. package/dist/test_identity_gap.d.ts.map +0 -1
  169. package/dist/test_identity_gap.js +0 -171
  170. package/dist/test_identity_gap.js.map +0 -1
  171. package/dist/test_intelligence_and_verifier.d.ts +0 -17
  172. package/dist/test_intelligence_and_verifier.d.ts.map +0 -1
  173. package/dist/test_intelligence_and_verifier.js +0 -162
  174. package/dist/test_intelligence_and_verifier.js.map +0 -1
  175. package/dist/test_latency_mcp_brief5.d.ts +0 -12
  176. package/dist/test_latency_mcp_brief5.d.ts.map +0 -1
  177. package/dist/test_latency_mcp_brief5.js +0 -200
  178. package/dist/test_latency_mcp_brief5.js.map +0 -1
  179. package/dist/test_limit_and_credit_card_capture.d.ts +0 -21
  180. package/dist/test_limit_and_credit_card_capture.d.ts.map +0 -1
  181. package/dist/test_limit_and_credit_card_capture.js +0 -216
  182. package/dist/test_limit_and_credit_card_capture.js.map +0 -1
  183. package/dist/test_list_graphs_topics.d.ts +0 -2
  184. package/dist/test_list_graphs_topics.d.ts.map +0 -1
  185. package/dist/test_list_graphs_topics.js +0 -26
  186. package/dist/test_list_graphs_topics.js.map +0 -1
  187. package/dist/test_live_deployed_mcp_search.d.ts +0 -2
  188. package/dist/test_live_deployed_mcp_search.d.ts.map +0 -1
  189. package/dist/test_live_deployed_mcp_search.js +0 -85
  190. package/dist/test_live_deployed_mcp_search.js.map +0 -1
  191. package/dist/test_live_deployed_split.d.ts +0 -2
  192. package/dist/test_live_deployed_split.d.ts.map +0 -1
  193. package/dist/test_live_deployed_split.js +0 -72
  194. package/dist/test_live_deployed_split.js.map +0 -1
  195. package/dist/test_live_endpoint_runner.d.ts +0 -2
  196. package/dist/test_live_endpoint_runner.d.ts.map +0 -1
  197. package/dist/test_live_endpoint_runner.js +0 -65
  198. package/dist/test_live_endpoint_runner.js.map +0 -1
  199. package/dist/test_live_mcp.d.ts +0 -2
  200. package/dist/test_live_mcp.d.ts.map +0 -1
  201. package/dist/test_live_mcp.js +0 -49
  202. package/dist/test_live_mcp.js.map +0 -1
  203. package/dist/test_live_neil_widget.d.ts +0 -2
  204. package/dist/test_live_neil_widget.d.ts.map +0 -1
  205. package/dist/test_live_neil_widget.js +0 -84
  206. package/dist/test_live_neil_widget.js.map +0 -1
  207. package/dist/test_local_list_graphs.d.ts +0 -2
  208. package/dist/test_local_list_graphs.d.ts.map +0 -1
  209. package/dist/test_local_list_graphs.js +0 -53
  210. package/dist/test_local_list_graphs.js.map +0 -1
  211. package/dist/test_longpoll_status.d.ts +0 -2
  212. package/dist/test_longpoll_status.d.ts.map +0 -1
  213. package/dist/test_longpoll_status.js +0 -117
  214. package/dist/test_longpoll_status.js.map +0 -1
  215. package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts +0 -2
  216. package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts.map +0 -1
  217. package/dist/test_mcp_find_expert_and_vocabulary_routing.js +0 -130
  218. package/dist/test_mcp_find_expert_and_vocabulary_routing.js.map +0 -1
  219. package/dist/test_next_moves.d.ts +0 -2
  220. package/dist/test_next_moves.d.ts.map +0 -1
  221. package/dist/test_next_moves.js +0 -714
  222. package/dist/test_next_moves.js.map +0 -1
  223. package/dist/test_next_moves_transcripts.d.ts +0 -2
  224. package/dist/test_next_moves_transcripts.d.ts.map +0 -1
  225. package/dist/test_next_moves_transcripts.js +0 -495
  226. package/dist/test_next_moves_transcripts.js.map +0 -1
  227. package/dist/test_nike_cmo_alias.d.ts +0 -2
  228. package/dist/test_nike_cmo_alias.d.ts.map +0 -1
  229. package/dist/test_nike_cmo_alias.js +0 -76
  230. package/dist/test_nike_cmo_alias.js.map +0 -1
  231. package/dist/test_on_request_caller_identity.d.ts +0 -13
  232. package/dist/test_on_request_caller_identity.d.ts.map +0 -1
  233. package/dist/test_on_request_caller_identity.js +0 -131
  234. package/dist/test_on_request_caller_identity.js.map +0 -1
  235. package/dist/test_on_request_intercept.d.ts +0 -10
  236. package/dist/test_on_request_intercept.d.ts.map +0 -1
  237. package/dist/test_on_request_intercept.js +0 -95
  238. package/dist/test_on_request_intercept.js.map +0 -1
  239. package/dist/test_onboarding_consent_and_errors.d.ts +0 -2
  240. package/dist/test_onboarding_consent_and_errors.d.ts.map +0 -1
  241. package/dist/test_onboarding_consent_and_errors.js +0 -110
  242. package/dist/test_onboarding_consent_and_errors.js.map +0 -1
  243. package/dist/test_onboarding_live_scenarios.d.ts +0 -2
  244. package/dist/test_onboarding_live_scenarios.d.ts.map +0 -1
  245. package/dist/test_onboarding_live_scenarios.js +0 -189
  246. package/dist/test_onboarding_live_scenarios.js.map +0 -1
  247. package/dist/test_referral_live.d.ts +0 -2
  248. package/dist/test_referral_live.d.ts.map +0 -1
  249. package/dist/test_referral_live.js +0 -33
  250. package/dist/test_referral_live.js.map +0 -1
  251. package/dist/test_referral_piers.d.ts +0 -2
  252. package/dist/test_referral_piers.d.ts.map +0 -1
  253. package/dist/test_referral_piers.js +0 -33
  254. package/dist/test_referral_piers.js.map +0 -1
  255. package/dist/test_report_editorial_briefing.d.ts +0 -21
  256. package/dist/test_report_editorial_briefing.d.ts.map +0 -1
  257. package/dist/test_report_editorial_briefing.js +0 -356
  258. package/dist/test_report_editorial_briefing.js.map +0 -1
  259. package/dist/test_search_graph_quality.d.ts +0 -2
  260. package/dist/test_search_graph_quality.d.ts.map +0 -1
  261. package/dist/test_search_graph_quality.js +0 -493
  262. package/dist/test_search_graph_quality.js.map +0 -1
  263. package/dist/test_service_to_service_auth.d.ts +0 -2
  264. package/dist/test_service_to_service_auth.d.ts.map +0 -1
  265. package/dist/test_service_to_service_auth.js +0 -199
  266. package/dist/test_service_to_service_auth.js.map +0 -1
  267. package/dist/test_session_next_moves_telemetry.d.ts +0 -2
  268. package/dist/test_session_next_moves_telemetry.d.ts.map +0 -1
  269. package/dist/test_session_next_moves_telemetry.js +0 -100
  270. package/dist/test_session_next_moves_telemetry.js.map +0 -1
  271. package/dist/test_skills.d.ts +0 -9
  272. package/dist/test_skills.d.ts.map +0 -1
  273. package/dist/test_skills.js +0 -267
  274. package/dist/test_skills.js.map +0 -1
  275. package/dist/test_sources_live.d.ts +0 -2
  276. package/dist/test_sources_live.d.ts.map +0 -1
  277. package/dist/test_sources_live.js +0 -30
  278. package/dist/test_sources_live.js.map +0 -1
  279. package/dist/test_specialist_and_analyst_tiers.d.ts +0 -2
  280. package/dist/test_specialist_and_analyst_tiers.d.ts.map +0 -1
  281. package/dist/test_specialist_and_analyst_tiers.js +0 -155
  282. package/dist/test_specialist_and_analyst_tiers.js.map +0 -1
  283. package/dist/test_token_resolution.d.ts +0 -2
  284. package/dist/test_token_resolution.d.ts.map +0 -1
  285. package/dist/test_token_resolution.js +0 -82
  286. package/dist/test_token_resolution.js.map +0 -1
  287. package/dist/test_v2_2_visual_hardening.d.ts +0 -5
  288. package/dist/test_v2_2_visual_hardening.d.ts.map +0 -1
  289. package/dist/test_v2_2_visual_hardening.js +0 -333
  290. package/dist/test_v2_2_visual_hardening.js.map +0 -1
  291. package/dist/test_verify_deprecate_render_spec.d.ts +0 -2
  292. package/dist/test_verify_deprecate_render_spec.d.ts.map +0 -1
  293. package/dist/test_verify_deprecate_render_spec.js +0 -217
  294. package/dist/test_verify_deprecate_render_spec.js.map +0 -1
  295. package/dist/test_verify_neil_demo.d.ts +0 -2
  296. package/dist/test_verify_neil_demo.d.ts.map +0 -1
  297. package/dist/test_verify_neil_demo.js +0 -149
  298. package/dist/test_verify_neil_demo.js.map +0 -1
  299. package/dist/test_waitable_research_and_grounded_links.d.ts +0 -2
  300. package/dist/test_waitable_research_and_grounded_links.d.ts.map +0 -1
  301. package/dist/test_waitable_research_and_grounded_links.js +0 -129
  302. package/dist/test_waitable_research_and_grounded_links.js.map +0 -1
  303. package/dist/test_widget_guards.d.ts +0 -2
  304. package/dist/test_widget_guards.d.ts.map +0 -1
  305. package/dist/test_widget_guards.js +0 -229
  306. package/dist/test_widget_guards.js.map +0 -1
  307. package/dist/test_zero_retention_alerts.d.ts +0 -14
  308. package/dist/test_zero_retention_alerts.d.ts.map +0 -1
  309. package/dist/test_zero_retention_alerts.js +0 -76
  310. package/dist/test_zero_retention_alerts.js.map +0 -1
  311. package/dist/verify_discoverability.d.ts +0 -2
  312. package/dist/verify_discoverability.d.ts.map +0 -1
  313. package/dist/verify_discoverability.js +0 -53
  314. package/dist/verify_discoverability.js.map +0 -1
@@ -12,38 +12,52 @@ import { fillBrandOneLiner, fillTrendFootprintIntro, fillMarketDataIntro, replac
12
12
  import { esc, FODDA_LOGO_URL } from './widgetShell.js';
13
13
  import { getDomainGraphIds } from './catalogCache.js';
14
14
  const PRESSURE_COLORS = {
15
- 'Direct competitor': { bg: '#FFF0E0', color: '#D97B2B', label: 'Direct competitor' },
15
+ 'Shared category': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Shared category' },
16
+ 'Appears alongside': { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' },
16
17
  'Adjacent signal': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Adjacent signal' },
17
18
  'Crossover mention': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover mention' },
18
19
  'Related brand': { bg: '#F5F0FF', color: '#663399', label: 'Related brand' },
19
- // Legacy labels (backward compat)
20
- 'Heritage challenger': { bg: '#FFF0E0', color: '#D97B2B', label: 'Heritage challenger' },
21
- 'Sibling challenger': { bg: '#FFF0E0', color: '#D97B2B', label: 'Sibling challenger' },
22
- 'Premium challenger': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Premium challenger' },
20
+ // Backward compat aliases mapped to neutral wording
21
+ 'Direct competitor': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Shared category' },
22
+ 'Heritage challenger': { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' },
23
+ 'Sibling challenger': { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' },
24
+ 'Premium challenger': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Shared category' },
23
25
  'Co-creation partner': { bg: '#EAF3DE', color: '#3A8F5C', label: 'Co-creation partner' },
24
26
  'Tech partner': { bg: '#EAF3DE', color: '#3A8F5C', label: 'Tech partner' },
25
27
  'Culture collaborator': { bg: '#FBEAF0', color: '#C94F7A', label: 'Culture collaborator' },
26
- 'Crossover threat': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover threat' },
27
- 'Category shadow': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Category shadow' },
28
+ 'Crossover threat': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover mention' },
29
+ 'Category shadow': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover mention' },
28
30
  };
29
- const DEFAULT_PRESSURE = { bg: '#FFF0E0', color: '#D97B2B', label: 'Competitor' };
30
- // Sector-aware pressure type assignment based on graph overlap
31
+ const DEFAULT_PRESSURE = { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' };
32
+ // Sector-aware pressure type assignment based on graph overlap or explicit API relationship_type
31
33
  function guessPressureType(_index, competitor, brandGraphIds, domainGraphIds) {
34
+ if (competitor?.relationship_type) {
35
+ const rel = String(competitor.relationship_type).toLowerCase();
36
+ if (rel === 'competitor' || rel === 'peer')
37
+ return 'Shared category';
38
+ if (rel === 'adjacent')
39
+ return 'Adjacent signal';
40
+ if (rel === 'partner')
41
+ return 'Co-creation partner';
42
+ if (rel === 'crossover')
43
+ return 'Crossover mention';
44
+ return 'Appears alongside';
45
+ }
32
46
  const compGraphArr = competitor?.graphIds || [];
33
47
  if (compGraphArr.length > 0 && brandGraphIds && brandGraphIds.size > 0) {
34
48
  const compGraphs = new Set(compGraphArr);
35
49
  const sharedGraphs = [...compGraphs].filter(g => brandGraphIds.has(g));
36
50
  if (sharedGraphs.length === 0)
37
51
  return 'Crossover mention';
38
- // Any shared DOMAIN graph = direct competitor (both in retail, both in beauty, etc.)
52
+ // Any shared DOMAIN graph = shared category (both in retail, both in beauty, etc.)
39
53
  const sharedDomain = domainGraphIds ? sharedGraphs.filter(g => domainGraphIds.has(g)) : [];
40
54
  if (sharedDomain.length > 0)
41
- return 'Direct competitor';
55
+ return 'Shared category';
42
56
  // Shared expert graph only = adjacent signal (co-mentioned in research, not same market)
43
57
  return 'Adjacent signal';
44
58
  }
45
- // Fallback: no graph data — conservative label
46
- return 'Related brand';
59
+ // Fallback: no graph data — conservative neutral label
60
+ return 'Appears alongside';
47
61
  }
48
62
  // ---------------------------------------------------------------------------
49
63
  // Network node orbit positions (fixed, up to 5 competitors)
@@ -165,6 +179,7 @@ function velocityClass(trend) {
165
179
  case 'accelerating': return { cls: 'vc-up', label: 'rising ↑' };
166
180
  case 'stable': return { cls: 'vc-steady', label: 'steady →' };
167
181
  case 'decelerating': return { cls: 'vc-slow', label: 'slowing ↓' };
182
+ case 'untracked': return { cls: 'vc-untracked', label: 'UNTRACKED' };
168
183
  default: return { cls: 'vc-build', label: 'building ↗' };
169
184
  }
170
185
  }
@@ -299,13 +314,16 @@ function formatStockPrice(priceWindow) {
299
314
  export async function renderBrandWidget(profile) {
300
315
  const brand = profile.brand || 'Brand';
301
316
  const trends = profile.trend_footprint || [];
302
- const evidence = profile.evidence_items || [];
303
- const competitors = profile.competitive_context?.co_occurring_brands || [];
317
+ const evidence = profile.evidence_items || profile.direct_evidence || [];
318
+ const competitors = (profile.competitive_context?.peers && profile.competitive_context.peers.length > 0)
319
+ ? profile.competitive_context.peers
320
+ : (profile.competitive_context?.co_occurring_brands || profile.competitive_context?.co_occurring || []);
304
321
  const crossGraph = profile.cross_graph_presence || [];
305
322
  const supplemental = profile.supplemental_signals || {};
306
323
  const lcDist = profile.summary?.lifecycle_distribution || {};
307
324
  const buildingCount = (lcDist.building || 0) + (lcDist.emerging || 0);
308
325
  const fadingCount = (lcDist.fading || 0) + (lcDist.mature || 0);
326
+ const isEmptyCoverage = profile.coverage?.status === 'empty' || (trends.length === 0 && evidence.length === 0);
309
327
  // Enrich trends with lifecycle if missing
310
328
  trends.forEach((t) => {
311
329
  if (!t.lifecycle || t.lifecycle === 'unknown') {
@@ -340,24 +358,29 @@ export async function renderBrandWidget(profile) {
340
358
  }
341
359
  });
342
360
  let rawVelocityTrend = 'stable';
343
- const totalWithMomentum = accCount + bldCount + stdCount + slowCount;
344
- if (totalWithMomentum > 0) {
345
- if ((accCount + bldCount) > slowCount && accCount >= bldCount && accCount > 0) {
346
- rawVelocityTrend = 'accelerating';
347
- }
348
- else if ((accCount + bldCount) > slowCount && bldCount > 0) {
349
- rawVelocityTrend = 'building';
350
- }
351
- else if (slowCount > (accCount + bldCount) && slowCount > stdCount) {
352
- rawVelocityTrend = 'decelerating';
361
+ if (isEmptyCoverage || trends.length === 0 || (profile.summary?.total_evidence_items || 0) === 0) {
362
+ rawVelocityTrend = 'untracked';
363
+ }
364
+ else {
365
+ const totalWithMomentum = accCount + bldCount + stdCount + slowCount;
366
+ if (totalWithMomentum > 0) {
367
+ if ((accCount + bldCount) > slowCount && accCount >= bldCount && accCount > 0) {
368
+ rawVelocityTrend = 'accelerating';
369
+ }
370
+ else if ((accCount + bldCount) > slowCount && bldCount > 0) {
371
+ rawVelocityTrend = 'building';
372
+ }
373
+ else if (slowCount > (accCount + bldCount) && slowCount > stdCount) {
374
+ rawVelocityTrend = 'decelerating';
375
+ }
376
+ else {
377
+ rawVelocityTrend = 'stable';
378
+ }
353
379
  }
354
380
  else {
355
- rawVelocityTrend = 'stable';
381
+ rawVelocityTrend = profile.summary?.evidence_velocity?.trend || 'stable';
356
382
  }
357
383
  }
358
- else {
359
- rawVelocityTrend = profile.summary?.evidence_velocity?.trend || 'stable';
360
- }
361
384
  const velocity = velocityClass(rawVelocityTrend);
362
385
  const timeline = profile.activity_timeline || [];
363
386
  // Build set of graphIds the target brand is present in (for sector-aware competitor labels)
@@ -586,19 +609,27 @@ export async function renderBrandWidget(profile) {
586
609
  const competitorListHtml = competitors.slice(0, 8).map((c, i) => {
587
610
  const pType = guessPressureType(i, c, brandGraphIds, domainGIds);
588
611
  const colors = PRESSURE_COLORS[pType] || DEFAULT_PRESSURE;
612
+ const bName = c.brand || c.name || '';
613
+ const count = c.co_occurrences ?? c.count ?? 1;
614
+ const countText = c.relationship_type === 'partner'
615
+ ? `Partner in ${count} initiative${count !== 1 ? 's' : ''}`
616
+ : `Appears alongside in ${count} evidence item${count !== 1 ? 's' : ''}`;
589
617
  return `<div class="cc">
590
618
  <div>
591
- <div class="cn">${esc(c.brand)}</div>
592
- <div class="cd">Co-occurs in ${c.co_occurrences} evidence item${c.co_occurrences !== 1 ? 's' : ''}</div>
619
+ <div class="cn">${esc(bName)}</div>
620
+ <div class="cd">${countText}</div>
593
621
  </div>
594
622
  <div class="ca">
595
623
  <span class="pb" style="background:${colors.bg};color:${colors.color};">${colors.label}</span>
596
- <button class="cv" onclick="sendPrompt('brand intelligence: ${esc(c.brand)}')">View ↗</button>
624
+ <button class="cv" onclick="sendPrompt('brand intelligence: ${esc(bName)}')">Explore ↗</button>
597
625
  </div>
598
626
  </div>`;
599
627
  }).join('\n');
600
628
  // ── Compare buttons ──
601
- const compareButtonsHtml = competitors.slice(0, 4).map((c) => `<button class="cp" onclick="sendPrompt('brand intelligence: ${esc(c.brand)}')">${esc(c.brand)} ↗</button>`).join('\n ');
629
+ const compareButtonsHtml = competitors.slice(0, 4).map((c) => {
630
+ const bName = c.brand || c.name || '';
631
+ return `<button class="cp" onclick="sendPrompt('brand intelligence: ${esc(bName)}')">${esc(bName)} ↗</button>`;
632
+ }).join('\n ');
602
633
  // ── Google Trends ──
603
634
  const gt = buildGoogleTrendsSVG(supplemental?.google_trends);
604
635
  const hasGoogleTrendsData = gt.polyline !== '0,88 300,88'; // false when API returned empty time-series
@@ -620,6 +651,18 @@ export async function renderBrandWidget(profile) {
620
651
  return true;
621
652
  }).sort((a, b) => (b.reviews || 0) - (a.reviews || 0));
622
653
  const amazonProductsHtml = products.slice(0, 4).map((p) => `<div class="ap"><div><div class="an2">${esc(p.name || p.title)}</div><div class="am2">${esc(p.price || '')}</div></div><div><div class="astar">${'★'.repeat(Math.round(p.rating || 0))}${'☆'.repeat(5 - Math.round(p.rating || 0))}</div><div class="am2">${(p.reviews || 0).toLocaleString()} reviews</div></div></div>`).join('\n');
654
+ const totalListings = amazon.product_count || 0;
655
+ const analyzed = amazon.products_analyzed || products.length || 0;
656
+ let amazonBasisSub = 'across listings';
657
+ if (analyzed > 0 && totalListings > analyzed) {
658
+ amazonBasisSub = `based on sample of ${analyzed} of ${totalListings.toLocaleString()} listings`;
659
+ }
660
+ else if (analyzed > 0) {
661
+ amazonBasisSub = `based on ${analyzed} listings`;
662
+ }
663
+ else if (totalListings > 0) {
664
+ amazonBasisSub = `based on ${totalListings.toLocaleString()} listings`;
665
+ }
623
666
  // ── Geographic bars ──
624
667
  const geoDist = profile.geographic_distribution || [];
625
668
  const geoMax = Math.max(...geoDist.map((g) => g.count || 0), 1);
@@ -646,29 +689,136 @@ export async function renderBrandWidget(profile) {
646
689
  if (!matchedSector) {
647
690
  matchedSector = censusSubs.find((c) => (c.name || '').toLowerCase().includes('non-store') || (c.name || '').toLowerCase().includes('e-commerce'));
648
691
  }
649
- const totalRetail = censusSnap?.total_retail_sales_millions;
650
- const totalMom = censusSnap?.month_over_month_change_pct;
692
+ const totalRetail = censusSnap?.headline_retail_and_food_services?.sales_millions || censusSnap?.total_retail_sales_millions || censusSnap?.latest_sales_m;
693
+ const totalMom = censusSnap?.headline_retail_and_food_services?.mom_change_pct ?? censusSnap?.month_over_month_change_pct ?? censusSnap?.mom_change_pct;
651
694
  const beaLabel = matchedSector ? esc(matchedSector.name).replace(/ Stores$/, '') : 'Retail sector';
652
695
  const beaValue = matchedSector?.sales_millions ? `$${(matchedSector.sales_millions / 1000).toFixed(1)}B` : '—';
653
696
  const beaSub = matchedSector?.mom_change_pct != null ? `${matchedSector.mom_change_pct > 0 ? '+' : ''}${matchedSector.mom_change_pct.toFixed(1)}% MoM` : '';
654
- const beaChangeLabel = 'Total US retail';
697
+ const beaChangeLabel = 'US retail & food';
655
698
  const beaChangeValue = totalRetail ? `$${(totalRetail / 1000).toFixed(0)}B` : '—';
656
699
  const beaChangeSub = totalMom != null ? `${totalMom > 0 ? '+' : ''}${totalMom.toFixed(1)}% MoM` : '';
700
+ // ── Conditional section HTML builders (Honour coverage status & data boundaries) ──
701
+ const emptyStateCalloutHtml = isEmptyCoverage
702
+ ? `<div class="ec" style="margin-top:1rem;padding:1.25rem;">
703
+ <div class="et" style="font-size:13px;font-weight:600;">Broaden your search</div>
704
+ <div class="ex" style="margin-top:6px;line-height:1.6;">Fodda does not currently track curated trend evidence specifically mentioning <strong>${esc(brand)}</strong>. Try exploring broader category trends in our domain graphs or consult a specialist practitioner for strategic perspective.</div>
705
+ </div>`
706
+ : '';
707
+ const caseStudiesSectionHtml = (!isEmptyCoverage && evidenceHtml)
708
+ ? `<div class="sec">Case Studies</div>\n${evidenceHtml}`
709
+ : '';
710
+ const trendsSectionHtml = (!isEmptyCoverage && (trendsHtml || weakSignalsHtml))
711
+ ? `<div class="sec">Relevant trends</div>
712
+ {{TREND_FOOTPRINT_INTRO}}
713
+ <div class="lcb">{{LIFECYCLE_BAR}}</div>
714
+ <div class="lcl">{{LIFECYCLE_LEGEND}}</div>
715
+ ${trendsHtml}
716
+ ${weakSignalsHtml}`
717
+ : '';
718
+ const competitiveSectionHtml = (!isEmptyCoverage && competitors.length > 0)
719
+ ? `<div class="sec">Co-occurring brands</div>\n${competitorListHtml}`
720
+ : '';
721
+ const earningsSectionHtml = (!isEmptyCoverage && earningsHtml)
722
+ ? `<div class="sl2">Quarterly Earnings & Wall Street Intelligence</div>
723
+ ${earningsHtml}
724
+ <p class="note">Source: Fodda Institutional Earnings Intelligence (SEC Filings & Executive Earnings Call Transcripts).</p>`
725
+ : '';
726
+ const financialSnapshotSectionHtml = (!isEmptyCoverage && financialSnapshotHtml)
727
+ ? `<div class="sl2">Institutional Financial Snapshot</div>
728
+ ${financialSnapshotHtml}
729
+ <p class="note">Source: Institutional SEC & Market Financial Filings.</p>`
730
+ : '';
731
+ // Supplemental section gating: never render no_signal / no_brand_match or untracked brands
732
+ const censusHasSignal = censusRaw?.data_status !== 'no_signal' && censusRaw?.data_status !== 'no_brand_match' && (totalRetail != null || matchedSector?.sales_millions != null);
733
+ const censusSectionHtml = (!isEmptyCoverage && censusHasSignal)
734
+ ? `<div class="sl2">US retail sales — Census Bureau (National market context)</div>
735
+ <div class="sg">
736
+ <div class="sk"><div class="skl">{{BEA_STAT_1_LABEL}}</div><div class="skv">{{BEA_STAT_1_VALUE}}</div><div class="sks">{{BEA_STAT_1_SUB}}</div></div>
737
+ <div class="sk"><div class="skl">{{BEA_STAT_2_LABEL}}</div><div class="skv">{{BEA_STAT_2_VALUE}}</div><div class="sks">{{BEA_STAT_2_SUB}}</div></div>
738
+ </div>
739
+ <p class="note">Macroeconomic context from the US Census Bureau. National retail volume, not brand-specific sales.</p>`
740
+ : '';
741
+ const gtHasSignal = supplemental?.google_trends?.data_status !== 'no_signal' && supplemental?.google_trends?.data_status !== 'no_brand_match' && hasGoogleTrendsData;
742
+ const gtSectionHtml = (!isEmptyCoverage && gtHasSignal) ? `<div class="sl2">${gt.description}</div>
743
+ <svg viewBox="0 0 300 96" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:96px;margin-bottom:4px;">
744
+ <defs>
745
+ <linearGradient id="gtg" x1="0" y1="0" x2="0" y2="1">
746
+ <stop offset="0%" stop-color="var(--fodda-accent, #663399)" stop-opacity=".18"/>
747
+ <stop offset="100%" stop-color="var(--fodda-accent, #663399)" stop-opacity="0"/>
748
+ </linearGradient>
749
+ </defs>
750
+ <polygon points="${gt.polygon}" fill="url(#gtg)"/>
751
+ <polyline points="${gt.polyline}" fill="none" stroke="var(--fodda-accent, #663399)" stroke-width="1.5" stroke-linejoin="round"/>
752
+ <line x1="0" y1="88" x2="300" y2="88" stroke="var(--color-border-tertiary)" stroke-width=".5"/>
753
+ <text x="2" y="93" font-size="7" fill="var(--color-text-secondary)" font-family="monospace">${gt.startDateLabel}</text>
754
+ <text x="150" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="middle" font-family="monospace">${gt.peakLabel}</text>
755
+ <text x="298" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="end" font-family="monospace">${gt.nowLabel}</text>
756
+ <circle cx="${gt.peakX}" cy="${gt.peakY}" r="2" fill="var(--fodda-accent, #663399)"/>
757
+ <circle cx="300" cy="${gt.annY}" r="2.5" fill="var(--fodda-accent, #663399)"/>
758
+ <text x="${gt.annX}" y="${gt.annY}" font-size="7" fill="var(--fodda-accent, #663399)" text-anchor="end" font-family="monospace">${gt.annText}</text>
759
+ </svg>
760
+ <p class="note">{{GT_CAPTION}}</p>` : '';
761
+ const gtComparisonSectionHtml = (!isEmptyCoverage && gtHasSignal && gt.comparisonBarsHtml) ? `<div class="sl2">Search interest — brand vs co-occurring brands</div>
762
+ ${gt.comparisonBarsHtml}` : '';
763
+ const relatedQueriesSectionHtml = (!isEmptyCoverage && gtHasSignal && gt.relatedQueriesHtml) ? `<div class="sl2">Top related queries</div>
764
+ <div class="rq">${gt.relatedQueriesHtml}</div>
765
+ <p class="note">{{RELATED_QUERIES_NOTE}}</p>` : '';
766
+ const wikiHasSignal = rawWiki?.data_status !== 'no_signal' && rawWiki?.data_status !== 'no_brand_match' && wikiData.length > 0;
767
+ const wikiSectionHtml = (!isEmptyCoverage && wikiHasSignal)
768
+ ? `<div class="sl2">Wikipedia — avg daily pageviews</div>
769
+ ${wikiBarsHtml}
770
+ <p class="note">{{WIKI_NOTE}}</p>`
771
+ : '';
772
+ const amazonHasSignal = amazonRaw?.data_status !== 'no_signal' && amazonRaw?.data_status !== 'no_brand_match' && (totalListings > 0 || products.length > 0);
773
+ const amazonSectionHtml = (!isEmptyCoverage && amazonHasSignal)
774
+ ? `<div class="sl2">Amazon footprint</div>
775
+ <div class="sg">
776
+ <div class="sk"><div class="skl">{{AMAZON_STAT_1_LABEL}}</div><div class="skv">{{AMAZON_STAT_1_VALUE}}</div><div class="sks">{{AMAZON_STAT_1_SUB}}</div></div>
777
+ <div class="sk"><div class="skl">{{AMAZON_STAT_2_LABEL}}</div><div class="skv">{{AMAZON_STAT_2_VALUE}}</div><div class="sks">{{AMAZON_STAT_2_SUB}}</div></div>
778
+ <div class="sk"><div class="skl">{{AMAZON_STAT_3_LABEL}}</div><div class="skv">{{AMAZON_STAT_3_VALUE}}</div><div class="sks">{{AMAZON_STAT_3_SUB}}</div></div>
779
+ <div class="sk"><div class="skl">{{AMAZON_STAT_4_LABEL}}</div><div class="skv">{{AMAZON_STAT_4_VALUE}}</div><div class="sks">{{AMAZON_STAT_4_SUB}}</div></div>
780
+ </div>
781
+ ${amazonProductsHtml}
782
+ <p class="note">{{AMAZON_CAPTION}}</p>`
783
+ : '';
784
+ const geoSectionHtml = (!isEmptyCoverage && geoBarsHtml)
785
+ ? `<div class="sl2">Geographic spread</div>
786
+ ${geoBarsHtml}`
787
+ : '';
788
+ const marketDataItems = [
789
+ censusSectionHtml,
790
+ gtSectionHtml,
791
+ gtComparisonSectionHtml,
792
+ relatedQueriesSectionHtml,
793
+ wikiSectionHtml,
794
+ amazonSectionHtml,
795
+ geoSectionHtml,
796
+ ].filter(Boolean);
797
+ const marketDataSectionHtml = (!isEmptyCoverage && marketDataItems.length > 0)
798
+ ? `<div class="sec">Market data</div>
799
+ {{MARKET_DATA_INTRO}}
800
+ ${marketDataItems.join('\n')}`
801
+ : '';
657
802
  // ── Source pills ──
658
803
  const graphNames = crossGraph.map((g) => g.graphName).filter(Boolean);
659
804
  const sourcePills = [
660
805
  ...graphNames.map((n) => `<span class="gp">${esc(n)}</span>`),
661
- (earningsSource === 'truth_layer' || earnings.length > 0) ? '<span class="gp">Earnings Intelligence</span>' : '',
662
- (marketData && marketData.data_status !== 'no_signal') ? '<span class="gp">Financial Snapshot</span>' : '',
663
- hasGoogleTrendsData ? '<span class="gp">Google Trends</span>' : '',
664
- supplemental?.wikipedia ? '<span class="gp">Wikipedia</span>' : '',
665
- supplemental?.amazon ? '<span class="gp">Amazon</span>' : '',
666
- supplemental?.census_retail ? '<span class="gp">US Census</span>' : '',
806
+ (!isEmptyCoverage && (earningsSource === 'truth_layer' || earnings.length > 0)) ? '<span class="gp">Earnings Intelligence</span>' : '',
807
+ (!isEmptyCoverage && marketData && marketData.data_status !== 'no_signal') ? '<span class="gp">Financial Snapshot</span>' : '',
808
+ (!isEmptyCoverage && gtHasSignal) ? '<span class="gp">Google Trends</span>' : '',
809
+ (!isEmptyCoverage && wikiHasSignal) ? '<span class="gp">Wikipedia</span>' : '',
810
+ (!isEmptyCoverage && amazonHasSignal) ? '<span class="gp">Amazon</span>' : '',
811
+ (!isEmptyCoverage && censusHasSignal) ? '<span class="gp">US Census</span>' : '',
667
812
  ].filter(Boolean).join('\n ');
668
813
  // ── Export slots (data-driven labels) ──
669
- const exportLabels = [
814
+ const exportLabels = isEmptyCoverage ? [
815
+ { label: 'Category brief', desc: `Explore market dynamics in ${brand}'s category`, prompt: `Write category brief for ${brand}'s market` },
816
+ { label: 'Find specialist', desc: `Consult experts covering this space`, prompt: `Find specialists who cover ${brand}'s category` },
817
+ { label: 'Category signals', desc: `Emerging opportunities in this sector`, prompt: `Emerging signals for ${brand}'s category` },
818
+ { label: 'Market research', desc: `Broad research across retail & consumer graphs`, prompt: `Research trends in ${brand}'s industry` },
819
+ ] : [
670
820
  { label: 'Editorial brief', desc: `Strategic analysis of ${brand}'s innovation position`, prompt: `Write editorial brief for ${brand}` },
671
- { label: 'Competitor comparison', desc: `Head-to-head with ${competitors[0]?.brand || 'top rival'}`, prompt: `Compare ${brand} vs ${competitors[0]?.brand || 'competitor'}` },
821
+ { label: 'Co-occurring landscape', desc: `Analysis of brands appearing alongside ${brand}`, prompt: `Compare brands appearing alongside ${brand}` },
672
822
  { label: 'Weak signal forecast', desc: `Emerging opportunities on ${brand}'s horizon`, prompt: `Weak signal forecast for ${brand}` },
673
823
  { label: 'Steal this idea', desc: `Actionable concepts from ${brand}'s playbook`, prompt: `Steal this idea from ${brand}` },
674
824
  ];
@@ -686,38 +836,19 @@ export async function renderBrandWidget(profile) {
686
836
  'TRENDS_HTML': trendsHtml,
687
837
  'WEAK_SIGNALS_HTML': weakSignalsHtml,
688
838
  'EVIDENCE_HTML': evidenceHtml,
689
- 'COMPETITIVE_SECTION_HTML': competitors.length > 0 ? `<div class="sec">Competitive</div>\n${competitorListHtml}` : '',
690
- 'GT_SECTION_HTML': hasGoogleTrendsData ? `<div class="sl2">${gt.description}</div>
691
- <svg viewBox="0 0 300 96" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:96px;margin-bottom:4px;">
692
- <defs>
693
- <linearGradient id="gtg" x1="0" y1="0" x2="0" y2="1">
694
- <stop offset="0%" stop-color="var(--fodda-accent, #663399)" stop-opacity=".18"/>
695
- <stop offset="100%" stop-color="var(--fodda-accent, #663399)" stop-opacity="0"/>
696
- </linearGradient>
697
- </defs>
698
- <polygon points="${gt.polygon}" fill="url(#gtg)"/>
699
- <polyline points="${gt.polyline}" fill="none" stroke="var(--fodda-accent, #663399)" stroke-width="1.5" stroke-linejoin="round"/>
700
- <line x1="0" y1="88" x2="300" y2="88" stroke="var(--color-border-tertiary)" stroke-width=".5"/>
701
- <text x="2" y="93" font-size="7" fill="var(--color-text-secondary)" font-family="monospace">${gt.startDateLabel}</text>
702
- <text x="150" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="middle" font-family="monospace">${gt.peakLabel}</text>
703
- <text x="298" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="end" font-family="monospace">${gt.nowLabel}</text>
704
- <circle cx="${gt.peakX}" cy="${gt.peakY}" r="2" fill="var(--fodda-accent, #663399)"/>
705
- <circle cx="300" cy="${gt.annY}" r="2.5" fill="var(--fodda-accent, #663399)"/>
706
- <text x="${gt.annX}" y="${gt.annY}" font-size="7" fill="var(--fodda-accent, #663399)" text-anchor="end" font-family="monospace">${gt.annText}</text>
707
- </svg>
708
- <p class="note">{{GT_CAPTION}}</p>` : '',
709
- 'GT_COMPARISON_SECTION_HTML': gt.comparisonBarsHtml ? `<div class="sl2">Search interest — brand vs competitors</div>
710
- ${gt.comparisonBarsHtml}` : '',
711
- 'RELATED_QUERIES_SECTION_HTML': gt.relatedQueriesHtml ? `<div class="sl2">Top related queries</div>
712
- <div class="rq">${gt.relatedQueriesHtml}</div>
713
- <p class="note">{{RELATED_QUERIES_NOTE}}</p>` : '',
714
- 'WIKI_BARS_HTML': wikiBarsHtml,
839
+ 'EMPTY_STATE_CALLOUT_HTML': emptyStateCalloutHtml,
840
+ 'CASE_STUDIES_SECTION_HTML': caseStudiesSectionHtml,
841
+ 'TRENDS_SECTION_HTML': trendsSectionHtml,
842
+ 'COMPETITIVE_SECTION_HTML': competitiveSectionHtml,
843
+ 'MARKET_DATA_SECTION_HTML': marketDataSectionHtml,
844
+ 'EARNINGS_SECTION_HTML': earningsSectionHtml,
845
+ 'FINANCIAL_SNAPSHOT_SECTION_HTML': financialSnapshotSectionHtml,
715
846
  'AMAZON_STAT_1_LABEL': 'Listings',
716
847
  'AMAZON_STAT_1_VALUE': (amazon.product_count || 0).toLocaleString(),
717
848
  'AMAZON_STAT_1_SUB': 'total listings',
718
849
  'AMAZON_STAT_2_LABEL': 'Median price',
719
850
  'AMAZON_STAT_2_VALUE': amazon?.price_range?.median || amazon?.median_price || '—',
720
- 'AMAZON_STAT_2_SUB': 'across all listings',
851
+ 'AMAZON_STAT_2_SUB': amazonBasisSub,
721
852
  'AMAZON_STAT_3_LABEL': 'Avg rating',
722
853
  'AMAZON_STAT_3_VALUE': String(amazon?.average_rating || '—'),
723
854
  'AMAZON_STAT_3_SUB': 'product average',
@@ -725,7 +856,7 @@ export async function renderBrandWidget(profile) {
725
856
  'AMAZON_STAT_4_VALUE': products[0] ? (products[0].reviews || 0).toLocaleString() : '—',
726
857
  'AMAZON_STAT_4_SUB': products[0] ? esc(products[0].name || products[0].title || '') : '',
727
858
  'AMAZON_PRODUCTS_HTML': amazonProductsHtml,
728
- 'AMAZON_CAPTION': 'Snapshot only. Source: Amazon.',
859
+ 'AMAZON_CAPTION': 'Snapshot sample only. Not a full catalogue census. Source: Amazon.',
729
860
  'BEA_STAT_1_LABEL': beaLabel,
730
861
  'BEA_STAT_1_VALUE': beaValue,
731
862
  'BEA_STAT_1_SUB': beaSub,
@@ -734,12 +865,6 @@ export async function renderBrandWidget(profile) {
734
865
  'BEA_STAT_2_SUB': beaChangeSub,
735
866
  'GEO_SECTION_HTML': geoBarsHtml ? `<div class="sl2">Geographic spread</div>
736
867
  ${geoBarsHtml}` : '',
737
- 'EARNINGS_SECTION_HTML': earningsHtml ? `<div class="sl2">Quarterly Earnings & Wall Street Intelligence</div>
738
- ${earningsHtml}
739
- <p class="note">Source: Fodda Institutional Earnings Intelligence (SEC Filings & Executive Earnings Call Transcripts).</p>` : '',
740
- 'FINANCIAL_SNAPSHOT_SECTION_HTML': financialSnapshotHtml ? `<div class="sl2">Institutional Financial Snapshot</div>
741
- ${financialSnapshotHtml}
742
- <p class="note">Source: Institutional SEC & Market Financial Filings.</p>` : '',
743
868
  'SOURCE_PILLS_HTML': sourcePills,
744
869
  'EXPORT_1_LABEL': exportLabels[0].label,
745
870
  'EXPORT_1_DESC': exportLabels[0].desc,
@@ -758,11 +883,16 @@ export async function renderBrandWidget(profile) {
758
883
  const fills = { ...slotFills };
759
884
  // ── Parallel Gemini fills: one-liner + section intros ──
760
885
  const [oneLiner, trendIntro, marketIntro] = await Promise.allSettled([
761
- fillBrandOneLiner(brand, trends, velocity.label, competitors.slice(0, 3).map((c) => c.brand)),
886
+ fillBrandOneLiner(brand, trends, velocity.label, competitors.slice(0, 3).map((c) => c.brand || c.name || '')),
762
887
  fillTrendFootprintIntro(brand, trends, profile.summary?.lifecycle_distribution || {}),
763
888
  fillMarketDataIntro(brand, supplemental),
764
889
  ]);
765
- fills['ONE_LINER'] = oneLiner.status === 'fulfilled' ? oneLiner.value : `${brand} is ${velocity.label} across ${trends.length} trend${trends.length !== 1 ? 's' : ''}.`;
890
+ if (isEmptyCoverage || trends.length === 0 || evidence.length === 0) {
891
+ fills['ONE_LINER'] = `Fodda has no curated trend evidence mentioning ${brand} yet.`;
892
+ }
893
+ else {
894
+ fills['ONE_LINER'] = oneLiner.status === 'fulfilled' ? oneLiner.value : `${brand} is ${velocity.label} across ${trends.length} trend${trends.length !== 1 ? 's' : ''}.`;
895
+ }
766
896
  const trendIntroText = trendIntro.status === 'fulfilled' ? trendIntro.value : '';
767
897
  fills['TREND_FOOTPRINT_INTRO'] = trendIntroText ? `<p class="si">${esc(trendIntroText)}</p>` : '';
768
898
  const marketIntroText = marketIntro.status === 'fulfilled' ? marketIntro.value : '';
@@ -775,9 +905,16 @@ export async function renderBrandWidget(profile) {
775
905
  fills['RELATED_QUERIES_NOTE'] = supplemental?.google_trends?.related_queries?.length ? `Top related searches when people look for ${brand}.` : '';
776
906
  fills['WIKI_NOTE'] = wikiData.length > 0 ? `Daily Wikipedia pageviews for ${brand}-related articles. Higher = more cultural attention.` : '';
777
907
  const safeBrandName = esc(brand).replace(/'/g, "\\'");
778
- const safeCompName = competitors[0]?.brand ? esc(competitors[0].brand).replace(/'/g, "\\'") : 'top rival';
779
- fills['SUGGESTED_NEXT_HTML'] = `<div style="display:flex;flex-wrap:wrap;gap:6px;">
780
- <button class="btn-out" onclick="sendPrompt('What are competitors of ${safeBrandName} doing differently?')">Competitive landscape</button>
908
+ const comp0 = competitors[0]?.brand || competitors[0]?.name;
909
+ const safeCompName = comp0 ? esc(comp0).replace(/'/g, "\\'") : 'top rival';
910
+ fills['SUGGESTED_NEXT_HTML'] = isEmptyCoverage
911
+ ? `<div style="display:flex;flex-wrap:wrap;gap:6px;">
912
+ <button class="btn-out" onclick="sendPrompt('Explore broader trends in ${safeBrandName}\\'s category')">Category trends</button>
913
+ <button class="btn-out" onclick="sendPrompt('Find domain specialists who cover ${safeBrandName}\\'s space')">Find expert</button>
914
+ <button class="btn-out" onclick="sendPrompt('Search market research on ${safeBrandName}')">Market research</button>
915
+ </div>`
916
+ : `<div style="display:flex;flex-wrap:wrap;gap:6px;">
917
+ <button class="btn-out" onclick="sendPrompt('What are other brands alongside ${safeBrandName} doing differently?')">Category landscape</button>
781
918
  <button class="btn-out" onclick="sendPrompt('Show me the evidence behind the strongest trend for ${safeBrandName}')">Deep dive</button>
782
919
  <button class="btn-out" onclick="sendPrompt('Compare ${safeBrandName} vs ${safeCompName}')">Head-to-head</button>
783
920
  </div>`;
@@ -846,6 +983,7 @@ export const TEMPLATE = `
846
983
  .vc-build{color:var(--color-text-info);}
847
984
  .vc-steady{color:var(--color-text-secondary);}
848
985
  .vc-slow{color:var(--color-text-warning);}
986
+ .vc-untracked{color:var(--color-text-secondary);opacity:0.7;}
849
987
  .pv{font-size:14px;font-family:Georgia,serif;font-style:italic;font-weight:400;line-height:1.65;padding:14px 16px;background:var(--pl);border:1px solid var(--p);border-left-width:3px;border-radius:4px;margin-bottom:1.25rem;color:var(--color-text-primary);}
850
988
  .lcb{display:flex;height:8px;border-radius:4px;overflow:hidden;margin-bottom:.75rem;gap:2px;}
851
989
  .lcl{display:flex;gap:12px;margin-bottom:1.25rem;flex-wrap:wrap;}
@@ -940,48 +1078,18 @@ export const TEMPLATE = `
940
1078
 
941
1079
  <div class="pv">{{ONE_LINER}}</div>
942
1080
 
943
- <div class="sec">Case Studies</div>
944
- {{EVIDENCE_HTML}}
1081
+ {{EMPTY_STATE_CALLOUT_HTML}}
1082
+
1083
+ {{CASE_STUDIES_SECTION_HTML}}
945
1084
 
946
1085
  {{EARNINGS_SECTION_HTML}}
947
1086
  {{FINANCIAL_SNAPSHOT_SECTION_HTML}}
948
1087
 
949
- <div class="sec">Relevant trends</div>
950
- {{TREND_FOOTPRINT_INTRO}}
951
- <div class="lcb">{{LIFECYCLE_BAR}}</div>
952
- <div class="lcl">{{LIFECYCLE_LEGEND}}</div>
953
- {{TRENDS_HTML}}
954
- {{WEAK_SIGNALS_HTML}}
1088
+ {{TRENDS_SECTION_HTML}}
955
1089
 
956
1090
  {{COMPETITIVE_SECTION_HTML}}
957
1091
 
958
- <div class="sec">Market data</div>
959
- {{MARKET_DATA_INTRO}}
960
- <div class="sl2">US retail sales — Census Bureau</div>
961
- <div class="sg">
962
- <div class="sk"><div class="skl">{{BEA_STAT_1_LABEL}}</div><div class="skv">{{BEA_STAT_1_VALUE}}</div><div class="sks">{{BEA_STAT_1_SUB}}</div></div>
963
- <div class="sk"><div class="skl">{{BEA_STAT_2_LABEL}}</div><div class="skv">{{BEA_STAT_2_VALUE}}</div><div class="sks">{{BEA_STAT_2_SUB}}</div></div>
964
- </div>
965
-
966
- {{GT_SECTION_HTML}}
967
- {{GT_COMPARISON_SECTION_HTML}}
968
- {{RELATED_QUERIES_SECTION_HTML}}
969
-
970
- <div class="sl2">Wikipedia — avg daily pageviews</div>
971
- {{WIKI_BARS_HTML}}
972
- <p class="note">{{WIKI_NOTE}}</p>
973
-
974
- <div class="sl2">Amazon footprint</div>
975
- <div class="sg">
976
- <div class="sk"><div class="skl">{{AMAZON_STAT_1_LABEL}}</div><div class="skv">{{AMAZON_STAT_1_VALUE}}</div><div class="sks">{{AMAZON_STAT_1_SUB}}</div></div>
977
- <div class="sk"><div class="skl">{{AMAZON_STAT_2_LABEL}}</div><div class="skv">{{AMAZON_STAT_2_VALUE}}</div><div class="sks">{{AMAZON_STAT_2_SUB}}</div></div>
978
- <div class="sk"><div class="skl">{{AMAZON_STAT_3_LABEL}}</div><div class="skv">{{AMAZON_STAT_3_VALUE}}</div><div class="sks">{{AMAZON_STAT_3_SUB}}</div></div>
979
- <div class="sk"><div class="skl">{{AMAZON_STAT_4_LABEL}}</div><div class="skv">{{AMAZON_STAT_4_VALUE}}</div><div class="sks">{{AMAZON_STAT_4_SUB}}</div></div>
980
- </div>
981
- {{AMAZON_PRODUCTS_HTML}}
982
- <p class="note">{{AMAZON_CAPTION}}</p>
983
-
984
- {{GEO_SECTION_HTML}}
1092
+ {{MARKET_DATA_SECTION_HTML}}
985
1093
 
986
1094
  <div class="sec">Explore further</div>
987
1095
  {{SUGGESTED_NEXT_HTML}}