@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.
@@ -1523,21 +1523,37 @@ function getRecognitionCtor() {
1523
1523
  const w = window;
1524
1524
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
1525
1525
  }
1526
+ function isTouchDevice() {
1527
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1528
+ try {
1529
+ return window.matchMedia("(pointer: coarse)").matches;
1530
+ } catch {
1531
+ return false;
1532
+ }
1533
+ }
1526
1534
  var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1535
+ var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
1536
+ var RELEASE_SETTLE_MS = 250;
1537
+ var RETRY_DELAY_MS = 350;
1538
+ var sleep = (ms) => new Promise((resolve) => {
1539
+ setTimeout(resolve, ms);
1540
+ });
1527
1541
  async function ensureMicrophoneAccess() {
1528
1542
  const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
1529
- if (!media?.getUserMedia) return { ok: true, confirmed: false };
1543
+ if (!media?.getUserMedia)
1544
+ return { ok: true, confirmed: false, primed: false };
1530
1545
  try {
1531
1546
  const status = await navigator.permissions?.query({
1532
1547
  name: "microphone"
1533
1548
  });
1534
- if (status?.state === "granted") return { ok: true, confirmed: true };
1549
+ if (status?.state === "granted")
1550
+ return { ok: true, confirmed: true, primed: false };
1535
1551
  } catch {
1536
1552
  }
1537
1553
  try {
1538
1554
  const stream = await media.getUserMedia({ audio: true });
1539
1555
  for (const track of stream.getTracks()) track.stop();
1540
- return { ok: true, confirmed: true };
1556
+ return { ok: true, confirmed: true, primed: true };
1541
1557
  } catch (error) {
1542
1558
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
1543
1559
  if (name === "NotAllowedError" || name === "SecurityError") {
@@ -1581,19 +1597,17 @@ function useSpeechInput({
1581
1597
  const recognitionRef = React.useRef(null);
1582
1598
  const startTokenRef = React.useRef(0);
1583
1599
  const micConfirmedRef = React.useRef(false);
1584
- const beginRecognitionRef = React.useRef(null);
1585
- const beginRecognition = React.useCallback(
1586
- (Ctor, token) => {
1587
- beginRecognitionRef.current?.(Ctor, token);
1588
- },
1589
- []
1590
- );
1600
+ const wantsListeningRef = React.useRef(false);
1601
+ const retriedRef = React.useRef(false);
1591
1602
  const finalRef = React.useRef(onFinalTranscript);
1592
1603
  const errorRef = React.useRef(onError);
1593
1604
  finalRef.current = onFinalTranscript;
1594
1605
  errorRef.current = onError;
1606
+ const langRef = React.useRef(lang);
1607
+ langRef.current = lang;
1595
1608
  const stop = React.useCallback(() => {
1596
1609
  startTokenRef.current += 1;
1610
+ wantsListeningRef.current = false;
1597
1611
  setListening(false);
1598
1612
  setInterim("");
1599
1613
  const recognition = recognitionRef.current;
@@ -1602,83 +1616,111 @@ function useSpeechInput({
1602
1616
  recognition.stop();
1603
1617
  } catch {
1604
1618
  }
1605
- setListening(false);
1606
- setInterim("");
1607
1619
  }, []);
1608
- const start = React.useCallback(() => {
1620
+ const openSession = React.useCallback((token) => {
1609
1621
  const Ctor = getRecognitionCtor();
1610
- if (!Ctor) return;
1611
- if (recognitionRef.current) stop();
1612
- setError(null);
1613
- setListening(true);
1622
+ if (!Ctor || token !== startTokenRef.current) return;
1623
+ const recognition = new Ctor();
1624
+ recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1625
+ recognition.continuous = !isTouchDevice();
1626
+ recognition.interimResults = true;
1627
+ recognition.maxAlternatives = 1;
1628
+ recognition.onstart = () => {
1629
+ if (token !== startTokenRef.current) return;
1630
+ setError(null);
1631
+ setListening(true);
1632
+ };
1633
+ recognition.onresult = (event) => {
1634
+ if (token !== startTokenRef.current) return;
1635
+ let settled = "";
1636
+ let pending = "";
1637
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
1638
+ const result = event.results[i];
1639
+ if (!result) continue;
1640
+ const text = result[0]?.transcript ?? "";
1641
+ if (result.isFinal) settled += text;
1642
+ else pending += text;
1643
+ }
1644
+ setInterim(pending);
1645
+ if (settled.trim() !== "") finalRef.current(settled);
1646
+ };
1647
+ recognition.onerror = (event) => {
1648
+ if (token !== startTokenRef.current) return;
1649
+ if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
1650
+ retriedRef.current = true;
1651
+ void sleep(RETRY_DELAY_MS).then(() => {
1652
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1653
+ return;
1654
+ openSessionRef.current?.(token);
1655
+ });
1656
+ return;
1657
+ }
1658
+ const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
1659
+ wantsListeningRef.current = false;
1660
+ setListening(false);
1661
+ setInterim("");
1662
+ if (message !== "") {
1663
+ setError(message);
1664
+ errorRef.current?.(message);
1665
+ }
1666
+ };
1667
+ recognition.onend = () => {
1668
+ if (token !== startTokenRef.current) return;
1669
+ setInterim("");
1670
+ if (wantsListeningRef.current && isTouchDevice()) {
1671
+ void sleep(120).then(() => {
1672
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1673
+ return;
1674
+ openSessionRef.current?.(token);
1675
+ });
1676
+ return;
1677
+ }
1678
+ setListening(false);
1679
+ };
1680
+ recognitionRef.current = recognition;
1681
+ try {
1682
+ recognition.start();
1683
+ } catch {
1684
+ wantsListeningRef.current = false;
1685
+ setListening(false);
1686
+ }
1687
+ }, []);
1688
+ const openSessionRef = React.useRef(null);
1689
+ openSessionRef.current = openSession;
1690
+ const start = React.useCallback(() => {
1691
+ if (!getRecognitionCtor()) return;
1614
1692
  startTokenRef.current += 1;
1615
1693
  const token = startTokenRef.current;
1616
- void ensureMicrophoneAccess().then((access) => {
1694
+ wantsListeningRef.current = true;
1695
+ retriedRef.current = false;
1696
+ micConfirmedRef.current = false;
1697
+ setError(null);
1698
+ setListening(true);
1699
+ void ensureMicrophoneAccess().then(async (access) => {
1617
1700
  if (token !== startTokenRef.current) return;
1618
- micConfirmedRef.current = access.ok && access.confirmed;
1619
1701
  if (!access.ok) {
1702
+ wantsListeningRef.current = false;
1620
1703
  setListening(false);
1621
1704
  setError(access.message);
1622
1705
  errorRef.current?.(access.message);
1623
1706
  return;
1624
1707
  }
1625
- beginRecognition(Ctor, token);
1626
- });
1627
- }, [beginRecognition, stop]);
1628
- const beginRecognitionImpl = React.useCallback(
1629
- (Ctor, token) => {
1630
- const recognition = new Ctor();
1631
- recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1632
- recognition.continuous = true;
1633
- recognition.interimResults = true;
1634
- recognition.maxAlternatives = 1;
1635
- recognition.onstart = () => {
1708
+ micConfirmedRef.current = access.confirmed;
1709
+ if (access.primed) {
1710
+ await sleep(RELEASE_SETTLE_MS);
1636
1711
  if (token !== startTokenRef.current) return;
1637
- setError(null);
1638
- setListening(true);
1639
- };
1640
- recognition.onresult = (event) => {
1641
- let settled = "";
1642
- let pending = "";
1643
- for (let i = event.resultIndex; i < event.results.length; i += 1) {
1644
- const result = event.results[i];
1645
- if (!result) continue;
1646
- const text = result[0]?.transcript ?? "";
1647
- if (result.isFinal) settled += text;
1648
- else pending += text;
1649
- }
1650
- setInterim(pending);
1651
- if (settled.trim() !== "") finalRef.current(settled);
1652
- };
1653
- recognition.onerror = (event) => {
1654
- 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);
1655
- setListening(false);
1656
- setInterim("");
1657
- if (message !== "") {
1658
- setError(message);
1659
- errorRef.current?.(message);
1660
- }
1661
- };
1662
- recognition.onend = () => {
1663
- setListening(false);
1664
- setInterim("");
1665
- };
1666
- recognitionRef.current = recognition;
1667
- try {
1668
- recognition.start();
1669
- } catch {
1670
- setListening(false);
1671
1712
  }
1672
- },
1673
- [lang]
1674
- );
1675
- beginRecognitionRef.current = beginRecognitionImpl;
1713
+ openSessionRef.current?.(token);
1714
+ });
1715
+ }, []);
1676
1716
  const toggle = React.useCallback(() => {
1677
1717
  if (listening) stop();
1678
1718
  else start();
1679
1719
  }, [listening, start, stop]);
1680
1720
  React.useEffect(
1681
1721
  () => () => {
1722
+ startTokenRef.current += 1;
1723
+ wantsListeningRef.current = false;
1682
1724
  const recognition = recognitionRef.current;
1683
1725
  if (!recognition) return;
1684
1726
  recognition.onresult = null;
@@ -1862,6 +1904,50 @@ var EXPLORE_CSS = `
1862
1904
  border-radius: 9999px;
1863
1905
  background-clip: content-box;
1864
1906
  }
1907
+
1908
+ /* The product strip scrolls inside itself; a visible scrollbar would read as a broken
1909
+ layout rather than an affordance, and reserving its gutter shifts the chips. */
1910
+ .boff-explore-strip {
1911
+ scrollbar-width: none;
1912
+ -ms-overflow-style: none;
1913
+ }
1914
+ .boff-explore-strip::-webkit-scrollbar { display: none; }
1915
+
1916
+ @keyframes boff-explore-fade-in {
1917
+ from { opacity: 0; transform: translateY(2px); }
1918
+ to { opacity: 1; transform: none; }
1919
+ }
1920
+ .boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
1921
+ @media (prefers-reduced-motion: reduce) {
1922
+ .boff-explore-fade { animation: none; }
1923
+ }
1924
+
1925
+ /* Nothing inside an answer may widen the page. Model output carries long URLs, code and
1926
+ tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
1927
+ WHOLE document horizontally scrollable, not just the bubble. */
1928
+ .boff-explore-body,
1929
+ .boff-explore-body p,
1930
+ .boff-explore-body li,
1931
+ .boff-explore-body a,
1932
+ .boff-explore-body h1,
1933
+ .boff-explore-body h2,
1934
+ .boff-explore-body h3 {
1935
+ overflow-wrap: anywhere;
1936
+ word-break: break-word;
1937
+ }
1938
+ .boff-explore-body pre {
1939
+ overflow-x: auto;
1940
+ max-width: 100%;
1941
+ }
1942
+ .boff-explore-body table {
1943
+ display: block;
1944
+ overflow-x: auto;
1945
+ max-width: 100%;
1946
+ }
1947
+ .boff-explore-body img {
1948
+ max-width: 100%;
1949
+ height: auto;
1950
+ }
1865
1951
  `;
1866
1952
  function ExploreStyles() {
1867
1953
  return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -1883,7 +1969,6 @@ var CAPABILITIES = [
1883
1969
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
1884
1970
  }
1885
1971
  ];
1886
- var PRODUCT_CHIP_PREVIEW = 12;
1887
1972
  var CTA_VARIANTS = {
1888
1973
  CONTACT: "default",
1889
1974
  WAITLIST: "default",
@@ -2084,7 +2169,7 @@ function AssistantBubble({
2084
2169
  ),
2085
2170
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
2086
2171
  body ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
2087
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
2172
+ /* @__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 }) }),
2088
2173
  streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
2089
2174
  ] }) : null,
2090
2175
  streaming ? /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { activity }) : null,
@@ -2146,6 +2231,200 @@ function ErrorBanner({
2146
2231
  }
2147
2232
  );
2148
2233
  }
2234
+ function PromptCarousel({
2235
+ prompts,
2236
+ onPrompt,
2237
+ disabled
2238
+ }) {
2239
+ const [index, setIndex] = React.useState(0);
2240
+ const [paused, setPaused] = React.useState(false);
2241
+ const count = prompts.length;
2242
+ const current = prompts[index % count] ?? prompts[0] ?? "";
2243
+ const go = React.useCallback(
2244
+ (delta) => {
2245
+ setIndex((i) => (i + delta + count) % count);
2246
+ },
2247
+ [count]
2248
+ );
2249
+ React.useEffect(() => {
2250
+ if (paused || disabled || count < 2) return;
2251
+ if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
2252
+ return;
2253
+ }
2254
+ const timer = setInterval(() => setIndex((i) => (i + 1) % count), 4500);
2255
+ return () => clearInterval(timer);
2256
+ }, [paused, disabled, count]);
2257
+ if (count === 0) return null;
2258
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2259
+ "div",
2260
+ {
2261
+ className: "flex min-w-0 items-center gap-1.5",
2262
+ onMouseEnter: () => setPaused(true),
2263
+ onMouseLeave: () => setPaused(false),
2264
+ onFocusCapture: () => setPaused(true),
2265
+ onBlurCapture: () => setPaused(false),
2266
+ children: [
2267
+ count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
2268
+ "button",
2269
+ {
2270
+ type: "button",
2271
+ "aria-label": "Previous suggestion",
2272
+ onClick: () => go(-1),
2273
+ 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",
2274
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
2275
+ }
2276
+ ) : null,
2277
+ /* @__PURE__ */ jsxRuntime.jsxs(
2278
+ "button",
2279
+ {
2280
+ "data-boff-explore": "chip",
2281
+ "data-chip-kind": "prompt",
2282
+ type: "button",
2283
+ disabled,
2284
+ onClick: () => onPrompt(current),
2285
+ 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",
2286
+ children: [
2287
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
2288
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: current }),
2289
+ /* @__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" })
2290
+ ]
2291
+ },
2292
+ current
2293
+ ),
2294
+ count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
2295
+ "button",
2296
+ {
2297
+ type: "button",
2298
+ "aria-label": "Next suggestion",
2299
+ onClick: () => go(1),
2300
+ 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",
2301
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
2302
+ }
2303
+ ) : null
2304
+ ]
2305
+ }
2306
+ );
2307
+ }
2308
+ function ProductStrip({
2309
+ products,
2310
+ focusProduct,
2311
+ onFocus
2312
+ }) {
2313
+ const [expanded, setExpanded] = React.useState(false);
2314
+ const chipClass = (active) => cn(
2315
+ "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",
2316
+ active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2317
+ );
2318
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
2319
+ /* @__PURE__ */ jsxRuntime.jsxs(
2320
+ "div",
2321
+ {
2322
+ className: cn(
2323
+ "boff-explore-strip flex min-w-0 flex-1 gap-2",
2324
+ expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
2325
+ ),
2326
+ children: [
2327
+ /* @__PURE__ */ jsxRuntime.jsx(
2328
+ "button",
2329
+ {
2330
+ "data-boff-explore": "chip",
2331
+ "data-chip-kind": "product",
2332
+ "data-product": "all",
2333
+ type: "button",
2334
+ "aria-pressed": focusProduct === null,
2335
+ onClick: () => onFocus(null),
2336
+ className: chipClass(focusProduct === null),
2337
+ children: "All products"
2338
+ }
2339
+ ),
2340
+ products.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
2341
+ "button",
2342
+ {
2343
+ "data-boff-explore": "chip",
2344
+ "data-chip-kind": "product",
2345
+ "data-product": product.slug,
2346
+ type: "button",
2347
+ "aria-pressed": focusProduct === product.slug,
2348
+ title: product.tagline ?? product.name,
2349
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2350
+ className: chipClass(focusProduct === product.slug),
2351
+ children: product.name
2352
+ },
2353
+ product.slug
2354
+ ))
2355
+ ]
2356
+ }
2357
+ ),
2358
+ products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2359
+ "button",
2360
+ {
2361
+ type: "button",
2362
+ "data-boff-explore": "products-toggle",
2363
+ onClick: () => setExpanded((v) => !v),
2364
+ 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",
2365
+ children: expanded ? "Show less" : `All ${products.length}`
2366
+ }
2367
+ ) : null
2368
+ ] });
2369
+ }
2370
+ function DisclaimerDialog({
2371
+ open,
2372
+ onClose,
2373
+ captchaOn
2374
+ }) {
2375
+ const closeRef = React.useRef(null);
2376
+ React.useEffect(() => {
2377
+ if (!open) return;
2378
+ const onKey = (event) => {
2379
+ if (event.key === "Escape") onClose();
2380
+ };
2381
+ document.addEventListener("keydown", onKey);
2382
+ closeRef.current?.focus();
2383
+ return () => document.removeEventListener("keydown", onKey);
2384
+ }, [open, onClose]);
2385
+ if (!open) return null;
2386
+ return /* @__PURE__ */ jsxRuntime.jsx(
2387
+ "div",
2388
+ {
2389
+ "data-boff-explore": "disclaimer-dialog",
2390
+ role: "dialog",
2391
+ "aria-modal": "true",
2392
+ "aria-label": "How this assistant works",
2393
+ className: "fixed inset-0 z-50 flex items-end justify-center bg-foreground/40 p-4 backdrop-blur-sm sm:items-center",
2394
+ onClick: onClose,
2395
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2396
+ "div",
2397
+ {
2398
+ className: "w-full max-w-md rounded-2xl border border-border bg-card p-5 shadow-xl",
2399
+ onClick: (event) => event.stopPropagation(),
2400
+ children: [
2401
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
2402
+ /* @__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" }) }),
2403
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
2404
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-card-foreground", children: "How this assistant works" }),
2405
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 space-y-2 text-xs leading-relaxed text-muted-foreground", children: [
2406
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources." }),
2407
+ /* @__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." }),
2408
+ captchaOn ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Protected by reCAPTCHA \u2014 the Google Privacy Policy and Terms of Service apply." }) : null
2409
+ ] })
2410
+ ] })
2411
+ ] }),
2412
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
2413
+ Button,
2414
+ {
2415
+ ref: closeRef,
2416
+ size: "sm",
2417
+ variant: "secondary",
2418
+ onClick: onClose,
2419
+ children: "Got it"
2420
+ }
2421
+ ) })
2422
+ ]
2423
+ }
2424
+ )
2425
+ }
2426
+ );
2427
+ }
2149
2428
  function WelcomeState({
2150
2429
  title,
2151
2430
  body,
@@ -2156,21 +2435,19 @@ function WelcomeState({
2156
2435
  onFocus,
2157
2436
  disabled
2158
2437
  }) {
2159
- const [showAllProducts, setShowAllProducts] = React.useState(false);
2160
- const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
2161
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
2438
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-8 sm:py-12", children: [
2162
2439
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
2163
2440
  /* @__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: [
2164
2441
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5" }),
2165
2442
  "AI answers, grounded in our documentation"
2166
2443
  ] }),
2167
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-5 text-balance text-3xl font-bold tracking-tight sm:text-4xl", children: title }),
2168
- /* @__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 })
2444
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
2445
+ /* @__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 })
2169
2446
  ] }),
2170
- /* @__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(
2447
+ /* @__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(
2171
2448
  "li",
2172
2449
  {
2173
- className: "rounded-xl border border-border bg-card p-4 text-left",
2450
+ className: "rounded-xl border border-border/70 bg-card/60 p-3.5 text-left transition-colors hover:border-border",
2174
2451
  children: [
2175
2452
  /* @__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" }) }),
2176
2453
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
@@ -2179,72 +2456,27 @@ function WelcomeState({
2179
2456
  },
2180
2457
  capTitle
2181
2458
  )) }),
2182
- prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-9", children: [
2183
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2184
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsxRuntime.jsxs(
2185
- "button",
2459
+ prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
2460
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2461
+ /* @__PURE__ */ jsxRuntime.jsx(
2462
+ PromptCarousel,
2186
2463
  {
2187
- "data-boff-explore": "chip",
2188
- "data-chip-kind": "prompt",
2189
- type: "button",
2190
- disabled,
2191
- onClick: () => onPrompt(prompt),
2192
- 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",
2193
- children: [
2194
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: prompt }),
2195
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2196
- ]
2197
- },
2198
- prompt
2199
- )) })
2464
+ prompts,
2465
+ onPrompt,
2466
+ disabled
2467
+ }
2468
+ )
2200
2469
  ] }) : null,
2201
- products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
2202
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2203
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
2204
- /* @__PURE__ */ jsxRuntime.jsx(
2205
- "button",
2206
- {
2207
- "data-boff-explore": "chip",
2208
- "data-chip-kind": "product",
2209
- "data-product": "all",
2210
- type: "button",
2211
- "aria-pressed": focusProduct === null,
2212
- onClick: () => onFocus(null),
2213
- className: cn(
2214
- "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",
2215
- focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2216
- ),
2217
- children: "All products"
2218
- }
2219
- ),
2220
- visibleProducts.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
2221
- "button",
2222
- {
2223
- "data-boff-explore": "chip",
2224
- "data-chip-kind": "product",
2225
- "data-product": product.slug,
2226
- type: "button",
2227
- "aria-pressed": focusProduct === product.slug,
2228
- title: product.tagline ?? product.name,
2229
- onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2230
- className: cn(
2231
- "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",
2232
- focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2233
- ),
2234
- children: product.name
2235
- },
2236
- product.slug
2237
- )),
2238
- products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsxRuntime.jsx(
2239
- "button",
2240
- {
2241
- type: "button",
2242
- onClick: () => setShowAllProducts((v) => !v),
2243
- 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",
2244
- children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
2245
- }
2246
- ) : null
2247
- ] })
2470
+ products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6", children: [
2471
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2472
+ /* @__PURE__ */ jsxRuntime.jsx(
2473
+ ProductStrip,
2474
+ {
2475
+ products,
2476
+ focusProduct,
2477
+ onFocus
2478
+ }
2479
+ )
2248
2480
  ] }) : null
2249
2481
  ] });
2250
2482
  }
@@ -2300,6 +2532,7 @@ function ExplorePage({
2300
2532
  }
2301
2533
  });
2302
2534
  speechStopRef.current = speech.stop;
2535
+ const [disclaimerOpen, setDisclaimerOpen] = React.useState(false);
2303
2536
  const scrollRef = React.useRef(null);
2304
2537
  const latestAssistantRef = React.useRef(null);
2305
2538
  const alignedForRef = React.useRef(null);
@@ -2392,7 +2625,7 @@ function ExplorePage({
2392
2625
  "data-boff-explore": "page",
2393
2626
  "data-phase": phase,
2394
2627
  className: cn(
2395
- "flex w-full flex-col bg-background text-foreground",
2628
+ "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
2396
2629
  heightMode === "auto" && "min-h-[70vh]",
2397
2630
  className
2398
2631
  ),
@@ -2534,7 +2767,7 @@ function ExplorePage({
2534
2767
  role: "log",
2535
2768
  "aria-live": "polite",
2536
2769
  "aria-label": "Explore conversation",
2537
- className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
2770
+ className: "boff-explore-scroll min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden",
2538
2771
  children: showWelcome ? /* @__PURE__ */ jsxRuntime.jsx(
2539
2772
  WelcomeState,
2540
2773
  {
@@ -2547,7 +2780,7 @@ function ExplorePage({
2547
2780
  onFocus: setFocusProduct,
2548
2781
  disabled: !canSend || busy
2549
2782
  }
2550
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
2783
+ ) : /* @__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(
2551
2784
  (message) => message.role === "USER" ? /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
2552
2785
  AssistantBubble,
2553
2786
  {
@@ -2561,7 +2794,15 @@ function ExplorePage({
2561
2794
  ) })
2562
2795
  }
2563
2796
  ),
2564
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-background px-4 pb-4 pt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
2797
+ /* @__PURE__ */ jsxRuntime.jsx(
2798
+ DisclaimerDialog,
2799
+ {
2800
+ open: disclaimerOpen,
2801
+ onClose: () => setDisclaimerOpen(false),
2802
+ captchaOn
2803
+ }
2804
+ ),
2805
+ /* @__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(
2565
2806
  "form",
2566
2807
  {
2567
2808
  "data-boff-explore": "composer",
@@ -2576,7 +2817,7 @@ function ExplorePage({
2576
2817
  "div",
2577
2818
  {
2578
2819
  className: cn(
2579
- "flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-colors",
2820
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
2580
2821
  overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
2581
2822
  ),
2582
2823
  children: [
@@ -2597,7 +2838,7 @@ function ExplorePage({
2597
2838
  },
2598
2839
  "aria-label": "Ask a question",
2599
2840
  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",
2600
- 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"
2841
+ 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"
2601
2842
  }
2602
2843
  ),
2603
2844
  speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -2688,48 +2929,19 @@ function ExplorePage({
2688
2929
  }
2689
2930
  )
2690
2931
  ] }),
2691
- /* @__PURE__ */ jsxRuntime.jsxs(
2692
- "p",
2693
- {
2694
- "data-boff-explore": "disclaimer",
2695
- className: "mt-2 text-xs leading-relaxed text-muted-foreground",
2696
- children: [
2697
- "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
2698
- " ",
2699
- "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.",
2700
- captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2701
- " ",
2702
- "This site is protected by reCAPTCHA; the Google",
2703
- " ",
2704
- /* @__PURE__ */ jsxRuntime.jsx(
2705
- "a",
2706
- {
2707
- href: "https://policies.google.com/privacy",
2708
- target: "_blank",
2709
- rel: "noopener noreferrer",
2710
- className: "underline underline-offset-2 hover:text-foreground",
2711
- children: "Privacy Policy"
2712
- }
2713
- ),
2714
- " ",
2715
- "and",
2716
- " ",
2717
- /* @__PURE__ */ jsxRuntime.jsx(
2718
- "a",
2719
- {
2720
- href: "https://policies.google.com/terms",
2721
- target: "_blank",
2722
- rel: "noopener noreferrer",
2723
- className: "underline underline-offset-2 hover:text-foreground",
2724
- children: "Terms of Service"
2725
- }
2726
- ),
2727
- " ",
2728
- "apply."
2729
- ] }) : null
2730
- ]
2731
- }
2732
- )
2932
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
2933
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: "Grounded in our docs \u2014 check anything important." }),
2934
+ /* @__PURE__ */ jsxRuntime.jsx(
2935
+ "button",
2936
+ {
2937
+ type: "button",
2938
+ "data-boff-explore": "disclaimer",
2939
+ onClick: () => setDisclaimerOpen(true),
2940
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2941
+ children: "Disclaimer"
2942
+ }
2943
+ )
2944
+ ] })
2733
2945
  ]
2734
2946
  }
2735
2947
  ) })