PageSourceSearch

https://centgen.ai/window/esearch.js

js centgen.ai collected 2026-09-28 06:14:21 UTC 37,646 bytes, 1,498 lines download raw bytes

1(function () {
2  const THEMES = {
3    "black-white": {
4      bg: "#ffffff",
5      panel: "#f7f7f8",
6      text: "#111827",
7      muted: "#64748b",
8      soft: "#eef2f7",
9      border: "#d9e2ec",
10      accent: "#111827",
11      accentSoft: "#f1f5f9",
12      card: "#ffffff",
13      shadow: "rgba(15, 23, 42, .08)",
14      imageBg: "#f8fafc",
15    },
16    "blue-white": {
17      bg: "#ffffff",
18      panel: "#f7fbff",
19      text: "#102033",
20      muted: "#64748b",
21      soft: "#eaf4ff",
22      border: "#d7e7fb",
23      accent: "#2563eb",
24      accentSoft: "#eaf2ff",
25      card: "#ffffff",
26      shadow: "rgba(37, 99, 235, .12)",
27      imageBg: "#f4f9ff",
28    },
29  };
30
31  const QUERY = "I'm looking for a soft rug that makes my living room feel warmer and more cozy";
32
33  const HERO_PRODUCTS = [
34    ["NOMA LIVING", "Nordic Wool Rug", "$399.00", "rug-a"],
35    ["HAVEN FORM", "Oak Coffee Table", "$549.00", "table-a"],
36    ["ROWAN & CO.", "Linen Lounge Chair", "$699.00", "chair-a"],
37    ["STUDIO HOME", "Floor Lamp", "$189.00", "lamp-a"],
38    ["ASHEN GOODS", "Bookshelf", "$449.00", "shelf-a"],
39    ["FIELDFORM", "Side Table", "$229.00", "table-b"],
40    ["KESTREL", "Fabric Sofa", "$1,299.00", "sofa-a"],
41    ["TERNARY HOUSE", "Dining Chair", "$159.00", "chair-b"],
42  ];
43
44  const SEARCH_RESULTS = [
45    ["NOMA LIVING", "Nordic Wool Rug", "$399.00", "rug-a"],
46    ["HAVEN FORM", "Handwoven Cozy Rug", "$459.00", "rug-b"],
47  ];
48
49  const PRODUCT_IMAGES = {
50    "rug-a": "./images/rug-a.png",
51    "table-a": "./images/table-a.png",
52    "chair-a": "./images/chair-a.png",
53    "lamp-a": "./images/lamp-a.png",
54    "shelf-a": "./images/shelf-a.png",
55    "table-b": "./images/table-b.png",
56    "sofa-a": "./images/sofa-a.png",
57    "chair-b": "./images/chair-b.png",
58    "rug-b": "./images/rug-b.png",
59  };
60
61  const DEFAULT_COPY = {
62    query: QUERY,
63    heroProducts: HERO_PRODUCTS,
64    searchResults: SEARCH_RESULTS,
65    startingAt: "Starting at",
66    resultsTitle: "Search Results",
67    tabs: [
68      ["Products", "2", true],
69      ["Articles", "0", false],
70      ["Pages", "0", false],
71    ],
72    sortBy: "Sort by",
73    relevance: "Relevance",
74    resultsFound: '2 results found for "rug"',
75    filters: "Filters",
76    price: "Price",
77    min: "Min",
78    max: "Max",
79    availableRange: "Available range: $0.00 to $1,999.00",
80    collection: "Collection",
81    collections: [
82      ["Lights", 10],
83      ["Rugs", 8],
84      ["Tables", 25],
85      ["Top Picks", 3],
86    ],
87    tags: "Tags",
88    tagItems: ["Minimal", "Standing", "Wood", "Home Office"],
89    kicker: "AI Search for ecommerce discovery",
90    title: "Understand shopper intent",
91    description:
92      "Ask naturally. The AI understands intent, context and desired atmosphere instead of relying on keyword matching.",
93    summaryLabel: "SUMMARY",
94    summaryText:
95      "The Nordic Wool Rug is the best match for a soft, cozy living-room feel because it’s plush, tufted wool, and has a cushioned pile that should add warmth underfoot. If you want other similarly inviting options, the Handwoven Cozy Rug offers a very soft sink-in feel, which can also make the room feel more comfortable and lived-in.",
96    searchIcon: "⌕",
97    searchButton: "Search",
98  };
99
100  const I18N_URL =
101    document.currentScript && document.currentScript.src
102      ? new URL("i18n.json", document.currentScript.src).href
103      : "/window/i18n.json";
104
105  let cachedI18n = null;
106  let didLoadI18n = false;
107
108  function detectLocale() {
109    const segments = window.location.pathname
110      .split("/")
111      .map((part) => part.toLowerCase())
112      .filter(Boolean);
113
114    if (segments.includes("ja")) return "ja";
115    if (segments.includes("en")) return "en";
116    if (segments.includes("zh")) return "zh";
117
118    return "zh";
119  }
120
121  function loadI18n() {
122    if (didLoadI18n) return cachedI18n;
123    didLoadI18n = true;
124
125    try {
126      const request = new XMLHttpRequest();
127      request.open("GET", I18N_URL, false);
128      request.overrideMimeType("application/json");
129      request.send(null);
130
131      if (request.status >= 200 && request.status < 300) {
132        cachedI18n = JSON.parse(request.responseText);
133      }
134    } catch (error) {
135      cachedI18n = null;
136    }
137
138    return cachedI18n;
139  }
140
141  function getCopy(options) {
142    const locale = (options && options.locale) || detectLocale();
143    const section = (loadI18n() || {})[locale] || {};
144    const copy = section.esearch || {};
145
146    return {
147      ...DEFAULT_COPY,
148      ...copy,
149      tabs: copy.tabs || DEFAULT_COPY.tabs,
150      collections: copy.collections || DEFAULT_COPY.collections,
151      tagItems: copy.tagItems || DEFAULT_COPY.tagItems,
152      heroProducts: copy.heroProducts || DEFAULT_COPY.heroProducts,
153      searchResults: copy.searchResults || DEFAULT_COPY.searchResults,
154    };
155  }
156
157  function injectStyles() {
158    if (document.getElementById("ai-search-demo-style")) return;
159
160    const style = document.createElement("style");
161    style.id = "ai-search-demo-style";
162    style.textContent = `
163      .ai-search-demo {
164        --bg:#fff;
165        --panel:#f7f7f8;
166        --text:#111827;
167        --muted:#64748b;
168        --soft:#eef2f7;
169        --border:#d9e2ec;
170        --accent:#111827;
171        --accent-soft:#f1f5f9;
172        --card:#fff;
173        --shadow:rgba(15,23,42,.08);
174        --image-bg:#f8fafc;
175
176        width:100%;
177        max-width:1180px;
178        height:680px;
179        background:var(--bg);
180        color:var(--text);
181        border:1px solid var(--border);
182        border-radius:24px;
183        overflow:auto;
184        box-shadow:0 26px 90px var(--shadow);
185        font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
186        position:relative;
187      }
188
189      .ai-search-restart {
190        position:absolute;
191        z-index:20;
192        top:14px;
193        right:14px;
194        display:grid;
195        place-items:center;
196        min-width:0;
197        width:34px;
198        height:34px;
199        padding:0;
200        border:1px solid var(--border);
201        border-radius:999px;
202        background:rgba(255,255,255,.82);
203        color:var(--accent);
204        box-shadow:0 10px 26px var(--shadow);
205        cursor:pointer;
206        opacity:0;
207        pointer-events:none;
208        transition:opacity .18s ease, transform .18s ease;
209      }
210
211      .ai-search-restart img {
212        width:16px;
213        height:16px;
214        display:block;
215        object-fit:contain;
216      }
217
218      .ai-search-restart.show {
219        opacity:1;
220        pointer-events:auto;
221      }
222
223      .ai-search-restart:hover {
224        transform:translateY(-1px);
225      }
226
227      .ai-search-stage {
228        height:100%;
229        position:relative;
230        background:radial-gradient(circle at 50% 0%, var(--accent-soft), transparent 38%), var(--bg);
231      }
232
233      .ai-search-hero {
234        height:100%;
235        display:flex;
236        align-items:flex-start;
237        justify-content:center;
238        padding:42px;
239      }
240
241      .ai-search-panel {
242        width:min(760px,100%);
243        text-align:center;
244      }
245
246      .ai-search-kicker {
247        display:inline-flex;
248        gap:8px;
249        align-items:center;
250        padding:8px 12px;
251        border:1px solid var(--border);
252        border-radius:999px;
253        color:var(--muted);
254        background:rgba(255,255,255,.72);
255        font-size:13px;
256        margin-bottom:20px;
257      }
258
259      .ai-search-kicker::before {
260        content:"";
261        width:8px;
262        height:8px;
263        border-radius:99px;
264        background:var(--accent);
265      }
266
267      .ai-search-title {
268        font-family:Georgia,"Times New Roman",serif;
269        font-size:clamp(44px,6vw,76px);
270        line-height:.98;
271        font-weight:400;
272        margin:0 0 18px;
273        letter-spacing:-.04em;
274      }
275
276      .ai-search-desc {
277        color:var(--muted);
278        margin:0 auto 28px;
279        max-width:520px;
280        line-height:1.65;
281      }
282
283      .ai-search-box {
284        display:flex;
285        align-items:center;
286        gap:12px;
287        min-height:64px;
288        background:var(--card);
289        border:1px solid var(--border);
290        border-radius:22px;
291        padding:10px 12px 10px 20px;
292        box-shadow:0 18px 60px var(--shadow);
293        text-align:left;
294      }
295
296      .ai-search-icon {
297        color:var(--muted);
298        font-size:20px;
299      }
300
301      .ai-search-query {
302        flex:1;
303        font-size:16px;
304        color:var(--text);
305        min-width:0;
306        min-height:24px;
307        line-height:1.5;
308        overflow:hidden;
309        text-overflow:ellipsis;
310        white-space:nowrap;
311      }
312
313      .ai-search-caret {
314        display:inline-block;
315        width:8px;
316        height:18px;
317        margin-left:2px;
318        background:var(--accent);
319        vertical-align:-3px;
320        animation:aiSearchBlink 1s infinite;
321      }
322
323      .ai-search-button {
324        border:0;
325        background:var(--accent);
326        color:#fff;
327        border-radius:16px;
328        min-width:106px;
329        height:44px;
330        padding:0 18px;
331        font-weight:750;
332        cursor:default;
333        transition:transform .16s ease;
334      }
335
336      .ai-fake-cursor {
337        position:absolute;
338        width:30px;
339        height:30px;
340        object-fit:contain;
341        z-index:9999;
342        pointer-events:none;
343        opacity:0;
344        left:var(--cursor-start-x, 30%);
345        top:var(--cursor-start-y, 70%);
346        transform:translate(-4px, -4px);
347        filter:drop-shadow(0 4px 10px rgba(0,0,0,.18));
348      }
349
350      .ai-fake-cursor.move {
351        opacity:1;
352        transition:
353          left 1.05s cubic-bezier(.22,.61,.36,1),
354          top 1.05s cubic-bezier(.22,.61,.36,1);
355        left:var(--cursor-end-x);
356        top:var(--cursor-end-y);
357      }
358
359      .ai-fake-cursor.tracking {
360        opacity:1;
361        transition:none;
362      }
363
364      .ai-fake-cursor.click {
365        transform:translate(-4px, -4px) scale(.86);
366      }
367
368      .ai-search-loader {
369        position:absolute;
370        top:0;
371        left:0;
372        width:100%;
373        height:100%;
374        background:rgba(255,255,255,.78);
375        -webkit-backdrop-filter:blur(10px);
376        backdrop-filter:blur(10px);
377        display:none;
378        align-items:center;
379        justify-content:center;
380        z-index:12;
381      }
382
383      .ai-search-loader.show {
384        display:flex;
385      }
386
387      .ai-search-spinner {
388        width:56px;
389        height:56px;
390        border-radius:999px;
391        border:4px solid var(--border);
392        border-top-color:var(--accent);
393        animation:aiSearchSpin .8s linear infinite;
394      }
395
396      .ai-results-page {
397        display:none;
398        height:100%;
399        background:var(--bg);
400        overflow:hidden;
401        padding:20px;
402      }
403
404      .ai-results-page.show {
405        display:flex;
406        flex-direction:column;
407        animation:aiSearchFade .38s ease both;
408      }
409
410      .ai-results-chrome {
411        flex:1;
412        min-height:0;
413        display:flex;
414        flex-direction:column;
415        border:1px solid var(--border);
416        border-radius:18px;
417        background:linear-gradient(180deg, rgba(255,255,255,.92), var(--card));
418        overflow:hidden;
419      }
420
421      .ai-results-header {
422        display:flex;
423        align-items:center;
424        justify-content:space-between;
425        gap:18px;
426        padding:18px 20px;
427        border-bottom:1px solid var(--border);
428        background:rgba(255,255,255,.72);
429      }
430
431      .ai-results-title-wrap {
432        min-width:0;
433      }
434
435      .ai-results-eyebrow {
436        color:var(--muted);
437        font-size:12px;
438        font-weight:800;
439        letter-spacing:.12em;
440        text-transform:uppercase;
441        margin-bottom:6px;
442      }
443
444      .ai-results-title {
445        font-family:Georgia,"Times New Roman",serif;
446        font-size:32px;
447        line-height:1.04;
448        font-weight:400;
449        margin:0 0 14px;
450        letter-spacing:0;
451        white-space:nowrap;
452      }
453
454      .ai-results-page > .ai-results-title {
455        flex:0 0 auto;
456        padding:18px 20px 0;
457      }
458
459      .ai-results-toolbar {
460        flex:0 0 auto;
461        display:flex;
462        align-items:center;
463        justify-content:space-between;
464        gap:12px;
465        margin:0 20px 14px;
466        padding:14px;
467        border:1px solid var(--border);
468        border-radius:18px;
469        background:var(--panel);
470      }
471
472      .ai-result-tabs {
473        display:flex;
474        gap:8px;
475        flex:0 0 auto;
476        min-width:0;
477      }
478
479      .ai-tab {
480        height:36px;
481        padding:0 12px;
482        display:inline-flex;
483        align-items:center;
484        gap:7px;
485        flex:0 0 auto;
486        border:1px solid var(--border);
487        border-radius:999px;
488        background:var(--card);
489        color:var(--muted);
490        font-weight:750;
491        font-size:12px;
492        white-space:nowrap;
493      }
494
495      .ai-tab.active {
496        color:var(--accent);
497        background:var(--accent-soft);
498      }
499
500      .ai-count {
501        flex:0 0 auto;
502        background:var(--soft);
503        border-radius:999px;
504        padding:2px 8px;
505        font-size:11px;
506      }
507
508      .ai-results-searchbar {
509        display:flex;
510        align-items:center;
511        gap:10px;
512        min-height:52px;
513        margin:16px 18px 14px;
514        padding:0 14px;
515        border:1px solid var(--border);
516        border-radius:16px;
517        background:var(--panel);
518        color:var(--text);
519      }
520
521      .ai-results-searchbar strong {
522        flex:1;
523        min-width:0;
524        overflow:hidden;
525        text-overflow:ellipsis;
526        white-space:nowrap;
527        font-size:14px;
528      }
529
530      .ai-results-searchbar span {
531        color:var(--muted);
532        font-size:12px;
533        white-space:nowrap;
534      }
535
536      .ai-sortline {
537        min-width:0;
538        display:flex;
539        align-items:center;
540        justify-content:flex-end;
541        gap:8px;
542        color:var(--muted);
543        font-size:12px;
544      }
545
546      .ai-sortline > strong,
547      .ai-sortline > span:last-child {
548        display:none;
549      }
550
551      .ai-results-controls {
552        display:flex;
553        align-items:center;
554        justify-content:space-between;
555        gap:12px;
556        padding:0 18px 16px;
557      }
558
559      .ai-filter-chips {
560        display:flex;
561        gap:8px;
562        min-width:0;
563        overflow:hidden;
564      }
565
566      .ai-chip {
567        flex:0 0 auto;
568        height:32px;
569        display:inline-flex;
570        align-items:center;
571        border:1px solid var(--border);
572        border-radius:999px;
573        padding:0 11px;
574        color:var(--muted);
575        background:var(--card);
576        font-size:12px;
577        font-weight:750;
578      }
579
580      .ai-select {
581        min-width:126px;
582        height:34px;
583        border:1px solid var(--border);
584        border-radius:999px;
585        background:var(--card);
586        display:flex;
587        align-items:center;
588        justify-content:space-between;
589        gap:8px;
590        padding:0 12px;
591        color:var(--text);
592        font-weight:700;
593        font-size:12px;
594        flex:0 0 auto;
595      }
596
597      .ai-layout-buttons {
598        display:none;
599        overflow:hidden;
600        border:1px solid var(--border);
601        border-radius:999px;
602        background:var(--card);
603        flex:0 0 auto;
604      }
605
606      .ai-layout-buttons span {
607        width:34px;
608        height:34px;
609        display:grid;
610        place-items:center;
611        color:var(--muted);
612        transition:background .16s ease, color .16s ease;
613      }
614
615      .ai-layout-buttons span.active {
616        color:var(--accent);
617        background:var(--accent-soft);
618      }
619
620      .ai-results-main {
621        flex:1;
622        min-height:0;
623        overflow:auto;
624        padding:0 18px 18px;
625      }
626
627      .ai-summary {
628        border:1px solid var(--border);
629        background:linear-gradient(180deg, rgba(255,255,255,.95), var(--card));
630        border-radius:16px;
631        padding:14px 16px;
632        margin-bottom:14px;
633        box-shadow:0 12px 32px var(--shadow);
634      }
635
636      .ai-summary-label {
637        color:var(--accent);
638        font-size:11px;
639        font-weight:850;
640        letter-spacing:.12em;
641        margin-bottom:8px;
642      }
643
644      .ai-summary-text {
645        color:var(--text);
646        font-size:14px;
647        line-height:1.62;
648        margin:0;
649      }
650
651      .ai-sidebar {
652        display:none;
653      }
654
655      .ai-product-grid {
656        display:grid;
657        grid-template-columns:repeat(3,minmax(0,1fr));
658        gap:10px;
659      }
660
661      .ai-product {
662        border:1px solid var(--border);
663        background:var(--card);
664        min-height:224px;
665        padding:10px;
666        border-radius:14px;
667        overflow:hidden;
668        display:flex;
669        flex-direction:column;
670        transition:transform .18s ease, box-shadow .18s ease, border-color .18s ease;
671      }
672
673      .ai-product:hover {
674        transform:translateY(-3px);
675        border-color:var(--accent);
676        box-shadow:0 16px 38px var(--shadow);
677      }
678
679      .ai-product-image {
680        width:100%;
681        flex:0 0 auto;
682        aspect-ratio:4 / 3;
683        min-height:86px;
684        background:var(--image-bg);
685        border:2px solid rgba(255,255,255,.85);
686        border-radius:12px;
687        display:grid;
688        place-items:center;
689        overflow:hidden;
690        margin-bottom:10px;
691      }
692
693      .ai-product-image img {
694        width:calc(100% - 12px);
695        height:calc(100% - 12px);
696        object-fit:contain;
697        display:block;
698        border-radius:10px;
699      }
700
701      .ai-swatches {
702        display:flex;
703        gap:6px;
704        margin-bottom:12px;
705      }
706
707      .ai-swatch {
708        width:28px;
709        height:20px;
710        border-radius:8px;
711        background:var(--card);
712        border:1px solid var(--border);
713        display:grid;
714        place-items:center;
715      }
716
717      .ai-swatch img {
718        width:24px;
719        height:16px;
720        object-fit:cover;
721        display:block;
722        border-radius:6px;
723      }
724
725      .ai-brand {
726        color:var(--muted);
727        letter-spacing:.12em;
728        font-size:9px;
729        margin-bottom:6px;
730        min-height:11px;
731        white-space:nowrap;
732        overflow:hidden;
733        text-overflow:ellipsis;
734      }
735
736      .ai-name {
737        font-family:Georgia,"Times New Roman",serif;
738        font-size:15px;
739        line-height:1.18;
740        min-height:36px;
741        margin-bottom:10px;
742        overflow-wrap:anywhere;
743        display:-webkit-box;
744        -webkit-line-clamp:2;
745        -webkit-box-orient:vertical;
746        overflow:hidden;
747      }
748
749      .ai-product-bottom {
750        display:flex;
751        justify-content:space-between;
752        align-items:baseline;
753        gap:5px;
754        margin-top:auto;
755      }
756
757      .ai-product-bottom span {
758        color:var(--muted);
759        font-size:12px;
760        font-weight:700;
761      }
762
763      .ai-product-bottom strong {
764        font-size:16px;
765      }
766
767      .ai-preview-grid {
768        margin-top:24px;
769        display:grid;
770        grid-template-columns:repeat(auto-fit,minmax(132px,1fr));
771        gap:10px;
772      }
773
774      .ai-preview-grid .ai-product {
775        min-height:174px;
776        padding:8px;
777        border-radius:12px;
778      }
779
780      .ai-preview-grid .ai-product-image {
781        aspect-ratio:16 / 9;
782        min-height:0;
783        height:auto;
784        margin-bottom:8px;
785      }
786
787      .ai-preview-grid .ai-swatches {
788        display:none;
789      }
790
791      .ai-preview-grid .ai-brand {
792        font-size:8px;
793        min-height:10px;
794        margin-bottom:5px;
795      }
796
797      .ai-preview-grid .ai-name {
798        font-size:13px;
799        line-height:1.16;
800        min-height:31px;
801        margin-bottom:8px;
802      }
803
804      .ai-preview-grid .ai-product-bottom {
805        min-width:0;
806      }
807
808      .ai-preview-grid .ai-product-bottom span {
809        font-size:10px;
810        min-width:0;
811        overflow:hidden;
812        text-overflow:ellipsis;
813        white-space:nowrap;
814      }
815
816      .ai-preview-grid .ai-product-bottom strong {
817        font-size:13px;
818        flex:0 0 auto;
819        white-space:nowrap;
820      }
821
822      .ai-tab{
823        cursor:pointer;
824      }
825
826      .ai-select{
827        cursor:pointer;
828      }
829
830      .ai-layout-buttons span{
831        cursor:pointer;
832      }
833
834      .ai-product{
835        cursor:pointer;
836      }
837
838      @keyframes aiSearchBlink {
839        50% { opacity:0; }
840      }
841
842      @keyframes aiSearchSpin {
843        to { transform:rotate(360deg); }
844      }
845
846      @keyframes aiSearchFade {
847        from { opacity:0; transform:translateY(8px); }
848        to { opacity:1; transform:translateY(0); }
849      }
850
851      @media (max-width:991px) {
852        .ai-layout-buttons {
853          display:none;
854          pointer-events:none;
855        }
856      }
857
858      @media (max-width:900px) {
859        .ai-search-demo { height:720px; }
860
861        .ai-preview-grid {
862          grid-template-columns:repeat(4,minmax(0,1fr));
863        }
864
865        .ai-preview-grid .ai-product-image {
866          min-height:48px;
867        }
868
869        .ai-product-grid {
870          grid-template-columns:repeat(2,minmax(0,1fr));
871        }
872      }
873
874      @media (max-width:560px) {
875        .ai-search-demo {
876          border-radius:18px;
877          height:700px;
878        }
879
880        .ai-search-hero {
881          padding:22px;
882        }
883
884        .ai-search-title {
885          font-size:44px;
886        }
887
888        .ai-search-button {
889          min-width:78px;
890        }
891
892        .ai-results-page {
893          padding:12px;
894        }
895
896        .ai-results-header {
897          align-items:flex-start;
898          flex-direction:column;
899          gap:12px;
900          padding:16px;
901        }
902
903        .ai-result-tabs {
904          width:100%;
905          max-width:100%;
906          overflow-x:auto;
907          overflow-y:hidden;
908          padding-bottom:2px;
909          -webkit-overflow-scrolling:touch;
910          scrollbar-width:none;
911        }
912
913        .ai-result-tabs::-webkit-scrollbar {
914          display:none;
915        }
916
917        .ai-tab {
918          min-width:max-content;
919        }
920
921        .ai-results-toolbar {
922          align-items:flex-start;
923          flex-direction:column;
924          margin-left:12px;
925          margin-right:12px;
926        }
927
928        .ai-sortline {
929          width:100%;
930          justify-content:space-between;
931        }
932
933        .ai-results-title {
934          white-space:normal;
935          font-size:28px;
936        }
937
938        .ai-results-controls {
939          align-items:flex-start;
940          flex-direction:column;
941        }
942
943        .ai-filter-chips {
944          width:100%;
945          overflow-x:auto;
946        }
947
948        .ai-preview-grid {
949          grid-template-columns:repeat(2,minmax(0,1fr));
950          gap:8px;
951          margin-top:18px;
952        }
953
954        .ai-preview-grid .ai-product-image {
955          min-height:58px;
956        }
957
958        .ai-preview-grid .ai-product:nth-child(n+5) {
959          display:none;
960        }
961
962        .ai-product-grid {
963          grid-template-columns:1fr;
964        }
965      }
966    `;
967
968    document.head.appendChild(style);
969  }
970
971  function el(tag, className, text) {
972    const node = document.createElement(tag);
973    if (className) node.className = className;
974    if (typeof text === "string") node.textContent = text;
975    return node;
976  }
977
978  function applyTheme(root, themeName) {
979    const theme = THEMES[themeName] || THEMES["blue-white"];
980
981    Object.keys(theme).forEach((key) => {
982      const cssKey =
983        "--" + key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
984      root.style.setProperty(cssKey, theme[key]);
985    });
986  }
987
988  function sleep(ms) {
989    return new Promise((resolve) => setTimeout(resolve, ms));
990  }
991
992  function syncLoaderToDemoViewport(loader) {
993    const root = loader.closest(".ai-search-demo");
994    if (!root) return;
995
996    loader.style.top = root.scrollTop + "px";
997    loader.style.left = root.scrollLeft + "px";
998    loader.style.width = root.clientWidth + "px";
999    loader.style.height = root.clientHeight + "px";
1000  }
1001
1002  function getSearchButtonPoint(button) {
1003    const root = button.closest(".ai-search-demo");
1004    const buttonRect = button.getBoundingClientRect();
1005    const rootRect = root.getBoundingClientRect();
1006
1007    return {
1008      x: buttonRect.left - rootRect.left + root.scrollLeft + 16,
1009      y: buttonRect.top - rootRect.top + root.scrollTop + buttonRect.height * 0.5,
1010    };
1011  }
1012
1013  function setCursorPoint(cursor, point) {
1014    cursor.style.setProperty("--cursor-end-x", point.x + "px");
1015    cursor.style.setProperty("--cursor-end-y", point.y + "px");
1016    cursor.style.left = point.x + "px";
1017    cursor.style.top = point.y + "px";
1018  }
1019
1020  function trackCursorToButton(cursor, button, duration) {
1021    const startTime = performance.now();
1022    const root = button.closest(".ai-search-demo");
1023    const start = {
1024      x: root.scrollLeft + root.clientWidth * 0.28,
1025      y: root.scrollTop + root.clientHeight * 0.72,
1026    };
1027
1028    cursor.style.setProperty("--cursor-start-x", start.x + "px");
1029    cursor.style.setProperty("--cursor-start-y", start.y + "px");
1030    cursor.style.left = start.x + "px";
1031    cursor.style.top = start.y + "px";
1032    cursor.classList.add("tracking");
1033
1034    return new Promise((resolve) => {
1035      function step(now) {
1036        const target = getSearchButtonPoint(button);
1037        const progress = Math.min((now - startTime) / duration, 1);
1038        const eased = 1 - Math.pow(1 - progress, 3);
1039
1040        setCursorPoint(cursor, {
1041          x: start.x + (target.x - start.x) * eased,
1042          y: start.y + (target.y - start.y) * eased,
1043        });
1044
1045        if (progress < 1) {
1046          requestAnimationFrame(step);
1047          return;
1048        }
1049
1050        setCursorPoint(cursor, getSearchButtonPoint(button));
1051        resolve();
1052      }
1053
1054      requestAnimationFrame(step);
1055    });
1056  }
1057
1058  function deskSvg(type) {
1059    const top =
1060      type === "desk-b" || type === "desk-c"
1061        ? "#caa978"
1062        : type === "desk-f"
1063        ? "#9b7652"
1064        : "#d9c4a4";
1065
1066    const leg =
1067      type === "desk-b" || type === "desk-c"
1068        ? "#d9dde2"
1069        : type === "desk-d"
1070        ? "#b99769"
1071        : "#c8aa7c";
1072
1073    const extra =
1074      type === "desk-b"
1075        ? '<rect x="58" y="72" width="104" height="9" rx="2" fill="#111827" opacity=".88"/>'
1076        : "";
1077
1078    return `
1079      <svg viewBox="0 0 220 150" aria-hidden="true">
1080        <ellipse cx="110" cy="128" rx="86" ry="10" fill="#dbe2ea" opacity=".6"/>
1081        <path d="M28 54 L170 43 L202 56 L58 72 Z" fill="${top}"/>
1082        <path d="M58 72 L202 56 L202 62 L58 78 Z" fill="#b89463" opacity=".55"/>
1083        ${extra}
1084        <path d="M52 75 L62 75 L54 124 L43 124 Z" fill="${leg}"/>
1085        <path d="M184 66 L194 65 L198 124 L187 124 Z" fill="${leg}"/>
1086        <path d="M62 112 L188 112 L188 119 L60 119 Z" fill="${leg}" opacity=".7"/>
1087      </svg>
1088    `;
1089  }
1090
1091  function iconGrid() {
1092    return `
1093      <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
1094        <rect x="4" y="4" width="6" height="6"/>
1095        <rect x="14" y="4" width="6" height="6"/>
1096        <rect x="4" y="14" width="6" height="6"/>
1097        <rect x="14" y="14" width="6" height="6"/>
1098      </svg>
1099    `;
1100  }
1101
1102  function iconList() {
1103    return `
1104      <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
1105        <rect x="4" y="6" width="4" height="4"/>
1106        <rect x="10" y="7" width="10" height="2"/>
1107        <rect x="4" y="14" width="4" height="4"/>
1108        <rect x="10" y="15" width="10" height="2"/>
1109      </svg>
1110    `;
1111  }
1112
1113  function appendProductImage(target, product, decorative) {
1114    const src = PRODUCT_IMAGES[product.img];
1115
1116    if (!src) {
1117      return;
1118    }
1119
1120    const img = el("img");
1121    img.src = src;
1122    img.alt = decorative ? "" : [product.brand, product.name].join(" ");
1123    img.loading = "lazy";
1124    target.appendChild(img);
1125  }
1126
1127  async function streamQuery(textNode, button, cursor, loader, hero, results, copy, restartButton) {
1128    textNode.textContent = "";
1129
1130    const caret = el("span", "ai-search-caret");
1131    textNode.appendChild(caret);
1132
1133    for (let i = 0; i <= copy.query.length; i++) {
1134      textNode.textContent = copy.query.slice(0, i);
1135      textNode.appendChild(caret);
1136      await sleep(34 + Math.random() * 22);
1137    }
1138
1139    caret.remove();
1140
1141    await sleep(450);
1142
1143    await trackCursorToButton(cursor, button, 1100);
1144    setCursorPoint(cursor, getSearchButtonPoint(button));
1145
1146    cursor.classList.add("click");
1147    button.style.transform = "scale(.97)";
1148
1149    await sleep(180);
1150
1151    button.style.transform = "";
1152    cursor.classList.remove("click");
1153    cursor.classList.remove("tracking");
1154
1155    const root = loader.closest(".ai-search-demo");
1156    const syncLoader = () => syncLoaderToDemoViewport(loader);
1157
1158    syncLoader();
1159    if (root) {
1160      root.addEventListener("scroll", syncLoader, { passive: true });
1161    }
1162
1163    loader.classList.add("show");
1164
1165    await sleep(1350);
1166
1167    if (root) {
1168      root.removeEventListener("scroll", syncLoader);
1169    }
1170
1171    hero.style.display = "none";
1172    loader.classList.remove("show");
1173    results.classList.add("show");
1174    restartButton.classList.add("show");
1175  }
1176
1177  function makeProduct(product, index, copy) {
1178    const card = el("div", "ai-product");
1179
1180    const image = el("div", "ai-product-image");
1181    appendProductImage(image, product, false);
1182
1183    const swatches = el("div", "ai-swatches");
1184
1185    const swatchOne = el("div", "ai-swatch");
1186    appendProductImage(swatchOne, product, true);
1187
1188    const swatchTwo = el("div", "ai-swatch");
1189    appendProductImage(swatchTwo, product, true);
1190
1191    swatches.appendChild(swatchOne);
1192    swatches.appendChild(swatchTwo);
1193
1194    card.appendChild(image);
1195    card.appendChild(swatches);
1196    card.appendChild(el("div", "ai-brand", product.brand));
1197    card.appendChild(el("div", "ai-name", product.name));
1198
1199    const bottom = el("div", "ai-product-bottom");
1200    bottom.appendChild(el("span", "", copy.startingAt));
1201    bottom.appendChild(el("strong", "", product.price));
1202    card.appendChild(bottom);
1203
1204    card.addEventListener("click", () => {
1205      card.style.transform = "scale(.98)";
1206      setTimeout(() => {
1207        card.style.transform = "";
1208      }, 120);
1209    });
1210
1211    return card;
1212  }
1213
1214  function buildResults(copy) {
1215    const page = el("div", "ai-results-page");
1216
1217    page.appendChild(el("h1", "ai-results-title", copy.resultsTitle));
1218
1219    const toolbar = el("div", "ai-results-toolbar");
1220
1221    const tabs = el("div", "ai-result-tabs");
1222
1223    copy.tabs.forEach(([name, count, active]) => {
1224      const tab = el("button", "ai-tab" + (active ? " active" : ""));
1225      tab.appendChild(el("span", "", name));
1226      tab.appendChild(el("span", "ai-count", count));
1227      tabs.appendChild(tab);
1228      tab.addEventListener("click", () => {
1229        document
1230          .querySelectorAll(".ai-tab")
1231          .forEach(t => t.classList.remove("active"));
1232
1233        tab.classList.add("active");
1234      });
1235    });
1236
1237    const sortline = el("div", "ai-sortline");
1238    sortline.appendChild(el("strong", "", copy.sortBy));
1239
1240    const select = el("div", "ai-select");
1241    select.appendChild(el("span", "", copy.relevance));
1242    select.appendChild(el("span", "", "⌄"));
1243    sortline.appendChild(select);
1244
1245    const layout = el("div", "ai-layout-buttons");
1246
1247    const grid = el("span", "active");
1248    grid.innerHTML = iconGrid();
1249
1250    const list = el("span");
1251    list.innerHTML = iconList();
1252
1253    grid.addEventListener("click", () => {
1254      productGrid.style.gridTemplateColumns =
1255        "repeat(3,minmax(0,1fr))";
1256      grid.classList.add("active");
1257      list.classList.remove("active");
1258    });
1259
1260    list.addEventListener("click", () => {
1261      productGrid.style.gridTemplateColumns = "1fr";
1262      list.classList.add("active");
1263      grid.classList.remove("active");
1264    });
1265
1266
1267
1268    layout.appendChild(grid);
1269    layout.appendChild(list);
1270
1271    sortline.appendChild(layout);
1272    sortline.appendChild(el("span", "", copy.resultsFound));
1273
1274    toolbar.appendChild(tabs);
1275    toolbar.appendChild(sortline);
1276
1277    page.appendChild(toolbar);
1278
1279    const main = el("div", "ai-results-main");
1280
1281    const sidebar = el("aside", "ai-sidebar");
1282    sidebar.appendChild(el("h2", "ai-filter-title", copy.filters));
1283
1284    const priceCard = el("div", "ai-filter-card");
1285
1286    const priceHead = el("div", "ai-filter-head");
1287    priceHead.appendChild(el("span", "", copy.price));
1288    priceHead.appendChild(el("span", "", "-"));
1289
1290    priceCard.appendChild(priceHead);
1291    priceCard.appendChild(el("div", "ai-price-line"));
1292
1293    const inputs = el("div", "ai-price-inputs");
1294
1295    const min = el("div");
1296    min.appendChild(el("label", "", copy.min));
1297    min.appendChild(el("div", "", "0"));
1298
1299    const max = el("div");
1300    max.appendChild(el("label", "", copy.max));
1301    max.appendChild(el("div", "", "1999"));
1302
1303    inputs.appendChild(min);
1304    inputs.appendChild(max);
1305
1306    priceCard.appendChild(inputs);
1307    priceCard.appendChild(
1308      el("div", "ai-range-note", copy.availableRange)
1309    );
1310
1311    sidebar.appendChild(priceCard);
1312
1313    const collectionCard = el("div", "ai-filter-card");
1314
1315    const collectionHead = el("div", "ai-filter-head");
1316    collectionHead.appendChild(el("span", "", copy.collection));
1317    collectionHead.appendChild(el("span", "", "-"));
1318
1319    collectionCard.appendChild(collectionHead);
1320
1321    copy.collections.forEach(([name, count]) => {
1322      const row = el("div", "ai-check");
1323      row.appendChild(document.createTextNode(name + " "));
1324      row.appendChild(el("b", "", String(count)));
1325      collectionCard.appendChild(row);
1326    });
1327
1328    sidebar.appendChild(collectionCard);
1329
1330    const tagsCard = el("div", "ai-filter-card");
1331
1332    const tagsHead = el("div", "ai-filter-head");
1333    tagsHead.appendChild(el("span", "", copy.tags));
1334    tagsHead.appendChild(el("span", "", "-"));
1335
1336    tagsCard.appendChild(tagsHead);
1337
1338    copy.tagItems.forEach((name) => {
1339      tagsCard.appendChild(el("div", "ai-check", name));
1340    });
1341
1342    sidebar.appendChild(tagsCard);
1343
1344    const summary = el("section", "ai-summary");
1345    summary.appendChild(el("div", "ai-summary-label", copy.summaryLabel));
1346    summary.appendChild(el("p", "ai-summary-text", copy.summaryText));
1347
1348    const productGrid = el("div", "ai-product-grid");
1349
1350    copy.searchResults.forEach((product, index) => {
1351      productGrid.appendChild(
1352        makeProduct(
1353          {
1354            brand: product[0],
1355            name: product[1],
1356            price: product[2],
1357            img: product[3],
1358          },
1359          index,
1360          copy
1361        )
1362      );
1363    });
1364
1365    main.appendChild(sidebar);
1366    main.appendChild(summary);
1367    main.appendChild(productGrid);
1368
1369    page.appendChild(main);
1370
1371    return page;
1372  }
1373
1374  function mount(target, options) {
1375    injectStyles();
1376
1377    const container =
1378      typeof target === "string" ? document.querySelector(target) : target;
1379
1380    if (!container) {
1381      throw new Error("AISearchDemo: mount target not found.");
1382    }
1383
1384    const opts = options || {};
1385    const copy = getCopy(opts);
1386
1387    const root = el("div", "ai-search-demo");
1388    applyTheme(root, opts.theme || "blue-white");
1389
1390    const restartButton = el("button", "ai-search-restart");
1391    restartButton.type = "button";
1392    restartButton.setAttribute("aria-label", "Replay demo");
1393    const restartIcon = el("img");
1394    restartIcon.src = "/images/replay.png";
1395    restartIcon.alt = "";
1396    restartIcon.draggable = false;
1397    restartButton.appendChild(restartIcon);
1398    restartButton.addEventListener("click", () => {
1399      container.innerHTML = "";
1400      mount(container, opts);
1401    });
1402
1403    const stage = el("div", "ai-search-stage");
1404    const hero = el("div", "ai-search-hero");
1405
1406    const panel = el("div", "ai-search-panel");
1407
1408    panel.appendChild(
1409      el("div", "ai-search-kicker", copy.kicker)
1410    );
1411
1412    panel.appendChild(el("h1", "ai-search-title", copy.title));
1413
1414    panel.appendChild(
1415      el(
1416        "p",
1417        "ai-search-desc",
1418        copy.description
1419      )
1420    );
1421
1422    const searchBox = el("div", "ai-search-box");
1423    searchBox.appendChild(el("div", "ai-search-icon", copy.searchIcon));
1424
1425    const query = el("div", "ai-search-query");
1426    searchBox.appendChild(query);
1427
1428    const button = el("button", "ai-search-button", copy.searchButton);
1429    searchBox.appendChild(button);
1430
1431    panel.appendChild(searchBox);
1432    const previewGrid = el("div", "ai-preview-grid");
1433
1434    copy.heroProducts.forEach((product, index) => {
1435      previewGrid.appendChild(
1436        makeProduct(
1437          {
1438            brand: product[0],
1439            name: product[1],
1440            price: product[2],
1441            img: product[3],
1442          },
1443          index,
1444          copy
1445        )
1446      );
1447    });
1448
1449    panel.appendChild(previewGrid);
1450    hero.appendChild(panel);
1451
1452    const cursor = el("img", "ai-fake-cursor");
1453    cursor.src = opts.cursorSrc || "/images/cursor.png";
1454    cursor.alt = "";
1455    cursor.draggable = false;
1456
1457    const loader = el("div", "ai-search-loader");
1458    loader.appendChild(el("div", "ai-search-spinner"));
1459
1460    const results = buildResults(copy);
1461
1462    stage.appendChild(hero);
1463    stage.appendChild(cursor);
1464    stage.appendChild(loader);
1465    stage.appendChild(results);
1466
1467    root.appendChild(stage);
1468    root.appendChild(restartButton);
1469
1470    container.innerHTML = "";
1471    container.appendChild(root);
1472
1473    const delay = typeof opts.delay === "number" ? opts.delay : 600;
1474
1475    setTimeout(() => {
1476      streamQuery(query, button, cursor, loader, hero, results, copy, restartButton);
1477    }, delay);
1478
1479    return {
1480      destroy() {
1481        container.innerHTML = "";
1482      },
1483
1484      restart() {
1485        container.innerHTML = "";
1486        mount(container, opts);
1487      },
1488
1489      setTheme(nextTheme) {
1490        applyTheme(root, nextTheme);
1491      },
1492    };
1493  }
1494
1495  window.AISearchDemo = {
1496    mount,
1497  };
1498})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.