@burdenoff/website-sdk 2026.826.1 → 2026.828.2

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 (92) hide show
  1. package/README.md +88 -0
  2. package/dist/client/index.d.mts +2 -2
  3. package/dist/client/index.d.ts +2 -2
  4. package/dist/client/index.js +13 -7
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/index.mjs +13 -7
  7. package/dist/client/index.mjs.map +1 -1
  8. package/dist/components/contact.js +13 -7
  9. package/dist/components/contact.js.map +1 -1
  10. package/dist/components/contact.mjs +13 -7
  11. package/dist/components/contact.mjs.map +1 -1
  12. package/dist/components/explore-cta.d.mts +26 -0
  13. package/dist/components/explore-cta.d.ts +26 -0
  14. package/dist/components/explore-cta.js +108 -0
  15. package/dist/components/explore-cta.js.map +1 -0
  16. package/dist/components/explore-cta.mjs +106 -0
  17. package/dist/components/explore-cta.mjs.map +1 -0
  18. package/dist/components/explore.d.mts +43 -0
  19. package/dist/components/explore.d.ts +43 -0
  20. package/dist/components/explore.js +2219 -0
  21. package/dist/components/explore.js.map +1 -0
  22. package/dist/components/explore.mjs +2193 -0
  23. package/dist/components/explore.mjs.map +1 -0
  24. package/dist/components/newsletter.js +13 -7
  25. package/dist/components/newsletter.js.map +1 -1
  26. package/dist/components/newsletter.mjs +13 -7
  27. package/dist/components/newsletter.mjs.map +1 -1
  28. package/dist/components/partners.js +13 -7
  29. package/dist/components/partners.js.map +1 -1
  30. package/dist/components/partners.mjs +13 -7
  31. package/dist/components/partners.mjs.map +1 -1
  32. package/dist/components/pricing.js +13 -7
  33. package/dist/components/pricing.js.map +1 -1
  34. package/dist/components/pricing.mjs +13 -7
  35. package/dist/components/pricing.mjs.map +1 -1
  36. package/dist/components/resource-links.js +13 -7
  37. package/dist/components/resource-links.js.map +1 -1
  38. package/dist/components/resource-links.mjs +13 -7
  39. package/dist/components/resource-links.mjs.map +1 -1
  40. package/dist/components/social-links.js +13 -7
  41. package/dist/components/social-links.js.map +1 -1
  42. package/dist/components/social-links.mjs +13 -7
  43. package/dist/components/social-links.mjs.map +1 -1
  44. package/dist/components/unsubscribe.js +13 -7
  45. package/dist/components/unsubscribe.js.map +1 -1
  46. package/dist/components/unsubscribe.mjs +13 -7
  47. package/dist/components/unsubscribe.mjs.map +1 -1
  48. package/dist/components/waitlist.js +13 -7
  49. package/dist/components/waitlist.js.map +1 -1
  50. package/dist/components/waitlist.mjs +13 -7
  51. package/dist/components/waitlist.mjs.map +1 -1
  52. package/dist/content/index.d.mts +2 -2
  53. package/dist/content/index.d.ts +2 -2
  54. package/dist/content/index.js +13 -7
  55. package/dist/content/index.js.map +1 -1
  56. package/dist/content/index.mjs +13 -7
  57. package/dist/content/index.mjs.map +1 -1
  58. package/dist/{graphql-client-Cv6TO6hn.d.mts → graphql-client-BXn6RYV0.d.mts} +31 -4
  59. package/dist/{graphql-client-Cv6TO6hn.d.ts → graphql-client-BXn6RYV0.d.ts} +31 -4
  60. package/dist/hooks/index.d.mts +1 -0
  61. package/dist/hooks/index.d.ts +1 -0
  62. package/dist/hooks/index.js +1145 -0
  63. package/dist/hooks/index.js.map +1 -0
  64. package/dist/hooks/index.mjs +1140 -0
  65. package/dist/hooks/index.mjs.map +1 -0
  66. package/dist/{index-Cts2CAMO.d.ts → index-BXg7cQLm.d.ts} +1 -1
  67. package/dist/index-CUJTTlYx.d.mts +244 -0
  68. package/dist/index-CUJTTlYx.d.ts +244 -0
  69. package/dist/{index-B36goDSY.d.mts → index-DYxQiEm5.d.mts} +1 -1
  70. package/dist/index.d.mts +9 -6
  71. package/dist/index.d.ts +9 -6
  72. package/dist/index.js +2000 -426
  73. package/dist/index.js.map +1 -1
  74. package/dist/index.mjs +1990 -428
  75. package/dist/index.mjs.map +1 -1
  76. package/dist/product/index.js +13 -7
  77. package/dist/product/index.js.map +1 -1
  78. package/dist/product/index.mjs +13 -7
  79. package/dist/product/index.mjs.map +1 -1
  80. package/dist/{provider-BZlnZDBb.d.mts → provider-CUfI8meE.d.mts} +1 -1
  81. package/dist/{provider-CRLmsW5h.d.ts → provider-D_uUM7_e.d.ts} +1 -1
  82. package/dist/store/index.js +13 -7
  83. package/dist/store/index.js.map +1 -1
  84. package/dist/store/index.mjs +13 -7
  85. package/dist/store/index.mjs.map +1 -1
  86. package/dist/utils/index.d.mts +49 -1
  87. package/dist/utils/index.d.ts +49 -1
  88. package/dist/utils/index.js +70 -0
  89. package/dist/utils/index.js.map +1 -1
  90. package/dist/utils/index.mjs +68 -1
  91. package/dist/utils/index.mjs.map +1 -1
  92. package/package.json +6 -1
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { createContext, useMemo, useContext, useState, useCallback, useLayoutEffect, useRef, useEffect } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { Instagram, Youtube, Facebook, Github, Linkedin, Twitter, Mail, Phone, MapPin, Clock, Send, CheckCircle, Upload, Globe, ChevronDown, Search, Rocket, ArrowRight, Download, History, ChevronLeft, ChevronRight, FileText } from 'lucide-react';
4
+ import { Instagram, Youtube, Facebook, Github, Linkedin, Twitter, Mail, Phone, MapPin, Clock, Send, CheckCircle, Upload, Globe, ChevronDown, Search, Rocket, ArrowRight, Download, Sparkles, RotateCcw, Square, ArrowUp, History, BookOpenText, Layers, Link2, TriangleAlert, ChevronLeft, ChevronRight, FileText, ExternalLink } from 'lucide-react';
5
5
  import ReCAPTCHA4 from 'react-google-recaptcha';
6
6
  import { toast } from 'sonner';
7
7
  import { Helmet } from 'react-helmet-async';
@@ -75,9 +75,15 @@ var _WebSDKClient = class _WebSDKClient {
75
75
  return this.config;
76
76
  }
77
77
  /**
78
- * Execute a GraphQL query (with deduplication and caching)
78
+ * Execute a GraphQL query (with deduplication and caching).
79
+ *
80
+ * Pass `{ cache: false }` to bypass the cache entirely — required for poll
81
+ * loops, where every tick must reach the network.
79
82
  */
80
- async query(query, variables) {
83
+ async query(query, variables, options) {
84
+ if (options?.cache === false) {
85
+ return this.request(query, variables, true, options.timeoutMs);
86
+ }
81
87
  const cacheKey = JSON.stringify({ query, variables });
82
88
  const cached = this.queryCache.get(cacheKey);
83
89
  if (cached) {
@@ -90,7 +96,7 @@ var _WebSDKClient = class _WebSDKClient {
90
96
  if (existing) {
91
97
  return existing;
92
98
  }
93
- const promise = this.request(query, variables).then((result) => {
99
+ const promise = this.request(query, variables, true, options?.timeoutMs).then((result) => {
94
100
  if (result.data && !result.errors?.length) {
95
101
  if (this.queryCache.size >= _WebSDKClient.MAX_CACHE_ENTRIES) {
96
102
  const oldestEntry = this.queryCache.keys().next().value;
@@ -110,8 +116,8 @@ var _WebSDKClient = class _WebSDKClient {
110
116
  /**
111
117
  * Execute a GraphQL mutation
112
118
  */
113
- async mutate(mutation, variables) {
114
- return this.request(mutation, variables, false);
119
+ async mutate(mutation, variables, options) {
120
+ return this.request(mutation, variables, false, options?.timeoutMs);
115
121
  }
116
122
  /** HTTP statuses worth another attempt — transient server/edge conditions. */
117
123
  static isRetriableStatus(status) {
@@ -182,8 +188,8 @@ var _WebSDKClient = class _WebSDKClient {
182
188
  clearTimeout(timer);
183
189
  }
184
190
  }
185
- async request(query, variables, retry = true) {
186
- const timeoutMs = this.config.timeoutMs ?? _WebSDKClient.DEFAULT_TIMEOUT_MS;
191
+ async request(query, variables, retry = true, timeoutOverrideMs) {
192
+ const timeoutMs = timeoutOverrideMs ?? this.config.timeoutMs ?? _WebSDKClient.DEFAULT_TIMEOUT_MS;
187
193
  const maxRetries = retry ? this.config.maxRetries ?? _WebSDKClient.DEFAULT_MAX_RETRIES : 0;
188
194
  let last;
189
195
  for (let i = 0; i <= maxRetries; i++) {
@@ -4708,228 +4714,6 @@ function ElectronDownloadLink({
4708
4714
  }
4709
4715
  );
4710
4716
  }
4711
- function RybbitAnalytics({
4712
- siteId,
4713
- scriptUrl = "https://analytics.burdenoff.com/api/script.js",
4714
- enabled = true
4715
- }) {
4716
- useEffect(() => {
4717
- if (!enabled || !siteId) return;
4718
- const existingScript = document.querySelector(
4719
- `script[data-site-id="${siteId}"]`
4720
- );
4721
- if (existingScript) return;
4722
- const script = document.createElement("script");
4723
- script.src = scriptUrl;
4724
- script.defer = true;
4725
- script.crossOrigin = "anonymous";
4726
- script.setAttribute("data-site-id", siteId);
4727
- document.head.appendChild(script);
4728
- return () => {
4729
- const scriptToRemove = document.querySelector(
4730
- `script[data-site-id="${siteId}"]`
4731
- );
4732
- if (scriptToRemove) {
4733
- document.head.removeChild(scriptToRemove);
4734
- }
4735
- };
4736
- }, [siteId, scriptUrl, enabled]);
4737
- return null;
4738
- }
4739
- var PUBLIC_JOB_POSTING_FIELDS = (
4740
- /* GraphQL */
4741
- `
4742
- id
4743
- slug
4744
- title
4745
- department
4746
- location
4747
- employmentType
4748
- openings
4749
- description
4750
- overview
4751
- responsibilities
4752
- learnings
4753
- qualifications
4754
- rounds {
4755
- roundNumber
4756
- name
4757
- roundType
4758
- isRequired
4759
- description
4760
- }
4761
- `
4762
- );
4763
- var PUBLIC_JOB_POSTINGS_QUERY = (
4764
- /* GraphQL */
4765
- `
4766
- query PublicJobPostings($productId: ID, $limit: Int, $offset: Int) {
4767
- publicJobPostings(productId: $productId, limit: $limit, offset: $offset) {
4768
- ${PUBLIC_JOB_POSTING_FIELDS}
4769
- }
4770
- }
4771
- `
4772
- );
4773
- var PUBLIC_JOB_POSTING_QUERY = (
4774
- /* GraphQL */
4775
- `
4776
- query PublicJobPosting($slug: String!, $productId: ID) {
4777
- publicJobPosting(slug: $slug, productId: $productId) {
4778
- ${PUBLIC_JOB_POSTING_FIELDS}
4779
- }
4780
- }
4781
- `
4782
- );
4783
- var PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY = (
4784
- /* GraphQL */
4785
- `
4786
- query PublicJobPostingsForSubmission(
4787
- $productId: ID
4788
- $limit: Int
4789
- $offset: Int
4790
- ) {
4791
- publicJobPostingsForSubmission(
4792
- productId: $productId
4793
- limit: $limit
4794
- offset: $offset
4795
- ) {
4796
- id
4797
- title
4798
- }
4799
- }
4800
- `
4801
- );
4802
- async function fetchPublicJobPostings(client, options) {
4803
- const result = await client.query(PUBLIC_JOB_POSTINGS_QUERY, {
4804
- productId: options?.productId,
4805
- limit: options?.limit ?? 50,
4806
- offset: options?.offset ?? 0
4807
- });
4808
- if (result.errors?.length) {
4809
- throw new Error(result.errors[0]?.message ?? "Unknown error");
4810
- }
4811
- return result.data?.publicJobPostings ?? [];
4812
- }
4813
- async function fetchPublicJobPosting(client, slug, options) {
4814
- const result = await client.query(PUBLIC_JOB_POSTING_QUERY, {
4815
- slug,
4816
- productId: options?.productId
4817
- });
4818
- if (result.errors?.length) {
4819
- throw new Error(result.errors[0]?.message ?? "Unknown error");
4820
- }
4821
- return result.data?.publicJobPosting ?? null;
4822
- }
4823
- async function fetchPublicJobPostingsForSubmission(client, options) {
4824
- const result = await client.query(PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY, {
4825
- productId: options?.productId,
4826
- limit: options?.limit ?? 50,
4827
- offset: options?.offset ?? 0
4828
- });
4829
- if (result.errors?.length) {
4830
- throw new Error(result.errors[0]?.message ?? "Unknown error");
4831
- }
4832
- return result.data?.publicJobPostingsForSubmission ?? [];
4833
- }
4834
- function usePublicJobPostings(options) {
4835
- const client = useWebSDK();
4836
- const config = useWebSDKConfig();
4837
- const { product } = useProduct();
4838
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
4839
- const [postings, setPostings] = useState([]);
4840
- const [loading, setLoading] = useState(true);
4841
- const [error, setError] = useState(null);
4842
- const fetchPostings = useCallback(async () => {
4843
- setLoading(true);
4844
- setError(null);
4845
- try {
4846
- const items = await fetchPublicJobPostings(client, {
4847
- productId: resolvedProductId,
4848
- limit: options?.limit,
4849
- offset: options?.offset
4850
- });
4851
- setPostings(items);
4852
- } catch (err) {
4853
- setError(
4854
- err instanceof Error ? err.message : "Failed to fetch job postings"
4855
- );
4856
- setPostings([]);
4857
- } finally {
4858
- setLoading(false);
4859
- }
4860
- }, [client, resolvedProductId, options?.limit, options?.offset]);
4861
- useEffect(() => {
4862
- fetchPostings();
4863
- }, [fetchPostings]);
4864
- return { postings, loading, error, refetch: fetchPostings };
4865
- }
4866
- function usePublicJobPostingsForSubmission(options) {
4867
- const client = useWebSDK();
4868
- const config = useWebSDKConfig();
4869
- const { product } = useProduct();
4870
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
4871
- const [postings, setPostings] = useState([]);
4872
- const [loading, setLoading] = useState(true);
4873
- const [error, setError] = useState(null);
4874
- const fetchPostings = useCallback(async () => {
4875
- setLoading(true);
4876
- setError(null);
4877
- try {
4878
- const items = await fetchPublicJobPostingsForSubmission(client, {
4879
- productId: resolvedProductId,
4880
- limit: options?.limit,
4881
- offset: options?.offset
4882
- });
4883
- setPostings(items);
4884
- } catch (err) {
4885
- setError(
4886
- err instanceof Error ? err.message : "Failed to fetch job postings"
4887
- );
4888
- setPostings([]);
4889
- } finally {
4890
- setLoading(false);
4891
- }
4892
- }, [client, resolvedProductId, options?.limit, options?.offset]);
4893
- useEffect(() => {
4894
- fetchPostings();
4895
- }, [fetchPostings]);
4896
- return { postings, loading, error, refetch: fetchPostings };
4897
- }
4898
- function usePublicJobPosting(slug, options) {
4899
- const client = useWebSDK();
4900
- const config = useWebSDKConfig();
4901
- const { product } = useProduct();
4902
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
4903
- const [posting, setPosting] = useState(null);
4904
- const [loading, setLoading] = useState(true);
4905
- const [error, setError] = useState(null);
4906
- const fetchPosting = useCallback(async () => {
4907
- if (!slug) {
4908
- setPosting(null);
4909
- setLoading(false);
4910
- return;
4911
- }
4912
- setLoading(true);
4913
- setError(null);
4914
- try {
4915
- const item = await fetchPublicJobPosting(client, slug, {
4916
- productId: resolvedProductId
4917
- });
4918
- setPosting(item);
4919
- } catch (err) {
4920
- setError(
4921
- err instanceof Error ? err.message : "Failed to fetch job posting"
4922
- );
4923
- setPosting(null);
4924
- } finally {
4925
- setLoading(false);
4926
- }
4927
- }, [client, resolvedProductId, slug]);
4928
- useEffect(() => {
4929
- fetchPosting();
4930
- }, [fetchPosting]);
4931
- return { posting, loading, error, refetch: fetchPosting };
4932
- }
4933
4717
  var PROSE_CSS = `
4934
4718
  .boff-prose {
4935
4719
  max-width: none;
@@ -5106,32 +4890,1985 @@ var PROSE_CSS = `
5106
4890
  function ProseStyles() {
5107
4891
  return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: PROSE_CSS } });
5108
4892
  }
5109
- var BLOG_POSTS_QUERY = `
5110
- query BlogPosts($productId: String!, $limit: Int, $offset: Int) {
5111
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
5112
- items {
5113
- id
5114
- title
4893
+
4894
+ // src/components/explore-types.ts
4895
+ var EXPLORE_MESSAGE_FIELDS = (
4896
+ /* GraphQL */
4897
+ `
4898
+ id
4899
+ conversationId
4900
+ role
4901
+ status
4902
+ content
4903
+ partialContent
4904
+ activity
4905
+ references {
4906
+ url
4907
+ title
4908
+ description
4909
+ product
4910
+ imageUrl
4911
+ }
4912
+ ctas {
4913
+ kind
4914
+ label
4915
+ url
4916
+ product
4917
+ }
4918
+ errorCode
4919
+ createdAt
4920
+ completedAt
4921
+ `
4922
+ );
4923
+ var EXPLORE_CATALOG_QUERY = (
4924
+ /* GraphQL */
4925
+ `
4926
+ query ExploreCatalog($productSlug: String) {
4927
+ exploreCatalog(productSlug: $productSlug) {
4928
+ enabled
4929
+ products {
5115
4930
  slug
5116
- lastUpdated
5117
- tags
5118
- seoDescription
5119
- excerpt
5120
- thumbnail
5121
- author
5122
- publishedAt
5123
- readingTime
5124
- featured
5125
- category
4931
+ name
4932
+ tagline
4933
+ website
5126
4934
  }
5127
- total
5128
- hasMore
4935
+ examplePrompts
4936
+ welcomeTitle
4937
+ welcomeBody
4938
+ limits {
4939
+ maxMessageLength
4940
+ maxTurnsPerConversation
4941
+ messagesPerDay
4942
+ historyTurns
4943
+ }
4944
+ captchaRequired
4945
+ recaptchaSiteKey
5129
4946
  }
5130
4947
  }
5131
- `;
5132
- var BLOG_POST_QUERY = `
5133
- query BlogPost($slug: String!, $productId: String) {
5134
- publicContentPage(slug: $slug, productId: $productId) {
4948
+ `
4949
+ );
4950
+ var EXPLORE_MESSAGE_QUERY = (
4951
+ /* GraphQL */
4952
+ `
4953
+ query ExploreMessage($conversationToken: String!, $id: ID!) {
4954
+ exploreMessage(conversationToken: $conversationToken, id: $id) {
4955
+ ${EXPLORE_MESSAGE_FIELDS}
4956
+ }
4957
+ }
4958
+ `
4959
+ );
4960
+ var SEND_EXPLORE_MESSAGE_MUTATION = (
4961
+ /* GraphQL */
4962
+ `
4963
+ mutation SendExploreMessage($input: SendExploreMessageInput!) {
4964
+ sendExploreMessage(input: $input) {
4965
+ conversation {
4966
+ id
4967
+ token
4968
+ productSlug
4969
+ turnCount
4970
+ createdAt
4971
+ }
4972
+ userMessage {
4973
+ ${EXPLORE_MESSAGE_FIELDS}
4974
+ }
4975
+ assistantMessage {
4976
+ ${EXPLORE_MESSAGE_FIELDS}
4977
+ }
4978
+ remainingToday
4979
+ }
4980
+ }
4981
+ `
4982
+ );
4983
+
4984
+ // src/data/products.ts
4985
+ var ECOSYSTEM_PRODUCTS = [
4986
+ {
4987
+ slug: "workspaces",
4988
+ name: "Workspaces",
4989
+ websiteUrl: "https://burdenoff.com"
4990
+ },
4991
+ {
4992
+ slug: "vibecontrols",
4993
+ name: "VibeControls",
4994
+ websiteUrl: "https://vibecontrols.com"
4995
+ },
4996
+ {
4997
+ slug: "fluidgrids",
4998
+ name: "FluidGrids",
4999
+ websiteUrl: "https://fluidgrids.com"
5000
+ },
5001
+ // Botlit ships on botlit.ai — botlit.com does not resolve.
5002
+ { slug: "botlit", name: "Botlit", websiteUrl: "https://www.botlit.ai" },
5003
+ {
5004
+ slug: "bigconsole",
5005
+ name: "BigConsole",
5006
+ websiteUrl: "https://bigconsole.com"
5007
+ },
5008
+ {
5009
+ slug: "semanticfed",
5010
+ name: "SemanticFed",
5011
+ websiteUrl: "https://semanticfed.com"
5012
+ },
5013
+ {
5014
+ slug: "intelwatchtower",
5015
+ name: "IntelWatchtower",
5016
+ websiteUrl: "https://intelwatchtower.com"
5017
+ },
5018
+ { slug: "buildmyiq", name: "BuildMyIQ", websiteUrl: "https://buildmyiq.com" },
5019
+ {
5020
+ slug: "assethandler",
5021
+ name: "AssetHandler",
5022
+ websiteUrl: "https://assethandler.com"
5023
+ },
5024
+ {
5025
+ slug: "crewfoundry",
5026
+ name: "CrewFoundry",
5027
+ websiteUrl: "https://crewfoundry.com"
5028
+ },
5029
+ {
5030
+ slug: "planmagnet",
5031
+ name: "PlanMagnet",
5032
+ websiteUrl: "https://planmagnet.com"
5033
+ },
5034
+ {
5035
+ slug: "eventfullymanaged",
5036
+ name: "EventfullyManaged",
5037
+ websiteUrl: "https://eventfullymanaged.com"
5038
+ },
5039
+ {
5040
+ slug: "headshotmarketing",
5041
+ name: "HeadshotMarketing",
5042
+ websiteUrl: "https://headshotmarketing.com"
5043
+ },
5044
+ {
5045
+ slug: "technospam",
5046
+ name: "TechnoSpam",
5047
+ websiteUrl: "https://technospam.com"
5048
+ },
5049
+ { slug: "kadaikodi", name: "Kadaikodi", websiteUrl: "https://kadaikodi.com" },
5050
+ {
5051
+ slug: "movethewheels",
5052
+ name: "MoveTheWheels",
5053
+ websiteUrl: "https://movethewheels.com"
5054
+ },
5055
+ {
5056
+ slug: "healthybowl",
5057
+ name: "Healthy Bowl",
5058
+ websiteUrl: "https://gethealthybowl.com"
5059
+ },
5060
+ { slug: "ggnomad", name: "GGNomad", websiteUrl: "https://ggnomad.com" },
5061
+ {
5062
+ slug: "timecampus",
5063
+ name: "TimeCampus",
5064
+ websiteUrl: "https://timecampus.com"
5065
+ },
5066
+ {
5067
+ slug: "manufacturedops",
5068
+ name: "ManufacturedOps",
5069
+ websiteUrl: "https://manufacturedops.com"
5070
+ },
5071
+ {
5072
+ slug: "theglobalfuel",
5073
+ name: "TheGlobalFuel",
5074
+ websiteUrl: "https://theglobalfuel.com"
5075
+ },
5076
+ {
5077
+ slug: "housingvista",
5078
+ name: "HousingVista",
5079
+ websiteUrl: "https://housingvista.com"
5080
+ },
5081
+ {
5082
+ slug: "proserviceworld",
5083
+ name: "ProServiceWorld",
5084
+ websiteUrl: "https://proserviceworld.com"
5085
+ },
5086
+ {
5087
+ slug: "mybodyshield",
5088
+ name: "MyBodyShield",
5089
+ websiteUrl: "https://mybodyshield.com"
5090
+ },
5091
+ {
5092
+ slug: "artistrybase",
5093
+ name: "ArtistryBase",
5094
+ websiteUrl: "https://artistrybase.com"
5095
+ },
5096
+ {
5097
+ slug: "theculturepost",
5098
+ name: "TheCulturePost",
5099
+ websiteUrl: "https://theculturepost.com"
5100
+ },
5101
+ { slug: "collabkin", name: "CollabKin", websiteUrl: "https://collabkin.com" },
5102
+ {
5103
+ slug: "comradecircle",
5104
+ name: "ComradeCircle",
5105
+ websiteUrl: "https://comradecircle.com"
5106
+ },
5107
+ {
5108
+ slug: "brainyrich",
5109
+ name: "BrainyRich",
5110
+ websiteUrl: "https://brainyrich.com"
5111
+ },
5112
+ {
5113
+ slug: "cosmicintersection",
5114
+ name: "CosmicIntersection",
5115
+ websiteUrl: "https://cosmicintersection.com"
5116
+ },
5117
+ {
5118
+ slug: "govcitizenhub",
5119
+ name: "GovCitizenHub",
5120
+ websiteUrl: "https://govcitizenhub.com"
5121
+ },
5122
+ {
5123
+ slug: "adaptercloud",
5124
+ name: "AdapterCloud",
5125
+ websiteUrl: "https://adaptercloud.com"
5126
+ },
5127
+ {
5128
+ slug: "subscriberbot",
5129
+ name: "Subscriber Bot",
5130
+ websiteUrl: "https://subscriberbot.com"
5131
+ },
5132
+ {
5133
+ slug: "hookmovies",
5134
+ name: "HookMovies",
5135
+ websiteUrl: "https://hookmovies.com"
5136
+ },
5137
+ {
5138
+ slug: "coolandlovely",
5139
+ name: "CoolAndLovely",
5140
+ websiteUrl: "https://coolandlovely.com"
5141
+ },
5142
+ {
5143
+ slug: "atheletebridge",
5144
+ name: "AthleteBridge",
5145
+ websiteUrl: "https://atheletebridge.com"
5146
+ },
5147
+ {
5148
+ slug: "labsofscience",
5149
+ name: "LabsOfScience",
5150
+ websiteUrl: "https://labsofscience.com"
5151
+ },
5152
+ {
5153
+ slug: "ecoimpacthub",
5154
+ name: "EcoImpactHub",
5155
+ websiteUrl: "https://ecoimpacthub.com"
5156
+ }
5157
+ ];
5158
+
5159
+ // src/utils/recaptcha-v3.ts
5160
+ var SCRIPT_ID = "boff-recaptcha-v3";
5161
+ var SCRIPT_SRC = "https://www.google.com/recaptcha/api.js?render=";
5162
+ var EXPLORE_RECAPTCHA_ACTION = "explore_chat";
5163
+ var loaders = /* @__PURE__ */ new Map();
5164
+ function isBrowser() {
5165
+ return typeof window !== "undefined" && typeof document !== "undefined";
5166
+ }
5167
+ function loadRecaptchaV3(siteKey) {
5168
+ if (!siteKey) {
5169
+ return Promise.reject(new Error("reCAPTCHA site key is required"));
5170
+ }
5171
+ if (!isBrowser()) {
5172
+ return Promise.reject(
5173
+ new Error("reCAPTCHA v3 can only be loaded in a browser")
5174
+ );
5175
+ }
5176
+ const existing = loaders.get(siteKey);
5177
+ if (existing) return existing;
5178
+ const promise = new Promise((resolve, reject) => {
5179
+ const ready = () => {
5180
+ const api = window.grecaptcha;
5181
+ if (!api) {
5182
+ reject(new Error("reCAPTCHA loaded but grecaptcha is unavailable"));
5183
+ return;
5184
+ }
5185
+ api.ready(() => resolve(api));
5186
+ };
5187
+ if (window.grecaptcha) {
5188
+ ready();
5189
+ return;
5190
+ }
5191
+ const scriptId = `${SCRIPT_ID}-${siteKey}`;
5192
+ const found = document.getElementById(scriptId);
5193
+ if (found) {
5194
+ found.addEventListener("load", ready, { once: true });
5195
+ found.addEventListener(
5196
+ "error",
5197
+ () => reject(new Error("Failed to load reCAPTCHA v3")),
5198
+ { once: true }
5199
+ );
5200
+ return;
5201
+ }
5202
+ const script = document.createElement("script");
5203
+ script.id = scriptId;
5204
+ script.src = `${SCRIPT_SRC}${encodeURIComponent(siteKey)}`;
5205
+ script.async = true;
5206
+ script.defer = true;
5207
+ script.addEventListener("load", ready, { once: true });
5208
+ script.addEventListener(
5209
+ "error",
5210
+ () => {
5211
+ loaders.delete(siteKey);
5212
+ reject(new Error("Failed to load reCAPTCHA v3"));
5213
+ },
5214
+ { once: true }
5215
+ );
5216
+ document.head.appendChild(script);
5217
+ });
5218
+ loaders.set(siteKey, promise);
5219
+ return promise;
5220
+ }
5221
+ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
5222
+ const api = await loadRecaptchaV3(siteKey);
5223
+ return api.execute(siteKey, { action });
5224
+ }
5225
+
5226
+ // src/hooks/use-explore-chat.ts
5227
+ var DEFAULT_STORAGE_KEY = "boff.explore.v1";
5228
+ var DEFAULT_POLL_INTERVAL_MS = 1500;
5229
+ var DEFAULT_POLL_TIMEOUT_MS = 9e4;
5230
+ var POLL_REQUEST_TIMEOUT_MS = 1e4;
5231
+ var SEND_REQUEST_TIMEOUT_MS = 18e3;
5232
+ var MAX_CONSECUTIVE_POLL_FAILURES = 4;
5233
+ var MAX_PERSISTED_MESSAGES = 40;
5234
+ var FALLBACK_EXPLORE_LIMITS = {
5235
+ maxMessageLength: 2e3,
5236
+ maxTurnsPerConversation: 20,
5237
+ messagesPerDay: 100,
5238
+ historyTurns: 6
5239
+ };
5240
+ var DEFAULT_EXPLORE_EXAMPLE_PROMPTS = [
5241
+ "What does this product actually do?",
5242
+ "Which Burdenoff product fits my team?",
5243
+ "How do I get started, step by step?",
5244
+ "What does it integrate with?"
5245
+ ];
5246
+ var FALLBACK_WELCOME_TITLE = "Ask anything about our products";
5247
+ var FALLBACK_WELCOME_BODY = "Get straight answers drawn from our product documentation and public pages \u2014 with links back to the exact page each answer came from.";
5248
+ function buildFallbackCatalog(examplePrompts) {
5249
+ return {
5250
+ enabled: true,
5251
+ products: ECOSYSTEM_PRODUCTS.map((p) => ({
5252
+ slug: p.slug,
5253
+ name: p.name,
5254
+ tagline: null,
5255
+ website: p.websiteUrl
5256
+ })),
5257
+ examplePrompts: examplePrompts?.length ? examplePrompts : DEFAULT_EXPLORE_EXAMPLE_PROMPTS,
5258
+ welcomeTitle: FALLBACK_WELCOME_TITLE,
5259
+ welcomeBody: FALLBACK_WELCOME_BODY,
5260
+ limits: FALLBACK_EXPLORE_LIMITS,
5261
+ captchaRequired: false,
5262
+ recaptchaSiteKey: null
5263
+ };
5264
+ }
5265
+ function normalizeCatalog(raw, fallback) {
5266
+ return {
5267
+ enabled: raw.enabled !== false,
5268
+ products: raw.products?.length ? raw.products : fallback.products,
5269
+ examplePrompts: raw.examplePrompts?.length ? raw.examplePrompts : fallback.examplePrompts,
5270
+ welcomeTitle: raw.welcomeTitle || fallback.welcomeTitle,
5271
+ welcomeBody: raw.welcomeBody || fallback.welcomeBody,
5272
+ limits: {
5273
+ maxMessageLength: raw.limits?.maxMessageLength || fallback.limits.maxMessageLength,
5274
+ maxTurnsPerConversation: raw.limits?.maxTurnsPerConversation || fallback.limits.maxTurnsPerConversation,
5275
+ messagesPerDay: raw.limits?.messagesPerDay || fallback.limits.messagesPerDay,
5276
+ historyTurns: raw.limits?.historyTurns || fallback.limits.historyTurns
5277
+ },
5278
+ captchaRequired: raw.captchaRequired === true,
5279
+ recaptchaSiteKey: raw.recaptchaSiteKey ?? null
5280
+ };
5281
+ }
5282
+ var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
5283
+ "RATE_LIMITED",
5284
+ "HTTP_429",
5285
+ "EXPLORE_GLOBAL_LIMIT"
5286
+ ]);
5287
+ var CAPTCHA_CODES = /* @__PURE__ */ new Set(["CAPTCHA_FAILED", "CAPTCHA_REQUIRED"]);
5288
+ var NETWORK_CODES = /* @__PURE__ */ new Set([
5289
+ "NETWORK_ERROR",
5290
+ "UPSTREAM_UNAVAILABLE",
5291
+ "CONVERSATION_BUSY",
5292
+ "PROVIDER_ERROR",
5293
+ "QUEUE_FULL"
5294
+ ]);
5295
+ var VALIDATION_CODES = /* @__PURE__ */ new Set([
5296
+ "MESSAGE_TOO_LONG",
5297
+ "MESSAGE_EMPTY",
5298
+ "CONVERSATION_LIMIT",
5299
+ "INVALID_INPUT"
5300
+ ]);
5301
+ function classifyExploreError(code, serverMessage) {
5302
+ const c = code ?? "UNKNOWN";
5303
+ if (RATE_LIMIT_CODES.has(c)) {
5304
+ return {
5305
+ kind: "rate-limit",
5306
+ code: c,
5307
+ message: "You've reached the message limit for now. Please try again later.",
5308
+ retryable: false
5309
+ };
5310
+ }
5311
+ if (CAPTCHA_CODES.has(c)) {
5312
+ return {
5313
+ kind: "captcha",
5314
+ code: c,
5315
+ message: "We couldn't verify that you're human. Please try sending that again.",
5316
+ retryable: true
5317
+ };
5318
+ }
5319
+ if (c === "TIMEOUT" || c === "TOOL_BUDGET") {
5320
+ return {
5321
+ kind: "timeout",
5322
+ code: c,
5323
+ message: "That answer took too long to come back. Try asking again.",
5324
+ retryable: true
5325
+ };
5326
+ }
5327
+ if (NETWORK_CODES.has(c)) {
5328
+ return {
5329
+ kind: "network",
5330
+ code: c,
5331
+ message: "We couldn't reach the assistant just now. Check your connection and try again.",
5332
+ retryable: true
5333
+ };
5334
+ }
5335
+ if (c === "EXPLORE_DISABLED") {
5336
+ return {
5337
+ kind: "disabled",
5338
+ code: c,
5339
+ message: "Explore is unavailable at the moment. Please check back soon.",
5340
+ retryable: false
5341
+ };
5342
+ }
5343
+ if (VALIDATION_CODES.has(c)) {
5344
+ return {
5345
+ kind: "validation",
5346
+ code: c,
5347
+ message: serverMessage || "That message couldn't be sent.",
5348
+ retryable: false
5349
+ };
5350
+ }
5351
+ if (c === "EMPTY_ANSWER") {
5352
+ return {
5353
+ kind: "unknown",
5354
+ code: c,
5355
+ message: "No answer came back for that one. Try rephrasing, or ask something more specific.",
5356
+ retryable: true
5357
+ };
5358
+ }
5359
+ return {
5360
+ kind: "unknown",
5361
+ code: c,
5362
+ message: serverMessage || "Something went wrong. Please try again.",
5363
+ retryable: true
5364
+ };
5365
+ }
5366
+ function readPersisted(key) {
5367
+ if (typeof window === "undefined") return null;
5368
+ try {
5369
+ const raw = window.sessionStorage.getItem(key);
5370
+ if (!raw) return null;
5371
+ const parsed = JSON.parse(raw);
5372
+ if (!parsed || typeof parsed !== "object") return null;
5373
+ const record = parsed;
5374
+ return {
5375
+ conversationToken: typeof record.conversationToken === "string" ? record.conversationToken : null,
5376
+ messages: Array.isArray(record.messages) ? record.messages.slice(-MAX_PERSISTED_MESSAGES) : [],
5377
+ focusProduct: typeof record.focusProduct === "string" ? record.focusProduct : null
5378
+ };
5379
+ } catch {
5380
+ return null;
5381
+ }
5382
+ }
5383
+ function visibleText(message) {
5384
+ return message.content || message.partialContent || "";
5385
+ }
5386
+ function countUserTurns(messages) {
5387
+ return messages.filter((m) => m.role === "USER").length;
5388
+ }
5389
+ function useExploreChat(options = {}) {
5390
+ const {
5391
+ productSlug,
5392
+ pageUrl,
5393
+ locale,
5394
+ examplePrompts,
5395
+ pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
5396
+ pollTimeoutMs = DEFAULT_POLL_TIMEOUT_MS,
5397
+ storageKey = DEFAULT_STORAGE_KEY,
5398
+ persist = true,
5399
+ onSend
5400
+ } = options;
5401
+ const client = useWebSDK();
5402
+ const promptKey = JSON.stringify(examplePrompts ?? []);
5403
+ const fallbackCatalog = useMemo(
5404
+ () => buildFallbackCatalog(JSON.parse(promptKey)),
5405
+ [promptKey]
5406
+ );
5407
+ const [catalog, setCatalog] = useState(fallbackCatalog);
5408
+ const [messages, setMessages] = useState([]);
5409
+ const [phase, setPhase] = useState("loading");
5410
+ const [error, setError] = useState(null);
5411
+ const [remainingToday, setRemainingToday] = useState(null);
5412
+ const [focusProduct, setFocusProductState] = useState(null);
5413
+ const [turnCount, setTurnCount] = useState(0);
5414
+ const [hydrated, setHydrated] = useState(false);
5415
+ const mountedRef = useRef(true);
5416
+ const pollGenerationRef = useRef(0);
5417
+ const pollTimerRef = useRef(null);
5418
+ const conversationTokenRef = useRef(null);
5419
+ const catalogRef = useRef(fallbackCatalog);
5420
+ const turnCountRef = useRef(0);
5421
+ const focusProductRef = useRef(null);
5422
+ const lastSentTextRef = useRef(null);
5423
+ const inFlightRef = useRef(false);
5424
+ catalogRef.current = catalog;
5425
+ const clearPollTimer = useCallback(() => {
5426
+ if (pollTimerRef.current !== null) {
5427
+ clearTimeout(pollTimerRef.current);
5428
+ pollTimerRef.current = null;
5429
+ }
5430
+ }, []);
5431
+ const cancelPolling = useCallback(() => {
5432
+ pollGenerationRef.current += 1;
5433
+ clearPollTimer();
5434
+ }, [clearPollTimer]);
5435
+ useEffect(() => {
5436
+ mountedRef.current = true;
5437
+ return () => {
5438
+ mountedRef.current = false;
5439
+ pollGenerationRef.current += 1;
5440
+ if (pollTimerRef.current !== null) {
5441
+ clearTimeout(pollTimerRef.current);
5442
+ pollTimerRef.current = null;
5443
+ }
5444
+ };
5445
+ }, []);
5446
+ useEffect(() => {
5447
+ if (!persist) {
5448
+ setHydrated(true);
5449
+ return;
5450
+ }
5451
+ const stored = readPersisted(storageKey);
5452
+ if (stored) {
5453
+ conversationTokenRef.current = stored.conversationToken;
5454
+ focusProductRef.current = stored.focusProduct;
5455
+ const settled = stored.messages.flatMap((m) => {
5456
+ if (m.role !== "ASSISTANT") return [m];
5457
+ if (m.status !== "PENDING" && m.status !== "RUNNING") return [m];
5458
+ const text = visibleText(m);
5459
+ if (!text) return [];
5460
+ return [{ ...m, status: "COMPLETED", content: text }];
5461
+ });
5462
+ setMessages(settled);
5463
+ setFocusProductState(stored.focusProduct);
5464
+ const turns = countUserTurns(settled);
5465
+ turnCountRef.current = turns;
5466
+ setTurnCount(turns);
5467
+ }
5468
+ setHydrated(true);
5469
+ }, [persist, storageKey]);
5470
+ useEffect(() => {
5471
+ if (!persist || !hydrated || typeof window === "undefined") return;
5472
+ try {
5473
+ const payload = {
5474
+ conversationToken: conversationTokenRef.current,
5475
+ messages: messages.slice(-MAX_PERSISTED_MESSAGES),
5476
+ focusProduct
5477
+ };
5478
+ if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
5479
+ window.sessionStorage.removeItem(storageKey);
5480
+ return;
5481
+ }
5482
+ window.sessionStorage.setItem(storageKey, JSON.stringify(payload));
5483
+ } catch {
5484
+ }
5485
+ }, [persist, hydrated, storageKey, messages, focusProduct]);
5486
+ useEffect(() => {
5487
+ let cancelled = false;
5488
+ const load = async () => {
5489
+ const res = await client.query(
5490
+ EXPLORE_CATALOG_QUERY,
5491
+ { productSlug: productSlug ?? null }
5492
+ );
5493
+ if (cancelled || !mountedRef.current) return;
5494
+ const raw = res.data?.exploreCatalog;
5495
+ if (res.errors?.length || !raw) {
5496
+ console.warn(
5497
+ "[WebSDK] exploreCatalog unavailable \u2014 using the built-in fallback catalog",
5498
+ res.errors?.[0]?.message
5499
+ );
5500
+ setPhase((prev) => prev === "loading" ? "ready" : prev);
5501
+ return;
5502
+ }
5503
+ const next = normalizeCatalog(raw, fallbackCatalog);
5504
+ setCatalog(next);
5505
+ catalogRef.current = next;
5506
+ setPhase((prev) => {
5507
+ if (!next.enabled) return "disabled";
5508
+ return prev === "loading" ? "ready" : prev;
5509
+ });
5510
+ };
5511
+ void load();
5512
+ return () => {
5513
+ cancelled = true;
5514
+ };
5515
+ }, [client, productSlug, fallbackCatalog]);
5516
+ const startPolling = useCallback(
5517
+ (conversationToken, messageId) => {
5518
+ pollGenerationRef.current += 1;
5519
+ const generation = pollGenerationRef.current;
5520
+ const startedAt = Date.now();
5521
+ let consecutiveFailures = 0;
5522
+ const isStale = () => !mountedRef.current || generation !== pollGenerationRef.current;
5523
+ const finishWithError = (chatError) => {
5524
+ inFlightRef.current = false;
5525
+ setMessages(
5526
+ (prev) => prev.flatMap((m) => {
5527
+ if (m.id !== messageId) return [m];
5528
+ const text = visibleText(m);
5529
+ if (!text) return [];
5530
+ return [{ ...m, status: "FAILED", content: text }];
5531
+ })
5532
+ );
5533
+ setError(chatError);
5534
+ setPhase(chatError.kind === "rate-limit" ? "limited" : "error");
5535
+ };
5536
+ const tick = async () => {
5537
+ if (isStale()) return;
5538
+ if (Date.now() - startedAt > pollTimeoutMs) {
5539
+ finishWithError(classifyExploreError("TIMEOUT"));
5540
+ return;
5541
+ }
5542
+ const res = await client.query(
5543
+ EXPLORE_MESSAGE_QUERY,
5544
+ { conversationToken, id: messageId },
5545
+ { cache: false, timeoutMs: POLL_REQUEST_TIMEOUT_MS }
5546
+ );
5547
+ if (isStale()) return;
5548
+ const first = res.errors?.[0];
5549
+ if (first) {
5550
+ const code = typeof first.extensions?.code === "string" ? first.extensions.code : void 0;
5551
+ const classified = classifyExploreError(code, first.message);
5552
+ if (!classified.retryable) {
5553
+ finishWithError(classified);
5554
+ return;
5555
+ }
5556
+ consecutiveFailures += 1;
5557
+ if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
5558
+ finishWithError(classified);
5559
+ return;
5560
+ }
5561
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5562
+ return;
5563
+ }
5564
+ const message = res.data?.exploreMessage;
5565
+ if (!message) {
5566
+ consecutiveFailures += 1;
5567
+ if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
5568
+ finishWithError(classifyExploreError("NOT_FOUND"));
5569
+ return;
5570
+ }
5571
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5572
+ return;
5573
+ }
5574
+ consecutiveFailures = 0;
5575
+ setMessages(
5576
+ (prev) => prev.map((m) => m.id === messageId ? message : m)
5577
+ );
5578
+ if (message.status === "COMPLETED") {
5579
+ inFlightRef.current = false;
5580
+ setError(null);
5581
+ setPhase("ready");
5582
+ return;
5583
+ }
5584
+ if (message.status === "FAILED") {
5585
+ finishWithError(
5586
+ classifyExploreError(message.errorCode ?? "UPSTREAM_UNAVAILABLE")
5587
+ );
5588
+ return;
5589
+ }
5590
+ setPhase("streaming");
5591
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5592
+ };
5593
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5594
+ },
5595
+ [client, pollIntervalMs, pollTimeoutMs]
5596
+ );
5597
+ const submit = useCallback(
5598
+ async (text, optimisticId, captchaAttempt) => {
5599
+ const activeCatalog = catalogRef.current;
5600
+ let recaptchaToken;
5601
+ if (activeCatalog.captchaRequired && activeCatalog.recaptchaSiteKey) {
5602
+ try {
5603
+ recaptchaToken = await getRecaptchaV3Token(
5604
+ activeCatalog.recaptchaSiteKey
5605
+ );
5606
+ } catch (err) {
5607
+ console.warn("[WebSDK] reCAPTCHA v3 token could not be minted", err);
5608
+ }
5609
+ }
5610
+ const input = {
5611
+ message: text,
5612
+ ...conversationTokenRef.current ? { conversationToken: conversationTokenRef.current } : {},
5613
+ ...focusProductRef.current || productSlug ? { productSlug: focusProductRef.current ?? productSlug } : {},
5614
+ ...recaptchaToken ? { recaptchaToken } : {},
5615
+ ...locale ? { locale } : {},
5616
+ ...pageUrl ? { pageUrl } : {}
5617
+ };
5618
+ const res = await client.mutate(
5619
+ SEND_EXPLORE_MESSAGE_MUTATION,
5620
+ { input },
5621
+ { timeoutMs: SEND_REQUEST_TIMEOUT_MS }
5622
+ );
5623
+ if (!mountedRef.current) return;
5624
+ const first = res.errors?.[0];
5625
+ const result = res.data?.sendExploreMessage;
5626
+ if (first || !result) {
5627
+ const code = typeof first?.extensions?.code === "string" ? first.extensions.code : void 0;
5628
+ const classified = classifyExploreError(code, first?.message);
5629
+ if (classified.kind === "captcha" && captchaAttempt === 0) {
5630
+ await submit(text, optimisticId, 1);
5631
+ return;
5632
+ }
5633
+ inFlightRef.current = false;
5634
+ setError(classified);
5635
+ setPhase(classified.kind === "rate-limit" ? "limited" : "error");
5636
+ return;
5637
+ }
5638
+ conversationTokenRef.current = result.conversation.token;
5639
+ turnCountRef.current = result.conversation.turnCount;
5640
+ setTurnCount(result.conversation.turnCount);
5641
+ setRemainingToday(result.remainingToday ?? null);
5642
+ const userMessage = result.userMessage;
5643
+ const assistantMessage = result.assistantMessage;
5644
+ setMessages((prev) => {
5645
+ const replaced = prev.some((m) => m.id === optimisticId) ? prev.map((m) => m.id === optimisticId ? userMessage : m) : [...prev, userMessage];
5646
+ return [...replaced, assistantMessage];
5647
+ });
5648
+ if (assistantMessage.status === "COMPLETED" || assistantMessage.status === "FAILED") {
5649
+ inFlightRef.current = false;
5650
+ if (assistantMessage.status === "FAILED") {
5651
+ const classified = classifyExploreError(
5652
+ assistantMessage.errorCode ?? "UPSTREAM_UNAVAILABLE"
5653
+ );
5654
+ setError(classified);
5655
+ setPhase("error");
5656
+ } else {
5657
+ setPhase("ready");
5658
+ }
5659
+ return;
5660
+ }
5661
+ setPhase("streaming");
5662
+ startPolling(result.conversation.token, assistantMessage.id);
5663
+ },
5664
+ [client, locale, pageUrl, productSlug, startPolling]
5665
+ );
5666
+ const send = useCallback(
5667
+ async (message) => {
5668
+ const text = message.trim();
5669
+ if (!text) return;
5670
+ if (inFlightRef.current) return;
5671
+ const activeCatalog = catalogRef.current;
5672
+ if (!activeCatalog.enabled) {
5673
+ setError(classifyExploreError("EXPLORE_DISABLED"));
5674
+ setPhase("disabled");
5675
+ return;
5676
+ }
5677
+ if (text.length > activeCatalog.limits.maxMessageLength) {
5678
+ setError({
5679
+ kind: "validation",
5680
+ code: "MESSAGE_TOO_LONG",
5681
+ message: `That message is ${text.length} characters \u2014 the limit is ${activeCatalog.limits.maxMessageLength}.`,
5682
+ retryable: false
5683
+ });
5684
+ return;
5685
+ }
5686
+ if (turnCountRef.current >= activeCatalog.limits.maxTurnsPerConversation) {
5687
+ setError({
5688
+ kind: "validation",
5689
+ code: "CONVERSATION_LIMIT",
5690
+ message: `This conversation has reached its ${activeCatalog.limits.maxTurnsPerConversation}-message limit. Start a new chat to keep going.`,
5691
+ retryable: false
5692
+ });
5693
+ return;
5694
+ }
5695
+ inFlightRef.current = true;
5696
+ lastSentTextRef.current = text;
5697
+ setError(null);
5698
+ setPhase("sending");
5699
+ cancelPolling();
5700
+ const optimistic = {
5701
+ id: makeOptimisticId(),
5702
+ conversationId: conversationTokenRef.current ?? "pending",
5703
+ role: "USER",
5704
+ status: "COMPLETED",
5705
+ content: text,
5706
+ partialContent: null,
5707
+ activity: [],
5708
+ references: [],
5709
+ ctas: [],
5710
+ errorCode: null,
5711
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5712
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
5713
+ };
5714
+ setMessages((prev) => [...prev, optimistic]);
5715
+ turnCountRef.current += 1;
5716
+ setTurnCount(turnCountRef.current);
5717
+ onSend?.(text);
5718
+ try {
5719
+ await submit(text, optimistic.id, 0);
5720
+ } catch (err) {
5721
+ if (!mountedRef.current) return;
5722
+ console.error("[WebSDK] Explore send failed", err);
5723
+ inFlightRef.current = false;
5724
+ setError(classifyExploreError("NETWORK_ERROR"));
5725
+ setPhase("error");
5726
+ }
5727
+ },
5728
+ [cancelPolling, onSend, submit]
5729
+ );
5730
+ const stop = useCallback(() => {
5731
+ cancelPolling();
5732
+ inFlightRef.current = false;
5733
+ setMessages(
5734
+ (prev) => prev.flatMap((m) => {
5735
+ if (m.role !== "ASSISTANT") return [m];
5736
+ if (m.status !== "PENDING" && m.status !== "RUNNING") return [m];
5737
+ const text = visibleText(m);
5738
+ if (!text) return [];
5739
+ return [
5740
+ {
5741
+ ...m,
5742
+ status: "COMPLETED",
5743
+ content: text,
5744
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
5745
+ }
5746
+ ];
5747
+ })
5748
+ );
5749
+ setPhase(
5750
+ (prev) => prev === "sending" || prev === "streaming" ? "ready" : prev
5751
+ );
5752
+ }, [cancelPolling]);
5753
+ const retry = useCallback(() => {
5754
+ const text = lastSentTextRef.current;
5755
+ if (!text) return;
5756
+ cancelPolling();
5757
+ inFlightRef.current = false;
5758
+ setMessages((prev) => {
5759
+ const next = [...prev];
5760
+ const last = next[next.length - 1];
5761
+ if (last?.role === "ASSISTANT" && last.status === "FAILED") next.pop();
5762
+ const prior = next[next.length - 1];
5763
+ if (prior?.role === "USER" && prior.content === text) next.pop();
5764
+ return next;
5765
+ });
5766
+ turnCountRef.current = Math.max(0, turnCountRef.current - 1);
5767
+ setTurnCount(turnCountRef.current);
5768
+ void send(text);
5769
+ }, [cancelPolling, send]);
5770
+ const reset = useCallback(() => {
5771
+ cancelPolling();
5772
+ inFlightRef.current = false;
5773
+ conversationTokenRef.current = null;
5774
+ lastSentTextRef.current = null;
5775
+ turnCountRef.current = 0;
5776
+ setTurnCount(0);
5777
+ setMessages([]);
5778
+ setError(null);
5779
+ setRemainingToday(null);
5780
+ setPhase(catalogRef.current.enabled ? "ready" : "disabled");
5781
+ if (typeof window !== "undefined") {
5782
+ try {
5783
+ window.sessionStorage.removeItem(storageKey);
5784
+ } catch {
5785
+ }
5786
+ }
5787
+ }, [cancelPolling, storageKey]);
5788
+ const setFocusProduct = useCallback((slug) => {
5789
+ focusProductRef.current = slug;
5790
+ setFocusProductState(slug);
5791
+ }, []);
5792
+ const activity = useMemo(() => {
5793
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
5794
+ const m = messages[i];
5795
+ if (!m || m.role !== "ASSISTANT") continue;
5796
+ if (m.status !== "PENDING" && m.status !== "RUNNING") return null;
5797
+ const lines = m.activity ?? [];
5798
+ return lines.length ? lines[lines.length - 1] ?? null : null;
5799
+ }
5800
+ return null;
5801
+ }, [messages]);
5802
+ const canSend = (phase === "ready" || phase === "error") && catalog.enabled && turnCount < catalog.limits.maxTurnsPerConversation;
5803
+ return {
5804
+ phase,
5805
+ catalog,
5806
+ messages,
5807
+ activity,
5808
+ remainingToday,
5809
+ error,
5810
+ focusProduct,
5811
+ setFocusProduct,
5812
+ send,
5813
+ stop,
5814
+ retry,
5815
+ reset,
5816
+ canSend
5817
+ };
5818
+ }
5819
+ var optimisticCounter = 0;
5820
+ function makeOptimisticId() {
5821
+ optimisticCounter += 1;
5822
+ return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
5823
+ }
5824
+ var EXPLORE_CSS = `
5825
+ @keyframes boff-explore-pulse {
5826
+ 0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
5827
+ 40% { opacity: 1; transform: translateY(-2px); }
5828
+ }
5829
+ .boff-explore-dot {
5830
+ display: inline-block;
5831
+ width: 5px;
5832
+ height: 5px;
5833
+ border-radius: 9999px;
5834
+ background-color: hsl(var(--muted-foreground));
5835
+ animation: boff-explore-pulse 1.2s ease-in-out infinite;
5836
+ }
5837
+ @keyframes boff-explore-caret {
5838
+ 0%, 45% { opacity: 1; }
5839
+ 50%, 100% { opacity: 0; }
5840
+ }
5841
+ .boff-explore-caret {
5842
+ display: inline-block;
5843
+ width: 2px;
5844
+ height: 1em;
5845
+ margin-left: 2px;
5846
+ vertical-align: text-bottom;
5847
+ background-color: hsl(var(--primary));
5848
+ animation: boff-explore-caret 1s step-end infinite;
5849
+ }
5850
+ @media (prefers-reduced-motion: reduce) {
5851
+ .boff-explore-dot,
5852
+ .boff-explore-caret { animation: none; }
5853
+ .boff-explore-dot { opacity: 0.6; }
5854
+ }
5855
+ .boff-explore-scroll {
5856
+ scrollbar-width: thin;
5857
+ scrollbar-color: hsl(var(--border)) transparent;
5858
+ }
5859
+ .boff-explore-scroll::-webkit-scrollbar { width: 10px; }
5860
+ .boff-explore-scroll::-webkit-scrollbar-track { background: transparent; }
5861
+ .boff-explore-scroll::-webkit-scrollbar-thumb {
5862
+ background-color: hsl(var(--border));
5863
+ border: 3px solid transparent;
5864
+ border-radius: 9999px;
5865
+ background-clip: content-box;
5866
+ }
5867
+ `;
5868
+ function ExploreStyles() {
5869
+ return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
5870
+ }
5871
+ var CAPABILITIES = [
5872
+ {
5873
+ icon: BookOpenText,
5874
+ title: "Grounded in our own docs",
5875
+ body: "Every answer comes from published product pages and documentation \u2014 never invented."
5876
+ },
5877
+ {
5878
+ icon: Layers,
5879
+ title: "Compare across the ecosystem",
5880
+ body: "Ask which product fits your team, or how two of them work together."
5881
+ },
5882
+ {
5883
+ icon: Link2,
5884
+ title: "Straight to the right page",
5885
+ body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
5886
+ }
5887
+ ];
5888
+ var PRODUCT_CHIP_PREVIEW = 12;
5889
+ var CTA_VARIANTS = {
5890
+ CONTACT: "default",
5891
+ WAITLIST: "default",
5892
+ APP: "default",
5893
+ PRICING: "secondary",
5894
+ STORE: "secondary",
5895
+ PARTNERS: "secondary",
5896
+ DOCS: "outline",
5897
+ SERVICES: "outline",
5898
+ OTHER: "outline"
5899
+ };
5900
+ function normalizeOffset(offset) {
5901
+ if (offset === void 0) return "0px";
5902
+ return typeof offset === "number" ? `${offset}px` : offset;
5903
+ }
5904
+ function isInternalPath(url) {
5905
+ return url.startsWith("/") && !url.startsWith("//");
5906
+ }
5907
+ function hostnameOf(url) {
5908
+ try {
5909
+ return new URL(url).hostname.replace(/^www\./, "");
5910
+ } catch {
5911
+ return "";
5912
+ }
5913
+ }
5914
+ function initialOf(value, fallback) {
5915
+ const source = (value || fallback).trim();
5916
+ return source ? source.slice(0, 1).toUpperCase() : "?";
5917
+ }
5918
+ function ReferenceCard({ reference }) {
5919
+ const [imageFailed, setImageFailed] = useState(false);
5920
+ const host = hostnameOf(reference.url);
5921
+ const showImage = Boolean(reference.imageUrl) && !imageFailed;
5922
+ return /* @__PURE__ */ jsxs(
5923
+ "a",
5924
+ {
5925
+ "data-boff-explore": "reference",
5926
+ href: reference.url,
5927
+ target: "_blank",
5928
+ rel: "noopener noreferrer",
5929
+ className: "group flex flex-col overflow-hidden rounded-xl border border-border bg-card transition-colors hover:border-primary/40 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
5930
+ children: [
5931
+ /* @__PURE__ */ jsx("div", { className: "relative aspect-[16/9] w-full overflow-hidden bg-muted", children: showImage ? /* @__PURE__ */ jsx(
5932
+ "img",
5933
+ {
5934
+ src: reference.imageUrl ?? "",
5935
+ alt: "",
5936
+ loading: "lazy",
5937
+ onError: () => setImageFailed(true),
5938
+ className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
5939
+ }
5940
+ ) : (
5941
+ /* Graceful fallback: a token-derived gradient plate, so a missing or
5942
+ broken preview still reads as a deliberate card, not a hole. */
5943
+ /* @__PURE__ */ jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/25 via-primary/5 to-secondary", children: /* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-primary/70", children: initialOf(reference.product, reference.title || host) }) })
5944
+ ) }),
5945
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-1.5 p-3", children: [
5946
+ /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
5947
+ reference.description ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: reference.description }) : null,
5948
+ /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center gap-2 pt-2", children: [
5949
+ reference.product ? /* @__PURE__ */ jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground", children: reference.product }) : null,
5950
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground", children: [
5951
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 shrink-0" }),
5952
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: host })
5953
+ ] })
5954
+ ] })
5955
+ ] })
5956
+ ]
5957
+ }
5958
+ );
5959
+ }
5960
+ function CtaButton({
5961
+ cta,
5962
+ onNavigate
5963
+ }) {
5964
+ const variant = CTA_VARIANTS[cta.kind] ?? "outline";
5965
+ const internal = isInternalPath(cta.url);
5966
+ if (internal && onNavigate) {
5967
+ return /* @__PURE__ */ jsx(
5968
+ Button,
5969
+ {
5970
+ "data-boff-explore": "cta",
5971
+ "data-kind": cta.kind,
5972
+ type: "button",
5973
+ size: "sm",
5974
+ variant,
5975
+ onClick: () => onNavigate(cta.url),
5976
+ children: cta.label
5977
+ }
5978
+ );
5979
+ }
5980
+ return /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", variant, children: /* @__PURE__ */ jsxs(
5981
+ "a",
5982
+ {
5983
+ "data-boff-explore": "cta",
5984
+ "data-kind": cta.kind,
5985
+ href: cta.url,
5986
+ ...internal ? {} : { target: "_blank", rel: "noopener noreferrer" },
5987
+ children: [
5988
+ cta.label,
5989
+ internal ? null : /* @__PURE__ */ jsx(ExternalLink, { className: "ml-1 h-3 w-3" })
5990
+ ]
5991
+ }
5992
+ ) });
5993
+ }
5994
+ function ThinkingIndicator({ activity }) {
5995
+ return /* @__PURE__ */ jsxs(
5996
+ "div",
5997
+ {
5998
+ "data-boff-explore": "thinking",
5999
+ className: "flex items-center gap-2.5 rounded-2xl border border-border bg-muted/60 px-3.5 py-2.5 text-sm text-muted-foreground",
6000
+ children: [
6001
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", "aria-hidden": "true", children: [
6002
+ /* @__PURE__ */ jsx("span", { className: "boff-explore-dot" }),
6003
+ /* @__PURE__ */ jsx(
6004
+ "span",
6005
+ {
6006
+ className: "boff-explore-dot",
6007
+ style: { animationDelay: "150ms" }
6008
+ }
6009
+ ),
6010
+ /* @__PURE__ */ jsx(
6011
+ "span",
6012
+ {
6013
+ className: "boff-explore-dot",
6014
+ style: { animationDelay: "300ms" }
6015
+ }
6016
+ )
6017
+ ] }),
6018
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: activity || "Thinking\u2026" })
6019
+ ]
6020
+ }
6021
+ );
6022
+ }
6023
+ function UserBubble({ message }) {
6024
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
6025
+ "div",
6026
+ {
6027
+ "data-boff-explore": "user",
6028
+ className: "max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-primary px-4 py-2.5 text-sm leading-relaxed text-primary-foreground shadow-sm",
6029
+ children: message.content
6030
+ }
6031
+ ) });
6032
+ }
6033
+ function AssistantBubble({
6034
+ message,
6035
+ activity,
6036
+ onNavigate
6037
+ }) {
6038
+ const streaming = message.status === "PENDING" || message.status === "RUNNING";
6039
+ const body = message.content || message.partialContent || "";
6040
+ const failed = message.status === "FAILED";
6041
+ return /* @__PURE__ */ jsxs(
6042
+ "div",
6043
+ {
6044
+ "data-boff-explore": "assistant",
6045
+ "data-status": message.status,
6046
+ className: "flex gap-3",
6047
+ children: [
6048
+ /* @__PURE__ */ jsx(
6049
+ "span",
6050
+ {
6051
+ "aria-hidden": "true",
6052
+ className: "mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
6053
+ children: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4" })
6054
+ }
6055
+ ),
6056
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
6057
+ body ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
6058
+ /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: body }) }),
6059
+ streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
6060
+ ] }) : null,
6061
+ streaming ? /* @__PURE__ */ jsx(ThinkingIndicator, { activity }) : null,
6062
+ failed && !body ? /* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tl-md border border-border bg-muted px-4 py-3 text-sm text-muted-foreground", children: "That answer didn't come through. Try asking again." }) : null,
6063
+ message.references.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
6064
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Sources" }),
6065
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsx(ReferenceCard, { reference }, reference.url)) })
6066
+ ] }) : null,
6067
+ message.ctas.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 pt-0.5", children: message.ctas.map((cta) => /* @__PURE__ */ jsx(
6068
+ CtaButton,
6069
+ {
6070
+ cta,
6071
+ onNavigate
6072
+ },
6073
+ `${cta.kind}-${cta.url}`
6074
+ )) }) : null
6075
+ ] })
6076
+ ]
6077
+ }
6078
+ );
6079
+ }
6080
+ function ErrorBanner({
6081
+ error,
6082
+ onRetry,
6083
+ onReset
6084
+ }) {
6085
+ const showRetry = error.retryable;
6086
+ const showReset = error.code === "CONVERSATION_LIMIT";
6087
+ return /* @__PURE__ */ jsxs(
6088
+ "div",
6089
+ {
6090
+ "data-boff-explore": "error",
6091
+ "data-error-kind": error.kind,
6092
+ role: "alert",
6093
+ className: "mb-3 flex items-start gap-3 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2.5",
6094
+ children: [
6095
+ /* @__PURE__ */ jsx(
6096
+ "span",
6097
+ {
6098
+ "aria-hidden": "true",
6099
+ className: "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-destructive text-destructive-foreground",
6100
+ children: /* @__PURE__ */ jsx(TriangleAlert, { className: "h-3 w-3" })
6101
+ }
6102
+ ),
6103
+ /* @__PURE__ */ jsx("p", { className: "flex-1 text-sm leading-relaxed text-foreground", children: error.message }),
6104
+ showRetry ? /* @__PURE__ */ jsx(
6105
+ Button,
6106
+ {
6107
+ "data-boff-explore": "retry",
6108
+ type: "button",
6109
+ size: "sm",
6110
+ variant: "outline",
6111
+ onClick: onRetry,
6112
+ children: "Retry"
6113
+ }
6114
+ ) : null,
6115
+ showReset ? /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onReset, children: "New chat" }) : null
6116
+ ]
6117
+ }
6118
+ );
6119
+ }
6120
+ function WelcomeState({
6121
+ title,
6122
+ body,
6123
+ prompts,
6124
+ products,
6125
+ focusProduct,
6126
+ onPrompt,
6127
+ onFocus,
6128
+ disabled
6129
+ }) {
6130
+ const [showAllProducts, setShowAllProducts] = useState(false);
6131
+ const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
6132
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
6133
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
6134
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-3 py-1 text-xs font-medium text-muted-foreground", children: [
6135
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
6136
+ "AI answers, grounded in our documentation"
6137
+ ] }),
6138
+ /* @__PURE__ */ jsx("h1", { className: "mt-5 text-balance text-3xl font-bold tracking-tight sm:text-4xl", children: title }),
6139
+ /* @__PURE__ */ jsx("p", { className: "mx-auto mt-3 max-w-2xl text-pretty text-base leading-relaxed text-muted-foreground sm:text-lg", children: body })
6140
+ ] }),
6141
+ /* @__PURE__ */ jsx("ul", { className: "mt-9 grid gap-3 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxs(
6142
+ "li",
6143
+ {
6144
+ className: "rounded-xl border border-border bg-card p-4 text-left",
6145
+ children: [
6146
+ /* @__PURE__ */ jsx("span", { className: "mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
6147
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
6148
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: capBody })
6149
+ ]
6150
+ },
6151
+ capTitle
6152
+ )) }),
6153
+ prompts.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-9", children: [
6154
+ /* @__PURE__ */ jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
6155
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsxs(
6156
+ "button",
6157
+ {
6158
+ "data-boff-explore": "chip",
6159
+ "data-chip-kind": "prompt",
6160
+ type: "button",
6161
+ disabled,
6162
+ onClick: () => onPrompt(prompt),
6163
+ className: "group inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-card px-3.5 py-1.5 text-left text-sm text-card-foreground transition-colors hover:border-primary/40 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6164
+ children: [
6165
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: prompt }),
6166
+ /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
6167
+ ]
6168
+ },
6169
+ prompt
6170
+ )) })
6171
+ ] }) : null,
6172
+ products.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-8", children: [
6173
+ /* @__PURE__ */ jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
6174
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
6175
+ /* @__PURE__ */ jsx(
6176
+ "button",
6177
+ {
6178
+ "data-boff-explore": "chip",
6179
+ "data-chip-kind": "product",
6180
+ "data-product": "all",
6181
+ type: "button",
6182
+ "aria-pressed": focusProduct === null,
6183
+ onClick: () => onFocus(null),
6184
+ className: cn(
6185
+ "rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6186
+ focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
6187
+ ),
6188
+ children: "All products"
6189
+ }
6190
+ ),
6191
+ visibleProducts.map((product) => /* @__PURE__ */ jsx(
6192
+ "button",
6193
+ {
6194
+ "data-boff-explore": "chip",
6195
+ "data-chip-kind": "product",
6196
+ "data-product": product.slug,
6197
+ type: "button",
6198
+ "aria-pressed": focusProduct === product.slug,
6199
+ title: product.tagline ?? product.name,
6200
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
6201
+ className: cn(
6202
+ "rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6203
+ focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
6204
+ ),
6205
+ children: product.name
6206
+ },
6207
+ product.slug
6208
+ )),
6209
+ products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsx(
6210
+ "button",
6211
+ {
6212
+ type: "button",
6213
+ onClick: () => setShowAllProducts((v) => !v),
6214
+ className: "rounded-full px-3 py-1 text-xs font-medium text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6215
+ children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
6216
+ }
6217
+ ) : null
6218
+ ] })
6219
+ ] }) : null
6220
+ ] });
6221
+ }
6222
+ function ExplorePage({
6223
+ seo,
6224
+ productSlug,
6225
+ examplePrompts,
6226
+ className,
6227
+ heightMode = "viewport",
6228
+ viewportOffset,
6229
+ onNavigate,
6230
+ onSend,
6231
+ welcomeTitle,
6232
+ welcomeBody
6233
+ }) {
6234
+ const pageUrl = useMemo(
6235
+ () => typeof window === "undefined" ? void 0 : window.location.href,
6236
+ []
6237
+ );
6238
+ const {
6239
+ phase,
6240
+ catalog,
6241
+ messages,
6242
+ activity,
6243
+ remainingToday,
6244
+ error,
6245
+ focusProduct,
6246
+ setFocusProduct,
6247
+ send,
6248
+ stop,
6249
+ retry,
6250
+ reset,
6251
+ canSend
6252
+ } = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
6253
+ const [draft, setDraft] = useState("");
6254
+ const textareaRef = useRef(null);
6255
+ const scrollRef = useRef(null);
6256
+ const stickToBottomRef = useRef(true);
6257
+ const composingRef = useRef(false);
6258
+ const busy = phase === "sending" || phase === "streaming";
6259
+ const showWelcome = messages.length === 0;
6260
+ const maxLength = catalog.limits.maxMessageLength;
6261
+ const overLimit = draft.length > maxLength;
6262
+ const canSubmit = canSend && draft.trim().length > 0 && !overLimit && !busy;
6263
+ const resolvedTitle = welcomeTitle ?? catalog.welcomeTitle;
6264
+ const resolvedBody = welcomeBody ?? catalog.welcomeBody;
6265
+ const prompts = catalog.examplePrompts;
6266
+ const focusedProductName = useMemo(
6267
+ () => catalog.products.find((p) => p.slug === focusProduct)?.name ?? null,
6268
+ [catalog.products, focusProduct]
6269
+ );
6270
+ useEffect(() => {
6271
+ const el = textareaRef.current;
6272
+ if (!el) return;
6273
+ el.style.height = "auto";
6274
+ el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
6275
+ }, [draft]);
6276
+ const handleScroll = useCallback(() => {
6277
+ const el = scrollRef.current;
6278
+ if (!el) return;
6279
+ stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
6280
+ }, []);
6281
+ useEffect(() => {
6282
+ if (!stickToBottomRef.current) return;
6283
+ const el = scrollRef.current;
6284
+ if (!el) return;
6285
+ el.scrollTop = el.scrollHeight;
6286
+ }, [messages, activity]);
6287
+ const submitDraft = useCallback(() => {
6288
+ const text = draft.trim();
6289
+ if (!text || overLimit || busy || !canSend) return;
6290
+ setDraft("");
6291
+ stickToBottomRef.current = true;
6292
+ void send(text);
6293
+ textareaRef.current?.focus();
6294
+ }, [busy, canSend, draft, overLimit, send]);
6295
+ const sendPrompt = useCallback(
6296
+ (prompt) => {
6297
+ if (!canSend || busy) return;
6298
+ setDraft("");
6299
+ stickToBottomRef.current = true;
6300
+ void send(prompt);
6301
+ },
6302
+ [busy, canSend, send]
6303
+ );
6304
+ const handleKeyDown = useCallback(
6305
+ (event) => {
6306
+ if (event.key !== "Enter" || event.shiftKey) return;
6307
+ if (composingRef.current || event.nativeEvent.isComposing) return;
6308
+ event.preventDefault();
6309
+ submitDraft();
6310
+ },
6311
+ [submitDraft]
6312
+ );
6313
+ const composerDisabled = phase === "limited" || phase === "disabled" || !catalog.enabled;
6314
+ const captchaOn = Boolean(
6315
+ catalog.captchaRequired && catalog.recaptchaSiteKey
6316
+ );
6317
+ const rootStyle = heightMode === "viewport" ? { height: `calc(100dvh - ${normalizeOffset(viewportOffset)})` } : void 0;
6318
+ return /* @__PURE__ */ jsxs(
6319
+ "div",
6320
+ {
6321
+ "data-boff-explore": "page",
6322
+ "data-phase": phase,
6323
+ className: cn(
6324
+ "flex w-full flex-col bg-background text-foreground",
6325
+ heightMode === "auto" && "min-h-[70vh]",
6326
+ className
6327
+ ),
6328
+ style: rootStyle,
6329
+ children: [
6330
+ seo ? /* @__PURE__ */ jsx(
6331
+ PageHead,
6332
+ {
6333
+ title: seo.title ?? "Explore \u2014 ask anything about our products",
6334
+ description: seo.description ?? "Ask questions in plain English and get answers drawn from our product documentation, with links to the exact pages they came from.",
6335
+ keywords: seo.keywords,
6336
+ image: seo.image,
6337
+ url: seo.url
6338
+ }
6339
+ ) : null,
6340
+ /* @__PURE__ */ jsx(ProseStyles, {}),
6341
+ /* @__PURE__ */ jsx(ExploreStyles, {}),
6342
+ !showWelcome ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 border-b border-border px-4 py-2.5", children: [
6343
+ /* @__PURE__ */ jsx(
6344
+ "span",
6345
+ {
6346
+ "aria-hidden": "true",
6347
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
6348
+ children: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4" })
6349
+ }
6350
+ ),
6351
+ /* @__PURE__ */ jsx("h1", { className: "truncate text-sm font-semibold", children: resolvedTitle }),
6352
+ focusedProductName ? /* @__PURE__ */ jsx("span", { className: "hidden shrink-0 rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground sm:inline", children: focusedProductName }) : null,
6353
+ /* @__PURE__ */ jsxs(
6354
+ Button,
6355
+ {
6356
+ "data-boff-explore": "new-chat",
6357
+ type: "button",
6358
+ size: "sm",
6359
+ variant: "ghost",
6360
+ className: "ml-auto shrink-0 text-muted-foreground",
6361
+ onClick: reset,
6362
+ children: [
6363
+ /* @__PURE__ */ jsx(RotateCcw, { className: "h-3.5 w-3.5" }),
6364
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "New chat" })
6365
+ ]
6366
+ }
6367
+ )
6368
+ ] }) : null,
6369
+ /* @__PURE__ */ jsx(
6370
+ "div",
6371
+ {
6372
+ ref: scrollRef,
6373
+ onScroll: handleScroll,
6374
+ "data-boff-explore": "messages",
6375
+ role: "log",
6376
+ "aria-live": "polite",
6377
+ "aria-label": "Explore conversation",
6378
+ className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
6379
+ children: showWelcome ? /* @__PURE__ */ jsx(
6380
+ WelcomeState,
6381
+ {
6382
+ title: resolvedTitle,
6383
+ body: resolvedBody,
6384
+ prompts,
6385
+ products: catalog.products,
6386
+ focusProduct,
6387
+ onPrompt: sendPrompt,
6388
+ onFocus: setFocusProduct,
6389
+ disabled: !canSend || busy
6390
+ }
6391
+ ) : /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
6392
+ (message) => message.role === "USER" ? /* @__PURE__ */ jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsx(
6393
+ AssistantBubble,
6394
+ {
6395
+ message,
6396
+ activity,
6397
+ onNavigate
6398
+ },
6399
+ message.id
6400
+ )
6401
+ ) })
6402
+ }
6403
+ ),
6404
+ /* @__PURE__ */ jsx("div", { className: "border-t border-border bg-background px-4 pb-4 pt-3", children: /* @__PURE__ */ jsxs(
6405
+ "form",
6406
+ {
6407
+ "data-boff-explore": "composer",
6408
+ className: "mx-auto w-full max-w-3xl",
6409
+ onSubmit: (event) => {
6410
+ event.preventDefault();
6411
+ submitDraft();
6412
+ },
6413
+ children: [
6414
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
6415
+ /* @__PURE__ */ jsxs(
6416
+ "div",
6417
+ {
6418
+ className: cn(
6419
+ "flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-colors",
6420
+ overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
6421
+ ),
6422
+ children: [
6423
+ /* @__PURE__ */ jsx(
6424
+ Textarea,
6425
+ {
6426
+ ref: textareaRef,
6427
+ value: draft,
6428
+ rows: 1,
6429
+ disabled: composerDisabled,
6430
+ onChange: (event) => setDraft(event.target.value),
6431
+ onKeyDown: handleKeyDown,
6432
+ onCompositionStart: () => {
6433
+ composingRef.current = true;
6434
+ },
6435
+ onCompositionEnd: () => {
6436
+ composingRef.current = false;
6437
+ },
6438
+ "aria-label": "Ask a question",
6439
+ placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? `Ask about ${focusedProductName}\u2026` : "Ask anything about our products\u2026",
6440
+ className: "max-h-[200px] min-h-[44px] flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
6441
+ }
6442
+ ),
6443
+ busy ? /* @__PURE__ */ jsx(
6444
+ Button,
6445
+ {
6446
+ "data-boff-explore": "stop",
6447
+ type: "button",
6448
+ size: "icon",
6449
+ variant: "secondary",
6450
+ "aria-label": "Stop generating",
6451
+ onClick: stop,
6452
+ children: /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" })
6453
+ }
6454
+ ) : /* @__PURE__ */ jsx(
6455
+ Button,
6456
+ {
6457
+ "data-boff-explore": "send",
6458
+ type: "submit",
6459
+ size: "icon",
6460
+ disabled: !canSubmit,
6461
+ "aria-label": "Send message",
6462
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
6463
+ }
6464
+ )
6465
+ ]
6466
+ }
6467
+ ),
6468
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
6469
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
6470
+ remainingToday !== null ? /* @__PURE__ */ jsxs("span", { "data-boff-explore": "remaining", children: [
6471
+ remainingToday,
6472
+ " message",
6473
+ remainingToday === 1 ? "" : "s",
6474
+ " left today"
6475
+ ] }) : null,
6476
+ /* @__PURE__ */ jsxs(
6477
+ "span",
6478
+ {
6479
+ className: cn(
6480
+ "ml-auto tabular-nums",
6481
+ overLimit && "font-medium text-destructive"
6482
+ ),
6483
+ children: [
6484
+ draft.length.toLocaleString(),
6485
+ " / ",
6486
+ maxLength.toLocaleString()
6487
+ ]
6488
+ }
6489
+ )
6490
+ ] }),
6491
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: [
6492
+ "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
6493
+ captchaOn ? /* @__PURE__ */ jsxs(Fragment, { children: [
6494
+ " ",
6495
+ "This site is protected by reCAPTCHA; the Google",
6496
+ " ",
6497
+ /* @__PURE__ */ jsx(
6498
+ "a",
6499
+ {
6500
+ href: "https://policies.google.com/privacy",
6501
+ target: "_blank",
6502
+ rel: "noopener noreferrer",
6503
+ className: "underline underline-offset-2 hover:text-foreground",
6504
+ children: "Privacy Policy"
6505
+ }
6506
+ ),
6507
+ " ",
6508
+ "and",
6509
+ " ",
6510
+ /* @__PURE__ */ jsx(
6511
+ "a",
6512
+ {
6513
+ href: "https://policies.google.com/terms",
6514
+ target: "_blank",
6515
+ rel: "noopener noreferrer",
6516
+ className: "underline underline-offset-2 hover:text-foreground",
6517
+ children: "Terms of Service"
6518
+ }
6519
+ ),
6520
+ " ",
6521
+ "apply."
6522
+ ] }) : null
6523
+ ] })
6524
+ ]
6525
+ }
6526
+ ) })
6527
+ ]
6528
+ }
6529
+ );
6530
+ }
6531
+ function useCurrentPath(explicit) {
6532
+ const [path, setPath] = useState(explicit ?? null);
6533
+ useEffect(() => {
6534
+ if (explicit !== void 0) {
6535
+ setPath(explicit);
6536
+ return;
6537
+ }
6538
+ if (typeof window === "undefined") return;
6539
+ const read = () => setPath(window.location.pathname);
6540
+ read();
6541
+ window.addEventListener("popstate", read);
6542
+ return () => window.removeEventListener("popstate", read);
6543
+ }, [explicit]);
6544
+ return path;
6545
+ }
6546
+ function samePath(a, b) {
6547
+ if (!a) return false;
6548
+ const norm = (v) => v.length > 1 ? v.replace(/\/+$/, "") : v;
6549
+ return norm(a) === norm(b);
6550
+ }
6551
+ function ExploreCta({
6552
+ variant,
6553
+ href = "/explore",
6554
+ label = "Ask AI",
6555
+ className,
6556
+ onNavigate,
6557
+ onClick,
6558
+ currentPath
6559
+ }) {
6560
+ const path = useCurrentPath(currentPath);
6561
+ const isActive = samePath(path, href);
6562
+ if (variant === "floating" && isActive) return null;
6563
+ const handleClick = (event) => {
6564
+ onClick?.();
6565
+ if (!onNavigate) return;
6566
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
6567
+ return;
6568
+ }
6569
+ event.preventDefault();
6570
+ onNavigate(href);
6571
+ };
6572
+ const classes = {
6573
+ // `shrink-0` matters: this sits in a host header's flex row, and without it the
6574
+ // surrounding nav gets squeezed and its links wrap onto two lines. The label is hidden
6575
+ // below 2xl for the same reason — most product headers are already close to full at
6576
+ // 1440px, and a 90px pill there pushes them over.
6577
+ header: "inline-flex shrink-0 items-center gap-1.5 rounded-full bg-gradient-to-r from-primary to-primary/70 px-2.5 py-1.5 text-sm font-medium text-primary-foreground shadow-sm transition-opacity hover:opacity-90 2xl:px-3.5",
6578
+ mobile: "inline-flex w-full items-center gap-2 rounded-lg border border-border bg-card px-3 py-2 text-sm font-medium text-card-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
6579
+ floating: "fixed bottom-5 right-5 z-40 inline-flex items-center gap-2 rounded-full bg-gradient-to-r from-primary to-primary/70 px-4 py-3 text-sm font-medium text-primary-foreground shadow-lg transition-opacity hover:opacity-90"
6580
+ };
6581
+ const hooks = {
6582
+ header: "cta-header",
6583
+ mobile: "cta-mobile",
6584
+ floating: "cta-floating"
6585
+ };
6586
+ return /* @__PURE__ */ jsxs(
6587
+ "a",
6588
+ {
6589
+ "data-boff-explore": hooks[variant],
6590
+ href,
6591
+ onClick: handleClick,
6592
+ "aria-label": label,
6593
+ "aria-current": isActive ? "page" : void 0,
6594
+ className: cn(
6595
+ classes[variant],
6596
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
6597
+ className
6598
+ ),
6599
+ children: [
6600
+ /* @__PURE__ */ jsx(
6601
+ Sparkles,
6602
+ {
6603
+ className: cn(
6604
+ "shrink-0",
6605
+ variant === "floating" ? "h-4 w-4" : "h-3.5 w-3.5"
6606
+ ),
6607
+ "aria-hidden": "true"
6608
+ }
6609
+ ),
6610
+ /* @__PURE__ */ jsx(
6611
+ "span",
6612
+ {
6613
+ className: cn(
6614
+ variant === "floating" && "hidden sm:inline",
6615
+ variant === "header" && "hidden 2xl:inline"
6616
+ ),
6617
+ children: label
6618
+ }
6619
+ )
6620
+ ]
6621
+ }
6622
+ );
6623
+ }
6624
+ function RybbitAnalytics({
6625
+ siteId,
6626
+ scriptUrl = "https://analytics.burdenoff.com/api/script.js",
6627
+ enabled = true
6628
+ }) {
6629
+ useEffect(() => {
6630
+ if (!enabled || !siteId) return;
6631
+ const existingScript = document.querySelector(
6632
+ `script[data-site-id="${siteId}"]`
6633
+ );
6634
+ if (existingScript) return;
6635
+ const script = document.createElement("script");
6636
+ script.src = scriptUrl;
6637
+ script.defer = true;
6638
+ script.crossOrigin = "anonymous";
6639
+ script.setAttribute("data-site-id", siteId);
6640
+ document.head.appendChild(script);
6641
+ return () => {
6642
+ const scriptToRemove = document.querySelector(
6643
+ `script[data-site-id="${siteId}"]`
6644
+ );
6645
+ if (scriptToRemove) {
6646
+ document.head.removeChild(scriptToRemove);
6647
+ }
6648
+ };
6649
+ }, [siteId, scriptUrl, enabled]);
6650
+ return null;
6651
+ }
6652
+ var PUBLIC_JOB_POSTING_FIELDS = (
6653
+ /* GraphQL */
6654
+ `
6655
+ id
6656
+ slug
6657
+ title
6658
+ department
6659
+ location
6660
+ employmentType
6661
+ openings
6662
+ description
6663
+ overview
6664
+ responsibilities
6665
+ learnings
6666
+ qualifications
6667
+ rounds {
6668
+ roundNumber
6669
+ name
6670
+ roundType
6671
+ isRequired
6672
+ description
6673
+ }
6674
+ `
6675
+ );
6676
+ var PUBLIC_JOB_POSTINGS_QUERY = (
6677
+ /* GraphQL */
6678
+ `
6679
+ query PublicJobPostings($productId: ID, $limit: Int, $offset: Int) {
6680
+ publicJobPostings(productId: $productId, limit: $limit, offset: $offset) {
6681
+ ${PUBLIC_JOB_POSTING_FIELDS}
6682
+ }
6683
+ }
6684
+ `
6685
+ );
6686
+ var PUBLIC_JOB_POSTING_QUERY = (
6687
+ /* GraphQL */
6688
+ `
6689
+ query PublicJobPosting($slug: String!, $productId: ID) {
6690
+ publicJobPosting(slug: $slug, productId: $productId) {
6691
+ ${PUBLIC_JOB_POSTING_FIELDS}
6692
+ }
6693
+ }
6694
+ `
6695
+ );
6696
+ var PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY = (
6697
+ /* GraphQL */
6698
+ `
6699
+ query PublicJobPostingsForSubmission(
6700
+ $productId: ID
6701
+ $limit: Int
6702
+ $offset: Int
6703
+ ) {
6704
+ publicJobPostingsForSubmission(
6705
+ productId: $productId
6706
+ limit: $limit
6707
+ offset: $offset
6708
+ ) {
6709
+ id
6710
+ title
6711
+ }
6712
+ }
6713
+ `
6714
+ );
6715
+ async function fetchPublicJobPostings(client, options) {
6716
+ const result = await client.query(PUBLIC_JOB_POSTINGS_QUERY, {
6717
+ productId: options?.productId,
6718
+ limit: options?.limit ?? 50,
6719
+ offset: options?.offset ?? 0
6720
+ });
6721
+ if (result.errors?.length) {
6722
+ throw new Error(result.errors[0]?.message ?? "Unknown error");
6723
+ }
6724
+ return result.data?.publicJobPostings ?? [];
6725
+ }
6726
+ async function fetchPublicJobPosting(client, slug, options) {
6727
+ const result = await client.query(PUBLIC_JOB_POSTING_QUERY, {
6728
+ slug,
6729
+ productId: options?.productId
6730
+ });
6731
+ if (result.errors?.length) {
6732
+ throw new Error(result.errors[0]?.message ?? "Unknown error");
6733
+ }
6734
+ return result.data?.publicJobPosting ?? null;
6735
+ }
6736
+ async function fetchPublicJobPostingsForSubmission(client, options) {
6737
+ const result = await client.query(PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY, {
6738
+ productId: options?.productId,
6739
+ limit: options?.limit ?? 50,
6740
+ offset: options?.offset ?? 0
6741
+ });
6742
+ if (result.errors?.length) {
6743
+ throw new Error(result.errors[0]?.message ?? "Unknown error");
6744
+ }
6745
+ return result.data?.publicJobPostingsForSubmission ?? [];
6746
+ }
6747
+ function usePublicJobPostings(options) {
6748
+ const client = useWebSDK();
6749
+ const config = useWebSDKConfig();
6750
+ const { product } = useProduct();
6751
+ const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
6752
+ const [postings, setPostings] = useState([]);
6753
+ const [loading, setLoading] = useState(true);
6754
+ const [error, setError] = useState(null);
6755
+ const fetchPostings = useCallback(async () => {
6756
+ setLoading(true);
6757
+ setError(null);
6758
+ try {
6759
+ const items = await fetchPublicJobPostings(client, {
6760
+ productId: resolvedProductId,
6761
+ limit: options?.limit,
6762
+ offset: options?.offset
6763
+ });
6764
+ setPostings(items);
6765
+ } catch (err) {
6766
+ setError(
6767
+ err instanceof Error ? err.message : "Failed to fetch job postings"
6768
+ );
6769
+ setPostings([]);
6770
+ } finally {
6771
+ setLoading(false);
6772
+ }
6773
+ }, [client, resolvedProductId, options?.limit, options?.offset]);
6774
+ useEffect(() => {
6775
+ fetchPostings();
6776
+ }, [fetchPostings]);
6777
+ return { postings, loading, error, refetch: fetchPostings };
6778
+ }
6779
+ function usePublicJobPostingsForSubmission(options) {
6780
+ const client = useWebSDK();
6781
+ const config = useWebSDKConfig();
6782
+ const { product } = useProduct();
6783
+ const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
6784
+ const [postings, setPostings] = useState([]);
6785
+ const [loading, setLoading] = useState(true);
6786
+ const [error, setError] = useState(null);
6787
+ const fetchPostings = useCallback(async () => {
6788
+ setLoading(true);
6789
+ setError(null);
6790
+ try {
6791
+ const items = await fetchPublicJobPostingsForSubmission(client, {
6792
+ productId: resolvedProductId,
6793
+ limit: options?.limit,
6794
+ offset: options?.offset
6795
+ });
6796
+ setPostings(items);
6797
+ } catch (err) {
6798
+ setError(
6799
+ err instanceof Error ? err.message : "Failed to fetch job postings"
6800
+ );
6801
+ setPostings([]);
6802
+ } finally {
6803
+ setLoading(false);
6804
+ }
6805
+ }, [client, resolvedProductId, options?.limit, options?.offset]);
6806
+ useEffect(() => {
6807
+ fetchPostings();
6808
+ }, [fetchPostings]);
6809
+ return { postings, loading, error, refetch: fetchPostings };
6810
+ }
6811
+ function usePublicJobPosting(slug, options) {
6812
+ const client = useWebSDK();
6813
+ const config = useWebSDKConfig();
6814
+ const { product } = useProduct();
6815
+ const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
6816
+ const [posting, setPosting] = useState(null);
6817
+ const [loading, setLoading] = useState(true);
6818
+ const [error, setError] = useState(null);
6819
+ const fetchPosting = useCallback(async () => {
6820
+ if (!slug) {
6821
+ setPosting(null);
6822
+ setLoading(false);
6823
+ return;
6824
+ }
6825
+ setLoading(true);
6826
+ setError(null);
6827
+ try {
6828
+ const item = await fetchPublicJobPosting(client, slug, {
6829
+ productId: resolvedProductId
6830
+ });
6831
+ setPosting(item);
6832
+ } catch (err) {
6833
+ setError(
6834
+ err instanceof Error ? err.message : "Failed to fetch job posting"
6835
+ );
6836
+ setPosting(null);
6837
+ } finally {
6838
+ setLoading(false);
6839
+ }
6840
+ }, [client, resolvedProductId, slug]);
6841
+ useEffect(() => {
6842
+ fetchPosting();
6843
+ }, [fetchPosting]);
6844
+ return { posting, loading, error, refetch: fetchPosting };
6845
+ }
6846
+ var BLOG_POSTS_QUERY = `
6847
+ query BlogPosts($productId: String!, $limit: Int, $offset: Int) {
6848
+ publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
6849
+ items {
6850
+ id
6851
+ title
6852
+ slug
6853
+ lastUpdated
6854
+ tags
6855
+ seoDescription
6856
+ excerpt
6857
+ thumbnail
6858
+ author
6859
+ publishedAt
6860
+ readingTime
6861
+ featured
6862
+ category
6863
+ }
6864
+ total
6865
+ hasMore
6866
+ }
6867
+ }
6868
+ `;
6869
+ var BLOG_POST_QUERY = `
6870
+ query BlogPost($slug: String!, $productId: String) {
6871
+ publicContentPage(slug: $slug, productId: $productId) {
5135
6872
  id
5136
6873
  title
5137
6874
  slug
@@ -9449,181 +11186,6 @@ function StoreProductDetailPage({
9449
11186
  ] });
9450
11187
  }
9451
11188
 
9452
- // src/data/products.ts
9453
- var ECOSYSTEM_PRODUCTS = [
9454
- {
9455
- slug: "workspaces",
9456
- name: "Workspaces",
9457
- websiteUrl: "https://burdenoff.com"
9458
- },
9459
- {
9460
- slug: "vibecontrols",
9461
- name: "VibeControls",
9462
- websiteUrl: "https://vibecontrols.com"
9463
- },
9464
- {
9465
- slug: "fluidgrids",
9466
- name: "FluidGrids",
9467
- websiteUrl: "https://fluidgrids.com"
9468
- },
9469
- // Botlit ships on botlit.ai — botlit.com does not resolve.
9470
- { slug: "botlit", name: "Botlit", websiteUrl: "https://www.botlit.ai" },
9471
- {
9472
- slug: "bigconsole",
9473
- name: "BigConsole",
9474
- websiteUrl: "https://bigconsole.com"
9475
- },
9476
- {
9477
- slug: "semanticfed",
9478
- name: "SemanticFed",
9479
- websiteUrl: "https://semanticfed.com"
9480
- },
9481
- {
9482
- slug: "intelwatchtower",
9483
- name: "IntelWatchtower",
9484
- websiteUrl: "https://intelwatchtower.com"
9485
- },
9486
- { slug: "buildmyiq", name: "BuildMyIQ", websiteUrl: "https://buildmyiq.com" },
9487
- {
9488
- slug: "assethandler",
9489
- name: "AssetHandler",
9490
- websiteUrl: "https://assethandler.com"
9491
- },
9492
- {
9493
- slug: "crewfoundry",
9494
- name: "CrewFoundry",
9495
- websiteUrl: "https://crewfoundry.com"
9496
- },
9497
- {
9498
- slug: "planmagnet",
9499
- name: "PlanMagnet",
9500
- websiteUrl: "https://planmagnet.com"
9501
- },
9502
- {
9503
- slug: "eventfullymanaged",
9504
- name: "EventfullyManaged",
9505
- websiteUrl: "https://eventfullymanaged.com"
9506
- },
9507
- {
9508
- slug: "headshotmarketing",
9509
- name: "HeadshotMarketing",
9510
- websiteUrl: "https://headshotmarketing.com"
9511
- },
9512
- {
9513
- slug: "technospam",
9514
- name: "TechnoSpam",
9515
- websiteUrl: "https://technospam.com"
9516
- },
9517
- { slug: "kadaikodi", name: "Kadaikodi", websiteUrl: "https://kadaikodi.com" },
9518
- {
9519
- slug: "movethewheels",
9520
- name: "MoveTheWheels",
9521
- websiteUrl: "https://movethewheels.com"
9522
- },
9523
- {
9524
- slug: "healthybowl",
9525
- name: "Healthy Bowl",
9526
- websiteUrl: "https://gethealthybowl.com"
9527
- },
9528
- { slug: "ggnomad", name: "GGNomad", websiteUrl: "https://ggnomad.com" },
9529
- {
9530
- slug: "timecampus",
9531
- name: "TimeCampus",
9532
- websiteUrl: "https://timecampus.com"
9533
- },
9534
- {
9535
- slug: "manufacturedops",
9536
- name: "ManufacturedOps",
9537
- websiteUrl: "https://manufacturedops.com"
9538
- },
9539
- {
9540
- slug: "theglobalfuel",
9541
- name: "TheGlobalFuel",
9542
- websiteUrl: "https://theglobalfuel.com"
9543
- },
9544
- {
9545
- slug: "housingvista",
9546
- name: "HousingVista",
9547
- websiteUrl: "https://housingvista.com"
9548
- },
9549
- {
9550
- slug: "proserviceworld",
9551
- name: "ProServiceWorld",
9552
- websiteUrl: "https://proserviceworld.com"
9553
- },
9554
- {
9555
- slug: "mybodyshield",
9556
- name: "MyBodyShield",
9557
- websiteUrl: "https://mybodyshield.com"
9558
- },
9559
- {
9560
- slug: "artistrybase",
9561
- name: "ArtistryBase",
9562
- websiteUrl: "https://artistrybase.com"
9563
- },
9564
- {
9565
- slug: "theculturepost",
9566
- name: "TheCulturePost",
9567
- websiteUrl: "https://theculturepost.com"
9568
- },
9569
- { slug: "collabkin", name: "CollabKin", websiteUrl: "https://collabkin.com" },
9570
- {
9571
- slug: "comradecircle",
9572
- name: "ComradeCircle",
9573
- websiteUrl: "https://comradecircle.com"
9574
- },
9575
- {
9576
- slug: "brainyrich",
9577
- name: "BrainyRich",
9578
- websiteUrl: "https://brainyrich.com"
9579
- },
9580
- {
9581
- slug: "cosmicintersection",
9582
- name: "CosmicIntersection",
9583
- websiteUrl: "https://cosmicintersection.com"
9584
- },
9585
- {
9586
- slug: "govcitizenhub",
9587
- name: "GovCitizenHub",
9588
- websiteUrl: "https://govcitizenhub.com"
9589
- },
9590
- {
9591
- slug: "adaptercloud",
9592
- name: "AdapterCloud",
9593
- websiteUrl: "https://adaptercloud.com"
9594
- },
9595
- {
9596
- slug: "subscriberbot",
9597
- name: "Subscriber Bot",
9598
- websiteUrl: "https://subscriberbot.com"
9599
- },
9600
- {
9601
- slug: "hookmovies",
9602
- name: "HookMovies",
9603
- websiteUrl: "https://hookmovies.com"
9604
- },
9605
- {
9606
- slug: "coolandlovely",
9607
- name: "CoolAndLovely",
9608
- websiteUrl: "https://coolandlovely.com"
9609
- },
9610
- {
9611
- slug: "atheletebridge",
9612
- name: "AthleteBridge",
9613
- websiteUrl: "https://atheletebridge.com"
9614
- },
9615
- {
9616
- slug: "labsofscience",
9617
- name: "LabsOfScience",
9618
- websiteUrl: "https://labsofscience.com"
9619
- },
9620
- {
9621
- slug: "ecoimpacthub",
9622
- name: "EcoImpactHub",
9623
- websiteUrl: "https://ecoimpacthub.com"
9624
- }
9625
- ];
9626
-
9627
- export { BlogListPage, BlogPostPage, Button, CareersApplyForm, CareersSubmissionForm, CaseStudiesListPage, CaseStudyPage, ChangelogPage, ContactPage, ContentRenderer, ContractsPage, DEFAULT_CONTACT_CATEGORIES, ECOSYSTEM_PRODUCTS, ElectronDownloadLink, Input, Label, LaunchCountdown, Markdown, NewsletterForm, NewsletterPage, PageHead, PartnersPage, PressKitPage, PricingPage, PricingSection, ProblemsSolvedSection, ProductCard, ProductProvider, ResourceLinks, RybbitAnalytics, Select, SocialLinks, StarRating, StoreBrowsePage, StoreHomePage, StoreProductDetailPage, Textarea, TypeBadge, UnsubscribePage, WaitlistForm, WaitlistPage, WebSDKClient, WebSDKProvider, WebsiteSwitcher, buttonVariants, cn, detectElectronPlatform, fetchPublicJobPosting, fetchPublicJobPostings, fetchPublicJobPostingsForSubmission, formatDownloads, formatPrice2 as formatPrice, isValidUrl, useContentPage, useContentPages, useProduct, useProductConfig, usePublicJobPosting, usePublicJobPostings, usePublicJobPostingsForSubmission, useWebSDK, useWebSDKConfig };
11189
+ export { BlogListPage, BlogPostPage, Button, CareersApplyForm, CareersSubmissionForm, CaseStudiesListPage, CaseStudyPage, ChangelogPage, ContactPage, ContentRenderer, ContractsPage, DEFAULT_CONTACT_CATEGORIES, DEFAULT_EXPLORE_EXAMPLE_PROMPTS, ECOSYSTEM_PRODUCTS, EXPLORE_CATALOG_QUERY, EXPLORE_MESSAGE_QUERY, EXPLORE_RECAPTCHA_ACTION, ElectronDownloadLink, ExploreCta, ExplorePage, FALLBACK_EXPLORE_LIMITS, Input, Label, LaunchCountdown, Markdown, NewsletterForm, NewsletterPage, PageHead, PartnersPage, PressKitPage, PricingPage, PricingSection, ProblemsSolvedSection, ProductCard, ProductProvider, ResourceLinks, RybbitAnalytics, SEND_EXPLORE_MESSAGE_MUTATION, Select, SocialLinks, StarRating, StoreBrowsePage, StoreHomePage, StoreProductDetailPage, Textarea, TypeBadge, UnsubscribePage, WaitlistForm, WaitlistPage, WebSDKClient, WebSDKProvider, WebsiteSwitcher, buttonVariants, classifyExploreError, cn, detectElectronPlatform, fetchPublicJobPosting, fetchPublicJobPostings, fetchPublicJobPostingsForSubmission, formatDownloads, formatPrice2 as formatPrice, getRecaptchaV3Token, isValidUrl, loadRecaptchaV3, useContentPage, useContentPages, useExploreChat, useProduct, useProductConfig, usePublicJobPosting, usePublicJobPostings, usePublicJobPostingsForSubmission, useWebSDK, useWebSDKConfig };
9628
11190
  //# sourceMappingURL=index.mjs.map
9629
11191
  //# sourceMappingURL=index.mjs.map