@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.js CHANGED
@@ -2438,16 +2438,16 @@ function useRateLimit(maxAttempts, windowMinutes) {
2438
2438
  }
2439
2439
  }, [maxAttempts]);
2440
2440
  function recordAttempt() {
2441
- const now = Date.now();
2441
+ const now2 = Date.now();
2442
2442
  const state = rateLimitRef.current;
2443
- if (state && now > state.resetTime) {
2443
+ if (state && now2 > state.resetTime) {
2444
2444
  rateLimitRef.current = null;
2445
2445
  }
2446
2446
  const current = rateLimitRef.current;
2447
2447
  if (!current) {
2448
2448
  rateLimitRef.current = {
2449
2449
  attempts: 1,
2450
- resetTime: now + windowMinutes * 60 * 1e3
2450
+ resetTime: now2 + windowMinutes * 60 * 1e3
2451
2451
  };
2452
2452
  return false;
2453
2453
  }
@@ -3542,16 +3542,16 @@ function useRateLimit2(maxAttempts, windowMinutes) {
3542
3542
  const [isLimited, setIsLimited] = React.useState(false);
3543
3543
  const [resetTime, setResetTime] = React.useState(null);
3544
3544
  function recordAttempt() {
3545
- const now = Date.now();
3545
+ const now2 = Date.now();
3546
3546
  const state = rateLimitRef.current;
3547
- if (state && now > state.resetTime) {
3547
+ if (state && now2 > state.resetTime) {
3548
3548
  rateLimitRef.current = null;
3549
3549
  }
3550
3550
  const current = rateLimitRef.current;
3551
3551
  if (!current) {
3552
3552
  rateLimitRef.current = {
3553
3553
  attempts: 1,
3554
- resetTime: now + windowMinutes * 60 * 1e3
3554
+ resetTime: now2 + windowMinutes * 60 * 1e3
3555
3555
  };
3556
3556
  return false;
3557
3557
  }
@@ -4585,8 +4585,8 @@ function LaunchCountdown({
4585
4585
  launchedMessage = "We have launched!",
4586
4586
  className
4587
4587
  }) {
4588
- const [now, setNow] = React.useState(() => Date.now());
4589
- const remainingMs = launchDate - now;
4588
+ const [now2, setNow] = React.useState(() => Date.now());
4589
+ const remainingMs = launchDate - now2;
4590
4590
  const hasLaunched = remainingMs <= 0;
4591
4591
  React.useEffect(() => {
4592
4592
  if (hasLaunched) return;
@@ -6004,6 +6004,23 @@ var EN = {
6004
6004
  nextSuggestion: "Next suggestion",
6005
6005
  conceptIllustration: "Concept illustration",
6006
6006
  opensInNewTab: "opens in a new tab",
6007
+ conceptStripTitle: "Or start from a concept",
6008
+ conceptStripLabel: "Concepts you can ask about",
6009
+ conceptAsk: "Ask about {title} in {product}",
6010
+ conceptAskQuestion: "How does {title} work in {product}?",
6011
+ copyAnswer: "Copy answer",
6012
+ copyFailed: "Couldn't copy",
6013
+ readAloud: "Read aloud",
6014
+ stopReading: "Stop reading",
6015
+ answerTable: "Table",
6016
+ groundedIn: "What this answer is based on",
6017
+ sourceCountOne: "1 source",
6018
+ sourceCountMany: "{count} sources",
6019
+ sourceKindConcept: "Concept",
6020
+ sourceKindDocs: "Documentation",
6021
+ sourceKindPricing: "Pricing",
6022
+ sourceKindUseCase: "Use case",
6023
+ sourceKindPage: "Product page",
6007
6024
  seoTitle: "Describe a problem, get an answer",
6008
6025
  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.",
6009
6026
  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.",
@@ -6072,6 +6089,23 @@ var ZH = {
6072
6089
  nextSuggestion: "\u4E0B\u4E00\u4E2A\u5EFA\u8BAE",
6073
6090
  conceptIllustration: "\u6982\u5FF5\u793A\u610F\u56FE",
6074
6091
  opensInNewTab: "\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00",
6092
+ conceptStripTitle: "\u6216\u8005\u4ECE\u4E00\u4E2A\u6982\u5FF5\u5F00\u59CB",
6093
+ conceptStripLabel: "\u4F60\u53EF\u4EE5\u8BE2\u95EE\u7684\u6982\u5FF5",
6094
+ conceptAsk: "\u8BE2\u95EE {product} \u4E2D\u7684 {title}",
6095
+ conceptAskQuestion: "{product} \u7684 {title} \u662F\u600E\u4E48\u8FD0\u4F5C\u7684\uFF1F",
6096
+ copyAnswer: "\u590D\u5236\u56DE\u7B54",
6097
+ copyFailed: "\u590D\u5236\u5931\u8D25",
6098
+ readAloud: "\u6717\u8BFB",
6099
+ stopReading: "\u505C\u6B62\u6717\u8BFB",
6100
+ answerTable: "\u8868\u683C",
6101
+ groundedIn: "\u8FD9\u4E2A\u56DE\u7B54\u7684\u4F9D\u636E",
6102
+ sourceCountOne: "1 \u4E2A\u6765\u6E90",
6103
+ sourceCountMany: "{count} \u4E2A\u6765\u6E90",
6104
+ sourceKindConcept: "\u6982\u5FF5",
6105
+ sourceKindDocs: "\u6587\u6863",
6106
+ sourceKindPricing: "\u4EF7\u683C",
6107
+ sourceKindUseCase: "\u5E94\u7528\u573A\u666F",
6108
+ sourceKindPage: "\u4EA7\u54C1\u9875\u9762",
6075
6109
  seoTitle: "\u63CF\u8FF0\u95EE\u9898\uFF0C\u83B7\u5F97\u7B54\u6848",
6076
6110
  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",
6077
6111
  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",
@@ -6136,6 +6170,23 @@ var HI = {
6136
6170
  nextSuggestion: "\u0905\u0917\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
6137
6171
  conceptIllustration: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E \u091A\u093F\u0924\u094D\u0930\u0923",
6138
6172
  opensInNewTab: "\u0928\u090F \u091F\u0948\u092C \u092E\u0947\u0902 \u0916\u0941\u0932\u0924\u093E \u0939\u0948",
6173
+ conceptStripTitle: "\u092F\u093E \u0915\u093F\u0938\u0940 \u0905\u0935\u0927\u093E\u0930\u0923\u093E \u0938\u0947 \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0902",
6174
+ 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",
6175
+ conceptAsk: "{product} \u092E\u0947\u0902 {title} \u0915\u0947 \u092C\u093E\u0930\u0947 \u092E\u0947\u0902 \u092A\u0942\u091B\u0947\u0902",
6176
+ conceptAskQuestion: "{product} \u092E\u0947\u0902 {title} \u0915\u0948\u0938\u0947 \u0915\u093E\u092E \u0915\u0930\u0924\u093E \u0939\u0948?",
6177
+ copyAnswer: "\u0909\u0924\u094D\u0924\u0930 \u0915\u0949\u092A\u0940 \u0915\u0930\u0947\u0902",
6178
+ copyFailed: "\u0915\u0949\u092A\u0940 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u093E",
6179
+ readAloud: "\u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0902",
6180
+ stopReading: "\u092A\u0922\u093C\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
6181
+ answerTable: "\u0924\u093E\u0932\u093F\u0915\u093E",
6182
+ groundedIn: "\u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948",
6183
+ sourceCountOne: "1 \u0938\u094D\u0930\u094B\u0924",
6184
+ sourceCountMany: "{count} \u0938\u094D\u0930\u094B\u0924",
6185
+ sourceKindConcept: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E",
6186
+ sourceKindDocs: "\u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C",
6187
+ sourceKindPricing: "\u092E\u0942\u0932\u094D\u092F",
6188
+ sourceKindUseCase: "\u0909\u092A\u092F\u094B\u0917 \u0915\u093E \u0909\u0926\u093E\u0939\u0930\u0923",
6189
+ sourceKindPage: "\u0909\u0924\u094D\u092A\u093E\u0926 \u092A\u0947\u091C",
6139
6190
  seoTitle: "\u0938\u092E\u0938\u094D\u092F\u093E \u092C\u0924\u093E\u090F\u0902, \u091C\u0935\u093E\u092C \u092A\u093E\u090F\u0902",
6140
6191
  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",
6141
6192
  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",
@@ -6204,6 +6255,23 @@ var ES = {
6204
6255
  nextSuggestion: "Sugerencia siguiente",
6205
6256
  conceptIllustration: "Ilustraci\xF3n del concepto",
6206
6257
  opensInNewTab: "se abre en una pesta\xF1a nueva",
6258
+ conceptStripTitle: "O empieza por un concepto",
6259
+ conceptStripLabel: "Conceptos sobre los que puedes preguntar",
6260
+ conceptAsk: "Preguntar por {title} en {product}",
6261
+ conceptAskQuestion: "\xBFC\xF3mo funciona {title} en {product}?",
6262
+ copyAnswer: "Copiar respuesta",
6263
+ copyFailed: "No se pudo copiar",
6264
+ readAloud: "Leer en voz alta",
6265
+ stopReading: "Dejar de leer",
6266
+ answerTable: "Tabla",
6267
+ groundedIn: "En qu\xE9 se basa esta respuesta",
6268
+ sourceCountOne: "1 fuente",
6269
+ sourceCountMany: "{count} fuentes",
6270
+ sourceKindConcept: "Concepto",
6271
+ sourceKindDocs: "Documentaci\xF3n",
6272
+ sourceKindPricing: "Precios",
6273
+ sourceKindUseCase: "Caso de uso",
6274
+ sourceKindPage: "P\xE1gina de producto",
6207
6275
  seoTitle: "Describe un problema y obt\xE9n una respuesta",
6208
6276
  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.",
6209
6277
  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.",
@@ -6272,6 +6340,23 @@ var AR = {
6272
6340
  nextSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u062A\u0627\u0644\u064A",
6273
6341
  conceptIllustration: "\u0631\u0633\u0645 \u062A\u0648\u0636\u064A\u062D\u064A \u0644\u0644\u0645\u0641\u0647\u0648\u0645",
6274
6342
  opensInNewTab: "\u064A\u064F\u0641\u062A\u062D \u0641\u064A \u0639\u0644\u0627\u0645\u0629 \u062A\u0628\u0648\u064A\u0628 \u062C\u062F\u064A\u062F\u0629",
6343
+ conceptStripTitle: "\u0623\u0648 \u0627\u0628\u062F\u0623 \u0645\u0646 \u0645\u0641\u0647\u0648\u0645",
6344
+ conceptStripLabel: "\u0645\u0641\u0627\u0647\u064A\u0645 \u064A\u0645\u0643\u0646\u0643 \u0627\u0644\u0633\u0624\u0627\u0644 \u0639\u0646\u0647\u0627",
6345
+ conceptAsk: "\u0627\u0633\u0623\u0644 \u0639\u0646 {title} \u0641\u064A {product}",
6346
+ conceptAskQuestion: "\u0643\u064A\u0641 \u064A\u0639\u0645\u0644 {title} \u0641\u064A {product}\u061F",
6347
+ copyAnswer: "\u0646\u0633\u062E \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
6348
+ copyFailed: "\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u0646\u0633\u062E",
6349
+ readAloud: "\u0627\u0642\u0631\u0623 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D",
6350
+ stopReading: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0642\u0631\u0627\u0621\u0629",
6351
+ answerTable: "\u062C\u062F\u0648\u0644",
6352
+ groundedIn: "\u0639\u0644\u0649 \u0645\u0627\u0630\u0627 \u062A\u0633\u062A\u0646\u062F \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
6353
+ sourceCountOne: "\u0645\u0635\u062F\u0631 \u0648\u0627\u062D\u062F",
6354
+ sourceCountMany: "{count} \u0645\u0635\u0627\u062F\u0631",
6355
+ sourceKindConcept: "\u0645\u0641\u0647\u0648\u0645",
6356
+ sourceKindDocs: "\u0627\u0644\u062A\u0648\u062B\u064A\u0642",
6357
+ sourceKindPricing: "\u0627\u0644\u0623\u0633\u0639\u0627\u0631",
6358
+ sourceKindUseCase: "\u062D\u0627\u0644\u0629 \u0627\u0633\u062A\u062E\u062F\u0627\u0645",
6359
+ sourceKindPage: "\u0635\u0641\u062D\u0629 \u0627\u0644\u0645\u0646\u062A\u062C",
6275
6360
  seoTitle: "\u0635\u0641 \u0645\u0634\u0643\u0644\u062A\u0643 \u0648\u0627\u062D\u0635\u0644 \u0639\u0644\u0649 \u0625\u062C\u0627\u0628\u0629",
6276
6361
  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.",
6277
6362
  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.",
@@ -6340,6 +6425,23 @@ var TA = {
6340
6425
  nextSuggestion: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4 \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
6341
6426
  conceptIllustration: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BB5\u0BBF\u0BB3\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BAE\u0BCD",
6342
6427
  opensInNewTab: "\u0BAA\u0BC1\u0BA4\u0BBF\u0BAF \u0BA4\u0BBE\u0BB5\u0BB2\u0BBF\u0BB2\u0BCD \u0BA4\u0BBF\u0BB1\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD",
6428
+ 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",
6429
+ 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",
6430
+ conceptAsk: "{product} \u0B87\u0BB2\u0BCD {title} \u0BAA\u0BB1\u0BCD\u0BB1\u0BBF\u0B95\u0BCD \u0B95\u0BC7\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
6431
+ 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?",
6432
+ copyAnswer: "\u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1",
6433
+ copyFailed: "\u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8",
6434
+ readAloud: "\u0B9A\u0BA4\u0BCD\u0BA4\u0BAE\u0BBE\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF",
6435
+ stopReading: "\u0BAA\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
6436
+ answerTable: "\u0B85\u0B9F\u0BCD\u0B9F\u0BB5\u0BA3\u0BC8",
6437
+ 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",
6438
+ sourceCountOne: "1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD",
6439
+ sourceCountMany: "{count} \u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
6440
+ sourceKindConcept: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
6441
+ sourceKindDocs: "\u0B86\u0BB5\u0BA3\u0BAE\u0BCD",
6442
+ sourceKindPricing: "\u0BB5\u0BBF\u0BB2\u0BC8",
6443
+ sourceKindUseCase: "\u0BAA\u0BAF\u0BA9\u0BCD\u0BAA\u0BBE\u0B9F\u0BCD\u0B9F\u0BC1 \u0BA8\u0BBF\u0BB2\u0BC8",
6444
+ sourceKindPage: "\u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1\u0BAA\u0BCD \u0BAA\u0B95\u0BCD\u0B95\u0BAE\u0BCD",
6343
6445
  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",
6344
6446
  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.",
6345
6447
  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.",
@@ -6363,6 +6465,17 @@ var STRINGS = {
6363
6465
  ar: AR,
6364
6466
  ta: TA
6365
6467
  };
6468
+ var SPEECH_TAGS = {
6469
+ en: "en-US",
6470
+ zh: "zh-CN",
6471
+ hi: "hi-IN",
6472
+ es: "es-ES",
6473
+ ar: "ar-SA",
6474
+ ta: "ta-IN"
6475
+ };
6476
+ function speechLocaleOf(locale) {
6477
+ return SPEECH_TAGS[locale];
6478
+ }
6366
6479
  function resolveExploreLocale(explicit) {
6367
6480
  const candidates = [
6368
6481
  explicit,
@@ -6441,11 +6554,9 @@ var EXPLORE_OWN_MESSAGE_FIELDS = (
6441
6554
  }
6442
6555
  `
6443
6556
  );
6444
- var EXPLORE_CATALOG_QUERY = (
6557
+ var EXPLORE_CATALOG_ESTABLISHED_FIELDS = (
6445
6558
  /* GraphQL */
6446
6559
  `
6447
- query ExploreCatalog($productSlug: String) {
6448
- exploreCatalog(productSlug: $productSlug) {
6449
6560
  enabled
6450
6561
  products {
6451
6562
  slug
@@ -6463,11 +6574,35 @@ var EXPLORE_CATALOG_QUERY = (
6463
6574
  historyTurns
6464
6575
  }
6465
6576
  captchaRequired
6466
- recaptchaSiteKey
6577
+ recaptchaSiteKey`
6578
+ );
6579
+ var EXPLORE_CATALOG_HIGHLIGHT_FIELD = (
6580
+ /* GraphQL */
6581
+ `
6582
+ conceptHighlights {
6583
+ product
6584
+ productName
6585
+ title
6586
+ url
6587
+ imageUrl
6588
+ alt
6589
+ }`
6590
+ );
6591
+ function exploreCatalogDocument(extraFields) {
6592
+ return (
6593
+ /* GraphQL */
6594
+ `
6595
+ query ExploreCatalog($productSlug: String) {
6596
+ exploreCatalog(productSlug: $productSlug) {${EXPLORE_CATALOG_ESTABLISHED_FIELDS}${extraFields}
6467
6597
  }
6468
6598
  }
6469
6599
  `
6600
+ );
6601
+ }
6602
+ var EXPLORE_CATALOG_QUERY = exploreCatalogDocument(
6603
+ EXPLORE_CATALOG_HIGHLIGHT_FIELD
6470
6604
  );
6605
+ var EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS = exploreCatalogDocument("");
6471
6606
  var EXPLORE_MESSAGE_QUERY = (
6472
6607
  /* GraphQL */
6473
6608
  `
@@ -7008,7 +7143,12 @@ function buildFallbackCatalog(examplePrompts, welcome) {
7008
7143
  welcomeBody: welcome?.body ?? FALLBACK_WELCOME_BODY,
7009
7144
  limits: FALLBACK_EXPLORE_LIMITS,
7010
7145
  captchaRequired: false,
7011
- recaptchaSiteKey: null
7146
+ recaptchaSiteKey: null,
7147
+ // Empty, and deliberately not synthesised from `ECOSYSTEM_PRODUCTS`: a concept highlight
7148
+ // needs a real published illustration URL, and this SDK has no registry of them. An
7149
+ // invented path would render 38 broken frames on the one screen every prerender captures.
7150
+ // The strip renders nothing at all when the list is empty, which is the honest fallback.
7151
+ conceptHighlights: []
7012
7152
  };
7013
7153
  }
7014
7154
  function normalizeCatalog(raw, fallback) {
@@ -7025,9 +7165,27 @@ function normalizeCatalog(raw, fallback) {
7025
7165
  historyTurns: raw.limits?.historyTurns || fallback.limits.historyTurns
7026
7166
  },
7027
7167
  captchaRequired: raw.captchaRequired === true,
7028
- recaptchaSiteKey: raw.recaptchaSiteKey ?? null
7168
+ recaptchaSiteKey: raw.recaptchaSiteKey ?? null,
7169
+ // `[ExploreConceptHighlight!]!` on the wire, so a gateway that KNOWS the field always
7170
+ // sends an array. The one response that arrives without it is the retry against an older
7171
+ // supergraph (`EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS`) — that gateway does not answer
7172
+ // the full document at all, it refuses it. Either way
7173
+ // `catalog.conceptHighlights.filter(...)` on `undefined` would throw inside render,
7174
+ // blanking the welcome screen over a decorative strip.
7175
+ conceptHighlights: raw.conceptHighlights?.length ? raw.conceptHighlights : fallback.conceptHighlights
7029
7176
  };
7030
7177
  }
7178
+ var CATALOG_DOCUMENT_REJECTED_CODES = /* @__PURE__ */ new Set([
7179
+ "GRAPHQL_VALIDATION_FAILED",
7180
+ "HTTP_400"
7181
+ ]);
7182
+ function catalogRejectedTheHighlightField(res) {
7183
+ if (res.data?.exploreCatalog) return false;
7184
+ return (res.errors ?? []).some((error) => {
7185
+ const code = error.extensions?.code;
7186
+ return typeof code === "string" && CATALOG_DOCUMENT_REJECTED_CODES.has(code) || error.message.includes("conceptHighlights");
7187
+ });
7188
+ }
7031
7189
  var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
7032
7190
  "RATE_LIMITED",
7033
7191
  "HTTP_429",
@@ -7367,11 +7525,19 @@ function useExploreChat(options = {}) {
7367
7525
  React.useEffect(() => {
7368
7526
  let cancelled = false;
7369
7527
  const load = async () => {
7370
- const res = await client.query(
7528
+ const variables = { productSlug: productSlug ?? null };
7529
+ let res = await client.query(
7371
7530
  EXPLORE_CATALOG_QUERY,
7372
- { productSlug: productSlug ?? null }
7531
+ variables
7373
7532
  );
7374
7533
  if (cancelled || !mountedRef.current) return;
7534
+ if (catalogRejectedTheHighlightField(res)) {
7535
+ res = await client.query(
7536
+ EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS,
7537
+ variables
7538
+ );
7539
+ if (cancelled || !mountedRef.current) return;
7540
+ }
7375
7541
  const raw = res.data?.exploreCatalog;
7376
7542
  if (res.errors?.length || !raw) {
7377
7543
  console.warn(
@@ -7817,6 +7983,224 @@ function makeOptimisticId() {
7817
7983
  optimisticCounter += 1;
7818
7984
  return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
7819
7985
  }
7986
+ var DRAIN_TARGET_MS = 1200;
7987
+ var MIN_CHARS_PER_SECOND = 90;
7988
+ var MAX_CHARS_PER_SECOND = 2200;
7989
+ var MAX_STEP_MS = 250;
7990
+ var COMMIT_INTERVAL_MS = 45;
7991
+ var FALLBACK_STEP_MS = 50;
7992
+ var FENCE = /^ {0,3}(?:```|~~~)/;
7993
+ var TABLE_ROW = /^ {0,3}\|/;
7994
+ var EMPTY_BLOCK_MARKER = /^ {0,3}(?:[-+>]|#{1,6}|\d{1,9}[.)])\s*$/;
7995
+ function isWordCharacter(char) {
7996
+ return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
7997
+ }
7998
+ function clamp(value, min, max) {
7999
+ return Math.min(Math.max(value, min), max);
8000
+ }
8001
+ function now() {
8002
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
8003
+ }
8004
+ function isHighSurrogate(code) {
8005
+ return code >= 55296 && code <= 56319;
8006
+ }
8007
+ function safeInlineEnd(line) {
8008
+ let codeSpan = null;
8009
+ let link = null;
8010
+ const emphasis = [];
8011
+ let at = 0;
8012
+ while (at < line.length) {
8013
+ const char = line[at] ?? "";
8014
+ if (char === "\\") {
8015
+ at += 2;
8016
+ continue;
8017
+ }
8018
+ if (char === "`") {
8019
+ let run = 1;
8020
+ while (line[at + run] === "`") run += 1;
8021
+ codeSpan = codeSpan === null ? at : null;
8022
+ at += run;
8023
+ continue;
8024
+ }
8025
+ if (codeSpan !== null) {
8026
+ at += 1;
8027
+ continue;
8028
+ }
8029
+ if (char === "[") {
8030
+ if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
8031
+ at += 1;
8032
+ continue;
8033
+ }
8034
+ if (char === "]") {
8035
+ if (link !== null && line[at + 1] !== "(") link = null;
8036
+ at += 1;
8037
+ continue;
8038
+ }
8039
+ if (char === ")") {
8040
+ link = null;
8041
+ at += 1;
8042
+ continue;
8043
+ }
8044
+ if (char === "*" || char === "_" || char === "~") {
8045
+ let run = 1;
8046
+ while (line[at + run] === char) run += 1;
8047
+ const start = at;
8048
+ at += run;
8049
+ const before = line[start - 1];
8050
+ const after = line[start + run];
8051
+ if (run < 2) continue;
8052
+ if (char === "_" && isWordCharacter(before) && isWordCharacter(after)) {
8053
+ continue;
8054
+ }
8055
+ const marker = char.repeat(run);
8056
+ const open2 = emphasis.findIndex((entry) => entry.marker === marker);
8057
+ if (open2 !== -1) {
8058
+ emphasis.splice(open2, 1);
8059
+ continue;
8060
+ }
8061
+ if (after !== void 0 && /\s/.test(after)) continue;
8062
+ emphasis.push({ marker, start });
8063
+ continue;
8064
+ }
8065
+ at += 1;
8066
+ }
8067
+ const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
8068
+ (value) => value !== null
8069
+ );
8070
+ return open.length === 0 ? line.length : Math.min(...open);
8071
+ }
8072
+ function revealBoundary(text, limit) {
8073
+ if (limit <= 0) return 0;
8074
+ const caughtUp = limit >= text.length;
8075
+ const head = caughtUp ? text : text.slice(0, limit);
8076
+ const lineStart = head.lastIndexOf("\n") + 1;
8077
+ const line = head.slice(lineStart);
8078
+ let fenceStart = null;
8079
+ let tableStart = null;
8080
+ let tableRows = 0;
8081
+ let at = 0;
8082
+ while (at < lineStart) {
8083
+ const breakAt = head.indexOf("\n", at);
8084
+ const end = breakAt === -1 ? lineStart : breakAt;
8085
+ const row = head.slice(at, end);
8086
+ if (fenceStart !== null) {
8087
+ if (FENCE.test(row)) fenceStart = null;
8088
+ } else if (FENCE.test(row)) {
8089
+ fenceStart = at;
8090
+ } else if (TABLE_ROW.test(row)) {
8091
+ if (tableStart === null) {
8092
+ tableStart = at;
8093
+ tableRows = 0;
8094
+ }
8095
+ tableRows += 1;
8096
+ } else if (tableStart !== null) {
8097
+ tableStart = null;
8098
+ tableRows = 0;
8099
+ }
8100
+ at = end + 1;
8101
+ }
8102
+ if (fenceStart === null) {
8103
+ const lastIsRow = TABLE_ROW.test(line);
8104
+ if (lastIsRow && tableStart === null) {
8105
+ tableStart = lineStart;
8106
+ tableRows = 0;
8107
+ } else if (!lastIsRow && line !== "") {
8108
+ tableStart = null;
8109
+ tableRows = 0;
8110
+ }
8111
+ if (tableStart !== null && tableRows < 2) return tableStart;
8112
+ }
8113
+ if (caughtUp) return text.length;
8114
+ if (fenceStart !== null) return fenceStart;
8115
+ if (TABLE_ROW.test(line)) return lineStart;
8116
+ if (EMPTY_BLOCK_MARKER.test(line)) return lineStart;
8117
+ const cut = lineStart + safeInlineEnd(line);
8118
+ return isHighSurrogate(text.charCodeAt(cut - 1)) ? cut - 1 : cut;
8119
+ }
8120
+ function prefersReducedMotion() {
8121
+ if (typeof window === "undefined") return true;
8122
+ try {
8123
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
8124
+ } catch {
8125
+ return false;
8126
+ }
8127
+ }
8128
+ function useProgressiveReveal(text, settled) {
8129
+ const [, setFrame] = React.useState(0);
8130
+ const [reduced] = React.useState(prefersReducedMotion);
8131
+ const immediate = settled || reduced;
8132
+ const textRef = React.useRef(text);
8133
+ const cursorRef = React.useRef(immediate ? text.length : 0);
8134
+ const cutRef = React.useRef(immediate ? text.length : 0);
8135
+ const committedRef = React.useRef(0);
8136
+ if (text !== textRef.current) {
8137
+ const revealed = textRef.current.slice(0, Math.floor(cursorRef.current));
8138
+ if (revealed !== "" && !text.startsWith(revealed)) {
8139
+ cursorRef.current = text.length;
8140
+ }
8141
+ cursorRef.current = Math.min(cursorRef.current, text.length);
8142
+ cutRef.current = Math.min(cutRef.current, text.length);
8143
+ textRef.current = text;
8144
+ }
8145
+ if (immediate) cursorRef.current = text.length;
8146
+ const cut = immediate ? text.length : Math.max(
8147
+ cutRef.current,
8148
+ revealBoundary(text, Math.floor(cursorRef.current))
8149
+ );
8150
+ cutRef.current = cut;
8151
+ React.useEffect(() => {
8152
+ if (immediate) return;
8153
+ if (cursorRef.current >= text.length) return;
8154
+ let cancelled = false;
8155
+ let cancelStep = null;
8156
+ let previous = now();
8157
+ const commit = (at, force) => {
8158
+ if (!force && at - committedRef.current < COMMIT_INTERVAL_MS) return;
8159
+ committedRef.current = at;
8160
+ setFrame((value) => value + 1);
8161
+ };
8162
+ const step = () => {
8163
+ cancelStep = null;
8164
+ if (cancelled) return;
8165
+ const at = now();
8166
+ const elapsed = clamp(at - previous, 0, MAX_STEP_MS);
8167
+ previous = at;
8168
+ const length = textRef.current.length;
8169
+ const pending = length - cursorRef.current;
8170
+ if (pending <= 0) {
8171
+ commit(at, true);
8172
+ return;
8173
+ }
8174
+ const perSecond = clamp(
8175
+ pending / (DRAIN_TARGET_MS / 1e3),
8176
+ MIN_CHARS_PER_SECOND,
8177
+ MAX_CHARS_PER_SECOND
8178
+ );
8179
+ cursorRef.current = Math.min(
8180
+ length,
8181
+ cursorRef.current + perSecond * elapsed / 1e3
8182
+ );
8183
+ const done = cursorRef.current >= length;
8184
+ commit(at, done);
8185
+ if (!done) schedule();
8186
+ };
8187
+ const schedule = () => {
8188
+ if (typeof window.requestAnimationFrame === "function") {
8189
+ const id2 = window.requestAnimationFrame(step);
8190
+ cancelStep = () => window.cancelAnimationFrame(id2);
8191
+ return;
8192
+ }
8193
+ const id = setTimeout(step, FALLBACK_STEP_MS);
8194
+ cancelStep = () => clearTimeout(id);
8195
+ };
8196
+ schedule();
8197
+ return () => {
8198
+ cancelled = true;
8199
+ cancelStep?.();
8200
+ };
8201
+ }, [text, immediate]);
8202
+ return text.slice(0, cut);
8203
+ }
7820
8204
  function getRecognitionCtor() {
7821
8205
  if (typeof window === "undefined") return null;
7822
8206
  const w = window;
@@ -8035,6 +8419,141 @@ function useSpeechInput({
8035
8419
  );
8036
8420
  return { supported, listening, interim, error, start, stop, toggle };
8037
8421
  }
8422
+ var SILENT_FAILURE_MS = 1500;
8423
+ function speechPlatform() {
8424
+ if (typeof window === "undefined") return null;
8425
+ const scope = window;
8426
+ const synth = scope.speechSynthesis;
8427
+ const Utterance = scope.SpeechSynthesisUtterance;
8428
+ if (!synth || typeof synth.speak !== "function") return null;
8429
+ if (typeof Utterance !== "function") return null;
8430
+ return { synth, Utterance };
8431
+ }
8432
+ function normaliseTag(tag) {
8433
+ return tag.trim().toLowerCase().replace(/_/g, "-");
8434
+ }
8435
+ function primaryOf(tag) {
8436
+ return normaliseTag(tag).split("-")[0] ?? "";
8437
+ }
8438
+ function pickVoice(voices, lang) {
8439
+ const wanted = normaliseTag(lang);
8440
+ if (wanted === "") return null;
8441
+ const primary = primaryOf(wanted);
8442
+ const exact = voices.filter((voice) => normaliseTag(voice.lang) === wanted);
8443
+ const related = voices.filter((voice) => primaryOf(voice.lang) === primary);
8444
+ return exact.find((voice) => voice.default) ?? exact[0] ?? related.find((voice) => voice.default) ?? related[0] ?? null;
8445
+ }
8446
+ var activeOwner = null;
8447
+ var activeStop = null;
8448
+ function stopSpeaking() {
8449
+ activeStop?.();
8450
+ }
8451
+ function useSpeechOutput({
8452
+ lang
8453
+ } = {}) {
8454
+ const [available, setAvailable] = React.useState(() => speechPlatform() !== null);
8455
+ const [speaking, setSpeaking] = React.useState(false);
8456
+ const utteranceRef = React.useRef(null);
8457
+ const watchdogRef = React.useRef(null);
8458
+ const ownerRef = React.useRef({});
8459
+ const stopRef = React.useRef(() => void 0);
8460
+ const langRef = React.useRef(lang);
8461
+ langRef.current = lang;
8462
+ const clearWatchdog = React.useCallback(() => {
8463
+ if (watchdogRef.current === null) return;
8464
+ clearTimeout(watchdogRef.current);
8465
+ watchdogRef.current = null;
8466
+ }, []);
8467
+ const settle = React.useCallback(() => {
8468
+ clearWatchdog();
8469
+ utteranceRef.current = null;
8470
+ if (activeOwner === ownerRef.current) {
8471
+ activeOwner = null;
8472
+ activeStop = null;
8473
+ }
8474
+ setSpeaking(false);
8475
+ }, [clearWatchdog]);
8476
+ const stop = React.useCallback(() => {
8477
+ const owned = utteranceRef.current !== null;
8478
+ settle();
8479
+ if (owned) speechPlatform()?.synth.cancel();
8480
+ }, [settle]);
8481
+ stopRef.current = stop;
8482
+ const speak = React.useCallback(
8483
+ (text) => {
8484
+ const platform = speechPlatform();
8485
+ if (!platform) {
8486
+ setAvailable(false);
8487
+ return;
8488
+ }
8489
+ const spoken = text.trim();
8490
+ if (spoken === "") return;
8491
+ if (activeStop && activeOwner !== ownerRef.current) activeStop();
8492
+ platform.synth.cancel();
8493
+ const utterance = new platform.Utterance(spoken);
8494
+ const wanted = langRef.current;
8495
+ if (wanted !== void 0 && wanted !== "") {
8496
+ const voice = pickVoice(platform.synth.getVoices(), wanted);
8497
+ utterance.lang = voice?.lang ?? wanted;
8498
+ if (voice) utterance.voice = voice;
8499
+ }
8500
+ const mine = () => utteranceRef.current === utterance;
8501
+ utterance.onstart = () => {
8502
+ if (!mine()) return;
8503
+ clearWatchdog();
8504
+ setSpeaking(true);
8505
+ };
8506
+ utterance.onend = () => {
8507
+ if (!mine()) return;
8508
+ settle();
8509
+ };
8510
+ utterance.onerror = (event) => {
8511
+ if (!mine()) return;
8512
+ const code = typeof event?.error === "string" ? event.error : "";
8513
+ settle();
8514
+ if (code !== "interrupted" && code !== "canceled") setAvailable(false);
8515
+ };
8516
+ utteranceRef.current = utterance;
8517
+ activeOwner = ownerRef.current;
8518
+ activeStop = () => {
8519
+ stopRef.current();
8520
+ };
8521
+ setSpeaking(true);
8522
+ platform.synth.speak(utterance);
8523
+ clearWatchdog();
8524
+ watchdogRef.current = setTimeout(() => {
8525
+ watchdogRef.current = null;
8526
+ if (!mine()) return;
8527
+ if (platform.synth.speaking) return;
8528
+ settle();
8529
+ setAvailable(false);
8530
+ }, SILENT_FAILURE_MS);
8531
+ },
8532
+ [clearWatchdog, settle]
8533
+ );
8534
+ const toggle = React.useCallback(
8535
+ (text) => {
8536
+ if (speaking) stop();
8537
+ else speak(text);
8538
+ },
8539
+ [speak, speaking, stop]
8540
+ );
8541
+ React.useEffect(() => {
8542
+ if (!speaking || typeof window === "undefined") return;
8543
+ const onHide = () => {
8544
+ stopRef.current();
8545
+ };
8546
+ window.addEventListener("pagehide", onHide);
8547
+ return () => window.removeEventListener("pagehide", onHide);
8548
+ }, [speaking]);
8549
+ React.useEffect(
8550
+ () => () => {
8551
+ stopRef.current();
8552
+ },
8553
+ []
8554
+ );
8555
+ return { supported: available, speaking, speak, stop, toggle };
8556
+ }
8038
8557
  var SWIPE_THRESHOLD = 40;
8039
8558
  function useSwipe({
8040
8559
  onSwipeLeft,
@@ -8302,6 +8821,115 @@ var EXPLORE_CSS = `
8302
8821
  height: auto;
8303
8822
  }
8304
8823
 
8824
+ /* ---- Comparison and pricing tables ----------------------------------------------------
8825
+ The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
8826
+ the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
8827
+ the page must not scroll sideways because of it, and it has to be readable on a 320px
8828
+ phone, which a four-column grid of 1px borders is not.
8829
+
8830
+ Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
8831
+ \`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
8832
+ cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
8833
+ of table from chaining to the page \u2014 the same rule the transcript needed. */
8834
+ .boff-explore-body .boff-explore-table {
8835
+ max-width: 100%;
8836
+ overflow-x: auto;
8837
+ overscroll-behavior-x: contain;
8838
+ margin: 0.75rem 0;
8839
+ border: 1px solid hsl(var(--border));
8840
+ border-radius: 0.75rem;
8841
+ background-color: hsl(var(--card));
8842
+ }
8843
+ .boff-explore-body .boff-explore-table:focus-visible {
8844
+ outline: 2px solid hsl(var(--ring));
8845
+ outline-offset: 2px;
8846
+ }
8847
+ .boff-explore-body .boff-explore-table table {
8848
+ /* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
8849
+ display: table;
8850
+ width: 100%;
8851
+ max-width: none;
8852
+ margin: 0;
8853
+ border-collapse: collapse;
8854
+ font-size: 0.8125rem;
8855
+ }
8856
+ .boff-explore-body .boff-explore-table th,
8857
+ .boff-explore-body .boff-explore-table td {
8858
+ border: 0;
8859
+ border-top: 1px solid hsl(var(--border));
8860
+ padding: 0.5rem 0.75rem;
8861
+ /* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
8862
+ table there reads against the grain of every other line on the page. */
8863
+ text-align: start;
8864
+ vertical-align: top;
8865
+ overflow-wrap: anywhere;
8866
+ /* Prices in a column line up under each other instead of drifting by digit width. */
8867
+ font-variant-numeric: tabular-nums;
8868
+ min-width: 6rem;
8869
+ }
8870
+ .boff-explore-body .boff-explore-table thead th {
8871
+ border-top: 0;
8872
+ background-color: hsl(var(--muted));
8873
+ color: hsl(var(--foreground));
8874
+ font-weight: 600;
8875
+ white-space: nowrap;
8876
+ }
8877
+ /* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
8878
+ the row's heading is what makes a price list scannable rather than a grid of strings. */
8879
+ .boff-explore-body .boff-explore-table tbody td:first-child {
8880
+ font-weight: 600;
8881
+ color: hsl(var(--card-foreground));
8882
+ }
8883
+
8884
+ /* Narrow: one card per row. Each cell prints its own column heading, so a value is never
8885
+ stranded without the question it answers, and the subject cell becomes the card's title.
8886
+ A media query rather than a measured width \u2014 these pages are serialised post-hydration,
8887
+ so anything measured here would ship to every phone as static HTML. */
8888
+ @media (max-width: 30rem) {
8889
+ .boff-explore-body .boff-explore-table {
8890
+ overflow-x: visible;
8891
+ }
8892
+ .boff-explore-body .boff-explore-table table,
8893
+ .boff-explore-body .boff-explore-table tbody,
8894
+ .boff-explore-body .boff-explore-table tr,
8895
+ .boff-explore-body .boff-explore-table td {
8896
+ display: block;
8897
+ width: 100%;
8898
+ min-width: 0;
8899
+ }
8900
+ .boff-explore-body .boff-explore-table thead {
8901
+ display: none;
8902
+ }
8903
+ .boff-explore-body .boff-explore-table tr {
8904
+ border-top: 1px solid hsl(var(--border));
8905
+ padding: 0.5rem 0;
8906
+ }
8907
+ .boff-explore-body .boff-explore-table tr:first-child {
8908
+ border-top: 0;
8909
+ }
8910
+ .boff-explore-body .boff-explore-table td {
8911
+ border: 0;
8912
+ padding: 0.125rem 0.75rem;
8913
+ }
8914
+ .boff-explore-body .boff-explore-table td[data-label]::before {
8915
+ content: attr(data-label);
8916
+ display: block;
8917
+ font-size: 0.6875rem;
8918
+ font-weight: 600;
8919
+ text-transform: uppercase;
8920
+ letter-spacing: 0.04em;
8921
+ color: hsl(var(--muted-foreground));
8922
+ }
8923
+ .boff-explore-body .boff-explore-table tbody td:first-child {
8924
+ font-size: 0.9375rem;
8925
+ padding-bottom: 0.25rem;
8926
+ }
8927
+ .boff-explore-body .boff-explore-table tbody td:first-child::before {
8928
+ /* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
8929
+ display: none;
8930
+ }
8931
+ }
8932
+
8305
8933
  /* ---- Ambient backdrop -----------------------------------------------------------------
8306
8934
  Two very low-opacity radial washes behind the welcome state, drifting slowly. Painted on
8307
8935
  a pseudo-element with pointer-events:none so it can never intercept a click \u2014 a
@@ -8473,7 +9101,7 @@ function isOwnElement(el, root) {
8473
9101
  function visualViewportHeight() {
8474
9102
  return window.visualViewport?.height ?? window.innerHeight;
8475
9103
  }
8476
- function clamp(value, min, max) {
9104
+ function clamp2(value, min, max) {
8477
9105
  return Math.min(Math.max(value, min), max);
8478
9106
  }
8479
9107
  function isRestoredNavigation() {
@@ -8538,14 +9166,196 @@ function isInlineImageAllowed(src, origins) {
8538
9166
  if (url.protocol !== "https:" || url.username || url.password) return false;
8539
9167
  return origins.has(url.origin);
8540
9168
  }
8541
- var InlineImageScopeContext = React.createContext(null);
9169
+ var REFERENCE_KIND_ORDER = [
9170
+ "CONCEPT",
9171
+ "DOCS",
9172
+ "PRICING",
9173
+ "USE_CASE",
9174
+ "PAGE"
9175
+ ];
9176
+ var PRODUCT_SUBDOMAINS = [
9177
+ "alphadocs.",
9178
+ "alphaapp.",
9179
+ "docs.",
9180
+ "alpha.",
9181
+ "app.",
9182
+ "www."
9183
+ ];
9184
+ function readCatalogUrl(raw) {
9185
+ let parsed;
9186
+ try {
9187
+ parsed = new URL(raw);
9188
+ } catch {
9189
+ return null;
9190
+ }
9191
+ if (parsed.protocol !== "https:") return null;
9192
+ const host = parsed.hostname.toLowerCase();
9193
+ const prefix = PRODUCT_SUBDOMAINS.find(
9194
+ (candidate) => host.startsWith(candidate)
9195
+ );
9196
+ const apex = prefix === void 0 ? host : host.slice(prefix.length);
9197
+ const port = parsed.port ? `:${parsed.port}` : "";
9198
+ return {
9199
+ origin: `https://${apex}${port}`,
9200
+ prefix,
9201
+ pathname: parsed.pathname
9202
+ };
9203
+ }
9204
+ function referenceKindOf(url, origins) {
9205
+ const parsed = readCatalogUrl(url);
9206
+ if (parsed === null || !origins.has(parsed.origin)) return null;
9207
+ if (parsed.prefix === "docs." || parsed.prefix === "alphadocs.")
9208
+ return "DOCS";
9209
+ const segment = parsed.pathname.toLowerCase().split("/").filter(Boolean)[0] ?? "";
9210
+ if (segment === "concept" || segment === "concepts") return "CONCEPT";
9211
+ if (segment === "docs") return "DOCS";
9212
+ if (segment === "pricing" || segment === "plans") return "PRICING";
9213
+ if (segment === "use-case" || segment === "use-cases" || segment === "solutions") {
9214
+ return "USE_CASE";
9215
+ }
9216
+ return "PAGE";
9217
+ }
9218
+ function productNamesByOrigin(products) {
9219
+ const names = /* @__PURE__ */ new Map();
9220
+ for (const product of products) {
9221
+ let url;
9222
+ try {
9223
+ url = new URL(product.website);
9224
+ } catch {
9225
+ continue;
9226
+ }
9227
+ if (url.protocol !== "https:") continue;
9228
+ const apex = url.hostname.replace(/^www\./, "");
9229
+ const port = url.port ? `:${url.port}` : "";
9230
+ const key = `https://${apex}${port}`;
9231
+ if (!names.has(key)) names.set(key, product.name);
9232
+ }
9233
+ return names;
9234
+ }
9235
+ function summariseReferences(references, origins, productNames) {
9236
+ const counts = /* @__PURE__ */ new Map();
9237
+ const products = [];
9238
+ for (const reference of references) {
9239
+ const kind = referenceKindOf(reference.url, origins);
9240
+ if (kind !== null) counts.set(kind, (counts.get(kind) ?? 0) + 1);
9241
+ const parsed = readCatalogUrl(reference.url);
9242
+ const name = (parsed === null ? void 0 : productNames.get(parsed.origin)) ?? (reference.product || null);
9243
+ if (name != null && !products.includes(name)) products.push(name);
9244
+ }
9245
+ return {
9246
+ total: references.length,
9247
+ kinds: REFERENCE_KIND_ORDER.filter((kind) => counts.has(kind)).map(
9248
+ (kind) => ({ kind, count: counts.get(kind) ?? 0 })
9249
+ ),
9250
+ products
9251
+ };
9252
+ }
9253
+ var CODE_FENCE = /^\s*(?:```|~~~)/;
9254
+ var TABLE_ROW_LINE = /^\s{0,3}\|/;
9255
+ var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
9256
+ function readableLine(raw, mode) {
9257
+ let line = raw;
9258
+ line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
9259
+ line = mode === "speech" ? (
9260
+ // A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
9261
+ // prompt asks for two to five links in every answer. The label IS the words.
9262
+ line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
9263
+ ) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
9264
+ line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
9265
+ line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
9266
+ line = line.replace(/^\s{0,3}>\s?/, "");
9267
+ if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
9268
+ line = line.replace(/`([^`]+)`/g, "$1");
9269
+ line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
9270
+ line = line.replace(/__([^_]+)__/g, "$1");
9271
+ line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
9272
+ line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
9273
+ return line.replace(/[ \t]+$/, "");
9274
+ }
9275
+ function tableCells(row) {
9276
+ const trimmed = row.trim();
9277
+ const cells = [];
9278
+ let cell = "";
9279
+ for (let at = 0; at < trimmed.length; at += 1) {
9280
+ const char = trimmed[at] ?? "";
9281
+ if (char === "\\") {
9282
+ cell += char + (trimmed[at + 1] ?? "");
9283
+ at += 1;
9284
+ continue;
9285
+ }
9286
+ if (char === "|") {
9287
+ cells.push(cell.trim());
9288
+ cell = "";
9289
+ continue;
9290
+ }
9291
+ cell += char;
9292
+ }
9293
+ cells.push(cell.trim());
9294
+ if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
9295
+ if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
9296
+ return cells;
9297
+ }
9298
+ function asSentence(text) {
9299
+ return /[.!?]$/.test(text) ? text : `${text}.`;
9300
+ }
9301
+ function spokenTable(rows) {
9302
+ if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
9303
+ const labels = tableCells(rows[0] ?? "");
9304
+ const spoken = [];
9305
+ for (const row of rows.slice(2)) {
9306
+ const parts = [];
9307
+ tableCells(row).forEach((value, column) => {
9308
+ if (value === "") return;
9309
+ const label = column === 0 ? "" : labels[column] ?? "";
9310
+ parts.push(label === "" ? value : `${label}: ${value}`);
9311
+ });
9312
+ if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
9313
+ }
9314
+ return spoken;
9315
+ }
9316
+ function readableText(markdown, mode) {
9317
+ const lines = [];
9318
+ let inFence = false;
9319
+ let table = [];
9320
+ const flushTable = () => {
9321
+ if (table.length === 0) return;
9322
+ for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
9323
+ table = [];
9324
+ };
9325
+ for (const raw of markdown.split("\n")) {
9326
+ if (CODE_FENCE.test(raw)) {
9327
+ flushTable();
9328
+ inFence = !inFence;
9329
+ continue;
9330
+ }
9331
+ if (inFence) {
9332
+ lines.push(raw);
9333
+ continue;
9334
+ }
9335
+ if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
9336
+ table.push(raw);
9337
+ continue;
9338
+ }
9339
+ flushTable();
9340
+ lines.push(readableLine(raw, mode));
9341
+ }
9342
+ flushTable();
9343
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
9344
+ }
9345
+ function answerPlainText(markdown) {
9346
+ return readableText(markdown, "clipboard");
9347
+ }
9348
+ function answerSpeechText(markdown) {
9349
+ return readableText(markdown, "speech");
9350
+ }
9351
+ var AnswerScopeContext = React.createContext(null);
8542
9352
  var InsideLinkContext = React.createContext(false);
8543
9353
  function AnswerLink(props) {
8544
9354
  return /* @__PURE__ */ jsxRuntime.jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownLink, { ...props }) });
8545
9355
  }
8546
9356
  var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
8547
9357
  function InlineConceptImage({ src, alt }) {
8548
- const scope = React.useContext(InlineImageScopeContext);
9358
+ const scope = React.useContext(AnswerScopeContext);
8549
9359
  const insideLink = React.useContext(InsideLinkContext);
8550
9360
  if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
8551
9361
  return null;
@@ -8593,9 +9403,70 @@ function InlineConceptImage({ src, alt }) {
8593
9403
  }
8594
9404
  ) });
8595
9405
  }
9406
+ function markdownText(node) {
9407
+ if (!node) return "";
9408
+ if (typeof node.value === "string") return node.value;
9409
+ return (node.children ?? []).map(markdownText).join("");
9410
+ }
9411
+ function hasHeaderCells(node) {
9412
+ return (node?.children ?? []).some((cell) => cell.tagName === "th");
9413
+ }
9414
+ function findHeaderRow(node) {
9415
+ for (const child of node?.children ?? []) {
9416
+ if (child.tagName === "tr" && hasHeaderCells(child)) return child;
9417
+ const nested = findHeaderRow(child);
9418
+ if (nested !== null) return nested;
9419
+ }
9420
+ return null;
9421
+ }
9422
+ function tableColumnLabels(node) {
9423
+ const row = findHeaderRow(node);
9424
+ if (row === null) return [];
9425
+ return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
9426
+ }
9427
+ var TableColumnsContext = React.createContext([]);
9428
+ function AnswerTable({ node, children }) {
9429
+ const scope = React.useContext(AnswerScopeContext);
9430
+ const table = /* @__PURE__ */ jsxRuntime.jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsxRuntime.jsx("table", { children }) });
9431
+ if (!scope) return table;
9432
+ return /* @__PURE__ */ jsxRuntime.jsx(
9433
+ "div",
9434
+ {
9435
+ "data-boff-explore": "table",
9436
+ role: "region",
9437
+ "aria-label": scope.strings.answerTable,
9438
+ tabIndex: 0,
9439
+ className: "boff-explore-table",
9440
+ children: table
9441
+ }
9442
+ );
9443
+ }
9444
+ function AnswerTableRow({
9445
+ node,
9446
+ children,
9447
+ ...props
9448
+ }) {
9449
+ const labels = React.useContext(TableColumnsContext);
9450
+ if (labels.length === 0 || hasHeaderCells(node)) {
9451
+ return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children });
9452
+ }
9453
+ let column = 0;
9454
+ const cells = React.Children.map(children, (cell) => {
9455
+ if (!React.isValidElement(cell)) return cell;
9456
+ const label = labels[column] ?? "";
9457
+ column += 1;
9458
+ if (label === "") return cell;
9459
+ return React.cloneElement(cell, {
9460
+ "data-label": label
9461
+ });
9462
+ });
9463
+ return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children: cells });
9464
+ }
8596
9465
  var ANSWER_MARKDOWN_COMPONENTS = {
8597
9466
  a: AnswerLink,
8598
- img: InlineConceptImage
9467
+ img: InlineConceptImage,
9468
+ table: AnswerTable,
9469
+ tr: AnswerTableRow
8599
9470
  };
8600
9471
  function ReferenceCard({
8601
9472
  reference,
@@ -8935,6 +9806,148 @@ function AnswerFeedback({
8935
9806
  }
8936
9807
  );
8937
9808
  }
9809
+ 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";
9810
+ function ProvenanceChips({
9811
+ strings: T,
9812
+ provenance
9813
+ }) {
9814
+ if (provenance.total === 0) return null;
9815
+ const kindLabels = {
9816
+ CONCEPT: T.sourceKindConcept,
9817
+ DOCS: T.sourceKindDocs,
9818
+ PRICING: T.sourceKindPricing,
9819
+ USE_CASE: T.sourceKindUseCase,
9820
+ PAGE: T.sourceKindPage
9821
+ };
9822
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9823
+ "ul",
9824
+ {
9825
+ "data-boff-explore": "provenance",
9826
+ "aria-label": T.groundedIn,
9827
+ className: "flex flex-wrap items-center gap-1.5",
9828
+ children: [
9829
+ /* @__PURE__ */ jsxRuntime.jsxs("li", { "data-boff-explore": "provenance-count", className: PROVENANCE_CHIP, children: [
9830
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BookOpenText, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
9831
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: provenance.total === 1 ? T.sourceCountOne : fill(T.sourceCountMany, { count: String(provenance.total) }) })
9832
+ ] }),
9833
+ provenance.kinds.map(({ kind, count }) => /* @__PURE__ */ jsxRuntime.jsx(
9834
+ "li",
9835
+ {
9836
+ "data-boff-explore": "provenance-kind",
9837
+ "data-kind": kind,
9838
+ "data-count": count,
9839
+ className: PROVENANCE_CHIP,
9840
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: count > 1 ? `${kindLabels[kind]} \xB7 ${count}` : kindLabels[kind] })
9841
+ },
9842
+ kind
9843
+ )),
9844
+ provenance.products.map((name) => /* @__PURE__ */ jsxRuntime.jsx(
9845
+ "li",
9846
+ {
9847
+ "data-boff-explore": "provenance-product",
9848
+ className: PROVENANCE_CHIP,
9849
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: name })
9850
+ },
9851
+ name
9852
+ ))
9853
+ ]
9854
+ }
9855
+ );
9856
+ }
9857
+ var COPY_FEEDBACK_MS = 2500;
9858
+ 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";
9859
+ var ANSWER_ACTION_IDLE = "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent hover:text-foreground";
9860
+ var ANSWER_ACTION_ACTIVE = "border-primary/40 bg-accent text-primary";
9861
+ function ReadAloudButton({
9862
+ strings: T,
9863
+ text,
9864
+ lang
9865
+ }) {
9866
+ const speech = useSpeechOutput({ lang });
9867
+ if (!speech.supported) return null;
9868
+ const label = speech.speaking ? T.stopReading : T.readAloud;
9869
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9870
+ "button",
9871
+ {
9872
+ "data-boff-explore": "read-aloud",
9873
+ "data-speaking": speech.speaking ? "true" : "false",
9874
+ type: "button",
9875
+ title: label,
9876
+ "aria-pressed": speech.speaking,
9877
+ onClick: () => speech.toggle(text),
9878
+ className: cn(
9879
+ ANSWER_ACTION_PILL,
9880
+ speech.speaking ? ANSWER_ACTION_ACTIVE : ANSWER_ACTION_IDLE
9881
+ ),
9882
+ children: [
9883
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Volume2, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
9884
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label })
9885
+ ]
9886
+ }
9887
+ );
9888
+ }
9889
+ function AnswerActions({
9890
+ strings: T,
9891
+ text,
9892
+ speechText,
9893
+ speechLang
9894
+ }) {
9895
+ const [outcome, setOutcome] = React.useState("idle");
9896
+ const resetRef = React.useRef(null);
9897
+ React.useEffect(
9898
+ () => () => {
9899
+ if (resetRef.current !== null) clearTimeout(resetRef.current);
9900
+ },
9901
+ []
9902
+ );
9903
+ const copy = React.useCallback(async () => {
9904
+ let copied = false;
9905
+ try {
9906
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
9907
+ if (typeof clipboard?.writeText === "function") {
9908
+ await clipboard.writeText(text);
9909
+ copied = true;
9910
+ }
9911
+ } catch {
9912
+ copied = false;
9913
+ }
9914
+ setOutcome(copied ? "copied" : "failed");
9915
+ if (resetRef.current !== null) clearTimeout(resetRef.current);
9916
+ resetRef.current = setTimeout(() => setOutcome("idle"), COPY_FEEDBACK_MS);
9917
+ }, [text]);
9918
+ const label = outcome === "copied" ? T.copied : outcome === "failed" ? T.copyFailed : T.copyAnswer;
9919
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9920
+ "div",
9921
+ {
9922
+ "data-boff-explore": "answer-actions",
9923
+ className: "flex flex-wrap items-center gap-2 pt-0.5",
9924
+ children: [
9925
+ /* @__PURE__ */ jsxRuntime.jsxs(
9926
+ "button",
9927
+ {
9928
+ "data-boff-explore": "copy-answer",
9929
+ "data-outcome": outcome,
9930
+ type: "button",
9931
+ title: label,
9932
+ onClick: () => {
9933
+ void copy();
9934
+ },
9935
+ className: cn(
9936
+ ANSWER_ACTION_PILL,
9937
+ outcome === "failed" ? "border-destructive/40 bg-card text-destructive" : ANSWER_ACTION_IDLE
9938
+ ),
9939
+ children: [
9940
+ outcome === "copied" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
9941
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label })
9942
+ ]
9943
+ }
9944
+ ),
9945
+ /* @__PURE__ */ jsxRuntime.jsx(ReadAloudButton, { strings: T, text: speechText, lang: speechLang }),
9946
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-boff-explore": "copy-status", role: "status", className: "sr-only", children: outcome === "idle" ? "" : label })
9947
+ ]
9948
+ }
9949
+ );
9950
+ }
8938
9951
  function UserBubble({
8939
9952
  strings: T,
8940
9953
  message
@@ -8980,6 +9993,8 @@ function AssistantBubble({
8980
9993
  message,
8981
9994
  activity,
8982
9995
  imageOrigins,
9996
+ productNames,
9997
+ speechLang,
8983
9998
  onNavigate,
8984
9999
  anchorRef,
8985
10000
  onFollow,
@@ -8990,6 +10005,7 @@ function AssistantBubble({
8990
10005
  const body = message.content || message.partialContent || "";
8991
10006
  const failed = message.status === "FAILED";
8992
10007
  const completed = message.status === "COMPLETED";
10008
+ const visible = useProgressiveReveal(body, !streaming);
8993
10009
  const [failedImages, setFailedImages] = React.useState(
8994
10010
  () => /* @__PURE__ */ new Set()
8995
10011
  );
@@ -8998,7 +10014,7 @@ function AssistantBubble({
8998
10014
  (current) => current.has(src) ? current : new Set(current).add(src)
8999
10015
  );
9000
10016
  }, []);
9001
- const imageScope = React.useMemo(
10017
+ const answerScope = React.useMemo(
9002
10018
  () => ({
9003
10019
  strings: T,
9004
10020
  enabled: completed,
@@ -9008,6 +10024,19 @@ function AssistantBubble({
9008
10024
  }),
9009
10025
  [T, completed, imageOrigins, failedImages, markImageFailed]
9010
10026
  );
10027
+ const provenance = React.useMemo(
10028
+ () => summariseReferences(message.references, imageOrigins, productNames),
10029
+ [message.references, imageOrigins, productNames]
10030
+ );
10031
+ const copyText = React.useMemo(
10032
+ () => answerPlainText(message.content),
10033
+ [message.content]
10034
+ );
10035
+ const speechText = React.useMemo(
10036
+ () => answerSpeechText(message.content),
10037
+ [message.content]
10038
+ );
10039
+ const grounded = completed && message.answered !== false;
9011
10040
  return /* @__PURE__ */ jsxRuntime.jsxs(
9012
10041
  "div",
9013
10042
  {
@@ -9053,10 +10082,11 @@ function AssistantBubble({
9053
10082
  ]
9054
10083
  }
9055
10084
  ) : null,
9056
- body ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
9057
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(InlineImageScopeContext.Provider, { value: imageScope, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { components: ANSWER_MARKDOWN_COMPONENTS, children: body }) }) }),
10085
+ visible ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
10086
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(AnswerScopeContext.Provider, { value: answerScope, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { components: ANSWER_MARKDOWN_COMPONENTS, children: visible }) }) }),
9058
10087
  streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
9059
10088
  ] }) : null,
10089
+ grounded && provenance.total > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ProvenanceChips, { strings: T, provenance }) : null,
9060
10090
  streaming ? /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { activity }) : null,
9061
10091
  failed && !body ? /* @__PURE__ */ jsxRuntime.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,
9062
10092
  message.references.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -9096,6 +10126,15 @@ function AssistantBubble({
9096
10126
  question
9097
10127
  )) })
9098
10128
  ] }) : null,
10129
+ completed && copyText !== "" ? /* @__PURE__ */ jsxRuntime.jsx(
10130
+ AnswerActions,
10131
+ {
10132
+ strings: T,
10133
+ text: copyText,
10134
+ speechText,
10135
+ speechLang
10136
+ }
10137
+ ) : null,
9099
10138
  completed && onFeedback ? /* @__PURE__ */ jsxRuntime.jsx(
9100
10139
  AnswerFeedback,
9101
10140
  {
@@ -9308,6 +10347,96 @@ function ProductStrip({
9308
10347
  }
9309
10348
  );
9310
10349
  }
10350
+ var MAX_CONCEPT_CARDS = 6;
10351
+ function ConceptStrip({
10352
+ strings: T,
10353
+ highlights,
10354
+ onAsk,
10355
+ disabled
10356
+ }) {
10357
+ const [failed, setFailed] = React.useState(() => /* @__PURE__ */ new Set());
10358
+ const markFailed = React.useCallback((src) => {
10359
+ setFailed(
10360
+ (current) => current.has(src) ? current : new Set(current).add(src)
10361
+ );
10362
+ }, []);
10363
+ const visible = highlights.filter(
10364
+ (highlight) => highlight.imageUrl !== "" && !failed.has(highlight.imageUrl)
10365
+ ).slice(0, MAX_CONCEPT_CARDS);
10366
+ if (visible.length === 0) return null;
10367
+ return /* @__PURE__ */ jsxRuntime.jsxs(
10368
+ "div",
10369
+ {
10370
+ "data-boff-explore": "concept-strip-block",
10371
+ className: "boff-explore-rise boff-explore-rise-1 mt-6",
10372
+ children: [
10373
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: T.conceptStripTitle }),
10374
+ /* @__PURE__ */ jsxRuntime.jsx(
10375
+ "ul",
10376
+ {
10377
+ "data-boff-explore": "concept-strip",
10378
+ "aria-label": T.conceptStripLabel,
10379
+ className: "boff-explore-strip flex min-w-0 snap-x gap-3 overflow-x-auto pb-1",
10380
+ children: visible.map((highlight) => /* @__PURE__ */ jsxRuntime.jsx(
10381
+ "li",
10382
+ {
10383
+ className: "shrink-0 snap-start",
10384
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
10385
+ "button",
10386
+ {
10387
+ "data-boff-explore": "concept-card",
10388
+ "data-product": highlight.product,
10389
+ type: "button",
10390
+ disabled,
10391
+ "aria-label": fill(T.conceptAsk, {
10392
+ title: highlight.title,
10393
+ product: highlight.productName
10394
+ }),
10395
+ onClick: () => onAsk(
10396
+ fill(T.conceptAskQuestion, {
10397
+ title: highlight.title,
10398
+ product: highlight.productName
10399
+ })
10400
+ ),
10401
+ 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",
10402
+ children: [
10403
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative block aspect-[16/9] w-full overflow-hidden bg-muted", children: [
10404
+ /* @__PURE__ */ jsxRuntime.jsx(
10405
+ "img",
10406
+ {
10407
+ src: highlight.imageUrl,
10408
+ alt: highlight.alt,
10409
+ loading: "lazy",
10410
+ decoding: "async",
10411
+ onError: () => markFailed(highlight.imageUrl),
10412
+ className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
10413
+ }
10414
+ ),
10415
+ /* @__PURE__ */ jsxRuntime.jsx(
10416
+ "span",
10417
+ {
10418
+ "aria-hidden": "true",
10419
+ 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",
10420
+ children: T.conceptIllustration
10421
+ }
10422
+ )
10423
+ ] }),
10424
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col gap-0.5 p-2.5", children: [
10425
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "line-clamp-2 text-xs font-semibold leading-snug text-card-foreground", children: highlight.title }),
10426
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: highlight.productName })
10427
+ ] })
10428
+ ]
10429
+ }
10430
+ )
10431
+ },
10432
+ `${highlight.product}:${highlight.url}`
10433
+ ))
10434
+ }
10435
+ )
10436
+ ]
10437
+ }
10438
+ );
10439
+ }
9311
10440
  function DisclaimerDialog({
9312
10441
  open,
9313
10442
  onClose,
@@ -9414,6 +10543,7 @@ function WelcomeState({
9414
10543
  body,
9415
10544
  prompts,
9416
10545
  products,
10546
+ conceptHighlights,
9417
10547
  focusProduct,
9418
10548
  onPrompt,
9419
10549
  onFocus,
@@ -9428,6 +10558,15 @@ function WelcomeState({
9428
10558
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
9429
10559
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-2.5 max-w-2xl text-pretty text-sm leading-relaxed text-muted-foreground sm:text-lg", children: body })
9430
10560
  ] }),
10561
+ /* @__PURE__ */ jsxRuntime.jsx(
10562
+ ConceptStrip,
10563
+ {
10564
+ strings: T,
10565
+ highlights: conceptHighlights,
10566
+ onAsk: onPrompt,
10567
+ disabled
10568
+ }
10569
+ ),
9431
10570
  /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(
9432
10571
  "li",
9433
10572
  {
@@ -9489,6 +10628,7 @@ function ExplorePage({
9489
10628
  );
9490
10629
  const T = React.useMemo(() => exploreStrings(activeLocale), [activeLocale]);
9491
10630
  const rtl = isRtlLocale(activeLocale);
10631
+ const speechLang = speechLocaleOf(activeLocale);
9492
10632
  const {
9493
10633
  phase,
9494
10634
  catalog,
@@ -9573,6 +10713,10 @@ function ExplorePage({
9573
10713
  () => inlineImageOrigins(catalog.products),
9574
10714
  [catalog.products]
9575
10715
  );
10716
+ const productNames = React.useMemo(
10717
+ () => productNamesByOrigin(catalog.products),
10718
+ [catalog.products]
10719
+ );
9576
10720
  React.useEffect(() => {
9577
10721
  const el = textareaRef.current;
9578
10722
  if (!el) return;
@@ -9589,11 +10733,11 @@ function ExplorePage({
9589
10733
  const layoutHeight = window.innerHeight;
9590
10734
  if (layoutHeight === 0) return;
9591
10735
  const top = Math.round(
9592
- clamp(el.getBoundingClientRect().top, 0, layoutHeight)
10736
+ clamp2(el.getBoundingClientRect().top, 0, layoutHeight)
9593
10737
  );
9594
10738
  const viewport = window.visualViewport;
9595
10739
  const inset = viewport ? Math.round(
9596
- clamp(
10740
+ clamp2(
9597
10741
  layoutHeight - viewport.height - viewport.offsetTop,
9598
10742
  0,
9599
10743
  layoutHeight
@@ -9874,6 +11018,7 @@ function ExplorePage({
9874
11018
  const text = draft.trim();
9875
11019
  if (!text || overLimit || busy || !canSend) return;
9876
11020
  stopDictation();
11021
+ stopSpeaking();
9877
11022
  setDraft("");
9878
11023
  stickToBottomRef.current = true;
9879
11024
  void send(text, toPayload(stagedRef.current));
@@ -9884,6 +11029,7 @@ function ExplorePage({
9884
11029
  const sendPrompt = React.useCallback(
9885
11030
  (prompt) => {
9886
11031
  if (!canSend || busy) return;
11032
+ stopSpeaking();
9887
11033
  setDraft("");
9888
11034
  stickToBottomRef.current = true;
9889
11035
  void send(prompt, toPayload(stagedRef.current));
@@ -10104,6 +11250,7 @@ function ExplorePage({
10104
11250
  body: resolvedBody,
10105
11251
  prompts,
10106
11252
  products: catalog.products,
11253
+ conceptHighlights: catalog.conceptHighlights,
10107
11254
  focusProduct,
10108
11255
  onPrompt: sendPrompt,
10109
11256
  onFocus: setFocusProduct,
@@ -10117,6 +11264,8 @@ function ExplorePage({
10117
11264
  message,
10118
11265
  activity,
10119
11266
  imageOrigins,
11267
+ productNames,
11268
+ speechLang,
10120
11269
  onNavigate,
10121
11270
  anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
10122
11271
  onFollow: (kind, url) => recordClick(message.id, kind, url),
@@ -15098,6 +16247,7 @@ exports.DEFAULT_CONTACT_CATEGORIES = DEFAULT_CONTACT_CATEGORIES;
15098
16247
  exports.DEFAULT_EXPLORE_EXAMPLE_PROMPTS = DEFAULT_EXPLORE_EXAMPLE_PROMPTS;
15099
16248
  exports.ECOSYSTEM_PRODUCTS = ECOSYSTEM_PRODUCTS;
15100
16249
  exports.EXPLORE_CATALOG_QUERY = EXPLORE_CATALOG_QUERY;
16250
+ exports.EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS = EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS;
15101
16251
  exports.EXPLORE_LOCALES = EXPLORE_LOCALES;
15102
16252
  exports.EXPLORE_MESSAGE_QUERY = EXPLORE_MESSAGE_QUERY;
15103
16253
  exports.EXPLORE_RECAPTCHA_ACTION = EXPLORE_RECAPTCHA_ACTION;
@@ -15150,14 +16300,18 @@ exports.getConceptBySlug = getConceptBySlug;
15150
16300
  exports.getRecaptchaV3Token = getRecaptchaV3Token;
15151
16301
  exports.isValidUrl = isValidUrl;
15152
16302
  exports.loadRecaptchaV3 = loadRecaptchaV3;
16303
+ exports.speechLocaleOf = speechLocaleOf;
16304
+ exports.stopSpeaking = stopSpeaking;
15153
16305
  exports.useContentPage = useContentPage;
15154
16306
  exports.useContentPages = useContentPages;
15155
16307
  exports.useExploreChat = useExploreChat;
15156
16308
  exports.useProduct = useProduct;
15157
16309
  exports.useProductConfig = useProductConfig;
16310
+ exports.useProgressiveReveal = useProgressiveReveal;
15158
16311
  exports.usePublicJobPosting = usePublicJobPosting;
15159
16312
  exports.usePublicJobPostings = usePublicJobPostings;
15160
16313
  exports.usePublicJobPostingsForSubmission = usePublicJobPostingsForSubmission;
16314
+ exports.useSpeechOutput = useSpeechOutput;
15161
16315
  exports.useWebSDK = useWebSDK;
15162
16316
  exports.useWebSDKConfig = useWebSDKConfig;
15163
16317
  //# sourceMappingURL=index.js.map