@blotoutio/providers-evo-search-sdk 1.65.2 → 1.66.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/index.js CHANGED
@@ -1,6 +1,26 @@
1
1
  var ProvidersEvoSearchSdk = (function () {
2
2
  'use strict';
3
3
 
4
+ // When the Search tab offers Ask AI alongside its results. `always`: on every
5
+ // result set (the default — the tab pill alone doesn't read as an offer).
6
+ // `weak`: only when retrieval had no lexical anchor, i.e. the response's
7
+ // `signals.bm25` is false, so the products on screen are a semantic guess.
8
+ // `never`: the search results never mention Ask AI (the zero-result recovery
9
+ // CTA is unaffected — that one is a dead end, not a nudge).
10
+ // The tuple is the single source of truth: the API payload enum, the worker's
11
+ // write validator and the manifest coercion all derive from it, so adding a
12
+ // mode can't leave one layer rejecting it.
13
+ // Default on: the segmented tab pill alone doesn't read as an offer, which is
14
+ // the whole reason the nudge exists. Merchants dial it back to 'weak'/'never'.
15
+ const DEFAULT_ASK_NUDGE_MODE = 'always';
16
+ // Merchant-authored label for the nudge. `{query}` is substituted with the
17
+ // shopper's search terms; a label without the token renders verbatim, which is
18
+ // the escape hatch for merchants who don't want the query echoed back.
19
+ // Substitution happens through Lit text bindings, so the query is escaped —
20
+ // never interpolate this into markup.
21
+ const ASK_NUDGE_QUERY_TOKEN = '{query}';
22
+ const DEFAULT_ASK_NUDGE_LABEL = `Ask AI about ${ASK_NUDGE_QUERY_TOKEN}`;
23
+
4
24
  /**
5
25
  * Known ad-network click ID query parameters and the human-readable provider
6
26
  * label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
@@ -631,7 +651,7 @@ var ProvidersEvoSearchSdk = (function () {
631
651
  const searchSessionKeyName = 'evoSearchSession';
632
652
  const SEARCH_SESSION_VERSION = 1;
633
653
  const getStoredSearchSession = () => {
634
- var _a, _b, _c, _d, _e, _f, _g;
654
+ var _a, _b, _c, _d, _e, _f, _g, _h;
635
655
  try {
636
656
  const raw = sessionStorage.getItem(searchSessionKeyName);
637
657
  if (!raw) {
@@ -653,10 +673,12 @@ var ProvidersEvoSearchSdk = (function () {
653
673
  suggestions: (_a = parsed.suggestions) !== null && _a !== void 0 ? _a : [],
654
674
  products: (_b = parsed.products) !== null && _b !== void 0 ? _b : [],
655
675
  collections: (_c = parsed.collections) !== null && _c !== void 0 ? _c : [],
656
- askAnswer: (_d = parsed.askAnswer) !== null && _d !== void 0 ? _d : '',
657
- askEvidence: (_e = parsed.askEvidence) !== null && _e !== void 0 ? _e : null,
658
- askFollowUps: (_f = parsed.askFollowUps) !== null && _f !== void 0 ? _f : [],
659
- askCitedTitles: (_g = parsed.askCitedTitles) !== null && _g !== void 0 ? _g : [],
676
+ // Absent on pre-nudge snapshots — true matches the widget's own default.
677
+ hadLexicalMatch: (_d = parsed.hadLexicalMatch) !== null && _d !== void 0 ? _d : true,
678
+ askAnswer: (_e = parsed.askAnswer) !== null && _e !== void 0 ? _e : '',
679
+ askEvidence: (_f = parsed.askEvidence) !== null && _f !== void 0 ? _f : null,
680
+ askFollowUps: (_g = parsed.askFollowUps) !== null && _g !== void 0 ? _g : [],
681
+ askCitedTitles: (_h = parsed.askCitedTitles) !== null && _h !== void 0 ? _h : [],
660
682
  };
661
683
  }
662
684
  catch {
@@ -1430,6 +1452,7 @@ var ProvidersEvoSearchSdk = (function () {
1430
1452
  const tag = ({ data }) => {
1431
1453
  try {
1432
1454
  return {
1455
+ sdkVersion: "1.66.0" || '',
1433
1456
  searchQuery: data['searchQuery'],
1434
1457
  searchResults: data['searchResults'],
1435
1458
  timestamp: Date.now(),
@@ -1438,6 +1461,7 @@ var ProvidersEvoSearchSdk = (function () {
1438
1461
  catch (err) {
1439
1462
  console.error(`EvoSearch SDK: Tagging failed - ${err}`);
1440
1463
  return {
1464
+ sdkVersion: "1.66.0" ,
1441
1465
  timestamp: Date.now(),
1442
1466
  };
1443
1467
  }
@@ -7567,12 +7591,12 @@ var ProvidersEvoSearchSdk = (function () {
7567
7591
 
7568
7592
  /* The AI rescue is the primary recovery on a dead-end search. */
7569
7593
  .chip-ask {
7570
- background: var(--evosearch-ask-accent, #2563eb);
7594
+ background: var(--evosearch-ask-accent, #7c3aed);
7571
7595
  color: #ffffff;
7572
7596
  }
7573
7597
 
7574
7598
  .chip-ask:hover {
7575
- background: var(--evosearch-ask-accent, #2563eb);
7599
+ background: var(--evosearch-ask-accent, #7c3aed);
7576
7600
  filter: brightness(0.95);
7577
7601
  }
7578
7602
 
@@ -7841,7 +7865,7 @@ var ProvidersEvoSearchSdk = (function () {
7841
7865
  /* Ask mode swaps the active accent; everything inside the container reads
7842
7866
  var(--evosearch-mode-accent) (custom props inherit to evs-box + cards). */
7843
7867
  .search-widget-container[data-mode='ask'] {
7844
- --evosearch-mode-accent: var(--evosearch-ask-accent, #2563eb);
7868
+ --evosearch-mode-accent: var(--evosearch-ask-accent, #7c3aed);
7845
7869
  }
7846
7870
 
7847
7871
  /* The theme font reaches form controls (which don't inherit it by default). */
@@ -8091,6 +8115,12 @@ var ProvidersEvoSearchSdk = (function () {
8091
8115
  max-height: 0;
8092
8116
  opacity: 0;
8093
8117
  pointer-events: none;
8118
+ /* The collapsed row is still a .body child, so the sibling rule above gives
8119
+ it a top margin AND another to whatever follows — 2.25em of dead space
8120
+ stacked around a zero-height element, which reads as a broken gap between
8121
+ the nudge and the results heading. Drop its own margin so the only gap
8122
+ left is the single one the next section is entitled to. */
8123
+ margin-top: 0;
8094
8124
  }
8095
8125
 
8096
8126
  /* inline-block (not flex) so the typed span and the bold completion stay on
@@ -8218,9 +8248,6 @@ var ProvidersEvoSearchSdk = (function () {
8218
8248
 
8219
8249
  /* Segmented Results/AI toggle — replaces the "Search with AI" CTA once an
8220
8250
  answer is in memory, letting the shopper flip between the two views. */
8221
- /* Dual-tone segmented control: two equal halves with a thumb that slides
8222
- between them (driven by the container's data-mode). The thumb is neutral
8223
- under Search and gains an amethyst→indigo wash under Ask AI. */
8224
8251
  /* Compact segmented control living inside the search bar. Two equal halves
8225
8252
  (grid 1fr 1fr) sized to their content so the sliding thumb stays aligned
8226
8253
  at 50%; flex:0 0 auto keeps it from stretching in the header row. */
@@ -8272,13 +8299,17 @@ var ProvidersEvoSearchSdk = (function () {
8272
8299
  .search-widget-container[data-mode='ask'] .view-toggle::before {
8273
8300
  transform: translateX(100%);
8274
8301
  border-radius: 0 999px 999px 0;
8275
- background: linear-gradient(
8276
- 135deg,
8277
- rgba(147, 51, 234, 0.12),
8278
- rgba(99, 102, 241, 0.1)
8279
- ),
8280
- #ffffff;
8281
- box-shadow: 0 1px 4px rgba(76, 29, 149, 0.2),
8302
+ background: color-mix(
8303
+ in srgb,
8304
+ var(--evosearch-ask-accent, #7c3aed) 10%,
8305
+ #ffffff
8306
+ );
8307
+ box-shadow: 0 1px 4px
8308
+ color-mix(
8309
+ in srgb,
8310
+ var(--evosearch-ask-accent, #7c3aed) 22%,
8311
+ transparent
8312
+ ),
8282
8313
  inset 0 -0.5px 0.5px rgba(10, 13, 18, 0.08);
8283
8314
  }
8284
8315
 
@@ -8314,11 +8345,13 @@ var ProvidersEvoSearchSdk = (function () {
8314
8345
  }
8315
8346
 
8316
8347
  .view-toggle-search:focus-visible {
8317
- box-shadow: 0 0 0 3px rgba(234, 88, 12, 0.3);
8348
+ box-shadow: 0 0 0 3px
8349
+ color-mix(in srgb, var(--evosearch-accent, #e0590b) 30%, transparent);
8318
8350
  }
8319
8351
 
8320
8352
  .view-toggle-ai:focus-visible {
8321
- box-shadow: 0 0 0 3px rgba(147, 51, 234, 0.3);
8353
+ box-shadow: 0 0 0 3px
8354
+ color-mix(in srgb, var(--evosearch-ask-accent, #7c3aed) 30%, transparent);
8322
8355
  }
8323
8356
 
8324
8357
  .view-toggle-search:hover:not(.is-active) {
@@ -8326,16 +8359,19 @@ var ProvidersEvoSearchSdk = (function () {
8326
8359
  }
8327
8360
 
8328
8361
  .view-toggle-ai:hover:not(.is-active) {
8329
- color: #9333ea;
8362
+ color: var(--evosearch-ask-accent, #7c3aed);
8330
8363
  }
8331
8364
 
8332
- /* Search vs Ask each carry their own accent when active — one coherent system. */
8365
+ /* Each half carries its own accent when active: Search the brand accent, Ask
8366
+ the --evosearch-ask-accent that state-block, the ask-answer rule and the
8367
+ follow-up chips already use — so one colour means "AI" everywhere rather
8368
+ than the toggle inventing a second one. */
8333
8369
  .view-toggle-search.is-active {
8334
- color: var(--evosearch-accent, #ea580c);
8370
+ color: var(--evosearch-accent, #e0590b);
8335
8371
  }
8336
8372
 
8337
8373
  .view-toggle-ai.is-active {
8338
- color: #9333ea;
8374
+ color: var(--evosearch-ask-accent, #7c3aed);
8339
8375
  }
8340
8376
 
8341
8377
  .view-toggle-btn bt-lucide-icon,
@@ -8344,7 +8380,7 @@ var ProvidersEvoSearchSdk = (function () {
8344
8380
  }
8345
8381
 
8346
8382
  .ask-answer {
8347
- border-left: 3px solid var(--evosearch-mode-accent, #2563eb);
8383
+ border-left: 3px solid var(--evosearch-mode-accent, #7c3aed);
8348
8384
  padding: 0 0 0 0.75em;
8349
8385
  line-height: 1.5;
8350
8386
  min-height: 1.5em;
@@ -8400,8 +8436,8 @@ var ProvidersEvoSearchSdk = (function () {
8400
8436
 
8401
8437
  .ask-starter-chip:hover {
8402
8438
  background: #fcfdfb;
8403
- border-color: var(--evosearch-ask-accent, #2563eb);
8404
- color: var(--evosearch-ask-accent, #2563eb);
8439
+ border-color: var(--evosearch-ask-accent, #7c3aed);
8440
+ color: var(--evosearch-ask-accent, #7c3aed);
8405
8441
  }
8406
8442
 
8407
8443
  .ask-starter-chip:active {
@@ -8499,6 +8535,89 @@ var ProvidersEvoSearchSdk = (function () {
8499
8535
  color: rgba(26, 28, 24, 0.6);
8500
8536
  }
8501
8537
 
8538
+ /* The Ask AI offer: a filled band leading the results column, directly under
8539
+ the search bar. It is the first thing in .body, so it is read before the
8540
+ grid is scanned rather than after it disappoints — and it can never be
8541
+ stranded in whitespace under a short result row. */
8542
+ .ask-nudge {
8543
+ appearance: none;
8544
+ display: flex;
8545
+ align-items: center;
8546
+ gap: 0.5em;
8547
+ width: 100%;
8548
+ padding: 0.625em 0.875em;
8549
+ border: 1px solid
8550
+ color-mix(in srgb, var(--evosearch-ask-accent, #7c3aed) 28%, transparent);
8551
+ border-radius: 0.625em;
8552
+ background: color-mix(
8553
+ in srgb,
8554
+ var(--evosearch-ask-accent, #7c3aed) 6%,
8555
+ #ffffff
8556
+ );
8557
+ color: var(--evosearch-ask-accent, #7c3aed);
8558
+ font-size: 0.8125em;
8559
+ font-weight: 600;
8560
+ text-align: left;
8561
+ cursor: pointer;
8562
+ transition: background 0.15s ease, border-color 0.15s ease;
8563
+ }
8564
+
8565
+ .ask-nudge:hover {
8566
+ background: color-mix(
8567
+ in srgb,
8568
+ var(--evosearch-ask-accent, #7c3aed) 11%,
8569
+ #ffffff
8570
+ );
8571
+ border-color: color-mix(
8572
+ in srgb,
8573
+ var(--evosearch-ask-accent, #7c3aed) 42%,
8574
+ transparent
8575
+ );
8576
+ }
8577
+
8578
+ /* The label takes the slack so the trailing arrow sits hard right, and a
8579
+ merchant-authored label that outruns the band ellipsises instead of
8580
+ wrapping it onto a third line. */
8581
+ .ask-nudge-text {
8582
+ flex: 1 1 auto;
8583
+ min-width: 0;
8584
+ overflow: hidden;
8585
+ text-overflow: ellipsis;
8586
+ white-space: nowrap;
8587
+ }
8588
+
8589
+ @supports (background-clip: text) or (-webkit-background-clip: text) {
8590
+ .ask-nudge-text {
8591
+ background: linear-gradient(
8592
+ 100deg,
8593
+ var(--evosearch-ask-accent, #7c3aed),
8594
+ var(--evosearch-ask-accent-alt, #6366f1)
8595
+ );
8596
+ -webkit-background-clip: text;
8597
+ background-clip: text;
8598
+ color: transparent;
8599
+ }
8600
+ }
8601
+
8602
+ /* Amethyst→indigo sweep on the label so the offer reads as generative rather
8603
+ than as a plain link. Guarded by @supports because the fallback for an
8604
+ unsupported background-clip is transparent text, i.e. an invisible CTA —
8605
+ outside the query the solid ask accent from .ask-nudge stands. The sparkle
8606
+ and arrow icons keep currentColor: gradient-filling icon strokes at 14px
8607
+ muddies them, and the solid accent still reads as the same family. */
8608
+ @supports (background-clip: text) or (-webkit-background-clip: text) {
8609
+ .ask-nudge > span {
8610
+ background: linear-gradient(
8611
+ 100deg,
8612
+ var(--evosearch-ask-accent, #7c3aed),
8613
+ var(--evosearch-ask-accent-alt, #6366f1)
8614
+ );
8615
+ -webkit-background-clip: text;
8616
+ background-clip: text;
8617
+ color: transparent;
8618
+ }
8619
+ }
8620
+
8502
8621
  .ask-followups {
8503
8622
  display: flex;
8504
8623
  flex-wrap: wrap;
@@ -8524,6 +8643,7 @@ var ProvidersEvoSearchSdk = (function () {
8524
8643
  .chip:focus-visible,
8525
8644
  .chip-remove:focus-visible,
8526
8645
  .suggestion:focus-visible,
8646
+ .ask-nudge:focus-visible,
8527
8647
  .view-toggle-btn:focus-visible,
8528
8648
  .bar-dismiss:focus-visible,
8529
8649
  .ask-starter-chip:focus-visible,
@@ -8540,7 +8660,7 @@ var ProvidersEvoSearchSdk = (function () {
8540
8660
  gap: 0.45em;
8541
8661
  border: 0;
8542
8662
  border-radius: 999px;
8543
- background: var(--evosearch-ask-accent, #2563eb);
8663
+ background: var(--evosearch-ask-accent, #7c3aed);
8544
8664
  color: #ffffff;
8545
8665
  font-size: 0.8125em;
8546
8666
  font-weight: 600;
@@ -8636,6 +8756,11 @@ var ProvidersEvoSearchSdk = (function () {
8636
8756
  this.suggestions = [];
8637
8757
  this.searchProducts = [];
8638
8758
  this.searchCollections = [];
8759
+ // Whether the current results had a lexical (BM25) anchor. False means the
8760
+ // grid is a purely semantic guess, which is what `askNudge: 'weak'` targets.
8761
+ // Defaults true so a worker that omits `signals` is treated as a normal
8762
+ // match rather than silently nudging on every query.
8763
+ this.hadLexicalMatch = true;
8639
8764
  this.recents = [];
8640
8765
  this.loading = false;
8641
8766
  this.hasError = false;
@@ -9218,7 +9343,7 @@ var ProvidersEvoSearchSdk = (function () {
9218
9343
  }));
9219
9344
  }
9220
9345
  async performAutocomplete(query) {
9221
- var _a;
9346
+ var _a, _b;
9222
9347
  const generation = ++this.requestGeneration;
9223
9348
  this.hasError = false;
9224
9349
  try {
@@ -9238,6 +9363,7 @@ var ProvidersEvoSearchSdk = (function () {
9238
9363
  this.suggestions = processed.suggestions;
9239
9364
  this.searchProducts = processed.products;
9240
9365
  this.searchCollections = (_a = response.collections) !== null && _a !== void 0 ? _a : [];
9366
+ this.hadLexicalMatch = ((_b = response.signals) === null || _b === void 0 ? void 0 : _b.bm25) !== false;
9241
9367
  this.debouncedQuery = query;
9242
9368
  this.loading = false;
9243
9369
  if (this.finalizeOnRender) {
@@ -9377,6 +9503,7 @@ var ProvidersEvoSearchSdk = (function () {
9377
9503
  this.showSuggestions = false;
9378
9504
  this.searchProducts = [];
9379
9505
  this.searchCollections = [];
9506
+ this.hadLexicalMatch = true;
9380
9507
  this.activeSuggestionIndex = -1;
9381
9508
  // Ask mode renders from a separate state set, so clearing the query alone
9382
9509
  // leaves the previous answer/evidence on screen. Reset it too and abort any
@@ -9478,6 +9605,7 @@ var ProvidersEvoSearchSdk = (function () {
9478
9605
  suggestions: this.suggestions,
9479
9606
  products: this.searchProducts,
9480
9607
  collections: this.searchCollections,
9608
+ hadLexicalMatch: this.hadLexicalMatch,
9481
9609
  askAnswer: this.askAnswer,
9482
9610
  askEvidence: this.askEvidence,
9483
9611
  askFollowUps: this.askFollowUps,
@@ -9498,6 +9626,9 @@ var ProvidersEvoSearchSdk = (function () {
9498
9626
  this.suggestions = snap.suggestions;
9499
9627
  this.searchProducts = snap.products;
9500
9628
  this.searchCollections = snap.collections;
9629
+ // Restored too, or a purely semantic result set would look like a lexical
9630
+ // match after a reopen and wrongly suppress the `weak` nudge.
9631
+ this.hadLexicalMatch = snap.hadLexicalMatch;
9501
9632
  // The restored query was already a real search in its first life; don't let
9502
9633
  // the settle/finalize path re-count it as a new one.
9503
9634
  this.lastFinalizedQuery = snap.query.trim().toLowerCase();
@@ -9543,7 +9674,11 @@ var ProvidersEvoSearchSdk = (function () {
9543
9674
  ${this.renderMain()}
9544
9675
  `;
9545
9676
  }
9677
+ // The nudge leads the column — directly under the search bar, above the
9678
+ // typeahead chips and the grid — so it is seen before the results are
9679
+ // scanned rather than after they disappoint.
9546
9680
  return b `
9681
+ ${this.shouldShowAskNudge() ? this.renderAskNudge() : A}
9547
9682
  ${this.cachedRailSections.map((section) => this.renderColumnSection(section))}
9548
9683
  ${this.renderMain()}
9549
9684
  `;
@@ -9934,25 +10069,12 @@ var ProvidersEvoSearchSdk = (function () {
9934
10069
  height="14"
9935
10070
  viewBox="0 0 24 24"
9936
10071
  fill="none"
9937
- stroke=${isAi ? 'url(#aiGrad)' : 'currentColor'}
10072
+ stroke="currentColor"
9938
10073
  stroke-width="2.2"
9939
10074
  stroke-linecap="round"
9940
10075
  stroke-linejoin="round"
9941
10076
  aria-hidden="true"
9942
10077
  >
9943
- <defs>
9944
- <linearGradient
9945
- id="aiGrad"
9946
- x1="0"
9947
- y1="0"
9948
- x2="24"
9949
- y2="24"
9950
- gradientUnits="userSpaceOnUse"
9951
- >
9952
- <stop offset="0" stop-color="#9333EA"></stop>
9953
- <stop offset="1" stop-color="#6366F1"></stop>
9954
- </linearGradient>
9955
- </defs>
9956
10078
  <path
9957
10079
  d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
9958
10080
  ></path>
@@ -10010,6 +10132,80 @@ var ProvidersEvoSearchSdk = (function () {
10010
10132
  </div>
10011
10133
  `;
10012
10134
  }
10135
+ // Whether the Ask AI offer belongs on screen right now. Lifted out of the
10136
+ // render so `renderColumn` can place the band above everything in the body
10137
+ // while still keeping the "real search results exist" conditions that only
10138
+ // renderMain used to know about.
10139
+ shouldShowAskNudge() {
10140
+ var _a, _b;
10141
+ if (!this.askActive || this.mode === 'ask') {
10142
+ return false;
10143
+ }
10144
+ const mode = (_b = (_a = this.uiPreferences) === null || _a === void 0 ? void 0 : _a.askNudge) !== null && _b !== void 0 ? _b : DEFAULT_ASK_NUDGE_MODE;
10145
+ if (mode === 'never' || (mode === 'weak' && this.hadLexicalMatch)) {
10146
+ return false;
10147
+ }
10148
+ // Never on the handpicked/empty state (no query to hand over), mid-load
10149
+ // (nothing to be unhappy with yet), on an error, or on the no-match state —
10150
+ // that one carries its own recovery CTA already. Collections count as
10151
+ // results: the same pair renderMain's `noMatches` tests, so a
10152
+ // collections-only match keeps the nudge rather than reading as empty.
10153
+ return (this.debouncedQuery.trim().length > 0 &&
10154
+ !this.loading &&
10155
+ !this.hasError &&
10156
+ (this.searchProducts.length > 0 || this.searchCollections.length > 0));
10157
+ }
10158
+ // Substitute the merchant's `{query}` token. Returned as separate strings so
10159
+ // Lit renders them as text bindings and escapes the shopper's query; never
10160
+ // build markup from this.
10161
+ askNudgeLabelParts(query) {
10162
+ var _a, _b;
10163
+ const label = ((_b = (_a = this.uiPreferences) === null || _a === void 0 ? void 0 : _a.askNudgeLabel) === null || _b === void 0 ? void 0 : _b.trim()) || DEFAULT_ASK_NUDGE_LABEL;
10164
+ return label.split(ASK_NUDGE_QUERY_TOKEN).join(`"${query}"`);
10165
+ }
10166
+ // Offer Ask AI above the results the shopper is about to scan. The tab pill
10167
+ // is too quiet to read as an offer, and the zero-result CTA never fires for
10168
+ // these queries by definition — this is the only place the offer lands while
10169
+ // the shopper still has the query in their head.
10170
+ renderAskNudge() {
10171
+ const query = this.debouncedQuery.trim();
10172
+ const label = this.askNudgeLabelParts(query);
10173
+ return b `
10174
+ <button
10175
+ type="button"
10176
+ class="ask-nudge"
10177
+ @click=${() => this.handleAskNudgeClick(query)}
10178
+ >
10179
+ <bt-lucide-icon
10180
+ name="sparkles"
10181
+ width="14"
10182
+ height="14"
10183
+ stroke="currentColor"
10184
+ stroke-width="1.5"
10185
+ ></bt-lucide-icon>
10186
+ <span class="ask-nudge-text">${label}</span>
10187
+ <bt-lucide-icon
10188
+ name="arrowRight"
10189
+ width="14"
10190
+ height="14"
10191
+ stroke="currentColor"
10192
+ stroke-width="1.5"
10193
+ ></bt-lucide-icon>
10194
+ </button>
10195
+ `;
10196
+ }
10197
+ // Same destination as the zero-result CTA: flip to Ask and run the query the
10198
+ // shopper already typed, so the words carry over verbatim.
10199
+ handleAskNudgeClick(query) {
10200
+ this.mode = 'ask';
10201
+ setStoredSearchMode('ask');
10202
+ // Reuse a held answer for this exact query, like the view toggle does —
10203
+ // re-submitting would open a second billed generation for the same words.
10204
+ const hasAnswer = this.askLoading || this.askAnswer.length > 0 || this.askEvidence !== null;
10205
+ if (!hasAnswer || this.lastAskedQuery !== query) {
10206
+ this.handleAskSubmit(query);
10207
+ }
10208
+ }
10013
10209
  renderMain() {
10014
10210
  var _a, _b, _c;
10015
10211
  const askInitial = this.mode === 'ask' &&
@@ -10225,6 +10421,10 @@ var ProvidersEvoSearchSdk = (function () {
10225
10421
  r$1(),
10226
10422
  __metadata("design:type", Array)
10227
10423
  ], SearchWidget.prototype, "searchCollections", void 0);
10424
+ __decorate([
10425
+ r$1(),
10426
+ __metadata("design:type", Object)
10427
+ ], SearchWidget.prototype, "hadLexicalMatch", void 0);
10228
10428
  __decorate([
10229
10429
  r$1(),
10230
10430
  __metadata("design:type", Array)
@@ -10284,7 +10484,15 @@ var ProvidersEvoSearchSdk = (function () {
10284
10484
  --evosearch-primary: #3a3d36;
10285
10485
  --evosearch-secondary: #6b6f64;
10286
10486
  --evosearch-accent: #e0590b;
10287
- --evosearch-ask-accent: #2563eb;
10487
+ /* Violet, not blue. The previous #2563eb read as a generic link colour, so
10488
+ every AI affordance looked like plain navigation. Violet is distinct from
10489
+ the brand accent AND from ordinary links, so "this is the AI" is legible
10490
+ without a second signal. Drives the mode toggle, the results nudge, the
10491
+ ask-answer rule, the follow-up chips and the state-block CTA. */
10492
+ --evosearch-ask-accent: #7c3aed;
10493
+ /* Paired end-stop for the gradient treatments (nudge label, toggle thumb).
10494
+ Same amethyst→indigo sweep the toggle icon used before it was unified. */
10495
+ --evosearch-ask-accent-alt: #6366f1;
10288
10496
  --evosearch-mode-accent: var(--evosearch-accent);
10289
10497
  --evosearch-white: #ffffff;
10290
10498
  --evosearch-border: #d6d8cf;
@@ -10360,6 +10568,25 @@ var ProvidersEvoSearchSdk = (function () {
10360
10568
  this.handpickedProducts = [];
10361
10569
  this.featuredCollections = [];
10362
10570
  this.askAiEnabled = false;
10571
+ // Host-page scroll locks (body-scroll-lock and friends) cancel every
10572
+ // document-level touchmove they didn't whitelist, which kills touch
10573
+ // scrolling inside the widget on iOS. Keep widget touches out of their
10574
+ // reach; never preventDefault, or we'd kill the scroll ourselves.
10575
+ // Known gap: a lock registered with { capture: true } runs before the
10576
+ // event reaches this host, so stopPropagation can't shield against it.
10577
+ this.touchmoveListener = (event) => event.stopPropagation();
10578
+ }
10579
+ connectedCallback() {
10580
+ super.connectedCallback();
10581
+ // Passive: this handler only stops propagation, and a blocking touchmove
10582
+ // listener would keep the widget's own scrolling off the compositor.
10583
+ this.addEventListener('touchmove', this.touchmoveListener, {
10584
+ passive: true,
10585
+ });
10586
+ }
10587
+ disconnectedCallback() {
10588
+ this.removeEventListener('touchmove', this.touchmoveListener);
10589
+ super.disconnectedCallback();
10363
10590
  }
10364
10591
  updated(changedProperties) {
10365
10592
  var _a;
@@ -10559,7 +10786,7 @@ var ProvidersEvoSearchSdk = (function () {
10559
10786
  // Runtime Ask AI config. Unlike trending (fetched once per session), this is
10560
10787
  // fetched on every open so a merchant's dashboard change takes effect without
10561
10788
  // a reload — there is no session-fetched latch, only an in-flight guard to
10562
- // dedupe concurrent opens. `askStarters`/`askAiEnabled` apply live.
10789
+ // dedupe concurrent opens. `askStarters`/`askAiEnabled`/`askNudge`(+label) apply live.
10563
10790
  const tryFetchConfig = (target) => {
10564
10791
  if (!target.api || configController) {
10565
10792
  return;
@@ -10577,6 +10804,13 @@ var ProvidersEvoSearchSdk = (function () {
10577
10804
  ...(data.askStarters !== undefined
10578
10805
  ? { askStarters: data.askStarters }
10579
10806
  : {}),
10807
+ // Only overlay what the worker actually sent: during a deploy window
10808
+ // a new SDK can hit an old `/config` that omits these, and writing
10809
+ // undefined would drop the manifest-seeded value.
10810
+ ...(data.askNudge !== undefined ? { askNudge: data.askNudge } : {}),
10811
+ ...(data.askNudgeLabel !== undefined
10812
+ ? { askNudgeLabel: data.askNudgeLabel }
10813
+ : {}),
10580
10814
  };
10581
10815
  }
10582
10816
  })