@burdenoff/website-sdk 2026.830.1 → 2026.922.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.
Files changed (60) hide show
  1. package/README.md +114 -3
  2. package/dist/components/contact.js +4 -1
  3. package/dist/components/contact.js.map +1 -1
  4. package/dist/components/contact.mjs +4 -1
  5. package/dist/components/contact.mjs.map +1 -1
  6. package/dist/components/explore.d.mts +24 -3
  7. package/dist/components/explore.d.ts +24 -3
  8. package/dist/components/explore.js +690 -236
  9. package/dist/components/explore.js.map +1 -1
  10. package/dist/components/explore.mjs +690 -238
  11. package/dist/components/explore.mjs.map +1 -1
  12. package/dist/components/newsletter.js +4 -1
  13. package/dist/components/newsletter.js.map +1 -1
  14. package/dist/components/newsletter.mjs +4 -1
  15. package/dist/components/newsletter.mjs.map +1 -1
  16. package/dist/components/partners.js +23 -13
  17. package/dist/components/partners.js.map +1 -1
  18. package/dist/components/partners.mjs +23 -13
  19. package/dist/components/partners.mjs.map +1 -1
  20. package/dist/components/pricing.js +4 -1
  21. package/dist/components/pricing.js.map +1 -1
  22. package/dist/components/pricing.mjs +4 -1
  23. package/dist/components/pricing.mjs.map +1 -1
  24. package/dist/components/unsubscribe.js +4 -1
  25. package/dist/components/unsubscribe.js.map +1 -1
  26. package/dist/components/unsubscribe.mjs +4 -1
  27. package/dist/components/unsubscribe.mjs.map +1 -1
  28. package/dist/components/waitlist.js +4 -1
  29. package/dist/components/waitlist.js.map +1 -1
  30. package/dist/components/waitlist.mjs +4 -1
  31. package/dist/components/waitlist.mjs.map +1 -1
  32. package/dist/content/index.js +23 -13
  33. package/dist/content/index.js.map +1 -1
  34. package/dist/content/index.mjs +23 -13
  35. package/dist/content/index.mjs.map +1 -1
  36. package/dist/{use-explore-chat-B8xKGid5.d.mts → explore-types-CLw19R76.d.mts} +1 -143
  37. package/dist/{use-explore-chat-B8xKGid5.d.ts → explore-types-CLw19R76.d.ts} +1 -143
  38. package/dist/hooks/index.d.mts +2 -1
  39. package/dist/hooks/index.d.ts +2 -1
  40. package/dist/hooks/index.js +4 -1
  41. package/dist/hooks/index.js.map +1 -1
  42. package/dist/hooks/index.mjs +4 -1
  43. package/dist/hooks/index.mjs.map +1 -1
  44. package/dist/index.d.mts +141 -2
  45. package/dist/index.d.ts +141 -2
  46. package/dist/index.js +2025 -400
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +2021 -403
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/seo/index.js +4 -1
  51. package/dist/seo/index.js.map +1 -1
  52. package/dist/seo/index.mjs +4 -1
  53. package/dist/seo/index.mjs.map +1 -1
  54. package/dist/store/index.js +23 -13
  55. package/dist/store/index.js.map +1 -1
  56. package/dist/store/index.mjs +23 -13
  57. package/dist/store/index.mjs.map +1 -1
  58. package/dist/use-explore-chat-CvW-kyIm.d.mts +145 -0
  59. package/dist/use-explore-chat-DdGBimiz.d.ts +145 -0
  60. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { Sparkles, RotateCcw, Link2, History, Trash2, Paperclip, X, Mic, Square,
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 } from 'react/jsx-runtime';
7
+ import { jsx, jsxs, Fragment } 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';
@@ -58,6 +58,8 @@ var EN = {
58
58
  askHuman: "ask a human",
59
59
  prevSuggestion: "Previous suggestion",
60
60
  nextSuggestion: "Next suggestion",
61
+ conceptIllustration: "Concept illustration",
62
+ opensInNewTab: "opens in a new tab",
61
63
  capabilities: [
62
64
  "Grounded in our own docs",
63
65
  "Compare across the ecosystem",
@@ -107,6 +109,8 @@ var ZH = {
107
109
  askHuman: "\u8054\u7CFB\u6211\u4EEC",
108
110
  prevSuggestion: "\u4E0A\u4E00\u4E2A\u5EFA\u8BAE",
109
111
  nextSuggestion: "\u4E0B\u4E00\u4E2A\u5EFA\u8BAE",
112
+ conceptIllustration: "\u6982\u5FF5\u793A\u610F\u56FE",
113
+ opensInNewTab: "\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00",
110
114
  capabilities: ["\u57FA\u4E8E\u6211\u4EEC\u81EA\u5DF1\u7684\u6587\u6863", "\u8DE8\u4EA7\u54C1\u5BF9\u6BD4", "\u76F4\u8FBE\u6B63\u786E\u7684\u9875\u9762"],
111
115
  prompts: [
112
116
  "\u8FD9\u4E2A\u4EA7\u54C1\u5230\u5E95\u80FD\u505A\u4EC0\u4E48\uFF1F",
@@ -152,6 +156,8 @@ var HI = {
152
156
  askHuman: "\u0939\u092E\u0938\u0947 \u0938\u0902\u092A\u0930\u094D\u0915 \u0915\u0930\u0947\u0902",
153
157
  prevSuggestion: "\u092A\u093F\u091B\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
154
158
  nextSuggestion: "\u0905\u0917\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
159
+ conceptIllustration: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E \u091A\u093F\u0924\u094D\u0930\u0923",
160
+ opensInNewTab: "\u0928\u090F \u091F\u0948\u092C \u092E\u0947\u0902 \u0916\u0941\u0932\u0924\u093E \u0939\u0948",
155
161
  capabilities: [
156
162
  "\u0939\u092E\u093E\u0930\u0947 \u0905\u092A\u0928\u0947 \u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C\u094B\u0902 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924",
157
163
  "\u0938\u092D\u0940 \u0909\u0924\u094D\u092A\u093E\u0926\u094B\u0902 \u0915\u0940 \u0924\u0941\u0932\u0928\u093E \u0915\u0930\u0947\u0902",
@@ -201,6 +207,8 @@ var ES = {
201
207
  askHuman: "habla con una persona",
202
208
  prevSuggestion: "Sugerencia anterior",
203
209
  nextSuggestion: "Sugerencia siguiente",
210
+ conceptIllustration: "Ilustraci\xF3n del concepto",
211
+ opensInNewTab: "se abre en una pesta\xF1a nueva",
204
212
  capabilities: [
205
213
  "Basado en nuestra documentaci\xF3n",
206
214
  "Compara todo el ecosistema",
@@ -250,6 +258,8 @@ var AR = {
250
258
  askHuman: "\u062A\u062D\u062F\u0651\u062B \u0625\u0644\u0649 \u0634\u062E\u0635",
251
259
  prevSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u0633\u0627\u0628\u0642",
252
260
  nextSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u062A\u0627\u0644\u064A",
261
+ conceptIllustration: "\u0631\u0633\u0645 \u062A\u0648\u0636\u064A\u062D\u064A \u0644\u0644\u0645\u0641\u0647\u0648\u0645",
262
+ opensInNewTab: "\u064A\u064F\u0641\u062A\u062D \u0641\u064A \u0639\u0644\u0627\u0645\u0629 \u062A\u0628\u0648\u064A\u0628 \u062C\u062F\u064A\u062F\u0629",
253
263
  capabilities: [
254
264
  "\u0645\u0628\u0646\u064A \u0639\u0644\u0649 \u0648\u062B\u0627\u0626\u0642\u0646\u0627",
255
265
  "\u0642\u0627\u0631\u0646 \u0639\u0628\u0631 \u0627\u0644\u0645\u0646\u0638\u0648\u0645\u0629",
@@ -299,6 +309,8 @@ var TA = {
299
309
  askHuman: "\u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BC8\u0BA4\u0BCD \u0BA4\u0BCA\u0B9F\u0BB0\u0BCD\u0BAA\u0BC1 \u0B95\u0BCA\u0BB3\u0BCD\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
300
310
  prevSuggestion: "\u0BAE\u0BC1\u0BA8\u0BCD\u0BA4\u0BC8\u0BAF \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
301
311
  nextSuggestion: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4 \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
312
+ conceptIllustration: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BB5\u0BBF\u0BB3\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BAE\u0BCD",
313
+ opensInNewTab: "\u0BAA\u0BC1\u0BA4\u0BBF\u0BAF \u0BA4\u0BBE\u0BB5\u0BB2\u0BBF\u0BB2\u0BCD \u0BA4\u0BBF\u0BB1\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD",
302
314
  capabilities: [
303
315
  "\u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B9A\u0BCA\u0BA8\u0BCD\u0BA4 \u0B86\u0BB5\u0BA3\u0B99\u0BCD\u0B95\u0BB3\u0BBF\u0BA9\u0BCD \u0B85\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD",
304
316
  "\u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BC8 \u0B92\u0BAA\u0BCD\u0BAA\u0BBF\u0B9F\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
@@ -339,19 +351,26 @@ function exploreStrings(locale) {
339
351
  function fill(template, values) {
340
352
  return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
341
353
  }
354
+ function MarkdownLink({
355
+ href,
356
+ children,
357
+ // react-markdown passes the hast node to every override; it is not a DOM attribute.
358
+ node: _node,
359
+ ...props
360
+ }) {
361
+ const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
362
+ return /* @__PURE__ */ jsx(
363
+ "a",
364
+ {
365
+ href,
366
+ ...props,
367
+ ...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
368
+ children
369
+ }
370
+ );
371
+ }
342
372
  var defaultComponents = {
343
- a: ({ href, children, ...props }) => {
344
- const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
345
- return /* @__PURE__ */ jsx(
346
- "a",
347
- {
348
- href,
349
- ...props,
350
- ...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
351
- children
352
- }
353
- );
354
- }
373
+ a: MarkdownLink
355
374
  };
356
375
  function Markdown({
357
376
  children,
@@ -912,10 +931,13 @@ var ECOSYSTEM_PRODUCTS = [
912
931
  name: "VibeControls",
913
932
  websiteUrl: "https://vibecontrols.com"
914
933
  },
934
+ // FluidGrids ships on www.fluidgrids.ai — fluidgrids.com only redirects there. The explore
935
+ // catalog and the site's own sitemap use this origin, and so must the fallback catalog
936
+ // built from this list, or /explore rejects FluidGrids concept images until it loads.
915
937
  {
916
938
  slug: "fluidgrids",
917
939
  name: "FluidGrids",
918
- websiteUrl: "https://fluidgrids.com"
940
+ websiteUrl: "https://www.fluidgrids.ai"
919
941
  },
920
942
  // Botlit ships on botlit.ai — botlit.com does not resolve.
921
943
  { slug: "botlit", name: "Botlit", websiteUrl: "https://www.botlit.ai" },
@@ -2219,6 +2241,9 @@ function resolveImageUrl(image, base) {
2219
2241
  return image;
2220
2242
  }
2221
2243
  }
2244
+ function serializeJsonLd(data) {
2245
+ return JSON.stringify(data).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
2246
+ }
2222
2247
  function PageHead({
2223
2248
  title,
2224
2249
  description,
@@ -2260,7 +2285,7 @@ function PageHead({
2260
2285
  fullImageUrl && /* @__PURE__ */ jsx("meta", { name: "twitter:image", content: fullImageUrl }),
2261
2286
  twitterHandle && /* @__PURE__ */ jsx("meta", { name: "twitter:creator", content: twitterHandle }),
2262
2287
  /* @__PURE__ */ jsx("meta", { name: "theme-color", content: themeColor }),
2263
- structuredDataItems.map((data, index) => /* @__PURE__ */ jsx("script", { type: "application/ld+json", children: JSON.stringify(data) }, `ld-${index}`)),
2288
+ structuredDataItems.map((data, index) => /* @__PURE__ */ jsx("script", { type: "application/ld+json", children: serializeJsonLd(data) }, `ld-${index}`)),
2264
2289
  additionalMeta?.map((meta, index) => /* @__PURE__ */ jsx(
2265
2290
  "meta",
2266
2291
  {
@@ -2467,6 +2492,7 @@ function toPayload(staged) {
2467
2492
  textContent
2468
2493
  }));
2469
2494
  }
2495
+ var SHORT_VIEWPORT_PX = 420;
2470
2496
  var EXPLORE_CSS = `
2471
2497
  @keyframes boff-explore-pulse {
2472
2498
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
@@ -2501,6 +2527,12 @@ var EXPLORE_CSS = `
2501
2527
  .boff-explore-scroll {
2502
2528
  scrollbar-width: thin;
2503
2529
  scrollbar-color: hsl(var(--border)) transparent;
2530
+ /* Stop the transcript handing its scroll to the page. Without this, the first
2531
+ swipe that exhausts the transcript chains to the document \u2014 and on a host site
2532
+ that is 65-70% footer, which is how a phone ends up showing nothing but the
2533
+ footer. Engines that do not support this property ignore the line and keep
2534
+ today's behaviour; the scroll correction above covers them. */
2535
+ overscroll-behavior: contain;
2504
2536
  }
2505
2537
  .boff-explore-scroll::-webkit-scrollbar { width: 10px; }
2506
2538
  .boff-explore-scroll::-webkit-scrollbar-track { background: transparent; }
@@ -2618,6 +2650,68 @@ var EXPLORE_CSS = `
2618
2650
  .boff-explore-busy::after { animation: none; }
2619
2651
  .boff-explore-rise { animation: none; opacity: 1; transform: none; }
2620
2652
  }
2653
+
2654
+ /* ---- Pane height ----------------------------------------------------------------------
2655
+ This used to be one inline declaration, \`height: calc(100dvh - 64px)\`, and it carried two
2656
+ defects. An inline style cannot carry a fallback, so every engine without \`dvh\` (iOS
2657
+ Safari < 15.4, Chrome/WebView < 108, Samsung Internet < 21, Firefox < 101) dropped it
2658
+ whole; and the 64px came from a prop hard-coded to 64 on all 41 host pages that matches
2659
+ none of them \u2014 the burdenoff/algoshred chrome is 80px, the sticky-header sites are 65.
2660
+
2661
+ A class plus custom properties lets the cascade do the work: \`vh\` is the floor and \`dvh\`
2662
+ an upgrade where it exists, while the two measured values stay *lengths*, never a height.
2663
+ That distinction is the whole prerender contract. The host sites snapshot \`/explore\` by
2664
+ loading the built SPA in headless Chromium and writing the POST-HYDRATION DOM to
2665
+ dist/explore/index.html, so every attribute and inline value this component sets after
2666
+ mount IS the static HTML CloudFront serves. A measured pixel *height* would therefore
2667
+ hand every phone the prerender viewport's height until the bundle executes; a measured
2668
+ *offset* is a site-header height, which is the same on every device to within a few
2669
+ pixels, and the height built from it stays viewport-relative on the visitor's own screen.
2670
+
2671
+ --boff-explore-offset chrome above the pane (measured root top; the prop pre-measure)
2672
+ --boff-explore-inset the slice of the layout viewport hidden below the visible area \u2014
2673
+ the on-screen keyboard, which \`dvh\` does not track. Zero unless
2674
+ something is actually covering the bottom, so a snapshot taken
2675
+ with no keyboard open never carries it. */
2676
+ .boff-explore-viewport {
2677
+ height: calc(100vh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
2678
+ }
2679
+ @supports (height: 100dvh) {
2680
+ .boff-explore-viewport {
2681
+ height: calc(100dvh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
2682
+ }
2683
+ }
2684
+
2685
+ /* Landscape phones and other short viewports: a one-screen pane leaves a ~124px transcript
2686
+ sliver under ~610px of chrome and content. At or below the threshold the pane stops
2687
+ pretending to be a window and lets the page's own scrolling carry the transcript. The
2688
+ min-height is the available height rather than a fixed rem value, so the fallback can
2689
+ never be taller than the screen it is rescuing. */
2690
+ @media (max-height: ${SHORT_VIEWPORT_PX}px) {
2691
+ .boff-explore-viewport {
2692
+ height: auto;
2693
+ min-height: calc(100vh - var(--boff-explore-offset, 0px));
2694
+ }
2695
+ }
2696
+ @supports (height: 100dvh) {
2697
+ @media (max-height: ${SHORT_VIEWPORT_PX}px) {
2698
+ .boff-explore-viewport {
2699
+ min-height: calc(100dvh - var(--boff-explore-offset, 0px));
2700
+ }
2701
+ }
2702
+ }
2703
+
2704
+ /* ---- Composer dock --------------------------------------------------------------------
2705
+ Three declarations, each the fallback for the one after it: a plain 1rem where \`max()\`
2706
+ is unknown, then the measured overlap of any FOREIGN fixed chrome sitting over the
2707
+ composer (the host sites' cookie banner is 229px tall and lands exactly there), then the
2708
+ same plus the home-indicator inset. \`--boff-explore-chrome\` is set on the root only once
2709
+ an overlap has actually been measured, so everywhere else this collapses to 1rem. */
2710
+ .boff-explore-composer {
2711
+ padding-bottom: 1rem;
2712
+ padding-bottom: max(1rem, var(--boff-explore-chrome, 0px));
2713
+ padding-bottom: max(1rem, env(safe-area-inset-bottom), var(--boff-explore-chrome, 0px));
2714
+ }
2621
2715
  `;
2622
2716
  function ExploreStyles() {
2623
2717
  return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -2639,6 +2733,8 @@ var CAPABILITIES = [
2639
2733
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
2640
2734
  }
2641
2735
  ];
2736
+ var MIN_VISIBLE_FRACTION = 0.25;
2737
+ var SDK_OVERLAY_SELECTOR = "[data-boff-explore]";
2642
2738
  var CTA_VARIANTS = {
2643
2739
  CONTACT: "default",
2644
2740
  WAITLIST: "default",
@@ -2654,6 +2750,28 @@ function normalizeOffset(offset) {
2654
2750
  if (offset === void 0) return "0px";
2655
2751
  return typeof offset === "number" ? `${offset}px` : offset;
2656
2752
  }
2753
+ function isOwnElement(el, root) {
2754
+ if (el === document.body || el === document.documentElement) return true;
2755
+ if (root.contains(el)) return true;
2756
+ return el.closest(SDK_OVERLAY_SELECTOR) !== null;
2757
+ }
2758
+ function visualViewportHeight() {
2759
+ return window.visualViewport?.height ?? window.innerHeight;
2760
+ }
2761
+ function clamp(value, min, max) {
2762
+ return Math.min(Math.max(value, min), max);
2763
+ }
2764
+ function isRestoredNavigation() {
2765
+ const entries = window.performance?.getEntriesByType?.("navigation");
2766
+ const entry = entries?.[0];
2767
+ if (!entry) return true;
2768
+ const navigation = entry;
2769
+ return navigation.type === "reload" || navigation.type === "back_forward";
2770
+ }
2771
+ function pageOwnsScrolling(el) {
2772
+ if (window.innerHeight > SHORT_VIEWPORT_PX) return false;
2773
+ return el.scrollHeight - el.clientHeight <= 1;
2774
+ }
2657
2775
  function isInternalPath(url) {
2658
2776
  return url.startsWith("/") && !url.startsWith("//");
2659
2777
  }
@@ -2677,6 +2795,93 @@ function logoUrlOf(url) {
2677
2795
  return null;
2678
2796
  }
2679
2797
  }
2798
+ function inlineImageOrigins(products) {
2799
+ const origins = /* @__PURE__ */ new Set();
2800
+ for (const product of products) {
2801
+ let url;
2802
+ try {
2803
+ url = new URL(product.website);
2804
+ } catch {
2805
+ continue;
2806
+ }
2807
+ if (url.protocol !== "https:") continue;
2808
+ const apex = url.hostname.replace(/^www\./, "");
2809
+ const port = url.port ? `:${url.port}` : "";
2810
+ origins.add(`https://${apex}${port}`);
2811
+ origins.add(`https://www.${apex}${port}`);
2812
+ }
2813
+ return origins;
2814
+ }
2815
+ function isInlineImageAllowed(src, origins) {
2816
+ if (typeof src !== "string" || src === "") return false;
2817
+ let url;
2818
+ try {
2819
+ url = new URL(src);
2820
+ } catch {
2821
+ return false;
2822
+ }
2823
+ if (url.protocol !== "https:" || url.username || url.password) return false;
2824
+ return origins.has(url.origin);
2825
+ }
2826
+ var InlineImageScopeContext = createContext(null);
2827
+ var InsideLinkContext = createContext(false);
2828
+ function AnswerLink(props) {
2829
+ return /* @__PURE__ */ jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsx(MarkdownLink, { ...props }) });
2830
+ }
2831
+ var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
2832
+ function InlineConceptImage({ src, alt }) {
2833
+ const scope = useContext(InlineImageScopeContext);
2834
+ const insideLink = useContext(InsideLinkContext);
2835
+ if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
2836
+ return null;
2837
+ }
2838
+ const { strings: T, onFailed } = scope;
2839
+ const picture = /* @__PURE__ */ jsxs(Fragment, { children: [
2840
+ /* @__PURE__ */ jsx(
2841
+ "img",
2842
+ {
2843
+ src,
2844
+ alt: alt ?? "",
2845
+ loading: "lazy",
2846
+ decoding: "async",
2847
+ onError: () => onFailed(src),
2848
+ className: "block h-auto max-h-80 w-auto max-w-full object-contain"
2849
+ }
2850
+ ),
2851
+ /* @__PURE__ */ jsx(
2852
+ "span",
2853
+ {
2854
+ "aria-hidden": "true",
2855
+ className: "pointer-events-none absolute left-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
2856
+ children: T.conceptIllustration
2857
+ }
2858
+ )
2859
+ ] });
2860
+ return /* @__PURE__ */ jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxs(
2861
+ "a",
2862
+ {
2863
+ href: src,
2864
+ target: "_blank",
2865
+ rel: "noopener noreferrer",
2866
+ className: cn(
2867
+ INLINE_IMAGE_FRAME,
2868
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2869
+ ),
2870
+ children: [
2871
+ picture,
2872
+ /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
2873
+ "(",
2874
+ T.opensInNewTab,
2875
+ ")"
2876
+ ] })
2877
+ ]
2878
+ }
2879
+ ) });
2880
+ }
2881
+ var ANSWER_MARKDOWN_COMPONENTS = {
2882
+ a: AnswerLink,
2883
+ img: InlineConceptImage
2884
+ };
2680
2885
  function ReferenceCard({
2681
2886
  reference,
2682
2887
  onFollow
@@ -2859,6 +3064,7 @@ function AssistantBubble({
2859
3064
  strings: T,
2860
3065
  message,
2861
3066
  activity,
3067
+ imageOrigins,
2862
3068
  onNavigate,
2863
3069
  anchorRef,
2864
3070
  onFollow,
@@ -2867,6 +3073,25 @@ function AssistantBubble({
2867
3073
  const streaming = message.status === "PENDING" || message.status === "RUNNING";
2868
3074
  const body = message.content || message.partialContent || "";
2869
3075
  const failed = message.status === "FAILED";
3076
+ const completed = message.status === "COMPLETED";
3077
+ const [failedImages, setFailedImages] = useState(
3078
+ () => /* @__PURE__ */ new Set()
3079
+ );
3080
+ const markImageFailed = useCallback((src) => {
3081
+ setFailedImages(
3082
+ (current) => current.has(src) ? current : new Set(current).add(src)
3083
+ );
3084
+ }, []);
3085
+ const imageScope = useMemo(
3086
+ () => ({
3087
+ strings: T,
3088
+ enabled: completed,
3089
+ origins: imageOrigins,
3090
+ failed: failedImages,
3091
+ onFailed: markImageFailed
3092
+ }),
3093
+ [T, completed, imageOrigins, failedImages, markImageFailed]
3094
+ );
2870
3095
  return /* @__PURE__ */ jsxs(
2871
3096
  "div",
2872
3097
  {
@@ -2913,7 +3138,7 @@ function AssistantBubble({
2913
3138
  }
2914
3139
  ) : null,
2915
3140
  body ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
2916
- /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: body }) }),
3141
+ /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(InlineImageScopeContext.Provider, { value: imageScope, children: /* @__PURE__ */ jsx(Markdown, { components: ANSWER_MARKDOWN_COMPONENTS, children: body }) }) }),
2917
3142
  streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
2918
3143
  ] }) : null,
2919
3144
  streaming ? /* @__PURE__ */ jsx(ThinkingIndicator, { activity }) : null,
@@ -3395,6 +3620,8 @@ function ExplorePage({
3395
3620
  const pickQueueRef = useRef(Promise.resolve());
3396
3621
  const [shareLabel, setShareLabel] = useState(T.share);
3397
3622
  const [shareUrl, setShareUrl] = useState(null);
3623
+ const rootRef = useRef(null);
3624
+ const composerRef = useRef(null);
3398
3625
  const scrollRef = useRef(null);
3399
3626
  const latestAssistantRef = useRef(null);
3400
3627
  const alignedForRef = useRef(null);
@@ -3412,17 +3639,206 @@ function ExplorePage({
3412
3639
  () => catalog.products.find((p) => p.slug === focusProduct)?.name ?? null,
3413
3640
  [catalog.products, focusProduct]
3414
3641
  );
3642
+ const imageOrigins = useMemo(
3643
+ () => inlineImageOrigins(catalog.products),
3644
+ [catalog.products]
3645
+ );
3415
3646
  useEffect(() => {
3416
3647
  const el = textareaRef.current;
3417
3648
  if (!el) return;
3418
3649
  el.style.height = "auto";
3419
3650
  el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
3420
3651
  }, [draft]);
3652
+ const [paneOffset, setPaneOffset] = useState(null);
3653
+ const [bottomInset, setBottomInset] = useState(0);
3654
+ const paneOffsetRef = useRef(0);
3655
+ const measurePane = useCallback(() => {
3656
+ if (heightMode !== "viewport") return;
3657
+ const el = rootRef.current;
3658
+ if (!el) return;
3659
+ const layoutHeight = window.innerHeight;
3660
+ if (layoutHeight === 0) return;
3661
+ const top = Math.round(
3662
+ clamp(el.getBoundingClientRect().top, 0, layoutHeight)
3663
+ );
3664
+ const viewport = window.visualViewport;
3665
+ const inset = viewport ? Math.round(
3666
+ clamp(
3667
+ layoutHeight - viewport.height - viewport.offsetTop,
3668
+ 0,
3669
+ layoutHeight
3670
+ )
3671
+ ) : 0;
3672
+ paneOffsetRef.current = top;
3673
+ setPaneOffset((current) => current === top ? current : top);
3674
+ setBottomInset((current) => current === inset ? current : inset);
3675
+ }, [heightMode]);
3676
+ const [chromeOverlap, setChromeOverlap] = useState(0);
3677
+ const measureChrome = useCallback(() => {
3678
+ const root = rootRef.current;
3679
+ const dock = composerRef.current;
3680
+ if (!root || !dock) return;
3681
+ if (typeof document.elementFromPoint !== "function") return;
3682
+ const viewportHeight = visualViewportHeight();
3683
+ const rect = dock.getBoundingClientRect();
3684
+ if (rect.width === 0 || viewportHeight === 0) return;
3685
+ if (rect.bottom <= 0 || rect.top >= viewportHeight) return;
3686
+ const edge = Math.min(rect.bottom, viewportHeight);
3687
+ const hit = document.elementFromPoint(rect.left + rect.width / 2, edge - 1);
3688
+ if (!hit || isOwnElement(hit, root)) {
3689
+ setChromeOverlap(0);
3690
+ return;
3691
+ }
3692
+ const overlap = Math.round(edge - hit.getBoundingClientRect().top);
3693
+ setChromeOverlap(overlap > 0 && overlap < viewportHeight ? overlap : 0);
3694
+ }, []);
3695
+ const remeasure = useCallback(() => {
3696
+ measurePane();
3697
+ measureChrome();
3698
+ }, [measurePane, measureChrome]);
3699
+ useEffect(() => {
3700
+ if (typeof window === "undefined") return;
3701
+ let frame = 0;
3702
+ const run = () => {
3703
+ frame = 0;
3704
+ remeasure();
3705
+ };
3706
+ const schedule = () => {
3707
+ if (frame) return;
3708
+ if (typeof window.requestAnimationFrame !== "function") {
3709
+ remeasure();
3710
+ return;
3711
+ }
3712
+ frame = window.requestAnimationFrame(run);
3713
+ };
3714
+ remeasure();
3715
+ const viewport = window.visualViewport;
3716
+ window.addEventListener("resize", schedule);
3717
+ window.addEventListener("orientationchange", schedule);
3718
+ viewport?.addEventListener("resize", schedule);
3719
+ viewport?.addEventListener("scroll", schedule);
3720
+ const observer = typeof MutationObserver === "function" ? new MutationObserver((records) => {
3721
+ const el = rootRef.current;
3722
+ if (el && records.every((record) => el.contains(record.target)))
3723
+ return;
3724
+ schedule();
3725
+ }) : null;
3726
+ observer?.observe(document.body, { childList: true, subtree: true });
3727
+ return () => {
3728
+ if (frame) window.cancelAnimationFrame(frame);
3729
+ window.removeEventListener("resize", schedule);
3730
+ window.removeEventListener("orientationchange", schedule);
3731
+ viewport?.removeEventListener("resize", schedule);
3732
+ viewport?.removeEventListener("scroll", schedule);
3733
+ observer?.disconnect();
3734
+ setPaneOffset(null);
3735
+ setBottomInset(0);
3736
+ setChromeOverlap(0);
3737
+ };
3738
+ }, [remeasure]);
3739
+ useEffect(() => {
3740
+ measureChrome();
3741
+ }, [measureChrome, paneOffset, bottomInset]);
3742
+ useEffect(() => {
3743
+ if (heightMode !== "viewport" || typeof window === "undefined") return;
3744
+ const { history: browserHistory } = window;
3745
+ const previousRestoration = "scrollRestoration" in browserHistory ? browserHistory.scrollRestoration : null;
3746
+ if (previousRestoration !== null)
3747
+ browserHistory.scrollRestoration = "manual";
3748
+ let frame = 0;
3749
+ let visitorMoved = false;
3750
+ const onVisitorMove = () => {
3751
+ visitorMoved = true;
3752
+ };
3753
+ const align = () => {
3754
+ if (visitorMoved) return;
3755
+ const el = rootRef.current;
3756
+ if (!el) return;
3757
+ const viewportHeight = visualViewportHeight();
3758
+ const rect = el.getBoundingClientRect();
3759
+ if (rect.height === 0 || viewportHeight === 0) return;
3760
+ const visible = Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0);
3761
+ const reachable = Math.min(rect.height, viewportHeight);
3762
+ if (visible >= reachable * MIN_VISIBLE_FRACTION) return;
3763
+ window.scrollTo({
3764
+ top: Math.max(0, window.scrollY + rect.top),
3765
+ left: window.scrollX,
3766
+ behavior: "auto"
3767
+ });
3768
+ if (typeof document.elementFromPoint === "function") {
3769
+ const probe = document.elementFromPoint(
3770
+ Math.max(0, rect.left + rect.width / 2),
3771
+ 1
3772
+ );
3773
+ if (probe && !isOwnElement(probe, el)) {
3774
+ const chrome = probe.getBoundingClientRect().bottom;
3775
+ if (chrome > 0 && chrome < viewportHeight / 2) {
3776
+ window.scrollBy({ top: -chrome, left: 0, behavior: "auto" });
3777
+ }
3778
+ }
3779
+ }
3780
+ remeasure();
3781
+ };
3782
+ const alignIfRestored = () => {
3783
+ if (!isRestoredNavigation()) return;
3784
+ align();
3785
+ };
3786
+ alignIfRestored();
3787
+ if (typeof window.requestAnimationFrame === "function") {
3788
+ frame = window.requestAnimationFrame(alignIfRestored);
3789
+ }
3790
+ const onPageShow = (event) => {
3791
+ if (!event.persisted) {
3792
+ alignIfRestored();
3793
+ return;
3794
+ }
3795
+ visitorMoved = false;
3796
+ align();
3797
+ };
3798
+ window.addEventListener("pageshow", onPageShow);
3799
+ window.addEventListener("wheel", onVisitorMove, { passive: true });
3800
+ window.addEventListener("touchmove", onVisitorMove, { passive: true });
3801
+ window.addEventListener("keydown", onVisitorMove, { passive: true });
3802
+ return () => {
3803
+ if (frame) window.cancelAnimationFrame(frame);
3804
+ window.removeEventListener("pageshow", onPageShow);
3805
+ window.removeEventListener("wheel", onVisitorMove);
3806
+ window.removeEventListener("touchmove", onVisitorMove);
3807
+ window.removeEventListener("keydown", onVisitorMove);
3808
+ if (previousRestoration !== null)
3809
+ browserHistory.scrollRestoration = previousRestoration;
3810
+ };
3811
+ }, [heightMode, remeasure]);
3421
3812
  const handleScroll = useCallback(() => {
3422
3813
  const el = scrollRef.current;
3423
3814
  if (!el) return;
3424
3815
  stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
3425
3816
  }, []);
3817
+ useEffect(() => {
3818
+ if (typeof window === "undefined") return;
3819
+ let frame = 0;
3820
+ const run = () => {
3821
+ frame = 0;
3822
+ const el = scrollRef.current;
3823
+ const dock = composerRef.current;
3824
+ if (!el || !dock) return;
3825
+ if (!pageOwnsScrolling(el)) return;
3826
+ stickToBottomRef.current = dock.getBoundingClientRect().bottom - visualViewportHeight() < 80;
3827
+ };
3828
+ const schedule = () => {
3829
+ if (frame) return;
3830
+ if (typeof window.requestAnimationFrame !== "function") {
3831
+ run();
3832
+ return;
3833
+ }
3834
+ frame = window.requestAnimationFrame(run);
3835
+ };
3836
+ window.addEventListener("scroll", schedule, { passive: true });
3837
+ return () => {
3838
+ if (frame) window.cancelAnimationFrame(frame);
3839
+ window.removeEventListener("scroll", schedule);
3840
+ };
3841
+ }, []);
3426
3842
  const latestAssistantId = useMemo(() => {
3427
3843
  for (let i = messages.length - 1; i >= 0; i -= 1) {
3428
3844
  const message = messages[i];
@@ -3438,16 +3854,32 @@ function ExplorePage({
3438
3854
  if (!stickToBottomRef.current) return;
3439
3855
  if (anchor.offsetHeight === 0) return;
3440
3856
  alignedForRef.current = latestAssistantId;
3441
- const delta = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
3442
- el.scrollTop = Math.max(0, el.scrollTop + delta - 12);
3857
+ if (!pageOwnsScrolling(el)) {
3858
+ const delta2 = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
3859
+ el.scrollTop = Math.max(0, el.scrollTop + delta2 - 12);
3860
+ return;
3861
+ }
3862
+ const delta = Math.round(
3863
+ anchor.getBoundingClientRect().top - paneOffsetRef.current - 12
3864
+ );
3865
+ if (delta !== 0) window.scrollBy({ top: delta, left: 0, behavior: "auto" });
3443
3866
  }, [latestAssistantId, messages]);
3444
3867
  useEffect(() => {
3868
+ if (messages.length === 0) return;
3445
3869
  if (!stickToBottomRef.current) return;
3446
3870
  if (latestAssistantId && alignedForRef.current === latestAssistantId)
3447
3871
  return;
3448
3872
  const el = scrollRef.current;
3449
3873
  if (!el) return;
3450
- el.scrollTop = el.scrollHeight;
3874
+ if (!pageOwnsScrolling(el)) {
3875
+ el.scrollTop = el.scrollHeight;
3876
+ return;
3877
+ }
3878
+ const dock = composerRef.current ?? el;
3879
+ const delta = Math.round(
3880
+ dock.getBoundingClientRect().bottom - visualViewportHeight()
3881
+ );
3882
+ if (delta > 0) window.scrollBy({ top: delta, left: 0, behavior: "auto" });
3451
3883
  }, [activity, latestAssistantId, messages]);
3452
3884
  const handleShare = useCallback(async () => {
3453
3885
  setShareLabel(T.copying);
@@ -3545,16 +3977,27 @@ function ExplorePage({
3545
3977
  const captchaOn = Boolean(
3546
3978
  catalog.captchaRequired && catalog.recaptchaSiteKey
3547
3979
  );
3548
- const rootStyle = heightMode === "viewport" ? { height: `calc(100dvh - ${normalizeOffset(viewportOffset)})` } : void 0;
3980
+ const rootStyle = {};
3981
+ if (heightMode === "viewport") {
3982
+ rootStyle["--boff-explore-offset"] = paneOffset === null ? normalizeOffset(viewportOffset) : `${paneOffset}px`;
3983
+ if (bottomInset > 0) {
3984
+ rootStyle["--boff-explore-inset"] = `${bottomInset}px`;
3985
+ }
3986
+ }
3987
+ if (chromeOverlap > 0) {
3988
+ rootStyle["--boff-explore-chrome"] = `${chromeOverlap}px`;
3989
+ }
3549
3990
  return /* @__PURE__ */ jsxs(
3550
3991
  "div",
3551
3992
  {
3993
+ ref: rootRef,
3552
3994
  "data-boff-explore": "page",
3553
3995
  "data-phase": phase,
3554
3996
  "data-locale": activeLocale,
3555
3997
  dir: rtl ? "rtl" : void 0,
3556
3998
  className: cn(
3557
3999
  "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
4000
+ heightMode === "viewport" && "boff-explore-viewport",
3558
4001
  heightMode === "auto" && "min-h-[70vh]",
3559
4002
  className
3560
4003
  ),
@@ -3742,6 +4185,7 @@ function ExplorePage({
3742
4185
  strings: T,
3743
4186
  message,
3744
4187
  activity,
4188
+ imageOrigins,
3745
4189
  onNavigate,
3746
4190
  anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
3747
4191
  onFollow: (kind, url) => recordClick(message.id, kind, url),
@@ -3787,254 +4231,262 @@ function ExplorePage({
3787
4231
  ]
3788
4232
  }
3789
4233
  ) : null,
3790
- /* @__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(
3791
- "form",
4234
+ /* @__PURE__ */ jsx(
4235
+ "div",
3792
4236
  {
3793
- "data-boff-explore": "composer",
3794
- className: "mx-auto w-full max-w-3xl",
3795
- onSubmit: (event) => {
3796
- event.preventDefault();
3797
- submitDraft();
3798
- },
3799
- children: [
3800
- error ? /* @__PURE__ */ jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
3801
- staged.length > 0 ? /* @__PURE__ */ jsx(
3802
- "ul",
3803
- {
3804
- "data-boff-explore": "attachments",
3805
- className: "mb-2 flex flex-wrap gap-1.5",
3806
- "aria-label": T.attach,
3807
- children: staged.map((entry) => /* @__PURE__ */ jsxs(
3808
- "li",
4237
+ ref: composerRef,
4238
+ "data-boff-explore": "composer-dock",
4239
+ className: "boff-explore-composer border-t border-border bg-background/85 px-4 pt-3 backdrop-blur-md",
4240
+ children: /* @__PURE__ */ jsxs(
4241
+ "form",
4242
+ {
4243
+ "data-boff-explore": "composer",
4244
+ className: "mx-auto w-full max-w-3xl",
4245
+ onSubmit: (event) => {
4246
+ event.preventDefault();
4247
+ submitDraft();
4248
+ },
4249
+ children: [
4250
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
4251
+ staged.length > 0 ? /* @__PURE__ */ jsx(
4252
+ "ul",
3809
4253
  {
3810
- "data-boff-explore": "attachment",
3811
- "data-text-extracted": entry.textExtracted ? "true" : "false",
3812
- className: "flex max-w-full items-center gap-1.5 rounded-full border border-border bg-muted/60 py-1 pl-2.5 pr-1 text-xs",
4254
+ "data-boff-explore": "attachments",
4255
+ className: "mb-2 flex flex-wrap gap-1.5",
4256
+ "aria-label": T.attach,
4257
+ children: staged.map((entry) => /* @__PURE__ */ jsxs(
4258
+ "li",
4259
+ {
4260
+ "data-boff-explore": "attachment",
4261
+ "data-text-extracted": entry.textExtracted ? "true" : "false",
4262
+ className: "flex max-w-full items-center gap-1.5 rounded-full border border-border bg-muted/60 py-1 pl-2.5 pr-1 text-xs",
4263
+ children: [
4264
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
4265
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", title: entry.name, children: entry.name }),
4266
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
4267
+ formatBytes(entry.size),
4268
+ entry.textExtracted ? "" : ` \xB7 ${T.attachmentNameOnly}`
4269
+ ] }),
4270
+ /* @__PURE__ */ jsx(
4271
+ "button",
4272
+ {
4273
+ type: "button",
4274
+ "data-boff-explore": "attachment-remove",
4275
+ onClick: () => removeAttachment(entry.id),
4276
+ "aria-label": fill(T.removeAttachment, { name: entry.name }),
4277
+ className: "ml-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-background hover:text-foreground",
4278
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
4279
+ }
4280
+ )
4281
+ ]
4282
+ },
4283
+ entry.id
4284
+ ))
4285
+ }
4286
+ ) : null,
4287
+ attachmentNotice !== null ? /* @__PURE__ */ jsx(
4288
+ "p",
4289
+ {
4290
+ "data-boff-explore": "attachment-notice",
4291
+ role: "status",
4292
+ className: "mb-2 text-xs text-destructive",
4293
+ children: attachmentNotice
4294
+ }
4295
+ ) : null,
4296
+ /* @__PURE__ */ jsxs(
4297
+ "div",
4298
+ {
4299
+ className: cn(
4300
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
4301
+ busy && "boff-explore-busy",
4302
+ overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
4303
+ ),
3813
4304
  children: [
3814
- /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
3815
- /* @__PURE__ */ jsx("span", { className: "truncate font-medium", title: entry.name, children: entry.name }),
3816
- /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
3817
- formatBytes(entry.size),
3818
- entry.textExtracted ? "" : ` \xB7 ${T.attachmentNameOnly}`
3819
- ] }),
3820
4305
  /* @__PURE__ */ jsx(
3821
- "button",
4306
+ Textarea,
4307
+ {
4308
+ ref: textareaRef,
4309
+ value: draft,
4310
+ rows: 1,
4311
+ disabled: composerDisabled,
4312
+ onChange: (event) => setDraft(event.target.value),
4313
+ onKeyDown: handleKeyDown,
4314
+ onCompositionStart: () => {
4315
+ composingRef.current = true;
4316
+ },
4317
+ onCompositionEnd: () => {
4318
+ composingRef.current = false;
4319
+ },
4320
+ "aria-label": "Ask a question",
4321
+ placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? fill(T.placeholderProduct, {
4322
+ product: focusedProductName
4323
+ }) : T.placeholder,
4324
+ 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-base shadow-none focus-visible:ring-0 sm:text-sm"
4325
+ }
4326
+ ),
4327
+ /* @__PURE__ */ jsx(
4328
+ "input",
4329
+ {
4330
+ ref: fileInputRef,
4331
+ type: "file",
4332
+ multiple: true,
4333
+ className: "hidden",
4334
+ onChange: (event) => {
4335
+ void handleFilesPicked(event.target.files);
4336
+ event.target.value = "";
4337
+ }
4338
+ }
4339
+ ),
4340
+ /* @__PURE__ */ jsx(
4341
+ Button,
4342
+ {
4343
+ "data-boff-explore": "attach",
4344
+ type: "button",
4345
+ size: "icon",
4346
+ variant: "ghost",
4347
+ disabled: composerDisabled || staged.length >= MAX_ATTACHMENTS,
4348
+ "aria-label": T.attach,
4349
+ title: `${T.attach} \u2014 ${fill(T.attachHint, {
4350
+ max: String(MAX_ATTACHMENTS),
4351
+ size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
4352
+ })}`,
4353
+ onClick: () => fileInputRef.current?.click(),
4354
+ children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
4355
+ }
4356
+ ),
4357
+ speech.supported ? /* @__PURE__ */ jsx(
4358
+ Button,
3822
4359
  {
4360
+ "data-boff-explore": "mic",
4361
+ "data-listening": speech.listening ? "true" : "false",
3823
4362
  type: "button",
3824
- "data-boff-explore": "attachment-remove",
3825
- onClick: () => removeAttachment(entry.id),
3826
- "aria-label": fill(T.removeAttachment, { name: entry.name }),
3827
- className: "ml-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-background hover:text-foreground",
3828
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
4363
+ size: "icon",
4364
+ variant: speech.listening ? "default" : "ghost",
4365
+ disabled: composerDisabled,
4366
+ "aria-label": speech.listening ? T.stopDictating : T.dictate,
4367
+ "aria-pressed": speech.listening,
4368
+ title: speech.listening ? T.stopDictating : T.dictate,
4369
+ onClick: speech.toggle,
4370
+ className: cn(speech.listening && "animate-pulse"),
4371
+ children: /* @__PURE__ */ jsx(Mic, { className: "h-4 w-4" })
4372
+ }
4373
+ ) : null,
4374
+ busy ? /* @__PURE__ */ jsx(
4375
+ Button,
4376
+ {
4377
+ "data-boff-explore": "stop",
4378
+ type: "button",
4379
+ size: "icon",
4380
+ variant: "secondary",
4381
+ "aria-label": T.stop,
4382
+ onClick: stop,
4383
+ children: /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" })
4384
+ }
4385
+ ) : /* @__PURE__ */ jsx(
4386
+ Button,
4387
+ {
4388
+ "data-boff-explore": "send",
4389
+ type: "submit",
4390
+ size: "icon",
4391
+ disabled: !canSubmit,
4392
+ "aria-label": T.send,
4393
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
3829
4394
  }
3830
4395
  )
3831
4396
  ]
3832
- },
3833
- entry.id
3834
- ))
3835
- }
3836
- ) : null,
3837
- attachmentNotice !== null ? /* @__PURE__ */ jsx(
3838
- "p",
3839
- {
3840
- "data-boff-explore": "attachment-notice",
3841
- role: "status",
3842
- className: "mb-2 text-xs text-destructive",
3843
- children: attachmentNotice
3844
- }
3845
- ) : null,
3846
- /* @__PURE__ */ jsxs(
3847
- "div",
3848
- {
3849
- className: cn(
3850
- "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
3851
- busy && "boff-explore-busy",
3852
- overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
4397
+ }
3853
4398
  ),
3854
- children: [
3855
- /* @__PURE__ */ jsx(
3856
- Textarea,
4399
+ speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxs(
4400
+ "p",
4401
+ {
4402
+ "data-boff-explore": "dictation",
4403
+ className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
4404
+ "aria-live": "polite",
4405
+ children: [
4406
+ /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
4407
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
4408
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
4409
+ ] }),
4410
+ /* @__PURE__ */ jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
4411
+ ]
4412
+ }
4413
+ ) : null,
4414
+ speech.error !== null ? /* @__PURE__ */ jsx(
4415
+ "p",
4416
+ {
4417
+ "data-boff-explore": "dictation-error",
4418
+ className: "mt-2 text-xs text-destructive",
4419
+ children: speech.error
4420
+ }
4421
+ ) : null,
4422
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
4423
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
4424
+ remainingToday !== null ? /* @__PURE__ */ jsxs("span", { "data-boff-explore": "remaining", children: [
4425
+ remainingToday,
4426
+ " message",
4427
+ remainingToday === 1 ? "" : "s",
4428
+ " left today"
4429
+ ] }) : null,
4430
+ /* @__PURE__ */ jsxs(
4431
+ "span",
3857
4432
  {
3858
- ref: textareaRef,
3859
- value: draft,
3860
- rows: 1,
3861
- disabled: composerDisabled,
3862
- onChange: (event) => setDraft(event.target.value),
3863
- onKeyDown: handleKeyDown,
3864
- onCompositionStart: () => {
3865
- composingRef.current = true;
3866
- },
3867
- onCompositionEnd: () => {
3868
- composingRef.current = false;
3869
- },
3870
- "aria-label": "Ask a question",
3871
- placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? fill(T.placeholderProduct, {
3872
- product: focusedProductName
3873
- }) : T.placeholder,
3874
- 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-base shadow-none focus-visible:ring-0 sm:text-sm"
4433
+ className: cn(
4434
+ "ml-auto tabular-nums",
4435
+ overLimit && "font-medium text-destructive"
4436
+ ),
4437
+ children: [
4438
+ draft.length.toLocaleString(),
4439
+ " / ",
4440
+ maxLength.toLocaleString()
4441
+ ]
3875
4442
  }
3876
- ),
4443
+ )
4444
+ ] }),
4445
+ shareUrl ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
3877
4446
  /* @__PURE__ */ jsx(
3878
4447
  "input",
3879
4448
  {
3880
- ref: fileInputRef,
3881
- type: "file",
3882
- multiple: true,
3883
- className: "hidden",
3884
- onChange: (event) => {
3885
- void handleFilesPicked(event.target.files);
3886
- event.target.value = "";
3887
- }
4449
+ "data-boff-explore": "share-url",
4450
+ readOnly: true,
4451
+ value: shareUrl,
4452
+ "aria-label": "Read-only link to this conversation",
4453
+ onFocus: (event) => event.currentTarget.select(),
4454
+ className: "min-w-0 flex-1 rounded-md border border-border bg-muted px-2 py-1 text-xs text-muted-foreground"
3888
4455
  }
3889
4456
  ),
3890
4457
  /* @__PURE__ */ jsx(
3891
- Button,
3892
- {
3893
- "data-boff-explore": "attach",
3894
- type: "button",
3895
- size: "icon",
3896
- variant: "ghost",
3897
- disabled: composerDisabled || staged.length >= MAX_ATTACHMENTS,
3898
- "aria-label": T.attach,
3899
- title: `${T.attach} \u2014 ${fill(T.attachHint, {
3900
- max: String(MAX_ATTACHMENTS),
3901
- size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
3902
- })}`,
3903
- onClick: () => fileInputRef.current?.click(),
3904
- children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
3905
- }
3906
- ),
3907
- speech.supported ? /* @__PURE__ */ jsx(
3908
- Button,
4458
+ "button",
3909
4459
  {
3910
- "data-boff-explore": "mic",
3911
- "data-listening": speech.listening ? "true" : "false",
3912
4460
  type: "button",
3913
- size: "icon",
3914
- variant: speech.listening ? "default" : "ghost",
3915
- disabled: composerDisabled,
3916
- "aria-label": speech.listening ? T.stopDictating : T.dictate,
3917
- "aria-pressed": speech.listening,
3918
- title: speech.listening ? T.stopDictating : T.dictate,
3919
- onClick: speech.toggle,
3920
- className: cn(speech.listening && "animate-pulse"),
3921
- children: /* @__PURE__ */ jsx(Mic, { className: "h-4 w-4" })
4461
+ className: "shrink-0 text-xs underline underline-offset-2",
4462
+ onClick: () => setShareUrl(null),
4463
+ children: "Done"
3922
4464
  }
3923
- ) : null,
3924
- busy ? /* @__PURE__ */ jsx(
3925
- Button,
4465
+ )
4466
+ ] }) : null,
4467
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
4468
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: T.disclaimerShort }),
4469
+ /* @__PURE__ */ jsx(
4470
+ "button",
3926
4471
  {
3927
- "data-boff-explore": "stop",
3928
4472
  type: "button",
3929
- size: "icon",
3930
- variant: "secondary",
3931
- "aria-label": T.stop,
3932
- onClick: stop,
3933
- children: /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" })
3934
- }
3935
- ) : /* @__PURE__ */ jsx(
3936
- Button,
3937
- {
3938
- "data-boff-explore": "send",
3939
- type: "submit",
3940
- size: "icon",
3941
- disabled: !canSubmit,
3942
- "aria-label": T.send,
3943
- children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
4473
+ "data-boff-explore": "disclaimer",
4474
+ onClick: () => setDisclaimerOpen(true),
4475
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4476
+ children: T.disclaimer
3944
4477
  }
3945
4478
  )
3946
- ]
3947
- }
3948
- ),
3949
- speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxs(
3950
- "p",
3951
- {
3952
- "data-boff-explore": "dictation",
3953
- className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
3954
- "aria-live": "polite",
3955
- children: [
3956
- /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
3957
- /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
3958
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
3959
- ] }),
3960
- /* @__PURE__ */ jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
3961
- ]
3962
- }
3963
- ) : null,
3964
- speech.error !== null ? /* @__PURE__ */ jsx(
3965
- "p",
3966
- {
3967
- "data-boff-explore": "dictation-error",
3968
- className: "mt-2 text-xs text-destructive",
3969
- children: speech.error
3970
- }
3971
- ) : null,
3972
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
3973
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
3974
- remainingToday !== null ? /* @__PURE__ */ jsxs("span", { "data-boff-explore": "remaining", children: [
3975
- remainingToday,
3976
- " message",
3977
- remainingToday === 1 ? "" : "s",
3978
- " left today"
3979
- ] }) : null,
3980
- /* @__PURE__ */ jsxs(
3981
- "span",
3982
- {
3983
- className: cn(
3984
- "ml-auto tabular-nums",
3985
- overLimit && "font-medium text-destructive"
3986
- ),
3987
- children: [
3988
- draft.length.toLocaleString(),
3989
- " / ",
3990
- maxLength.toLocaleString()
3991
- ]
3992
- }
3993
- )
3994
- ] }),
3995
- shareUrl ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
3996
- /* @__PURE__ */ jsx(
3997
- "input",
3998
- {
3999
- "data-boff-explore": "share-url",
4000
- readOnly: true,
4001
- value: shareUrl,
4002
- "aria-label": "Read-only link to this conversation",
4003
- onFocus: (event) => event.currentTarget.select(),
4004
- className: "min-w-0 flex-1 rounded-md border border-border bg-muted px-2 py-1 text-xs text-muted-foreground"
4005
- }
4006
- ),
4007
- /* @__PURE__ */ jsx(
4008
- "button",
4009
- {
4010
- type: "button",
4011
- className: "shrink-0 text-xs underline underline-offset-2",
4012
- onClick: () => setShareUrl(null),
4013
- children: "Done"
4014
- }
4015
- )
4016
- ] }) : null,
4017
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
4018
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: T.disclaimerShort }),
4019
- /* @__PURE__ */ jsx(
4020
- "button",
4021
- {
4022
- type: "button",
4023
- "data-boff-explore": "disclaimer",
4024
- onClick: () => setDisclaimerOpen(true),
4025
- className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4026
- children: T.disclaimer
4027
- }
4028
- )
4029
- ] })
4030
- ]
4479
+ ] })
4480
+ ]
4481
+ }
4482
+ )
4031
4483
  }
4032
- ) })
4484
+ )
4033
4485
  ]
4034
4486
  }
4035
4487
  );
4036
4488
  }
4037
4489
 
4038
- export { ExplorePage, logoUrlOf };
4490
+ export { ExplorePage, inlineImageOrigins, isInlineImageAllowed, logoUrlOf };
4039
4491
  //# sourceMappingURL=explore.mjs.map
4040
4492
  //# sourceMappingURL=explore.mjs.map