hunk.code_window:             var fetch = self._fetchData();
               var animation = wSaleUtils.animateClone($navButton, $(ev.currentTarget).parents('.card'), 25, 40);
               Promise.all([fetch, animation]).then(function (values) {
                   self._render();
               });
           });
       },
  
hunk.labels: keep keep add keep keep keep keep keep
hunk.after_edit:                 wSaleUtils.updateCartNavBar(data);
  
hunk.file_path: addons/website_sale/static/src/snippets/s_dynamic_snippet_products/000.js
hunk.type: add
file: odoo.define('website_sale.utils', function (require) {
  'use strict';
  
  const wUtils = require('website.utils');
  
  const cartHandlerMixin = {
      getRedirectOption() {
          const html = document.documentElement;
          this.stayOnPageOption = html.dataset.add2cartRedirect !== '0';
      },
      getCartHandlerOptions(ev) {
          this.isBuyNow = ev.currentTarget.classList.contains('o_we_buy_now');
          const targetSelector = ev.currentTarget.dataset.animationSelector || 'img';
          this.$itemImgContainer = this.$(ev.currentTarget).closest(`:has(${targetSelector})`);
      },
      /**
       * Used to add product depending on stayOnPageOption value.
       */
      addToCart(params) {
          if (this.isBuyNow) {
              params.express = true;
          } else if (this.stayOnPageOption) {
              return this._addToCartInPage(params);
          }
          return wUtils.sendRequest('/shop/cart/update', params);
      },
      /**
       * @private
       */
      _addToCartInPage(params) {
          params.force_create = true;
          this._rpc({
              route: "/shop/cart/update_json",
              params: params,
          }).then(data => {
              updateCartNavBar(data);
              animateClone($('header .o_wsale_my_cart').first(), this.$itemImgContainer, 25, 40);
          });
      },
  };
  
  function animateClone($cart, $elem, offsetTop, offsetLeft) {
      $cart.find('.o_animate_blink').addClass('o_red_highlight o_shadow_animation').delay(500).queue(function () {
          $(this).removeClass("o_shadow_animation").dequeue();
      }).delay(2000).queue(function () {
          $(this).removeClass("o_red_highlight").dequeue();
      });
      return new Promise(function (resolve, reject) {
          var $imgtodrag = $elem.find('img').eq(0);
          if ($imgtodrag.length) {
              var $imgclone = $imgtodrag.clone()
                  .offset({
                      top: $imgtodrag.offset().top,
                      left: $imgtodrag.offset().left
                  })
                  .addClass('o_website_sale_animate')
                  .appendTo(document.body)
                  .css({
                      // Keep the same size on cloned img.
                      width: $imgtodrag.width(),
                      height: $imgtodrag.height(),
                  })
                  .animate({
                      top: $cart.offset().top + offsetTop,
                      left: $cart.offset().left + offsetLeft,
                      width: 75,
                      height: 75,
                  }, 1000, 'easeInOutExpo');
  
              $imgclone.animate({
                  width: 0,
                  height: 0,
              }, function () {
                  resolve();
                  $(this).detach();
              });
          } else {
              resolve();
          }
      });
  }
  
  /**
   * Updates both navbar cart
   * @param {Object} data
   */
  function updateCartNavBar(data) {
      var $qtyNavBar = $(".my_cart_quantity");
      _.each($qtyNavBar, function (qty) {
          var $qty = $(qty);
          $qty.parents('li:first').removeClass('d-none');
          $qty.html(data.cart_quantity).hide().fadeIn(600);
      });
      $(".js_cart_lines").first().before(data['website_sale.cart_lines']).end().remove();
      $(".js_cart_summary").first().before(data['website_sale.short_cart_summary']).end().remove();
  }
  
  return {
      animateClone: animateClone,
      updateCartNavBar: updateCartNavBar,
      cartHandlerMixin: cartHandlerMixin,
  };
  });
  