@burdenoff/website-sdk 2026.828.4 → 2026.828.6
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/explore-cta.js +9 -7
- package/dist/components/explore-cta.js.map +1 -1
- package/dist/components/explore-cta.mjs +9 -7
- package/dist/components/explore-cta.mjs.map +1 -1
- package/dist/components/explore.d.mts +11 -1
- package/dist/components/explore.d.ts +11 -1
- package/dist/components/explore.js +561 -57
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +563 -60
- package/dist/components/explore.mjs.map +1 -1
- package/dist/hooks/index.d.mts +24 -1
- package/dist/hooks/index.d.ts +24 -1
- package/dist/hooks/index.js +298 -5
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +298 -6
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +569 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +570 -65
- package/dist/index.mjs.map +1 -1
- package/dist/{index-CUJTTlYx.d.mts → use-explore-chat-DhvZhH8f.d.mts} +21 -2
- package/dist/{index-CUJTTlYx.d.ts → use-explore-chat-DhvZhH8f.d.ts} +21 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5276,6 +5276,8 @@ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
|
|
|
5276
5276
|
|
|
5277
5277
|
// src/hooks/use-explore-chat.ts
|
|
5278
5278
|
var DEFAULT_STORAGE_KEY = "boff.explore.v1";
|
|
5279
|
+
var HISTORY_SUFFIX = ".history";
|
|
5280
|
+
var MAX_ARCHIVED_CONVERSATIONS = 15;
|
|
5279
5281
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
5280
5282
|
var DEFAULT_POLL_TIMEOUT_MS = 9e4;
|
|
5281
5283
|
var POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
@@ -5414,10 +5416,58 @@ function classifyExploreError(code, serverMessage) {
|
|
|
5414
5416
|
retryable: true
|
|
5415
5417
|
};
|
|
5416
5418
|
}
|
|
5419
|
+
function historyKey(key) {
|
|
5420
|
+
return `${key}${HISTORY_SUFFIX}`;
|
|
5421
|
+
}
|
|
5422
|
+
function readArchive(key) {
|
|
5423
|
+
if (typeof window === "undefined") return [];
|
|
5424
|
+
try {
|
|
5425
|
+
const raw = window.localStorage.getItem(historyKey(key));
|
|
5426
|
+
if (!raw) return [];
|
|
5427
|
+
const parsed = JSON.parse(raw);
|
|
5428
|
+
if (!Array.isArray(parsed)) return [];
|
|
5429
|
+
return parsed.filter(
|
|
5430
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.token === "string" && Array.isArray(entry.messages)
|
|
5431
|
+
);
|
|
5432
|
+
} catch {
|
|
5433
|
+
return [];
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
function writeArchive(key, entries) {
|
|
5437
|
+
if (typeof window === "undefined") return;
|
|
5438
|
+
try {
|
|
5439
|
+
if (entries.length === 0) {
|
|
5440
|
+
window.localStorage.removeItem(historyKey(key));
|
|
5441
|
+
return;
|
|
5442
|
+
}
|
|
5443
|
+
window.localStorage.setItem(historyKey(key), JSON.stringify(entries));
|
|
5444
|
+
} catch {
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
function upsertArchive(key, thread) {
|
|
5448
|
+
if (!thread.token) return readArchive(key);
|
|
5449
|
+
const real = thread.messages.filter(
|
|
5450
|
+
(m) => m.content.trim() !== "" || m.role === "USER"
|
|
5451
|
+
);
|
|
5452
|
+
if (real.length === 0) return readArchive(key);
|
|
5453
|
+
const firstUser = real.find((m) => m.role === "USER");
|
|
5454
|
+
const entry = {
|
|
5455
|
+
token: thread.token,
|
|
5456
|
+
title: (firstUser?.content ?? "Conversation").trim().slice(0, 80),
|
|
5457
|
+
updatedAt: real[real.length - 1]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5458
|
+
messageCount: real.length,
|
|
5459
|
+
focusProduct: thread.focusProduct,
|
|
5460
|
+
messages: real.slice(-20)
|
|
5461
|
+
};
|
|
5462
|
+
const rest = readArchive(key).filter((e) => e.token !== entry.token);
|
|
5463
|
+
const next = [entry, ...rest].slice(0, MAX_ARCHIVED_CONVERSATIONS);
|
|
5464
|
+
writeArchive(key, next);
|
|
5465
|
+
return next;
|
|
5466
|
+
}
|
|
5417
5467
|
function readPersisted(key) {
|
|
5418
5468
|
if (typeof window === "undefined") return null;
|
|
5419
5469
|
try {
|
|
5420
|
-
const raw = window.
|
|
5470
|
+
const raw = window.localStorage.getItem(key);
|
|
5421
5471
|
if (!raw) return null;
|
|
5422
5472
|
const parsed = JSON.parse(raw);
|
|
5423
5473
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -5462,6 +5512,8 @@ function useExploreChat(options = {}) {
|
|
|
5462
5512
|
const [remainingToday, setRemainingToday] = React.useState(null);
|
|
5463
5513
|
const [focusProduct, setFocusProductState] = React.useState(null);
|
|
5464
5514
|
const [turnCount, setTurnCount] = React.useState(0);
|
|
5515
|
+
const messagesRef = React.useRef([]);
|
|
5516
|
+
const [archive, setArchive] = React.useState([]);
|
|
5465
5517
|
const [hydrated, setHydrated] = React.useState(false);
|
|
5466
5518
|
const mountedRef = React.useRef(true);
|
|
5467
5519
|
const pollGenerationRef = React.useRef(0);
|
|
@@ -5499,6 +5551,7 @@ function useExploreChat(options = {}) {
|
|
|
5499
5551
|
setHydrated(true);
|
|
5500
5552
|
return;
|
|
5501
5553
|
}
|
|
5554
|
+
setArchive(readArchive(storageKey));
|
|
5502
5555
|
const stored = readPersisted(storageKey);
|
|
5503
5556
|
if (stored) {
|
|
5504
5557
|
conversationTokenRef.current = stored.conversationToken;
|
|
@@ -5527,13 +5580,28 @@ function useExploreChat(options = {}) {
|
|
|
5527
5580
|
focusProduct
|
|
5528
5581
|
};
|
|
5529
5582
|
if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
|
|
5530
|
-
window.
|
|
5583
|
+
window.localStorage.removeItem(storageKey);
|
|
5531
5584
|
return;
|
|
5532
5585
|
}
|
|
5533
|
-
window.
|
|
5586
|
+
window.localStorage.setItem(storageKey, JSON.stringify(payload));
|
|
5534
5587
|
} catch {
|
|
5535
5588
|
}
|
|
5536
5589
|
}, [persist, hydrated, storageKey, messages, focusProduct]);
|
|
5590
|
+
React.useEffect(() => {
|
|
5591
|
+
messagesRef.current = messages;
|
|
5592
|
+
if (!persist || !hydrated) return;
|
|
5593
|
+
const settled = messages.some(
|
|
5594
|
+
(m) => m.role === "ASSISTANT" && (m.status === "COMPLETED" || m.status === "FAILED")
|
|
5595
|
+
);
|
|
5596
|
+
if (!settled) return;
|
|
5597
|
+
setArchive(
|
|
5598
|
+
upsertArchive(storageKey, {
|
|
5599
|
+
token: conversationTokenRef.current,
|
|
5600
|
+
messages,
|
|
5601
|
+
focusProduct: focusProductRef.current
|
|
5602
|
+
})
|
|
5603
|
+
);
|
|
5604
|
+
}, [messages, persist, hydrated, storageKey]);
|
|
5537
5605
|
React.useEffect(() => {
|
|
5538
5606
|
let cancelled = false;
|
|
5539
5607
|
const load = async () => {
|
|
@@ -5819,6 +5887,13 @@ function useExploreChat(options = {}) {
|
|
|
5819
5887
|
void send(text);
|
|
5820
5888
|
}, [cancelPolling, send]);
|
|
5821
5889
|
const reset = React.useCallback(() => {
|
|
5890
|
+
setArchive(
|
|
5891
|
+
upsertArchive(storageKey, {
|
|
5892
|
+
token: conversationTokenRef.current,
|
|
5893
|
+
messages: messagesRef.current,
|
|
5894
|
+
focusProduct: focusProductRef.current
|
|
5895
|
+
})
|
|
5896
|
+
);
|
|
5822
5897
|
cancelPolling();
|
|
5823
5898
|
inFlightRef.current = false;
|
|
5824
5899
|
conversationTokenRef.current = null;
|
|
@@ -5831,11 +5906,48 @@ function useExploreChat(options = {}) {
|
|
|
5831
5906
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
5832
5907
|
if (typeof window !== "undefined") {
|
|
5833
5908
|
try {
|
|
5834
|
-
window.
|
|
5909
|
+
window.localStorage.removeItem(storageKey);
|
|
5835
5910
|
} catch {
|
|
5836
5911
|
}
|
|
5837
5912
|
}
|
|
5838
5913
|
}, [cancelPolling, storageKey]);
|
|
5914
|
+
const openConversation = React.useCallback(
|
|
5915
|
+
(token) => {
|
|
5916
|
+
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
5917
|
+
if (!entry) return;
|
|
5918
|
+
upsertArchive(storageKey, {
|
|
5919
|
+
token: conversationTokenRef.current,
|
|
5920
|
+
messages: messagesRef.current,
|
|
5921
|
+
focusProduct: focusProductRef.current
|
|
5922
|
+
});
|
|
5923
|
+
cancelPolling();
|
|
5924
|
+
inFlightRef.current = false;
|
|
5925
|
+
conversationTokenRef.current = entry.token;
|
|
5926
|
+
turnCountRef.current = entry.messages.filter(
|
|
5927
|
+
(m) => m.role === "USER"
|
|
5928
|
+
).length;
|
|
5929
|
+
setTurnCount(turnCountRef.current);
|
|
5930
|
+
setMessages(entry.messages);
|
|
5931
|
+
focusProductRef.current = entry.focusProduct;
|
|
5932
|
+
setFocusProductState(entry.focusProduct);
|
|
5933
|
+
setError(null);
|
|
5934
|
+
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
5935
|
+
setArchive(readArchive(storageKey));
|
|
5936
|
+
},
|
|
5937
|
+
[cancelPolling, storageKey]
|
|
5938
|
+
);
|
|
5939
|
+
const deleteConversation = React.useCallback(
|
|
5940
|
+
(token) => {
|
|
5941
|
+
const next = readArchive(storageKey).filter((e) => e.token !== token);
|
|
5942
|
+
writeArchive(storageKey, next);
|
|
5943
|
+
setArchive(next);
|
|
5944
|
+
},
|
|
5945
|
+
[storageKey]
|
|
5946
|
+
);
|
|
5947
|
+
const clearHistory = React.useCallback(() => {
|
|
5948
|
+
writeArchive(storageKey, []);
|
|
5949
|
+
setArchive([]);
|
|
5950
|
+
}, [storageKey]);
|
|
5839
5951
|
const setFocusProduct = React.useCallback((slug) => {
|
|
5840
5952
|
focusProductRef.current = slug;
|
|
5841
5953
|
setFocusProductState(slug);
|
|
@@ -5864,7 +5976,11 @@ function useExploreChat(options = {}) {
|
|
|
5864
5976
|
stop,
|
|
5865
5977
|
retry,
|
|
5866
5978
|
reset,
|
|
5867
|
-
canSend
|
|
5979
|
+
canSend,
|
|
5980
|
+
history: archive,
|
|
5981
|
+
openConversation,
|
|
5982
|
+
deleteConversation,
|
|
5983
|
+
clearHistory
|
|
5868
5984
|
};
|
|
5869
5985
|
}
|
|
5870
5986
|
var optimisticCounter = 0;
|
|
@@ -5872,6 +5988,182 @@ function makeOptimisticId() {
|
|
|
5872
5988
|
optimisticCounter += 1;
|
|
5873
5989
|
return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
|
|
5874
5990
|
}
|
|
5991
|
+
function getRecognitionCtor() {
|
|
5992
|
+
if (typeof window === "undefined") return null;
|
|
5993
|
+
const w = window;
|
|
5994
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
5995
|
+
}
|
|
5996
|
+
var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
5997
|
+
async function ensureMicrophoneAccess() {
|
|
5998
|
+
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
5999
|
+
if (!media?.getUserMedia) return { ok: true };
|
|
6000
|
+
try {
|
|
6001
|
+
const status = await navigator.permissions?.query({
|
|
6002
|
+
name: "microphone"
|
|
6003
|
+
});
|
|
6004
|
+
if (status?.state === "granted") return { ok: true };
|
|
6005
|
+
if (status?.state === "denied")
|
|
6006
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
6007
|
+
} catch {
|
|
6008
|
+
}
|
|
6009
|
+
try {
|
|
6010
|
+
const stream = await media.getUserMedia({ audio: true });
|
|
6011
|
+
for (const track of stream.getTracks()) track.stop();
|
|
6012
|
+
return { ok: true };
|
|
6013
|
+
} catch (error) {
|
|
6014
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
6015
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
6016
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
6017
|
+
}
|
|
6018
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
6019
|
+
return { ok: false, message: "No microphone was found." };
|
|
6020
|
+
}
|
|
6021
|
+
return {
|
|
6022
|
+
ok: false,
|
|
6023
|
+
message: "Dictation could not start. You can type instead."
|
|
6024
|
+
};
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
function describeError(code) {
|
|
6028
|
+
switch (code) {
|
|
6029
|
+
case "not-allowed":
|
|
6030
|
+
case "service-not-allowed":
|
|
6031
|
+
return MIC_DENIED_MESSAGE;
|
|
6032
|
+
case "no-speech":
|
|
6033
|
+
return "I didn't catch anything \u2014 try again a little closer to the mic.";
|
|
6034
|
+
case "audio-capture":
|
|
6035
|
+
return "No microphone was found.";
|
|
6036
|
+
case "network":
|
|
6037
|
+
return "Speech recognition needs a network connection.";
|
|
6038
|
+
case "aborted":
|
|
6039
|
+
return "";
|
|
6040
|
+
default:
|
|
6041
|
+
return "Dictation stopped unexpectedly. You can type instead.";
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
function useSpeechInput({
|
|
6045
|
+
onFinalTranscript,
|
|
6046
|
+
lang,
|
|
6047
|
+
onError
|
|
6048
|
+
}) {
|
|
6049
|
+
const [supported] = React.useState(() => getRecognitionCtor() !== null);
|
|
6050
|
+
const [listening, setListening] = React.useState(false);
|
|
6051
|
+
const [interim, setInterim] = React.useState("");
|
|
6052
|
+
const [error, setError] = React.useState(null);
|
|
6053
|
+
const recognitionRef = React.useRef(null);
|
|
6054
|
+
const startTokenRef = React.useRef(0);
|
|
6055
|
+
const beginRecognitionRef = React.useRef(null);
|
|
6056
|
+
const beginRecognition = React.useCallback(
|
|
6057
|
+
(Ctor, token) => {
|
|
6058
|
+
beginRecognitionRef.current?.(Ctor, token);
|
|
6059
|
+
},
|
|
6060
|
+
[]
|
|
6061
|
+
);
|
|
6062
|
+
const finalRef = React.useRef(onFinalTranscript);
|
|
6063
|
+
const errorRef = React.useRef(onError);
|
|
6064
|
+
finalRef.current = onFinalTranscript;
|
|
6065
|
+
errorRef.current = onError;
|
|
6066
|
+
const stop = React.useCallback(() => {
|
|
6067
|
+
startTokenRef.current += 1;
|
|
6068
|
+
setListening(false);
|
|
6069
|
+
setInterim("");
|
|
6070
|
+
const recognition = recognitionRef.current;
|
|
6071
|
+
if (!recognition) return;
|
|
6072
|
+
try {
|
|
6073
|
+
recognition.stop();
|
|
6074
|
+
} catch {
|
|
6075
|
+
}
|
|
6076
|
+
setListening(false);
|
|
6077
|
+
setInterim("");
|
|
6078
|
+
}, []);
|
|
6079
|
+
const start = React.useCallback(() => {
|
|
6080
|
+
const Ctor = getRecognitionCtor();
|
|
6081
|
+
if (!Ctor) return;
|
|
6082
|
+
if (recognitionRef.current) stop();
|
|
6083
|
+
setError(null);
|
|
6084
|
+
setListening(true);
|
|
6085
|
+
startTokenRef.current += 1;
|
|
6086
|
+
const token = startTokenRef.current;
|
|
6087
|
+
void ensureMicrophoneAccess().then((access) => {
|
|
6088
|
+
if (token !== startTokenRef.current) return;
|
|
6089
|
+
if (!access.ok) {
|
|
6090
|
+
setListening(false);
|
|
6091
|
+
setError(access.message);
|
|
6092
|
+
errorRef.current?.(access.message);
|
|
6093
|
+
return;
|
|
6094
|
+
}
|
|
6095
|
+
beginRecognition(Ctor, token);
|
|
6096
|
+
});
|
|
6097
|
+
}, [beginRecognition, stop]);
|
|
6098
|
+
const beginRecognitionImpl = React.useCallback(
|
|
6099
|
+
(Ctor, token) => {
|
|
6100
|
+
const recognition = new Ctor();
|
|
6101
|
+
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
6102
|
+
recognition.continuous = true;
|
|
6103
|
+
recognition.interimResults = true;
|
|
6104
|
+
recognition.maxAlternatives = 1;
|
|
6105
|
+
recognition.onstart = () => {
|
|
6106
|
+
if (token !== startTokenRef.current) return;
|
|
6107
|
+
setError(null);
|
|
6108
|
+
setListening(true);
|
|
6109
|
+
};
|
|
6110
|
+
recognition.onresult = (event) => {
|
|
6111
|
+
let settled = "";
|
|
6112
|
+
let pending = "";
|
|
6113
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
6114
|
+
const result = event.results[i];
|
|
6115
|
+
if (!result) continue;
|
|
6116
|
+
const text = result[0]?.transcript ?? "";
|
|
6117
|
+
if (result.isFinal) settled += text;
|
|
6118
|
+
else pending += text;
|
|
6119
|
+
}
|
|
6120
|
+
setInterim(pending);
|
|
6121
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
6122
|
+
};
|
|
6123
|
+
recognition.onerror = (event) => {
|
|
6124
|
+
const message = describeError(event.error);
|
|
6125
|
+
setListening(false);
|
|
6126
|
+
setInterim("");
|
|
6127
|
+
if (message !== "") {
|
|
6128
|
+
setError(message);
|
|
6129
|
+
errorRef.current?.(message);
|
|
6130
|
+
}
|
|
6131
|
+
};
|
|
6132
|
+
recognition.onend = () => {
|
|
6133
|
+
setListening(false);
|
|
6134
|
+
setInterim("");
|
|
6135
|
+
};
|
|
6136
|
+
recognitionRef.current = recognition;
|
|
6137
|
+
try {
|
|
6138
|
+
recognition.start();
|
|
6139
|
+
} catch {
|
|
6140
|
+
setListening(false);
|
|
6141
|
+
}
|
|
6142
|
+
},
|
|
6143
|
+
[lang]
|
|
6144
|
+
);
|
|
6145
|
+
beginRecognitionRef.current = beginRecognitionImpl;
|
|
6146
|
+
const toggle = React.useCallback(() => {
|
|
6147
|
+
if (listening) stop();
|
|
6148
|
+
else start();
|
|
6149
|
+
}, [listening, start, stop]);
|
|
6150
|
+
React.useEffect(
|
|
6151
|
+
() => () => {
|
|
6152
|
+
const recognition = recognitionRef.current;
|
|
6153
|
+
if (!recognition) return;
|
|
6154
|
+
recognition.onresult = null;
|
|
6155
|
+
recognition.onerror = null;
|
|
6156
|
+
recognition.onend = null;
|
|
6157
|
+
recognition.onstart = null;
|
|
6158
|
+
try {
|
|
6159
|
+
recognition.abort();
|
|
6160
|
+
} catch {
|
|
6161
|
+
}
|
|
6162
|
+
},
|
|
6163
|
+
[]
|
|
6164
|
+
);
|
|
6165
|
+
return { supported, listening, interim, error, start, stop, toggle };
|
|
6166
|
+
}
|
|
5875
6167
|
var EXPLORE_CSS = `
|
|
5876
6168
|
@keyframes boff-explore-pulse {
|
|
5877
6169
|
0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
|
|
@@ -5966,10 +6258,33 @@ function initialOf(value, fallback) {
|
|
|
5966
6258
|
const source = (value || fallback).trim();
|
|
5967
6259
|
return source ? source.slice(0, 1).toUpperCase() : "?";
|
|
5968
6260
|
}
|
|
6261
|
+
function logoUrlOf(url) {
|
|
6262
|
+
try {
|
|
6263
|
+
const { origin, protocol } = new URL(url);
|
|
6264
|
+
if (protocol !== "https:" && protocol !== "http:") return null;
|
|
6265
|
+
return `${origin}/favicon.svg`;
|
|
6266
|
+
} catch {
|
|
6267
|
+
return null;
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
5969
6270
|
function ReferenceCard({ reference }) {
|
|
5970
6271
|
const [imageFailed, setImageFailed] = React.useState(false);
|
|
6272
|
+
const [logoFailed, setLogoFailed] = React.useState(false);
|
|
5971
6273
|
const host = hostnameOf(reference.url);
|
|
6274
|
+
const logoUrl = logoUrlOf(reference.url);
|
|
5972
6275
|
const showImage = Boolean(reference.imageUrl) && !imageFailed;
|
|
6276
|
+
const showLogo = Boolean(logoUrl) && !logoFailed;
|
|
6277
|
+
const letterPlate = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/25 via-primary/5 to-secondary", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-primary/70 sm:text-2xl", children: initialOf(reference.product, reference.title || host) }) });
|
|
6278
|
+
const logoImg = showLogo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6279
|
+
"img",
|
|
6280
|
+
{
|
|
6281
|
+
src: logoUrl ?? "",
|
|
6282
|
+
alt: "",
|
|
6283
|
+
loading: "lazy",
|
|
6284
|
+
onError: () => setLogoFailed(true),
|
|
6285
|
+
className: "h-full w-full object-contain p-1.5 sm:p-0"
|
|
6286
|
+
}
|
|
6287
|
+
) : letterPlate;
|
|
5973
6288
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5974
6289
|
"a",
|
|
5975
6290
|
{
|
|
@@ -5977,9 +6292,10 @@ function ReferenceCard({ reference }) {
|
|
|
5977
6292
|
href: reference.url,
|
|
5978
6293
|
target: "_blank",
|
|
5979
6294
|
rel: "noopener noreferrer",
|
|
5980
|
-
className: "group flex flex-
|
|
6295
|
+
className: "group flex flex-row items-center gap-3 overflow-hidden rounded-xl border border-border bg-card p-2 transition-colors hover:border-primary/40 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:flex-col sm:items-stretch sm:gap-0 sm:p-0",
|
|
5981
6296
|
children: [
|
|
5982
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative
|
|
6297
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
|
|
6298
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative hidden aspect-[16/9] w-full overflow-hidden bg-muted sm:block", children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5983
6299
|
"img",
|
|
5984
6300
|
{
|
|
5985
6301
|
src: reference.imageUrl ?? "",
|
|
@@ -5988,17 +6304,22 @@ function ReferenceCard({ reference }) {
|
|
|
5988
6304
|
onError: () => setImageFailed(true),
|
|
5989
6305
|
className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
5990
6306
|
}
|
|
5991
|
-
) : (
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
6307
|
+
) : showLogo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/15 via-transparent to-secondary/60 p-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6308
|
+
"img",
|
|
6309
|
+
{
|
|
6310
|
+
src: logoUrl ?? "",
|
|
6311
|
+
alt: "",
|
|
6312
|
+
loading: "lazy",
|
|
6313
|
+
onError: () => setLogoFailed(true),
|
|
6314
|
+
className: "max-h-full max-w-full object-contain"
|
|
6315
|
+
}
|
|
6316
|
+
) }) : letterPlate }),
|
|
6317
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
|
|
5997
6318
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
|
|
5998
|
-
reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: reference.description }) : null,
|
|
5999
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
6000
|
-
reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground", children: reference.product }) : null,
|
|
6001
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "
|
|
6319
|
+
reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground max-sm:hidden", children: reference.description }) : null,
|
|
6320
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
|
|
6321
|
+
reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground max-sm:hidden", children: reference.product }) : null,
|
|
6322
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground sm:ml-auto", children: [
|
|
6002
6323
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3 shrink-0" }),
|
|
6003
6324
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: host })
|
|
6004
6325
|
] })
|
|
@@ -6084,7 +6405,8 @@ function UserBubble({ message }) {
|
|
|
6084
6405
|
function AssistantBubble({
|
|
6085
6406
|
message,
|
|
6086
6407
|
activity,
|
|
6087
|
-
onNavigate
|
|
6408
|
+
onNavigate,
|
|
6409
|
+
anchorRef
|
|
6088
6410
|
}) {
|
|
6089
6411
|
const streaming = message.status === "PENDING" || message.status === "RUNNING";
|
|
6090
6412
|
const body = message.content || message.partialContent || "";
|
|
@@ -6092,9 +6414,10 @@ function AssistantBubble({
|
|
|
6092
6414
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6093
6415
|
"div",
|
|
6094
6416
|
{
|
|
6417
|
+
ref: anchorRef,
|
|
6095
6418
|
"data-boff-explore": "assistant",
|
|
6096
6419
|
"data-status": message.status,
|
|
6097
|
-
className: "flex gap-3",
|
|
6420
|
+
className: "flex scroll-mt-4 gap-3",
|
|
6098
6421
|
children: [
|
|
6099
6422
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6100
6423
|
"span",
|
|
@@ -6299,11 +6622,32 @@ function ExplorePage({
|
|
|
6299
6622
|
stop,
|
|
6300
6623
|
retry,
|
|
6301
6624
|
reset,
|
|
6302
|
-
canSend
|
|
6625
|
+
canSend,
|
|
6626
|
+
history,
|
|
6627
|
+
openConversation,
|
|
6628
|
+
deleteConversation,
|
|
6629
|
+
clearHistory
|
|
6303
6630
|
} = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
|
|
6304
6631
|
const [draft, setDraft] = React.useState("");
|
|
6305
6632
|
const textareaRef = React.useRef(null);
|
|
6633
|
+
const [historyOpen, setHistoryOpen] = React.useState(false);
|
|
6634
|
+
const speechStopRef = React.useRef(() => void 0);
|
|
6635
|
+
const stopDictation = React.useCallback(() => {
|
|
6636
|
+
speechStopRef.current();
|
|
6637
|
+
}, []);
|
|
6638
|
+
const speech = useSpeechInput({
|
|
6639
|
+
onFinalTranscript: (text) => {
|
|
6640
|
+
setDraft((current) => {
|
|
6641
|
+
const joined = current.trim() === "" ? text.trimStart() : `${current.trimEnd()} ${text.trim()}`;
|
|
6642
|
+
return joined;
|
|
6643
|
+
});
|
|
6644
|
+
textareaRef.current?.focus();
|
|
6645
|
+
}
|
|
6646
|
+
});
|
|
6647
|
+
speechStopRef.current = speech.stop;
|
|
6306
6648
|
const scrollRef = React.useRef(null);
|
|
6649
|
+
const latestAssistantRef = React.useRef(null);
|
|
6650
|
+
const alignedForRef = React.useRef(null);
|
|
6307
6651
|
const stickToBottomRef = React.useRef(true);
|
|
6308
6652
|
const composingRef = React.useRef(false);
|
|
6309
6653
|
const busy = phase === "sending" || phase === "streaming";
|
|
@@ -6329,20 +6673,41 @@ function ExplorePage({
|
|
|
6329
6673
|
if (!el) return;
|
|
6330
6674
|
stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
6331
6675
|
}, []);
|
|
6676
|
+
const latestAssistantId = React.useMemo(() => {
|
|
6677
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
6678
|
+
const message = messages[i];
|
|
6679
|
+
if (message && message.role === "ASSISTANT") return message.id;
|
|
6680
|
+
}
|
|
6681
|
+
return null;
|
|
6682
|
+
}, [messages]);
|
|
6332
6683
|
React.useEffect(() => {
|
|
6684
|
+
const el = scrollRef.current;
|
|
6685
|
+
const anchor = latestAssistantRef.current;
|
|
6686
|
+
if (!el || !anchor || !latestAssistantId) return;
|
|
6687
|
+
if (alignedForRef.current === latestAssistantId) return;
|
|
6333
6688
|
if (!stickToBottomRef.current) return;
|
|
6689
|
+
if (anchor.offsetHeight === 0) return;
|
|
6690
|
+
alignedForRef.current = latestAssistantId;
|
|
6691
|
+
const delta = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
|
|
6692
|
+
el.scrollTop = Math.max(0, el.scrollTop + delta - 12);
|
|
6693
|
+
}, [latestAssistantId, messages]);
|
|
6694
|
+
React.useEffect(() => {
|
|
6695
|
+
if (!stickToBottomRef.current) return;
|
|
6696
|
+
if (latestAssistantId && alignedForRef.current === latestAssistantId)
|
|
6697
|
+
return;
|
|
6334
6698
|
const el = scrollRef.current;
|
|
6335
6699
|
if (!el) return;
|
|
6336
6700
|
el.scrollTop = el.scrollHeight;
|
|
6337
|
-
}, [
|
|
6701
|
+
}, [activity, latestAssistantId, messages]);
|
|
6338
6702
|
const submitDraft = React.useCallback(() => {
|
|
6339
6703
|
const text = draft.trim();
|
|
6340
6704
|
if (!text || overLimit || busy || !canSend) return;
|
|
6705
|
+
stopDictation();
|
|
6341
6706
|
setDraft("");
|
|
6342
6707
|
stickToBottomRef.current = true;
|
|
6343
6708
|
void send(text);
|
|
6344
6709
|
textareaRef.current?.focus();
|
|
6345
|
-
}, [busy, canSend, draft, overLimit, send]);
|
|
6710
|
+
}, [busy, canSend, draft, overLimit, send, stopDictation]);
|
|
6346
6711
|
const sendPrompt = React.useCallback(
|
|
6347
6712
|
(prompt) => {
|
|
6348
6713
|
if (!canSend || busy) return;
|
|
@@ -6415,8 +6780,96 @@ function ExplorePage({
|
|
|
6415
6780
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "New chat" })
|
|
6416
6781
|
]
|
|
6417
6782
|
}
|
|
6418
|
-
)
|
|
6783
|
+
),
|
|
6784
|
+
history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6785
|
+
Button,
|
|
6786
|
+
{
|
|
6787
|
+
"data-boff-explore": "history-toggle",
|
|
6788
|
+
type: "button",
|
|
6789
|
+
size: "sm",
|
|
6790
|
+
variant: "ghost",
|
|
6791
|
+
className: "shrink-0 text-muted-foreground",
|
|
6792
|
+
"aria-expanded": historyOpen,
|
|
6793
|
+
onClick: () => {
|
|
6794
|
+
setHistoryOpen((open) => !open);
|
|
6795
|
+
},
|
|
6796
|
+
children: [
|
|
6797
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.History, { className: "h-3.5 w-3.5" }),
|
|
6798
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden sm:inline", children: [
|
|
6799
|
+
"History (",
|
|
6800
|
+
history.length,
|
|
6801
|
+
")"
|
|
6802
|
+
] })
|
|
6803
|
+
]
|
|
6804
|
+
}
|
|
6805
|
+
) : null
|
|
6419
6806
|
] }) : null,
|
|
6807
|
+
historyOpen && history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6808
|
+
"div",
|
|
6809
|
+
{
|
|
6810
|
+
"data-boff-explore": "history-panel",
|
|
6811
|
+
className: "border-b border-border bg-muted/30 px-4 py-3",
|
|
6812
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl", children: [
|
|
6813
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
|
|
6814
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Your recent conversations" }),
|
|
6815
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6816
|
+
Button,
|
|
6817
|
+
{
|
|
6818
|
+
"data-boff-explore": "history-clear",
|
|
6819
|
+
type: "button",
|
|
6820
|
+
size: "sm",
|
|
6821
|
+
variant: "ghost",
|
|
6822
|
+
className: "h-7 shrink-0 text-xs text-muted-foreground hover:text-destructive",
|
|
6823
|
+
onClick: () => {
|
|
6824
|
+
clearHistory();
|
|
6825
|
+
setHistoryOpen(false);
|
|
6826
|
+
},
|
|
6827
|
+
children: [
|
|
6828
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
|
|
6829
|
+
"Clear history"
|
|
6830
|
+
]
|
|
6831
|
+
}
|
|
6832
|
+
)
|
|
6833
|
+
] }),
|
|
6834
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: history.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [
|
|
6835
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6836
|
+
"button",
|
|
6837
|
+
{
|
|
6838
|
+
"data-boff-explore": "history-item",
|
|
6839
|
+
type: "button",
|
|
6840
|
+
className: "flex-1 truncate rounded-md px-2 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
6841
|
+
onClick: () => {
|
|
6842
|
+
openConversation(entry.token);
|
|
6843
|
+
setHistoryOpen(false);
|
|
6844
|
+
},
|
|
6845
|
+
children: [
|
|
6846
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: entry.title }),
|
|
6847
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
|
|
6848
|
+
entry.messageCount,
|
|
6849
|
+
" message",
|
|
6850
|
+
entry.messageCount === 1 ? "" : "s"
|
|
6851
|
+
] })
|
|
6852
|
+
]
|
|
6853
|
+
}
|
|
6854
|
+
),
|
|
6855
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6856
|
+
Button,
|
|
6857
|
+
{
|
|
6858
|
+
type: "button",
|
|
6859
|
+
size: "icon",
|
|
6860
|
+
variant: "ghost",
|
|
6861
|
+
className: "h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive",
|
|
6862
|
+
"aria-label": `Delete conversation: ${entry.title}`,
|
|
6863
|
+
onClick: () => {
|
|
6864
|
+
deleteConversation(entry.token);
|
|
6865
|
+
},
|
|
6866
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
|
|
6867
|
+
}
|
|
6868
|
+
)
|
|
6869
|
+
] }, entry.token)) })
|
|
6870
|
+
] })
|
|
6871
|
+
}
|
|
6872
|
+
) : null,
|
|
6420
6873
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6421
6874
|
"div",
|
|
6422
6875
|
{
|
|
@@ -6445,7 +6898,8 @@ function ExplorePage({
|
|
|
6445
6898
|
{
|
|
6446
6899
|
message,
|
|
6447
6900
|
activity,
|
|
6448
|
-
onNavigate
|
|
6901
|
+
onNavigate,
|
|
6902
|
+
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
|
|
6449
6903
|
},
|
|
6450
6904
|
message.id
|
|
6451
6905
|
)
|
|
@@ -6491,6 +6945,23 @@ function ExplorePage({
|
|
|
6491
6945
|
className: "max-h-[200px] min-h-[44px] flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
|
|
6492
6946
|
}
|
|
6493
6947
|
),
|
|
6948
|
+
speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6949
|
+
Button,
|
|
6950
|
+
{
|
|
6951
|
+
"data-boff-explore": "mic",
|
|
6952
|
+
"data-listening": speech.listening ? "true" : "false",
|
|
6953
|
+
type: "button",
|
|
6954
|
+
size: "icon",
|
|
6955
|
+
variant: speech.listening ? "default" : "ghost",
|
|
6956
|
+
disabled: composerDisabled,
|
|
6957
|
+
"aria-label": speech.listening ? "Stop dictating" : "Dictate your question",
|
|
6958
|
+
"aria-pressed": speech.listening,
|
|
6959
|
+
title: speech.listening ? "Stop dictating" : "Dictate your question",
|
|
6960
|
+
onClick: speech.toggle,
|
|
6961
|
+
className: cn(speech.listening && "animate-pulse"),
|
|
6962
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-4 w-4" })
|
|
6963
|
+
}
|
|
6964
|
+
) : null,
|
|
6494
6965
|
busy ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6495
6966
|
Button,
|
|
6496
6967
|
{
|
|
@@ -6516,6 +6987,29 @@ function ExplorePage({
|
|
|
6516
6987
|
]
|
|
6517
6988
|
}
|
|
6518
6989
|
),
|
|
6990
|
+
speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6991
|
+
"p",
|
|
6992
|
+
{
|
|
6993
|
+
"data-boff-explore": "dictation",
|
|
6994
|
+
className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
|
|
6995
|
+
"aria-live": "polite",
|
|
6996
|
+
children: [
|
|
6997
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
|
|
6998
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
|
|
6999
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
7000
|
+
] }),
|
|
7001
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
|
|
7002
|
+
]
|
|
7003
|
+
}
|
|
7004
|
+
) : null,
|
|
7005
|
+
speech.error !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
7006
|
+
"p",
|
|
7007
|
+
{
|
|
7008
|
+
"data-boff-explore": "dictation-error",
|
|
7009
|
+
className: "mt-2 text-xs text-destructive",
|
|
7010
|
+
children: speech.error
|
|
7011
|
+
}
|
|
7012
|
+
) : null,
|
|
6519
7013
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
6520
7014
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
|
|
6521
7015
|
remainingToday !== null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-boff-explore": "remaining", children: [
|
|
@@ -6539,39 +7033,48 @@ function ExplorePage({
|
|
|
6539
7033
|
}
|
|
6540
7034
|
)
|
|
6541
7035
|
] }),
|
|
6542
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6543
|
-
"
|
|
6544
|
-
|
|
6545
|
-
" ",
|
|
6546
|
-
"
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
"
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
7036
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
7037
|
+
"p",
|
|
7038
|
+
{
|
|
7039
|
+
"data-boff-explore": "disclaimer",
|
|
7040
|
+
className: "mt-2 text-xs leading-relaxed text-muted-foreground",
|
|
7041
|
+
children: [
|
|
7042
|
+
"Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
|
|
7043
|
+
" ",
|
|
7044
|
+
"Conversations are saved in this browser so you can come back to them, and are also stored on our servers to help us improve these answers. Please don't share personal or confidential information.",
|
|
7045
|
+
captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
7046
|
+
" ",
|
|
7047
|
+
"This site is protected by reCAPTCHA; the Google",
|
|
7048
|
+
" ",
|
|
7049
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7050
|
+
"a",
|
|
7051
|
+
{
|
|
7052
|
+
href: "https://policies.google.com/privacy",
|
|
7053
|
+
target: "_blank",
|
|
7054
|
+
rel: "noopener noreferrer",
|
|
7055
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
7056
|
+
children: "Privacy Policy"
|
|
7057
|
+
}
|
|
7058
|
+
),
|
|
7059
|
+
" ",
|
|
7060
|
+
"and",
|
|
7061
|
+
" ",
|
|
7062
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7063
|
+
"a",
|
|
7064
|
+
{
|
|
7065
|
+
href: "https://policies.google.com/terms",
|
|
7066
|
+
target: "_blank",
|
|
7067
|
+
rel: "noopener noreferrer",
|
|
7068
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
7069
|
+
children: "Terms of Service"
|
|
7070
|
+
}
|
|
7071
|
+
),
|
|
7072
|
+
" ",
|
|
7073
|
+
"apply."
|
|
7074
|
+
] }) : null
|
|
7075
|
+
]
|
|
7076
|
+
}
|
|
7077
|
+
)
|
|
6575
7078
|
]
|
|
6576
7079
|
}
|
|
6577
7080
|
) })
|
|
@@ -6621,11 +7124,12 @@ function ExploreCta({
|
|
|
6621
7124
|
onNavigate(href);
|
|
6622
7125
|
};
|
|
6623
7126
|
const classes = {
|
|
6624
|
-
//
|
|
6625
|
-
//
|
|
6626
|
-
//
|
|
6627
|
-
//
|
|
6628
|
-
|
|
7127
|
+
// A quiet icon control, deliberately NOT a second gradient pill: a header should carry one
|
|
7128
|
+
// primary CTA ("Get started"), and a competing coloured button next to it reads as clutter.
|
|
7129
|
+
// This matches the theme toggle's visual weight, so the row scans as [utilities] [CTA].
|
|
7130
|
+
// The prominent, labelled entry point to /explore is the floating bubble, which is visible
|
|
7131
|
+
// on every page without scrolling. `shrink-0` keeps it from squeezing the nav.
|
|
7132
|
+
header: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
6629
7133
|
mobile: "inline-flex w-full items-center gap-2 rounded-lg border border-border bg-card px-3 py-2 text-sm font-medium text-card-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
6630
7134
|
floating: "fixed bottom-5 right-5 z-40 inline-flex items-center gap-2 rounded-full bg-gradient-to-r from-primary to-primary/70 px-4 py-3 text-sm font-medium text-primary-foreground shadow-lg transition-opacity hover:opacity-90"
|
|
6631
7135
|
};
|
|
@@ -6653,7 +7157,7 @@ function ExploreCta({
|
|
|
6653
7157
|
{
|
|
6654
7158
|
className: cn(
|
|
6655
7159
|
"shrink-0",
|
|
6656
|
-
variant === "floating" ? "h-4 w-4" : "h-3.5 w-3.5"
|
|
7160
|
+
variant === "floating" ? "h-4 w-4" : variant === "header" ? "h-5 w-5" : "h-3.5 w-3.5"
|
|
6657
7161
|
),
|
|
6658
7162
|
"aria-hidden": "true"
|
|
6659
7163
|
}
|
|
@@ -6663,7 +7167,8 @@ function ExploreCta({
|
|
|
6663
7167
|
{
|
|
6664
7168
|
className: cn(
|
|
6665
7169
|
variant === "floating" && "hidden sm:inline",
|
|
6666
|
-
|
|
7170
|
+
// Never labelled in the header — see the class comment above.
|
|
7171
|
+
variant === "header" && "hidden"
|
|
6667
7172
|
),
|
|
6668
7173
|
children: label
|
|
6669
7174
|
}
|