@burdenoff/website-sdk 2026.922.3 → 2026.922.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
- import { createContext, useMemo, useContext, useState, useCallback, useLayoutEffect, useRef, useEffect, useId, version } from 'react';
2
+ import { createContext, useMemo, useContext, useState, useCallback, useLayoutEffect, useRef, useEffect, useId, version, Children, isValidElement, cloneElement } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { Instagram, Youtube, Facebook, Github, Linkedin, Twitter, Mail, Phone, MapPin, Clock, Send, CheckCircle, Upload, Globe, ChevronDown, Search, Rocket, ArrowRight, Download, ImageIcon, ChevronRight, Maximize2, Check, ArrowLeft, Sparkles, RotateCcw, Link2, History, Trash2, Paperclip, X, Mic, Square, ArrowUp, Info, ChevronLeft, BookOpenText, Layers, TriangleAlert, FileText, ExternalLink, ThumbsUp, ThumbsDown } from 'lucide-react';
4
+ import { Instagram, Youtube, Facebook, Github, Linkedin, Twitter, Mail, Phone, MapPin, Clock, Send, CheckCircle, Upload, Globe, ChevronDown, Search, Rocket, ArrowRight, Download, ImageIcon, ChevronRight, Maximize2, Check, ArrowLeft, Sparkles, RotateCcw, Link2, History, Trash2, Paperclip, X, Mic, Square, ArrowUp, Info, ChevronLeft, BookOpenText, Layers, TriangleAlert, FileText, ExternalLink, Copy, ThumbsUp, ThumbsDown, Volume2 } from 'lucide-react';
5
5
  import ReCAPTCHA4 from 'react-google-recaptcha';
6
6
  import { toast } from 'sonner';
7
7
  import { Helmet } from 'react-helmet-async';
@@ -2408,16 +2408,16 @@ function useRateLimit(maxAttempts, windowMinutes) {
2408
2408
  }
2409
2409
  }, [maxAttempts]);
2410
2410
  function recordAttempt() {
2411
- const now = Date.now();
2411
+ const now2 = Date.now();
2412
2412
  const state = rateLimitRef.current;
2413
- if (state && now > state.resetTime) {
2413
+ if (state && now2 > state.resetTime) {
2414
2414
  rateLimitRef.current = null;
2415
2415
  }
2416
2416
  const current = rateLimitRef.current;
2417
2417
  if (!current) {
2418
2418
  rateLimitRef.current = {
2419
2419
  attempts: 1,
2420
- resetTime: now + windowMinutes * 60 * 1e3
2420
+ resetTime: now2 + windowMinutes * 60 * 1e3
2421
2421
  };
2422
2422
  return false;
2423
2423
  }
@@ -3512,16 +3512,16 @@ function useRateLimit2(maxAttempts, windowMinutes) {
3512
3512
  const [isLimited, setIsLimited] = useState(false);
3513
3513
  const [resetTime, setResetTime] = useState(null);
3514
3514
  function recordAttempt() {
3515
- const now = Date.now();
3515
+ const now2 = Date.now();
3516
3516
  const state = rateLimitRef.current;
3517
- if (state && now > state.resetTime) {
3517
+ if (state && now2 > state.resetTime) {
3518
3518
  rateLimitRef.current = null;
3519
3519
  }
3520
3520
  const current = rateLimitRef.current;
3521
3521
  if (!current) {
3522
3522
  rateLimitRef.current = {
3523
3523
  attempts: 1,
3524
- resetTime: now + windowMinutes * 60 * 1e3
3524
+ resetTime: now2 + windowMinutes * 60 * 1e3
3525
3525
  };
3526
3526
  return false;
3527
3527
  }
@@ -4555,8 +4555,8 @@ function LaunchCountdown({
4555
4555
  launchedMessage = "We have launched!",
4556
4556
  className
4557
4557
  }) {
4558
- const [now, setNow] = useState(() => Date.now());
4559
- const remainingMs = launchDate - now;
4558
+ const [now2, setNow] = useState(() => Date.now());
4559
+ const remainingMs = launchDate - now2;
4560
4560
  const hasLaunched = remainingMs <= 0;
4561
4561
  useEffect(() => {
4562
4562
  if (hasLaunched) return;
@@ -5974,6 +5974,23 @@ var EN = {
5974
5974
  nextSuggestion: "Next suggestion",
5975
5975
  conceptIllustration: "Concept illustration",
5976
5976
  opensInNewTab: "opens in a new tab",
5977
+ conceptStripTitle: "Or start from a concept",
5978
+ conceptStripLabel: "Concepts you can ask about",
5979
+ conceptAsk: "Ask about {title} in {product}",
5980
+ conceptAskQuestion: "How does {title} work in {product}?",
5981
+ copyAnswer: "Copy answer",
5982
+ copyFailed: "Couldn't copy",
5983
+ readAloud: "Read aloud",
5984
+ stopReading: "Stop reading",
5985
+ answerTable: "Table",
5986
+ groundedIn: "What this answer is based on",
5987
+ sourceCountOne: "1 source",
5988
+ sourceCountMany: "{count} sources",
5989
+ sourceKindConcept: "Concept",
5990
+ sourceKindDocs: "Documentation",
5991
+ sourceKindPricing: "Pricing",
5992
+ sourceKindUseCase: "Use case",
5993
+ sourceKindPage: "Product page",
5977
5994
  seoTitle: "Describe a problem, get an answer",
5978
5995
  seoDescription: "Describe the problem you're facing in your own words and get an answer drawn from our product documentation, with links to the exact pages it came from.",
5979
5996
  seoDescriptionProduct: "Describe the problem you're facing in your own words and get an answer drawn from {product}'s documentation \u2014 and every other product we build \u2014 with links to the exact pages it came from.",
@@ -6042,6 +6059,23 @@ var ZH = {
6042
6059
  nextSuggestion: "\u4E0B\u4E00\u4E2A\u5EFA\u8BAE",
6043
6060
  conceptIllustration: "\u6982\u5FF5\u793A\u610F\u56FE",
6044
6061
  opensInNewTab: "\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00",
6062
+ conceptStripTitle: "\u6216\u8005\u4ECE\u4E00\u4E2A\u6982\u5FF5\u5F00\u59CB",
6063
+ conceptStripLabel: "\u4F60\u53EF\u4EE5\u8BE2\u95EE\u7684\u6982\u5FF5",
6064
+ conceptAsk: "\u8BE2\u95EE {product} \u4E2D\u7684 {title}",
6065
+ conceptAskQuestion: "{product} \u7684 {title} \u662F\u600E\u4E48\u8FD0\u4F5C\u7684\uFF1F",
6066
+ copyAnswer: "\u590D\u5236\u56DE\u7B54",
6067
+ copyFailed: "\u590D\u5236\u5931\u8D25",
6068
+ readAloud: "\u6717\u8BFB",
6069
+ stopReading: "\u505C\u6B62\u6717\u8BFB",
6070
+ answerTable: "\u8868\u683C",
6071
+ groundedIn: "\u8FD9\u4E2A\u56DE\u7B54\u7684\u4F9D\u636E",
6072
+ sourceCountOne: "1 \u4E2A\u6765\u6E90",
6073
+ sourceCountMany: "{count} \u4E2A\u6765\u6E90",
6074
+ sourceKindConcept: "\u6982\u5FF5",
6075
+ sourceKindDocs: "\u6587\u6863",
6076
+ sourceKindPricing: "\u4EF7\u683C",
6077
+ sourceKindUseCase: "\u5E94\u7528\u573A\u666F",
6078
+ sourceKindPage: "\u4EA7\u54C1\u9875\u9762",
6045
6079
  seoTitle: "\u63CF\u8FF0\u95EE\u9898\uFF0C\u83B7\u5F97\u7B54\u6848",
6046
6080
  seoDescription: "\u7528\u4F60\u81EA\u5DF1\u7684\u8BDD\u63CF\u8FF0\u9047\u5230\u7684\u95EE\u9898\uFF0C\u5373\u53EF\u83B7\u5F97\u6765\u81EA\u6211\u4EEC\u4EA7\u54C1\u6587\u6863\u7684\u56DE\u7B54\uFF0C\u5E76\u9644\u4E0A\u7B54\u6848\u51FA\u5904\u7684\u9875\u9762\u94FE\u63A5\u3002",
6047
6081
  seoDescriptionProduct: "\u7528\u4F60\u81EA\u5DF1\u7684\u8BDD\u63CF\u8FF0\u9047\u5230\u7684\u95EE\u9898\uFF0C\u5373\u53EF\u83B7\u5F97\u6765\u81EA {product} \u53CA\u6211\u4EEC\u5176\u4ED6\u4EA7\u54C1\u6587\u6863\u7684\u56DE\u7B54\uFF0C\u5E76\u9644\u4E0A\u7B54\u6848\u51FA\u5904\u7684\u9875\u9762\u94FE\u63A5\u3002",
@@ -6106,6 +6140,23 @@ var HI = {
6106
6140
  nextSuggestion: "\u0905\u0917\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
6107
6141
  conceptIllustration: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E \u091A\u093F\u0924\u094D\u0930\u0923",
6108
6142
  opensInNewTab: "\u0928\u090F \u091F\u0948\u092C \u092E\u0947\u0902 \u0916\u0941\u0932\u0924\u093E \u0939\u0948",
6143
+ conceptStripTitle: "\u092F\u093E \u0915\u093F\u0938\u0940 \u0905\u0935\u0927\u093E\u0930\u0923\u093E \u0938\u0947 \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0902",
6144
+ conceptStripLabel: "\u091C\u093F\u0928 \u0905\u0935\u0927\u093E\u0930\u0923\u093E\u0913\u0902 \u0915\u0947 \u092C\u093E\u0930\u0947 \u092E\u0947\u0902 \u0906\u092A \u092A\u0942\u091B \u0938\u0915\u0924\u0947 \u0939\u0948\u0902",
6145
+ conceptAsk: "{product} \u092E\u0947\u0902 {title} \u0915\u0947 \u092C\u093E\u0930\u0947 \u092E\u0947\u0902 \u092A\u0942\u091B\u0947\u0902",
6146
+ conceptAskQuestion: "{product} \u092E\u0947\u0902 {title} \u0915\u0948\u0938\u0947 \u0915\u093E\u092E \u0915\u0930\u0924\u093E \u0939\u0948?",
6147
+ copyAnswer: "\u0909\u0924\u094D\u0924\u0930 \u0915\u0949\u092A\u0940 \u0915\u0930\u0947\u0902",
6148
+ copyFailed: "\u0915\u0949\u092A\u0940 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u093E",
6149
+ readAloud: "\u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0902",
6150
+ stopReading: "\u092A\u0922\u093C\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
6151
+ answerTable: "\u0924\u093E\u0932\u093F\u0915\u093E",
6152
+ groundedIn: "\u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948",
6153
+ sourceCountOne: "1 \u0938\u094D\u0930\u094B\u0924",
6154
+ sourceCountMany: "{count} \u0938\u094D\u0930\u094B\u0924",
6155
+ sourceKindConcept: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E",
6156
+ sourceKindDocs: "\u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C",
6157
+ sourceKindPricing: "\u092E\u0942\u0932\u094D\u092F",
6158
+ sourceKindUseCase: "\u0909\u092A\u092F\u094B\u0917 \u0915\u093E \u0909\u0926\u093E\u0939\u0930\u0923",
6159
+ sourceKindPage: "\u0909\u0924\u094D\u092A\u093E\u0926 \u092A\u0947\u091C",
6109
6160
  seoTitle: "\u0938\u092E\u0938\u094D\u092F\u093E \u092C\u0924\u093E\u090F\u0902, \u091C\u0935\u093E\u092C \u092A\u093E\u090F\u0902",
6110
6161
  seoDescription: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \u0905\u092A\u0928\u0947 \u0936\u092C\u094D\u0926\u094B\u0902 \u092E\u0947\u0902 \u092C\u0924\u093E\u090F\u0902 \u0914\u0930 \u0939\u092E\u093E\u0930\u0947 \u0909\u0924\u094D\u092A\u093E\u0926 \u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C\u094B\u0902 \u0938\u0947 \u0932\u093F\u092F\u093E \u0917\u092F\u093E \u091C\u0935\u093E\u092C \u092A\u093E\u090F\u0902, \u0938\u093E\u0925 \u092E\u0947\u0902 \u0909\u0928\u094D\u0939\u0940\u0902 \u092A\u0928\u094D\u0928\u094B\u0902 \u0915\u0947 \u0932\u093F\u0902\u0915 \u091C\u0939\u093E\u0901 \u0938\u0947 \u0935\u0939 \u0906\u092F\u093E \u0939\u0948\u0964",
6111
6162
  seoDescriptionProduct: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \u0905\u092A\u0928\u0947 \u0936\u092C\u094D\u0926\u094B\u0902 \u092E\u0947\u0902 \u092C\u0924\u093E\u090F\u0902 \u0914\u0930 {product} \u0924\u0925\u093E \u0939\u092E\u093E\u0930\u0947 \u092C\u093E\u0915\u0940 \u0909\u0924\u094D\u092A\u093E\u0926\u094B\u0902 \u0915\u0947 \u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C\u094B\u0902 \u0938\u0947 \u0932\u093F\u092F\u093E \u0917\u092F\u093E \u091C\u0935\u093E\u092C \u092A\u093E\u090F\u0902, \u0938\u093E\u0925 \u092E\u0947\u0902 \u0909\u0928\u094D\u0939\u0940\u0902 \u092A\u0928\u094D\u0928\u094B\u0902 \u0915\u0947 \u0932\u093F\u0902\u0915 \u091C\u0939\u093E\u0901 \u0938\u0947 \u0935\u0939 \u0906\u092F\u093E \u0939\u0948\u0964",
@@ -6174,6 +6225,23 @@ var ES = {
6174
6225
  nextSuggestion: "Sugerencia siguiente",
6175
6226
  conceptIllustration: "Ilustraci\xF3n del concepto",
6176
6227
  opensInNewTab: "se abre en una pesta\xF1a nueva",
6228
+ conceptStripTitle: "O empieza por un concepto",
6229
+ conceptStripLabel: "Conceptos sobre los que puedes preguntar",
6230
+ conceptAsk: "Preguntar por {title} en {product}",
6231
+ conceptAskQuestion: "\xBFC\xF3mo funciona {title} en {product}?",
6232
+ copyAnswer: "Copiar respuesta",
6233
+ copyFailed: "No se pudo copiar",
6234
+ readAloud: "Leer en voz alta",
6235
+ stopReading: "Dejar de leer",
6236
+ answerTable: "Tabla",
6237
+ groundedIn: "En qu\xE9 se basa esta respuesta",
6238
+ sourceCountOne: "1 fuente",
6239
+ sourceCountMany: "{count} fuentes",
6240
+ sourceKindConcept: "Concepto",
6241
+ sourceKindDocs: "Documentaci\xF3n",
6242
+ sourceKindPricing: "Precios",
6243
+ sourceKindUseCase: "Caso de uso",
6244
+ sourceKindPage: "P\xE1gina de producto",
6177
6245
  seoTitle: "Describe un problema y obt\xE9n una respuesta",
6178
6246
  seoDescription: "Describe con tus palabras el problema que tienes y recibe una respuesta extra\xEDda de la documentaci\xF3n de nuestros productos, con enlaces a las p\xE1ginas exactas de las que procede.",
6179
6247
  seoDescriptionProduct: "Describe con tus palabras el problema que tienes y recibe una respuesta extra\xEDda de la documentaci\xF3n de {product} y del resto de nuestros productos, con enlaces a las p\xE1ginas exactas de las que procede.",
@@ -6242,6 +6310,23 @@ var AR = {
6242
6310
  nextSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u062A\u0627\u0644\u064A",
6243
6311
  conceptIllustration: "\u0631\u0633\u0645 \u062A\u0648\u0636\u064A\u062D\u064A \u0644\u0644\u0645\u0641\u0647\u0648\u0645",
6244
6312
  opensInNewTab: "\u064A\u064F\u0641\u062A\u062D \u0641\u064A \u0639\u0644\u0627\u0645\u0629 \u062A\u0628\u0648\u064A\u0628 \u062C\u062F\u064A\u062F\u0629",
6313
+ conceptStripTitle: "\u0623\u0648 \u0627\u0628\u062F\u0623 \u0645\u0646 \u0645\u0641\u0647\u0648\u0645",
6314
+ conceptStripLabel: "\u0645\u0641\u0627\u0647\u064A\u0645 \u064A\u0645\u0643\u0646\u0643 \u0627\u0644\u0633\u0624\u0627\u0644 \u0639\u0646\u0647\u0627",
6315
+ conceptAsk: "\u0627\u0633\u0623\u0644 \u0639\u0646 {title} \u0641\u064A {product}",
6316
+ conceptAskQuestion: "\u0643\u064A\u0641 \u064A\u0639\u0645\u0644 {title} \u0641\u064A {product}\u061F",
6317
+ copyAnswer: "\u0646\u0633\u062E \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
6318
+ copyFailed: "\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u0646\u0633\u062E",
6319
+ readAloud: "\u0627\u0642\u0631\u0623 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D",
6320
+ stopReading: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0642\u0631\u0627\u0621\u0629",
6321
+ answerTable: "\u062C\u062F\u0648\u0644",
6322
+ groundedIn: "\u0639\u0644\u0649 \u0645\u0627\u0630\u0627 \u062A\u0633\u062A\u0646\u062F \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
6323
+ sourceCountOne: "\u0645\u0635\u062F\u0631 \u0648\u0627\u062D\u062F",
6324
+ sourceCountMany: "{count} \u0645\u0635\u0627\u062F\u0631",
6325
+ sourceKindConcept: "\u0645\u0641\u0647\u0648\u0645",
6326
+ sourceKindDocs: "\u0627\u0644\u062A\u0648\u062B\u064A\u0642",
6327
+ sourceKindPricing: "\u0627\u0644\u0623\u0633\u0639\u0627\u0631",
6328
+ sourceKindUseCase: "\u062D\u0627\u0644\u0629 \u0627\u0633\u062A\u062E\u062F\u0627\u0645",
6329
+ sourceKindPage: "\u0635\u0641\u062D\u0629 \u0627\u0644\u0645\u0646\u062A\u062C",
6245
6330
  seoTitle: "\u0635\u0641 \u0645\u0634\u0643\u0644\u062A\u0643 \u0648\u0627\u062D\u0635\u0644 \u0639\u0644\u0649 \u0625\u062C\u0627\u0628\u0629",
6246
6331
  seoDescription: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629 \u0627\u0644\u062A\u064A \u062A\u0648\u0627\u062C\u0647\u0647\u0627 \u0628\u0643\u0644\u0645\u0627\u062A\u0643 \u0648\u0627\u062D\u0635\u0644 \u0639\u0644\u0649 \u0625\u062C\u0627\u0628\u0629 \u0645\u0633\u062A\u0645\u062F\u0629 \u0645\u0646 \u0648\u062B\u0627\u0626\u0642 \u0645\u0646\u062A\u062C\u0627\u062A\u0646\u0627\u060C \u0645\u0639 \u0631\u0648\u0627\u0628\u0637 \u0625\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u062A\u064A \u062C\u0627\u0621\u062A \u0645\u0646\u0647\u0627.",
6247
6332
  seoDescriptionProduct: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629 \u0627\u0644\u062A\u064A \u062A\u0648\u0627\u062C\u0647\u0647\u0627 \u0628\u0643\u0644\u0645\u0627\u062A\u0643 \u0648\u0627\u062D\u0635\u0644 \u0639\u0644\u0649 \u0625\u062C\u0627\u0628\u0629 \u0645\u0633\u062A\u0645\u062F\u0629 \u0645\u0646 \u0648\u062B\u0627\u0626\u0642 {product} \u0648\u0628\u0642\u064A\u0629 \u0645\u0646\u062A\u062C\u0627\u062A\u0646\u0627\u060C \u0645\u0639 \u0631\u0648\u0627\u0628\u0637 \u0625\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u062A\u064A \u062C\u0627\u0621\u062A \u0645\u0646\u0647\u0627.",
@@ -6310,6 +6395,23 @@ var TA = {
6310
6395
  nextSuggestion: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4 \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
6311
6396
  conceptIllustration: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BB5\u0BBF\u0BB3\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BAE\u0BCD",
6312
6397
  opensInNewTab: "\u0BAA\u0BC1\u0BA4\u0BBF\u0BAF \u0BA4\u0BBE\u0BB5\u0BB2\u0BBF\u0BB2\u0BCD \u0BA4\u0BBF\u0BB1\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD",
6398
+ conceptStripTitle: "\u0B85\u0BB2\u0BCD\u0BB2\u0BA4\u0BC1 \u0B92\u0BB0\u0BC1 \u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BA4\u0BCA\u0B9F\u0B99\u0BCD\u0B95\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
6399
+ conceptStripLabel: "\u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B95\u0BC7\u0B9F\u0BCD\u0B95\u0B95\u0BCD\u0B95\u0BC2\u0B9F\u0BBF\u0BAF \u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1\u0B95\u0BB3\u0BCD",
6400
+ conceptAsk: "{product} \u0B87\u0BB2\u0BCD {title} \u0BAA\u0BB1\u0BCD\u0BB1\u0BBF\u0B95\u0BCD \u0B95\u0BC7\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
6401
+ conceptAskQuestion: "{product} \u0B87\u0BB2\u0BCD {title} \u0B8E\u0BAA\u0BCD\u0BAA\u0B9F\u0BBF \u0BB5\u0BC7\u0BB2\u0BC8 \u0B9A\u0BC6\u0BAF\u0BCD\u0B95\u0BBF\u0BB1\u0BA4\u0BC1?",
6402
+ copyAnswer: "\u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1",
6403
+ copyFailed: "\u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8",
6404
+ readAloud: "\u0B9A\u0BA4\u0BCD\u0BA4\u0BAE\u0BBE\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF",
6405
+ stopReading: "\u0BAA\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
6406
+ answerTable: "\u0B85\u0B9F\u0BCD\u0B9F\u0BB5\u0BA3\u0BC8",
6407
+ groundedIn: "\u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B8E\u0BA4\u0BC8 \u0B85\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0B9F\u0BC8\u0BAF\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BA4\u0BC1",
6408
+ sourceCountOne: "1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD",
6409
+ sourceCountMany: "{count} \u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
6410
+ sourceKindConcept: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
6411
+ sourceKindDocs: "\u0B86\u0BB5\u0BA3\u0BAE\u0BCD",
6412
+ sourceKindPricing: "\u0BB5\u0BBF\u0BB2\u0BC8",
6413
+ sourceKindUseCase: "\u0BAA\u0BAF\u0BA9\u0BCD\u0BAA\u0BBE\u0B9F\u0BCD\u0B9F\u0BC1 \u0BA8\u0BBF\u0BB2\u0BC8",
6414
+ sourceKindPage: "\u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1\u0BAA\u0BCD \u0BAA\u0B95\u0BCD\u0B95\u0BAE\u0BCD",
6313
6415
  seoTitle: "\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD, \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0BAA\u0BC6\u0BB1\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
6314
6416
  seoDescription: "\u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B8E\u0BA4\u0BBF\u0BB0\u0BCD\u0B95\u0BCA\u0BB3\u0BCD\u0BB3\u0BC1\u0BAE\u0BCD \u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0B89\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B9A\u0BCA\u0BA8\u0BCD\u0BA4 \u0BB5\u0BBE\u0BB0\u0BCD\u0BA4\u0BCD\u0BA4\u0BC8\u0B95\u0BB3\u0BBF\u0BB2\u0BCD \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD; \u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1 \u0B86\u0BB5\u0BA3\u0B99\u0BCD\u0B95\u0BB3\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B95\u0BBF\u0B9F\u0BC8\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD, \u0B85\u0BA4\u0BC1 \u0BB5\u0BA8\u0BCD\u0BA4 \u0BAA\u0B95\u0BCD\u0B95\u0B99\u0BCD\u0B95\u0BB3\u0BC1\u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0B87\u0BA3\u0BC8\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BC1\u0B9F\u0BA9\u0BCD.",
6315
6417
  seoDescriptionProduct: "{product} \u0BAE\u0BB1\u0BCD\u0BB1\u0BC1\u0BAE\u0BCD \u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAA\u0BBF\u0BB1 \u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BBF\u0BA9\u0BCD \u0B86\u0BB5\u0BA3\u0B99\u0BCD\u0B95\u0BB3\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0BAA\u0BC6\u0BB1, \u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B8E\u0BA4\u0BBF\u0BB0\u0BCD\u0B95\u0BCA\u0BB3\u0BCD\u0BB3\u0BC1\u0BAE\u0BCD \u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0B89\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B9A\u0BCA\u0BA8\u0BCD\u0BA4 \u0BB5\u0BBE\u0BB0\u0BCD\u0BA4\u0BCD\u0BA4\u0BC8\u0B95\u0BB3\u0BBF\u0BB2\u0BCD \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u2014 \u0B85\u0BA4\u0BC1 \u0BB5\u0BA8\u0BCD\u0BA4 \u0BAA\u0B95\u0BCD\u0B95\u0B99\u0BCD\u0B95\u0BB3\u0BC1\u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0B87\u0BA3\u0BC8\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BC1\u0B9F\u0BA9\u0BCD.",
@@ -6333,6 +6435,17 @@ var STRINGS = {
6333
6435
  ar: AR,
6334
6436
  ta: TA
6335
6437
  };
6438
+ var SPEECH_TAGS = {
6439
+ en: "en-US",
6440
+ zh: "zh-CN",
6441
+ hi: "hi-IN",
6442
+ es: "es-ES",
6443
+ ar: "ar-SA",
6444
+ ta: "ta-IN"
6445
+ };
6446
+ function speechLocaleOf(locale) {
6447
+ return SPEECH_TAGS[locale];
6448
+ }
6336
6449
  function resolveExploreLocale(explicit) {
6337
6450
  const candidates = [
6338
6451
  explicit,
@@ -6411,11 +6524,9 @@ var EXPLORE_OWN_MESSAGE_FIELDS = (
6411
6524
  }
6412
6525
  `
6413
6526
  );
6414
- var EXPLORE_CATALOG_QUERY = (
6527
+ var EXPLORE_CATALOG_ESTABLISHED_FIELDS = (
6415
6528
  /* GraphQL */
6416
6529
  `
6417
- query ExploreCatalog($productSlug: String) {
6418
- exploreCatalog(productSlug: $productSlug) {
6419
6530
  enabled
6420
6531
  products {
6421
6532
  slug
@@ -6433,11 +6544,35 @@ var EXPLORE_CATALOG_QUERY = (
6433
6544
  historyTurns
6434
6545
  }
6435
6546
  captchaRequired
6436
- recaptchaSiteKey
6547
+ recaptchaSiteKey`
6548
+ );
6549
+ var EXPLORE_CATALOG_HIGHLIGHT_FIELD = (
6550
+ /* GraphQL */
6551
+ `
6552
+ conceptHighlights {
6553
+ product
6554
+ productName
6555
+ title
6556
+ url
6557
+ imageUrl
6558
+ alt
6559
+ }`
6560
+ );
6561
+ function exploreCatalogDocument(extraFields) {
6562
+ return (
6563
+ /* GraphQL */
6564
+ `
6565
+ query ExploreCatalog($productSlug: String) {
6566
+ exploreCatalog(productSlug: $productSlug) {${EXPLORE_CATALOG_ESTABLISHED_FIELDS}${extraFields}
6437
6567
  }
6438
6568
  }
6439
6569
  `
6570
+ );
6571
+ }
6572
+ var EXPLORE_CATALOG_QUERY = exploreCatalogDocument(
6573
+ EXPLORE_CATALOG_HIGHLIGHT_FIELD
6440
6574
  );
6575
+ var EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS = exploreCatalogDocument("");
6441
6576
  var EXPLORE_MESSAGE_QUERY = (
6442
6577
  /* GraphQL */
6443
6578
  `
@@ -6978,7 +7113,12 @@ function buildFallbackCatalog(examplePrompts, welcome) {
6978
7113
  welcomeBody: welcome?.body ?? FALLBACK_WELCOME_BODY,
6979
7114
  limits: FALLBACK_EXPLORE_LIMITS,
6980
7115
  captchaRequired: false,
6981
- recaptchaSiteKey: null
7116
+ recaptchaSiteKey: null,
7117
+ // Empty, and deliberately not synthesised from `ECOSYSTEM_PRODUCTS`: a concept highlight
7118
+ // needs a real published illustration URL, and this SDK has no registry of them. An
7119
+ // invented path would render 38 broken frames on the one screen every prerender captures.
7120
+ // The strip renders nothing at all when the list is empty, which is the honest fallback.
7121
+ conceptHighlights: []
6982
7122
  };
6983
7123
  }
6984
7124
  function normalizeCatalog(raw, fallback) {
@@ -6995,9 +7135,27 @@ function normalizeCatalog(raw, fallback) {
6995
7135
  historyTurns: raw.limits?.historyTurns || fallback.limits.historyTurns
6996
7136
  },
6997
7137
  captchaRequired: raw.captchaRequired === true,
6998
- recaptchaSiteKey: raw.recaptchaSiteKey ?? null
7138
+ recaptchaSiteKey: raw.recaptchaSiteKey ?? null,
7139
+ // `[ExploreConceptHighlight!]!` on the wire, so a gateway that KNOWS the field always
7140
+ // sends an array. The one response that arrives without it is the retry against an older
7141
+ // supergraph (`EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS`) — that gateway does not answer
7142
+ // the full document at all, it refuses it. Either way
7143
+ // `catalog.conceptHighlights.filter(...)` on `undefined` would throw inside render,
7144
+ // blanking the welcome screen over a decorative strip.
7145
+ conceptHighlights: raw.conceptHighlights?.length ? raw.conceptHighlights : fallback.conceptHighlights
6999
7146
  };
7000
7147
  }
7148
+ var CATALOG_DOCUMENT_REJECTED_CODES = /* @__PURE__ */ new Set([
7149
+ "GRAPHQL_VALIDATION_FAILED",
7150
+ "HTTP_400"
7151
+ ]);
7152
+ function catalogRejectedTheHighlightField(res) {
7153
+ if (res.data?.exploreCatalog) return false;
7154
+ return (res.errors ?? []).some((error) => {
7155
+ const code = error.extensions?.code;
7156
+ return typeof code === "string" && CATALOG_DOCUMENT_REJECTED_CODES.has(code) || error.message.includes("conceptHighlights");
7157
+ });
7158
+ }
7001
7159
  var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
7002
7160
  "RATE_LIMITED",
7003
7161
  "HTTP_429",
@@ -7337,11 +7495,19 @@ function useExploreChat(options = {}) {
7337
7495
  useEffect(() => {
7338
7496
  let cancelled = false;
7339
7497
  const load = async () => {
7340
- const res = await client.query(
7498
+ const variables = { productSlug: productSlug ?? null };
7499
+ let res = await client.query(
7341
7500
  EXPLORE_CATALOG_QUERY,
7342
- { productSlug: productSlug ?? null }
7501
+ variables
7343
7502
  );
7344
7503
  if (cancelled || !mountedRef.current) return;
7504
+ if (catalogRejectedTheHighlightField(res)) {
7505
+ res = await client.query(
7506
+ EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS,
7507
+ variables
7508
+ );
7509
+ if (cancelled || !mountedRef.current) return;
7510
+ }
7345
7511
  const raw = res.data?.exploreCatalog;
7346
7512
  if (res.errors?.length || !raw) {
7347
7513
  console.warn(
@@ -7787,6 +7953,224 @@ function makeOptimisticId() {
7787
7953
  optimisticCounter += 1;
7788
7954
  return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
7789
7955
  }
7956
+ var DRAIN_TARGET_MS = 1200;
7957
+ var MIN_CHARS_PER_SECOND = 90;
7958
+ var MAX_CHARS_PER_SECOND = 2200;
7959
+ var MAX_STEP_MS = 250;
7960
+ var COMMIT_INTERVAL_MS = 45;
7961
+ var FALLBACK_STEP_MS = 50;
7962
+ var FENCE = /^ {0,3}(?:```|~~~)/;
7963
+ var TABLE_ROW = /^ {0,3}\|/;
7964
+ var EMPTY_BLOCK_MARKER = /^ {0,3}(?:[-+>]|#{1,6}|\d{1,9}[.)])\s*$/;
7965
+ function isWordCharacter(char) {
7966
+ return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
7967
+ }
7968
+ function clamp(value, min, max) {
7969
+ return Math.min(Math.max(value, min), max);
7970
+ }
7971
+ function now() {
7972
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
7973
+ }
7974
+ function isHighSurrogate(code) {
7975
+ return code >= 55296 && code <= 56319;
7976
+ }
7977
+ function safeInlineEnd(line) {
7978
+ let codeSpan = null;
7979
+ let link = null;
7980
+ const emphasis = [];
7981
+ let at = 0;
7982
+ while (at < line.length) {
7983
+ const char = line[at] ?? "";
7984
+ if (char === "\\") {
7985
+ at += 2;
7986
+ continue;
7987
+ }
7988
+ if (char === "`") {
7989
+ let run = 1;
7990
+ while (line[at + run] === "`") run += 1;
7991
+ codeSpan = codeSpan === null ? at : null;
7992
+ at += run;
7993
+ continue;
7994
+ }
7995
+ if (codeSpan !== null) {
7996
+ at += 1;
7997
+ continue;
7998
+ }
7999
+ if (char === "[") {
8000
+ if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
8001
+ at += 1;
8002
+ continue;
8003
+ }
8004
+ if (char === "]") {
8005
+ if (link !== null && line[at + 1] !== "(") link = null;
8006
+ at += 1;
8007
+ continue;
8008
+ }
8009
+ if (char === ")") {
8010
+ link = null;
8011
+ at += 1;
8012
+ continue;
8013
+ }
8014
+ if (char === "*" || char === "_" || char === "~") {
8015
+ let run = 1;
8016
+ while (line[at + run] === char) run += 1;
8017
+ const start = at;
8018
+ at += run;
8019
+ const before = line[start - 1];
8020
+ const after = line[start + run];
8021
+ if (run < 2) continue;
8022
+ if (char === "_" && isWordCharacter(before) && isWordCharacter(after)) {
8023
+ continue;
8024
+ }
8025
+ const marker = char.repeat(run);
8026
+ const open2 = emphasis.findIndex((entry) => entry.marker === marker);
8027
+ if (open2 !== -1) {
8028
+ emphasis.splice(open2, 1);
8029
+ continue;
8030
+ }
8031
+ if (after !== void 0 && /\s/.test(after)) continue;
8032
+ emphasis.push({ marker, start });
8033
+ continue;
8034
+ }
8035
+ at += 1;
8036
+ }
8037
+ const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
8038
+ (value) => value !== null
8039
+ );
8040
+ return open.length === 0 ? line.length : Math.min(...open);
8041
+ }
8042
+ function revealBoundary(text, limit) {
8043
+ if (limit <= 0) return 0;
8044
+ const caughtUp = limit >= text.length;
8045
+ const head = caughtUp ? text : text.slice(0, limit);
8046
+ const lineStart = head.lastIndexOf("\n") + 1;
8047
+ const line = head.slice(lineStart);
8048
+ let fenceStart = null;
8049
+ let tableStart = null;
8050
+ let tableRows = 0;
8051
+ let at = 0;
8052
+ while (at < lineStart) {
8053
+ const breakAt = head.indexOf("\n", at);
8054
+ const end = breakAt === -1 ? lineStart : breakAt;
8055
+ const row = head.slice(at, end);
8056
+ if (fenceStart !== null) {
8057
+ if (FENCE.test(row)) fenceStart = null;
8058
+ } else if (FENCE.test(row)) {
8059
+ fenceStart = at;
8060
+ } else if (TABLE_ROW.test(row)) {
8061
+ if (tableStart === null) {
8062
+ tableStart = at;
8063
+ tableRows = 0;
8064
+ }
8065
+ tableRows += 1;
8066
+ } else if (tableStart !== null) {
8067
+ tableStart = null;
8068
+ tableRows = 0;
8069
+ }
8070
+ at = end + 1;
8071
+ }
8072
+ if (fenceStart === null) {
8073
+ const lastIsRow = TABLE_ROW.test(line);
8074
+ if (lastIsRow && tableStart === null) {
8075
+ tableStart = lineStart;
8076
+ tableRows = 0;
8077
+ } else if (!lastIsRow && line !== "") {
8078
+ tableStart = null;
8079
+ tableRows = 0;
8080
+ }
8081
+ if (tableStart !== null && tableRows < 2) return tableStart;
8082
+ }
8083
+ if (caughtUp) return text.length;
8084
+ if (fenceStart !== null) return fenceStart;
8085
+ if (TABLE_ROW.test(line)) return lineStart;
8086
+ if (EMPTY_BLOCK_MARKER.test(line)) return lineStart;
8087
+ const cut = lineStart + safeInlineEnd(line);
8088
+ return isHighSurrogate(text.charCodeAt(cut - 1)) ? cut - 1 : cut;
8089
+ }
8090
+ function prefersReducedMotion() {
8091
+ if (typeof window === "undefined") return true;
8092
+ try {
8093
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
8094
+ } catch {
8095
+ return false;
8096
+ }
8097
+ }
8098
+ function useProgressiveReveal(text, settled) {
8099
+ const [, setFrame] = useState(0);
8100
+ const [reduced] = useState(prefersReducedMotion);
8101
+ const immediate = settled || reduced;
8102
+ const textRef = useRef(text);
8103
+ const cursorRef = useRef(immediate ? text.length : 0);
8104
+ const cutRef = useRef(immediate ? text.length : 0);
8105
+ const committedRef = useRef(0);
8106
+ if (text !== textRef.current) {
8107
+ const revealed = textRef.current.slice(0, Math.floor(cursorRef.current));
8108
+ if (revealed !== "" && !text.startsWith(revealed)) {
8109
+ cursorRef.current = text.length;
8110
+ }
8111
+ cursorRef.current = Math.min(cursorRef.current, text.length);
8112
+ cutRef.current = Math.min(cutRef.current, text.length);
8113
+ textRef.current = text;
8114
+ }
8115
+ if (immediate) cursorRef.current = text.length;
8116
+ const cut = immediate ? text.length : Math.max(
8117
+ cutRef.current,
8118
+ revealBoundary(text, Math.floor(cursorRef.current))
8119
+ );
8120
+ cutRef.current = cut;
8121
+ useEffect(() => {
8122
+ if (immediate) return;
8123
+ if (cursorRef.current >= text.length) return;
8124
+ let cancelled = false;
8125
+ let cancelStep = null;
8126
+ let previous = now();
8127
+ const commit = (at, force) => {
8128
+ if (!force && at - committedRef.current < COMMIT_INTERVAL_MS) return;
8129
+ committedRef.current = at;
8130
+ setFrame((value) => value + 1);
8131
+ };
8132
+ const step = () => {
8133
+ cancelStep = null;
8134
+ if (cancelled) return;
8135
+ const at = now();
8136
+ const elapsed = clamp(at - previous, 0, MAX_STEP_MS);
8137
+ previous = at;
8138
+ const length = textRef.current.length;
8139
+ const pending = length - cursorRef.current;
8140
+ if (pending <= 0) {
8141
+ commit(at, true);
8142
+ return;
8143
+ }
8144
+ const perSecond = clamp(
8145
+ pending / (DRAIN_TARGET_MS / 1e3),
8146
+ MIN_CHARS_PER_SECOND,
8147
+ MAX_CHARS_PER_SECOND
8148
+ );
8149
+ cursorRef.current = Math.min(
8150
+ length,
8151
+ cursorRef.current + perSecond * elapsed / 1e3
8152
+ );
8153
+ const done = cursorRef.current >= length;
8154
+ commit(at, done);
8155
+ if (!done) schedule();
8156
+ };
8157
+ const schedule = () => {
8158
+ if (typeof window.requestAnimationFrame === "function") {
8159
+ const id2 = window.requestAnimationFrame(step);
8160
+ cancelStep = () => window.cancelAnimationFrame(id2);
8161
+ return;
8162
+ }
8163
+ const id = setTimeout(step, FALLBACK_STEP_MS);
8164
+ cancelStep = () => clearTimeout(id);
8165
+ };
8166
+ schedule();
8167
+ return () => {
8168
+ cancelled = true;
8169
+ cancelStep?.();
8170
+ };
8171
+ }, [text, immediate]);
8172
+ return text.slice(0, cut);
8173
+ }
7790
8174
  function getRecognitionCtor() {
7791
8175
  if (typeof window === "undefined") return null;
7792
8176
  const w = window;
@@ -8005,6 +8389,141 @@ function useSpeechInput({
8005
8389
  );
8006
8390
  return { supported, listening, interim, error, start, stop, toggle };
8007
8391
  }
8392
+ var SILENT_FAILURE_MS = 1500;
8393
+ function speechPlatform() {
8394
+ if (typeof window === "undefined") return null;
8395
+ const scope = window;
8396
+ const synth = scope.speechSynthesis;
8397
+ const Utterance = scope.SpeechSynthesisUtterance;
8398
+ if (!synth || typeof synth.speak !== "function") return null;
8399
+ if (typeof Utterance !== "function") return null;
8400
+ return { synth, Utterance };
8401
+ }
8402
+ function normaliseTag(tag) {
8403
+ return tag.trim().toLowerCase().replace(/_/g, "-");
8404
+ }
8405
+ function primaryOf(tag) {
8406
+ return normaliseTag(tag).split("-")[0] ?? "";
8407
+ }
8408
+ function pickVoice(voices, lang) {
8409
+ const wanted = normaliseTag(lang);
8410
+ if (wanted === "") return null;
8411
+ const primary = primaryOf(wanted);
8412
+ const exact = voices.filter((voice) => normaliseTag(voice.lang) === wanted);
8413
+ const related = voices.filter((voice) => primaryOf(voice.lang) === primary);
8414
+ return exact.find((voice) => voice.default) ?? exact[0] ?? related.find((voice) => voice.default) ?? related[0] ?? null;
8415
+ }
8416
+ var activeOwner = null;
8417
+ var activeStop = null;
8418
+ function stopSpeaking() {
8419
+ activeStop?.();
8420
+ }
8421
+ function useSpeechOutput({
8422
+ lang
8423
+ } = {}) {
8424
+ const [available, setAvailable] = useState(() => speechPlatform() !== null);
8425
+ const [speaking, setSpeaking] = useState(false);
8426
+ const utteranceRef = useRef(null);
8427
+ const watchdogRef = useRef(null);
8428
+ const ownerRef = useRef({});
8429
+ const stopRef = useRef(() => void 0);
8430
+ const langRef = useRef(lang);
8431
+ langRef.current = lang;
8432
+ const clearWatchdog = useCallback(() => {
8433
+ if (watchdogRef.current === null) return;
8434
+ clearTimeout(watchdogRef.current);
8435
+ watchdogRef.current = null;
8436
+ }, []);
8437
+ const settle = useCallback(() => {
8438
+ clearWatchdog();
8439
+ utteranceRef.current = null;
8440
+ if (activeOwner === ownerRef.current) {
8441
+ activeOwner = null;
8442
+ activeStop = null;
8443
+ }
8444
+ setSpeaking(false);
8445
+ }, [clearWatchdog]);
8446
+ const stop = useCallback(() => {
8447
+ const owned = utteranceRef.current !== null;
8448
+ settle();
8449
+ if (owned) speechPlatform()?.synth.cancel();
8450
+ }, [settle]);
8451
+ stopRef.current = stop;
8452
+ const speak = useCallback(
8453
+ (text) => {
8454
+ const platform = speechPlatform();
8455
+ if (!platform) {
8456
+ setAvailable(false);
8457
+ return;
8458
+ }
8459
+ const spoken = text.trim();
8460
+ if (spoken === "") return;
8461
+ if (activeStop && activeOwner !== ownerRef.current) activeStop();
8462
+ platform.synth.cancel();
8463
+ const utterance = new platform.Utterance(spoken);
8464
+ const wanted = langRef.current;
8465
+ if (wanted !== void 0 && wanted !== "") {
8466
+ const voice = pickVoice(platform.synth.getVoices(), wanted);
8467
+ utterance.lang = voice?.lang ?? wanted;
8468
+ if (voice) utterance.voice = voice;
8469
+ }
8470
+ const mine = () => utteranceRef.current === utterance;
8471
+ utterance.onstart = () => {
8472
+ if (!mine()) return;
8473
+ clearWatchdog();
8474
+ setSpeaking(true);
8475
+ };
8476
+ utterance.onend = () => {
8477
+ if (!mine()) return;
8478
+ settle();
8479
+ };
8480
+ utterance.onerror = (event) => {
8481
+ if (!mine()) return;
8482
+ const code = typeof event?.error === "string" ? event.error : "";
8483
+ settle();
8484
+ if (code !== "interrupted" && code !== "canceled") setAvailable(false);
8485
+ };
8486
+ utteranceRef.current = utterance;
8487
+ activeOwner = ownerRef.current;
8488
+ activeStop = () => {
8489
+ stopRef.current();
8490
+ };
8491
+ setSpeaking(true);
8492
+ platform.synth.speak(utterance);
8493
+ clearWatchdog();
8494
+ watchdogRef.current = setTimeout(() => {
8495
+ watchdogRef.current = null;
8496
+ if (!mine()) return;
8497
+ if (platform.synth.speaking) return;
8498
+ settle();
8499
+ setAvailable(false);
8500
+ }, SILENT_FAILURE_MS);
8501
+ },
8502
+ [clearWatchdog, settle]
8503
+ );
8504
+ const toggle = useCallback(
8505
+ (text) => {
8506
+ if (speaking) stop();
8507
+ else speak(text);
8508
+ },
8509
+ [speak, speaking, stop]
8510
+ );
8511
+ useEffect(() => {
8512
+ if (!speaking || typeof window === "undefined") return;
8513
+ const onHide = () => {
8514
+ stopRef.current();
8515
+ };
8516
+ window.addEventListener("pagehide", onHide);
8517
+ return () => window.removeEventListener("pagehide", onHide);
8518
+ }, [speaking]);
8519
+ useEffect(
8520
+ () => () => {
8521
+ stopRef.current();
8522
+ },
8523
+ []
8524
+ );
8525
+ return { supported: available, speaking, speak, stop, toggle };
8526
+ }
8008
8527
  var SWIPE_THRESHOLD = 40;
8009
8528
  function useSwipe({
8010
8529
  onSwipeLeft,
@@ -8272,6 +8791,115 @@ var EXPLORE_CSS = `
8272
8791
  height: auto;
8273
8792
  }
8274
8793
 
8794
+ /* ---- Comparison and pricing tables ----------------------------------------------------
8795
+ The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
8796
+ the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
8797
+ the page must not scroll sideways because of it, and it has to be readable on a 320px
8798
+ phone, which a four-column grid of 1px borders is not.
8799
+
8800
+ Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
8801
+ \`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
8802
+ cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
8803
+ of table from chaining to the page \u2014 the same rule the transcript needed. */
8804
+ .boff-explore-body .boff-explore-table {
8805
+ max-width: 100%;
8806
+ overflow-x: auto;
8807
+ overscroll-behavior-x: contain;
8808
+ margin: 0.75rem 0;
8809
+ border: 1px solid hsl(var(--border));
8810
+ border-radius: 0.75rem;
8811
+ background-color: hsl(var(--card));
8812
+ }
8813
+ .boff-explore-body .boff-explore-table:focus-visible {
8814
+ outline: 2px solid hsl(var(--ring));
8815
+ outline-offset: 2px;
8816
+ }
8817
+ .boff-explore-body .boff-explore-table table {
8818
+ /* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
8819
+ display: table;
8820
+ width: 100%;
8821
+ max-width: none;
8822
+ margin: 0;
8823
+ border-collapse: collapse;
8824
+ font-size: 0.8125rem;
8825
+ }
8826
+ .boff-explore-body .boff-explore-table th,
8827
+ .boff-explore-body .boff-explore-table td {
8828
+ border: 0;
8829
+ border-top: 1px solid hsl(var(--border));
8830
+ padding: 0.5rem 0.75rem;
8831
+ /* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
8832
+ table there reads against the grain of every other line on the page. */
8833
+ text-align: start;
8834
+ vertical-align: top;
8835
+ overflow-wrap: anywhere;
8836
+ /* Prices in a column line up under each other instead of drifting by digit width. */
8837
+ font-variant-numeric: tabular-nums;
8838
+ min-width: 6rem;
8839
+ }
8840
+ .boff-explore-body .boff-explore-table thead th {
8841
+ border-top: 0;
8842
+ background-color: hsl(var(--muted));
8843
+ color: hsl(var(--foreground));
8844
+ font-weight: 600;
8845
+ white-space: nowrap;
8846
+ }
8847
+ /* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
8848
+ the row's heading is what makes a price list scannable rather than a grid of strings. */
8849
+ .boff-explore-body .boff-explore-table tbody td:first-child {
8850
+ font-weight: 600;
8851
+ color: hsl(var(--card-foreground));
8852
+ }
8853
+
8854
+ /* Narrow: one card per row. Each cell prints its own column heading, so a value is never
8855
+ stranded without the question it answers, and the subject cell becomes the card's title.
8856
+ A media query rather than a measured width \u2014 these pages are serialised post-hydration,
8857
+ so anything measured here would ship to every phone as static HTML. */
8858
+ @media (max-width: 30rem) {
8859
+ .boff-explore-body .boff-explore-table {
8860
+ overflow-x: visible;
8861
+ }
8862
+ .boff-explore-body .boff-explore-table table,
8863
+ .boff-explore-body .boff-explore-table tbody,
8864
+ .boff-explore-body .boff-explore-table tr,
8865
+ .boff-explore-body .boff-explore-table td {
8866
+ display: block;
8867
+ width: 100%;
8868
+ min-width: 0;
8869
+ }
8870
+ .boff-explore-body .boff-explore-table thead {
8871
+ display: none;
8872
+ }
8873
+ .boff-explore-body .boff-explore-table tr {
8874
+ border-top: 1px solid hsl(var(--border));
8875
+ padding: 0.5rem 0;
8876
+ }
8877
+ .boff-explore-body .boff-explore-table tr:first-child {
8878
+ border-top: 0;
8879
+ }
8880
+ .boff-explore-body .boff-explore-table td {
8881
+ border: 0;
8882
+ padding: 0.125rem 0.75rem;
8883
+ }
8884
+ .boff-explore-body .boff-explore-table td[data-label]::before {
8885
+ content: attr(data-label);
8886
+ display: block;
8887
+ font-size: 0.6875rem;
8888
+ font-weight: 600;
8889
+ text-transform: uppercase;
8890
+ letter-spacing: 0.04em;
8891
+ color: hsl(var(--muted-foreground));
8892
+ }
8893
+ .boff-explore-body .boff-explore-table tbody td:first-child {
8894
+ font-size: 0.9375rem;
8895
+ padding-bottom: 0.25rem;
8896
+ }
8897
+ .boff-explore-body .boff-explore-table tbody td:first-child::before {
8898
+ /* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
8899
+ display: none;
8900
+ }
8901
+ }
8902
+
8275
8903
  /* ---- Ambient backdrop -----------------------------------------------------------------
8276
8904
  Two very low-opacity radial washes behind the welcome state, drifting slowly. Painted on
8277
8905
  a pseudo-element with pointer-events:none so it can never intercept a click \u2014 a
@@ -8443,7 +9071,7 @@ function isOwnElement(el, root) {
8443
9071
  function visualViewportHeight() {
8444
9072
  return window.visualViewport?.height ?? window.innerHeight;
8445
9073
  }
8446
- function clamp(value, min, max) {
9074
+ function clamp2(value, min, max) {
8447
9075
  return Math.min(Math.max(value, min), max);
8448
9076
  }
8449
9077
  function isRestoredNavigation() {
@@ -8508,14 +9136,196 @@ function isInlineImageAllowed(src, origins) {
8508
9136
  if (url.protocol !== "https:" || url.username || url.password) return false;
8509
9137
  return origins.has(url.origin);
8510
9138
  }
8511
- var InlineImageScopeContext = createContext(null);
9139
+ var REFERENCE_KIND_ORDER = [
9140
+ "CONCEPT",
9141
+ "DOCS",
9142
+ "PRICING",
9143
+ "USE_CASE",
9144
+ "PAGE"
9145
+ ];
9146
+ var PRODUCT_SUBDOMAINS = [
9147
+ "alphadocs.",
9148
+ "alphaapp.",
9149
+ "docs.",
9150
+ "alpha.",
9151
+ "app.",
9152
+ "www."
9153
+ ];
9154
+ function readCatalogUrl(raw) {
9155
+ let parsed;
9156
+ try {
9157
+ parsed = new URL(raw);
9158
+ } catch {
9159
+ return null;
9160
+ }
9161
+ if (parsed.protocol !== "https:") return null;
9162
+ const host = parsed.hostname.toLowerCase();
9163
+ const prefix = PRODUCT_SUBDOMAINS.find(
9164
+ (candidate) => host.startsWith(candidate)
9165
+ );
9166
+ const apex = prefix === void 0 ? host : host.slice(prefix.length);
9167
+ const port = parsed.port ? `:${parsed.port}` : "";
9168
+ return {
9169
+ origin: `https://${apex}${port}`,
9170
+ prefix,
9171
+ pathname: parsed.pathname
9172
+ };
9173
+ }
9174
+ function referenceKindOf(url, origins) {
9175
+ const parsed = readCatalogUrl(url);
9176
+ if (parsed === null || !origins.has(parsed.origin)) return null;
9177
+ if (parsed.prefix === "docs." || parsed.prefix === "alphadocs.")
9178
+ return "DOCS";
9179
+ const segment = parsed.pathname.toLowerCase().split("/").filter(Boolean)[0] ?? "";
9180
+ if (segment === "concept" || segment === "concepts") return "CONCEPT";
9181
+ if (segment === "docs") return "DOCS";
9182
+ if (segment === "pricing" || segment === "plans") return "PRICING";
9183
+ if (segment === "use-case" || segment === "use-cases" || segment === "solutions") {
9184
+ return "USE_CASE";
9185
+ }
9186
+ return "PAGE";
9187
+ }
9188
+ function productNamesByOrigin(products) {
9189
+ const names = /* @__PURE__ */ new Map();
9190
+ for (const product of products) {
9191
+ let url;
9192
+ try {
9193
+ url = new URL(product.website);
9194
+ } catch {
9195
+ continue;
9196
+ }
9197
+ if (url.protocol !== "https:") continue;
9198
+ const apex = url.hostname.replace(/^www\./, "");
9199
+ const port = url.port ? `:${url.port}` : "";
9200
+ const key = `https://${apex}${port}`;
9201
+ if (!names.has(key)) names.set(key, product.name);
9202
+ }
9203
+ return names;
9204
+ }
9205
+ function summariseReferences(references, origins, productNames) {
9206
+ const counts = /* @__PURE__ */ new Map();
9207
+ const products = [];
9208
+ for (const reference of references) {
9209
+ const kind = referenceKindOf(reference.url, origins);
9210
+ if (kind !== null) counts.set(kind, (counts.get(kind) ?? 0) + 1);
9211
+ const parsed = readCatalogUrl(reference.url);
9212
+ const name = (parsed === null ? void 0 : productNames.get(parsed.origin)) ?? (reference.product || null);
9213
+ if (name != null && !products.includes(name)) products.push(name);
9214
+ }
9215
+ return {
9216
+ total: references.length,
9217
+ kinds: REFERENCE_KIND_ORDER.filter((kind) => counts.has(kind)).map(
9218
+ (kind) => ({ kind, count: counts.get(kind) ?? 0 })
9219
+ ),
9220
+ products
9221
+ };
9222
+ }
9223
+ var CODE_FENCE = /^\s*(?:```|~~~)/;
9224
+ var TABLE_ROW_LINE = /^\s{0,3}\|/;
9225
+ var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
9226
+ function readableLine(raw, mode) {
9227
+ let line = raw;
9228
+ line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
9229
+ line = mode === "speech" ? (
9230
+ // A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
9231
+ // prompt asks for two to five links in every answer. The label IS the words.
9232
+ line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
9233
+ ) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
9234
+ line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
9235
+ line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
9236
+ line = line.replace(/^\s{0,3}>\s?/, "");
9237
+ if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
9238
+ line = line.replace(/`([^`]+)`/g, "$1");
9239
+ line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
9240
+ line = line.replace(/__([^_]+)__/g, "$1");
9241
+ line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
9242
+ line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
9243
+ return line.replace(/[ \t]+$/, "");
9244
+ }
9245
+ function tableCells(row) {
9246
+ const trimmed = row.trim();
9247
+ const cells = [];
9248
+ let cell = "";
9249
+ for (let at = 0; at < trimmed.length; at += 1) {
9250
+ const char = trimmed[at] ?? "";
9251
+ if (char === "\\") {
9252
+ cell += char + (trimmed[at + 1] ?? "");
9253
+ at += 1;
9254
+ continue;
9255
+ }
9256
+ if (char === "|") {
9257
+ cells.push(cell.trim());
9258
+ cell = "";
9259
+ continue;
9260
+ }
9261
+ cell += char;
9262
+ }
9263
+ cells.push(cell.trim());
9264
+ if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
9265
+ if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
9266
+ return cells;
9267
+ }
9268
+ function asSentence(text) {
9269
+ return /[.!?]$/.test(text) ? text : `${text}.`;
9270
+ }
9271
+ function spokenTable(rows) {
9272
+ if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
9273
+ const labels = tableCells(rows[0] ?? "");
9274
+ const spoken = [];
9275
+ for (const row of rows.slice(2)) {
9276
+ const parts = [];
9277
+ tableCells(row).forEach((value, column) => {
9278
+ if (value === "") return;
9279
+ const label = column === 0 ? "" : labels[column] ?? "";
9280
+ parts.push(label === "" ? value : `${label}: ${value}`);
9281
+ });
9282
+ if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
9283
+ }
9284
+ return spoken;
9285
+ }
9286
+ function readableText(markdown, mode) {
9287
+ const lines = [];
9288
+ let inFence = false;
9289
+ let table = [];
9290
+ const flushTable = () => {
9291
+ if (table.length === 0) return;
9292
+ for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
9293
+ table = [];
9294
+ };
9295
+ for (const raw of markdown.split("\n")) {
9296
+ if (CODE_FENCE.test(raw)) {
9297
+ flushTable();
9298
+ inFence = !inFence;
9299
+ continue;
9300
+ }
9301
+ if (inFence) {
9302
+ lines.push(raw);
9303
+ continue;
9304
+ }
9305
+ if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
9306
+ table.push(raw);
9307
+ continue;
9308
+ }
9309
+ flushTable();
9310
+ lines.push(readableLine(raw, mode));
9311
+ }
9312
+ flushTable();
9313
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
9314
+ }
9315
+ function answerPlainText(markdown) {
9316
+ return readableText(markdown, "clipboard");
9317
+ }
9318
+ function answerSpeechText(markdown) {
9319
+ return readableText(markdown, "speech");
9320
+ }
9321
+ var AnswerScopeContext = createContext(null);
8512
9322
  var InsideLinkContext = createContext(false);
8513
9323
  function AnswerLink(props) {
8514
9324
  return /* @__PURE__ */ jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsx(MarkdownLink, { ...props }) });
8515
9325
  }
8516
9326
  var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
8517
9327
  function InlineConceptImage({ src, alt }) {
8518
- const scope = useContext(InlineImageScopeContext);
9328
+ const scope = useContext(AnswerScopeContext);
8519
9329
  const insideLink = useContext(InsideLinkContext);
8520
9330
  if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
8521
9331
  return null;
@@ -8563,9 +9373,70 @@ function InlineConceptImage({ src, alt }) {
8563
9373
  }
8564
9374
  ) });
8565
9375
  }
9376
+ function markdownText(node) {
9377
+ if (!node) return "";
9378
+ if (typeof node.value === "string") return node.value;
9379
+ return (node.children ?? []).map(markdownText).join("");
9380
+ }
9381
+ function hasHeaderCells(node) {
9382
+ return (node?.children ?? []).some((cell) => cell.tagName === "th");
9383
+ }
9384
+ function findHeaderRow(node) {
9385
+ for (const child of node?.children ?? []) {
9386
+ if (child.tagName === "tr" && hasHeaderCells(child)) return child;
9387
+ const nested = findHeaderRow(child);
9388
+ if (nested !== null) return nested;
9389
+ }
9390
+ return null;
9391
+ }
9392
+ function tableColumnLabels(node) {
9393
+ const row = findHeaderRow(node);
9394
+ if (row === null) return [];
9395
+ return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
9396
+ }
9397
+ var TableColumnsContext = createContext([]);
9398
+ function AnswerTable({ node, children }) {
9399
+ const scope = useContext(AnswerScopeContext);
9400
+ const table = /* @__PURE__ */ jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsx("table", { children }) });
9401
+ if (!scope) return table;
9402
+ return /* @__PURE__ */ jsx(
9403
+ "div",
9404
+ {
9405
+ "data-boff-explore": "table",
9406
+ role: "region",
9407
+ "aria-label": scope.strings.answerTable,
9408
+ tabIndex: 0,
9409
+ className: "boff-explore-table",
9410
+ children: table
9411
+ }
9412
+ );
9413
+ }
9414
+ function AnswerTableRow({
9415
+ node,
9416
+ children,
9417
+ ...props
9418
+ }) {
9419
+ const labels = useContext(TableColumnsContext);
9420
+ if (labels.length === 0 || hasHeaderCells(node)) {
9421
+ return /* @__PURE__ */ jsx("tr", { ...props, children });
9422
+ }
9423
+ let column = 0;
9424
+ const cells = Children.map(children, (cell) => {
9425
+ if (!isValidElement(cell)) return cell;
9426
+ const label = labels[column] ?? "";
9427
+ column += 1;
9428
+ if (label === "") return cell;
9429
+ return cloneElement(cell, {
9430
+ "data-label": label
9431
+ });
9432
+ });
9433
+ return /* @__PURE__ */ jsx("tr", { ...props, children: cells });
9434
+ }
8566
9435
  var ANSWER_MARKDOWN_COMPONENTS = {
8567
9436
  a: AnswerLink,
8568
- img: InlineConceptImage
9437
+ img: InlineConceptImage,
9438
+ table: AnswerTable,
9439
+ tr: AnswerTableRow
8569
9440
  };
8570
9441
  function ReferenceCard({
8571
9442
  reference,
@@ -8905,6 +9776,148 @@ function AnswerFeedback({
8905
9776
  }
8906
9777
  );
8907
9778
  }
9779
+ var PROVENANCE_CHIP = "inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-muted/60 px-2 py-0.5 text-[11px] leading-5 text-muted-foreground";
9780
+ function ProvenanceChips({
9781
+ strings: T,
9782
+ provenance
9783
+ }) {
9784
+ if (provenance.total === 0) return null;
9785
+ const kindLabels = {
9786
+ CONCEPT: T.sourceKindConcept,
9787
+ DOCS: T.sourceKindDocs,
9788
+ PRICING: T.sourceKindPricing,
9789
+ USE_CASE: T.sourceKindUseCase,
9790
+ PAGE: T.sourceKindPage
9791
+ };
9792
+ return /* @__PURE__ */ jsxs(
9793
+ "ul",
9794
+ {
9795
+ "data-boff-explore": "provenance",
9796
+ "aria-label": T.groundedIn,
9797
+ className: "flex flex-wrap items-center gap-1.5",
9798
+ children: [
9799
+ /* @__PURE__ */ jsxs("li", { "data-boff-explore": "provenance-count", className: PROVENANCE_CHIP, children: [
9800
+ /* @__PURE__ */ jsx(BookOpenText, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
9801
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: provenance.total === 1 ? T.sourceCountOne : fill(T.sourceCountMany, { count: String(provenance.total) }) })
9802
+ ] }),
9803
+ provenance.kinds.map(({ kind, count }) => /* @__PURE__ */ jsx(
9804
+ "li",
9805
+ {
9806
+ "data-boff-explore": "provenance-kind",
9807
+ "data-kind": kind,
9808
+ "data-count": count,
9809
+ className: PROVENANCE_CHIP,
9810
+ children: /* @__PURE__ */ jsx("span", { className: "truncate", children: count > 1 ? `${kindLabels[kind]} \xB7 ${count}` : kindLabels[kind] })
9811
+ },
9812
+ kind
9813
+ )),
9814
+ provenance.products.map((name) => /* @__PURE__ */ jsx(
9815
+ "li",
9816
+ {
9817
+ "data-boff-explore": "provenance-product",
9818
+ className: PROVENANCE_CHIP,
9819
+ children: /* @__PURE__ */ jsx("span", { className: "truncate", children: name })
9820
+ },
9821
+ name
9822
+ ))
9823
+ ]
9824
+ }
9825
+ );
9826
+ }
9827
+ var COPY_FEEDBACK_MS = 2500;
9828
+ var ANSWER_ACTION_PILL = "inline-flex max-w-full items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
9829
+ var ANSWER_ACTION_IDLE = "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent hover:text-foreground";
9830
+ var ANSWER_ACTION_ACTIVE = "border-primary/40 bg-accent text-primary";
9831
+ function ReadAloudButton({
9832
+ strings: T,
9833
+ text,
9834
+ lang
9835
+ }) {
9836
+ const speech = useSpeechOutput({ lang });
9837
+ if (!speech.supported) return null;
9838
+ const label = speech.speaking ? T.stopReading : T.readAloud;
9839
+ return /* @__PURE__ */ jsxs(
9840
+ "button",
9841
+ {
9842
+ "data-boff-explore": "read-aloud",
9843
+ "data-speaking": speech.speaking ? "true" : "false",
9844
+ type: "button",
9845
+ title: label,
9846
+ "aria-pressed": speech.speaking,
9847
+ onClick: () => speech.toggle(text),
9848
+ className: cn(
9849
+ ANSWER_ACTION_PILL,
9850
+ speech.speaking ? ANSWER_ACTION_ACTIVE : ANSWER_ACTION_IDLE
9851
+ ),
9852
+ children: [
9853
+ /* @__PURE__ */ jsx(Volume2, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
9854
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
9855
+ ]
9856
+ }
9857
+ );
9858
+ }
9859
+ function AnswerActions({
9860
+ strings: T,
9861
+ text,
9862
+ speechText,
9863
+ speechLang
9864
+ }) {
9865
+ const [outcome, setOutcome] = useState("idle");
9866
+ const resetRef = useRef(null);
9867
+ useEffect(
9868
+ () => () => {
9869
+ if (resetRef.current !== null) clearTimeout(resetRef.current);
9870
+ },
9871
+ []
9872
+ );
9873
+ const copy = useCallback(async () => {
9874
+ let copied = false;
9875
+ try {
9876
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
9877
+ if (typeof clipboard?.writeText === "function") {
9878
+ await clipboard.writeText(text);
9879
+ copied = true;
9880
+ }
9881
+ } catch {
9882
+ copied = false;
9883
+ }
9884
+ setOutcome(copied ? "copied" : "failed");
9885
+ if (resetRef.current !== null) clearTimeout(resetRef.current);
9886
+ resetRef.current = setTimeout(() => setOutcome("idle"), COPY_FEEDBACK_MS);
9887
+ }, [text]);
9888
+ const label = outcome === "copied" ? T.copied : outcome === "failed" ? T.copyFailed : T.copyAnswer;
9889
+ return /* @__PURE__ */ jsxs(
9890
+ "div",
9891
+ {
9892
+ "data-boff-explore": "answer-actions",
9893
+ className: "flex flex-wrap items-center gap-2 pt-0.5",
9894
+ children: [
9895
+ /* @__PURE__ */ jsxs(
9896
+ "button",
9897
+ {
9898
+ "data-boff-explore": "copy-answer",
9899
+ "data-outcome": outcome,
9900
+ type: "button",
9901
+ title: label,
9902
+ onClick: () => {
9903
+ void copy();
9904
+ },
9905
+ className: cn(
9906
+ ANSWER_ACTION_PILL,
9907
+ outcome === "failed" ? "border-destructive/40 bg-card text-destructive" : ANSWER_ACTION_IDLE
9908
+ ),
9909
+ children: [
9910
+ outcome === "copied" ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
9911
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
9912
+ ]
9913
+ }
9914
+ ),
9915
+ /* @__PURE__ */ jsx(ReadAloudButton, { strings: T, text: speechText, lang: speechLang }),
9916
+ /* @__PURE__ */ jsx("span", { "data-boff-explore": "copy-status", role: "status", className: "sr-only", children: outcome === "idle" ? "" : label })
9917
+ ]
9918
+ }
9919
+ );
9920
+ }
8908
9921
  function UserBubble({
8909
9922
  strings: T,
8910
9923
  message
@@ -8950,6 +9963,8 @@ function AssistantBubble({
8950
9963
  message,
8951
9964
  activity,
8952
9965
  imageOrigins,
9966
+ productNames,
9967
+ speechLang,
8953
9968
  onNavigate,
8954
9969
  anchorRef,
8955
9970
  onFollow,
@@ -8960,6 +9975,7 @@ function AssistantBubble({
8960
9975
  const body = message.content || message.partialContent || "";
8961
9976
  const failed = message.status === "FAILED";
8962
9977
  const completed = message.status === "COMPLETED";
9978
+ const visible = useProgressiveReveal(body, !streaming);
8963
9979
  const [failedImages, setFailedImages] = useState(
8964
9980
  () => /* @__PURE__ */ new Set()
8965
9981
  );
@@ -8968,7 +9984,7 @@ function AssistantBubble({
8968
9984
  (current) => current.has(src) ? current : new Set(current).add(src)
8969
9985
  );
8970
9986
  }, []);
8971
- const imageScope = useMemo(
9987
+ const answerScope = useMemo(
8972
9988
  () => ({
8973
9989
  strings: T,
8974
9990
  enabled: completed,
@@ -8978,6 +9994,19 @@ function AssistantBubble({
8978
9994
  }),
8979
9995
  [T, completed, imageOrigins, failedImages, markImageFailed]
8980
9996
  );
9997
+ const provenance = useMemo(
9998
+ () => summariseReferences(message.references, imageOrigins, productNames),
9999
+ [message.references, imageOrigins, productNames]
10000
+ );
10001
+ const copyText = useMemo(
10002
+ () => answerPlainText(message.content),
10003
+ [message.content]
10004
+ );
10005
+ const speechText = useMemo(
10006
+ () => answerSpeechText(message.content),
10007
+ [message.content]
10008
+ );
10009
+ const grounded = completed && message.answered !== false;
8981
10010
  return /* @__PURE__ */ jsxs(
8982
10011
  "div",
8983
10012
  {
@@ -9023,10 +10052,11 @@ function AssistantBubble({
9023
10052
  ]
9024
10053
  }
9025
10054
  ) : null,
9026
- body ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
9027
- /* @__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 }) }) }),
10055
+ visible ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
10056
+ /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(AnswerScopeContext.Provider, { value: answerScope, children: /* @__PURE__ */ jsx(Markdown, { components: ANSWER_MARKDOWN_COMPONENTS, children: visible }) }) }),
9028
10057
  streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
9029
10058
  ] }) : null,
10059
+ grounded && provenance.total > 0 ? /* @__PURE__ */ jsx(ProvenanceChips, { strings: T, provenance }) : null,
9030
10060
  streaming ? /* @__PURE__ */ jsx(ThinkingIndicator, { activity }) : null,
9031
10061
  failed && !body ? /* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tl-md border border-border bg-muted px-4 py-3 text-sm text-muted-foreground", children: T.answerDidNotArrive }) : null,
9032
10062
  message.references.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
@@ -9066,6 +10096,15 @@ function AssistantBubble({
9066
10096
  question
9067
10097
  )) })
9068
10098
  ] }) : null,
10099
+ completed && copyText !== "" ? /* @__PURE__ */ jsx(
10100
+ AnswerActions,
10101
+ {
10102
+ strings: T,
10103
+ text: copyText,
10104
+ speechText,
10105
+ speechLang
10106
+ }
10107
+ ) : null,
9069
10108
  completed && onFeedback ? /* @__PURE__ */ jsx(
9070
10109
  AnswerFeedback,
9071
10110
  {
@@ -9278,6 +10317,96 @@ function ProductStrip({
9278
10317
  }
9279
10318
  );
9280
10319
  }
10320
+ var MAX_CONCEPT_CARDS = 6;
10321
+ function ConceptStrip({
10322
+ strings: T,
10323
+ highlights,
10324
+ onAsk,
10325
+ disabled
10326
+ }) {
10327
+ const [failed, setFailed] = useState(() => /* @__PURE__ */ new Set());
10328
+ const markFailed = useCallback((src) => {
10329
+ setFailed(
10330
+ (current) => current.has(src) ? current : new Set(current).add(src)
10331
+ );
10332
+ }, []);
10333
+ const visible = highlights.filter(
10334
+ (highlight) => highlight.imageUrl !== "" && !failed.has(highlight.imageUrl)
10335
+ ).slice(0, MAX_CONCEPT_CARDS);
10336
+ if (visible.length === 0) return null;
10337
+ return /* @__PURE__ */ jsxs(
10338
+ "div",
10339
+ {
10340
+ "data-boff-explore": "concept-strip-block",
10341
+ className: "boff-explore-rise boff-explore-rise-1 mt-6",
10342
+ children: [
10343
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: T.conceptStripTitle }),
10344
+ /* @__PURE__ */ jsx(
10345
+ "ul",
10346
+ {
10347
+ "data-boff-explore": "concept-strip",
10348
+ "aria-label": T.conceptStripLabel,
10349
+ className: "boff-explore-strip flex min-w-0 snap-x gap-3 overflow-x-auto pb-1",
10350
+ children: visible.map((highlight) => /* @__PURE__ */ jsx(
10351
+ "li",
10352
+ {
10353
+ className: "shrink-0 snap-start",
10354
+ children: /* @__PURE__ */ jsxs(
10355
+ "button",
10356
+ {
10357
+ "data-boff-explore": "concept-card",
10358
+ "data-product": highlight.product,
10359
+ type: "button",
10360
+ disabled,
10361
+ "aria-label": fill(T.conceptAsk, {
10362
+ title: highlight.title,
10363
+ product: highlight.productName
10364
+ }),
10365
+ onClick: () => onAsk(
10366
+ fill(T.conceptAskQuestion, {
10367
+ title: highlight.title,
10368
+ product: highlight.productName
10369
+ })
10370
+ ),
10371
+ className: "group flex w-40 flex-col overflow-hidden rounded-xl border border-border bg-card text-left 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 sm:w-48",
10372
+ children: [
10373
+ /* @__PURE__ */ jsxs("span", { className: "relative block aspect-[16/9] w-full overflow-hidden bg-muted", children: [
10374
+ /* @__PURE__ */ jsx(
10375
+ "img",
10376
+ {
10377
+ src: highlight.imageUrl,
10378
+ alt: highlight.alt,
10379
+ loading: "lazy",
10380
+ decoding: "async",
10381
+ onError: () => markFailed(highlight.imageUrl),
10382
+ className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
10383
+ }
10384
+ ),
10385
+ /* @__PURE__ */ jsx(
10386
+ "span",
10387
+ {
10388
+ "aria-hidden": "true",
10389
+ className: "pointer-events-none absolute left-1.5 top-1.5 rounded-full border border-border bg-background/90 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-foreground",
10390
+ children: T.conceptIllustration
10391
+ }
10392
+ )
10393
+ ] }),
10394
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5 p-2.5", children: [
10395
+ /* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-xs font-semibold leading-snug text-card-foreground", children: highlight.title }),
10396
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: highlight.productName })
10397
+ ] })
10398
+ ]
10399
+ }
10400
+ )
10401
+ },
10402
+ `${highlight.product}:${highlight.url}`
10403
+ ))
10404
+ }
10405
+ )
10406
+ ]
10407
+ }
10408
+ );
10409
+ }
9281
10410
  function DisclaimerDialog({
9282
10411
  open,
9283
10412
  onClose,
@@ -9384,6 +10513,7 @@ function WelcomeState({
9384
10513
  body,
9385
10514
  prompts,
9386
10515
  products,
10516
+ conceptHighlights,
9387
10517
  focusProduct,
9388
10518
  onPrompt,
9389
10519
  onFocus,
@@ -9398,6 +10528,15 @@ function WelcomeState({
9398
10528
  /* @__PURE__ */ jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
9399
10529
  /* @__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 })
9400
10530
  ] }),
10531
+ /* @__PURE__ */ jsx(
10532
+ ConceptStrip,
10533
+ {
10534
+ strings: T,
10535
+ highlights: conceptHighlights,
10536
+ onAsk: onPrompt,
10537
+ disabled
10538
+ }
10539
+ ),
9401
10540
  /* @__PURE__ */ jsx("ul", { className: "boff-explore-rise boff-explore-rise-1 mt-6 grid grid-cols-3 gap-2 sm:mt-7 sm:gap-2.5", children: CAPABILITIES.map(({ icon: Icon, body: capBody }, i) => /* @__PURE__ */ jsxs(
9402
10541
  "li",
9403
10542
  {
@@ -9459,6 +10598,7 @@ function ExplorePage({
9459
10598
  );
9460
10599
  const T = useMemo(() => exploreStrings(activeLocale), [activeLocale]);
9461
10600
  const rtl = isRtlLocale(activeLocale);
10601
+ const speechLang = speechLocaleOf(activeLocale);
9462
10602
  const {
9463
10603
  phase,
9464
10604
  catalog,
@@ -9543,6 +10683,10 @@ function ExplorePage({
9543
10683
  () => inlineImageOrigins(catalog.products),
9544
10684
  [catalog.products]
9545
10685
  );
10686
+ const productNames = useMemo(
10687
+ () => productNamesByOrigin(catalog.products),
10688
+ [catalog.products]
10689
+ );
9546
10690
  useEffect(() => {
9547
10691
  const el = textareaRef.current;
9548
10692
  if (!el) return;
@@ -9559,11 +10703,11 @@ function ExplorePage({
9559
10703
  const layoutHeight = window.innerHeight;
9560
10704
  if (layoutHeight === 0) return;
9561
10705
  const top = Math.round(
9562
- clamp(el.getBoundingClientRect().top, 0, layoutHeight)
10706
+ clamp2(el.getBoundingClientRect().top, 0, layoutHeight)
9563
10707
  );
9564
10708
  const viewport = window.visualViewport;
9565
10709
  const inset = viewport ? Math.round(
9566
- clamp(
10710
+ clamp2(
9567
10711
  layoutHeight - viewport.height - viewport.offsetTop,
9568
10712
  0,
9569
10713
  layoutHeight
@@ -9844,6 +10988,7 @@ function ExplorePage({
9844
10988
  const text = draft.trim();
9845
10989
  if (!text || overLimit || busy || !canSend) return;
9846
10990
  stopDictation();
10991
+ stopSpeaking();
9847
10992
  setDraft("");
9848
10993
  stickToBottomRef.current = true;
9849
10994
  void send(text, toPayload(stagedRef.current));
@@ -9854,6 +10999,7 @@ function ExplorePage({
9854
10999
  const sendPrompt = useCallback(
9855
11000
  (prompt) => {
9856
11001
  if (!canSend || busy) return;
11002
+ stopSpeaking();
9857
11003
  setDraft("");
9858
11004
  stickToBottomRef.current = true;
9859
11005
  void send(prompt, toPayload(stagedRef.current));
@@ -10074,6 +11220,7 @@ function ExplorePage({
10074
11220
  body: resolvedBody,
10075
11221
  prompts,
10076
11222
  products: catalog.products,
11223
+ conceptHighlights: catalog.conceptHighlights,
10077
11224
  focusProduct,
10078
11225
  onPrompt: sendPrompt,
10079
11226
  onFocus: setFocusProduct,
@@ -10087,6 +11234,8 @@ function ExplorePage({
10087
11234
  message,
10088
11235
  activity,
10089
11236
  imageOrigins,
11237
+ productNames,
11238
+ speechLang,
10090
11239
  onNavigate,
10091
11240
  anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
10092
11241
  onFollow: (kind, url) => recordClick(message.id, kind, url),
@@ -15048,6 +16197,6 @@ function StoreProductDetailPage({
15048
16197
  ] });
15049
16198
  }
15050
16199
 
15051
- export { BlogListPage, BlogPostPage, Button, CONCEPTS_PATH, CONCEPT_ILLUSTRATION_NOTICE, CONCEPT_PATH_PREFIX, CareersApplyForm, CareersSubmissionForm, CaseStudiesListPage, CaseStudyPage, ChangelogPage, ConceptDetailPage, ConceptsPage, ContactPage, ContentRenderer, ContractsPage, DEFAULT_CONTACT_CATEGORIES, DEFAULT_EXPLORE_EXAMPLE_PROMPTS, ECOSYSTEM_PRODUCTS, EXPLORE_CATALOG_QUERY, EXPLORE_LOCALES, EXPLORE_MESSAGE_QUERY, EXPLORE_RECAPTCHA_ACTION, ElectronDownloadLink, ExploreCta, ExplorePage, FALLBACK_EXPLORE_LIMITS, Input, Label, LaunchCountdown, Markdown, NewsletterForm, NewsletterPage, PageHead, PartnersPage, PressKitPage, PricingPage, PricingSection, ProblemsSolvedSection, ProductCard, ProductProvider, ResourceLinks, RybbitAnalytics, SEND_EXPLORE_MESSAGE_MUTATION, Select, SocialLinks, StarRating, StoreBrowsePage, StoreHomePage, StoreProductDetailPage, Textarea, TypeBadge, UnsubscribePage, WaitlistForm, WaitlistPage, WebSDKClient, WebSDKProvider, WebsiteSwitcher, buttonVariants, classifyExploreError, cn, conceptPath, detectElectronPlatform, fetchPublicJobPosting, fetchPublicJobPostings, fetchPublicJobPostingsForSubmission, formatDownloads, formatPrice2 as formatPrice, getConceptBySlug, getRecaptchaV3Token, isValidUrl, loadRecaptchaV3, useContentPage, useContentPages, useExploreChat, useProduct, useProductConfig, usePublicJobPosting, usePublicJobPostings, usePublicJobPostingsForSubmission, useWebSDK, useWebSDKConfig };
16200
+ export { BlogListPage, BlogPostPage, Button, CONCEPTS_PATH, CONCEPT_ILLUSTRATION_NOTICE, CONCEPT_PATH_PREFIX, CareersApplyForm, CareersSubmissionForm, CaseStudiesListPage, CaseStudyPage, ChangelogPage, ConceptDetailPage, ConceptsPage, ContactPage, ContentRenderer, ContractsPage, DEFAULT_CONTACT_CATEGORIES, DEFAULT_EXPLORE_EXAMPLE_PROMPTS, ECOSYSTEM_PRODUCTS, EXPLORE_CATALOG_QUERY, EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS, EXPLORE_LOCALES, EXPLORE_MESSAGE_QUERY, EXPLORE_RECAPTCHA_ACTION, ElectronDownloadLink, ExploreCta, ExplorePage, FALLBACK_EXPLORE_LIMITS, Input, Label, LaunchCountdown, Markdown, NewsletterForm, NewsletterPage, PageHead, PartnersPage, PressKitPage, PricingPage, PricingSection, ProblemsSolvedSection, ProductCard, ProductProvider, ResourceLinks, RybbitAnalytics, SEND_EXPLORE_MESSAGE_MUTATION, Select, SocialLinks, StarRating, StoreBrowsePage, StoreHomePage, StoreProductDetailPage, Textarea, TypeBadge, UnsubscribePage, WaitlistForm, WaitlistPage, WebSDKClient, WebSDKProvider, WebsiteSwitcher, buttonVariants, classifyExploreError, cn, conceptPath, detectElectronPlatform, fetchPublicJobPosting, fetchPublicJobPostings, fetchPublicJobPostingsForSubmission, formatDownloads, formatPrice2 as formatPrice, getConceptBySlug, getRecaptchaV3Token, isValidUrl, loadRecaptchaV3, speechLocaleOf, stopSpeaking, useContentPage, useContentPages, useExploreChat, useProduct, useProductConfig, useProgressiveReveal, usePublicJobPosting, usePublicJobPostings, usePublicJobPostingsForSubmission, useSpeechOutput, useWebSDK, useWebSDKConfig };
15052
16201
  //# sourceMappingURL=index.mjs.map
15053
16202
  //# sourceMappingURL=index.mjs.map