@burdenoff/website-sdk 2026.922.4 → 2026.922.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1745,7 +1745,360 @@ function useSpeechInput({
1745
1745
  );
1746
1746
  return { supported, listening, interim, error, start, stop, toggle };
1747
1747
  }
1748
+ var SILENT_FAILURE_MS = 1500;
1749
+ function speechPlatform() {
1750
+ if (typeof window === "undefined") return null;
1751
+ const scope = window;
1752
+ const synth = scope.speechSynthesis;
1753
+ const Utterance = scope.SpeechSynthesisUtterance;
1754
+ if (!synth || typeof synth.speak !== "function") return null;
1755
+ if (typeof Utterance !== "function") return null;
1756
+ return { synth, Utterance };
1757
+ }
1758
+ function normaliseTag(tag) {
1759
+ return tag.trim().toLowerCase().replace(/_/g, "-");
1760
+ }
1761
+ function primaryOf(tag) {
1762
+ return normaliseTag(tag).split("-")[0] ?? "";
1763
+ }
1764
+ function pickVoice(voices, lang) {
1765
+ const wanted = normaliseTag(lang);
1766
+ if (wanted === "") return null;
1767
+ const primary = primaryOf(wanted);
1768
+ const exact = voices.filter((voice) => normaliseTag(voice.lang) === wanted);
1769
+ const related = voices.filter((voice) => primaryOf(voice.lang) === primary);
1770
+ return exact.find((voice) => voice.default) ?? exact[0] ?? related.find((voice) => voice.default) ?? related[0] ?? null;
1771
+ }
1772
+ var activeOwner = null;
1773
+ var activeStop = null;
1774
+ function stopSpeaking() {
1775
+ activeStop?.();
1776
+ }
1777
+ function useSpeechOutput({
1778
+ lang
1779
+ } = {}) {
1780
+ const [available, setAvailable] = useState(() => speechPlatform() !== null);
1781
+ const [speaking, setSpeaking] = useState(false);
1782
+ const utteranceRef = useRef(null);
1783
+ const watchdogRef = useRef(null);
1784
+ const ownerRef = useRef({});
1785
+ const stopRef = useRef(() => void 0);
1786
+ const langRef = useRef(lang);
1787
+ langRef.current = lang;
1788
+ const clearWatchdog = useCallback(() => {
1789
+ if (watchdogRef.current === null) return;
1790
+ clearTimeout(watchdogRef.current);
1791
+ watchdogRef.current = null;
1792
+ }, []);
1793
+ const settle = useCallback(() => {
1794
+ clearWatchdog();
1795
+ utteranceRef.current = null;
1796
+ if (activeOwner === ownerRef.current) {
1797
+ activeOwner = null;
1798
+ activeStop = null;
1799
+ }
1800
+ setSpeaking(false);
1801
+ }, [clearWatchdog]);
1802
+ const stop = useCallback(() => {
1803
+ const owned = utteranceRef.current !== null;
1804
+ settle();
1805
+ if (owned) speechPlatform()?.synth.cancel();
1806
+ }, [settle]);
1807
+ stopRef.current = stop;
1808
+ const speak = useCallback(
1809
+ (text) => {
1810
+ const platform = speechPlatform();
1811
+ if (!platform) {
1812
+ setAvailable(false);
1813
+ return;
1814
+ }
1815
+ const spoken = text.trim();
1816
+ if (spoken === "") return;
1817
+ if (activeStop && activeOwner !== ownerRef.current) activeStop();
1818
+ platform.synth.cancel();
1819
+ const utterance = new platform.Utterance(spoken);
1820
+ const wanted = langRef.current;
1821
+ if (wanted !== void 0 && wanted !== "") {
1822
+ const voice = pickVoice(platform.synth.getVoices(), wanted);
1823
+ utterance.lang = voice?.lang ?? wanted;
1824
+ if (voice) utterance.voice = voice;
1825
+ }
1826
+ const mine = () => utteranceRef.current === utterance;
1827
+ utterance.onstart = () => {
1828
+ if (!mine()) return;
1829
+ clearWatchdog();
1830
+ setSpeaking(true);
1831
+ };
1832
+ utterance.onend = () => {
1833
+ if (!mine()) return;
1834
+ settle();
1835
+ };
1836
+ utterance.onerror = (event) => {
1837
+ if (!mine()) return;
1838
+ const code = typeof event?.error === "string" ? event.error : "";
1839
+ settle();
1840
+ if (code !== "interrupted" && code !== "canceled") setAvailable(false);
1841
+ };
1842
+ utteranceRef.current = utterance;
1843
+ activeOwner = ownerRef.current;
1844
+ activeStop = () => {
1845
+ stopRef.current();
1846
+ };
1847
+ setSpeaking(true);
1848
+ platform.synth.speak(utterance);
1849
+ clearWatchdog();
1850
+ watchdogRef.current = setTimeout(() => {
1851
+ watchdogRef.current = null;
1852
+ if (!mine()) return;
1853
+ if (platform.synth.speaking) return;
1854
+ settle();
1855
+ setAvailable(false);
1856
+ }, SILENT_FAILURE_MS);
1857
+ },
1858
+ [clearWatchdog, settle]
1859
+ );
1860
+ const toggle = useCallback(
1861
+ (text) => {
1862
+ if (speaking) stop();
1863
+ else speak(text);
1864
+ },
1865
+ [speak, speaking, stop]
1866
+ );
1867
+ useEffect(() => {
1868
+ if (!speaking || typeof window === "undefined") return;
1869
+ const onHide = () => {
1870
+ stopRef.current();
1871
+ };
1872
+ window.addEventListener("pagehide", onHide);
1873
+ return () => window.removeEventListener("pagehide", onHide);
1874
+ }, [speaking]);
1875
+ useEffect(
1876
+ () => () => {
1877
+ stopRef.current();
1878
+ },
1879
+ []
1880
+ );
1881
+ return { supported: available, speaking, speak, stop, toggle };
1882
+ }
1883
+ var DRAIN_TARGET_MS = 1200;
1884
+ var MIN_CHARS_PER_SECOND = 90;
1885
+ var MAX_CHARS_PER_SECOND = 2200;
1886
+ var MAX_STEP_MS = 250;
1887
+ var COMMIT_INTERVAL_MS = 45;
1888
+ var FALLBACK_STEP_MS = 50;
1889
+ var FENCE = /^ {0,3}(?:```|~~~)/;
1890
+ var TABLE_ROW = /^ {0,3}\|/;
1891
+ var EMPTY_BLOCK_MARKER = /^ {0,3}(?:[-+>]|#{1,6}|\d{1,9}[.)])\s*$/;
1892
+ function isWordCharacter(char) {
1893
+ return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
1894
+ }
1895
+ function clamp(value, min, max) {
1896
+ return Math.min(Math.max(value, min), max);
1897
+ }
1898
+ function now() {
1899
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
1900
+ }
1901
+ function isHighSurrogate(code) {
1902
+ return code >= 55296 && code <= 56319;
1903
+ }
1904
+ function safeInlineEnd(line) {
1905
+ let codeSpan = null;
1906
+ let link = null;
1907
+ const emphasis = [];
1908
+ let at = 0;
1909
+ while (at < line.length) {
1910
+ const char = line[at] ?? "";
1911
+ if (char === "\\") {
1912
+ at += 2;
1913
+ continue;
1914
+ }
1915
+ if (char === "`") {
1916
+ let run = 1;
1917
+ while (line[at + run] === "`") run += 1;
1918
+ codeSpan = codeSpan === null ? at : null;
1919
+ at += run;
1920
+ continue;
1921
+ }
1922
+ if (codeSpan !== null) {
1923
+ at += 1;
1924
+ continue;
1925
+ }
1926
+ if (char === "[") {
1927
+ if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
1928
+ at += 1;
1929
+ continue;
1930
+ }
1931
+ if (char === "]") {
1932
+ if (link !== null && line[at + 1] !== "(") link = null;
1933
+ at += 1;
1934
+ continue;
1935
+ }
1936
+ if (char === ")") {
1937
+ link = null;
1938
+ at += 1;
1939
+ continue;
1940
+ }
1941
+ if (char === "*" || char === "_" || char === "~") {
1942
+ let run = 1;
1943
+ while (line[at + run] === char) run += 1;
1944
+ const start = at;
1945
+ at += run;
1946
+ const before = line[start - 1];
1947
+ const after = line[start + run];
1948
+ if (run < 2) continue;
1949
+ if (char === "_" && isWordCharacter(before) && isWordCharacter(after)) {
1950
+ continue;
1951
+ }
1952
+ const marker = char.repeat(run);
1953
+ const open2 = emphasis.findIndex((entry) => entry.marker === marker);
1954
+ if (open2 !== -1) {
1955
+ emphasis.splice(open2, 1);
1956
+ continue;
1957
+ }
1958
+ if (after !== void 0 && /\s/.test(after)) continue;
1959
+ emphasis.push({ marker, start });
1960
+ continue;
1961
+ }
1962
+ at += 1;
1963
+ }
1964
+ const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
1965
+ (value) => value !== null
1966
+ );
1967
+ return open.length === 0 ? line.length : Math.min(...open);
1968
+ }
1969
+ function revealBoundary(text, limit) {
1970
+ if (limit <= 0) return 0;
1971
+ const caughtUp = limit >= text.length;
1972
+ const head = caughtUp ? text : text.slice(0, limit);
1973
+ const lineStart = head.lastIndexOf("\n") + 1;
1974
+ const line = head.slice(lineStart);
1975
+ let fenceStart = null;
1976
+ let tableStart = null;
1977
+ let tableRows = 0;
1978
+ let at = 0;
1979
+ while (at < lineStart) {
1980
+ const breakAt = head.indexOf("\n", at);
1981
+ const end = breakAt === -1 ? lineStart : breakAt;
1982
+ const row = head.slice(at, end);
1983
+ if (fenceStart !== null) {
1984
+ if (FENCE.test(row)) fenceStart = null;
1985
+ } else if (FENCE.test(row)) {
1986
+ fenceStart = at;
1987
+ } else if (TABLE_ROW.test(row)) {
1988
+ if (tableStart === null) {
1989
+ tableStart = at;
1990
+ tableRows = 0;
1991
+ }
1992
+ tableRows += 1;
1993
+ } else if (tableStart !== null) {
1994
+ tableStart = null;
1995
+ tableRows = 0;
1996
+ }
1997
+ at = end + 1;
1998
+ }
1999
+ if (fenceStart === null) {
2000
+ const lastIsRow = TABLE_ROW.test(line);
2001
+ if (lastIsRow && tableStart === null) {
2002
+ tableStart = lineStart;
2003
+ tableRows = 0;
2004
+ } else if (!lastIsRow && line !== "") {
2005
+ tableStart = null;
2006
+ tableRows = 0;
2007
+ }
2008
+ if (tableStart !== null && tableRows < 2) return tableStart;
2009
+ }
2010
+ if (caughtUp) return text.length;
2011
+ if (fenceStart !== null) return fenceStart;
2012
+ if (TABLE_ROW.test(line)) return lineStart;
2013
+ if (EMPTY_BLOCK_MARKER.test(line)) return lineStart;
2014
+ const cut = lineStart + safeInlineEnd(line);
2015
+ return isHighSurrogate(text.charCodeAt(cut - 1)) ? cut - 1 : cut;
2016
+ }
2017
+ function prefersReducedMotion() {
2018
+ if (typeof window === "undefined") return true;
2019
+ try {
2020
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
2021
+ } catch {
2022
+ return false;
2023
+ }
2024
+ }
2025
+ function useProgressiveReveal(text, settled) {
2026
+ const [, setFrame] = useState(0);
2027
+ const [reduced] = useState(prefersReducedMotion);
2028
+ const immediate = settled || reduced;
2029
+ const textRef = useRef(text);
2030
+ const cursorRef = useRef(immediate ? text.length : 0);
2031
+ const cutRef = useRef(immediate ? text.length : 0);
2032
+ const committedRef = useRef(0);
2033
+ if (text !== textRef.current) {
2034
+ const revealed = textRef.current.slice(0, Math.floor(cursorRef.current));
2035
+ if (revealed !== "" && !text.startsWith(revealed)) {
2036
+ cursorRef.current = text.length;
2037
+ }
2038
+ cursorRef.current = Math.min(cursorRef.current, text.length);
2039
+ cutRef.current = Math.min(cutRef.current, text.length);
2040
+ textRef.current = text;
2041
+ }
2042
+ if (immediate) cursorRef.current = text.length;
2043
+ const cut = immediate ? text.length : Math.max(
2044
+ cutRef.current,
2045
+ revealBoundary(text, Math.floor(cursorRef.current))
2046
+ );
2047
+ cutRef.current = cut;
2048
+ useEffect(() => {
2049
+ if (immediate) return;
2050
+ if (cursorRef.current >= text.length) return;
2051
+ let cancelled = false;
2052
+ let cancelStep = null;
2053
+ let previous = now();
2054
+ const commit = (at, force) => {
2055
+ if (!force && at - committedRef.current < COMMIT_INTERVAL_MS) return;
2056
+ committedRef.current = at;
2057
+ setFrame((value) => value + 1);
2058
+ };
2059
+ const step = () => {
2060
+ cancelStep = null;
2061
+ if (cancelled) return;
2062
+ const at = now();
2063
+ const elapsed = clamp(at - previous, 0, MAX_STEP_MS);
2064
+ previous = at;
2065
+ const length = textRef.current.length;
2066
+ const pending = length - cursorRef.current;
2067
+ if (pending <= 0) {
2068
+ commit(at, true);
2069
+ return;
2070
+ }
2071
+ const perSecond = clamp(
2072
+ pending / (DRAIN_TARGET_MS / 1e3),
2073
+ MIN_CHARS_PER_SECOND,
2074
+ MAX_CHARS_PER_SECOND
2075
+ );
2076
+ cursorRef.current = Math.min(
2077
+ length,
2078
+ cursorRef.current + perSecond * elapsed / 1e3
2079
+ );
2080
+ const done = cursorRef.current >= length;
2081
+ commit(at, done);
2082
+ if (!done) schedule();
2083
+ };
2084
+ const schedule = () => {
2085
+ if (typeof window.requestAnimationFrame === "function") {
2086
+ const id2 = window.requestAnimationFrame(step);
2087
+ cancelStep = () => window.cancelAnimationFrame(id2);
2088
+ return;
2089
+ }
2090
+ const id = setTimeout(step, FALLBACK_STEP_MS);
2091
+ cancelStep = () => clearTimeout(id);
2092
+ };
2093
+ schedule();
2094
+ return () => {
2095
+ cancelled = true;
2096
+ cancelStep?.();
2097
+ };
2098
+ }, [text, immediate]);
2099
+ return text.slice(0, cut);
2100
+ }
1748
2101
 
1749
- export { DEFAULT_EXPLORE_EXAMPLE_PROMPTS, FALLBACK_EXPLORE_LIMITS, classifyExploreError, useExploreChat, useSpeechInput };
2102
+ export { DEFAULT_EXPLORE_EXAMPLE_PROMPTS, FALLBACK_EXPLORE_LIMITS, classifyExploreError, stopSpeaking, useExploreChat, useProgressiveReveal, useSpeechInput, useSpeechOutput };
1750
2103
  //# sourceMappingURL=index.mjs.map
1751
2104
  //# sourceMappingURL=index.mjs.map