@burdenoff/website-sdk 2026.925.2 → 2026.926.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/contact.js +8 -0
- package/dist/components/contact.js.map +1 -1
- package/dist/components/contact.mjs +8 -0
- package/dist/components/contact.mjs.map +1 -1
- package/dist/components/explore.d.mts +1 -1
- package/dist/components/explore.d.ts +1 -1
- package/dist/components/explore.js +1101 -518
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +1103 -521
- package/dist/components/explore.mjs.map +1 -1
- package/dist/components/newsletter.js +8 -0
- package/dist/components/newsletter.js.map +1 -1
- package/dist/components/newsletter.mjs +8 -0
- package/dist/components/newsletter.mjs.map +1 -1
- package/dist/components/partners.js +11 -2
- package/dist/components/partners.js.map +1 -1
- package/dist/components/partners.mjs +11 -2
- package/dist/components/partners.mjs.map +1 -1
- package/dist/components/resource-links.js +8 -0
- package/dist/components/resource-links.js.map +1 -1
- package/dist/components/resource-links.mjs +8 -0
- package/dist/components/resource-links.mjs.map +1 -1
- package/dist/components/social-links.js +8 -0
- package/dist/components/social-links.js.map +1 -1
- package/dist/components/social-links.mjs +8 -0
- package/dist/components/social-links.mjs.map +1 -1
- package/dist/components/waitlist.js +8 -0
- package/dist/components/waitlist.js.map +1 -1
- package/dist/components/waitlist.mjs +8 -0
- package/dist/components/waitlist.mjs.map +1 -1
- package/dist/content/index.d.mts +1 -1
- package/dist/content/index.d.ts +1 -1
- package/dist/content/index.js +11 -2
- package/dist/content/index.js.map +1 -1
- package/dist/content/index.mjs +11 -2
- package/dist/content/index.mjs.map +1 -1
- package/dist/{explore-C0RFTKAi.d.ts → explore-Cam_ElSJ.d.ts} +70 -12
- package/dist/{explore-DzOf7n4m.d.mts → explore-hTQm-_SF.d.mts} +70 -12
- package/dist/hooks/index.js +51 -13
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +51 -13
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/{index-BMWQNZDX.d.mts → index-gm3pBCJT.d.mts} +8 -2
- package/dist/{index-BqHF-G-S.d.ts → index-ySbOLSD5.d.ts} +8 -2
- package/dist/index.d.mts +32 -11
- package/dist/index.d.ts +32 -11
- package/dist/index.js +901 -267
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +892 -269
- package/dist/index.mjs.map +1 -1
- package/dist/product/index.d.mts +16 -1
- package/dist/product/index.d.ts +16 -1
- package/dist/product/index.js +61 -17
- package/dist/product/index.js.map +1 -1
- package/dist/product/index.mjs +61 -18
- package/dist/product/index.mjs.map +1 -1
- package/dist/store/index.js +11 -2
- package/dist/store/index.js.map +1 -1
- package/dist/store/index.mjs +11 -2
- package/dist/store/index.mjs.map +1 -1
- package/dist/utils/index.d.mts +41 -1
- package/dist/utils/index.d.ts +41 -1
- package/dist/utils/index.js +42 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +35 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
4
|
var lucideReact = require('lucide-react');
|
|
5
|
+
var reactDom = require('react-dom');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
7
|
var ReactMarkdown = require('react-markdown');
|
|
6
8
|
var rehypeSanitize = require('rehype-sanitize');
|
|
7
9
|
var remarkGfm = require('remark-gfm');
|
|
8
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
9
10
|
var clsx = require('clsx');
|
|
10
11
|
var tailwindMerge = require('tailwind-merge');
|
|
11
12
|
var reactHelmetAsync = require('react-helmet-async');
|
|
@@ -40,478 +41,12 @@ var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
|
|
|
40
41
|
var __defProp = Object.defineProperty;
|
|
41
42
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
42
43
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
43
|
-
function MarkdownLink({
|
|
44
|
-
href,
|
|
45
|
-
children,
|
|
46
|
-
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
47
|
-
node: _node,
|
|
48
|
-
...props
|
|
49
|
-
}) {
|
|
50
|
-
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
51
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
52
|
-
"a",
|
|
53
|
-
{
|
|
54
|
-
href,
|
|
55
|
-
...props,
|
|
56
|
-
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
57
|
-
children
|
|
58
|
-
}
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
var defaultComponents = {
|
|
62
|
-
a: MarkdownLink
|
|
63
|
-
};
|
|
64
|
-
function Markdown({
|
|
65
|
-
children,
|
|
66
|
-
className,
|
|
67
|
-
inline,
|
|
68
|
-
components
|
|
69
|
-
}) {
|
|
70
|
-
const content = /* @__PURE__ */ jsxRuntime.jsx(
|
|
71
|
-
ReactMarkdown__default.default,
|
|
72
|
-
{
|
|
73
|
-
remarkPlugins: [remarkGfm__default.default],
|
|
74
|
-
rehypePlugins: [rehypeSanitize__default.default],
|
|
75
|
-
components: { ...defaultComponents, ...components },
|
|
76
|
-
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
77
|
-
children
|
|
78
|
-
}
|
|
79
|
-
);
|
|
80
|
-
if (inline) {
|
|
81
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: content });
|
|
82
|
-
}
|
|
83
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: content });
|
|
84
|
-
}
|
|
85
|
-
function cn(...inputs) {
|
|
86
|
-
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
87
|
-
}
|
|
88
|
-
var EXPLORE_ANSWER_CSS = `
|
|
89
|
-
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
90
|
-
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
91
|
-
WHOLE document horizontally scrollable, not just the bubble. */
|
|
92
|
-
.boff-explore-body,
|
|
93
|
-
.boff-explore-body p,
|
|
94
|
-
.boff-explore-body li,
|
|
95
|
-
.boff-explore-body a,
|
|
96
|
-
.boff-explore-body h1,
|
|
97
|
-
.boff-explore-body h2,
|
|
98
|
-
.boff-explore-body h3,
|
|
99
|
-
/* h4-h6 as well: a published answer page pushes the model's own headings down a level or
|
|
100
|
-
two so they nest under the page's, and a long unbroken word is no narrower for it. */
|
|
101
|
-
.boff-explore-body h4,
|
|
102
|
-
.boff-explore-body h5,
|
|
103
|
-
.boff-explore-body h6 {
|
|
104
|
-
overflow-wrap: anywhere;
|
|
105
|
-
word-break: break-word;
|
|
106
|
-
}
|
|
107
|
-
.boff-explore-body pre {
|
|
108
|
-
overflow-x: auto;
|
|
109
|
-
max-width: 100%;
|
|
110
|
-
}
|
|
111
|
-
.boff-explore-body table {
|
|
112
|
-
display: block;
|
|
113
|
-
overflow-x: auto;
|
|
114
|
-
max-width: 100%;
|
|
115
|
-
}
|
|
116
|
-
.boff-explore-body img {
|
|
117
|
-
max-width: 100%;
|
|
118
|
-
height: auto;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
122
|
-
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
123
|
-
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
124
|
-
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
125
|
-
phone, which a four-column grid of 1px borders is not.
|
|
126
|
-
|
|
127
|
-
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
128
|
-
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
129
|
-
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
130
|
-
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
131
|
-
.boff-explore-body .boff-explore-table {
|
|
132
|
-
max-width: 100%;
|
|
133
|
-
overflow-x: auto;
|
|
134
|
-
overscroll-behavior-x: contain;
|
|
135
|
-
margin: 0.75rem 0;
|
|
136
|
-
border: 1px solid hsl(var(--border));
|
|
137
|
-
border-radius: 0.75rem;
|
|
138
|
-
background-color: hsl(var(--card));
|
|
139
|
-
}
|
|
140
|
-
.boff-explore-body .boff-explore-table:focus-visible {
|
|
141
|
-
outline: 2px solid hsl(var(--ring));
|
|
142
|
-
outline-offset: 2px;
|
|
143
|
-
}
|
|
144
|
-
.boff-explore-body .boff-explore-table table {
|
|
145
|
-
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
146
|
-
display: table;
|
|
147
|
-
width: 100%;
|
|
148
|
-
max-width: none;
|
|
149
|
-
margin: 0;
|
|
150
|
-
border-collapse: collapse;
|
|
151
|
-
font-size: 0.8125rem;
|
|
152
|
-
}
|
|
153
|
-
.boff-explore-body .boff-explore-table th,
|
|
154
|
-
.boff-explore-body .boff-explore-table td {
|
|
155
|
-
border: 0;
|
|
156
|
-
border-top: 1px solid hsl(var(--border));
|
|
157
|
-
padding: 0.5rem 0.75rem;
|
|
158
|
-
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
159
|
-
table there reads against the grain of every other line on the page. */
|
|
160
|
-
text-align: start;
|
|
161
|
-
vertical-align: top;
|
|
162
|
-
overflow-wrap: anywhere;
|
|
163
|
-
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
164
|
-
font-variant-numeric: tabular-nums;
|
|
165
|
-
min-width: 6rem;
|
|
166
|
-
}
|
|
167
|
-
.boff-explore-body .boff-explore-table thead th {
|
|
168
|
-
border-top: 0;
|
|
169
|
-
background-color: hsl(var(--muted));
|
|
170
|
-
color: hsl(var(--foreground));
|
|
171
|
-
font-weight: 600;
|
|
172
|
-
white-space: nowrap;
|
|
173
|
-
}
|
|
174
|
-
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
175
|
-
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
176
|
-
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
177
|
-
font-weight: 600;
|
|
178
|
-
color: hsl(var(--card-foreground));
|
|
179
|
-
}
|
|
180
44
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.boff-explore-body .boff-explore-table {
|
|
187
|
-
overflow-x: visible;
|
|
188
|
-
}
|
|
189
|
-
.boff-explore-body .boff-explore-table table,
|
|
190
|
-
.boff-explore-body .boff-explore-table tbody,
|
|
191
|
-
.boff-explore-body .boff-explore-table tr,
|
|
192
|
-
.boff-explore-body .boff-explore-table td {
|
|
193
|
-
display: block;
|
|
194
|
-
width: 100%;
|
|
195
|
-
min-width: 0;
|
|
196
|
-
}
|
|
197
|
-
.boff-explore-body .boff-explore-table thead {
|
|
198
|
-
display: none;
|
|
199
|
-
}
|
|
200
|
-
.boff-explore-body .boff-explore-table tr {
|
|
201
|
-
border-top: 1px solid hsl(var(--border));
|
|
202
|
-
padding: 0.5rem 0;
|
|
203
|
-
}
|
|
204
|
-
.boff-explore-body .boff-explore-table tr:first-child {
|
|
205
|
-
border-top: 0;
|
|
206
|
-
}
|
|
207
|
-
.boff-explore-body .boff-explore-table td {
|
|
208
|
-
border: 0;
|
|
209
|
-
padding: 0.125rem 0.75rem;
|
|
210
|
-
}
|
|
211
|
-
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
212
|
-
content: attr(data-label);
|
|
213
|
-
display: block;
|
|
214
|
-
font-size: 0.6875rem;
|
|
215
|
-
font-weight: 600;
|
|
216
|
-
text-transform: uppercase;
|
|
217
|
-
letter-spacing: 0.04em;
|
|
218
|
-
color: hsl(var(--muted-foreground));
|
|
219
|
-
}
|
|
220
|
-
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
221
|
-
font-size: 0.9375rem;
|
|
222
|
-
padding-bottom: 0.25rem;
|
|
223
|
-
}
|
|
224
|
-
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
225
|
-
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
226
|
-
display: none;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
`;
|
|
230
|
-
function ExploreAnswerStyles() {
|
|
231
|
-
return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_ANSWER_CSS } });
|
|
232
|
-
}
|
|
233
|
-
function inlineImageOrigins(products) {
|
|
234
|
-
const origins = /* @__PURE__ */ new Set();
|
|
235
|
-
for (const product of products) {
|
|
236
|
-
let url;
|
|
237
|
-
try {
|
|
238
|
-
url = new URL(product.website);
|
|
239
|
-
} catch {
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (url.protocol !== "https:") continue;
|
|
243
|
-
const apex = url.hostname.replace(/^www\./, "");
|
|
244
|
-
const port = url.port ? `:${url.port}` : "";
|
|
245
|
-
origins.add(`https://${apex}${port}`);
|
|
246
|
-
origins.add(`https://www.${apex}${port}`);
|
|
247
|
-
}
|
|
248
|
-
return origins;
|
|
249
|
-
}
|
|
250
|
-
function isInlineImageAllowed(src, origins) {
|
|
251
|
-
if (typeof src !== "string" || src === "") return false;
|
|
252
|
-
let url;
|
|
253
|
-
try {
|
|
254
|
-
url = new URL(src);
|
|
255
|
-
} catch {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
if (url.protocol !== "https:" || url.username || url.password) return false;
|
|
259
|
-
return origins.has(url.origin);
|
|
260
|
-
}
|
|
261
|
-
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
262
|
-
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
263
|
-
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
264
|
-
function readableLine(raw, mode) {
|
|
265
|
-
let line = raw;
|
|
266
|
-
line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
|
|
267
|
-
line = mode === "speech" ? (
|
|
268
|
-
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
269
|
-
// prompt asks for two to five links in every answer. The label IS the words.
|
|
270
|
-
line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
|
|
271
|
-
) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
272
|
-
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
273
|
-
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
274
|
-
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
275
|
-
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
276
|
-
line = line.replace(/`([^`]+)`/g, "$1");
|
|
277
|
-
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
278
|
-
line = line.replace(/__([^_]+)__/g, "$1");
|
|
279
|
-
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
280
|
-
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
281
|
-
return line.replace(/[ \t]+$/, "");
|
|
282
|
-
}
|
|
283
|
-
function tableCells(row) {
|
|
284
|
-
const trimmed = row.trim();
|
|
285
|
-
const cells = [];
|
|
286
|
-
let cell = "";
|
|
287
|
-
for (let at = 0; at < trimmed.length; at += 1) {
|
|
288
|
-
const char = trimmed[at] ?? "";
|
|
289
|
-
if (char === "\\") {
|
|
290
|
-
cell += char + (trimmed[at + 1] ?? "");
|
|
291
|
-
at += 1;
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (char === "|") {
|
|
295
|
-
cells.push(cell.trim());
|
|
296
|
-
cell = "";
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
cell += char;
|
|
300
|
-
}
|
|
301
|
-
cells.push(cell.trim());
|
|
302
|
-
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
303
|
-
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
304
|
-
return cells;
|
|
305
|
-
}
|
|
306
|
-
function asSentence(text) {
|
|
307
|
-
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
308
|
-
}
|
|
309
|
-
function spokenTable(rows) {
|
|
310
|
-
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
311
|
-
const labels = tableCells(rows[0] ?? "");
|
|
312
|
-
const spoken = [];
|
|
313
|
-
for (const row of rows.slice(2)) {
|
|
314
|
-
const parts = [];
|
|
315
|
-
tableCells(row).forEach((value, column) => {
|
|
316
|
-
if (value === "") return;
|
|
317
|
-
const label = column === 0 ? "" : labels[column] ?? "";
|
|
318
|
-
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
319
|
-
});
|
|
320
|
-
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
321
|
-
}
|
|
322
|
-
return spoken;
|
|
323
|
-
}
|
|
324
|
-
function readableText(markdown, mode) {
|
|
325
|
-
const lines = [];
|
|
326
|
-
let inFence = false;
|
|
327
|
-
let table = [];
|
|
328
|
-
const flushTable = () => {
|
|
329
|
-
if (table.length === 0) return;
|
|
330
|
-
for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
|
|
331
|
-
table = [];
|
|
332
|
-
};
|
|
333
|
-
for (const raw of markdown.split("\n")) {
|
|
334
|
-
if (CODE_FENCE.test(raw)) {
|
|
335
|
-
flushTable();
|
|
336
|
-
inFence = !inFence;
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
if (inFence) {
|
|
340
|
-
lines.push(raw);
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
344
|
-
table.push(raw);
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
flushTable();
|
|
348
|
-
lines.push(readableLine(raw, mode));
|
|
349
|
-
}
|
|
350
|
-
flushTable();
|
|
351
|
-
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
352
|
-
}
|
|
353
|
-
function answerPlainText(markdown) {
|
|
354
|
-
return readableText(markdown, "clipboard");
|
|
355
|
-
}
|
|
356
|
-
function answerSpeechText(markdown) {
|
|
357
|
-
return readableText(markdown, "speech");
|
|
358
|
-
}
|
|
359
|
-
var AnswerScopeContext = React.createContext(null);
|
|
360
|
-
var InsideLinkContext = React.createContext(false);
|
|
361
|
-
function AnswerLink(props) {
|
|
362
|
-
return /* @__PURE__ */ jsxRuntime.jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownLink, { ...props }) });
|
|
363
|
-
}
|
|
364
|
-
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
365
|
-
function InlineConceptImage({ src, alt }) {
|
|
366
|
-
const scope = React.useContext(AnswerScopeContext);
|
|
367
|
-
const insideLink = React.useContext(InsideLinkContext);
|
|
368
|
-
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
const { strings: T, onFailed } = scope;
|
|
372
|
-
const picture = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
373
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
374
|
-
"img",
|
|
375
|
-
{
|
|
376
|
-
src,
|
|
377
|
-
alt: alt ?? "",
|
|
378
|
-
loading: "lazy",
|
|
379
|
-
decoding: "async",
|
|
380
|
-
onError: () => onFailed(src),
|
|
381
|
-
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
382
|
-
}
|
|
383
|
-
),
|
|
384
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
385
|
-
"span",
|
|
386
|
-
{
|
|
387
|
-
"aria-hidden": "true",
|
|
388
|
-
className: "pointer-events-none absolute left-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
389
|
-
children: T.conceptIllustration
|
|
390
|
-
}
|
|
391
|
-
)
|
|
392
|
-
] });
|
|
393
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
394
|
-
"a",
|
|
395
|
-
{
|
|
396
|
-
href: src,
|
|
397
|
-
target: "_blank",
|
|
398
|
-
rel: "noopener noreferrer",
|
|
399
|
-
className: cn(
|
|
400
|
-
INLINE_IMAGE_FRAME,
|
|
401
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
402
|
-
),
|
|
403
|
-
children: [
|
|
404
|
-
picture,
|
|
405
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sr-only", children: [
|
|
406
|
-
"(",
|
|
407
|
-
T.opensInNewTab,
|
|
408
|
-
")"
|
|
409
|
-
] })
|
|
410
|
-
]
|
|
411
|
-
}
|
|
412
|
-
) });
|
|
413
|
-
}
|
|
414
|
-
function markdownText(node) {
|
|
415
|
-
if (!node) return "";
|
|
416
|
-
if (typeof node.value === "string") return node.value;
|
|
417
|
-
return (node.children ?? []).map(markdownText).join("");
|
|
418
|
-
}
|
|
419
|
-
function hasHeaderCells(node) {
|
|
420
|
-
return (node?.children ?? []).some((cell) => cell.tagName === "th");
|
|
421
|
-
}
|
|
422
|
-
function findHeaderRow(node) {
|
|
423
|
-
for (const child of node?.children ?? []) {
|
|
424
|
-
if (child.tagName === "tr" && hasHeaderCells(child)) return child;
|
|
425
|
-
const nested = findHeaderRow(child);
|
|
426
|
-
if (nested !== null) return nested;
|
|
427
|
-
}
|
|
428
|
-
return null;
|
|
429
|
-
}
|
|
430
|
-
function tableColumnLabels(node) {
|
|
431
|
-
const row = findHeaderRow(node);
|
|
432
|
-
if (row === null) return [];
|
|
433
|
-
return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
|
|
434
|
-
}
|
|
435
|
-
var TableColumnsContext = React.createContext([]);
|
|
436
|
-
function AnswerTable({ node, children }) {
|
|
437
|
-
const scope = React.useContext(AnswerScopeContext);
|
|
438
|
-
const table = /* @__PURE__ */ jsxRuntime.jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsxRuntime.jsx("table", { children }) });
|
|
439
|
-
if (!scope) return table;
|
|
440
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
441
|
-
"div",
|
|
442
|
-
{
|
|
443
|
-
"data-boff-explore": "table",
|
|
444
|
-
role: "region",
|
|
445
|
-
"aria-label": scope.strings.answerTable,
|
|
446
|
-
tabIndex: 0,
|
|
447
|
-
className: "boff-explore-table",
|
|
448
|
-
children: table
|
|
449
|
-
}
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
function AnswerTableRow({
|
|
453
|
-
node,
|
|
454
|
-
children,
|
|
455
|
-
...props
|
|
456
|
-
}) {
|
|
457
|
-
const labels = React.useContext(TableColumnsContext);
|
|
458
|
-
if (labels.length === 0 || hasHeaderCells(node)) {
|
|
459
|
-
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children });
|
|
460
|
-
}
|
|
461
|
-
let column = 0;
|
|
462
|
-
const cells = React.Children.map(children, (cell) => {
|
|
463
|
-
if (!React.isValidElement(cell)) return cell;
|
|
464
|
-
const label = labels[column] ?? "";
|
|
465
|
-
column += 1;
|
|
466
|
-
if (label === "") return cell;
|
|
467
|
-
return React.cloneElement(cell, {
|
|
468
|
-
"data-label": label
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children: cells });
|
|
472
|
-
}
|
|
473
|
-
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
474
|
-
a: AnswerLink,
|
|
475
|
-
img: InlineConceptImage,
|
|
476
|
-
table: AnswerTable,
|
|
477
|
-
tr: AnswerTableRow
|
|
478
|
-
};
|
|
479
|
-
function ExploreAnswerBody({
|
|
480
|
-
children,
|
|
481
|
-
strings,
|
|
482
|
-
imageOrigins,
|
|
483
|
-
enabled = true,
|
|
484
|
-
components,
|
|
485
|
-
className
|
|
486
|
-
}) {
|
|
487
|
-
const [failed, setFailed] = React.useState(() => /* @__PURE__ */ new Set());
|
|
488
|
-
const onFailed = React.useCallback((src) => {
|
|
489
|
-
setFailed(
|
|
490
|
-
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
491
|
-
);
|
|
492
|
-
}, []);
|
|
493
|
-
const scope = React.useMemo(
|
|
494
|
-
() => ({
|
|
495
|
-
strings,
|
|
496
|
-
enabled,
|
|
497
|
-
origins: imageOrigins,
|
|
498
|
-
failed,
|
|
499
|
-
onFailed
|
|
500
|
-
}),
|
|
501
|
-
[strings, enabled, imageOrigins, failed, onFailed]
|
|
502
|
-
);
|
|
503
|
-
const overrides = React.useMemo(
|
|
504
|
-
() => components ? { ...ANSWER_MARKDOWN_COMPONENTS, ...components } : ANSWER_MARKDOWN_COMPONENTS,
|
|
505
|
-
[components]
|
|
506
|
-
);
|
|
507
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("boff-prose boff-explore-body min-w-0", className), children: /* @__PURE__ */ jsxRuntime.jsx(AnswerScopeContext.Provider, { value: scope, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { components: overrides, children }) }) });
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// src/components/explore-i18n.ts
|
|
511
|
-
var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
|
|
512
|
-
var RTL_LOCALES = /* @__PURE__ */ new Set(["ar"]);
|
|
513
|
-
function isRtlLocale(locale) {
|
|
514
|
-
return RTL_LOCALES.has(locale);
|
|
45
|
+
// src/components/explore-i18n.ts
|
|
46
|
+
var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
|
|
47
|
+
var RTL_LOCALES = /* @__PURE__ */ new Set(["ar"]);
|
|
48
|
+
function isRtlLocale(locale) {
|
|
49
|
+
return RTL_LOCALES.has(locale);
|
|
515
50
|
}
|
|
516
51
|
var EN = {
|
|
517
52
|
badge: "AI answers, grounded in our documentation",
|
|
@@ -594,6 +129,13 @@ var EN = {
|
|
|
594
129
|
readAloud: "Read aloud",
|
|
595
130
|
stopReading: "Stop reading",
|
|
596
131
|
answerTable: "Table",
|
|
132
|
+
productVideo: "Product video",
|
|
133
|
+
productVideoTitle: "{product} product video",
|
|
134
|
+
playVideo: "Play {title}",
|
|
135
|
+
closeVideo: "Close video",
|
|
136
|
+
enterFullScreen: "Full screen",
|
|
137
|
+
exitFullScreen: "Exit full screen",
|
|
138
|
+
videoUnavailable: "This video can't be played right now.",
|
|
597
139
|
groundedIn: "What this answer is based on",
|
|
598
140
|
sourceCountOne: "1 source",
|
|
599
141
|
sourceCountMany: "{count} sources",
|
|
@@ -695,6 +237,13 @@ var ZH = {
|
|
|
695
237
|
readAloud: "\u6717\u8BFB",
|
|
696
238
|
stopReading: "\u505C\u6B62\u6717\u8BFB",
|
|
697
239
|
answerTable: "\u8868\u683C",
|
|
240
|
+
productVideo: "\u4EA7\u54C1\u89C6\u9891",
|
|
241
|
+
productVideoTitle: "{product} \u4EA7\u54C1\u89C6\u9891",
|
|
242
|
+
playVideo: "\u64AD\u653E {title}",
|
|
243
|
+
closeVideo: "\u5173\u95ED\u89C6\u9891",
|
|
244
|
+
enterFullScreen: "\u5168\u5C4F",
|
|
245
|
+
exitFullScreen: "\u9000\u51FA\u5168\u5C4F",
|
|
246
|
+
videoUnavailable: "\u6682\u65F6\u65E0\u6CD5\u64AD\u653E\u6B64\u89C6\u9891\u3002",
|
|
698
247
|
groundedIn: "\u8FD9\u4E2A\u56DE\u7B54\u7684\u4F9D\u636E",
|
|
699
248
|
sourceCountOne: "1 \u4E2A\u6765\u6E90",
|
|
700
249
|
sourceCountMany: "{count} \u4E2A\u6765\u6E90",
|
|
@@ -792,6 +341,13 @@ var HI = {
|
|
|
792
341
|
readAloud: "\u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0902",
|
|
793
342
|
stopReading: "\u092A\u0922\u093C\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
794
343
|
answerTable: "\u0924\u093E\u0932\u093F\u0915\u093E",
|
|
344
|
+
productVideo: "\u0909\u0924\u094D\u092A\u093E\u0926 \u0935\u0940\u0921\u093F\u092F\u094B",
|
|
345
|
+
productVideoTitle: "{product} \u0909\u0924\u094D\u092A\u093E\u0926 \u0935\u0940\u0921\u093F\u092F\u094B",
|
|
346
|
+
playVideo: "{title} \u091A\u0932\u093E\u090F\u0901",
|
|
347
|
+
closeVideo: "\u0935\u0940\u0921\u093F\u092F\u094B \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
348
|
+
enterFullScreen: "\u092A\u0942\u0930\u094D\u0923 \u0938\u094D\u0915\u094D\u0930\u0940\u0928",
|
|
349
|
+
exitFullScreen: "\u092A\u0942\u0930\u094D\u0923 \u0938\u094D\u0915\u094D\u0930\u0940\u0928 \u0938\u0947 \u092C\u093E\u0939\u0930 \u0928\u093F\u0915\u0932\u0947\u0902",
|
|
350
|
+
videoUnavailable: "\u092F\u0939 \u0935\u0940\u0921\u093F\u092F\u094B \u0905\u092D\u0940 \u0928\u0939\u0940\u0902 \u091A\u0932 \u0938\u0915\u0924\u093E\u0964",
|
|
795
351
|
groundedIn: "\u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948",
|
|
796
352
|
sourceCountOne: "1 \u0938\u094D\u0930\u094B\u0924",
|
|
797
353
|
sourceCountMany: "{count} \u0938\u094D\u0930\u094B\u0924",
|
|
@@ -893,6 +449,13 @@ var ES = {
|
|
|
893
449
|
readAloud: "Leer en voz alta",
|
|
894
450
|
stopReading: "Dejar de leer",
|
|
895
451
|
answerTable: "Tabla",
|
|
452
|
+
productVideo: "V\xEDdeo del producto",
|
|
453
|
+
productVideoTitle: "V\xEDdeo de {product}",
|
|
454
|
+
playVideo: "Reproducir {title}",
|
|
455
|
+
closeVideo: "Cerrar v\xEDdeo",
|
|
456
|
+
enterFullScreen: "Pantalla completa",
|
|
457
|
+
exitFullScreen: "Salir de pantalla completa",
|
|
458
|
+
videoUnavailable: "Ahora mismo no se puede reproducir este v\xEDdeo.",
|
|
896
459
|
groundedIn: "En qu\xE9 se basa esta respuesta",
|
|
897
460
|
sourceCountOne: "1 fuente",
|
|
898
461
|
sourceCountMany: "{count} fuentes",
|
|
@@ -994,6 +557,13 @@ var AR = {
|
|
|
994
557
|
readAloud: "\u0627\u0642\u0631\u0623 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D",
|
|
995
558
|
stopReading: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0642\u0631\u0627\u0621\u0629",
|
|
996
559
|
answerTable: "\u062C\u062F\u0648\u0644",
|
|
560
|
+
productVideo: "\u0641\u064A\u062F\u064A\u0648 \u0627\u0644\u0645\u0646\u062A\u062C",
|
|
561
|
+
productVideoTitle: "\u0641\u064A\u062F\u064A\u0648 {product}",
|
|
562
|
+
playVideo: "\u062A\u0634\u063A\u064A\u0644 {title}",
|
|
563
|
+
closeVideo: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0641\u064A\u062F\u064A\u0648",
|
|
564
|
+
enterFullScreen: "\u0645\u0644\u0621 \u0627\u0644\u0634\u0627\u0634\u0629",
|
|
565
|
+
exitFullScreen: "\u0627\u0644\u062E\u0631\u0648\u062C \u0645\u0646 \u0645\u0644\u0621 \u0627\u0644\u0634\u0627\u0634\u0629",
|
|
566
|
+
videoUnavailable: "\u062A\u0639\u0630\u0651\u0631 \u062A\u0634\u063A\u064A\u0644 \u0647\u0630\u0627 \u0627\u0644\u0641\u064A\u062F\u064A\u0648 \u0627\u0644\u0622\u0646.",
|
|
997
567
|
groundedIn: "\u0639\u0644\u0649 \u0645\u0627\u0630\u0627 \u062A\u0633\u062A\u0646\u062F \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
|
|
998
568
|
sourceCountOne: "\u0645\u0635\u062F\u0631 \u0648\u0627\u062D\u062F",
|
|
999
569
|
sourceCountMany: "{count} \u0645\u0635\u0627\u062F\u0631",
|
|
@@ -1095,6 +665,13 @@ var TA = {
|
|
|
1095
665
|
readAloud: "\u0B9A\u0BA4\u0BCD\u0BA4\u0BAE\u0BBE\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF",
|
|
1096
666
|
stopReading: "\u0BAA\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
|
|
1097
667
|
answerTable: "\u0B85\u0B9F\u0BCD\u0B9F\u0BB5\u0BA3\u0BC8",
|
|
668
|
+
productVideo: "\u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1 \u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB",
|
|
669
|
+
productVideoTitle: "{product} \u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1 \u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB",
|
|
670
|
+
playVideo: "{title} \u0B87\u0BAF\u0B95\u0BCD\u0B95\u0BC1",
|
|
671
|
+
closeVideo: "\u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB\u0BB5\u0BC8 \u0BAE\u0BC2\u0B9F\u0BC1",
|
|
672
|
+
enterFullScreen: "\u0BAE\u0BC1\u0BB4\u0BC1\u0BA4\u0BCD \u0BA4\u0BBF\u0BB0\u0BC8",
|
|
673
|
+
exitFullScreen: "\u0BAE\u0BC1\u0BB4\u0BC1\u0BA4\u0BCD \u0BA4\u0BBF\u0BB0\u0BC8\u0BAF\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BB5\u0BC6\u0BB3\u0BBF\u0BAF\u0BC7\u0BB1\u0BC1",
|
|
674
|
+
videoUnavailable: "\u0B87\u0BA8\u0BCD\u0BA4 \u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB\u0BB5\u0BC8 \u0B87\u0BAA\u0BCD\u0BAA\u0BCB\u0BA4\u0BC1 \u0B87\u0BAF\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8.",
|
|
1098
675
|
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",
|
|
1099
676
|
sourceCountOne: "1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD",
|
|
1100
677
|
sourceCountMany: "{count} \u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
@@ -1137,25 +714,977 @@ var SPEECH_TAGS = {
|
|
|
1137
714
|
function speechLocaleOf(locale) {
|
|
1138
715
|
return SPEECH_TAGS[locale];
|
|
1139
716
|
}
|
|
1140
|
-
function resolveExploreLocale(explicit) {
|
|
1141
|
-
const candidates = [
|
|
1142
|
-
explicit,
|
|
1143
|
-
typeof document === "undefined" ? void 0 : document.documentElement.lang,
|
|
1144
|
-
typeof navigator === "undefined" ? void 0 : navigator.language
|
|
1145
|
-
];
|
|
1146
|
-
for (const candidate of candidates) {
|
|
1147
|
-
if (!candidate) continue;
|
|
1148
|
-
const primary = candidate.toLowerCase().split("-")[0];
|
|
1149
|
-
const match = EXPLORE_LOCALES.find((locale) => locale === primary);
|
|
1150
|
-
if (match) return match;
|
|
717
|
+
function resolveExploreLocale(explicit) {
|
|
718
|
+
const candidates = [
|
|
719
|
+
explicit,
|
|
720
|
+
typeof document === "undefined" ? void 0 : document.documentElement.lang,
|
|
721
|
+
typeof navigator === "undefined" ? void 0 : navigator.language
|
|
722
|
+
];
|
|
723
|
+
for (const candidate of candidates) {
|
|
724
|
+
if (!candidate) continue;
|
|
725
|
+
const primary = candidate.toLowerCase().split("-")[0];
|
|
726
|
+
const match = EXPLORE_LOCALES.find((locale) => locale === primary);
|
|
727
|
+
if (match) return match;
|
|
728
|
+
}
|
|
729
|
+
return "en";
|
|
730
|
+
}
|
|
731
|
+
function exploreStrings(locale) {
|
|
732
|
+
return STRINGS[locale] ?? EN;
|
|
733
|
+
}
|
|
734
|
+
function fill(template, values) {
|
|
735
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
|
|
736
|
+
}
|
|
737
|
+
function fullscreenSupport() {
|
|
738
|
+
if (typeof document === "undefined") return null;
|
|
739
|
+
if (document.fullscreenEnabled && typeof document.documentElement.requestFullscreen === "function") {
|
|
740
|
+
return "element";
|
|
741
|
+
}
|
|
742
|
+
if (typeof HTMLVideoElement !== "undefined" && "webkitEnterFullscreen" in HTMLVideoElement.prototype) {
|
|
743
|
+
return "video";
|
|
744
|
+
}
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
function ignoreRejection(result) {
|
|
748
|
+
result?.catch(() => void 0);
|
|
749
|
+
}
|
|
750
|
+
var VIDEO_DIALOG_CSS = `
|
|
751
|
+
.boff-explore-video-panel:fullscreen {
|
|
752
|
+
display: flex;
|
|
753
|
+
flex-direction: column;
|
|
754
|
+
gap: 0.75rem;
|
|
755
|
+
padding: 1rem;
|
|
756
|
+
background-color: hsl(var(--background));
|
|
757
|
+
color: hsl(var(--foreground));
|
|
758
|
+
}
|
|
759
|
+
.boff-explore-video-panel:fullscreen .boff-explore-video-frame {
|
|
760
|
+
display: flex;
|
|
761
|
+
flex: 1 1 auto;
|
|
762
|
+
min-height: 0;
|
|
763
|
+
align-items: center;
|
|
764
|
+
justify-content: center;
|
|
765
|
+
border: 0;
|
|
766
|
+
border-radius: 0;
|
|
767
|
+
box-shadow: none;
|
|
768
|
+
background-color: hsl(var(--background));
|
|
769
|
+
}
|
|
770
|
+
.boff-explore-video-panel:fullscreen video {
|
|
771
|
+
width: 100%;
|
|
772
|
+
height: 100%;
|
|
773
|
+
max-height: none;
|
|
774
|
+
}
|
|
775
|
+
`;
|
|
776
|
+
var DIALOG_BUTTON = "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-border bg-card text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
777
|
+
function ExploreVideoDialog({
|
|
778
|
+
videoUrl,
|
|
779
|
+
posterUrl,
|
|
780
|
+
title,
|
|
781
|
+
strings: T,
|
|
782
|
+
dir,
|
|
783
|
+
onClose
|
|
784
|
+
}) {
|
|
785
|
+
const dialogRef = React.useRef(null);
|
|
786
|
+
const panelRef = React.useRef(null);
|
|
787
|
+
const videoRef = React.useRef(null);
|
|
788
|
+
const closeRef = React.useRef(null);
|
|
789
|
+
const nativeModalRef = React.useRef(false);
|
|
790
|
+
const titleId = React.useId();
|
|
791
|
+
const [support] = React.useState(fullscreenSupport);
|
|
792
|
+
const [fullscreen, setFullscreen] = React.useState(false);
|
|
793
|
+
const [failed, setFailed] = React.useState(false);
|
|
794
|
+
React.useEffect(() => {
|
|
795
|
+
const dialog = dialogRef.current;
|
|
796
|
+
if (!dialog) return;
|
|
797
|
+
if (typeof dialog.showModal === "function") {
|
|
798
|
+
if (!dialog.open) dialog.showModal();
|
|
799
|
+
nativeModalRef.current = true;
|
|
800
|
+
} else {
|
|
801
|
+
dialog.setAttribute("open", "");
|
|
802
|
+
nativeModalRef.current = false;
|
|
803
|
+
}
|
|
804
|
+
closeRef.current?.focus();
|
|
805
|
+
ignoreRejection(videoRef.current?.play());
|
|
806
|
+
return () => {
|
|
807
|
+
if (dialog.open && typeof dialog.close === "function") dialog.close();
|
|
808
|
+
};
|
|
809
|
+
}, []);
|
|
810
|
+
React.useEffect(() => {
|
|
811
|
+
const sync = () => {
|
|
812
|
+
const current = document.fullscreenElement;
|
|
813
|
+
setFullscreen(
|
|
814
|
+
current !== null && dialogRef.current?.contains(current) === true
|
|
815
|
+
);
|
|
816
|
+
};
|
|
817
|
+
const enter = () => setFullscreen(true);
|
|
818
|
+
const leave = () => setFullscreen(false);
|
|
819
|
+
const video = videoRef.current;
|
|
820
|
+
document.addEventListener("fullscreenchange", sync);
|
|
821
|
+
video?.addEventListener("webkitbeginfullscreen", enter);
|
|
822
|
+
video?.addEventListener("webkitendfullscreen", leave);
|
|
823
|
+
return () => {
|
|
824
|
+
document.removeEventListener("fullscreenchange", sync);
|
|
825
|
+
video?.removeEventListener("webkitbeginfullscreen", enter);
|
|
826
|
+
video?.removeEventListener("webkitendfullscreen", leave);
|
|
827
|
+
};
|
|
828
|
+
}, [failed]);
|
|
829
|
+
const leaveFullscreen = React.useCallback(() => {
|
|
830
|
+
const current = document.fullscreenElement;
|
|
831
|
+
if (current && dialogRef.current?.contains(current) && typeof document.exitFullscreen === "function") {
|
|
832
|
+
ignoreRejection(document.exitFullscreen());
|
|
833
|
+
}
|
|
834
|
+
const video = videoRef.current;
|
|
835
|
+
if (video?.webkitDisplayingFullscreen) video.webkitExitFullscreen?.();
|
|
836
|
+
}, []);
|
|
837
|
+
const requestClose = React.useCallback(() => {
|
|
838
|
+
videoRef.current?.pause();
|
|
839
|
+
leaveFullscreen();
|
|
840
|
+
onClose();
|
|
841
|
+
}, [leaveFullscreen, onClose]);
|
|
842
|
+
const toggleFullscreen = React.useCallback(() => {
|
|
843
|
+
if (fullscreen) {
|
|
844
|
+
leaveFullscreen();
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (support === "element") {
|
|
848
|
+
const panel = panelRef.current;
|
|
849
|
+
if (panel) ignoreRejection(panel.requestFullscreen());
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const video = videoRef.current;
|
|
853
|
+
video?.webkitEnterFullscreen?.();
|
|
854
|
+
}, [fullscreen, leaveFullscreen, support]);
|
|
855
|
+
const handleClose = React.useCallback(
|
|
856
|
+
(event) => {
|
|
857
|
+
if (event.currentTarget.open) return;
|
|
858
|
+
requestClose();
|
|
859
|
+
},
|
|
860
|
+
[requestClose]
|
|
861
|
+
);
|
|
862
|
+
const handleKeyDown = React.useCallback(
|
|
863
|
+
(event) => {
|
|
864
|
+
if (event.key === "Escape" && !nativeModalRef.current) {
|
|
865
|
+
event.preventDefault();
|
|
866
|
+
requestClose();
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
[requestClose]
|
|
870
|
+
);
|
|
871
|
+
const handleStageClick = React.useCallback(
|
|
872
|
+
(event) => {
|
|
873
|
+
if (event.target === event.currentTarget) requestClose();
|
|
874
|
+
},
|
|
875
|
+
[requestClose]
|
|
876
|
+
);
|
|
877
|
+
if (typeof document === "undefined") return null;
|
|
878
|
+
const fullscreenLabel = fullscreen ? T.exitFullScreen : T.enterFullScreen;
|
|
879
|
+
return reactDom.createPortal(
|
|
880
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
881
|
+
"dialog",
|
|
882
|
+
{
|
|
883
|
+
ref: dialogRef,
|
|
884
|
+
"data-boff-explore": "video-dialog",
|
|
885
|
+
"aria-labelledby": titleId,
|
|
886
|
+
dir,
|
|
887
|
+
onClose: handleClose,
|
|
888
|
+
onKeyDown: handleKeyDown,
|
|
889
|
+
className: "fixed inset-0 z-50 m-0 h-full max-h-none w-full max-w-none overflow-y-auto overscroll-contain border-0 bg-background/95 p-0 text-foreground backdrop-blur-sm",
|
|
890
|
+
children: [
|
|
891
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: VIDEO_DIALOG_CSS } }),
|
|
892
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
893
|
+
"div",
|
|
894
|
+
{
|
|
895
|
+
onClick: handleStageClick,
|
|
896
|
+
className: "flex min-h-full items-center justify-center p-4 sm:p-6",
|
|
897
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
898
|
+
"div",
|
|
899
|
+
{
|
|
900
|
+
ref: panelRef,
|
|
901
|
+
className: "boff-explore-video-panel flex w-full min-w-0 max-w-[960px] flex-col gap-3",
|
|
902
|
+
children: [
|
|
903
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
904
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
905
|
+
"h2",
|
|
906
|
+
{
|
|
907
|
+
id: titleId,
|
|
908
|
+
className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground sm:text-base",
|
|
909
|
+
children: title
|
|
910
|
+
}
|
|
911
|
+
),
|
|
912
|
+
support !== null && !failed ? (
|
|
913
|
+
// A plain button whose name says what pressing it does NOW, and no
|
|
914
|
+
// `aria-pressed`: a toggle button's name must not change with its state, or it
|
|
915
|
+
// announces "Exit full screen, pressed" — two answers to one question.
|
|
916
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
917
|
+
"button",
|
|
918
|
+
{
|
|
919
|
+
type: "button",
|
|
920
|
+
"data-boff-explore": "video-fullscreen",
|
|
921
|
+
"aria-label": fullscreenLabel,
|
|
922
|
+
title: fullscreenLabel,
|
|
923
|
+
onClick: toggleFullscreen,
|
|
924
|
+
className: DIALOG_BUTTON,
|
|
925
|
+
children: fullscreen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize, { "aria-hidden": "true", className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
926
|
+
}
|
|
927
|
+
)
|
|
928
|
+
) : null,
|
|
929
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
930
|
+
"button",
|
|
931
|
+
{
|
|
932
|
+
ref: closeRef,
|
|
933
|
+
type: "button",
|
|
934
|
+
"data-boff-explore": "video-close",
|
|
935
|
+
"aria-label": T.closeVideo,
|
|
936
|
+
title: T.closeVideo,
|
|
937
|
+
onClick: requestClose,
|
|
938
|
+
className: DIALOG_BUTTON,
|
|
939
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
940
|
+
}
|
|
941
|
+
)
|
|
942
|
+
] }),
|
|
943
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-explore-video-frame overflow-hidden rounded-xl border border-border bg-muted shadow-lg", children: failed ? (
|
|
944
|
+
// A black box with a spinning control tells the visitor nothing; this says
|
|
945
|
+
// what happened, in their language.
|
|
946
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
947
|
+
"div",
|
|
948
|
+
{
|
|
949
|
+
role: "alert",
|
|
950
|
+
className: "flex aspect-video w-full flex-col items-center justify-center gap-2 p-6 text-center text-sm text-muted-foreground",
|
|
951
|
+
children: [
|
|
952
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
953
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: T.videoUnavailable })
|
|
954
|
+
]
|
|
955
|
+
}
|
|
956
|
+
)
|
|
957
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
958
|
+
"video",
|
|
959
|
+
{
|
|
960
|
+
ref: videoRef,
|
|
961
|
+
"data-boff-explore": "video-player",
|
|
962
|
+
src: videoUrl,
|
|
963
|
+
poster: posterUrl,
|
|
964
|
+
controls: true,
|
|
965
|
+
controlsList: support === "element" ? "nofullscreen" : void 0,
|
|
966
|
+
playsInline: true,
|
|
967
|
+
autoPlay: true,
|
|
968
|
+
preload: "metadata",
|
|
969
|
+
onError: () => setFailed(true),
|
|
970
|
+
className: "block max-h-[80vh] w-full bg-muted object-contain"
|
|
971
|
+
}
|
|
972
|
+
) })
|
|
973
|
+
]
|
|
974
|
+
}
|
|
975
|
+
)
|
|
976
|
+
}
|
|
977
|
+
)
|
|
978
|
+
]
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
document.body
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
function MarkdownLink({
|
|
985
|
+
href,
|
|
986
|
+
children,
|
|
987
|
+
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
988
|
+
node: _node,
|
|
989
|
+
...props
|
|
990
|
+
}) {
|
|
991
|
+
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
992
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
993
|
+
"a",
|
|
994
|
+
{
|
|
995
|
+
href,
|
|
996
|
+
...props,
|
|
997
|
+
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
998
|
+
children
|
|
999
|
+
}
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
var defaultComponents = {
|
|
1003
|
+
a: MarkdownLink
|
|
1004
|
+
};
|
|
1005
|
+
function Markdown({
|
|
1006
|
+
children,
|
|
1007
|
+
className,
|
|
1008
|
+
inline,
|
|
1009
|
+
components,
|
|
1010
|
+
rehypePlugins
|
|
1011
|
+
}) {
|
|
1012
|
+
const content = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1013
|
+
ReactMarkdown__default.default,
|
|
1014
|
+
{
|
|
1015
|
+
remarkPlugins: [remarkGfm__default.default],
|
|
1016
|
+
rehypePlugins: [rehypeSanitize__default.default, ...rehypePlugins ?? []],
|
|
1017
|
+
components: { ...defaultComponents, ...components },
|
|
1018
|
+
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
1019
|
+
children
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
if (inline) {
|
|
1023
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: content });
|
|
1024
|
+
}
|
|
1025
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: content });
|
|
1026
|
+
}
|
|
1027
|
+
function cn(...inputs) {
|
|
1028
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
1029
|
+
}
|
|
1030
|
+
var EXPLORE_ANSWER_CSS = `
|
|
1031
|
+
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
1032
|
+
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
1033
|
+
WHOLE document horizontally scrollable, not just the bubble. */
|
|
1034
|
+
.boff-explore-body,
|
|
1035
|
+
.boff-explore-body p,
|
|
1036
|
+
.boff-explore-body li,
|
|
1037
|
+
.boff-explore-body a,
|
|
1038
|
+
.boff-explore-body h1,
|
|
1039
|
+
.boff-explore-body h2,
|
|
1040
|
+
.boff-explore-body h3,
|
|
1041
|
+
/* h4-h6 as well: a published answer page pushes the model's own headings down a level or
|
|
1042
|
+
two so they nest under the page's, and a long unbroken word is no narrower for it. */
|
|
1043
|
+
.boff-explore-body h4,
|
|
1044
|
+
.boff-explore-body h5,
|
|
1045
|
+
.boff-explore-body h6 {
|
|
1046
|
+
overflow-wrap: anywhere;
|
|
1047
|
+
word-break: break-word;
|
|
1048
|
+
}
|
|
1049
|
+
.boff-explore-body pre {
|
|
1050
|
+
overflow-x: auto;
|
|
1051
|
+
max-width: 100%;
|
|
1052
|
+
}
|
|
1053
|
+
.boff-explore-body table {
|
|
1054
|
+
display: block;
|
|
1055
|
+
overflow-x: auto;
|
|
1056
|
+
max-width: 100%;
|
|
1057
|
+
}
|
|
1058
|
+
.boff-explore-body img {
|
|
1059
|
+
max-width: 100%;
|
|
1060
|
+
height: auto;
|
|
1061
|
+
}
|
|
1062
|
+
/* A product-video poster fills the card's reserved 16:9 frame and is cropped to it. The
|
|
1063
|
+
blanket \`img { height: auto }\` above \u2014 and ProseStyles' own \u2014 outrank a height utility,
|
|
1064
|
+
and would size it by its own aspect ratio instead, leaving a band of the frame showing
|
|
1065
|
+
whenever a poster is not exactly 16:9. */
|
|
1066
|
+
.boff-explore-body .boff-explore-video-poster {
|
|
1067
|
+
height: 100%;
|
|
1068
|
+
max-width: none;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
1072
|
+
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
1073
|
+
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
1074
|
+
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
1075
|
+
phone, which a four-column grid of 1px borders is not.
|
|
1076
|
+
|
|
1077
|
+
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
1078
|
+
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
1079
|
+
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
1080
|
+
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
1081
|
+
.boff-explore-body .boff-explore-table {
|
|
1082
|
+
max-width: 100%;
|
|
1083
|
+
overflow-x: auto;
|
|
1084
|
+
overscroll-behavior-x: contain;
|
|
1085
|
+
margin: 0.75rem 0;
|
|
1086
|
+
border: 1px solid hsl(var(--border));
|
|
1087
|
+
border-radius: 0.75rem;
|
|
1088
|
+
background-color: hsl(var(--card));
|
|
1089
|
+
}
|
|
1090
|
+
.boff-explore-body .boff-explore-table:focus-visible {
|
|
1091
|
+
outline: 2px solid hsl(var(--ring));
|
|
1092
|
+
outline-offset: 2px;
|
|
1093
|
+
}
|
|
1094
|
+
.boff-explore-body .boff-explore-table table {
|
|
1095
|
+
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
1096
|
+
display: table;
|
|
1097
|
+
width: 100%;
|
|
1098
|
+
max-width: none;
|
|
1099
|
+
margin: 0;
|
|
1100
|
+
border-collapse: collapse;
|
|
1101
|
+
font-size: 0.8125rem;
|
|
1102
|
+
}
|
|
1103
|
+
.boff-explore-body .boff-explore-table th,
|
|
1104
|
+
.boff-explore-body .boff-explore-table td {
|
|
1105
|
+
border: 0;
|
|
1106
|
+
border-top: 1px solid hsl(var(--border));
|
|
1107
|
+
padding: 0.5rem 0.75rem;
|
|
1108
|
+
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
1109
|
+
table there reads against the grain of every other line on the page. */
|
|
1110
|
+
text-align: start;
|
|
1111
|
+
vertical-align: top;
|
|
1112
|
+
overflow-wrap: anywhere;
|
|
1113
|
+
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
1114
|
+
font-variant-numeric: tabular-nums;
|
|
1115
|
+
min-width: 6rem;
|
|
1116
|
+
}
|
|
1117
|
+
.boff-explore-body .boff-explore-table thead th {
|
|
1118
|
+
border-top: 0;
|
|
1119
|
+
background-color: hsl(var(--muted));
|
|
1120
|
+
color: hsl(var(--foreground));
|
|
1121
|
+
font-weight: 600;
|
|
1122
|
+
white-space: nowrap;
|
|
1123
|
+
}
|
|
1124
|
+
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
1125
|
+
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
1126
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
1127
|
+
font-weight: 600;
|
|
1128
|
+
color: hsl(var(--card-foreground));
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/* Narrow: one card per row. Each cell prints its own column heading, so a value is never
|
|
1132
|
+
stranded without the question it answers, and the subject cell becomes the card's title.
|
|
1133
|
+
A media query rather than a measured width \u2014 these pages are serialised post-hydration,
|
|
1134
|
+
so anything measured here would ship to every phone as static HTML. */
|
|
1135
|
+
@media (max-width: 30rem) {
|
|
1136
|
+
.boff-explore-body .boff-explore-table {
|
|
1137
|
+
overflow-x: visible;
|
|
1138
|
+
}
|
|
1139
|
+
.boff-explore-body .boff-explore-table table,
|
|
1140
|
+
.boff-explore-body .boff-explore-table tbody,
|
|
1141
|
+
.boff-explore-body .boff-explore-table tr,
|
|
1142
|
+
.boff-explore-body .boff-explore-table td {
|
|
1143
|
+
display: block;
|
|
1144
|
+
width: 100%;
|
|
1145
|
+
min-width: 0;
|
|
1146
|
+
}
|
|
1147
|
+
.boff-explore-body .boff-explore-table thead {
|
|
1148
|
+
display: none;
|
|
1149
|
+
}
|
|
1150
|
+
.boff-explore-body .boff-explore-table tr {
|
|
1151
|
+
border-top: 1px solid hsl(var(--border));
|
|
1152
|
+
padding: 0.5rem 0;
|
|
1153
|
+
}
|
|
1154
|
+
.boff-explore-body .boff-explore-table tr:first-child {
|
|
1155
|
+
border-top: 0;
|
|
1156
|
+
}
|
|
1157
|
+
.boff-explore-body .boff-explore-table td {
|
|
1158
|
+
border: 0;
|
|
1159
|
+
padding: 0.125rem 0.75rem;
|
|
1160
|
+
}
|
|
1161
|
+
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
1162
|
+
content: attr(data-label);
|
|
1163
|
+
display: block;
|
|
1164
|
+
font-size: 0.6875rem;
|
|
1165
|
+
font-weight: 600;
|
|
1166
|
+
text-transform: uppercase;
|
|
1167
|
+
letter-spacing: 0.04em;
|
|
1168
|
+
color: hsl(var(--muted-foreground));
|
|
1169
|
+
}
|
|
1170
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
1171
|
+
font-size: 0.9375rem;
|
|
1172
|
+
padding-bottom: 0.25rem;
|
|
1173
|
+
}
|
|
1174
|
+
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
1175
|
+
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
1176
|
+
display: none;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
`;
|
|
1180
|
+
function ExploreAnswerStyles() {
|
|
1181
|
+
return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_ANSWER_CSS } });
|
|
1182
|
+
}
|
|
1183
|
+
function inlineImageOrigins(products) {
|
|
1184
|
+
const origins = /* @__PURE__ */ new Set();
|
|
1185
|
+
for (const product of products) {
|
|
1186
|
+
let url;
|
|
1187
|
+
try {
|
|
1188
|
+
url = new URL(product.website);
|
|
1189
|
+
} catch {
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (url.protocol !== "https:") continue;
|
|
1193
|
+
const apex = url.hostname.replace(/^www\./, "");
|
|
1194
|
+
const port = url.port ? `:${url.port}` : "";
|
|
1195
|
+
origins.add(`https://${apex}${port}`);
|
|
1196
|
+
origins.add(`https://www.${apex}${port}`);
|
|
1197
|
+
}
|
|
1198
|
+
return origins;
|
|
1199
|
+
}
|
|
1200
|
+
var VIDEO_FILE_PATH = /\.(?:mp4|webm|m4v|mov|vtt)$/i;
|
|
1201
|
+
function catalogUrlOf(value, origins) {
|
|
1202
|
+
if (typeof value !== "string" || value === "") return null;
|
|
1203
|
+
let url;
|
|
1204
|
+
try {
|
|
1205
|
+
url = new URL(value);
|
|
1206
|
+
} catch {
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
if (url.protocol !== "https:" || url.username || url.password) return null;
|
|
1210
|
+
return origins.has(url.origin) ? url : null;
|
|
1211
|
+
}
|
|
1212
|
+
function isInlineImageAllowed(src, origins) {
|
|
1213
|
+
const url = catalogUrlOf(src, origins);
|
|
1214
|
+
return url !== null && !VIDEO_FILE_PATH.test(url.pathname);
|
|
1215
|
+
}
|
|
1216
|
+
var INLINE_VIDEO_PATH = /\.(?:mp4|webm)$/i;
|
|
1217
|
+
function isInlineVideoAllowed(href, origins) {
|
|
1218
|
+
const url = catalogUrlOf(href, origins);
|
|
1219
|
+
return url !== null && INLINE_VIDEO_PATH.test(url.pathname);
|
|
1220
|
+
}
|
|
1221
|
+
function apexOriginOf(url) {
|
|
1222
|
+
const apex = url.hostname.replace(/^www\./, "");
|
|
1223
|
+
const port = url.port ? `:${url.port}` : "";
|
|
1224
|
+
return `https://${apex}${port}`;
|
|
1225
|
+
}
|
|
1226
|
+
function productSlugsByOrigin(products) {
|
|
1227
|
+
const slugs = /* @__PURE__ */ new Map();
|
|
1228
|
+
for (const product of products) {
|
|
1229
|
+
let url;
|
|
1230
|
+
try {
|
|
1231
|
+
url = new URL(product.website);
|
|
1232
|
+
} catch {
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (url.protocol !== "https:") continue;
|
|
1236
|
+
const key = apexOriginOf(url);
|
|
1237
|
+
if (!slugs.has(key)) slugs.set(key, product.slug);
|
|
1238
|
+
}
|
|
1239
|
+
return slugs;
|
|
1240
|
+
}
|
|
1241
|
+
function videoProductOf(href, slugs) {
|
|
1242
|
+
const url = new URL(href);
|
|
1243
|
+
return slugs.get(apexOriginOf(url)) ?? url.hostname.replace(/^www\./, "");
|
|
1244
|
+
}
|
|
1245
|
+
function isVideoFileTarget(href) {
|
|
1246
|
+
if (typeof href !== "string") return false;
|
|
1247
|
+
try {
|
|
1248
|
+
return VIDEO_FILE_PATH.test(
|
|
1249
|
+
new URL(href, "https://answer.invalid").pathname
|
|
1250
|
+
);
|
|
1251
|
+
} catch {
|
|
1252
|
+
return false;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
1256
|
+
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
1257
|
+
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
1258
|
+
var LINKED_IMAGE = /\[!\[([^[\]]*)\]\([^)\s]*(?:\s+"[^"]*")?\)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
1259
|
+
var IMAGE = /!\[[^[\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g;
|
|
1260
|
+
var LINK = /\[([^[\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
1261
|
+
function readableLine(raw, mode) {
|
|
1262
|
+
let line = raw;
|
|
1263
|
+
if (line.includes("](")) {
|
|
1264
|
+
line = line.replace(LINKED_IMAGE, (_match, alt, href) => {
|
|
1265
|
+
if (mode === "speech") return "";
|
|
1266
|
+
return alt.trim() === "" ? href : `${alt.trim()} (${href})`;
|
|
1267
|
+
});
|
|
1268
|
+
line = line.replace(IMAGE, "");
|
|
1269
|
+
line = mode === "speech" ? (
|
|
1270
|
+
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
1271
|
+
// prompt asks for two to five links in every answer. The label IS the words.
|
|
1272
|
+
line.replace(LINK, "$1")
|
|
1273
|
+
) : line.replace(LINK, "$1 ($2)");
|
|
1274
|
+
}
|
|
1275
|
+
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
1276
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
1277
|
+
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
1278
|
+
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
1279
|
+
line = line.replace(/`([^`]+)`/g, "$1");
|
|
1280
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
1281
|
+
line = line.replace(/__([^_]+)__/g, "$1");
|
|
1282
|
+
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
1283
|
+
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
1284
|
+
return line.replace(/[ \t]+$/, "");
|
|
1285
|
+
}
|
|
1286
|
+
function tableCells(row) {
|
|
1287
|
+
const trimmed = row.trim();
|
|
1288
|
+
const cells = [];
|
|
1289
|
+
let cell = "";
|
|
1290
|
+
for (let at = 0; at < trimmed.length; at += 1) {
|
|
1291
|
+
const char = trimmed[at] ?? "";
|
|
1292
|
+
if (char === "\\") {
|
|
1293
|
+
cell += char + (trimmed[at + 1] ?? "");
|
|
1294
|
+
at += 1;
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
if (char === "|") {
|
|
1298
|
+
cells.push(cell.trim());
|
|
1299
|
+
cell = "";
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
cell += char;
|
|
1303
|
+
}
|
|
1304
|
+
cells.push(cell.trim());
|
|
1305
|
+
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
1306
|
+
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
1307
|
+
return cells;
|
|
1308
|
+
}
|
|
1309
|
+
function asSentence(text) {
|
|
1310
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
1311
|
+
}
|
|
1312
|
+
function spokenTable(rows) {
|
|
1313
|
+
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
1314
|
+
const labels = tableCells(rows[0] ?? "");
|
|
1315
|
+
const spoken = [];
|
|
1316
|
+
for (const row of rows.slice(2)) {
|
|
1317
|
+
const parts = [];
|
|
1318
|
+
tableCells(row).forEach((value, column) => {
|
|
1319
|
+
if (value === "") return;
|
|
1320
|
+
const label = column === 0 ? "" : labels[column] ?? "";
|
|
1321
|
+
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
1322
|
+
});
|
|
1323
|
+
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
1324
|
+
}
|
|
1325
|
+
return spoken;
|
|
1326
|
+
}
|
|
1327
|
+
function readableText(markdown, mode) {
|
|
1328
|
+
const lines = [];
|
|
1329
|
+
let inFence = false;
|
|
1330
|
+
let table = [];
|
|
1331
|
+
const flushTable = () => {
|
|
1332
|
+
if (table.length === 0) return;
|
|
1333
|
+
for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
|
|
1334
|
+
table = [];
|
|
1335
|
+
};
|
|
1336
|
+
for (const raw of markdown.split("\n")) {
|
|
1337
|
+
if (CODE_FENCE.test(raw)) {
|
|
1338
|
+
flushTable();
|
|
1339
|
+
inFence = !inFence;
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (inFence) {
|
|
1343
|
+
lines.push(raw);
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
1347
|
+
table.push(raw);
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
flushTable();
|
|
1351
|
+
lines.push(readableLine(raw, mode));
|
|
1352
|
+
}
|
|
1353
|
+
flushTable();
|
|
1354
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
1355
|
+
}
|
|
1356
|
+
function answerPlainText(markdown) {
|
|
1357
|
+
return readableText(markdown, "clipboard");
|
|
1358
|
+
}
|
|
1359
|
+
function answerSpeechText(markdown) {
|
|
1360
|
+
return readableText(markdown, "speech");
|
|
1361
|
+
}
|
|
1362
|
+
var AnswerScopeContext = React.createContext(null);
|
|
1363
|
+
var InsideLinkContext = React.createContext(false);
|
|
1364
|
+
function AnswerLink(props) {
|
|
1365
|
+
const scope = React.useContext(AnswerScopeContext);
|
|
1366
|
+
const role = videoRoleOf(props.node);
|
|
1367
|
+
if (role !== void 0 && scope !== null) {
|
|
1368
|
+
const video = role === "card" && scope.enabled ? productVideoOf(props.node) : null;
|
|
1369
|
+
if (video === null) return null;
|
|
1370
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1371
|
+
InlineVideoCard,
|
|
1372
|
+
{
|
|
1373
|
+
href: video.href,
|
|
1374
|
+
poster: video.poster,
|
|
1375
|
+
alt: video.alt,
|
|
1376
|
+
scope
|
|
1377
|
+
}
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
return /* @__PURE__ */ jsxRuntime.jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownLink, { ...props }) });
|
|
1381
|
+
}
|
|
1382
|
+
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
1383
|
+
function InlineConceptImage({ src, alt }) {
|
|
1384
|
+
const scope = React.useContext(AnswerScopeContext);
|
|
1385
|
+
const insideLink = React.useContext(InsideLinkContext);
|
|
1386
|
+
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
1387
|
+
return null;
|
|
1151
1388
|
}
|
|
1152
|
-
|
|
1389
|
+
const { strings: T, onFailed } = scope;
|
|
1390
|
+
const picture = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1391
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1392
|
+
"img",
|
|
1393
|
+
{
|
|
1394
|
+
src,
|
|
1395
|
+
alt: alt ?? "",
|
|
1396
|
+
loading: "lazy",
|
|
1397
|
+
decoding: "async",
|
|
1398
|
+
onError: () => onFailed(src),
|
|
1399
|
+
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
1400
|
+
}
|
|
1401
|
+
),
|
|
1402
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1403
|
+
"span",
|
|
1404
|
+
{
|
|
1405
|
+
"aria-hidden": "true",
|
|
1406
|
+
className: "pointer-events-none absolute left-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
1407
|
+
children: T.conceptIllustration
|
|
1408
|
+
}
|
|
1409
|
+
)
|
|
1410
|
+
] });
|
|
1411
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1412
|
+
"a",
|
|
1413
|
+
{
|
|
1414
|
+
href: src,
|
|
1415
|
+
target: "_blank",
|
|
1416
|
+
rel: "noopener noreferrer",
|
|
1417
|
+
className: cn(
|
|
1418
|
+
INLINE_IMAGE_FRAME,
|
|
1419
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1420
|
+
),
|
|
1421
|
+
children: [
|
|
1422
|
+
picture,
|
|
1423
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sr-only", children: [
|
|
1424
|
+
"(",
|
|
1425
|
+
T.opensInNewTab,
|
|
1426
|
+
")"
|
|
1427
|
+
] })
|
|
1428
|
+
]
|
|
1429
|
+
}
|
|
1430
|
+
) });
|
|
1153
1431
|
}
|
|
1154
|
-
function
|
|
1155
|
-
|
|
1432
|
+
function markdownText(node) {
|
|
1433
|
+
if (!node) return "";
|
|
1434
|
+
if (typeof node.value === "string") return node.value;
|
|
1435
|
+
return (node.children ?? []).map(markdownText).join("");
|
|
1156
1436
|
}
|
|
1157
|
-
function
|
|
1158
|
-
return
|
|
1437
|
+
function hasHeaderCells(node) {
|
|
1438
|
+
return (node?.children ?? []).some((cell) => cell.tagName === "th");
|
|
1439
|
+
}
|
|
1440
|
+
function findHeaderRow(node) {
|
|
1441
|
+
for (const child of node?.children ?? []) {
|
|
1442
|
+
if (child.tagName === "tr" && hasHeaderCells(child)) return child;
|
|
1443
|
+
const nested = findHeaderRow(child);
|
|
1444
|
+
if (nested !== null) return nested;
|
|
1445
|
+
}
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
function tableColumnLabels(node) {
|
|
1449
|
+
const row = findHeaderRow(node);
|
|
1450
|
+
if (row === null) return [];
|
|
1451
|
+
return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
|
|
1452
|
+
}
|
|
1453
|
+
var TableColumnsContext = React.createContext([]);
|
|
1454
|
+
function AnswerTable({ node, children }) {
|
|
1455
|
+
const scope = React.useContext(AnswerScopeContext);
|
|
1456
|
+
const table = /* @__PURE__ */ jsxRuntime.jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsxRuntime.jsx("table", { children }) });
|
|
1457
|
+
if (!scope) return table;
|
|
1458
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1459
|
+
"div",
|
|
1460
|
+
{
|
|
1461
|
+
"data-boff-explore": "table",
|
|
1462
|
+
role: "region",
|
|
1463
|
+
"aria-label": scope.strings.answerTable,
|
|
1464
|
+
tabIndex: 0,
|
|
1465
|
+
className: "boff-explore-table",
|
|
1466
|
+
children: table
|
|
1467
|
+
}
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
function AnswerTableRow({
|
|
1471
|
+
node,
|
|
1472
|
+
children,
|
|
1473
|
+
...props
|
|
1474
|
+
}) {
|
|
1475
|
+
const labels = React.useContext(TableColumnsContext);
|
|
1476
|
+
if (labels.length === 0 || hasHeaderCells(node)) {
|
|
1477
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children });
|
|
1478
|
+
}
|
|
1479
|
+
let column = 0;
|
|
1480
|
+
const cells = React.Children.map(children, (cell) => {
|
|
1481
|
+
if (!React.isValidElement(cell)) return cell;
|
|
1482
|
+
const label = labels[column] ?? "";
|
|
1483
|
+
column += 1;
|
|
1484
|
+
if (label === "") return cell;
|
|
1485
|
+
return React.cloneElement(cell, {
|
|
1486
|
+
"data-label": label
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children: cells });
|
|
1490
|
+
}
|
|
1491
|
+
function linkedImageOf(node) {
|
|
1492
|
+
if (node?.tagName !== "a") return null;
|
|
1493
|
+
const content = (node.children ?? []).filter(
|
|
1494
|
+
(child) => child.tagName !== void 0 || (child.value ?? "").trim() !== ""
|
|
1495
|
+
);
|
|
1496
|
+
const image = content.length === 1 ? content[0] : void 0;
|
|
1497
|
+
if (image?.tagName !== "img") return null;
|
|
1498
|
+
const alt = image.properties?.alt;
|
|
1499
|
+
return {
|
|
1500
|
+
href: node.properties?.href,
|
|
1501
|
+
src: image.properties?.src,
|
|
1502
|
+
alt: typeof alt === "string" ? alt.trim() : ""
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function isCardableVideo(embed, origins) {
|
|
1506
|
+
if (!isInlineVideoAllowed(embed.href, origins) || !isInlineImageAllowed(embed.src, origins)) {
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
return apexOriginOf(new URL(embed.href)) === apexOriginOf(new URL(embed.src));
|
|
1510
|
+
}
|
|
1511
|
+
var NO_VIDEO_CONTAINERS = /* @__PURE__ */ new Set(["li", "table"]);
|
|
1512
|
+
function rehypeProductVideo(options) {
|
|
1513
|
+
return (tree) => {
|
|
1514
|
+
let claimed = false;
|
|
1515
|
+
const visit = (node, parent, contained) => {
|
|
1516
|
+
const embed = linkedImageOf(node);
|
|
1517
|
+
if (embed !== null && isVideoFileTarget(embed.href)) {
|
|
1518
|
+
const card = !claimed && !contained && parent?.tagName === "p" && isCardableVideo(embed, options.origins);
|
|
1519
|
+
if (card) claimed = true;
|
|
1520
|
+
const data = typeof node.data === "object" ? node.data : null;
|
|
1521
|
+
node.data = { ...data, boffVideo: card ? "card" : "drop" };
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
const inside = contained || node.tagName !== void 0 && NO_VIDEO_CONTAINERS.has(node.tagName);
|
|
1525
|
+
for (const child of node.children ?? []) visit(child, node, inside);
|
|
1526
|
+
};
|
|
1527
|
+
visit(tree, null, false);
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
function videoRoleOf(node) {
|
|
1531
|
+
const data = node?.data;
|
|
1532
|
+
if (typeof data !== "object" || data === null || !("boffVideo" in data)) {
|
|
1533
|
+
return void 0;
|
|
1534
|
+
}
|
|
1535
|
+
return data.boffVideo === "card" || data.boffVideo === "drop" ? data.boffVideo : void 0;
|
|
1536
|
+
}
|
|
1537
|
+
function productVideoOf(node) {
|
|
1538
|
+
const embed = linkedImageOf(node);
|
|
1539
|
+
if (embed === null || typeof embed.href !== "string" || typeof embed.src !== "string") {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
return { href: embed.href, poster: embed.src, alt: embed.alt };
|
|
1543
|
+
}
|
|
1544
|
+
var CANONICAL_VIDEO_ALT = /^(.+?)\s+product video$/i;
|
|
1545
|
+
function videoTitleOf(alt, T) {
|
|
1546
|
+
const product = CANONICAL_VIDEO_ALT.exec(alt)?.[1]?.trim();
|
|
1547
|
+
if (product) return fill(T.productVideoTitle, { product });
|
|
1548
|
+
return alt === "" ? T.productVideo : alt;
|
|
1549
|
+
}
|
|
1550
|
+
var INLINE_VIDEO_FRAME = "group relative block aspect-video w-full max-w-md overflow-hidden rounded-xl border border-border bg-muted shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
1551
|
+
function InlineVideoCard({
|
|
1552
|
+
href,
|
|
1553
|
+
poster,
|
|
1554
|
+
alt,
|
|
1555
|
+
scope
|
|
1556
|
+
}) {
|
|
1557
|
+
const { strings: T, failed, onFailed, productSlugs } = scope;
|
|
1558
|
+
const [dialogDir, setDialogDir] = React.useState(null);
|
|
1559
|
+
const buttonRef = React.useRef(null);
|
|
1560
|
+
const returnFocusRef = React.useRef(false);
|
|
1561
|
+
const open = dialogDir !== null;
|
|
1562
|
+
React.useEffect(() => {
|
|
1563
|
+
if (open || !returnFocusRef.current) return;
|
|
1564
|
+
returnFocusRef.current = false;
|
|
1565
|
+
const button = buttonRef.current;
|
|
1566
|
+
if (button?.isConnected) button.focus();
|
|
1567
|
+
}, [open]);
|
|
1568
|
+
const openPlayer = React.useCallback((event) => {
|
|
1569
|
+
const dir = event.currentTarget.closest("[dir]")?.getAttribute("dir");
|
|
1570
|
+
returnFocusRef.current = true;
|
|
1571
|
+
setDialogDir(dir === "rtl" ? "rtl" : "ltr");
|
|
1572
|
+
}, []);
|
|
1573
|
+
const closePlayer = React.useCallback(() => setDialogDir(null), []);
|
|
1574
|
+
const name = videoTitleOf(alt, T);
|
|
1575
|
+
const posterFailed = failed.has(poster);
|
|
1576
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1577
|
+
"span",
|
|
1578
|
+
{
|
|
1579
|
+
"data-boff-explore": "inline-video",
|
|
1580
|
+
"data-product": videoProductOf(href, productSlugs),
|
|
1581
|
+
className: "my-3 block",
|
|
1582
|
+
children: [
|
|
1583
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1584
|
+
"button",
|
|
1585
|
+
{
|
|
1586
|
+
ref: buttonRef,
|
|
1587
|
+
type: "button",
|
|
1588
|
+
"aria-haspopup": "dialog",
|
|
1589
|
+
"aria-label": fill(T.playVideo, { title: name }),
|
|
1590
|
+
onClick: openPlayer,
|
|
1591
|
+
className: INLINE_VIDEO_FRAME,
|
|
1592
|
+
children: [
|
|
1593
|
+
posterFailed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1594
|
+
"span",
|
|
1595
|
+
{
|
|
1596
|
+
"aria-hidden": "true",
|
|
1597
|
+
className: "absolute inset-0 flex items-center justify-center bg-muted text-muted-foreground",
|
|
1598
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Film, { className: "h-10 w-10" })
|
|
1599
|
+
}
|
|
1600
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1601
|
+
"img",
|
|
1602
|
+
{
|
|
1603
|
+
src: poster,
|
|
1604
|
+
alt: "",
|
|
1605
|
+
loading: "lazy",
|
|
1606
|
+
decoding: "async",
|
|
1607
|
+
onError: () => onFailed(poster),
|
|
1608
|
+
className: "boff-explore-video-poster absolute inset-0 h-full w-full object-cover"
|
|
1609
|
+
}
|
|
1610
|
+
),
|
|
1611
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1612
|
+
"span",
|
|
1613
|
+
{
|
|
1614
|
+
"aria-hidden": "true",
|
|
1615
|
+
className: "pointer-events-none absolute inset-0 flex items-center justify-center",
|
|
1616
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-14 w-14 items-center justify-center rounded-full border border-border bg-background/90 text-foreground shadow-md transition-transform group-hover:scale-105", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-6 w-6 fill-current" }) })
|
|
1617
|
+
}
|
|
1618
|
+
),
|
|
1619
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1620
|
+
"span",
|
|
1621
|
+
{
|
|
1622
|
+
"aria-hidden": "true",
|
|
1623
|
+
className: "pointer-events-none absolute start-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
1624
|
+
children: T.productVideo
|
|
1625
|
+
}
|
|
1626
|
+
)
|
|
1627
|
+
]
|
|
1628
|
+
}
|
|
1629
|
+
),
|
|
1630
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1631
|
+
ExploreVideoDialog,
|
|
1632
|
+
{
|
|
1633
|
+
videoUrl: href,
|
|
1634
|
+
posterUrl: poster,
|
|
1635
|
+
title: name,
|
|
1636
|
+
strings: T,
|
|
1637
|
+
dir: dialogDir,
|
|
1638
|
+
onClose: closePlayer
|
|
1639
|
+
}
|
|
1640
|
+
) : null
|
|
1641
|
+
]
|
|
1642
|
+
}
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
1646
|
+
a: AnswerLink,
|
|
1647
|
+
img: InlineConceptImage,
|
|
1648
|
+
table: AnswerTable,
|
|
1649
|
+
tr: AnswerTableRow
|
|
1650
|
+
};
|
|
1651
|
+
var NO_PRODUCT_SLUGS = /* @__PURE__ */ new Map();
|
|
1652
|
+
function ExploreAnswerBody({
|
|
1653
|
+
children,
|
|
1654
|
+
strings,
|
|
1655
|
+
imageOrigins,
|
|
1656
|
+
videoOrigins = imageOrigins,
|
|
1657
|
+
productSlugs = NO_PRODUCT_SLUGS,
|
|
1658
|
+
enabled = true,
|
|
1659
|
+
components,
|
|
1660
|
+
className
|
|
1661
|
+
}) {
|
|
1662
|
+
const [failed, setFailed] = React.useState(() => /* @__PURE__ */ new Set());
|
|
1663
|
+
const onFailed = React.useCallback((src) => {
|
|
1664
|
+
setFailed(
|
|
1665
|
+
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
1666
|
+
);
|
|
1667
|
+
}, []);
|
|
1668
|
+
const scope = React.useMemo(
|
|
1669
|
+
() => ({
|
|
1670
|
+
strings,
|
|
1671
|
+
enabled,
|
|
1672
|
+
origins: imageOrigins,
|
|
1673
|
+
productSlugs,
|
|
1674
|
+
failed,
|
|
1675
|
+
onFailed
|
|
1676
|
+
}),
|
|
1677
|
+
[strings, enabled, imageOrigins, productSlugs, failed, onFailed]
|
|
1678
|
+
);
|
|
1679
|
+
const rehypePlugins = React.useMemo(
|
|
1680
|
+
() => [[rehypeProductVideo, { origins: videoOrigins }]],
|
|
1681
|
+
[videoOrigins]
|
|
1682
|
+
);
|
|
1683
|
+
const overrides = React.useMemo(
|
|
1684
|
+
() => components ? { ...ANSWER_MARKDOWN_COMPONENTS, ...components } : ANSWER_MARKDOWN_COMPONENTS,
|
|
1685
|
+
[components]
|
|
1686
|
+
);
|
|
1687
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("boff-prose boff-explore-body min-w-0", className), children: /* @__PURE__ */ jsxRuntime.jsx(AnswerScopeContext.Provider, { value: scope, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { components: overrides, rehypePlugins, children }) }) });
|
|
1159
1688
|
}
|
|
1160
1689
|
|
|
1161
1690
|
// src/components/explore-types.ts
|
|
@@ -1768,6 +2297,35 @@ function useWebSDKConfig() {
|
|
|
1768
2297
|
return client.getConfig();
|
|
1769
2298
|
}
|
|
1770
2299
|
|
|
2300
|
+
// src/utils/product-slug.ts
|
|
2301
|
+
var PRODUCT_SLUG_RENAMES = Object.freeze(
|
|
2302
|
+
[Object.freeze({ legacy: "botlit", current: "botmanor" })]
|
|
2303
|
+
);
|
|
2304
|
+
Object.freeze(
|
|
2305
|
+
Object.fromEntries(PRODUCT_SLUG_RENAMES.map((r) => [r.legacy, r.current]))
|
|
2306
|
+
);
|
|
2307
|
+
var PRODUCT_SLUG_EMIT = Object.freeze({ productSlug: "legacy" });
|
|
2308
|
+
function findRename(slug) {
|
|
2309
|
+
const wanted = slug.toLowerCase();
|
|
2310
|
+
return PRODUCT_SLUG_RENAMES.find(
|
|
2311
|
+
(r) => r.legacy === wanted || r.current === wanted
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
function normalizeProductSlug(slug) {
|
|
2315
|
+
if (slug == null) return slug;
|
|
2316
|
+
return findRename(slug)?.current ?? slug;
|
|
2317
|
+
}
|
|
2318
|
+
function legacyProductSlug(slug) {
|
|
2319
|
+
return findRename(slug)?.legacy ?? slug;
|
|
2320
|
+
}
|
|
2321
|
+
function isSameProductSlug(a, b) {
|
|
2322
|
+
if (a == null || b == null) return a === b;
|
|
2323
|
+
return a === b || normalizeProductSlug(a) === normalizeProductSlug(b);
|
|
2324
|
+
}
|
|
2325
|
+
function emitProductSlug(slug) {
|
|
2326
|
+
return PRODUCT_SLUG_EMIT.productSlug === "current" ? normalizeProductSlug(slug) : legacyProductSlug(slug);
|
|
2327
|
+
}
|
|
2328
|
+
|
|
1771
2329
|
// src/data/products.ts
|
|
1772
2330
|
var ECOSYSTEM_PRODUCTS = [
|
|
1773
2331
|
{
|
|
@@ -1790,7 +2348,12 @@ var ECOSYSTEM_PRODUCTS = [
|
|
|
1790
2348
|
},
|
|
1791
2349
|
// BotManor ships on www.botmanor.com (the apex redirects there; the old botlit.ai domain
|
|
1792
2350
|
// still serves the same site but is no longer the name we link to).
|
|
1793
|
-
|
|
2351
|
+
// The slug follows the botlit → botmanor rename switch (utils/product-slug.ts).
|
|
2352
|
+
{
|
|
2353
|
+
slug: emitProductSlug("botmanor"),
|
|
2354
|
+
name: "BotManor",
|
|
2355
|
+
websiteUrl: "https://www.botmanor.com"
|
|
2356
|
+
},
|
|
1794
2357
|
{
|
|
1795
2358
|
slug: "bigconsole",
|
|
1796
2359
|
name: "BigConsole",
|
|
@@ -3064,7 +3627,7 @@ function isHighSurrogate(code) {
|
|
|
3064
3627
|
}
|
|
3065
3628
|
function safeInlineEnd(line) {
|
|
3066
3629
|
let codeSpan = null;
|
|
3067
|
-
|
|
3630
|
+
const links = [];
|
|
3068
3631
|
const emphasis = [];
|
|
3069
3632
|
let at = 0;
|
|
3070
3633
|
while (at < line.length) {
|
|
@@ -3073,6 +3636,13 @@ function safeInlineEnd(line) {
|
|
|
3073
3636
|
at += 2;
|
|
3074
3637
|
continue;
|
|
3075
3638
|
}
|
|
3639
|
+
const innermost = links[links.length - 1];
|
|
3640
|
+
if (innermost !== void 0 && innermost.kind !== "label") {
|
|
3641
|
+
if (char === "(") links.push({ kind: "paren", start: innermost.start });
|
|
3642
|
+
else if (char === ")") links.pop();
|
|
3643
|
+
at += 1;
|
|
3644
|
+
continue;
|
|
3645
|
+
}
|
|
3076
3646
|
if (char === "`") {
|
|
3077
3647
|
let run = 1;
|
|
3078
3648
|
while (line[at + run] === "`") run += 1;
|
|
@@ -3085,17 +3655,17 @@ function safeInlineEnd(line) {
|
|
|
3085
3655
|
continue;
|
|
3086
3656
|
}
|
|
3087
3657
|
if (char === "[") {
|
|
3088
|
-
|
|
3089
|
-
at += 1;
|
|
3090
|
-
continue;
|
|
3091
|
-
}
|
|
3092
|
-
if (char === "]") {
|
|
3093
|
-
if (link !== null && line[at + 1] !== "(") link = null;
|
|
3658
|
+
links.push({ kind: "label", start: line[at - 1] === "!" ? at - 1 : at });
|
|
3094
3659
|
at += 1;
|
|
3095
3660
|
continue;
|
|
3096
3661
|
}
|
|
3097
|
-
if (char === "
|
|
3098
|
-
|
|
3662
|
+
if (char === "]" && innermost !== void 0) {
|
|
3663
|
+
links.pop();
|
|
3664
|
+
if (line[at + 1] === "(") {
|
|
3665
|
+
links.push({ kind: "destination", start: innermost.start });
|
|
3666
|
+
at += 2;
|
|
3667
|
+
continue;
|
|
3668
|
+
}
|
|
3099
3669
|
at += 1;
|
|
3100
3670
|
continue;
|
|
3101
3671
|
}
|
|
@@ -3122,9 +3692,11 @@ function safeInlineEnd(line) {
|
|
|
3122
3692
|
}
|
|
3123
3693
|
at += 1;
|
|
3124
3694
|
}
|
|
3125
|
-
const open = [
|
|
3126
|
-
|
|
3127
|
-
|
|
3695
|
+
const open = [
|
|
3696
|
+
codeSpan,
|
|
3697
|
+
links[0]?.start ?? null,
|
|
3698
|
+
emphasis[0]?.start ?? null
|
|
3699
|
+
].filter((value) => value !== null);
|
|
3128
3700
|
return open.length === 0 ? line.length : Math.min(...open);
|
|
3129
3701
|
}
|
|
3130
3702
|
function revealBoundary(text, limit) {
|
|
@@ -5111,6 +5683,7 @@ function AssistantBubble({
|
|
|
5111
5683
|
message,
|
|
5112
5684
|
activity,
|
|
5113
5685
|
imageOrigins,
|
|
5686
|
+
productSlugs,
|
|
5114
5687
|
productNames,
|
|
5115
5688
|
speechLang,
|
|
5116
5689
|
onNavigate,
|
|
@@ -5225,6 +5798,7 @@ function AssistantBubble({
|
|
|
5225
5798
|
{
|
|
5226
5799
|
strings: T,
|
|
5227
5800
|
imageOrigins,
|
|
5801
|
+
productSlugs,
|
|
5228
5802
|
enabled: completed,
|
|
5229
5803
|
className: "boff-prose-sm text-card-foreground",
|
|
5230
5804
|
children: visible
|
|
@@ -5495,21 +6069,24 @@ function ProductStrip({
|
|
|
5495
6069
|
children: T.allProducts
|
|
5496
6070
|
}
|
|
5497
6071
|
),
|
|
5498
|
-
products.map((product) =>
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
"
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
6072
|
+
products.map((product) => {
|
|
6073
|
+
const focused = isSameProductSlug(focusProduct, product.slug);
|
|
6074
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6075
|
+
"button",
|
|
6076
|
+
{
|
|
6077
|
+
"data-boff-explore": "chip",
|
|
6078
|
+
"data-chip-kind": "product",
|
|
6079
|
+
"data-product": product.slug,
|
|
6080
|
+
type: "button",
|
|
6081
|
+
"aria-pressed": focused,
|
|
6082
|
+
title: product.tagline ?? product.name,
|
|
6083
|
+
onClick: () => onFocus(focused ? null : product.slug),
|
|
6084
|
+
className: chipClass(focused),
|
|
6085
|
+
children: product.name
|
|
6086
|
+
},
|
|
6087
|
+
product.slug
|
|
6088
|
+
);
|
|
6089
|
+
})
|
|
5513
6090
|
]
|
|
5514
6091
|
}
|
|
5515
6092
|
),
|
|
@@ -5967,11 +6544,11 @@ function ExplorePage({
|
|
|
5967
6544
|
const resolvedBody = welcomeBody ?? catalog.welcomeBody;
|
|
5968
6545
|
const prompts = catalog.examplePrompts;
|
|
5969
6546
|
const focusedProductName = React.useMemo(
|
|
5970
|
-
() => catalog.products.find((p) => p.slug
|
|
6547
|
+
() => catalog.products.find((p) => isSameProductSlug(p.slug, focusProduct))?.name ?? null,
|
|
5971
6548
|
[catalog.products, focusProduct]
|
|
5972
6549
|
);
|
|
5973
6550
|
const headProductName = React.useMemo(
|
|
5974
|
-
() => productSlug ? catalog.products.find((p) => p.slug
|
|
6551
|
+
() => productSlug ? catalog.products.find((p) => isSameProductSlug(p.slug, productSlug))?.name ?? null : null,
|
|
5975
6552
|
[catalog.products, productSlug]
|
|
5976
6553
|
);
|
|
5977
6554
|
const imageOrigins = React.useMemo(
|
|
@@ -5982,6 +6559,10 @@ function ExplorePage({
|
|
|
5982
6559
|
() => productNamesByOrigin(catalog.products),
|
|
5983
6560
|
[catalog.products]
|
|
5984
6561
|
);
|
|
6562
|
+
const productSlugs = React.useMemo(
|
|
6563
|
+
() => productSlugsByOrigin(catalog.products),
|
|
6564
|
+
[catalog.products]
|
|
6565
|
+
);
|
|
5985
6566
|
React.useEffect(() => {
|
|
5986
6567
|
const el = textareaRef.current;
|
|
5987
6568
|
if (!el) return;
|
|
@@ -6544,6 +7125,7 @@ function ExplorePage({
|
|
|
6544
7125
|
message,
|
|
6545
7126
|
activity,
|
|
6546
7127
|
imageOrigins,
|
|
7128
|
+
productSlugs,
|
|
6547
7129
|
productNames,
|
|
6548
7130
|
speechLang,
|
|
6549
7131
|
onNavigate,
|
|
@@ -6874,6 +7456,7 @@ exports.answerSpeechText = answerSpeechText;
|
|
|
6874
7456
|
exports.exploreQuestionHref = exploreQuestionHref;
|
|
6875
7457
|
exports.inlineImageOrigins = inlineImageOrigins;
|
|
6876
7458
|
exports.isInlineImageAllowed = isInlineImageAllowed;
|
|
7459
|
+
exports.isInlineVideoAllowed = isInlineVideoAllowed;
|
|
6877
7460
|
exports.logoUrlOf = logoUrlOf;
|
|
6878
7461
|
exports.productNamesByOrigin = productNamesByOrigin;
|
|
6879
7462
|
exports.referenceKindOf = referenceKindOf;
|