ttp-agent-sdk 2.44.0 → 2.45.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.
Files changed (34) hide show
  1. package/dist/agent-widget.dev.js +2131 -646
  2. package/dist/agent-widget.dev.js.map +1 -1
  3. package/dist/agent-widget.esm.js +1 -1
  4. package/dist/agent-widget.esm.js.map +1 -1
  5. package/dist/agent-widget.js +1 -1
  6. package/dist/agent-widget.js.map +1 -1
  7. package/dist/demos/demo-sdk-loader.js +1 -0
  8. package/dist/demos/test-ecommerce.html +23 -42
  9. package/dist/demos/test-tour.html +15 -33
  10. package/dist/demos/test-widget.html +38 -18
  11. package/dist/demos/widget-customization-dev.html +15 -6
  12. package/dist/demos/widget-customization.html +14 -6
  13. package/dist/examples/demo-sdk-loader.js +1 -0
  14. package/dist/examples/ocado-cart-network-probe.js +1 -0
  15. package/dist/examples/sobeys-cart-dispatch-probe.js +1 -0
  16. package/dist/examples/sobeys-cart-read-test.html +112 -0
  17. package/dist/examples/sobeys-cart-read-test.js +1 -0
  18. package/dist/examples/sobeys-cart-subscribe-probe.js +1 -0
  19. package/dist/examples/sobeys-cart-subscribe-test.html +168 -0
  20. package/dist/examples/sobeys-cart-subscribe-test.js +1 -0
  21. package/dist/examples/sobeys-inject-probe-min.js +1 -0
  22. package/dist/examples/sobeys-inject-probe.js +1 -0
  23. package/dist/examples/test-ecommerce.html +23 -42
  24. package/dist/examples/test-tour.html +15 -33
  25. package/dist/examples/test-widget.html +38 -18
  26. package/dist/examples/widget-customization-dev.html +15 -6
  27. package/dist/examples/widget-customization.html +14 -6
  28. package/examples/demo-sdk-loader.js +249 -0
  29. package/examples/test-ecommerce.html +23 -42
  30. package/examples/test-tour.html +15 -33
  31. package/examples/test-widget.html +38 -18
  32. package/examples/widget-customization-dev.html +15 -6
  33. package/examples/widget-customization.html +14 -6
  34. package/package.json +2 -1
@@ -10627,6 +10627,264 @@ var AudioRecorder = /*#__PURE__*/function (_EventEmitter) {
10627
10627
  }(_EventEmitter_js__WEBPACK_IMPORTED_MODULE_0__["default"]);
10628
10628
 
10629
10629
 
10630
+ /***/ }),
10631
+
10632
+ /***/ "./src/core/ClientScriptManager.js":
10633
+ /*!*****************************************!*\
10634
+ !*** ./src/core/ClientScriptManager.js ***!
10635
+ \*****************************************/
10636
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
10637
+
10638
+ "use strict";
10639
+ __webpack_require__.r(__webpack_exports__);
10640
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
10641
+ /* harmony export */ CLIENT_TOOLS_PARTNER_ID: () => (/* binding */ CLIENT_TOOLS_PARTNER_ID),
10642
+ /* harmony export */ ClientScriptManager: () => (/* binding */ ClientScriptManager)
10643
+ /* harmony export */ });
10644
+ /* harmony import */ var _ecommerce_PartnerScriptRunner_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../ecommerce/PartnerScriptRunner.js */ "./src/ecommerce/PartnerScriptRunner.js");
10645
+ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
10646
+ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
10647
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
10648
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
10649
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
10650
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
10651
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
10652
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
10653
+ function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
10654
+ function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
10655
+ function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
10656
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
10657
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
10658
+ /**
10659
+ * ClientScriptManager
10660
+ *
10661
+ * Flavor-independent owner of the reserved `__client_tools__` partner bundle.
10662
+ *
10663
+ * The backend pushes a second, flavor-independent `partner_bundle` whose
10664
+ * `partner_id` is the sentinel `__client_tools__`. It carries per-tool client
10665
+ * scripts (each an optional `pre_code_js`, a required `code_js` main step, and
10666
+ * an optional `post_code_js`) plus auto-run library scripts. Unlike the
10667
+ * ecommerce flavor's partner bundle, this one must run regardless of which
10668
+ * flavor (if any) is active, on BOTH the voice (`VoiceSDK_v2`) and text-chat
10669
+ * (`TextChatSDK`) channels.
10670
+ *
10671
+ * This manager owns a single dedicated {@link PartnerScriptRunner} instance —
10672
+ * constructed with `{ genericCtx: true }` so client-tool scripts get a
10673
+ * platform-agnostic ctx (args / host / fetch / log / emit / runAdapter, no
10674
+ * ecommerce-only platform / normalizeCart / refreshUI). It reuses the runner
10675
+ * class as-is; it does not fork it.
10676
+ *
10677
+ * Coexistence: the ecommerce flavor keeps its own runner and its own
10678
+ * `partner_bundle` handling for every other partner id. Each SDK routes by
10679
+ * `partnerId` — `__client_tools__` → this manager, anything else → the existing
10680
+ * flavor path. Bundles are therefore kept separate per partner id.
10681
+ *
10682
+ * Wire contract (unchanged):
10683
+ * bundle : { t:'partner_bundle', partner_id:'__client_tools__',
10684
+ * adapters:{ tool:{ code_js, pre_code_js?, post_code_js?, ... } },
10685
+ * autoRun:{ name:{ code_js } } }
10686
+ * invoke : { t:'run_partner_script', requestId, partnerId:'__client_tools__', action, args }
10687
+ * result : { t:'run_partner_script_result', requestId, partnerId, action, ok, result|error }
10688
+ */
10689
+
10690
+
10691
+ /** Reserved partner id for the flavor-independent client-tool bundle. */
10692
+ var CLIENT_TOOLS_PARTNER_ID = '__client_tools__';
10693
+
10694
+ /** Mirror of ClientToolsRegistry.MAX_RESULT_SIZE — 50KB cap on result payloads. */
10695
+ var MAX_RESULT_SIZE = 50000;
10696
+ var LOG = '[ClientScriptManager]';
10697
+ var ClientScriptManager = /*#__PURE__*/function () {
10698
+ /**
10699
+ * @param {object} opts
10700
+ * @param {(msg: object) => void} opts.send Send a JSON frame back to the backend
10701
+ * (VoiceSDK.sendMessage / TextChatSDK.sendRaw). Used for both run_partner_script_result
10702
+ * envelopes and adapter ctx.emit() pushes.
10703
+ * @param {string} [opts.label] Short tag for logs (e.g. 'voice' / 'text').
10704
+ */
10705
+ function ClientScriptManager() {
10706
+ var _this = this;
10707
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
10708
+ send = _ref.send,
10709
+ _ref$label = _ref.label,
10710
+ label = _ref$label === void 0 ? '' : _ref$label;
10711
+ _classCallCheck(this, ClientScriptManager);
10712
+ this._send = typeof send === 'function' ? send : function () {};
10713
+ this._label = label;
10714
+ this.runner = new _ecommerce_PartnerScriptRunner_js__WEBPACK_IMPORTED_MODULE_0__.PartnerScriptRunner({
10715
+ genericCtx: true
10716
+ });
10717
+ // Unsolicited adapter pushes (ctx.emit) go back over the same channel.
10718
+ this.runner.setEmitCallback(function (msg) {
10719
+ return _this._send(msg);
10720
+ });
10721
+ }
10722
+
10723
+ /**
10724
+ * True when this message is a `partner_bundle` addressed to the sentinel
10725
+ * `__client_tools__` partner id. Both SDKs use this to route the bundle here
10726
+ * instead of (or in addition to) the flavor path.
10727
+ * @param {object} message
10728
+ * @returns {boolean}
10729
+ */
10730
+ return _createClass(ClientScriptManager, [{
10731
+ key: "setBundle",
10732
+ value:
10733
+ /**
10734
+ * Handle a `partner_bundle` for `__client_tools__`. Mirrors
10735
+ * EcommerceFlavor's bundle handling: merge autoRun entries into the adapters
10736
+ * map (so ctx.runAdapter can reach them), load the bundle (replacing any
10737
+ * previous one for this partner — reconnect case), then execute each autoRun
10738
+ * entry exactly once.
10739
+ * @param {object} data the partner_bundle message
10740
+ */
10741
+ function setBundle(data) {
10742
+ var _this2 = this;
10743
+ // emit callback is wired in the constructor and survives bundle reloads;
10744
+ // re-assert it here so it is always fresh for the active SDK instance.
10745
+ this.runner.setEmitCallback(function (msg) {
10746
+ return _this2._send(msg);
10747
+ });
10748
+ var merged = _objectSpread(_objectSpread({}, data), {}, {
10749
+ adapters: _objectSpread(_objectSpread({}, (data === null || data === void 0 ? void 0 : data.adapters) || {}), (data === null || data === void 0 ? void 0 : data.autoRun) || {})
10750
+ });
10751
+ this.runner.setBundle(merged);
10752
+ var _loop = function _loop() {
10753
+ var action = _Object$keys[_i];
10754
+ _this2.runner.runAdapter(action, {}).then(function (r) {
10755
+ return console.log("".concat(LOG).concat(_this2._tag(), " auto-run '").concat(action, "':"), r);
10756
+ }).catch(function (e) {
10757
+ return console.warn("".concat(LOG).concat(_this2._tag(), " auto-run '").concat(action, "' failed:"), e);
10758
+ });
10759
+ };
10760
+ for (var _i = 0, _Object$keys = Object.keys((data === null || data === void 0 ? void 0 : data.autoRun) || {}); _i < _Object$keys.length; _i++) {
10761
+ _loop();
10762
+ }
10763
+ }
10764
+
10765
+ /**
10766
+ * Execute a `run_partner_script` envelope against the client-tool runner and
10767
+ * post the `run_partner_script_result` back over the channel. Enforces the
10768
+ * 50KB result-size cap (oversize → ok:false, error:'RESULT_TOO_LARGE').
10769
+ *
10770
+ * An unknown action (e.g. bundle not yet received) resolves to ok:false with
10771
+ * the runner's `adapter_not_in_bundle` reason — same behavior as the existing
10772
+ * path.
10773
+ *
10774
+ * @param {object} message { requestId, partnerId, action, args }
10775
+ */
10776
+ }, {
10777
+ key: "handleRunPartnerScript",
10778
+ value: (function () {
10779
+ var _handleRunPartnerScript = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(message) {
10780
+ var _ref2, requestId, partnerId, action, args, startedAt, result, elapsedMs, ok, serializedSize, sizeKB, limitKB, reason, _elapsedMs, _t;
10781
+ return _regenerator().w(function (_context) {
10782
+ while (1) switch (_context.p = _context.n) {
10783
+ case 0:
10784
+ _ref2 = message || {}, requestId = _ref2.requestId, partnerId = _ref2.partnerId, action = _ref2.action;
10785
+ args = message && message.args || {};
10786
+ if (requestId) {
10787
+ _context.n = 1;
10788
+ break;
10789
+ }
10790
+ console.warn("".concat(LOG).concat(this._tag(), " run_partner_script missing requestId; dropping"), message);
10791
+ return _context.a(2);
10792
+ case 1:
10793
+ startedAt = Date.now();
10794
+ console.log("".concat(LOG).concat(this._tag(), " run_partner_script dispatch partner=").concat(partnerId, " action=").concat(action, " requestId=").concat(requestId));
10795
+ _context.p = 2;
10796
+ _context.n = 3;
10797
+ return this.runner.runAdapter(action, args);
10798
+ case 3:
10799
+ result = _context.v;
10800
+ elapsedMs = Date.now() - startedAt;
10801
+ ok = result && _typeof(result) === 'object' && 'ok' in result ? !!result.ok : true; // 50KB result-size cap (mirror ClientToolsRegistry.MAX_RESULT_SIZE).
10802
+ serializedSize = 0;
10803
+ try {
10804
+ serializedSize = JSON.stringify(result).length;
10805
+ } catch (_unused) {
10806
+ serializedSize = 0;
10807
+ }
10808
+ if (!(serializedSize > MAX_RESULT_SIZE)) {
10809
+ _context.n = 4;
10810
+ break;
10811
+ }
10812
+ sizeKB = Math.round(serializedSize / 1024);
10813
+ limitKB = Math.round(MAX_RESULT_SIZE / 1024);
10814
+ console.error("".concat(LOG).concat(this._tag(), " result for '").concat(action, "' exceeds size limit (").concat(sizeKB, "KB > ").concat(limitKB, "KB)"));
10815
+ this._send({
10816
+ t: 'run_partner_script_result',
10817
+ requestId: requestId,
10818
+ partnerId: partnerId,
10819
+ action: action,
10820
+ ok: false,
10821
+ error: 'RESULT_TOO_LARGE'
10822
+ });
10823
+ return _context.a(2);
10824
+ case 4:
10825
+ if (ok) {
10826
+ console.log("".concat(LOG).concat(this._tag(), " run_partner_script ok partner=").concat(partnerId, " action=").concat(action, " requestId=").concat(requestId, " (").concat(elapsedMs, "ms)"));
10827
+ } else {
10828
+ reason = result && (result.reason || result.error) || 'unknown';
10829
+ console.warn("".concat(LOG).concat(this._tag(), " run_partner_script failed partner=").concat(partnerId, " action=").concat(action, " requestId=").concat(requestId, " reason=").concat(reason, " (").concat(elapsedMs, "ms)"), result);
10830
+ }
10831
+ this._send({
10832
+ t: 'run_partner_script_result',
10833
+ requestId: requestId,
10834
+ partnerId: partnerId,
10835
+ action: action,
10836
+ ok: ok,
10837
+ result: result
10838
+ });
10839
+ _context.n = 6;
10840
+ break;
10841
+ case 5:
10842
+ _context.p = 5;
10843
+ _t = _context.v;
10844
+ _elapsedMs = Date.now() - startedAt;
10845
+ console.error("".concat(LOG).concat(this._tag(), " run_partner_script threw partner=").concat(partnerId, " action=").concat(action, " requestId=").concat(requestId, " (").concat(_elapsedMs, "ms):"), _t);
10846
+ this._send({
10847
+ t: 'run_partner_script_result',
10848
+ requestId: requestId,
10849
+ partnerId: partnerId,
10850
+ action: action,
10851
+ ok: false,
10852
+ error: _t && _t.message || String(_t)
10853
+ });
10854
+ case 6:
10855
+ return _context.a(2);
10856
+ }
10857
+ }, _callee, this, [[2, 5]]);
10858
+ }));
10859
+ function handleRunPartnerScript(_x) {
10860
+ return _handleRunPartnerScript.apply(this, arguments);
10861
+ }
10862
+ return handleRunPartnerScript;
10863
+ }())
10864
+ }, {
10865
+ key: "_tag",
10866
+ value: function _tag() {
10867
+ return this._label ? " [".concat(this._label, "]") : '';
10868
+ }
10869
+ }], [{
10870
+ key: "isClientToolsBundle",
10871
+ value: function isClientToolsBundle(message) {
10872
+ return !!message && (message.t === 'partner_bundle' || message.type === 'partner_bundle') && message.partner_id === CLIENT_TOOLS_PARTNER_ID;
10873
+ }
10874
+
10875
+ /**
10876
+ * True when this run_partner_script envelope targets the sentinel partner id.
10877
+ * @param {object} message
10878
+ * @returns {boolean}
10879
+ */
10880
+ }, {
10881
+ key: "isClientToolsScript",
10882
+ value: function isClientToolsScript(message) {
10883
+ return !!message && message.partnerId === CLIENT_TOOLS_PARTNER_ID;
10884
+ }
10885
+ }]);
10886
+ }();
10887
+
10630
10888
  /***/ }),
10631
10889
 
10632
10890
  /***/ "./src/core/ClientToolsRegistry.js":
@@ -10999,6 +11257,7 @@ __webpack_require__.r(__webpack_exports__);
10999
11257
  /* harmony export */ });
11000
11258
  /* harmony import */ var _EventEmitter_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./EventEmitter.js */ "./src/core/EventEmitter.js");
11001
11259
  /* harmony import */ var _ClientToolsRegistry_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ClientToolsRegistry.js */ "./src/core/ClientToolsRegistry.js");
11260
+ /* harmony import */ var _ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ClientScriptManager.js */ "./src/core/ClientScriptManager.js");
11002
11261
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
11003
11262
  function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
11004
11263
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
@@ -11030,6 +11289,7 @@ function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf
11030
11289
  */
11031
11290
 
11032
11291
 
11292
+
11033
11293
  var RECONNECT_DELAY_MS = 1000;
11034
11294
  var MAX_RECONNECT_ATTEMPTS = 5;
11035
11295
  var TEXT_CHAT_PATH = '/chat/text';
@@ -11067,6 +11327,18 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
11067
11327
  }, function () {
11068
11328
  return _this.config.conversationId;
11069
11329
  }, config.sharedToolHandlers || null);
11330
+
11331
+ // Flavor-independent client-tool script manager (reserved partner id
11332
+ // `__client_tools__`). Owns its own PartnerScriptRunner so client-tool
11333
+ // scripts run regardless of flavor, alongside (and separate from) the
11334
+ // ecommerce flavor's partner runner. Sends result/emit frames over this
11335
+ // text-chat WebSocket so the response returns on the channel it arrived on.
11336
+ _this.clientScriptManager = new _ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_2__.ClientScriptManager({
11337
+ send: function send(message) {
11338
+ return _this.sendRaw(message);
11339
+ },
11340
+ label: 'text'
11341
+ });
11070
11342
  return _this;
11071
11343
  }
11072
11344
 
@@ -11190,6 +11462,16 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
11190
11462
  return;
11191
11463
  }
11192
11464
 
11465
+ // Flavor-independent client-tool bundle (partner_id `__client_tools__`)
11466
+ // is loaded by the dedicated ClientScriptManager. Every other partner id
11467
+ // falls through to the flavor (via the emitted 'message' event), so the
11468
+ // ecommerce flavor's own bundle handling keeps working untouched.
11469
+ if (data.t === 'partner_bundle' && _ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_2__.ClientScriptManager.isClientToolsBundle(data)) {
11470
+ _this2.clientScriptManager.setBundle(data);
11471
+ _this2.emit('message', data);
11472
+ return;
11473
+ }
11474
+
11193
11475
  // Server responds with hello_ack (same format as voice SDK)
11194
11476
  if (data.t === 'hello_ack') {
11195
11477
  if (!data.ok) {
@@ -11412,9 +11694,15 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
11412
11694
  console.warn('[TextChatSDK] run_partner_script missing requestId; dropping', message);
11413
11695
  return _context2.a(2);
11414
11696
  case 1:
11697
+ if (!_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_2__.ClientScriptManager.isClientToolsScript(message)) {
11698
+ _context2.n = 2;
11699
+ break;
11700
+ }
11701
+ return _context2.a(2, this.clientScriptManager.handleRunPartnerScript(message));
11702
+ case 2:
11415
11703
  handler = (_this$clientToolsRegi = this.clientToolsRegistry) === null || _this$clientToolsRegi === void 0 || (_this$clientToolsRegi = _this$clientToolsRegi.handlers) === null || _this$clientToolsRegi === void 0 ? void 0 : _this$clientToolsRegi.get('run_partner_script');
11416
11704
  if (handler) {
11417
- _context2.n = 2;
11705
+ _context2.n = 3;
11418
11706
  break;
11419
11707
  }
11420
11708
  console.error("[TextChatSDK] no run_partner_script handler registered (ecommerce flavor not loaded?); requestId=".concat(requestId, " partner=").concat(partnerId, " action=").concat(action));
@@ -11427,15 +11715,15 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
11427
11715
  error: 'no_handler_registered'
11428
11716
  });
11429
11717
  return _context2.a(2);
11430
- case 2:
11718
+ case 3:
11431
11719
  startedAt = Date.now();
11432
- _context2.p = 3;
11433
- _context2.n = 4;
11720
+ _context2.p = 4;
11721
+ _context2.n = 5;
11434
11722
  return handler({
11435
11723
  action: action,
11436
11724
  args: args
11437
11725
  });
11438
- case 4:
11726
+ case 5:
11439
11727
  result = _context2.v;
11440
11728
  ok = result && _typeof(result) === 'object' && 'ok' in result ? !!result.ok : true;
11441
11729
  elapsedMs = Date.now() - startedAt;
@@ -11453,10 +11741,10 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
11453
11741
  ok: ok,
11454
11742
  result: result
11455
11743
  });
11456
- _context2.n = 6;
11744
+ _context2.n = 7;
11457
11745
  break;
11458
- case 5:
11459
- _context2.p = 5;
11746
+ case 6:
11747
+ _context2.p = 6;
11460
11748
  _t = _context2.v;
11461
11749
  _elapsedMs = Date.now() - startedAt;
11462
11750
  console.error("[TextChatSDK] run_partner_script threw partner=".concat(partnerId, " action=").concat(action, " requestId=").concat(requestId, " (").concat(_elapsedMs, "ms):"), _t);
@@ -11468,10 +11756,10 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
11468
11756
  ok: false,
11469
11757
  error: _t && _t.message || String(_t)
11470
11758
  });
11471
- case 6:
11759
+ case 7:
11472
11760
  return _context2.a(2);
11473
11761
  }
11474
- }, _callee2, this, [[3, 5]]);
11762
+ }, _callee2, this, [[4, 6]]);
11475
11763
  }));
11476
11764
  function _handleRunPartnerScript(_x2) {
11477
11765
  return _handleRunPartnerScript2.apply(this, arguments);
@@ -11522,7 +11810,7 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
11522
11810
 
11523
11811
  // SDK build time for debugging
11524
11812
  if (true) {
11525
- helloMessage.lastBuildTime = "2026-05-07T11:24:41.900Z";
11813
+ helloMessage.lastBuildTime = "2026-06-12T15:13:11.602Z";
11526
11814
  }
11527
11815
  try {
11528
11816
  this.ws.send(JSON.stringify(helloMessage));
@@ -12304,9 +12592,8 @@ __webpack_require__.r(__webpack_exports__);
12304
12592
  /* harmony import */ var _EcommercePresenter_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./EcommercePresenter.js */ "./src/ecommerce/EcommercePresenter.js");
12305
12593
  /* harmony import */ var _ProductPicker_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ProductPicker.js */ "./src/ecommerce/ProductPicker.js");
12306
12594
  /* harmony import */ var _CartToast_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./CartToast.js */ "./src/ecommerce/CartToast.js");
12307
- /* harmony import */ var _CartSummary_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./CartSummary.js */ "./src/ecommerce/CartSummary.js");
12308
- /* harmony import */ var _FlyToCart_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./FlyToCart.js */ "./src/ecommerce/FlyToCart.js");
12309
- /* harmony import */ var _ecommerceStyles_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./ecommerceStyles.js */ "./src/ecommerce/ecommerceStyles.js");
12595
+ /* harmony import */ var _FlyToCart_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./FlyToCart.js */ "./src/ecommerce/FlyToCart.js");
12596
+ /* harmony import */ var _ecommerceStyles_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ecommerceStyles.js */ "./src/ecommerce/ecommerceStyles.js");
12310
12597
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
12311
12598
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
12312
12599
  function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
@@ -12325,8 +12612,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
12325
12612
  * DesktopPresenter — Desktop UI strategy for e-commerce.
12326
12613
  *
12327
12614
  * Renders products via ProductPicker inside the shadow-root #ttpEcommerceArea,
12328
- * manages panel expansion/shrinking, compact voice mode switching, CartToast,
12329
- * and CartSummary.
12615
+ * manages panel expansion/shrinking, compact voice mode switching, and CartToast.
12330
12616
  *
12331
12617
  * Implements the EcommercePresenter interface.
12332
12618
  */
@@ -12335,7 +12621,6 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
12335
12621
 
12336
12622
 
12337
12623
 
12338
-
12339
12624
  var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12340
12625
  /**
12341
12626
  * @param {Object} config
@@ -12351,7 +12636,7 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12351
12636
  _this.shadowRoot = config.shadowRoot;
12352
12637
  _this.onProductAdd = config.onProductAdd || function () {};
12353
12638
  _this.onProductUndo = config.onProductUndo || function () {};
12354
- _this.flyToCart = new _FlyToCart_js__WEBPACK_IMPORTED_MODULE_4__.FlyToCart({
12639
+ _this.flyToCart = new _FlyToCart_js__WEBPACK_IMPORTED_MODULE_3__.FlyToCart({
12355
12640
  getCartTarget: function getCartTarget() {
12356
12641
  return _this._getCartTarget();
12357
12642
  },
@@ -12382,7 +12667,6 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12382
12667
  return _this._restoreWaveform();
12383
12668
  }
12384
12669
  });
12385
- _this.cartSummary = new _CartSummary_js__WEBPACK_IMPORTED_MODULE_3__.CartSummary();
12386
12670
  _this.container = document.createElement('div');
12387
12671
  _this.container.id = 'ttpEcommerceArea';
12388
12672
  _this.container.className = 'ttp-ecommerce-area';
@@ -12460,50 +12744,10 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12460
12744
  }
12461
12745
  }, {
12462
12746
  key: "_triggerCartBump",
12463
- value: function _triggerCartBump() {
12464
- var _this3 = this;
12465
- this._cartBump = true;
12466
- this._updateCartBumpStyles();
12467
- setTimeout(function () {
12468
- _this3._cartBump = false;
12469
- _this3._updateCartBumpStyles();
12470
- }, 450);
12471
- }
12747
+ value: function _triggerCartBump() {}
12472
12748
  }, {
12473
12749
  key: "_triggerRipple",
12474
- value: function _triggerRipple() {
12475
- var _this4 = this;
12476
- this._ripple = true;
12477
- this._updateRippleStyles();
12478
- setTimeout(function () {
12479
- _this4._ripple = false;
12480
- _this4._updateRippleStyles();
12481
- }, 600);
12482
- }
12483
- }, {
12484
- key: "_updateCartBumpStyles",
12485
- value: function _updateCartBumpStyles() {
12486
- if (!this.cartSummary.element) return;
12487
- var info = this.cartSummary.element.querySelector('.ttp-cart-info');
12488
- if (info) {
12489
- info.style.transform = 'none';
12490
- info.style.transition = '';
12491
- }
12492
- }
12493
- }, {
12494
- key: "_updateRippleStyles",
12495
- value: function _updateRippleStyles() {
12496
- if (!this.cartSummary.element || !this._ripple) return;
12497
- var existing = this.cartSummary.element.querySelector('.ttp-fly-ripple');
12498
- if (existing) existing.remove();
12499
- var ripple = document.createElement('div');
12500
- ripple.className = 'ttp-fly-ripple';
12501
- this.cartSummary.element.style.position = 'relative';
12502
- this.cartSummary.element.appendChild(ripple);
12503
- setTimeout(function () {
12504
- return ripple.remove();
12505
- }, 600);
12506
- }
12750
+ value: function _triggerRipple() {}
12507
12751
 
12508
12752
  // ═══════════════════════════════════════════
12509
12753
  // EcommercePresenter interface
@@ -12548,7 +12792,6 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12548
12792
  key: "onProductAdded",
12549
12793
  value: function onProductAdded(product, cartInfo) {
12550
12794
  this._ensureReady();
12551
- this.cartSummary.update(cartInfo.itemCount, cartInfo.total, cartInfo.currency);
12552
12795
  this._toastActive = true;
12553
12796
  /* Hide products and shrink to toast height (not original) — full shrink happens when toast is gone */
12554
12797
  this.hideProducts({
@@ -12567,9 +12810,9 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12567
12810
  }, {
12568
12811
  key: "_setPanelHeightForToast",
12569
12812
  value: function _setPanelHeightForToast() {
12570
- var _this5 = this;
12813
+ var _this3 = this;
12571
12814
  var apply = function apply() {
12572
- var panel = _this5._queryId('text-chat-panel');
12815
+ var panel = _this3._queryId('text-chat-panel');
12573
12816
  if (!panel) return;
12574
12817
  var height = Math.min(DesktopPresenter.TOAST_HEIGHT_PX, Math.floor(window.innerHeight * 0.92));
12575
12818
  panel.classList.add('ttp-panel-expanded');
@@ -12599,14 +12842,10 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12599
12842
  }
12600
12843
  }, {
12601
12844
  key: "updateCart",
12602
- value: function updateCart(cartInfo) {
12603
- this._ensureReady();
12604
- this.cartSummary.update(cartInfo.itemCount, cartInfo.total, cartInfo.currency);
12605
- }
12845
+ value: function updateCart(_cartInfo) {}
12606
12846
  }, {
12607
12847
  key: "destroy",
12608
12848
  value: function destroy() {
12609
- var _this$cartSummary$ele;
12610
12849
  if (this.flyToCart) this.flyToCart.destroy();
12611
12850
  this.productPicker.hide();
12612
12851
  this.cartToast.hide();
@@ -12615,9 +12854,6 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12615
12854
  if (this.voiceActiveState) {
12616
12855
  this.voiceActiveState.classList.remove('ttp-products-visible');
12617
12856
  }
12618
- if ((_this$cartSummary$ele = this.cartSummary.element) !== null && _this$cartSummary$ele !== void 0 && _this$cartSummary$ele.parentNode) {
12619
- this.cartSummary.element.parentNode.removeChild(this.cartSummary.element);
12620
- }
12621
12857
  if (this.container && this.container.parentNode) {
12622
12858
  this.container.parentNode.removeChild(this.container);
12623
12859
  }
@@ -12638,7 +12874,7 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12638
12874
  }, {
12639
12875
  key: "_injectStyles",
12640
12876
  value: function _injectStyles() {
12641
- var css = (0,_ecommerceStyles_js__WEBPACK_IMPORTED_MODULE_5__.generateEcommerceCSS)();
12877
+ var css = (0,_ecommerceStyles_js__WEBPACK_IMPORTED_MODULE_4__.generateEcommerceCSS)();
12642
12878
  var root = this.shadowRoot;
12643
12879
  if (!root) return;
12644
12880
 
@@ -12683,18 +12919,6 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12683
12919
  } else {
12684
12920
  this.voiceActiveState.appendChild(this.container);
12685
12921
  }
12686
-
12687
- /* Cart inside input area — unified section (like mobile) */
12688
- var inputArea = this.voiceActiveState.querySelector('.voice-text-input-area');
12689
- if (inputArea) {
12690
- var cartEl = this.cartSummary.render();
12691
- cartEl.classList.add('ttp-cart-summary-inline');
12692
- inputArea.appendChild(cartEl);
12693
- } else {
12694
- var _cartEl = this.cartSummary.render();
12695
- _cartEl.classList.add('ttp-cart-summary-bottom');
12696
- this.voiceActiveState.appendChild(_cartEl);
12697
- }
12698
12922
  this.isAttached = true;
12699
12923
  }
12700
12924
 
@@ -12716,26 +12940,20 @@ var DesktopPresenter = /*#__PURE__*/function (_EcommercePresenter) {
12716
12940
  this.container.style.overflowY = '';
12717
12941
  if (this.voiceActiveState) this.voiceActiveState.classList.remove('ttp-products-visible');
12718
12942
  }
12719
-
12720
- /* Cart: visible only in products mode (fly target) or when items > 0 */
12721
- if (this.cartSummary.element) {
12722
- var show = mode === 'products' || mode === 'summary' && this.cartSummary.itemCount > 0;
12723
- this.cartSummary.element.classList.toggle('ttp-cart-empty', !show);
12724
- }
12725
12943
  }
12726
12944
  }, {
12727
12945
  key: "_expandPanelToFit",
12728
12946
  value: function _expandPanelToFit(productCount, layout) {
12729
- var _this6 = this;
12947
+ var _this4 = this;
12730
12948
  if (!this._widgetDomRoot()) return;
12731
12949
  var panel = this._queryId('text-chat-panel');
12732
12950
  if (!panel) return;
12733
12951
  panel.classList.add('ttp-panel-expanded');
12734
12952
  var activeState = this._queryId('voiceActiveState');
12735
12953
  requestAnimationFrame(function () {
12736
- var _this6$container;
12954
+ var _this4$container;
12737
12955
  var compact = activeState === null || activeState === void 0 ? void 0 : activeState.querySelector('.compact-voice-section');
12738
- var picker = (_this6$container = _this6.container) === null || _this6$container === void 0 ? void 0 : _this6$container.querySelector('.ttp-product-picker');
12956
+ var picker = (_this4$container = _this4.container) === null || _this4$container === void 0 ? void 0 : _this4$container.querySelector('.ttp-product-picker');
12739
12957
  var transcript = activeState === null || activeState === void 0 ? void 0 : activeState.querySelector('.desktop-conversation-panel');
12740
12958
  var inputRow = activeState === null || activeState === void 0 ? void 0 : activeState.querySelector('.voice-text-input-area');
12741
12959
  var needed = 0;
@@ -13142,65 +13360,470 @@ var EcommercePresenter = /*#__PURE__*/function () {
13142
13360
 
13143
13361
  /***/ }),
13144
13362
 
13145
- /***/ "./src/ecommerce/FlyToCart.js":
13146
- /*!************************************!*\
13147
- !*** ./src/ecommerce/FlyToCart.js ***!
13148
- \************************************/
13363
+ /***/ "./src/ecommerce/EcommerceVoiceStrip.js":
13364
+ /*!**********************************************!*\
13365
+ !*** ./src/ecommerce/EcommerceVoiceStrip.js ***!
13366
+ \**********************************************/
13149
13367
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
13150
13368
 
13151
13369
  "use strict";
13152
13370
  __webpack_require__.r(__webpack_exports__);
13153
13371
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
13154
- /* harmony export */ FlyToCart: () => (/* binding */ FlyToCart)
13372
+ /* harmony export */ EcommerceVoiceStrip: () => (/* binding */ EcommerceVoiceStrip)
13155
13373
  /* harmony export */ });
13156
- /* harmony import */ var html2canvas__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! html2canvas */ "./node_modules/html2canvas/dist/html2canvas.js");
13157
- /* harmony import */ var html2canvas__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(html2canvas__WEBPACK_IMPORTED_MODULE_0__);
13374
+ /* harmony import */ var _widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../widget/voice/rtlText.js */ "./src/widget/voice/rtlText.js");
13158
13375
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
13159
- function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
13160
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
13161
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
13162
13376
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
13163
13377
  function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
13164
13378
  function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
13165
13379
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
13166
13380
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
13167
13381
  /**
13168
- * FlyToCart — Fly-to-cart animation with tornado funnel effect.
13382
+ * EcommerceVoiceStrip
13169
13383
  *
13170
- * When the user taps "Add to Cart", the product card flies straight down
13171
- * into the cart icon with a 3D tornado distortion, particle trail, and impact effects.
13384
+ * Fixed bottom-strip voice UI for desktop sessions with flavor.callView === 'minimized'.
13385
+ * (Historically ecommerce-only; now any flavored widget may opt in.) Requires
13386
+ * window.innerWidth > 768.
13172
13387
  *
13173
- * Uses html2canvas to capture the card as an image avoids DOM stacking/overflow
13174
- * clipping when the widget is in an iframe or a container with overflow:hidden.
13388
+ * Appended to document.body (outside shadow DOM). Control actions delegate to
13389
+ * VoiceInterface via callbacks; optional getVoiceUiState() keeps icons in sync.
13175
13390
  *
13176
- * Usage:
13177
- * const flyToCart = new FlyToCart({
13178
- * getCartTarget: () => document.querySelector('.ttp-cart-icon'),
13179
- * onCartBump: () => { ... },
13180
- * });
13181
- * flyToCart.triggerFly(cardElement, product, () => { /* add to cart API *\/ });
13391
+ * Public API:
13392
+ * strip.show(opts?) – opts.reset false = reveal without clearing transcript
13393
+ * strip.hide()
13394
+ * strip.syncFromVoice() refresh mute / pause / speaker icons from getVoiceUiState
13395
+ * strip.updateTranscript(text) – assistant line (replaces any user text on the strip)
13396
+ * strip.updateUserTranscript(text, isFinal) user STT on the same line (replaces agent text); no "You:" prefix; warm text color
13397
+ * strip.updateTimer(text)
13398
+ * strip.setMuted(bool) – legacy sync (prefer syncFromVoice)
13399
+ * strip.setHeld(bool) – legacy pause visual (maps to paused)
13400
+ * strip.destroy()
13182
13401
  */
13183
13402
 
13184
-
13185
- var DURATION = 1000;
13186
- var FlyToCart = /*#__PURE__*/function () {
13187
- function FlyToCart() {
13188
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
13189
- _classCallCheck(this, FlyToCart);
13190
- this.getCartTarget = options.getCartTarget || function () {
13191
- return null;
13192
- };
13193
- this.onCartBump = options.onCartBump || function () {};
13194
- this.onRipple = options.onRipple || function () {};
13195
- this.animatingId = null;
13196
- this.trailParticles = [];
13197
- this.canvas = null;
13198
- this.rafId = null;
13199
- this._canvasInitialized = false;
13403
+ var EcommerceVoiceStrip = /*#__PURE__*/function () {
13404
+ /**
13405
+ * @param {Object} opts
13406
+ * @param {Object} [opts.theme] – match floating pill: pillGradient, pillTextColor, pillDotColor, endCallButtonColor
13407
+ * @param {() => { isMicMuted: boolean, isPaused: boolean, isSpeakerMuted: boolean }} [opts.getVoiceUiState]
13408
+ * @param {Function} opts.onHangup
13409
+ * @param {Function} opts.onToggleMute
13410
+ * @param {Function} opts.onTogglePause
13411
+ * @param {Function} opts.onToggleSpeaker
13412
+ * @param {Function} opts.onSendText
13413
+ * @param {string} [opts.language] Widget language (BCP-47); with `opts.direction` drives RTL transcript/input.
13414
+ * @param {string} [opts.direction] Widget `direction` (`ltr` | `rtl`); Hebrew/Arabic `language` also enables RTL text.
13415
+ * @param {{ brandUrl: string, brandName: string } | null} [opts.footer] – "Powered by" row at bottom of strip
13416
+ */
13417
+ function EcommerceVoiceStrip() {
13418
+ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
13419
+ _classCallCheck(this, EcommerceVoiceStrip);
13420
+ this.theme = opts.theme || {};
13421
+ this.footer = opts.footer || null;
13422
+ this.getVoiceUiState = typeof opts.getVoiceUiState === 'function' ? opts.getVoiceUiState : null;
13423
+ this._language = typeof opts.language === 'string' ? opts.language : '';
13424
+ this._direction = opts.direction === 'rtl' || opts.direction === 'ltr' ? opts.direction : '';
13425
+ this.onHangup = opts.onHangup || function () {};
13426
+ this.onToggleMute = opts.onToggleMute || function () {};
13427
+ this.onTogglePause = opts.onTogglePause || function () {};
13428
+ this.onToggleSpeaker = opts.onToggleSpeaker || function () {};
13429
+ this.onSendText = opts.onSendText || function () {};
13430
+ this._kbdOpen = false;
13431
+ this._el = null;
13432
+ this._build();
13200
13433
  }
13201
- return _createClass(FlyToCart, [{
13202
- key: "isAnimating",
13203
- get: function get() {
13434
+ return _createClass(EcommerceVoiceStrip, [{
13435
+ key: "_applyStripTextDirection",
13436
+ value: function _applyStripTextDirection() {
13437
+ if (!this._el) return;
13438
+ var stripCfg = {
13439
+ language: this._language,
13440
+ direction: this._direction
13441
+ };
13442
+ var rtlChrome = (0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.configPrefersRtlText)(stripCfg);
13443
+ var transcript = this._el.querySelector('#ttp-ecom-transcript');
13444
+ var center = this._el.querySelector('#ttp-ecom-center');
13445
+ var inputRow = this._el.querySelector('#ttp-ecom-input-row');
13446
+ var input = this._el.querySelector('#ttp-ecom-input');
13447
+ var preferredLang = this._language && this._language.trim();
13448
+ if (transcript) {
13449
+ transcript.setAttribute('dir', (0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(transcript.textContent || '', stripCfg));
13450
+ if (rtlChrome && preferredLang) transcript.setAttribute('lang', preferredLang);else transcript.removeAttribute('lang');
13451
+ }
13452
+ if (center) {
13453
+ if (rtlChrome) {
13454
+ center.setAttribute('dir', 'rtl');
13455
+ if (preferredLang) center.setAttribute('lang', preferredLang);else center.removeAttribute('lang');
13456
+ } else {
13457
+ center.removeAttribute('dir');
13458
+ center.removeAttribute('lang');
13459
+ }
13460
+ }
13461
+ if (rtlChrome) {
13462
+ if (inputRow) {
13463
+ inputRow.setAttribute('dir', 'rtl');
13464
+ if (preferredLang) inputRow.setAttribute('lang', preferredLang);else inputRow.removeAttribute('lang');
13465
+ }
13466
+ if (input) {
13467
+ input.setAttribute('dir', 'rtl');
13468
+ if (preferredLang) input.setAttribute('lang', preferredLang);else input.removeAttribute('lang');
13469
+ }
13470
+ } else {
13471
+ if (inputRow) {
13472
+ inputRow.removeAttribute('dir');
13473
+ inputRow.removeAttribute('lang');
13474
+ }
13475
+ if (input) {
13476
+ input.setAttribute('dir', 'auto');
13477
+ input.removeAttribute('lang');
13478
+ }
13479
+ }
13480
+ }
13481
+ }, {
13482
+ key: "_build",
13483
+ value: function _build() {
13484
+ this._injectStyles();
13485
+ var wrapper = document.createElement('div');
13486
+ wrapper.id = 'ttp-ecom-strip-wrapper';
13487
+ wrapper.innerHTML = this._html();
13488
+ document.body.appendChild(wrapper);
13489
+ this._el = wrapper;
13490
+ var strip = this._el.querySelector('#ttp-ecom-strip');
13491
+ if (strip) this._applyTheme(strip);
13492
+ this._applyStripTextDirection();
13493
+ this._wireEvents();
13494
+ this._el.style.display = 'none';
13495
+ }
13496
+ }, {
13497
+ key: "_applyTheme",
13498
+ value: function _applyTheme(strip) {
13499
+ var t = this.theme;
13500
+ var g = t.pillGradient || 'linear-gradient(135deg, #581c87, #312e81, #1e1b4b)';
13501
+ var fg = t.pillTextColor || '#ffffff';
13502
+ var accent = t.pillDotColor || '#4ade80';
13503
+ var endC = t.endCallButtonColor || '#ef4444';
13504
+ strip.style.setProperty('--ecom-strip-bg', g);
13505
+ strip.style.setProperty('--ecom-fg', fg);
13506
+ strip.style.setProperty('--ecom-accent', accent);
13507
+ strip.style.setProperty('--ecom-end', endC);
13508
+ }
13509
+ }, {
13510
+ key: "_html",
13511
+ value: function _html() {
13512
+ return "\n <div id=\"ttp-ecom-strip\" role=\"region\" aria-label=\"Voice call\">\n <div id=\"ttp-ecom-live-edge\" aria-live=\"polite\">\n <div id=\"ttp-ecom-live-row\">\n <span id=\"ttp-ecom-dot\" class=\"ttp-ecom-live-dot\"></span>\n <span id=\"ttp-ecom-live-label\">LIVE \xB7 00:00</span>\n </div>\n </div>\n <div id=\"ttp-ecom-main-row\">\n <div id=\"ttp-ecom-left-controls\" class=\"ttp-ecom-controls\">\n <button type=\"button\" class=\"ttp-ecom-ctrl\" id=\"ttp-ecom-btn-mic\" title=\"Mute / Unmute\" aria-pressed=\"false\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"ttp-ecom-mic-on\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n </svg>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"ttp-ecom-mic-off\" style=\"display:none\" aria-hidden=\"true\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\" stroke=\"currentColor\" stroke-width=\"1.8\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" stroke=\"currentColor\" stroke-width=\"1.8\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#fca5a5\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-ecom-ctrl\" id=\"ttp-ecom-btn-pause\" title=\"Pause / Resume\" aria-pressed=\"false\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" class=\"ttp-ecom-pause-ico\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" class=\"ttp-ecom-play-ico\" style=\"display:none\" aria-hidden=\"true\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-ecom-ctrl\" id=\"ttp-ecom-btn-speaker\" title=\"Speaker\" aria-pressed=\"false\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"ttp-ecom-spk-on\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"ttp-ecom-spk-off\" style=\"display:none\" aria-hidden=\"true\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#fca5a5\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n </svg>\n </button>\n </div>\n\n <div id=\"ttp-ecom-center\">\n <div id=\"ttp-ecom-transcript\" aria-live=\"polite\">Connecting\u2026</div>\n </div>\n\n <div id=\"ttp-ecom-right-cluster\" class=\"ttp-ecom-controls\">\n <div class=\"ttp-ecom-mini-wave\" aria-hidden=\"true\">\n <span class=\"ttp-ecom-wb\"></span><span class=\"ttp-ecom-wb\"></span><span class=\"ttp-ecom-wb\"></span>\n <span class=\"ttp-ecom-wb\"></span><span class=\"ttp-ecom-wb\"></span>\n </div>\n <button type=\"button\" class=\"ttp-ecom-ctrl\" id=\"ttp-ecom-btn-kbd\" title=\"Type a message\" aria-pressed=\"false\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\"/>\n <path d=\"M6 8h.01M10 8h.01M14 8h.01M18 8h.01M6 12h.01M10 12h.01M14 12h.01M18 12h.01M8 16h8\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-ecom-ctrl ttp-ecom-ctrl-end\" id=\"ttp-ecom-btn-end\" title=\"End call\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.99 12 19.79 19.79 0 0 1 1.97 3.4 2 2 0 0 1 3.95 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <div id=\"ttp-ecom-input-row\">\n <input type=\"text\" id=\"ttp-ecom-input\" placeholder=\"Type your message\u2026\" autocomplete=\"off\" />\n <button type=\"button\" id=\"ttp-ecom-send\" title=\"Send\" class=\"ttp-ecom-send\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/>\n </svg>\n </button>\n </div>\n ".concat(this._footerHtml(), "\n </div>\n ");
13513
+ }
13514
+ }, {
13515
+ key: "_footerHtml",
13516
+ value: function _footerHtml() {
13517
+ if (!this.footer) return '';
13518
+ var _this$footer = this.footer,
13519
+ brandUrl = _this$footer.brandUrl,
13520
+ brandName = _this$footer.brandName;
13521
+ return "\n <div id=\"ttp-ecom-footer\" class=\"ttp-ecom-footer\">\n <span>Powered by <a href=\"".concat(brandUrl, "\" target=\"_blank\" rel=\"noopener noreferrer\"><b>").concat(brandName, "</b></a></span>\n </div>");
13522
+ }
13523
+ }, {
13524
+ key: "_wireEvents",
13525
+ value: function _wireEvents() {
13526
+ var _this = this;
13527
+ var $ = function $(id) {
13528
+ return _this._el.querySelector('#' + id);
13529
+ };
13530
+ $('ttp-ecom-btn-mic').addEventListener('click', function () {
13531
+ _this.onToggleMute();
13532
+ _this.syncFromVoice();
13533
+ });
13534
+ $('ttp-ecom-btn-pause').addEventListener('click', function () {
13535
+ _this.onTogglePause();
13536
+ _this.syncFromVoice();
13537
+ });
13538
+ $('ttp-ecom-btn-speaker').addEventListener('click', function () {
13539
+ _this.onToggleSpeaker();
13540
+ _this.syncFromVoice();
13541
+ });
13542
+ $('ttp-ecom-btn-kbd').addEventListener('click', function () {
13543
+ _this._kbdOpen = !_this._kbdOpen;
13544
+ $('ttp-ecom-btn-kbd').classList.toggle('ttp-ecom-ctrl-active', _this._kbdOpen);
13545
+ $('ttp-ecom-input-row').classList.toggle('open', _this._kbdOpen);
13546
+ if (_this._kbdOpen) setTimeout(function () {
13547
+ return $('ttp-ecom-input').focus();
13548
+ }, 50);
13549
+ });
13550
+ $('ttp-ecom-btn-end').addEventListener('click', function () {
13551
+ return _this.onHangup();
13552
+ });
13553
+ var sendFn = function sendFn() {
13554
+ var input = $('ttp-ecom-input');
13555
+ var text = input.value.trim();
13556
+ if (!text) return;
13557
+ _this.onSendText(text);
13558
+ _this.updateUserTranscript(text, true);
13559
+ input.value = '';
13560
+ };
13561
+ $('ttp-ecom-send').addEventListener('click', sendFn);
13562
+ $('ttp-ecom-input').addEventListener('keydown', function (e) {
13563
+ if (e.key === 'Enter') sendFn();
13564
+ });
13565
+ }
13566
+ }, {
13567
+ key: "show",
13568
+ value: function show() {
13569
+ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
13570
+ if (!this._el) return;
13571
+ if (opts.reset !== false) this._reset();
13572
+ this._el.style.display = '';
13573
+ this.syncFromVoice();
13574
+ }
13575
+ }, {
13576
+ key: "hide",
13577
+ value: function hide() {
13578
+ if (!this._el) return;
13579
+ this._el.style.display = 'none';
13580
+ }
13581
+ }, {
13582
+ key: "syncFromVoice",
13583
+ value: function syncFromVoice() {
13584
+ if (!this._el || !this.getVoiceUiState) return;
13585
+ var s;
13586
+ try {
13587
+ s = this.getVoiceUiState();
13588
+ } catch (_) {
13589
+ return;
13590
+ }
13591
+ var micMuted = !!s.isMicMuted;
13592
+ var paused = !!s.isPaused;
13593
+ var spkMuted = !!s.isSpeakerMuted;
13594
+ var micBtn = this._el.querySelector('#ttp-ecom-btn-mic');
13595
+ var micOn = this._el.querySelector('.ttp-ecom-mic-on');
13596
+ var micOff = this._el.querySelector('.ttp-ecom-mic-off');
13597
+ if (micBtn && micOn && micOff) {
13598
+ micBtn.classList.toggle('ttp-ecom-ctrl-warn', micMuted);
13599
+ micBtn.setAttribute('aria-pressed', micMuted ? 'true' : 'false');
13600
+ micOn.style.display = micMuted ? 'none' : 'block';
13601
+ micOff.style.display = micMuted ? 'block' : 'none';
13602
+ }
13603
+ var pauseBtn = this._el.querySelector('#ttp-ecom-btn-pause');
13604
+ var pauseIco = this._el.querySelector('.ttp-ecom-pause-ico');
13605
+ var playIco = this._el.querySelector('.ttp-ecom-play-ico');
13606
+ if (pauseBtn && pauseIco && playIco) {
13607
+ pauseBtn.classList.toggle('ttp-ecom-ctrl-warn', paused);
13608
+ pauseBtn.setAttribute('aria-pressed', paused ? 'true' : 'false');
13609
+ pauseIco.style.display = paused ? 'none' : 'block';
13610
+ playIco.style.display = paused ? 'block' : 'none';
13611
+ }
13612
+ var spkBtn = this._el.querySelector('#ttp-ecom-btn-speaker');
13613
+ var spkOn = this._el.querySelector('.ttp-ecom-spk-on');
13614
+ var spkOff = this._el.querySelector('.ttp-ecom-spk-off');
13615
+ if (spkBtn && spkOn && spkOff) {
13616
+ spkBtn.classList.toggle('ttp-ecom-ctrl-warn', spkMuted);
13617
+ spkBtn.setAttribute('aria-pressed', spkMuted ? 'true' : 'false');
13618
+ spkOn.style.display = spkMuted ? 'none' : 'block';
13619
+ spkOff.style.display = spkMuted ? 'block' : 'none';
13620
+ }
13621
+ var dot = this._el.querySelector('#ttp-ecom-dot');
13622
+ if (dot) dot.classList.toggle('paused', paused);
13623
+ this._syncMiniWave(micMuted || paused || spkMuted);
13624
+ }
13625
+ }, {
13626
+ key: "_syncMiniWave",
13627
+ value: function _syncMiniWave(frozen) {
13628
+ var _this$_el;
13629
+ (_this$_el = this._el) === null || _this$_el === void 0 || _this$_el.querySelectorAll('.ttp-ecom-mini-wave .ttp-ecom-wb').forEach(function (b) {
13630
+ b.classList.toggle('paused', frozen);
13631
+ });
13632
+ }
13633
+ }, {
13634
+ key: "updateTranscript",
13635
+ value: function updateTranscript(text) {
13636
+ var _this$_language;
13637
+ if (!this._el) return;
13638
+ var el = this._el.querySelector('#ttp-ecom-transcript');
13639
+ if (!el) return;
13640
+ var t = text != null ? String(text) : '';
13641
+ if (!t.trim()) return;
13642
+ var stripCfg = {
13643
+ language: this._language,
13644
+ direction: this._direction
13645
+ };
13646
+ el.classList.remove('ttp-ecom-transcript--user');
13647
+ el.textContent = t;
13648
+ el.style.color = '';
13649
+ el.setAttribute('dir', (0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(t, stripCfg));
13650
+ if ((0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.configPrefersRtlText)(stripCfg) && (_this$_language = this._language) !== null && _this$_language !== void 0 && _this$_language.trim()) el.setAttribute('lang', this._language.trim());else el.removeAttribute('lang');
13651
+ }
13652
+
13653
+ /**
13654
+ * User STT (interim + final) on the same line as the assistant transcript; replaces agent text while user is speaking.
13655
+ * No "You:" prefix — color distinguishes user vs agent.
13656
+ * @param {string} text
13657
+ * @param {boolean} [isFinal]
13658
+ */
13659
+ }, {
13660
+ key: "updateUserTranscript",
13661
+ value: function updateUserTranscript(text, isFinal) {
13662
+ if (!this._el) return;
13663
+ var el = this._el.querySelector('#ttp-ecom-transcript');
13664
+ if (!el) return;
13665
+ var stripCfg = {
13666
+ language: this._language,
13667
+ direction: this._direction
13668
+ };
13669
+ var utter = text != null ? String(text) : '';
13670
+ var trimmed = utter.trim();
13671
+ if (!trimmed) {
13672
+ el.textContent = '';
13673
+ el.classList.remove('ttp-ecom-transcript--user');
13674
+ el.style.color = '';
13675
+ el.removeAttribute('dir');
13676
+ el.removeAttribute('lang');
13677
+ return;
13678
+ }
13679
+ el.classList.add('ttp-ecom-transcript--user');
13680
+ el.textContent = utter;
13681
+ el.style.color = '';
13682
+ el.setAttribute('dir', (0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(utter, stripCfg));
13683
+ var preferredLang = this._language && this._language.trim();
13684
+ if ((0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.configPrefersRtlText)(stripCfg) && preferredLang) el.setAttribute('lang', preferredLang);else el.removeAttribute('lang');
13685
+ }
13686
+ }, {
13687
+ key: "updateTimer",
13688
+ value: function updateTimer(timerText) {
13689
+ if (!this._el) return;
13690
+ var el = this._el.querySelector('#ttp-ecom-live-label');
13691
+ if (el) el.textContent = 'LIVE · ' + timerText;
13692
+ }
13693
+ }, {
13694
+ key: "setMuted",
13695
+ value: function setMuted(muted) {
13696
+ if (!this.getVoiceUiState) return;
13697
+ this.syncFromVoice();
13698
+ }
13699
+ }, {
13700
+ key: "setHeld",
13701
+ value: function setHeld(held) {
13702
+ if (!this.getVoiceUiState) return;
13703
+ this.syncFromVoice();
13704
+ }
13705
+ }, {
13706
+ key: "destroy",
13707
+ value: function destroy() {
13708
+ if (this._el && this._el.parentNode) this._el.parentNode.removeChild(this._el);
13709
+ this._el = null;
13710
+ var style = document.getElementById('ttp-ecom-strip-styles');
13711
+ if (style) style.parentNode.removeChild(style);
13712
+ }
13713
+ }, {
13714
+ key: "_reset",
13715
+ value: function _reset() {
13716
+ var _this2 = this,
13717
+ _$,
13718
+ _$2;
13719
+ this._kbdOpen = false;
13720
+ var $ = function $(id) {
13721
+ return _this2._el.querySelector('#' + id);
13722
+ };
13723
+ var tx = $('ttp-ecom-transcript');
13724
+ if (tx) {
13725
+ var _this$_language2;
13726
+ tx.textContent = 'Connecting…';
13727
+ tx.classList.remove('ttp-ecom-transcript--user');
13728
+ tx.style.color = '';
13729
+ var stripCfg = {
13730
+ language: this._language,
13731
+ direction: this._direction
13732
+ };
13733
+ tx.setAttribute('dir', (0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(tx.textContent, stripCfg));
13734
+ if ((0,_widget_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.configPrefersRtlText)(stripCfg) && (_this$_language2 = this._language) !== null && _this$_language2 !== void 0 && _this$_language2.trim()) tx.setAttribute('lang', this._language.trim());else tx.removeAttribute('lang');
13735
+ }
13736
+ var live = $('ttp-ecom-live-label');
13737
+ if (live) {
13738
+ live.textContent = 'LIVE · 00:00';
13739
+ live.style.color = '';
13740
+ }
13741
+ var dot = $('ttp-ecom-dot');
13742
+ if (dot) {
13743
+ dot.style.background = '';
13744
+ dot.classList.remove('paused');
13745
+ }
13746
+ (_$ = $('ttp-ecom-input-row')) === null || _$ === void 0 || _$.classList.remove('open');
13747
+ (_$2 = $('ttp-ecom-btn-kbd')) === null || _$2 === void 0 || _$2.classList.remove('ttp-ecom-ctrl-active');
13748
+ this._syncMiniWave(false);
13749
+ this.syncFromVoice();
13750
+ }
13751
+ }, {
13752
+ key: "_injectStyles",
13753
+ value: function _injectStyles() {
13754
+ if (document.getElementById('ttp-ecom-strip-styles')) return;
13755
+ var staleClip = document.getElementById('ttp-ecom-tab-clip-defs');
13756
+ if (staleClip) staleClip.remove();
13757
+ var style = document.createElement('style');
13758
+ style.id = 'ttp-ecom-strip-styles';
13759
+ style.textContent = EcommerceVoiceStrip._CSS;
13760
+ document.head.appendChild(style);
13761
+ }
13762
+ }]);
13763
+ }();
13764
+ EcommerceVoiceStrip._CSS = "\n#ttp-ecom-strip-wrapper {\n position: fixed;\n left: 50%;\n bottom: 0;\n transform: translateX(-50%);\n width: min(720px, calc(100vw - 40px));\n max-width: calc(100vw - 40px);\n z-index: 2147483647;\n box-sizing: border-box;\n padding: 0 14px max(14px, env(safe-area-inset-bottom, 0px));\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n font-feature-settings: \"kern\" 1, \"liga\" 1;\n pointer-events: none;\n}\n#ttp-ecom-strip-wrapper * { box-sizing: border-box; }\n\n/* Layered glass dock: rim light, soft lift, optional backdrop tint */\n#ttp-ecom-strip {\n pointer-events: auto;\n position: relative;\n isolation: isolate;\n margin: 0;\n border-radius: 26px;\n border: 1px solid rgba(255, 255, 255, 0.18);\n background: var(--ecom-strip-bg, linear-gradient(145deg, #5b21b6 0%, #3730a3 42%, #1e1b4b 100%));\n background-clip: padding-box;\n color: var(--ecom-fg, #ffffff);\n box-shadow:\n 0 0 0 1px rgba(0, 0, 0, 0.18) inset,\n 0 1px 0 rgba(255, 255, 255, 0.14) inset,\n 0 -20px 40px -8px rgba(0, 0, 0, 0.22) inset,\n 0 12px 32px rgba(0, 0, 0, 0.22),\n 0 28px 56px -16px rgba(0, 0, 0, 0.45),\n 0 0 80px -20px rgba(99, 102, 241, 0.35);\n overflow: visible;\n}\n\n#ttp-ecom-strip::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n z-index: 0;\n background: linear-gradient(\n 165deg,\n rgba(255, 255, 255, 0.08) 0%,\n rgba(255, 255, 255, 0) 42%,\n rgba(0, 0, 0, 0.12) 100%\n );\n}\n\n/* LIVE chip \u2014 glass capsule (backdrop blur), straddles top edge */\n#ttp-ecom-live-edge {\n position: absolute;\n left: 50%;\n top: 0;\n transform: translate(-50%, -50%);\n z-index: 4;\n max-width: calc(100% - 52px);\n pointer-events: none;\n padding: 3px 14px 4px;\n border-radius: 999px;\n background: rgba(8, 6, 24, 0.78);\n border: 1px solid rgba(255, 255, 255, 0.28);\n box-shadow:\n 0 0 0 1px rgba(0, 0, 0, 0.35),\n 0 4px 16px rgba(0, 0, 0, 0.35),\n inset 0 1px 0 rgba(255, 255, 255, 0.12);\n}\n@supports (backdrop-filter: blur(1px)) {\n #ttp-ecom-live-edge {\n backdrop-filter: blur(14px) saturate(180%);\n -webkit-backdrop-filter: blur(14px) saturate(180%);\n }\n}\n\n#ttp-ecom-main-row {\n position: relative;\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 18px 16px 12px;\n min-height: 48px;\n}\n\n#ttp-ecom-footer {\n position: relative;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 16px 10px;\n margin-top: -4px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n padding-top: 6px;\n}\n#ttp-ecom-footer span {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.35);\n white-space: nowrap;\n}\n#ttp-ecom-footer a {\n color: rgba(167, 139, 250, 0.95);\n text-decoration: none;\n font-weight: 600;\n}\n#ttp-ecom-footer a:hover { opacity: 0.85; }\n#ttp-ecom-footer a b { font-weight: 700; }\n\n#ttp-ecom-left-controls,\n#ttp-ecom-right-cluster {\n display: flex;\n align-items: center;\n gap: 7px;\n flex-shrink: 0;\n}\n\n/* Inset \u201Cmeter bridge\u201D for transcript \u2014 keeps one-line layout, adds depth */\n#ttp-ecom-center {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n justify-content: center;\n text-align: start;\n padding: 7px 12px 7px 14px;\n border-radius: 16px;\n background: rgba(0, 0, 0, 0.16);\n border: 1px solid rgba(255, 255, 255, 0.08);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.07),\n inset 0 -1px 0 rgba(0, 0, 0, 0.12);\n}\n\n#ttp-ecom-live-row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-width: 0;\n}\n\n#ttp-ecom-live-label {\n font-size: 9px;\n font-weight: 800;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--ecom-accent, #4ade80);\n opacity: 0.98;\n white-space: nowrap;\n}\n\n#ttp-ecom-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--ecom-accent, #4ade80);\n flex-shrink: 0;\n box-shadow: 0 0 14px rgba(74, 222, 128, 0.45);\n animation: ttp-ecom-blink 1.35s ease-in-out infinite;\n}\n#ttp-ecom-dot.paused {\n animation: none;\n opacity: 0.45;\n background: rgba(255,255,255,0.35);\n box-shadow: none;\n}\n@keyframes ttp-ecom-blink {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.25; transform: scale(0.92); }\n}\n\n#ttp-ecom-transcript {\n font-size: 12px;\n line-height: 1.42;\n letter-spacing: 0.015em;\n color: rgba(255, 255, 255, 0.94);\n width: 100%;\n max-width: 100%;\n overflow: hidden;\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n}\n\n#ttp-ecom-transcript.ttp-ecom-transcript--user {\n color: rgba(255, 244, 214, 0.98);\n font-weight: 600;\n letter-spacing: 0.02em;\n text-shadow: 0 0 28px rgba(251, 191, 36, 0.22);\n}\n\n/* Squircle glass controls */\n#ttp-ecom-strip .ttp-ecom-ctrl {\n width: 36px;\n height: 36px;\n border-radius: 12px;\n border: 1px solid rgba(255, 255, 255, 0.16);\n background: linear-gradient(165deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.05) 100%);\n color: rgba(255, 255, 255, 0.92);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n position: relative;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.18),\n 0 2px 8px rgba(0, 0, 0, 0.15);\n transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.14s ease;\n outline: none;\n}\n#ttp-ecom-strip .ttp-ecom-ctrl:hover {\n background: linear-gradient(165deg, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.08) 100%);\n border-color: rgba(255, 255, 255, 0.32);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.22),\n 0 4px 14px rgba(0, 0, 0, 0.2);\n}\n#ttp-ecom-strip .ttp-ecom-ctrl:active { transform: scale(0.94); }\n#ttp-ecom-strip .ttp-ecom-ctrl:focus-visible {\n outline: 2px solid rgba(165, 180, 252, 0.95);\n outline-offset: 2px;\n}\n#ttp-ecom-strip .ttp-ecom-ctrl-active {\n background: linear-gradient(165deg, rgba(129, 140, 248, 0.45) 0%, rgba(99, 102, 241, 0.28) 100%) !important;\n border-color: rgba(196, 181, 253, 0.65) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.2),\n 0 0 0 1px rgba(99, 102, 241, 0.35),\n 0 6px 18px rgba(79, 70, 229, 0.35) !important;\n}\n#ttp-ecom-strip .ttp-ecom-ctrl-warn {\n background: linear-gradient(165deg, rgba(252, 165, 165, 0.22) 0%, rgba(239, 68, 68, 0.2) 100%) !important;\n border-color: rgba(252, 165, 165, 0.55) !important;\n}\n\n#ttp-ecom-strip .ttp-ecom-ctrl-end {\n width: 40px;\n height: 40px;\n border-radius: 14px;\n background: linear-gradient(165deg, #fb7185 0%, var(--ecom-end, #ef4444) 52%, #991b1b 100%) !important;\n border: none !important;\n color: #fff !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.25),\n 0 0 0 2px rgba(0, 0, 0, 0.12),\n 0 6px 22px rgba(239, 68, 68, 0.48);\n transform: rotate(135deg);\n transition: filter 0.18s ease, transform 0.14s ease, box-shadow 0.2s ease;\n}\n#ttp-ecom-strip .ttp-ecom-ctrl-end:hover {\n filter: brightness(1.06);\n transform: rotate(135deg) scale(1.05);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.28),\n 0 0 0 2px rgba(0, 0, 0, 0.12),\n 0 10px 28px rgba(239, 68, 68, 0.52);\n}\n#ttp-ecom-strip .ttp-ecom-ctrl-end:active {\n transform: rotate(135deg) scale(0.94);\n}\n\n/* Mini waveform */\n#ttp-ecom-strip .ttp-ecom-mini-wave {\n display: flex;\n align-items: center;\n gap: 3px;\n height: 24px;\n padding: 0 4px;\n opacity: 0.9;\n}\n#ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb {\n width: 3px;\n border-radius: 3px;\n background: linear-gradient(180deg, #ddd6fe 0%, var(--ecom-accent, #a78bfa) 100%);\n animation: ttp-ecom-wa 0.78s ease-in-out infinite;\n}\n#ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb:nth-child(1) { height: 6px; animation-delay: 0s; }\n#ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb:nth-child(2) { height: 11px; animation-delay: 0.1s; }\n#ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb:nth-child(3) { height: 16px; animation-delay: 0.2s; }\n#ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb:nth-child(4) { height: 9px; animation-delay: 0.15s; }\n#ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb:nth-child(5) { height: 12px; animation-delay: 0.05s; }\n@keyframes ttp-ecom-wa {\n 0%, 100% { transform: scaleY(0.38); opacity: 0.5; }\n 50% { transform: scaleY(1); opacity: 1; }\n}\n#ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb.paused {\n animation-play-state: paused;\n transform: scaleY(0.32);\n opacity: 0.28;\n}\n\n/* Input row */\n#ttp-ecom-input-row {\n display: none;\n position: relative;\n z-index: 1;\n align-items: center;\n gap: 10px;\n padding: 0 14px 12px;\n margin-top: -2px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n#ttp-ecom-input-row.open {\n display: flex;\n}\n#ttp-ecom-input {\n flex: 1;\n height: 40px;\n border-radius: 12px;\n border: 1px solid rgba(255, 255, 255, 0.18);\n background: rgba(0, 0, 0, 0.22);\n color: #fff;\n font-size: 14px;\n padding: 0 14px;\n outline: none;\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n transition: border-color 0.18s ease, box-shadow 0.18s ease;\n}\n#ttp-ecom-input::placeholder { color: rgba(255, 255, 255, 0.42); }\n#ttp-ecom-input:focus {\n border-color: rgba(165, 180, 252, 0.65);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.2),\n 0 0 0 3px rgba(99, 102, 241, 0.25);\n}\n\n#ttp-ecom-strip .ttp-ecom-send {\n width: 36px;\n height: 36px;\n border-radius: 12px;\n border: none;\n background: linear-gradient(165deg, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.1) 100%);\n color: #fff;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);\n transition: background 0.18s ease, transform 0.12s ease;\n}\n#ttp-ecom-strip .ttp-ecom-send:hover {\n background: linear-gradient(165deg, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.14) 100%);\n}\n#ttp-ecom-strip .ttp-ecom-send:active { transform: scale(0.94); }\n\n@media (max-width: 520px) {\n #ttp-ecom-strip .ttp-ecom-mini-wave { display: none; }\n #ttp-ecom-main-row { gap: 7px; padding: 16px 12px 10px; }\n #ttp-ecom-strip .ttp-ecom-ctrl { width: 36px; height: 36px; }\n #ttp-ecom-strip .ttp-ecom-ctrl-end { width: 40px; height: 40px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n #ttp-ecom-dot { animation: none; opacity: 0.9; }\n #ttp-ecom-strip .ttp-ecom-mini-wave .ttp-ecom-wb { animation: none; opacity: 0.65; }\n}\n";
13765
+
13766
+ /***/ }),
13767
+
13768
+ /***/ "./src/ecommerce/FlyToCart.js":
13769
+ /*!************************************!*\
13770
+ !*** ./src/ecommerce/FlyToCart.js ***!
13771
+ \************************************/
13772
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
13773
+
13774
+ "use strict";
13775
+ __webpack_require__.r(__webpack_exports__);
13776
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
13777
+ /* harmony export */ FlyToCart: () => (/* binding */ FlyToCart)
13778
+ /* harmony export */ });
13779
+ /* harmony import */ var html2canvas__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! html2canvas */ "./node_modules/html2canvas/dist/html2canvas.js");
13780
+ /* harmony import */ var html2canvas__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(html2canvas__WEBPACK_IMPORTED_MODULE_0__);
13781
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
13782
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
13783
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
13784
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
13785
+ function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
13786
+ function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
13787
+ function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
13788
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
13789
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
13790
+ /**
13791
+ * FlyToCart — Fly-to-cart animation with tornado funnel effect.
13792
+ *
13793
+ * When the user taps "Add to Cart", the product card flies straight down
13794
+ * into the cart icon with a 3D tornado distortion, particle trail, and impact effects.
13795
+ *
13796
+ * Uses html2canvas to capture the card as an image — avoids DOM stacking/overflow
13797
+ * clipping when the widget is in an iframe or a container with overflow:hidden.
13798
+ *
13799
+ * Usage:
13800
+ * const flyToCart = new FlyToCart({
13801
+ * getCartTarget: () => document.querySelector('.ttp-cart-icon'),
13802
+ * onCartBump: () => { ... },
13803
+ * });
13804
+ * flyToCart.triggerFly(cardElement, product, () => { /* add to cart API *\/ });
13805
+ */
13806
+
13807
+
13808
+ var DURATION = 1000;
13809
+ var FlyToCart = /*#__PURE__*/function () {
13810
+ function FlyToCart() {
13811
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
13812
+ _classCallCheck(this, FlyToCart);
13813
+ this.getCartTarget = options.getCartTarget || function () {
13814
+ return null;
13815
+ };
13816
+ this.onCartBump = options.onCartBump || function () {};
13817
+ this.onRipple = options.onRipple || function () {};
13818
+ this.animatingId = null;
13819
+ this.trailParticles = [];
13820
+ this.canvas = null;
13821
+ this.rafId = null;
13822
+ this._canvasInitialized = false;
13823
+ }
13824
+ return _createClass(FlyToCart, [{
13825
+ key: "isAnimating",
13826
+ get: function get() {
13204
13827
  return this.animatingId !== null;
13205
13828
  }
13206
13829
  }, {
@@ -13567,13 +14190,10 @@ var MobilePresenter = /*#__PURE__*/function (_EcommercePresenter) {
13567
14190
  if (this.carousel && this.carousel.container) {
13568
14191
  this.carousel.showToast(product.name, cartInfo);
13569
14192
  }
13570
- this._updateMobileCartRow(cartInfo);
13571
14193
  }
13572
14194
  }, {
13573
14195
  key: "updateCart",
13574
- value: function updateCart(cartInfo) {
13575
- this._updateMobileCartRow(cartInfo);
13576
- }
14196
+ value: function updateCart(_cartInfo) {}
13577
14197
  }, {
13578
14198
  key: "destroy",
13579
14199
  value: function destroy() {
@@ -13583,27 +14203,6 @@ var MobilePresenter = /*#__PURE__*/function (_EcommercePresenter) {
13583
14203
  this.carousel = null;
13584
14204
  }
13585
14205
  }
13586
-
13587
- // ═══════════════════════════════════════════
13588
- // Mobile cart row in the minimized voice bar
13589
- // ═══════════════════════════════════════════
13590
- }, {
13591
- key: "_updateMobileCartRow",
13592
- value: function _updateMobileCartRow(_ref) {
13593
- var itemCount = _ref.itemCount,
13594
- total = _ref.total,
13595
- currency = _ref.currency;
13596
- var row = document.getElementById('mobileCartRow');
13597
- var textEl = document.getElementById('mobileCartText');
13598
- if (!row || !textEl) return;
13599
- if (itemCount <= 0) {
13600
- row.style.display = 'none';
13601
- return;
13602
- }
13603
- var itemText = "".concat(itemCount, " item").concat(itemCount !== 1 ? 's' : '');
13604
- textEl.textContent = "".concat(itemText, " \xB7 ").concat(currency).concat(total.toFixed(2));
13605
- row.style.display = 'flex';
13606
- }
13607
14206
  }]);
13608
14207
  }(_EcommercePresenter_js__WEBPACK_IMPORTED_MODULE_0__.EcommercePresenter);
13609
14208
 
@@ -14192,14 +14791,11 @@ __webpack_require__.r(__webpack_exports__);
14192
14791
  /* harmony export */ PartnerScriptRunner: () => (/* binding */ PartnerScriptRunner),
14193
14792
  /* harmony export */ buildRunPartnerScriptTool: () => (/* binding */ buildRunPartnerScriptTool)
14194
14793
  /* harmony export */ });
14195
- function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
14196
- function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
14197
- function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
14198
- function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
14794
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
14795
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
14796
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
14199
14797
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
14200
14798
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
14201
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
14202
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
14203
14799
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
14204
14800
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
14205
14801
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
@@ -14207,11 +14803,18 @@ function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o =
14207
14803
  function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
14208
14804
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
14209
14805
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
14210
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
14211
14806
  function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
14212
14807
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
14213
14808
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
14214
14809
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
14810
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
14811
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
14812
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
14813
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
14814
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
14815
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
14816
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
14817
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
14215
14818
  /**
14216
14819
  * PartnerScriptRunner
14217
14820
  *
@@ -14219,7 +14822,8 @@ function _asyncToGenerator(n) { return function () { var t = this, e = arguments
14219
14822
  *
14220
14823
  * Backend flow:
14221
14824
  * 1. On session start (after flavor is applied), backend sends
14222
- * `{ t: 'partner_bundle', partner_id, platform, adapters: { action: {...} } }`.
14825
+ * `{ t: 'partner_bundle', partner_id, platform,
14826
+ * adapters: { action: {...} }, autoRun: { action: {...} } }`.
14223
14827
  * Adapters are object-keyed because action is a unique key within a
14224
14828
  * partner's bundle — the DB constraint enforces it, the voice LLM's
14225
14829
  * tool schema assumes it.
@@ -14245,10 +14849,85 @@ function _asyncToGenerator(n) { return function () { var t = this, e = arguments
14245
14849
  * - refreshUI : platform-aware UI nudge
14246
14850
  * - normalizeCart: raw-cart → canonical cart (platform-dispatched)
14247
14851
  * - runAdapter : (action, args) → Promise<result> (invokes another adapter)
14852
+ * - emit : (msg) → void — send an unsolicited message back to the backend
14853
+ *
14854
+ * Orchestration (DB columns before_actions / after_actions on each adapter row):
14855
+ * runAdapter(action) runs, in order:
14856
+ * 1. each before_actions[] entry (full runAdapter — hooks can chain)
14857
+ * 2. code_js body (category event + adapter fn)
14858
+ * 3. each after_actions[] entry when the body returned ok !== false
14859
+ * (args include _parentResult with the body return value)
14860
+ * before-hook failures are logged and do not block the main body.
14861
+ *
14862
+ * Inline pre/post hooks (entry-level pre_code_js / post_code_js):
14863
+ * Some bundles (notably the reserved `__client_tools__` client-tool bundle)
14864
+ * ship a 3-step entry: an optional `pre_code_js`, the required `code_js`
14865
+ * (main), and an optional `post_code_js`. These compile alongside `code_js`
14866
+ * and run synchronously in order around the main body:
14867
+ * - pre is best-effort: an error is logged and main still runs;
14868
+ * - main's return value is the result sent back; a main error → ok:false;
14869
+ * - post runs only if main succeeded (mirrors after-hook gating); post
14870
+ * errors are logged best-effort.
14871
+ * A bad pre/post poisons only its own entry (same isolation as code_js).
14872
+ *
14873
+ * Combined ordering when an entry has both named hooks and inline hooks:
14874
+ * named before_actions → inline pre → main → inline post → named after_actions
14875
+ *
14876
+ * Category-based event suppression:
14877
+ * When an adapter entry has a non-null `category` field, runAdapter fires a
14878
+ * CustomEvent on window named `netcost:partner-action:{category}` BEFORE
14879
+ * executing the adapter. Subscribers (e.g. cart.subscribe) listen for this
14880
+ * event to suppress their own duplicate notifications.
14248
14881
  */
14249
14882
 
14250
14883
  var LOG = '[PartnerScriptRunner]';
14251
14884
 
14885
+ /** Max nested runAdapter depth (before/after hooks calling other hooked actions). */
14886
+ var MAX_ORCHESTRATION_DEPTH = 16;
14887
+ function adapterResultOk(result) {
14888
+ if (!result || _typeof(result) !== 'object') return true;
14889
+ if (!('ok' in result)) return true;
14890
+ return !!result.ok;
14891
+ }
14892
+
14893
+ /**
14894
+ * Best-effort IDs from run_partner_script args for diagnostics (cart.add, Shopify, etc.).
14895
+ * @param {Record<string, unknown>} raw
14896
+ * @returns {string[]}
14897
+ */
14898
+ function extractProductIdsFromPartnerArgs(raw) {
14899
+ if (!raw || _typeof(raw) !== 'object') return [];
14900
+ var out = [];
14901
+ var push = function push(v) {
14902
+ if (v == null || v === '') return;
14903
+ var s = String(v);
14904
+ if (s) out.push(s);
14905
+ };
14906
+ push(raw.productId);
14907
+ push(raw.sku);
14908
+ push(raw.retailerProductId);
14909
+ push(raw.variantId);
14910
+ if (Array.isArray(raw.items)) {
14911
+ var _iterator = _createForOfIteratorHelper(raw.items),
14912
+ _step;
14913
+ try {
14914
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
14915
+ var i = _step.value;
14916
+ if (!i || _typeof(i) !== 'object') continue;
14917
+ push(i.productId);
14918
+ push(i.sku);
14919
+ push(i.retailerProductId);
14920
+ push(i.variantId);
14921
+ }
14922
+ } catch (err) {
14923
+ _iterator.e(err);
14924
+ } finally {
14925
+ _iterator.f();
14926
+ }
14927
+ }
14928
+ return _toConsumableArray(new Set(out));
14929
+ }
14930
+
14252
14931
  // ---------------------------------------------------------------------------
14253
14932
  // Compilation
14254
14933
  // ---------------------------------------------------------------------------
@@ -14377,20 +15056,42 @@ function refreshUIForPlatform(platform) {
14377
15056
  // ---------------------------------------------------------------------------
14378
15057
 
14379
15058
  var PartnerScriptRunner = /*#__PURE__*/function () {
15059
+ /**
15060
+ * @param {{ genericCtx?: boolean }} [opts]
15061
+ * genericCtx: when true, ctx omits the ecommerce-specific helpers
15062
+ * (platform / normalizeCart / refreshUI). Used by the flavor-independent
15063
+ * `__client_tools__` runner. Default false preserves the ecommerce ctx.
15064
+ */
14380
15065
  function PartnerScriptRunner() {
15066
+ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
14381
15067
  _classCallCheck(this, PartnerScriptRunner);
14382
- /** @type {Map<string, {fn?: Function, version?: number, codeSha256?: string, meta?: object, poisoned?: boolean, error?: string}>} */
15068
+ this.genericCtx = !!(opts && opts.genericCtx);
15069
+ /** @type {Map<string, {fn?: Function, version?: number, codeSha256?: string, meta?: object, category?: string|null, beforeActions?: string[], afterActions?: string[], poisoned?: boolean, error?: string}>} */
14383
15070
  this.adapters = new Map();
14384
15071
  this.partnerId = null;
14385
15072
  this.platform = null;
14386
15073
  this.bundleVersion = 0; // monotonic, bumped on every setBundle
15074
+ /** @type {((msg: object) => void) | null} */
15075
+ this._emitCallback = null;
14387
15076
  }
14388
15077
 
14389
15078
  /**
14390
- * Replace the cached bundle from a `partner_bundle` message.
14391
- * @param {{partner_id?: string, platform?: string|null, adapters?: Record<string,{version?:number,code_js:string,code_sha256?:string,meta?:object}>}} msg
15079
+ * Register the callback that adapters can use to send unsolicited messages
15080
+ * back to the backend via ctx.emit(msg). Called by EcommerceFlavor after it
15081
+ * loads the bundle so the callback is always fresh for the active SDK instance.
15082
+ * @param {(msg: object) => void} fn
14392
15083
  */
14393
15084
  return _createClass(PartnerScriptRunner, [{
15085
+ key: "setEmitCallback",
15086
+ value: function setEmitCallback(fn) {
15087
+ this._emitCallback = typeof fn === 'function' ? fn : null;
15088
+ }
15089
+
15090
+ /**
15091
+ * Replace the cached bundle from a `partner_bundle` message.
15092
+ * @param {{partner_id?: string, platform?: string|null, adapters?: Record<string,{version?:number,code_js:string,code_sha256?:string,meta?:object,before_actions?:string[],after_actions?:string[]}>}} msg
15093
+ */
15094
+ }, {
14394
15095
  key: "setBundle",
14395
15096
  value: function setBundle(msg) {
14396
15097
  var adapters = (msg === null || msg === void 0 ? void 0 : msg.adapters) || {};
@@ -14415,22 +15116,33 @@ var PartnerScriptRunner = /*#__PURE__*/function () {
14415
15116
  continue;
14416
15117
  }
14417
15118
  try {
14418
- var _entry$version2;
15119
+ var _entry$version2, _entry$category;
14419
15120
  var fn = compileAdapter(entry.code_js);
15121
+ // Inline pre/post hooks (optional). Compiled with the same
15122
+ // AsyncFunction strict-mode wrapper as the main body so a syntax
15123
+ // error in a hook poisons only this entry (caught below).
15124
+ var preFn = typeof entry.pre_code_js === 'string' ? compileAdapter(entry.pre_code_js) : null;
15125
+ var postFn = typeof entry.post_code_js === 'string' ? compileAdapter(entry.post_code_js) : null;
14420
15126
  this.adapters.set(action, {
14421
15127
  fn: fn,
15128
+ preFn: preFn,
15129
+ postFn: postFn,
14422
15130
  version: (_entry$version2 = entry.version) !== null && _entry$version2 !== void 0 ? _entry$version2 : null,
14423
15131
  codeSha256: entry.code_sha256 || null,
14424
- meta: entry.meta || {}
15132
+ category: (_entry$category = entry.category) !== null && _entry$category !== void 0 ? _entry$category : null,
15133
+ meta: entry.meta || {},
15134
+ beforeActions: Array.isArray(entry.before_actions) ? entry.before_actions : [],
15135
+ afterActions: Array.isArray(entry.after_actions) ? entry.after_actions : []
14425
15136
  });
14426
15137
  compiled++;
14427
15138
  } catch (err) {
14428
- var _entry$version3;
15139
+ var _entry$version3, _entry$category2;
14429
15140
  console.error(LOG, "compile failed for '".concat(action, "':"), err);
14430
15141
  this.adapters.set(action, {
14431
15142
  poisoned: true,
14432
15143
  error: String((err === null || err === void 0 ? void 0 : err.message) || err),
14433
- version: (_entry$version3 = entry.version) !== null && _entry$version3 !== void 0 ? _entry$version3 : null
15144
+ version: (_entry$version3 = entry.version) !== null && _entry$version3 !== void 0 ? _entry$version3 : null,
15145
+ category: (_entry$category2 = entry.category) !== null && _entry$category2 !== void 0 ? _entry$category2 : null
14434
15146
  });
14435
15147
  poisoned++;
14436
15148
  }
@@ -14466,24 +15178,62 @@ var PartnerScriptRunner = /*#__PURE__*/function () {
14466
15178
  /**
14467
15179
  * Execute an adapter by action name. Used both by the run_partner_script
14468
15180
  * tool handler and by ctx.runAdapter for intra-bundle chaining.
15181
+ *
15182
+ * When the adapter row defines before_actions / after_actions (from
15183
+ * ttp.partner_adapters), those run automatically around code_js.
15184
+ *
15185
+ * If the adapter has a non-null `category`, fires a window CustomEvent
15186
+ * named `netcost:partner-action:{category}` BEFORE executing code_js.
15187
+ * Subscribers (e.g. cart.subscribe) use this to suppress their own
15188
+ * duplicate notifications for the same mutation.
15189
+ *
15190
+ * @param {string} action
15191
+ * @param {Record<string, unknown>} [args]
15192
+ * @param {number} [depth] internal recursion guard
14469
15193
  */
14470
15194
  }, {
14471
15195
  key: "runAdapter",
14472
15196
  value: (function () {
14473
15197
  var _runAdapter = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(action) {
14474
- var _entry$version4;
14475
15198
  var args,
15199
+ depth,
14476
15200
  entry,
14477
- ctx,
15201
+ safeArgs,
15202
+ beforeActions,
15203
+ afterActions,
15204
+ _iterator2,
15205
+ _step2,
15206
+ _hookAction,
15207
+ _hookResult,
15208
+ result,
15209
+ afterArgs,
15210
+ _iterator3,
15211
+ _step3,
15212
+ hookAction,
15213
+ hookResult,
14478
15214
  _args2 = arguments,
14479
- _t;
15215
+ _t,
15216
+ _t2;
14480
15217
  return _regenerator().w(function (_context2) {
14481
15218
  while (1) switch (_context2.p = _context2.n) {
14482
15219
  case 0:
14483
15220
  args = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
15221
+ depth = _args2.length > 2 && _args2[2] !== undefined ? _args2[2] : 0;
15222
+ if (!(depth > MAX_ORCHESTRATION_DEPTH)) {
15223
+ _context2.n = 1;
15224
+ break;
15225
+ }
15226
+ console.error("".concat(LOG, " orchestration depth exceeded for '").concat(action, "' (max ").concat(MAX_ORCHESTRATION_DEPTH, ")"));
15227
+ return _context2.a(2, {
15228
+ ok: false,
15229
+ reason: 'orchestration_depth_exceeded',
15230
+ action: action,
15231
+ depth: depth
15232
+ });
15233
+ case 1:
14484
15234
  entry = this.adapters.get(action);
14485
15235
  if (entry) {
14486
- _context2.n = 1;
15236
+ _context2.n = 2;
14487
15237
  break;
14488
15238
  }
14489
15239
  return _context2.a(2, {
@@ -14491,9 +15241,9 @@ var PartnerScriptRunner = /*#__PURE__*/function () {
14491
15241
  reason: 'adapter_not_in_bundle',
14492
15242
  action: action
14493
15243
  });
14494
- case 1:
15244
+ case 2:
14495
15245
  if (!entry.poisoned) {
14496
- _context2.n = 2;
15246
+ _context2.n = 3;
14497
15247
  break;
14498
15248
  }
14499
15249
  return _context2.a(2, {
@@ -14502,40 +15252,225 @@ var PartnerScriptRunner = /*#__PURE__*/function () {
14502
15252
  action: action,
14503
15253
  detail: entry.error
14504
15254
  });
14505
- case 2:
14506
- ctx = this._buildCtx(args, action, entry);
14507
- console.log("".concat(LOG, " exec ").concat(action, " v").concat((_entry$version4 = entry.version) !== null && _entry$version4 !== void 0 ? _entry$version4 : '?', " ") + "sha=".concat((entry.codeSha256 || '').slice(0, 12)));
14508
- _context2.p = 3;
14509
- _context2.n = 4;
14510
- return entry.fn(ctx);
14511
- case 4:
14512
- return _context2.a(2, _context2.v);
15255
+ case 3:
15256
+ safeArgs = args && _typeof(args) === 'object' ? args : {};
15257
+ beforeActions = entry.beforeActions || [];
15258
+ afterActions = entry.afterActions || [];
15259
+ _iterator2 = _createForOfIteratorHelper(beforeActions);
15260
+ _context2.p = 4;
15261
+ _iterator2.s();
14513
15262
  case 5:
14514
- _context2.p = 5;
14515
- _t = _context2.v;
14516
- console.error("".concat(LOG, " ").concat(action, " threw:"), _t);
14517
- return _context2.a(2, {
14518
- ok: false,
14519
- reason: 'adapter_threw',
14520
- error: String((_t === null || _t === void 0 ? void 0 : _t.message) || _t)
14521
- });
14522
- }
14523
- }, _callee2, this, [[3, 5]]);
14524
- }));
14525
- function runAdapter(_x) {
14526
- return _runAdapter.apply(this, arguments);
14527
- }
14528
- return runAdapter;
14529
- }())
14530
- }, {
14531
- key: "_buildCtx",
14532
- value: function _buildCtx(args, action /*, entry */) {
15263
+ if ((_step2 = _iterator2.n()).done) {
15264
+ _context2.n = 9;
15265
+ break;
15266
+ }
15267
+ _hookAction = _step2.value;
15268
+ if (!(!_hookAction || typeof _hookAction !== 'string')) {
15269
+ _context2.n = 6;
15270
+ break;
15271
+ }
15272
+ return _context2.a(3, 8);
15273
+ case 6:
15274
+ console.log("".concat(LOG, " before hook ").concat(action, " \u2192 ").concat(_hookAction));
15275
+ _context2.n = 7;
15276
+ return this.runAdapter(_hookAction, safeArgs, depth + 1);
15277
+ case 7:
15278
+ _hookResult = _context2.v;
15279
+ if (!adapterResultOk(_hookResult)) {
15280
+ console.warn("".concat(LOG, " before hook '").concat(_hookAction, "' for '").concat(action, "' returned ok=false \u2014 continuing with main body:"), _hookResult);
15281
+ }
15282
+ case 8:
15283
+ _context2.n = 5;
15284
+ break;
15285
+ case 9:
15286
+ _context2.n = 11;
15287
+ break;
15288
+ case 10:
15289
+ _context2.p = 10;
15290
+ _t = _context2.v;
15291
+ _iterator2.e(_t);
15292
+ case 11:
15293
+ _context2.p = 11;
15294
+ _iterator2.f();
15295
+ return _context2.f(11);
15296
+ case 12:
15297
+ _context2.n = 13;
15298
+ return this._runAdapterBody(action, safeArgs, entry);
15299
+ case 13:
15300
+ result = _context2.v;
15301
+ if (!(adapterResultOk(result) && afterActions.length > 0)) {
15302
+ _context2.n = 22;
15303
+ break;
15304
+ }
15305
+ afterArgs = _objectSpread(_objectSpread({}, safeArgs), {}, {
15306
+ _parentResult: result
15307
+ });
15308
+ _iterator3 = _createForOfIteratorHelper(afterActions);
15309
+ _context2.p = 14;
15310
+ _iterator3.s();
15311
+ case 15:
15312
+ if ((_step3 = _iterator3.n()).done) {
15313
+ _context2.n = 19;
15314
+ break;
15315
+ }
15316
+ hookAction = _step3.value;
15317
+ if (!(!hookAction || typeof hookAction !== 'string')) {
15318
+ _context2.n = 16;
15319
+ break;
15320
+ }
15321
+ return _context2.a(3, 18);
15322
+ case 16:
15323
+ console.log("".concat(LOG, " after hook ").concat(action, " \u2192 ").concat(hookAction));
15324
+ _context2.n = 17;
15325
+ return this.runAdapter(hookAction, afterArgs, depth + 1);
15326
+ case 17:
15327
+ hookResult = _context2.v;
15328
+ if (!adapterResultOk(hookResult)) {
15329
+ console.warn("".concat(LOG, " after hook '").concat(hookAction, "' for '").concat(action, "' returned ok=false:"), hookResult);
15330
+ }
15331
+ case 18:
15332
+ _context2.n = 15;
15333
+ break;
15334
+ case 19:
15335
+ _context2.n = 21;
15336
+ break;
15337
+ case 20:
15338
+ _context2.p = 20;
15339
+ _t2 = _context2.v;
15340
+ _iterator3.e(_t2);
15341
+ case 21:
15342
+ _context2.p = 21;
15343
+ _iterator3.f();
15344
+ return _context2.f(21);
15345
+ case 22:
15346
+ return _context2.a(2, result);
15347
+ }
15348
+ }, _callee2, this, [[14, 20, 21, 22], [4, 10, 11, 12]]);
15349
+ }));
15350
+ function runAdapter(_x) {
15351
+ return _runAdapter.apply(this, arguments);
15352
+ }
15353
+ return runAdapter;
15354
+ }()
15355
+ /**
15356
+ * Run code_js only — category event + compiled fn. No before/after hooks.
15357
+ * @private
15358
+ */
15359
+ )
15360
+ }, {
15361
+ key: "_runAdapterBody",
15362
+ value: (function () {
15363
+ var _runAdapterBody2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(action, args, entry) {
15364
+ var _entry$version4;
15365
+ var eventName, safeArgs, productIds, _productIds$, idStr, _idStr, ctx, result, postCtx, _t3, _t4, _t5;
15366
+ return _regenerator().w(function (_context3) {
15367
+ while (1) switch (_context3.p = _context3.n) {
15368
+ case 0:
15369
+ // Fire category event so subscribers can suppress duplicate handling.
15370
+ if (entry.category) {
15371
+ eventName = "netcost:partner-action:".concat(entry.category);
15372
+ safeArgs = args || {};
15373
+ productIds = extractProductIdsFromPartnerArgs(safeArgs);
15374
+ try {
15375
+ window.dispatchEvent(new CustomEvent(eventName, {
15376
+ detail: {
15377
+ action: action,
15378
+ args: safeArgs
15379
+ }
15380
+ }));
15381
+ idStr = productIds.length ? productIds.join(',') : '(none)';
15382
+ console.log("".concat(LOG, " dispatched ").concat(eventName, " action=").concat(action, " productIds=").concat(idStr), {
15383
+ action: action,
15384
+ category: entry.category,
15385
+ productIds: productIds,
15386
+ productId: (_productIds$ = productIds[0]) !== null && _productIds$ !== void 0 ? _productIds$ : null
15387
+ });
15388
+ } catch (e) {
15389
+ _idStr = productIds.length ? productIds.join(',') : '(none)';
15390
+ console.warn("".concat(LOG, " failed to dispatch category event '").concat(eventName, "' for '").concat(action, "' productIds=").concat(_idStr, ":"), e);
15391
+ }
15392
+ }
15393
+ ctx = this._buildCtx(args, action, entry);
15394
+ console.log("".concat(LOG, " exec ").concat(action, " v").concat((_entry$version4 = entry.version) !== null && _entry$version4 !== void 0 ? _entry$version4 : '?', " ") + "sha=".concat((entry.codeSha256 || '').slice(0, 12)));
15395
+
15396
+ // Inline pre-hook (best-effort): an error is logged and main still runs.
15397
+ if (!entry.preFn) {
15398
+ _context3.n = 4;
15399
+ break;
15400
+ }
15401
+ _context3.p = 1;
15402
+ _context3.n = 2;
15403
+ return entry.preFn(ctx);
15404
+ case 2:
15405
+ _context3.n = 4;
15406
+ break;
15407
+ case 3:
15408
+ _context3.p = 3;
15409
+ _t3 = _context3.v;
15410
+ console.warn("".concat(LOG, " ").concat(action, " inline pre-hook threw (continuing with main body):"), _t3);
15411
+ case 4:
15412
+ _context3.p = 4;
15413
+ _context3.n = 5;
15414
+ return entry.fn(ctx);
15415
+ case 5:
15416
+ result = _context3.v;
15417
+ _context3.n = 7;
15418
+ break;
15419
+ case 6:
15420
+ _context3.p = 6;
15421
+ _t4 = _context3.v;
15422
+ console.error("".concat(LOG, " ").concat(action, " threw:"), _t4);
15423
+ return _context3.a(2, {
15424
+ ok: false,
15425
+ reason: 'adapter_threw',
15426
+ error: String((_t4 === null || _t4 === void 0 ? void 0 : _t4.message) || _t4)
15427
+ });
15428
+ case 7:
15429
+ if (!(entry.postFn && adapterResultOk(result))) {
15430
+ _context3.n = 11;
15431
+ break;
15432
+ }
15433
+ postCtx = this._buildCtx(_objectSpread(_objectSpread({}, args || {}), {}, {
15434
+ _mainResult: result
15435
+ }), action, entry);
15436
+ _context3.p = 8;
15437
+ _context3.n = 9;
15438
+ return entry.postFn(postCtx);
15439
+ case 9:
15440
+ _context3.n = 11;
15441
+ break;
15442
+ case 10:
15443
+ _context3.p = 10;
15444
+ _t5 = _context3.v;
15445
+ console.warn("".concat(LOG, " ").concat(action, " inline post-hook threw (ignored):"), _t5);
15446
+ case 11:
15447
+ return _context3.a(2, result);
15448
+ }
15449
+ }, _callee3, this, [[8, 10], [4, 6], [1, 3]]);
15450
+ }));
15451
+ function _runAdapterBody(_x2, _x3, _x4) {
15452
+ return _runAdapterBody2.apply(this, arguments);
15453
+ }
15454
+ return _runAdapterBody;
15455
+ }())
15456
+ }, {
15457
+ key: "_buildCtx",
15458
+ value: function _buildCtx(args, action /*, entry */) {
14533
15459
  var _this = this;
14534
15460
  var platform = this.platform;
14535
- return {
15461
+ var emitCallback = this._emitCallback;
15462
+ // Send an unsolicited message back to the backend (e.g. cart_change_event).
15463
+ // No-op if no emitCallback is registered.
15464
+ var emit = function emit(msg) {
15465
+ if (typeof emitCallback === 'function') {
15466
+ emitCallback(msg);
15467
+ } else {
15468
+ console.warn("".concat(LOG, " [adapter:").concat(action, "] ctx.emit called but no emitCallback registered"));
15469
+ }
15470
+ };
15471
+ var ctx = {
14536
15472
  args: args || {},
14537
15473
  host: window.location.hostname,
14538
- platform: platform,
14539
15474
  fetch: window.fetch.bind(window),
14540
15475
  log: function log() {
14541
15476
  var _console;
@@ -14544,14 +15479,22 @@ var PartnerScriptRunner = /*#__PURE__*/function () {
14544
15479
  }
14545
15480
  return (_console = console).log.apply(_console, ["[adapter:".concat(action, "]")].concat(a));
14546
15481
  },
14547
- refreshUI: refreshUIForPlatform(platform),
14548
- normalizeCart: function normalizeCart(raw) {
14549
- return normalizeCartByPlatform(platform, raw);
14550
- },
14551
15482
  runAdapter: function runAdapter(a, subArgs) {
14552
15483
  return _this.runAdapter(a, subArgs);
14553
- }
15484
+ },
15485
+ emit: emit
14554
15486
  };
15487
+ // Ecommerce-specific helpers are omitted for the flavor-independent
15488
+ // `__client_tools__` runner (genericCtx). Client-tool scripts get a
15489
+ // platform-agnostic ctx with no cart normalization or UI nudges.
15490
+ if (!this.genericCtx) {
15491
+ ctx.platform = platform;
15492
+ ctx.refreshUI = refreshUIForPlatform(platform);
15493
+ ctx.normalizeCart = function (raw) {
15494
+ return normalizeCartByPlatform(platform, raw);
15495
+ };
15496
+ }
15497
+ return ctx;
14555
15498
  }
14556
15499
  }]);
14557
15500
  }();
@@ -14568,28 +15511,28 @@ function buildRunPartnerScriptTool(runner) {
14568
15511
  return {
14569
15512
  name: 'run_partner_script',
14570
15513
  handler: function () {
14571
- var _handler = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
15514
+ var _handler = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
14572
15515
  var parameters,
14573
15516
  action,
14574
15517
  args,
14575
- _args3 = arguments;
14576
- return _regenerator().w(function (_context3) {
14577
- while (1) switch (_context3.n) {
15518
+ _args4 = arguments;
15519
+ return _regenerator().w(function (_context4) {
15520
+ while (1) switch (_context4.n) {
14578
15521
  case 0:
14579
- parameters = _args3.length > 0 && _args3[0] !== undefined ? _args3[0] : {};
15522
+ parameters = _args4.length > 0 && _args4[0] !== undefined ? _args4[0] : {};
14580
15523
  action = parameters.action, args = parameters.args;
14581
15524
  if (action) {
14582
- _context3.n = 1;
15525
+ _context4.n = 1;
14583
15526
  break;
14584
15527
  }
14585
- return _context3.a(2, {
15528
+ return _context4.a(2, {
14586
15529
  ok: false,
14587
15530
  reason: 'missing_action'
14588
15531
  });
14589
15532
  case 1:
14590
- return _context3.a(2, runner.runAdapter(action, args || {}));
15533
+ return _context4.a(2, runner.runAdapter(action, args || {}));
14591
15534
  }
14592
- }, _callee3);
15535
+ }, _callee4);
14593
15536
  }));
14594
15537
  function handler() {
14595
15538
  return _handler.apply(this, arguments);
@@ -15365,7 +16308,7 @@ __webpack_require__.r(__webpack_exports__);
15365
16308
  * Follows the same pattern as Styles.generateCSS().
15366
16309
  */
15367
16310
  function generateEcommerceCSS() {
15368
- return "\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n ECOMMERCE AREA\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n\n/*\n * When products are visible, expand the panel dynamically.\n * Actual height is set via inline style by EcommerceManager._expandPanelToFit().\n * When the class is removed, the panel returns to its CSS-defined default.\n */\n#text-chat-panel.ttp-panel-expanded {\n transition: height 0.25s ease;\n}\n\n/*\n * Layout overrides ONLY when ecommerce/hotels overlay is active.\n * Scoped under .ttp-products-visible / .ttp-rooms-visible so the\n * normal voice CSS takes effect when products/rooms are hidden.\n *\n * voiceActiveState is a flex column: [voice-controls] [ecommerce] [conversation].\n * Ecommerce area fills the middle and scrolls; conversation panel stays at bottom.\n */\n#voiceActiveState.ttp-products-visible,\n#voiceActiveState.ttp-rooms-visible {\n flex: 1 1 0 !important;\n min-height: 0 !important;\n overflow: hidden !important;\n}\n\n.ttp-products-visible > .desktop-voice-section,\n.ttp-rooms-visible > .desktop-voice-section {\n flex-shrink: 1 !important;\n}\n\n/* Conversation + input always stick to bottom; ecommerce area above grows to fill. */\n.ttp-products-visible > .desktop-conversation-panel,\n.ttp-rooms-visible > .desktop-conversation-panel {\n flex: 0 0 auto !important;\n margin-top: auto !important;\n display: flex !important;\n flex-direction: column !important;\n}\n\n.ttp-products-visible .desktop-conversation-panel .live-transcript-collapsed,\n.ttp-rooms-visible .desktop-conversation-panel .live-transcript-collapsed {\n flex: 0 0 auto !important;\n min-height: 40px !important;\n overflow: hidden !important;\n}\n\n.ttp-products-visible .desktop-conversation-panel .voice-text-input-area,\n.ttp-rooms-visible .desktop-conversation-panel .voice-text-input-area {\n margin-top: auto !important;\n padding: 3px 16px !important;\n}\n\n.ttp-products-visible .desktop-conversation-panel .voice-input-hint,\n.ttp-rooms-visible .desktop-conversation-panel .voice-input-hint {\n display: none !important;\n}\n\n#voiceActiveState.ttp-products-visible .desktop-voice-section {\n display: none !important;\n}\n\n#voiceActiveState.ttp-products-visible .compact-voice-section {\n display: flex !important;\n}\n\n#voiceActiveState.ttp-products-visible .desktop-conversation-panel {\n flex: 0 0 auto !important;\n}\n\n#voiceActiveState.ttp-products-visible .conversation-history {\n display: none !important;\n}\n\n#voiceActiveState.ttp-products-visible .live-transcript-collapsed {\n flex: 0 0 auto !important;\n}\n\n.voice-text-input-area {\n flex-shrink: 0 !important;\n}\n\n.ttp-products-visible .conversation-history,\n.ttp-rooms-visible .conversation-history {\n flex: 1 1 0 !important;\n min-height: 0 !important;\n overflow-y: auto !important;\n}\n\n.ttp-ecommerce-area {\n display: flex !important;\n flex-direction: column !important;\n width: 100%;\n flex: 1 1 0 !important;\n min-height: 0 !important;\n position: relative !important;\n overflow-x: hidden !important;\n overflow-y: hidden !important;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n PRODUCT PICKER\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-product-picker {\n background: rgba(255,255,255,0.04);\n border-radius: 14px;\n border: 1px solid rgba(255,255,255,0.08);\n box-shadow: none;\n margin: 8px 10px;\n overflow-x: hidden !important;\n overflow-y: auto !important;\n animation: ttpSlideUp 0.25s ease;\n display: flex !important;\n flex-direction: column;\n flex: 1;\n min-height: 0 !important;\n}\n\n@keyframes ttpSlideUp {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.ttp-picker-header {\n display: flex !important;\n justify-content: space-between;\n align-items: center;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(255,255,255,0.06);\n flex-shrink: 0;\n}\n\n.ttp-picker-title {\n font-size: 12px;\n font-weight: 600;\n color: rgba(255,255,255,0.5);\n}\n\n.ttp-picker-close {\n background: none;\n border: none;\n cursor: pointer;\n color: rgba(255,255,255,0.4);\n font-size: 14px;\n padding: 2px 6px;\n border-radius: 4px;\n}\n.ttp-picker-close:hover {\n background: rgba(255,255,255,0.08);\n color: rgba(255,255,255,0.7);\n}\n\n/* Horizontal card layout */\n.ttp-products-horizontal {\n display: flex !important;\n align-items: flex-start;\n flex-shrink: 0;\n gap: 10px;\n padding: 12px;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n}\n.ttp-products-horizontal::-webkit-scrollbar { height: 4px; }\n.ttp-products-horizontal::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }\n.ttp-products-horizontal::-webkit-scrollbar-track { background: transparent; }\n\n/* Vertical list layout */\n.ttp-products-vertical {\n padding: 8px 12px;\n overflow-y: auto !important;\n display: flex !important;\n flex-direction: column;\n gap: 6px;\n flex: 1;\n min-height: 0 !important;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n PRODUCT CARD (horizontal)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-product-card {\n width: 170px !important;\n flex-shrink: 0 !important;\n background: rgba(255,255,255,0.04);\n border-radius: 12px;\n border: 1px solid rgba(255,255,255,0.08);\n overflow: hidden;\n transition: border-color 0.15s;\n}\n.ttp-product-card:hover { border-color: rgba(109,86,245,0.5); }\n\n.ttp-product-image {\n height: 110px !important;\n background: rgba(0,0,0,0.2);\n display: flex !important;\n align-items: center;\n justify-content: center;\n position: relative;\n overflow: hidden;\n}\n.ttp-product-image img {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n object-fit: contain !important;\n display: block !important;\n}\n\n.ttp-stock-badge {\n position: absolute;\n top: 6px; right: 6px;\n background: rgba(239,68,68,0.15);\n color: #ef4444;\n font-size: 9px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: 4px;\n}\n\n.ttp-product-details {\n padding: 10px 12px 14px;\n}\n\n.ttp-product-name {\n font-size: 13px;\n font-weight: 600;\n color: #e8e4f0;\n line-height: 1.3;\n min-height: 30px;\n}\n\n.ttp-product-page-link {\n display: inline-flex;\n align-items: center;\n margin-left: 5px;\n font-size: 10px;\n color: #a78bfa;\n text-decoration: none;\n opacity: 0.7;\n vertical-align: middle;\n}\n.ttp-product-page-link:hover { opacity: 1; text-decoration: underline; }\n\n.ttp-product-meta {\n font-size: 10px;\n color: rgba(255,255,255,0.35);\n margin-top: 2px;\n}\n\n.ttp-product-footer {\n display: flex !important;\n align-items: center;\n justify-content: space-between;\n margin-top: 6px;\n}\n\n.ttp-product-price {\n font-size: 14px;\n font-weight: 700;\n color: #e8e4f0;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n PRODUCT COMPACT (list view)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-product-compact {\n display: flex !important;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n background: rgba(255,255,255,0.04);\n border-radius: 10px;\n border: 1px solid rgba(255,255,255,0.08);\n transition: border-color 0.15s;\n}\n.ttp-product-compact:hover { border-color: rgba(109,86,245,0.5); }\n\n.ttp-product-image-sm {\n width: 72px !important; height: 72px !important;\n border-radius: 10px;\n background: rgba(0,0,0,0.2);\n display: flex !important;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: hidden;\n}\n.ttp-product-image-sm img {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n object-fit: contain !important;\n display: block !important;\n}\n\n.ttp-product-compact .ttp-product-info {\n flex: 1;\n min-width: 0;\n}\n.ttp-product-compact .ttp-product-name {\n min-height: auto;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #e8e4f0;\n}\n.ttp-product-compact .ttp-product-action {\n text-align: right;\n flex-shrink: 0 !important;\n}\n.ttp-product-compact .ttp-product-price {\n font-size: 13px;\n}\n\n.ttp-out-of-stock {\n font-size: 10px;\n color: #f87171;\n font-weight: 500;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n ADD BUTTON\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-add-btn {\n padding: 6px 14px;\n border-radius: 8px;\n border: 1.5px solid rgba(109,86,245,0.6);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n background: transparent;\n color: #a78bfa;\n transition: all 0.15s;\n}\n.ttp-add-btn:hover { background: rgba(109,86,245,0.15); border-color: #a78bfa; }\n.ttp-add-btn:active { transform: scale(0.95); }\n.ttp-add-btn.ttp-added { background: rgba(109,86,245,0.6) !important; border-color: #7c3aed !important; color: #fff !important; }\n.ttp-add-btn:disabled { cursor: default; opacity: 0.5; }\n.ttp-add-btn.ttp-update-btn { background: rgba(245,158,11,0.15); border-color: rgba(245,158,11,0.5); color: #fbbf24; }\n.ttp-add-btn.ttp-update-btn:hover { background: rgba(245,158,11,0.25); border-color: #fbbf24; }\n\n.ttp-products-animating .ttp-add-btn {\n opacity: 0.4;\n pointer-events: none;\n cursor: not-allowed;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n CART TOAST\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n/* Toast replaces waveform in desktop voice section */\n.ttp-cart-toast {\n display: flex !important;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n margin: 8px 12px;\n background: rgba(16,185,129,0.1);\n border-radius: 12px;\n border: 1px solid rgba(16,185,129,0.2);\n animation: ttpToastIn 0.25s ease;\n flex-shrink: 0;\n box-shadow: none;\n}\n\n@keyframes ttpToastIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ttp-cart-toast.ttp-toast-hiding {\n animation: ttpSlideDown 0.3s ease forwards;\n}\n\n@keyframes ttpSlideDown {\n to { opacity: 0; transform: translateY(-8px); height: 0; padding: 0; margin: 0; overflow: hidden; }\n}\n\n.ttp-toast-icon {\n width: 24px !important; height: 24px !important;\n border-radius: 50%;\n background: #10b981;\n color: #fff;\n display: flex !important;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 700;\n flex-shrink: 0;\n}\n\n.ttp-toast-content { flex: 1; min-width: 0; }\n.ttp-toast-title { font-size: 12px; font-weight: 600; color: #34d399; }\n.ttp-toast-product {\n font-size: 11px;\n color: rgba(52,211,153,0.7);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.ttp-toast-undo {\n background: none;\n border: 1px solid rgba(52,211,153,0.3);\n border-radius: 6px;\n padding: 3px 8px;\n font-size: 10px;\n color: #34d399;\n cursor: pointer;\n font-weight: 600;\n flex-shrink: 0;\n}\n.ttp-toast-undo:hover { background: rgba(52,211,153,0.1); }\n\n/* Fly-to-cart desktop target (when cart summary is hidden) */\n.ttp-desktop-fly-target {\n position: absolute;\n bottom: 12px;\n right: 80px;\n width: 24px;\n height: 24px;\n pointer-events: none;\n}\n\n/* Fly-to-cart ripple effect */\n.ttp-fly-ripple {\n position: absolute;\n left: 10px;\n top: 50%;\n width: 30px;\n height: 30px;\n margin-top: -15px;\n border-radius: 50%;\n border: 2px solid rgba(74,222,128,0.7);\n animation: ttp-ripple-expand 0.6s ease-out forwards;\n pointer-events: none;\n}\n\n@keyframes ttp-ripple-expand {\n 0% { transform: scale(0.5); opacity: 0.8; }\n 100% { transform: scale(3); opacity: 0; }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n CART SUMMARY BAR\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-cart-summary {\n display: flex !important;\n justify-content: space-between;\n align-items: center;\n padding: 8px 14px;\n margin: 4px 12px 0;\n margin-right: 20px;\n margin-top: auto !important;\n flex-shrink: 0 !important;\n background: rgba(255,255,255,0.04);\n border-radius: 12px;\n border: 1px solid rgba(255,255,255,0.08);\n}\n\n/* Hide cart when empty \u2014 overrides display:flex !important */\n.ttp-cart-summary.ttp-cart-empty {\n display: none !important;\n}\n\n/* Desktop: cart below input (fallback when input area not found) */\n.ttp-cart-summary-bottom {\n margin-top: 0 !important;\n margin: 8px 12px 12px !important;\n flex-shrink: 0 !important;\n}\n\n/* Desktop: cart inside input area \u2014 unified section (like mobile) */\n.ttp-cart-summary-inline {\n margin: 0 !important;\n padding: 0 !important;\n padding-top: 8px !important;\n flex-shrink: 0 !important;\n background: transparent !important;\n border: none !important;\n border-top: 1px solid rgba(255, 255, 255, 0.06) !important;\n border-radius: 0 !important;\n width: 100% !important;\n flex-basis: 100% !important;\n}\n\n.ttp-cart-info {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.ttp-cart-icon { font-size: 14px; }\n.ttp-cart-count { font-size: 12px; font-weight: 600; color: rgba(255,255,255,0.6); }\n.ttp-cart-separator { font-size: 12px; color: rgba(255,255,255,0.25); }\n.ttp-cart-total { font-size: 13px; font-weight: 700; color: #e8e4f0; }\n.ttp-cart-view {\n font-size: 12px;\n font-weight: 600;\n color: #a78bfa;\n text-decoration: none;\n cursor: pointer;\n white-space: nowrap;\n}\n.ttp-cart-view:hover {\n color: #c4b5fd;\n text-decoration: underline;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n QUANTITY/WEIGHT STEPPER\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-stepper {\n display: flex !important;\n align-items: center;\n gap: 2px;\n margin: 4px 0;\n}\n\n.ttp-stepper-btn {\n width: 24px !important;\n height: 24px !important;\n border-radius: 6px;\n border: 1px solid rgba(255,255,255,0.15);\n background: rgba(255,255,255,0.06);\n color: rgba(255,255,255,0.6);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n display: flex !important;\n align-items: center;\n justify-content: center;\n padding: 0;\n line-height: 1;\n transition: all 0.15s;\n flex-shrink: 0;\n}\n\n.ttp-stepper-btn:hover:not(:disabled) {\n background: rgba(255,255,255,0.12);\n border-color: rgba(255,255,255,0.25);\n}\n\n.ttp-stepper-btn:active:not(:disabled) {\n transform: scale(0.9);\n}\n\n.ttp-stepper-btn:disabled,\n.ttp-stepper-disabled {\n opacity: 0.35;\n cursor: default;\n}\n\n.ttp-stepper-value {\n font-size: 12px;\n font-weight: 600;\n color: #e8e4f0;\n min-width: 36px;\n text-align: center;\n user-select: none;\n}\n\n.ttp-product-compact .ttp-stepper {\n justify-content: flex-end;\n}\n\n.ttp-product-compact .ttp-product-action {\n display: flex !important;\n flex-direction: column;\n align-items: flex-end;\n gap: 2px;\n}\n\n.ttp-product-card .ttp-stepper {\n justify-content: center;\n margin: 4px 0;\n}\n\n.ttp-product-card .ttp-product-footer {\n display: flex !important;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n VARIATION DESKTOP PICKER\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.picker-relative {\n position: relative !important;\n flex: 1 1 0 !important;\n min-height: 0 !important;\n display: flex !important;\n flex-direction: column !important;\n overflow: hidden;\n}\n\n/* When variant picker is shown, center it horizontally and vertically */\n.picker-relative.ttp-var-picker-active {\n justify-content: center !important;\n align-items: center !important;\n}\n\n.ttp-var-picker {\n background: rgba(255,255,255,0.04);\n border-radius: 14px;\n border: 1px solid rgba(255,255,255,0.08);\n display: flex !important;\n flex-direction: column;\n animation: ttpVarFadeIn 0.2s ease;\n overflow: hidden;\n width: 100%;\n flex-shrink: 0 !important;\n}\n\n@keyframes ttpVarFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.ttp-var-header {\n display: flex !important;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(255,255,255,0.06);\n flex-shrink: 0;\n}\n\n.ttp-var-back-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: rgba(255,255,255,0.5);\n font-size: 16px;\n padding: 0;\n display: flex;\n align-items: center;\n line-height: 1;\n font-family: inherit;\n}\n.ttp-var-back-btn:hover { color: #e8e4f0; }\n\n.ttp-var-title {\n font-size: 13px;\n font-weight: 700;\n color: #e8e4f0;\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.ttp-var-page-link {\n font-size: 11px;\n color: #a78bfa;\n text-decoration: none;\n font-weight: 500;\n white-space: nowrap;\n flex-shrink: 0;\n}\n.ttp-var-page-link:hover { text-decoration: underline; }\n\n.ttp-var-body {\n flex: 1;\n overflow-y: auto;\n padding: 12px 14px;\n display: flex !important;\n flex-direction: column;\n gap: 14px;\n}\n\n.ttp-var-preview {\n display: flex !important;\n gap: 12px;\n align-items: flex-start;\n}\n\n.ttp-var-preview-img {\n width: 72px !important;\n height: 72px !important;\n border-radius: 12px;\n background: rgba(0,0,0,0.2);\n border: 1px solid rgba(255,255,255,0.08);\n display: flex !important;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: hidden;\n transition: all 0.2s ease;\n}\n\n.ttp-var-preview-img img {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n object-fit: contain !important;\n display: block !important;\n padding: 6px;\n}\n\n.ttp-var-preview-info { flex: 1; min-width: 0; }\n\n.ttp-var-preview-brand {\n font-size: 10px;\n color: rgba(255,255,255,0.4);\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.ttp-var-preview-name {\n font-size: 14px;\n font-weight: 700;\n color: #e8e4f0;\n margin: 2px 0 4px;\n line-height: 1.2;\n}\n\n.ttp-var-preview-price {\n font-size: 18px;\n font-weight: 700;\n color: #7c3aed;\n}\n\n.ttp-var-preview-selected {\n font-size: 11px;\n color: rgba(255,255,255,0.4);\n margin-top: 3px;\n}\n\n.ttp-var-section { display: flex !important; flex-direction: column; gap: 8px; }\n\n.ttp-var-section-label {\n font-size: 11px;\n font-weight: 700;\n color: rgba(255,255,255,0.5);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n}\n\n.ttp-var-swatches { display: flex !important; flex-wrap: wrap; gap: 8px; }\n\n.ttp-var-swatch {\n display: block !important;\n width: 28px !important;\n height: 28px !important;\n border-radius: 50%;\n cursor: pointer;\n border: 3px solid transparent;\n transition: all 0.15s;\n position: relative;\n flex-shrink: 0;\n}\n.ttp-var-swatch:hover { transform: scale(1.1); }\n.ttp-var-swatch.selected {\n border-color: #7c3aed;\n box-shadow: 0 0 0 2px #fff, 0 0 0 4px #7c3aed;\n}\n.ttp-var-swatch.oos {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.ttp-var-pills { display: flex !important; flex-wrap: wrap; gap: 6px; }\n\n.ttp-var-pill {\n padding: 5px 12px;\n border-radius: 8px;\n border: 1.5px solid rgba(255,255,255,0.15);\n background: rgba(255,255,255,0.04);\n font-size: 12px;\n font-weight: 600;\n color: rgba(255,255,255,0.6);\n cursor: pointer;\n transition: all 0.15s;\n font-family: inherit;\n}\n.ttp-var-pill:hover:not(:disabled) { border-color: #7c3aed; color: #a78bfa; }\n.ttp-var-pill.selected { border-color: #7c3aed; background: #7c3aed; color: #fff; }\n.ttp-var-pill.oos { opacity: 0.35; cursor: not-allowed; text-decoration: line-through; }\n\n.ttp-var-footer {\n padding: 10px 14px 12px;\n border-top: 1px solid rgba(255,255,255,0.06);\n flex-shrink: 0;\n}\n\n.ttp-var-add-btn {\n width: 100%;\n padding: 10px;\n background: linear-gradient(135deg, #7c3aed, #6d28d9);\n color: #fff;\n border: none;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n font-family: inherit;\n box-shadow: 0 3px 10px rgba(124,58,237,0.3);\n transition: all 0.15s;\n}\n.ttp-var-add-btn:hover:not(:disabled) { transform: scale(0.99); }\n.ttp-var-add-btn:disabled {\n background: rgba(255,255,255,0.1);\n box-shadow: none;\n cursor: not-allowed;\n color: rgba(255,255,255,0.3);\n}\n\n.ttp-var-spinner {\n width: 24px;\n height: 24px;\n border: 3px solid rgba(255,255,255,0.1);\n border-top-color: #7c3aed;\n border-radius: 50%;\n animation: ttpVarSpin 0.7s linear infinite;\n}\n@keyframes ttpVarSpin {\n to { transform: rotate(360deg); }\n}\n\n/* Amber \"Choose\" button for variant products */\n.ttp-add-btn.ttp-choose-btn {\n background: linear-gradient(135deg, #f59e0b, #d97706);\n box-shadow: 0 2px 8px rgba(245,158,11,0.3);\n color: #fff;\n}\n\n ";
16311
+ return "\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n ECOMMERCE AREA\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n\n/*\n * When products are visible, expand the panel dynamically.\n * Actual height is set via inline style by EcommerceManager._expandPanelToFit().\n * When the class is removed, the panel returns to its CSS-defined default.\n */\n#text-chat-panel.ttp-panel-expanded {\n transition: height 0.25s ease;\n}\n\n/*\n * Layout overrides ONLY when ecommerce/hotels overlay is active.\n * Scoped under .ttp-products-visible / .ttp-rooms-visible so the\n * normal voice CSS takes effect when products/rooms are hidden.\n *\n * voiceActiveState is a flex column: [voice-controls] [ecommerce] [conversation].\n * Ecommerce area fills the middle and scrolls; conversation panel stays at bottom.\n */\n#voiceActiveState.ttp-products-visible,\n#voiceActiveState.ttp-rooms-visible {\n flex: 1 1 0 !important;\n min-height: 0 !important;\n overflow: hidden !important;\n}\n\n.ttp-products-visible > .desktop-voice-section,\n.ttp-rooms-visible > .desktop-voice-section {\n flex-shrink: 1 !important;\n}\n\n/* Conversation + input always stick to bottom; ecommerce area above grows to fill. */\n.ttp-products-visible > .desktop-conversation-panel,\n.ttp-rooms-visible > .desktop-conversation-panel {\n flex: 0 0 auto !important;\n margin-top: auto !important;\n display: flex !important;\n flex-direction: column !important;\n}\n\n.ttp-products-visible .desktop-conversation-panel .live-transcript-collapsed,\n.ttp-rooms-visible .desktop-conversation-panel .live-transcript-collapsed {\n flex: 0 0 auto !important;\n min-height: 40px !important;\n overflow: hidden !important;\n}\n\n.ttp-products-visible .desktop-conversation-panel .voice-text-input-area,\n.ttp-rooms-visible .desktop-conversation-panel .voice-text-input-area {\n margin-top: auto !important;\n padding: 3px 16px !important;\n}\n\n.ttp-products-visible .desktop-conversation-panel .voice-input-hint,\n.ttp-rooms-visible .desktop-conversation-panel .voice-input-hint {\n display: none !important;\n}\n\n#voiceActiveState.ttp-products-visible .desktop-voice-section {\n display: none !important;\n}\n\n#voiceActiveState.ttp-products-visible .compact-voice-section {\n display: flex !important;\n}\n\n#voiceActiveState.ttp-products-visible .desktop-conversation-panel {\n flex: 0 0 auto !important;\n}\n\n#voiceActiveState.ttp-products-visible .conversation-history {\n display: none !important;\n}\n\n#voiceActiveState.ttp-products-visible .live-transcript-collapsed {\n flex: 0 0 auto !important;\n}\n\n.voice-text-input-area {\n flex-shrink: 0 !important;\n}\n\n.ttp-products-visible .conversation-history,\n.ttp-rooms-visible .conversation-history {\n flex: 1 1 0 !important;\n min-height: 0 !important;\n overflow-y: auto !important;\n}\n\n.ttp-ecommerce-area {\n display: flex !important;\n flex-direction: column !important;\n width: 100%;\n flex: 1 1 0 !important;\n min-height: 0 !important;\n position: relative !important;\n overflow-x: hidden !important;\n overflow-y: hidden !important;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n PRODUCT PICKER\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-product-picker {\n background: rgba(255,255,255,0.04);\n border-radius: 14px;\n border: 1px solid rgba(255,255,255,0.08);\n box-shadow: none;\n margin: 8px 10px;\n overflow-x: hidden !important;\n overflow-y: auto !important;\n animation: ttpSlideUp 0.25s ease;\n display: flex !important;\n flex-direction: column;\n flex: 1;\n min-height: 0 !important;\n}\n\n@keyframes ttpSlideUp {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.ttp-picker-header {\n display: flex !important;\n justify-content: space-between;\n align-items: center;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(255,255,255,0.06);\n flex-shrink: 0;\n}\n\n.ttp-picker-title {\n font-size: 12px;\n font-weight: 600;\n color: rgba(255,255,255,0.5);\n}\n\n.ttp-picker-close {\n background: none;\n border: none;\n cursor: pointer;\n color: rgba(255,255,255,0.4);\n font-size: 14px;\n padding: 2px 6px;\n border-radius: 4px;\n}\n.ttp-picker-close:hover {\n background: rgba(255,255,255,0.08);\n color: rgba(255,255,255,0.7);\n}\n\n/* Horizontal card layout */\n.ttp-products-horizontal {\n display: flex !important;\n align-items: flex-start;\n flex-shrink: 0;\n gap: 10px;\n padding: 12px;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n}\n.ttp-products-horizontal::-webkit-scrollbar { height: 4px; }\n.ttp-products-horizontal::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }\n.ttp-products-horizontal::-webkit-scrollbar-track { background: transparent; }\n\n/* Vertical list layout */\n.ttp-products-vertical {\n padding: 8px 12px;\n overflow-y: auto !important;\n display: flex !important;\n flex-direction: column;\n gap: 6px;\n flex: 1;\n min-height: 0 !important;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n PRODUCT CARD (horizontal)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-product-card {\n width: 170px !important;\n flex-shrink: 0 !important;\n background: rgba(255,255,255,0.04);\n border-radius: 12px;\n border: 1px solid rgba(255,255,255,0.08);\n overflow: hidden;\n transition: border-color 0.15s;\n}\n.ttp-product-card:hover { border-color: rgba(109,86,245,0.5); }\n\n.ttp-product-image {\n height: 110px !important;\n background: rgba(0,0,0,0.2);\n display: flex !important;\n align-items: center;\n justify-content: center;\n position: relative;\n overflow: hidden;\n}\n.ttp-product-image img {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n object-fit: contain !important;\n display: block !important;\n}\n\n.ttp-stock-badge {\n position: absolute;\n top: 6px; right: 6px;\n background: rgba(239,68,68,0.15);\n color: #ef4444;\n font-size: 9px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: 4px;\n}\n\n.ttp-product-details {\n padding: 10px 12px 14px;\n}\n\n.ttp-product-name {\n font-size: 13px;\n font-weight: 600;\n color: #e8e4f0;\n line-height: 1.3;\n min-height: 30px;\n}\n\n.ttp-product-page-link {\n display: inline-flex;\n align-items: center;\n margin-left: 5px;\n font-size: 10px;\n color: #a78bfa;\n text-decoration: none;\n opacity: 0.7;\n vertical-align: middle;\n}\n.ttp-product-page-link:hover { opacity: 1; text-decoration: underline; }\n\n.ttp-product-meta {\n font-size: 10px;\n color: rgba(255,255,255,0.35);\n margin-top: 2px;\n}\n\n.ttp-product-footer {\n display: flex !important;\n align-items: center;\n justify-content: space-between;\n margin-top: 6px;\n}\n\n.ttp-product-price {\n font-size: 14px;\n font-weight: 700;\n color: #e8e4f0;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n PRODUCT COMPACT (list view)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-product-compact {\n display: flex !important;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n background: rgba(255,255,255,0.04);\n border-radius: 10px;\n border: 1px solid rgba(255,255,255,0.08);\n transition: border-color 0.15s;\n}\n.ttp-product-compact:hover { border-color: rgba(109,86,245,0.5); }\n\n.ttp-product-image-sm {\n width: 72px !important; height: 72px !important;\n border-radius: 10px;\n background: rgba(0,0,0,0.2);\n display: flex !important;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: hidden;\n}\n.ttp-product-image-sm img {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n object-fit: contain !important;\n display: block !important;\n}\n\n.ttp-product-compact .ttp-product-info {\n flex: 1;\n min-width: 0;\n}\n.ttp-product-compact .ttp-product-name {\n min-height: auto;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #e8e4f0;\n}\n.ttp-product-compact .ttp-product-action {\n text-align: right;\n flex-shrink: 0 !important;\n}\n.ttp-product-compact .ttp-product-price {\n font-size: 13px;\n}\n\n.ttp-out-of-stock {\n font-size: 10px;\n color: #f87171;\n font-weight: 500;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n ADD BUTTON\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-add-btn {\n padding: 6px 14px;\n border-radius: 8px;\n border: 1.5px solid rgba(109,86,245,0.6);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n background: transparent;\n color: #a78bfa;\n transition: all 0.15s;\n}\n.ttp-add-btn:hover { background: rgba(109,86,245,0.15); border-color: #a78bfa; }\n.ttp-add-btn:active { transform: scale(0.95); }\n.ttp-add-btn.ttp-added { background: rgba(109,86,245,0.6) !important; border-color: #7c3aed !important; color: #fff !important; }\n.ttp-add-btn:disabled { cursor: default; opacity: 0.5; }\n.ttp-add-btn.ttp-update-btn { background: rgba(245,158,11,0.15); border-color: rgba(245,158,11,0.5); color: #fbbf24; }\n.ttp-add-btn.ttp-update-btn:hover { background: rgba(245,158,11,0.25); border-color: #fbbf24; }\n\n.ttp-products-animating .ttp-add-btn {\n opacity: 0.4;\n pointer-events: none;\n cursor: not-allowed;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n CART TOAST\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n/* Toast replaces waveform in desktop voice section */\n.ttp-cart-toast {\n display: flex !important;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n margin: 8px 12px;\n background: rgba(16,185,129,0.1);\n border-radius: 12px;\n border: 1px solid rgba(16,185,129,0.2);\n animation: ttpToastIn 0.25s ease;\n flex-shrink: 0;\n box-shadow: none;\n}\n\n@keyframes ttpToastIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ttp-cart-toast.ttp-toast-hiding {\n animation: ttpSlideDown 0.3s ease forwards;\n}\n\n@keyframes ttpSlideDown {\n to { opacity: 0; transform: translateY(-8px); height: 0; padding: 0; margin: 0; overflow: hidden; }\n}\n\n.ttp-toast-icon {\n width: 24px !important; height: 24px !important;\n border-radius: 50%;\n background: #10b981;\n color: #fff;\n display: flex !important;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 700;\n flex-shrink: 0;\n}\n\n.ttp-toast-content { flex: 1; min-width: 0; }\n.ttp-toast-title { font-size: 12px; font-weight: 600; color: #34d399; }\n.ttp-toast-product {\n font-size: 11px;\n color: rgba(52,211,153,0.7);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.ttp-toast-undo {\n background: none;\n border: 1px solid rgba(52,211,153,0.3);\n border-radius: 6px;\n padding: 3px 8px;\n font-size: 10px;\n color: #34d399;\n cursor: pointer;\n font-weight: 600;\n flex-shrink: 0;\n}\n.ttp-toast-undo:hover { background: rgba(52,211,153,0.1); }\n\n/* Fly-to-cart desktop target (when cart summary is hidden) */\n.ttp-desktop-fly-target {\n position: absolute;\n bottom: 12px;\n right: 80px;\n width: 24px;\n height: 24px;\n pointer-events: none;\n}\n\n/* Fly-to-cart ripple effect */\n.ttp-fly-ripple {\n position: absolute;\n left: 10px;\n top: 50%;\n width: 30px;\n height: 30px;\n margin-top: -15px;\n border-radius: 50%;\n border: 2px solid rgba(74,222,128,0.7);\n animation: ttp-ripple-expand 0.6s ease-out forwards;\n pointer-events: none;\n}\n\n@keyframes ttp-ripple-expand {\n 0% { transform: scale(0.5); opacity: 0.8; }\n 100% { transform: scale(3); opacity: 0; }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n QUANTITY/WEIGHT STEPPER\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.ttp-stepper {\n display: flex !important;\n align-items: center;\n gap: 2px;\n margin: 4px 0;\n}\n\n.ttp-stepper-btn {\n width: 24px !important;\n height: 24px !important;\n border-radius: 6px;\n border: 1px solid rgba(255,255,255,0.15);\n background: rgba(255,255,255,0.06);\n color: rgba(255,255,255,0.6);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n display: flex !important;\n align-items: center;\n justify-content: center;\n padding: 0;\n line-height: 1;\n transition: all 0.15s;\n flex-shrink: 0;\n}\n\n.ttp-stepper-btn:hover:not(:disabled) {\n background: rgba(255,255,255,0.12);\n border-color: rgba(255,255,255,0.25);\n}\n\n.ttp-stepper-btn:active:not(:disabled) {\n transform: scale(0.9);\n}\n\n.ttp-stepper-btn:disabled,\n.ttp-stepper-disabled {\n opacity: 0.35;\n cursor: default;\n}\n\n.ttp-stepper-value {\n font-size: 12px;\n font-weight: 600;\n color: #e8e4f0;\n min-width: 36px;\n text-align: center;\n user-select: none;\n}\n\n.ttp-product-compact .ttp-stepper {\n justify-content: flex-end;\n}\n\n.ttp-product-compact .ttp-product-action {\n display: flex !important;\n flex-direction: column;\n align-items: flex-end;\n gap: 2px;\n}\n\n.ttp-product-card .ttp-stepper {\n justify-content: center;\n margin: 4px 0;\n}\n\n.ttp-product-card .ttp-product-footer {\n display: flex !important;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n VARIATION DESKTOP PICKER\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.picker-relative {\n position: relative !important;\n flex: 1 1 0 !important;\n min-height: 0 !important;\n display: flex !important;\n flex-direction: column !important;\n overflow: hidden;\n}\n\n/* When variant picker is shown, center it horizontally and vertically */\n.picker-relative.ttp-var-picker-active {\n justify-content: center !important;\n align-items: center !important;\n}\n\n.ttp-var-picker {\n background: rgba(255,255,255,0.04);\n border-radius: 14px;\n border: 1px solid rgba(255,255,255,0.08);\n display: flex !important;\n flex-direction: column;\n animation: ttpVarFadeIn 0.2s ease;\n overflow: hidden;\n width: 100%;\n flex-shrink: 0 !important;\n}\n\n@keyframes ttpVarFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.ttp-var-header {\n display: flex !important;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(255,255,255,0.06);\n flex-shrink: 0;\n}\n\n.ttp-var-back-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: rgba(255,255,255,0.5);\n font-size: 16px;\n padding: 0;\n display: flex;\n align-items: center;\n line-height: 1;\n font-family: inherit;\n}\n.ttp-var-back-btn:hover { color: #e8e4f0; }\n\n.ttp-var-title {\n font-size: 13px;\n font-weight: 700;\n color: #e8e4f0;\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.ttp-var-page-link {\n font-size: 11px;\n color: #a78bfa;\n text-decoration: none;\n font-weight: 500;\n white-space: nowrap;\n flex-shrink: 0;\n}\n.ttp-var-page-link:hover { text-decoration: underline; }\n\n.ttp-var-body {\n flex: 1;\n overflow-y: auto;\n padding: 12px 14px;\n display: flex !important;\n flex-direction: column;\n gap: 14px;\n}\n\n.ttp-var-preview {\n display: flex !important;\n gap: 12px;\n align-items: flex-start;\n}\n\n.ttp-var-preview-img {\n width: 72px !important;\n height: 72px !important;\n border-radius: 12px;\n background: rgba(0,0,0,0.2);\n border: 1px solid rgba(255,255,255,0.08);\n display: flex !important;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: hidden;\n transition: all 0.2s ease;\n}\n\n.ttp-var-preview-img img {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n object-fit: contain !important;\n display: block !important;\n padding: 6px;\n}\n\n.ttp-var-preview-info { flex: 1; min-width: 0; }\n\n.ttp-var-preview-brand {\n font-size: 10px;\n color: rgba(255,255,255,0.4);\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.ttp-var-preview-name {\n font-size: 14px;\n font-weight: 700;\n color: #e8e4f0;\n margin: 2px 0 4px;\n line-height: 1.2;\n}\n\n.ttp-var-preview-price {\n font-size: 18px;\n font-weight: 700;\n color: #7c3aed;\n}\n\n.ttp-var-preview-selected {\n font-size: 11px;\n color: rgba(255,255,255,0.4);\n margin-top: 3px;\n}\n\n.ttp-var-section { display: flex !important; flex-direction: column; gap: 8px; }\n\n.ttp-var-section-label {\n font-size: 11px;\n font-weight: 700;\n color: rgba(255,255,255,0.5);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n}\n\n.ttp-var-swatches { display: flex !important; flex-wrap: wrap; gap: 8px; }\n\n.ttp-var-swatch {\n display: block !important;\n width: 28px !important;\n height: 28px !important;\n border-radius: 50%;\n cursor: pointer;\n border: 3px solid transparent;\n transition: all 0.15s;\n position: relative;\n flex-shrink: 0;\n}\n.ttp-var-swatch:hover { transform: scale(1.1); }\n.ttp-var-swatch.selected {\n border-color: #7c3aed;\n box-shadow: 0 0 0 2px #fff, 0 0 0 4px #7c3aed;\n}\n.ttp-var-swatch.oos {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.ttp-var-pills { display: flex !important; flex-wrap: wrap; gap: 6px; }\n\n.ttp-var-pill {\n padding: 5px 12px;\n border-radius: 8px;\n border: 1.5px solid rgba(255,255,255,0.15);\n background: rgba(255,255,255,0.04);\n font-size: 12px;\n font-weight: 600;\n color: rgba(255,255,255,0.6);\n cursor: pointer;\n transition: all 0.15s;\n font-family: inherit;\n}\n.ttp-var-pill:hover:not(:disabled) { border-color: #7c3aed; color: #a78bfa; }\n.ttp-var-pill.selected { border-color: #7c3aed; background: #7c3aed; color: #fff; }\n.ttp-var-pill.oos { opacity: 0.35; cursor: not-allowed; text-decoration: line-through; }\n\n.ttp-var-footer {\n padding: 10px 14px 12px;\n border-top: 1px solid rgba(255,255,255,0.06);\n flex-shrink: 0;\n}\n\n.ttp-var-add-btn {\n width: 100%;\n padding: 10px;\n background: linear-gradient(135deg, #7c3aed, #6d28d9);\n color: #fff;\n border: none;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n font-family: inherit;\n box-shadow: 0 3px 10px rgba(124,58,237,0.3);\n transition: all 0.15s;\n}\n.ttp-var-add-btn:hover:not(:disabled) { transform: scale(0.99); }\n.ttp-var-add-btn:disabled {\n background: rgba(255,255,255,0.1);\n box-shadow: none;\n cursor: not-allowed;\n color: rgba(255,255,255,0.3);\n}\n\n.ttp-var-spinner {\n width: 24px;\n height: 24px;\n border: 3px solid rgba(255,255,255,0.1);\n border-top-color: #7c3aed;\n border-radius: 50%;\n animation: ttpVarSpin 0.7s linear infinite;\n}\n@keyframes ttpVarSpin {\n to { transform: rotate(360deg); }\n}\n\n/* Amber \"Choose\" button for variant products */\n.ttp-add-btn.ttp-choose-btn {\n background: linear-gradient(135deg, #f59e0b, #d97706);\n box-shadow: 0 2px 8px rgba(245,158,11,0.3);\n color: #fff;\n}\n\n ";
15369
16312
  }
15370
16313
 
15371
16314
  /***/ }),
@@ -15960,13 +16903,13 @@ __webpack_require__.r(__webpack_exports__);
15960
16903
  /* harmony import */ var _ecommerce_EcommerceManager_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../ecommerce/EcommerceManager.js */ "./src/ecommerce/EcommerceManager.js");
15961
16904
  /* harmony import */ var _ecommerce_PartnerScriptRunner_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../ecommerce/PartnerScriptRunner.js */ "./src/ecommerce/PartnerScriptRunner.js");
15962
16905
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
15963
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
15964
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
15965
- function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
15966
16906
  function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
15967
16907
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
15968
16908
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
15969
16909
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
16910
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
16911
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
16912
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
15970
16913
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
15971
16914
  function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
15972
16915
  function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
@@ -16067,10 +17010,41 @@ var EcommerceFlavor = /*#__PURE__*/function (_BaseFlavor) {
16067
17010
  // === DB-backed per-partner adapters (replaces CDN hooks) ===
16068
17011
  // Backend pushes the full bundle once at session start; the widget then
16069
17012
  // executes individual adapters via the 'run_partner_script' client tool.
16070
- // Shape: { t: 'partner_bundle', partner_id, platform, adapters: { action: {...} } }
17013
+ // Shape: { t: 'partner_bundle', partner_id, platform,
17014
+ // adapters: { action: {...} }, ← on-demand, invoked by LLM
17015
+ // autoRun: { action: {...} } } ← executed immediately on load
16071
17016
  'partner_bundle': function partner_bundle(data) {
16072
- var _this2$_partnerScript;
16073
- (_this2$_partnerScript = _this2._partnerScriptRunner) === null || _this2$_partnerScript === void 0 || _this2$_partnerScript.setBundle(data);
17017
+ // The reserved `__client_tools__` bundle is owned by the
17018
+ // flavor-independent ClientScriptManager at the SDK level skip it
17019
+ // here so the two bundles stay separate per partner id.
17020
+ if ((data === null || data === void 0 ? void 0 : data.partner_id) === '__client_tools__') return;
17021
+ var runner = _this2._partnerScriptRunner;
17022
+ if (!runner) return;
17023
+
17024
+ // Wire the emit callback BEFORE setBundle so auto-run adapters can use it.
17025
+ runner.setEmitCallback(function (msg) {
17026
+ return _this2.sendToVoiceSDK(msg);
17027
+ });
17028
+
17029
+ // Merge autoRun entries into the adapters map so the runner can look
17030
+ // them up by action name (e.g. for ctx.runAdapter chaining).
17031
+ var merged = _objectSpread(_objectSpread({}, data), {}, {
17032
+ adapters: _objectSpread(_objectSpread({}, (data === null || data === void 0 ? void 0 : data.adapters) || {}), (data === null || data === void 0 ? void 0 : data.autoRun) || {})
17033
+ });
17034
+ runner.setBundle(merged);
17035
+
17036
+ // Execute every autoRun adapter once immediately.
17037
+ var _loop = function _loop() {
17038
+ var action = _Object$keys[_i];
17039
+ runner.runAdapter(action, {}).then(function (r) {
17040
+ return console.log("[EcommerceFlavor] auto-run '".concat(action, "':"), r);
17041
+ }).catch(function (e) {
17042
+ return console.warn("[EcommerceFlavor] auto-run '".concat(action, "' failed:"), e);
17043
+ });
17044
+ };
17045
+ for (var _i = 0, _Object$keys = Object.keys((data === null || data === void 0 ? void 0 : data.autoRun) || {}); _i < _Object$keys.length; _i++) {
17046
+ _loop();
17047
+ }
16074
17048
  },
16075
17049
  // Shopify: see file header — add_to_store_cart uses /cart/add.js + /cart.js.
16076
17050
  'add_to_store_cart': function add_to_store_cart(data) {
@@ -18925,6 +19899,7 @@ __webpack_require__.r(__webpack_exports__);
18925
19899
  /* harmony export */ TTPEcommerceWidget: () => (/* reexport safe */ _ecommerce_TTPEcommerceWidget_js__WEBPACK_IMPORTED_MODULE_7__.TTPEcommerceWidget),
18926
19900
  /* harmony export */ TextChatSDK: () => (/* reexport safe */ _core_TextChatSDK_js__WEBPACK_IMPORTED_MODULE_1__["default"]),
18927
19901
  /* harmony export */ VERSION: () => (/* binding */ VERSION),
19902
+ /* harmony export */ VoiceSDK: () => (/* binding */ VoiceSDK),
18928
19903
  /* harmony export */ VoiceSDK_v2: () => (/* reexport safe */ _v2_VoiceSDK_js__WEBPACK_IMPORTED_MODULE_0__["default"]),
18929
19904
  /* harmony export */ WebSocketManager: () => (/* reexport safe */ _core_WebSocketManager_js__WEBPACK_IMPORTED_MODULE_2__["default"]),
18930
19905
  /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__),
@@ -18955,6 +19930,11 @@ __webpack_require__.r(__webpack_exports__);
18955
19930
  // Core SDK
18956
19931
 
18957
19932
 
19933
+ // Backward-compatible alias: the legacy `VoiceSDK` name (v1) now points at the
19934
+ // v2 implementation. The v2 class defaults to `protocolVersion: 2`, so existing
19935
+ // code and documentation that references `VoiceSDK` keeps working on v2.
19936
+ var VoiceSDK = _v2_VoiceSDK_js__WEBPACK_IMPORTED_MODULE_0__["default"];
19937
+
18958
19938
 
18959
19939
 
18960
19940
 
@@ -18976,8 +19956,8 @@ __webpack_require__.r(__webpack_exports__);
18976
19956
 
18977
19957
 
18978
19958
  // Version - injected at build time from package.json via webpack DefinePlugin
18979
- var VERSION = "2.44.0";
18980
- var BUILD_TIME = "2026-05-07T11:24:41.900Z";
19959
+ var VERSION = "2.45.0";
19960
+ var BUILD_TIME = "2026-06-12T15:13:11.602Z";
18981
19961
  console.log("%c TTP Agent SDK v".concat(VERSION, " (").concat(BUILD_TIME, ") "), 'background: #4f46e5; color: white; font-size: 12px; font-weight: bold; padding: 2px 6px; border-radius: 4px;');
18982
19962
 
18983
19963
  // Named exports
@@ -18986,6 +19966,7 @@ console.log("%c TTP Agent SDK v".concat(VERSION, " (").concat(BUILD_TIME, ") "),
18986
19966
  // Default export
18987
19967
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({
18988
19968
  VoiceSDK_v2: _v2_VoiceSDK_js__WEBPACK_IMPORTED_MODULE_0__["default"],
19969
+ VoiceSDK: VoiceSDK,
18989
19970
  TextChatSDK: _core_TextChatSDK_js__WEBPACK_IMPORTED_MODULE_1__["default"],
18990
19971
  WebSocketManager: _core_WebSocketManager_js__WEBPACK_IMPORTED_MODULE_2__["default"],
18991
19972
  AudioRecorder: _core_AudioRecorder_js__WEBPACK_IMPORTED_MODULE_3__["default"],
@@ -23441,8 +24422,10 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23441
24422
  console.log('🛑 AudioPlayer: Ignoring PCM chunk - playback was stopped (barge-in)');
23442
24423
  return _context.a(2);
23443
24424
  case 1:
23444
- // Pre-process frame immediately (convert to AudioBuffer)
23445
- preparedFrame = this.prepareChunk(pcmData);
24425
+ _context.n = 2;
24426
+ return this.prepareChunk(pcmData);
24427
+ case 2:
24428
+ preparedFrame = _context.v;
23446
24429
  if (preparedFrame) {
23447
24430
  // CRITICAL: Clear empty sentence timeout since chunks are arriving
23448
24431
  // This resets the timer for the current sentence
@@ -23499,7 +24482,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23499
24482
  // Emit error event
23500
24483
  this.emit('playbackError', new Error('Failed to prepare PCM chunk for playback'));
23501
24484
  }
23502
- case 2:
24485
+ case 3:
23503
24486
  return _context.a(2);
23504
24487
  }
23505
24488
  }, _callee, this);
@@ -23518,87 +24501,99 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23518
24501
  )
23519
24502
  }, {
23520
24503
  key: "prepareChunk",
23521
- value: function prepareChunk(pcmData) {
23522
- try {
23523
- var _this$outputFormat2;
23524
- // Ensure output format is set
23525
- if (!this.outputFormat) {
23526
- console.error('❌ AudioPlayer: Cannot prepare chunk - outputFormat not set');
23527
- console.error(' Call setOutputFormat() before playing audio chunks');
23528
- return null;
23529
- }
23530
-
23531
- // Ensure audio context is initialized
23532
-
23533
- if (!this.audioContext) {
23534
- this.initializeAudioContext();
23535
- }
23536
- if (!this.audioContext) {
23537
- console.error('❌ AudioPlayer: Cannot prepare chunk - AudioContext not available');
23538
- return null;
23539
- }
23540
-
23541
- // Ensure even byte count for 16-bit PCM
23542
-
23543
- var processedData = pcmData;
23544
- if (pcmData.byteLength % 2 !== 0) {
23545
- console.warn('⚠️ Odd PCM chunk size, padding:', pcmData.byteLength);
23546
- var padded = new Uint8Array(pcmData.byteLength + 1);
23547
- padded.set(new Uint8Array(pcmData), 0);
23548
- padded[pcmData.byteLength] = 0; // Silence padding
23549
-
23550
- processedData = padded.buffer;
23551
- }
23552
-
23553
- // Convert Int16 PCM to Float32 (pre-processing)
23554
-
23555
- var int16Array = new Int16Array(processedData);
23556
- var float32Array = new Float32Array(int16Array.length);
23557
- var NORMALIZATION = 1.0 / 32768.0;
23558
- var length = int16Array.length;
23559
- for (var i = 0; i < length; i++) {
23560
- float32Array[i] = int16Array[i] * NORMALIZATION;
23561
- }
23562
-
23563
- // Create audio buffer with the ACTUAL sample rate of the audio data
24504
+ value: (function () {
24505
+ var _prepareChunk = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(pcmData) {
24506
+ var _this$outputFormat2, processedData, padded, int16Array, float32Array, NORMALIZATION, length, i, audioDataSampleRate, contextSampleRate, audioBuffer, chunkDuration, sampleCount, actualDuration, _t;
24507
+ return _regenerator().w(function (_context2) {
24508
+ while (1) switch (_context2.p = _context2.n) {
24509
+ case 0:
24510
+ _context2.p = 0;
24511
+ if (this.outputFormat) {
24512
+ _context2.n = 1;
24513
+ break;
24514
+ }
24515
+ console.error('❌ AudioPlayer: Cannot prepare chunk - outputFormat not set');
24516
+ console.error(' Call setOutputFormat() before playing audio chunks');
24517
+ return _context2.a(2, null);
24518
+ case 1:
24519
+ if (this.audioContext) {
24520
+ _context2.n = 2;
24521
+ break;
24522
+ }
24523
+ _context2.n = 2;
24524
+ return this.initializeAudioContext();
24525
+ case 2:
24526
+ if (this.audioContext) {
24527
+ _context2.n = 3;
24528
+ break;
24529
+ }
24530
+ console.error('❌ AudioPlayer: Cannot prepare chunk - AudioContext not available');
24531
+ return _context2.a(2, null);
24532
+ case 3:
24533
+ // Ensure even byte count for 16-bit PCM
24534
+ processedData = pcmData;
24535
+ if (pcmData.byteLength % 2 !== 0) {
24536
+ console.warn('⚠️ Odd PCM chunk size, padding:', pcmData.byteLength);
24537
+ padded = new Uint8Array(pcmData.byteLength + 1);
24538
+ padded.set(new Uint8Array(pcmData), 0);
24539
+ padded[pcmData.byteLength] = 0; // Silence padding
23564
24540
 
23565
- var audioDataSampleRate = ((_this$outputFormat2 = this.outputFormat) === null || _this$outputFormat2 === void 0 ? void 0 : _this$outputFormat2.sampleRate) || this.audioContext.sampleRate;
23566
- var contextSampleRate = this.audioContext.sampleRate;
23567
- var audioBuffer = this.audioContext.createBuffer(1,
23568
- // mono
24541
+ processedData = padded.buffer;
24542
+ }
23569
24543
 
23570
- float32Array.length, audioDataSampleRate);
23571
- audioBuffer.getChannelData(0).set(float32Array);
24544
+ // Convert Int16 PCM to Float32 (pre-processing)
24545
+ int16Array = new Int16Array(processedData);
24546
+ float32Array = new Float32Array(int16Array.length);
24547
+ NORMALIZATION = 1.0 / 32768.0;
24548
+ length = int16Array.length;
24549
+ for (i = 0; i < length; i++) {
24550
+ float32Array[i] = int16Array[i] * NORMALIZATION;
24551
+ }
23572
24552
 
23573
- // Calculate duration (handle browser resampling)
24553
+ // Create audio buffer with the ACTUAL sample rate of the audio data
24554
+ audioDataSampleRate = ((_this$outputFormat2 = this.outputFormat) === null || _this$outputFormat2 === void 0 ? void 0 : _this$outputFormat2.sampleRate) || this.audioContext.sampleRate;
24555
+ contextSampleRate = this.audioContext.sampleRate;
24556
+ audioBuffer = this.audioContext.createBuffer(1,
24557
+ // mono
23574
24558
 
23575
- var chunkDuration = audioBuffer.duration;
23576
- var sampleCount = float32Array.length;
23577
- var actualDuration = chunkDuration;
23578
- if (contextSampleRate !== audioDataSampleRate) {
23579
- actualDuration = sampleCount / contextSampleRate;
23580
- }
24559
+ float32Array.length, audioDataSampleRate);
24560
+ audioBuffer.getChannelData(0).set(float32Array);
23581
24561
 
23582
- // Return prepared frame
24562
+ // Calculate duration (handle browser resampling)
24563
+ chunkDuration = audioBuffer.duration;
24564
+ sampleCount = float32Array.length;
24565
+ actualDuration = chunkDuration;
24566
+ if (contextSampleRate !== audioDataSampleRate) {
24567
+ actualDuration = sampleCount / contextSampleRate;
24568
+ }
23583
24569
 
23584
- return {
23585
- buffer: audioBuffer,
23586
- duration: actualDuration,
23587
- sampleRate: audioDataSampleRate,
23588
- contextSampleRate: contextSampleRate
23589
- };
23590
- } catch (error) {
23591
- console.error('❌ AudioPlayer: Error preparing chunk:', error);
23592
- return null;
24570
+ // Return prepared frame
24571
+ return _context2.a(2, {
24572
+ buffer: audioBuffer,
24573
+ duration: actualDuration,
24574
+ sampleRate: audioDataSampleRate,
24575
+ contextSampleRate: contextSampleRate
24576
+ });
24577
+ case 4:
24578
+ _context2.p = 4;
24579
+ _t = _context2.v;
24580
+ console.error('❌ AudioPlayer: Error preparing chunk:', _t);
24581
+ return _context2.a(2, null);
24582
+ }
24583
+ }, _callee2, this, [[0, 4]]);
24584
+ }));
24585
+ function prepareChunk(_x2) {
24586
+ return _prepareChunk.apply(this, arguments);
23593
24587
  }
23594
- }
23595
-
24588
+ return prepareChunk;
24589
+ }()
23596
24590
  /**
23597
24591
  * Apply fade-out to audio buffer (stub method to prevent errors)
23598
24592
  * This method is called by some code paths but doesn't need to do anything
23599
24593
  * @param {AudioBuffer} audioBuffer - Audio buffer to fade
23600
24594
  * @returns {AudioBuffer} - Same audio buffer (no modification)
23601
24595
  */
24596
+ )
23602
24597
  }, {
23603
24598
  key: "applyFadeOut",
23604
24599
  value: function applyFadeOut(audioBuffer) {
@@ -23614,24 +24609,24 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23614
24609
  }, {
23615
24610
  key: "schedulePreparedFrames",
23616
24611
  value: (function () {
23617
- var _schedulePreparedFrames = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
24612
+ var _schedulePreparedFrames = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
23618
24613
  var _this4 = this;
23619
- var queuedFrames, targetLookaheadFrames, scheduledCount, _loop, _ret, _t;
23620
- return _regenerator().w(function (_context3) {
23621
- while (1) switch (_context3.p = _context3.n) {
24614
+ var queuedFrames, targetLookaheadFrames, scheduledCount, _loop, _ret, _t2;
24615
+ return _regenerator().w(function (_context4) {
24616
+ while (1) switch (_context4.p = _context4.n) {
23622
24617
  case 0:
23623
24618
  if (!this._isStopped) {
23624
- _context3.n = 1;
24619
+ _context4.n = 1;
23625
24620
  break;
23626
24621
  }
23627
24622
  console.log('🛑 AudioPlayer: Not scheduling frames - playback was stopped (barge-in)');
23628
- return _context3.a(2);
24623
+ return _context4.a(2);
23629
24624
  case 1:
23630
24625
  if (!this.isSchedulingFrames) {
23631
- _context3.n = 2;
24626
+ _context4.n = 2;
23632
24627
  break;
23633
24628
  }
23634
- return _context3.a(2);
24629
+ return _context4.a(2);
23635
24630
  case 2:
23636
24631
  this.isSchedulingFrames = true;
23637
24632
 
@@ -23644,17 +24639,8 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23644
24639
  if (targetLookaheadFrames === 0 && queuedFrames > 0) {
23645
24640
  targetLookaheadFrames = 1; // At least schedule 1 frame
23646
24641
  }
23647
- _context3.p = 3;
23648
- // Initialize audio context if needed
23649
-
23650
- if (!this.audioContext) {
23651
- this.initializeAudioContext();
23652
- }
23653
-
23654
- // ✅ NEW: Wait for AudioContext to be in 'running' state before proceeding
23655
- // This is critical for mobile devices where AudioContext initialization takes time
23656
- // Replaces fixed timeouts with event-driven waiting
23657
- _context3.n = 4;
24642
+ _context4.p = 3;
24643
+ _context4.n = 4;
23658
24644
  return this.waitForAudioContextReady();
23659
24645
  case 4:
23660
24646
  // Schedule frames up to target lookahead (ensures smooth playback)
@@ -23662,23 +24648,23 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23662
24648
  scheduledCount = 0;
23663
24649
  _loop = /*#__PURE__*/_regenerator().m(function _loop() {
23664
24650
  var preparedFrame, source, currentTime, isMobile, startupBuffer, minStartTime, startTime;
23665
- return _regenerator().w(function (_context2) {
23666
- while (1) switch (_context2.n) {
24651
+ return _regenerator().w(function (_context3) {
24652
+ while (1) switch (_context3.n) {
23667
24653
  case 0:
23668
24654
  if (!_this4._isStopped) {
23669
- _context2.n = 1;
24655
+ _context3.n = 1;
23670
24656
  break;
23671
24657
  }
23672
24658
  console.log('🛑 AudioPlayer: Stopping frame scheduling - playback was stopped');
23673
- return _context2.a(2, 0);
24659
+ return _context3.a(2, 0);
23674
24660
  case 1:
23675
24661
  // Get next prepared frame
23676
24662
  preparedFrame = _this4.preparedBuffer.shift();
23677
24663
  if (preparedFrame) {
23678
- _context2.n = 2;
24664
+ _context3.n = 2;
23679
24665
  break;
23680
24666
  }
23681
- return _context2.a(2, 0);
24667
+ return _context3.a(2, 0);
23682
24668
  case 2:
23683
24669
  // Create source and schedule playback
23684
24670
  source = _this4.audioContext.createBufferSource();
@@ -23832,25 +24818,25 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23832
24818
  _this4.emit('playbackStarted');
23833
24819
  }
23834
24820
  case 3:
23835
- return _context2.a(2);
24821
+ return _context3.a(2);
23836
24822
  }
23837
24823
  }, _loop);
23838
24824
  });
23839
24825
  case 5:
23840
24826
  if (!(this.preparedBuffer.length > 0 && scheduledCount < targetLookaheadFrames)) {
23841
- _context3.n = 8;
24827
+ _context4.n = 8;
23842
24828
  break;
23843
24829
  }
23844
- return _context3.d(_regeneratorValues(_loop()), 6);
24830
+ return _context4.d(_regeneratorValues(_loop()), 6);
23845
24831
  case 6:
23846
- _ret = _context3.v;
24832
+ _ret = _context4.v;
23847
24833
  if (!(_ret === 0)) {
23848
- _context3.n = 7;
24834
+ _context4.n = 7;
23849
24835
  break;
23850
24836
  }
23851
- return _context3.a(3, 8);
24837
+ return _context4.a(3, 8);
23852
24838
  case 7:
23853
- _context3.n = 5;
24839
+ _context4.n = 5;
23854
24840
  break;
23855
24841
  case 8:
23856
24842
  // All prepared frames scheduled, reset flag
@@ -23892,18 +24878,18 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23892
24878
  }
23893
24879
  }, 10); // Check every 10ms for new frames
23894
24880
  }
23895
- _context3.n = 10;
24881
+ _context4.n = 10;
23896
24882
  break;
23897
24883
  case 9:
23898
- _context3.p = 9;
23899
- _t = _context3.v;
23900
- console.error('❌ AudioPlayer v2: Error scheduling frames:', _t);
23901
- this.emit('playbackError', _t);
24884
+ _context4.p = 9;
24885
+ _t2 = _context4.v;
24886
+ console.error('❌ AudioPlayer v2: Error scheduling frames:', _t2);
24887
+ this.emit('playbackError', _t2);
23902
24888
  this.isSchedulingFrames = false;
23903
24889
  case 10:
23904
- return _context3.a(2);
24890
+ return _context4.a(2);
23905
24891
  }
23906
- }, _callee2, this, [[3, 9]]);
24892
+ }, _callee3, this, [[3, 9]]);
23907
24893
  }));
23908
24894
  function schedulePreparedFrames() {
23909
24895
  return _schedulePreparedFrames.apply(this, arguments);
@@ -23918,56 +24904,47 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23918
24904
  }, {
23919
24905
  key: "processPcmQueue",
23920
24906
  value: (function () {
23921
- var _processPcmQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
24907
+ var _processPcmQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
23922
24908
  var _this5 = this;
23923
- var _this$outputFormat3, _this$outputFormat4, pcmData, processedData, padded, int16Array, float32Array, NORMALIZATION, length, i, audioDataSampleRate, contextSampleRate, audioBuffer, source, currentTime, chunkDuration, sampleCount, actualDuration, startTime, _t2;
23924
- return _regenerator().w(function (_context4) {
23925
- while (1) switch (_context4.p = _context4.n) {
24909
+ var _this$outputFormat3, _this$outputFormat4, pcmData, processedData, padded, int16Array, float32Array, NORMALIZATION, length, i, audioDataSampleRate, contextSampleRate, audioBuffer, source, currentTime, chunkDuration, sampleCount, actualDuration, startTime, _t3;
24910
+ return _regenerator().w(function (_context5) {
24911
+ while (1) switch (_context5.p = _context5.n) {
23926
24912
  case 0:
23927
24913
  if (!this._isStopped) {
23928
- _context4.n = 1;
24914
+ _context5.n = 1;
23929
24915
  break;
23930
24916
  }
23931
24917
  console.log('🛑 AudioPlayer: Not processing PCM queue - playback was stopped (barge-in)');
23932
- return _context4.a(2);
24918
+ return _context5.a(2);
23933
24919
  case 1:
23934
24920
  if (!this.isProcessingPcmQueue) {
23935
- _context4.n = 2;
24921
+ _context5.n = 2;
23936
24922
  break;
23937
24923
  }
23938
- return _context4.a(2);
24924
+ return _context5.a(2);
23939
24925
  case 2:
23940
24926
  this.isProcessingPcmQueue = true;
23941
- _context4.p = 3;
23942
- // Initialize audio context if needed
23943
-
23944
- if (!this.audioContext) {
23945
- this.initializeAudioContext();
23946
- }
23947
-
23948
- // ✅ NEW: Wait for AudioContext to be in 'running' state before proceeding
23949
- // This is critical for mobile devices where AudioContext initialization takes time
23950
- // Replaces fixed timeouts with event-driven waiting
23951
- _context4.n = 4;
24927
+ _context5.p = 3;
24928
+ _context5.n = 4;
23952
24929
  return this.waitForAudioContextReady();
23953
24930
  case 4:
23954
24931
  if (!(this.pcmChunkQueue.length > 0)) {
23955
- _context4.n = 7;
24932
+ _context5.n = 7;
23956
24933
  break;
23957
24934
  }
23958
24935
  if (!this._isStopped) {
23959
- _context4.n = 5;
24936
+ _context5.n = 5;
23960
24937
  break;
23961
24938
  }
23962
24939
  console.log('🛑 AudioPlayer: Stopping PCM queue processing - playback was stopped');
23963
- return _context4.a(3, 7);
24940
+ return _context5.a(3, 7);
23964
24941
  case 5:
23965
24942
  pcmData = this.pcmChunkQueue.shift();
23966
24943
  if (pcmData) {
23967
- _context4.n = 6;
24944
+ _context5.n = 6;
23968
24945
  break;
23969
24946
  }
23970
- return _context4.a(3, 4);
24947
+ return _context5.a(3, 4);
23971
24948
  case 6:
23972
24949
  // Ensure even byte count for 16-bit PCM
23973
24950
  processedData = pcmData;
@@ -24060,25 +25037,25 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24060
25037
  this.isPlaying = true;
24061
25038
  this.emit('playbackStarted');
24062
25039
  }
24063
- _context4.n = 4;
25040
+ _context5.n = 4;
24064
25041
  break;
24065
25042
  case 7:
24066
25043
  // end while loop
24067
25044
 
24068
25045
  // All chunks scheduled, reset processing flag
24069
25046
  this.isProcessingPcmQueue = false;
24070
- _context4.n = 9;
25047
+ _context5.n = 9;
24071
25048
  break;
24072
25049
  case 8:
24073
- _context4.p = 8;
24074
- _t2 = _context4.v;
24075
- console.error('❌ AudioPlayer v2: Error playing chunk:', _t2);
24076
- this.emit('playbackError', _t2);
25050
+ _context5.p = 8;
25051
+ _t3 = _context5.v;
25052
+ console.error('❌ AudioPlayer v2: Error playing chunk:', _t3);
25053
+ this.emit('playbackError', _t3);
24077
25054
  this.isProcessingPcmQueue = false;
24078
25055
  case 9:
24079
- return _context4.a(2);
25056
+ return _context5.a(2);
24080
25057
  }
24081
- }, _callee3, this, [[3, 8]]);
25058
+ }, _callee4, this, [[3, 8]]);
24082
25059
  }));
24083
25060
  function processPcmQueue() {
24084
25061
  return _processPcmQueue.apply(this, arguments);
@@ -24463,6 +25440,40 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24463
25440
  return header;
24464
25441
  }
24465
25442
 
25443
+ /**
25444
+ * Drop stale instance/shared references to a closed AudioContext so
25445
+ * initializeAudioContext() cannot no-op while the context stays closed.
25446
+ * @private
25447
+ */
25448
+ }, {
25449
+ key: "_invalidateClosedAudioContext",
25450
+ value: function _invalidateClosedAudioContext() {
25451
+ if (this._audioContextStateChangeHandler && this.audioContext) {
25452
+ try {
25453
+ this.audioContext.removeEventListener('statechange', this._audioContextStateChangeHandler);
25454
+ } catch (_) {/* already detached */}
25455
+ this._audioContextStateChangeHandler = null;
25456
+ }
25457
+ if (this._waitForReadyStateHandler && this.audioContext) {
25458
+ try {
25459
+ this.audioContext.removeEventListener('statechange', this._waitForReadyStateHandler);
25460
+ } catch (_) {/* already detached */}
25461
+ this._waitForReadyStateHandler = null;
25462
+ }
25463
+ if (this.gainNode) {
25464
+ try {
25465
+ this.gainNode.disconnect();
25466
+ } catch (_) {/* ignore */}
25467
+ this.gainNode = null;
25468
+ }
25469
+ if (this.audioContext === _sharedPlayerContext) {
25470
+ _sharedPlayerContext = null;
25471
+ _sharedPlayerSampleRate = null;
25472
+ }
25473
+ this.audioContext = null;
25474
+ this._audioContextPrimed = false;
25475
+ }
25476
+
24466
25477
  /**
24467
25478
  * Wait for AudioContext to be in 'running' state before proceeding
24468
25479
  * This is critical for mobile devices where AudioContext initialization takes time
@@ -24472,41 +25483,85 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24472
25483
  }, {
24473
25484
  key: "waitForAudioContextReady",
24474
25485
  value: (function () {
24475
- var _waitForAudioContextReady = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
25486
+ var _waitForAudioContextReady = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
25487
+ return _regenerator().w(function (_context6) {
25488
+ while (1) switch (_context6.n) {
25489
+ case 0:
25490
+ return _context6.a(2, this._waitForAudioContextReadyImpl(0));
25491
+ }
25492
+ }, _callee5, this);
25493
+ }));
25494
+ function waitForAudioContextReady() {
25495
+ return _waitForAudioContextReady.apply(this, arguments);
25496
+ }
25497
+ return waitForAudioContextReady;
25498
+ }())
25499
+ }, {
25500
+ key: "_waitForAudioContextReadyImpl",
25501
+ value: function () {
25502
+ var _waitForAudioContextReadyImpl2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(attempt) {
24476
25503
  var _this6 = this;
24477
- var audioContext;
24478
- return _regenerator().w(function (_context5) {
24479
- while (1) switch (_context5.n) {
25504
+ var MAX_RECREATE_ATTEMPTS, _this$audioContext2, audioContext;
25505
+ return _regenerator().w(function (_context7) {
25506
+ while (1) switch (_context7.n) {
24480
25507
  case 0:
24481
- if (!this.audioContext) {
24482
- this.initializeAudioContext();
25508
+ MAX_RECREATE_ATTEMPTS = 3;
25509
+ if (!(!this.audioContext || this.audioContext.state === 'closed')) {
25510
+ _context7.n = 1;
25511
+ break;
25512
+ }
25513
+ if (((_this$audioContext2 = this.audioContext) === null || _this$audioContext2 === void 0 ? void 0 : _this$audioContext2.state) === 'closed') {
25514
+ this._invalidateClosedAudioContext();
24483
25515
  }
25516
+ _context7.n = 1;
25517
+ return this.initializeAudioContext();
25518
+ case 1:
24484
25519
  if (this.audioContext) {
24485
- _context5.n = 1;
25520
+ _context7.n = 2;
24486
25521
  break;
24487
25522
  }
24488
25523
  console.error('❌ AudioPlayer: Failed to create AudioContext');
24489
- return _context5.a(2, Promise.resolve());
24490
- case 1:
25524
+ return _context7.a(2);
25525
+ case 2:
24491
25526
  audioContext = this.audioContext;
24492
25527
  if (!(audioContext.state === 'running')) {
24493
- _context5.n = 2;
25528
+ _context7.n = 3;
24494
25529
  break;
24495
25530
  }
24496
- return _context5.a(2, Promise.resolve());
24497
- case 2:
25531
+ return _context7.a(2);
25532
+ case 3:
25533
+ if (!(audioContext.state === 'closed')) {
25534
+ _context7.n = 6;
25535
+ break;
25536
+ }
25537
+ if (!(attempt >= MAX_RECREATE_ATTEMPTS)) {
25538
+ _context7.n = 4;
25539
+ break;
25540
+ }
25541
+ console.error('❌ AudioPlayer: AudioContext still closed after recreation attempts — aborting playback setup');
25542
+ return _context7.a(2);
25543
+ case 4:
25544
+ if (attempt === 0) {
25545
+ console.warn('⚠️ AudioPlayer: AudioContext was closed, recreating...');
25546
+ }
25547
+ this._invalidateClosedAudioContext();
25548
+ _context7.n = 5;
25549
+ return this.initializeAudioContext();
25550
+ case 5:
25551
+ return _context7.a(2, this._waitForAudioContextReadyImpl(attempt + 1));
25552
+ case 6:
24498
25553
  if (!(audioContext.state === 'suspended')) {
24499
- _context5.n = 4;
25554
+ _context7.n = 8;
24500
25555
  break;
24501
25556
  }
24502
- _context5.n = 3;
25557
+ _context7.n = 7;
24503
25558
  return audioContext.resume();
24504
- case 3:
25559
+ case 7:
24505
25560
  if (!(audioContext.state !== 'running')) {
24506
- _context5.n = 4;
25561
+ _context7.n = 8;
24507
25562
  break;
24508
25563
  }
24509
- return _context5.a(2, new Promise(function (resolve) {
25564
+ return _context7.a(2, new Promise(function (resolve) {
24510
25565
  var timeout = setTimeout(function () {
24511
25566
  if (audioContext && _this6._waitForReadyStateHandler) {
24512
25567
  audioContext.removeEventListener('statechange', _this6._waitForReadyStateHandler);
@@ -24543,161 +25598,190 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24543
25598
  resolve();
24544
25599
  }
24545
25600
  }));
24546
- case 4:
24547
- if (!(audioContext.state === 'closed')) {
24548
- _context5.n = 5;
25601
+ case 8:
25602
+ return _context7.a(2);
25603
+ }
25604
+ }, _callee6, this);
25605
+ }));
25606
+ function _waitForAudioContextReadyImpl(_x3) {
25607
+ return _waitForAudioContextReadyImpl2.apply(this, arguments);
25608
+ }
25609
+ return _waitForAudioContextReadyImpl;
25610
+ }()
25611
+ /**
25612
+ * Initialize audio context with correct sample rate
25613
+ */
25614
+ }, {
25615
+ key: "initializeAudioContext",
25616
+ value: (function () {
25617
+ var _initializeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
25618
+ var _this$outputFormat5,
25619
+ _this7 = this,
25620
+ _this$outputFormat6;
25621
+ var desiredSampleRate, currentSampleRate, canReuseShared, setupAfterResume, _t4;
25622
+ return _regenerator().w(function (_context8) {
25623
+ while (1) switch (_context8.p = _context8.n) {
25624
+ case 0:
25625
+ // Use negotiated sample rate, default to 24kHz to match typical server/TTS output
25626
+ desiredSampleRate = ((_this$outputFormat5 = this.outputFormat) === null || _this$outputFormat5 === void 0 ? void 0 : _this$outputFormat5.sampleRate) || 24000;
25627
+ if (![24000, 44100, 48000].includes(desiredSampleRate)) {
25628
+ console.log("\u2139\uFE0F AudioPlayer: Backend requested ".concat(desiredSampleRate, "Hz, but browser may resample"));
25629
+ console.log(" Consider requesting 24000Hz, 44100Hz, or 48000Hz from backend to reduce resampling");
25630
+ }
25631
+ if (_sharedPlayerContext && _sharedPlayerContext.state === 'closed') {
25632
+ _sharedPlayerContext = null;
25633
+ _sharedPlayerSampleRate = null;
25634
+ }
25635
+
25636
+ // Check if current instance AudioContext exists and matches
25637
+ if (!this.audioContext) {
25638
+ _context8.n = 3;
25639
+ break;
25640
+ }
25641
+ if (!(this.audioContext.state === 'closed')) {
25642
+ _context8.n = 1;
25643
+ break;
25644
+ }
25645
+ this._invalidateClosedAudioContext();
25646
+ _context8.n = 3;
25647
+ break;
25648
+ case 1:
25649
+ currentSampleRate = this.audioContext.sampleRate;
25650
+ if (!(Math.abs(currentSampleRate - desiredSampleRate) > 100)) {
25651
+ _context8.n = 2;
25652
+ break;
25653
+ }
25654
+ console.warn("\u26A0\uFE0F AudioPlayer: AudioContext sample rate (".concat(currentSampleRate, "Hz) doesn't match format (").concat(desiredSampleRate, "Hz), recreating..."));
25655
+ this.stopImmediate();
25656
+ this._cleanupAudioContext();
25657
+ _context8.n = 3;
25658
+ break;
25659
+ case 2:
25660
+ return _context8.a(2);
25661
+ case 3:
25662
+ // iOS FIX: Reuse shared AudioContext if available and compatible.
25663
+ // iOS WebKit doesn't release audio hardware synchronously on AudioContext.close(),
25664
+ // causing newly created AudioContexts to fail silently.
25665
+ canReuseShared = _sharedPlayerContext && _sharedPlayerContext.state !== 'closed' && _sharedPlayerSampleRate === desiredSampleRate;
25666
+ if (!canReuseShared) {
25667
+ _context8.n = 8;
24549
25668
  break;
24550
25669
  }
24551
- console.warn('⚠️ AudioPlayer: AudioContext was closed, recreating...');
24552
- this.initializeAudioContext();
24553
- return _context5.a(2, this.waitForAudioContextReady());
24554
- case 5:
24555
- return _context5.a(2, Promise.resolve());
25670
+ console.log("\u267B\uFE0F AudioPlayer: Reusing shared AudioContext at ".concat(desiredSampleRate, "Hz (iOS-safe)"));
25671
+ this.audioContext = _sharedPlayerContext;
25672
+ setupAfterResume = function setupAfterResume() {
25673
+ _this7.setupAudioContextStateMonitoring();
25674
+ if (_this7.gainNode) {
25675
+ try {
25676
+ _this7.gainNode.disconnect();
25677
+ } catch (e) {
25678
+ console.warn('⚠️ AudioPlayer: Error disconnecting old GainNode:', e);
25679
+ }
25680
+ }
25681
+ _this7.gainNode = _this7.audioContext.createGain();
25682
+ _this7.gainNode.gain.value = 1.0;
25683
+ _this7.gainNode.connect(_this7.audioContext.destination);
25684
+ if (!_this7._audioContextPrimed) {
25685
+ _this7._primeAudioContext();
25686
+ }
25687
+ };
25688
+ if (!(this.audioContext.state === 'suspended')) {
25689
+ _context8.n = 7;
25690
+ break;
25691
+ }
25692
+ _context8.p = 4;
25693
+ _context8.n = 5;
25694
+ return this.audioContext.resume();
25695
+ case 5:
25696
+ _context8.n = 7;
25697
+ break;
25698
+ case 6:
25699
+ _context8.p = 6;
25700
+ _t4 = _context8.v;
25701
+ console.warn('⚠️ AudioPlayer: Error resuming shared context:', _t4);
25702
+ case 7:
25703
+ setupAfterResume();
25704
+ return _context8.a(2);
25705
+ case 8:
25706
+ console.log("\uD83C\uDFB5 AudioPlayer: Creating AudioContext at ".concat(desiredSampleRate, "Hz (from outputFormat: ").concat(((_this$outputFormat6 = this.outputFormat) === null || _this$outputFormat6 === void 0 ? void 0 : _this$outputFormat6.sampleRate) || 'not set', ")"));
25707
+
25708
+ // Close old shared context if sample rate changed
25709
+ if (_sharedPlayerContext && _sharedPlayerContext.state !== 'closed') {
25710
+ console.log('🔄 AudioPlayer: Closing old shared AudioContext (sample rate changed)');
25711
+ try {
25712
+ _sharedPlayerContext.close();
25713
+ } catch (e) {
25714
+ console.warn('⚠️ AudioPlayer: Error closing old shared context:', e);
25715
+ }
25716
+ }
25717
+ try {
25718
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)({
25719
+ sampleRate: desiredSampleRate,
25720
+ latencyHint: 'playback'
25721
+ });
25722
+
25723
+ // Store as shared context
25724
+ _sharedPlayerContext = this.audioContext;
25725
+ _sharedPlayerSampleRate = desiredSampleRate;
25726
+ console.log("\u2705 AudioContext created at ".concat(this.audioContext.sampleRate, "Hz (requested: ").concat(desiredSampleRate, "Hz)"));
25727
+ if (Math.abs(this.audioContext.sampleRate - desiredSampleRate) > 100) {
25728
+ console.error("\u274C CRITICAL: Browser sample rate mismatch!");
25729
+ console.error(" Requested: ".concat(desiredSampleRate, "Hz"));
25730
+ console.error(" Got: ".concat(this.audioContext.sampleRate, "Hz"));
25731
+ console.error(" This WILL cause audio distortion/noise!");
25732
+ console.error(" Solution: Backend should send ".concat(this.audioContext.sampleRate, "Hz audio instead"));
25733
+ } else if (this.audioContext.sampleRate !== desiredSampleRate) {
25734
+ console.warn("\u26A0\uFE0F Browser adjusted sample rate: ".concat(desiredSampleRate, "Hz \u2192 ").concat(this.audioContext.sampleRate, "Hz"));
25735
+ console.warn(" Browser will automatically resample audio.");
25736
+ }
25737
+ this.setupAudioContextStateMonitoring();
25738
+ this.gainNode = this.audioContext.createGain();
25739
+ this.gainNode.gain.value = 1.0;
25740
+ this.gainNode.connect(this.audioContext.destination);
25741
+ console.log('✅ AudioPlayer: GainNode created for volume control');
25742
+ this._primeAudioContext();
25743
+ } catch (error) {
25744
+ console.error("\u274C Failed to create AudioContext:", error);
25745
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
25746
+ _sharedPlayerContext = this.audioContext;
25747
+ _sharedPlayerSampleRate = this.audioContext.sampleRate;
25748
+ console.log("\u2139\uFE0F Using browser default: ".concat(this.audioContext.sampleRate, "Hz"));
25749
+ this.setupAudioContextStateMonitoring();
25750
+ this.gainNode = this.audioContext.createGain();
25751
+ this.gainNode.gain.value = 1.0;
25752
+ this.gainNode.connect(this.audioContext.destination);
25753
+ console.log('✅ AudioPlayer: GainNode created for volume control (fallback)');
25754
+ this._primeAudioContext();
25755
+ }
25756
+ case 9:
25757
+ return _context8.a(2);
24556
25758
  }
24557
- }, _callee4, this);
25759
+ }, _callee7, this, [[4, 6]]);
24558
25760
  }));
24559
- function waitForAudioContextReady() {
24560
- return _waitForAudioContextReady.apply(this, arguments);
25761
+ function initializeAudioContext() {
25762
+ return _initializeAudioContext.apply(this, arguments);
24561
25763
  }
24562
- return waitForAudioContextReady;
25764
+ return initializeAudioContext;
24563
25765
  }()
24564
- /**
24565
- /**
24566
- *
24567
- * Initialize audio context with correct sample rate
24568
- *
24569
- */
24570
- )
24571
- }, {
24572
- key: "initializeAudioContext",
24573
- value: function initializeAudioContext() {
24574
- var _this$outputFormat5,
24575
- _this7 = this,
24576
- _this$outputFormat6;
24577
- // Use negotiated sample rate, default to 24kHz to match typical server/TTS output
24578
- var desiredSampleRate = ((_this$outputFormat5 = this.outputFormat) === null || _this$outputFormat5 === void 0 ? void 0 : _this$outputFormat5.sampleRate) || 24000;
24579
- if (![24000, 44100, 48000].includes(desiredSampleRate)) {
24580
- console.log("\u2139\uFE0F AudioPlayer: Backend requested ".concat(desiredSampleRate, "Hz, but browser may resample"));
24581
- console.log(" Consider requesting 24000Hz, 44100Hz, or 48000Hz from backend to reduce resampling");
24582
- }
24583
-
24584
- // Check if current instance AudioContext exists and matches
24585
- if (this.audioContext) {
24586
- var currentSampleRate = this.audioContext.sampleRate;
24587
- if (Math.abs(currentSampleRate - desiredSampleRate) > 100) {
24588
- console.warn("\u26A0\uFE0F AudioPlayer: AudioContext sample rate (".concat(currentSampleRate, "Hz) doesn't match format (").concat(desiredSampleRate, "Hz), recreating..."));
24589
- this.stopImmediate();
24590
- this._cleanupAudioContext();
24591
- } else {
24592
- return;
24593
- }
24594
- }
24595
-
24596
- // iOS FIX: Reuse shared AudioContext if available and compatible.
24597
- // iOS WebKit doesn't release audio hardware synchronously on AudioContext.close(),
24598
- // causing newly created AudioContexts to fail silently.
24599
- var canReuseShared = _sharedPlayerContext && _sharedPlayerContext.state !== 'closed' && _sharedPlayerSampleRate === desiredSampleRate;
24600
- if (canReuseShared) {
24601
- console.log("\u267B\uFE0F AudioPlayer: Reusing shared AudioContext at ".concat(desiredSampleRate, "Hz (iOS-safe)"));
24602
- this.audioContext = _sharedPlayerContext;
24603
- var setupAfterResume = function setupAfterResume() {
24604
- _this7.setupAudioContextStateMonitoring();
24605
- if (_this7.gainNode) {
24606
- try {
24607
- _this7.gainNode.disconnect();
24608
- } catch (e) {
24609
- console.warn('⚠️ AudioPlayer: Error disconnecting old GainNode:', e);
24610
- }
24611
- }
24612
- _this7.gainNode = _this7.audioContext.createGain();
24613
- _this7.gainNode.gain.value = 1.0;
24614
- _this7.gainNode.connect(_this7.audioContext.destination);
24615
- if (!_this7._audioContextPrimed) {
24616
- _this7._primeAudioContext();
24617
- }
24618
- };
24619
- if (this.audioContext.state === 'suspended') {
24620
- this.audioContext.resume().then(function () {
24621
- return setupAfterResume();
24622
- }).catch(function (e) {
24623
- console.warn('⚠️ AudioPlayer: Error resuming shared context:', e);
24624
- setupAfterResume();
24625
- });
24626
- } else {
24627
- setupAfterResume();
24628
- }
24629
- return;
24630
- }
24631
- console.log("\uD83C\uDFB5 AudioPlayer: Creating AudioContext at ".concat(desiredSampleRate, "Hz (from outputFormat: ").concat(((_this$outputFormat6 = this.outputFormat) === null || _this$outputFormat6 === void 0 ? void 0 : _this$outputFormat6.sampleRate) || 'not set', ")"));
24632
-
24633
- // Close old shared context if sample rate changed
24634
- if (_sharedPlayerContext && _sharedPlayerContext.state !== 'closed') {
24635
- console.log('🔄 AudioPlayer: Closing old shared AudioContext (sample rate changed)');
24636
- try {
24637
- _sharedPlayerContext.close();
24638
- } catch (e) {
24639
- console.warn('⚠️ AudioPlayer: Error closing old shared context:', e);
24640
- }
24641
- }
24642
- try {
24643
- this.audioContext = new (window.AudioContext || window.webkitAudioContext)({
24644
- sampleRate: desiredSampleRate,
24645
- latencyHint: 'playback'
24646
- });
24647
-
24648
- // Store as shared context
24649
- _sharedPlayerContext = this.audioContext;
24650
- _sharedPlayerSampleRate = desiredSampleRate;
24651
- console.log("\u2705 AudioContext created at ".concat(this.audioContext.sampleRate, "Hz (requested: ").concat(desiredSampleRate, "Hz)"));
24652
- if (Math.abs(this.audioContext.sampleRate - desiredSampleRate) > 100) {
24653
- console.error("\u274C CRITICAL: Browser sample rate mismatch!");
24654
- console.error(" Requested: ".concat(desiredSampleRate, "Hz"));
24655
- console.error(" Got: ".concat(this.audioContext.sampleRate, "Hz"));
24656
- console.error(" This WILL cause audio distortion/noise!");
24657
- console.error(" Solution: Backend should send ".concat(this.audioContext.sampleRate, "Hz audio instead"));
24658
- } else if (this.audioContext.sampleRate !== desiredSampleRate) {
24659
- console.warn("\u26A0\uFE0F Browser adjusted sample rate: ".concat(desiredSampleRate, "Hz \u2192 ").concat(this.audioContext.sampleRate, "Hz"));
24660
- console.warn(" Browser will automatically resample audio.");
24661
- }
24662
- this.setupAudioContextStateMonitoring();
24663
- this.gainNode = this.audioContext.createGain();
24664
- this.gainNode.gain.value = 1.0;
24665
- this.gainNode.connect(this.audioContext.destination);
24666
- console.log('✅ AudioPlayer: GainNode created for volume control');
24667
- this._primeAudioContext();
24668
- } catch (error) {
24669
- console.error("\u274C Failed to create AudioContext:", error);
24670
- this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
24671
- _sharedPlayerContext = this.audioContext;
24672
- _sharedPlayerSampleRate = this.audioContext.sampleRate;
24673
- console.log("\u2139\uFE0F Using browser default: ".concat(this.audioContext.sampleRate, "Hz"));
24674
- this.setupAudioContextStateMonitoring();
24675
- this.gainNode = this.audioContext.createGain();
24676
- this.gainNode.gain.value = 1.0;
24677
- this.gainNode.connect(this.audioContext.destination);
24678
- console.log('✅ AudioPlayer: GainNode created for volume control (fallback)');
24679
- this._primeAudioContext();
24680
- }
24681
- }
24682
-
24683
25766
  /**
24684
25767
  * Prime AudioContext with silent buffer to initialize Android hardware
24685
25768
  * This prevents the first audio frame from being cut off on Android devices
24686
25769
  */
25770
+ )
24687
25771
  }, {
24688
25772
  key: "_primeAudioContext",
24689
25773
  value: (function () {
24690
- var _primeAudioContext2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
24691
- var sampleRate, silentBuffer, source, _t3;
24692
- return _regenerator().w(function (_context6) {
24693
- while (1) switch (_context6.p = _context6.n) {
25774
+ var _primeAudioContext2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
25775
+ var sampleRate, silentBuffer, source, _t5;
25776
+ return _regenerator().w(function (_context9) {
25777
+ while (1) switch (_context9.p = _context9.n) {
24694
25778
  case 0:
24695
- _context6.p = 0;
25779
+ _context9.p = 0;
24696
25780
  if (!(!this.audioContext || this._audioContextPrimed)) {
24697
- _context6.n = 1;
25781
+ _context9.n = 1;
24698
25782
  break;
24699
25783
  }
24700
- return _context6.a(2);
25784
+ return _context9.a(2);
24701
25785
  case 1:
24702
25786
  sampleRate = this.audioContext.sampleRate;
24703
25787
  silentBuffer = this.audioContext.createBuffer(1, sampleRate * 0.15, sampleRate);
@@ -24705,23 +25789,23 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24705
25789
  source.buffer = silentBuffer;
24706
25790
  source.connect(this.audioContext.destination);
24707
25791
  source.start();
24708
- _context6.n = 2;
25792
+ _context9.n = 2;
24709
25793
  return new Promise(function (resolve) {
24710
25794
  return setTimeout(resolve, 150);
24711
25795
  });
24712
25796
  case 2:
24713
25797
  this._audioContextPrimed = true;
24714
25798
  console.log('✅ AudioPlayer: Audio hardware primed with silent buffer');
24715
- _context6.n = 4;
25799
+ _context9.n = 4;
24716
25800
  break;
24717
25801
  case 3:
24718
- _context6.p = 3;
24719
- _t3 = _context6.v;
24720
- console.error('❌ AudioPlayer: Audio priming failed:', _t3);
25802
+ _context9.p = 3;
25803
+ _t5 = _context9.v;
25804
+ console.error('❌ AudioPlayer: Audio priming failed:', _t5);
24721
25805
  case 4:
24722
- return _context6.a(2);
25806
+ return _context9.a(2);
24723
25807
  }
24724
- }, _callee5, this, [[0, 3]]);
25808
+ }, _callee8, this, [[0, 3]]);
24725
25809
  }));
24726
25810
  function _primeAudioContext() {
24727
25811
  return _primeAudioContext2.apply(this, arguments);
@@ -24846,55 +25930,48 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24846
25930
  }, {
24847
25931
  key: "processQueue",
24848
25932
  value: (function () {
24849
- var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
25933
+ var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9() {
24850
25934
  var _this9 = this;
24851
- var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t4;
24852
- return _regenerator().w(function (_context7) {
24853
- while (1) switch (_context7.p = _context7.n) {
25935
+ var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t6;
25936
+ return _regenerator().w(function (_context0) {
25937
+ while (1) switch (_context0.p = _context0.n) {
24854
25938
  case 0:
24855
25939
  if (!this._isStopped) {
24856
- _context7.n = 1;
25940
+ _context0.n = 1;
24857
25941
  break;
24858
25942
  }
24859
25943
  console.log('🛑 AudioPlayer: Not processing queue - playback was stopped (barge-in)');
24860
- return _context7.a(2);
25944
+ return _context0.a(2);
24861
25945
  case 1:
24862
25946
  if (!(this.isProcessingQueue || this.audioQueue.length === 0)) {
24863
- _context7.n = 2;
25947
+ _context0.n = 2;
24864
25948
  break;
24865
25949
  }
24866
- return _context7.a(2);
25950
+ return _context0.a(2);
24867
25951
  case 2:
24868
25952
  this.isProcessingQueue = true;
24869
25953
  audioBlob = this.audioQueue.shift();
24870
25954
  if (audioBlob) {
24871
- _context7.n = 3;
25955
+ _context0.n = 3;
24872
25956
  break;
24873
25957
  }
24874
25958
  this.isProcessingQueue = false;
24875
- return _context7.a(2);
25959
+ return _context0.a(2);
24876
25960
  case 3:
24877
- _context7.p = 3;
24878
- wasFirstPlay = !this.isPlaying && this.currentSource === null; // Initialize audio context if needed
24879
- if (!this.audioContext) {
24880
- this.initializeAudioContext();
24881
- }
24882
-
24883
- // ✅ NEW: Wait for AudioContext to be in 'running' state before proceeding
24884
- // This is critical for mobile devices where AudioContext initialization takes time
24885
- // Replaces fixed timeouts with event-driven waiting
24886
- _context7.n = 4;
25961
+ _context0.p = 3;
25962
+ wasFirstPlay = !this.isPlaying && this.currentSource === null; // Ensure context exists and is running (handles lazy init + suspended shared context)
25963
+ _context0.n = 4;
24887
25964
  return this.waitForAudioContextReady();
24888
25965
  case 4:
24889
25966
  audioContext = this.audioContext; // Decode audio
24890
- _context7.n = 5;
25967
+ _context0.n = 5;
24891
25968
  return audioBlob.arrayBuffer();
24892
25969
  case 5:
24893
- arrayBuffer = _context7.v;
24894
- _context7.n = 6;
25970
+ arrayBuffer = _context0.v;
25971
+ _context0.n = 6;
24895
25972
  return audioContext.decodeAudioData(arrayBuffer);
24896
25973
  case 6:
24897
- audioBuffer = _context7.v;
25974
+ audioBuffer = _context0.v;
24898
25975
  shouldEmitStart = wasFirstPlay && !this.isPlaying && this.currentSource === null; // Create source
24899
25976
  source = audioContext.createBufferSource();
24900
25977
  source.buffer = audioBuffer;
@@ -24937,14 +26014,14 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24937
26014
  // Start playback - AudioContext should now be fully ready
24938
26015
 
24939
26016
  source.start();
24940
- _context7.n = 8;
26017
+ _context0.n = 8;
24941
26018
  break;
24942
26019
  case 7:
24943
- _context7.p = 7;
24944
- _t4 = _context7.v;
24945
- console.error('❌ AudioPlayer v2: Error processing queue:', _t4);
26020
+ _context0.p = 7;
26021
+ _t6 = _context0.v;
26022
+ console.error('❌ AudioPlayer v2: Error processing queue:', _t6);
24946
26023
  this.currentSource = null;
24947
- this.emit('playbackError', _t4);
26024
+ this.emit('playbackError', _t6);
24948
26025
 
24949
26026
  // Try next chunk
24950
26027
 
@@ -24959,9 +26036,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24959
26036
  this.emit('playbackStopped');
24960
26037
  }
24961
26038
  case 8:
24962
- return _context7.a(2);
26039
+ return _context0.a(2);
24963
26040
  }
24964
- }, _callee6, this, [[3, 7]]);
26041
+ }, _callee9, this, [[3, 7]]);
24965
26042
  }));
24966
26043
  function processQueue() {
24967
26044
  return _processQueue.apply(this, arguments);
@@ -25241,39 +26318,43 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
25241
26318
  }, {
25242
26319
  key: "resumeAudioContext",
25243
26320
  value: (function () {
25244
- var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
25245
- var _t5;
25246
- return _regenerator().w(function (_context8) {
25247
- while (1) switch (_context8.p = _context8.n) {
26321
+ var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0() {
26322
+ var _t7;
26323
+ return _regenerator().w(function (_context1) {
26324
+ while (1) switch (_context1.p = _context1.n) {
25248
26325
  case 0:
25249
26326
  if (!(!this.audioContext || this.audioContext.state === 'closed')) {
25250
- _context8.n = 2;
26327
+ _context1.n = 2;
25251
26328
  break;
25252
26329
  }
25253
- _context8.n = 1;
26330
+ _context1.n = 1;
25254
26331
  return this.initializeAudioContext();
25255
26332
  case 1:
25256
- return _context8.a(2);
26333
+ _context1.n = 6;
26334
+ break;
25257
26335
  case 2:
25258
26336
  if (!(this.audioContext.state === 'suspended')) {
25259
- _context8.n = 6;
26337
+ _context1.n = 6;
25260
26338
  break;
25261
26339
  }
25262
- _context8.p = 3;
25263
- _context8.n = 4;
26340
+ _context1.p = 3;
26341
+ _context1.n = 4;
25264
26342
  return this.audioContext.resume();
25265
26343
  case 4:
25266
26344
  console.log('✅ AudioPlayer v2: AudioContext resumed after mic permission');
25267
- _context8.n = 6;
26345
+ _context1.n = 6;
25268
26346
  break;
25269
26347
  case 5:
25270
- _context8.p = 5;
25271
- _t5 = _context8.v;
25272
- console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t5);
26348
+ _context1.p = 5;
26349
+ _t7 = _context1.v;
26350
+ console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t7);
25273
26351
  case 6:
25274
- return _context8.a(2);
26352
+ _context1.n = 7;
26353
+ return this.waitForAudioContextReady();
26354
+ case 7:
26355
+ return _context1.a(2);
25275
26356
  }
25276
- }, _callee7, this, [[3, 5]]);
26357
+ }, _callee0, this, [[3, 5]]);
25277
26358
  }));
25278
26359
  function resumeAudioContext() {
25279
26360
  return _resumeAudioContext.apply(this, arguments);
@@ -25343,6 +26424,8 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
25343
26424
  }
25344
26425
  _sharedPlayerContext = null;
25345
26426
  _sharedPlayerSampleRate = null;
26427
+ // Note: live AudioPlayer instances may still hold a reference to the now-closed
26428
+ // context until their next waitForAudioContextReady() / initializeAudioContext().
25346
26429
  }
25347
26430
  }]);
25348
26431
  }(_shared_EventEmitter_js__WEBPACK_IMPORTED_MODULE_0__["default"]);
@@ -25366,8 +26449,9 @@ __webpack_require__.r(__webpack_exports__);
25366
26449
  /* harmony import */ var _core_AudioRecorder_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../core/AudioRecorder.js */ "./src/core/AudioRecorder.js");
25367
26450
  /* harmony import */ var _utils_AudioFormatConverter_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils/AudioFormatConverter.js */ "./src/v2/utils/AudioFormatConverter.js");
25368
26451
  /* harmony import */ var _core_ClientToolsRegistry_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../core/ClientToolsRegistry.js */ "./src/core/ClientToolsRegistry.js");
25369
- /* harmony import */ var _utils_screenshot_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/screenshot.js */ "./src/utils/screenshot.js");
25370
- /* harmony import */ var _utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../utils/visual-tools.js */ "./src/utils/visual-tools.js");
26452
+ /* harmony import */ var _core_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../core/ClientScriptManager.js */ "./src/core/ClientScriptManager.js");
26453
+ /* harmony import */ var _utils_screenshot_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../utils/screenshot.js */ "./src/utils/screenshot.js");
26454
+ /* harmony import */ var _utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../utils/visual-tools.js */ "./src/utils/visual-tools.js");
25371
26455
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
25372
26456
  function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
25373
26457
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
@@ -25401,6 +26485,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
25401
26485
 
25402
26486
 
25403
26487
 
26488
+
25404
26489
  /**
25405
26490
 
25406
26491
  * VoiceSDK v2 - Multi-codec speech-to-speech SDK
@@ -25578,6 +26663,18 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
25578
26663
  return _this.conversationId;
25579
26664
  }, config.sharedToolHandlers || null);
25580
26665
 
26666
+ // Flavor-independent client-tool script manager (reserved partner id
26667
+ // `__client_tools__`). Owns its own PartnerScriptRunner so client-tool
26668
+ // scripts run regardless of flavor, alongside (and separate from) the
26669
+ // ecommerce flavor's partner runner. Sends result/emit frames over the
26670
+ // voice WebSocket.
26671
+ _this.clientScriptManager = new _core_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_5__.ClientScriptManager({
26672
+ send: function send(message) {
26673
+ return _this.sendMessage(message);
26674
+ },
26675
+ label: 'voice'
26676
+ });
26677
+
25581
26678
  // ===== REGISTER BUILT-IN SDK TOOLS =====
25582
26679
  // These tools work automatically - users don't need to implement them
25583
26680
  // Backend controls availability via agent configuration
@@ -25603,7 +26700,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
25603
26700
  key: "_registerBuiltInTools",
25604
26701
  value: function _registerBuiltInTools() {
25605
26702
  try {
25606
- (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_6__.registerVisualTools)(this.clientToolsRegistry);
26703
+ (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_7__.registerVisualTools)(this.clientToolsRegistry);
25607
26704
  } catch (error) {
25608
26705
  console.error('❌ VoiceSDK: Error registering built-in tools:', error);
25609
26706
  console.error(' Error details:', error.message, error.stack);
@@ -26362,7 +27459,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26362
27459
 
26363
27460
  // Include SDK build time for debugging
26364
27461
  if (true) {
26365
- helloMessage.lastBuildTime = "2026-05-07T11:24:41.900Z";
27462
+ helloMessage.lastBuildTime = "2026-06-12T15:13:11.602Z";
26366
27463
  }
26367
27464
 
26368
27465
  // Page context is intentionally NOT attached to the hello message.
@@ -26551,6 +27648,16 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26551
27648
  // envelope which the backend dispatcher routes to the callback.
26552
27649
  this._handleRunPartnerScript(message);
26553
27650
  break;
27651
+ case 'partner_bundle':
27652
+ // Flavor-independent client-tool bundle (partner_id `__client_tools__`)
27653
+ // is loaded by the dedicated ClientScriptManager. Every other partner
27654
+ // id falls through to the flavor (via the emitted 'message' event), so
27655
+ // the ecommerce flavor's own bundle handling keeps working untouched.
27656
+ if (_core_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_5__.ClientScriptManager.isClientToolsBundle(message)) {
27657
+ this.clientScriptManager.setBundle(message);
27658
+ }
27659
+ this.emit('message', message);
27660
+ break;
26554
27661
  case 'text_inject_ack':
26555
27662
  this.handleTextInjectAck(message);
26556
27663
  break;
@@ -26573,7 +27680,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26573
27680
  case 'get_page_context':
26574
27681
  // Handle get_page_context request from backend
26575
27682
  console.log('📖 [VISUAL ASSISTANT] Backend requested page context');
26576
- (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_6__.extractPageContext)().then(function (pageContext) {
27683
+ (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_7__.extractPageContext)().then(function (pageContext) {
26577
27684
  // Use existing DOM scanner
26578
27685
  _this6.sendMessage({
26579
27686
  t: 'page_context',
@@ -26594,7 +27701,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26594
27701
  console.log('📸 [VISUAL ASSISTANT] Backend requested screenshot');
26595
27702
  try {
26596
27703
  // Use existing screenshot capture function
26597
- (0,_utils_screenshot_js__WEBPACK_IMPORTED_MODULE_5__.captureScreenshot)().then(function (screenshot) {
27704
+ (0,_utils_screenshot_js__WEBPACK_IMPORTED_MODULE_6__.captureScreenshot)().then(function (screenshot) {
26598
27705
  _this6.sendMessage({
26599
27706
  t: 'screenshot',
26600
27707
  screenshot: {
@@ -27074,9 +28181,15 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
27074
28181
  console.warn('⚠️ VoiceSDK v2: run_partner_script missing requestId; dropping', message);
27075
28182
  return _context3.a(2);
27076
28183
  case 1:
28184
+ if (!_core_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_5__.ClientScriptManager.isClientToolsScript(message)) {
28185
+ _context3.n = 2;
28186
+ break;
28187
+ }
28188
+ return _context3.a(2, this.clientScriptManager.handleRunPartnerScript(message));
28189
+ case 2:
27077
28190
  handler = (_this$clientToolsRegi = this.clientToolsRegistry) === null || _this$clientToolsRegi === void 0 || (_this$clientToolsRegi = _this$clientToolsRegi.handlers) === null || _this$clientToolsRegi === void 0 ? void 0 : _this$clientToolsRegi.get('run_partner_script');
27078
28191
  if (handler) {
27079
- _context3.n = 2;
28192
+ _context3.n = 3;
27080
28193
  break;
27081
28194
  }
27082
28195
  console.error("\uD83E\uDDE9 VoiceSDK v2: no run_partner_script handler registered (ecommerce flavor not loaded?); requestId=".concat(requestId, " partner=").concat(partnerId, " action=").concat(action));
@@ -27089,16 +28202,16 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
27089
28202
  error: 'no_handler_registered'
27090
28203
  });
27091
28204
  return _context3.a(2);
27092
- case 2:
28205
+ case 3:
27093
28206
  startedAt = Date.now();
27094
28207
  console.log("\uD83E\uDDE9 VoiceSDK v2: run_partner_script dispatch partner=".concat(partnerId, " action=").concat(action, " requestId=").concat(requestId));
27095
- _context3.p = 3;
27096
- _context3.n = 4;
28208
+ _context3.p = 4;
28209
+ _context3.n = 5;
27097
28210
  return handler({
27098
28211
  action: action,
27099
28212
  args: args
27100
28213
  });
27101
- case 4:
28214
+ case 5:
27102
28215
  result = _context3.v;
27103
28216
  elapsedMs = Date.now() - startedAt; // Adapters return { ok, ... } themselves. Honor that flag if present;
27104
28217
  // otherwise treat presence-of-result as success.
@@ -27126,10 +28239,10 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
27126
28239
  ok: ok,
27127
28240
  result: result
27128
28241
  });
27129
- _context3.n = 6;
28242
+ _context3.n = 7;
27130
28243
  break;
27131
- case 5:
27132
- _context3.p = 5;
28244
+ case 6:
28245
+ _context3.p = 6;
27133
28246
  _t2 = _context3.v;
27134
28247
  _elapsedMs = Date.now() - startedAt;
27135
28248
  console.error("\uD83E\uDDE9 VoiceSDK v2: run_partner_script threw partner=".concat(partnerId, " action=").concat(action, " requestId=").concat(requestId, " (").concat(_elapsedMs, "ms):"), _t2);
@@ -27141,10 +28254,10 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
27141
28254
  ok: false,
27142
28255
  error: _t2 && _t2.message || String(_t2)
27143
28256
  });
27144
- case 6:
28257
+ case 7:
27145
28258
  return _context3.a(2);
27146
28259
  }
27147
- }, _callee3, this, [[3, 5]]);
28260
+ }, _callee3, this, [[4, 6]]);
27148
28261
  }));
27149
28262
  function _handleRunPartnerScript(_x) {
27150
28263
  return _handleRunPartnerScript2.apply(this, arguments);
@@ -30668,6 +31781,7 @@ __webpack_require__.r(__webpack_exports__);
30668
31781
  /* harmony import */ var _TextInterface_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./TextInterface.js */ "./src/widget/TextInterface.js");
30669
31782
  /* harmony import */ var _widget_translations_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./widget-translations.js */ "./src/widget/widget-translations.js");
30670
31783
  /* harmony import */ var _flavors_index_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../flavors/index.js */ "./src/flavors/index.js");
31784
+ /* harmony import */ var _ecommerce_EcommerceVoiceStrip_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../ecommerce/EcommerceVoiceStrip.js */ "./src/ecommerce/EcommerceVoiceStrip.js");
30671
31785
  var _excluded = ["agentName"];
30672
31786
  function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
30673
31787
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
@@ -30695,6 +31809,7 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
30695
31809
 
30696
31810
 
30697
31811
 
31812
+
30698
31813
  /** Fallback for `buildWebSocketUrl()` when no optional `websocketUrl` override is set (VoiceSDK v2 uses the same default). */
30699
31814
  var DEFAULT_VOICE_WEBSOCKET_URL = 'wss://speech.talktopc.com/ws/conv';
30700
31815
  function isNonEmptyConfigString(v) {
@@ -30789,6 +31904,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
30789
31904
  function TTPChatWidget() {
30790
31905
  var _this$config$behavior,
30791
31906
  _this$config$panel,
31907
+ _this$config$flavor,
30792
31908
  _this$config$text,
30793
31909
  _this$config$text2,
30794
31910
  _this$config$voice,
@@ -30841,6 +31957,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
30841
31957
  var showVoiceUi = widgetModeForVoice === 'unified' || widgetModeForVoice === 'voice-only';
30842
31958
  var panelBgForBrand = ((_this$config$panel = this.config.panel) === null || _this$config$panel === void 0 ? void 0 : _this$config$panel.backgroundColor) || '#16161e';
30843
31959
  var talkToPcLink = talkToPcLinkColorForPanel(computeIsLightPanel(panelBgForBrand), this.config.voice, this._rawConfig, showVoiceUi);
31960
+ var desktopMinimizedEcommerceStripUi = typeof window !== 'undefined' && window.innerWidth > 768 && ((_this$config$flavor = this.config.flavor) === null || _this$config$flavor === void 0 ? void 0 : _this$config$flavor.callView) === 'minimized';
30844
31961
  var voiceConfig = _objectSpread(_objectSpread(_objectSpread({}, this.config), this.config.voice), {}, {
30845
31962
  agentName: this.config.agentName,
30846
31963
  _talkToPcLinkColor: talkToPcLink,
@@ -30864,8 +31981,24 @@ var TTPChatWidget = /*#__PURE__*/function () {
30864
31981
  outputSampleRate: ((_this$config$voice4 = this.config.voice) === null || _this$config$voice4 === void 0 ? void 0 : _this$config$voice4.outputSampleRate) || 24000,
30865
31982
  outputChannels: ((_this$config$voice5 = this.config.voice) === null || _this$config$voice5 === void 0 ? void 0 : _this$config$voice5.outputChannels) || 1,
30866
31983
  outputBitDepth: ((_this$config$voice6 = this.config.voice) === null || _this$config$voice6 === void 0 ? void 0 : _this$config$voice6.outputBitDepth) || 16,
30867
- // Pass event callbacks to voice interface
30868
- onConversationStart: this.config.onConversationStart,
31984
+ desktopMinimizedEcommerceStripUi: desktopMinimizedEcommerceStripUi,
31985
+ onDesktopMinimizedStripCallStart: desktopMinimizedEcommerceStripUi ? function () {
31986
+ var _this$_ecommerceVoice;
31987
+ (_this$_ecommerceVoice = _this._ecommerceVoiceStrip) === null || _this$_ecommerceVoice === void 0 || _this$_ecommerceVoice.show();
31988
+ if (_this.isOpen) _this.minimize();
31989
+ _this._setPanelFooterVisible(false);
31990
+ _this._setEcommerceStripLauncherVisible(false);
31991
+ } : undefined,
31992
+ onDesktopMinimizedStripLauncherRestore: desktopMinimizedEcommerceStripUi ? function () {
31993
+ return _this._restoreDesktopEcommerceStripChrome();
31994
+ } : undefined,
31995
+ // Pass event callbacks to voice interface (forward only; panel is not auto-minimized on call start)
31996
+ onConversationStart: function onConversationStart() {
31997
+ if (typeof _this.config.onConversationStart === 'function') {
31998
+ var _this$config;
31999
+ (_this$config = _this.config).onConversationStart.apply(_this$config, arguments);
32000
+ }
32001
+ },
30869
32002
  onConversationEnd: this.config.onConversationEnd,
30870
32003
  onBargeIn: this.config.onBargeIn,
30871
32004
  onAudioStartPlaying: this.config.onAudioStartPlaying,
@@ -30933,7 +32066,9 @@ var TTPChatWidget = /*#__PURE__*/function () {
30933
32066
  },
30934
32067
  onMobileError: function onMobileError(message) {
30935
32068
  _this.showMobileLandingError(message);
30936
- }
32069
+ },
32070
+ // Root layout direction — must be last so `...this.config.voice` cannot overwrite with `undefined`
32071
+ direction: this.getEffectiveTextDirection()
30937
32072
  });
30938
32073
  this.voiceInterface = new _VoiceInterface_js__WEBPACK_IMPORTED_MODULE_1__.VoiceInterface(voiceConfig);
30939
32074
 
@@ -30981,11 +32116,12 @@ var TTPChatWidget = /*#__PURE__*/function () {
30981
32116
  // Check for resume state and auto-resume if needed
30982
32117
  this.checkAndHandleResume();
30983
32118
 
30984
- // Initialize flavor module if configured
32119
+ // Initialize flavor module if configured (use merged config so flavor matches this.config)
30985
32120
  this._flavor = null;
30986
- this._flavorObj = config.flavor && _typeof(config.flavor) === 'object' ? config.flavor : null;
32121
+ this._flavorObj = this.config.flavor && _typeof(this.config.flavor) === 'object' ? this.config.flavor : null;
30987
32122
  if (this._flavorObj && this._flavorObj.type) {
30988
32123
  this._initFlavor(this._flavorObj.type);
32124
+ this._initEcommerceVoiceStrip();
30989
32125
  } else if (this._flavorObj && !this._flavorObj.type) {
30990
32126
  console.error('[TTPChatWidget] flavor object is set but missing required "type" field. Widget will run in vanilla mode.');
30991
32127
  }
@@ -31147,8 +32283,8 @@ var TTPChatWidget = /*#__PURE__*/function () {
31147
32283
  }, {
31148
32284
  key: "getEffectiveTextDirection",
31149
32285
  value: function getEffectiveTextDirection() {
31150
- var _this$config;
31151
- return ((_this$config = this.config) === null || _this$config === void 0 ? void 0 : _this$config.direction) === 'rtl' ? 'rtl' : 'ltr';
32286
+ var _this$config2;
32287
+ return ((_this$config2 = this.config) === null || _this$config2 === void 0 ? void 0 : _this$config2.direction) === 'rtl' ? 'rtl' : 'ltr';
31152
32288
  }
31153
32289
 
31154
32290
  /**
@@ -31200,6 +32336,52 @@ var TTPChatWidget = /*#__PURE__*/function () {
31200
32336
  this._doTogglePanel();
31201
32337
  }
31202
32338
 
32339
+ /**
32340
+ * DOM root that contains the launcher (`#text-chat-button-container`), matching createWidget.
32341
+ * @returns {ShadowRoot|Document}
32342
+ */
32343
+ }, {
32344
+ key: "_launcherDomRoot",
32345
+ value: function _launcherDomRoot() {
32346
+ if (this.config.useShadowDOM !== false && this.shadowRoot && this.shadowRoot !== document) {
32347
+ return this.shadowRoot;
32348
+ }
32349
+ return document;
32350
+ }
32351
+
32352
+ /**
32353
+ * Show or hide the floating launcher for desktop callView:minimized (bottom strip mode).
32354
+ * Widget CSS uses `display: flex !important` on the container — hide with `none !important`.
32355
+ * @param {boolean} visible
32356
+ */
32357
+ }, {
32358
+ key: "_setEcommerceStripLauncherVisible",
32359
+ value: function _setEcommerceStripLauncherVisible(visible) {
32360
+ var _this$config$behavior4;
32361
+ if ((_this$config$behavior4 = this.config.behavior) !== null && _this$config$behavior4 !== void 0 && _this$config$behavior4.hidden) return;
32362
+ var root = this._launcherDomRoot();
32363
+ var container = root.getElementById('text-chat-button-container');
32364
+ var btn = root.getElementById('text-chat-button');
32365
+ if (visible) {
32366
+ if (container) container.style.removeProperty('display');
32367
+ if (btn) btn.style.removeProperty('display');
32368
+ } else if (container) {
32369
+ container.style.setProperty('display', 'none', 'important');
32370
+ } else if (btn) {
32371
+ btn.style.setProperty('display', 'none', 'important');
32372
+ }
32373
+ }
32374
+
32375
+ /** Hide bottom strip and restore floating launcher (desktop minimized call UI). */
32376
+ }, {
32377
+ key: "_restoreDesktopEcommerceStripChrome",
32378
+ value: function _restoreDesktopEcommerceStripChrome() {
32379
+ var _this$_ecommerceVoice2;
32380
+ (_this$_ecommerceVoice2 = this._ecommerceVoiceStrip) === null || _this$_ecommerceVoice2 === void 0 || _this$_ecommerceVoice2.hide();
32381
+ this._setPanelFooterVisible(true);
32382
+ this._setEcommerceStripLauncherVisible(true);
32383
+ }
32384
+
31203
32385
  /**
31204
32386
  * Wire the universal widget-control message listener on both the
31205
32387
  * text-chat SDK and the voice SDK. Two channels, one set of message types
@@ -31269,7 +32451,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
31269
32451
  }, {
31270
32452
  key: "mergeWithDefaults",
31271
32453
  value: function mergeWithDefaults(userConfig) {
31272
- var _userConfig$button, _userConfig$button2, _userConfig$icon, _userConfig$icon2, _userConfig$icon3, _userConfig$icon4, _userConfig$icon5, _userConfig$button3, _userConfig$button4, _userConfig$button5, _userConfig$icon7, _userConfig$button6, _userConfig$button7, _userConfig$button8, _userConfig$button9, _userConfig$icon8, _userConfig$panel, _userConfig$panel2, _userConfig$panel3, _userConfig$panel4, _userConfig$panel5, _userConfig$panel6, _userConfig$footer, _userConfig$footer2, _userConfig$footer3, _userConfig$footer4, _userConfig$tooltips, _userConfig$tooltips2, _userConfig$tooltips3, _userConfig$tooltips4, _userConfig$tooltips5, _userConfig$tooltips6, _userConfig$messages, _userConfig$messages2, _userConfig$messages3, _userConfig$messages4, _userConfig$messages5, _userConfig$messages6, _userConfig$messages7, _userConfig$messages8, _userConfig$messages9, _userConfig$messages0, _userConfig$messages1, _userConfig$messages10, _userConfig$messages11, _userConfig$animation, _userConfig$animation2, _userConfig$animation3, _userConfig$animation4, _userConfig$promptAni, _userConfig$promptAni2, _userConfig$promptAni3, _userConfig$promptAni4, _userConfig$promptAni5, _userConfig$promptAni6, _userConfig$promptAni7, _userConfig$promptAni8, _userConfig$promptAni9, _userConfig$promptAni0, _userConfig$behavior, _userConfig$behavior2, _userConfig$behavior3, _userConfig$behavior4, _userConfig$behavior5, _userConfig$behavior6, _userConfig$behavior7, _userConfig$behavior8, _userConfig$accessibi, _userConfig$accessibi2, _userConfig$accessibi3;
32454
+ var _userConfig$button, _userConfig$button2, _userConfig$icon, _userConfig$icon2, _userConfig$icon3, _userConfig$icon4, _userConfig$icon5, _userConfig$button3, _userConfig$button4, _userConfig$button5, _userConfig$icon7, _userConfig$button6, _userConfig$button7, _userConfig$button8, _userConfig$button9, _userConfig$icon8, _userConfig$panel, _userConfig$panel2, _userConfig$panel3, _userConfig$panel4, _userConfig$panel5, _userConfig$panel6, _userConfig$footer, _userConfig$footer2, _userConfig$footer3, _userConfig$footer4, _userConfig$footer5, _userConfig$tooltips, _userConfig$tooltips2, _userConfig$tooltips3, _userConfig$tooltips4, _userConfig$tooltips5, _userConfig$tooltips6, _userConfig$messages, _userConfig$messages2, _userConfig$messages3, _userConfig$messages4, _userConfig$messages5, _userConfig$messages6, _userConfig$messages7, _userConfig$messages8, _userConfig$messages9, _userConfig$messages0, _userConfig$messages1, _userConfig$messages10, _userConfig$messages11, _userConfig$animation, _userConfig$animation2, _userConfig$animation3, _userConfig$animation4, _userConfig$promptAni, _userConfig$promptAni2, _userConfig$promptAni3, _userConfig$promptAni4, _userConfig$promptAni5, _userConfig$promptAni6, _userConfig$promptAni7, _userConfig$promptAni8, _userConfig$promptAni9, _userConfig$promptAni0, _userConfig$behavior, _userConfig$behavior2, _userConfig$behavior3, _userConfig$behavior4, _userConfig$behavior5, _userConfig$behavior6, _userConfig$behavior7, _userConfig$behavior8, _userConfig$accessibi, _userConfig$accessibi2, _userConfig$accessibi3;
31273
32455
  // Handle legacy position string format
31274
32456
  var positionConfig = userConfig.position || 'bottom-right';
31275
32457
  if (typeof positionConfig === 'string') {
@@ -31535,7 +32717,9 @@ var TTPChatWidget = /*#__PURE__*/function () {
31535
32717
  // Default true
31536
32718
  backgroundColor: ((_userConfig$footer2 = userConfig.footer) === null || _userConfig$footer2 === void 0 ? void 0 : _userConfig$footer2.backgroundColor) || '#f9fafb',
31537
32719
  textColor: ((_userConfig$footer3 = userConfig.footer) === null || _userConfig$footer3 === void 0 ? void 0 : _userConfig$footer3.textColor) || '#9ca3af',
31538
- hoverColor: ((_userConfig$footer4 = userConfig.footer) === null || _userConfig$footer4 === void 0 ? void 0 : _userConfig$footer4.hoverColor) || '#7C3AED'
32720
+ hoverColor: ((_userConfig$footer4 = userConfig.footer) === null || _userConfig$footer4 === void 0 ? void 0 : _userConfig$footer4.hoverColor) || '#7C3AED',
32721
+ // Brand shown in "Powered by" footer. 'talktopc' (default) or 'speacart'.
32722
+ brand: ((_userConfig$footer5 = userConfig.footer) === null || _userConfig$footer5 === void 0 ? void 0 : _userConfig$footer5.brand) === 'speacart' ? 'speacart' : 'talktopc'
31539
32723
  }, userConfig.footer),
31540
32724
  // Tooltips Configuration
31541
32725
  tooltips: _objectSpread({
@@ -31767,8 +32951,8 @@ var TTPChatWidget = /*#__PURE__*/function () {
31767
32951
 
31768
32952
  // On desktop, ensure voice hero is shown for unified/voice-only (in-panel mode picker removed)
31769
32953
  if (window.innerWidth > 768) {
31770
- var _this$config$behavior4;
31771
- var widgetMode = ((_this$config$behavior4 = this.config.behavior) === null || _this$config$behavior4 === void 0 ? void 0 : _this$config$behavior4.mode) || 'unified';
32954
+ var _this$config$behavior5;
32955
+ var widgetMode = ((_this$config$behavior5 = this.config.behavior) === null || _this$config$behavior5 === void 0 ? void 0 : _this$config$behavior5.mode) || 'unified';
31772
32956
  var preferVoice = widgetMode === 'unified' || widgetMode === 'voice-only';
31773
32957
  if (preferVoice && this.currentView !== 'text') {
31774
32958
  this.showVoice();
@@ -31866,7 +33050,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
31866
33050
  }, {
31867
33051
  key: "generateWidgetHTML",
31868
33052
  value: function generateWidgetHTML() {
31869
- var _this$config$behavior5,
33053
+ var _this$config$behavior6,
31870
33054
  _this7 = this;
31871
33055
  var pos = this.config.position;
31872
33056
  var btn = this.config.button;
@@ -31895,7 +33079,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
31895
33079
 
31896
33080
  // Generate position styles
31897
33081
  var positionStyles = this.generatePositionStyles();
31898
- var widgetMode = ((_this$config$behavior5 = this.config.behavior) === null || _this$config$behavior5 === void 0 ? void 0 : _this$config$behavior5.mode) || 'unified';
33082
+ var widgetMode = ((_this$config$behavior6 = this.config.behavior) === null || _this$config$behavior6 === void 0 ? void 0 : _this$config$behavior6.mode) || 'unified';
31899
33083
  var showVoice = widgetMode === 'unified' || widgetMode === 'voice-only';
31900
33084
  var showText = widgetMode === 'unified' || widgetMode === 'text-only';
31901
33085
  var voiceEnabled = showVoice;
@@ -31947,7 +33131,24 @@ var TTPChatWidget = /*#__PURE__*/function () {
31947
33131
  var pillWaveformBars = '<span class="bar"></span><span class="bar"></span><span class="bar"></span><span class="bar"></span><span class="bar"></span>';
31948
33132
  var pillIconInner = pillCustomImage ? "<img class=\"ttp-launcher-logo\" src=\"".concat(pillCustomImage, "\" alt=\"\" />") : "<div class=\"ttp-pill-waveform\" aria-hidden=\"true\">".concat(pillWaveformBars, "</div>");
31949
33133
  var pillIconWrapClass = pillCustomImage ? 'ttp-pill-icon' : 'ttp-pill-icon ttp-pill-icon--wave';
31950
- return "\n ".concat(styleTag, "\n \n ").concat(this.config.behavior.hidden ? '' : "\n <div id=\"text-chat-button-container\">\n ".concat(promptBubbleHTML, "\n ").concat(isPromptEnabled && promptConfig.showPulseRings !== false ? this.generatePulseRingsHTML(promptConfig) : '', "\n <!-- Desktop pill launcher (hidden on mobile) -->\n <button type=\"button\" id=\"text-chat-button\" aria-label=\"Open chat with ").concat(pillTitle, "\">\n <div class=\"").concat(pillIconWrapClass, "\">\n ").concat(pillIconInner, "\n </div>\n <div class=\"ttp-pill-text\">\n <span class=\"ttp-pill-title\">").concat(pillTitle, "</span>\n <span class=\"ttp-pill-status\">\n <span class=\"ttp-launcher-dot\"></span>\n ").concat(header.onlineIndicatorText !== undefined && header.onlineIndicatorText !== null ? header.onlineIndicatorText : t('online'), "\n </span>\n </div>\n </button>\n <!-- Mobile FAB (hidden on desktop) -->\n <button class=\"ttp-mobile-fab\" aria-label=\"").concat(this.config.accessibility.ariaLabel, "\">\n <div class=\"ttp-mobile-fab__icon-wrap\">\n <div class=\"ttp-mobile-fab__waveform\">\n <span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span>\n </div>\n </div>\n <div class=\"ttp-mobile-fab__label-wrap\">\n <span class=\"ttp-mobile-fab__label\">").concat(mobileFabLabel, "</span>\n <span class=\"ttp-mobile-fab__status\">\n <span class=\"ttp-mobile-fab__dot\"></span>\n ").concat(header.onlineIndicatorText !== undefined && header.onlineIndicatorText !== null ? header.onlineIndicatorText : t('online'), "\n </span>\n </div>\n </button>\n </div>\n "), "\n\n ").concat(this.config.behavior.hidden ? '' : "\n <div id=\"ttpMobileLanding\" class=\"ttp-mobile-landing\">\n <div class=\"ttp-mobile-landing__header\">\n <div class=\"ttp-mobile-landing__avatar\">\n <div class=\"ttp-mobile-landing__waveform\">\n <span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span>\n </div>\n </div>\n <div class=\"ttp-mobile-landing__info\">\n <div class=\"ttp-mobile-landing__name\">".concat(mobileLandingHeroName, "</div>\n <div class=\"ttp-mobile-landing__status\">\n <span class=\"ttp-mobile-landing__dot\"></span>\n ").concat(this.config.landing.statusText || t('online') + ' • ' + (this.config.landing.subtitle || 'Ready to help'), "\n </div>\n </div>\n <button class=\"ttp-mobile-landing__close\" aria-label=\"Close\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"ttp-mobile-landing__error\" id=\"ttpMobileLandingError\" style=\"display:none;\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/>\n </svg>\n <span class=\"ttp-mobile-landing__error-text\"></span>\n </div>\n <div class=\"ttp-mobile-landing__actions\">\n ").concat(showVoice ? "\n <button class=\"ttp-mobile-landing__btn ttp-mobile-landing__btn--call\" id=\"ttpMobileCallBtn\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z\"/>\n </svg>\n ".concat(this.config.landing.callButtonText || this.config.landing.voiceCardTitle || t('voiceCall'), "\n </button>\n ") : '', "\n ").concat(showText ? "\n <button class=\"ttp-mobile-landing__btn ttp-mobile-landing__btn--text\" id=\"ttpMobileChatBtn\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n ".concat(this.config.landing.chatButtonText || this.config.landing.textCardTitle || t('textChat'), "\n </button>\n ") : '', "\n </div>\n ").concat(this.config.footer.show !== false ? "\n <div class=\"ttp-mobile-landing__footer\">\n <div class=\"ttp-mobile-landing__powered\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"10\" height=\"10\">\n <polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/>\n </svg>\n Powered by <a href=\"https://talktopc.com\" target=\"_blank\" rel=\"noopener\">TalkToPC</a>\n </div>\n </div>\n " : '', "\n </div>\n "), "\n \n <div id=\"text-chat-panel\">\n <div class=\"widget-shell\">\n <div class=\"panel-inner widget-container\" style=\"direction: ").concat(this.config.direction, ";\">\n\n ").concat(showVoice ? this.voiceInterface.generateHTML() : '', "\n ").concat(showText ? this.textInterface.generateHTML() : '', "\n ").concat(this.generateFooterHTML(), "\n </div>\n </div>\n </div>\n ");
33134
+ return "\n ".concat(styleTag, "\n \n ").concat(this.config.behavior.hidden ? '' : "\n <div id=\"text-chat-button-container\">\n ".concat(promptBubbleHTML, "\n ").concat(isPromptEnabled && promptConfig.showPulseRings !== false ? this.generatePulseRingsHTML(promptConfig) : '', "\n <!-- Desktop pill launcher (hidden on mobile) -->\n <button type=\"button\" id=\"text-chat-button\" aria-label=\"Open chat with ").concat(pillTitle, "\">\n <div class=\"").concat(pillIconWrapClass, "\">\n ").concat(pillIconInner, "\n </div>\n <div class=\"ttp-pill-text\">\n <span class=\"ttp-pill-title\">").concat(pillTitle, "</span>\n <span class=\"ttp-pill-status\">\n <span class=\"ttp-launcher-dot\"></span>\n ").concat(header.onlineIndicatorText !== undefined && header.onlineIndicatorText !== null ? header.onlineIndicatorText : t('online'), "\n </span>\n </div>\n </button>\n <!-- Mobile FAB (hidden on desktop) -->\n <button class=\"ttp-mobile-fab\" aria-label=\"").concat(this.config.accessibility.ariaLabel, "\">\n <div class=\"ttp-mobile-fab__icon-wrap\">\n <div class=\"ttp-mobile-fab__waveform\">\n <span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span>\n </div>\n </div>\n <div class=\"ttp-mobile-fab__label-wrap\">\n <span class=\"ttp-mobile-fab__label\">").concat(mobileFabLabel, "</span>\n <span class=\"ttp-mobile-fab__status\">\n <span class=\"ttp-mobile-fab__dot\"></span>\n ").concat(header.onlineIndicatorText !== undefined && header.onlineIndicatorText !== null ? header.onlineIndicatorText : t('online'), "\n </span>\n </div>\n </button>\n </div>\n "), "\n\n ").concat(this.config.behavior.hidden ? '' : "\n <div id=\"ttpMobileLanding\" class=\"ttp-mobile-landing\">\n <div class=\"ttp-mobile-landing__header\">\n <div class=\"ttp-mobile-landing__avatar\">\n <div class=\"ttp-mobile-landing__waveform\">\n <span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span><span class=\"bar\"></span>\n </div>\n </div>\n <div class=\"ttp-mobile-landing__info\">\n <div class=\"ttp-mobile-landing__name\">".concat(mobileLandingHeroName, "</div>\n <div class=\"ttp-mobile-landing__status\">\n <span class=\"ttp-mobile-landing__dot\"></span>\n ").concat(this.config.landing.statusText || t('online') + ' • ' + (this.config.landing.subtitle || 'Ready to help'), "\n </div>\n </div>\n <button class=\"ttp-mobile-landing__close\" aria-label=\"Close\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"ttp-mobile-landing__error\" id=\"ttpMobileLandingError\" style=\"display:none;\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/>\n </svg>\n <span class=\"ttp-mobile-landing__error-text\"></span>\n </div>\n <div class=\"ttp-mobile-landing__actions\">\n ").concat(showVoice ? "\n <button class=\"ttp-mobile-landing__btn ttp-mobile-landing__btn--call\" id=\"ttpMobileCallBtn\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z\"/>\n </svg>\n ".concat(this.config.landing.callButtonText || this.config.landing.voiceCardTitle || t('voiceCall'), "\n </button>\n ") : '', "\n ").concat(showText ? "\n <button class=\"ttp-mobile-landing__btn ttp-mobile-landing__btn--text\" id=\"ttpMobileChatBtn\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n ".concat(this.config.landing.chatButtonText || this.config.landing.textCardTitle || t('textChat'), "\n </button>\n ") : '', "\n </div>\n ").concat(this.config.footer.show !== false ? "\n <div class=\"ttp-mobile-landing__footer\">\n <div class=\"ttp-mobile-landing__powered\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"10\" height=\"10\">\n <polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/>\n </svg>\n Powered by <a href=\"".concat(this.config.footer.brand === 'speacart' ? 'https://speacart.com' : 'https://talktopc.com', "\" target=\"_blank\" rel=\"noopener\">").concat(this.config.footer.brand === 'speacart' ? 'SpeaCart' : 'TalkToPC', "</a>\n </div>\n </div>\n ") : '', "\n </div>\n "), "\n \n <div id=\"text-chat-panel\">\n <div class=\"widget-shell\">\n <div class=\"panel-inner widget-container\" style=\"direction: ").concat(this.config.direction, ";\">\n\n ").concat(showVoice ? this.voiceInterface.generateHTML() : '', "\n ").concat(showText ? this.textInterface.generateHTML() : '', "\n ").concat(this.generateFooterHTML(), "\n </div>\n </div>\n </div>\n ");
33135
+ }
33136
+
33137
+ /**
33138
+ * Resolved footer branding for panel + desktop voice strip.
33139
+ * @returns {{ brandUrl: string, brandName: string } | null}
33140
+ */
33141
+ }, {
33142
+ key: "getFooterBranding",
33143
+ value: function getFooterBranding() {
33144
+ var footer = this.config.footer;
33145
+ if (!(footer !== null && footer !== void 0 && footer.show)) return null;
33146
+ var refSource = encodeURIComponent(window.location.hostname || 'widget');
33147
+ var isSpeacart = footer.brand === 'speacart';
33148
+ return {
33149
+ brandUrl: isSpeacart ? 'https://speacart.com' : "https://talktopc.com?ref=widget&source=".concat(refSource),
33150
+ brandName: isSpeacart ? 'SpeaCart' : 'TalkToPC'
33151
+ };
31951
33152
  }
31952
33153
 
31953
33154
  /**
@@ -31956,11 +33157,24 @@ var TTPChatWidget = /*#__PURE__*/function () {
31956
33157
  }, {
31957
33158
  key: "generateFooterHTML",
31958
33159
  value: function generateFooterHTML() {
31959
- var footer = this.config.footer;
31960
- if (!footer.show) return '';
31961
- var refSource = encodeURIComponent(window.location.hostname || 'widget');
31962
- var brandUrl = "https://talktopc.com?ref=widget&source=".concat(refSource);
31963
- return "\n <div class=\"widget-footer ttp-footer\">\n <span>Powered by <a href=\"".concat(brandUrl, "\" target=\"_blank\" rel=\"noopener noreferrer\"><b>TalkToPC</b></a></span>\n </div>\n ");
33160
+ var branding = this.getFooterBranding();
33161
+ if (!branding) return '';
33162
+ var brandUrl = branding.brandUrl,
33163
+ brandName = branding.brandName;
33164
+ return "\n <div class=\"widget-footer ttp-footer\">\n <span>Powered by <a href=\"".concat(brandUrl, "\" target=\"_blank\" rel=\"noopener noreferrer\"><b>").concat(brandName, "</b></a></span>\n </div>\n ");
33165
+ }
33166
+
33167
+ /**
33168
+ * Hide in-panel footer while the desktop bottom strip owns the active call UI.
33169
+ * @param {boolean} visible
33170
+ */
33171
+ }, {
33172
+ key: "_setPanelFooterVisible",
33173
+ value: function _setPanelFooterVisible(visible) {
33174
+ var _root$querySelector;
33175
+ var root = this.shadowRoot || document;
33176
+ var footer = (_root$querySelector = root.querySelector) === null || _root$querySelector === void 0 ? void 0 : _root$querySelector.call(root, '.ttp-footer');
33177
+ if (footer) footer.style.display = visible ? '' : 'none';
31964
33178
  }
31965
33179
  }, {
31966
33180
  key: "generatePositionStyles",
@@ -32056,7 +33270,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
32056
33270
  }, {
32057
33271
  key: "generateCSS",
32058
33272
  value: function generateCSS(positionStyles, buttonSize, iconSize) {
32059
- var _this$config$behavior6, _this$config$text3, _this$config$position, _this$config$position2, _this$config$position3, _this$config$position4;
33273
+ var _this$config$behavior7, _this$config$text3, _this$config$position, _this$config$position2, _this$config$position3, _this$config$position4;
32060
33274
  var btn = this.config.button;
32061
33275
  var icon = this.config.icon;
32062
33276
  var panel = this.config.panel;
@@ -32065,7 +33279,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
32065
33279
  var anim = this.config.animation;
32066
33280
 
32067
33281
  // Determine which interfaces to show
32068
- var widgetMode = ((_this$config$behavior6 = this.config.behavior) === null || _this$config$behavior6 === void 0 ? void 0 : _this$config$behavior6.mode) || 'unified';
33282
+ var widgetMode = ((_this$config$behavior7 = this.config.behavior) === null || _this$config$behavior7 === void 0 ? void 0 : _this$config$behavior7.mode) || 'unified';
32069
33283
  var showVoice = widgetMode === 'unified' || widgetMode === 'voice-only';
32070
33284
  var showText = widgetMode === 'unified' || widgetMode === 'text-only';
32071
33285
 
@@ -32099,7 +33313,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
32099
33313
  key: "setupWidgetEvents",
32100
33314
  value: function setupWidgetEvents() {
32101
33315
  var _this8 = this,
32102
- _this$config$behavior7;
33316
+ _this$config$behavior8;
32103
33317
  if (!this.shadowRoot) return;
32104
33318
 
32105
33319
  // Setup prompt animation visibility and interactions
@@ -32169,7 +33383,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
32169
33383
  }
32170
33384
 
32171
33385
  // Voice selection events (based on widget mode)
32172
- var widgetMode = ((_this$config$behavior7 = this.config.behavior) === null || _this$config$behavior7 === void 0 ? void 0 : _this$config$behavior7.mode) || 'unified';
33386
+ var widgetMode = ((_this$config$behavior8 = this.config.behavior) === null || _this$config$behavior8 === void 0 ? void 0 : _this$config$behavior8.mode) || 'unified';
32173
33387
  var isUnified = widgetMode === 'unified';
32174
33388
  var showVoice = widgetMode === 'unified' || widgetMode === 'voice-only';
32175
33389
  var showText = widgetMode === 'unified' || widgetMode === 'text-only';
@@ -32956,8 +34170,9 @@ var TTPChatWidget = /*#__PURE__*/function () {
32956
34170
  }, {
32957
34171
  key: "updateConfig",
32958
34172
  value: function updateConfig(newConfig) {
32959
- var _this$config2,
32960
- _this$config3,
34173
+ var _this$config3,
34174
+ _this$config4,
34175
+ _this$config$flavor2,
32961
34176
  _this$config$voice8,
32962
34177
  _this$config$voice9,
32963
34178
  _this$config$voice0,
@@ -33081,9 +34296,9 @@ var TTPChatWidget = /*#__PURE__*/function () {
33081
34296
  });
33082
34297
 
33083
34298
  // Store current language before merge to detect changes
33084
- var oldLanguage = ((_this$config2 = this.config) === null || _this$config2 === void 0 ? void 0 : _this$config2.language) || 'en';
34299
+ var oldLanguage = ((_this$config3 = this.config) === null || _this$config3 === void 0 ? void 0 : _this$config3.language) || 'en';
33085
34300
  // Store current useShadowDOM setting
33086
- var oldUseShadowDOM = ((_this$config3 = this.config) === null || _this$config3 === void 0 ? void 0 : _this$config3.useShadowDOM) !== false;
34301
+ var oldUseShadowDOM = ((_this$config4 = this.config) === null || _this$config4 === void 0 ? void 0 : _this$config4.useShadowDOM) !== false;
33087
34302
  this.config = this.mergeWithDefaults(mergedConfig);
33088
34303
 
33089
34304
  // Check if useShadowDOM changed - if so, need to recreate container
@@ -33161,6 +34376,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
33161
34376
  // Recreate interfaces with new config (after mergeWithDefaults, so language is current)
33162
34377
  // Ensure language is correctly passed - use the merged config value
33163
34378
  var currentLanguage = this.config.language || 'en';
34379
+ var desktopMinimizedEcommerceStripUi = typeof window !== 'undefined' && window.innerWidth > 768 && ((_this$config$flavor2 = this.config.flavor) === null || _this$config$flavor2 === void 0 ? void 0 : _this$config$flavor2.callView) === 'minimized';
33164
34380
  var voiceConfig = _objectSpread(_objectSpread(_objectSpread({}, this.config), this.config.voice), {}, {
33165
34381
  agentName: this.config.agentName,
33166
34382
  language: currentLanguage,
@@ -33178,8 +34394,24 @@ var TTPChatWidget = /*#__PURE__*/function () {
33178
34394
  outputSampleRate: ((_this$config$voice0 = this.config.voice) === null || _this$config$voice0 === void 0 ? void 0 : _this$config$voice0.outputSampleRate) || 24000,
33179
34395
  outputChannels: ((_this$config$voice1 = this.config.voice) === null || _this$config$voice1 === void 0 ? void 0 : _this$config$voice1.outputChannels) || 1,
33180
34396
  outputBitDepth: ((_this$config$voice10 = this.config.voice) === null || _this$config$voice10 === void 0 ? void 0 : _this$config$voice10.outputBitDepth) || 16,
33181
- // Pass event callbacks to voice interface
33182
- onConversationStart: this.config.onConversationStart,
34397
+ desktopMinimizedEcommerceStripUi: desktopMinimizedEcommerceStripUi,
34398
+ onDesktopMinimizedStripCallStart: desktopMinimizedEcommerceStripUi ? function () {
34399
+ var _this13$_ecommerceVoi;
34400
+ (_this13$_ecommerceVoi = _this13._ecommerceVoiceStrip) === null || _this13$_ecommerceVoi === void 0 || _this13$_ecommerceVoi.show();
34401
+ if (_this13.isOpen) _this13.minimize();
34402
+ _this13._setPanelFooterVisible(false);
34403
+ _this13._setEcommerceStripLauncherVisible(false);
34404
+ } : undefined,
34405
+ onDesktopMinimizedStripLauncherRestore: desktopMinimizedEcommerceStripUi ? function () {
34406
+ return _this13._restoreDesktopEcommerceStripChrome();
34407
+ } : undefined,
34408
+ // Pass event callbacks to voice interface (forward only; panel is not auto-minimized on call start)
34409
+ onConversationStart: function onConversationStart() {
34410
+ if (typeof _this13.config.onConversationStart === 'function') {
34411
+ var _this13$config;
34412
+ (_this13$config = _this13.config).onConversationStart.apply(_this13$config, arguments);
34413
+ }
34414
+ },
33183
34415
  onConversationEnd: this.config.onConversationEnd,
33184
34416
  onBargeIn: this.config.onBargeIn,
33185
34417
  onAudioStartPlaying: this.config.onAudioStartPlaying,
@@ -33229,7 +34461,8 @@ var TTPChatWidget = /*#__PURE__*/function () {
33229
34461
  } else {
33230
34462
  _this13.showLanding();
33231
34463
  }
33232
- }
34464
+ },
34465
+ direction: this.getEffectiveTextDirection()
33233
34466
  });
33234
34467
  this.voiceInterface = new _VoiceInterface_js__WEBPACK_IMPORTED_MODULE_1__.VoiceInterface(voiceConfig);
33235
34468
 
@@ -33252,6 +34485,136 @@ var TTPChatWidget = /*#__PURE__*/function () {
33252
34485
  if (this.textInterface && this.textInterface.updateInputAttributes) {
33253
34486
  this.textInterface.updateInputAttributes();
33254
34487
  }
34488
+
34489
+ // Recreated VoiceInterface = new AgentSDK; re-wire ecommerce bottom strip to onConversationStart / transcript.
34490
+ this._flavorObj = this.config.flavor && _typeof(this.config.flavor) === 'object' ? this.config.flavor : null;
34491
+ if (this._ecommerceVoiceStrip) {
34492
+ this._ecommerceVoiceStrip.destroy();
34493
+ this._ecommerceVoiceStrip = null;
34494
+ }
34495
+ this._initEcommerceVoiceStrip();
34496
+ }
34497
+
34498
+ /**
34499
+ * Initialize the bottom-strip voice UI for desktop + flavor.callView:"minimized".
34500
+ *
34501
+ * Activates when flavor.callView === "minimized" and Desktop (window.innerWidth > 768).
34502
+ *
34503
+ * The strip renders as a fixed bar at the bottom of the page and replaces the
34504
+ * in-panel voice active state during a call. Landing and idle views are unchanged.
34505
+ */
34506
+ }, {
34507
+ key: "_initEcommerceVoiceStrip",
34508
+ value: function _initEcommerceVoiceStrip() {
34509
+ var _this14 = this,
34510
+ _this$voiceInterface6;
34511
+ var flavor = this.config.flavor && _typeof(this.config.flavor) === 'object' ? this.config.flavor : null;
34512
+ if (window.innerWidth <= 768) return;
34513
+ if (!flavor || flavor.callView !== 'minimized') return;
34514
+ if (this._ecommerceVoiceStrip) {
34515
+ this._ecommerceVoiceStrip.destroy();
34516
+ this._ecommerceVoiceStrip = null;
34517
+ }
34518
+ var v = this.config.voice || {};
34519
+ var strip;
34520
+ strip = new _ecommerce_EcommerceVoiceStrip_js__WEBPACK_IMPORTED_MODULE_5__.EcommerceVoiceStrip({
34521
+ language: this.config.language || 'en',
34522
+ direction: this.getEffectiveTextDirection(),
34523
+ footer: this.getFooterBranding(),
34524
+ theme: {
34525
+ pillGradient: v.pillGradient || 'linear-gradient(135deg, #581c87, #312e81, #1e1b4b)',
34526
+ pillTextColor: v.pillTextColor || '#ffffff',
34527
+ pillDotColor: v.pillDotColor || '#4ade80',
34528
+ endCallButtonColor: v.endCallButtonColor || '#ef4444'
34529
+ },
34530
+ getVoiceUiState: function getVoiceUiState() {
34531
+ var _this14$voiceInterfac, _this14$voiceInterfac2, _this14$voiceInterfac3;
34532
+ return {
34533
+ isMicMuted: !!((_this14$voiceInterfac = _this14.voiceInterface) !== null && _this14$voiceInterfac !== void 0 && _this14$voiceInterfac.isMicMuted),
34534
+ isPaused: !!((_this14$voiceInterfac2 = _this14.voiceInterface) !== null && _this14$voiceInterfac2 !== void 0 && _this14$voiceInterfac2.isPaused),
34535
+ isSpeakerMuted: !!((_this14$voiceInterfac3 = _this14.voiceInterface) !== null && _this14$voiceInterfac3 !== void 0 && _this14$voiceInterfac3.isSpeakerMuted)
34536
+ };
34537
+ },
34538
+ onHangup: function onHangup() {
34539
+ if (_this14.voiceInterface) _this14.voiceInterface.endVoiceCall();
34540
+ },
34541
+ onToggleMute: function onToggleMute() {
34542
+ if (_this14.voiceInterface) _this14.voiceInterface.toggleMute();
34543
+ strip.syncFromVoice();
34544
+ },
34545
+ onTogglePause: function onTogglePause() {
34546
+ if (_this14.voiceInterface) _this14.voiceInterface.togglePause();
34547
+ strip.syncFromVoice();
34548
+ },
34549
+ onToggleSpeaker: function onToggleSpeaker() {
34550
+ if (_this14.voiceInterface) _this14.voiceInterface.toggleSpeaker();
34551
+ strip.syncFromVoice();
34552
+ },
34553
+ onSendText: function onSendText(text) {
34554
+ var _this14$voiceInterfac4;
34555
+ var voiceSDK = (_this14$voiceInterfac4 = _this14.voiceInterface) === null || _this14$voiceInterfac4 === void 0 || (_this14$voiceInterfac4 = _this14$voiceInterfac4.sdk) === null || _this14$voiceInterfac4 === void 0 ? void 0 : _this14$voiceInterfac4.voiceSDK;
34556
+ if (voiceSDK && typeof voiceSDK.sendMessage === 'function') {
34557
+ voiceSDK.sendMessage({
34558
+ t: 'text_inject',
34559
+ text: text
34560
+ });
34561
+ }
34562
+ }
34563
+ });
34564
+ this._ecommerceVoiceStrip = strip;
34565
+
34566
+ // Show strip when conversation starts
34567
+ var agentSDK = (_this$voiceInterface6 = this.voiceInterface) === null || _this$voiceInterface6 === void 0 ? void 0 : _this$voiceInterface6.sdk;
34568
+ if (agentSDK) {
34569
+ var origConvStart = agentSDK.onConversationStart;
34570
+ agentSDK.onConversationStart = function () {
34571
+ strip.show({
34572
+ reset: false
34573
+ });
34574
+ strip.syncFromVoice();
34575
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
34576
+ args[_key] = arguments[_key];
34577
+ }
34578
+ if (typeof origConvStart === 'function') origConvStart.call.apply(origConvStart, [agentSDK].concat(args));
34579
+ };
34580
+
34581
+ // Feed transcript updates into the strip
34582
+ var origOnTranscript = agentSDK.onTranscript;
34583
+ agentSDK.onTranscript = function (text) {
34584
+ strip.updateTranscript(text);
34585
+ if (typeof origOnTranscript === 'function') origOnTranscript.call(agentSDK, text);
34586
+ };
34587
+ var origOnUserTranscript = agentSDK.onUserTranscript;
34588
+ agentSDK.onUserTranscript = function (utext, isFinal) {
34589
+ try {
34590
+ strip.updateUserTranscript(utext, isFinal);
34591
+ } catch (e) {
34592
+ console.warn('[TTPChatWidget] ecommerce strip user transcript:', e);
34593
+ }
34594
+ if (typeof origOnUserTranscript === 'function') {
34595
+ origOnUserTranscript.call(agentSDK, utext, isFinal);
34596
+ }
34597
+ };
34598
+ }
34599
+
34600
+ // Hide strip when call ends (config.onCallEnd is called by endVoiceCall())
34601
+ if (this.voiceInterface) {
34602
+ var _this$voiceInterface$2;
34603
+ var origOnCallEnd = this.voiceInterface.config.onCallEnd;
34604
+ this.voiceInterface.config.onCallEnd = function () {
34605
+ _this14._restoreDesktopEcommerceStripChrome();
34606
+ if (typeof origOnCallEnd === 'function') origOnCallEnd();
34607
+ };
34608
+
34609
+ // Feed timer ticks into the strip
34610
+ var origUpdateTimer = (_this$voiceInterface$2 = this.voiceInterface._updateTimerElements) === null || _this$voiceInterface$2 === void 0 ? void 0 : _this$voiceInterface$2.bind(this.voiceInterface);
34611
+ if (origUpdateTimer) {
34612
+ this.voiceInterface._updateTimerElements = function (timerText, elapsed) {
34613
+ strip.updateTimer(timerText);
34614
+ origUpdateTimer(timerText, elapsed);
34615
+ };
34616
+ }
34617
+ }
33255
34618
  }
33256
34619
  }, {
33257
34620
  key: "destroy",
@@ -33289,6 +34652,11 @@ var TTPChatWidget = /*#__PURE__*/function () {
33289
34652
  this._flavor.destroy();
33290
34653
  this._flavor = null;
33291
34654
  }
34655
+ // Clean up ecommerce voice strip
34656
+ if (this._ecommerceVoiceStrip) {
34657
+ this._ecommerceVoiceStrip.destroy();
34658
+ this._ecommerceVoiceStrip = null;
34659
+ }
33292
34660
  }
33293
34661
 
33294
34662
  // Delegated to VoiceInterface
@@ -33358,7 +34726,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
33358
34726
  key: "injectData",
33359
34727
  value: (function () {
33360
34728
  var _injectData = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(data) {
33361
- var _this$voiceInterface6;
34729
+ var _this$voiceInterface7;
33362
34730
  var options,
33363
34731
  voiceSDK,
33364
34732
  ack,
@@ -33383,7 +34751,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
33383
34751
  throw new Error('Voice call is not active');
33384
34752
  case 2:
33385
34753
  // Check if SDK is available
33386
- voiceSDK = (_this$voiceInterface6 = this.voiceInterface) === null || _this$voiceInterface6 === void 0 ? void 0 : _this$voiceInterface6.getVoiceSDK();
34754
+ voiceSDK = (_this$voiceInterface7 = this.voiceInterface) === null || _this$voiceInterface7 === void 0 ? void 0 : _this$voiceInterface7.getVoiceSDK();
33387
34755
  if (voiceSDK) {
33388
34756
  _context6.n = 3;
33389
34757
  break;
@@ -34330,6 +35698,7 @@ __webpack_require__.r(__webpack_exports__);
34330
35698
  /* harmony import */ var _voice_handlers_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./voice/handlers.js */ "./src/widget/voice/handlers.js");
34331
35699
  /* harmony import */ var _voice_desktop_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./voice/desktop.js */ "./src/widget/voice/desktop.js");
34332
35700
  /* harmony import */ var _voice_mobile_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./voice/mobile.js */ "./src/widget/voice/mobile.js");
35701
+ /* harmony import */ var _voice_rtlText_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./voice/rtlText.js */ "./src/widget/voice/rtlText.js");
34333
35702
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
34334
35703
  function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
34335
35704
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
@@ -34358,6 +35727,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
34358
35727
 
34359
35728
 
34360
35729
 
35730
+
34361
35731
  var VoiceInterface = /*#__PURE__*/function () {
34362
35732
  function VoiceInterface(config) {
34363
35733
  var _this$config$behavior, _config$agentSettings;
@@ -34772,13 +36142,20 @@ var VoiceInterface = /*#__PURE__*/function () {
34772
36142
  return this.getText('userTranscriptPrefix', 'userTranscriptPrefix');
34773
36143
  }
34774
36144
 
36145
+ /** Assistant / user live transcript and bubbles: RTL for `direction: 'rtl'` or Hebrew/Arabic `language`. */
36146
+ }, {
36147
+ key: "prefersRtlText",
36148
+ value: function prefersRtlText() {
36149
+ return (0,_voice_rtlText_js__WEBPACK_IMPORTED_MODULE_12__.configPrefersRtlText)(this.config);
36150
+ }
36151
+
34775
36152
  /**
34776
36153
  * Live user STT line: correct RTL/LTR order, visible gap after "אתה:", and sensible ? placement (utterance in dir=auto).
34777
36154
  */
34778
36155
  }, {
34779
36156
  key: "_renderUserLiveTranscriptSnippet",
34780
36157
  value: function _renderUserLiveTranscriptSnippet(userPrefix, text) {
34781
- var isRtl = this.config.direction === 'rtl';
36158
+ var isRtl = this.prefersRtlText();
34782
36159
  var raw = userPrefix !== undefined && userPrefix !== null ? String(userPrefix) : '';
34783
36160
  var trimmedPrefix = raw.replace(/\s+$/, '');
34784
36161
  var sepChar = isRtl ? "\xA0" : ' ';
@@ -35115,7 +36492,7 @@ var VoiceInterface = /*#__PURE__*/function () {
35115
36492
  value: function () {
35116
36493
  var _proceedWithVoiceCall = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
35117
36494
  var _this3 = this;
35118
- var isResumeCall, panel, header, toggleText, originalSection, compactSection, idleState, _idleState, activeState, voiceInterface, connected, serverRejected, originalOnError, originalOnDisconnected, attempts, _vs, vs, hasDisclaimers, sendDisclaimerAckAfterMobileMicReady, _vs$lastDisclaimerPay, disclaimerTexts, _this$config$inputFor, _this$config$connecti, micStream, primeSr, floatingButton, mobileFab, delayConfig, streamToUse, _this$sdk$voiceSDK, error, existingBar, _existingBar, _idleState2, _floatingButton, updateTimer, isDisclaimerDeclined, deviceInfo, _t, _t2, _t3, _t4, _t5;
36495
+ var isResumeCall, panel, header, toggleText, originalSection, compactSection, idleState, _idleState, activeState, stripOnly, voiceInterface, connected, serverRejected, originalOnError, originalOnDisconnected, attempts, _vs, vs, hasDisclaimers, sendDisclaimerAckAfterMobileMicReady, _vs$lastDisclaimerPay, disclaimerTexts, _this$config$inputFor, _this$config$connecti, micStream, primeSr, floatingButton, mobileFab, delayConfig, streamToUse, _this$sdk$voiceSDK, error, existingBar, _existingBar, _idleState2, _floatingButton, updateTimer, isDisclaimerDeclined, deviceInfo, _t, _t2, _t3, _t4, _t5;
35119
36496
  return _regenerator().w(function (_context4) {
35120
36497
  while (1) switch (_context4.p = _context4.n) {
35121
36498
  case 0:
@@ -35163,12 +36540,22 @@ var VoiceInterface = /*#__PURE__*/function () {
35163
36540
  this.showMobileCallBarAwaitingBrowserMic();
35164
36541
  this.applyMobileBarConnectingCopy();
35165
36542
  } else {
35166
- // Desktop: show active call UI while connecting
36543
+ // Desktop: full in-panel active UI, except flavor.callView:minimized (bottom strip only).
35167
36544
  this.resetConnectingState();
35168
36545
  _idleState = this.shadowRoot.getElementById('voiceIdleState');
35169
36546
  activeState = this.shadowRoot.getElementById('voiceActiveState');
35170
36547
  if (_idleState) _idleState.style.display = 'none';
35171
- if (activeState) activeState.style.display = 'flex';
36548
+ stripOnly = this.config.desktopMinimizedEcommerceStripUi === true;
36549
+ if (activeState) {
36550
+ activeState.style.display = stripOnly ? 'none' : 'flex';
36551
+ }
36552
+ if (stripOnly && typeof this.config.onDesktopMinimizedStripCallStart === 'function') {
36553
+ try {
36554
+ this.config.onDesktopMinimizedStripCallStart();
36555
+ } catch (e) {
36556
+ console.warn('[VoiceInterface] onDesktopMinimizedStripCallStart:', e);
36557
+ }
36558
+ }
35172
36559
  }
35173
36560
  voiceInterface = this.shadowRoot.getElementById('voiceInterface'); // Adjust sizes based on container dimensions
35174
36561
  setTimeout(function () {
@@ -35463,6 +36850,10 @@ var VoiceInterface = /*#__PURE__*/function () {
35463
36850
  this._clearChatPanelDisplayLock();
35464
36851
  _floatingButton = this.shadowRoot.getElementById('text-chat-button') || document.getElementById('text-chat-button');
35465
36852
  if (_floatingButton) _floatingButton.style.display = '';
36853
+ } else if (this.config.desktopMinimizedEcommerceStripUi === true && typeof this.config.onDesktopMinimizedStripLauncherRestore === 'function') {
36854
+ try {
36855
+ this.config.onDesktopMinimizedStripLauncherRestore();
36856
+ } catch (_) {}
35466
36857
  }
35467
36858
  throw _t4;
35468
36859
  case 29:
@@ -35627,6 +37018,13 @@ var VoiceInterface = /*#__PURE__*/function () {
35627
37018
  var activeState = this.shadowRoot.getElementById('voiceActiveState');
35628
37019
  if (idleState) idleState.style.display = 'flex';
35629
37020
  if (activeState) activeState.style.display = 'none';
37021
+ if (typeof this.config.onDesktopMinimizedStripLauncherRestore === 'function') {
37022
+ try {
37023
+ this.config.onDesktopMinimizedStripLauncherRestore();
37024
+ } catch (e) {
37025
+ console.warn('[VoiceInterface] onDesktopMinimizedStripLauncherRestore:', e);
37026
+ }
37027
+ }
35630
37028
  }
35631
37029
 
35632
37030
  /**
@@ -38058,6 +39456,7 @@ __webpack_require__.r(__webpack_exports__);
38058
39456
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
38059
39457
  /* harmony export */ Mobile: () => (/* binding */ Mobile)
38060
39458
  /* harmony export */ });
39459
+ /* harmony import */ var _rtlText_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rtlText.js */ "./src/widget/voice/rtlText.js");
38061
39460
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
38062
39461
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
38063
39462
  function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
@@ -38081,6 +39480,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
38081
39480
  * - _mobileCallStartTime: number | null
38082
39481
  */
38083
39482
 
39483
+
38084
39484
  var Mobile = /*#__PURE__*/function () {
38085
39485
  function Mobile(voiceInterface) {
38086
39486
  _classCallCheck(this, Mobile);
@@ -38131,11 +39531,11 @@ var Mobile = /*#__PURE__*/function () {
38131
39531
  // Create container div
38132
39532
  var container = document.createElement('div');
38133
39533
  container.id = 'mobile-voice-call-bar-container';
38134
- var textDir = this.config.direction === 'rtl' ? 'rtl' : 'ltr';
39534
+ var textDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.configPrefersRtlText)(this.config) ? 'rtl' : 'ltr';
38135
39535
  container.setAttribute('dir', textDir);
38136
39536
  container.innerHTML = "\n <!-- Duration Badge (hidden until first audio) -->\n <div class=\"mobile-duration-badge mobile-duration-badge--hidden\" id=\"mobileDurationBadge\">\n <div class=\"mobile-duration-dot\"></div>\n <span id=\"mobileDurationText\">00:00</span>\n </div>\n \n <!-- Main Bar -->\n <div class=\"mobile-voice-bar\">\n <!-- Top Row: Status, Waveform/Spinner, Controls -->\n <div class=\"mobile-top-row\">\n <!-- Status Indicator -->\n <div class=\"mobile-status-indicator\">\n <div class=\"mobile-status-dot\" id=\"mobileStatusDot\"></div>\n <span class=\"mobile-status-text\" id=\"mobileStatusText\">".concat(this.t('listening'), "</span>\n </div>\n \n <!-- Centered: Spinner (loading) or Waveform (after first audio) -->\n <div class=\"mobile-waveform-center\" id=\"mobileWaveform\">\n <div class=\"mobile-waveform-spinner\" id=\"mobileWaveformSpinner\" aria-hidden=\"false\">\n <div class=\"mobile-waveform-spinner-inner\"></div>\n </div>\n <div class=\"mobile-waveform-bars mobile-waveform-bars--hidden\" id=\"mobileWaveformBars\">\n ").concat(Array(7).fill(0).map(function (_, i) {
38137
39537
  return "<div class=\"mobile-waveform-bar\" data-index=\"".concat(i, "\"></div>");
38138
- }).join(''), "\n </div>\n </div>\n \n <!-- Controls -->\n <div class=\"mobile-controls\">\n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileMuteBtn\" aria-label=\"Mute microphone\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-icon\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-off-icon\" style=\"display: none;\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobilePauseBtn\" aria-label=\"Pause call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" class=\"mobile-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"#fff\" stroke=\"none\" class=\"mobile-play-icon\" style=\"display: none;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileSpeakerBtn\" aria-label=\"Toggle speaker\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-off-icon\" style=\"display: none;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-end-call-btn\" id=\"mobileEndCallBtn\" aria-label=\"End call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91\"/>\n <line x1=\"22\" x2=\"2\" y1=\"2\" y2=\"22\"/>\n </svg>\n </button>\n </div>\n </div>\n \n <!-- Bottom Row: Transcript (expandable) -->\n <button type=\"button\" class=\"mobile-transcript-row\" id=\"mobileTranscriptRow\">\n <p class=\"mobile-transcript-text\" id=\"mobileTranscriptText\" dir=\"").concat(textDir, "\">").concat(this.t('transcriptWillAppear'), "</p>\n <div class=\"mobile-transcript-footer\">\n <span class=\"mobile-expand-hint\">tap to expand & type</span>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"opacity: 0.6;\">\n <path d=\"M7 17L17 7M17 7H7M17 7V17\"/>\n </svg>\n </div>\n </button>\n\n <!-- Fly-to-cart target: always visible for animation (positioned where cart icon appears) -->\n <div id=\"ttpFlyToCartTarget\" class=\"ttp-fly-target\" aria-hidden=\"true\"></div>\n\n <!-- Cart Row - shown when items are in cart -->\n <div class=\"mobile-cart-row\" id=\"mobileCartRow\" style=\"display: none;\">\n <div class=\"mobile-cart-row__content\">\n <span class=\"mobile-cart-row__icon\" data-ttp-fly-target>\uD83D\uDED2</span>\n <span class=\"mobile-cart-row__text\" id=\"mobileCartText\">0 items \xB7 \u20AA0.00</span>\n </div>\n </div>\n </div>\n \n <!-- Expanded Transcript Overlay -->\n <div class=\"mobile-transcript-overlay\" id=\"mobileTranscriptOverlay\">\n <div class=\"mobile-expanded-transcript\" id=\"mobileExpandedTranscript\">\n <!-- Header -->\n <div class=\"mobile-transcript-header\">\n <div class=\"mobile-header-left\">\n <div class=\"mobile-header-status\">\n <div class=\"mobile-header-status-dot\"></div>\n <span class=\"mobile-header-status-text\" id=\"mobileHeaderStatusText\">").concat(this.t('listening'), "</span>\n <span class=\"mobile-header-duration\" id=\"mobileHeaderDuration\">00:00</span>\n </div>\n <span class=\"mobile-transcript-label\">CONVERSATION</span>\n </div>\n <button type=\"button\" class=\"mobile-close-transcript-btn\" id=\"mobileCloseTranscriptBtn\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n \n <!-- Messages Container -->\n <div class=\"mobile-messages-container\" id=\"mobileMessagesContainer\">\n <!-- Messages will be dynamically added here -->\n </div>\n \n <!-- Text Input Area -->\n <div class=\"mobile-input-area\">\n <div class=\"mobile-input-wrapper\">\n <input\n type=\"text\"\n placeholder=\"Type a message...\"\n id=\"mobileTextInput\"\n class=\"mobile-text-input\"\n dir=\"").concat(textDir, "\"\n />\n <button type=\"button\" class=\"mobile-send-btn\" id=\"mobileSendBtn\" disabled>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n <p class=\"mobile-input-hint\">\uD83C\uDF99\uFE0F ").concat(this.t('voiceActive') || 'Voice is still active', " \u2022 ").concat(this.t('typeOrSpeak') || 'Type or speak', "</p>\n </div>\n </div>\n </div>\n ");
39538
+ }).join(''), "\n </div>\n </div>\n \n <!-- Controls -->\n <div class=\"mobile-controls\">\n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileMuteBtn\" aria-label=\"Mute microphone\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-icon\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-off-icon\" style=\"display: none;\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobilePauseBtn\" aria-label=\"Pause call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" class=\"mobile-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"#fff\" stroke=\"none\" class=\"mobile-play-icon\" style=\"display: none;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileSpeakerBtn\" aria-label=\"Toggle speaker\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-off-icon\" style=\"display: none;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-end-call-btn\" id=\"mobileEndCallBtn\" aria-label=\"End call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91\"/>\n <line x1=\"22\" x2=\"2\" y1=\"2\" y2=\"22\"/>\n </svg>\n </button>\n </div>\n </div>\n \n <!-- Bottom Row: Transcript (expandable) -->\n <button type=\"button\" class=\"mobile-transcript-row\" id=\"mobileTranscriptRow\">\n <p class=\"mobile-transcript-text\" id=\"mobileTranscriptText\" dir=\"").concat(textDir, "\">").concat(this.t('transcriptWillAppear'), "</p>\n <div class=\"mobile-transcript-footer\">\n <span class=\"mobile-expand-hint\">tap to expand & type</span>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"opacity: 0.6;\">\n <path d=\"M7 17L17 7M17 7H7M17 7V17\"/>\n </svg>\n </div>\n </button>\n\n <!-- Fly-to-cart target: always visible for animation (positioned where cart icon appears) -->\n <div id=\"ttpFlyToCartTarget\" class=\"ttp-fly-target\" aria-hidden=\"true\"></div>\n </div>\n \n <!-- Expanded Transcript Overlay -->\n <div class=\"mobile-transcript-overlay\" id=\"mobileTranscriptOverlay\">\n <div class=\"mobile-expanded-transcript\" id=\"mobileExpandedTranscript\">\n <!-- Header -->\n <div class=\"mobile-transcript-header\">\n <div class=\"mobile-header-left\">\n <div class=\"mobile-header-status\">\n <div class=\"mobile-header-status-dot\"></div>\n <span class=\"mobile-header-status-text\" id=\"mobileHeaderStatusText\">").concat(this.t('listening'), "</span>\n <span class=\"mobile-header-duration\" id=\"mobileHeaderDuration\">00:00</span>\n </div>\n <span class=\"mobile-transcript-label\">CONVERSATION</span>\n </div>\n <button type=\"button\" class=\"mobile-close-transcript-btn\" id=\"mobileCloseTranscriptBtn\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n \n <!-- Messages Container -->\n <div class=\"mobile-messages-container\" id=\"mobileMessagesContainer\">\n <!-- Messages will be dynamically added here -->\n </div>\n \n <!-- Text Input Area -->\n <div class=\"mobile-input-area\">\n <div class=\"mobile-input-wrapper\">\n <input\n type=\"text\"\n placeholder=\"Type a message...\"\n id=\"mobileTextInput\"\n class=\"mobile-text-input\"\n dir=\"").concat(textDir, "\"\n />\n <button type=\"button\" class=\"mobile-send-btn\" id=\"mobileSendBtn\" disabled>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n <p class=\"mobile-input-hint\">\uD83C\uDF99\uFE0F ").concat(this.t('voiceActive') || 'Voice is still active', " \u2022 ").concat(this.t('typeOrSpeak') || 'Type or speak', "</p>\n </div>\n </div>\n </div>\n ");
38139
39539
 
38140
39540
  // Append to body
38141
39541
  document.body.appendChild(container);
@@ -38407,7 +39807,7 @@ var Mobile = /*#__PURE__*/function () {
38407
39807
  // Render message
38408
39808
  var messageEl = document.createElement('div');
38409
39809
  messageEl.className = "mobile-message-bubble ".concat(type);
38410
- messageEl.setAttribute('dir', this.config.direction === 'rtl' ? 'rtl' : 'ltr');
39810
+ messageEl.setAttribute('dir', (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(text, this.config));
38411
39811
  messageEl.textContent = text;
38412
39812
  messagesContainer.appendChild(messageEl);
38413
39813
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
@@ -38475,6 +39875,68 @@ var Mobile = /*#__PURE__*/function () {
38475
39875
 
38476
39876
  /***/ }),
38477
39877
 
39878
+ /***/ "./src/widget/voice/rtlText.js":
39879
+ /*!*************************************!*\
39880
+ !*** ./src/widget/voice/rtlText.js ***!
39881
+ \*************************************/
39882
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
39883
+
39884
+ "use strict";
39885
+ __webpack_require__.r(__webpack_exports__);
39886
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
39887
+ /* harmony export */ configPrefersRtlText: () => (/* binding */ configPrefersRtlText),
39888
+ /* harmony export */ dirForAssistantStreamingText: () => (/* binding */ dirForAssistantStreamingText),
39889
+ /* harmony export */ dirForTranscriptSnippet: () => (/* binding */ dirForTranscriptSnippet)
39890
+ /* harmony export */ });
39891
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
39892
+ /**
39893
+ * RTL for voice / transcript text when the widget is in a right-to-left locale.
39894
+ * Uses explicit `direction: 'rtl'` from merged config, or language base `he` / `ar`
39895
+ * when direction was not set (or was lost on nested config spreads).
39896
+ *
39897
+ * @param {{ direction?: string, language?: string } | null | undefined} config
39898
+ * @returns {boolean}
39899
+ */
39900
+ function configPrefersRtlText(config) {
39901
+ if (!config || _typeof(config) !== 'object') return false;
39902
+ if (config.direction === 'rtl') return true;
39903
+ var base = String(config.language || '').toLowerCase().split(/[-_]/)[0];
39904
+ return base === 'he' || base === 'ar';
39905
+ }
39906
+
39907
+ /**
39908
+ * `dir` for elements showing streamed assistant / agent text (and similar dynamic strings).
39909
+ * Uses `rtl` when the widget is explicitly RTL; otherwise `auto` so Hebrew (or other RTL)
39910
+ * agent copy still lays out correctly when Language/Direction are English + LTR.
39911
+ *
39912
+ * @param {{ direction?: string, language?: string } | null | undefined} config
39913
+ * @returns {'rtl' | 'auto'}
39914
+ */
39915
+ function dirForAssistantStreamingText(config) {
39916
+ return configPrefersRtlText(config) ? 'rtl' : 'auto';
39917
+ }
39918
+ var RE_HEBREW = /[\u0590-\u05FF]/;
39919
+ var RE_ARABIC = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/;
39920
+
39921
+ /**
39922
+ * `dir` for a single assistant / agent transcript string.
39923
+ * When the widget chrome is LTR but the line contains Hebrew or Arabic letters,
39924
+ * use explicit `rtl` so neutral punctuation (e.g. "?") stays at the logical sentence end.
39925
+ *
39926
+ * @param {string|null|undefined} text
39927
+ * @param {{ direction?: string, language?: string } | null | undefined} config
39928
+ * @returns {'rtl' | 'ltr' | 'auto'}
39929
+ */
39930
+ function dirForTranscriptSnippet(text, config) {
39931
+ if (configPrefersRtlText(config)) return 'rtl';
39932
+ var s = text != null ? String(text) : '';
39933
+ if (!s.trim()) return 'auto';
39934
+ if (RE_HEBREW.test(s) || RE_ARABIC.test(s)) return 'rtl';
39935
+ return 'ltr';
39936
+ }
39937
+
39938
+ /***/ }),
39939
+
38478
39940
  /***/ "./src/widget/voice/styles.js":
38479
39941
  /*!************************************!*\
38480
39942
  !*** ./src/widget/voice/styles.js ***!
@@ -38570,7 +40032,7 @@ var Styles = /*#__PURE__*/function () {
38570
40032
  key: "generateMobileCSS",
38571
40033
  value: function generateMobileCSS() {
38572
40034
  var important = ' !important';
38573
- return "\n /* Mobile Voice Call UI - Container positioning */\n #mobile-voice-call-bar-container {\n position: fixed !important;\n bottom: 0 !important;\n left: 0 !important;\n right: 0 !important;\n z-index: 99999 !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n padding: 0 12px 12px !important;\n pointer-events: none".concat(important, ";\n box-sizing: border-box !important;\n width: 100% !important;\n max-width: 100vw !important;\n margin: 0 !important;\n overflow: visible !important;\n isolation: isolate !important;\n }\n\n #mobile-voice-call-bar-container *,\n #mobile-voice-call-bar-container *::before,\n #mobile-voice-call-bar-container *::after {\n box-sizing: border-box !important;\n }\n\n /* Mobile loading: small spinner in waveform area until first audio */\n .mobile-waveform-spinner {\n position: absolute !important;\n left: 50% !important;\n top: 50% !important;\n transform: translate(-50%, -50%) !important;\n width: 16px !important;\n height: 16px !important;\n }\n\n .mobile-waveform-spinner-inner {\n width: 100% !important;\n height: 100% !important;\n border: 2px solid rgba(255,255,255,0.3) !important;\n border-top-color: #fff !important;\n border-radius: 50% !important;\n animation: mobileLoadingSpin 0.8s linear infinite !important;\n }\n\n .mobile-waveform-spinner--hidden {\n display: none !important;\n }\n\n @keyframes mobileLoadingSpin {\n to { transform: rotate(360deg); }\n }\n\n .mobile-waveform-bars--hidden {\n visibility: hidden !important;\n opacity: 0 !important;\n }\n\n .mobile-duration-badge--hidden {\n visibility: hidden !important;\n opacity: 0 !important;\n pointer-events: none !important;\n }\n\n .mobile-duration-badge {\n position: absolute !important;\n top: -28px !important;\n left: 50% !important;\n transform: translateX(-50%) !important;\n display: flex !important;\n align-items: center !important;\n gap: 6px !important;\n padding: 5px 14px !important;\n background: rgba(0,0,0,0.75) !important;\n border-radius: 20px !important;\n font-size: 12px !important;\n font-weight: 700 !important;\n color: #fff !important;\n backdrop-filter: blur(8px) !important;\n -webkit-backdrop-filter: blur(8px) !important;\n z-index: 10 !important;\n pointer-events: auto !important;\n visibility: visible !important;\n opacity: 1 !important;\n }\n\n /* Fly-to-cart target: invisible anchor for animation when cart row is hidden */\n .ttp-fly-target {\n position: absolute").concat(important, ";\n bottom: 14px").concat(important, ";\n left: 30px").concat(important, ";\n width: 24px").concat(important, ";\n height: 24px").concat(important, ";\n pointer-events: none").concat(important, ";\n }\n\n /* \u2500\u2500 Mobile Cart Row \u2500\u2500 */\n\n .mobile-cart-row {\n display: flex").concat(important, ";\n align-items: center").concat(important, ";\n justify-content: space-between").concat(important, ";\n padding: 7px 16px").concat(important, ";\n background: rgba(255, 255, 255, 0.08)").concat(important, ";\n border-top: 1px solid rgba(255, 255, 255, 0.1)").concat(important, ";\n border-radius: 0 0 16px 16px").concat(important, ";\n animation: mobileCartRowIn 0.25s ease-out").concat(important, ";\n pointer-events: auto").concat(important, ";\n }\n\n @keyframes mobileCartRowIn {\n from { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; }\n to { opacity: 1; max-height: 50px; padding-top: 7px; padding-bottom: 7px; }\n }\n\n .mobile-cart-row__content {\n display: flex").concat(important, ";\n align-items: center").concat(important, ";\n gap: 8px").concat(important, ";\n }\n\n .mobile-cart-row__icon {\n font-size: 13px").concat(important, ";\n }\n\n .mobile-cart-row__text {\n font-size: 12px").concat(important, ";\n font-weight: 600").concat(important, ";\n color: rgba(255, 255, 255, 0.9)").concat(important, ";\n letter-spacing: 0.2px").concat(important, ";\n }\n\n /* Shared mobile styles */\n ").concat(this._getSharedMobileCSS(important), "\n ");
40035
+ return "\n /* Mobile Voice Call UI - Container positioning */\n #mobile-voice-call-bar-container {\n position: fixed !important;\n bottom: 0 !important;\n left: 0 !important;\n right: 0 !important;\n z-index: 99999 !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n padding: 0 12px 12px !important;\n pointer-events: none".concat(important, ";\n box-sizing: border-box !important;\n width: 100% !important;\n max-width: 100vw !important;\n margin: 0 !important;\n overflow: visible !important;\n isolation: isolate !important;\n }\n\n #mobile-voice-call-bar-container *,\n #mobile-voice-call-bar-container *::before,\n #mobile-voice-call-bar-container *::after {\n box-sizing: border-box !important;\n }\n\n /* Mobile loading: small spinner in waveform area until first audio */\n .mobile-waveform-spinner {\n position: absolute !important;\n left: 50% !important;\n top: 50% !important;\n transform: translate(-50%, -50%) !important;\n width: 16px !important;\n height: 16px !important;\n }\n\n .mobile-waveform-spinner-inner {\n width: 100% !important;\n height: 100% !important;\n border: 2px solid rgba(255,255,255,0.3) !important;\n border-top-color: #fff !important;\n border-radius: 50% !important;\n animation: mobileLoadingSpin 0.8s linear infinite !important;\n }\n\n .mobile-waveform-spinner--hidden {\n display: none !important;\n }\n\n @keyframes mobileLoadingSpin {\n to { transform: rotate(360deg); }\n }\n\n .mobile-waveform-bars--hidden {\n visibility: hidden !important;\n opacity: 0 !important;\n }\n\n .mobile-duration-badge--hidden {\n visibility: hidden !important;\n opacity: 0 !important;\n pointer-events: none !important;\n }\n\n .mobile-duration-badge {\n position: absolute !important;\n top: -28px !important;\n left: 50% !important;\n transform: translateX(-50%) !important;\n display: flex !important;\n align-items: center !important;\n gap: 6px !important;\n padding: 5px 14px !important;\n background: rgba(0,0,0,0.75) !important;\n border-radius: 20px !important;\n font-size: 12px !important;\n font-weight: 700 !important;\n color: #fff !important;\n backdrop-filter: blur(8px) !important;\n -webkit-backdrop-filter: blur(8px) !important;\n z-index: 10 !important;\n pointer-events: auto !important;\n visibility: visible !important;\n opacity: 1 !important;\n }\n\n /* Fly-to-cart target: invisible anchor for animation when cart row is hidden */\n .ttp-fly-target {\n position: absolute").concat(important, ";\n bottom: 14px").concat(important, ";\n left: 30px").concat(important, ";\n width: 24px").concat(important, ";\n height: 24px").concat(important, ";\n pointer-events: none").concat(important, ";\n }\n\n /* Shared mobile styles */\n ").concat(this._getSharedMobileCSS(important), "\n ");
38574
40036
  }
38575
40037
  }]);
38576
40038
  }();
@@ -38591,6 +40053,7 @@ __webpack_require__.r(__webpack_exports__);
38591
40053
  /* harmony export */ resolveIdleHeaderAvatarUrl: () => (/* binding */ resolveIdleHeaderAvatarUrl),
38592
40054
  /* harmony export */ sanitizeHeaderAvatarImageUrl: () => (/* binding */ sanitizeHeaderAvatarImageUrl)
38593
40055
  /* harmony export */ });
40056
+ /* harmony import */ var _rtlText_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rtlText.js */ "./src/widget/voice/rtlText.js");
38594
40057
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
38595
40058
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
38596
40059
  function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
@@ -38611,6 +40074,8 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
38611
40074
  * - None (stateless template generation)
38612
40075
  */
38613
40076
 
40077
+
40078
+
38614
40079
  /** Allow only http(s) absolute URLs for idle header avatar images. */
38615
40080
  function sanitizeHeaderAvatarImageUrl(raw) {
38616
40081
  var s = raw != null && String(raw).trim() || '';
@@ -38686,7 +40151,7 @@ var Templates = /*#__PURE__*/function () {
38686
40151
  }, {
38687
40152
  key: "generateHTML",
38688
40153
  value: function generateHTML() {
38689
- var _this$config$whatsapp;
40154
+ var _this$config$behavior, _this$config$whatsapp;
38690
40155
  // Mobile UI - minimized bar design
38691
40156
  if (this.voiceInterface.isMobile) {
38692
40157
  return this.generateMobileHTML();
@@ -38702,8 +40167,11 @@ var Templates = /*#__PURE__*/function () {
38702
40167
  var headerAvatarUrl = resolveIdleHeaderAvatarUrl(this.config);
38703
40168
  var idleAvatarInner = headerAvatarUrl ? "<img src=\"".concat(escapeHtmlAttr(headerAvatarUrl), "\" alt=\"\" class=\"ttp-avatar-img\" />") : avatarInitial;
38704
40169
  var idleAvatarClass = headerAvatarUrl ? 'ttp-avatar ttp-avatar--image' : 'ttp-avatar';
38705
- var textDir = this.config.direction === 'rtl' ? 'rtl' : 'ltr';
38706
- return "<div class=\"voice-interface\" id=\"voiceInterface\">\n <!-- Idle View -->\n <div id=\"voiceIdleState\">\n <div class=\"ttp-hero\">\n <div class=\"ttp-header\">\n <div class=\"ttp-agent\">\n <div class=\"ttp-avatar-wrap\">\n <div class=\"".concat(idleAvatarClass, "\">").concat(idleAvatarInner, "</div>\n <div class=\"ttp-online-dot\"></div>\n </div>\n <div>\n <div class=\"ttp-agent-name\">").concat(agentName, "</div>\n <div class=\"ttp-agent-role\">").concat(agentRole, "</div>\n </div>\n </div>\n <button type=\"button\" class=\"ttp-close-btn\" id=\"ttpCloseIdle\" aria-label=\"Close widget\">\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M10 2L2 10M2 2l8 8\"/>\n </svg>\n </button>\n </div>\n <div class=\"ttp-headline\">").concat(headline, "</div>\n <div class=\"ttp-subline\">").concat(subline, "</div>\n </div>\n\n <div class=\"ttp-body\">\n <button type=\"button\" class=\"ttp-btn-primary\" id=\"startCallBtn\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"").concat(this.config.startCallButtonTextColor || 'white', "\" stroke-width=\"2\">\n <path d=\"M12 2a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V5a3 3 0 0 1 3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2M12 19v3M8 22h8\"/>\n </svg>\n ").concat(this.getText('startCallButtonText', 'startCall') || 'Start Voice Call', "\n </button>\n <button type=\"button\" class=\"ttp-btn-secondary\" id=\"startTextBtn\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n ").concat(this.config.sendMessageText || this.getText('sendMessageText', 'sendMessage') || 'Send a Message', "\n </button>\n ").concat((_this$config$whatsapp = this.config.whatsapp) !== null && _this$config$whatsapp !== void 0 && _this$config$whatsapp.number ? "\n <a class=\"ttp-btn-wa\" id=\"ttpWhatsAppBtn\"\n href=\"https://wa.me/".concat(this.config.whatsapp.number).concat(this.config.whatsapp.text ? '?text=' + encodeURIComponent(this.config.whatsapp.text) : '', "\"\n target=\"_blank\" rel=\"noopener\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"#25d366\">\n <path d=\"M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.655-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z\"/>\n </svg>\n WhatsApp\n </a>\n ") : '', "\n </div>\n </div>\n\n <!-- Active Call View -->\n <div id=\"voiceActiveState\" style=\"display: none;\">\n <!-- Full voice section (hidden when items/rooms are shown) -->\n <div class=\"desktop-voice-section\" id=\"desktopVoiceSection\">\n <div class=\"ttp-call-hero\">\n <div class=\"ttp-call-topbar\">\n <div class=\"ttp-rec\">\n <div class=\"ttp-rec-dot\"></div>\n <span class=\"ttp-timer\" id=\"desktopTimerText\">00:00</span>\n </div>\n <span class=\"ttp-call-label\">Voice call active</span>\n <button type=\"button\" class=\"ttp-close-btn\" id=\"ttpCloseActive\" aria-label=\"Close widget\">\n <svg width=\"9\" height=\"9\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M10 2L2 10M2 2l8 8\"/>\n </svg>\n </button>\n </div>\n\n <div class=\"ttp-orb-wrap\">\n <div class=\"ttp-orb-rings\">\n <div class=\"ttp-ring ttp-ring-1\"></div>\n <div class=\"ttp-ring ttp-ring-2\"></div>\n <div class=\"ttp-orb\" id=\"desktopMainWaveform\">\n <div class=\"ttp-wave\" id=\"ttp-wave\"></div>\n </div>\n </div>\n <div class=\"ttp-listen-label\" id=\"desktopStatusText\">").concat(this.t('listening') || 'Listening to you...', "</div>\n </div>\n </div>\n\n <div class=\"ttp-controls\">\n <button type=\"button\" class=\"ttp-ctrl-btn\" id=\"desktopMuteBtn\" title=\"").concat(this.getTooltip('mute'), "\" aria-label=\"Mute microphone\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-mic-icon\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-mic-off-icon\" style=\"display: none; position: absolute;\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-ctrl-btn\" id=\"desktopPauseBtn\" title=\"Pause call\" aria-label=\"Pause call\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" class=\"desktop-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" class=\"desktop-play-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-ctrl-btn\" id=\"desktopSpeakerBtn\" title=\"").concat(this.getTooltip('speaker'), "\" aria-label=\"Toggle speaker\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-speaker-off-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-end-btn\" id=\"desktopEndCallBtn\" title=\"").concat(this.getTooltip('endCall'), "\" aria-label=\"End call\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.99 12 19.79 19.79 0 0 1 1.97 3.4 2 2 0 0 1 3.95 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Compact voice section (shown when items/rooms are visible) -->\n <div class=\"compact-voice-section\" id=\"compactVoiceSection\" style=\"display: none;\">\n <div class=\"compact-left\">\n <div class=\"compact-avatar\" id=\"desktopAvatarCompact\">\n <div class=\"ttp-orb compact-orb\" id=\"desktopMainWaveformCompact\">\n <div class=\"ttp-wave\"></div>\n </div>\n </div>\n <div class=\"compact-info\">\n <div class=\"compact-timer\" id=\"desktopTimerCompact\">\n <span class=\"ttp-rec-dot\"></span>\n <span id=\"compactTimerText\">00:00</span>\n </div>\n <div class=\"compact-status\" id=\"desktopStatusCompact\">\n <span id=\"compactStatusText\">").concat(this.t('listening') || 'Listening...', "</span>\n </div>\n </div>\n </div>\n <div class=\"compact-controls\">\n <button type=\"button\" class=\"compact-control-btn secondary\" id=\"desktopMuteBtnCompact\" title=\"").concat(this.getTooltip('mute'), "\" aria-label=\"Mute microphone\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-mic-icon\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-mic-off-icon\" style=\"display: none; position: absolute;\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"compact-control-btn secondary\" id=\"desktopPauseBtnCompact\" title=\"Pause call\" aria-label=\"Pause call\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" class=\"desktop-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" class=\"desktop-play-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"compact-control-btn secondary\" id=\"desktopSpeakerBtnCompact\" title=\"").concat(this.getTooltip('speaker'), "\" aria-label=\"Toggle speaker\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-speaker-off-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"compact-control-btn danger\" id=\"desktopEndCallBtnCompact\" title=\"").concat(this.getTooltip('endCall'), "\" aria-label=\"End call\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" style=\"overflow: visible;\">\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.99 12 19.79 19.79 0 0 1 1.97 3.4 2 2 0 0 1 3.95 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\" transform=\"rotate(135 12 12)\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Transcript -->\n <div class=\"ttp-transcript desktop-conversation-panel\" id=\"desktopConversationPanel\" dir=\"").concat(textDir, "\">\n <div class=\"ttp-transcript-hdr\" id=\"conversationHeader\">\n <span class=\"ttp-live-label\">Live</span>\n <span class=\"ttp-toggle-text\" id=\"conversationToggleText\">Show history</span>\n <div class=\"ttp-dots\">\n <div class=\"ttp-dot\"></div>\n <div class=\"ttp-dot\"></div>\n <div class=\"ttp-dot\"></div>\n </div>\n </div>\n <div class=\"live-transcript-collapsed\" id=\"liveTranscriptCollapsed\">\n <div class=\"ttp-transcript-text\" id=\"liveTextCollapsed\">\n ").concat(this.t('transcriptWillAppear') || 'Transcript will appear here...', "<span class=\"ttp-cursor\"></span>\n </div>\n </div>\n <div class=\"conversation-history\" id=\"conversationHistory\">\n <div class=\"conversation-messages\" id=\"conversationMessages\"></div>\n <div class=\"live-message-row\" id=\"liveMessageRow\" style=\"display: none;\">\n <div class=\"message-bubble\" dir=\"").concat(textDir, "\">\n <span class=\"live-badge\">Live</span>\n <span id=\"liveMessageText\"></span><span class=\"ttp-cursor\"></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Type row -->\n <div class=\"ttp-type-row voice-text-input-area\">\n <input class=\"ttp-input\" type=\"text\" id=\"voiceTextInput\" dir=\"").concat(textDir, "\" placeholder=\"").concat(this.getText('inputPlaceholder', 'typeMessage') || 'Or type here…', "\" aria-label=\"Type a message\" />\n <button type=\"button\" class=\"ttp-send-btn\" id=\"voiceSendBtn\" aria-label=\"Send message\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\">\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/>\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\"/>\n </svg>\n </button>\n </div>\n </div>\n </div>");
40170
+ var textDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.configPrefersRtlText)(this.config) ? 'rtl' : 'ltr';
40171
+ var transcriptPlaceholder = this.t('transcriptWillAppear') || '';
40172
+ var transcriptDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(transcriptPlaceholder, this.config);
40173
+ var voiceInputDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForAssistantStreamingText)(this.config);
40174
+ return "<div class=\"voice-interface\" id=\"voiceInterface\">\n <!-- Idle View -->\n <div id=\"voiceIdleState\">\n <div class=\"ttp-hero\">\n <div class=\"ttp-header\">\n <div class=\"ttp-agent\">\n <div class=\"ttp-avatar-wrap\">\n <div class=\"".concat(idleAvatarClass, "\">").concat(idleAvatarInner, "</div>\n <div class=\"ttp-online-dot\"></div>\n </div>\n <div>\n <div class=\"ttp-agent-name\">").concat(agentName, "</div>\n <div class=\"ttp-agent-role\">").concat(agentRole, "</div>\n </div>\n </div>\n <button type=\"button\" class=\"ttp-close-btn\" id=\"ttpCloseIdle\" aria-label=\"Close widget\">\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M10 2L2 10M2 2l8 8\"/>\n </svg>\n </button>\n </div>\n <div class=\"ttp-headline\">").concat(headline, "</div>\n <div class=\"ttp-subline\">").concat(subline, "</div>\n </div>\n\n <div class=\"ttp-body\">\n <button type=\"button\" class=\"ttp-btn-primary\" id=\"startCallBtn\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"").concat(this.config.startCallButtonTextColor || 'white', "\" stroke-width=\"2\">\n <path d=\"M12 2a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V5a3 3 0 0 1 3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2M12 19v3M8 22h8\"/>\n </svg>\n ").concat(this.getText('startCallButtonText', 'startCall') || 'Start Voice Call', "\n </button>\n ").concat((((_this$config$behavior = this.config.behavior) === null || _this$config$behavior === void 0 ? void 0 : _this$config$behavior.mode) || 'unified') !== 'voice-only' ? "\n <button type=\"button\" class=\"ttp-btn-secondary\" id=\"startTextBtn\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n ".concat(this.config.sendMessageText || this.getText('sendMessageText', 'sendMessage') || 'Send a Message', "\n </button>\n ") : '', "\n ").concat((_this$config$whatsapp = this.config.whatsapp) !== null && _this$config$whatsapp !== void 0 && _this$config$whatsapp.number ? "\n <a class=\"ttp-btn-wa\" id=\"ttpWhatsAppBtn\"\n href=\"https://wa.me/".concat(this.config.whatsapp.number).concat(this.config.whatsapp.text ? '?text=' + encodeURIComponent(this.config.whatsapp.text) : '', "\"\n target=\"_blank\" rel=\"noopener\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"#25d366\">\n <path d=\"M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.655-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z\"/>\n </svg>\n WhatsApp\n </a>\n ") : '', "\n </div>\n </div>\n\n <!-- Active Call View -->\n <div id=\"voiceActiveState\" style=\"display: none;\">\n <!-- Full voice section (hidden when items/rooms are shown) -->\n <div class=\"desktop-voice-section\" id=\"desktopVoiceSection\">\n <div class=\"ttp-call-hero\">\n <div class=\"ttp-call-topbar\">\n <div class=\"ttp-rec\">\n <div class=\"ttp-rec-dot\"></div>\n <span class=\"ttp-timer\" id=\"desktopTimerText\">00:00</span>\n </div>\n <span class=\"ttp-call-label\">Voice call active</span>\n <button type=\"button\" class=\"ttp-close-btn\" id=\"ttpCloseActive\" aria-label=\"Close widget\">\n <svg width=\"9\" height=\"9\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M10 2L2 10M2 2l8 8\"/>\n </svg>\n </button>\n </div>\n\n <div class=\"ttp-orb-wrap\">\n <div class=\"ttp-orb-rings\">\n <div class=\"ttp-ring ttp-ring-1\"></div>\n <div class=\"ttp-ring ttp-ring-2\"></div>\n <div class=\"ttp-orb\" id=\"desktopMainWaveform\">\n <div class=\"ttp-wave\" id=\"ttp-wave\"></div>\n </div>\n </div>\n <div class=\"ttp-listen-label\" id=\"desktopStatusText\">").concat(this.t('listening') || 'Listening to you...', "</div>\n </div>\n </div>\n\n <div class=\"ttp-controls\">\n <button type=\"button\" class=\"ttp-ctrl-btn\" id=\"desktopMuteBtn\" title=\"").concat(this.getTooltip('mute'), "\" aria-label=\"Mute microphone\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-mic-icon\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-mic-off-icon\" style=\"display: none; position: absolute;\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-ctrl-btn\" id=\"desktopPauseBtn\" title=\"Pause call\" aria-label=\"Pause call\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" class=\"desktop-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" class=\"desktop-play-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-ctrl-btn\" id=\"desktopSpeakerBtn\" title=\"").concat(this.getTooltip('speaker'), "\" aria-label=\"Toggle speaker\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-speaker-off-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"ttp-end-btn\" id=\"desktopEndCallBtn\" title=\"").concat(this.getTooltip('endCall'), "\" aria-label=\"End call\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.99 12 19.79 19.79 0 0 1 1.97 3.4 2 2 0 0 1 3.95 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Compact voice section (shown when items/rooms are visible) -->\n <div class=\"compact-voice-section\" id=\"compactVoiceSection\" style=\"display: none;\">\n <div class=\"compact-left\">\n <div class=\"compact-avatar\" id=\"desktopAvatarCompact\">\n <div class=\"ttp-orb compact-orb\" id=\"desktopMainWaveformCompact\">\n <div class=\"ttp-wave\"></div>\n </div>\n </div>\n <div class=\"compact-info\">\n <div class=\"compact-timer\" id=\"desktopTimerCompact\">\n <span class=\"ttp-rec-dot\"></span>\n <span id=\"compactTimerText\">00:00</span>\n </div>\n <div class=\"compact-status\" id=\"desktopStatusCompact\">\n <span id=\"compactStatusText\">").concat(this.t('listening') || 'Listening...', "</span>\n </div>\n </div>\n </div>\n <div class=\"compact-controls\">\n <button type=\"button\" class=\"compact-control-btn secondary\" id=\"desktopMuteBtnCompact\" title=\"").concat(this.getTooltip('mute'), "\" aria-label=\"Mute microphone\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-mic-icon\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-mic-off-icon\" style=\"display: none; position: absolute;\">\n <path d=\"M12 1a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V4a3 3 0 0 1 3-3z\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"compact-control-btn secondary\" id=\"desktopPauseBtnCompact\" title=\"Pause call\" aria-label=\"Pause call\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" class=\"desktop-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" class=\"desktop-play-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"compact-control-btn secondary\" id=\"desktopSpeakerBtnCompact\" title=\"").concat(this.getTooltip('speaker'), "\" aria-label=\"Toggle speaker\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" class=\"desktop-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"desktop-speaker-off-icon\" style=\"display: none; position: absolute;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" stroke=\"currentColor\" stroke-width=\"1.8\" fill=\"none\"/>\n <line x1=\"5\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\" opacity=\"0.85\"/>\n </svg>\n </button>\n <button type=\"button\" class=\"compact-control-btn danger\" id=\"desktopEndCallBtnCompact\" title=\"").concat(this.getTooltip('endCall'), "\" aria-label=\"End call\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" style=\"overflow: visible;\">\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.99 12 19.79 19.79 0 0 1 1.97 3.4 2 2 0 0 1 3.95 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\" transform=\"rotate(135 12 12)\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Transcript -->\n <div class=\"ttp-transcript desktop-conversation-panel\" id=\"desktopConversationPanel\" dir=\"").concat(textDir, "\">\n <div class=\"ttp-transcript-hdr\" id=\"conversationHeader\">\n <span class=\"ttp-live-label\">Live</span>\n <span class=\"ttp-toggle-text\" id=\"conversationToggleText\">Show history</span>\n <div class=\"ttp-dots\">\n <div class=\"ttp-dot\"></div>\n <div class=\"ttp-dot\"></div>\n <div class=\"ttp-dot\"></div>\n </div>\n </div>\n <div class=\"live-transcript-collapsed\" id=\"liveTranscriptCollapsed\">\n <div class=\"ttp-transcript-text\" id=\"liveTextCollapsed\" dir=\"").concat(transcriptDir, "\">\n ").concat(this.t('transcriptWillAppear') || 'Transcript will appear here...', "<span class=\"ttp-cursor\"></span>\n </div>\n </div>\n <div class=\"conversation-history\" id=\"conversationHistory\">\n <div class=\"conversation-messages\" id=\"conversationMessages\"></div>\n <div class=\"live-message-row\" id=\"liveMessageRow\" style=\"display: none;\">\n <div class=\"message-bubble\" dir=\"").concat(transcriptDir, "\">\n <span class=\"live-badge\">Live</span>\n <span id=\"liveMessageText\"></span><span class=\"ttp-cursor\"></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Type row -->\n <div class=\"ttp-type-row voice-text-input-area\">\n <input class=\"ttp-input\" type=\"text\" id=\"voiceTextInput\" dir=\"").concat(voiceInputDir, "\" placeholder=\"").concat(this.getText('inputPlaceholder', 'typeMessage') || 'Or type here…', "\" aria-label=\"Type a message\" />\n <button type=\"button\" class=\"ttp-send-btn\" id=\"voiceSendBtn\" aria-label=\"Send message\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\">\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/>\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\"/>\n </svg>\n </button>\n </div>\n </div>\n </div>");
38707
40175
  }
38708
40176
 
38709
40177
  /**
@@ -38713,13 +40181,16 @@ var Templates = /*#__PURE__*/function () {
38713
40181
  key: "generateMobileHTML",
38714
40182
  value: function generateMobileHTML() {
38715
40183
  // For mobile, idle state goes in widget, but active state will be appended to body
38716
- var textDir = this.config.direction === 'rtl' ? 'rtl' : 'ltr';
40184
+ var textDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.configPrefersRtlText)(this.config) ? 'rtl' : 'ltr';
40185
+ var transcriptPlaceholder = this.t('transcriptWillAppear') || '';
40186
+ var transcriptDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(transcriptPlaceholder, this.config);
40187
+ var voiceInputDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForAssistantStreamingText)(this.config);
38717
40188
  var headerAvatarUrl = resolveIdleHeaderAvatarUrl(this.config);
38718
40189
  var mobileAvatarInner = headerAvatarUrl ? "<img src=\"".concat(escapeHtmlAttr(headerAvatarUrl), "\" alt=\"\" class=\"voice-avatar-img\" />") : '🤖';
38719
40190
  var mobileAvatarClass = headerAvatarUrl ? 'voice-avatar voice-avatar--image' : 'voice-avatar';
38720
40191
  return "<div class=\"voice-interface mobile-voice-interface\" id=\"voiceInterface\" dir=\"".concat(textDir, "\">\n <!-- Before Call State (same as desktop) -->\n <div id=\"voiceIdleState\">\n <div class=\"").concat(mobileAvatarClass, "\" id=\"voiceAvatar\">").concat(mobileAvatarInner, "</div>\n <div class=\"voice-status\">\n <div class=\"voice-status-title\">").concat(this.getText('startCallTitle', 'clickToStartCall'), "</div>\n <div class=\"voice-status-subtitle\">").concat(this.getText('startCallSubtitle', 'realTimeVoice'), "</div>\n </div>\n <button type=\"button\" class=\"start-call-btn\" id=\"startCallBtn\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M20.01 15.38c-1.23 0-2.42-.2-3.53-.56-.35-.12-.74-.03-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z\"/>\n </svg>\n <span>").concat(this.getText('startCallButtonText', 'startCall'), "</span>\n </button>\n </div>\n \n <!-- Mobile Active Call State - Hidden placeholder (actual bar will be appended to body) -->\n <div id=\"voiceActiveState\" class=\"mobile-active-state\" style=\"display: none;\">\n <!-- Duration Badge -->\n <div class=\"mobile-duration-badge\" id=\"mobileDurationBadge\">\n <div class=\"mobile-duration-dot\"></div>\n <span id=\"mobileDurationText\">00:00</span>\n </div>\n \n <!-- Main Bar -->\n <div class=\"mobile-voice-bar\">\n <!-- Top Row: Status, Waveform, Controls -->\n <div class=\"mobile-top-row\">\n <!-- Status Indicator -->\n <div class=\"mobile-status-indicator\">\n <div class=\"mobile-status-dot\" id=\"mobileStatusDot\"></div>\n <span class=\"mobile-status-text\" id=\"mobileStatusText\">Listening...</span>\n </div>\n \n <!-- Centered Waveform -->\n <div class=\"mobile-waveform-center\" id=\"mobileWaveform\">\n ").concat(Array(7).fill(0).map(function (_, i) {
38721
40192
  return "<div class=\"mobile-waveform-bar\" data-index=\"".concat(i, "\"></div>");
38722
- }).join(''), "\n </div>\n \n <!-- Controls -->\n <div class=\"mobile-controls\">\n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileMuteBtn\" aria-label=\"Mute microphone\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-icon\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-off-icon\" style=\"display: none;\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobilePauseBtn\" aria-label=\"Pause call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" class=\"mobile-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"#fff\" stroke=\"none\" class=\"mobile-play-icon\" style=\"display: none;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n\n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileSpeakerBtn\" aria-label=\"Toggle speaker\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-off-icon\" style=\"display: none;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n\n <button type=\"button\" class=\"mobile-end-call-btn\" id=\"mobileEndCallBtn\" aria-label=\"End call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91\"/>\n <line x1=\"22\" x2=\"2\" y1=\"2\" y2=\"22\"/>\n </svg>\n </button>\n </div>\n </div>\n \n <!-- Bottom Row: Transcript (expandable) -->\n <button type=\"button\" class=\"mobile-transcript-row\" id=\"mobileTranscriptRow\">\n <p class=\"mobile-transcript-text\" id=\"mobileTranscriptText\" dir=\"").concat(textDir, "\">").concat(this.t('transcriptWillAppear'), "</p>\n <div class=\"mobile-transcript-footer\">\n <span class=\"mobile-expand-hint\">tap to expand & type</span>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"opacity: 0.6;\">\n <path d=\"M7 17L17 7M17 7H7M17 7V17\"/>\n </svg>\n </div>\n </button>\n </div>\n </div>\n \n <!-- Expanded Transcript Overlay -->\n <div class=\"mobile-transcript-overlay\" id=\"mobileTranscriptOverlay\">\n <div class=\"mobile-expanded-transcript\" id=\"mobileExpandedTranscript\">\n <!-- Header -->\n <div class=\"mobile-transcript-header\">\n <div class=\"mobile-header-left\">\n <div class=\"mobile-header-status\">\n <div class=\"mobile-header-status-dot\"></div>\n <span class=\"mobile-header-status-text\" id=\"mobileHeaderStatusText\">Listening...</span>\n <span class=\"mobile-header-duration\" id=\"mobileHeaderDuration\">00:00</span>\n </div>\n <span class=\"mobile-transcript-label\">CONVERSATION</span>\n </div>\n <button type=\"button\" class=\"mobile-close-transcript-btn\" id=\"mobileCloseTranscriptBtn\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n \n <!-- Messages Container -->\n <div class=\"mobile-messages-container\" id=\"mobileMessagesContainer\">\n <!-- Messages will be dynamically added here -->\n </div>\n \n <!-- Text Input Area -->\n <div class=\"mobile-input-area\">\n <div class=\"mobile-input-wrapper\">\n <input\n type=\"text\"\n placeholder=\"Type a message...\"\n id=\"mobileTextInput\"\n class=\"mobile-text-input\"\n dir=\"").concat(textDir, "\"\n />\n <button type=\"button\" class=\"mobile-send-btn\" id=\"mobileSendBtn\" disabled>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n <p class=\"mobile-input-hint\">\uD83C\uDF99\uFE0F Voice is still active \u2022 Type or speak</p>\n </div>\n </div>\n </div>\n </div>");
40193
+ }).join(''), "\n </div>\n \n <!-- Controls -->\n <div class=\"mobile-controls\">\n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileMuteBtn\" aria-label=\"Mute microphone\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-icon\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-mic-off-icon\" style=\"display: none;\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n \n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobilePauseBtn\" aria-label=\"Pause call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" class=\"mobile-pause-icon\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"#fff\" stroke=\"none\" class=\"mobile-play-icon\" style=\"display: none;\">\n <polygon points=\"6,4 20,12 6,20\"/>\n </svg>\n </button>\n\n <button type=\"button\" class=\"mobile-control-btn\" id=\"mobileSpeakerBtn\" aria-label=\"Toggle speaker\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-icon\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n </svg>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"mobile-speaker-off-icon\" style=\"display: none;\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"/>\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/>\n <line x1=\"4\" y1=\"4\" x2=\"20\" y2=\"20\" stroke=\"#ef4444\" stroke-width=\"1.5\" opacity=\"0.85\"/>\n </svg>\n </button>\n\n <button type=\"button\" class=\"mobile-end-call-btn\" id=\"mobileEndCallBtn\" aria-label=\"End call\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91\"/>\n <line x1=\"22\" x2=\"2\" y1=\"2\" y2=\"22\"/>\n </svg>\n </button>\n </div>\n </div>\n \n <!-- Bottom Row: Transcript (expandable) -->\n <button type=\"button\" class=\"mobile-transcript-row\" id=\"mobileTranscriptRow\">\n <p class=\"mobile-transcript-text\" id=\"mobileTranscriptText\" dir=\"").concat(transcriptDir, "\">").concat(this.t('transcriptWillAppear'), "</p>\n <div class=\"mobile-transcript-footer\">\n <span class=\"mobile-expand-hint\">tap to expand & type</span>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"opacity: 0.6;\">\n <path d=\"M7 17L17 7M17 7H7M17 7V17\"/>\n </svg>\n </div>\n </button>\n </div>\n </div>\n \n <!-- Expanded Transcript Overlay -->\n <div class=\"mobile-transcript-overlay\" id=\"mobileTranscriptOverlay\">\n <div class=\"mobile-expanded-transcript\" id=\"mobileExpandedTranscript\">\n <!-- Header -->\n <div class=\"mobile-transcript-header\">\n <div class=\"mobile-header-left\">\n <div class=\"mobile-header-status\">\n <div class=\"mobile-header-status-dot\"></div>\n <span class=\"mobile-header-status-text\" id=\"mobileHeaderStatusText\">Listening...</span>\n <span class=\"mobile-header-duration\" id=\"mobileHeaderDuration\">00:00</span>\n </div>\n <span class=\"mobile-transcript-label\">CONVERSATION</span>\n </div>\n <button type=\"button\" class=\"mobile-close-transcript-btn\" id=\"mobileCloseTranscriptBtn\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n \n <!-- Messages Container -->\n <div class=\"mobile-messages-container\" id=\"mobileMessagesContainer\">\n <!-- Messages will be dynamically added here -->\n </div>\n \n <!-- Text Input Area -->\n <div class=\"mobile-input-area\">\n <div class=\"mobile-input-wrapper\">\n <input\n type=\"text\"\n placeholder=\"Type a message...\"\n id=\"mobileTextInput\"\n class=\"mobile-text-input\"\n dir=\"").concat(voiceInputDir, "\"\n />\n <button type=\"button\" class=\"mobile-send-btn\" id=\"mobileSendBtn\" disabled>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n <p class=\"mobile-input-hint\">\uD83C\uDF99\uFE0F Voice is still active \u2022 Type or speak</p>\n </div>\n </div>\n </div>\n </div>");
38723
40194
  }
38724
40195
  }]);
38725
40196
  }();
@@ -39142,6 +40613,7 @@ __webpack_require__.r(__webpack_exports__);
39142
40613
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
39143
40614
  /* harmony export */ Transcript: () => (/* binding */ Transcript)
39144
40615
  /* harmony export */ });
40616
+ /* harmony import */ var _rtlText_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rtlText.js */ "./src/widget/voice/rtlText.js");
39145
40617
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
39146
40618
  function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
39147
40619
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
@@ -39154,6 +40626,8 @@ function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o =
39154
40626
  function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
39155
40627
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
39156
40628
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
40629
+
40630
+
39157
40631
  /**
39158
40632
  * Transcript - Manages transcript display and conversation history
39159
40633
  *
@@ -39206,12 +40680,17 @@ var Transcript = /*#__PURE__*/function () {
39206
40680
  key: "updateTranscript",
39207
40681
  value: function updateTranscript(text) {
39208
40682
  console.log('📝 [TRANSCRIPT] updateTranscript() called with text:', text ? text.substring(0, 50) + '...' : 'null/empty');
40683
+ var cfg = this.voiceInterface.config;
40684
+ var hasContent = !!(text && text.trim());
40685
+ var snippetForDir = hasContent ? text : this.t('transcriptWillAppear') || '';
40686
+ var assistantDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(snippetForDir, cfg);
39209
40687
 
39210
40688
  // Mobile transcript (use container-scoped lookup for Shopify/embedded contexts)
39211
40689
  var mobileBar = document.getElementById('mobile-voice-call-bar-container');
39212
40690
  if (mobileBar) {
39213
40691
  var mobileTranscriptText = mobileBar.querySelector('#mobileTranscriptText');
39214
40692
  if (mobileTranscriptText) {
40693
+ mobileTranscriptText.setAttribute('dir', assistantDir);
39215
40694
  mobileTranscriptText.textContent = text;
39216
40695
  mobileTranscriptText.style.color = '';
39217
40696
  }
@@ -39232,6 +40711,7 @@ var Transcript = /*#__PURE__*/function () {
39232
40711
  var lastMessage = messagesContainer.querySelector('.mobile-message-bubble.assistant:last-child');
39233
40712
  if (lastMessage) {
39234
40713
  lastMessage.textContent = text;
40714
+ lastMessage.setAttribute('dir', (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(text, cfg));
39235
40715
  } else {
39236
40716
  this.voiceInterface.addMobileMessage('assistant', text);
39237
40717
  }
@@ -39242,6 +40722,7 @@ var Transcript = /*#__PURE__*/function () {
39242
40722
  // Desktop transcript - Update collapsed live text (2 lines)
39243
40723
  var liveTextCollapsed = this.voiceInterface.shadowRoot.getElementById('liveTextCollapsed');
39244
40724
  if (liveTextCollapsed) {
40725
+ liveTextCollapsed.setAttribute('dir', assistantDir);
39245
40726
  liveTextCollapsed.style.color = '';
39246
40727
  if (text && text.trim()) {
39247
40728
  liveTextCollapsed.textContent = text;
@@ -39430,6 +40911,10 @@ var Transcript = /*#__PURE__*/function () {
39430
40911
  // Update message text
39431
40912
  liveMessageText.textContent = this.currentLiveMessage.content;
39432
40913
  liveMessageRow.style.display = 'flex';
40914
+ var bubble = liveMessageRow.querySelector('.message-bubble');
40915
+ if (bubble) {
40916
+ bubble.setAttribute('dir', (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(this.currentLiveMessage.content, this.voiceInterface.config));
40917
+ }
39433
40918
 
39434
40919
  // Re-add cursor
39435
40920
  var cursor = liveMessageText.querySelector('.typing-cursor') || document.createElement('span');
@@ -39485,11 +40970,11 @@ var Transcript = /*#__PURE__*/function () {
39485
40970
  messagesToRender = messagesToRender.slice(0, -1);
39486
40971
  }
39487
40972
  }
39488
- var textDir = this.config.direction === 'rtl' ? 'rtl' : 'ltr';
40973
+ var bubbleDir = (0,_rtlText_js__WEBPACK_IMPORTED_MODULE_0__.dirForTranscriptSnippet)(msg.content, this.config);
39489
40974
  console.log('📝 [TRANSCRIPT] Rendering', messagesToRender.length, 'messages (live message excluded)');
39490
40975
  container.innerHTML = messagesToRender.map(function (msg) {
39491
40976
  var isUser = msg.role === 'user';
39492
- return "\n <div class=\"message-row ".concat(msg.role, "\">\n <div class=\"message-avatar ").concat(msg.role, "\">\n ").concat(isUser ? 'U' : '<div class="orb"></div>', "\n </div>\n <div class=\"message-bubble\" dir=\"").concat(textDir, "\">").concat(_this.escapeHtml(msg.content), "</div>\n </div>\n ");
40977
+ return "\n <div class=\"message-row ".concat(msg.role, "\">\n <div class=\"message-avatar ").concat(msg.role, "\">\n ").concat(isUser ? 'U' : '<div class="orb"></div>', "\n </div>\n <div class=\"message-bubble\" dir=\"").concat(bubbleDir, "\">").concat(_this.escapeHtml(msg.content), "</div>\n </div>\n ");
39493
40978
  }).join('');
39494
40979
 
39495
40980
  // Scroll to bottom