@burdenoff/website-sdk 2026.828.7 → 2026.829.1

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
@@ -5993,21 +5993,37 @@ function getRecognitionCtor() {
5993
5993
  const w = window;
5994
5994
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
5995
5995
  }
5996
+ function isTouchDevice() {
5997
+ if (typeof window === "undefined" || !window.matchMedia) return false;
5998
+ try {
5999
+ return window.matchMedia("(pointer: coarse)").matches;
6000
+ } catch {
6001
+ return false;
6002
+ }
6003
+ }
5996
6004
  var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
6005
+ var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
6006
+ var RELEASE_SETTLE_MS = 250;
6007
+ var RETRY_DELAY_MS = 350;
6008
+ var sleep = (ms) => new Promise((resolve) => {
6009
+ setTimeout(resolve, ms);
6010
+ });
5997
6011
  async function ensureMicrophoneAccess() {
5998
6012
  const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
5999
- if (!media?.getUserMedia) return { ok: true, confirmed: false };
6013
+ if (!media?.getUserMedia)
6014
+ return { ok: true, confirmed: false, primed: false };
6000
6015
  try {
6001
6016
  const status = await navigator.permissions?.query({
6002
6017
  name: "microphone"
6003
6018
  });
6004
- if (status?.state === "granted") return { ok: true, confirmed: true };
6019
+ if (status?.state === "granted")
6020
+ return { ok: true, confirmed: true, primed: false };
6005
6021
  } catch {
6006
6022
  }
6007
6023
  try {
6008
6024
  const stream = await media.getUserMedia({ audio: true });
6009
6025
  for (const track of stream.getTracks()) track.stop();
6010
- return { ok: true, confirmed: true };
6026
+ return { ok: true, confirmed: true, primed: true };
6011
6027
  } catch (error) {
6012
6028
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
6013
6029
  if (name === "NotAllowedError" || name === "SecurityError") {
@@ -6051,19 +6067,17 @@ function useSpeechInput({
6051
6067
  const recognitionRef = React.useRef(null);
6052
6068
  const startTokenRef = React.useRef(0);
6053
6069
  const micConfirmedRef = React.useRef(false);
6054
- const beginRecognitionRef = React.useRef(null);
6055
- const beginRecognition = React.useCallback(
6056
- (Ctor, token) => {
6057
- beginRecognitionRef.current?.(Ctor, token);
6058
- },
6059
- []
6060
- );
6070
+ const wantsListeningRef = React.useRef(false);
6071
+ const retriedRef = React.useRef(false);
6061
6072
  const finalRef = React.useRef(onFinalTranscript);
6062
6073
  const errorRef = React.useRef(onError);
6063
6074
  finalRef.current = onFinalTranscript;
6064
6075
  errorRef.current = onError;
6076
+ const langRef = React.useRef(lang);
6077
+ langRef.current = lang;
6065
6078
  const stop = React.useCallback(() => {
6066
6079
  startTokenRef.current += 1;
6080
+ wantsListeningRef.current = false;
6067
6081
  setListening(false);
6068
6082
  setInterim("");
6069
6083
  const recognition = recognitionRef.current;
@@ -6072,83 +6086,111 @@ function useSpeechInput({
6072
6086
  recognition.stop();
6073
6087
  } catch {
6074
6088
  }
6075
- setListening(false);
6076
- setInterim("");
6077
6089
  }, []);
6078
- const start = React.useCallback(() => {
6090
+ const openSession = React.useCallback((token) => {
6079
6091
  const Ctor = getRecognitionCtor();
6080
- if (!Ctor) return;
6081
- if (recognitionRef.current) stop();
6082
- setError(null);
6083
- setListening(true);
6092
+ if (!Ctor || token !== startTokenRef.current) return;
6093
+ const recognition = new Ctor();
6094
+ recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
6095
+ recognition.continuous = !isTouchDevice();
6096
+ recognition.interimResults = true;
6097
+ recognition.maxAlternatives = 1;
6098
+ recognition.onstart = () => {
6099
+ if (token !== startTokenRef.current) return;
6100
+ setError(null);
6101
+ setListening(true);
6102
+ };
6103
+ recognition.onresult = (event) => {
6104
+ if (token !== startTokenRef.current) return;
6105
+ let settled = "";
6106
+ let pending = "";
6107
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
6108
+ const result = event.results[i];
6109
+ if (!result) continue;
6110
+ const text = result[0]?.transcript ?? "";
6111
+ if (result.isFinal) settled += text;
6112
+ else pending += text;
6113
+ }
6114
+ setInterim(pending);
6115
+ if (settled.trim() !== "") finalRef.current(settled);
6116
+ };
6117
+ recognition.onerror = (event) => {
6118
+ if (token !== startTokenRef.current) return;
6119
+ if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
6120
+ retriedRef.current = true;
6121
+ void sleep(RETRY_DELAY_MS).then(() => {
6122
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
6123
+ return;
6124
+ openSessionRef.current?.(token);
6125
+ });
6126
+ return;
6127
+ }
6128
+ const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
6129
+ wantsListeningRef.current = false;
6130
+ setListening(false);
6131
+ setInterim("");
6132
+ if (message !== "") {
6133
+ setError(message);
6134
+ errorRef.current?.(message);
6135
+ }
6136
+ };
6137
+ recognition.onend = () => {
6138
+ if (token !== startTokenRef.current) return;
6139
+ setInterim("");
6140
+ if (wantsListeningRef.current && isTouchDevice()) {
6141
+ void sleep(120).then(() => {
6142
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
6143
+ return;
6144
+ openSessionRef.current?.(token);
6145
+ });
6146
+ return;
6147
+ }
6148
+ setListening(false);
6149
+ };
6150
+ recognitionRef.current = recognition;
6151
+ try {
6152
+ recognition.start();
6153
+ } catch {
6154
+ wantsListeningRef.current = false;
6155
+ setListening(false);
6156
+ }
6157
+ }, []);
6158
+ const openSessionRef = React.useRef(null);
6159
+ openSessionRef.current = openSession;
6160
+ const start = React.useCallback(() => {
6161
+ if (!getRecognitionCtor()) return;
6084
6162
  startTokenRef.current += 1;
6085
6163
  const token = startTokenRef.current;
6086
- void ensureMicrophoneAccess().then((access) => {
6164
+ wantsListeningRef.current = true;
6165
+ retriedRef.current = false;
6166
+ micConfirmedRef.current = false;
6167
+ setError(null);
6168
+ setListening(true);
6169
+ void ensureMicrophoneAccess().then(async (access) => {
6087
6170
  if (token !== startTokenRef.current) return;
6088
- micConfirmedRef.current = access.ok && access.confirmed;
6089
6171
  if (!access.ok) {
6172
+ wantsListeningRef.current = false;
6090
6173
  setListening(false);
6091
6174
  setError(access.message);
6092
6175
  errorRef.current?.(access.message);
6093
6176
  return;
6094
6177
  }
6095
- beginRecognition(Ctor, token);
6096
- });
6097
- }, [beginRecognition, stop]);
6098
- const beginRecognitionImpl = React.useCallback(
6099
- (Ctor, token) => {
6100
- const recognition = new Ctor();
6101
- recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
6102
- recognition.continuous = true;
6103
- recognition.interimResults = true;
6104
- recognition.maxAlternatives = 1;
6105
- recognition.onstart = () => {
6178
+ micConfirmedRef.current = access.confirmed;
6179
+ if (access.primed) {
6180
+ await sleep(RELEASE_SETTLE_MS);
6106
6181
  if (token !== startTokenRef.current) return;
6107
- setError(null);
6108
- setListening(true);
6109
- };
6110
- recognition.onresult = (event) => {
6111
- let settled = "";
6112
- let pending = "";
6113
- for (let i = event.resultIndex; i < event.results.length; i += 1) {
6114
- const result = event.results[i];
6115
- if (!result) continue;
6116
- const text = result[0]?.transcript ?? "";
6117
- if (result.isFinal) settled += text;
6118
- else pending += text;
6119
- }
6120
- setInterim(pending);
6121
- if (settled.trim() !== "") finalRef.current(settled);
6122
- };
6123
- recognition.onerror = (event) => {
6124
- const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? "Dictation isn't available in this browser. You can type instead." : describeError(event.error);
6125
- setListening(false);
6126
- setInterim("");
6127
- if (message !== "") {
6128
- setError(message);
6129
- errorRef.current?.(message);
6130
- }
6131
- };
6132
- recognition.onend = () => {
6133
- setListening(false);
6134
- setInterim("");
6135
- };
6136
- recognitionRef.current = recognition;
6137
- try {
6138
- recognition.start();
6139
- } catch {
6140
- setListening(false);
6141
6182
  }
6142
- },
6143
- [lang]
6144
- );
6145
- beginRecognitionRef.current = beginRecognitionImpl;
6183
+ openSessionRef.current?.(token);
6184
+ });
6185
+ }, []);
6146
6186
  const toggle = React.useCallback(() => {
6147
6187
  if (listening) stop();
6148
6188
  else start();
6149
6189
  }, [listening, start, stop]);
6150
6190
  React.useEffect(
6151
6191
  () => () => {
6192
+ startTokenRef.current += 1;
6193
+ wantsListeningRef.current = false;
6152
6194
  const recognition = recognitionRef.current;
6153
6195
  if (!recognition) return;
6154
6196
  recognition.onresult = null;
@@ -6207,6 +6249,50 @@ var EXPLORE_CSS = `
6207
6249
  border-radius: 9999px;
6208
6250
  background-clip: content-box;
6209
6251
  }
6252
+
6253
+ /* The product strip scrolls inside itself; a visible scrollbar would read as a broken
6254
+ layout rather than an affordance, and reserving its gutter shifts the chips. */
6255
+ .boff-explore-strip {
6256
+ scrollbar-width: none;
6257
+ -ms-overflow-style: none;
6258
+ }
6259
+ .boff-explore-strip::-webkit-scrollbar { display: none; }
6260
+
6261
+ @keyframes boff-explore-fade-in {
6262
+ from { opacity: 0; transform: translateY(2px); }
6263
+ to { opacity: 1; transform: none; }
6264
+ }
6265
+ .boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
6266
+ @media (prefers-reduced-motion: reduce) {
6267
+ .boff-explore-fade { animation: none; }
6268
+ }
6269
+
6270
+ /* Nothing inside an answer may widen the page. Model output carries long URLs, code and
6271
+ tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
6272
+ WHOLE document horizontally scrollable, not just the bubble. */
6273
+ .boff-explore-body,
6274
+ .boff-explore-body p,
6275
+ .boff-explore-body li,
6276
+ .boff-explore-body a,
6277
+ .boff-explore-body h1,
6278
+ .boff-explore-body h2,
6279
+ .boff-explore-body h3 {
6280
+ overflow-wrap: anywhere;
6281
+ word-break: break-word;
6282
+ }
6283
+ .boff-explore-body pre {
6284
+ overflow-x: auto;
6285
+ max-width: 100%;
6286
+ }
6287
+ .boff-explore-body table {
6288
+ display: block;
6289
+ overflow-x: auto;
6290
+ max-width: 100%;
6291
+ }
6292
+ .boff-explore-body img {
6293
+ max-width: 100%;
6294
+ height: auto;
6295
+ }
6210
6296
  `;
6211
6297
  function ExploreStyles() {
6212
6298
  return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -6228,7 +6314,6 @@ var CAPABILITIES = [
6228
6314
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
6229
6315
  }
6230
6316
  ];
6231
- var PRODUCT_CHIP_PREVIEW = 12;
6232
6317
  var CTA_VARIANTS = {
6233
6318
  CONTACT: "default",
6234
6319
  WAITLIST: "default",
@@ -6429,7 +6514,7 @@ function AssistantBubble({
6429
6514
  ),
6430
6515
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
6431
6516
  body ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
6432
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
6517
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
6433
6518
  streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
6434
6519
  ] }) : null,
6435
6520
  streaming ? /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { activity }) : null,
@@ -6491,6 +6576,200 @@ function ErrorBanner({
6491
6576
  }
6492
6577
  );
6493
6578
  }
6579
+ function PromptCarousel({
6580
+ prompts,
6581
+ onPrompt,
6582
+ disabled
6583
+ }) {
6584
+ const [index, setIndex] = React.useState(0);
6585
+ const [paused, setPaused] = React.useState(false);
6586
+ const count = prompts.length;
6587
+ const current = prompts[index % count] ?? prompts[0] ?? "";
6588
+ const go = React.useCallback(
6589
+ (delta) => {
6590
+ setIndex((i) => (i + delta + count) % count);
6591
+ },
6592
+ [count]
6593
+ );
6594
+ React.useEffect(() => {
6595
+ if (paused || disabled || count < 2) return;
6596
+ if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
6597
+ return;
6598
+ }
6599
+ const timer = setInterval(() => setIndex((i) => (i + 1) % count), 4500);
6600
+ return () => clearInterval(timer);
6601
+ }, [paused, disabled, count]);
6602
+ if (count === 0) return null;
6603
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6604
+ "div",
6605
+ {
6606
+ className: "flex min-w-0 items-center gap-1.5",
6607
+ onMouseEnter: () => setPaused(true),
6608
+ onMouseLeave: () => setPaused(false),
6609
+ onFocusCapture: () => setPaused(true),
6610
+ onBlurCapture: () => setPaused(false),
6611
+ children: [
6612
+ count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
6613
+ "button",
6614
+ {
6615
+ type: "button",
6616
+ "aria-label": "Previous suggestion",
6617
+ onClick: () => go(-1),
6618
+ className: "hidden h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:inline-flex",
6619
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
6620
+ }
6621
+ ) : null,
6622
+ /* @__PURE__ */ jsxRuntime.jsxs(
6623
+ "button",
6624
+ {
6625
+ "data-boff-explore": "chip",
6626
+ "data-chip-kind": "prompt",
6627
+ type: "button",
6628
+ disabled,
6629
+ onClick: () => onPrompt(current),
6630
+ className: "boff-explore-fade group flex min-w-0 flex-1 items-center gap-2 rounded-full border border-border bg-card px-4 py-2 text-left text-sm text-card-foreground shadow-sm transition-colors hover:border-primary/50 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6631
+ children: [
6632
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
6633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: current }),
6634
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "ml-auto h-3.5 w-3.5 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
6635
+ ]
6636
+ },
6637
+ current
6638
+ ),
6639
+ count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
6640
+ "button",
6641
+ {
6642
+ type: "button",
6643
+ "aria-label": "Next suggestion",
6644
+ onClick: () => go(1),
6645
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6646
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
6647
+ }
6648
+ ) : null
6649
+ ]
6650
+ }
6651
+ );
6652
+ }
6653
+ function ProductStrip({
6654
+ products,
6655
+ focusProduct,
6656
+ onFocus
6657
+ }) {
6658
+ const [expanded, setExpanded] = React.useState(false);
6659
+ const chipClass = (active) => cn(
6660
+ "shrink-0 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",
6661
+ active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
6662
+ );
6663
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
6664
+ /* @__PURE__ */ jsxRuntime.jsxs(
6665
+ "div",
6666
+ {
6667
+ className: cn(
6668
+ "boff-explore-strip flex min-w-0 flex-1 gap-2",
6669
+ expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
6670
+ ),
6671
+ children: [
6672
+ /* @__PURE__ */ jsxRuntime.jsx(
6673
+ "button",
6674
+ {
6675
+ "data-boff-explore": "chip",
6676
+ "data-chip-kind": "product",
6677
+ "data-product": "all",
6678
+ type: "button",
6679
+ "aria-pressed": focusProduct === null,
6680
+ onClick: () => onFocus(null),
6681
+ className: chipClass(focusProduct === null),
6682
+ children: "All products"
6683
+ }
6684
+ ),
6685
+ products.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
6686
+ "button",
6687
+ {
6688
+ "data-boff-explore": "chip",
6689
+ "data-chip-kind": "product",
6690
+ "data-product": product.slug,
6691
+ type: "button",
6692
+ "aria-pressed": focusProduct === product.slug,
6693
+ title: product.tagline ?? product.name,
6694
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
6695
+ className: chipClass(focusProduct === product.slug),
6696
+ children: product.name
6697
+ },
6698
+ product.slug
6699
+ ))
6700
+ ]
6701
+ }
6702
+ ),
6703
+ products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
6704
+ "button",
6705
+ {
6706
+ type: "button",
6707
+ "data-boff-explore": "products-toggle",
6708
+ onClick: () => setExpanded((v) => !v),
6709
+ className: "shrink-0 whitespace-nowrap rounded-full px-2 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",
6710
+ children: expanded ? "Show less" : `All ${products.length}`
6711
+ }
6712
+ ) : null
6713
+ ] });
6714
+ }
6715
+ function DisclaimerDialog({
6716
+ open,
6717
+ onClose,
6718
+ captchaOn
6719
+ }) {
6720
+ const closeRef = React.useRef(null);
6721
+ React.useEffect(() => {
6722
+ if (!open) return;
6723
+ const onKey = (event) => {
6724
+ if (event.key === "Escape") onClose();
6725
+ };
6726
+ document.addEventListener("keydown", onKey);
6727
+ closeRef.current?.focus();
6728
+ return () => document.removeEventListener("keydown", onKey);
6729
+ }, [open, onClose]);
6730
+ if (!open) return null;
6731
+ return /* @__PURE__ */ jsxRuntime.jsx(
6732
+ "div",
6733
+ {
6734
+ "data-boff-explore": "disclaimer-dialog",
6735
+ role: "dialog",
6736
+ "aria-modal": "true",
6737
+ "aria-label": "How this assistant works",
6738
+ className: "fixed inset-0 z-50 flex items-end justify-center bg-foreground/40 p-4 backdrop-blur-sm sm:items-center",
6739
+ onClick: onClose,
6740
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
6741
+ "div",
6742
+ {
6743
+ className: "w-full max-w-md rounded-2xl border border-border bg-card p-5 shadow-xl",
6744
+ onClick: (event) => event.stopPropagation(),
6745
+ children: [
6746
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
6747
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TriangleAlert, { className: "h-4 w-4" }) }),
6748
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
6749
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-card-foreground", children: "How this assistant works" }),
6750
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 space-y-2 text-xs leading-relaxed text-muted-foreground", children: [
6751
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources." }),
6752
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "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." }),
6753
+ captchaOn ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Protected by reCAPTCHA \u2014 the Google Privacy Policy and Terms of Service apply." }) : null
6754
+ ] })
6755
+ ] })
6756
+ ] }),
6757
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
6758
+ Button,
6759
+ {
6760
+ ref: closeRef,
6761
+ size: "sm",
6762
+ variant: "secondary",
6763
+ onClick: onClose,
6764
+ children: "Got it"
6765
+ }
6766
+ ) })
6767
+ ]
6768
+ }
6769
+ )
6770
+ }
6771
+ );
6772
+ }
6494
6773
  function WelcomeState({
6495
6774
  title,
6496
6775
  body,
@@ -6501,21 +6780,19 @@ function WelcomeState({
6501
6780
  onFocus,
6502
6781
  disabled
6503
6782
  }) {
6504
- const [showAllProducts, setShowAllProducts] = React.useState(false);
6505
- const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
6506
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
6783
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-8 sm:py-12", children: [
6507
6784
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
6508
6785
  /* @__PURE__ */ jsxRuntime.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: [
6509
6786
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5" }),
6510
6787
  "AI answers, grounded in our documentation"
6511
6788
  ] }),
6512
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-5 text-balance text-3xl font-bold tracking-tight sm:text-4xl", children: title }),
6513
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-3 max-w-2xl text-pretty text-base leading-relaxed text-muted-foreground sm:text-lg", children: body })
6789
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
6790
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-2.5 max-w-2xl text-pretty text-sm leading-relaxed text-muted-foreground sm:text-lg", children: body })
6514
6791
  ] }),
6515
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-9 grid gap-3 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxRuntime.jsxs(
6792
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-7 grid gap-2.5 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxRuntime.jsxs(
6516
6793
  "li",
6517
6794
  {
6518
- className: "rounded-xl border border-border bg-card p-4 text-left",
6795
+ className: "rounded-xl border border-border/70 bg-card/60 p-3.5 text-left transition-colors hover:border-border",
6519
6796
  children: [
6520
6797
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" }) }),
6521
6798
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
@@ -6524,72 +6801,27 @@ function WelcomeState({
6524
6801
  },
6525
6802
  capTitle
6526
6803
  )) }),
6527
- prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-9", children: [
6528
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
6529
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsxRuntime.jsxs(
6530
- "button",
6804
+ prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
6805
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
6806
+ /* @__PURE__ */ jsxRuntime.jsx(
6807
+ PromptCarousel,
6531
6808
  {
6532
- "data-boff-explore": "chip",
6533
- "data-chip-kind": "prompt",
6534
- type: "button",
6535
- disabled,
6536
- onClick: () => onPrompt(prompt),
6537
- 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",
6538
- children: [
6539
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: prompt }),
6540
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
6541
- ]
6542
- },
6543
- prompt
6544
- )) })
6809
+ prompts,
6810
+ onPrompt,
6811
+ disabled
6812
+ }
6813
+ )
6545
6814
  ] }) : null,
6546
- products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
6547
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
6548
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
6549
- /* @__PURE__ */ jsxRuntime.jsx(
6550
- "button",
6551
- {
6552
- "data-boff-explore": "chip",
6553
- "data-chip-kind": "product",
6554
- "data-product": "all",
6555
- type: "button",
6556
- "aria-pressed": focusProduct === null,
6557
- onClick: () => onFocus(null),
6558
- className: cn(
6559
- "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",
6560
- focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
6561
- ),
6562
- children: "All products"
6563
- }
6564
- ),
6565
- visibleProducts.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
6566
- "button",
6567
- {
6568
- "data-boff-explore": "chip",
6569
- "data-chip-kind": "product",
6570
- "data-product": product.slug,
6571
- type: "button",
6572
- "aria-pressed": focusProduct === product.slug,
6573
- title: product.tagline ?? product.name,
6574
- onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
6575
- className: cn(
6576
- "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",
6577
- focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
6578
- ),
6579
- children: product.name
6580
- },
6581
- product.slug
6582
- )),
6583
- products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsxRuntime.jsx(
6584
- "button",
6585
- {
6586
- type: "button",
6587
- onClick: () => setShowAllProducts((v) => !v),
6588
- 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",
6589
- children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
6590
- }
6591
- ) : null
6592
- ] })
6815
+ products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6", children: [
6816
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
6817
+ /* @__PURE__ */ jsxRuntime.jsx(
6818
+ ProductStrip,
6819
+ {
6820
+ products,
6821
+ focusProduct,
6822
+ onFocus
6823
+ }
6824
+ )
6593
6825
  ] }) : null
6594
6826
  ] });
6595
6827
  }
@@ -6645,6 +6877,7 @@ function ExplorePage({
6645
6877
  }
6646
6878
  });
6647
6879
  speechStopRef.current = speech.stop;
6880
+ const [disclaimerOpen, setDisclaimerOpen] = React.useState(false);
6648
6881
  const scrollRef = React.useRef(null);
6649
6882
  const latestAssistantRef = React.useRef(null);
6650
6883
  const alignedForRef = React.useRef(null);
@@ -6737,7 +6970,7 @@ function ExplorePage({
6737
6970
  "data-boff-explore": "page",
6738
6971
  "data-phase": phase,
6739
6972
  className: cn(
6740
- "flex w-full flex-col bg-background text-foreground",
6973
+ "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
6741
6974
  heightMode === "auto" && "min-h-[70vh]",
6742
6975
  className
6743
6976
  ),
@@ -6879,7 +7112,7 @@ function ExplorePage({
6879
7112
  role: "log",
6880
7113
  "aria-live": "polite",
6881
7114
  "aria-label": "Explore conversation",
6882
- className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
7115
+ className: "boff-explore-scroll min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden",
6883
7116
  children: showWelcome ? /* @__PURE__ */ jsxRuntime.jsx(
6884
7117
  WelcomeState,
6885
7118
  {
@@ -6892,7 +7125,7 @@ function ExplorePage({
6892
7125
  onFocus: setFocusProduct,
6893
7126
  disabled: !canSend || busy
6894
7127
  }
6895
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
7128
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full min-w-0 max-w-3xl space-y-6 px-4 py-6", children: messages.map(
6896
7129
  (message) => message.role === "USER" ? /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
6897
7130
  AssistantBubble,
6898
7131
  {
@@ -6906,7 +7139,15 @@ function ExplorePage({
6906
7139
  ) })
6907
7140
  }
6908
7141
  ),
6909
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-background px-4 pb-4 pt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
7142
+ /* @__PURE__ */ jsxRuntime.jsx(
7143
+ DisclaimerDialog,
7144
+ {
7145
+ open: disclaimerOpen,
7146
+ onClose: () => setDisclaimerOpen(false),
7147
+ captchaOn
7148
+ }
7149
+ ),
7150
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-background/85 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3 backdrop-blur-md", children: /* @__PURE__ */ jsxRuntime.jsxs(
6910
7151
  "form",
6911
7152
  {
6912
7153
  "data-boff-explore": "composer",
@@ -6921,7 +7162,7 @@ function ExplorePage({
6921
7162
  "div",
6922
7163
  {
6923
7164
  className: cn(
6924
- "flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-colors",
7165
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
6925
7166
  overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
6926
7167
  ),
6927
7168
  children: [
@@ -6942,7 +7183,7 @@ function ExplorePage({
6942
7183
  },
6943
7184
  "aria-label": "Ask a question",
6944
7185
  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",
6945
- 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"
7186
+ className: "max-h-[200px] min-h-[44px] w-full min-w-0 flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
6946
7187
  }
6947
7188
  ),
6948
7189
  speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -7033,48 +7274,19 @@ function ExplorePage({
7033
7274
  }
7034
7275
  )
7035
7276
  ] }),
7036
- /* @__PURE__ */ jsxRuntime.jsxs(
7037
- "p",
7038
- {
7039
- "data-boff-explore": "disclaimer",
7040
- className: "mt-2 text-xs leading-relaxed text-muted-foreground",
7041
- children: [
7042
- "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
7043
- " ",
7044
- "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.",
7045
- captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7046
- " ",
7047
- "This site is protected by reCAPTCHA; the Google",
7048
- " ",
7049
- /* @__PURE__ */ jsxRuntime.jsx(
7050
- "a",
7051
- {
7052
- href: "https://policies.google.com/privacy",
7053
- target: "_blank",
7054
- rel: "noopener noreferrer",
7055
- className: "underline underline-offset-2 hover:text-foreground",
7056
- children: "Privacy Policy"
7057
- }
7058
- ),
7059
- " ",
7060
- "and",
7061
- " ",
7062
- /* @__PURE__ */ jsxRuntime.jsx(
7063
- "a",
7064
- {
7065
- href: "https://policies.google.com/terms",
7066
- target: "_blank",
7067
- rel: "noopener noreferrer",
7068
- className: "underline underline-offset-2 hover:text-foreground",
7069
- children: "Terms of Service"
7070
- }
7071
- ),
7072
- " ",
7073
- "apply."
7074
- ] }) : null
7075
- ]
7076
- }
7077
- )
7277
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
7278
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: "Grounded in our docs \u2014 check anything important." }),
7279
+ /* @__PURE__ */ jsxRuntime.jsx(
7280
+ "button",
7281
+ {
7282
+ type: "button",
7283
+ "data-boff-explore": "disclaimer",
7284
+ onClick: () => setDisclaimerOpen(true),
7285
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
7286
+ children: "Disclaimer"
7287
+ }
7288
+ )
7289
+ ] })
7078
7290
  ]
7079
7291
  }
7080
7292
  ) })