@huskel/sdk 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
38
38
  // src/index.ts
39
39
  var index_exports = {};
40
40
  __export(index_exports, {
41
+ AIChatButton: () => AIChatButton,
41
42
  ChatWidget: () => ChatWidget,
42
43
  HuskelAPI: () => HuskelAPI,
43
44
  HuskelClient: () => HuskelClient,
@@ -140,6 +141,10 @@ var HuskelAPI = class {
140
141
  async searchVector(query, limit = 10) {
141
142
  return this.post("/search/vector", { query, siteId: this.siteId, limit });
142
143
  }
144
+ // Autocomplete — pure in-memory Trie, <1ms, no Upstash call. Only true prefix matches.
145
+ async searchAutocomplete(query, limit = 8) {
146
+ return this.post("/search/autocomplete", { query, siteId: this.siteId, limit });
147
+ }
143
148
  // LLM chat — conversational search with history context.
144
149
  async chat(query, history = []) {
145
150
  log("info", "chat query", query);
@@ -455,29 +460,34 @@ function useSearch() {
455
460
  const [results, setResults] = (0, import_react3.useState)([]);
456
461
  const [loading, setLoading] = (0, import_react3.useState)(false);
457
462
  const [error, setError] = (0, import_react3.useState)(null);
458
- const abortRef = (0, import_react3.useRef)(null);
459
- const search = (0, import_react3.useCallback)(async (query, limit = 10) => {
460
- var _a, _b, _c;
463
+ const genRef = (0, import_react3.useRef)(0);
464
+ const search = (0, import_react3.useCallback)(async (query, limit = 8) => {
465
+ var _a, _b;
461
466
  if (!query.trim()) {
462
467
  setResults([]);
468
+ setLoading(false);
463
469
  return;
464
470
  }
465
- (_a = abortRef.current) == null ? void 0 : _a.abort();
466
- abortRef.current = new AbortController();
467
- setLoading(true);
471
+ const gen = ++genRef.current;
468
472
  setError(null);
469
473
  try {
470
- const res = await client.api.searchVector(query, limit);
471
- setResults((_b = res.results) != null ? _b : []);
474
+ const res = await client.api.searchAutocomplete(query, limit);
475
+ if (gen === genRef.current) {
476
+ setResults((_a = res.results) != null ? _a : []);
477
+ }
472
478
  } catch (e) {
473
- setError((_c = e.message) != null ? _c : "Search failed");
479
+ if (gen === genRef.current) {
480
+ setError((_b = e.message) != null ? _b : "Search failed");
481
+ }
474
482
  } finally {
475
- setLoading(false);
483
+ if (gen === genRef.current) setLoading(false);
476
484
  }
477
485
  }, [client]);
478
486
  const clear = (0, import_react3.useCallback)(() => {
487
+ genRef.current++;
479
488
  setResults([]);
480
489
  setError(null);
490
+ setLoading(false);
481
491
  }, []);
482
492
  return { results, loading, error, search, clear };
483
493
  }
@@ -575,22 +585,142 @@ function useChat() {
575
585
  // src/components/SearchBar.tsx
576
586
  var import_react7 = require("react");
577
587
  var import_jsx_runtime2 = require("react/jsx-runtime");
578
- var S = `
579
- .hsk-wrap{position:relative;width:100%;font-family:inherit}
580
- .hsk-input{width:100%;padding:10px 16px;font-size:15px;border:1.5px solid #e2e2e2;border-radius:8px;outline:none;box-sizing:border-box;background:#fff;transition:border-color .2s}
581
- .hsk-input:focus{border-color:#f47c3c}
582
- .hsk-drop{position:absolute;top:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #e2e2e2;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);z-index:9999;max-height:360px;overflow-y:auto}
583
- .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}
584
- .hsk-item:hover{background:#faf5f1}
585
- .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}
586
- .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
587
- .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}
588
- .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}
588
+ var CSS = `
589
+ /* \u2500\u2500 Light mode defaults (CSS custom properties) \u2500\u2500 */
590
+ .hsk-sb-wrap {
591
+ --hsk-bg: #fff;
592
+ --hsk-border: #d1d5db;
593
+ --hsk-text: #111;
594
+ --hsk-muted: #9ca3af;
595
+ --hsk-hover: #f3f4f6;
596
+ --hsk-drop-shadow: 0 8px 30px rgba(0,0,0,.12);
597
+ position: relative;
598
+ width: 100%;
599
+ font-family: inherit;
600
+ }
601
+
602
+ /* \u2500\u2500 Dark mode overrides at wrap level so ALL children inherit \u2500\u2500 */
603
+ @media (prefers-color-scheme: dark) {
604
+ .hsk-sb-wrap {
605
+ --hsk-bg: #1a1a1b;
606
+ --hsk-border: #2a2a2d;
607
+ --hsk-text: #f3f3f2;
608
+ --hsk-muted: #666;
609
+ --hsk-hover: #1e1e1f;
610
+ --hsk-drop-shadow: 0 12px 40px rgba(0,0,0,.5);
611
+ }
612
+ }
613
+
614
+ /* \u2500\u2500 Input \u2500\u2500 */
615
+ .hsk-sb-input {
616
+ width: 100%;
617
+ padding: 10px 16px 10px 40px;
618
+ font-size: 14px;
619
+ border-radius: 9999px;
620
+ border: 1.5px solid var(--hsk-border);
621
+ outline: none;
622
+ box-sizing: border-box;
623
+ background: var(--hsk-bg);
624
+ color: var(--hsk-text);
625
+ transition: border-color .15s, box-shadow .15s;
626
+ font-family: inherit;
627
+ }
628
+ .hsk-sb-input::placeholder { color: var(--hsk-muted); }
629
+ .hsk-sb-input:focus {
630
+ border-color: #ff6a33;
631
+ box-shadow: 0 0 0 3px rgba(255,106,51,.12);
632
+ }
633
+ .hsk-sb-icon {
634
+ position: absolute;
635
+ left: 14px;
636
+ top: 50%;
637
+ transform: translateY(-50%);
638
+ color: var(--hsk-muted);
639
+ pointer-events: none;
640
+ display: flex;
641
+ align-items: center;
642
+ }
643
+
644
+ /* \u2500\u2500 Dropdown \u2500\u2500 */
645
+ .hsk-sb-drop {
646
+ position: absolute;
647
+ top: calc(100% + 6px);
648
+ left: 0; right: 0;
649
+ background: var(--hsk-bg);
650
+ border: 1px solid var(--hsk-border);
651
+ border-radius: 12px;
652
+ box-shadow: var(--hsk-drop-shadow);
653
+ z-index: 9999;
654
+ overflow: hidden;
655
+ padding: 6px 0;
656
+ }
657
+
658
+ /* \u2500\u2500 Row \u2500\u2500 */
659
+ .hsk-sb-row {
660
+ display: flex;
661
+ align-items: center;
662
+ gap: 12px;
663
+ padding: 9px 16px;
664
+ cursor: pointer;
665
+ transition: background .1s;
666
+ }
667
+ .hsk-sb-row:hover { background: var(--hsk-hover); }
668
+ .hsk-sb-row-icon {
669
+ color: var(--hsk-muted);
670
+ flex-shrink: 0;
671
+ display: flex;
672
+ align-items: center;
673
+ }
674
+ .hsk-sb-row-body { flex: 1; min-width: 0; }
675
+ .hsk-sb-row-title {
676
+ font-size: 13px;
677
+ font-weight: 500;
678
+ color: var(--hsk-text);
679
+ white-space: nowrap;
680
+ overflow: hidden;
681
+ text-overflow: ellipsis;
682
+ line-height: 1.3;
683
+ }
684
+ .hsk-sb-row-sub {
685
+ font-size: 11px;
686
+ color: var(--hsk-muted);
687
+ margin-top: 2px;
688
+ white-space: nowrap;
689
+ overflow: hidden;
690
+ text-overflow: ellipsis;
691
+ }
692
+ .hsk-sb-empty {
693
+ padding: 14px 16px;
694
+ font-size: 13px;
695
+ color: var(--hsk-muted);
696
+ }
697
+
698
+ /* \u2500\u2500 Thin accent bar while loading (non-intrusive, no text) \u2500\u2500 */
699
+ .hsk-sb-loading-bar {
700
+ height: 2px;
701
+ background: linear-gradient(90deg, transparent, #ff6a33, transparent);
702
+ background-size: 200% 100%;
703
+ animation: hsk-sweep 0.9s linear infinite;
704
+ position: absolute;
705
+ top: 0; left: 0; right: 0;
706
+ }
707
+ @keyframes hsk-sweep {
708
+ 0% { background-position: 200% 0; }
709
+ 100% { background-position: -200% 0; }
710
+ }
711
+
712
+ /* \u2500\u2500 Staggered fade-in \u2500\u2500 */
713
+ .hsk-sb-fade { animation: hsk-fin .1s ease-out both; }
714
+ @keyframes hsk-fin { from { opacity:0; transform:translateY(3px); } to { opacity:1; transform:none; } }
589
715
  `;
716
+ var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "15", height: "15", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
717
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
718
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "13", y1: "13", x2: "18", y2: "18" })
719
+ ] });
590
720
  function SearchBar({
591
- placeholder = "Search for what you want \u2014 how you want",
721
+ placeholder = "Search products\u2026",
592
722
  limit = 10,
593
- debounceMs = 300,
723
+ debounceMs = 80,
594
724
  onSelect,
595
725
  className,
596
726
  inputClassName,
@@ -609,61 +739,77 @@ function SearchBar({
609
739
  setOpen(false);
610
740
  return;
611
741
  }
742
+ setOpen(true);
612
743
  timer.current = setTimeout(() => {
613
744
  search(query, limit);
614
- setOpen(true);
615
745
  }, debounceMs);
616
746
  return () => clearTimeout(timer.current);
617
- }, [query, search, clear, limit, debounceMs]);
747
+ }, [query]);
618
748
  (0, import_react7.useEffect)(() => {
619
- const handler = (e) => {
749
+ const h = (e) => {
620
750
  if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
621
751
  };
622
- document.addEventListener("mousedown", handler);
623
- return () => document.removeEventListener("mousedown", handler);
752
+ document.addEventListener("mousedown", h);
753
+ return () => document.removeEventListener("mousedown", h);
624
754
  }, []);
625
755
  const handleSelect = (r) => {
626
756
  setOpen(false);
627
757
  setQuery(r.product.name);
628
758
  onSelect == null ? void 0 : onSelect(r);
629
759
  };
760
+ const showDrop = open && query.trim().length > 0;
630
761
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
631
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: S }),
632
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-wrap ${className != null ? className : ""}`, ref: wrap, children: [
762
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: CSS }),
763
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-sb-wrap ${className != null ? className : ""}`, ref: wrap, children: [
764
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
633
765
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
634
766
  "input",
635
767
  {
636
- className: `hsk-input ${inputClassName != null ? inputClassName : ""}`,
768
+ className: `hsk-sb-input ${inputClassName != null ? inputClassName : ""}`,
637
769
  type: "text",
638
770
  value: query,
639
771
  placeholder,
640
772
  onChange: (e) => setQuery(e.target.value),
641
- onFocus: () => results.length && setOpen(true)
773
+ onFocus: () => results.length > 0 && query.trim() && setOpen(true),
774
+ autoComplete: "off",
775
+ spellCheck: false
642
776
  }
643
777
  ),
644
- open && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-drop ${dropdownClassName != null ? dropdownClassName : ""}`, children: [
645
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-msg", children: "Searching\u2026" }),
646
- !loading && results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-msg", children: [
647
- 'No results for "',
778
+ showDrop && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-sb-drop ${dropdownClassName != null ? dropdownClassName : ""}`, style: { position: "absolute" }, children: [
779
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
780
+ results.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-empty", children: [
781
+ "No results for \u201C",
648
782
  query,
649
- '"'
783
+ "\u201D"
650
784
  ] }),
651
- results.map(
652
- (r) => {
653
- var _a, _b;
654
- return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { onClick: () => handleSelect(r), children: renderResult(r) }, r.id) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-item", onClick: () => handleSelect(r), children: [
655
- ((_a = r.product.images) == null ? void 0 : _a[0]) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: r.product.images[0], alt: r.product.name }),
656
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
657
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-item-name", children: r.product.name }),
658
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-item-price", children: [
659
- (_b = r.product.currency) != null ? _b : "KES",
660
- " ",
661
- r.product.price
785
+ results.map((r, i) => {
786
+ var _a;
787
+ return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
788
+ "div",
789
+ {
790
+ onClick: () => handleSelect(r),
791
+ className: "hsk-sb-fade",
792
+ style: { animationDelay: `${i * 18}ms` },
793
+ children: renderResult(r)
794
+ },
795
+ r.id
796
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
797
+ "div",
798
+ {
799
+ className: "hsk-sb-row hsk-sb-fade",
800
+ style: { animationDelay: `${i * 18}ms` },
801
+ onClick: () => handleSelect(r),
802
+ children: [
803
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
804
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
805
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-title", children: r.product.name }),
806
+ (r.product.category || r.product.brand) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
662
807
  ] })
663
- ] })
664
- ] }, r.id);
665
- }
666
- )
808
+ ]
809
+ },
810
+ r.id
811
+ );
812
+ })
667
813
  ] })
668
814
  ] })
669
815
  ] });
@@ -671,39 +817,392 @@ function SearchBar({
671
817
 
672
818
  // src/components/Sparkle.tsx
673
819
  var import_react8 = require("react");
820
+ var import_react_dom = require("react-dom");
674
821
  var import_jsx_runtime3 = require("react/jsx-runtime");
675
- var S2 = `
676
- .hsk-sparkle{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:12px;font-weight:600;background:#f47c3c;color:#fff;border:none;border-radius:20px;cursor:pointer;transition:opacity .2s,transform .15s}
677
- .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}
678
- .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}
822
+ var CSS2 = `
823
+ /* \u2500\u2500 Trigger button \u2500\u2500 */
824
+ .hsk-sp-btn {
825
+ display: inline-flex;
826
+ align-items: center;
827
+ justify-content: center;
828
+ width: 32px;
829
+ height: 32px;
830
+ border-radius: 8px;
831
+ border: 1px solid var(--hsk-sp-border, rgba(255,106,51,.35));
832
+ background: var(--hsk-sp-bg, rgba(255,106,51,.08));
833
+ color: #ff6a33;
834
+ cursor: pointer;
835
+ font-size: 15px;
836
+ line-height: 1;
837
+ transition: background .15s, border-color .15s, transform .12s;
838
+ flex-shrink: 0;
839
+ padding: 0;
840
+ }
841
+ .hsk-sp-btn:hover {
842
+ background: rgba(255,106,51,.18);
843
+ border-color: rgba(255,106,51,.7);
844
+ transform: scale(1.1);
845
+ }
846
+ .hsk-sp-btn:active { transform: scale(.92); }
847
+
848
+ /* \u2500\u2500 Backdrop \u2500\u2500 */
849
+ .hsk-sp-backdrop {
850
+ position: fixed;
851
+ inset: 0;
852
+ z-index: 99998;
853
+ display: flex;
854
+ align-items: flex-start;
855
+ justify-content: center;
856
+ padding: clamp(48px, 10vh, 96px) 16px 40px;
857
+ animation: hsk-bd-in .2s ease-out both;
858
+ overflow-y: auto;
859
+ }
860
+ @keyframes hsk-bd-in { from { opacity:0; } to { opacity:1; } }
861
+
862
+ /* \u2500\u2500 Modal card \u2500\u2500 */
863
+ .hsk-sp-card {
864
+ width: 100%;
865
+ max-width: 600px;
866
+ border-radius: 18px;
867
+ overflow: hidden;
868
+ animation: hsk-card-in .24s cubic-bezier(.34,1.36,.64,1) both;
869
+ flex-shrink: 0;
870
+ /* light */
871
+ background: var(--hsk-modal-card-bg, #fff);
872
+ border: 1px solid var(--hsk-modal-card-border, rgba(0,0,0,.08));
873
+ box-shadow: 0 32px 80px rgba(0,0,0,.18), 0 2px 8px rgba(0,0,0,.06);
874
+ }
875
+ @keyframes hsk-card-in {
876
+ from { opacity:0; transform: scale(.96) translateY(-12px); }
877
+ to { opacity:1; transform: scale(1) translateY(0); }
878
+ }
879
+
880
+ /* \u2500\u2500 Header \u2500\u2500 */
881
+ .hsk-sp-header {
882
+ display: flex;
883
+ align-items: center;
884
+ gap: 10px;
885
+ padding: 18px 20px 14px;
886
+ border-bottom: 1px solid var(--hsk-modal-divide, rgba(0,0,0,.07));
887
+ }
888
+ .hsk-sp-header-icon { font-size: 18px; color: #ff6a33; flex-shrink: 0; }
889
+ .hsk-sp-header-body { flex: 1; min-width: 0; }
890
+ .hsk-sp-header-title {
891
+ font-size: 14px;
892
+ font-weight: 600;
893
+ color: var(--hsk-modal-text, #111);
894
+ white-space: nowrap;
895
+ overflow: hidden;
896
+ text-overflow: ellipsis;
897
+ }
898
+ .hsk-sp-header-sub {
899
+ font-size: 11px;
900
+ color: var(--hsk-modal-muted, #888);
901
+ margin-top: 2px;
902
+ }
903
+ .hsk-sp-close {
904
+ width: 30px; height: 30px;
905
+ border-radius: 8px;
906
+ border: 1px solid var(--hsk-modal-divide, rgba(0,0,0,.1));
907
+ background: none;
908
+ color: var(--hsk-modal-muted, #888);
909
+ cursor: pointer;
910
+ font-size: 18px;
911
+ display: flex; align-items: center; justify-content: center;
912
+ transition: all .15s;
913
+ flex-shrink: 0;
914
+ }
915
+ .hsk-sp-close:hover { border-color: #ff6a33; color: #ff6a33; }
916
+
917
+ /* \u2500\u2500 Thin loading accent bar \u2500\u2500 */
918
+ .hsk-sp-bar {
919
+ height: 2px;
920
+ background: linear-gradient(90deg, transparent 0%, #ff6a33 40%, #ffaa80 60%, transparent 100%);
921
+ background-size: 200% 100%;
922
+ animation: hsk-bar .9s linear infinite;
923
+ }
924
+ @keyframes hsk-bar { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
925
+
926
+ /* \u2500\u2500 Results list \u2500\u2500 */
927
+ .hsk-sp-results { padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; }
928
+ .hsk-sp-empty { padding: 40px; text-align: center; font-size: 13px; color: var(--hsk-modal-muted, #aaa); }
929
+
930
+ /* \u2500\u2500 Result card (toast-inspired: slides up from bottom) \u2500\u2500 */
931
+ .hsk-sp-item {
932
+ display: flex;
933
+ gap: 14px;
934
+ padding: 14px;
935
+ border-radius: 12px;
936
+ border: 1px solid var(--hsk-modal-item-border, rgba(0,0,0,.07));
937
+ background: var(--hsk-modal-item-bg, #f9f9f9);
938
+ animation: hsk-toast-up .28s cubic-bezier(.22,.68,0,1.2) both;
939
+ overflow: hidden;
940
+ }
941
+ @keyframes hsk-toast-up {
942
+ from { opacity:0; transform: translateY(18px) scale(.97); }
943
+ to { opacity:1; transform: translateY(0) scale(1); }
944
+ }
945
+
946
+ /* image */
947
+ .hsk-sp-img-wrap {
948
+ width: 72px; height: 72px;
949
+ border-radius: 10px;
950
+ background: #fff;
951
+ border: 1px solid var(--hsk-modal-divide, rgba(0,0,0,.07));
952
+ flex-shrink: 0;
953
+ overflow: hidden;
954
+ display: flex; align-items: center; justify-content: center;
955
+ padding: 6px;
956
+ }
957
+ .hsk-sp-img-wrap img { max-width: 100%; max-height: 100%; object-fit: contain; }
958
+ .hsk-sp-img-placeholder { font-size: 26px; }
959
+
960
+ /* body */
961
+ .hsk-sp-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
962
+ .hsk-sp-item-name {
963
+ font-size: 14px;
964
+ font-weight: 600;
965
+ color: var(--hsk-modal-text, #111);
966
+ line-height: 1.35;
967
+ overflow: hidden;
968
+ display: -webkit-box;
969
+ -webkit-line-clamp: 2;
970
+ -webkit-box-orient: vertical;
971
+ }
972
+ .hsk-sp-item-cat {
973
+ font-size: 11px;
974
+ font-weight: 600;
975
+ color: #ff6a33;
976
+ text-transform: uppercase;
977
+ letter-spacing: .05em;
978
+ }
979
+ .hsk-sp-item-price-row { display: flex; align-items: baseline; gap: 8px; margin-top: 2px; }
980
+ .hsk-sp-item-price {
981
+ font-size: 18px;
982
+ font-weight: 700;
983
+ color: var(--hsk-modal-text, #111);
984
+ }
985
+ .hsk-sp-item-currency { font-size: 12px; color: var(--hsk-modal-muted, #888); }
986
+
987
+ /* actions */
988
+ .hsk-sp-actions { display: flex; gap: 6px; margin-top: 8px; }
989
+ .hsk-sp-action {
990
+ flex: 1;
991
+ padding: 7px 10px;
992
+ border-radius: 8px;
993
+ font-size: 12px;
994
+ font-weight: 600;
995
+ cursor: pointer;
996
+ border: 1px solid transparent;
997
+ transition: all .15s;
998
+ text-align: center;
999
+ font-family: inherit;
1000
+ }
1001
+ .hsk-sp-action-primary {
1002
+ background: #ff6a33;
1003
+ color: #fff;
1004
+ border-color: #ff6a33;
1005
+ }
1006
+ .hsk-sp-action-primary:hover { background: #e55d2a; }
1007
+ .hsk-sp-action-secondary {
1008
+ background: var(--hsk-action-sec-bg, rgba(0,0,0,.06));
1009
+ color: var(--hsk-modal-muted, #666);
1010
+ border-color: var(--hsk-modal-divide, rgba(0,0,0,.1));
1011
+ }
1012
+ .hsk-sp-action-secondary:hover {
1013
+ background: var(--hsk-action-sec-bg-hover, rgba(0,0,0,.1));
1014
+ color: var(--hsk-modal-text, #333);
1015
+ }
1016
+
1017
+ /* \u2500\u2500 Footer \u2500\u2500 */
1018
+ .hsk-sp-footer {
1019
+ padding: 12px 20px;
1020
+ border-top: 1px solid var(--hsk-modal-divide, rgba(0,0,0,.07));
1021
+ display: flex;
1022
+ align-items: center;
1023
+ gap: 8px;
1024
+ }
1025
+ .hsk-sp-badge {
1026
+ font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
1027
+ color: #ff6a33;
1028
+ background: rgba(255,106,51,.1);
1029
+ border: 1px solid rgba(255,106,51,.25);
1030
+ padding: 2px 8px;
1031
+ border-radius: 999px;
1032
+ }
1033
+ .hsk-sp-esc { font-size: 11px; color: var(--hsk-modal-muted, #bbb); margin-left: auto; }
1034
+
1035
+ /* \u2500\u2500 Dark mode \u2500\u2500 */
1036
+ @media (prefers-color-scheme: dark) {
1037
+ .hsk-sp-card {
1038
+ --hsk-modal-card-bg: #111112;
1039
+ --hsk-modal-card-border: rgba(255,255,255,.07);
1040
+ --hsk-modal-text: #f3f3f2;
1041
+ --hsk-modal-muted: #666;
1042
+ --hsk-modal-divide: rgba(255,255,255,.07);
1043
+ --hsk-modal-item-bg: #1a1a1b;
1044
+ --hsk-modal-item-border: rgba(255,255,255,.06);
1045
+ --hsk-action-sec-bg: rgba(255,255,255,.07);
1046
+ --hsk-action-sec-bg-hover: rgba(255,255,255,.12);
1047
+ }
1048
+ .hsk-sp-img-wrap { background: #242425; border-color: rgba(255,255,255,.08); }
1049
+ }
679
1050
  `;
680
- function Sparkle({ productName, limit = 5, onResult, className }) {
681
- const client = useHuskelContext();
682
- const [loading, setLoading] = (0, import_react8.useState)(false);
683
- const handleClick = async () => {
684
- setLoading(true);
685
- try {
686
- const res = await client.api.searchVector(productName, limit);
687
- onResult == null ? void 0 : onResult(res.results);
688
- } catch (e) {
689
- console.error("[Huskel Sparkle]", e);
690
- } finally {
691
- setLoading(false);
1051
+ function SparkleModal({ productName, limit, backdropColor, backdropBlur, onClose, onNavigate, onResult }) {
1052
+ const { results, loading, search } = useSearch();
1053
+ const initiated = (0, import_react8.useRef)(false);
1054
+ (0, import_react8.useEffect)(() => {
1055
+ if (!initiated.current) {
1056
+ initiated.current = true;
1057
+ search(productName, limit);
1058
+ }
1059
+ }, []);
1060
+ (0, import_react8.useEffect)(() => {
1061
+ if (results.length > 0) onResult == null ? void 0 : onResult(results);
1062
+ }, [results]);
1063
+ (0, import_react8.useEffect)(() => {
1064
+ const h = (e) => {
1065
+ if (e.key === "Escape") onClose();
1066
+ };
1067
+ document.addEventListener("keydown", h);
1068
+ return () => document.removeEventListener("keydown", h);
1069
+ }, []);
1070
+ const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "16px";
1071
+ const bg = backdropColor != null ? backdropColor : void 0;
1072
+ const handleNav = (r) => {
1073
+ const prevent = onNavigate == null ? void 0 : onNavigate(r);
1074
+ if (prevent !== false) {
1075
+ onClose();
1076
+ if (r.product.url) window.location.href = r.product.url;
692
1077
  }
693
1078
  };
694
1079
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
695
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: S2 }),
696
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { className: `hsk-sparkle ${className != null ? className : ""}`, onClick: handleClick, disabled: loading, children: [
697
- "\u2726 ",
698
- loading ? "Finding\u2026" : "Similar"
699
- ] })
1080
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: CSS2 }),
1081
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1082
+ "div",
1083
+ {
1084
+ className: "hsk-sp-backdrop",
1085
+ onClick: onClose,
1086
+ style: {
1087
+ backdropFilter: `blur(${blurVal})`,
1088
+ WebkitBackdropFilter: `blur(${blurVal})`,
1089
+ background: bg != null ? bg : void 0
1090
+ /* CSS handles light/dark via @media if bg not forced */
1091
+ },
1092
+ children: [
1093
+ !bg && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `
1094
+ @media (prefers-color-scheme: dark) { .hsk-sp-backdrop { background: rgba(0,0,0,.80); } }
1095
+ @media (prefers-color-scheme: light) { .hsk-sp-backdrop { background: rgba(240,240,245,.70); } }
1096
+ ` }),
1097
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-card", onClick: (e) => e.stopPropagation(), children: [
1098
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header", children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-header-icon", children: "\u2726" }),
1100
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header-body", children: [
1101
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header-title", children: [
1102
+ "Similar to \u201C",
1103
+ productName,
1104
+ "\u201D"
1105
+ ] }),
1106
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-header-sub", children: "AI vector similarity \xB7 instant results" })
1107
+ ] }),
1108
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: "\xD7" })
1109
+ ] }),
1110
+ loading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-bar" }),
1111
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-results", children: [
1112
+ !loading && results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-empty", children: "No similar products found." }),
1113
+ results.map((r, i) => {
1114
+ var _a, _b, _c;
1115
+ const price = parseFloat(((_a = r.product.price) == null ? void 0 : _a.replace(/[^0-9.]/g, "")) || "0");
1116
+ const currency = (_b = r.product.currency) != null ? _b : "KES";
1117
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1118
+ "div",
1119
+ {
1120
+ className: "hsk-sp-item",
1121
+ style: { animationDelay: `${i * 55}ms` },
1122
+ children: [
1123
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-img-wrap", children: ((_c = r.product.images) == null ? void 0 : _c[0]) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
1124
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-item-body", children: [
1125
+ r.product.category && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-item-cat", children: r.product.category }),
1126
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-item-name", children: r.product.name }),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
1128
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-item-currency", children: currency }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
1130
+ ] }),
1131
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-actions", children: [
1132
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1133
+ "button",
1134
+ {
1135
+ className: "hsk-sp-action hsk-sp-action-primary",
1136
+ onClick: () => handleNav(r),
1137
+ children: "View Product"
1138
+ }
1139
+ ),
1140
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1141
+ "button",
1142
+ {
1143
+ className: "hsk-sp-action hsk-sp-action-secondary",
1144
+ onClick: () => onClose(),
1145
+ children: "Add to Cart"
1146
+ }
1147
+ )
1148
+ ] })
1149
+ ] })
1150
+ ]
1151
+ },
1152
+ r.id
1153
+ );
1154
+ })
1155
+ ] }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-footer", children: [
1157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-badge", children: "\u2726 Huskel AI" }),
1158
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-esc", children: "Esc to close" })
1159
+ ] })
1160
+ ] })
1161
+ ]
1162
+ }
1163
+ )
1164
+ ] });
1165
+ }
1166
+ function Sparkle({ productName, limit = 8, onResult, backdropColor, backdropBlur, className, onNavigate }) {
1167
+ const [open, setOpen] = (0, import_react8.useState)(false);
1168
+ const [mounted, setMounted] = (0, import_react8.useState)(false);
1169
+ (0, import_react8.useEffect)(() => {
1170
+ setMounted(true);
1171
+ }, []);
1172
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1173
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: CSS2 }),
1174
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1175
+ "button",
1176
+ {
1177
+ className: `hsk-sp-btn ${className != null ? className : ""}`,
1178
+ onClick: () => setOpen(true),
1179
+ title: "Find similar products",
1180
+ "aria-label": "Find similar products",
1181
+ children: "\u2726"
1182
+ }
1183
+ ),
1184
+ open && mounted && (0, import_react_dom.createPortal)(
1185
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1186
+ SparkleModal,
1187
+ {
1188
+ productName,
1189
+ limit,
1190
+ backdropColor,
1191
+ backdropBlur,
1192
+ onClose: () => setOpen(false),
1193
+ onResult,
1194
+ onNavigate
1195
+ }
1196
+ ),
1197
+ document.body
1198
+ )
700
1199
  ] });
701
1200
  }
702
1201
 
703
1202
  // src/components/ChatWidget.tsx
704
1203
  var import_react9 = require("react");
705
1204
  var import_jsx_runtime4 = require("react/jsx-runtime");
706
- var S3 = `
1205
+ var S = `
707
1206
  .hsk-chat-widget{display:flex;flex-direction:column;height:100%;min-height:320px;font-family:inherit;background:#0f0f10;border:1px solid #2a2a2d;border-radius:12px;overflow:hidden}
708
1207
  .hsk-chat-header{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid #1e1e1f;background:#111112;flex-shrink:0}
709
1208
  .hsk-chat-title{font-size:14px;font-weight:600;color:#f3f3f2}
@@ -784,7 +1283,7 @@ function ChatWidget({ placeholder = "Ask about anything in our store\u2026", tit
784
1283
  t.style.height = Math.min(t.scrollHeight, 120) + "px";
785
1284
  };
786
1285
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
787
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: S3 }),
1286
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: S }),
788
1287
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `hsk-chat-widget ${className != null ? className : ""}`, children: [
789
1288
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-header", children: [
790
1289
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: 16, color: "#ff6a33" }, children: "\u2726" }),
@@ -843,8 +1342,802 @@ function ChatWidget({ placeholder = "Ask about anything in our store\u2026", tit
843
1342
  ] })
844
1343
  ] });
845
1344
  }
1345
+
1346
+ // src/components/AIChatButton.tsx
1347
+ var import_react10 = require("react");
1348
+ var import_react_dom2 = require("react-dom");
1349
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1350
+ var CSS3 = `
1351
+ /* \u2500\u2500 Trigger button \u2500\u2500 */
1352
+ .hsk-cb-btn {
1353
+ display: inline-flex;
1354
+ align-items: center;
1355
+ gap: 7px;
1356
+ padding: 8px 16px;
1357
+ border-radius: 9999px;
1358
+ border: 1px solid rgba(255,106,51,.4);
1359
+ background: rgba(255,106,51,.1);
1360
+ color: #ff6a33;
1361
+ font-size: 13px;
1362
+ font-weight: 600;
1363
+ cursor: pointer;
1364
+ transition: background .15s, border-color .15s, transform .12s, box-shadow .15s;
1365
+ font-family: inherit;
1366
+ white-space: nowrap;
1367
+ }
1368
+ .hsk-cb-btn:hover {
1369
+ background: rgba(255,106,51,.18);
1370
+ border-color: rgba(255,106,51,.7);
1371
+ box-shadow: 0 4px 16px rgba(255,106,51,.2);
1372
+ }
1373
+ .hsk-cb-btn:active { transform: scale(.95); }
1374
+ .hsk-cb-btn-icon { font-size: 15px; line-height: 1; }
1375
+
1376
+ /* \u2500\u2500 Full-screen overlay \u2500\u2500 */
1377
+ .hsk-cb-overlay {
1378
+ position: fixed;
1379
+ inset: 0;
1380
+ z-index: 99999;
1381
+ display: flex;
1382
+ flex-direction: column;
1383
+ animation: hsk-overlay-in .2s ease-out both;
1384
+ }
1385
+ @keyframes hsk-overlay-in {
1386
+ from { opacity: 0; }
1387
+ to { opacity: 1; }
1388
+ }
1389
+
1390
+ /* \u2500\u2500 Panel (Claude-style, centered column) \u2500\u2500 */
1391
+ .hsk-cb-panel {
1392
+ position: relative;
1393
+ display: flex;
1394
+ flex-direction: column;
1395
+ height: 100%;
1396
+ max-width: 780px;
1397
+ width: 100%;
1398
+ margin: 0 auto;
1399
+ animation: hsk-panel-in .28s cubic-bezier(.34,1.2,.64,1) both;
1400
+ }
1401
+ @keyframes hsk-panel-in {
1402
+ from { opacity: 0; transform: translateY(24px); }
1403
+ to { opacity: 1; transform: translateY(0); }
1404
+ }
1405
+
1406
+ /* \u2500\u2500 Top bar \u2500\u2500 */
1407
+ .hsk-cb-topbar {
1408
+ display: flex;
1409
+ align-items: center;
1410
+ justify-content: space-between;
1411
+ padding: 20px 28px 12px;
1412
+ flex-shrink: 0;
1413
+ }
1414
+ .hsk-cb-topbar-left {
1415
+ display: flex;
1416
+ align-items: center;
1417
+ gap: 10px;
1418
+ }
1419
+ .hsk-cb-topbar-icon {
1420
+ font-size: 22px;
1421
+ color: #ff6a33;
1422
+ line-height: 1;
1423
+ animation: hsk-sparkle-spin 6s linear infinite;
1424
+ }
1425
+ @keyframes hsk-sparkle-spin {
1426
+ 0%,100% { transform: rotate(0deg) scale(1); }
1427
+ 25% { transform: rotate(15deg) scale(1.1); }
1428
+ 75% { transform: rotate(-10deg) scale(.95); }
1429
+ }
1430
+ .hsk-cb-topbar-title {
1431
+ font-size: 16px;
1432
+ font-weight: 700;
1433
+ color: var(--hsk-chat-text, #111);
1434
+ letter-spacing: -.01em;
1435
+ }
1436
+ .hsk-cb-topbar-sub {
1437
+ font-size: 12px;
1438
+ color: var(--hsk-chat-muted, #888);
1439
+ margin-top: 2px;
1440
+ }
1441
+ .hsk-cb-topbar-actions {
1442
+ display: flex;
1443
+ align-items: center;
1444
+ gap: 8px;
1445
+ }
1446
+ .hsk-cb-topbar-btn {
1447
+ height: 34px;
1448
+ padding: 0 14px;
1449
+ border-radius: 8px;
1450
+ border: 1px solid var(--hsk-chat-divide, rgba(0,0,0,.1));
1451
+ background: none;
1452
+ color: var(--hsk-chat-muted, #888);
1453
+ font-size: 12px;
1454
+ font-weight: 500;
1455
+ cursor: pointer;
1456
+ transition: all .15s;
1457
+ font-family: inherit;
1458
+ }
1459
+ .hsk-cb-topbar-btn:hover {
1460
+ border-color: #ff6a33;
1461
+ color: #ff6a33;
1462
+ }
1463
+ .hsk-cb-close {
1464
+ width: 34px; height: 34px;
1465
+ border-radius: 8px;
1466
+ border: 1px solid var(--hsk-chat-divide, rgba(0,0,0,.1));
1467
+ background: none;
1468
+ color: var(--hsk-chat-muted, #888);
1469
+ cursor: pointer;
1470
+ font-size: 20px;
1471
+ display: flex; align-items: center; justify-content: center;
1472
+ transition: all .15s;
1473
+ flex-shrink: 0;
1474
+ font-family: inherit;
1475
+ line-height: 1;
1476
+ }
1477
+ .hsk-cb-close:hover { border-color: #ff6a33; color: #ff6a33; }
1478
+
1479
+ /* \u2500\u2500 Messages scroll area \u2500\u2500 */
1480
+ .hsk-cb-msgs {
1481
+ flex: 1;
1482
+ overflow-y: auto;
1483
+ padding: 8px 28px 0;
1484
+ display: flex;
1485
+ flex-direction: column;
1486
+ gap: 0;
1487
+ scroll-behavior: smooth;
1488
+ scrollbar-width: thin;
1489
+ scrollbar-color: var(--hsk-chat-divide, rgba(0,0,0,.1)) transparent;
1490
+ }
1491
+
1492
+ /* \u2500\u2500 Empty / welcome state \u2500\u2500 */
1493
+ .hsk-cb-empty {
1494
+ flex: 1;
1495
+ display: flex;
1496
+ flex-direction: column;
1497
+ align-items: center;
1498
+ justify-content: center;
1499
+ gap: 20px;
1500
+ padding: 60px 32px;
1501
+ text-align: center;
1502
+ }
1503
+ .hsk-cb-empty-icon {
1504
+ font-size: 48px;
1505
+ color: #ff6a33;
1506
+ animation: hsk-sparkle-spin 4s linear infinite;
1507
+ }
1508
+ .hsk-cb-empty-title {
1509
+ font-size: 26px;
1510
+ font-weight: 700;
1511
+ color: var(--hsk-chat-text, #111);
1512
+ letter-spacing: -.02em;
1513
+ }
1514
+ .hsk-cb-empty-sub {
1515
+ font-size: 14px;
1516
+ color: var(--hsk-chat-muted, #888);
1517
+ line-height: 1.7;
1518
+ max-width: 380px;
1519
+ }
1520
+ .hsk-cb-chips {
1521
+ display: flex;
1522
+ flex-wrap: wrap;
1523
+ gap: 8px;
1524
+ justify-content: center;
1525
+ margin-top: 4px;
1526
+ }
1527
+ .hsk-cb-chip {
1528
+ padding: 8px 16px;
1529
+ border-radius: 9999px;
1530
+ border: 1px solid var(--hsk-chat-divide, rgba(0,0,0,.1));
1531
+ background: var(--hsk-chat-source-bg, rgba(0,0,0,.03));
1532
+ color: var(--hsk-chat-text, #333);
1533
+ font-size: 13px;
1534
+ cursor: pointer;
1535
+ transition: all .15s;
1536
+ font-family: inherit;
1537
+ }
1538
+ .hsk-cb-chip:hover {
1539
+ border-color: #ff6a33;
1540
+ color: #ff6a33;
1541
+ background: rgba(255,106,51,.06);
1542
+ }
1543
+
1544
+ /* \u2500\u2500 Message rows \u2500\u2500 */
1545
+ .hsk-cb-msg-group {
1546
+ padding: 20px 0;
1547
+ border-bottom: 1px solid var(--hsk-chat-divide, rgba(0,0,0,.05));
1548
+ animation: hsk-msg-in .22s ease-out both;
1549
+ }
1550
+ .hsk-cb-msg-group:last-child { border-bottom: none; }
1551
+ @keyframes hsk-msg-in {
1552
+ from { opacity: 0; transform: translateY(10px); }
1553
+ to { opacity: 1; transform: translateY(0); }
1554
+ }
1555
+
1556
+ /* User message */
1557
+ .hsk-cb-user-msg {
1558
+ display: flex;
1559
+ justify-content: flex-end;
1560
+ margin-bottom: 20px;
1561
+ }
1562
+ .hsk-cb-user-bubble {
1563
+ background: #ff6a33;
1564
+ color: #fff;
1565
+ padding: 12px 20px;
1566
+ border-radius: 22px 22px 6px 22px;
1567
+ font-size: 15px;
1568
+ line-height: 1.6;
1569
+ max-width: 72%;
1570
+ font-weight: 500;
1571
+ }
1572
+
1573
+ /* AI message - no bubble, just clean text like Claude */
1574
+ .hsk-cb-ai-msg {
1575
+ display: flex;
1576
+ align-items: flex-start;
1577
+ gap: 14px;
1578
+ }
1579
+ .hsk-cb-ai-icon {
1580
+ width: 28px; height: 28px;
1581
+ border-radius: 50%;
1582
+ background: rgba(255,106,51,.12);
1583
+ border: 1px solid rgba(255,106,51,.25);
1584
+ color: #ff6a33;
1585
+ font-size: 13px;
1586
+ display: flex; align-items: center; justify-content: center;
1587
+ flex-shrink: 0;
1588
+ margin-top: 2px;
1589
+ }
1590
+ .hsk-cb-ai-body { flex: 1; min-width: 0; }
1591
+ .hsk-cb-ai-text {
1592
+ font-size: 15px;
1593
+ line-height: 1.75;
1594
+ color: var(--hsk-chat-text, #111);
1595
+ white-space: pre-wrap;
1596
+ }
1597
+
1598
+ /* \u2500\u2500 Sources horizontal carousel \u2500\u2500 */
1599
+ .hsk-cb-sources-wrap {
1600
+ position: relative;
1601
+ margin-top: 20px;
1602
+ }
1603
+ .hsk-cb-sources {
1604
+ display: flex;
1605
+ flex-direction: row;
1606
+ gap: 14px;
1607
+ overflow-x: auto;
1608
+ scroll-snap-type: x mandatory;
1609
+ scrollbar-width: none;
1610
+ -ms-overflow-style: none;
1611
+ padding-bottom: 4px;
1612
+ }
1613
+ .hsk-cb-sources::-webkit-scrollbar { display: none; }
1614
+ /* Feathered right edge */
1615
+ .hsk-cb-sources-fade {
1616
+ position: absolute;
1617
+ right: 0; top: 0; bottom: 4px;
1618
+ width: 90px;
1619
+ pointer-events: none;
1620
+ }
1621
+ /* Scroll-next pill */
1622
+ .hsk-cb-sources-next {
1623
+ position: absolute;
1624
+ right: 10px;
1625
+ top: 50%;
1626
+ transform: translateY(-50%);
1627
+ width: 30px; height: 30px;
1628
+ border-radius: 50%;
1629
+ border: 1px solid var(--hsk-chat-divide, rgba(0,0,0,.12));
1630
+ background: var(--hsk-chat-bg, #0e0e0f);
1631
+ color: var(--hsk-chat-text, #eee);
1632
+ cursor: pointer;
1633
+ font-size: 16px;
1634
+ display: flex; align-items: center; justify-content: center;
1635
+ box-shadow: 0 2px 12px rgba(0,0,0,.2);
1636
+ transition: all .15s;
1637
+ z-index: 3;
1638
+ font-family: inherit;
1639
+ line-height: 1;
1640
+ }
1641
+ .hsk-cb-sources-next:hover { border-color: #ff6a33; color: #ff6a33; }
1642
+ /* Card: no border, no bg, no radius \u2014 clean dark canvas */
1643
+ .hsk-cb-source {
1644
+ flex: 0 0 188px;
1645
+ scroll-snap-align: start;
1646
+ border-radius: 0;
1647
+ border: none;
1648
+ background: transparent;
1649
+ cursor: pointer;
1650
+ transition: transform .14s, opacity .14s;
1651
+ animation: hsk-card-in .26s ease-out both;
1652
+ overflow: visible;
1653
+ }
1654
+ @keyframes hsk-card-in {
1655
+ from { opacity: 0; transform: translateX(16px); }
1656
+ to { opacity: 1; transform: none; }
1657
+ }
1658
+ .hsk-cb-source:hover { transform: translateY(-3px); opacity: .92; }
1659
+ .hsk-cb-src-imgwrap {
1660
+ width: 188px;
1661
+ height: 188px;
1662
+ overflow: hidden;
1663
+ border-radius: 0;
1664
+ display: block;
1665
+ }
1666
+ .hsk-cb-src-imgwrap img {
1667
+ width: 100%; height: 100%;
1668
+ object-fit: cover;
1669
+ transition: transform .22s;
1670
+ display: block;
1671
+ }
1672
+ .hsk-cb-source:hover .hsk-cb-src-imgwrap img { transform: scale(1.05); }
1673
+ .hsk-cb-src-imgwrap-empty {
1674
+ width: 188px;
1675
+ height: 188px;
1676
+ background: var(--hsk-chat-divide, rgba(255,255,255,.06));
1677
+ display: flex; align-items: center; justify-content: center;
1678
+ color: var(--hsk-chat-muted, #555);
1679
+ font-size: 32px;
1680
+ }
1681
+ .hsk-cb-src-info {
1682
+ padding: 8px 2px 0;
1683
+ }
1684
+ .hsk-cb-src-name {
1685
+ font-size: 13px;
1686
+ font-weight: 600;
1687
+ color: var(--hsk-chat-text, #eee);
1688
+ line-height: 1.4;
1689
+ display: -webkit-box;
1690
+ -webkit-line-clamp: 2;
1691
+ -webkit-box-orient: vertical;
1692
+ overflow: hidden;
1693
+ }
1694
+ .hsk-cb-src-price {
1695
+ font-size: 13px;
1696
+ color: #ff6a33;
1697
+ font-weight: 700;
1698
+ margin-top: 3px;
1699
+ }
1700
+
1701
+ /* \u2500\u2500 Selected product inline card \u2500\u2500 */
1702
+ .hsk-cb-selected-product {
1703
+ display: flex;
1704
+ align-items: flex-start;
1705
+ gap: 14px;
1706
+ margin-top: 16px;
1707
+ padding: 14px;
1708
+ border: 1px solid var(--hsk-chat-divide, rgba(255,255,255,.08));
1709
+ border-left: 3px solid #ff6a33;
1710
+ background: var(--hsk-chat-source-bg, rgba(255,255,255,.03));
1711
+ cursor: pointer;
1712
+ transition: border-color .15s;
1713
+ animation: hsk-msg-in .2s ease-out both;
1714
+ }
1715
+ .hsk-cb-selected-product:hover { border-left-color: rgba(255,106,51,.6); }
1716
+ .hsk-cb-selected-img {
1717
+ width: 64px; height: 64px;
1718
+ object-fit: cover;
1719
+ flex-shrink: 0;
1720
+ }
1721
+ .hsk-cb-selected-info { flex: 1; min-width: 0; }
1722
+ .hsk-cb-selected-name {
1723
+ font-size: 13px; font-weight: 700;
1724
+ color: var(--hsk-chat-text, #eee);
1725
+ margin-bottom: 3px;
1726
+ }
1727
+ .hsk-cb-selected-price {
1728
+ font-size: 13px; color: #ff6a33; font-weight: 700;
1729
+ }
1730
+
1731
+ /* \u2500\u2500 Typing indicator \u2500\u2500 */
1732
+ .hsk-cb-typing-row {
1733
+ display: flex;
1734
+ align-items: flex-start;
1735
+ gap: 14px;
1736
+ padding: 20px 0;
1737
+ }
1738
+ .hsk-cb-typing {
1739
+ display: flex;
1740
+ gap: 5px;
1741
+ padding: 14px 18px;
1742
+ }
1743
+ .hsk-cb-dot {
1744
+ width: 7px; height: 7px;
1745
+ border-radius: 50%;
1746
+ background: var(--hsk-chat-muted, #ccc);
1747
+ animation: hsk-dot-pulse 1.2s ease-in-out infinite;
1748
+ }
1749
+ .hsk-cb-dot:nth-child(2) { animation-delay: .18s; }
1750
+ .hsk-cb-dot:nth-child(3) { animation-delay: .36s; }
1751
+ @keyframes hsk-dot-pulse {
1752
+ 0%,100% { opacity: .3; transform: scale(.75); }
1753
+ 50% { opacity: 1; transform: scale(1); }
1754
+ }
1755
+
1756
+ /* \u2500\u2500 Input area \u2500\u2500 */
1757
+ .hsk-cb-input-wrap {
1758
+ padding: 16px 28px 28px;
1759
+ flex-shrink: 0;
1760
+ }
1761
+ .hsk-cb-input-box {
1762
+ display: flex;
1763
+ align-items: flex-end;
1764
+ gap: 10px;
1765
+ background: var(--hsk-chat-input-bg, rgba(0,0,0,.04));
1766
+ border: 1.5px solid var(--hsk-chat-divide, rgba(0,0,0,.1));
1767
+ border-radius: 18px;
1768
+ padding: 14px 14px 14px 20px;
1769
+ transition: border-color .15s, box-shadow .15s;
1770
+ }
1771
+ .hsk-cb-input-box:focus-within {
1772
+ border-color: #ff6a33;
1773
+ box-shadow: 0 0 0 3px rgba(255,106,51,.1);
1774
+ }
1775
+ .hsk-cb-textarea {
1776
+ flex: 1;
1777
+ background: transparent;
1778
+ border: none;
1779
+ outline: none;
1780
+ resize: none;
1781
+ font-size: 15px;
1782
+ color: var(--hsk-chat-text, #111);
1783
+ min-height: 24px;
1784
+ max-height: 140px;
1785
+ line-height: 1.55;
1786
+ font-family: inherit;
1787
+ }
1788
+ .hsk-cb-textarea::placeholder { color: var(--hsk-chat-muted, #aaa); }
1789
+ .hsk-cb-send {
1790
+ width: 38px; height: 38px;
1791
+ border-radius: 10px;
1792
+ background: #ff6a33;
1793
+ border: none;
1794
+ color: #fff;
1795
+ cursor: pointer;
1796
+ font-size: 18px;
1797
+ display: flex; align-items: center; justify-content: center;
1798
+ flex-shrink: 0;
1799
+ transition: opacity .15s, transform .1s, background .15s;
1800
+ font-family: inherit;
1801
+ }
1802
+ .hsk-cb-send:hover { opacity: .88; }
1803
+ .hsk-cb-send:active { transform: scale(.9); }
1804
+ .hsk-cb-send:disabled { opacity: .3; cursor: not-allowed; background: var(--hsk-chat-muted, #ccc); }
1805
+ .hsk-cb-hint {
1806
+ text-align: center;
1807
+ font-size: 11px;
1808
+ color: var(--hsk-chat-muted, #bbb);
1809
+ margin-top: 10px;
1810
+ }
1811
+
1812
+ /* \u2500\u2500 Error \u2500\u2500 */
1813
+ .hsk-cb-error {
1814
+ margin: 8px 0;
1815
+ padding: 10px 14px;
1816
+ border-radius: 10px;
1817
+ background: rgba(239,68,68,.08);
1818
+ border: 1px solid rgba(239,68,68,.2);
1819
+ color: #ef4444;
1820
+ font-size: 13px;
1821
+ }
1822
+
1823
+ /* \u2500\u2500 Dark mode \u2500\u2500 */
1824
+ @media (prefers-color-scheme: dark) {
1825
+ .hsk-cb-overlay {
1826
+ --hsk-chat-bg: #0e0e0f;
1827
+ --hsk-chat-text: #f0efed;
1828
+ --hsk-chat-muted: #555;
1829
+ --hsk-chat-divide: rgba(255,255,255,.07);
1830
+ --hsk-chat-input-bg: rgba(255,255,255,.05);
1831
+ --hsk-chat-source-bg: rgba(255,255,255,.04);
1832
+ --hsk-fade-bg: #0e0e0f;
1833
+ }
1834
+ .hsk-cb-overlay {
1835
+ background: rgba(0,0,0,.92) !important;
1836
+ }
1837
+ }
1838
+ @media (prefers-color-scheme: light) {
1839
+ .hsk-cb-overlay {
1840
+ --hsk-chat-bg: #fafafa;
1841
+ --hsk-chat-text: #111;
1842
+ --hsk-chat-muted: #999;
1843
+ --hsk-chat-divide: rgba(0,0,0,.08);
1844
+ --hsk-chat-input-bg: rgba(0,0,0,.04);
1845
+ --hsk-chat-source-bg: rgba(0,0,0,.025);
1846
+ --hsk-fade-bg: #fafafa;
1847
+ }
1848
+ .hsk-cb-overlay {
1849
+ background: rgba(240,240,244,.88) !important;
1850
+ }
1851
+ }
1852
+ `;
1853
+ var CHIPS = [
1854
+ "Cheapest smartphone",
1855
+ "Smart TV under KSh 20,000",
1856
+ "Noise-cancelling headphones",
1857
+ "Best laptop for students"
1858
+ ];
1859
+ function SourcesCarousel({ sources, onSelectSource }) {
1860
+ const railRef = (0, import_react10.useRef)(null);
1861
+ const [showNext, setShowNext] = (0, import_react10.useState)(false);
1862
+ const measure = (0, import_react10.useCallback)(() => {
1863
+ const el = railRef.current;
1864
+ if (!el) return;
1865
+ const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
1866
+ setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
1867
+ }, []);
1868
+ (0, import_react10.useEffect)(() => {
1869
+ measure();
1870
+ const el = railRef.current;
1871
+ if (!el) return;
1872
+ const ro = new ResizeObserver(measure);
1873
+ ro.observe(el);
1874
+ el.addEventListener("scroll", measure, { passive: true });
1875
+ return () => {
1876
+ ro.disconnect();
1877
+ el.removeEventListener("scroll", measure);
1878
+ };
1879
+ }, [measure, sources]);
1880
+ const scrollNext = () => {
1881
+ var _a;
1882
+ (_a = railRef.current) == null ? void 0 : _a.scrollBy({ left: 170, behavior: "smooth" });
1883
+ };
1884
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-sources-wrap", children: [
1885
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-sources", ref: railRef, children: sources.map((src, si) => {
1886
+ var _a;
1887
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1888
+ "div",
1889
+ {
1890
+ className: "hsk-cb-source",
1891
+ style: { animationDelay: `${si * 50}ms` },
1892
+ onClick: () => onSelectSource == null ? void 0 : onSelectSource(src),
1893
+ children: [
1894
+ src.image ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-src-imgwrap", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: src.image, alt: src.name, loading: "lazy" }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-src-imgwrap-empty", children: "\u2726" }),
1895
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-src-info", children: [
1896
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-src-name", children: src.name }),
1897
+ src.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-src-price", children: [
1898
+ (_a = src.currency) != null ? _a : "KES",
1899
+ " ",
1900
+ parseFloat(src.price.replace(/[^0-9.]/g, "") || "0").toLocaleString()
1901
+ ] })
1902
+ ] })
1903
+ ]
1904
+ },
1905
+ si
1906
+ );
1907
+ }) }),
1908
+ showNext && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1909
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1910
+ "div",
1911
+ {
1912
+ className: "hsk-cb-sources-fade",
1913
+ style: { background: "linear-gradient(to right, transparent, var(--hsk-fade-bg, #0e0e0f))" }
1914
+ }
1915
+ ),
1916
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hsk-cb-sources-next", onClick: scrollNext, "aria-label": "See more", children: "\u203A" })
1917
+ ] })
1918
+ ] });
1919
+ }
1920
+ function ChatModal({
1921
+ title = "AI Shopping Assistant",
1922
+ placeholder = "Ask me anything \u2014 gifts, budget, use case\u2026",
1923
+ backdropColor,
1924
+ backdropBlur,
1925
+ onClose,
1926
+ onSelectSource
1927
+ }) {
1928
+ var _a, _b;
1929
+ const { messages, sources, loading, error, send, reset } = useChat();
1930
+ const [input, setInput] = (0, import_react10.useState)("");
1931
+ const [selectedProduct, setSelectedProduct] = (0, import_react10.useState)(null);
1932
+ const bottomRef = (0, import_react10.useRef)(null);
1933
+ const textareaRef = (0, import_react10.useRef)(null);
1934
+ (0, import_react10.useEffect)(() => {
1935
+ var _a2;
1936
+ (_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
1937
+ }, [messages, loading, selectedProduct]);
1938
+ (0, import_react10.useEffect)(() => {
1939
+ const h = (e) => {
1940
+ if (e.key === "Escape") onClose();
1941
+ };
1942
+ document.addEventListener("keydown", h);
1943
+ return () => document.removeEventListener("keydown", h);
1944
+ }, []);
1945
+ const handleSourceClick = (src) => {
1946
+ var _a2;
1947
+ setSelectedProduct(src);
1948
+ onSelectSource == null ? void 0 : onSelectSource(src);
1949
+ const q = `Tell me more about the ${src.name}${src.price ? ` (${(_a2 = src.currency) != null ? _a2 : "KES"} ${src.price})` : ""} \u2014 what are its key specs, who is it best for, and is it worth buying?`;
1950
+ send(q);
1951
+ };
1952
+ const handleSend = async (text) => {
1953
+ const q = (text != null ? text : input).trim();
1954
+ if (!q || loading) return;
1955
+ setSelectedProduct(null);
1956
+ setInput("");
1957
+ if (textareaRef.current) {
1958
+ textareaRef.current.style.height = "auto";
1959
+ }
1960
+ await send(q);
1961
+ };
1962
+ const handleKeyDown = (e) => {
1963
+ if (e.key === "Enter" && !e.shiftKey) {
1964
+ e.preventDefault();
1965
+ handleSend();
1966
+ }
1967
+ };
1968
+ const handleInput = (e) => {
1969
+ setInput(e.target.value);
1970
+ const t = e.target;
1971
+ t.style.height = "auto";
1972
+ t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
1973
+ };
1974
+ const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "20px";
1975
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1976
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: CSS3 }),
1977
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1978
+ "div",
1979
+ {
1980
+ className: "hsk-cb-overlay",
1981
+ onClick: onClose,
1982
+ style: __spreadValues({
1983
+ backdropFilter: `blur(${blurVal})`,
1984
+ WebkitBackdropFilter: `blur(${blurVal})`
1985
+ }, backdropColor ? { background: backdropColor } : {}),
1986
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-panel", onClick: (e) => e.stopPropagation(), children: [
1987
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-topbar", children: [
1988
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-topbar-left", children: [
1989
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-cb-topbar-icon", children: "\u2726" }),
1990
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1991
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-topbar-title", children: title }),
1992
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-topbar-sub", children: "Powered by Huskel AI \xB7 searches the whole catalogue" })
1993
+ ] })
1994
+ ] }),
1995
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-topbar-actions", children: [
1996
+ messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hsk-cb-topbar-btn", onClick: reset, children: "Clear chat" }),
1997
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hsk-cb-close", onClick: onClose, "aria-label": "Close", children: "\xD7" })
1998
+ ] })
1999
+ ] }),
2000
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-msgs", children: [
2001
+ messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-empty", children: [
2002
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-empty-icon", children: "\u2726" }),
2003
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-empty-title", children: "What can I help you find?" }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-empty-sub", children: "Ask about products, budgets, gift ideas, specs \u2014 I'll search the entire catalogue for you." }),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-chips", children: CHIPS.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2006
+ "button",
2007
+ {
2008
+ className: "hsk-cb-chip",
2009
+ onClick: () => handleSend(chip),
2010
+ children: chip
2011
+ },
2012
+ chip
2013
+ )) })
2014
+ ] }) : messages.map((msg, idx) => {
2015
+ const isLast = idx === messages.length - 1;
2016
+ const isUser = msg.role === "user";
2017
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-msg-group", children: isUser ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-user-msg", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-user-bubble", children: msg.content }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2018
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-ai-icon", children: "\u2726" }),
2019
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-ai-body", children: [
2020
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-ai-text", children: msg.content }),
2021
+ isLast && sources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2022
+ SourcesCarousel,
2023
+ {
2024
+ sources,
2025
+ onSelectSource: handleSourceClick
2026
+ }
2027
+ )
2028
+ ] })
2029
+ ] }) }, idx);
2030
+ }),
2031
+ selectedProduct && loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2032
+ "div",
2033
+ {
2034
+ className: "hsk-cb-selected-product",
2035
+ onClick: () => selectedProduct.url && window.open(selectedProduct.url, "_blank"),
2036
+ children: [
2037
+ selectedProduct.image && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { className: "hsk-cb-selected-img", src: selectedProduct.image, alt: selectedProduct.name }),
2038
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-selected-info", children: [
2039
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-selected-name", children: selectedProduct.name }),
2040
+ selectedProduct.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-selected-price", children: [
2041
+ (_a = selectedProduct.currency) != null ? _a : "KES",
2042
+ " ",
2043
+ parseFloat(((_b = selectedProduct.price) != null ? _b : "").replace(/[^0-9.]/g, "") || "0").toLocaleString()
2044
+ ] })
2045
+ ] })
2046
+ ]
2047
+ }
2048
+ ),
2049
+ loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-typing-row", children: [
2050
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-ai-icon", children: "\u2726" }),
2051
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-typing", children: [
2052
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-dot" }),
2053
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-dot" }),
2054
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-dot" })
2055
+ ] })
2056
+ ] }),
2057
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-error", children: error }),
2058
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: bottomRef, style: { height: 1 } })
2059
+ ] }),
2060
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-input-wrap", children: [
2061
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-input-box", children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2063
+ "textarea",
2064
+ {
2065
+ ref: textareaRef,
2066
+ className: "hsk-cb-textarea",
2067
+ value: input,
2068
+ onChange: handleInput,
2069
+ onKeyDown: handleKeyDown,
2070
+ placeholder,
2071
+ rows: 1,
2072
+ disabled: loading,
2073
+ autoFocus: true
2074
+ }
2075
+ ),
2076
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2077
+ "button",
2078
+ {
2079
+ className: "hsk-cb-send",
2080
+ onClick: () => handleSend(),
2081
+ disabled: !input.trim() || loading,
2082
+ "aria-label": "Send",
2083
+ children: "\u2191"
2084
+ }
2085
+ )
2086
+ ] }),
2087
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-hint", children: "Huskel AI \xB7 searches the whole catalogue in real time" })
2088
+ ] })
2089
+ ] })
2090
+ }
2091
+ )
2092
+ ] });
2093
+ }
2094
+ function AIChatButton({
2095
+ label,
2096
+ title,
2097
+ placeholder,
2098
+ backdropColor,
2099
+ backdropBlur,
2100
+ className,
2101
+ onSelectSource
2102
+ }) {
2103
+ const [open, setOpen] = (0, import_react10.useState)(false);
2104
+ const [mounted, setMounted] = (0, import_react10.useState)(false);
2105
+ (0, import_react10.useEffect)(() => {
2106
+ setMounted(true);
2107
+ }, []);
2108
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2109
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: CSS3 }),
2110
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2111
+ "button",
2112
+ {
2113
+ className: `hsk-cb-btn ${className != null ? className : ""}`,
2114
+ onClick: () => setOpen(true),
2115
+ "aria-label": "Open AI chat",
2116
+ children: [
2117
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-cb-btn-icon", children: "\u2726" }),
2118
+ label !== void 0 ? label : null
2119
+ ]
2120
+ }
2121
+ ),
2122
+ open && mounted && (0, import_react_dom2.createPortal)(
2123
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2124
+ ChatModal,
2125
+ {
2126
+ title,
2127
+ placeholder,
2128
+ backdropColor,
2129
+ backdropBlur,
2130
+ onClose: () => setOpen(false),
2131
+ onSelectSource
2132
+ }
2133
+ ),
2134
+ document.body
2135
+ )
2136
+ ] });
2137
+ }
846
2138
  // Annotate the CommonJS export names for ESM import in node:
847
2139
  0 && (module.exports = {
2140
+ AIChatButton,
848
2141
  ChatWidget,
849
2142
  HuskelAPI,
850
2143
  HuskelClient,