fodda-mcp 1.38.0 → 1.46.97

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 (346) hide show
  1. package/README.md +14 -91
  2. package/dist/agents/fodda-researcher/skills.d.ts +1 -1
  3. package/dist/agents/fodda-researcher/skills.d.ts.map +1 -1
  4. package/dist/agents/fodda-researcher/skills.js +1 -1
  5. package/dist/brandTemplate.d.ts +1 -1
  6. package/dist/brandTemplate.d.ts.map +1 -1
  7. package/dist/brandTemplate.js +278 -83
  8. package/dist/brandTemplate.js.map +1 -1
  9. package/dist/catalogCache.d.ts +39 -0
  10. package/dist/catalogCache.d.ts.map +1 -1
  11. package/dist/catalogCache.js +293 -22
  12. package/dist/catalogCache.js.map +1 -1
  13. package/dist/coverageRelevance.d.ts +251 -0
  14. package/dist/coverageRelevance.d.ts.map +1 -0
  15. package/dist/coverageRelevance.js +1857 -0
  16. package/dist/coverageRelevance.js.map +1 -0
  17. package/dist/editorialFill.d.ts +5 -0
  18. package/dist/editorialFill.d.ts.map +1 -1
  19. package/dist/editorialFill.js +1 -1
  20. package/dist/editorialFill.js.map +1 -1
  21. package/dist/enrichment.d.ts +26 -0
  22. package/dist/enrichment.d.ts.map +1 -1
  23. package/dist/enrichment.js +442 -29
  24. package/dist/enrichment.js.map +1 -1
  25. package/dist/errorHandling.d.ts +35 -7
  26. package/dist/errorHandling.d.ts.map +1 -1
  27. package/dist/errorHandling.js +225 -155
  28. package/dist/errorHandling.js.map +1 -1
  29. package/dist/index.d.ts +25 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +411 -106
  32. package/dist/index.js.map +1 -1
  33. package/dist/pricingCache.d.ts.map +1 -1
  34. package/dist/pricingCache.js +55 -3
  35. package/dist/pricingCache.js.map +1 -1
  36. package/dist/queryCache.d.ts.map +1 -1
  37. package/dist/queryCache.js +2 -0
  38. package/dist/queryCache.js.map +1 -1
  39. package/dist/reportBriefing.d.ts +89 -0
  40. package/dist/reportBriefing.d.ts.map +1 -0
  41. package/dist/reportBriefing.js +662 -0
  42. package/dist/reportBriefing.js.map +1 -0
  43. package/dist/searchTemplate.d.ts.map +1 -1
  44. package/dist/searchTemplate.js +65 -53
  45. package/dist/searchTemplate.js.map +1 -1
  46. package/dist/services/graphRouter.d.ts +7 -0
  47. package/dist/services/graphRouter.d.ts.map +1 -0
  48. package/dist/services/graphRouter.js +7 -0
  49. package/dist/services/graphRouter.js.map +1 -0
  50. package/dist/services/notifications.d.ts +2 -2
  51. package/dist/services/notifications.js +3 -3
  52. package/dist/services/notifications.js.map +1 -1
  53. package/dist/sessionTracker.d.ts +42 -3
  54. package/dist/sessionTracker.d.ts.map +1 -1
  55. package/dist/sessionTracker.js +261 -14
  56. package/dist/sessionTracker.js.map +1 -1
  57. package/dist/skillClient.d.ts.map +1 -1
  58. package/dist/skillClient.js +11 -6
  59. package/dist/skillClient.js.map +1 -1
  60. package/dist/svgVisuals.d.ts +17 -19
  61. package/dist/svgVisuals.d.ts.map +1 -1
  62. package/dist/svgVisuals.js +189 -233
  63. package/dist/svgVisuals.js.map +1 -1
  64. package/dist/systemPrompt.d.ts +7 -2
  65. package/dist/systemPrompt.d.ts.map +1 -1
  66. package/dist/systemPrompt.js +153 -81
  67. package/dist/systemPrompt.js.map +1 -1
  68. package/dist/telemetry.d.ts.map +1 -1
  69. package/dist/telemetry.js +15 -0
  70. package/dist/telemetry.js.map +1 -1
  71. package/dist/test_adjacent_trends_polymorphic.d.ts +2 -0
  72. package/dist/test_adjacent_trends_polymorphic.d.ts.map +1 -0
  73. package/dist/test_adjacent_trends_polymorphic.js +154 -0
  74. package/dist/test_adjacent_trends_polymorphic.js.map +1 -0
  75. package/dist/test_book_a_call_live.d.ts +2 -0
  76. package/dist/test_book_a_call_live.d.ts.map +1 -0
  77. package/dist/test_book_a_call_live.js +161 -0
  78. package/dist/test_book_a_call_live.js.map +1 -0
  79. package/dist/test_brand_intelligence_endpoint.js +2 -2
  80. package/dist/test_byo_mcp_submission_clarity.d.ts +2 -0
  81. package/dist/test_byo_mcp_submission_clarity.d.ts.map +1 -0
  82. package/dist/test_byo_mcp_submission_clarity.js +142 -0
  83. package/dist/test_byo_mcp_submission_clarity.js.map +1 -0
  84. package/dist/test_capabilities_guidance.d.ts +2 -0
  85. package/dist/test_capabilities_guidance.d.ts.map +1 -0
  86. package/dist/test_capabilities_guidance.js +372 -0
  87. package/dist/test_capabilities_guidance.js.map +1 -0
  88. package/dist/test_client_provenance.d.ts +2 -0
  89. package/dist/test_client_provenance.d.ts.map +1 -0
  90. package/dist/test_client_provenance.js +77 -0
  91. package/dist/test_client_provenance.js.map +1 -0
  92. package/dist/test_connection_surface_cleanup.d.ts +2 -0
  93. package/dist/test_connection_surface_cleanup.d.ts.map +1 -0
  94. package/dist/test_connection_surface_cleanup.js +203 -0
  95. package/dist/test_connection_surface_cleanup.js.map +1 -0
  96. package/dist/test_consult_routing.d.ts +2 -0
  97. package/dist/test_consult_routing.d.ts.map +1 -0
  98. package/dist/test_consult_routing.js +86 -0
  99. package/dist/test_consult_routing.js.map +1 -0
  100. package/dist/test_consult_split.d.ts +2 -0
  101. package/dist/test_consult_split.d.ts.map +1 -0
  102. package/dist/test_consult_split.js +30 -0
  103. package/dist/test_consult_split.js.map +1 -0
  104. package/dist/test_coverage_relevance.d.ts +12 -0
  105. package/dist/test_coverage_relevance.d.ts.map +1 -0
  106. package/dist/test_coverage_relevance.js +162 -0
  107. package/dist/test_coverage_relevance.js.map +1 -0
  108. package/dist/test_coverage_replay.d.ts +2 -0
  109. package/dist/test_coverage_replay.d.ts.map +1 -0
  110. package/dist/test_coverage_replay.js +170 -0
  111. package/dist/test_coverage_replay.js.map +1 -0
  112. package/dist/test_dcr_and_legacy_deprecation.d.ts +2 -0
  113. package/dist/test_dcr_and_legacy_deprecation.d.ts.map +1 -0
  114. package/dist/test_dcr_and_legacy_deprecation.js +143 -0
  115. package/dist/test_dcr_and_legacy_deprecation.js.map +1 -0
  116. package/dist/test_demand_signal_logging.d.ts +2 -0
  117. package/dist/test_demand_signal_logging.d.ts.map +1 -0
  118. package/dist/test_demand_signal_logging.js +92 -0
  119. package/dist/test_demand_signal_logging.js.map +1 -0
  120. package/dist/test_earnings_activity_scope.d.ts +5 -0
  121. package/dist/test_earnings_activity_scope.d.ts.map +1 -0
  122. package/dist/test_earnings_activity_scope.js +101 -0
  123. package/dist/test_earnings_activity_scope.js.map +1 -0
  124. package/dist/test_earnings_divergence_live.d.ts +5 -0
  125. package/dist/test_earnings_divergence_live.d.ts.map +1 -0
  126. package/dist/test_earnings_divergence_live.js +135 -0
  127. package/dist/test_earnings_divergence_live.js.map +1 -0
  128. package/dist/test_earnings_hygiene_and_binding.d.ts +5 -0
  129. package/dist/test_earnings_hygiene_and_binding.d.ts.map +1 -0
  130. package/dist/test_earnings_hygiene_and_binding.js +104 -0
  131. package/dist/test_earnings_hygiene_and_binding.js.map +1 -0
  132. package/dist/test_exec_quotes_render.d.ts +2 -0
  133. package/dist/test_exec_quotes_render.d.ts.map +1 -0
  134. package/dist/test_exec_quotes_render.js +86 -0
  135. package/dist/test_exec_quotes_render.js.map +1 -0
  136. package/dist/test_expert_intro.d.ts +2 -0
  137. package/dist/test_expert_intro.d.ts.map +1 -0
  138. package/dist/test_expert_intro.js +57 -0
  139. package/dist/test_expert_intro.js.map +1 -0
  140. package/dist/test_expert_layer.d.ts +11 -0
  141. package/dist/test_expert_layer.d.ts.map +1 -0
  142. package/dist/test_expert_layer.js +356 -0
  143. package/dist/test_expert_layer.js.map +1 -0
  144. package/dist/test_financial_snapshot_render.d.ts +2 -0
  145. package/dist/test_financial_snapshot_render.d.ts.map +1 -0
  146. package/dist/test_financial_snapshot_render.js +167 -0
  147. package/dist/test_financial_snapshot_render.js.map +1 -0
  148. package/dist/test_gap_alert.d.ts +12 -0
  149. package/dist/test_gap_alert.d.ts.map +1 -0
  150. package/dist/test_gap_alert.js +51 -0
  151. package/dist/test_gap_alert.js.map +1 -0
  152. package/dist/test_grok_offering_surfaces.d.ts +2 -0
  153. package/dist/test_grok_offering_surfaces.d.ts.map +1 -0
  154. package/dist/test_grok_offering_surfaces.js +155 -0
  155. package/dist/test_grok_offering_surfaces.js.map +1 -0
  156. package/dist/test_human_agent_live.d.ts +2 -0
  157. package/dist/test_human_agent_live.d.ts.map +1 -0
  158. package/dist/test_human_agent_live.js +48 -0
  159. package/dist/test_human_agent_live.js.map +1 -0
  160. package/dist/test_identity_gap.d.ts +2 -0
  161. package/dist/test_identity_gap.d.ts.map +1 -0
  162. package/dist/test_identity_gap.js +171 -0
  163. package/dist/test_identity_gap.js.map +1 -0
  164. package/dist/test_intelligence_and_verifier.d.ts +17 -0
  165. package/dist/test_intelligence_and_verifier.d.ts.map +1 -0
  166. package/dist/test_intelligence_and_verifier.js +162 -0
  167. package/dist/test_intelligence_and_verifier.js.map +1 -0
  168. package/dist/test_latency_mcp_brief5.d.ts +12 -0
  169. package/dist/test_latency_mcp_brief5.d.ts.map +1 -0
  170. package/dist/test_latency_mcp_brief5.js +200 -0
  171. package/dist/test_latency_mcp_brief5.js.map +1 -0
  172. package/dist/test_limit_and_credit_card_capture.d.ts +21 -0
  173. package/dist/test_limit_and_credit_card_capture.d.ts.map +1 -0
  174. package/dist/test_limit_and_credit_card_capture.js +216 -0
  175. package/dist/test_limit_and_credit_card_capture.js.map +1 -0
  176. package/dist/test_list_graphs_topics.js +1 -1
  177. package/dist/test_list_graphs_topics.js.map +1 -1
  178. package/dist/test_live_deployed_mcp_search.d.ts +2 -0
  179. package/dist/test_live_deployed_mcp_search.d.ts.map +1 -0
  180. package/dist/test_live_deployed_mcp_search.js +85 -0
  181. package/dist/test_live_deployed_mcp_search.js.map +1 -0
  182. package/dist/test_live_deployed_split.d.ts +2 -0
  183. package/dist/test_live_deployed_split.d.ts.map +1 -0
  184. package/dist/test_live_deployed_split.js +72 -0
  185. package/dist/test_live_deployed_split.js.map +1 -0
  186. package/dist/test_live_endpoint_runner.js +2 -2
  187. package/dist/test_live_endpoint_runner.js.map +1 -1
  188. package/dist/test_live_mcp.js +1 -1
  189. package/dist/test_live_mcp.js.map +1 -1
  190. package/dist/test_live_neil_widget.d.ts +2 -0
  191. package/dist/test_live_neil_widget.d.ts.map +1 -0
  192. package/dist/test_live_neil_widget.js +84 -0
  193. package/dist/test_live_neil_widget.js.map +1 -0
  194. package/dist/test_longpoll_status.d.ts +2 -0
  195. package/dist/test_longpoll_status.d.ts.map +1 -0
  196. package/dist/test_longpoll_status.js +117 -0
  197. package/dist/test_longpoll_status.js.map +1 -0
  198. package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts +2 -0
  199. package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts.map +1 -0
  200. package/dist/test_mcp_find_expert_and_vocabulary_routing.js +130 -0
  201. package/dist/test_mcp_find_expert_and_vocabulary_routing.js.map +1 -0
  202. package/dist/test_next_moves.d.ts +2 -0
  203. package/dist/test_next_moves.d.ts.map +1 -0
  204. package/dist/test_next_moves.js +714 -0
  205. package/dist/test_next_moves.js.map +1 -0
  206. package/dist/test_next_moves_transcripts.d.ts +2 -0
  207. package/dist/test_next_moves_transcripts.d.ts.map +1 -0
  208. package/dist/test_next_moves_transcripts.js +495 -0
  209. package/dist/test_next_moves_transcripts.js.map +1 -0
  210. package/dist/test_on_request_caller_identity.d.ts +13 -0
  211. package/dist/test_on_request_caller_identity.d.ts.map +1 -0
  212. package/dist/test_on_request_caller_identity.js +131 -0
  213. package/dist/test_on_request_caller_identity.js.map +1 -0
  214. package/dist/test_on_request_intercept.d.ts +10 -0
  215. package/dist/test_on_request_intercept.d.ts.map +1 -0
  216. package/dist/test_on_request_intercept.js +95 -0
  217. package/dist/test_on_request_intercept.js.map +1 -0
  218. package/dist/test_onboarding_consent_and_errors.d.ts +2 -0
  219. package/dist/test_onboarding_consent_and_errors.d.ts.map +1 -0
  220. package/dist/test_onboarding_consent_and_errors.js +110 -0
  221. package/dist/test_onboarding_consent_and_errors.js.map +1 -0
  222. package/dist/test_onboarding_live_scenarios.d.ts +2 -0
  223. package/dist/test_onboarding_live_scenarios.d.ts.map +1 -0
  224. package/dist/test_onboarding_live_scenarios.js +189 -0
  225. package/dist/test_onboarding_live_scenarios.js.map +1 -0
  226. package/dist/test_referral_live.d.ts +2 -0
  227. package/dist/test_referral_live.d.ts.map +1 -0
  228. package/dist/test_referral_live.js +33 -0
  229. package/dist/test_referral_live.js.map +1 -0
  230. package/dist/test_referral_piers.d.ts +2 -0
  231. package/dist/test_referral_piers.d.ts.map +1 -0
  232. package/dist/test_referral_piers.js +33 -0
  233. package/dist/test_referral_piers.js.map +1 -0
  234. package/dist/test_report_editorial_briefing.d.ts +21 -0
  235. package/dist/test_report_editorial_briefing.d.ts.map +1 -0
  236. package/dist/test_report_editorial_briefing.js +356 -0
  237. package/dist/test_report_editorial_briefing.js.map +1 -0
  238. package/dist/test_search_graph_quality.d.ts +2 -0
  239. package/dist/test_search_graph_quality.d.ts.map +1 -0
  240. package/dist/test_search_graph_quality.js +493 -0
  241. package/dist/test_search_graph_quality.js.map +1 -0
  242. package/dist/test_service_to_service_auth.d.ts +2 -0
  243. package/dist/test_service_to_service_auth.d.ts.map +1 -0
  244. package/dist/test_service_to_service_auth.js +199 -0
  245. package/dist/test_service_to_service_auth.js.map +1 -0
  246. package/dist/test_session_next_moves_telemetry.d.ts +2 -0
  247. package/dist/test_session_next_moves_telemetry.d.ts.map +1 -0
  248. package/dist/test_session_next_moves_telemetry.js +100 -0
  249. package/dist/test_session_next_moves_telemetry.js.map +1 -0
  250. package/dist/test_sources_live.d.ts +2 -0
  251. package/dist/test_sources_live.d.ts.map +1 -0
  252. package/dist/test_sources_live.js +30 -0
  253. package/dist/test_sources_live.js.map +1 -0
  254. package/dist/test_specialist_and_analyst_tiers.d.ts +2 -0
  255. package/dist/test_specialist_and_analyst_tiers.d.ts.map +1 -0
  256. package/dist/test_specialist_and_analyst_tiers.js +155 -0
  257. package/dist/test_specialist_and_analyst_tiers.js.map +1 -0
  258. package/dist/test_v2_2_visual_hardening.d.ts +5 -0
  259. package/dist/test_v2_2_visual_hardening.d.ts.map +1 -0
  260. package/dist/test_v2_2_visual_hardening.js +333 -0
  261. package/dist/test_v2_2_visual_hardening.js.map +1 -0
  262. package/dist/test_verify_deprecate_render_spec.d.ts +2 -0
  263. package/dist/test_verify_deprecate_render_spec.d.ts.map +1 -0
  264. package/dist/test_verify_deprecate_render_spec.js +217 -0
  265. package/dist/test_verify_deprecate_render_spec.js.map +1 -0
  266. package/dist/test_verify_neil_demo.d.ts +2 -0
  267. package/dist/test_verify_neil_demo.d.ts.map +1 -0
  268. package/dist/test_verify_neil_demo.js +149 -0
  269. package/dist/test_verify_neil_demo.js.map +1 -0
  270. package/dist/test_waitable_research_and_grounded_links.d.ts +2 -0
  271. package/dist/test_waitable_research_and_grounded_links.d.ts.map +1 -0
  272. package/dist/test_waitable_research_and_grounded_links.js +129 -0
  273. package/dist/test_waitable_research_and_grounded_links.js.map +1 -0
  274. package/dist/test_widget_guards.d.ts +2 -0
  275. package/dist/test_widget_guards.d.ts.map +1 -0
  276. package/dist/test_widget_guards.js +229 -0
  277. package/dist/test_widget_guards.js.map +1 -0
  278. package/dist/test_zero_retention_alerts.d.ts +14 -0
  279. package/dist/test_zero_retention_alerts.d.ts.map +1 -0
  280. package/dist/test_zero_retention_alerts.js +76 -0
  281. package/dist/test_zero_retention_alerts.js.map +1 -0
  282. package/dist/toolHandlers.d.ts +38 -1
  283. package/dist/toolHandlers.d.ts.map +1 -1
  284. package/dist/toolHandlers.js +4290 -711
  285. package/dist/toolHandlers.js.map +1 -1
  286. package/dist/tools.d.ts +22 -1
  287. package/dist/tools.d.ts.map +1 -1
  288. package/dist/tools.js +29 -4
  289. package/dist/tools.js.map +1 -1
  290. package/dist/verify_tools_endpoint.js +19 -5
  291. package/dist/verify_tools_endpoint.js.map +1 -1
  292. package/dist/widgetShell.d.ts +2 -2
  293. package/dist/widgetShell.d.ts.map +1 -1
  294. package/dist/widgetShell.js +1 -1
  295. package/dist/widgetShell.js.map +1 -1
  296. package/package.json +1 -1
  297. package/server.json +3 -31
  298. package/dist/debug_tools.d.ts +0 -2
  299. package/dist/debug_tools.d.ts.map +0 -1
  300. package/dist/debug_tools.js +0 -19
  301. package/dist/debug_tools.js.map +0 -1
  302. package/dist/inspect_list_graphs.d.ts +0 -2
  303. package/dist/inspect_list_graphs.d.ts.map +0 -1
  304. package/dist/inspect_list_graphs.js +0 -25
  305. package/dist/inspect_list_graphs.js.map +0 -1
  306. package/dist/scratch_test.d.ts.map +0 -1
  307. package/dist/scripts/simulate_gemini.d.ts +0 -2
  308. package/dist/scripts/simulate_gemini.d.ts.map +0 -1
  309. package/dist/scripts/simulate_gemini.js +0 -100
  310. package/dist/scripts/simulate_gemini.js.map +0 -1
  311. package/dist/scripts/verify_registry.d.ts +0 -2
  312. package/dist/scripts/verify_registry.d.ts.map +0 -1
  313. package/dist/scripts/verify_registry.js +0 -77
  314. package/dist/scripts/verify_registry.js.map +0 -1
  315. package/dist/src/index.d.ts +0 -2
  316. package/dist/src/index.d.ts.map +0 -1
  317. package/dist/src/index.js +0 -332
  318. package/dist/src/index.js.map +0 -1
  319. package/dist/src/tools.d.ts +0 -12
  320. package/dist/src/tools.d.ts.map +0 -1
  321. package/dist/src/tools.js +0 -104
  322. package/dist/src/tools.js.map +0 -1
  323. package/dist/src/types.d.ts +0 -44
  324. package/dist/src/types.d.ts.map +0 -1
  325. package/dist/src/types.js +0 -2
  326. package/dist/src/types.js.map +0 -1
  327. package/dist/src/verify.d.ts +0 -2
  328. package/dist/src/verify.d.ts.map +0 -1
  329. package/dist/src/verify.js +0 -47
  330. package/dist/src/verify.js.map +0 -1
  331. package/dist/src/verify_hmac.d.ts +0 -2
  332. package/dist/src/verify_hmac.d.ts.map +0 -1
  333. package/dist/src/verify_hmac.js +0 -106
  334. package/dist/src/verify_hmac.js.map +0 -1
  335. package/dist/src/verify_tools_endpoint.d.ts +0 -2
  336. package/dist/src/verify_tools_endpoint.d.ts.map +0 -1
  337. package/dist/src/verify_tools_endpoint.js +0 -40
  338. package/dist/src/verify_tools_endpoint.js.map +0 -1
  339. package/dist/test-waverunner-response.d.ts +0 -2
  340. package/dist/test-waverunner-response.d.ts.map +0 -1
  341. package/dist/test-waverunner-response.js +0 -20
  342. package/dist/test-waverunner-response.js.map +0 -1
  343. package/dist/test_coverage_r2.d.ts +0 -2
  344. package/dist/test_coverage_r2.d.ts.map +0 -1
  345. package/dist/test_coverage_scratch.d.ts +0 -2
  346. package/dist/test_coverage_scratch.d.ts.map +0 -1
@@ -175,12 +175,23 @@ function velocityClass(trend) {
175
175
  function gtY(value) {
176
176
  return Math.round(88 - (value / 100 * 76));
177
177
  }
178
+ function formatMonthYear(dStr) {
179
+ if (!dStr)
180
+ return '12m ago';
181
+ const d = new Date(dStr);
182
+ if (isNaN(d.getTime()))
183
+ return '12m ago';
184
+ const mon = d.toLocaleString('en-US', { month: 'short' });
185
+ const yr = String(d.getFullYear()).slice(-2);
186
+ return `${mon} '${yr}`;
187
+ }
178
188
  function buildGoogleTrendsSVG(gtData) {
179
189
  const defaults = {
180
190
  polyline: '0,88 300,88', polygon: '0,88 300,88,300,88,0,88',
181
191
  peakX: 150, peakY: 88, peakLabel: '', nowLabel: '',
182
192
  annX: 297, annY: 84, annText: '', description: 'Google Trends',
183
193
  caption: 'Relative interest (0–100). Not absolute volume. Source: Google Trends.',
194
+ startDateLabel: '12m ago',
184
195
  relatedQueriesHtml: '',
185
196
  comparisonBarsHtml: '',
186
197
  };
@@ -212,6 +223,7 @@ function buildGoogleTrendsSVG(gtData) {
212
223
  const peakY = gtY(peakVal);
213
224
  const peakDate = points[peakIdx]?.date || '';
214
225
  const trend = lastVal > peakVal * 0.9 ? '↑ near peak' : lastVal > peakVal * 0.6 ? '↑ rising' : '→ moderate';
226
+ const startDateLabel = points[0]?.date ? formatMonthYear(points[0].date) : '12m ago';
215
227
  const relatedQueriesHtml = (gt.related_queries || [])
216
228
  .slice(0, 8)
217
229
  .map((q) => `<span class="rqp">${esc(q)}</span>`)
@@ -229,6 +241,7 @@ function buildGoogleTrendsSVG(gtData) {
229
241
  annX: 297, annY: lastY - 4, annText: trend,
230
242
  description: `"${gt.query || 'brand'}", US, past 12 months`,
231
243
  caption: 'Relative interest (0–100). Not absolute volume. Source: Google Trends.',
244
+ startDateLabel,
232
245
  relatedQueriesHtml,
233
246
  comparisonBarsHtml,
234
247
  };
@@ -241,6 +254,46 @@ function barRow(label, value, maxValue) {
241
254
  return `<div class="br"><div class="brl">${esc(label)}</div><div class="brt"><div class="brf" style="width:${pct}%"></div></div><div class="brc">${value.toLocaleString()}</div></div>`;
242
255
  }
243
256
  // ---------------------------------------------------------------------------
257
+ // Financial data formatting helpers
258
+ // ---------------------------------------------------------------------------
259
+ function formatFinancialCurrency(val) {
260
+ if (val === undefined || val === null || isNaN(val))
261
+ return '—';
262
+ const abs = Math.abs(val);
263
+ const sign = val < 0 ? '-' : '';
264
+ if (abs >= 1e9) {
265
+ return `${sign}$${(abs / 1e9).toFixed(2)}B`;
266
+ }
267
+ if (abs >= 1e6) {
268
+ return `${sign}$${(abs / 1e6).toFixed(1)}M`;
269
+ }
270
+ if (abs >= 1e3) {
271
+ return `${sign}$${(abs / 1e3).toFixed(1)}K`;
272
+ }
273
+ return `${sign}$${abs.toLocaleString()}`;
274
+ }
275
+ function formatEps(eps, dilutedEps) {
276
+ if ((eps === undefined || eps === null || isNaN(eps)) && (dilutedEps === undefined || dilutedEps === null || isNaN(dilutedEps))) {
277
+ return '—';
278
+ }
279
+ const fmt = (v) => `${v < 0 ? '-' : ''}$${Math.abs(v).toFixed(2)}`;
280
+ if (eps != null && !isNaN(eps) && dilutedEps != null && !isNaN(dilutedEps)) {
281
+ return `${fmt(eps)} (Diluted ${fmt(dilutedEps)})`;
282
+ }
283
+ if (eps != null && !isNaN(eps)) {
284
+ return fmt(eps);
285
+ }
286
+ return `${fmt(dilutedEps)} (diluted)`;
287
+ }
288
+ function formatStockPrice(priceWindow) {
289
+ if (!priceWindow || priceWindow.current_price == null || isNaN(priceWindow.current_price))
290
+ return '';
291
+ const dateStr = priceWindow.as_of_date ? ` (as of ${esc(priceWindow.as_of_date)})` : '';
292
+ const p = priceWindow.current_price;
293
+ const priceFormatted = `${p < 0 ? '-' : ''}$${Math.abs(p).toFixed(2)}`;
294
+ return `${priceFormatted}${dateStr}`;
295
+ }
296
+ // ---------------------------------------------------------------------------
244
297
  // Main render function
245
298
  // ---------------------------------------------------------------------------
246
299
  export async function renderBrandWidget(profile) {
@@ -250,7 +303,62 @@ export async function renderBrandWidget(profile) {
250
303
  const competitors = profile.competitive_context?.co_occurring_brands || [];
251
304
  const crossGraph = profile.cross_graph_presence || [];
252
305
  const supplemental = profile.supplemental_signals || {};
253
- const velocity = velocityClass(profile.summary?.evidence_velocity?.trend || 'stable');
306
+ const lcDist = profile.summary?.lifecycle_distribution || {};
307
+ const buildingCount = (lcDist.building || 0) + (lcDist.emerging || 0);
308
+ const fadingCount = (lcDist.fading || 0) + (lcDist.mature || 0);
309
+ // Enrich trends with lifecycle if missing
310
+ trends.forEach((t) => {
311
+ if (!t.lifecycle || t.lifecycle === 'unknown') {
312
+ t.lifecycle = computeLifecycle(t);
313
+ }
314
+ });
315
+ // Derive headline verdict from per-trend momentums and lifecycle distribution
316
+ let accCount = 0;
317
+ let bldCount = 0;
318
+ let stdCount = 0;
319
+ let slowCount = 0;
320
+ trends.forEach((t) => {
321
+ const m = typeof t.momentum === 'string' ? t.momentum : (t.momentum?.trend || t.momentum?.state || '');
322
+ if (m === 'accelerating' || t.momentum?.fastMover)
323
+ accCount++;
324
+ else if (m === 'building')
325
+ bldCount++;
326
+ else if (m === 'slowing' || m === 'fading')
327
+ slowCount++;
328
+ else if (m === 'steady')
329
+ stdCount++;
330
+ else {
331
+ // fallback to lifecycle if momentum not explicitly set
332
+ if (t.lifecycle === 'emerging')
333
+ accCount++;
334
+ else if (t.lifecycle === 'building')
335
+ bldCount++;
336
+ else if (t.lifecycle === 'fading')
337
+ slowCount++;
338
+ else
339
+ stdCount++;
340
+ }
341
+ });
342
+ 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';
353
+ }
354
+ else {
355
+ rawVelocityTrend = 'stable';
356
+ }
357
+ }
358
+ else {
359
+ rawVelocityTrend = profile.summary?.evidence_velocity?.trend || 'stable';
360
+ }
361
+ const velocity = velocityClass(rawVelocityTrend);
254
362
  const timeline = profile.activity_timeline || [];
255
363
  // Build set of graphIds the target brand is present in (for sector-aware competitor labels)
256
364
  const brandGraphIds = new Set(crossGraph.map((g) => g.graphId).filter(Boolean));
@@ -267,28 +375,28 @@ export async function renderBrandWidget(profile) {
267
375
  const parts = [];
268
376
  if (truthLayer.headline) {
269
377
  parts.push(`<div class="ec">
270
- <div class="et" style="font-size:13px;font-weight:600;">${esc(truthLayer.company || '')} — ${esc(truthLayer.period || '')}</div>
378
+ <div class="et" style="font-size:13px;font-weight:600;">${esc(truthLayer.company || '')} — ${esc(truthLayer.period || '')} Earnings Call Analysis</div>
271
379
  <div class="ex" style="font-weight:500;margin-top:4px;">${esc(truthLayer.headline)}</div>
272
380
  </div>`);
273
381
  }
274
382
  if (truthLayer.analyst_concerns) {
275
383
  parts.push(`<div class="ec">
276
- <div class="et" style="font-size:11px;font-weight:500;color:var(--color-text-warning);">What Analysts Are Pressing On</div>
384
+ <div class="et" style="font-size:11px;font-weight:600;color:var(--color-text-warning);">Wall Street Analyst Concerns & Q&A Focus</div>
277
385
  <div class="ex">${esc(truthLayer.analyst_concerns)}</div>
278
386
  </div>`);
279
387
  }
280
388
  if (truthLayer.analyst_sentiment) {
281
389
  parts.push(`<div class="ec">
282
- <div class="et" style="font-size:11px;font-weight:500;">Analyst Sentiment</div>
390
+ <div class="et" style="font-size:11px;font-weight:600;">Institutional Analyst Consensus & Sentiment</div>
283
391
  <div class="ex">${esc(truthLayer.analyst_sentiment)}</div>
284
392
  </div>`);
285
393
  }
286
394
  // Activity fields — show if present
287
395
  const activities = [
288
- { key: 'marketing_activity', label: 'Marketing' },
289
- { key: 'retail_activity', label: 'Retail' },
290
- { key: 'technology_activity', label: 'Technology' },
291
- { key: 'sustainability_activity', label: 'Sustainability' },
396
+ { key: 'marketing_activity', label: 'Marketing & Brand Building' },
397
+ { key: 'retail_activity', label: 'Retail & Channel Expansion' },
398
+ { key: 'technology_activity', label: 'Technology & AI Infrastructure' },
399
+ { key: 'sustainability_activity', label: 'ESG & Sustainability' },
292
400
  ];
293
401
  const activityPills = activities
294
402
  .filter(a => truthLayer[a.key])
@@ -296,13 +404,13 @@ export async function renderBrandWidget(profile) {
296
404
  .join('');
297
405
  if (activityPills) {
298
406
  parts.push(`<div class="ec">
299
- <div class="et" style="font-size:11px;font-weight:500;">Strategic Activity</div>
407
+ <div class="et" style="font-size:11px;font-weight:600;">Management Focus & Strategic Investment Areas</div>
300
408
  <div class="rq" style="margin-top:4px;">${activityPills}</div>
301
409
  </div>`);
302
410
  }
303
411
  if (truthLayer.exec_sentiment) {
304
412
  parts.push(`<div class="ec">
305
- <div class="et" style="font-size:11px;font-weight:500;">Executive Sentiment</div>
413
+ <div class="et" style="font-size:11px;font-weight:600;">C-Suite Executive Outlook & Forward Guidance</div>
306
414
  <div class="ex">${esc(truthLayer.exec_sentiment)}${truthLayer.quote_from_ceo ? ` — "${esc(truthLayer.quote_from_ceo)}"` : ''}</div>
307
415
  </div>`);
308
416
  }
@@ -312,7 +420,7 @@ export async function renderBrandWidget(profile) {
312
420
  .map((t) => `<span class="rqp">${esc(t.trend_name)}</span>`)
313
421
  .join('');
314
422
  parts.push(`<div class="ec">
315
- <div class="et" style="font-size:11px;font-weight:500;color:var(--color-text-success);">Validated Consumer Trends</div>
423
+ <div class="et" style="font-size:11px;font-weight:600;color:var(--color-text-success);">Market Trends Confirmed by Executives</div>
316
424
  <div class="rq" style="margin-top:4px;">${trendPills}</div>
317
425
  </div>`);
318
426
  }
@@ -335,6 +443,59 @@ export async function renderBrandWidget(profile) {
335
443
  </div>`;
336
444
  }).join('\n');
337
445
  }
446
+ // ── Institutional Financial Snapshot (Market Data) section ──
447
+ const marketData = profile.market_data;
448
+ let financialSnapshotHtml = '';
449
+ if (marketData && marketData.data_status !== 'no_signal') {
450
+ const symbol = marketData.primary_symbol || marketData.ticker || '';
451
+ const profileInfo = marketData.company_profile;
452
+ const companyName = profileInfo?.company_name || profile.brand || '';
453
+ const sector = profileInfo?.sector || '';
454
+ const industry = profileInfo?.industry || '';
455
+ const priceStr = formatStockPrice(marketData.price_window);
456
+ const quarters = (marketData.quarterly_financials || []).slice(0, 2);
457
+ const hasContent = Boolean(symbol || profileInfo || priceStr || quarters.length > 0);
458
+ if (hasContent) {
459
+ const headerTitle = companyName ? `${esc(companyName)}${symbol ? ` (${esc(symbol)})` : ''}` : esc(symbol);
460
+ const metaTokens = [];
461
+ if (sector)
462
+ metaTokens.push(`Sector: ${esc(sector)}`);
463
+ if (industry)
464
+ metaTokens.push(`Industry: ${esc(industry)}`);
465
+ const metaHtml = metaTokens.length > 0
466
+ ? `<div style="font-size:11px;color:var(--color-text-secondary);margin-top:2px;margin-bottom:8px;">${metaTokens.join(' · ')}</div>`
467
+ : '';
468
+ const priceHtml = priceStr
469
+ ? `<div style="font-size:12px;color:var(--color-text-primary);margin-bottom:8px;font-weight:500;">Stock Price: <span style="font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:600;">${priceStr}</span></div>`
470
+ : '';
471
+ let quartersHtml = '';
472
+ if (quarters.length > 0) {
473
+ const qCards = quarters.map((q) => {
474
+ const qPeriod = q.period || q.period_label || 'Quarterly Financials';
475
+ const rev = formatFinancialCurrency(q.revenue);
476
+ const opInc = formatFinancialCurrency(q.operating_income);
477
+ const netInc = formatFinancialCurrency(q.net_income);
478
+ const eps = formatEps(q.eps, q.diluted_eps);
479
+ return `<div style="background:var(--color-background-secondary);border-radius:6px;padding:8px 10px;margin-top:6px;">
480
+ <div style="font-size:11px;font-weight:600;color:var(--color-text-primary);margin-bottom:4px;">${esc(qPeriod)}</div>
481
+ <div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;font-size:11px;">
482
+ <div><span style="color:var(--color-text-secondary);">Revenue:</span> <strong style="font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;">${rev}</strong></div>
483
+ <div><span style="color:var(--color-text-secondary);">Op. Income:</span> <strong style="font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;">${opInc}</strong></div>
484
+ <div><span style="color:var(--color-text-secondary);">Net Income:</span> <strong style="font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;">${netInc}</strong></div>
485
+ <div><span style="color:var(--color-text-secondary);">EPS:</span> <strong style="font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;">${eps}</strong></div>
486
+ </div>
487
+ </div>`;
488
+ }).join('\n');
489
+ quartersHtml = `<div style="margin-top:6px;">${qCards}</div>`;
490
+ }
491
+ financialSnapshotHtml = `<div class="ec">
492
+ <div class="et" style="font-size:13px;font-weight:600;">${headerTitle}</div>
493
+ ${metaHtml}
494
+ ${priceHtml}
495
+ ${quartersHtml}
496
+ </div>`;
497
+ }
498
+ }
338
499
  // Enrich trends with lifecycle if missing
339
500
  trends.forEach((t) => {
340
501
  if (!t.lifecycle || t.lifecycle === 'unknown') {
@@ -342,7 +503,6 @@ export async function renderBrandWidget(profile) {
342
503
  }
343
504
  });
344
505
  // ── Lifecycle bar ──
345
- const lcDist = profile.summary?.lifecycle_distribution || {};
346
506
  const lcTotal = Object.values(lcDist).reduce((a, b) => a + b, 0);
347
507
  const lcColors = { building: 'var(--color-text-info)', emerging: 'var(--color-text-success)', mature: 'var(--color-text-secondary)', fading: 'var(--color-text-warning)' };
348
508
  const lifecycleBar = Object.entries(lcDist)
@@ -359,8 +519,7 @@ export async function renderBrandWidget(profile) {
359
519
  weakSignals.forEach((t) => weakSignalNames.add(t.trend_name?.trim()));
360
520
  // ── Trend cards (exclude weak signals to avoid duplication) ──
361
521
  const mainTrends = trends.filter((t) => !weakSignalNames.has(t.trend_name?.trim()));
362
- const trendsHtml = mainTrends.map((t, i) => {
363
- const spark = deriveSparklineShape(t);
522
+ const trendsHtml = mainTrends.map((t) => {
364
523
  const tName = (t.trend_name || '').trim();
365
524
  const badges = [
366
525
  `<span class="bdp">${esc(t.graphName)}</span>`,
@@ -405,8 +564,10 @@ export async function renderBrandWidget(profile) {
405
564
  const parsed = new Date(ev.published_at);
406
565
  date = isNaN(parsed.getTime()) ? '' : parsed.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
407
566
  }
408
- const titleHtml = ev.source_url
409
- ? `<a href="${esc(ev.source_url)}">${esc(ev.title)}</a>`
567
+ const dateBadge = date ? `<span class="bd">${esc(date)}</span>` : '';
568
+ const hasRealUrl = ev.source_url && !ev.source_url.startsWith('https://fodda.ai') && !ev.source_url.startsWith('https://www.fodda.ai');
569
+ const titleHtml = hasRealUrl
570
+ ? `<a href="${esc(ev.source_url)}" target="_blank" rel="noopener noreferrer">${esc(ev.title)}</a>`
410
571
  : esc(ev.title);
411
572
  const speakerHtml = ev.speaker_name ? `<div style="font-size:11px;color:var(--color-text-secondary);margin-top:4px;font-style:italic;">${esc(ev.speaker_name)}${ev.speaker_title ? ', ' + esc(ev.speaker_title) : ''}</div>` : '';
412
573
  return `<div class="ec">
@@ -416,7 +577,7 @@ export async function renderBrandWidget(profile) {
416
577
  <div class="em">
417
578
  <span class="lb ${catClass(ev.category)} bd">${esc(ev.category)}</span>
418
579
  <span class="bdp">${esc(ev.graphName)}</span>
419
- <span class="bd">${date}</span>
580
+ ${dateBadge}
420
581
  ${placeBadge}
421
582
  </div>
422
583
  </div>`;
@@ -436,24 +597,6 @@ export async function renderBrandWidget(profile) {
436
597
  </div>
437
598
  </div>`;
438
599
  }).join('\n');
439
- // ── Network SVG ──
440
- const topComps = competitors.slice(0, 5);
441
- const networkNodesHtml = topComps.map((c, i) => {
442
- const pos = ORBIT_POSITIONS[i] || { cx: 150, cy: 113 };
443
- const pType = guessPressureType(i, c, brandGraphIds, domainGIds);
444
- const colors = PRESSURE_COLORS[pType] || DEFAULT_PRESSURE;
445
- return `<line x1="150" y1="113" x2="${pos.cx}" y2="${pos.cy}" stroke="${colors.color}" stroke-width="0.5" stroke-dasharray="3,3"/>
446
- <circle cx="${pos.cx}" cy="${pos.cy}" r="18" fill="${colors.bg}" stroke="${colors.color}" stroke-width="1"/>
447
- <text x="${pos.cx}" y="${pos.cy + 3}" class="svgt" font-size="7.5" text-anchor="middle" fill="${colors.color}">${esc(truncSvg(c.brand))}</text>`;
448
- }).join('\n ');
449
- // Network legend — bottom-right (avoid overlapping left orbit node)
450
- const legendTypes = [...new Set(topComps.map((c, i) => guessPressureType(i, c, brandGraphIds, domainGIds)))].slice(0, 5);
451
- const networkLegendHtml = legendTypes.map((pType, i) => {
452
- const colors = PRESSURE_COLORS[pType] || DEFAULT_PRESSURE;
453
- const y = 190 + i * 11;
454
- return `<rect x="200" y="${y}" width="6" height="6" rx="1" fill="${colors.bg}" stroke="${colors.color}" stroke-width=".5"/>
455
- <text x="210" y="${y + 5}" class="svgt" font-size="6" fill="var(--color-text-secondary)">${pType}</text>`;
456
- }).join('\n ');
457
600
  // ── Compare buttons ──
458
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 ');
459
602
  // ── Google Trends ──
@@ -515,6 +658,8 @@ export async function renderBrandWidget(profile) {
515
658
  const graphNames = crossGraph.map((g) => g.graphName).filter(Boolean);
516
659
  const sourcePills = [
517
660
  ...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>' : '',
518
663
  hasGoogleTrendsData ? '<span class="gp">Google Trends</span>' : '',
519
664
  supplemental?.wikipedia ? '<span class="gp">Wikipedia</span>' : '',
520
665
  supplemental?.amazon ? '<span class="gp">Amazon</span>' : '',
@@ -541,24 +686,24 @@ export async function renderBrandWidget(profile) {
541
686
  'TRENDS_HTML': trendsHtml,
542
687
  'WEAK_SIGNALS_HTML': weakSignalsHtml,
543
688
  'EVIDENCE_HTML': evidenceHtml,
544
- 'COMPETITOR_LIST_HTML': competitorListHtml,
689
+ 'COMPETITIVE_SECTION_HTML': competitors.length > 0 ? `<div class="sec">Competitive</div>\n${competitorListHtml}` : '',
545
690
  'GT_SECTION_HTML': hasGoogleTrendsData ? `<div class="sl2">${gt.description}</div>
546
691
  <svg viewBox="0 0 300 96" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:96px;margin-bottom:4px;">
547
692
  <defs>
548
693
  <linearGradient id="gtg" x1="0" y1="0" x2="0" y2="1">
549
- <stop offset="0%" stop-color="#663399" stop-opacity=".1"/>
550
- <stop offset="100%" stop-color="#663399" stop-opacity="0"/>
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"/>
551
696
  </linearGradient>
552
697
  </defs>
553
698
  <polygon points="${gt.polygon}" fill="url(#gtg)"/>
554
- <polyline points="${gt.polyline}" fill="none" stroke="#663399" stroke-width="1.5" stroke-linejoin="round"/>
699
+ <polyline points="${gt.polyline}" fill="none" stroke="var(--fodda-accent, #663399)" stroke-width="1.5" stroke-linejoin="round"/>
555
700
  <line x1="0" y1="88" x2="300" y2="88" stroke="var(--color-border-tertiary)" stroke-width=".5"/>
556
- <text x="2" y="93" font-size="7" fill="var(--color-text-secondary)" font-family="monospace">Apr '25</text>
701
+ <text x="2" y="93" font-size="7" fill="var(--color-text-secondary)" font-family="monospace">${gt.startDateLabel}</text>
557
702
  <text x="150" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="middle" font-family="monospace">${gt.peakLabel}</text>
558
703
  <text x="298" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="end" font-family="monospace">${gt.nowLabel}</text>
559
- <circle cx="${gt.peakX}" cy="${gt.peakY}" r="2" fill="#663399"/>
560
- <circle cx="300" cy="${gt.annY}" r="2.5" fill="#663399"/>
561
- <text x="${gt.annX}" y="${gt.annY}" font-size="7" fill="#663399" text-anchor="end" font-family="monospace">${gt.annText}</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>
562
707
  </svg>
563
708
  <p class="note">{{GT_CAPTION}}</p>` : '',
564
709
  'GT_COMPARISON_SECTION_HTML': gt.comparisonBarsHtml ? `<div class="sl2">Search interest — brand vs competitors</div>
@@ -589,9 +734,12 @@ export async function renderBrandWidget(profile) {
589
734
  'BEA_STAT_2_SUB': beaChangeSub,
590
735
  'GEO_SECTION_HTML': geoBarsHtml ? `<div class="sl2">Geographic spread</div>
591
736
  ${geoBarsHtml}` : '',
592
- 'EARNINGS_SECTION_HTML': earningsHtml ? `<div class="sl2">Earnings intelligence</div>
737
+ 'EARNINGS_SECTION_HTML': earningsHtml ? `<div class="sl2">Quarterly Earnings & Wall Street Intelligence</div>
593
738
  ${earningsHtml}
594
- <p class="note">Source: Fodda Earnings Intelligence.</p>` : '',
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>` : '',
595
743
  'SOURCE_PILLS_HTML': sourcePills,
596
744
  'EXPORT_1_LABEL': exportLabels[0].label,
597
745
  'EXPORT_1_DESC': exportLabels[0].desc,
@@ -626,10 +774,12 @@ export async function renderBrandWidget(profile) {
626
774
  fills['GT_CAPTION'] = supplemental?.google_trends ? `12-month search interest for "${brand}". Peak: ${supplemental.google_trends.peak_interest || '—'}, current: ${supplemental.google_trends.latest_value || '—'}. Source: Google Trends.` : `Search interest data for "${brand}". Source: Google Trends.`;
627
775
  fills['RELATED_QUERIES_NOTE'] = supplemental?.google_trends?.related_queries?.length ? `Top related searches when people look for ${brand}.` : '';
628
776
  fills['WIKI_NOTE'] = wikiData.length > 0 ? `Daily Wikipedia pageviews for ${brand}-related articles. Higher = more cultural attention.` : '';
777
+ const safeBrandName = esc(brand).replace(/'/g, "\\'");
778
+ const safeCompName = competitors[0]?.brand ? esc(competitors[0].brand).replace(/'/g, "\\'") : 'top rival';
629
779
  fills['SUGGESTED_NEXT_HTML'] = `<div style="display:flex;flex-wrap:wrap;gap:6px;">
630
- <button class="btn-out" onclick="sendPrompt('What are ${brand.replace(/'/g, "\\'")}'s competitors doing differently?')">Competitive landscape</button>
631
- <button class="btn-out" onclick="sendPrompt('Show me the evidence behind ${brand.replace(/'/g, "\\'")}'s strongest trend')">Deep dive</button>
632
- <button class="btn-out" onclick="sendPrompt('How does ${brand.replace(/'/g, "\\'")}' compare to ${competitors[0]?.brand.replace(/'/g, "\\'") || 'its top competitor'}?')">Head-to-head</button>
780
+ <button class="btn-out" onclick="sendPrompt('What are competitors of ${safeBrandName} doing differently?')">Competitive landscape</button>
781
+ <button class="btn-out" onclick="sendPrompt('Show me the evidence behind the strongest trend for ${safeBrandName}')">Deep dive</button>
782
+ <button class="btn-out" onclick="sendPrompt('Compare ${safeBrandName} vs ${safeCompName}')">Head-to-head</button>
633
783
  </div>`;
634
784
  html = replaceSlots(html, fills);
635
785
  // ── Final cleanup: strip any remaining unfilled {{...}} placeholders ──
@@ -637,25 +787,66 @@ export async function renderBrandWidget(profile) {
637
787
  const editorialContext = { brand, total_trends: trends.length };
638
788
  return { widget_html: html, editorial_context: editorialContext, open_slots: [] };
639
789
  }
640
- // const TEMPLATE = `
641
790
  export const TEMPLATE = `
642
791
  <style>
643
- :root{--p:#663399;--pl:#F5F0FF;--pm:#9B72CC;--pl-on:#663399;}
644
- @media(prefers-color-scheme:dark){:root{--p:#9B72CC;--pl:rgba(155,114,204,0.14);--pm:#663399;--pl-on:#C4A7E8;}}
645
- .w{border:1px solid var(--p);border-top:3px solid var(--p);border-radius:6px;padding:1.25rem;font-family:var(--font-mono);}
792
+ .fodda-brand-widget, .w {
793
+ --fodda-bg: #ffffff;
794
+ --fodda-bg-secondary: #f8fafc;
795
+ --fodda-text: #18181b;
796
+ --fodda-muted: #71717a;
797
+ --fodda-line: #e4e4e7;
798
+ --fodda-accent: #663399;
799
+ --fodda-accent-light: #f5f0ff;
800
+ --p: var(--fodda-accent);
801
+ --pl: var(--fodda-accent-light);
802
+ --pm: var(--fodda-line);
803
+ --pl-on: var(--fodda-accent);
804
+ --color-text-primary: var(--fodda-text);
805
+ --color-text-secondary: var(--fodda-muted);
806
+ --color-border-tertiary: var(--fodda-line);
807
+ --color-background-primary: var(--fodda-bg);
808
+ --color-background-secondary: var(--fodda-bg-secondary);
809
+ --color-text-info: #2563eb;
810
+ --color-text-success: #16a34a;
811
+ --color-text-warning: #d97706;
812
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
813
+ color: var(--fodda-text);
814
+ background: var(--fodda-bg);
815
+ border: 1px solid var(--fodda-line);
816
+ border-top: 3px solid var(--fodda-accent);
817
+ border-radius: 6px;
818
+ padding: 1.25rem;
819
+ }
820
+ @media (prefers-color-scheme: dark) {
821
+ .fodda-brand-widget, .w {
822
+ --fodda-bg: #18181b;
823
+ --fodda-bg-secondary: #27272a;
824
+ --fodda-text: #f4f4f5;
825
+ --fodda-muted: #a1a1aa;
826
+ --fodda-line: #3f3f46;
827
+ --fodda-accent: #9d65d4;
828
+ --fodda-accent-light: rgba(157, 101, 212, 0.15);
829
+ --p: var(--fodda-accent);
830
+ --pl: var(--fodda-accent-light);
831
+ --pm: var(--fodda-line);
832
+ --pl-on: #c4a7e8;
833
+ --color-text-info: #60a5fa;
834
+ --color-text-success: #4ade80;
835
+ --color-text-warning: #fbbf24;
836
+ }
837
+ }
646
838
  .hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;padding-bottom:1rem;border-bottom:.5px solid var(--color-border-tertiary);}
647
839
  .logo{display:flex;align-items:center;gap:10px;}
648
- .logo img{height:24px;width:auto;display:block;}
649
- .lt{font-size:13px;font-weight:500;}.ls{font-size:10px;color:var(--color-text-secondary);}
650
- .bfolio{font-family:var(--font-mono);font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--color-text-secondary);}
840
+ .lt{font-size:13px;font-weight:600;}.ls{font-size:10px;color:var(--color-text-secondary);}
841
+ .bfolio{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--color-text-secondary);}
651
842
  .bh{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.75rem;}
652
- .bn{font-family:var(--font-serif);font-style:italic;font-weight:400;font-size:22px;letter-spacing:-0.01em;}
653
- .vc{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;font-family:var(--font-mono);}
843
+ .bn{font-family:Georgia,serif;font-style:italic;font-weight:400;font-size:22px;letter-spacing:-0.01em;}
844
+ .vc{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}
654
845
  .vc-up{color:var(--color-text-success);}
655
846
  .vc-build{color:var(--color-text-info);}
656
847
  .vc-steady{color:var(--color-text-secondary);}
657
848
  .vc-slow{color:var(--color-text-warning);}
658
- .pv{font-size:14px;font-family:var(--font-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);}
849
+ .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);}
659
850
  .lcb{display:flex;height:8px;border-radius:4px;overflow:hidden;margin-bottom:.75rem;gap:2px;}
660
851
  .lcl{display:flex;gap:12px;margin-bottom:1.25rem;flex-wrap:wrap;}
661
852
  .lci{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--color-text-secondary);}
@@ -663,37 +854,37 @@ export const TEMPLATE = `
663
854
  .card{background:var(--color-background-primary);border:1px solid var(--color-border-tertiary);border-radius:4px;padding:1rem 1.25rem;margin-bottom:8px;transition:border-color .15s,background .15s;}
664
855
  .card:hover{border-color:var(--p);background:var(--pl);}
665
856
  .th{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:6px;gap:8px;}
666
- .tn{font-size:13px;font-weight:500;}
857
+ .tn{font-size:13px;font-weight:600;}
667
858
  .ta{display:flex;align-items:center;gap:4px;flex-shrink:0;}
668
859
  .td{font-size:12px;color:var(--color-text-secondary);margin-bottom:5px;line-height:1.5;}
669
- .lb{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;white-space:nowrap;font-family:var(--font-mono);background:transparent;}
860
+ .lb{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;background:transparent;}
670
861
  .lb-b{color:var(--color-text-info);}
671
862
  .lb-e{color:var(--color-text-success);}
672
863
  .lb-m{color:var(--color-text-secondary);}
673
864
  .lb-f{color:var(--color-text-warning);}
674
- .bd{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;background:transparent;color:var(--color-text-secondary);font-family:var(--font-mono);}
675
- .bd-fast{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;background:transparent;color:#7A4000;font-family:var(--font-mono);}
676
- .bdp{font-size:10px;padding:2px 8px;border-radius:20px;background:var(--pl);color:var(--pl-on);font-family:var(--font-mono);}
865
+ .bd{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;background:transparent;color:var(--color-text-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}
866
+ .bd-fast{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;background:transparent;color:#7A4000;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}
867
+ .bdp{font-size:10px;padding:2px 8px;border-radius:20px;background:var(--pl);color:var(--pl-on);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}
677
868
  .brow{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;}
678
869
  .wl{font-size:11px;color:var(--color-text-secondary);margin:1.25rem 0 8px;display:flex;align-items:center;gap:6px;}
679
870
  .wd{width:6px;height:6px;border-radius:50%;background:var(--color-text-warning);display:inline-block;}
680
871
  .ec{border:1px dashed var(--color-border-tertiary);border-radius:4px;margin-bottom:8px;padding:1rem 1.25rem;}
681
- .et a{color:var(--color-text-info);text-decoration:none;font-size:13px;font-weight:500;}.et a:hover{text-decoration:underline;}
872
+ .et a{color:var(--color-text-info);text-decoration:none;font-size:13px;font-weight:600;}.et a:hover{text-decoration:underline;}
682
873
  .ex{font-size:12px;color:var(--color-text-secondary);line-height:1.5;margin:6px 0 8px;}
683
874
  .em{display:flex;gap:6px;flex-wrap:wrap;}
684
875
  .cit{font-size:10px;color:var(--color-text-secondary);margin-top:8px;padding-top:8px;border-top:.5px solid var(--color-border-tertiary);}
685
876
  .cit a{color:var(--color-text-info);text-decoration:none;}
686
- .cat-cs{background:var(--color-background-info);color:var(--color-text-info);}
877
+ .cat-cs{background:var(--color-background-secondary);color:var(--color-text-info);}
687
878
  .cat-si{background:var(--pl);color:var(--p);}
688
- .cat-me{background:var(--color-background-success);color:var(--color-text-success);}
689
- .cat-qu{background:var(--color-background-warning);color:var(--color-text-warning);}
879
+ .cat-me{background:var(--color-background-secondary);color:var(--color-text-success);}
880
+ .cat-qu{background:var(--color-background-secondary);color:var(--color-text-warning);}
690
881
  .cat-in{background:var(--color-background-secondary);color:var(--color-text-secondary);}
691
882
  .cc{background:var(--color-background-primary);border:1px solid var(--color-border-tertiary);border-radius:4px;padding:.875rem 1.25rem;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center;}
692
- .cn{font-size:13px;font-weight:500;margin-bottom:3px;}
883
+ .cn{font-size:13px;font-weight:600;margin-bottom:3px;}
693
884
  .cd{font-size:11px;color:var(--color-text-secondary);}
694
885
  .ca{display:flex;flex-direction:column;align-items:flex-end;gap:6px;}
695
- .pb{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;font-family:var(--font-mono);background:transparent;}
696
- .sl2, .sec{font-size:13px;font-weight:500;color:var(--color-text-primary);margin:1.25rem 0 8px;}.sl2:first-child, .sec:first-child{margin-top:0;}
886
+ .pb{font-size:9px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;padding:3px 8px;border:1px solid currentColor;border-radius:2px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;background:transparent;}
887
+ .sl2, .sec{font-size:13px;font-weight:600;color:var(--color-text-primary);margin:1.25rem 0 8px;}.sl2:first-child, .sec:first-child{margin-top:0;}
697
888
  .br{display:flex;align-items:center;gap:8px;margin-bottom:5px;font-size:12px;}
698
889
  .brl{width:120px;color:var(--color-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:0;}
699
890
  .brt{flex:1;height:4px;background:var(--color-background-secondary);border-radius:3px;overflow:hidden;}
@@ -702,38 +893,41 @@ export const TEMPLATE = `
702
893
  .sg{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:1rem;}
703
894
  .sk{background:var(--color-background-secondary);border-radius:8px;padding:10px 12px;}
704
895
  .skl{font-size:10px;color:var(--color-text-secondary);margin-bottom:3px;}
705
- .skv{font-family:var(--font-serif);font-style:italic;font-size:22px;font-weight:400;}.sks{font-size:10px;color:var(--color-text-secondary);margin-top:2px;}
896
+ .skv{font-family:Georgia,serif;font-style:italic;font-size:22px;font-weight:400;}.sks{font-size:10px;color:var(--color-text-secondary);margin-top:2px;}
706
897
  .rq{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:.75rem;}
707
898
  .rqp{font-size:10px;padding:3px 9px;border-radius:20px;background:var(--color-background-secondary);color:var(--color-text-secondary);border:.5px solid var(--color-border-tertiary);}
708
- .xb{display:flex;align-items:center;gap:10px;padding:12px 16px;background:var(--color-background-secondary);border:.5px solid var(--color-border-tertiary);border-radius:8px;cursor:pointer;text-align:left;width:100%;margin-bottom:8px;font-family:var(--font-mono);transition:all .15s;}
899
+ .xb{display:flex;align-items:center;gap:10px;padding:12px 16px;background:var(--color-background-secondary);border:.5px solid var(--color-border-tertiary);border-radius:8px;cursor:pointer;text-align:left;width:100%;margin-bottom:8px;font-family:inherit;transition:all .15s;}
709
900
  .xb:hover{background:var(--pl);border-color:var(--pm);}
710
- .xi{font-size:11px;width:28px;font-weight:500;flex-shrink:0;color:var(--color-text-secondary);}
901
+ .xi{font-size:11px;width:28px;font-weight:600;flex-shrink:0;color:var(--color-text-secondary);}
711
902
  .xd{font-size:11px;color:var(--color-text-secondary);margin-top:2px;}
712
- .xbl{font-weight:500;font-size:12px;}
903
+ .xbl{font-weight:600;font-size:12px;}
713
904
  .gf{display:flex;gap:5px;flex-wrap:wrap;margin-top:1.5rem;padding-top:1rem;border-top:.5px solid var(--color-border-tertiary);align-items:center;}
714
905
  .gfl{font-size:10px;color:var(--color-text-secondary);margin-right:2px;}
715
906
  .gp{font-size:10px;padding:2px 9px;border-radius:20px;background:var(--pl);color:var(--pl-on);border:.5px solid var(--pm);}
716
907
  .ap{background:var(--color-background-secondary);border-radius:8px;margin-bottom:6px;padding:.75rem 1rem;display:flex;justify-content:space-between;align-items:center;}
717
- .an2{font-size:12px;font-weight:500;}
908
+ .an2{font-size:12px;font-weight:600;}
718
909
  .am2{font-size:11px;color:var(--color-text-secondary);margin-top:2px;}
719
910
  .astar{font-size:11px;color:var(--p);}
720
911
  .note{font-size:10px;color:var(--color-text-secondary);margin-bottom:1.25rem;}
721
- .btn-out{font-size:9px;padding:2px 7px;cursor:pointer;border:.5px solid var(--pm);border-radius:20px;background:var(--pl);color:var(--pl-on);font-family:var(--font-mono);}
912
+ .btn-out{font-size:9px;padding:2px 7px;cursor:pointer;border:.5px solid var(--pm);border-radius:20px;background:var(--pl);color:var(--pl-on);font-family:inherit;}
722
913
  .btn-out:hover{background:var(--p);color:#fff;border-color:var(--p);}
723
- .cp{font-size:11px;background:var(--pl);color:var(--pl-on);border:.5px solid var(--pm);border-radius:20px;padding:3px 10px;cursor:pointer;font-family:var(--font-mono);transition:all .15s;}
914
+ .cp{font-size:11px;background:var(--pl);color:var(--pl-on);border:.5px solid var(--pm);border-radius:20px;padding:3px 10px;cursor:pointer;font-family:inherit;transition:all .15s;}
724
915
  .cp:hover{background:var(--p);color:#fff;}
725
- .cv{font-size:10px;background:var(--pl);color:var(--pl-on);border:.5px solid var(--pm);border-radius:20px;padding:2px 8px;cursor:pointer;font-family:var(--font-mono);transition:all .15s;}
916
+ .cv{font-size:10px;background:var(--pl);color:var(--pl-on);border:.5px solid var(--pm);border-radius:20px;padding:2px 8px;cursor:pointer;font-family:inherit;transition:all .15s;}
726
917
  .cv:hover{background:var(--p);color:#fff;}
727
918
  .an{font-size:13px;line-height:1.7;color:var(--color-text-primary);}
728
919
  .an p{margin:0 0 .75rem;}
729
- .an strong{font-weight:500;}
920
+ .an strong{font-weight:600;}
730
921
  .si{font-size:12px;line-height:1.65;color:var(--color-text-secondary);font-style:italic;margin:0 0 1rem;}
731
922
  </style>
732
923
 
733
- <div class="w">
924
+ <div class="fodda-brand-widget w">
734
925
  <div class="hd">
735
926
  <div class="logo">
736
- <img src="${FODDA_LOGO_URL}" alt="Fodda" style="height:24px;width:24px;"/>
927
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="height:24px;width:24px;flex-shrink:0;">
928
+ <rect width="24" height="24" rx="6" fill="var(--fodda-accent)"/>
929
+ <circle cx="12" cy="12" r="5" fill="#ffffff"/>
930
+ </svg>
737
931
  <div><div class="lt">Fodda</div><div class="ls">Brand Intelligence</div></div>
738
932
  </div>
739
933
  <div class="bfolio">{{FOLIO}}</div>
@@ -750,6 +944,7 @@ export const TEMPLATE = `
750
944
  {{EVIDENCE_HTML}}
751
945
 
752
946
  {{EARNINGS_SECTION_HTML}}
947
+ {{FINANCIAL_SNAPSHOT_SECTION_HTML}}
753
948
 
754
949
  <div class="sec">Relevant trends</div>
755
950
  {{TREND_FOOTPRINT_INTRO}}
@@ -758,8 +953,7 @@ export const TEMPLATE = `
758
953
  {{TRENDS_HTML}}
759
954
  {{WEAK_SIGNALS_HTML}}
760
955
 
761
- <div class="sec">Competitive</div>
762
- {{COMPETITOR_LIST_HTML}}
956
+ {{COMPETITIVE_SECTION_HTML}}
763
957
 
764
958
  <div class="sec">Market data</div>
765
959
  {{MARKET_DATA_INTRO}}
@@ -799,5 +993,6 @@ export const TEMPLATE = `
799
993
  <div class="gf">
800
994
  <span class="gfl">Sources:</span>
801
995
  {{SOURCE_PILLS_HTML}}
996
+ </div>
802
997
  </div>`;
803
998
  //# sourceMappingURL=brandTemplate.js.map