@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.
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext } from 'react';
3
- import { Sparkles, RotateCcw, History, Trash2, Mic, Square, ArrowUp, BookOpenText, Layers, Link2, TriangleAlert, ExternalLink } from 'lucide-react';
3
+ import { Sparkles, RotateCcw, History, Trash2, Mic, Square, ArrowUp, BookOpenText, Layers, Link2, TriangleAlert, ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeSanitize from 'rehype-sanitize';
6
6
  import remarkGfm from 'remark-gfm';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
8
  import { Helmet } from 'react-helmet-async';
9
9
  import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
@@ -1497,21 +1497,37 @@ function getRecognitionCtor() {
1497
1497
  const w = window;
1498
1498
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
1499
1499
  }
1500
+ function isTouchDevice() {
1501
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1502
+ try {
1503
+ return window.matchMedia("(pointer: coarse)").matches;
1504
+ } catch {
1505
+ return false;
1506
+ }
1507
+ }
1500
1508
  var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1509
+ var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
1510
+ var RELEASE_SETTLE_MS = 250;
1511
+ var RETRY_DELAY_MS = 350;
1512
+ var sleep = (ms) => new Promise((resolve) => {
1513
+ setTimeout(resolve, ms);
1514
+ });
1501
1515
  async function ensureMicrophoneAccess() {
1502
1516
  const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
1503
- if (!media?.getUserMedia) return { ok: true, confirmed: false };
1517
+ if (!media?.getUserMedia)
1518
+ return { ok: true, confirmed: false, primed: false };
1504
1519
  try {
1505
1520
  const status = await navigator.permissions?.query({
1506
1521
  name: "microphone"
1507
1522
  });
1508
- if (status?.state === "granted") return { ok: true, confirmed: true };
1523
+ if (status?.state === "granted")
1524
+ return { ok: true, confirmed: true, primed: false };
1509
1525
  } catch {
1510
1526
  }
1511
1527
  try {
1512
1528
  const stream = await media.getUserMedia({ audio: true });
1513
1529
  for (const track of stream.getTracks()) track.stop();
1514
- return { ok: true, confirmed: true };
1530
+ return { ok: true, confirmed: true, primed: true };
1515
1531
  } catch (error) {
1516
1532
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
1517
1533
  if (name === "NotAllowedError" || name === "SecurityError") {
@@ -1555,19 +1571,17 @@ function useSpeechInput({
1555
1571
  const recognitionRef = useRef(null);
1556
1572
  const startTokenRef = useRef(0);
1557
1573
  const micConfirmedRef = useRef(false);
1558
- const beginRecognitionRef = useRef(null);
1559
- const beginRecognition = useCallback(
1560
- (Ctor, token) => {
1561
- beginRecognitionRef.current?.(Ctor, token);
1562
- },
1563
- []
1564
- );
1574
+ const wantsListeningRef = useRef(false);
1575
+ const retriedRef = useRef(false);
1565
1576
  const finalRef = useRef(onFinalTranscript);
1566
1577
  const errorRef = useRef(onError);
1567
1578
  finalRef.current = onFinalTranscript;
1568
1579
  errorRef.current = onError;
1580
+ const langRef = useRef(lang);
1581
+ langRef.current = lang;
1569
1582
  const stop = useCallback(() => {
1570
1583
  startTokenRef.current += 1;
1584
+ wantsListeningRef.current = false;
1571
1585
  setListening(false);
1572
1586
  setInterim("");
1573
1587
  const recognition = recognitionRef.current;
@@ -1576,83 +1590,111 @@ function useSpeechInput({
1576
1590
  recognition.stop();
1577
1591
  } catch {
1578
1592
  }
1579
- setListening(false);
1580
- setInterim("");
1581
1593
  }, []);
1582
- const start = useCallback(() => {
1594
+ const openSession = useCallback((token) => {
1583
1595
  const Ctor = getRecognitionCtor();
1584
- if (!Ctor) return;
1585
- if (recognitionRef.current) stop();
1586
- setError(null);
1587
- setListening(true);
1596
+ if (!Ctor || token !== startTokenRef.current) return;
1597
+ const recognition = new Ctor();
1598
+ recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1599
+ recognition.continuous = !isTouchDevice();
1600
+ recognition.interimResults = true;
1601
+ recognition.maxAlternatives = 1;
1602
+ recognition.onstart = () => {
1603
+ if (token !== startTokenRef.current) return;
1604
+ setError(null);
1605
+ setListening(true);
1606
+ };
1607
+ recognition.onresult = (event) => {
1608
+ if (token !== startTokenRef.current) return;
1609
+ let settled = "";
1610
+ let pending = "";
1611
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
1612
+ const result = event.results[i];
1613
+ if (!result) continue;
1614
+ const text = result[0]?.transcript ?? "";
1615
+ if (result.isFinal) settled += text;
1616
+ else pending += text;
1617
+ }
1618
+ setInterim(pending);
1619
+ if (settled.trim() !== "") finalRef.current(settled);
1620
+ };
1621
+ recognition.onerror = (event) => {
1622
+ if (token !== startTokenRef.current) return;
1623
+ if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
1624
+ retriedRef.current = true;
1625
+ void sleep(RETRY_DELAY_MS).then(() => {
1626
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1627
+ return;
1628
+ openSessionRef.current?.(token);
1629
+ });
1630
+ return;
1631
+ }
1632
+ const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
1633
+ wantsListeningRef.current = false;
1634
+ setListening(false);
1635
+ setInterim("");
1636
+ if (message !== "") {
1637
+ setError(message);
1638
+ errorRef.current?.(message);
1639
+ }
1640
+ };
1641
+ recognition.onend = () => {
1642
+ if (token !== startTokenRef.current) return;
1643
+ setInterim("");
1644
+ if (wantsListeningRef.current && isTouchDevice()) {
1645
+ void sleep(120).then(() => {
1646
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1647
+ return;
1648
+ openSessionRef.current?.(token);
1649
+ });
1650
+ return;
1651
+ }
1652
+ setListening(false);
1653
+ };
1654
+ recognitionRef.current = recognition;
1655
+ try {
1656
+ recognition.start();
1657
+ } catch {
1658
+ wantsListeningRef.current = false;
1659
+ setListening(false);
1660
+ }
1661
+ }, []);
1662
+ const openSessionRef = useRef(null);
1663
+ openSessionRef.current = openSession;
1664
+ const start = useCallback(() => {
1665
+ if (!getRecognitionCtor()) return;
1588
1666
  startTokenRef.current += 1;
1589
1667
  const token = startTokenRef.current;
1590
- void ensureMicrophoneAccess().then((access) => {
1668
+ wantsListeningRef.current = true;
1669
+ retriedRef.current = false;
1670
+ micConfirmedRef.current = false;
1671
+ setError(null);
1672
+ setListening(true);
1673
+ void ensureMicrophoneAccess().then(async (access) => {
1591
1674
  if (token !== startTokenRef.current) return;
1592
- micConfirmedRef.current = access.ok && access.confirmed;
1593
1675
  if (!access.ok) {
1676
+ wantsListeningRef.current = false;
1594
1677
  setListening(false);
1595
1678
  setError(access.message);
1596
1679
  errorRef.current?.(access.message);
1597
1680
  return;
1598
1681
  }
1599
- beginRecognition(Ctor, token);
1600
- });
1601
- }, [beginRecognition, stop]);
1602
- const beginRecognitionImpl = useCallback(
1603
- (Ctor, token) => {
1604
- const recognition = new Ctor();
1605
- recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1606
- recognition.continuous = true;
1607
- recognition.interimResults = true;
1608
- recognition.maxAlternatives = 1;
1609
- recognition.onstart = () => {
1682
+ micConfirmedRef.current = access.confirmed;
1683
+ if (access.primed) {
1684
+ await sleep(RELEASE_SETTLE_MS);
1610
1685
  if (token !== startTokenRef.current) return;
1611
- setError(null);
1612
- setListening(true);
1613
- };
1614
- recognition.onresult = (event) => {
1615
- let settled = "";
1616
- let pending = "";
1617
- for (let i = event.resultIndex; i < event.results.length; i += 1) {
1618
- const result = event.results[i];
1619
- if (!result) continue;
1620
- const text = result[0]?.transcript ?? "";
1621
- if (result.isFinal) settled += text;
1622
- else pending += text;
1623
- }
1624
- setInterim(pending);
1625
- if (settled.trim() !== "") finalRef.current(settled);
1626
- };
1627
- recognition.onerror = (event) => {
1628
- 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);
1629
- setListening(false);
1630
- setInterim("");
1631
- if (message !== "") {
1632
- setError(message);
1633
- errorRef.current?.(message);
1634
- }
1635
- };
1636
- recognition.onend = () => {
1637
- setListening(false);
1638
- setInterim("");
1639
- };
1640
- recognitionRef.current = recognition;
1641
- try {
1642
- recognition.start();
1643
- } catch {
1644
- setListening(false);
1645
1686
  }
1646
- },
1647
- [lang]
1648
- );
1649
- beginRecognitionRef.current = beginRecognitionImpl;
1687
+ openSessionRef.current?.(token);
1688
+ });
1689
+ }, []);
1650
1690
  const toggle = useCallback(() => {
1651
1691
  if (listening) stop();
1652
1692
  else start();
1653
1693
  }, [listening, start, stop]);
1654
1694
  useEffect(
1655
1695
  () => () => {
1696
+ startTokenRef.current += 1;
1697
+ wantsListeningRef.current = false;
1656
1698
  const recognition = recognitionRef.current;
1657
1699
  if (!recognition) return;
1658
1700
  recognition.onresult = null;
@@ -1836,6 +1878,50 @@ var EXPLORE_CSS = `
1836
1878
  border-radius: 9999px;
1837
1879
  background-clip: content-box;
1838
1880
  }
1881
+
1882
+ /* The product strip scrolls inside itself; a visible scrollbar would read as a broken
1883
+ layout rather than an affordance, and reserving its gutter shifts the chips. */
1884
+ .boff-explore-strip {
1885
+ scrollbar-width: none;
1886
+ -ms-overflow-style: none;
1887
+ }
1888
+ .boff-explore-strip::-webkit-scrollbar { display: none; }
1889
+
1890
+ @keyframes boff-explore-fade-in {
1891
+ from { opacity: 0; transform: translateY(2px); }
1892
+ to { opacity: 1; transform: none; }
1893
+ }
1894
+ .boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
1895
+ @media (prefers-reduced-motion: reduce) {
1896
+ .boff-explore-fade { animation: none; }
1897
+ }
1898
+
1899
+ /* Nothing inside an answer may widen the page. Model output carries long URLs, code and
1900
+ tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
1901
+ WHOLE document horizontally scrollable, not just the bubble. */
1902
+ .boff-explore-body,
1903
+ .boff-explore-body p,
1904
+ .boff-explore-body li,
1905
+ .boff-explore-body a,
1906
+ .boff-explore-body h1,
1907
+ .boff-explore-body h2,
1908
+ .boff-explore-body h3 {
1909
+ overflow-wrap: anywhere;
1910
+ word-break: break-word;
1911
+ }
1912
+ .boff-explore-body pre {
1913
+ overflow-x: auto;
1914
+ max-width: 100%;
1915
+ }
1916
+ .boff-explore-body table {
1917
+ display: block;
1918
+ overflow-x: auto;
1919
+ max-width: 100%;
1920
+ }
1921
+ .boff-explore-body img {
1922
+ max-width: 100%;
1923
+ height: auto;
1924
+ }
1839
1925
  `;
1840
1926
  function ExploreStyles() {
1841
1927
  return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -1857,7 +1943,6 @@ var CAPABILITIES = [
1857
1943
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
1858
1944
  }
1859
1945
  ];
1860
- var PRODUCT_CHIP_PREVIEW = 12;
1861
1946
  var CTA_VARIANTS = {
1862
1947
  CONTACT: "default",
1863
1948
  WAITLIST: "default",
@@ -2058,7 +2143,7 @@ function AssistantBubble({
2058
2143
  ),
2059
2144
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
2060
2145
  body ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
2061
- /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: body }) }),
2146
+ /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: body }) }),
2062
2147
  streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
2063
2148
  ] }) : null,
2064
2149
  streaming ? /* @__PURE__ */ jsx(ThinkingIndicator, { activity }) : null,
@@ -2120,6 +2205,200 @@ function ErrorBanner({
2120
2205
  }
2121
2206
  );
2122
2207
  }
2208
+ function PromptCarousel({
2209
+ prompts,
2210
+ onPrompt,
2211
+ disabled
2212
+ }) {
2213
+ const [index, setIndex] = useState(0);
2214
+ const [paused, setPaused] = useState(false);
2215
+ const count = prompts.length;
2216
+ const current = prompts[index % count] ?? prompts[0] ?? "";
2217
+ const go = useCallback(
2218
+ (delta) => {
2219
+ setIndex((i) => (i + delta + count) % count);
2220
+ },
2221
+ [count]
2222
+ );
2223
+ useEffect(() => {
2224
+ if (paused || disabled || count < 2) return;
2225
+ if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
2226
+ return;
2227
+ }
2228
+ const timer = setInterval(() => setIndex((i) => (i + 1) % count), 4500);
2229
+ return () => clearInterval(timer);
2230
+ }, [paused, disabled, count]);
2231
+ if (count === 0) return null;
2232
+ return /* @__PURE__ */ jsxs(
2233
+ "div",
2234
+ {
2235
+ className: "flex min-w-0 items-center gap-1.5",
2236
+ onMouseEnter: () => setPaused(true),
2237
+ onMouseLeave: () => setPaused(false),
2238
+ onFocusCapture: () => setPaused(true),
2239
+ onBlurCapture: () => setPaused(false),
2240
+ children: [
2241
+ count > 1 ? /* @__PURE__ */ jsx(
2242
+ "button",
2243
+ {
2244
+ type: "button",
2245
+ "aria-label": "Previous suggestion",
2246
+ onClick: () => go(-1),
2247
+ 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",
2248
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
2249
+ }
2250
+ ) : null,
2251
+ /* @__PURE__ */ jsxs(
2252
+ "button",
2253
+ {
2254
+ "data-boff-explore": "chip",
2255
+ "data-chip-kind": "prompt",
2256
+ type: "button",
2257
+ disabled,
2258
+ onClick: () => onPrompt(current),
2259
+ 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",
2260
+ children: [
2261
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
2262
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: current }),
2263
+ /* @__PURE__ */ jsx(ArrowUp, { className: "ml-auto h-3.5 w-3.5 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2264
+ ]
2265
+ },
2266
+ current
2267
+ ),
2268
+ count > 1 ? /* @__PURE__ */ jsx(
2269
+ "button",
2270
+ {
2271
+ type: "button",
2272
+ "aria-label": "Next suggestion",
2273
+ onClick: () => go(1),
2274
+ 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",
2275
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
2276
+ }
2277
+ ) : null
2278
+ ]
2279
+ }
2280
+ );
2281
+ }
2282
+ function ProductStrip({
2283
+ products,
2284
+ focusProduct,
2285
+ onFocus
2286
+ }) {
2287
+ const [expanded, setExpanded] = useState(false);
2288
+ const chipClass = (active) => cn(
2289
+ "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",
2290
+ active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2291
+ );
2292
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
2293
+ /* @__PURE__ */ jsxs(
2294
+ "div",
2295
+ {
2296
+ className: cn(
2297
+ "boff-explore-strip flex min-w-0 flex-1 gap-2",
2298
+ expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
2299
+ ),
2300
+ children: [
2301
+ /* @__PURE__ */ jsx(
2302
+ "button",
2303
+ {
2304
+ "data-boff-explore": "chip",
2305
+ "data-chip-kind": "product",
2306
+ "data-product": "all",
2307
+ type: "button",
2308
+ "aria-pressed": focusProduct === null,
2309
+ onClick: () => onFocus(null),
2310
+ className: chipClass(focusProduct === null),
2311
+ children: "All products"
2312
+ }
2313
+ ),
2314
+ products.map((product) => /* @__PURE__ */ jsx(
2315
+ "button",
2316
+ {
2317
+ "data-boff-explore": "chip",
2318
+ "data-chip-kind": "product",
2319
+ "data-product": product.slug,
2320
+ type: "button",
2321
+ "aria-pressed": focusProduct === product.slug,
2322
+ title: product.tagline ?? product.name,
2323
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2324
+ className: chipClass(focusProduct === product.slug),
2325
+ children: product.name
2326
+ },
2327
+ product.slug
2328
+ ))
2329
+ ]
2330
+ }
2331
+ ),
2332
+ products.length > 0 ? /* @__PURE__ */ jsx(
2333
+ "button",
2334
+ {
2335
+ type: "button",
2336
+ "data-boff-explore": "products-toggle",
2337
+ onClick: () => setExpanded((v) => !v),
2338
+ 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",
2339
+ children: expanded ? "Show less" : `All ${products.length}`
2340
+ }
2341
+ ) : null
2342
+ ] });
2343
+ }
2344
+ function DisclaimerDialog({
2345
+ open,
2346
+ onClose,
2347
+ captchaOn
2348
+ }) {
2349
+ const closeRef = useRef(null);
2350
+ useEffect(() => {
2351
+ if (!open) return;
2352
+ const onKey = (event) => {
2353
+ if (event.key === "Escape") onClose();
2354
+ };
2355
+ document.addEventListener("keydown", onKey);
2356
+ closeRef.current?.focus();
2357
+ return () => document.removeEventListener("keydown", onKey);
2358
+ }, [open, onClose]);
2359
+ if (!open) return null;
2360
+ return /* @__PURE__ */ jsx(
2361
+ "div",
2362
+ {
2363
+ "data-boff-explore": "disclaimer-dialog",
2364
+ role: "dialog",
2365
+ "aria-modal": "true",
2366
+ "aria-label": "How this assistant works",
2367
+ className: "fixed inset-0 z-50 flex items-end justify-center bg-foreground/40 p-4 backdrop-blur-sm sm:items-center",
2368
+ onClick: onClose,
2369
+ children: /* @__PURE__ */ jsxs(
2370
+ "div",
2371
+ {
2372
+ className: "w-full max-w-md rounded-2xl border border-border bg-card p-5 shadow-xl",
2373
+ onClick: (event) => event.stopPropagation(),
2374
+ children: [
2375
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2376
+ /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4" }) }),
2377
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2378
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-card-foreground", children: "How this assistant works" }),
2379
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2 text-xs leading-relaxed text-muted-foreground", children: [
2380
+ /* @__PURE__ */ jsx("p", { children: "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources." }),
2381
+ /* @__PURE__ */ 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." }),
2382
+ captchaOn ? /* @__PURE__ */ jsx("p", { children: "Protected by reCAPTCHA \u2014 the Google Privacy Policy and Terms of Service apply." }) : null
2383
+ ] })
2384
+ ] })
2385
+ ] }),
2386
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsx(
2387
+ Button,
2388
+ {
2389
+ ref: closeRef,
2390
+ size: "sm",
2391
+ variant: "secondary",
2392
+ onClick: onClose,
2393
+ children: "Got it"
2394
+ }
2395
+ ) })
2396
+ ]
2397
+ }
2398
+ )
2399
+ }
2400
+ );
2401
+ }
2123
2402
  function WelcomeState({
2124
2403
  title,
2125
2404
  body,
@@ -2130,21 +2409,19 @@ function WelcomeState({
2130
2409
  onFocus,
2131
2410
  disabled
2132
2411
  }) {
2133
- const [showAllProducts, setShowAllProducts] = useState(false);
2134
- const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
2135
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
2412
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-8 sm:py-12", children: [
2136
2413
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2137
2414
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-3 py-1 text-xs font-medium text-muted-foreground", children: [
2138
2415
  /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
2139
2416
  "AI answers, grounded in our documentation"
2140
2417
  ] }),
2141
- /* @__PURE__ */ jsx("h1", { className: "mt-5 text-balance text-3xl font-bold tracking-tight sm:text-4xl", children: title }),
2142
- /* @__PURE__ */ jsx("p", { className: "mx-auto mt-3 max-w-2xl text-pretty text-base leading-relaxed text-muted-foreground sm:text-lg", children: body })
2418
+ /* @__PURE__ */ jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
2419
+ /* @__PURE__ */ 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 })
2143
2420
  ] }),
2144
- /* @__PURE__ */ jsx("ul", { className: "mt-9 grid gap-3 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxs(
2421
+ /* @__PURE__ */ jsx("ul", { className: "mt-7 grid gap-2.5 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxs(
2145
2422
  "li",
2146
2423
  {
2147
- className: "rounded-xl border border-border bg-card p-4 text-left",
2424
+ className: "rounded-xl border border-border/70 bg-card/60 p-3.5 text-left transition-colors hover:border-border",
2148
2425
  children: [
2149
2426
  /* @__PURE__ */ jsx("span", { className: "mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
2150
2427
  /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
@@ -2153,72 +2430,27 @@ function WelcomeState({
2153
2430
  },
2154
2431
  capTitle
2155
2432
  )) }),
2156
- prompts.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-9", children: [
2157
- /* @__PURE__ */ jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2158
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsxs(
2159
- "button",
2433
+ prompts.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-8", children: [
2434
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2435
+ /* @__PURE__ */ jsx(
2436
+ PromptCarousel,
2160
2437
  {
2161
- "data-boff-explore": "chip",
2162
- "data-chip-kind": "prompt",
2163
- type: "button",
2164
- disabled,
2165
- onClick: () => onPrompt(prompt),
2166
- 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",
2167
- children: [
2168
- /* @__PURE__ */ jsx("span", { className: "truncate", children: prompt }),
2169
- /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2170
- ]
2171
- },
2172
- prompt
2173
- )) })
2438
+ prompts,
2439
+ onPrompt,
2440
+ disabled
2441
+ }
2442
+ )
2174
2443
  ] }) : null,
2175
- products.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-8", children: [
2176
- /* @__PURE__ */ jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2177
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2178
- /* @__PURE__ */ jsx(
2179
- "button",
2180
- {
2181
- "data-boff-explore": "chip",
2182
- "data-chip-kind": "product",
2183
- "data-product": "all",
2184
- type: "button",
2185
- "aria-pressed": focusProduct === null,
2186
- onClick: () => onFocus(null),
2187
- className: cn(
2188
- "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",
2189
- focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2190
- ),
2191
- children: "All products"
2192
- }
2193
- ),
2194
- visibleProducts.map((product) => /* @__PURE__ */ jsx(
2195
- "button",
2196
- {
2197
- "data-boff-explore": "chip",
2198
- "data-chip-kind": "product",
2199
- "data-product": product.slug,
2200
- type: "button",
2201
- "aria-pressed": focusProduct === product.slug,
2202
- title: product.tagline ?? product.name,
2203
- onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2204
- className: cn(
2205
- "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",
2206
- focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2207
- ),
2208
- children: product.name
2209
- },
2210
- product.slug
2211
- )),
2212
- products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsx(
2213
- "button",
2214
- {
2215
- type: "button",
2216
- onClick: () => setShowAllProducts((v) => !v),
2217
- 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",
2218
- children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
2219
- }
2220
- ) : null
2221
- ] })
2444
+ products.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
2445
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2446
+ /* @__PURE__ */ jsx(
2447
+ ProductStrip,
2448
+ {
2449
+ products,
2450
+ focusProduct,
2451
+ onFocus
2452
+ }
2453
+ )
2222
2454
  ] }) : null
2223
2455
  ] });
2224
2456
  }
@@ -2274,6 +2506,7 @@ function ExplorePage({
2274
2506
  }
2275
2507
  });
2276
2508
  speechStopRef.current = speech.stop;
2509
+ const [disclaimerOpen, setDisclaimerOpen] = useState(false);
2277
2510
  const scrollRef = useRef(null);
2278
2511
  const latestAssistantRef = useRef(null);
2279
2512
  const alignedForRef = useRef(null);
@@ -2366,7 +2599,7 @@ function ExplorePage({
2366
2599
  "data-boff-explore": "page",
2367
2600
  "data-phase": phase,
2368
2601
  className: cn(
2369
- "flex w-full flex-col bg-background text-foreground",
2602
+ "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
2370
2603
  heightMode === "auto" && "min-h-[70vh]",
2371
2604
  className
2372
2605
  ),
@@ -2508,7 +2741,7 @@ function ExplorePage({
2508
2741
  role: "log",
2509
2742
  "aria-live": "polite",
2510
2743
  "aria-label": "Explore conversation",
2511
- className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
2744
+ className: "boff-explore-scroll min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden",
2512
2745
  children: showWelcome ? /* @__PURE__ */ jsx(
2513
2746
  WelcomeState,
2514
2747
  {
@@ -2521,7 +2754,7 @@ function ExplorePage({
2521
2754
  onFocus: setFocusProduct,
2522
2755
  disabled: !canSend || busy
2523
2756
  }
2524
- ) : /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
2757
+ ) : /* @__PURE__ */ jsx("div", { className: "mx-auto w-full min-w-0 max-w-3xl space-y-6 px-4 py-6", children: messages.map(
2525
2758
  (message) => message.role === "USER" ? /* @__PURE__ */ jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsx(
2526
2759
  AssistantBubble,
2527
2760
  {
@@ -2535,7 +2768,15 @@ function ExplorePage({
2535
2768
  ) })
2536
2769
  }
2537
2770
  ),
2538
- /* @__PURE__ */ jsx("div", { className: "border-t border-border bg-background px-4 pb-4 pt-3", children: /* @__PURE__ */ jsxs(
2771
+ /* @__PURE__ */ jsx(
2772
+ DisclaimerDialog,
2773
+ {
2774
+ open: disclaimerOpen,
2775
+ onClose: () => setDisclaimerOpen(false),
2776
+ captchaOn
2777
+ }
2778
+ ),
2779
+ /* @__PURE__ */ 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__ */ jsxs(
2539
2780
  "form",
2540
2781
  {
2541
2782
  "data-boff-explore": "composer",
@@ -2550,7 +2791,7 @@ function ExplorePage({
2550
2791
  "div",
2551
2792
  {
2552
2793
  className: cn(
2553
- "flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-colors",
2794
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
2554
2795
  overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
2555
2796
  ),
2556
2797
  children: [
@@ -2571,7 +2812,7 @@ function ExplorePage({
2571
2812
  },
2572
2813
  "aria-label": "Ask a question",
2573
2814
  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",
2574
- 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"
2815
+ 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"
2575
2816
  }
2576
2817
  ),
2577
2818
  speech.supported ? /* @__PURE__ */ jsx(
@@ -2662,48 +2903,19 @@ function ExplorePage({
2662
2903
  }
2663
2904
  )
2664
2905
  ] }),
2665
- /* @__PURE__ */ jsxs(
2666
- "p",
2667
- {
2668
- "data-boff-explore": "disclaimer",
2669
- className: "mt-2 text-xs leading-relaxed text-muted-foreground",
2670
- children: [
2671
- "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
2672
- " ",
2673
- "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.",
2674
- captchaOn ? /* @__PURE__ */ jsxs(Fragment, { children: [
2675
- " ",
2676
- "This site is protected by reCAPTCHA; the Google",
2677
- " ",
2678
- /* @__PURE__ */ jsx(
2679
- "a",
2680
- {
2681
- href: "https://policies.google.com/privacy",
2682
- target: "_blank",
2683
- rel: "noopener noreferrer",
2684
- className: "underline underline-offset-2 hover:text-foreground",
2685
- children: "Privacy Policy"
2686
- }
2687
- ),
2688
- " ",
2689
- "and",
2690
- " ",
2691
- /* @__PURE__ */ jsx(
2692
- "a",
2693
- {
2694
- href: "https://policies.google.com/terms",
2695
- target: "_blank",
2696
- rel: "noopener noreferrer",
2697
- className: "underline underline-offset-2 hover:text-foreground",
2698
- children: "Terms of Service"
2699
- }
2700
- ),
2701
- " ",
2702
- "apply."
2703
- ] }) : null
2704
- ]
2705
- }
2706
- )
2906
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
2907
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: "Grounded in our docs \u2014 check anything important." }),
2908
+ /* @__PURE__ */ jsx(
2909
+ "button",
2910
+ {
2911
+ type: "button",
2912
+ "data-boff-explore": "disclaimer",
2913
+ onClick: () => setDisclaimerOpen(true),
2914
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2915
+ children: "Disclaimer"
2916
+ }
2917
+ )
2918
+ ] })
2707
2919
  ]
2708
2920
  }
2709
2921
  ) })