PageSourceSearch

https://town-country-market.com/wp-content/themes/svt/js/recipes.js?ver=1791092785

js town-country-market.com collected 2026-10-04 05:47:41 UTC 36,160 bytes, 716 lines download raw bytes

1
2
3(function($){
4    window.addEventListener('locationSet',function() {
5        var typingTimer;                // Timer identifier
6        var doneTypingInterval = 300;   // Time in ms, e.g., 300ms
7        var $input = $('#recipe_search');
8        var searchCache = new Map();    // Cache to store search results
9
10        function generateRecipeBoxHtmlSearch(recipes, url) {
11            if(typeof url == "undefined") {
12                url = embeddata.recipeinfo;
13            }
14            return recipes.map(recipe => `
15                <div class="svt-recipe-box">
16                    <a href="${url}?id=${recipe.id}">
17                        <div class="svt-recipe-img-wrap">
18                            <img src="https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages_tn/${recipe.id}.jpg" 
19                                onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" 
20                                alt="${recipe.displayName}">
21                        </div>
22                        <div class="svt-recipe-info-wrap">
23                            <h6>${recipe.tags[0]}</h6>
24                            <h2>${recipe.displayName}</h2>
25                            <div class="svt-recipe-short-desc">${recipe.description}</div>
26                            <div class="svt-recipe-meta">
27                                <span>Serves ${recipe.servings}</span>
28                                <span>${recipe.prepTime}</span>
29                                <span>${recipe.difficulty}</span>
30                            </div>
31                        </div>
32                    </a>
33                </div>
34            `).join('');
35        }
36
37        async function searchRecipe( query ) {
38            $('.recipe-listing-wrap').hide();
39
40            $('.svt-recipe-search-result-wrap h5 span').html(query);
41            $('.svt-recipe-search-result-wrap').fadeIn();
42
43            if (searchCache.has(query)) {
44                renderSearchResults(searchCache.get(query), query);
45            }
46            else {
47                const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key);
48            
49                const location = getCookie('selectedLocation');
50                const pricelist = getCookie('selectedPriceList');
51
52                let headers = {
53                    'apikey': hsdata.hs_api_key,
54                    'location': location,
55                    'pricelist': pricelist
56                };
57
58                const response = await api.getData('/recipe/search?searchPhrase='+query, headers);
59                if(response.results) {
60                    $('.shimmer-loading').hide();
61                    html = generateRecipeBoxHtmlSearch(response.results);
62                    searchCache.set(query, html);
63                    renderSearchResults(html, query);
64                }
65                else {
66                    $('.svt-recipe-result-grid').html('<p>No recipes found.</p>');
67                    $('.svt-recipe-result-grid').show();
68                }
69            }
70        }
71
72        // On keyup, start the countdown
73        $input.on('input', function () {
74            const $this = $(this);
75            const query = $this.val().trim();
76            
77            if (query.length >= 3) {
78                $('.recipe-listing-wrap').hide();
79                searchRecipe(query);
80            }
81            else {
82                $('.recipe-listing-wrap').show();
83                $('.recipe-listing-wrap').find('.slick-slider').slick('refresh');
84                $('.svt-recipe-search-result-wrap').hide();
85            }
86        });
87
88        $('.svt-recipe-search-form').submit(function() {
89            return false;   // Prevent form submission
90        });
91
92        // On keydown, clear the countdown
93        $input.on('keydown', function () {
94            clearTimeout(typingTimer);
95        });
96
97        // If input already has value, trigger search
98        if ($input.length && $input.val() !== '') {
99            const query = $input.val().trim();
100            searchRecipe(query);
101            // doneTyping();
102        }
103
104        // User is "finished typing," do something
105        function doneTyping() {
106            var keyword = $input.val().trim();
107
108            // Hide existing content during search
109            $('.svt-recipe-content').hide();
110
111            // If the input is empty, show all content
112            if (keyword === '') {
113                $('.recipe-listing-wrap').show();
114                $('.svt-recipe-search-result-wrap').hide();
115            } else {
116                // Check if search result is already in cache
117                if (searchCache.has(keyword)) {
118                    // Use cached result
119                    renderSearchResults(searchCache.get(keyword), keyword);
120                } else {
121                    // Perform AJAX search if not cached
122                    $.ajax({
123                        type: 'GET',
124                        data: { action: 'recipe_search', keyword: keyword },
125                        dataType: 'json',
126                        url: sitedata.ajax_url,
127                        beforeSend: function() {
128                            $('.svt-recipe-search-result-wrap span').html(keyword);
129                            $('.svt-recipe-search-result-wrap').fadeIn();
130                            $('.shimmer-loading').show();
131                            $('.svt-recipe-result-grid').hide();
132                            $('.recipe-listing-wrap').hide();
133                        },
134                        success: function(response) {
135                            $('.shimmer-loading').hide();
136                            
137                            if (response.html) {
138                                // Cache the result for future use
139                                searchCache.set(keyword, response.html);
140                                
141                                // Render the search results
142                                renderSearchResults(response.html, keyword);
143                            } else {
144                                // Handle case where no result is found
145                                $('.svt-recipe-result-grid').html('<p>No recipes found.</p>');
146                                $('.svt-recipe-result-grid').show();
147                            }
148                        },
149                        error: function() {
150                            console.error('Search failed.');
151                        }
152                    });
153                }
154            }
155        }
156
157        // Function to render search results
158        function renderSearchResults(html, keyword) {
159            $('.svt-recipe-result-grid').html(html);
160            $('.svt-recipe-result-grid').show();
161        }
162
163        if ($('.svt-featured-recipes-container').length) {
164            // Cache DOM selections and frequently used data
165            const featuredRecipeContainer = $('.svt-featured-recipes-container');
166            const featuredRecipeList = featuredRecipeContainer.find('.svt-featured-recipes');
167            const ids = featuredRecipeContainer.data('ids').split(',');
168            const recipeURL = featuredRecipeContainer.data('recipe-url');
169
170            // Use fetch instead of $.ajax
171            const fetchRecipe = async (id) => {
172                const location = getCookie('selectedLocation');
173                const pricelist = getCookie('selectedPriceList');
174                const cachedRecipe = null; //sessionStorage.getItem(`recipe_${id}_${location}_${pricelist}`);
175                if (cachedRecipe) {
176                    // You can return the cached recipe if it's found
177                    return JSON.parse(cachedRecipe);
178                }
179            
180                const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key);
181                let headers = {
182                    'apikey': hsdata.hs_api_key,
183                    'location': location,
184                    'pricelist': pricelist
185                };
186            
187                try {
188                    // Use await to wait for the response from the API
189                    const response = await api.getData(`/recipe?recipeId=${id}`, headers);
190                    // console.log(response);
191                    // Save the response in sessionStorage and return the data
192                    //sessionStorage.setItem(`recipe_${id}_${location}_${pricelist}`, JSON.stringify(response));
193                    return response;
194                } catch (error) {
195                    console.error('Error fetching recipe data:', error);
196                    return null;  // Return null if there's an error
197                }
198            };            
199
200            // Generate HTML for a recipe
201            const generateRecipeHtml = (recipeObj, isLeftSide) => {
202                const { id, tags, displayName } = recipeObj;
203                const type = tags[0];
204                const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages/${id}.jpg`;
205
206                return `
207                <div class="${isLeftSide ? 'svt-featured-recipe-left' : ''}">
208                    <div class="svt-recipe-box">
209                        <a href="${recipeURL}?id=${id}">
210                            <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" alt="${displayName}">
211                            <div class="svt-recipe-info-wrap">
212                                <h6>${type}</h6>
213                                <h2>${displayName}</h2>
214                            </div>
215                        </a>
216                    </div>
217                </div>`;
218            };
219
220            // Main function to load and display recipes
221            const loadFeaturedRecipes = async () => {
222                try {
223                    const recipes = await Promise.all(ids.map(fetchRecipe));
224                    
225                    let featuredHtml = recipes.reduce((html, recipe, index) => {
226                        if (index === 0) {
227                            return html + generateRecipeHtml(recipe, true);
228                        } else if (index === 1) {
229                            return html + '<div class="svt-featured-recipe-right">' + generateRecipeHtml(recipe, false);
230                        } else {
231                            return html + generateRecipeHtml(recipe, false);
232                        }
233                    }, '');
234
235                    if (recipes.length > 1) {
236                        featuredHtml += '</div>'; // Close svt-featured-recipe-right if it was opened
237                    }
238
239                    $('.svt-featured-recipes-skeleton').hide();
240                    featuredRecipeList.html(featuredHtml);
241                } catch (error) {
242                    console.error("Error loading recipes: ", error);
243                }
244            };
245
246            // Execute the main function
247            loadFeaturedRecipes();
248        }        
249        // Generic function to load recipes
250        async function loadRecipes(type) {
251            const location = getCookie('selectedLocation');
252            const pricelist = getCookie('selectedPriceList');
253            
254            const config = {
255                trending: {
256                    container: '.svt-trending-recipe-wrap-container',
257                    skeleton: '.svt-trending-recipe-skeleton',
258                    storageKey: `trending_recipes_${location}_${pricelist}`,
259                    action: '/recipes?sectionName=rlp-top'
260                },
261                seasonal: {
262                    container: '.svt-seasonal-recipe-wrap-container',
263                    skeleton: '.svt-seasonal-recipe-skeleton',
264                    storageKey: `seasonal_recipes_${location}_${pricelist}`,
265                    action: '/recipes?sectionName=rlp-seasonal'
266                }
267            };
268
269            const { container, skeleton, storageKey, action } = config[type];
270
271            if (!$(container).length) return;
272
273            const recipeSkeleton = $(skeleton);
274            const recipeContainer = $(container);
275            const recipeList = recipeContainer.find('.svt-recipe-carousel');
276            const recipeURL = recipeContainer.data('recipe-url');
277
278            let recipes = null; //sessionStorage.getItem(storageKey);
279
280            if (!recipes) {
281                const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key);
282                
283                let headers = {
284                    'apikey': hsdata.hs_api_key,
285                    'location': location,
286                    'pricelist': pricelist
287                };
288
289                try {
290                    // Use await to wait for the response from the API
291                    const response = await api.getData(action, headers);
292                    recipes = JSON.stringify(response);
293                    // sessionStorage.setItem(storageKey, recipes);
294                } catch (error) {
295                    console.error('Error fetching recipe data:', error);
296                    return null;  // Return null if there's an error
297                }
298            }
299
300            renderRecipes(JSON.parse(recipes), recipeSkeleton, recipeList, recipeURL);
301        }
302
303        function renderRecipes(recipes, skeleton, list, url) {
304            const html = generateRecipeBoxHtml(recipes, url);
305            skeleton.hide();
306            list.html(html);
307            initializeSlick(list);
308        }
309
310        function generateRecipeBoxHtml(recipes, url) {
311            return recipes.map(recipe => `
312                <div class="svt-recipe-box">
313                    <a href="${url}?id=${recipe.id}">
314                        <div class="svt-recipe-img-wrap">
315                            <img src="https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages_tn/${recipe.id}.jpg" 
316                                onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" 
317                                alt="${recipe.displayName}">
318                        </div>
319                        <div class="svt-recipe-info-wrap">
320                            <h6>${recipe.tags[0]}</h6>
321                            <h2>${recipe.displayName}</h2>
322                            <div class="svt-recipe-short-desc">${recipe.description}</div>
323                            <div class="svt-recipe-meta">
324                                <span>Serves ${recipe.servings}</span>
325                                <span>${recipe.prepTime}</span>
326                                <span>${recipe.difficulty}</span>
327                            </div>
328                        </div>
329                    </a>
330                </div>
331            `).join('');
332        }
333
334        // Execute the main functions
335        loadRecipes('trending');
336        loadRecipes('seasonal');
337        
338        function initializeSlick(recipeList) {
339            recipeList.removeClass('slick-initialized slick-slider');
340            setTimeout(() => {
341                // console.log(recipeList);
342                recipeList.slick({
343                    slidesToShow: 3,
344                    slidesToScroll: 1,
345                    dots: true,
346                    arrows: true,
347                    prevArrow: '<button type="button" class="slick-prev" aria-hidden="true" aria-label="Previous"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M15.5303 5.46967C15.2374 5.17678 14.7626 5.17678 14.4697 5.46967L8.46967 11.4697C8.17678 11.7626 8.17678 12.2374 8.46967 12.5303L14.4697 18.5303C14.7626 18.8232 15.2374 18.8232 15.5303 18.5303C15.8232 18.2374 15.8232 17.7626 15.5303 17.4697L10.0607 12L15.5303 6.53033C15.8232 6.23744 15.8232 5.76256 15.5303 5.46967Z" fill="currentColor"></path></svg></button>',
348                    nextArrow: '<button type="button" class="slick-next" aria-hidden="true" aria-label="Next"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.46967 5.46967C8.76256 5.17678 9.23744 5.17678 9.53033 5.46967L15.5303 11.4697C15.8232 11.7626 15.8232 12.2374 15.5303 12.5303L9.53033 18.5303C9.23744 18.8232 8.76256 18.8232 8.46967 18.5303C8.17678 18.2374 8.17678 17.7626 8.46967 17.4697L13.9393 12L8.46967 6.53033C8.17678 6.23744 8.17678 5.76256 8.46967 5.46967Z" fill="currentColor"></path></svg></button>',
349                    responsive: [
350                        { breakpoint: 0, settings: { slidesToShow: 1, arrows: false } },
351                        { breakpoint: 767, settings: { slidesToShow: 1, arrows: false } },
352                        { breakpoint: 1024, settings: { slidesToShow: 3 } }
353                    ]
354                });
355            }, 100);
356        }
357
358        if($('.svt-recommanded-recipe-list').length) {
359            const recommandedRecipeSkeleton =  $('.svt-recommanded-recipe-skeleton');
360            const recommandedRecipeList =  $('.svt-recommanded-recipe-list');
361            const recipeURL = recommandedRecipeList.data('recipe-url');
362
363            recommandedRecipeSkeleton.show();
364
365            const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key);
366            
367            const location = getCookie('selectedLocation');
368            const pricelist = getCookie('selectedPriceList');
369
370            let headers = {
371                'apikey': hsdata.hs_api_key,
372                'location': location,
373                'pricelist': pricelist
374            };
375
376            let user = localStorage.getItem('user');
377            if(user != null) {
378                user = JSON.parse(user);
379                headers.emailid = user.email;
380                headers.authToken = user.authToken;
381            }
382
383            api.getData('/recipes?sectionName=rlp-foryou', headers)
384                .then(response => {
385                    const html = generateRecommandedRecipeHTML(response);
386                    recommandedRecipeSkeleton.hide();
387                    recommandedRecipeList.html(html);
388                })
389                .catch(console.error);
390
391            function generateRecommandedRecipeHTML( recipes ) {
392                let recommandedHtml = recipes.reduce((html, recipe, index) => {
393                    if (index === 0) {
394                        return html + generateRecommRecipeHtml(recipe, true);
395                    } else if (index === 1) {
396                        return html + '<div class="svt-recommanded-recipe-left-right">' + generateRecommRecipeHtml(recipe, false);
397                    } else {
398                        return html + generateRecommRecipeHtml(recipe, false);
399                    }
400                }, '');
401
402                if (recipes.length > 1) {
403                    recommandedHtml += '</div>'; // Close svt-featured-recipe-right if it was opened
404                }
405                return recommandedHtml;
406            }
407
408            const generateRecommRecipeHtml = (recipeObj, isLeftSide) => {
409                const { id, tags, displayName, description, servings, prepTime, difficulty } = recipeObj;
410                const type = tags[0];
411                const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages/${id}.jpg`;
412    
413                var box = '';
414                if(isLeftSide == true) {
415                    box += `<div class="${isLeftSide ? 'svt-recommanded-recipe-left' : ''}">`;
416                }
417                box += `<div class="svt-recipe-box">
418                        <a href="${recipeURL}?id=${id}">
419                            <div class="svt-recipe-img-wrap">
420                                <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" alt="${displayName}">
421                            </div>
422                            <div class="svt-recipe-info-wrap">
423                                <h6>${type}</h6>
424                                <h2>${displayName}</h2>`;
425                                if(isLeftSide == true) {
426                                    box += `
427                                    <div class="svt-recipe-short-desc">${description}</div>
428                                    <div class="svt-recipe-meta">
429                                        <span>Serves ${servings}</span>
430                                        <span>${prepTime}</span>
431                                        <span>${difficulty}</span>
432                                    </div>
433                                    `;
434                                }
435                                box += `
436                            </div>
437                        </a>
438                    </div>`;
439                if(isLeftSide == true) {
440                    box += `</div>`;
441                }
442
443                return box;
444            };
445        }
446        
447        if($('.svt-cat-recipe-listing').length) {
448            // Cache DOM selections
449            const catRecipeLoader = $('.svt-recipe-listing-skeleton');
450            const catRecipeListing = $('.svt-cat-recipe-listing');
451            const cat = catRecipeListing.data('cat');
452            const recipeURL = catRecipeListing.data('recipe-url');
453
454            // Show loader immediately
455            catRecipeLoader.show();
456
457            const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key);
458            
459            const location = getCookie('selectedLocation');
460            const pricelist = getCookie('selectedPriceList');
461
462            let headers = {
463                'apikey': hsdata.hs_api_key,
464                'location': location,
465                'pricelist': pricelist
466            };
467
468            api.getData('/recipes?sectionName=rtp-all&sectionParams='+encodeURIComponent(cat), headers)
469                .then(response => {
470                    const html = generateRecipeWithMetaBoxHtml(response);
471                    catRecipeLoader.hide();
472                    catRecipeListing.html(html);
473                })
474                .catch(console.error);
475
476            function generateRecipeWithMetaBoxHtml(recipes) {
477                // Use map and join instead of forEach and string concatenation
478                return recipes.map(recipeObj => {
479                    const { id, tags, displayName, description, servings, prepTime, difficulty } = recipeObj;
480                    const type = tags[0];
481                    const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages_tn/${id}.jpg`;
482                    
483                    return `
484                    <div class="svt-recipe-box">
485                        <a href="${recipeURL}?id=${id}">
486                            <div class="svt-recipe-img-wrap">
487                                <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" alt="${displayName}">
488                            </div>
489                            <div class="svt-recipe-info-wrap">
490                                <h6>${type}</h6>
491                                <h2>${displayName}</h2>
492                                <div class="svt-recipe-short-desc">${description}</div>
493                                <div class="svt-recipe-meta">
494                                    <span>Serves ${servings}</span>
495                                    <span>${prepTime}</span>
496                                    <span>${difficulty}</span>
497                                </div>
498                            </div>
499                        </a>
500                    </div>`;
501                }).join('');
502            }
503        }
504
505        const loadRelatedRecipes = function( recipeName, recipeID ) {
506            let html = '';
507            const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key);                
508            
509            let headers = {
510                'apikey': hsdata.hs_api_key,
511                'location': getCookie('selectedLocation'),
512                'pricelist': getCookie('selectedPriceList'),
513            };
514
515            api.getData('/recipes?sectionName=rdp-related&sectionParams='+recipeName+'&sectionParams='+recipeID,headers).then(response => {
516                const recipes = response;
517                if(recipes) {
518                    html += `
519                    <div class="svt-related-recipes">
520                        <div class="svt-related-recipes-head">
521                            <h2>You may also like</h2>
522                        </div>
523                        <div class="svt-recipe-carousel-wrap">
524                            <div class="svt-recipe-carousel">
525                                ${(() => generateRecipeBoxHtml(recipes,embeddata.recipeinfo))()}
526                            </div>
527                        </div>
528                    </div>
529                    `;
530                    $('.svt-recipe-detail-wrap .svt-page-container').append(html);
531                    initializeSlick($('.svt-recipe-carousel'));
532                }
533            })
534            .catch(error => {
535                console.error('Error posting data:', error);
536            });
537        }
538
539        if($('.svt-recipe-detail-wrap').length) {
540
541            const fetchRecipe = async (id) => {
542                const location = getCookie('selectedLocation');
543                const pricelist = getCookie('selectedPriceList');
544                // const cachedRecipe = sessionStorage.getItem(`recipe_${id}_${location}_${pricelist}`);                
545                // if (cachedRecipe) {
546                //     // You can return the cached recipe if it's found
547                //     return JSON.parse(cachedRecipe);
548                // }
549            
550                const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key);
551                let headers = {
552                    'apikey': hsdata.hs_api_key,
553                    'location': location,
554                    'pricelist': pricelist
555                };
556            
557                try {
558                    // Use await to wait for the response from the API
559                    const response = await api.getData(`/recipe?recipeId=${id}`, headers);
560                    // console.log(response);
561                    // Save the response in sessionStorage and return the data
562                    // sessionStorage.setItem(`recipe_${id}_${location}_${pricelist}`, JSON.stringify(response));
563                    return response;
564                } catch (error) {
565                    console.error('Error fetching recipe data:', error);
566                    return null;  // Return null if there's an error
567                }
568            };
569
570            let recipeID = getUrlParameter('id');
571            const recipeInfo = fetchRecipe(recipeID);
572            let html = '';
573            recipeInfo.then(data => {
574                const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages/${data.id}.jpg`;
575                const type = data.tags[0];
576                const instructions = data.instructions;
577                const ingredients = data.ingredients;
578                const recipeProducts = data.products;
579                const productIDs = recipeProducts.map(item => item.name);
580                $('i.recipe-name').html(data.displayName);
581                html += `
582                <div class="svt-page-container">
583                    <div class="svt-recipe-detail-breadcrumb">
584                        <a href="${sitedata.home_url}">Home</a>
585                        <svg width="6" height="11" viewBox="0 0 6 11" fill="none" xmlns="http://www.w3.org/2000/svg">
586                            <path d="M1 9.5L5 5.5L1 1.5" stroke="#232323" stroke-width="1.3" stroke-linecap="round"
587                                stroke-linejoin="round" />
588                        </svg>
589                        <a href="${sitedata.home_url}/recipes/">Recipes</a>
590                        <svg width="6" height="11" viewBox="0 0 6 11" fill="none" xmlns="http://www.w3.org/2000/svg">
591                            <path d="M1 9.5L5 5.5L1 1.5" stroke="#232323" stroke-width="1.3" stroke-linecap="round"
592                                stroke-linejoin="round" />
593                        </svg>
594                        <span class="recipe-name">${data.displayName}</span>
595                    </div>
596                    <div class="svt-recipe-detail-info-wrap">
597                        <div class="svt-recipe-detail-img">
598                            <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='<?php echo get_stylesheet_directory_uri().'/images/Recipe.svg'; ?>'" alt="">
599                        </div>
600                        <div class="svt-recipe-detail-info">
601                            <h6>${type}</h6>
602                            <h1>${data.displayName}</h1>
603                            <div class="svt-recipe-detail-desc">
604                                ${data.description}
605                            </div>
606                            <div class="svt-recipe-detail-meta">
607                                <span>Serves <strong>${data.servings}</strong></span>
608                                <span>Time <strong>${data.prepTime}</strong></span>
609                                <span>Difficulty <strong>${data.difficulty}</strong></span>
610                            </div>
611                        </div>
612                    </div>
613                    <div class="svt-recipe-other-info-wrap">
614                        <div class="svt-instructions-wrap">
615                            <h4>Instructions</h4>
616                            <ol>
617                                ${(() => instructions.map(ins => `<li>${ins}</li>`).join(''))()}
618                            </ol>
619                        </div>
620                        <div class="svt-ingredient-products-wrap">
621                            <div class="svt-ingredient-wrap">
622                                <h4>Ingredients</h4>
623                                <ul>
624                                ${(() => ingredients.map(ing => `<li>${ing}</li>`).join(''))()}
625                                </ul>
626                            </div>
627                            <div class="svt-recipe-products-wrap">
628                                <div class="svt-recipe-products-header">
629                                    <h4>Shop Ingredients</h4>
630                                    <div class="add-to-cart-bulk-wrap">
631                                        <a href="#" class="add-to-cart-bulk">
632                                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"
633                                                fill="none">
634                                                <path
635                                                    d="M8.75 1C8.75 0.585786 8.41421 0.25 8 0.25C7.58579 0.25 7.25 0.585786 7.25 1V7.25H1C0.585786 7.25 0.25 7.58579 0.25 8C0.25 8.41421 0.585786 8.75 1 8.75H7.25V15C7.25 15.4142 7.58579 15.75 8 15.75C8.41421 15.75 8.75 15.4142 8.75 15V8.75H15C15.4142 8.75 15.75 8.41421 15.75 8C15.75 7.58579 15.4142 7.25 15 7.25H8.75V1Z"
636                                                    fill="white" />
637                                            </svg>
638                                            <span>Add All <span class="product-count"></span> Items</span>
639                                        </a>
640                                        <div class="bulk-subtotal-wrap">Subtotal: <span class="subtotal"></span></div>
641                                    </div>
642                                </div>
643                                <div class="svt-recipe-products-list" data-product-ids='${productIDs}'>
644                                    
645                                </div>
646                                <div class="svt-recipe-products-footer">
647                                    <div class="add-to-cart-bulk-wrap">
648                                        <a href="#" class="add-to-cart-bulk">
649                                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewB
649ox="0 0 16 16"
650                                                fill="none">
651                                                <path
652                                                    d="M8.75 1C8.75 0.585786 8.41421 0.25 8 0.25C7.58579 0.25 7.25 0.585786 7.25 1V7.25H1C0.585786 7.25 0.25 7.58579 0.25 8C0.25 8.41421 0.585786 8.75 1 8.75H7.25V15C7.25 15.4142 7.58579 15.75 8 15.75C8.41421 15.75 8.75 15.4142 8.75 15V8.75H15C15.4142 8.75 15.75 8.41421 15.75 8C15.75 7.58579 15.4142 7.25 15 7.25H8.75V1Z"
653                                                    fill="white" />
654                                            </svg>
655                                            <span>Add All <span class="product-count"></span> Items</span>
656                                        </a>
657                                        <div class="bulk-subtotal-wrap">Subtotal: <span class="subtotal"></span></div>
658                                    </div>
659                                </div>
660                            </div>
661                        </div>
662                    </div>
663                </div>
664                `;
665                $('.svt-recipe-detail-wrap').html(html);
666                loadRelatedRecipes(data.displayName,data.id);
667            }).catch(error => {
668                console.log(error);
669            });
670        }
671    });
672
673    function loadRecipeProducts( productIDs, productsData ) {
674
675        const { products, promotions } = productsData;
676        let html = '';
677
678        // Filter recipe products
679        const recipeProducts = products.filter(product => productIDs.includes(product.name));
680
681        // Calculate subtotal
682        const subTotal = recipeProducts.reduce((acc, product) => acc + parseFloat(product.price), 0);
683
684        // Generate HTML for products
685        html = recipeProducts.map(product => renderProductCard(product, promotions)).join('');
686
687        // Update DOM in one batch
688        $('.svt-recipe-products-wrap .add-to-cart-bulk-wrap').fadeIn();
689        $('.svt-recipe-products-wrap .add-to-cart-bulk-wrap .product-count').html(recipeProducts.length);
690        $('.svt-recipe-products-wrap .add-to-cart-bulk-wrap .subtotal').html(`$${subTotal.toFixed(2)}`);
691        $('.svt-recipe-products-list .shimmer-product-boxes').hide();
692        $('.svt-recipe-products-list').append(html);
693
694    }
695
696    $(window).on('CACHE_DATA_READY', function(event) {
697        const productsData = event.detail.data;
698        if($('.svt-recipe-products-list').length) {
699            let productIDs = $('.svt-recipe-products-list').data('product-ids');
700            if(typeof productIDs == "undefined") {
701                let recipeProductsTimer = setInterval(function() {
702                    productIDs = $('.svt-recipe-products-list').data('product-ids');
703                    if(typeof productIDs != "undefined") {
704                        productIDs = productIDs.split(',');
705                        loadRecipeProducts( productIDs, productsData );        
706                        clearInterval(recipeProductsTimer);
707                    }
708                },200);
709            }
710            else {
711                productIDs = productIDs.split(',');
712                loadRecipeProducts( productIDs, productsData );
713            }
714        }
715    });
716})(jQuery);

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.