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