@burdenoff/website-sdk 2026.828.4 → 2026.828.5

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.
package/dist/index.js CHANGED
@@ -5276,6 +5276,8 @@ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
5276
5276
 
5277
5277
  // src/hooks/use-explore-chat.ts
5278
5278
  var DEFAULT_STORAGE_KEY = "boff.explore.v1";
5279
+ var HISTORY_SUFFIX = ".history";
5280
+ var MAX_ARCHIVED_CONVERSATIONS = 15;
5279
5281
  var DEFAULT_POLL_INTERVAL_MS = 1500;
5280
5282
  var DEFAULT_POLL_TIMEOUT_MS = 9e4;
5281
5283
  var POLL_REQUEST_TIMEOUT_MS = 1e4;
@@ -5414,10 +5416,58 @@ function classifyExploreError(code, serverMessage) {
5414
5416
  retryable: true
5415
5417
  };
5416
5418
  }
5419
+ function historyKey(key) {
5420
+ return `${key}${HISTORY_SUFFIX}`;
5421
+ }
5422
+ function readArchive(key) {
5423
+ if (typeof window === "undefined") return [];
5424
+ try {
5425
+ const raw = window.localStorage.getItem(historyKey(key));
5426
+ if (!raw) return [];
5427
+ const parsed = JSON.parse(raw);
5428
+ if (!Array.isArray(parsed)) return [];
5429
+ return parsed.filter(
5430
+ (entry) => typeof entry === "object" && entry !== null && typeof entry.token === "string" && Array.isArray(entry.messages)
5431
+ );
5432
+ } catch {
5433
+ return [];
5434
+ }
5435
+ }
5436
+ function writeArchive(key, entries) {
5437
+ if (typeof window === "undefined") return;
5438
+ try {
5439
+ if (entries.length === 0) {
5440
+ window.localStorage.removeItem(historyKey(key));
5441
+ return;
5442
+ }
5443
+ window.localStorage.setItem(historyKey(key), JSON.stringify(entries));
5444
+ } catch {
5445
+ }
5446
+ }
5447
+ function upsertArchive(key, thread) {
5448
+ if (!thread.token) return readArchive(key);
5449
+ const real = thread.messages.filter(
5450
+ (m) => m.content.trim() !== "" || m.role === "USER"
5451
+ );
5452
+ if (real.length === 0) return readArchive(key);
5453
+ const firstUser = real.find((m) => m.role === "USER");
5454
+ const entry = {
5455
+ token: thread.token,
5456
+ title: (firstUser?.content ?? "Conversation").trim().slice(0, 80),
5457
+ updatedAt: real[real.length - 1]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5458
+ messageCount: real.length,
5459
+ focusProduct: thread.focusProduct,
5460
+ messages: real.slice(-20)
5461
+ };
5462
+ const rest = readArchive(key).filter((e) => e.token !== entry.token);
5463
+ const next = [entry, ...rest].slice(0, MAX_ARCHIVED_CONVERSATIONS);
5464
+ writeArchive(key, next);
5465
+ return next;
5466
+ }
5417
5467
  function readPersisted(key) {
5418
5468
  if (typeof window === "undefined") return null;
5419
5469
  try {
5420
- const raw = window.sessionStorage.getItem(key);
5470
+ const raw = window.localStorage.getItem(key);
5421
5471
  if (!raw) return null;
5422
5472
  const parsed = JSON.parse(raw);
5423
5473
  if (!parsed || typeof parsed !== "object") return null;
@@ -5462,6 +5512,8 @@ function useExploreChat(options = {}) {
5462
5512
  const [remainingToday, setRemainingToday] = React.useState(null);
5463
5513
  const [focusProduct, setFocusProductState] = React.useState(null);
5464
5514
  const [turnCount, setTurnCount] = React.useState(0);
5515
+ const messagesRef = React.useRef([]);
5516
+ const [archive, setArchive] = React.useState([]);
5465
5517
  const [hydrated, setHydrated] = React.useState(false);
5466
5518
  const mountedRef = React.useRef(true);
5467
5519
  const pollGenerationRef = React.useRef(0);
@@ -5499,6 +5551,7 @@ function useExploreChat(options = {}) {
5499
5551
  setHydrated(true);
5500
5552
  return;
5501
5553
  }
5554
+ setArchive(readArchive(storageKey));
5502
5555
  const stored = readPersisted(storageKey);
5503
5556
  if (stored) {
5504
5557
  conversationTokenRef.current = stored.conversationToken;
@@ -5527,13 +5580,28 @@ function useExploreChat(options = {}) {
5527
5580
  focusProduct
5528
5581
  };
5529
5582
  if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
5530
- window.sessionStorage.removeItem(storageKey);
5583
+ window.localStorage.removeItem(storageKey);
5531
5584
  return;
5532
5585
  }
5533
- window.sessionStorage.setItem(storageKey, JSON.stringify(payload));
5586
+ window.localStorage.setItem(storageKey, JSON.stringify(payload));
5534
5587
  } catch {
5535
5588
  }
5536
5589
  }, [persist, hydrated, storageKey, messages, focusProduct]);
5590
+ React.useEffect(() => {
5591
+ messagesRef.current = messages;
5592
+ if (!persist || !hydrated) return;
5593
+ const settled = messages.some(
5594
+ (m) => m.role === "ASSISTANT" && (m.status === "COMPLETED" || m.status === "FAILED")
5595
+ );
5596
+ if (!settled) return;
5597
+ setArchive(
5598
+ upsertArchive(storageKey, {
5599
+ token: conversationTokenRef.current,
5600
+ messages,
5601
+ focusProduct: focusProductRef.current
5602
+ })
5603
+ );
5604
+ }, [messages, persist, hydrated, storageKey]);
5537
5605
  React.useEffect(() => {
5538
5606
  let cancelled = false;
5539
5607
  const load = async () => {
@@ -5819,6 +5887,13 @@ function useExploreChat(options = {}) {
5819
5887
  void send(text);
5820
5888
  }, [cancelPolling, send]);
5821
5889
  const reset = React.useCallback(() => {
5890
+ setArchive(
5891
+ upsertArchive(storageKey, {
5892
+ token: conversationTokenRef.current,
5893
+ messages: messagesRef.current,
5894
+ focusProduct: focusProductRef.current
5895
+ })
5896
+ );
5822
5897
  cancelPolling();
5823
5898
  inFlightRef.current = false;
5824
5899
  conversationTokenRef.current = null;
@@ -5831,11 +5906,48 @@ function useExploreChat(options = {}) {
5831
5906
  setPhase(catalogRef.current.enabled ? "ready" : "disabled");
5832
5907
  if (typeof window !== "undefined") {
5833
5908
  try {
5834
- window.sessionStorage.removeItem(storageKey);
5909
+ window.localStorage.removeItem(storageKey);
5835
5910
  } catch {
5836
5911
  }
5837
5912
  }
5838
5913
  }, [cancelPolling, storageKey]);
5914
+ const openConversation = React.useCallback(
5915
+ (token) => {
5916
+ const entry = readArchive(storageKey).find((e) => e.token === token);
5917
+ if (!entry) return;
5918
+ upsertArchive(storageKey, {
5919
+ token: conversationTokenRef.current,
5920
+ messages: messagesRef.current,
5921
+ focusProduct: focusProductRef.current
5922
+ });
5923
+ cancelPolling();
5924
+ inFlightRef.current = false;
5925
+ conversationTokenRef.current = entry.token;
5926
+ turnCountRef.current = entry.messages.filter(
5927
+ (m) => m.role === "USER"
5928
+ ).length;
5929
+ setTurnCount(turnCountRef.current);
5930
+ setMessages(entry.messages);
5931
+ focusProductRef.current = entry.focusProduct;
5932
+ setFocusProductState(entry.focusProduct);
5933
+ setError(null);
5934
+ setPhase(catalogRef.current.enabled ? "ready" : "disabled");
5935
+ setArchive(readArchive(storageKey));
5936
+ },
5937
+ [cancelPolling, storageKey]
5938
+ );
5939
+ const deleteConversation = React.useCallback(
5940
+ (token) => {
5941
+ const next = readArchive(storageKey).filter((e) => e.token !== token);
5942
+ writeArchive(storageKey, next);
5943
+ setArchive(next);
5944
+ },
5945
+ [storageKey]
5946
+ );
5947
+ const clearHistory = React.useCallback(() => {
5948
+ writeArchive(storageKey, []);
5949
+ setArchive([]);
5950
+ }, [storageKey]);
5839
5951
  const setFocusProduct = React.useCallback((slug) => {
5840
5952
  focusProductRef.current = slug;
5841
5953
  setFocusProductState(slug);
@@ -5864,7 +5976,11 @@ function useExploreChat(options = {}) {
5864
5976
  stop,
5865
5977
  retry,
5866
5978
  reset,
5867
- canSend
5979
+ canSend,
5980
+ history: archive,
5981
+ openConversation,
5982
+ deleteConversation,
5983
+ clearHistory
5868
5984
  };
5869
5985
  }
5870
5986
  var optimisticCounter = 0;
@@ -5872,6 +5988,119 @@ function makeOptimisticId() {
5872
5988
  optimisticCounter += 1;
5873
5989
  return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
5874
5990
  }
5991
+ function getRecognitionCtor() {
5992
+ if (typeof window === "undefined") return null;
5993
+ const w = window;
5994
+ return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
5995
+ }
5996
+ function describeError(code) {
5997
+ switch (code) {
5998
+ case "not-allowed":
5999
+ case "service-not-allowed":
6000
+ return "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
6001
+ case "no-speech":
6002
+ return "I didn't catch anything \u2014 try again a little closer to the mic.";
6003
+ case "audio-capture":
6004
+ return "No microphone was found.";
6005
+ case "network":
6006
+ return "Speech recognition needs a network connection.";
6007
+ case "aborted":
6008
+ return "";
6009
+ default:
6010
+ return "Dictation stopped unexpectedly. You can type instead.";
6011
+ }
6012
+ }
6013
+ function useSpeechInput({
6014
+ onFinalTranscript,
6015
+ lang,
6016
+ onError
6017
+ }) {
6018
+ const [supported] = React.useState(() => getRecognitionCtor() !== null);
6019
+ const [listening, setListening] = React.useState(false);
6020
+ const [interim, setInterim] = React.useState("");
6021
+ const [error, setError] = React.useState(null);
6022
+ const recognitionRef = React.useRef(null);
6023
+ const finalRef = React.useRef(onFinalTranscript);
6024
+ const errorRef = React.useRef(onError);
6025
+ finalRef.current = onFinalTranscript;
6026
+ errorRef.current = onError;
6027
+ const stop = React.useCallback(() => {
6028
+ const recognition = recognitionRef.current;
6029
+ if (!recognition) return;
6030
+ try {
6031
+ recognition.stop();
6032
+ } catch {
6033
+ }
6034
+ setListening(false);
6035
+ setInterim("");
6036
+ }, []);
6037
+ const start = React.useCallback(() => {
6038
+ const Ctor = getRecognitionCtor();
6039
+ if (!Ctor) return;
6040
+ if (recognitionRef.current) stop();
6041
+ const recognition = new Ctor();
6042
+ recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
6043
+ recognition.continuous = true;
6044
+ recognition.interimResults = true;
6045
+ recognition.maxAlternatives = 1;
6046
+ recognition.onstart = () => {
6047
+ setError(null);
6048
+ setListening(true);
6049
+ };
6050
+ recognition.onresult = (event) => {
6051
+ let settled = "";
6052
+ let pending = "";
6053
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
6054
+ const result = event.results[i];
6055
+ if (!result) continue;
6056
+ const text = result[0]?.transcript ?? "";
6057
+ if (result.isFinal) settled += text;
6058
+ else pending += text;
6059
+ }
6060
+ setInterim(pending);
6061
+ if (settled.trim() !== "") finalRef.current(settled);
6062
+ };
6063
+ recognition.onerror = (event) => {
6064
+ const message = describeError(event.error);
6065
+ setListening(false);
6066
+ setInterim("");
6067
+ if (message !== "") {
6068
+ setError(message);
6069
+ errorRef.current?.(message);
6070
+ }
6071
+ };
6072
+ recognition.onend = () => {
6073
+ setListening(false);
6074
+ setInterim("");
6075
+ };
6076
+ recognitionRef.current = recognition;
6077
+ try {
6078
+ recognition.start();
6079
+ } catch {
6080
+ setListening(false);
6081
+ }
6082
+ }, [lang, stop]);
6083
+ const toggle = React.useCallback(() => {
6084
+ if (listening) stop();
6085
+ else start();
6086
+ }, [listening, start, stop]);
6087
+ React.useEffect(
6088
+ () => () => {
6089
+ const recognition = recognitionRef.current;
6090
+ if (!recognition) return;
6091
+ recognition.onresult = null;
6092
+ recognition.onerror = null;
6093
+ recognition.onend = null;
6094
+ recognition.onstart = null;
6095
+ try {
6096
+ recognition.abort();
6097
+ } catch {
6098
+ }
6099
+ },
6100
+ []
6101
+ );
6102
+ return { supported, listening, interim, error, start, stop, toggle };
6103
+ }
5875
6104
  var EXPLORE_CSS = `
5876
6105
  @keyframes boff-explore-pulse {
5877
6106
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
@@ -6299,10 +6528,29 @@ function ExplorePage({
6299
6528
  stop,
6300
6529
  retry,
6301
6530
  reset,
6302
- canSend
6531
+ canSend,
6532
+ history,
6533
+ openConversation,
6534
+ deleteConversation,
6535
+ clearHistory
6303
6536
  } = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
6304
6537
  const [draft, setDraft] = React.useState("");
6305
6538
  const textareaRef = React.useRef(null);
6539
+ const [historyOpen, setHistoryOpen] = React.useState(false);
6540
+ const speechStopRef = React.useRef(() => void 0);
6541
+ const stopDictation = React.useCallback(() => {
6542
+ speechStopRef.current();
6543
+ }, []);
6544
+ const speech = useSpeechInput({
6545
+ onFinalTranscript: (text) => {
6546
+ setDraft((current) => {
6547
+ const joined = current.trim() === "" ? text.trimStart() : `${current.trimEnd()} ${text.trim()}`;
6548
+ return joined;
6549
+ });
6550
+ textareaRef.current?.focus();
6551
+ }
6552
+ });
6553
+ speechStopRef.current = speech.stop;
6306
6554
  const scrollRef = React.useRef(null);
6307
6555
  const stickToBottomRef = React.useRef(true);
6308
6556
  const composingRef = React.useRef(false);
@@ -6338,11 +6586,12 @@ function ExplorePage({
6338
6586
  const submitDraft = React.useCallback(() => {
6339
6587
  const text = draft.trim();
6340
6588
  if (!text || overLimit || busy || !canSend) return;
6589
+ stopDictation();
6341
6590
  setDraft("");
6342
6591
  stickToBottomRef.current = true;
6343
6592
  void send(text);
6344
6593
  textareaRef.current?.focus();
6345
- }, [busy, canSend, draft, overLimit, send]);
6594
+ }, [busy, canSend, draft, overLimit, send, stopDictation]);
6346
6595
  const sendPrompt = React.useCallback(
6347
6596
  (prompt) => {
6348
6597
  if (!canSend || busy) return;
@@ -6415,8 +6664,96 @@ function ExplorePage({
6415
6664
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "New chat" })
6416
6665
  ]
6417
6666
  }
6418
- )
6667
+ ),
6668
+ history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
6669
+ Button,
6670
+ {
6671
+ "data-boff-explore": "history-toggle",
6672
+ type: "button",
6673
+ size: "sm",
6674
+ variant: "ghost",
6675
+ className: "shrink-0 text-muted-foreground",
6676
+ "aria-expanded": historyOpen,
6677
+ onClick: () => {
6678
+ setHistoryOpen((open) => !open);
6679
+ },
6680
+ children: [
6681
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.History, { className: "h-3.5 w-3.5" }),
6682
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden sm:inline", children: [
6683
+ "History (",
6684
+ history.length,
6685
+ ")"
6686
+ ] })
6687
+ ]
6688
+ }
6689
+ ) : null
6419
6690
  ] }) : null,
6691
+ historyOpen && history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
6692
+ "div",
6693
+ {
6694
+ "data-boff-explore": "history-panel",
6695
+ className: "border-b border-border bg-muted/30 px-4 py-3",
6696
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl", children: [
6697
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
6698
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Your recent conversations" }),
6699
+ /* @__PURE__ */ jsxRuntime.jsxs(
6700
+ Button,
6701
+ {
6702
+ "data-boff-explore": "history-clear",
6703
+ type: "button",
6704
+ size: "sm",
6705
+ variant: "ghost",
6706
+ className: "h-7 shrink-0 text-xs text-muted-foreground hover:text-destructive",
6707
+ onClick: () => {
6708
+ clearHistory();
6709
+ setHistoryOpen(false);
6710
+ },
6711
+ children: [
6712
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
6713
+ "Clear history"
6714
+ ]
6715
+ }
6716
+ )
6717
+ ] }),
6718
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: history.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [
6719
+ /* @__PURE__ */ jsxRuntime.jsxs(
6720
+ "button",
6721
+ {
6722
+ "data-boff-explore": "history-item",
6723
+ type: "button",
6724
+ className: "flex-1 truncate rounded-md px-2 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
6725
+ onClick: () => {
6726
+ openConversation(entry.token);
6727
+ setHistoryOpen(false);
6728
+ },
6729
+ children: [
6730
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: entry.title }),
6731
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
6732
+ entry.messageCount,
6733
+ " message",
6734
+ entry.messageCount === 1 ? "" : "s"
6735
+ ] })
6736
+ ]
6737
+ }
6738
+ ),
6739
+ /* @__PURE__ */ jsxRuntime.jsx(
6740
+ Button,
6741
+ {
6742
+ type: "button",
6743
+ size: "icon",
6744
+ variant: "ghost",
6745
+ className: "h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive",
6746
+ "aria-label": `Delete conversation: ${entry.title}`,
6747
+ onClick: () => {
6748
+ deleteConversation(entry.token);
6749
+ },
6750
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
6751
+ }
6752
+ )
6753
+ ] }, entry.token)) })
6754
+ ] })
6755
+ }
6756
+ ) : null,
6420
6757
  /* @__PURE__ */ jsxRuntime.jsx(
6421
6758
  "div",
6422
6759
  {
@@ -6491,6 +6828,23 @@ function ExplorePage({
6491
6828
  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"
6492
6829
  }
6493
6830
  ),
6831
+ speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
6832
+ Button,
6833
+ {
6834
+ "data-boff-explore": "mic",
6835
+ "data-listening": speech.listening ? "true" : "false",
6836
+ type: "button",
6837
+ size: "icon",
6838
+ variant: speech.listening ? "default" : "ghost",
6839
+ disabled: composerDisabled,
6840
+ "aria-label": speech.listening ? "Stop dictating" : "Dictate your question",
6841
+ "aria-pressed": speech.listening,
6842
+ title: speech.listening ? "Stop dictating" : "Dictate your question",
6843
+ onClick: speech.toggle,
6844
+ className: cn(speech.listening && "animate-pulse"),
6845
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-4 w-4" })
6846
+ }
6847
+ ) : null,
6494
6848
  busy ? /* @__PURE__ */ jsxRuntime.jsx(
6495
6849
  Button,
6496
6850
  {
@@ -6516,6 +6870,29 @@ function ExplorePage({
6516
6870
  ]
6517
6871
  }
6518
6872
  ),
6873
+ speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxRuntime.jsxs(
6874
+ "p",
6875
+ {
6876
+ "data-boff-explore": "dictation",
6877
+ className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
6878
+ "aria-live": "polite",
6879
+ children: [
6880
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
6881
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
6882
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
6883
+ ] }),
6884
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
6885
+ ]
6886
+ }
6887
+ ) : null,
6888
+ speech.error !== null ? /* @__PURE__ */ jsxRuntime.jsx(
6889
+ "p",
6890
+ {
6891
+ "data-boff-explore": "dictation-error",
6892
+ className: "mt-2 text-xs text-destructive",
6893
+ children: speech.error
6894
+ }
6895
+ ) : null,
6519
6896
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
6520
6897
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
6521
6898
  remainingToday !== null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-boff-explore": "remaining", children: [
@@ -6539,39 +6916,48 @@ function ExplorePage({
6539
6916
  }
6540
6917
  )
6541
6918
  ] }),
6542
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: [
6543
- "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
6544
- captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6545
- " ",
6546
- "This site is protected by reCAPTCHA; the Google",
6547
- " ",
6548
- /* @__PURE__ */ jsxRuntime.jsx(
6549
- "a",
6550
- {
6551
- href: "https://policies.google.com/privacy",
6552
- target: "_blank",
6553
- rel: "noopener noreferrer",
6554
- className: "underline underline-offset-2 hover:text-foreground",
6555
- children: "Privacy Policy"
6556
- }
6557
- ),
6558
- " ",
6559
- "and",
6560
- " ",
6561
- /* @__PURE__ */ jsxRuntime.jsx(
6562
- "a",
6563
- {
6564
- href: "https://policies.google.com/terms",
6565
- target: "_blank",
6566
- rel: "noopener noreferrer",
6567
- className: "underline underline-offset-2 hover:text-foreground",
6568
- children: "Terms of Service"
6569
- }
6570
- ),
6571
- " ",
6572
- "apply."
6573
- ] }) : null
6574
- ] })
6919
+ /* @__PURE__ */ jsxRuntime.jsxs(
6920
+ "p",
6921
+ {
6922
+ "data-boff-explore": "disclaimer",
6923
+ className: "mt-2 text-xs leading-relaxed text-muted-foreground",
6924
+ children: [
6925
+ "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
6926
+ " ",
6927
+ "Conversations are saved in this browser so you can come back to them, and are also stored on our servers to help us improve these answers. Please don't share personal or confidential information.",
6928
+ captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6929
+ " ",
6930
+ "This site is protected by reCAPTCHA; the Google",
6931
+ " ",
6932
+ /* @__PURE__ */ jsxRuntime.jsx(
6933
+ "a",
6934
+ {
6935
+ href: "https://policies.google.com/privacy",
6936
+ target: "_blank",
6937
+ rel: "noopener noreferrer",
6938
+ className: "underline underline-offset-2 hover:text-foreground",
6939
+ children: "Privacy Policy"
6940
+ }
6941
+ ),
6942
+ " ",
6943
+ "and",
6944
+ " ",
6945
+ /* @__PURE__ */ jsxRuntime.jsx(
6946
+ "a",
6947
+ {
6948
+ href: "https://policies.google.com/terms",
6949
+ target: "_blank",
6950
+ rel: "noopener noreferrer",
6951
+ className: "underline underline-offset-2 hover:text-foreground",
6952
+ children: "Terms of Service"
6953
+ }
6954
+ ),
6955
+ " ",
6956
+ "apply."
6957
+ ] }) : null
6958
+ ]
6959
+ }
6960
+ )
6575
6961
  ]
6576
6962
  }
6577
6963
  ) })
@@ -6621,11 +7007,12 @@ function ExploreCta({
6621
7007
  onNavigate(href);
6622
7008
  };
6623
7009
  const classes = {
6624
- // `shrink-0` matters: this sits in a host header's flex row, and without it the
6625
- // surrounding nav gets squeezed and its links wrap onto two lines. The label is hidden
6626
- // below 2xl for the same reason most product headers are already close to full at
6627
- // 1440px, and a 90px pill there pushes them over.
6628
- 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",
7010
+ // A quiet icon control, deliberately NOT a second gradient pill: a header should carry one
7011
+ // primary CTA ("Get started"), and a competing coloured button next to it reads as clutter.
7012
+ // This matches the theme toggle's visual weight, so the row scans as [utilities] [CTA].
7013
+ // The prominent, labelled entry point to /explore is the floating bubble, which is visible
7014
+ // on every page without scrolling. `shrink-0` keeps it from squeezing the nav.
7015
+ header: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
6629
7016
  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",
6630
7017
  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"
6631
7018
  };
@@ -6653,7 +7040,7 @@ function ExploreCta({
6653
7040
  {
6654
7041
  className: cn(
6655
7042
  "shrink-0",
6656
- variant === "floating" ? "h-4 w-4" : "h-3.5 w-3.5"
7043
+ variant === "floating" ? "h-4 w-4" : variant === "header" ? "h-5 w-5" : "h-3.5 w-3.5"
6657
7044
  ),
6658
7045
  "aria-hidden": "true"
6659
7046
  }
@@ -6663,7 +7050,8 @@ function ExploreCta({
6663
7050
  {
6664
7051
  className: cn(
6665
7052
  variant === "floating" && "hidden sm:inline",
6666
- variant === "header" && "hidden 2xl:inline"
7053
+ // Never labelled in the header see the class comment above.
7054
+ variant === "header" && "hidden"
6667
7055
  ),
6668
7056
  children: label
6669
7057
  }